From 68100f7f245128c4985252ed892efcd26ef7b7c0 Mon Sep 17 00:00:00 2001 From: RockYang Date: Mon, 20 May 2024 15:11:14 +0800 Subject: [PATCH 01/20] rename project name to geekai --- README.md | 186 +++++------------- deploy/conf/config.toml | 158 ++++++++------- .../conf.d/{chatgpt-plus.conf => geekai.conf} | 4 +- deploy/conf/xxl-job/application.properties | 2 +- deploy/docker-compose.yaml | 33 ++-- web/package.json | 2 +- 6 files changed, 146 insertions(+), 239 deletions(-) rename deploy/conf/nginx/conf.d/{chatgpt-plus.conf => geekai.conf} (92%) diff --git a/README.md b/README.md index dc7d09dd..4113b504 100644 --- a/README.md +++ b/README.md @@ -1,160 +1,62 @@ -# ChatGPT-Plus +# GeekAI-PLUS +基于 GeekAI 项目开发的高级版,增加了很多高级功能,比如思维导图,Dalle 绘画等。**高级版源码不会一次性开放,只提供镜像给大家免费使用**,源码会逐步逐步按照版同步迁移到[社区版(GeekAI)](https://github.com/yangjian102621/geekai)。所以如果大家想要二次开发,请移步去社区版。 -**ChatGPT-PLUS** 基于 AI 大语言模型 API 实现的 AI 助手全套开源解决方案,自带运营管理后台,开箱即用。集成了 OpenAI, Azure, -ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 MidJourney 和 Stable Diffusion AI绘画功能。 +## 演示站点 +[Geek-AI 创作系统](https://www.geekai.me) -主要特性: +## 文档地址 +[Geek-AI 文档](https://www.geekai.me/docs/) -- 完整的开源系统,前端应用和后台管理系统皆可开箱即用。 -- 基于 Websocket 实现,完美的打字机体验。 -- 内置了各种预训练好的角色应用,比如小红书写手,英语翻译大师,苏格拉底,孔子,乔布斯,周报助手等。轻松满足你的各种聊天和应用需求。 -- 支持 OPenAI,Azure,文心一言,讯飞星火,清华 ChatGLM等多个大语言模型。 -- 支持 Suno 文生音乐 -- 支持 MidJourney / Stable Diffusion AI 绘画集成,文生图,图生图,换脸,融图。开箱即用。 -- 支持使用个人微信二维码作为充值收费的支付渠道,无需企业支付通道。 -- 已集成支付宝支付功能,微信支付,支持多种会员套餐和点卡购买功能。 -- 集成插件 API 功能,可结合大语言模型的 function 功能开发各种强大的插件,已内置实现了微博热搜,今日头条,今日早报和 AI - 绘画函数插件。 - -### 🚀 更多功能请查看 [Geek-AI](https://github.com/yangjian102621/geek-ai) - -- [x] 更友好的 UI 界面 -- [x] 支持 Dall-E 文生图功能 -- [x] 支持文生思维导图 -- [x] 支持为模型绑定指定的 API KEY,支持为角色绑定指定的模型等功能 -- [x] 支持网站 Logo 版权等信息的修改 +## 部署 +1. 安装 docker 和 docker-compose 程序,这个自行解决。 +2. 直接在项目根目录运行启动命令: + ```shell + docker-compose up -d + ``` ## 功能截图 - -### PC 端聊天界面 - -![ChatGPT Chat Page](/docs/imgs/gpt.gif) - -### AI 对话界面 - -![ChatGPT new Chat Page](/docs/imgs/chat-new.png) - +**!!!多图预警!!!** +### 首页 +![首页](imgs/home.png) +### 注册 +![注册](imgs/register.png) +### AI 对话 +![AI对话](imgs/chat.png) ### MidJourney 专业绘画界面 +![MJ绘画](imgs/mj-image.png) +![MJ绘画](imgs/mj-list.png) +### Stable-Diffusion 专业绘画 +![SD绘画](imgs/sd.png) +### DALLE 绘画 +![Dalle](imgs/dalle.png) -![mid-journey](/docs/imgs/mj_image.jpg) +### 画廊 +![画廊](imgs/img-wall.png) -### Stable-Diffusion 专业绘画页面 - -![Stable-Diffusion](/docs/imgs/sd_image.jpg) -![Stable-Diffusion](/docs/imgs/sd_image_detail.jpg) - -### 绘图作品展 - -![ChatGPT image_list](/docs/imgs/image-list.png) - -### AI应用列表 - -![ChatGPT-app-list](/docs/imgs/app-list.jpg) +### 思维导图 +![思维导图](imgs/markmap.png) ### 会员充值 - -![会员充值](/docs/imgs/member.png) - -### 自动调用函数插件 - -![ChatGPT function plugin](/docs/imgs/plugin.png) -![ChatGPT function plugin](/docs/imgs/mj.jpg) +![会员充值](imgs/member.png) ### 管理后台 +![管理后台](imgs/admin.png) +### 管理后台暗黑主题 +![深色主题](imgs/admin-dark.png) -![ChatGPT admin](/docs/imgs/admin_dashboard.png) -![ChatGPT admin](/docs/imgs/admin_config.jpg) -![ChatGPT admin](/docs/imgs/admin_models.jpg) -![ChatGPT admin](/docs/imgs/admin_user.png) +### H5 首页 +![H5首页](imgs/mobile-home.png) -### 移动端 Web 页面 +### H5 对话 +![对话列表](imgs/mobile-chat-list.png) +![对话列表](imgs/mobile-chat.png) -![Mobile chat list](/docs/imgs/mobile_chat_list.png) -![Mobile chat session](/docs/imgs/mobile_chat_session.png) -![Mobile chat setting](/docs/imgs/mobile_user_profile.png) -![Mobile chat setting](/docs/imgs/mobile_pay.png) +### H5 MidJourney 绘画 +![H5 MidJourney](imgs/mobile-mj.png) -### 体验地址 +### H5 Stable-Diffusion 绘画 +![H5 SD](imgs/mobile-sd.png) -> 免费体验地址:[https://ai.r9it.com/chat](https://ai.r9it.com/chat)
-> **注意:请合法使用,禁止输出任何敏感、不友好或违规的内容!!!** +### H5 会员充值 +![H5 Member](imgs/mobile-member.png) -## 快速部署 - -**演示站不提供任何充值点卡售卖或者VIP充值服务。** 如果您体验过后觉得还不错的话,可以花两分钟用下面的一键部署脚本自己部署一套。 - -```shell -bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v4.0.4-36d397add2.sh)" -``` - -最新版本的一键部署脚本请参考 [**ChatGPT-Plus 文档**](https://ai.r9it.com/docs/install/)。 - -目前仅支持 Ubuntu 和 Centos 系统。 部署成功之后可以访问下面地址 - -* 前端访问地址:http://localhost:8080/chat 使用移动设备访问会自动跳转到移动端页面。 -* 后台管理地址:http://localhost:8080/admin -* 移动端地址:http://localhost:8080/mobile -* 初始后台管理账号:admin/admin123 -* 初始前端体验账号:18575670125/12345678 - -服务启动成功之后不能立刻使用,需要先登录管理后台 -> API-KEY 去添加一个 OpenAI 或者文心一言,科大讯飞等至少一个平台的 API -KEY。 - -![](https://ai.r9it.com/docs/images/env/admin_api_keys.png) - -另外,如果您目前还没有 OpenAI 的 API KEY的,推荐您去 https://api.chat-plus.net 购买,**无需魔法,高速稳定,且价格还远低于 OpenAI -官方**。 - -## 使用须知 - -1. 本项目基于 Apache2.0 协议,免费开放全部源代码,可以作为个人学习使用或者商用。 -2. 如需商用必须保留版权信息,请自觉遵守。确保合法合规使用,在运营过程中产生的一切任何后果自负,与作者无关。 - -## 项目地址 - -* Github 地址:https://github.com/yangjian102621/chatgpt-plus -* 码云地址:https://gitee.com/blackfox/chatgpt-plus - -## 客户端下载 - -目前已经支持 Win/Linux/Mac/Android 客户端,下载地址为:https://github.com/yangjian102621/chatgpt-plus/releases/tag/v3.1.2 - -## TODOLIST - -* [ ] 支持基于知识库的 AI 问答 -* [ ] 会员邀请注册推广功能 -* [ ] 微信支付功能 - -## 项目文档 - -最新的部署视频教程:[https://www.bilibili.com/video/BV1Cc411t7CX/](https://www.bilibili.com/video/BV1Cc411t7CX/) - -详细的部署和开发文档请参考 [**ChatGPT-Plus 文档**](https://ai.r9it.com/docs/)。 - -加微信进入微信讨论群可获取 **一键部署脚本(添加好友时请注明来自Github!!!)。** - -![微信名片](docs/imgs/wx.png) - -## 参与贡献 - -个人的力量始终有限,任何形式的贡献都是欢迎的,包括但不限于贡献代码,优化文档,提交 issue 和 PR 等。 - -#### 特此声明:由于个人时间有限,不接受在微信或者微信群给开发者提 Bug,有问题或者优化建议请提交 Issue 和 PR。非常感谢您的配合! - -### Commit 类型 - -* feat: 新特性或功能 -* fix: 缺陷修复 -* docs: 文档更新 -* style: 代码风格或者组件样式更新 -* refactor: 代码重构,不引入新功能和缺陷修复 -* opt: 性能优化 -* chore: 一些不涉及到功能变动的小提交,比如修改文字表述,修改注释等 - -## 打赏 - -如果你觉得这个项目对你有帮助,并且情况允许的话,可以请作者喝杯咖啡,非常感谢你的支持~ - -![打赏](docs/imgs/donate.png) - -![Star History Chart](https://api.star-history.com/svg?repos=yangjian102621/chatgpt-plus&type=Date) diff --git a/deploy/conf/config.toml b/deploy/conf/config.toml index c47046ee..b76bc26f 100644 --- a/deploy/conf/config.toml +++ b/deploy/conf/config.toml @@ -1,106 +1,110 @@ Listen = "0.0.0.0:5678" -ProxyURL = "" # 如 http://127.0.0.1:7777 -MysqlDns = "root:12345678@tcp(chatgpt-plus-mysql:3306)/chatgpt_plus?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=True&loc=Local" -StaticDir = "./static" # 静态资源的目录 -StaticUrl = "/static" # 静态资源访问 URL +ProxyURL = "" +MysqlDns = "root:12345678@tcp(geekai-mysql:3306)/chatgpt_plus?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=True&loc=Local" +StaticDir = "./static" +StaticUrl = "/static" WeChatBot = false [Session] - SecretKey = "azyehq3ivunjhbntz78isj00i4hz2mt9xtddysfucxakadq4qbfrt0b7q3lnvg80" # 注意:这个是 JWT Token 授权密钥,生产环境请务必更换 + SecretKey = "azyehq3ivunjhbntz78isj00i4hz2mt9xtddysfucxakadq4qbfrt0b7q3lnvg80" MaxAge = 86400 [AdminSession] SecretKey = "wr1uzwz2meai4z9j0e0tsyf6x523ui6zpnyaim4x2x37meakv13349llqpipyk40" MaxAge = 8640000 -[Redis] # redis 配置信息 - Host = "chatgpt-plus-redis" +[Redis] + Host = "geekai-redis" Port = 6379 Password = "12345678" DB = 0 -[ApiConfig] # 微博热搜,今日头条等函数服务 API 配置,此为第三方插件服务,如需使用请联系作者开通 +[ApiConfig] ApiURL = "http://service.r9it.com:9001" AppId = "" Token = "" +[SMS] + Active = "Ali" + [SMS.Ali] + AccessKey = "" + AccessSecret = "" + Product = "Dysmsapi" + Domain = "dysmsapi.aliyuncs.com" + Sign = "" + CodeTempId = "" + [SMS.Bao] + Username = "" + Password = "" + Domain = "api.smsbao.com" + Sign = "【极客学长】" + CodeTemplate = "您的验证码是{code}。5分钟有效,若非本人操作,请忽略本短信。" -[SMS] # Sms 配置,用于发送短信 - Active = "Ali" # 当前启用的短信服务,默认使用阿里云 - [SMS.Bao] - Username = "" - Password = "" - Domain = "api.smsbao.com" - Sign = "【极客学长】" - CodeTemplate = "您的验证码是{code}。5分钟有效,若非本人操作,请忽略本短信。" - [SMS.Ali] - AccessKey = "" - AccessSecret = "" - Product = "Dysmsapi" - Domain = "dysmsapi.aliyuncs.com" - Sign = "" - CodeTempId = "" - -[OSS] # OSS 配置,用于存储 MJ 绘画图片 - Active = "Local" # 默认使用本地文件存储引擎 - [OSS.Local] - BasePath = "./static/upload" # 本地文件上传根路径 - BaseURL = "/static/upload" # 本地上传文件根 URL 如果是线上,则直接设置为 /static/upload 即可 - [OSS.Minio] - Endpoint = "" # 如 172.22.11.200:9000 - AccessKey = "" # 自己去 Minio 控制台去创建一个 Access Key - AccessSecret = "" - Bucket = "chatgpt-plus" # 替换为你自己创建的 Bucket,注意要给 Bucket 设置公开的读权限,否则会出现图片无法显示。 - UseSSL = false - Domain = "" # 地址必须是能够通过公网访问的,否则会出现图片无法显示。 - [OSS.QiNiu] # 七牛云 OSS 配置 - Zone = "z2" # 区域,z0:华东,z1: 华北,na0:北美,as0:新加坡 - AccessKey = "" - AccessSecret = "" - Bucket = "" - Domain = "" # OSS Bucket 所绑定的域名,如 https://img.r9it.com - [OSS.AliYun] - Endpoint = "oss-cn-hangzhou.aliyuncs.com" - AccessKey = "" - AccessSecret = "" - Bucket = "chatgpt-plus" - SubDir = "" - Domain = "" +[OSS] + Active = "Local" + [OSS.Local] + BasePath = "./static/upload" + BaseURL = "/static/upload" + [OSS.Minio] + Endpoint = "" + AccessKey = "" + AccessSecret = "" + Bucket = "geekai" + SubDir = "" + UseSSL = false + Domain = "" + [OSS.QiNiu] + Zone = "z2" + AccessKey = "" + AccessSecret = "" + Bucket = "" + SubDir = "" + Domain = "" + [OSS.AliYun] + Endpoint = "oss-cn-hangzhou.aliyuncs.com" + AccessKey = "" + AccessSecret = "" + Bucket = "geekai" + SubDir = "" + Domain = "" [[MjProxyConfigs]] - Enabled = true + Enabled = false ApiURL = "http://midjourney-proxy:8082" + Mode = "" ApiKey = "sk-geekmaster" [[MjPlusConfigs]] - Enabled = false - ApiURL = "https://api.chat-plus.net" - Mode = "fast" - ApiKey = "sk-xxx" + Enabled = false + ApiURL = "https://api.chat-plus.net" + Mode = "fast" + ApiKey = "sk-xxx" [[SdConfigs]] Enabled = false + Model = "" ApiURL = "" ApiKey = "" -[XXLConfig] # xxl-job 配置,需要你部署 XXL-JOB 定时任务工具,用来定期清理未支付订单和清理过期 VIP,如果你没有启用支付服务,则该服务也无需启动 - Enabled = true # 是否启用 XXL JOB 服务 - ServerAddr = "http://xxl-job-admin:8080/xxl-job-admin" # xxl-job-admin 管理地址 - ExecutorIp = "chatgpt-plus-api" # 执行器 IP 地址 +[XXLConfig] # xxl-job 配置,需要你部署 XXL-JOB 定时任务工具,用来定期清理未支付订单和清理过期 VIP,如果你没有启用支付服务,则该服务也无需启动 + Enabled = true # 是否启用 XXL JOB 服务 + ServerAddr = "http://xxl-job-admin:8080/xxl-job-admin" # xxl-job-admin 管理地址 + ExecutorIp = "geekai-api" # 执行器 IP 地址 ExecutorPort = "9999" # 执行器服务端口 - AccessToken = "GeekMaster" # 执行器 API 通信 token - RegistryKey = "chatgpt-plus" # 任务注册 key + AccessToken = "GeekMaster" # 执行器 API 通信 token + RegistryKey = "chatgpt-plus" # 任务注册 key,需要与 xxl-job 管理后台配置一致,请不要随意改动 [AlipayConfig] - Enabled = false # 启用支付宝支付通道 - SandBox = false # 是否启用沙盒模式 - UserId = "2088721020750581" # 商户ID - AppId = "9021000131658023" # App Id - PrivateKey = "certs/alipay/privateKey.txt" # 应用私钥 - PublicKey = "certs/alipay/appPublicCert.crt" # 应用公钥证书 - AlipayPublicKey = "certs/alipay/alipayPublicCert.crt" # 支付宝公钥证书 - RootCert = "certs/alipay/alipayRootCert.crt" # 支付宝根证书 - NotifyURL = "https://ai.r9it.com/api/payment/alipay/notify" # 支付异步回调地址 + Enabled = false + SandBox = false + AppId = "9021000131658023" + UserId = "2088721020750581" + PrivateKey = "certs/alipay/privateKey.txt" + PublicKey = "certs/alipay/appPublicCert.crt" + AlipayPublicKey = "certs/alipay/alipayPublicCert.crt" + RootCert = "certs/alipay/alipayRootCert.crt" + NotifyURL = "https://ai.r9it.com/api/payment/alipay/notify" + ReturnURL = "" [HuPiPayConfig] Enabled = false @@ -109,19 +113,21 @@ WeChatBot = false AppSecret = "" ApiURL = "https://api.xunhupay.com" NotifyURL = "https://ai.r9it.com/api/payment/hupipay/notify" + ReturnURL = "" -[SmtpConfig] # 注意,阿里云服务器禁用了25号端口,请使用 465 端口,并开启 TLS 连接 +[SmtpConfig] # 注意,阿里云服务器禁用了25号端口,请使用 465 端口,并开启 TLS 连接 UseTls = false Host = "smtp.163.com" Port = 25 AppName = "极客学长" - From = "test@163.com" # 发件邮箱人地址 - Password = "" #邮箱 stmp 服务授权码 + From = "test@163.com" # 发件邮箱人地址 + Password = "" #邮箱 stmp 服务授权码 -[JPayConfig] # PayJs 支付配置 +[JPayConfig] Enabled = false - Name = "wechat" # 请不要改动 - AppId = "" # 商户 ID + Name = "wechat" # 请不要改动 + AppId = "" # 商户 ID PrivateKey = "" # 秘钥 ApiURL = "https://payjs.cn" - NotifyURL = "https://ai.r9it.com/api/payment/payjs/notify" # 异步回调地址,域名改成你自己的 \ No newline at end of file + NotifyURL = "https://ai.r9it.com/api/payment/payjs/notify" + ReturnURL = "" diff --git a/deploy/conf/nginx/conf.d/chatgpt-plus.conf b/deploy/conf/nginx/conf.d/geekai.conf similarity index 92% rename from deploy/conf/nginx/conf.d/chatgpt-plus.conf rename to deploy/conf/nginx/conf.d/geekai.conf index e4d85e3a..1b8e5aba 100644 --- a/deploy/conf/nginx/conf.d/chatgpt-plus.conf +++ b/deploy/conf/nginx/conf.d/geekai.conf @@ -35,12 +35,12 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; - proxy_pass http://chatgpt-plus-api:5678; + proxy_pass http://geekai-api:5678; } # 静态资源转发 location /static/ { - proxy_pass http://chatgpt-plus-api:5678; + proxy_pass http://geekai-api:5678; } } } diff --git a/deploy/conf/xxl-job/application.properties b/deploy/conf/xxl-job/application.properties index b25e3fe1..ff4126e3 100644 --- a/deploy/conf/xxl-job/application.properties +++ b/deploy/conf/xxl-job/application.properties @@ -23,7 +23,7 @@ mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml #mybatis.type-aliases-package=com.xxl.job.admin.core.model ### xxl-job, datasource -spring.datasource.url=jdbc:mysql://chatgpt-plus-mysql:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai +spring.datasource.url=jdbc:mysql://geekai-mysql:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=12345678 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver diff --git a/deploy/docker-compose.yaml b/deploy/docker-compose.yaml index 7b9a204b..860baffe 100644 --- a/deploy/docker-compose.yaml +++ b/deploy/docker-compose.yaml @@ -1,9 +1,9 @@ version: '3' services: # mysql - chatgpt-plus-mysql: + geekai-mysql: image: mysql:8.0.33 - container_name: chatgpt-plus-mysql + container_name: geekai-mysql command: --default-authentication-plugin=mysql_native_password restart: always environment: @@ -15,12 +15,12 @@ services: - ./data/mysql/data:/var/lib/mysql - ./logs/mysql:/var/log/mysql - ./data/mysql/init.d:/docker-entrypoint-initdb.d/ - - # redis - chatgpt-plus-redis: + + # redis + geekai-redis: image: redis:6.0.16 - restart: always - container_name: chatgpt-plus-redis + restart: always + container_name: geekai-redis command: redis-server --requirepass 12345678 volumes : - ./data/redis:/data @@ -52,14 +52,13 @@ services: # 后端 API 程序 - chatgpt-plus-api: - image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:v4.0.4-amd64 - container_name: chatgpt-plus-api + geekai-api: + image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.0.7-amd64 + container_name: geekai-api restart: always depends_on: - - chatgpt-plus-mysql - - chatgpt-plus-redis - - xxl-job-admin + - geekai-mysql + - geekai-redis environment: - DEBUG=false - LOG_LEVEL=info @@ -75,12 +74,12 @@ services: - ./data/leveldb:/var/www/app/data # 前端应用 - chatgpt-plus-web: - image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:v4.0.4-amd64 - container_name: chatgpt-plus-web + geekai-web: + image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.0.7-amd64 + container_name: geekai-web restart: always depends_on: - - chatgpt-plus-api + - geekai-api ports: - "8080:8080" volumes: diff --git a/web/package.json b/web/package.json index dcd7ab6a..a38dc236 100644 --- a/web/package.json +++ b/web/package.json @@ -1,5 +1,5 @@ { - "name": "chatgpt-plus-web", + "name": "geekai-web", "version": "0.1.0", "private": true, "scripts": { From a6de958daa95eee0b5afdebe66ecb1f8743b2142 Mon Sep 17 00:00:00 2001 From: RockYang Date: Tue, 21 May 2024 11:03:11 +0800 Subject: [PATCH 02/20] update docker image url --- build/build.sh | 12 ++++++------ deploy/.gitignore | 3 ++- deploy/conf/config.toml | 4 ++-- deploy/docker-compose.yaml | 8 ++++---- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/build/build.sh b/build/build.sh index 6505f9f3..f73d8c01 100755 --- a/build/build.sh +++ b/build/build.sh @@ -14,15 +14,15 @@ npm run build cd ../build # remove docker image if exists -docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:$version-$arch +docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-plus-api:$version-$arch # build docker image for Geek-AI API -docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:$version-$arch -f dockerfile-api-go ../ +docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-plus-api:$version-$arch -f dockerfile-api-go ../ # build docker image for Geek-AI-web -docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:$version-$arch -docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:$version-$arch -f dockerfile-vue ../ +docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-plus-web:$version-$arch +docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-plus-web:$version-$arch -f dockerfile-vue ../ if [ "$3" = "push" ];then - docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:$version-$arch - docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:$version-$arch + docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-plus-api:$version-$arch + docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-plus-web:$version-$arch fi diff --git a/deploy/.gitignore b/deploy/.gitignore index 20efd31f..d8b2bbf6 100644 --- a/deploy/.gitignore +++ b/deploy/.gitignore @@ -1,4 +1,5 @@ data/mysql/data +data/leveldb logs static/* -redis/redis \ No newline at end of file +redis/redis diff --git a/deploy/conf/config.toml b/deploy/conf/config.toml index b76bc26f..f005f7e2 100644 --- a/deploy/conf/config.toml +++ b/deploy/conf/config.toml @@ -70,7 +70,7 @@ WeChatBot = false [[MjProxyConfigs]] Enabled = false - ApiURL = "http://midjourney-proxy:8082" + ApiURL = "http://geekai-midjourney-proxy:8082" Mode = "" ApiKey = "sk-geekmaster" @@ -88,7 +88,7 @@ WeChatBot = false [XXLConfig] # xxl-job 配置,需要你部署 XXL-JOB 定时任务工具,用来定期清理未支付订单和清理过期 VIP,如果你没有启用支付服务,则该服务也无需启动 Enabled = true # 是否启用 XXL JOB 服务 - ServerAddr = "http://xxl-job-admin:8080/xxl-job-admin" # xxl-job-admin 管理地址 + ServerAddr = "http://geekai-xxl-job-admin:8080/xxl-job-admin" # xxl-job-admin 管理地址 ExecutorIp = "geekai-api" # 执行器 IP 地址 ExecutorPort = "9999" # 执行器服务端口 AccessToken = "GeekMaster" # 执行器 API 通信 token diff --git a/deploy/docker-compose.yaml b/deploy/docker-compose.yaml index 860baffe..6fe64a2c 100644 --- a/deploy/docker-compose.yaml +++ b/deploy/docker-compose.yaml @@ -28,7 +28,7 @@ services: - "6380:6379" xxl-job-admin: - container_name: xxl-job-admin + container_name: geekai-xxl-job-admin image: xuxueli/xxl-job-admin:2.4.0 restart: always ports: @@ -42,7 +42,7 @@ services: midjourney-proxy: image: novicezk/midjourney-proxy:2.6.0 - container_name: midjourney-proxy + container_name: geekai-midjourney-proxy restart: always ports: - "8082:8080" @@ -53,7 +53,7 @@ services: # 后端 API 程序 geekai-api: - image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.0.7-amd64 + image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-plus-api:v4.0.7-amd64 container_name: geekai-api restart: always depends_on: @@ -75,7 +75,7 @@ services: # 前端应用 geekai-web: - image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.0.7-amd64 + image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-plus-web:v4.0.7-amd64 container_name: geekai-web restart: always depends_on: From 5253d657b68a987ff998d3187eb4816249f098a6 Mon Sep 17 00:00:00 2001 From: RockYang Date: Tue, 21 May 2024 11:55:38 +0800 Subject: [PATCH 03/20] add logs for updating database failed --- CHANGELOG.md | 2 ++ api/handler/admin/api_key_handler.go | 5 ++++- api/handler/admin/chat_handler.go | 1 + api/handler/admin/chat_model_handler.go | 4 ++++ api/handler/admin/chat_role_handler.go | 4 ++++ api/handler/admin/function_handler.go | 2 ++ api/handler/admin/menu_handler.go | 4 ++++ api/handler/admin/order_handler.go | 1 + api/handler/admin/product_handler.go | 4 ++++ api/handler/admin/reward_handler.go | 1 + api/handler/admin/user_handler.go | 2 ++ api/handler/chat_role_handler.go | 2 +- api/handler/dalle_handler.go | 1 + api/handler/mj_handler.go | 5 +++-- api/handler/reward_handler.go | 4 +++- api/handler/sd_handler.go | 3 ++- api/handler/user_handler.go | 3 ++- 17 files changed, 41 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d1794da..cb7f91bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # 更新日志 +## v4.0.8 +* 功能优化:当数据库更新失败的时候记录错误日志 ## v4.0.7 diff --git a/api/handler/admin/api_key_handler.go b/api/handler/admin/api_key_handler.go index 87eab556..58ea37f3 100644 --- a/api/handler/admin/api_key_handler.go +++ b/api/handler/admin/api_key_handler.go @@ -57,6 +57,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) { apiKey.Name = data.Name res := h.DB.Save(&apiKey) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -83,7 +84,7 @@ func (h *ApiKeyHandler) List(c *gin.Context) { if t != "" { session = session.Where("type", t) } - + var items []model.ApiKey var keys = make([]vo.ApiKey, 0) res := session.Find(&items) @@ -118,6 +119,7 @@ func (h *ApiKeyHandler) Set(c *gin.Context) { res := h.DB.Model(&model.ApiKey{}).Where("id = ?", data.Id).Update(data.Filed, data.Value) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -133,6 +135,7 @@ func (h *ApiKeyHandler) Remove(c *gin.Context) { res := h.DB.Where("id", id).Delete(&model.ApiKey{}) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/admin/chat_handler.go b/api/handler/admin/chat_handler.go index af76e76e..f51cf85f 100644 --- a/api/handler/admin/chat_handler.go +++ b/api/handler/admin/chat_handler.go @@ -261,6 +261,7 @@ func (h *ChatHandler) RemoveMessage(c *gin.Context) { id := h.GetInt(c, "id", 0) tx := h.DB.Unscoped().Where("id = ?", id).Delete(&model.ChatMessage{}) if tx.Error != nil { + logger.Error("error with update database:", tx.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/admin/chat_model_handler.go b/api/handler/admin/chat_model_handler.go index f30991a5..dd594b23 100644 --- a/api/handler/admin/chat_model_handler.go +++ b/api/handler/admin/chat_model_handler.go @@ -69,6 +69,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) { res = h.DB.Create(&item) } if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -140,6 +141,7 @@ func (h *ChatModelHandler) Set(c *gin.Context) { res := h.DB.Model(&model.ChatModel{}).Where("id = ?", data.Id).Update(data.Filed, data.Value) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -160,6 +162,7 @@ func (h *ChatModelHandler) Sort(c *gin.Context) { for index, id := range data.Ids { res := h.DB.Model(&model.ChatModel{}).Where("id = ?", id).Update("sort_num", data.Sorts[index]) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -177,6 +180,7 @@ func (h *ChatModelHandler) Remove(c *gin.Context) { res := h.DB.Where("id = ?", id).Delete(&model.ChatModel{}) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/admin/chat_role_handler.go b/api/handler/admin/chat_role_handler.go index 791104a7..3e69ae33 100644 --- a/api/handler/admin/chat_role_handler.go +++ b/api/handler/admin/chat_role_handler.go @@ -48,6 +48,7 @@ func (h *ChatRoleHandler) Save(c *gin.Context) { } res := h.DB.Save(&role) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -115,6 +116,7 @@ func (h *ChatRoleHandler) Sort(c *gin.Context) { for index, id := range data.Ids { res := h.DB.Model(&model.ChatRole{}).Where("id = ?", id).Update("sort_num", data.Sorts[index]) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -137,6 +139,7 @@ func (h *ChatRoleHandler) Set(c *gin.Context) { res := h.DB.Model(&model.ChatRole{}).Where("id = ?", data.Id).Update(data.Filed, data.Value) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -152,6 +155,7 @@ func (h *ChatRoleHandler) Remove(c *gin.Context) { } res := h.DB.Where("id", id).Delete(&model.ChatRole{}) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "删除失败!") return } diff --git a/api/handler/admin/function_handler.go b/api/handler/admin/function_handler.go index 474f32b7..c9e8005f 100644 --- a/api/handler/admin/function_handler.go +++ b/api/handler/admin/function_handler.go @@ -71,6 +71,7 @@ func (h *FunctionHandler) Set(c *gin.Context) { res := h.DB.Model(&model.Function{}).Where("id = ?", data.Id).Update(data.Filed, data.Value) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -103,6 +104,7 @@ func (h *FunctionHandler) Remove(c *gin.Context) { if id > 0 { res := h.DB.Delete(&model.Function{Id: uint(id)}) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/admin/menu_handler.go b/api/handler/admin/menu_handler.go index ef1991e2..001c601d 100644 --- a/api/handler/admin/menu_handler.go +++ b/api/handler/admin/menu_handler.go @@ -50,6 +50,7 @@ func (h *MenuHandler) Save(c *gin.Context) { Enabled: data.Enabled, }) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -86,6 +87,7 @@ func (h *MenuHandler) Enable(c *gin.Context) { res := h.DB.Model(&model.Menu{}).Where("id", data.Id).UpdateColumn("enabled", data.Enabled) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -106,6 +108,7 @@ func (h *MenuHandler) Sort(c *gin.Context) { for index, id := range data.Ids { res := h.DB.Model(&model.Menu{}).Where("id", id).Update("sort_num", data.Sorts[index]) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -120,6 +123,7 @@ func (h *MenuHandler) Remove(c *gin.Context) { if id > 0 { res := h.DB.Where("id", id).Delete(&model.Menu{}) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/admin/order_handler.go b/api/handler/admin/order_handler.go index a64fa281..ab6752b6 100644 --- a/api/handler/admin/order_handler.go +++ b/api/handler/admin/order_handler.go @@ -94,6 +94,7 @@ func (h *OrderHandler) Remove(c *gin.Context) { res = h.DB.Unscoped().Where("id = ?", id).Delete(&model.Order{}) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/admin/product_handler.go b/api/handler/admin/product_handler.go index 92f0d228..e2b66c7d 100644 --- a/api/handler/admin/product_handler.go +++ b/api/handler/admin/product_handler.go @@ -57,6 +57,7 @@ func (h *ProductHandler) Save(c *gin.Context) { } res := h.DB.Save(&item) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -107,6 +108,7 @@ func (h *ProductHandler) Enable(c *gin.Context) { res := h.DB.Model(&model.Product{}).Where("id", data.Id).UpdateColumn("enabled", data.Enabled) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -127,6 +129,7 @@ func (h *ProductHandler) Sort(c *gin.Context) { for index, id := range data.Ids { res := h.DB.Model(&model.Product{}).Where("id", id).Update("sort_num", data.Sorts[index]) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -141,6 +144,7 @@ func (h *ProductHandler) Remove(c *gin.Context) { if id > 0 { res := h.DB.Where("id", id).Delete(&model.Product{}) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/admin/reward_handler.go b/api/handler/admin/reward_handler.go index 50daa21d..2dc2e281 100644 --- a/api/handler/admin/reward_handler.go +++ b/api/handler/admin/reward_handler.go @@ -72,6 +72,7 @@ func (h *RewardHandler) Remove(c *gin.Context) { if data.Id > 0 { res := h.DB.Where("id = ?", data.Id).Delete(&model.Reward{}) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/admin/user_handler.go b/api/handler/admin/user_handler.go index 965c06f3..bd9b926b 100644 --- a/api/handler/admin/user_handler.go +++ b/api/handler/admin/user_handler.go @@ -111,6 +111,7 @@ func (h *UserHandler) Save(c *gin.Context) { res = h.DB.Select("username", "status", "vip", "power", "chat_roles_json", "chat_models_json", "expired_time").Updates(&user) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -156,6 +157,7 @@ func (h *UserHandler) Save(c *gin.Context) { } if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败") return } diff --git a/api/handler/chat_role_handler.go b/api/handler/chat_role_handler.go index f246cb37..707e7f4f 100644 --- a/api/handler/chat_role_handler.go +++ b/api/handler/chat_role_handler.go @@ -96,7 +96,7 @@ func (h *ChatRoleHandler) UpdateRole(c *gin.Context) { res := h.DB.Model(&model.User{}).Where("id = ?", user.Id).UpdateColumn("chat_roles_json", utils.JsonEncode(data.Keys)) if res.Error != nil { - logger.Error("添加应用失败:", err) + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/dalle_handler.go b/api/handler/dalle_handler.go index a62fd712..07cd032c 100644 --- a/api/handler/dalle_handler.go +++ b/api/handler/dalle_handler.go @@ -253,6 +253,7 @@ func (h *DallJobHandler) Publish(c *gin.Context) { res := h.DB.Model(&model.DallJob{Id: data.Id}).UpdateColumn("publish", true) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败") return } diff --git a/api/handler/mj_handler.go b/api/handler/mj_handler.go index 2f210801..822df42a 100644 --- a/api/handler/mj_handler.go +++ b/api/handler/mj_handler.go @@ -8,6 +8,8 @@ package handler // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( + "encoding/base64" + "fmt" "geekai/core" "geekai/core/types" "geekai/service" @@ -17,8 +19,6 @@ import ( "geekai/store/vo" "geekai/utils" "geekai/utils/resp" - "encoding/base64" - "fmt" "net/http" "strings" "time" @@ -511,6 +511,7 @@ func (h *MidJourneyHandler) Publish(c *gin.Context) { res := h.DB.Model(&model.MidJourneyJob{Id: data.Id}).UpdateColumn("publish", data.Action) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败") return } diff --git a/api/handler/reward_handler.go b/api/handler/reward_handler.go index ec019e97..44045f58 100644 --- a/api/handler/reward_handler.go +++ b/api/handler/reward_handler.go @@ -8,13 +8,13 @@ package handler // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( + "fmt" "geekai/core" "geekai/core/types" "geekai/store/model" "geekai/store/vo" "geekai/utils" "geekai/utils/resp" - "fmt" "github.com/gin-gonic/gin" "gorm.io/gorm" "math" @@ -73,6 +73,7 @@ func (h *RewardHandler) Verify(c *gin.Context) { res = tx.Model(&user).UpdateColumn("power", gorm.Expr("power + ?", exchange.Power)) if res.Error != nil { tx.Rollback() + logger.Error("添加应用失败:", res.Error) resp.ERROR(c, "更新数据库失败!") return } @@ -84,6 +85,7 @@ func (h *RewardHandler) Verify(c *gin.Context) { res = tx.Updates(&item) if res.Error != nil { tx.Rollback() + logger.Error("添加应用失败:", res.Error) resp.ERROR(c, "更新数据库失败!") return } diff --git a/api/handler/sd_handler.go b/api/handler/sd_handler.go index c4a5c3ef..e30e837d 100644 --- a/api/handler/sd_handler.go +++ b/api/handler/sd_handler.go @@ -8,6 +8,7 @@ package handler // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ import ( + "fmt" "geekai/core" "geekai/core/types" "geekai/service" @@ -18,7 +19,6 @@ import ( "geekai/store/vo" "geekai/utils" "geekai/utils/resp" - "fmt" "net/http" "time" @@ -325,6 +325,7 @@ func (h *SdJobHandler) Publish(c *gin.Context) { res := h.DB.Model(&model.SdJob{Id: data.Id}).UpdateColumn("publish", true) if res.Error != nil { + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败") return } diff --git a/api/handler/user_handler.go b/api/handler/user_handler.go index 5df76df1..263a7139 100644 --- a/api/handler/user_handler.go +++ b/api/handler/user_handler.go @@ -349,7 +349,7 @@ func (h *UserHandler) UpdatePass(c *gin.Context) { newPass := utils.GenPassword(data.Password, user.Salt) res := h.DB.Model(&user).UpdateColumn("password", newPass) if res.Error != nil { - logger.Error("更新数据库失败: ", res.Error) + logger.Error("error with update database:", res.Error) resp.ERROR(c, "更新数据库失败") return } @@ -430,6 +430,7 @@ func (h *UserHandler) BindUsername(c *gin.Context) { res = h.DB.Model(&user).UpdateColumn("username", data.Username) if res.Error != nil { + logger.Error(res.Error) resp.ERROR(c, "更新数据库失败") return } From 9273df4af2e5ef34c386b626758d5b999362bb2c Mon Sep 17 00:00:00 2001 From: RockYang Date: Tue, 21 May 2024 17:36:47 +0800 Subject: [PATCH 04/20] auto resize the input element rows, when use inputed more than one line --- CHANGELOG.md | 1 + web/src/assets/css/chat-plus.styl | 127 ++++++++++++++++++------------ web/src/views/ChatPlus.vue | 79 +++++++++++-------- 3 files changed, 125 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb7f91bc..69813b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 更新日志 ## v4.0.8 * 功能优化:当数据库更新失败的时候记录错误日志 +* 功能优化:聊天输入框会随着输入内容的增多自动调整高度 ## v4.0.7 diff --git a/web/src/assets/css/chat-plus.styl b/web/src/assets/css/chat-plus.styl index 32ba0808..8c4cdc6e 100644 --- a/web/src/assets/css/chat-plus.styl +++ b/web/src/assets/css/chat-plus.styl @@ -129,7 +129,7 @@ $borderColor = #4676d0; --el-main-padding: 0; margin: 0; - .chat-box { + .chat-container { min-width: 0; flex: 1; background-color: var(--el-bg-color) @@ -138,6 +138,7 @@ $borderColor = #4676d0; #container { overflow: hidden; width: 100%; + position relative ::-webkit-scrollbar { width: 0; @@ -165,68 +166,94 @@ $borderColor = #4676d0; } .input-box { - background-color: #ffffff - display: flex; - justify-content: center; - align-items: center; - box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1); - padding 0 15px; + position absolute + bottom 0 + width 100% - .tool-item { - margin-right 15px - border-radius: 6px; - color: #19c37d; + .input-box-inner { display flex - justify-content center - justify-items center - padding 6px - cursor pointer - background #F2F2F2 + background-color: #ffffff + justify-content: center; + align-items: center; + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1); + padding 0 15px; - &:hover { - background #D5FAD3 - } + .tool-item { + margin-right 15px + border-radius: 6px; + color: #19c37d; + display flex + justify-content center + justify-items center + padding 6px + cursor pointer + background #F2F2F2 - .iconfont { - font-size: 24px; - } - } + &:hover { + background #D5FAD3 + } - .input-container { - width 100% - margin: 0; - border: none; - padding: 10px 0; - display flex - justify-content center - position relative - - .el-textarea { - .el-textarea__inner::-webkit-scrollbar { - width: 0; - height: 0; + .iconfont { + font-size: 24px; } } - .select-file { - position absolute; - right 48px; - top 20px; - } + .input-body { + width 100% + margin: 0; + border: none; + padding: 10px 0; + display flex + justify-content center + position relative - .send-btn { - position absolute; - right 12px; - top 20px; + .hide-div { + white-space: pre-wrap; /* 保持文本换行 */ + visibility: hidden; /* 隐藏 div */ + position: absolute; /* 脱离文档流 */ + line-height: 24px + font-size 14px + word-wrap: break-word; /* 允许单词换行 */ + overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */ + } + + .input-border { + display flex + width 100% + overflow hidden + border: 2px solid #21AA93 + border-radius 10px + padding 10px + + + .prompt-input::-webkit-scrollbar { + width: 0; + height: 0; + } + + .prompt-input { + width 100% + line-height: 24px + border none + font-size 14px + background none + resize: none + } + + .send-btn { + width 32px + margin-left 10px + .el-button { + padding 8px 5px; + border-radius 6px; + font-size 20px; + } + } - .el-button { - padding 8px 5px; - border-radius 6px; - font-size 20px; } } - } + } } diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index f59e33bc..e3b352c7 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -65,9 +65,9 @@ -
+
-
+
@@ -84,8 +84,8 @@
- -
+
+
-
- - - - - - - - - - - +
+
{{prompt}}
+
+ + + + + + + + + + + +
+
- +
@@ -237,11 +239,13 @@ const roleId = ref(0) const newChatItem = ref(null); const isLogin = ref(false) const showHello = ref(true) -const textInput = ref(null) +const inputRef = ref(null) +const textHeightRef = ref(null) const showNotice = ref(false) const notice = ref("") const noticeKey = ref("SYSTEM_NOTICE") const store = useSharedStore(); +const row = ref(1) if (isMobile()) { router.replace("/mobile/chat") @@ -705,8 +709,19 @@ const enableInput = () => { showStopGenerate.value = false; } -// 登录输入框输入事件处理 -const inputKeyDown = function (e) { +const onInput = (e) => { + // 根据输入的内容自动计算输入框的行数 + const lineHeight = parseFloat(window.getComputedStyle(inputRef.value).lineHeight) + textHeightRef.value.style.width = inputRef.value.clientWidth + 'px'; // 设定宽度和 textarea 相同 + const lines = Math.floor(textHeightRef.value.clientHeight / lineHeight); + inputRef.value.scrollTo(0, inputRef.value.scrollHeight) + if (prompt.value.length === 0) { + row.value = 1 + } else if (row.value <= 7) { + row.value = lines + } + + // 输入回车自动提交 if (e.keyCode === 13) { if (e.ctrlKey) { // Ctrl + Enter 换行 prompt.value += "\n"; @@ -720,7 +735,7 @@ const inputKeyDown = function (e) { // 自动填充 prompt const autofillPrompt = (text) => { prompt.value = text - textInput.value.focus() + inputRef.value.focus() // sendMessage() } // 发送消息 From 6c300bb0183b56e94afe145cd14f24562c4895e7 Mon Sep 17 00:00:00 2001 From: RockYang Date: Tue, 21 May 2024 17:54:03 +0800 Subject: [PATCH 05/20] fixed bug for mobile chat page change chat model not work --- CHANGELOG.md | 1 + web/src/assets/css/chat-app.styl | 3 ++- web/src/views/mobile/ChatSession.vue | 7 ++++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69813b2d..3fae673a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## v4.0.8 * 功能优化:当数据库更新失败的时候记录错误日志 * 功能优化:聊天输入框会随着输入内容的增多自动调整高度 +* Bug修复:修复移动端聊天页面模型切换不生效的Bug ## v4.0.7 diff --git a/web/src/assets/css/chat-app.styl b/web/src/assets/css/chat-app.styl index 55deed14..c7692bd4 100644 --- a/web/src/assets/css/chat-app.styl +++ b/web/src/assets/css/chat-app.styl @@ -13,8 +13,9 @@ .item { display flex flex-flow row - border 1px solid #3c3c3c + border 1px solid rgb(80,80,80) padding 10px + background rgba(60,60,60 0.5) .image { width 80px diff --git a/web/src/views/mobile/ChatSession.vue b/web/src/views/mobile/ChatSession.vue index ca6c8368..a8c0fde8 100644 --- a/web/src/views/mobile/ChatSession.vue +++ b/web/src/views/mobile/ChatSession.vue @@ -182,7 +182,7 @@ httpGet('/api/model/list').then(res => { models.value[i].mValue = models.value[i].value models.value[i].value = models.value[i].id } - modelValue.value = getModelValue(modelId.value) + modelValue.value = getModelName(modelId.value) // 加载角色列表 httpGet(`/api/role/list`).then((res) => { roles.value = res.data; @@ -236,6 +236,7 @@ const newChat = (item) => { const options = item.selectedOptions roleId.value = options[0].value modelId.value = options[1].value + modelValue.value = getModelName(modelId.value) chatId.value = "" chatData.value = [] role.value = getRoleById(roleId.value) @@ -553,10 +554,10 @@ const getRoleById = function (rid) { return null; } -const getModelValue = (model_id) => { +const getModelName = (model_id) => { for (let i = 0; i < models.value.length; i++) { if (models.value[i].id === model_id) { - return models.value[i].mValue + return models.value[i].text } } return "" From 627396dbf7f326bede61a728e0df6124e7658a7b Mon Sep 17 00:00:00 2001 From: RockYang Date: Wed, 22 May 2024 11:47:04 +0800 Subject: [PATCH 06/20] check if the api url in whitelist for mj plus client --- api/service/dalle/service.go | 2 +- api/service/mj/plus_client.go | 41 +++++++++++++++++++++++++++++------ api/service/mj/pool.go | 7 +----- api/service/mj/service.go | 8 ++++++- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/api/service/dalle/service.go b/api/service/dalle/service.go index bab51923..3dfbe6c0 100644 --- a/api/service/dalle/service.go +++ b/api/service/dalle/service.go @@ -146,7 +146,7 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) { Model: "dall-e-3", Prompt: prompt, N: 1, - Size: "1024x1024", + Size: task.Size, Style: task.Style, Quality: task.Quality, }). diff --git a/api/service/mj/plus_client.go b/api/service/mj/plus_client.go index 7f85fd61..beb8943c 100644 --- a/api/service/mj/plus_client.go +++ b/api/service/mj/plus_client.go @@ -12,6 +12,7 @@ import ( "errors" "fmt" "geekai/core/types" + "geekai/service" "geekai/utils" "github.com/imroc/req/v3" "io" @@ -22,20 +23,30 @@ import ( // PlusClient MidJourney Plus ProxyClient type PlusClient struct { - Config types.MjPlusConfig - apiURL string - client *req.Client + Config types.MjPlusConfig + apiURL string + client *req.Client + licenseService *service.LicenseService } -func NewPlusClient(config types.MjPlusConfig) *PlusClient { +func NewPlusClient(config types.MjPlusConfig, licenseService *service.LicenseService) *PlusClient { return &PlusClient{ - Config: config, - apiURL: config.ApiURL, - client: req.C().SetTimeout(time.Minute).SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"), + Config: config, + apiURL: config.ApiURL, + client: req.C().SetTimeout(time.Minute).SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"), + licenseService: licenseService, } } +func (c *PlusClient) preCheck() error { + return c.licenseService.IsValidApiURL(c.Config.ApiURL) +} + func (c *PlusClient) Imagine(task types.MjTask) (ImageRes, error) { + if err := c.preCheck(); err != nil { + return ImageRes{}, err + } + apiURL := fmt.Sprintf("%s/mj-%s/mj/submit/imagine", c.apiURL, c.Config.Mode) prompt := fmt.Sprintf("%s %s", task.Prompt, task.Params) if task.NegPrompt != "" { @@ -79,6 +90,10 @@ func (c *PlusClient) Imagine(task types.MjTask) (ImageRes, error) { // Blend 融图 func (c *PlusClient) Blend(task types.MjTask) (ImageRes, error) { + if err := c.preCheck(); err != nil { + return ImageRes{}, err + } + apiURL := fmt.Sprintf("%s/mj-%s/mj/submit/blend", c.apiURL, c.Config.Mode) logger.Info("API URL: ", apiURL) body := ImageReq{ @@ -118,6 +133,10 @@ func (c *PlusClient) Blend(task types.MjTask) (ImageRes, error) { // SwapFace 换脸 func (c *PlusClient) SwapFace(task types.MjTask) (ImageRes, error) { + if err := c.preCheck(); err != nil { + return ImageRes{}, err + } + apiURL := fmt.Sprintf("%s/mj-%s/mj/insight-face/swap", c.apiURL, c.Config.Mode) // 生成图片 Base64 编码 if len(task.ImgArr) != 2 { @@ -167,6 +186,10 @@ func (c *PlusClient) SwapFace(task types.MjTask) (ImageRes, error) { // Upscale 放大指定的图片 func (c *PlusClient) Upscale(task types.MjTask) (ImageRes, error) { + if err := c.preCheck(); err != nil { + return ImageRes{}, err + } + body := map[string]string{ "customId": fmt.Sprintf("MJ::JOB::upsample::%d::%s", task.Index, task.MessageHash), "taskId": task.MessageId, @@ -194,6 +217,10 @@ func (c *PlusClient) Upscale(task types.MjTask) (ImageRes, error) { // Variation 以指定的图片的视角进行变换再创作,注意需要在对应的频道中关闭 Remix 变换,否则 Variation 指令将不会生效 func (c *PlusClient) Variation(task types.MjTask) (ImageRes, error) { + if err := c.preCheck(); err != nil { + return ImageRes{}, err + } + body := map[string]string{ "customId": fmt.Sprintf("MJ::JOB::variation::%d::%s", task.Index, task.MessageHash), "taskId": task.MessageId, diff --git a/api/service/mj/pool.go b/api/service/mj/pool.go index b28a3d2d..ddddd280 100644 --- a/api/service/mj/pool.go +++ b/api/service/mj/pool.go @@ -62,13 +62,8 @@ func (p *ServicePool) InitServices(plusConfigs []types.MjPlusConfig, proxyConfig if config.Enabled == false { continue } - err := p.licenseService.IsValidApiURL(config.ApiURL) - if err != nil { - logger.Errorf("创建 MJ-PLUS 服务失败:%v", err) - continue - } - cli := NewPlusClient(config) + cli := NewPlusClient(config, p.licenseService) name := fmt.Sprintf("mj-plus-service-%d", k) plusService := NewService(name, p.taskQueue, p.notifyQueue, p.db, cli) go func() { diff --git a/api/service/mj/service.go b/api/service/mj/service.go index e72d7476..baccd281 100644 --- a/api/service/mj/service.go +++ b/api/service/mj/service.go @@ -108,7 +108,13 @@ func (s *Service) Run() { } if err != nil || (res.Code != 1 && res.Code != 22) { - errMsg := fmt.Sprintf("%v,%s", err, res.Description) + var errMsg string + if err != nil { + errMsg = err.Error() + } else { + errMsg = fmt.Sprintf("%v,%s", err, res.Description) + } + logger.Error("绘画任务执行失败:", errMsg) job.Progress = -1 job.ErrMsg = errMsg From 962de0183c8d3c3f0d348905ea361cea62ef072d Mon Sep 17 00:00:00 2001 From: RockYang Date: Wed, 22 May 2024 15:32:44 +0800 Subject: [PATCH 07/20] extract code for saving chat history --- api/core/types/chat.go | 18 +-- api/core/types/config.go | 44 ++++++- api/handler/admin/config_handler.go | 15 ++- api/handler/admin/types.go | 12 ++ api/handler/chatimpl/azure_handler.go | 102 +------------- api/handler/chatimpl/baidu_handler.go | 96 +------------- api/handler/chatimpl/chat_handler.go | 139 ++++++++++++++++---- api/handler/chatimpl/chatglm_handler.go | 101 +------------- api/handler/chatimpl/openai_handler.go | 126 +----------------- api/handler/chatimpl/qwen_handler.go | 98 +------------- api/handler/chatimpl/xunfei_handler.go | 83 +----------- api/main.go | 2 +- web/src/components/admin/AdminSidebar.vue | 6 +- web/src/router.js | 10 +- web/src/views/admin/AIDrawing.vue | 4 +- web/src/views/admin/ApiKey.vue | 76 ++++------- web/src/views/admin/{Roles.vue => Apps.vue} | 30 ++--- web/src/views/admin/ChatModel.vue | 16 +-- 18 files changed, 261 insertions(+), 717 deletions(-) create mode 100644 api/handler/admin/types.go rename web/src/views/admin/{Roles.vue => Apps.vue} (92%) diff --git a/api/core/types/chat.go b/api/core/types/chat.go index 4e90afaa..3827b860 100644 --- a/api/core/types/chat.go +++ b/api/core/types/chat.go @@ -61,15 +61,15 @@ type ChatSession struct { } type ChatModel struct { - Id uint `json:"id"` - Platform Platform `json:"platform"` - Name string `json:"name"` - Value string `json:"value"` - Power int `json:"power"` - MaxTokens int `json:"max_tokens"` // 最大响应长度 - MaxContext int `json:"max_context"` // 最大上下文长度 - Temperature float32 `json:"temperature"` // 模型温度 - KeyId int `json:"key_id"` // 绑定 API KEY + Id uint `json:"id"` + Platform string `json:"platform"` + Name string `json:"name"` + Value string `json:"value"` + Power int `json:"power"` + MaxTokens int `json:"max_tokens"` // 最大响应长度 + MaxContext int `json:"max_context"` // 最大上下文长度 + Temperature float32 `json:"temperature"` // 模型温度 + KeyId int `json:"key_id"` // 绑定 API KEY } type ApiError struct { diff --git a/api/core/types/config.go b/api/core/types/config.go index b0ec7ebe..8536cd51 100644 --- a/api/core/types/config.go +++ b/api/core/types/config.go @@ -137,14 +137,44 @@ func (c RedisConfig) Url() string { return fmt.Sprintf("%s:%d", c.Host, c.Port) } -type Platform string +type Platform struct { + Name string `json:"name"` + Value string `json:"value"` + ChatURL string `json:"chat_url"` + ImgURL string `json:"img_url"` +} -const OpenAI = Platform("OpenAI") -const Azure = Platform("Azure") -const ChatGLM = Platform("ChatGLM") -const Baidu = Platform("Baidu") -const XunFei = Platform("XunFei") -const QWen = Platform("QWen") +var OpenAI = Platform{ + Name: "OpenAI - GPT", + Value: "OpenAI", + ChatURL: "https://api.chat-plus.net/v1/chat/completions", + ImgURL: "https://api.chat-plus.net/v1/images/generations", +} +var Azure = Platform{ + Name: "微软 - Azure", + Value: "Azure", + ChatURL: "https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15", +} +var ChatGLM = Platform{ + Name: "智谱 - ChatGLM", + Value: "ChatGLM", + ChatURL: "https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke", +} +var Baidu = Platform{ + Name: "百度 - 文心大模型", + Value: "Baidu", + ChatURL: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}", +} +var XunFei = Platform{ + Name: "讯飞 - 星火大模型", + Value: "XunFei", + ChatURL: "wss://spark-api.xf-yun.com/{version}/chat", +} +var QWen = Platform{ + Name: "阿里 - 通义千问", + Value: "QWen", + ChatURL: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation", +} type SystemConfig struct { Title string `json:"title,omitempty"` diff --git a/api/handler/admin/config_handler.go b/api/handler/admin/config_handler.go index 6cf571e2..584b026b 100644 --- a/api/handler/admin/config_handler.go +++ b/api/handler/admin/config_handler.go @@ -28,8 +28,8 @@ type ConfigHandler struct { handler.BaseHandler levelDB *store.LevelDB licenseService *service.LicenseService - mjServicePool *mj.ServicePool - sdServicePool *sd.ServicePool + mjServicePool *mj.ServicePool + sdServicePool *sd.ServicePool } func NewConfigHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB, licenseService *service.LicenseService, mjPool *mj.ServicePool, sdPool *sd.ServicePool) *ConfigHandler { @@ -140,12 +140,13 @@ func (h *ConfigHandler) GetLicense(c *gin.Context) { resp.SUCCESS(c, license) } -// GetDrawingConfig 获取AI绘画配置 -func (h *ConfigHandler) GetDrawingConfig(c *gin.Context) { +// GetAppConfig 获取内置配置 +func (h *ConfigHandler) GetAppConfig(c *gin.Context) { resp.SUCCESS(c, gin.H{ - "mj_plus": h.App.Config.MjPlusConfigs, - "mj_proxy": h.App.Config.MjProxyConfigs, - "sd": h.App.Config.SdConfigs, + "mj_plus": h.App.Config.MjPlusConfigs, + "mj_proxy": h.App.Config.MjProxyConfigs, + "sd": h.App.Config.SdConfigs, + "platforms": Platforms, }) } diff --git a/api/handler/admin/types.go b/api/handler/admin/types.go new file mode 100644 index 00000000..c06139ba --- /dev/null +++ b/api/handler/admin/types.go @@ -0,0 +1,12 @@ +package admin + +import "geekai/core/types" + +var Platforms = []types.Platform{ + types.OpenAI, + types.QWen, + types.XunFei, + types.ChatGLM, + types.Baidu, + types.Azure, +} diff --git a/api/handler/chatimpl/azure_handler.go b/api/handler/chatimpl/azure_handler.go index ac5ad7ff..bd28d720 100644 --- a/api/handler/chatimpl/azure_handler.go +++ b/api/handler/chatimpl/azure_handler.go @@ -17,11 +17,9 @@ import ( "geekai/store/model" "geekai/store/vo" "geekai/utils" - "html/template" "io" "strings" "time" - "unicode/utf8" ) // 微软 Azure 模型消息发送实现 @@ -101,104 +99,12 @@ func (h *ChatHandler) sendAzureMessage( // 消息发送成功 if len(contents) > 0 { - - if message.Role == "" { - message.Role = "assistant" - } - message.Content = strings.Join(contents, "") - useMsg := types.Message{Role: "user", Content: prompt} - - // 更新上下文消息,如果是调用函数则不需要更新上下文 - if h.App.SysConfig.EnableContext { - chatCtx = append(chatCtx, useMsg) // 提问消息 - chatCtx = append(chatCtx, message) // 回复消息 - h.App.ChatContexts.Put(session.ChatId, chatCtx) - } - - // 追加聊天记录 - // for prompt - promptToken, err := utils.CalcTokens(prompt, req.Model) - if err != nil { - logger.Error(err) - } - historyUserMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.PromptMsg, - Icon: userVo.Avatar, - Content: template.HTMLEscapeString(prompt), - Tokens: promptToken, - UseContext: true, - Model: req.Model, - } - historyUserMsg.CreatedAt = promptCreatedAt - historyUserMsg.UpdatedAt = promptCreatedAt - res := h.DB.Save(&historyUserMsg) - if res.Error != nil { - logger.Error("failed to save prompt history message: ", res.Error) - } - - // 计算本次对话消耗的总 token 数量 - replyTokens, _ := utils.CalcTokens(message.Content, req.Model) - replyTokens += getTotalTokens(req) - - historyReplyMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.ReplyMsg, - Icon: role.Icon, - Content: message.Content, - Tokens: replyTokens, - UseContext: true, - Model: req.Model, - } - historyReplyMsg.CreatedAt = replyCreatedAt - historyReplyMsg.UpdatedAt = replyCreatedAt - res = h.DB.Create(&historyReplyMsg) - if res.Error != nil { - logger.Error("failed to save reply history message: ", res.Error) - } - - // 更新用户算力 - h.subUserPower(userVo, session, promptToken, replyTokens) - - // 保存当前会话 - var chatItem model.ChatItem - res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem) - if res.Error != nil { - chatItem.ChatId = session.ChatId - chatItem.UserId = session.UserId - chatItem.RoleId = role.Id - chatItem.ModelId = session.Model.Id - if utf8.RuneCountInString(prompt) > 30 { - chatItem.Title = string([]rune(prompt)[:30]) + "..." - } else { - chatItem.Title = prompt - } - chatItem.Model = req.Model - h.DB.Create(&chatItem) - } + h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt) } + } else { - body, err := io.ReadAll(response.Body) - if err != nil { - return fmt.Errorf("error with reading response: %v", err) - } - var res types.ApiError - err = json.Unmarshal(body, &res) - if err != nil { - return fmt.Errorf("error with decode response: %v", err) - } - - if strings.Contains(res.Error.Message, "maximum context length") { - logger.Error(res.Error.Message) - h.App.ChatContexts.Delete(session.ChatId) - return h.sendMessage(ctx, session, role, prompt, ws) - } else { - return fmt.Errorf("请求 Azure API 失败:%v", res.Error) - } + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("请求大模型 API 失败:%s", body) } return nil diff --git a/api/handler/chatimpl/baidu_handler.go b/api/handler/chatimpl/baidu_handler.go index 6e591a33..783ac3e9 100644 --- a/api/handler/chatimpl/baidu_handler.go +++ b/api/handler/chatimpl/baidu_handler.go @@ -17,12 +17,10 @@ import ( "geekai/store/model" "geekai/store/vo" "geekai/utils" - "html/template" "io" "net/http" "strings" "time" - "unicode/utf8" ) type baiduResp struct { @@ -130,99 +128,11 @@ func (h *ChatHandler) sendBaiduMessage( // 消息发送成功 if len(contents) > 0 { - if message.Role == "" { - message.Role = "assistant" - } - message.Content = strings.Join(contents, "") - useMsg := types.Message{Role: "user", Content: prompt} - - // 更新上下文消息,如果是调用函数则不需要更新上下文 - if h.App.SysConfig.EnableContext { - chatCtx = append(chatCtx, useMsg) // 提问消息 - chatCtx = append(chatCtx, message) // 回复消息 - h.App.ChatContexts.Put(session.ChatId, chatCtx) - } - - // 追加聊天记录 - // for prompt - promptToken, err := utils.CalcTokens(prompt, req.Model) - if err != nil { - logger.Error(err) - } - historyUserMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.PromptMsg, - Icon: userVo.Avatar, - Content: template.HTMLEscapeString(prompt), - Tokens: promptToken, - UseContext: true, - Model: req.Model, - } - historyUserMsg.CreatedAt = promptCreatedAt - historyUserMsg.UpdatedAt = promptCreatedAt - res := h.DB.Save(&historyUserMsg) - if res.Error != nil { - logger.Error("failed to save prompt history message: ", res.Error) - } - - // for reply - // 计算本次对话消耗的总 token 数量 - replyTokens, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens := replyTokens + getTotalTokens(req) - historyReplyMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.ReplyMsg, - Icon: role.Icon, - Content: message.Content, - Tokens: totalTokens, - UseContext: true, - Model: req.Model, - } - historyReplyMsg.CreatedAt = replyCreatedAt - historyReplyMsg.UpdatedAt = replyCreatedAt - res = h.DB.Create(&historyReplyMsg) - if res.Error != nil { - logger.Error("failed to save reply history message: ", res.Error) - } - // 更新用户算力 - h.subUserPower(userVo, session, promptToken, replyTokens) - - // 保存当前会话 - var chatItem model.ChatItem - res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem) - if res.Error != nil { - chatItem.ChatId = session.ChatId - chatItem.UserId = session.UserId - chatItem.RoleId = role.Id - chatItem.ModelId = session.Model.Id - if utf8.RuneCountInString(prompt) > 30 { - chatItem.Title = string([]rune(prompt)[:30]) + "..." - } else { - chatItem.Title = prompt - } - chatItem.Model = req.Model - h.DB.Create(&chatItem) - } + h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt) } } else { - body, err := io.ReadAll(response.Body) - if err != nil { - return fmt.Errorf("error with reading response: %v", err) - } - - var res struct { - Code int `json:"error_code"` - Msg string `json:"error_msg"` - } - err = json.Unmarshal(body, &res) - if err != nil { - return fmt.Errorf("error with decode response: %v", err) - } - utils.ReplyMessage(ws, "请求百度文心大模型 API 失败:"+res.Msg) + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("请求大模型 API 失败:%s", body) } return nil diff --git a/api/handler/chatimpl/chat_handler.go b/api/handler/chatimpl/chat_handler.go index 47a23acf..894a347a 100644 --- a/api/handler/chatimpl/chat_handler.go +++ b/api/handler/chatimpl/chat_handler.go @@ -23,11 +23,13 @@ import ( "geekai/store/vo" "geekai/utils" "geekai/utils/resp" + "html/template" "net/http" "net/url" "regexp" "strings" "time" + "unicode/utf8" "github.com/gin-gonic/gin" "github.com/go-redis/redis/v8" @@ -122,7 +124,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) { MaxContext: chatModel.MaxContext, Temperature: chatModel.Temperature, KeyId: chatModel.KeyId, - Platform: types.Platform(chatModel.Platform)} + Platform: chatModel.Platform} logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username) // 保存会话连接 @@ -218,11 +220,11 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio Stream: true, } switch session.Model.Platform { - case types.Azure, types.ChatGLM, types.Baidu, types.XunFei: + case types.Azure.Value, types.ChatGLM.Value, types.Baidu.Value, types.XunFei.Value: req.Temperature = session.Model.Temperature req.MaxTokens = session.Model.MaxTokens break - case types.OpenAI: + case types.OpenAI.Value: req.Temperature = session.Model.Temperature req.MaxTokens = session.Model.MaxTokens // OpenAI 支持函数功能 @@ -261,7 +263,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio req.Tools = tools req.ToolChoice = "auto" } - case types.QWen: + case types.QWen.Value: req.Parameters = map[string]interface{}{ "max_tokens": session.Model.MaxTokens, "temperature": session.Model.Temperature, @@ -325,14 +327,14 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio reqMgs = append(reqMgs, m) } - if session.Model.Platform == types.QWen { + if session.Model.Platform == types.QWen.Value { req.Input = make(map[string]interface{}) reqMgs = append(reqMgs, types.Message{ Role: "user", Content: prompt, }) req.Input["messages"] = reqMgs - } else if session.Model.Platform == types.OpenAI { // extract image for gpt-vision model + } else if session.Model.Platform == types.OpenAI.Value { // extract image for gpt-vision model imgURLs := utils.ExtractImgURL(prompt) logger.Debugf("detected IMG: %+v", imgURLs) var content interface{} @@ -370,17 +372,17 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio logger.Debugf("%+v", req.Messages) switch session.Model.Platform { - case types.Azure: + case types.Azure.Value: return h.sendAzureMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws) - case types.OpenAI: + case types.OpenAI.Value: return h.sendOpenAiMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws) - case types.ChatGLM: + case types.ChatGLM.Value: return h.sendChatGLMMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws) - case types.Baidu: + case types.Baidu.Value: return h.sendBaiduMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws) - case types.XunFei: + case types.XunFei.Value: return h.sendXunFeiMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws) - case types.QWen: + case types.QWen.Value: return h.sendQWenMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws) } @@ -467,7 +469,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi } // ONLY allow apiURL in blank list - if session.Model.Platform == types.OpenAI { + if session.Model.Platform == types.OpenAI.Value { err := h.licenseService.IsValidApiURL(apiKey.ApiURL) if err != nil { return nil, err @@ -476,19 +478,19 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi var apiURL string switch session.Model.Platform { - case types.Azure: + case types.Azure.Value: md := strings.Replace(req.Model, ".", "", 1) apiURL = strings.Replace(apiKey.ApiURL, "{model}", md, 1) break - case types.ChatGLM: + case types.ChatGLM.Value: apiURL = strings.Replace(apiKey.ApiURL, "{model}", req.Model, 1) req.Prompt = req.Messages // 使用 prompt 字段替代 message 字段 req.Messages = nil break - case types.Baidu: + case types.Baidu.Value: apiURL = strings.Replace(apiKey.ApiURL, "{model}", req.Model, 1) break - case types.QWen: + case types.QWen.Value: apiURL = apiKey.ApiURL req.Messages = nil break @@ -498,7 +500,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi // 更新 API KEY 的最后使用时间 h.DB.Model(apiKey).UpdateColumn("last_used_at", time.Now().Unix()) // 百度文心,需要串接 access_token - if session.Model.Platform == types.Baidu { + if session.Model.Platform == types.Baidu.Value { token, err := h.getBaiduToken(apiKey.Value) if err != nil { return nil, err @@ -534,22 +536,22 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi } logger.Debugf("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s, Model: %s", session.Model.Platform, apiURL, apiKey.Value, apiKey.ProxyURL, req.Model) switch session.Model.Platform { - case types.Azure: + case types.Azure.Value: request.Header.Set("api-key", apiKey.Value) break - case types.ChatGLM: + case types.ChatGLM.Value: token, err := h.getChatGLMToken(apiKey.Value) if err != nil { return nil, err } request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) break - case types.Baidu: + case types.Baidu.Value: request.RequestURI = "" - case types.OpenAI: + case types.OpenAI.Value: request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value)) break - case types.QWen: + case types.QWen.Value: request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value)) request.Header.Set("X-DashScope-SSE", "enable") break @@ -583,6 +585,97 @@ func (h *ChatHandler) subUserPower(userVo vo.User, session *types.ChatSession, p } +func (h *ChatHandler) saveChatHistory( + req types.ApiRequest, + prompt string, + contents []string, + message types.Message, + chatCtx []types.Message, + session *types.ChatSession, + role model.ChatRole, + userVo vo.User, + promptCreatedAt time.Time, + replyCreatedAt time.Time) { + if message.Role == "" { + message.Role = "assistant" + } + message.Content = strings.Join(contents, "") + useMsg := types.Message{Role: "user", Content: prompt} + + // 更新上下文消息,如果是调用函数则不需要更新上下文 + if h.App.SysConfig.EnableContext { + chatCtx = append(chatCtx, useMsg) // 提问消息 + chatCtx = append(chatCtx, message) // 回复消息 + h.App.ChatContexts.Put(session.ChatId, chatCtx) + } + + // 追加聊天记录 + // for prompt + promptToken, err := utils.CalcTokens(prompt, req.Model) + if err != nil { + logger.Error(err) + } + historyUserMsg := model.ChatMessage{ + UserId: userVo.Id, + ChatId: session.ChatId, + RoleId: role.Id, + Type: types.PromptMsg, + Icon: userVo.Avatar, + Content: template.HTMLEscapeString(prompt), + Tokens: promptToken, + UseContext: true, + Model: req.Model, + } + historyUserMsg.CreatedAt = promptCreatedAt + historyUserMsg.UpdatedAt = promptCreatedAt + res := h.DB.Save(&historyUserMsg) + if res.Error != nil { + logger.Error("failed to save prompt history message: ", res.Error) + } + + // for reply + // 计算本次对话消耗的总 token 数量 + replyTokens, _ := utils.CalcTokens(message.Content, req.Model) + totalTokens := replyTokens + getTotalTokens(req) + historyReplyMsg := model.ChatMessage{ + UserId: userVo.Id, + ChatId: session.ChatId, + RoleId: role.Id, + Type: types.ReplyMsg, + Icon: role.Icon, + Content: message.Content, + Tokens: totalTokens, + UseContext: true, + Model: req.Model, + } + historyReplyMsg.CreatedAt = replyCreatedAt + historyReplyMsg.UpdatedAt = replyCreatedAt + res = h.DB.Create(&historyReplyMsg) + if res.Error != nil { + logger.Error("failed to save reply history message: ", res.Error) + } + + // 更新用户算力 + h.subUserPower(userVo, session, promptToken, replyTokens) + + // 保存当前会话 + var chatItem model.ChatItem + res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem) + if res.Error != nil { + chatItem.ChatId = session.ChatId + chatItem.UserId = session.UserId + chatItem.RoleId = role.Id + chatItem.ModelId = session.Model.Id + if utf8.RuneCountInString(prompt) > 30 { + chatItem.Title = string([]rune(prompt)[:30]) + "..." + } else { + chatItem.Title = prompt + } + chatItem.Model = req.Model + h.DB.Create(&chatItem) + } +} + // 将AI回复消息中生成的图片链接下载到本地 func (h *ChatHandler) extractImgUrl(text string) string { pattern := `!\[([^\]]*)]\(([^)]+)\)` diff --git a/api/handler/chatimpl/chatglm_handler.go b/api/handler/chatimpl/chatglm_handler.go index 53e83bcb..0192abc8 100644 --- a/api/handler/chatimpl/chatglm_handler.go +++ b/api/handler/chatimpl/chatglm_handler.go @@ -10,7 +10,6 @@ package chatimpl import ( "bufio" "context" - "encoding/json" "errors" "fmt" "geekai/core/types" @@ -18,11 +17,9 @@ import ( "geekai/store/vo" "geekai/utils" "github.com/golang-jwt/jwt/v5" - "html/template" "io" "strings" "time" - "unicode/utf8" ) // 清华大学 ChatGML 消息发送实现 @@ -108,103 +105,11 @@ func (h *ChatHandler) sendChatGLMMessage( // 消息发送成功 if len(contents) > 0 { - if message.Role == "" { - message.Role = "assistant" - } - message.Content = strings.Join(contents, "") - useMsg := types.Message{Role: "user", Content: prompt} - - // 更新上下文消息,如果是调用函数则不需要更新上下文 - if h.App.SysConfig.EnableContext { - chatCtx = append(chatCtx, useMsg) // 提问消息 - chatCtx = append(chatCtx, message) // 回复消息 - h.App.ChatContexts.Put(session.ChatId, chatCtx) - } - - // 追加聊天记录 - // for prompt - promptToken, err := utils.CalcTokens(prompt, req.Model) - if err != nil { - logger.Error(err) - } - historyUserMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.PromptMsg, - Icon: userVo.Avatar, - Content: template.HTMLEscapeString(prompt), - Tokens: promptToken, - UseContext: true, - Model: req.Model, - } - historyUserMsg.CreatedAt = promptCreatedAt - historyUserMsg.UpdatedAt = promptCreatedAt - res := h.DB.Save(&historyUserMsg) - if res.Error != nil { - logger.Error("failed to save prompt history message: ", res.Error) - } - - // for reply - // 计算本次对话消耗的总 token 数量 - replyTokens, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens := replyTokens + getTotalTokens(req) - historyReplyMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.ReplyMsg, - Icon: role.Icon, - Content: message.Content, - Tokens: totalTokens, - UseContext: true, - Model: req.Model, - } - historyReplyMsg.CreatedAt = replyCreatedAt - historyReplyMsg.UpdatedAt = replyCreatedAt - res = h.DB.Create(&historyReplyMsg) - if res.Error != nil { - logger.Error("failed to save reply history message: ", res.Error) - } - - // 更新用户算力 - h.subUserPower(userVo, session, promptToken, replyTokens) - - // 保存当前会话 - var chatItem model.ChatItem - res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem) - if res.Error != nil { - chatItem.ChatId = session.ChatId - chatItem.UserId = session.UserId - chatItem.RoleId = role.Id - chatItem.ModelId = session.Model.Id - if utf8.RuneCountInString(prompt) > 30 { - chatItem.Title = string([]rune(prompt)[:30]) + "..." - } else { - chatItem.Title = prompt - } - chatItem.Model = req.Model - h.DB.Create(&chatItem) - } + h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt) } } else { - body, err := io.ReadAll(response.Body) - if err != nil { - return fmt.Errorf("error with reading response: %v", err) - } - - var res struct { - Code int `json:"code"` - Success bool `json:"success"` - Msg string `json:"msg"` - } - err = json.Unmarshal(body, &res) - if err != nil { - return fmt.Errorf("error with decode response: %v", err) - } - if !res.Success { - utils.ReplyMessage(ws, "请求 ChatGLM 失败:"+res.Msg) - } + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("请求大模型 API 失败:%s", body) } return nil diff --git a/api/handler/chatimpl/openai_handler.go b/api/handler/chatimpl/openai_handler.go index 3878c46c..fb953b79 100644 --- a/api/handler/chatimpl/openai_handler.go +++ b/api/handler/chatimpl/openai_handler.go @@ -17,13 +17,10 @@ import ( "geekai/store/model" "geekai/store/vo" "geekai/utils" - "html/template" + req2 "github.com/imroc/req/v3" "io" "strings" "time" - "unicode/utf8" - - req2 "github.com/imroc/req/v3" ) // OPenAI 消息发送实现 @@ -178,126 +175,11 @@ func (h *ChatHandler) sendOpenAiMessage( // 消息发送成功 if len(contents) > 0 { - if message.Role == "" { - message.Role = "assistant" - } - message.Content = strings.Join(contents, "") - useMsg := types.Message{Role: "user", Content: prompt} - - // 更新上下文消息,如果是调用函数则不需要更新上下文 - if h.App.SysConfig.EnableContext && toolCall == false { - chatCtx = append(chatCtx, useMsg) // 提问消息 - chatCtx = append(chatCtx, message) // 回复消息 - h.App.ChatContexts.Put(session.ChatId, chatCtx) - } - - // 追加聊天记录 - useContext := true - if toolCall { - useContext = false - } - - // for prompt - promptToken, err := utils.CalcTokens(prompt, req.Model) - if err != nil { - logger.Error(err) - } - historyUserMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.PromptMsg, - Icon: userVo.Avatar, - Content: template.HTMLEscapeString(prompt), - Tokens: promptToken, - UseContext: useContext, - Model: req.Model, - } - historyUserMsg.CreatedAt = promptCreatedAt - historyUserMsg.UpdatedAt = promptCreatedAt - res := h.DB.Save(&historyUserMsg) - if res.Error != nil { - logger.Error("failed to save prompt history message: ", res.Error) - } - - // 计算本次对话消耗的总 token 数量 - var replyTokens = 0 - if toolCall { // prompt + 函数名 + 参数 token - tokens, _ := utils.CalcTokens(function.Name, req.Model) - replyTokens += tokens - tokens, _ = utils.CalcTokens(utils.InterfaceToString(arguments), req.Model) - replyTokens += tokens - } else { - replyTokens, _ = utils.CalcTokens(message.Content, req.Model) - } - replyTokens += getTotalTokens(req) - - historyReplyMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.ReplyMsg, - Icon: role.Icon, - Content: h.extractImgUrl(message.Content), - Tokens: replyTokens, - UseContext: useContext, - Model: req.Model, - } - historyReplyMsg.CreatedAt = replyCreatedAt - historyReplyMsg.UpdatedAt = replyCreatedAt - res = h.DB.Create(&historyReplyMsg) - if res.Error != nil { - logger.Error("failed to save reply history message: ", res.Error) - } - - // 更新用户算力 - h.subUserPower(userVo, session, promptToken, replyTokens) - - // 保存当前会话 - var chatItem model.ChatItem - res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem) - if res.Error != nil { - chatItem.ChatId = session.ChatId - chatItem.UserId = session.UserId - chatItem.RoleId = role.Id - chatItem.ModelId = session.Model.Id - if utf8.RuneCountInString(prompt) > 30 { - chatItem.Title = string([]rune(prompt)[:30]) + "..." - } else { - chatItem.Title = prompt - } - chatItem.Model = req.Model - h.DB.Create(&chatItem) - } + h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt) } } else { - body, err := io.ReadAll(response.Body) - if err != nil { - utils.ReplyMessage(ws, "请求 OpenAI API 失败:"+err.Error()) - return fmt.Errorf("error with reading response: %v", err) - } - var res types.ApiError - err = json.Unmarshal(body, &res) - if err != nil { - utils.ReplyMessage(ws, "请求 OpenAI API 失败:\n"+"```\n"+string(body)+"```") - return fmt.Errorf("error with decode response: %v", err) - } - - // OpenAI API 调用异常处理 - if strings.Contains(res.Error.Message, "This key is associated with a deactivated account") { - utils.ReplyMessage(ws, "请求 OpenAI API 失败:API KEY 所关联的账户被禁用。") - // 移除当前 API key - h.DB.Where("value = ?", apiKey).Delete(&model.ApiKey{}) - } else if strings.Contains(res.Error.Message, "You exceeded your current quota") { - utils.ReplyMessage(ws, "请求 OpenAI API 失败:API KEY 触发并发限制,请稍后再试。") - } else if strings.Contains(res.Error.Message, "This model's maximum context length") { - logger.Error(res.Error.Message) - utils.ReplyMessage(ws, "当前会话上下文长度超出限制,已为您清空会话上下文!") - h.App.ChatContexts.Delete(session.ChatId) - return h.sendMessage(ctx, session, role, prompt, ws) - } else { - utils.ReplyMessage(ws, "请求 OpenAI API 失败:"+res.Error.Message) - } + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("请求 OpenAI API 失败:%s", body) } return nil diff --git a/api/handler/chatimpl/qwen_handler.go b/api/handler/chatimpl/qwen_handler.go index 7b7eb1ac..28bf66bb 100644 --- a/api/handler/chatimpl/qwen_handler.go +++ b/api/handler/chatimpl/qwen_handler.go @@ -10,18 +10,15 @@ package chatimpl import ( "bufio" "context" - "encoding/json" "fmt" "geekai/core/types" "geekai/store/model" "geekai/store/vo" "geekai/utils" "github.com/syndtr/goleveldb/leveldb/errors" - "html/template" "io" "strings" "time" - "unicode/utf8" ) type qWenResp struct { @@ -142,100 +139,11 @@ func (h *ChatHandler) sendQWenMessage( // 消息发送成功 if len(contents) > 0 { - if message.Role == "" { - message.Role = "assistant" - } - message.Content = strings.Join(contents, "") - useMsg := types.Message{Role: "user", Content: prompt} - - // 更新上下文消息,如果是调用函数则不需要更新上下文 - if h.App.SysConfig.EnableContext { - chatCtx = append(chatCtx, useMsg) // 提问消息 - chatCtx = append(chatCtx, message) // 回复消息 - h.App.ChatContexts.Put(session.ChatId, chatCtx) - } - - // 追加聊天记录 - // for prompt - promptToken, err := utils.CalcTokens(prompt, req.Model) - if err != nil { - logger.Error(err) - } - historyUserMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.PromptMsg, - Icon: userVo.Avatar, - Content: template.HTMLEscapeString(prompt), - Tokens: promptToken, - UseContext: true, - Model: req.Model, - } - historyUserMsg.CreatedAt = promptCreatedAt - historyUserMsg.UpdatedAt = promptCreatedAt - res := h.DB.Save(&historyUserMsg) - if res.Error != nil { - logger.Error("failed to save prompt history message: ", res.Error) - } - - // for reply - // 计算本次对话消耗的总 token 数量 - replyTokens, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens := replyTokens + getTotalTokens(req) - historyReplyMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.ReplyMsg, - Icon: role.Icon, - Content: message.Content, - Tokens: totalTokens, - UseContext: true, - Model: req.Model, - } - historyReplyMsg.CreatedAt = replyCreatedAt - historyReplyMsg.UpdatedAt = replyCreatedAt - res = h.DB.Create(&historyReplyMsg) - if res.Error != nil { - logger.Error("failed to save reply history message: ", res.Error) - } - - // 更新用户算力 - h.subUserPower(userVo, session, promptToken, replyTokens) - - // 保存当前会话 - var chatItem model.ChatItem - res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem) - if res.Error != nil { - chatItem.ChatId = session.ChatId - chatItem.UserId = session.UserId - chatItem.RoleId = role.Id - chatItem.ModelId = session.Model.Id - if utf8.RuneCountInString(prompt) > 30 { - chatItem.Title = string([]rune(prompt)[:30]) + "..." - } else { - chatItem.Title = prompt - } - chatItem.Model = req.Model - h.DB.Create(&chatItem) - } + h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt) } } else { - body, err := io.ReadAll(response.Body) - if err != nil { - return fmt.Errorf("error with reading response: %v", err) - } - - var res struct { - Code int `json:"error_code"` - Msg string `json:"error_msg"` - } - err = json.Unmarshal(body, &res) - if err != nil { - return fmt.Errorf("error with decode response: %v", err) - } - utils.ReplyMessage(ws, "请求通义千问大模型 API 失败:"+res.Msg) + body, _ := io.ReadAll(response.Body) + return fmt.Errorf("请求大模型 API 失败:%s", body) } return nil diff --git a/api/handler/chatimpl/xunfei_handler.go b/api/handler/chatimpl/xunfei_handler.go index afc1c165..e4a081fc 100644 --- a/api/handler/chatimpl/xunfei_handler.go +++ b/api/handler/chatimpl/xunfei_handler.go @@ -21,13 +21,11 @@ import ( "geekai/utils" "github.com/gorilla/websocket" "gorm.io/gorm" - "html/template" "io" "net/http" "net/url" "strings" "time" - "unicode/utf8" ) type xunFeiResp struct { @@ -181,89 +179,10 @@ func (h *ChatHandler) sendXunFeiMessage( } } - // 消息发送成功 if len(contents) > 0 { - if message.Role == "" { - message.Role = "assistant" - } - message.Content = strings.Join(contents, "") - useMsg := types.Message{Role: "user", Content: prompt} - - // 更新上下文消息,如果是调用函数则不需要更新上下文 - if h.App.SysConfig.EnableContext { - chatCtx = append(chatCtx, useMsg) // 提问消息 - chatCtx = append(chatCtx, message) // 回复消息 - h.App.ChatContexts.Put(session.ChatId, chatCtx) - } - - // 追加聊天记录 - // for prompt - promptToken, err := utils.CalcTokens(prompt, req.Model) - if err != nil { - logger.Error(err) - } - historyUserMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.PromptMsg, - Icon: userVo.Avatar, - Content: template.HTMLEscapeString(prompt), - Tokens: promptToken, - UseContext: true, - Model: req.Model, - } - historyUserMsg.CreatedAt = promptCreatedAt - historyUserMsg.UpdatedAt = promptCreatedAt - res := h.DB.Save(&historyUserMsg) - if res.Error != nil { - logger.Error("failed to save prompt history message: ", res.Error) - } - - // for reply - // 计算本次对话消耗的总 token 数量 - replyTokens, _ := utils.CalcTokens(message.Content, req.Model) - totalTokens := replyTokens + getTotalTokens(req) - historyReplyMsg := model.ChatMessage{ - UserId: userVo.Id, - ChatId: session.ChatId, - RoleId: role.Id, - Type: types.ReplyMsg, - Icon: role.Icon, - Content: message.Content, - Tokens: totalTokens, - UseContext: true, - Model: req.Model, - } - historyReplyMsg.CreatedAt = replyCreatedAt - historyReplyMsg.UpdatedAt = replyCreatedAt - res = h.DB.Create(&historyReplyMsg) - if res.Error != nil { - logger.Error("failed to save reply history message: ", res.Error) - } - - // 更新用户算力 - h.subUserPower(userVo, session, promptToken, replyTokens) - - // 保存当前会话 - var chatItem model.ChatItem - res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem) - if res.Error != nil { - chatItem.ChatId = session.ChatId - chatItem.UserId = session.UserId - chatItem.RoleId = role.Id - chatItem.ModelId = session.Model.Id - if utf8.RuneCountInString(prompt) > 30 { - chatItem.Title = string([]rune(prompt)[:30]) + "..." - } else { - chatItem.Title = prompt - } - chatItem.Model = req.Model - h.DB.Create(&chatItem) - } + h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt) } - return nil } diff --git a/api/main.go b/api/main.go index 55a7075a..9b4458cf 100644 --- a/api/main.go +++ b/api/main.go @@ -304,7 +304,7 @@ func main() { group.GET("config/get", h.Get) group.POST("active", h.Active) group.GET("config/get/license", h.GetLicense) - group.GET("config/get/draw", h.GetDrawingConfig) + group.GET("config/get/app", h.GetAppConfig) group.POST("config/update/draw", h.SaveDrawingConfig) }), fx.Invoke(func(s *core.AppServer, h *admin.ManagerHandler) { diff --git a/web/src/components/admin/AdminSidebar.vue b/web/src/components/admin/AdminSidebar.vue index df24ddc8..4c0aebda 100644 --- a/web/src/components/admin/AdminSidebar.vue +++ b/web/src/components/admin/AdminSidebar.vue @@ -92,9 +92,9 @@ const items = [ }, { - icon: 'role', - index: '/admin/role', - title: '角色管理', + icon: 'menu', + index: '/admin/app', + title: '应用管理', }, { icon: 'api-key', diff --git a/web/src/router.js b/web/src/router.js index a03c5ec6..64fdecb6 100644 --- a/web/src/router.js +++ b/web/src/router.js @@ -46,7 +46,7 @@ const routes = [ component: () => import('@/views/Member.vue'), }, { - name: 'chat-role', + name: 'chat-app', path: '/apps', meta: {title: '应用中心'}, component: () => import('@/views/ChatApps.vue'), @@ -139,10 +139,10 @@ const routes = [ component: () => import('@/views/admin/Users.vue'), }, { - path: '/admin/role', - name: 'admin-role', - meta: {title: '角色管理'}, - component: () => import('@/views/admin/Roles.vue'), + path: '/admin/app', + name: 'admin-app', + meta: {title: '应用管理'}, + component: () => import('@/views/admin/Apps.vue'), }, { path: '/admin/apikey', diff --git a/web/src/views/admin/AIDrawing.vue b/web/src/views/admin/AIDrawing.vue index b193924c..66836b6b 100644 --- a/web/src/views/admin/AIDrawing.vue +++ b/web/src/views/admin/AIDrawing.vue @@ -119,12 +119,12 @@ const mjModels = ref([ {name: "急速(Turbo)", value: "turbo"}, ]) -httpGet("/api/admin/config/get/draw").then(res => { +httpGet("/api/admin/config/get/app").then(res => { sdConfigs.value = res.data.sd mjPlusConfigs.value = res.data.mj_plus mjProxyConfigs.value = res.data.mj_proxy }).catch(e =>{ - ElMessage.error("获取AI绘画配置失败:"+e.message) + ElMessage.error("获取配置失败:"+e.message) }) const addConfig = (configs) => { diff --git a/web/src/views/admin/ApiKey.vue b/web/src/views/admin/ApiKey.vue index fec5d14c..aacc05a2 100644 --- a/web/src/views/admin/ApiKey.vue +++ b/web/src/views/admin/ApiKey.vue @@ -87,7 +87,7 @@ - + {{ item.name }} @@ -99,7 +99,8 @@ + placeholder="必须填土完整的 Chat API URL,如:https://api.openai.com/v1/chat/completions"/> +
如果你使用了第三方中转,这里就填写中转地址
@@ -126,7 +127,7 @@ import {onMounted, onUnmounted, reactive, ref} from "vue"; import {httpGet, httpPost} from "@/utils/http"; import {ElMessage} from "element-plus"; import {dateFormat, removeArrayItem, substr} from "@/utils/libs"; -import {DocumentCopy, Plus, ShoppingCart} from "@element-plus/icons-vue"; +import {DocumentCopy, Plus, ShoppingCart, InfoFilled} from "@element-plus/icons-vue"; import ClipboardJS from "clipboard"; // 变量定义 @@ -142,39 +143,7 @@ const rules = reactive({ const loading = ref(true) const formRef = ref(null) const title = ref("") -const platforms = ref([ - { - name: "【OpenAI/中转】ChatGPT", - value: "OpenAI", - api_url: "https://api.chat-plus.net/v1/chat/completions", - img_url: "https://api.chat-plus.net/v1/images/generations" - }, - { - name: "【讯飞】星火大模型", - value: "XunFei", - api_url: "wss://spark-api.xf-yun.com/{version}/chat" - }, - { - name: "【清华智普】ChatGLM", - value: "ChatGLM", - api_url: "https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke" - }, - { - name: "【百度】文心一言", - value: "Baidu", - api_url: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}" - }, - { - name: "【微软】Azure", - value: "Azure", - api_url: "https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15" - }, - { - name: "【阿里】千义通问", - value: "QWen", - api_url: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation" - }, -]) +const platforms = ref([]) const types = ref([ {name: "聊天", value: "chat"}, {name: "绘画", value: "img"}, @@ -191,6 +160,12 @@ onMounted(() => { clipboard.value.on('error', () => { ElMessage.error('复制失败!'); }) + + httpGet("/api/admin/config/get/app").then(res => { + platforms.value = res.data.platforms + }).catch(e =>{ + ElMessage.error("获取配置失败:"+e.message) + }) }) onUnmounted(() => { @@ -263,21 +238,24 @@ const set = (filed, row) => { }) } -const changePlatform = () => { - let platform = null +const selectedPlatform = ref(null) +const changePlatform = (value) => { + console.log(value) for (let v of platforms.value) { - if (v.value === item.value.platform) { - platform = v - break + if (v.value === value) { + selectedPlatform.value = v + item.value.api_url = v.chat_url } } - if (platform !== null) { - if (item.value.type === "img" && platform.img_url) { - item.value.api_url = platform.img_url - } else { - item.value.api_url = platform.api_url - } +} +const changeType = (value) => { + if (selectedPlatform.value) { + if(value === 'img') { + item.value.api_url = selectedPlatform.value.img_url + } else { + item.value.api_url = selectedPlatform.value.chat_url + } } } @@ -306,7 +284,9 @@ const changePlatform = () => { .el-form { .el-form-item__content { - + .info { + color #999999 + } .el-icon { padding-left: 10px; } diff --git a/web/src/views/admin/Roles.vue b/web/src/views/admin/Apps.vue similarity index 92% rename from web/src/views/admin/Roles.vue rename to web/src/views/admin/Apps.vue index b32de7c7..c85baf77 100644 --- a/web/src/views/admin/Roles.vue +++ b/web/src/views/admin/Apps.vue @@ -9,25 +9,25 @@ - + - + - + @@ -36,7 +36,7 @@