Compare commits

..

46 Commits
v3.1.8 ... pr_3

Author SHA1 Message Date
RockYang
babef8baae feat: refactor midjourney image creating page 2023-12-06 18:54:30 +08:00
liyuwanglan
ae8239e5de 修改 2023-12-05 11:08:03 +08:00
RockYang
dae91ed243 fix: fixed bug for upload image failed 2023-11-29 17:46:46 +08:00
RockYang
de42a428e6 opt: create new chat session when change role or model, fix bug for mobile no validate 2023-11-29 17:36:27 +08:00
RockYang
63c7041e1f docs: update change logs 2023-11-28 15:33:30 +08:00
RockYang
b1263ddc69 docs: 添加一键部署脚本 2023-11-28 15:25:51 +08:00
RockYang
7e50e17aaf opt: 缩略图生成算法 2023-11-28 14:50:19 +08:00
RockYang
a7265c4251 feat: 为大图片生成缩略图,加快前端图片加载速度 2023-11-28 12:04:02 +08:00
RockYang
6f39f639bd fix: fix bug for oss image domain 2023-11-28 07:27:18 +08:00
RockYang
a7db123437 docs: update build script 2023-11-27 18:24:52 +08:00
RockYang
241c714a8b Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2023-11-27 12:03:23 +08:00
RockYang
67ac3cfe32 feat: merge mysql and redis docker service to docker-compose.yaml file 2023-11-27 10:56:18 +08:00
RockYang
c926e0afcc Merge pull request #55 from openjst/main
修改code显示颜色样式
2023-11-27 10:27:13 +08:00
RockYang
5bc07e6d57 docs: make a full docker-compose.yaml 2023-11-27 07:21:37 +08:00
openjst
c3666a9a71 修改code显示颜色样式 2023-11-26 23:26:08 +08:00
RockYang
23b5ffa97d feat: implements image function replace Mj with DALL-E-3 2023-11-26 20:37:48 +08:00
RockYang
a2c7a75705 feat: add type field for api key 2023-11-24 18:05:59 +08:00
RockYang
d68f2ef12c feat: add support for registing use force use invite code 2023-11-24 12:02:28 +08:00
RockYang
67d30353f0 opt: optimize image preview for MidJourney image list page, only preview current image not for all images 2023-11-23 17:55:12 +08:00
RockYang
4813163eac opt: 增加中间件自动对HTTP请求的参数去掉首尾空格 2023-11-23 17:50:55 +08:00
RockYang
5c5210625e opt: optimize styles for invitation page 2023-11-23 17:40:15 +08:00
RockYang
a4a1eec30b feat: add invitation and promotion functions 2023-11-23 16:30:15 +08:00
RockYang
d35164506a docs: add database sql file for v3.1.9 2023-11-23 09:58:01 +08:00
RockYang
1ed08f01ea docs: update change log 2023-11-23 09:46:49 +08:00
RockYang
eca07ab830 docs: update change log and readme file 2023-11-23 09:14:42 +08:00
RockYang
3512715704 feat: 支持讯飞大模型 v3.0 2023-11-23 07:11:13 +08:00
RockYang
6d07881141 feat: reset password function is ready 2023-11-22 18:00:45 +08:00
RockYang
251fe626f2 feat: add copy code btn in chat page, fixed bug for code wrap in model of ChatGLM and XunFei 2023-11-22 17:00:43 +08:00
RockYang
5fee3a9288 feat: add feekback orcode in chat page 2023-11-22 12:05:58 +08:00
RockYang
9b68d8101e fix: fixed bug for enable user vip in admin console not work 2023-11-22 11:30:58 +08:00
RockYang
cfe6f27d48 option: replace leveldb with redis in storing message code 2023-11-22 10:57:24 +08:00
RockYang
b314dd0900 Merge pull request #52 from KunMingStar/fix_migrate
fix: remove dead code
2023-11-22 08:23:01 +08:00
tongkunming
950fab6374 fix: remove dead code 2023-11-21 22:18:53 +08:00
RockYang
9d1f5c42ce chore: remove dead code 2023-11-21 19:04:53 +08:00
RockYang
a84046390b feat: add wechat id card for closing register 2023-11-20 16:38:04 +08:00
RockYang
aa29323a8a docs: update docs installation docs url 2023-11-17 18:42:13 +08:00
RockYang
d5617b7c3a docs: update readme and config sample files 2023-11-16 17:20:38 +08:00
RockYang
1ef60a9e5e fix: fix bug with missing chat context 2023-11-15 18:20:34 +08:00
RockYang
fb6e395ad8 fix: unbind event for login page when the component is unmount 2023-11-14 18:19:40 +08:00
RockYang
d9216060bc feat: add link on logo 2023-11-14 13:50:44 +08:00
RockYang
bcaa9a92e5 fix: fix member page flex styles 2023-11-14 11:21:16 +08:00
RockYang
576adc9036 fix: deducating the user's img call quota after stable diffusion callback 2023-11-14 08:59:39 +08:00
RockYang
00de18be9a feat: 更改 MJ 和 SD 菜单图标 2023-11-13 11:27:42 +08:00
RockYang
c61d32816a Merge branch 'main' of github.com:yangjian102621/chatgpt-plus 2023-11-13 10:11:45 +08:00
RockYang
f3fbb0b89c feat: 更改 MJ 和 SD 菜单图标 2023-11-13 10:11:21 +08:00
RockYang
e311a39632 chore: change xxl-job name 2023-11-12 15:39:12 +08:00
113 changed files with 5829 additions and 1157 deletions

View File

@@ -1,5 +1,31 @@
# 更新日志
## v3.2.0
* 功能新增:新增邀请注册功能
* 功能优化增加中间件自动对HTTP请求的参数去掉首尾空格
* 功能优化:增加中间件自动为大图片生成缩略图
* 功能优化MidJourney 页面图片加载优化,实现图片预览懒加载
* 功能新增:新增 DALL-E-3 绘画支持,并作为对话页面默认绘画插件
* Bug修复修复阿里云 OSS 域名设置不起做用的bug
* Bug修复修复MidJourney绘图失败后重复添加到队列的问题
## v3.1.9
* 功能新增:增加讯飞星火大模型 v3.0 支持
* 功能新增:新增找回密码功能
* 功能新增:支持 Markdown 代码复制功能
* Bug修复: xxl-job 任务调度失败的 Bug
* 功能优化:优化前端页面菜单图标,使用自定义图标替换 icon-font
* Bug修复Stable-Diffusion 绘画成功之后没有扣减用户画图次数
* 功能优化:优化会员充值页面 ItemList 组件
* 功能优化:给首页 Logo 增加链接
* Bug修复[新建会话时,提示"请输入合法的手机号" ](https://github.com/yangjian102621/chatgpt-plus/issues/51)
* Bug修复聊天上下文失效问题
* 功能优化:关闭注册时显示联系管理员二维码
* 功能优化:移除 leveldb 依赖,使用 redis 替换相应的功能
* Bug修复后台启用用户 VIP 不生效问题
* 功能优化:充值支付页面的支付说明文字可以后台配置
* Bug修复ChatGLM百度文心科大讯飞模型输出代码不换行问题
## v3.1.8
1. 功能新增:新增会员套餐充值,点卡充值,订单系统,集成支付宝支付通道

334
README.md
View File

@@ -42,7 +42,7 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
### 会员充值
![会员充值](/docs/imgs/alipay.png)
![会员充值](/docs/imgs/member.png)
### 自动调用函数插件
@@ -73,39 +73,6 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
1. 本项目基于 MIT 协议,免费开放全部源代码,可以作为个人学习使用或者商用。
2. 如需商用必须保留版权信息,请自觉遵守。确保合法合规使用,在运营过程中产生的一切任何后果自负,与作者无关。
## 项目介绍
这一套完整的系统,包括前端聊天应用和一个后台管理系统。系统有用户鉴权,你可以自己使用,也可以部署直接给 C 端用户提供
ChatGPT 的服务。
### 项目的技术架构
新版的系统前后端都进行大改动的重构,后端还是用的 Gin Web 框架,但是作者整合了 fx 自动注入框架,整个后端应用结构非常简洁,特别适合二次开发。
另外,数据存储用 MySQL 替换了 leveldb, 因为要对 C 端后期会涉及到很多业务数据查询统计leveldb 已经完全不够用了。
> Gin + fx + MySQL
3.0 版本之后会陆续添加其他语言的 API 实现,比如 PHPJava 等。考虑到作者精力有限api 目录已经添加了,有兴趣的同学自主去认领各自擅长的语言去实现。
前端的框架还是:
> Vue3 + Element-Plus
前后台的页面风格已经全部变了,几乎所有页面样式代码都重写了。逻辑代码还是沿用之前的,毕竟功能没有太大的变化。
此次重构改版主要是为了后面功能的扩展准备了。
新版本已经实现的功能如下:
1. 引入用户体系,新增用户注册和登录功能。
2. 聊天页面改版,实现了跟 ChatGPT 官方版本一致的聊天体验。
3. 创建会话的时候可以选择聊天角色和模型。
4. 新增聊天设置功能,用户可以导入自己的 API KEY
5. 保存聊天记录,支持聊天上下文。
6. 重构后台管理模块,更友好,扩展性更好的后台管理系统。
7. 引入 ip2region 组件记录用户的登录IP和地址。
8. 支持会话搜索过滤。
9. 支持微信支付充值
## 项目地址
@@ -117,301 +84,19 @@ ChatGPT 的服务。
目前已经支持 Win/Linux/Mac/Android 客户端下载地址为https://github.com/yangjian102621/chatgpt-plus/releases/tag/v3.1.2
## TODOLIST
* [x] 整合 Midjourney AI 绘画 API
* [x] 开发移动端聊天页面
* [x] 接入微信收款功能
* [x] 支持 ChatGPT 函数功能,通过函数实现插件
* [x] 开发桌面版应用
* [x] 开发手机 App 客户端
* [x] 支付宝支付功能
* [ ] 支持基于知识库的 AI 问答
* [ ] 会员推广功能
* [ ] 会员邀请注册推广功能
* [ ] 微信支付功能
## Docker 快速部署
>
鉴于最新不少网友反馈在部署的时候遇到一些问题,大部分问题都是相同的,所以我这边做了一个视频教程 [五分钟部署自己的 ChatGPT 服务](https://www.bilibili.com/video/BV1H14y1B7Qw/)。
> 习惯看视频教程的朋友可以去看视频教程,视频的语速比较慢,建议 2 倍速观看。
V3.0.0 版本以后已经支持使用容器部署了,跳过所有的繁琐的环境准备,一条命令就可以轻松部署上线。
### 1. 导入数据库
首先我们需要创建一个 MySQL 容器,并导入初始数据库。
## 项目文档
chatgpt-plus v3.2.0 一键部署脚本来了,真的只需运行一条命令,就可以完成部署:
```shell
cd docker/mysql
# 创建 mysql 容器
docker-compose up -d
# 导入数据库
docker exec -i chatgpt-plus-mysql sh -c 'exec mysql -uroot -p12345678' < ../../database/chatgpt_plus-v3.1.8.sql
bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v3.2.0-24e4849229.sh)"
```
目前只支持 Ubuntu 系统,推荐 Ubuntu 22.04 LTS
如果你本地已经安装了 MySQL 服务,那么你只需手动导入数据库即可
```shell
# 连接数据库
mysql -u username -p password
# 导入数据库
source database/chatgpt_plus.sql
```
### 2. 修改配置文档
修改配置文档 `docker/conf/config.toml` 配置文档,修改代理地址和管理员密码:
```toml
Listen = "0.0.0.0:5678"
ProxyURL = "" # 如 http://127.0.0.1:7777
MysqlDns = "root:12345678@tcp(172.22.11.200:3307)/chatgpt_plus?charset=utf8&parseTime=True&loc=Local"
StaticDir = "./static" # 静态资源的目录
StaticUrl = "/static" # 静态资源访问 URL
AesEncryptKey = ""
WeChatBot = false # 是否启动微信机器人
[Session]
SecretKey = "azyehq3ivunjhbntz78isj00i4hz2mt9xtddysfucxakadq4qbfrt0b7q3lnvg80" # 注意:这个是 JWT Token 授权密钥,生产环境请务必更换
MaxAge = 86400
[Manager]
Username = "admin"
Password = "admin123" # 如果是生产环境的话,这里管理员的密码记得修改
[Redis] # redis 配置信息
Host = "localhost"
Port = 6379
Password = ""
DB = 0
[ApiConfig] # 微博热搜,今日头条等函数服务 API 配置,此为第三方插件服务,如需使用请联系作者开通
ApiURL = ""
AppId = ""
Token = ""
[SmsConfig] # 阿里云短信服务配置
AccessKey = ""
AccessSecret = ""
Product = "Dysmsapi"
Domain = "dysmsapi.aliyuncs.com"
[ExtConfig] # MidJourney和微信机器人服务 API 配置,开通此功能需要配合 chatpgt-plus-exts 项目部署
ApiURL = "" # 插件扩展 API 地址
Token = "" # 这个 token 随便填,只要确保跟 chatgpt-plus-exts 项目的 token 一样就行
[OSS] # OSS 配置,用于存储 MJ 绘画图片
Active = "local" # 默认使用本地文件存储引擎
[OSS.Local]
BasePath = "./static/upload" # 本地文件上传根路径
BaseURL = "http://localhost:5678/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
[MjConfig] # MidJourney AI 绘画配置
Enabled = false # 是否启动 MidJourney 机器人服务
UserToken = "" # 用户授权 Token
BotToken = "" # Discord 机器人 Token
GuildId = "" # 服务器 ID
ChanelId = "" # 频道 ID
[SdConfig]
Enabled = false # 是否启动 Stable Diffusion 机器人服务
ApiURL = "http://172.22.11.200:7860" # stable-diffusion-webui API 地址
ApiKey = "" # 如果开启了授权,这里需要配置授权的 ApiKey
Txt2ImgJsonPath = "res/text2img.json" # 文生图的 API 请求报文 json 模板允许自定义请求json报文因为不同版本的 API 绘图的参数以及 fn_index 会不同。
[XXLConfig] # xxl-job 配置,需要你部署 XXL-JOB 定时任务工具,用来定期清理未支付订单和清理过期 VIP如果你没有启用支付服务则该服务也无需启动
Enabled = false # 是否启用 XXL JOB 服务
ServerAddr = "http://172.22.11.47:8080/xxl-job-admin" # xxl-job-admin 管理地址
ExecutorIp = "172.22.11.47" # 执行器 IP 地址
ExecutorPort = "9999" # 执行器服务端口
AccessToken = "xxl-job-api-token" # 执行器 API 通信 token
RegistryKey = "chatgpt-plus" # 任务注册 key
[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 = "http://r9it.com:6004/api/payment/alipay/notify" # 支付异步回调地址
```
> 1. 如果你不知道如何获取 Discord 用户 Token 和 Bot Token
请查参考 [Midjourney如何集成到自己的平台](https://zhuanlan.zhihu.com/p/631079476)。
> 2. `Txt2ImgJsonPath`
的默认用的是使用最广泛的 [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) 项目的
API如果你用的是其他版本比如秋叶的懒人包部署的那么请将对应的 text2img 的参数报文复制放在 `res/text2img.json`
文件中即可。
修改 nginx 配置文档 `docker/conf/nginx/conf.d/chatgpt-plus.conf`,把后端转发的地址改成当前主机的内网 IP 地址。
```shell
# 这里配置后端 API 的转发
location /api/ {
proxy_http_version 1.1;
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
proxy_send_timeout 12s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
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://172.28.173.76:6789; # 这里改成后端服务的内网 IP 地址
# 静态资源转发
location /static/ {
proxy_pass http://172.22.11.47:5678; # 这里改成后端服务的内网 IP 地址
}
}
```
### 3. 启动应用
先修改 `docker/docker-compose.yaml` 文件中的镜像地址,改成最新的版本:
```yaml
version: '3'
services:
# 后端 API 镜像
chatgpt-plus-api:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:v3.1.8 #这里改成最新的 release 版本
container_name: chatgpt-plus-api
restart: always
environment:
- DEBUG=false
- LOG_LEVEL=info
- CONFIG_FILE=config.toml
ports:
- "5678:5678"
volumes:
- /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime
- ./conf/config.toml:/var/www/app/config.toml
- ./logs:/var/www/app/logs
- ./static:/var/www/app/static
# 前端应用镜像
chatgpt-plus-web:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:v3.1.8 #这里改成最新的 release 版本
container_name: chatgpt-plus-web
restart: always
ports:
- "8080:8080" # 这边是对外的端口,支持 808080和443
volumes:
- ./logs/nginx:/var/log/nginx
- ./conf/nginx/conf.d:/etc/nginx/conf.d
- ./conf/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
```
```shell
cd docker
docker-compose up -d
```
* 前端访问地址http://localhost:8080/chat
* 后台管理地址http://localhost:8080/admin
* 移动端地址http://localhost:8080/mobile
> 注意:你得访问后台管理系统 http://localhost:8080/admin
> 输入你前面配置文档中设置的管理员用户名和密码登录。
> 然后进入 `API KEY 管理` 菜单,添加一个 OpenAI 的 API KEY 才可以正常开启 AI 对话。
![add API Key](docs/imgs/apikey_add.png)
最后进入前端聊天页面 [http://localhost:8080/chat](http://localhost:8080/chat)
你可以注册新用户,也可以使用系统默认有个账号:`18575670125/12345678` 登录聊天。
祝你使用愉快!!!
## 本地开发调试
本地开发同样要分别运行前端和后端程序。
### 运行后端程序
1. 同样你首先要 [导入数据库](#1-导入数据库)
2. 然后 [修改配置文档](#2-修改配置文档)
3. 运行后端程序:
```shell
cd api
# 1. 先下载依赖
go mod tidy
# 2. 运行程序
go run main.go
# 如果你安装了 fresh 可以使用 fresh 实现热启动
fresh -c fresh.conf
```
### 运行前端程序
同样先拷贝配置文档:
```shell
cd web
cp .env.production .env.development
```
编辑 `.env.development` 文件,修改后端 API 的访问路径:
```ini
VUE_APP_API_HOST=http://localhost:5678
VUE_APP_WS_HOST=ws://localhost:5678
```
配置好了之后就可以运行前端应用了:
```
# 安装依赖
npm install
# 运行
npm run dev
```
* 前端页面http://localhost:8888/chat
* 后台管理页面http://localhost:8888/admin
## 项目打包
由于本项目是采用异构开发的方式,所项目打包分成两步:首先编译后端程序,然后再打包前端应用。
### 打包前端
```shell
cd web
npm run build
```
### 打包后端
你可以根据个人需求将项目打包成 windows/linux/darwin 平台项目。
```shell
cd api
# for all platforms
make clean all
# for linux only
make clean linux
```
打包后的可执行文件在 `bin` 目录下。
详细部署文档请参考 [ChatGPT-Plus 文档](https://ai.r9it.com/docs/)
## 参与贡献
@@ -421,7 +106,7 @@ make clean linux
![微信名片](docs/imgs/wx.png)
#### 特此声明:不接受在微信或者微信群给开发者提 Bug有问题或者优化建议请提交 Issue 和 PR。非常感谢您的配合
#### 特此声明:由于个人时间有限,不接受在微信或者微信群给开发者提 Bug有问题或者优化建议请提交 Issue 和 PR。非常感谢您的配合
### Commit 类型
@@ -437,8 +122,7 @@ make clean linux
如果你觉得这个项目对你有帮助,并且情况允许的话,可以请作者喝杯咖啡,非常感谢你的支持~
![支付宝打赏](docs/imgs/alipay.png)
![微信打赏](docs/imgs/wechat-pay.png)
![打赏](docs/imgs/donate.png)
![Star History Chart](https://api.star-history.com/svg?repos=yangjian102621/chatgpt-plus&type=Date)

View File

@@ -33,10 +33,6 @@ WeChatBot = false
Sign = ""
CodeTempId = ""
[ExtConfig] # MidJourney和微信机器人服务 API 配置,开通此功能需要配合 chatpgt-plus-exts 项目部署
ApiURL = "" # 插件扩展 API 地址
Token = "" # 这个 token 随便填,只要确保跟 chatgpt-plus-exts 项目的 token 一样就行
[OSS] # OSS 配置,用于存储 MJ 绘画图片
Active = "local" # 默认使用本地文件存储引擎
[OSS.Local]

View File

@@ -1,6 +1,7 @@
package core
import (
"bytes"
"chatplus/core/types"
"chatplus/service/fun"
"chatplus/store/model"
@@ -11,9 +12,14 @@ import (
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
"github.com/nfnt/resize"
"gorm.io/gorm"
"image"
"image/jpeg"
"io"
"log"
"net/http"
"os"
"runtime/debug"
"strings"
"time"
@@ -57,7 +63,9 @@ func (s *AppServer) Init(debug bool, client *redis.Client) {
logger.Info("Enabled debug mode")
}
s.Engine.Use(corsMiddleware())
s.Engine.Use(staticResourceMiddleware())
s.Engine.Use(authorizeMiddleware(s, client))
s.Engine.Use(parameterHandlerMiddleware())
s.Engine.Use(errorHandler)
// 添加静态资源访问
s.Engine.Static("/static", s.Config.StaticDir)
@@ -139,15 +147,14 @@ func corsMiddleware() gin.HandlerFunc {
func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.URL.Path == "/api/user/login" ||
c.Request.URL.Path == "/api/user/resetPass" ||
c.Request.URL.Path == "/api/admin/login" ||
c.Request.URL.Path == "/api/user/register" ||
c.Request.URL.Path == "/api/reward/notify" ||
c.Request.URL.Path == "/api/mj/notify" ||
c.Request.URL.Path == "/api/chat/history" ||
c.Request.URL.Path == "/api/chat/detail" ||
c.Request.URL.Path == "/api/role/list" ||
c.Request.URL.Path == "/api/mj/jobs" ||
c.Request.URL.Path == "/api/mj/proxy" ||
c.Request.URL.Path == "/api/invite/hits" ||
c.Request.URL.Path == "/api/sd/jobs" ||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
@@ -211,3 +218,126 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
c.Set(types.LoginUserID, claims["user_id"])
}
}
// 统一参数处理
func parameterHandlerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// GET 参数处理
params := c.Request.URL.Query()
for key, values := range params {
for i, value := range values {
params[key][i] = strings.TrimSpace(value)
}
}
// 更新参数
c.Request.URL.RawQuery = params.Encode()
contentType := c.Request.Header.Get("Content-Type")
if strings.Contains(contentType, "multipart/form-data") {
c.Next()
return
}
// POST JSON 参数处理
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
c.Next()
return
}
// 还原请求体
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 将请求体解析为 JSON
var jsonData map[string]interface{}
if err := c.ShouldBindJSON(&jsonData); err != nil {
c.Next()
return
}
// 对 JSON 数据中的字符串值去除两端空格
trimJSONStrings(jsonData)
// 更新请求体
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
c.Next()
}
}
// 递归对 JSON 数据中的字符串值去除两端空格
func trimJSONStrings(data interface{}) {
switch v := data.(type) {
case map[string]interface{}:
for key, value := range v {
switch valueType := value.(type) {
case string:
v[key] = strings.TrimSpace(valueType)
case map[string]interface{}, []interface{}:
trimJSONStrings(value)
}
}
case []interface{}:
for i, value := range v {
switch valueType := value.(type) {
case string:
v[i] = strings.TrimSpace(valueType)
case map[string]interface{}, []interface{}:
trimJSONStrings(value)
}
}
}
}
// 静态资源中间件
func staticResourceMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
url := c.Request.URL.String()
// 拦截生成缩略图请求
if strings.HasPrefix(url, "/static/") && strings.Contains(url, "?imageView2") {
r := strings.SplitAfter(url, "imageView2")
size := strings.Split(r[1], "/")
if len(size) != 8 {
c.String(http.StatusNotFound, "invalid thumb args")
return
}
with := utils.IntValue(size[3], 0)
height := utils.IntValue(size[5], 0)
quality := utils.IntValue(size[7], 75)
// 打开图片文件
filePath := strings.TrimLeft(c.Request.URL.Path, "/")
file, err := os.Open(filePath)
if err != nil {
c.String(http.StatusNotFound, "Image not found")
return
}
defer file.Close()
// 解码图片
img, _, err := image.Decode(file)
if err != nil {
c.String(http.StatusInternalServerError, "Error decoding image")
return
}
var newImg image.Image
if height == 0 || with == 0 {
// 固定宽度,高度自适应
newImg = resize.Resize(uint(with), uint(height), img, resize.Lanczos3)
} else {
// 生成缩略图
newImg = resize.Thumbnail(uint(with), uint(height), img, resize.Lanczos3)
}
var buffer bytes.Buffer
err = jpeg.Encode(&buffer, newImg, &jpeg.Options{Quality: quality})
if err != nil {
log.Fatal(err)
}
// 直接输出图像数据流
c.Data(http.StatusOK, "image/jpeg", buffer.Bytes())
return
}
c.Next()
}
}

View File

@@ -124,6 +124,11 @@ type UserChatConfig struct {
ApiKeys map[Platform]string `json:"api_keys"`
}
type InviteReward struct {
ChatCalls int `json:"chat_calls"`
ImgCalls int `json:"img_calls"`
}
type ModelAPIConfig struct {
ApiURL string `json:"api_url,omitempty"`
Temperature float32 `json:"temperature"`
@@ -132,19 +137,22 @@ type ModelAPIConfig struct {
}
type SystemConfig struct {
Title string `json:"title"`
AdminTitle string `json:"admin_title"`
Models []string `json:"models"`
UserInitCalls int `json:"user_init_calls"` // 新用户注册默认总送多少次调用
InitImgCalls int `json:"init_img_calls"`
VipMonthCalls int `json:"vip_month_calls"` // 会员每个赠送的调用次数
EnabledRegister bool `json:"enabled_register"`
EnabledMsg bool `json:"enabled_msg"` // 启用短信验证码服务
EnabledDraw bool `json:"enabled_draw"` // 启动 AI 绘画功能
RewardImg string `json:"reward_img"` // 众筹收款二维码地址
EnabledFunction bool `json:"enabled_function"` // 启用 API 函数功能
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
EnabledAlipay bool `json:"enabled_alipay"` // 是否启用支付宝支付通道
OrderPayTimeout int `json:"order_pay_timeout"` //订单支付超时时间
DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型
Title string `json:"title"`
AdminTitle string `json:"admin_title"`
Models []string `json:"models"`
InitChatCalls int `json:"init_chat_calls"` // 新用户注册赠送对话次数
InitImgCalls int `json:"init_img_calls"` // 新用户注册赠送绘图次数
VipMonthCalls int `json:"vip_month_calls"` // 会员每个赠送的调用次数
EnabledRegister bool `json:"enabled_register"` // 是否启用注册功能,关闭注册功能之后将无法注册
EnabledMsg bool `json:"enabled_msg"` // 是否启用短信验证码服务
RewardImg string `json:"reward_img"` // 众筹收款二维码地址
EnabledFunction bool `json:"enabled_function"` // 启用 API 函数功能
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
EnabledAlipay bool `json:"enabled_alipay"` // 是否启用支付宝支付通道
OrderPayTimeout int `json:"order_pay_timeout"` //订单支付超时时间
DefaultModels []string `json:"default_models"` // 默认开通的 AI 模型
OrderPayInfoText string `json:"order_pay_info_text"` // 订单支付页面说明文字
InviteChatCalls int `json:"invite_chat_calls"` // 邀请用户注册奖励对话次数
InviteImgCalls int `json:"invite_img_calls"` // 邀请用户注册奖励绘图次数
ForceInvite bool `json:"force_invite"` // 是否强制必须使用邀请码才能注册
}

View File

@@ -23,10 +23,10 @@ type Property struct {
}
const (
FuncZaoBao = "zao_bao" // 每日早报
FuncHeadLine = "headline" // 今日头条
FuncWeibo = "weibo_hot" // 微博热搜
FuncMidJourney = "mid_journey" // MJ 绘画
FuncZaoBao = "zao_bao" // 每日早报
FuncHeadLine = "headline" // 今日头条
FuncWeibo = "weibo_hot" // 微博热搜
FuncImage = "draw_image" // AI 绘画
)
var InnerFunctions = []Function{
@@ -76,14 +76,14 @@ var InnerFunctions = []Function{
},
{
Name: FuncMidJourney,
Description: "AI 绘画工具,使用 MJ MidJourney API 进行 AI 绘画",
Name: FuncImage,
Description: "AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画",
Parameters: Parameters{
Type: "object",
Properties: map[string]Property{
"prompt": {
Type: "string",
Description: "提示词,如果该参数中有中文的话,则需要翻译成英文。提示词中的参数作为提示的一部分,不要删除",
Description: "提示词,如果该参数中有中文的话,则需要翻译成英文。",
},
},
Required: []string{},

View File

@@ -19,7 +19,6 @@ require (
github.com/qiniu/go-sdk/v7 v7.17.1
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/smartwalle/alipay/v3 v3.2.15
github.com/syndtr/goleveldb v1.0.0
go.uber.org/zap v1.23.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/driver/mysql v1.4.7
@@ -87,7 +86,6 @@ require (
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect

View File

@@ -29,7 +29,6 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/eatmoreapple/openwechat v1.2.1 h1:ez4oqF/Y2NSEX/DbPV8lvj7JlfkYqvieeo4awx5lzfU=
github.com/eatmoreapple/openwechat v1.2.1/go.mod h1:61HOzTyvLobGdgWhL68jfGNwTJEv0mhQ1miCXQrvWU8=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
@@ -68,12 +67,8 @@ github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJ
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -89,7 +84,6 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imroc/req/v3 v3.37.2 h1:vEemuA0cq9zJ6lhe+mSRhsZm951bT0CdiSH47+KTn6I=
github.com/imroc/req/v3 v3.37.2/go.mod h1:DECzjVIrj6jcUr5n6e+z0ygmCO93rx4Jy0RjOEe1YCI=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -138,12 +132,9 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
@@ -199,8 +190,6 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
@@ -242,7 +231,6 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -251,13 +239,11 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -304,15 +290,12 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -5,13 +5,10 @@ import (
"chatplus/core/types"
"chatplus/handler"
logger2 "chatplus/logger"
"chatplus/store/model"
"chatplus/utils"
"chatplus/utils/resp"
"context"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
"strings"
"time"
"github.com/gin-gonic/gin"
@@ -82,67 +79,3 @@ func (h *ManagerHandler) Session(c *gin.Context) {
resp.SUCCESS(c)
}
}
// Migrate 数据修正
func (h *ManagerHandler) Migrate(c *gin.Context) {
opt := c.Query("opt")
switch opt {
case "user":
// 将用户订阅角色的数据结构从 map 改成数组
var users []model.User
h.db.Find(&users)
for _, u := range users {
var m map[string]int
var roleKeys = make([]string, 0)
err := utils.JsonDecode(u.ChatRoles, &m)
if err != nil {
continue
}
for k := range m {
roleKeys = append(roleKeys, k)
}
u.ChatRoles = utils.JsonEncode(roleKeys)
h.db.Updates(&u)
}
break
case "role":
// 修改角色图片,改成绝对路径
var roles []model.ChatRole
h.db.Find(&roles)
for _, r := range roles {
if !strings.HasPrefix(r.Icon, "/") {
r.Icon = "/" + r.Icon
h.db.Updates(&r)
}
}
break
case "history":
// 修改角色图片,改成绝对路径
var message []model.HistoryMessage
h.db.Find(&message)
for _, r := range message {
if !strings.HasPrefix(r.Icon, "/") {
r.Icon = "/" + r.Icon
h.db.Updates(&r)
}
}
break
case "avatar":
// 更新用户的头像地址
var users []model.User
h.db.Find(&users)
for _, u := range users {
if !strings.HasPrefix(u.Avatar, "/") {
u.Avatar = "/" + u.Avatar
h.db.Updates(&u)
}
}
break
}
resp.SUCCESS(c, "SUCCESS")
}

View File

@@ -27,6 +27,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
var data struct {
Id uint `json:"id"`
Platform string `json:"platform"`
Type string `json:"type"`
Value string `json:"value"`
}
if err := c.ShouldBindJSON(&data); err != nil {
@@ -40,7 +41,8 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
}
apiKey.Platform = data.Platform
apiKey.Value = data.Value
res := h.db.Debug().Save(&apiKey)
apiKey.Type = data.Type
res := h.db.Save(&apiKey)
if res.Error != nil {
resp.ERROR(c, "更新数据库失败!")
return

View File

@@ -31,6 +31,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
Value string `json:"value"`
Enabled bool `json:"enabled"`
SortNum int `json:"sort_num"`
Open bool `json:"open"`
Platform string `json:"platform"`
Weight int `json:"weight"`
CreatedAt int64 `json:"created_at"`
@@ -40,7 +41,14 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
return
}
item := model.ChatModel{Platform: data.Platform, Name: data.Name, Value: data.Value, Enabled: data.Enabled, SortNum: data.SortNum, Weight: data.Weight}
item := model.ChatModel{
Platform: data.Platform,
Name: data.Name,
Value: data.Value,
Enabled: data.Enabled,
SortNum: data.SortNum,
Open: data.Open,
Weight: data.Weight}
item.Id = data.Id
if item.Id > 0 {
item.CreatedAt = time.Unix(data.CreatedAt, 0)
@@ -89,10 +97,11 @@ func (h *ChatModelHandler) List(c *gin.Context) {
resp.SUCCESS(c, cms)
}
func (h *ChatModelHandler) Enable(c *gin.Context) {
func (h *ChatModelHandler) Set(c *gin.Context) {
var data struct {
Id uint `json:"id"`
Enabled bool `json:"enabled"`
Id uint `json:"id"`
Filed string `json:"filed"`
Value interface{} `json:"value"`
}
if err := c.ShouldBindJSON(&data); err != nil {
@@ -100,7 +109,7 @@ func (h *ChatModelHandler) Enable(c *gin.Context) {
return
}
res := h.db.Model(&model.ChatModel{}).Where("id = ?", data.Id).Update("enabled", data.Enabled)
res := h.db.Model(&model.ChatModel{}).Where("id = ?", data.Id).Update(data.Filed, data.Value)
if res.Error != nil {
resp.ERROR(c, "更新数据库失败!")
return

View File

@@ -2,6 +2,7 @@ package admin
import (
"chatplus/core"
"chatplus/core/types"
"chatplus/handler"
"chatplus/store/model"
"chatplus/utils/resp"
@@ -22,10 +23,10 @@ func NewDashboardHandler(app *core.AppServer, db *gorm.DB) *DashboardHandler {
}
type statsVo struct {
Users int64 `json:"users"`
Chats int64 `json:"chats"`
Tokens int64 `json:"tokens"`
Rewards float64 `json:"rewards"`
Users int64 `json:"users"`
Chats int64 `json:"chats"`
Tokens int `json:"tokens"`
Income float64 `json:"income"`
}
func (h *DashboardHandler) Stats(c *gin.Context) {
@@ -47,17 +48,24 @@ func (h *DashboardHandler) Stats(c *gin.Context) {
}
// tokens took stats
var tokenCount int64
res = h.db.Model(&model.HistoryMessage{}).Select("sum(tokens) as total").Where("created_at > ?", zeroTime).Scan(&tokenCount)
if res.Error == nil {
stats.Tokens = tokenCount
var historyMessages []model.HistoryMessage
res = h.db.Where("created_at > ?", zeroTime).Find(&historyMessages)
for _, item := range historyMessages {
stats.Tokens += item.Tokens
}
// reward revenue
var amount float64
res = h.db.Model(&model.Reward{}).Select("sum(amount) as total").Where("created_at > ?", zeroTime).Scan(&amount)
if res.Error == nil {
stats.Rewards = amount
// 众筹收入
var rewards []model.Reward
res = h.db.Where("created_at > ?", zeroTime).Find(&rewards)
for _, item := range rewards {
stats.Income += item.Amount
}
// 订单收入
var orders []model.Order
res = h.db.Where("status = ?", types.OrderPaidSuccess).Where("created_at > ?", zeroTime).Find(&orders)
for _, item := range orders {
stats.Income += item.Amount
}
resp.SUCCESS(c, stats)
}

View File

@@ -70,6 +70,7 @@ func (h *UserHandler) Save(c *gin.Context) {
ChatModels []string `json:"chat_models"`
ExpiredTime string `json:"expired_time"`
Status bool `json:"status"`
Vip bool `json:"vip"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -86,6 +87,7 @@ func (h *UserHandler) Save(c *gin.Context) {
"calls": data.Calls,
"img_calls": data.ImgCalls,
"status": data.Status,
"vip": data.Vip,
"chat_roles_json": utils.JsonEncode(data.ChatRoles),
"chat_models_json": utils.JsonEncode(data.ChatModels),
"expired_time": utils.Str2stamp(data.ExpiredTime),

View File

@@ -49,3 +49,11 @@ func (h *BaseHandler) GetUserKey(c *gin.Context) string {
}
return fmt.Sprintf("users/%v", userId)
}
func (h *BaseHandler) GetLoginUserId(c *gin.Context) uint {
userId, ok := c.Get(types.LoginUserID)
if !ok {
return 0
}
return uint(utils.IntValue(utils.InterfaceToString(userId), 0))
}

View File

@@ -39,7 +39,10 @@ func (h *ChatModelHandler) List(c *gin.Context) {
return
}
res := h.db.Where("enabled = ?", true).Where("value IN ?", models).Order("sort_num ASC").Find(&items)
// 查询用户有权限访问的模型以及所有开放的模型
res := h.db.Where("enabled = ?", true).Where(
h.db.Where("value IN ?", models).Or("open =?", true),
).Order("sort_num ASC").Find(&items)
if res.Error == nil {
for _, item := range items {
var cm vo.ChatModel

View File

@@ -127,12 +127,12 @@ func (h *ChatHandler) sendAzureMessage(
logger.Debugf("函数名称: %s, 函数参数:%s", functionName, params)
// for creating image, check if the user's img_calls > 0
if functionName == types.FuncMidJourney && userVo.ImgCalls <= 0 {
if functionName == types.FuncImage && userVo.ImgCalls <= 0 {
utils.ReplyMessage(ws, "**当前用户剩余绘图次数已用尽,请扫描下面二维码联系管理员!**")
utils.ReplyMessage(ws, ErrImg)
} else {
f := h.App.Functions[functionName]
if functionName == types.FuncMidJourney {
if functionName == types.FuncImage {
params["user_id"] = userVo.Id
params["role_id"] = role.Id
params["chat_id"] = session.ChatId
@@ -149,9 +149,8 @@ func (h *ChatHandler) sendAzureMessage(
contents = append(contents, msg)
} else {
content := data
if functionName == types.FuncMidJourney {
content = fmt.Sprintf("绘画提示词:%s 已推送任务到 MidJourney 机器人,请耐心等待任务执行...", data)
h.mjService.ChatClients.Put(session.SessionId, ws)
if functionName == types.FuncImage {
content = fmt.Sprintf("下面是根据您的描述创作的图片,他们描绘了 【%s】 的场景", params["prompt"])
// update user's img_calls
h.db.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
}

View File

@@ -84,6 +84,11 @@ func (h *ChatHandler) sendBaiduMessage(
content = line[5:]
}
// 处理代码换行
if len(content) == 0 {
content = "\n"
}
var resp baiduResp
err := utils.JsonDecode(content, &resp)
if err != nil {

View File

@@ -7,7 +7,6 @@ import (
"chatplus/handler"
logger2 "chatplus/logger"
"chatplus/service/mj"
"chatplus/store"
"chatplus/store/model"
"chatplus/store/vo"
"chatplus/utils"
@@ -34,15 +33,13 @@ var logger = logger2.GetLogger()
type ChatHandler struct {
handler.BaseHandler
db *gorm.DB
leveldb *store.LevelDB
redis *redis.Client
mjService *mj.Service
}
func NewChatHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB, redis *redis.Client, service *mj.Service) *ChatHandler {
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, service *mj.Service) *ChatHandler {
h := ChatHandler{
db: db,
leveldb: levelDB,
redis: redis,
mjService: service,
}
@@ -227,9 +224,6 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
if h.App.SysConfig.EnabledFunction {
var functions = make([]types.Function, 0)
for _, f := range types.InnerFunctions {
if !h.App.SysConfig.EnabledDraw && f.Name == types.FuncMidJourney {
continue
}
functions = append(functions, f)
}
req.Functions = functions
@@ -258,17 +252,15 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
// loading the role context
var messages []types.Message
if len(messages) > 0 {
err := utils.JsonDecode(role.Context, &messages)
if err == nil {
for _, v := range messages {
tks, _ := utils.CalcTokens(v.Content, req.Model)
if tokens+tks >= types.ModelToTokens[req.Model] {
break
}
tokens += tks
chatCtx = append(chatCtx, v)
err := utils.JsonDecode(role.Context, &messages)
if err == nil {
for _, v := range messages {
tks, _ := utils.CalcTokens(v.Content, req.Model)
if tokens+tks >= types.ModelToTokens[req.Model] {
break
}
tokens += tks
chatCtx = append(chatCtx, v)
}
}
@@ -410,7 +402,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
}
if *apiKey == "" {
var key model.ApiKey
res := h.db.Where("platform = ?", platform).Order("last_used_at ASC").First(&key)
res := h.db.Where("platform = ? AND type = ?", platform, "chat").Order("last_used_at ASC").First(&key)
if res.Error != nil {
return nil, errors.New("no available key, please import key")
}

View File

@@ -71,6 +71,10 @@ func (h *ChatHandler) sendChatGLMMessage(
if strings.HasPrefix(line, "data:") {
content = line[5:]
}
// 处理代码换行
if len(content) == 0 {
content = "\n"
}
switch event {
case "add":
if len(contents) == 0 {

View File

@@ -126,12 +126,12 @@ func (h *ChatHandler) sendOpenAiMessage(
logger.Debugf("函数名称: %s, 函数参数:%s", functionName, params)
// for creating image, check if the user's img_calls > 0
if functionName == types.FuncMidJourney && userVo.ImgCalls <= 0 {
if functionName == types.FuncImage && userVo.ImgCalls <= 0 {
utils.ReplyMessage(ws, "**当前用户剩余绘图次数已用尽,请扫描下面二维码联系管理员!**")
utils.ReplyMessage(ws, ErrImg)
} else {
f := h.App.Functions[functionName]
if functionName == types.FuncMidJourney {
if functionName == types.FuncImage {
params["user_id"] = userVo.Id
params["role_id"] = role.Id
params["chat_id"] = session.ChatId
@@ -148,9 +148,8 @@ func (h *ChatHandler) sendOpenAiMessage(
contents = append(contents, msg)
} else {
content := data
if functionName == types.FuncMidJourney {
content = fmt.Sprintf("绘画提示词:%s 已推送任务到 MidJourney 机器人,请耐心等待任务执行...", data)
h.mjService.ChatClients.Put(session.SessionId, ws)
if functionName == types.FuncImage {
content = fmt.Sprintf("下面是根据您的描述创作的图片,他们描绘了 【%s】 的场景。%s", params["prompt"], data)
// update user's img_calls
h.db.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
}

View File

@@ -48,6 +48,12 @@ type xunFeiResp struct {
} `json:"payload"`
}
var Model2URL = map[string]string{
"generalv1": "1.1",
"generalv2": "v2.1",
"generalv3": "v3.1",
}
// 科大讯飞消息发送实现
func (h *ChatHandler) sendXunFeiMessage(
@@ -63,7 +69,7 @@ func (h *ChatHandler) sendXunFeiMessage(
var apiKey = userVo.ChatConfig.ApiKeys[session.Model.Platform]
if apiKey == "" {
var key model.ApiKey
res := h.db.Where("platform = ?", session.Model.Platform).Order("last_used_at ASC").First(&key)
res := h.db.Where("platform = ? AND type = ?", session.Model.Platform, "chat").Order("last_used_at ASC").First(&key)
if res.Error != nil {
utils.ReplyMessage(ws, "抱歉😔😔😔,系统已经没有可用的 API KEY请联系管理员")
return nil
@@ -82,13 +88,7 @@ func (h *ChatHandler) sendXunFeiMessage(
return nil
}
var apiURL string
if req.Model == "generalv2" {
apiURL = strings.Replace(h.App.ChatConfig.XunFei.ApiURL, "{version}", "v2.1", 1)
} else {
apiURL = strings.Replace(h.App.ChatConfig.XunFei.ApiURL, "{version}", "v1.1", 1)
}
apiURL := strings.Replace(h.App.ChatConfig.XunFei.ApiURL, "{version}", Model2URL[req.Model], 1)
wsURL, err := assembleAuthUrl(apiURL, key[1], key[2])
//握手并建立websocket 连接
conn, resp, err := d.Dial(wsURL, nil)
@@ -138,6 +138,10 @@ func (h *ChatHandler) sendXunFeiMessage(
}
content = result.Payload.Choices.Text[0].Content
// 处理代码换行
if len(content) == 0 {
content = "\n"
}
contents = append(contents, content)
// 第一个结果
if result.Payload.Choices.Status == 0 {

View File

@@ -0,0 +1,96 @@
package handler
import (
"chatplus/core"
"chatplus/core/types"
"chatplus/store/model"
"chatplus/store/vo"
"chatplus/utils"
"chatplus/utils/resp"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"strings"
)
// InviteHandler 用户邀请
type InviteHandler struct {
BaseHandler
db *gorm.DB
}
func NewInviteHandler(app *core.AppServer, db *gorm.DB) *InviteHandler {
h := InviteHandler{db: db}
h.App = app
return &h
}
// Code 获取当前用户邀请码
func (h *InviteHandler) Code(c *gin.Context) {
userId := h.GetLoginUserId(c)
var inviteCode model.InviteCode
res := h.db.Where("user_id = ?", userId).First(&inviteCode)
// 如果邀请码不存在,则创建一个
if res.Error != nil {
code := strings.ToUpper(utils.RandString(8))
for {
res = h.db.Where("code = ?", code).First(&inviteCode)
if res.Error != nil { // 不存在相同的邀请码则退出
break
}
}
inviteCode.UserId = userId
inviteCode.Code = code
h.db.Create(&inviteCode)
}
var codeVo vo.InviteCode
err := utils.CopyObject(inviteCode, &codeVo)
if err != nil {
resp.ERROR(c, "拷贝对象失败")
return
}
resp.SUCCESS(c, codeVo)
}
// List Log 用户邀请记录
func (h *InviteHandler) List(c *gin.Context) {
var data struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
userId := h.GetLoginUserId(c)
session := h.db.Session(&gorm.Session{}).Where("inviter_id = ?", userId)
var total int64
session.Model(&model.InviteLog{}).Count(&total)
var items []model.InviteLog
var list = make([]vo.InviteLog, 0)
offset := (data.Page - 1) * data.PageSize
res := session.Order("id DESC").Offset(offset).Limit(data.PageSize).Find(&items)
if res.Error == nil {
for _, item := range items {
var v vo.InviteLog
err := utils.CopyObject(item, &v)
if err == nil {
v.Id = item.Id
v.CreatedAt = item.CreatedAt.Unix()
list = append(list, v)
} else {
logger.Error(err)
}
}
}
resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, list))
}
// Hits 访问邀请码
func (h *InviteHandler) Hits(c *gin.Context) {
code := c.Query("code")
h.db.Model(&model.InviteCode{}).Where("code = ?", code).UpdateColumn("hits", gorm.Expr("hits + ?", 1))
resp.SUCCESS(c)
}

View File

@@ -80,6 +80,7 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
var data struct {
SessionId string `json:"session_id"`
Prompt string `json:"prompt"`
NegPrompt string `json:"neg_prompt"`
Rate string `json:"rate"`
Model string `json:"model"`
Chaos int `json:"chaos"`
@@ -87,6 +88,8 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
Seed int64 `json:"seed"`
Stylize int `json:"stylize"`
Img string `json:"img"`
Tile bool `json:"tile"`
Quality float32 `json:"quality"`
Weight float32 `json:"weight"`
}
if err := c.ShouldBindJSON(&data); err != nil {
@@ -119,8 +122,17 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
if data.Raw {
prompt += " --style raw"
}
if data.Quality > 0 {
prompt += fmt.Sprintf(" --q %.2f", data.Quality)
}
if data.NegPrompt != "" {
prompt += fmt.Sprintf(" --no %s", data.NegPrompt)
}
if data.Tile {
prompt += " --tile "
}
if data.Model != "" && !strings.Contains(prompt, "--v") && !strings.Contains(prompt, "--niji") {
prompt += data.Model
prompt += fmt.Sprintf(" %s", data.Model)
}
idValue, _ := c.Get(types.LoginUserID)
@@ -348,8 +360,8 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) {
continue
}
if item.Progress < 100 {
// 30 分钟还没完成的任务直接删除
if time.Now().Sub(item.CreatedAt) > time.Minute*30 {
// 10 分钟还没完成的任务直接删除
if time.Now().Sub(item.CreatedAt) > time.Minute*10 {
h.db.Delete(&item)
continue
}

View File

@@ -4,23 +4,23 @@ import (
"chatplus/core"
"chatplus/core/types"
"chatplus/service"
"chatplus/store"
"chatplus/utils"
"chatplus/utils/resp"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
)
const CodeStorePrefix = "/verify/codes/"
type SmsHandler struct {
BaseHandler
leveldb *store.LevelDB
redis *redis.Client
sms *service.AliYunSmsService
captcha *service.CaptchaService
}
func NewSmsHandler(app *core.AppServer, db *store.LevelDB, sms *service.AliYunSmsService, captcha *service.CaptchaService) *SmsHandler {
handler := &SmsHandler{leveldb: db, sms: sms, captcha: captcha}
func NewSmsHandler(app *core.AppServer, client *redis.Client, sms *service.AliYunSmsService, captcha *service.CaptchaService) *SmsHandler {
handler := &SmsHandler{redis: client, sms: sms, captcha: captcha}
handler.App = app
return handler
}
@@ -50,7 +50,7 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
}
// 存储验证码,等待后面注册验证
err = h.leveldb.Put(CodeStorePrefix+data.Mobile, code)
_, err = h.redis.Set(c, CodeStorePrefix+data.Mobile, code, 0).Result()
if err != nil {
resp.ERROR(c, "验证码保存失败")
return
@@ -58,13 +58,3 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
resp.SUCCESS(c)
}
type statusVo struct {
EnabledMsgService bool `json:"enabled_msg_service"`
EnabledRegister bool `json:"enabled_register"`
}
// Status check if the message service is enabled
func (h *SmsHandler) Status(c *gin.Context) {
resp.SUCCESS(c, statusVo{EnabledMsgService: h.App.SysConfig.EnabledMsg, EnabledRegister: h.App.SysConfig.EnabledRegister})
}

View File

@@ -3,7 +3,6 @@ package handler
import (
"chatplus/core"
"chatplus/core/types"
"chatplus/store"
"chatplus/store/model"
"chatplus/store/vo"
"chatplus/utils"
@@ -23,7 +22,6 @@ type UserHandler struct {
BaseHandler
db *gorm.DB
searcher *xdb.Searcher
leveldb *store.LevelDB
redis *redis.Client
}
@@ -31,9 +29,8 @@ func NewUserHandler(
app *core.AppServer,
db *gorm.DB,
searcher *xdb.Searcher,
levelDB *store.LevelDB,
client *redis.Client) *UserHandler {
handler := &UserHandler{db: db, searcher: searcher, leveldb: levelDB, redis: client}
handler := &UserHandler{db: db, searcher: searcher, redis: client}
handler.App = app
return handler
}
@@ -42,9 +39,10 @@ func NewUserHandler(
func (h *UserHandler) Register(c *gin.Context) {
// parameters process
var data struct {
Mobile string `json:"mobile"`
Password string `json:"password"`
Code int `json:"code"`
Mobile string `json:"mobile"`
Password string `json:"password"`
Code string `json:"code"`
InviteCode string `json:"invite_code"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
@@ -64,14 +62,28 @@ func (h *UserHandler) Register(c *gin.Context) {
// 检查验证码
key := CodeStorePrefix + data.Mobile
if h.App.SysConfig.EnabledMsg {
var code int
err := h.leveldb.Get(key, &code)
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "短信验证码错误")
return
}
}
// 验证邀请码
inviteCode := model.InviteCode{}
if data.InviteCode == "" {
if h.App.SysConfig.ForceInvite {
resp.ERROR(c, "当前系统设定必须使用邀请码才能注册")
return
}
} else {
res := h.db.Where("code = ?", data.InviteCode).First(&inviteCode)
if res.Error != nil {
resp.ERROR(c, "无效的邀请码")
return
}
}
// check if the username is exists
var item model.User
res := h.db.Where("mobile = ?", data.Mobile).First(&item)
@@ -96,7 +108,7 @@ func (h *UserHandler) Register(c *gin.Context) {
types.ChatGLM: "",
},
}),
Calls: h.App.SysConfig.UserInitCalls,
Calls: h.App.SysConfig.InitChatCalls,
ImgCalls: h.App.SysConfig.InitImgCalls,
}
res = h.db.Create(&user)
@@ -106,8 +118,28 @@ func (h *UserHandler) Register(c *gin.Context) {
return
}
// 记录邀请关系
if data.InviteCode != "" {
// 增加邀请数量
h.db.Model(&model.InviteCode{}).Where("code = ?", data.InviteCode).UpdateColumn("reg_num", gorm.Expr("reg_num + ?", 1))
if h.App.SysConfig.InviteChatCalls > 0 {
h.db.Model(&model.User{}).Where("id = ?", inviteCode.UserId).UpdateColumn("calls", gorm.Expr("calls + ?", h.App.SysConfig.InviteChatCalls))
}
if h.App.SysConfig.InviteImgCalls > 0 {
h.db.Model(&model.User{}).Where("id = ?", inviteCode.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", h.App.SysConfig.InviteImgCalls))
}
// 添加邀请记录
h.db.Create(&model.InviteLog{
InviterId: inviteCode.UserId,
UserId: user.Id,
Username: user.Mobile,
InviteCode: inviteCode.Code,
Reward: utils.JsonEncode(types.InviteReward{ChatCalls: h.App.SysConfig.InviteChatCalls, ImgCalls: h.App.SysConfig.InviteImgCalls}),
})
}
if h.App.SysConfig.EnabledMsg {
_ = h.leveldb.Delete(key) // 注册成功,删除短信验证码
_ = h.redis.Del(c, key) // 注册成功,删除短信验证码
}
// 自动登录创建 token
@@ -279,8 +311,8 @@ func (h *UserHandler) ProfileUpdate(c *gin.Context) {
resp.SUCCESS(c)
}
// Password 更新密码
func (h *UserHandler) Password(c *gin.Context) {
// UpdatePass 更新密码
func (h *UserHandler) UpdatePass(c *gin.Context) {
var data struct {
OldPass string `json:"old_pass"`
Password string `json:"password"`
@@ -319,17 +351,65 @@ func (h *UserHandler) Password(c *gin.Context) {
resp.SUCCESS(c)
}
// ResetPass 重置密码
func (h *UserHandler) ResetPass(c *gin.Context) {
var data struct {
Mobile string
Code string // 验证码
Password string // 新密码
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
var user model.User
res := h.db.Where("mobile", data.Mobile).First(&user)
if res.Error != nil {
resp.ERROR(c, "用户不存在!")
return
}
// 检查验证码
key := CodeStorePrefix + data.Mobile
if h.App.SysConfig.EnabledMsg {
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "短信验证码错误")
return
}
}
password := utils.GenPassword(data.Password, user.Salt)
user.Password = password
res = h.db.Updates(&user)
if res.Error != nil {
resp.ERROR(c)
} else {
h.redis.Del(c, key)
resp.SUCCESS(c)
}
}
// BindMobile 绑定手机号
func (h *UserHandler) BindMobile(c *gin.Context) {
var data struct {
Mobile string `json:"mobile"`
Code int `json:"code"`
Code string `json:"code"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
// 检查验证码
key := CodeStorePrefix + data.Mobile
code, err := h.redis.Get(c, key).Result()
if err != nil || code != data.Code {
resp.ERROR(c, "短信验证码错误")
return
}
// 检查手机号是否被其他账号绑定
var item model.User
res := h.db.Where("mobile = ?", data.Mobile).First(&item)
@@ -338,15 +418,6 @@ func (h *UserHandler) BindMobile(c *gin.Context) {
return
}
// 检查验证码
key := CodeStorePrefix + data.Mobile
var code int
err := h.leveldb.Get(key, &code)
if err != nil || code != data.Code {
resp.ERROR(c, "短信验证码错误")
return
}
user, err := utils.GetLoginUser(c, h.db)
if err != nil {
resp.NotAuth(c)
@@ -359,6 +430,6 @@ func (h *UserHandler) BindMobile(c *gin.Context) {
return
}
_ = h.leveldb.Delete(key) // 删除短信验证码
_ = h.redis.Del(c, key) // 删除短信验证码
resp.SUCCESS(c)
}

View File

@@ -94,7 +94,6 @@ func main() {
// 初始化数据库
fx.Provide(store.NewGormConfig),
fx.Provide(store.NewMysql),
fx.Provide(store.NewLevelDB),
fx.Provide(store.NewRedisClient),
fx.Provide(func() embed.FS {
@@ -217,8 +216,9 @@ func main() {
group.GET("session", h.Session)
group.GET("profile", h.Profile)
group.POST("profile/update", h.ProfileUpdate)
group.POST("password", h.Password)
group.POST("password", h.UpdatePass)
group.POST("bind/mobile", h.BindMobile)
group.POST("resetPass", h.ResetPass)
}),
fx.Invoke(func(s *core.AppServer, h *chatimpl.ChatHandler) {
group := s.Engine.Group("/api/chat/")
@@ -237,7 +237,6 @@ func main() {
}),
fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
group := s.Engine.Group("/api/sms/")
group.GET("status", h.Status)
group.POST("code", h.SendCode)
}),
fx.Invoke(func(s *core.AppServer, h *handler.CaptchaHandler) {
@@ -275,7 +274,6 @@ func main() {
group.POST("login", h.Login)
group.GET("logout", h.Logout)
group.GET("session", h.Session)
group.GET("migrate", h.Migrate)
}),
fx.Invoke(func(s *core.AppServer, h *admin.ApiKeyHandler) {
group := s.Engine.Group("/api/admin/apikey/")
@@ -314,7 +312,7 @@ func main() {
group := s.Engine.Group("/api/admin/model/")
group.POST("save", h.Save)
group.GET("list", h.List)
group.POST("enable", h.Enable)
group.POST("set", h.Set)
group.POST("sort", h.Sort)
group.GET("remove", h.Remove)
}),
@@ -347,6 +345,14 @@ func main() {
group.GET("list", h.List)
}),
fx.Provide(handler.NewInviteHandler),
fx.Invoke(func(s *core.AppServer, h *handler.InviteHandler) {
group := s.Engine.Group("/api/invite/")
group.GET("code", h.Code)
group.POST("list", h.List)
group.GET("hits", h.Hits)
}),
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
err := s.Run(db)
if err != nil {

View File

@@ -2,18 +2,16 @@ package service
import (
"chatplus/core/types"
"chatplus/store"
"fmt"
"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
)
type AliYunSmsService struct {
config *types.AliYunSmsConfig
db *store.LevelDB
client *dysmsapi.Client
}
func NewAliYunSmsService(config *types.AppConfig, db *store.LevelDB) (*AliYunSmsService, error) {
func NewAliYunSmsService(config *types.AppConfig) (*AliYunSmsService, error) {
// 创建阿里云短信客户端
client, err := dysmsapi.NewClientWithAccessKey(
"cn-hangzhou",
@@ -25,7 +23,6 @@ func NewAliYunSmsService(config *types.AppConfig, db *store.LevelDB) (*AliYunSms
return &AliYunSmsService{
config: &config.SmsConfig,
db: db,
client: client,
}, nil
}

View File

@@ -0,0 +1,92 @@
package fun
import (
"chatplus/core/types"
"chatplus/service/oss"
"chatplus/store/model"
"chatplus/utils"
"fmt"
"github.com/imroc/req/v3"
"gorm.io/gorm"
)
// AI 绘画函数
type FuncImage struct {
name string
apiURL string
db *gorm.DB
uploadManager *oss.UploaderManager
proxyURL string
}
func NewImageFunc(db *gorm.DB, manager *oss.UploaderManager, config *types.AppConfig) FuncImage {
return FuncImage{
db: db,
name: "DALL-E3 绘画",
uploadManager: manager,
proxyURL: config.ProxyURL,
apiURL: "https://api.openai.com/v1/images/generations",
}
}
type imgReq struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
N int `json:"n"`
Size string `json:"size"`
}
type imgRes struct {
Created int64 `json:"created"`
Data []struct {
RevisedPrompt string `json:"revised_prompt"`
Url string `json:"url"`
} `json:"data"`
}
type ErrRes struct {
Error struct {
Code interface{} `json:"code"`
Message string `json:"message"`
Param interface{} `json:"param"`
Type string `json:"type"`
} `json:"error"`
}
func (f FuncImage) Invoke(params map[string]interface{}) (string, error) {
logger.Infof("绘画参数:%+v", params)
prompt := utils.InterfaceToString(params["prompt"])
// 获取绘图 API KEY
var apiKey model.ApiKey
f.db.Where("platform = ? AND type = ?", types.OpenAI, "img").Order("last_used_at ASC").First(&apiKey)
var res imgRes
var errRes ErrRes
r, err := req.C().SetProxyURL(f.proxyURL).R().SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+apiKey.Value).
SetBody(imgReq{
Model: "dall-e-3",
Prompt: prompt,
N: 1,
Size: "1024x1024",
}).
SetErrorResult(&errRes).
SetSuccessResult(&res).Post(f.apiURL)
if err != nil || r.IsErrorState() {
return "", fmt.Errorf("error with http request: %v%v%s", err, r.Err, errRes.Error.Message)
}
// 存储图片
imgURL, err := f.uploadManager.GetUploadHandler().PutImg(res.Data[0].Url, false)
if err != nil {
return "", fmt.Errorf("下载图片失败: %s", err.Error())
}
logger.Info(imgURL)
return fmt.Sprintf("\n\n![](%s)\n", imgURL), nil
}
func (f FuncImage) Name() string {
return f.name
}
var _ Function = &FuncImage{}

View File

@@ -1,42 +0,0 @@
package fun
import (
"chatplus/core/types"
"chatplus/service/mj"
"chatplus/utils"
)
// AI 绘画函数
type FuncMidJourney struct {
name string
service *mj.Service
}
func NewMidJourneyFunc(mjService *mj.Service) FuncMidJourney {
return FuncMidJourney{
name: "MidJourney AI 绘画",
service: mjService}
}
func (f FuncMidJourney) Invoke(params map[string]interface{}) (string, error) {
logger.Infof("MJ 绘画参数:%+v", params)
prompt := utils.InterfaceToString(params["prompt"])
f.service.PushTask(types.MjTask{
SessionId: utils.InterfaceToString(params["session_id"]),
Src: types.TaskSrcChat,
Type: types.TaskImage,
Prompt: prompt,
UserId: utils.IntValue(utils.InterfaceToString(params["user_id"]), 0),
RoleId: utils.IntValue(utils.InterfaceToString(params["role_id"]), 0),
Icon: utils.InterfaceToString(params["icon"]),
ChatId: utils.InterfaceToString(params["chat_id"]),
})
return prompt, nil
}
func (f FuncMidJourney) Name() string {
return f.name
}
var _ Function = &FuncMidJourney{}

View File

@@ -3,7 +3,8 @@ package fun
import (
"chatplus/core/types"
logger2 "chatplus/logger"
"chatplus/service/mj"
"chatplus/service/oss"
"gorm.io/gorm"
)
type Function interface {
@@ -29,11 +30,11 @@ type dataItem struct {
Remark string `json:"remark"`
}
func NewFunctions(config *types.AppConfig, mjService *mj.Service) map[string]Function {
func NewFunctions(config *types.AppConfig, db *gorm.DB, manager *oss.UploaderManager) map[string]Function {
return map[string]Function{
types.FuncZaoBao: NewZaoBao(config.ApiConfig),
types.FuncWeibo: NewWeiboHot(config.ApiConfig),
types.FuncHeadLine: NewHeadLines(config.ApiConfig),
types.FuncMidJourney: NewMidJourneyFunc(mjService),
types.FuncZaoBao: NewZaoBao(config.ApiConfig),
types.FuncWeibo: NewWeiboHot(config.ApiConfig),
types.FuncHeadLine: NewHeadLines(config.ApiConfig),
types.FuncImage: NewImageFunc(db, manager, config),
}
}

View File

@@ -72,11 +72,19 @@ func (s *Service) Run() {
}
if err != nil {
logger.Error("绘画任务执行失败:", err)
if task.RetryCount <= 5 {
s.taskQueue.RPush(task)
// 删除任务
s.db.Delete(&model.MidJourneyJob{Id: uint(task.Id)})
// 推送任务到前端
client := s.Clients.Get(task.SessionId)
if client != nil {
utils.ReplyChunkMessage(client, vo.MidJourneyJob{
Type: task.Type.String(),
UserId: task.UserId,
MessageId: task.MessageId,
Progress: -1,
Prompt: task.Prompt,
})
}
task.RetryCount += 1
time.Sleep(time.Second * 3)
continue
}

View File

@@ -61,7 +61,7 @@ func (s AliYunOss) PutFile(ctx *gin.Context, name string) (string, error) {
return "", err
}
return fmt.Sprintf("https://%s.%s/%s", s.config.Bucket, s.config.Endpoint, objectKey), nil
return fmt.Sprintf("https://%s.%s/%s", s.config.Bucket, s.config.Domain, objectKey), nil
}
func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
@@ -86,7 +86,7 @@ func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
if err != nil {
return "", err
}
return fmt.Sprintf("https://%s.%s/%s", s.config.Bucket, s.config.Endpoint, objectKey), nil
return fmt.Sprintf("https://%s.%s/%s", s.config.Bucket, s.config.Domain, objectKey), nil
}
func (s AliYunOss) Delete(fileURL string) error {

View File

@@ -288,7 +288,7 @@ func (s *Service) callback(data CBReq) {
jobVo.ImgURL = data.ImageData
}
// 扣减绘图次数
s.db.Where("id = ?", jobVo.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
s.db.Model(&model.User{}).Where("id = ?", jobVo.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls - ?", 1))
// 推送任务到前端
if client != nil {
utils.ReplyChunkMessage(client, jobVo)

View File

@@ -38,13 +38,13 @@ func NewXXLJobExecutor(config *types.AppConfig, db *gorm.DB) *XXLJobExecutor {
}
func (e *XXLJobExecutor) Run() error {
e.executor.RegTask("ClearOrder", e.ClearOrder)
e.executor.RegTask("ClearOrders", e.ClearOrders)
e.executor.RegTask("ResetVipCalls", e.ResetVipCalls)
return e.executor.Run()
}
// ClearOrder 清理未支付的订单,如果没有抛出异常则表示执行成功
func (e *XXLJobExecutor) ClearOrder(cxt context.Context, param *xxl.RunReq) (msg string) {
// ClearOrders 清理未支付的订单,如果没有抛出异常则表示执行成功
func (e *XXLJobExecutor) ClearOrders(cxt context.Context, param *xxl.RunReq) (msg string) {
logger.Debug("执行清理未支付订单...")
var sysConfig model.Config
res := e.db.Where("marker", "system").First(&sysConfig)
@@ -63,7 +63,8 @@ func (e *XXLJobExecutor) ClearOrder(cxt context.Context, param *xxl.RunReq) (msg
}
timeout := time.Now().Unix() - int64(config.OrderPayTimeout)
start := utils.Stamp2str(timeout)
res = e.db.Where("status != ? AND created_at < ?", types.OrderPaidSuccess, start).Delete(&model.Order{})
// 这里不是用软删除,而是永久删除订单
res = e.db.Unscoped().Where("status != ? AND created_at < ?", types.OrderPaidSuccess, start).Delete(&model.Order{})
return fmt.Sprintf("Clear order successfully, affect rows: %d", res.RowsAffected)
}

View File

@@ -1 +0,0 @@
hello, world!

View File

@@ -1,96 +0,0 @@
package store
import (
"chatplus/store/vo"
"encoding/json"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
type LevelDB struct {
driver *leveldb.DB
}
func NewLevelDB() (*LevelDB, error) {
db, err := leveldb.OpenFile("data/leveldb", nil)
if err != nil {
return nil, err
}
return &LevelDB{
driver: db,
}, nil
}
func (db *LevelDB) Put(key string, value interface{}) error {
bytes, err := json.Marshal(value)
if err != nil {
return err
}
return db.driver.Put([]byte(key), bytes, nil)
}
func (db *LevelDB) Get(key string, value interface{}) error {
bytes, err := db.driver.Get([]byte(key), nil)
if err != nil {
return err
}
return json.Unmarshal(bytes, &value)
}
func (db *LevelDB) Search(prefix string) []string {
var items = make([]string, 0)
iter := db.driver.NewIterator(util.BytesPrefix([]byte(prefix)), nil)
defer iter.Release()
for iter.Next() {
items = append(items, string(iter.Value()))
}
return items
}
func (db *LevelDB) SearchPage(prefix string, page int, pageSize int) *vo.Page {
var items = make([]string, 0)
iter := db.driver.NewIterator(util.BytesPrefix([]byte(prefix)), nil)
defer iter.Release()
res := &vo.Page{Page: page, PageSize: pageSize}
// 计算数据总数和总页数
total := 0
for iter.Next() {
total++
}
res.TotalPage = (total + pageSize - 1) / pageSize
res.Total = int64(total)
// 计算目标页码的起始和结束位置
start := (page - 1) * pageSize
if start > total {
return nil
}
end := start + pageSize
if end > total {
end = total
}
// 跳转到目标页码的起始位置
count := 0
for iter.Next() {
if count >= start {
items = append(items, string(iter.Value()))
}
count++
}
iter.Release()
res.Items = items
return res
}
func (db *LevelDB) Delete(key string) error {
return db.driver.Delete([]byte(key), nil)
}
// Close release resources
func (db *LevelDB) Close() error {
return db.driver.Close()
}

View File

@@ -4,6 +4,7 @@ package model
type ApiKey struct {
BaseModel
Platform string
Type string // 用途 chat => 聊天img => 绘图
Value string // API Key 的值
LastUsedAt int64 // 最后使用时间
}

View File

@@ -7,5 +7,6 @@ type ChatModel struct {
Value string // API Key 的值
SortNum int
Enabled bool
Weight int // 对话权重,每次对话扣减多少次对话额度
Weight int // 对话权重,每次对话扣减多少次对话额度
Open bool // 是否开放模型给所有人使用
}

View File

@@ -0,0 +1,12 @@
package model
import "time"
type InviteCode struct {
Id uint `gorm:"primarykey;column:id"`
UserId uint
Code string
Hits int // 点击次数
RegNum int // 注册人数
CreatedAt time.Time
}

View File

@@ -0,0 +1,15 @@
package model
import (
"time"
)
type InviteLog struct {
Id uint `gorm:"primarykey;column:id"`
InviterId uint
UserId uint
Username string
InviteCode string
Reward string `gorm:"column:reward_json"` // 邀请奖励
CreatedAt time.Time
}

View File

@@ -21,6 +21,9 @@ func NewGormConfig() *gorm.Config {
func NewMysql(config *gorm.Config, appConfig *types.AppConfig) (*gorm.DB, error) {
db, err := gorm.Open(mysql.Open(appConfig.MysqlDns), config)
if err != nil {
return nil, err
}
sqlDB, err := db.DB()
if err != nil {
@@ -29,8 +32,6 @@ func NewMysql(config *gorm.Config, appConfig *types.AppConfig) (*gorm.DB, error)
sqlDB.SetMaxIdleConns(32)
sqlDB.SetMaxOpenConns(512)
sqlDB.SetConnMaxLifetime(time.Hour)
if err != nil {
return nil, err
}
return db, nil
}

View File

@@ -4,6 +4,7 @@ package vo
type ApiKey struct {
BaseVo
Platform string `json:"platform"`
Type string `json:"type"`
Value string `json:"value"` // API Key 的值
LastUsedAt int64 `json:"last_used_at"` // 最后使用时间
}

View File

@@ -8,4 +8,5 @@ type ChatModel struct {
Enabled bool `json:"enabled"`
SortNum int `json:"sort_num"`
Weight int `json:"weight"`
Open bool `json:"open"`
}

View File

@@ -0,0 +1,10 @@
package vo
type InviteCode struct {
Id uint `json:"id"`
UserId uint `json:"user_id"`
Code string `json:"code"`
Hits int `json:"hits"`
RegNum int `json:"reg_num"`
CreatedAt int64 `json:"created_at"`
}

View File

@@ -0,0 +1,15 @@
package vo
import (
"chatplus/core/types"
)
type InviteLog struct {
Id uint `json:"id"`
InviterId uint `json:"inviter_id"`
UserId uint `json:"user_id"`
Username string `json:"username"`
InviteCode string `json:"invite_code"`
Reward types.InviteReward `json:"reward"`
CreatedAt int64 `json:"created_at"`
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
"github.com/nfnt/resize"
"github.com/skip2/go-qrcode"
"image"
@@ -14,8 +15,6 @@ import (
"reflect"
"strconv"
"strings"
"github.com/lionsoul2014/ip2region/binding/golang/xdb"
)
// CopyObject 拷贝对象
@@ -60,6 +59,8 @@ func CopyObject(src interface{}, dst interface{}) error {
value.Set(reflect.ValueOf(""))
}
}
} else if field.Type.Kind() != value.Type().Kind() { // 不同类型的字段过滤掉
continue
} else { // 简单数据类型的强制类型转换
switch value.Kind() {
case reflect.Int:

View File

@@ -9,7 +9,7 @@ make clean linux
cd ../web
npm run build
cd ../docker
cd ../build
# remove docker image if exists
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version
@@ -23,4 +23,4 @@ docker build --platform linux/amd64 -t registry.cn-shenzhen.aliyuncs.com/geekmas
if [ "$2" = "push" ];then
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:$version
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:$version
fi
fi

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
ALTER TABLE `chatgpt_chat_models` ADD `open` TINYINT(1) NOT NULL COMMENT '是否开放模型' AFTER `weight`;

View File

@@ -0,0 +1,28 @@
CREATE TABLE `chatgpt_invite_logs` (
`id` int NOT NULL,
`inviter_id` int NOT NULL COMMENT '邀请人ID',
`user_id` int NOT NULL COMMENT '注册用户ID',
`username` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`invite_code` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邀请码',
`calls` smallint NOT NULL COMMENT '奖励对话次数',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
ALTER TABLE `chatgpt_invite_logs` ADD PRIMARY KEY (`id`);
ALTER TABLE `chatgpt_invite_logs` MODIFY `id` int NOT NULL AUTO_INCREMENT;
ALTER TABLE `chatgpt_invite_logs` CHANGE `calls` `reward_json` TEXT NOT NULL COMMENT '邀请奖励';
CREATE TABLE `chatgpt_invite_codes` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`code` char(8) NOT NULL COMMENT '邀请码',
`hits` int NOT NULL COMMENT '点击次数',
`reg_num` smallint NOT NULL COMMENT '注册数量',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户邀请码';
ALTER TABLE `chatgpt_invite_codes` ADD PRIMARY KEY (`id`);
ALTER TABLE `chatgpt_invite_codes` MODIFY `id` int NOT NULL AUTO_INCREMENT;
ALTER TABLE `chatgpt_invite_codes` ADD UNIQUE(`code`);
ALTER TABLE `chatgpt_api_keys` ADD `type` VARCHAR(10) NOT NULL DEFAULT 'chat' COMMENT '用途chat=>聊天img=>图片)' AFTER `value`;

View File

@@ -2,3 +2,4 @@ mysql/data/*
mysql/logs/*
logs
static/*
redis/data/*

View File

@@ -1,6 +1,6 @@
Listen = "0.0.0.0:5678"
ProxyURL = "" # 如 http://127.0.0.1:7777
MysqlDns = "root:12345678@tcp(172.22.11.200:3307)/chatgpt_plus?charset=utf8&parseTime=True&loc=Local"
MysqlDns = "root:12345678@tcp(chatgpt-plus-mysql:3306)/chatgpt_plus?charset=utf8&parseTime=True&loc=Local"
StaticDir = "./static" # 静态资源的目录
StaticUrl = "/static" # 静态资源访问 URL
AesEncryptKey = ""
@@ -15,9 +15,9 @@ WeChatBot = false
Password = "admin123" # 如果是生产环境的话,这里管理员的密码记得修改
[Redis] # redis 配置信息
Host = "localhost"
Host = "chatgpt-plus-redis"
Port = 6379
Password = ""
Password = "12345678"
DB = 0
[ApiConfig] # 微博热搜,今日头条等函数服务 API 配置,此为第三方插件服务,如需使用请联系作者开通
@@ -33,10 +33,6 @@ WeChatBot = false
Sign = ""
CodeTempId = ""
[ExtConfig] # MidJourney和微信机器人服务 API 配置,开通此功能需要配合 chatpgt-plus-exts 项目部署
ApiURL = "" # 插件扩展 API 地址
Token = "" # 这个 token 随便填,只要确保跟 chatgpt-plus-exts 项目的 token 一样就行
[OSS] # OSS 配置,用于存储 MJ 绘画图片
Active = "local" # 默认使用本地文件存储引擎
[OSS.Local]
@@ -71,7 +67,7 @@ WeChatBot = false
[XXLConfig] # xxl-job 配置,需要你部署 XXL-JOB 定时任务工具,用来定期清理未支付订单和清理过期 VIP如果你没有启用支付服务则该服务也无需启动
Enabled = false # 是否启用 XXL JOB 服务
ServerAddr = "http://172.22.11.47:8080/xxl-job-admin" # xxl-job-admin 管理地址
ServerAddr = "http://chatgpt-plus-xxl-job:8080/xxl-job-admin" # xxl-job-admin 管理地址
ExecutorIp = "172.22.11.47" # 执行器 IP 地址
ExecutorPort = "9999" # 执行器服务端口
AccessToken = "xxl-job-api-token" # 执行器 API 通信 token
@@ -86,4 +82,4 @@ WeChatBot = false
PublicKey = "certs/alipay/appPublicCert.crt" # 应用公钥证书
AlipayPublicKey = "certs/alipay/alipayPublicCert.crt" # 支付宝公钥证书
RootCert = "certs/alipay/alipayRootCert.crt" # 支付宝根证书
NotifyURL = "http://r9it.com:6004/api/payment/alipay/notify" # 支付异步回调地址
NotifyURL = "http://r9it.com:6004/api/payment/alipay/notify" # 支付异步回调地址

View File

@@ -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://172.22.11.47:5678; # 这里改成后端服务的内网 IP 地址
proxy_pass http://chatgpt-plus-api:5678;
}
# 静态资源转发
location /static/ {
proxy_pass http://172.22.11.47:5678; # 这里改成后端服务的内网 IP 地址
proxy_pass http://chatgpt-plus-api:5678;
}
}
}

View File

@@ -1,16 +1,47 @@
version: '3'
services:
# mysql
chatgpt-plus-mysql:
image: mysql:8.0.33
container_name: chatgpt-plus-mysql
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
- MYSQL_ROOT_PASSWORD=12345678
ports:
- "3307:3306"
volumes :
- ./mysql/conf/my.cnf:/etc/mysql/my.cnf
- ./mysql/data:/var/lib/mysql
- ./mysql/logs:/var/log/mysql
- ./mysql/init.d:/docker-entrypoint-initdb.d/
# redis
chatgpt-plus-redis:
image: redis:6.0.16
restart: always
container_name: chatgpt-plus-redis
command: redis-server --requirepass 12345678
volumes :
- ./redis/data:/data
ports:
- "6380:6379"
# 后端 API 程序
chatgpt-plus-api:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:v3.1.5
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-api:v3.1.8.1
container_name: chatgpt-plus-api
restart: always
depends_on:
- chatgpt-plus-mysql
- chatgpt-plus-redis
environment:
- DEBUG=false
- LOG_LEVEL=info
- CONFIG_FILE=config.toml
ports:
- "5678:5678"
- "9999:9999"
volumes:
- /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime
- ./conf/config.toml:/var/www/app/config.toml
@@ -19,9 +50,11 @@ services:
# 前端应用
chatgpt-plus-web:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:v3.1.5
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/chatgpt-plus-web:v3.1.8.1
container_name: chatgpt-plus-web
restart: always
depends_on:
- chatgpt-plus-api
ports:
- "8080:8080"
volumes:

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +0,0 @@
version: '3'
services:
minio:
image: minio/minio
container_name: minio
volumes:
- ./data:/data
ports:
- "9010:9000"
- "9011:9001"
environment:
MINIO_ROOT_USER: minio
MINIO_ROOT_PASSWORD: minio@pass
command: server /data --console-address ":9001" --address ":9000"

View File

@@ -1,19 +0,0 @@
version: '3'
services:
# 后端 API 程序
mysql:
image: mysql:8.0.33
container_name: chatgpt-plus-mysql
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
- MYSQL_ROOT_PASSWORD=12345678
ports:
- "3307:3306"
volumes:
- ./conf/my.cnf:/etc/mysql/my.cnf
- ./data:/var/lib/mysql
- ./logs:/var/log/mysql

BIN
docs/imgs/donate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -5,4 +5,4 @@ VUE_APP_PASS=12345678
VUE_APP_ADMIN_USER=admin
VUE_APP_ADMIN_PASS=admin123
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
VUE_APP_TITLE="ChatGPT-PLUS V3"
VUE_APP_TITLE="Chat-Plus V3"

View File

@@ -1,4 +1,4 @@
VUE_APP_API_HOST=
VUE_APP_WS_HOST=
VUE_APP_KEY_PREFIX=ChatPLUS_
VUE_APP_TITLE="ChatGPT-PLUS V3"
VUE_APP_TITLE="Chat-Plus V3"

323
web/package-lock.json generated
View File

@@ -21,6 +21,7 @@
"markdown-it": "^13.0.1",
"md-editor-v3": "^2.2.1",
"pinia": "^2.1.4",
"qrcode": "^1.5.3",
"qs": "^6.11.1",
"sortablejs": "^1.15.0",
"v3-waterfall": "^1.2.1",
@@ -3361,7 +3362,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -4763,6 +4763,14 @@
}
}
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@@ -4973,6 +4981,11 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true
},
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -5150,8 +5163,7 @@
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/emojis-list": {
"version": "3.0.0",
@@ -5162,6 +5174,11 @@
"node": ">= 4"
}
},
"node_modules/encode-utf8": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/encode-utf8/-/encode-utf8-1.0.3.tgz",
"integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -6035,7 +6052,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
@@ -6181,7 +6197,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
@@ -6720,7 +6735,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -7089,7 +7103,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"dependencies": {
"p-locate": "^4.1.0"
},
@@ -8115,7 +8128,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"dependencies": {
"p-try": "^2.0.0"
},
@@ -8127,7 +8139,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"dependencies": {
"p-limit": "^2.2.0"
},
@@ -8152,7 +8163,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true,
"engines": {
"node": ">=6"
}
@@ -8238,7 +8248,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": {
"node": ">=8"
}
@@ -8358,6 +8367,14 @@
"node": ">=8"
}
},
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmmirror.com/portfinder/-/portfinder-1.0.28.tgz",
@@ -9020,6 +9037,119 @@
"node": ">=6"
}
},
"node_modules/qrcode": {
"version": "1.5.3",
"resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.3.tgz",
"integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
"dependencies": {
"dijkstrajs": "^1.0.1",
"encode-utf8": "^1.0.3",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/qrcode/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"engines": {
"node": ">=6"
}
},
"node_modules/qrcode/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/qrcode/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/qrcode/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/qrcode/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"node_modules/qrcode/node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/qrcode/node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/qs": {
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
@@ -9258,7 +9388,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9272,6 +9401,11 @@
"node": ">=0.10.0"
}
},
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz",
@@ -9547,6 +9681,11 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -9852,7 +9991,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -9866,7 +10004,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -11194,6 +11331,11 @@
"which": "bin/which"
}
},
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
},
"node_modules/wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-2.0.0.tgz",
@@ -13972,8 +14114,7 @@
"ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "3.2.1",
@@ -15081,6 +15222,11 @@
"ms": "2.1.2"
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="
},
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
@@ -15242,6 +15388,11 @@
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
"dev": true
},
"dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -15399,8 +15550,7 @@
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"emojis-list": {
"version": "3.0.0",
@@ -15408,6 +15558,11 @@
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true
},
"encode-utf8": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/encode-utf8/-/encode-utf8-1.0.3.tgz",
"integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw=="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -16104,7 +16259,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"dev": true,
"requires": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
@@ -16210,8 +16364,7 @@
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-intrinsic": {
"version": "1.1.1",
@@ -16639,8 +16792,7 @@
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
},
"is-glob": {
"version": "4.0.3",
@@ -16933,7 +17085,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"dev": true,
"requires": {
"p-locate": "^4.1.0"
}
@@ -17749,7 +17900,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
@@ -17758,7 +17908,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"dev": true,
"requires": {
"p-limit": "^2.2.0"
}
@@ -17776,8 +17925,7 @@
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"param-case": {
"version": "3.0.4",
@@ -17852,8 +18000,7 @@
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
},
"path-is-absolute": {
"version": "1.0.1",
@@ -17922,6 +18069,11 @@
"find-up": "^4.0.0"
}
},
"pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
},
"portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmmirror.com/portfinder/-/portfinder-1.0.28.tgz",
@@ -18364,6 +18516,97 @@
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"dev": true
},
"qrcode": {
"version": "1.5.3",
"resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.3.tgz",
"integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==",
"requires": {
"dijkstrajs": "^1.0.1",
"encode-utf8": "^1.0.3",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"requires": {
"color-convert": "^2.0.1"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"requires": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"requires": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
}
},
"y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
},
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"requires": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
}
},
"yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"qs": {
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz",
@@ -18559,8 +18802,7 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
},
"require-from-string": {
"version": "2.0.2",
@@ -18568,6 +18810,11 @@
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz",
@@ -18809,6 +19056,11 @@
"send": "0.17.2"
}
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -19077,7 +19329,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -19088,7 +19339,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.1"
}
@@ -20109,6 +20359,11 @@
"isexe": "^2.0.0"
}
},
"which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
},
"wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-2.0.0.tgz",

View File

@@ -21,6 +21,7 @@
"markdown-it": "^13.0.1",
"md-editor-v3": "^2.2.1",
"pinia": "^2.1.4",
"qrcode": "^1.5.3",
"qs": "^6.11.1",
"sortablejs": "^1.15.0",
"v3-waterfall": "^1.2.1",

BIN
web/public/images/chat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
web/public/images/mj.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

Before

Width:  |  Height:  |  Size: 476 KiB

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 526 KiB

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
web/public/images/sd.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -39,4 +39,157 @@ html, body {
}
}
/* 省略显示 */
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sl {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.sl3 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.sl4 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
/* 居中布局 */
.auto_center{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
.h_center{
width: 100%;
position: absolute;
top: 50%;
transform: translateY(-50%);
-webkit-transform: translateY(-50%);
}
.w_center{
position: absolute;
left: 50%;
transform: translateX(-50%);
-webkit-transform: translateX(-50%);
}
/* flex布局 */
.flex-row {
display: flex;
flex-direction: row;
}
.flex-col {
display: flex;
flex-direction: column;
}
.justify-start {
justify-content: flex-start;
}
.justify-end {
justify-content: flex-end;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
.justify-around {
justify-content: space-around;
}
.justify-evenly {
justify-content: space-evenly;
}
.items-start {
align-items: flex-start;
}
.items-end {
align-items: flex-end;
}
.items-center {
align-items: center;
}
.items-baseline {
align-items: baseline;
}
.items-stretch {
align-items: stretch;
}
.self-start {
align-self: flex-start;
}
.self-end {
align-self: flex-end;
}
.self-center {
align-self: center;
}
.self-baseline {
align-self: baseline;
}
.self-stretch {
align-self: stretch;
}
.flex-1 {
flex: 1 1 0%;
}
.flex-auto {
flex: 1 1 auto;
}
.grow {
flex-grow: 1;
}
.grow-0 {
flex-grow: 0;
}
.shrink {
flex-shrink: 1;
}
.shrink-0 {
flex-shrink: 0;
}
.shrink-1 {
flex-shrink: 1;
}
.relative {
position: relative;
}
</style>

View File

@@ -35,7 +35,6 @@
}
.page-mj .inner .mj-box .mj-params .param-line .el-icon {
position: relative;
top: 3px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content {
background-color: #383838;
@@ -43,26 +42,62 @@
padding: 8px 14px;
display: flex;
cursor: pointer;
margin-bottom: 10px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content:hover {
background-color: #585858;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .icon {
width: 20px;
height: 20px;
margin-bottom: 5px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape {
width: 16px;
height: 16px;
margin-right: 5px;
margin-bottom: 5px;
border: 1px solid #c4c4c4;
border-radius: 3px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.vertical {
width: 12px;
height: 20px;
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.size9-16 {
width: 9px;
height: 16px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.horizontal {
height: 12px;
width: 20px;
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.size16-9 {
height: 9px;
width: 16px;
position: relative;
top: 3px;
margin: 4px 0 7px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.size3-4 {
width: 12px;
height: 16px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.size4-3 {
height: 12px;
width: 16px;
position: relative;
margin: 4px 0 4px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.size2-3 {
width: 11px;
height: 16px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.size3-2 {
height: 11px;
width: 16px;
position: relative;
margin: 4px 0 5px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.size1-2 {
width: 8px;
height: 16px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content .shape.size2-1 {
height: 8px;
width: 16px;
position: relative;
margin: 4px 0 8px;
}
.page-mj .inner .mj-box .mj-params .param-line .grid-content.active {
color: #47fff1;
@@ -76,6 +111,7 @@
border: 1px solid #454545;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
display: flex;
flex-flow: column;
align-items: center;
@@ -98,10 +134,20 @@
}
.page-mj .inner .mj-box .mj-params .param-line .form-item-inner {
display: flex;
align-items: center;
}
.page-mj .inner .mj-box .mj-params .param-line .form-item-inner .el-select {
--el-select-input-focus-border-color: #47fff1;
--el-input-focus-border-color: #47fff1;
}
.page-mj .inner .mj-box .mj-params .param-line .form-item-inner .el-input__wrapper {
background: #383838;
}
.page-mj .inner .mj-box .mj-params .param-line .form-item-inner .el-input__inner {
color: #fff;
}
.page-mj .inner .mj-box .mj-params .param-line .form-item-inner .el-icon {
margin-left: 10px;
margin-top: 2px;
}
.page-mj .inner .mj-box .mj-params .param-line .img-uploader .el-upload {
border: 1px dashed var(--el-border-color);
@@ -123,19 +169,11 @@
text-align: center;
}
.page-mj .inner .mj-box .mj-params .param-line.pt {
display: flex;
align-items: center;
padding-top: 5px;
padding-bottom: 5px;
}
.page-mj .inner .mj-box .submit-btn {
padding: 10px 15px 0 15px;
text-align: center;
}
.page-mj .inner .mj-box .submit-btn .el-button {
width: 100%;
}
.page-mj .inner .mj-box .submit-btn .el-button span {
color: #2d3a4b;
}
.page-mj .inner .el-form .el-form-item__label {
color: #fff;
}
@@ -149,6 +187,86 @@
color: #fff;
overflow-x: hidden;
}
.page-mj .inner .task-list-box .task-list-inner .el-tabs {
--el-tabs-header-height: 55px;
}
.page-mj .inner .task-list-box .task-list-inner .el-tabs__item {
color: #fff;
font-size: 18px;
}
.page-mj .inner .task-list-box .task-list-inner .title-tabs .el-tabs__item.is-active {
color: #47fff1;
font-size: 18px;
}
.page-mj .inner .task-list-box .task-list-inner .title-tabs .el-tabs__active-bar {
background-color: #47fff1;
}
.page-mj .inner .task-list-box .task-list-inner .title-tabs .el-tabs__content {
padding: 10px 0;
}
.page-mj .inner .task-list-box .task-list-inner .el-textarea {
--el-input-focus-border-color: #47fff1;
}
.page-mj .inner .task-list-box .task-list-inner .el-textarea__inner {
background: transparent;
color: #fff;
}
.page-mj .inner .task-list-box .task-list-inner .el-input__wrapper {
background: transparent;
padding: 5px;
}
.page-mj .inner .task-list-box .task-list-inner .text {
margin-bottom: 10px;
color: #6b778c;
font-size: 15px;
}
.page-mj .inner .task-list-box .task-list-inner .param-line.pt {
padding-top: 5px;
padding-bottom: 5px;
}
.page-mj .inner .task-list-box .task-list-inner .form-item-inner {
display: flex;
align-items: center;
}
.page-mj .inner .task-list-box .task-list-inner .form-item-inner .el-icon {
margin-left: 10px;
}
.page-mj .inner .task-list-box .task-list-inner .el-form-item__label {
color: #fff;
}
.page-mj .inner .task-list-box .task-list-inner .img-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 300px;
transition: var(--el-transition-duration-fast);
margin-bottom: 20px;
}
.page-mj .inner .task-list-box .task-list-inner .img-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.page-mj .inner .task-list-box .task-list-inner .img-uploader .el-upload .el-icon.uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100%;
height: 120px;
text-align: center;
}
.page-mj .inner .task-list-box .task-list-inner .submit-btn {
display: flex;
margin: 20px 0;
}
.page-mj .inner .task-list-box .task-list-inner .submit-btn .el-button {
width: 200px;
}
.page-mj .inner .task-list-box .task-list-inner .submit-btn .text-info {
width: 100%;
display: flex;
justify-content: right;
align-items: center;
}
.page-mj .inner .task-list-box .running-job-list .job-item {
width: 100%;
padding: 2px;
@@ -253,5 +371,4 @@
margin-left: 10px;
cursor: pointer;
position: relative;
top: 2px;
}

View File

@@ -40,7 +40,6 @@
.el-icon {
position relative
top 3px
}
.grid-content {
@@ -49,29 +48,72 @@
padding 8px 14px
display flex
cursor pointer
margin-bottom: 10px;
&:hover {
background-color #585858
}
.icon {
width 20px
height 20px
margin-bottom 5px
}
.shape {
width 16px
height 16px
margin-right 5px
margin-bottom 5px
border 1px solid #C4C4C4
border-radius 3px
}
.shape.vertical {
width 12px
height 20px
.shape.size9-16 {
width 9px
height 16px
}
.shape.horizontal {
height 12px
width 20px
.shape.size16-9 {
height 9px
width 16px
position relative
top 3px
margin 4px 0 7px
}
.shape.size3-4 {
width 12px
height 16px
}
.shape.size4-3 {
height 12px
width 16px
position relative
margin 4px 0 4px
}
.shape.size2-3 {
width 11px
height 16px
}
.shape.size3-2 {
height 11px
width 16px
position relative
margin 4px 0 5px
}
.shape.size1-2 {
width 8px
height 16px
}
.shape.size2-1 {
height 8px
width 16px
position relative
margin 4px 0 8px
}
}
@@ -90,6 +132,7 @@
border 1px solid #454545
border-radius 5px
padding 10px
margin-bottom 10px
display flex
flex-flow column
align-items center
@@ -118,14 +161,26 @@
.form-item-inner {
display flex
align-items: center
.el-select {
--el-select-input-focus-border-color: #47FFF1;
--el-input-focus-border-color: #47FFF1;
}
.el-input__wrapper {
background: #383838;
}
.el-input__inner {
color: #fff
}
.el-icon {
margin-left 10px
margin-top 2px
}
}
.img-uploader {
.el-upload {
border: 1px dashed var(--el-border-color);
@@ -153,23 +208,12 @@
}
.param-line.pt {
display: flex
align-items: center
padding-top 5px
padding-bottom 5px
}
}
.submit-btn {
padding 10px 15px 0 15px
text-align center
.el-button {
width 100%
span {
color #2D3A4B
}
}
}
}
.el-form {
@@ -192,6 +236,5 @@
margin-left 10px
cursor pointer
position relative
top 2px
}
}

View File

@@ -72,6 +72,86 @@
color: #fff;
overflow-x: hidden;
}
.page-sd .inner .task-list-box .task-list-inner .el-tabs {
--el-tabs-header-height: 55px;
}
.page-sd .inner .task-list-box .task-list-inner .el-tabs__item {
color: #fff;
font-size: 18px;
}
.page-sd .inner .task-list-box .task-list-inner .title-tabs .el-tabs__item.is-active {
color: #47fff1;
font-size: 18px;
}
.page-sd .inner .task-list-box .task-list-inner .title-tabs .el-tabs__active-bar {
background-color: #47fff1;
}
.page-sd .inner .task-list-box .task-list-inner .title-tabs .el-tabs__content {
padding: 10px 0;
}
.page-sd .inner .task-list-box .task-list-inner .el-textarea {
--el-input-focus-border-color: #47fff1;
}
.page-sd .inner .task-list-box .task-list-inner .el-textarea__inner {
background: transparent;
color: #fff;
}
.page-sd .inner .task-list-box .task-list-inner .el-input__wrapper {
background: transparent;
padding: 5px;
}
.page-sd .inner .task-list-box .task-list-inner .text {
margin-bottom: 10px;
color: #6b778c;
font-size: 15px;
}
.page-sd .inner .task-list-box .task-list-inner .param-line.pt {
padding-top: 5px;
padding-bottom: 5px;
}
.page-sd .inner .task-list-box .task-list-inner .form-item-inner {
display: flex;
align-items: center;
}
.page-sd .inner .task-list-box .task-list-inner .form-item-inner .el-icon {
margin-left: 10px;
}
.page-sd .inner .task-list-box .task-list-inner .el-form-item__label {
color: #fff;
}
.page-sd .inner .task-list-box .task-list-inner .img-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 300px;
transition: var(--el-transition-duration-fast);
margin-bottom: 20px;
}
.page-sd .inner .task-list-box .task-list-inner .img-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.page-sd .inner .task-list-box .task-list-inner .img-uploader .el-upload .el-icon.uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100%;
height: 120px;
text-align: center;
}
.page-sd .inner .task-list-box .task-list-inner .submit-btn {
display: flex;
margin: 20px 0;
}
.page-sd .inner .task-list-box .task-list-inner .submit-btn .el-button {
width: 200px;
}
.page-sd .inner .task-list-box .task-list-inner .submit-btn .text-info {
width: 100%;
display: flex;
justify-content: right;
align-items: center;
}
.page-sd .inner .task-list-box .running-job-list .job-item {
width: 100%;
padding: 2px;

View File

@@ -4,6 +4,109 @@
color #ffffff
overflow-x hidden
.task-list-inner {
.el-tabs {
--el-tabs-header-height: 55px;
}
.el-tabs__item {
color: #fff;
font-size: 18px;
}
.title-tabs .el-tabs__item.is-active {
color: #47FFF1;
font-size: 18px;
}
.title-tabs .el-tabs__active-bar {
background-color: #47FFF1;
}
.title-tabs .el-tabs__content {
padding: 10px 0;
}
.el-textarea {
--el-input-focus-border-color: #47FFF1;
}
.el-textarea__inner {
background: transparent;
color: #fff;
}
.el-input__wrapper {
background: transparent;
padding 5px
}
.text {
margin-bottom: 10px;
color: #6b778c;
font-size: 15px
}
.param-line.pt {
padding-top 5px
padding-bottom 5px
}
.form-item-inner {
display flex
align-items: center
.el-icon {
margin-left 10px
}
}
.el-form-item__label {
color #ffffff
}
.img-uploader {
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width 300px;
transition: var(--el-transition-duration-fast);
margin-bottom: 20px;
&:hover {
border-color: var(--el-color-primary);
}
.el-icon.uploader-icon {
font-size: 28px
color: #8c939d
width 100%
height: 120px
text-align: center
}
}
}
.submit-btn {
display flex
margin: 20px 0
.el-button {
width 200px
}
.text-info {
width 100%
display flex
justify-content right
align-items center
}
}
}
.running-job-list {
.job-item {
//border: 1px solid #454545;

View File

@@ -18,7 +18,7 @@
<el-form-item label="手机验证码">
<el-row :gutter="20">
<el-col :span="16">
<el-input v-model.number="form.code" maxlength="6"/>
<el-input v-model="form.code" maxlength="6"/>
</el-col>
<el-col :span="8">
<send-msg size="" :mobile="form.mobile"/>

View File

@@ -31,11 +31,11 @@
<script>
import {defineComponent} from "vue"
import {Clock, DocumentCopy} from "@element-plus/icons-vue";
import {Clock, DocumentCopy, Position} from "@element-plus/icons-vue";
export default defineComponent({
name: 'ChatReply',
components: {Clock, DocumentCopy},
components: {Position, Clock, DocumentCopy},
props: {
content: {
type: String,
@@ -111,8 +111,8 @@ export default defineComponent({
line-height 1.5
code {
color #f1f1f1
background-color #202121
color #374151
background-color #e7e7e8
padding 0 3px;
border-radius 5px;
}
@@ -126,6 +126,39 @@ export default defineComponent({
margin-top 0
}
.code-container {
position relative
.hljs {
border-radius 10px
line-height 1.5
}
.copy-code-btn {
position: absolute;
right 10px
top 10px
cursor pointer
font-size 12px
color #c1c1c1
&:hover {
color #20a0ff
}
}
}
.lang-name {
position absolute;
right 10px
bottom 50px
padding 2px 6px 4px 6px
background-color #444444
border-radius 10px
color #00e0e0
}
// 设置表格边框
table {

View File

@@ -0,0 +1,99 @@
<template>
<div class="invite-list" v-loading="loading">
<el-row v-if="items.length > 0">
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
style="--el-table-border-color:#373C47;
--el-table-tr-bg-color:#2D323B;
--el-table-row-hover-bg-color:#373C47;
--el-table-header-bg-color:#474E5C;
--el-table-text-color:#d1d1d1">
<el-table-column prop="username" label="用户"/>
<el-table-column prop="invite_code" label="邀请码"/>
<el-table-column label="邀请奖励">
<template #default="scope">
<span>对话:{{ scope.row['reward']['chat_calls'] }}次</span>
<span>绘图:{{ scope.row['reward']['chat_calls'] }}次</span>
</template>
</el-table-column>
<el-table-column label="注册时间">
<template #default="scope">
<span>{{ dateFormat(scope.row['created_at']) }}</span>
</template>
</el-table-column>
</el-table>
</el-row>
<el-empty :image-size="100" v-else/>
<div class="pagination">
<el-pagination v-if="total > 0" background
layout="total,prev, pager, next"
:hide-on-single-page="true"
v-model:current-page="page"
v-model:page-size="pageSize"
@current-change="fetchData()"
:total="total"/>
</div>
</div>
</template>
<script setup>
import {onMounted, ref, watch} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {dateFormat} from "@/utils/libs";
import {DocumentCopy} from "@element-plus/icons-vue";
import Clipboard from "clipboard";
const items = ref([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(10)
const loading = ref(true)
onMounted(() => {
fetchData()
const clipboard = new Clipboard('.copy-order-no');
clipboard.on('success', () => {
ElMessage.success("复制成功");
})
clipboard.on('error', () => {
ElMessage.error('复制失败!');
})
})
// 获取数据
const fetchData = () => {
httpPost('/api/invite/list', {page: page.value, page_size: pageSize.value}).then((res) => {
if (res.data) {
items.value = res.data.items
total.value = res.data.total
page.value = res.data.page
pageSize.value = res.data.page_size
}
loading.value = false
}).catch(e => {
ElMessage.error("获取数据失败" + e.message);
})
}
</script>
<style scoped lang="stylus">
.invite-list {
.pagination {
margin: 20px 0 0 0;
display: flex;
justify-content: center;
width: 100%;
}
.copy-order-no {
cursor pointer
position relative
left 6px
top 2px
color #20a0ff
}
}
</style>

View File

@@ -45,9 +45,9 @@ onMounted(() => {
})
const computeSize = () => {
const w = container.value.offsetWidth - 8 // 减去滚动条的宽度
const w = container.value.offsetWidth - 10 // 减去滚动条的宽度
let cols = Math.floor(w / props.width)
itemWidth.value = Math.ceil(w / cols)
itemWidth.value = Math.floor(w / cols)
}
window.onresize = () => {

View File

@@ -0,0 +1,108 @@
<template>
<div class="reset-pass">
<el-dialog
v-model="showDialog"
:close-on-click-modal="true"
width="540px"
:before-close="close"
:title="title"
>
<div class="form">
<el-form :model="form" label-width="120px" label-position="left">
<el-form-item label="手机号码">
<el-input v-model="form.mobile"/>
</el-form-item>
<el-form-item label="手机验证码">
<el-row :gutter="20">
<el-col :span="16">
<el-input v-model="form.code" maxlength="6"/>
</el-col>
<el-col :span="8">
<send-msg size="" :mobile="form.mobile"/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="新密码">
<el-input v-model="form.password" type="password"/>
</el-form-item>
<el-form-item label="重复密码">
<el-input v-model="form.repass" type="password"/>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="save" round>
重置密码
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {computed, ref} from "vue";
import SendMsg from "@/components/SendMsg.vue";
import {ElMessage} from "element-plus";
import {httpPost} from "@/utils/http";
import {validateMobile} from "@/utils/validate";
const props = defineProps({
show: Boolean,
mobile: String
});
const showDialog = computed(() => {
return props.show
})
const title = ref('重置密码')
const form = ref({
mobile: '',
code: '',
password: '',
repass: ''
})
const emits = defineEmits(['hide']);
const save = () => {
if (!validateMobile(form.value.mobile)) {
return ElMessage.error("请输入正确的手机号码");
}
if (form.value.code === '') {
return ElMessage.error("请输入短信验证码");
}
if (form.value.repass !== form.value.password) {
return ElMessage.error("两次输入密码不一致");
}
httpPost('/api/user/resetPass', form.value).then(() => {
ElMessage.success({
message: '重置密码成功', duration: 1000, onClose: () => emits('hide', false)
})
}).catch(e => {
ElMessage.error("重置密码失败:" + e.message);
})
}
const close = function () {
emits('hide', false);
}
</script>
<style lang="stylus">
.reset-pass {
.form {
padding 10px 40px
}
.el-dialog__footer {
text-align center
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="user-bill" v-loading="loading">
<el-row>
<el-row v-if="items.length > 0">
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
style="--el-table-border-color:#373C47;
--el-table-tr-bg-color:#2D323B;
@@ -31,7 +31,7 @@
</el-table-column>
</el-table>
</el-row>
<el-empty :image-size="100" v-else/>
<div class="pagination">
<el-pagination v-if="total > 0" background
layout="total,prev, pager, next"

View File

@@ -27,7 +27,7 @@ export function setSessionId(sessionId) {
}
export function getUserToken() {
return Storage.get(UserTokenKey)
return Storage.get(UserTokenKey) ?? ""
}
export function setUserToken(token) {

View File

@@ -6,6 +6,6 @@ export function validateEmail(email) {
}
export function validateMobile(mobile) {
const regex = /^1[345789]\d{9}$/;
const regex = /^1[3456789]\d{9}$/;
return regex.test(mobile);
}

Some files were not shown because too many files have changed in this diff Show More