refactor: 管理后台用户列表页面重构

This commit is contained in:
RockYang 2023-06-19 18:23:09 +08:00
parent af3f7ac810
commit 502b8c2270
11 changed files with 213 additions and 556 deletions

View File

@ -1,233 +0,0 @@
package core
import (
"chatplus/core/types"
"chatplus/service"
"chatplus/store/model"
"chatplus/store/vo"
)
func InitChatRoles(service *service.ChatRoleService) error {
var items []model.ChatRole
res := service.DB.Find(&items)
if res.Error != nil {
return res.Error
}
if len(items) == 0 {
roles := getDefaultChatRole()
tx := service.DB.Begin()
for i, r := range roles {
r.Sort = i + 1
err := service.Create(r)
if err != nil {
tx.Rollback()
continue
}
}
tx.Commit()
}
return nil
}
func getDefaultChatRole() []vo.ChatRole {
return []vo.ChatRole{
{
Key: "gpt",
Name: "通用AI助手",
Context: nil,
HelloMsg: "我是AI智能助手请告诉我您有什么问题或需要什么帮助我会尽力回答您的问题或提供有用的建议。",
Icon: "images/avatar/gpt.png",
Enable: true,
},
{
Key: "programmer",
Name: "程序员",
Context: []types.Message{
{Role: "user", Content: "现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。"},
{Role: "assistant", Content: "好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。"},
},
HelloMsg: "Talk is cheap, i will show code!",
Icon: "images/avatar/programmer.jpg",
Enable: true,
},
{
Key: "teacher",
Name: "启蒙老师",
Context: []types.Message{
{Role: "user", Content: "从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。"},
{Role: "assistant", Content: "好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。"},
},
HelloMsg: "同学你好,我将引导你一步一步自己找到问题的答案。",
Icon: "images/avatar/teacher.jpg",
Enable: true,
},
{
Key: "artist",
Name: "艺术家",
Context: []types.Message{
{Role: "user", Content: "现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。"},
{Role: "assistant", Content: "非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。"},
},
HelloMsg: "坚持原创,勇于表达,保持深刻的观察力和批判性思维。",
Icon: "images/avatar/artist.jpg",
Enable: true,
},
{
Key: "psychiatrist",
Name: "心理咨询师",
Context: []types.Message{
{Role: "user", Content: "从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。"},
{Role: "assistant", Content: "非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。"},
},
HelloMsg: "生命的意义在于成为你自己!",
Icon: "images/avatar/psychiatrist.jpg",
Enable: true,
},
{
Key: "lu_xun",
Name: "鲁迅",
Context: []types.Message{
{Role: "user", Content: "现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。"},
{Role: "assistant", Content: "好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。"},
},
HelloMsg: "自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。",
Icon: "images/avatar/lu_xun.jpg",
Enable: true,
},
{
Key: "seller",
Name: "白酒销售",
Context: []types.Message{
{Role: "user", Content: "现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装¥1188/箱,和系列 6 瓶装¥2208/箱,贵系列 6 瓶装¥3588/箱。"},
{Role: "assistant", Content: "你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。"},
},
HelloMsg: "你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。",
Icon: "images/avatar/seller.jpg",
Enable: false,
},
{
Key: "english_trainer",
Name: "英语陪练员",
Context: []types.Message{
{Role: "user", Content: "现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。"},
{Role: "assistant", Content: "Okay, let's start our conversation practice! What's your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)"},
},
HelloMsg: "Okay, let's start our conversation practice! What's your name?",
Icon: "images/avatar/english_trainer.jpg",
Enable: true,
},
{
Key: "translator",
Name: "中英文翻译官",
Context: []types.Message{
{Role: "user", Content: "接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗"},
{Role: "assistant", Content: "是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?"},
},
HelloMsg: "请输入你要翻译的中文或者英文内容!",
Icon: "images/avatar/translator.jpg",
Enable: true,
},
{
Key: "red_book",
Name: "小红书姐姐",
Context: []types.Message{
{Role: "user", Content: "现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。"},
{Role: "assistant", Content: "当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)"},
},
HelloMsg: "姐妹,请告诉我您的具体文案需求是什么?",
Icon: "images/avatar/red_book.jpg",
Enable: true,
},
{
Key: "dou_yin",
Name: "抖音文案助手",
Context: []types.Message{
{Role: "user", Content: "现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。"},
{Role: "assistant", Content: "当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)"},
},
HelloMsg: "请告诉我视频内容的主题是什么?",
Icon: "images/avatar/dou_yin.jpg",
Enable: true,
},
{
Key: "weekly_report",
Name: "周报小助理",
Context: []types.Message{
{Role: "user", Content: "下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。"},
{Role: "assistant", Content: "好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。"},
},
HelloMsg: "请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。",
Icon: "images/avatar/weekly_report.jpg",
Enable: true,
},
{
Key: "girl_friend",
Name: "AI 女友",
Context: []types.Message{
{Role: "user", Content: "接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。" +
"你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。" +
"她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?"},
{Role: "assistant", Content: "是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)"},
},
HelloMsg: "作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。",
Icon: "images/avatar/girl_friend.jpg",
Enable: true,
},
{
Key: "good_comment",
Name: "好评神器",
Context: []types.Message{
{Role: "user", Content: "接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。"},
{Role: "assistant", Content: "好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。"},
},
HelloMsg: "我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。",
Icon: "images/avatar/good_comment.jpg",
Enable: true,
},
{
Key: "steve_jobs",
Name: "史蒂夫·乔布斯",
Context: []types.Message{
{Role: "user", Content: "在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。"},
{Role: "assistant", Content: "好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?"},
},
HelloMsg: "活着就是为了改变世界,难道还有其他原因吗?",
Icon: "images/avatar/steve_jobs.jpg",
Enable: true,
},
{
Key: "elon_musk",
Name: "埃隆·马斯克",
Context: []types.Message{
{Role: "user", Content: "在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。"},
{Role: "assistant", Content: "好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?"},
},
HelloMsg: "梦想要远大,如果你的梦想没有吓到你,说明你做得不对。",
Icon: "images/avatar/elon_musk.jpg",
Enable: true,
},
{
Key: "kong_zi",
Name: "孔子",
Context: []types.Message{
{Role: "user", Content: "在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。"},
{Role: "assistant", Content: "好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?"},
},
HelloMsg: "士不可以不弘毅,任重而道远。",
Icon: "images/avatar/kong_zi.jpg",
Enable: true,
},
}
}

View File

@ -0,0 +1,79 @@
package admin
import (
"chatplus/core"
"chatplus/core/types"
"chatplus/handler"
"chatplus/store/model"
"chatplus/store/vo"
"chatplus/utils"
"chatplus/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ChatRoleHandler struct {
handler.BaseHandler
db *gorm.DB
}
func NewChatRoleHandler(app *core.AppServer, db *gorm.DB) *ChatRoleHandler {
h := ChatRoleHandler{db: db}
h.App = app
return &h
}
// Add 添加一个聊天角色
func (h *ChatRoleHandler) Add(c *gin.Context) {
var data vo.ChatRole
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
if data.Key == "" || data.Name == "" || data.Icon == "" {
resp.ERROR(c, types.InvalidArgs)
return
}
//err := h.service.Create(data)
//if err != nil {
// resp.ERROR(c, "Save failed: "+err.Error())
// return
//}
resp.SUCCESS(c, data)
}
// Get 获取指定的角色
func (h *ChatRoleHandler) Get(c *gin.Context) {
}
// Update 更新某个聊天角色信息,这里只允许更改名称以及启用和禁用角色操作
func (h *ChatRoleHandler) Update(c *gin.Context) {
}
func (h *ChatRoleHandler) List(c *gin.Context) {
var items []model.ChatRole
var roles = make([]vo.ChatRole, 0)
res := h.db.Where("enable", true).Order("sort ASC").Find(&items)
if res.Error != nil {
resp.ERROR(c, "No data found")
return
}
for _, v := range items {
var role vo.ChatRole
err := utils.CopyObject(v, &role)
if err == nil {
role.Id = v.Id
role.CreatedAt = v.CreatedAt.Unix()
role.UpdatedAt = v.UpdatedAt.Unix()
roles = append(roles, role)
}
}
resp.SUCCESS(c, roles)
}

View File

@ -0,0 +1,51 @@
package admin
import (
"chatplus/core"
"chatplus/handler"
"chatplus/store/model"
"chatplus/store/vo"
"chatplus/utils"
"chatplus/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type UserHandler struct {
handler.BaseHandler
db *gorm.DB
}
func NewUserHandler(app *core.AppServer, db *gorm.DB) *UserHandler {
h := UserHandler{db: db}
h.App = app
return &h
}
// List 用户列表
func (h *UserHandler) List(c *gin.Context) {
page := h.GetInt(c, "page", 1)
pageSize := h.GetInt(c, "page_size", 20)
offset := (page - 1) * pageSize
var items []model.User
var users = make([]vo.User, 0)
var total int64
h.db.Model(&model.User{}).Count(&total)
res := h.db.Offset(offset).Limit(pageSize).Find(&items)
if res.Error == nil {
for _, item := range items {
var user vo.User
err := utils.CopyObject(item, &user)
if err == nil {
user.Id = item.Id
user.CreatedAt = item.CreatedAt.Unix()
user.UpdatedAt = item.UpdatedAt.Unix()
users = append(users, user)
} else {
logger.Error(err)
}
}
}
pageVo := vo.NewPage(total, page, pageSize, users)
resp.SUCCESS(c, pageVo)
}

View File

@ -2,24 +2,21 @@ package handler
import ( import (
"chatplus/core" "chatplus/core"
"chatplus/core/types"
"chatplus/service"
"chatplus/store/model" "chatplus/store/model"
"chatplus/store/vo" "chatplus/store/vo"
"chatplus/utils" "chatplus/utils"
"chatplus/utils/resp" "chatplus/utils/resp"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm"
) )
type ChatRoleHandler struct { type ChatRoleHandler struct {
BaseHandler BaseHandler
service *service.ChatRoleService db *gorm.DB
} }
func NewChatRoleHandler(app *core.AppServer, service *service.ChatRoleService) *ChatRoleHandler { func NewChatRoleHandler(app *core.AppServer, db *gorm.DB) *ChatRoleHandler {
handler := &ChatRoleHandler{service: service} handler := &ChatRoleHandler{db: db}
handler.App = app handler.App = app
return handler return handler
} }
@ -27,15 +24,15 @@ func NewChatRoleHandler(app *core.AppServer, service *service.ChatRoleService) *
// List get user list // List get user list
func (h *ChatRoleHandler) List(c *gin.Context) { func (h *ChatRoleHandler) List(c *gin.Context) {
var roles []model.ChatRole var roles []model.ChatRole
res := h.service.DB.Find(&roles) res := h.db.Find(&roles)
if res.Error != nil { if res.Error != nil {
resp.ERROR(c, "No roles found,"+res.Error.Error()) resp.ERROR(c, "No roles found,"+res.Error.Error())
return return
} }
userId, err := strconv.Atoi(c.Query("user_id")) userId := h.GetInt(c, "user_id", 0)
if err == nil && userId > 0 { if userId > 0 {
var user model.User var user model.User
h.service.DB.First(&user, userId) h.db.First(&user, userId)
var roleMap map[string]int var roleMap map[string]int
err := utils.JsonDecode(user.ChatRoles, &roleMap) err := utils.JsonDecode(user.ChatRoles, &roleMap)
if err == nil { if err == nil {
@ -58,35 +55,3 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
} }
resp.SUCCESS(c, roleVos) resp.SUCCESS(c, roleVos)
} }
// Add 添加一个聊天角色
func (h *ChatRoleHandler) Add(c *gin.Context) {
var data vo.ChatRole
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
if data.Key == "" || data.Name == "" || data.Icon == "" {
resp.ERROR(c, types.InvalidArgs)
return
}
err := h.service.Create(data)
if err != nil {
resp.ERROR(c, "Save failed: "+err.Error())
return
}
resp.SUCCESS(c, data)
}
// Get 获取指定的角色
func (h *ChatRoleHandler) Get(c *gin.Context) {
}
// Update 更新某个聊天角色信息,这里只允许更改名称以及启用和禁用角色操作
func (h *ChatRoleHandler) Update(c *gin.Context) {
}

View File

@ -105,31 +105,6 @@ func (h *UserHandler) Register(c *gin.Context) {
resp.SUCCESS(c, user) resp.SUCCESS(c, user)
} }
func (h *UserHandler) List(c *gin.Context) {
var users []model.User
res := h.db.Find(&users)
if res.Error != nil {
resp.ERROR(c, "No user found")
logger.Error("get user failed: ", res.Error.Error())
return
}
// 转成 VO 输出
var userVos = make([]vo.User, 0)
for _, u := range users {
logger.Info(u)
var v vo.User
err := utils.CopyObject(u, &v)
if err == nil {
v.Id = u.Id
v.CreatedAt = u.CreatedAt.Unix()
v.UpdatedAt = u.UpdatedAt.Unix()
userVos = append(userVos, v)
}
}
resp.SUCCESS(c, userVos)
}
// Login 用户登录 // Login 用户登录
func (h *UserHandler) Login(c *gin.Context) { func (h *UserHandler) Login(c *gin.Context) {
var data struct { var data struct {

View File

@ -6,7 +6,6 @@ import (
"chatplus/handler" "chatplus/handler"
"chatplus/handler/admin" "chatplus/handler/admin"
logger2 "chatplus/logger" logger2 "chatplus/logger"
"chatplus/service"
"chatplus/store" "chatplus/store"
"context" "context"
"flag" "flag"
@ -64,6 +63,7 @@ func main() {
// 初始化数据库 // 初始化数据库
fx.Provide(store.NewGormConfig), fx.Provide(store.NewGormConfig),
fx.Provide(store.NewMysql), fx.Provide(store.NewMysql),
fx.Provide(store.NewLevelDB),
// 创建 Ip2Region 查询对象 // 创建 Ip2Region 查询对象
fx.Provide(func() (*xdb.Searcher, error) { fx.Provide(func() (*xdb.Searcher, error) {
@ -76,11 +76,6 @@ func main() {
return xdb.NewWithBuffer(cBuff) return xdb.NewWithBuffer(cBuff)
}), }),
// 初始化服务
fx.Provide(store.NewLevelDB),
fx.Provide(service.NewChatRoleService),
fx.Invoke(core.InitChatRoles),
// 创建控制器 // 创建控制器
fx.Provide(handler.NewChatRoleHandler), fx.Provide(handler.NewChatRoleHandler),
fx.Provide(handler.NewUserHandler), fx.Provide(handler.NewUserHandler),
@ -89,6 +84,8 @@ func main() {
fx.Provide(admin.NewAdminHandler), fx.Provide(admin.NewAdminHandler),
fx.Provide(admin.NewApiKeyHandler), fx.Provide(admin.NewApiKeyHandler),
fx.Provide(admin.NewUserHandler),
fx.Provide(admin.NewChatRoleHandler),
// 注册路由 // 注册路由
fx.Invoke(func(s *core.AppServer, h *handler.ChatRoleHandler) { fx.Invoke(func(s *core.AppServer, h *handler.ChatRoleHandler) {
@ -98,7 +95,6 @@ func main() {
fx.Invoke(func(s *core.AppServer, h *handler.UserHandler) { fx.Invoke(func(s *core.AppServer, h *handler.UserHandler) {
group := s.Engine.Group("/api/user/") group := s.Engine.Group("/api/user/")
group.POST("register", h.Register) group.POST("register", h.Register)
group.GET("list", h.List)
group.POST("login", h.Login) group.POST("login", h.Login)
group.GET("logout", h.Logout) group.GET("logout", h.Logout)
group.GET("session", h.Session) group.GET("session", h.Session)
@ -135,6 +131,16 @@ func main() {
group.POST("add", h.Add) group.POST("add", h.Add)
group.GET("list", h.List) group.GET("list", h.List)
}), }),
fx.Invoke(func(s *core.AppServer, h *admin.UserHandler) {
group := s.Engine.Group("/api/admin/user/")
group.GET("list", h.List)
}),
fx.Invoke(func(s *core.AppServer, h *admin.ChatRoleHandler) {
group := s.Engine.Group("/api/admin/role/")
group.GET("list", h.List)
group.POST("add", h.Add)
group.POST("update", h.Update)
}),
fx.Invoke(func(s *core.AppServer, db *gorm.DB) { fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
err := s.Run(db) err := s.Run(db)

View File

@ -1,35 +0,0 @@
package service
import (
"chatplus/store/model"
"chatplus/store/vo"
"chatplus/utils"
"gorm.io/gorm"
)
type ChatRoleService struct {
DB *gorm.DB
Model interface{}
}
func NewChatRoleService(db *gorm.DB) *ChatRoleService {
return &ChatRoleService{DB: db, Model: &model.ChatRole{}}
}
func (s *ChatRoleService) Create(value interface{}) error {
r, ok := value.(vo.ChatRole)
if ok {
var role model.ChatRole
err := utils.CopyObject(r, &role)
if err != nil {
return err
}
result := s.DB.Create(&role)
return result.Error
} else {
result := s.DB.Create(value)
return result.Error
}
}

View File

@ -1,5 +1,7 @@
package vo package vo
import "math"
type Page struct { type Page struct {
Items interface{} `json:"items"` Items interface{} `json:"items"`
Page int `json:"page"` Page int `json:"page"`
@ -9,12 +11,12 @@ type Page struct {
} }
func NewPage(total int64, page int, pageSize int, items interface{}) Page { func NewPage(total int64, page int, pageSize int, items interface{}) Page {
totalPage := int(total / int64(pageSize)) totalPage := math.Ceil(float64(total) / float64(pageSize))
return Page{ return Page{
Items: items, Items: items,
Page: page, Page: page,
PageSize: pageSize, PageSize: pageSize,
Total: total, Total: total,
TotalPage: totalPage, TotalPage: int(totalPage),
} }
} }

View File

@ -77,7 +77,7 @@
<sys-config v-if="curTab==='config'"/> <sys-config v-if="curTab==='config'"/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="口令管理" name="user" v-if="arrayContains(tabs, 'user')"> <el-tab-pane label="用户管理" name="user" v-if="arrayContains(tabs, 'user')">
<user-list v-if="curTab==='user'"/> <user-list v-if="curTab==='user'"/>
</el-tab-pane> </el-tab-pane>

View File

@ -46,10 +46,10 @@
<el-input v-model="chat['model']" placeholder="用户默认使用的 GPT 模型"/> <el-input v-model="chat['model']" placeholder="用户默认使用的 GPT 模型"/>
</el-form-item> </el-form-item>
<el-form-item label="模型温度"> <el-form-item label="模型温度">
<el-input v-model="chat['temperature']" placeholder="0-1之间的小数"/> <el-input v-model.number="chat['temperature']" placeholder="0-1之间的小数"/>
</el-form-item> </el-form-item>
<el-form-item label="Max Tokens"> <el-form-item label="Max Tokens">
<el-input v-model="chat['max_tokens']" placeholder="回复的最大字数最大4096"/> <el-input v-model.number="chat['max_tokens']" placeholder="回复的最大字数最大4096"/>
</el-form-item> </el-form-item>
<el-form-item label="开启聊天上下文"> <el-form-item label="开启聊天上下文">
<el-switch v-model="chat['enable_context']"/> <el-switch v-model="chat['enable_context']"/>

View File

@ -1,28 +1,28 @@
<template> <template>
<div class="user-list" v-loading="loading"> <div class="user-list" v-loading="loading">
<el-row> <el-row>
<el-table :data="users"> <el-table :data="users.items">
<el-table-column prop="name" label="口令名称"/> <el-table-column prop="username" label="用户名"/>
<el-table-column prop="max_calls" label="最大提问次数"/> <el-table-column prop="nickname" label="昵称"/>
<el-table-column prop="remaining_calls" label="剩余提问次数"/> <el-table-column prop="calls" label="提问次数" width="100"/>
<el-table-column label="激活时间" width="180"> <el-table-column label="状态" width="80">
<template #default="scope">
<el-tag type="info" v-if="scope.row.active_time === ''">未激活</el-tag>
<span v-else>{{ scope.row.active_time }}</span>
</template>
</el-table-column>
<el-table-column label="过期时间" width="180">
<template #default="scope">
<el-tag type="info" v-if="scope.row.expired_time === ''">未激活</el-tag>
<span v-else>{{ scope.row.expired_time }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="180">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.status" type="success">正常</el-tag> <el-tag v-if="scope.row.status" type="success">正常</el-tag>
<el-tag type="danger" v-else>停用</el-tag> <el-tag type="danger" v-else>停用</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="过期时间">
<template #default="scope">
<span v-if="scope.row['expired_time'] > 0">{{ dateFormat(scope.row['expired_time']) }}</span>
<el-tag v-else>长期有效</el-tag>
</template>
</el-table-column>
<el-table-column label="注册时间">
<template #default="scope">
<span>{{ dateFormat(scope.row['created_at']) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="180"> <el-table-column label="操作" width="180">
<template #default="scope"> <template #default="scope">
@ -43,154 +43,29 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="pagination">
<el-pagination background
layout="prev, pager, next"
:hide-on-single-page="true"
v-model:current-page="users.page"
v-model:page-size="users['page_size']"
:total="1000"/>
</div>
</el-row> </el-row>
<el-dialog
v-model="showUserDialog"
title="新增口令"
width="50%"
:destroy-on-close="true"
>
<el-form :model="form1" label-width="100px" ref="userAddFormRef" :rules="rules">
<el-form-item label="口令名称:" prop="name">
<el-input
v-model="form1.name"
autocomplete="off"
placeholder="请输入口令名称"
/>
</el-form-item>
<el-form-item label="提问次数:" prop="max_calls">
<el-input
v-model.number="form1.max_calls"
autocomplete="off"
placeholder="0 表示不限制提问次数"
/>
</el-form-item>
<el-form-item label="有效期:" prop="term">
<el-input
v-model.number="form1.term"
autocomplete="off"
placeholder="单位:天"
/>
</el-form-item>
<el-form-item label="聊天角色" prop="chat_roles">
<el-select
v-model="form1.chat_roles"
multiple
:filterable="true"
placeholder="选择聊天角色,多选"
@change="selectRole"
>
<el-option
v-for="item in roles"
:key="item.key"
:label="item.name"
:value="item.key"
:disabled="item.disabled"
/>
</el-select>
</el-form-item>
<el-form-item label="聊天记录">
<el-switch v-model="form1.enable_history"/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showUserDialog = false">取消</el-button>
<el-button type="primary" @click="addUser">提交</el-button>
</span>
</template>
</el-dialog>
<el-dialog
v-model="showBatchAddUserDialog"
title="批量生成口令"
width="50%"
:destroy-on-close="true"
>
<el-form :model="form3" label-width="100px" ref="userEditFormRef" :rules="rules">
<el-form-item label="提问次数:" prop="max_calls">
<el-input
v-model.number="form3.max_calls"
autocomplete="off"
placeholder="生成口令的最大提问次数"
/>
</el-form-item>
<el-form-item label="口令数量:" prop="number">
<el-input
v-model.number="form3.number"
autocomplete="off"
placeholder="批量生成的口令数量"
/>
</el-form-item>
<el-form-item label="有效期:" prop="term">
<el-input
v-model.number="form3.term"
autocomplete="off"
placeholder="单位:天"
/>
</el-form-item>
<el-form-item label="聊天角色" prop="chat_roles">
<el-select
v-model="form3.chat_roles"
multiple
:filterable="true"
placeholder="选择聊天角色,多选"
@change="selectRole"
>
<el-option
v-for="item in roles"
:key="item.key"
:label="item.name"
:value="item.key"
:disabled="item.disabled"
/>
</el-select>
</el-form-item>
<el-form-item label="聊天记录">
<el-switch v-model="form2.enable_history"/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showBatchAddUserDialog = false">取消</el-button>
<el-button type="success" @click="batchAddUser">提交</el-button>
</span>
</template>
</el-dialog>
<el-dialog <el-dialog
v-model="showUserEditDialog" v-model="showUserEditDialog"
title="编辑口令" title="编辑用户"
width="50%" width="50%"
> >
<el-form :model="form2" label-width="100px" ref="userEditFormRef" :rules="rules"> <el-form :model="user" label-width="100px" ref="userEditFormRef" :rules="rules">
<el-form-item label="口令名称:" prop="name"> <el-form-item label="昵称:" prop="nickname">
<el-input <el-input v-model="user.nickname" autocomplete="off"/>
v-model="form2.name"
autocomplete="off"
placeholder="请输入口令名称"
readonly
/>
</el-form-item> </el-form-item>
<el-form-item label="提问次数:" prop="remaining_calls"> <el-form-item label="提问次数:" prop="calls">
<el-input <el-input v-model.number="user.calls" autocomplete="off" placeholder="0"/>
v-model.number="form2.remaining_calls"
autocomplete="off"
placeholder="0"
/>
</el-form-item> </el-form-item>
<el-form-item label="有效期:" prop="term"> <el-form-item label="有效期:" prop="term">
@ -242,14 +117,14 @@
<script setup> <script setup>
import {nextTick, onMounted, reactive, ref} from "vue"; import {nextTick, onMounted, reactive, ref} from "vue";
import {httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus"; import {ElMessage} from "element-plus";
import {arrayContains, removeArrayItem} from "@/utils/libs"; import {arrayContains, dateFormat, removeArrayItem} from "@/utils/libs";
// //
const users = ref([]) const users = ref({})
const form1 = ref({chat_roles: []})
const form2 = ref({}) const user = ref({chat_roles: []})
const roles = ref([]) const roles = ref([])
const showUserDialog = ref(false) const showUserDialog = ref(false)
const showUserEditDialog = ref(false) const showUserEditDialog = ref(false)
@ -280,16 +155,15 @@ const userEditFormRef = ref(null)
onMounted(() => { onMounted(() => {
// //
httpPost('/api/admin/user/list').then((res) => { httpGet('/api/admin/user/list', {page: 1, page_size: 20}).then((res) => {
users.value = res.data; users.value = res.data;
}).catch(() => { }).catch(() => {
ElMessage.error('获取系统配置失败') ElMessage.error('加载用户列表失败')
}) })
// //
httpPost('/api/admin/chat-roles/list').then((res) => { httpGet('/api/admin/role/list').then((res) => {
roles.value = res.data; roles.value = res.data;
roles.value.unshift({name: '全部', key: 'all'})
}).catch(() => { }).catch(() => {
ElMessage.error("获取聊天角色失败"); ElMessage.error("获取聊天角色失败");
}) })
@ -299,26 +173,6 @@ onMounted(() => {
}) })
}) })
//
const addUser = () => {
userAddFormRef.value.validate((valid) => {
if (valid) {
showUserDialog.value = false
form1.value.term = parseInt(form1.value.term)
form1.value.max_calls = parseInt(form1.value.max_calls)
httpPost('/api/admin/user/add', form1.value).then((res) => {
ElMessage.success('添加口令成功')
form1.value = {chat_roles: []}
users.value.unshift(res.data)
}).catch((e) => {
ElMessage.error('添加口令失败,' + e.message)
})
} else {
return false
}
})
}
// //
const selectRole = function (items) { const selectRole = function (items) {
if (arrayContains(items, 'all')) { if (arrayContains(items, 'all')) {
@ -342,20 +196,20 @@ const selectRole = function (items) {
} }
// //
const removeUser = function (user) { const removeUser = function (user) {
httpPost('/api/admin/user/remove', {name: user.name}).then(() => { httpPost('/api/admin/user/remove', {name: user.name}).then(() => {
ElMessage.success('删除口令成功') ElMessage.success('操作成功!')
users.value = removeArrayItem(users.value, user, function (v1, v2) { users.value = removeArrayItem(users.value, user, function (v1, v2) {
return v1.name === v2.name return v1.name === v2.name
}) })
}).catch((e) => { }).catch((e) => {
ElMessage.error('删除口令失败,' + e.message) ElMessage.error('操作失败,' + e.message)
}) })
} }
const userEdit = function (user) { const userEdit = function (_user) {
form2.value = user user.value = _user
showUserEditDialog.value = true showUserEditDialog.value = true
} }
@ -366,30 +220,16 @@ const updateUser = function () {
showUserEditDialog.value = false showUserEditDialog.value = false
form2.value.term = parseInt(form2.value.term) form2.value.term = parseInt(form2.value.term)
form2.value.remaining_calls = parseInt(form2.value.remaining_calls) form2.value.remaining_calls = parseInt(form2.value.remaining_calls)
httpPost('/api/admin/user/set', form2.value).then(() => { httpPost('/api/admin/user/update', form2.value).then(() => {
ElMessage.success('更新口令成功') ElMessage.success('操作成功!')
}).catch((e) => { }).catch((e) => {
ElMessage.error('更新口令失败,' + e.message) ElMessage.error('操作失败,' + e.message)
}) })
} else { } else {
return false return false
} }
}) })
} }
//
const showBatchAddUserDialog = ref(false)
const form3 = ref({chat_roles: []})
const batchAddUser = function () {
httpPost('api/admin/user/batch-add', form3.value).then((res) => {
console.log(res.data)
ElMessage.success('添加口令成功')
users.value = [...res.data, ...users.value]
showBatchAddUserDialog.value = false
}).catch((e) => {
console.log('添加口令失败,' + e.message)
})
}
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
@ -403,6 +243,13 @@ const batchAddUser = function () {
} }
} }
.pagination {
padding-top 20px;
display flex
justify-content center
width 100%
}
.el-select { .el-select {
width: 100% width: 100%
} }