Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
babef8baae | ||
|
|
ae8239e5de | ||
|
|
dae91ed243 | ||
|
|
de42a428e6 | ||
|
|
63c7041e1f | ||
|
|
b1263ddc69 | ||
|
|
7e50e17aaf | ||
|
|
a7265c4251 | ||
|
|
6f39f639bd | ||
|
|
a7db123437 | ||
|
|
241c714a8b | ||
|
|
67ac3cfe32 | ||
|
|
c926e0afcc | ||
|
|
5bc07e6d57 | ||
|
|
c3666a9a71 | ||
|
|
23b5ffa97d | ||
|
|
a2c7a75705 | ||
|
|
d68f2ef12c | ||
|
|
67d30353f0 | ||
|
|
4813163eac | ||
|
|
5c5210625e | ||
|
|
a4a1eec30b | ||
|
|
d35164506a | ||
|
|
1ed08f01ea | ||
|
|
eca07ab830 | ||
|
|
3512715704 | ||
|
|
6d07881141 | ||
|
|
251fe626f2 | ||
|
|
5fee3a9288 | ||
|
|
9b68d8101e | ||
|
|
cfe6f27d48 | ||
|
|
b314dd0900 | ||
|
|
950fab6374 | ||
|
|
9d1f5c42ce | ||
|
|
a84046390b | ||
|
|
aa29323a8a | ||
|
|
d5617b7c3a | ||
|
|
1ef60a9e5e | ||
|
|
fb6e395ad8 | ||
|
|
d9216060bc | ||
|
|
bcaa9a92e5 | ||
|
|
576adc9036 | ||
|
|
00de18be9a | ||
|
|
c61d32816a | ||
|
|
f3fbb0b89c | ||
|
|
e311a39632 |
26
CHANGELOG.md
@@ -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
@@ -42,7 +42,7 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
|
||||
|
||||
### 会员充值
|
||||
|
||||

|
||||

|
||||
|
||||
### 自动调用函数插件
|
||||
|
||||
@@ -73,39 +73,6 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
|
||||
1. 本项目基于 MIT 协议,免费开放全部源代码,可以作为个人学习使用或者商用。
|
||||
2. 如需商用必须保留版权信息,请自觉遵守。确保合法合规使用,在运营过程中产生的一切任何后果自负,与作者无关。
|
||||
|
||||
## 项目介绍
|
||||
|
||||
这一套完整的系统,包括前端聊天应用和一个后台管理系统。系统有用户鉴权,你可以自己使用,也可以部署直接给 C 端用户提供
|
||||
ChatGPT 的服务。
|
||||
|
||||
### 项目的技术架构
|
||||
|
||||
新版的系统前后端都进行大改动的重构,后端还是用的 Gin Web 框架,但是作者整合了 fx 自动注入框架,整个后端应用结构非常简洁,特别适合二次开发。
|
||||
另外,数据存储用 MySQL 替换了 leveldb, 因为要对 C 端,后期会涉及到很多业务数据查询统计,leveldb 已经完全不够用了。
|
||||
|
||||
> Gin + fx + MySQL
|
||||
|
||||
3.0 版本之后会陆续添加其他语言的 API 实现,比如 PHP,Java 等。考虑到作者精力有限,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" # 这边是对外的端口,支持 8080,80和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 对话。
|
||||
|
||||

|
||||
|
||||
最后进入前端聊天页面 [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
|
||||
|
||||

|
||||
|
||||
#### 特此声明:不接受在微信或者微信群给开发者提 Bug,有问题或者优化建议请提交 Issue 和 PR。非常感谢您的配合!
|
||||
#### 特此声明:由于个人时间有限,不接受在微信或者微信群给开发者提 Bug,有问题或者优化建议请提交 Issue 和 PR。非常感谢您的配合!
|
||||
|
||||
### Commit 类型
|
||||
|
||||
@@ -437,8 +122,7 @@ make clean linux
|
||||
|
||||
如果你觉得这个项目对你有帮助,并且情况允许的话,可以请作者喝杯咖啡,非常感谢你的支持~
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
@@ -135,16 +140,19 @@ 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"`
|
||||
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"` // 启用短信验证码服务
|
||||
EnabledDraw bool `json:"enabled_draw"` // 启动 AI 绘画功能
|
||||
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"` // 是否强制必须使用邀请码才能注册
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ const (
|
||||
FuncZaoBao = "zao_bao" // 每日早报
|
||||
FuncHeadLine = "headline" // 今日头条
|
||||
FuncWeibo = "weibo_hot" // 微博热搜
|
||||
FuncMidJourney = "mid_journey" // MJ 绘画
|
||||
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{},
|
||||
|
||||
@@ -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
|
||||
|
||||
17
api/go.sum
@@ -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=
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@ package admin
|
||||
|
||||
import (
|
||||
"chatplus/core"
|
||||
"chatplus/core/types"
|
||||
"chatplus/handler"
|
||||
"chatplus/store/model"
|
||||
"chatplus/utils/resp"
|
||||
@@ -24,8 +25,8 @@ 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"`
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,7 +252,6 @@ 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 {
|
||||
@@ -270,7 +263,6 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
||||
chatCtx = append(chatCtx, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loading recent chat history as chat context
|
||||
if chatConfig.ContextDeep > 0 {
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
96
api/handler/invite_handler.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -44,7 +41,8 @@ func (h *UserHandler) Register(c *gin.Context) {
|
||||
var data struct {
|
||||
Mobile string `json:"mobile"`
|
||||
Password string `json:"password"`
|
||||
Code int `json:"code"`
|
||||
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)
|
||||
}
|
||||
|
||||
16
api/main.go
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
92
api/service/fun/func_img.go
Normal 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\n", imgURL), nil
|
||||
}
|
||||
|
||||
func (f FuncImage) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
var _ Function = &FuncImage{}
|
||||
@@ -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{}
|
||||
@@ -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.FuncImage: NewImageFunc(db, manager, config),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
hello, world!
|
||||
@@ -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()
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package model
|
||||
type ApiKey struct {
|
||||
BaseModel
|
||||
Platform string
|
||||
Type string // 用途 chat => 聊天,img => 绘图
|
||||
Value string // API Key 的值
|
||||
LastUsedAt int64 // 最后使用时间
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ type ChatModel struct {
|
||||
SortNum int
|
||||
Enabled bool
|
||||
Weight int // 对话权重,每次对话扣减多少次对话额度
|
||||
Open bool // 是否开放模型给所有人使用
|
||||
}
|
||||
|
||||
12
api/store/model/invite_code.go
Normal 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
|
||||
}
|
||||
15
api/store/model/invite_log.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"` // 最后使用时间
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ type ChatModel struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
SortNum int `json:"sort_num"`
|
||||
Weight int `json:"weight"`
|
||||
Open bool `json:"open"`
|
||||
}
|
||||
|
||||
10
api/store/vo/invite_code.go
Normal 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"`
|
||||
}
|
||||
15
api/store/vo/invite_log.go
Normal 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"`
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
1603
database/chatgpt_plus-v3.1.9.sql
Normal file
1
database/update-v3.1.9.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `chatgpt_chat_models` ADD `open` TINYINT(1) NOT NULL COMMENT '是否开放模型' AFTER `weight`;
|
||||
28
database/update-v3.2.0.sql
Normal 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`;
|
||||
1
docker/.gitignore → deploy/.gitignore
vendored
@@ -2,3 +2,4 @@ mysql/data/*
|
||||
mysql/logs/*
|
||||
logs
|
||||
static/*
|
||||
redis/data/*
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
1603
deploy/mysql/init.d/chatgpt_plus-v3.1.9.sql
Normal 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"
|
||||
@@ -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
|
After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 88 KiB |
@@ -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"
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
|
After Width: | Height: | Size: 5.1 KiB |
BIN
web/public/images/mj.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 476 KiB After Width: | Height: | Size: 476 KiB |
BIN
web/public/images/mj/mj-v4.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
web/public/images/mj/mj-v5.1.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 526 KiB After Width: | Height: | Size: 526 KiB |
BIN
web/public/images/mj/mj-v5.jpg
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
web/public/images/mj/nj1.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
web/public/images/mj/nj2.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
web/public/images/mj/nj3.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
web/public/images/mj/nj4.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
web/public/images/mj/rate_16_9.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
web/public/images/mj/rate_1_1.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/public/images/mj/rate_1_2.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/public/images/mj/rate_2_1.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
web/public/images/mj/rate_3_4.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/public/images/mj/rate_4_3.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/public/images/mj/rate_9_16.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/public/images/sd.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
153
web/src/App.vue
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
99
web/src/components/InviteList.vue
Normal 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>
|
||||
@@ -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 = () => {
|
||||
|
||||
108
web/src/components/ResetPass.vue
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -27,7 +27,7 @@ export function setSessionId(sessionId) {
|
||||
}
|
||||
|
||||
export function getUserToken() {
|
||||
return Storage.get(UserTokenKey)
|
||||
return Storage.get(UserTokenKey) ?? ""
|
||||
}
|
||||
|
||||
export function setUserToken(token) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||