diff --git a/README.md b/README.md index 2687fbfd..149a41ae 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,12 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 MidJourney 和 Stable Diffusion AI绘画功能。主要有如下特性: * 完整的开源系统,前端应用和后台管理系统皆可开箱即用。 -* 聊天体验跟 ChatGPT 官方版本完全一致。 -* 内置了各种预训练好的角色,比如小红书写手,英语翻译大师,苏格拉底,孔子,乔布斯,周报助手等。轻松满足你的各种聊天和应用需求。 +* 基于 Websocket 实现,完美的打字机体验。 +* 内置了各种预训练好的角色应用,比如小红书写手,英语翻译大师,苏格拉底,孔子,乔布斯,周报助手等。轻松满足你的各种聊天和应用需求。 +* 支持 OPenAI,Azure,文心一言,讯飞星火,清华 ChatGLM等多个大语言模型。 * 支持 MidJourney / Stable Diffusion AI 绘画集成,开箱即用。 * 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。(可定制开发其他支付通道支持) -* 集成插件 API 功能,可结合 GPT 开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI 绘画函数插件。 +* 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI 绘画函数插件。 ## 功能截图 @@ -16,34 +17,30 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了  -### 新版聊天界面 +### AI 对话界面  -### MidJourney 专业绘画界面(v3.1.3) +### MidJourney 专业绘画界面 + - +### Stable-Diffusion 专业绘画页面 + + +### 绘图作品展 + +### AI应用列表 + ### 自动调用函数插件 -  -  -### 绘图作品展 - - - -### 登录页面 - - - ### 管理后台  - - - + +  ### 移动端 Web 页面 @@ -306,8 +303,8 @@ docker-compose up -d  -最后登录前端聊天页面 [http://localhost:8080/chat](http://localhost:8080/chat) -你可以注册新用户,也可以使用系统默认有个账号:`geekmaster/12345678` 登录聊天。 +最后进入前端聊天页面 [http://localhost:8080/chat](http://localhost:8080/chat) +你可以注册新用户,也可以使用系统默认有个账号:`18575670125/12345678` 登录聊天。 祝你使用愉快!!! @@ -411,5 +408,7 @@ make clean linux   + + diff --git a/api/core/types/config.go b/api/core/types/config.go index 1e30051a..3e39a20f 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -120,4 +120,5 @@ type SystemConfig struct { RewardImg string `json:"reward_img"` // 众筹收款二维码地址 EnabledFunction bool `json:"enabled_function"` // 启用 API 函数功能 EnabledReward bool `json:"enabled_reward"` // 启用众筹功能 + DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型 } diff --git a/api/handler/admin/chat_model_handler.go b/api/handler/admin/chat_model_handler.go index 1d40f6e2..1232175c 100644 --- a/api/handler/admin/chat_model_handler.go +++ b/api/handler/admin/chat_model_handler.go @@ -32,6 +32,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) { Enabled bool `json:"enabled"` SortNum int `json:"sort_num"` Platform string `json:"platform"` + Weight int `json:"weight"` CreatedAt int64 `json:"created_at"` } if err := c.ShouldBindJSON(&data); err != nil { @@ -39,7 +40,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) { return } - item := model.ChatModel{Platform: data.Platform, Name: data.Name, Value: data.Value, Enabled: data.Enabled} + item := model.ChatModel{Platform: data.Platform, Name: data.Name, Value: data.Value, Enabled: data.Enabled, SortNum: data.SortNum, Weight: data.Weight} item.Id = data.Id if item.Id > 0 { item.CreatedAt = time.Unix(data.CreatedAt, 0) diff --git a/api/handler/admin/user_handler.go b/api/handler/admin/user_handler.go index aef0f58b..57392258 100644 --- a/api/handler/admin/user_handler.go +++ b/api/handler/admin/user_handler.go @@ -67,6 +67,7 @@ func (h *UserHandler) Save(c *gin.Context) { Calls int `json:"calls"` ImgCalls int `json:"img_calls"` ChatRoles []string `json:"chat_roles"` + ChatModels []string `json:"chat_models"` ExpiredTime string `json:"expired_time"` Status bool `json:"status"` } @@ -81,12 +82,13 @@ func (h *UserHandler) Save(c *gin.Context) { user.Id = data.Id // 此处需要用 map 更新,用结构体无法更新 0 值 res = h.db.Model(&user).Updates(map[string]interface{}{ - "mobile": data.Mobile, - "calls": data.Calls, - "img_calls": data.ImgCalls, - "status": data.Status, - "chat_roles_json": utils.JsonEncode(data.ChatRoles), - "expired_time": utils.Str2stamp(data.ExpiredTime), + "mobile": data.Mobile, + "calls": data.Calls, + "img_calls": data.ImgCalls, + "status": data.Status, + "chat_roles_json": utils.JsonEncode(data.ChatRoles), + "chat_models_json": utils.JsonEncode(data.ChatModels), + "expired_time": utils.Str2stamp(data.ExpiredTime), }) } else { salt := utils.RandString(8) @@ -97,6 +99,7 @@ func (h *UserHandler) Save(c *gin.Context) { Salt: salt, Status: true, ChatRoles: utils.JsonEncode(data.ChatRoles), + ChatModels: utils.JsonEncode(data.ChatModels), ExpiredTime: utils.Str2stamp(data.ExpiredTime), ChatConfig: utils.JsonEncode(types.UserChatConfig{ ApiKeys: map[types.Platform]string{ diff --git a/api/handler/chat_model_handler.go b/api/handler/chat_model_handler.go index 59ea2d76..949a45a2 100644 --- a/api/handler/chat_model_handler.go +++ b/api/handler/chat_model_handler.go @@ -24,8 +24,22 @@ func NewChatModelHandler(app *core.AppServer, db *gorm.DB) *ChatModelHandler { // List 模型列表 func (h *ChatModelHandler) List(c *gin.Context) { var items []model.ChatModel - var cms = make([]vo.ChatModel, 0) - res := h.db.Where("enabled = ?", true).Order("sort_num ASC").Find(&items) + var chatModels = make([]vo.ChatModel, 0) + // 只加载用户订阅的 AI 模型 + user, err := utils.GetLoginUser(c, h.db) + if err != nil { + resp.NotAuth(c) + return + } + + var models []string + err = utils.JsonDecode(user.ChatModels, &models) + if err != nil { + resp.ERROR(c, "当前用户没有订阅任何模型") + return + } + + res := h.db.Where("enabled = ?", true).Where("value IN ?", models).Order("sort_num ASC").Find(&items) if res.Error == nil { for _, item := range items { var cm vo.ChatModel @@ -34,11 +48,11 @@ func (h *ChatModelHandler) List(c *gin.Context) { cm.Id = item.Id cm.CreatedAt = item.CreatedAt.Unix() cm.UpdatedAt = item.UpdatedAt.Unix() - cms = append(cms, cm) + chatModels = append(chatModels, cm) } else { logger.Error(err) } } } - resp.SUCCESS(c, cms) + resp.SUCCESS(c, chatModels) } diff --git a/api/handler/chatimpl/chat_handler.go b/api/handler/chatimpl/chat_handler.go index 4defda96..83493d4e 100644 --- a/api/handler/chatimpl/chat_handler.go +++ b/api/handler/chatimpl/chat_handler.go @@ -393,7 +393,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf req.Messages = nil break case types.Baidu: - apiURL = h.App.ChatConfig.Baidu.ApiURL + apiURL = strings.Replace(h.App.ChatConfig.Baidu.ApiURL, "{model}", req.Model, 1) break default: apiURL = h.App.ChatConfig.OpenAI.ApiURL diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index 79f58417..a74eff5b 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -82,12 +82,13 @@ func (h *UserHandler) Register(c *gin.Context) { salt := utils.RandString(8) user := model.User{ - Password: utils.GenPassword(data.Password, salt), - Avatar: "/images/avatar/user.png", - Salt: salt, - Status: true, - Mobile: data.Mobile, - ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色 + Password: utils.GenPassword(data.Password, salt), + Avatar: "/images/avatar/user.png", + Salt: salt, + Status: true, + Mobile: data.Mobile, + ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色 + ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型 ChatConfig: utils.JsonEncode(types.UserChatConfig{ ApiKeys: map[types.Platform]string{ types.OpenAI: "", diff --git a/api/store/model/chat_model.go b/api/store/model/chat_model.go index 89639e7d..fb8a63ec 100644 --- a/api/store/model/chat_model.go +++ b/api/store/model/chat_model.go @@ -7,4 +7,5 @@ type ChatModel struct { Value string // API Key 的值 SortNum int Enabled bool + Weight int // 对话权重,每次对话扣减多少次对话额度 } diff --git a/api/store/model/user.go b/api/store/model/user.go index 1129307c..ccbf6442 100644 --- a/api/store/model/user.go +++ b/api/store/model/user.go @@ -11,6 +11,7 @@ type User struct { ImgCalls int // 剩余绘图次数 ChatConfig string `gorm:"column:chat_config_json"` // 聊天配置 json ChatRoles string `gorm:"column:chat_roles_json"` // 聊天角色 + ChatModels string `gorm:"column:chat_models_json"` // AI 模型,不同的用户拥有不同的聊天模型 ExpiredTime int64 // 账户到期时间 Status bool `gorm:"default:true"` // 当前状态 LastLoginAt int64 // 最后登录时间 diff --git a/api/store/vo/chat_model.go b/api/store/vo/chat_model.go index b42cdafa..7ed783ec 100644 --- a/api/store/vo/chat_model.go +++ b/api/store/vo/chat_model.go @@ -6,4 +6,6 @@ type ChatModel struct { Name string `json:"name"` Value string `json:"value"` Enabled bool `json:"enabled"` + SortNum int `json:"sort_num"` + Weight int `json:"weight"` } diff --git a/api/store/vo/user.go b/api/store/vo/user.go index 7b00b873..8c291a96 100644 --- a/api/store/vo/user.go +++ b/api/store/vo/user.go @@ -12,6 +12,7 @@ type User struct { ImgCalls int `json:"img_calls"` ChatConfig types.UserChatConfig `json:"chat_config"` // 聊天配置 ChatRoles []string `json:"chat_roles"` // 聊天角色集合 + ChatModels []string `json:"chat_models"` // AI模型集合 ExpiredTime int64 `json:"expired_time"` // 账户到期时间 Status bool `json:"status"` // 当前状态 LastLoginAt int64 `json:"last_login_at"` // 最后登录时间 diff --git a/database/update-v3.1.7.sql b/database/update-v3.1.7.sql new file mode 100644 index 00000000..ceda008c --- /dev/null +++ b/database/update-v3.1.7.sql @@ -0,0 +1,6 @@ +-- 增加字段保存用户开通的 AI 模型 +ALTER TABLE `chatgpt_users` ADD `chat_models_json` TEXT NOT NULL COMMENT 'AI模型 json' AFTER `chat_roles_json`; + +-- 为每个模型设置对话权重 +ALTER TABLE `chatgpt_chat_models` ADD `weight` TINYINT(3) NOT NULL COMMENT '对话权重,每次对话扣减多少次对话额度' AFTER `enabled`; +UPDATE `chatgpt_chat_models` SET weight = 1; \ No newline at end of file diff --git a/docs/imgs/admin_config.jpg b/docs/imgs/admin_config.jpg new file mode 100644 index 00000000..b3af5262 Binary files /dev/null and b/docs/imgs/admin_config.jpg differ diff --git a/docs/imgs/admin_config.png b/docs/imgs/admin_config.png deleted file mode 100644 index 0956ef15..00000000 Binary files a/docs/imgs/admin_config.png and /dev/null differ diff --git a/docs/imgs/admin_models.jpg b/docs/imgs/admin_models.jpg new file mode 100644 index 00000000..3649dde2 Binary files /dev/null and b/docs/imgs/admin_models.jpg differ diff --git a/docs/imgs/app-list.jpg b/docs/imgs/app-list.jpg new file mode 100644 index 00000000..1a48c4eb Binary files /dev/null and b/docs/imgs/app-list.jpg differ diff --git a/docs/imgs/login.png b/docs/imgs/login.png deleted file mode 100644 index a1e72b39..00000000 Binary files a/docs/imgs/login.png and /dev/null differ diff --git a/docs/imgs/mj_image.jpg b/docs/imgs/mj_image.jpg new file mode 100644 index 00000000..cee3584a Binary files /dev/null and b/docs/imgs/mj_image.jpg differ diff --git a/docs/imgs/mj_image.png b/docs/imgs/mj_image.png deleted file mode 100644 index cbc22f54..00000000 Binary files a/docs/imgs/mj_image.png and /dev/null differ diff --git a/docs/imgs/sd_image.jpg b/docs/imgs/sd_image.jpg new file mode 100644 index 00000000..4bb6bfc2 Binary files /dev/null and b/docs/imgs/sd_image.jpg differ diff --git a/docs/imgs/sd_image_detail.jpg b/docs/imgs/sd_image_detail.jpg new file mode 100644 index 00000000..a1c9c03c Binary files /dev/null and b/docs/imgs/sd_image_detail.jpg differ diff --git a/web/src/assets/css/admin-form.css b/web/src/assets/css/admin-form.css new file mode 100644 index 00000000..b94fc52d --- /dev/null +++ b/web/src/assets/css/admin-form.css @@ -0,0 +1,10 @@ +.el-form-item__content .tip-input { + display: flex; + width: 100%; +} +.el-form-item__content .tip-input .input { + width: 100%; +} +.el-form-item__content .tip-input .info { + margin-left: 6px; +} diff --git a/web/src/assets/css/admin-form.styl b/web/src/assets/css/admin-form.styl new file mode 100644 index 00000000..11f03a34 --- /dev/null +++ b/web/src/assets/css/admin-form.styl @@ -0,0 +1,15 @@ +.el-form-item__content { + .tip-input { + display flex + width 100% + + .input { + width 100% + } + + .info { + margin-left 6px + } + + } +} \ No newline at end of file diff --git a/web/src/assets/css/image-mj.css b/web/src/assets/css/image-mj.css index 1264073b..494eba13 100644 --- a/web/src/assets/css/image-mj.css +++ b/web/src/assets/css/image-mj.css @@ -209,7 +209,7 @@ font-size: 20px; cursor: pointer; } -.page-mj .inner .task-list-box .finish-job-list .job-item:hover { +.page-mj .inner .task-list-box .finish-job-list .animate:hover { box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ transform: translateY(-10px); /* 向上移动10像素 */ } diff --git a/web/src/assets/css/image-sd.css b/web/src/assets/css/image-sd.css index 6a9e72ed..6799e825 100644 --- a/web/src/assets/css/image-sd.css +++ b/web/src/assets/css/image-sd.css @@ -132,7 +132,7 @@ font-size: 20px; cursor: pointer; } -.page-sd .inner .task-list-box .finish-job-list .job-item:hover { +.page-sd .inner .task-list-box .finish-job-list .animate:hover { box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */ transform: translateY(-10px); /* 向上移动10像素 */ } diff --git a/web/src/assets/css/task-list.styl b/web/src/assets/css/task-list.styl index c89bc225..b3817a64 100644 --- a/web/src/assets/css/task-list.styl +++ b/web/src/assets/css/task-list.styl @@ -80,8 +80,9 @@ } } } + } - + .animate { &:hover { box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */ transform: translateY(-10px); /* 向上移动10像素 */ diff --git a/web/src/views/ImageSd.vue b/web/src/views/ImageSd.vue index 8fff7a50..e33f0a4a 100644 --- a/web/src/views/ImageSd.vue +++ b/web/src/views/ImageSd.vue @@ -332,7 +332,7 @@