Compare commits

...

36 Commits

Author SHA1 Message Date
RockYang
79adc871ef export the newest database sql file 2024-09-05 15:29:00 +08:00
RockYang
8144fada25 merged v4.0.9 and fixed conflicts 2024-09-05 11:02:32 +08:00
RockYang
754ba02263 fixed build script 2024-08-01 18:45:53 +08:00
RockYang
7ddf57ae06 merge v4.0.8 2024-08-01 18:09:00 +08:00
RockYang
cc5180a6f7 update readme 2024-08-01 08:52:46 +08:00
RockYang
9f44c34d34 update docs url 2024-07-16 18:15:34 +08:00
RockYang
b793b81768 update geekai image version 2024-07-05 11:09:13 +08:00
RockYang
0375164f40 add database files 2024-06-22 12:17:35 +08:00
RockYang
691294b444 finish mobile wechat payment 2024-06-22 12:10:43 +08:00
RockYang
c24b4d7074 update change log files 2024-06-14 18:23:54 +08:00
RockYang
ab24398748 wechat payment is ready for PC 2024-06-12 14:20:37 +08:00
RockYang
6110522b54 change payment component, upgrade golang to 1.22.4 2024-06-11 11:48:41 +08:00
RockYang
bcdf5e3776 fix bug: free model not record the chat history 2024-06-06 15:01:32 +08:00
RockYang
2207830db9 fixe page styles 2024-06-05 18:08:23 +08:00
RockYang
d52dfbfef4 fixed bug markmap generation 2024-06-04 16:21:08 +08:00
RockYang
66ccb387e8 dalle3 and gptt-4o api compatible with azure 2024-06-03 18:34:37 +08:00
RockYang
3cc2263dc7 fixed bug for function call error None is not of type 'array' 2024-05-30 09:59:44 +08:00
RockYang
f0a3c5d8ae fixed bug for mobile chat share 2024-05-30 08:37:14 +08:00
RockYang
2a4ef27774 add v4.0.8 database sql file 2024-05-29 17:41:37 +08:00
RockYang
2b057f32aa feat: add dalle3 page for h5 2024-05-29 17:25:01 +08:00
RockYang
bc6451026f feat: add system config for enable rand background image for index page 2024-05-29 16:24:56 +08:00
RockYang
99fd596862 feat: add system config for enable rand background image for index page 2024-05-29 16:23:42 +08:00
RockYang
f0959b5df6 fix markdown formula parse plugin 2024-05-29 13:49:45 +08:00
RockYang
1b0938b33f micro fixs 2024-05-27 17:39:17 +08:00
RockYang
02faff461a fixed bug for dalle prompt translate 2024-05-27 11:42:14 +08:00
RockYang
e18e5a38c6 put model and app selector on the top of chat page 2024-05-24 12:33:22 +08:00
RockYang
2f9b1b7835 fixed bug for payment api authorization 2024-05-24 11:31:38 +08:00
RockYang
717b137a6d chore: use config value for order pay timeout 2024-05-22 18:15:06 +08:00
RockYang
f755bdccae feat: add sign check for PC QR code payment 2024-05-22 17:47:53 +08:00
RockYang
4bba77ab47 extract code for saving chat history 2024-05-22 15:32:44 +08:00
RockYang
6944a32ff3 check if the api url in whitelist for mj plus client 2024-05-22 11:47:04 +08:00
RockYang
5742b40aee fixed bug for mobile chat page change chat model not work 2024-05-21 17:54:03 +08:00
RockYang
7f1ec90748 auto resize the input element rows, when use inputed more than one line 2024-05-21 17:36:47 +08:00
RockYang
bee19392c1 add logs for updating database failed 2024-05-21 11:55:38 +08:00
RockYang
00d31a2379 update docker image url 2024-05-21 11:03:11 +08:00
RockYang
5d65505ab7 rename project name to geekai 2024-05-20 15:11:14 +08:00
101 changed files with 5034 additions and 2408 deletions

View File

@@ -1,4 +1,25 @@
# 更新日志
## v4.0.9
* 环境升级:升级 Golang 到 go1.22.4
* 功能增加:接入微信商户号支付渠道
* Bug修复修复前端页面菜单把页面撑开底部留白问题
* 功能优化:聊天页面自动根据内容调整输入框的高度
* Bug修复修复Dalle绘图失败退回算力的问题
* 功能优化:邀请码注册时被邀请人也可以获得赠送的算力
* 功能优化:允许设置邮件验证码的抬头
* Bug修复修复免费模型不会记录聊天记录的bug
* Bug修复修复聊天输入公式显示异常的Bug
## v4.0.8
* 功能优化:升级 mathjax 公式解析插件,修复公式因为图片访问限制而无法显示的问题
* 功能优化:当数据库更新失败的时候记录错误日志
* 功能优化:聊天输入框会随着输入内容的增多自动调整高度
* Bug修复修复移动端聊天页面模型切换不生效的Bug
* 功能优化给PC端扫码支付增加签名验证和有效期验证
* Bug修复修复支付码生成API权限控制的问题
* Bug修复模型算力设置为0时不扣减用户算力并且不记录算力消费日志
* 功能优化:新增随机背景配置项,可以在后台设置,首页使用 Bing 壁纸作为背景图片
* 功能新增H5端支持 Dalle 绘图
## v4.0.7

View File

@@ -1,5 +1,5 @@
# GeekAI
### 本项目已经正式更名为 GeekAI请大家及时更新代码克隆地址
> 根据[《生成式人工智能服务管理暂行办法》](https://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务
**GeekAI** 基于 AI 大语言模型 API 实现的 AI 助手全套开源解决方案,自带运营管理后台,开箱即用。集成了 OpenAI, Azure,
ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了 MidJourney 和 Stable Diffusion AI绘画功能。
@@ -26,63 +26,16 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
- [x] 支持网站 Logo 版权等信息的修改
## 功能截图
### PC 端聊天界面
![ChatGPT Chat Page](/docs/imgs/gpt.gif)
### AI 对话界面
![ChatGPT new Chat Page](/docs/imgs/chat-new.png)
### MidJourney 专业绘画界面
![mid-journey](/docs/imgs/mj_image.jpg)
### Stable-Diffusion 专业绘画页面
![Stable-Diffusion](/docs/imgs/sd_image.jpg)
![Stable-Diffusion](/docs/imgs/sd_image_detail.jpg)
### 绘图作品展
![ChatGPT image_list](/docs/imgs/image-list.png)
### AI应用列表
![ChatGPT-app-list](/docs/imgs/app-list.jpg)
### 会员充值
![会员充值](/docs/imgs/member.png)
### 自动调用函数插件
![ChatGPT function plugin](/docs/imgs/plugin.png)
![ChatGPT function plugin](/docs/imgs/mj.jpg)
### 管理后台
![ChatGPT admin](/docs/imgs/admin_dashboard.png)
![ChatGPT admin](/docs/imgs/admin_config.jpg)
![ChatGPT admin](/docs/imgs/admin_models.jpg)
![ChatGPT admin](/docs/imgs/admin_user.png)
### 移动端 Web 页面
![Mobile chat list](/docs/imgs/mobile_chat_list.png)
![Mobile chat session](/docs/imgs/mobile_chat_session.png)
![Mobile chat setting](/docs/imgs/mobile_user_profile.png)
![Mobile chat setting](/docs/imgs/mobile_pay.png)
请参考 [GeekAI 项目介绍](https://docs.geekai.me/info/)。
### 体验地址
> 免费体验地址:[https://ai.r9it.com/chat](https://ai.r9it.com/chat) <br/>
> 免费体验地址:[https://chat.geekai.me](https://chat.geekai.me) <br/>
> **注意:请合法使用,禁止输出任何敏感、不友好或违规的内容!!!**
## 快速部署
请参考文档 [**GeekAI 快速部署**](https://ai.r9it.com/docs/install/)。
请参考文档 [**GeekAI 快速部署**](https://docs.geekai.me/install/)。
## 使用须知
@@ -101,14 +54,14 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
## TODOLIST
* [ ] 支持基于知识库的 AI 问答
* [ ] 会员邀请注册推广功能
* [ ] 文生视频,文生歌曲功能
* [ ] 微信支付功能
## 项目文档
最新的部署视频教程:[https://www.bilibili.com/video/BV1Cc411t7CX/](https://www.bilibili.com/video/BV1Cc411t7CX/)
详细的部署和开发文档请参考 [**GeekAI 文档**](https://ai.r9it.com/docs/)。
详细的部署和开发文档请参考 [**GeekAI 文档**](https://docs.geekai.me)。
加微信进入微信讨论群可获取 **一键部署脚本添加好友时请注明来自Github!!!)。**

3
api/.gitignore vendored
View File

@@ -17,4 +17,5 @@ bin
data
config.toml
static/upload
storage.json
storage.json
res/certs/wechat/apiclient_key.pem

View File

@@ -9,12 +9,12 @@ package core
import (
"bytes"
"context"
"fmt"
"geekai/core/types"
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
"context"
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v5"
@@ -227,15 +227,20 @@ func needLogin(c *gin.Context) bool {
c.Request.URL.Path == "/api/sd/client" ||
c.Request.URL.Path == "/api/dall/imgWall" ||
c.Request.URL.Path == "/api/dall/client" ||
c.Request.URL.Path == "/api/config/get" ||
c.Request.URL.Path == "/api/product/list" ||
c.Request.URL.Path == "/api/menu/list" ||
c.Request.URL.Path == "/api/markMap/client" ||
c.Request.URL.Path == "/api/payment/alipay/notify" ||
c.Request.URL.Path == "/api/payment/hupipay/notify" ||
c.Request.URL.Path == "/api/payment/payjs/notify" ||
c.Request.URL.Path == "/api/payment/wechat/notify" ||
c.Request.URL.Path == "/api/payment/doPay" ||
c.Request.URL.Path == "/api/payment/payWays" ||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
strings.HasPrefix(c.Request.URL.Path, "/api/config/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
strings.HasPrefix(c.Request.URL.Path, "/api/payment/") ||
strings.HasPrefix(c.Request.URL.Path, "/static/") {
return false
}

View File

@@ -61,15 +61,15 @@ type ChatSession struct {
}
type ChatModel struct {
Id uint `json:"id"`
Platform Platform `json:"platform"`
Name string `json:"name"`
Value string `json:"value"`
Power int `json:"power"`
MaxTokens int `json:"max_tokens"` // 最大响应长度
MaxContext int `json:"max_context"` // 最大上下文长度
Temperature float32 `json:"temperature"` // 模型温度
KeyId int `json:"key_id"` // 绑定 API KEY
Id uint `json:"id"`
Platform string `json:"platform"`
Name string `json:"name"`
Value string `json:"value"`
Power int `json:"power"`
MaxTokens int `json:"max_tokens"` // 最大响应长度
MaxContext int `json:"max_context"` // 最大上下文长度
Temperature float32 `json:"temperature"` // 模型温度
KeyId int `json:"key_id"` // 绑定 API KEY
}
type ApiError struct {

View File

@@ -29,11 +29,12 @@ type AppConfig struct {
WeChatBot bool // 是否启用微信机器人
SdConfigs []StableDiffusionConfig // sd AI draw service pool
XXLConfig XXLConfig
AlipayConfig AlipayConfig
HuPiPayConfig HuPiPayConfig
SmtpConfig SmtpConfig // 邮件发送配置
JPayConfig JPayConfig // payjs 支付配置
XXLConfig XXLConfig
AlipayConfig AlipayConfig // 支付宝支付渠道配置
HuPiPayConfig HuPiPayConfig // 虎皮椒支付配置
SmtpConfig SmtpConfig // 邮件发送配置
JPayConfig JPayConfig // payjs 支付配置
WechatPayConfig WechatPayConfig // 微信支付渠道配置
}
type SmtpConfig struct {
@@ -85,6 +86,17 @@ type AlipayConfig struct {
ReturnURL string // 支付成功返回地址
}
type WechatPayConfig struct {
Enabled bool // 是否启用该支付通道
AppId string // 公众号的APPID,如wxd678efh567hg6787
MchId string // 直连商户的商户号,由微信支付生成并下发
SerialNo string // 商户证书的证书序列号
PrivateKey string // 用户私钥文件路径
ApiV3Key string // API V3 秘钥
NotifyURL string // 异步通知回调
ReturnURL string // 支付成功返回地址
}
type HuPiPayConfig struct { //虎皮椒第四方支付配置
Enabled bool // 是否启用该支付通道
Name string // 支付名称wechat/alipay
@@ -126,29 +138,65 @@ type RedisConfig struct {
const LicenseKey = "Geek-AI-License"
type License struct {
Key string `json:"key"` // 许可证书密钥
MachineId string `json:"machine_id"` // 机器码
UserNum int `json:"user_num"` // 用户数量
ExpiredAt int64 `json:"expired_at"` // 过期时间
IsActive bool `json:"is_active"` // 是否激活
Key string `json:"key"` // 许可证书密钥
MachineId string `json:"machine_id"` // 机器码
ExpiredAt int64 `json:"expired_at"` // 过期时间
IsActive bool `json:"is_active"` // 是否激活
Configs LicenseConfig `json:"configs"`
}
type LicenseConfig struct {
UserNum int `json:"user_num"` // 用户数量
DeCopy bool `json:"de_copy"` // 去版权
}
func (c RedisConfig) Url() string {
return fmt.Sprintf("%s:%d", c.Host, c.Port)
}
type Platform string
type Platform struct {
Name string `json:"name"`
Value string `json:"value"`
ChatURL string `json:"chat_url"`
ImgURL string `json:"img_url"`
}
const OpenAI = Platform("OpenAI")
const Azure = Platform("Azure")
const ChatGLM = Platform("ChatGLM")
const Baidu = Platform("Baidu")
const XunFei = Platform("XunFei")
const QWen = Platform("QWen")
var OpenAI = Platform{
Name: "OpenAI - GPT",
Value: "OpenAI",
ChatURL: "https://api.chat-plus.net/v1/chat/completions",
ImgURL: "https://api.chat-plus.net/v1/images/generations",
}
var Azure = Platform{
Name: "微软 - Azure",
Value: "Azure",
ChatURL: "https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15",
}
var ChatGLM = Platform{
Name: "智谱 - ChatGLM",
Value: "ChatGLM",
ChatURL: "https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke",
}
var Baidu = Platform{
Name: "百度 - 文心大模型",
Value: "Baidu",
ChatURL: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}",
}
var XunFei = Platform{
Name: "讯飞 - 星火大模型",
Value: "XunFei",
ChatURL: "wss://spark-api.xf-yun.com/{version}/chat",
}
var QWen = Platform{
Name: "阿里 - 通义千问",
Value: "QWen",
ChatURL: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation",
}
type SystemConfig struct {
Title string `json:"title,omitempty"`
AdminTitle string `json:"admin_title,omitempty"`
Title string `json:"title,omitempty"` // 网站标题
Slogan string `json:"slogan,omitempty"` // 网站 slogan
AdminTitle string `json:"admin_title,omitempty"` // 管理后台标题
Logo string `json:"logo,omitempty"`
InitPower int `json:"init_power,omitempty"` // 新用户注册赠送算力值
DailyPower int `json:"daily_power,omitempty"` // 每日赠送算力
@@ -177,4 +225,6 @@ type SystemConfig struct {
ContextDeep int `json:"context_deep,omitempty"`
SdNegPrompt string `json:"sd_neg_prompt"` // SD 默认反向提示词
RandBg bool `json:"rand_bg"` // 前端首页是否启用随机背景
}

View File

@@ -24,5 +24,4 @@ type Function struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]interface{} `json:"parameters"`
Required interface{} `json:"required,omitempty"`
}

View File

@@ -28,7 +28,6 @@ type MjTask struct {
TaskId string `json:"task_id"`
ImgArr []string `json:"img_arr"`
ChannelId string `json:"channel_id"`
SessionId string `json:"session_id"`
Type TaskType `json:"type"`
UserId int `json:"user_id"`
Prompt string `json:"prompt,omitempty"`
@@ -42,7 +41,6 @@ type MjTask struct {
type SdTask struct {
Id int `json:"id"` // job 数据库ID
SessionId string `json:"session_id"`
Type TaskType `json:"type"`
UserId int `json:"user_id"`
Params SdTaskParams `json:"params"`

View File

@@ -19,7 +19,6 @@ require (
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480
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
go.uber.org/zap v1.23.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gorm.io/driver/mysql v1.4.7
@@ -28,15 +27,26 @@ require (
require github.com/xxl-job/xxl-job-executor-go v1.2.0
require (
github.com/go-pay/gopay v1.5.101
github.com/mojocn/base64Captcha v1.3.1
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/shopspring/decimal v1.3.1
github.com/syndtr/goleveldb v1.0.0
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
)
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-pay/crypto v0.0.1 // indirect
github.com/go-pay/errgroup v0.0.2 // indirect
github.com/go-pay/util v0.0.2 // indirect
github.com/go-pay/xlog v0.0.2 // indirect
github.com/go-pay/xtime v0.0.2 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.4.0 // indirect
)
@@ -74,9 +84,6 @@ require (
github.com/refraction-networking/utls v1.3.2 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartwalle/ncrypto v1.0.2 // indirect
github.com/smartwalle/ngx v1.0.6 // indirect
github.com/smartwalle/nsign v1.0.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
go.uber.org/dig v1.16.1 // indirect
golang.org/x/arch v0.3.0 // indirect

View File

@@ -43,6 +43,20 @@ github.com/go-basic/ipv4 v1.0.0 h1:gjyFAa1USC1hhXTkPOwBWDPfMcUaIM+tvo1XzV9EZxs=
github.com/go-basic/ipv4 v1.0.0/go.mod h1:etLBnaxbidQfuqE6wgZQfs38nEWNmzALkxDZe4xY8Dg=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-pay/crypto v0.0.1 h1:B6InT8CLfSLc6nGRVx9VMJRBBazFMjr293+jl0lLXUY=
github.com/go-pay/crypto v0.0.1/go.mod h1:41oEIvHMKbNcYlWUlRWtsnC6+ASgh7u29z0gJXe5bes=
github.com/go-pay/errgroup v0.0.2 h1:5mZMdm0TDClDm2S3G0/sm0f8AuQRtz0dOrTHDR9R8Cc=
github.com/go-pay/errgroup v0.0.2/go.mod h1:0+4b8mvFMS71MIzsaC+gVvB4x37I93lRb2dqrwuU8x8=
github.com/go-pay/gopay v1.5.101 h1:rVb+sfv6hiQtknAlZnTTLvU27NvFJ4p0yglN/vPpGXI=
github.com/go-pay/gopay v1.5.101/go.mod h1:AW4Yj8jDZX9BM1/GTLTY1Gy5SHjiq8kQvG5sBTN2sxI=
github.com/go-pay/util v0.0.2 h1:goJ4f6kNY5zzdtg1Cj8oWC+Cw7bfg/qq2rJangMAb9U=
github.com/go-pay/util v0.0.2/go.mod h1:qM8VbyF1n7YAPZBSJONSPMPsPedhUTktewUAdf1AjPg=
github.com/go-pay/xlog v0.0.2 h1:kUg5X8/5VZAPDg1J5eGjA3MG0/H5kK6Ew0dW/Bycsws=
github.com/go-pay/xlog v0.0.2/go.mod h1:DbjMADPK4+Sjxj28ekK9goqn4zmyY4hql/zRiab+S9E=
github.com/go-pay/xtime v0.0.2 h1:7YR4/iuELsEHpJ6LUO0SVK80hQxDO9MLCfuVYIiTCRM=
github.com/go-pay/xtime v0.0.2/go.mod h1:W1yRbJaSt4CSBcdAtLBQ8xajiN/Pl5hquGczUcUE9xE=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@@ -176,20 +190,14 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/smartwalle/alipay/v3 v3.2.15 h1:3fvFJnINKKAOXHR/Iv20k1Z7KJ+nOh3oK214lELPqG8=
github.com/smartwalle/alipay/v3 v3.2.15/go.mod h1:niTNB609KyUYuAx9Bex/MawEjv2yPx4XOjxSAkqmGjE=
github.com/smartwalle/ncrypto v1.0.2 h1:pTAhCqtPCMhpOwFXX+EcMdR6PNzruBNoGQrN2S1GbGI=
github.com/smartwalle/ncrypto v1.0.2/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk=
github.com/smartwalle/ngx v1.0.6 h1:JPNqNOIj+2nxxFtrSkJO+vKJfeNUSEQueck/Wworjps=
github.com/smartwalle/ngx v1.0.6/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
github.com/smartwalle/nsign v1.0.8 h1:78KWtwKPrdt4Xsn+tNEBVxaTLIJBX9YRX0ZSrMUeuHo=
github.com/smartwalle/nsign v1.0.8/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -204,6 +212,10 @@ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gt
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/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
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=
@@ -215,6 +227,8 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
github.com/xxl-job/xxl-job-executor-go v1.2.0 h1:MTl2DpwrK2+hNjRRks2k7vB3oy+3onqm9OaSarneeLQ=
github.com/xxl-job/xxl-job-executor-go v1.2.0/go.mod h1:bUFhz/5Irp9zkdYk5MxhQcDDT6LlZrI8+rv5mHtQ1mo=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -261,6 +275,7 @@ golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@@ -57,6 +57,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
apiKey.Name = data.Name
res := h.DB.Save(&apiKey)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -75,6 +76,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
func (h *ApiKeyHandler) List(c *gin.Context) {
status := h.GetBool(c, "status")
t := h.GetTrim(c, "type")
platform := h.GetTrim(c, "platform")
session := h.DB.Session(&gorm.Session{})
if status {
@@ -83,7 +85,10 @@ func (h *ApiKeyHandler) List(c *gin.Context) {
if t != "" {
session = session.Where("type", t)
}
if platform != "" {
session = session.Where("platform", platform)
}
var items []model.ApiKey
var keys = make([]vo.ApiKey, 0)
res := session.Find(&items)
@@ -118,6 +123,7 @@ func (h *ApiKeyHandler) Set(c *gin.Context) {
res := h.DB.Model(&model.ApiKey{}).Where("id = ?", data.Id).Update(data.Filed, data.Value)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -133,6 +139,7 @@ func (h *ApiKeyHandler) Remove(c *gin.Context) {
res := h.DB.Where("id", id).Delete(&model.ApiKey{})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -261,6 +261,7 @@ func (h *ChatHandler) RemoveMessage(c *gin.Context) {
id := h.GetInt(c, "id", 0)
tx := h.DB.Unscoped().Where("id = ?", id).Delete(&model.ChatMessage{})
if tx.Error != nil {
logger.Error("error with update database", tx.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -69,6 +69,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
res = h.DB.Create(&item)
}
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -88,9 +89,13 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
func (h *ChatModelHandler) List(c *gin.Context) {
session := h.DB.Session(&gorm.Session{})
enable := h.GetBool(c, "enable")
platform := h.GetTrim(c, "platform")
if enable {
session = session.Where("enabled", enable)
}
if platform != "" {
session = session.Where("platform", platform)
}
var items []model.ChatModel
var cms = make([]vo.ChatModel, 0)
res := session.Order("sort_num ASC").Find(&items)
@@ -140,6 +145,7 @@ func (h *ChatModelHandler) Set(c *gin.Context) {
res := h.DB.Model(&model.ChatModel{}).Where("id = ?", data.Id).Update(data.Filed, data.Value)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -160,6 +166,7 @@ func (h *ChatModelHandler) Sort(c *gin.Context) {
for index, id := range data.Ids {
res := h.DB.Model(&model.ChatModel{}).Where("id = ?", id).Update("sort_num", data.Sorts[index])
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -177,6 +184,7 @@ func (h *ChatModelHandler) Remove(c *gin.Context) {
res := h.DB.Where("id = ?", id).Delete(&model.ChatModel{})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -48,6 +48,7 @@ func (h *ChatRoleHandler) Save(c *gin.Context) {
}
res := h.DB.Save(&role)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -115,6 +116,7 @@ func (h *ChatRoleHandler) Sort(c *gin.Context) {
for index, id := range data.Ids {
res := h.DB.Model(&model.ChatRole{}).Where("id = ?", id).Update("sort_num", data.Sorts[index])
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -137,6 +139,7 @@ func (h *ChatRoleHandler) Set(c *gin.Context) {
res := h.DB.Model(&model.ChatRole{}).Where("id = ?", data.Id).Update(data.Filed, data.Value)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -152,6 +155,7 @@ func (h *ChatRoleHandler) Remove(c *gin.Context) {
}
res := h.DB.Where("id", id).Delete(&model.ChatRole{})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "删除失败!")
return
}

View File

@@ -11,21 +11,35 @@ import (
"geekai/core"
"geekai/core/types"
"geekai/handler"
"geekai/service"
"geekai/service/mj"
"geekai/service/sd"
"geekai/store"
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
"github.com/gin-gonic/gin"
"github.com/shirou/gopsutil/host"
"gorm.io/gorm"
)
type ConfigHandler struct {
handler.BaseHandler
levelDB *store.LevelDB
levelDB *store.LevelDB
licenseService *service.LicenseService
mjServicePool *mj.ServicePool
sdServicePool *sd.ServicePool
}
func NewConfigHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB) *ConfigHandler {
return &ConfigHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}, levelDB: levelDB}
func NewConfigHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB, licenseService *service.LicenseService, mjPool *mj.ServicePool, sdPool *sd.ServicePool) *ConfigHandler {
return &ConfigHandler{
BaseHandler: handler.BaseHandler{App: app, DB: db},
levelDB: levelDB,
mjServicePool: mjPool,
sdServicePool: sdPool,
licenseService: licenseService,
}
}
func (h *ConfigHandler) Update(c *gin.Context) {
@@ -95,3 +109,89 @@ func (h *ConfigHandler) Get(c *gin.Context) {
resp.SUCCESS(c, value)
}
// Active 激活系统
func (h *ConfigHandler) Active(c *gin.Context) {
var data struct {
License string `json:"license"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
info, err := host.Info()
if err != nil {
resp.ERROR(c, err.Error())
return
}
err = h.licenseService.ActiveLicense(data.License, info.HostID)
if err != nil {
resp.ERROR(c, err.Error())
return
}
resp.SUCCESS(c, info.HostID)
}
// GetLicense 获取 License 信息
func (h *ConfigHandler) GetLicense(c *gin.Context) {
license := h.licenseService.GetLicense()
resp.SUCCESS(c, license)
}
// GetAppConfig 获取内置配置
func (h *ConfigHandler) GetAppConfig(c *gin.Context) {
resp.SUCCESS(c, gin.H{
"mj_plus": h.App.Config.MjPlusConfigs,
"mj_proxy": h.App.Config.MjProxyConfigs,
"sd": h.App.Config.SdConfigs,
"platforms": Platforms,
})
}
// SaveDrawingConfig 保存AI绘画配置
func (h *ConfigHandler) SaveDrawingConfig(c *gin.Context) {
var data struct {
Sd []types.StableDiffusionConfig `json:"sd"`
MjPlus []types.MjPlusConfig `json:"mj_plus"`
MjProxy []types.MjProxyConfig `json:"mj_proxy"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
changed := false
if configChanged(data.Sd, h.App.Config.SdConfigs) {
logger.Debugf("SD 配置变动了")
h.App.Config.SdConfigs = data.Sd
h.sdServicePool.InitServices(data.Sd)
changed = true
}
if configChanged(data.MjPlus, h.App.Config.MjPlusConfigs) || configChanged(data.MjProxy, h.App.Config.MjProxyConfigs) {
logger.Debugf("MidJourney 配置变动了")
h.App.Config.MjPlusConfigs = data.MjPlus
h.App.Config.MjProxyConfigs = data.MjProxy
h.mjServicePool.InitServices(data.MjPlus, data.MjProxy)
changed = true
}
if changed {
err := core.SaveConfig(h.App.Config)
if err != nil {
resp.ERROR(c, "更新配置文档失败!")
return
}
}
resp.SUCCESS(c)
}
func configChanged(c1 interface{}, c2 interface{}) bool {
encode1 := utils.JsonEncode(c1)
encode2 := utils.JsonEncode(c2)
return utils.Md5(encode1) != utils.Md5(encode2)
}

View File

@@ -71,6 +71,7 @@ func (h *FunctionHandler) Set(c *gin.Context) {
res := h.DB.Model(&model.Function{}).Where("id = ?", data.Id).Update(data.Filed, data.Value)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -103,6 +104,7 @@ func (h *FunctionHandler) Remove(c *gin.Context) {
if id > 0 {
res := h.DB.Delete(&model.Function{Id: uint(id)})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -50,6 +50,7 @@ func (h *MenuHandler) Save(c *gin.Context) {
Enabled: data.Enabled,
})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -86,6 +87,7 @@ func (h *MenuHandler) Enable(c *gin.Context) {
res := h.DB.Model(&model.Menu{}).Where("id", data.Id).UpdateColumn("enabled", data.Enabled)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -106,6 +108,7 @@ func (h *MenuHandler) Sort(c *gin.Context) {
for index, id := range data.Ids {
res := h.DB.Model(&model.Menu{}).Where("id", id).Update("sort_num", data.Sorts[index])
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -120,6 +123,7 @@ func (h *MenuHandler) Remove(c *gin.Context) {
if id > 0 {
res := h.DB.Where("id", id).Delete(&model.Menu{})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -94,6 +94,7 @@ func (h *OrderHandler) Remove(c *gin.Context) {
res = h.DB.Unscoped().Where("id = ?", id).Delete(&model.Order{})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -57,6 +57,7 @@ func (h *ProductHandler) Save(c *gin.Context) {
}
res := h.DB.Save(&item)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -107,6 +108,7 @@ func (h *ProductHandler) Enable(c *gin.Context) {
res := h.DB.Model(&model.Product{}).Where("id", data.Id).UpdateColumn("enabled", data.Enabled)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -127,6 +129,7 @@ func (h *ProductHandler) Sort(c *gin.Context) {
for index, id := range data.Ids {
res := h.DB.Model(&model.Product{}).Where("id", id).Update("sort_num", data.Sorts[index])
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -141,6 +144,7 @@ func (h *ProductHandler) Remove(c *gin.Context) {
if id > 0 {
res := h.DB.Where("id", id).Delete(&model.Product{})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -72,6 +72,7 @@ func (h *RewardHandler) Remove(c *gin.Context) {
if data.Id > 0 {
res := h.DB.Where("id = ?", data.Id).Delete(&model.Reward{})
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -0,0 +1,12 @@
package admin
import "geekai/core/types"
var Platforms = []types.Platform{
types.OpenAI,
types.QWen,
types.XunFei,
types.ChatGLM,
types.Baidu,
types.Azure,
}

View File

@@ -12,6 +12,7 @@ import (
"geekai/core"
"geekai/core/types"
"geekai/handler"
"geekai/service"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
@@ -24,10 +25,11 @@ import (
type UserHandler struct {
handler.BaseHandler
licenseService *service.LicenseService
}
func NewUserHandler(app *core.AppServer, db *gorm.DB) *UserHandler {
return &UserHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
func NewUserHandler(app *core.AppServer, db *gorm.DB, licenseService *service.LicenseService) *UserHandler {
return &UserHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}, licenseService: licenseService}
}
// List 用户列表
@@ -82,7 +84,13 @@ func (h *UserHandler) Save(c *gin.Context) {
resp.ERROR(c, types.InvalidArgs)
return
}
// 检测最大注册人数
var totalUser int64
h.DB.Model(&model.User{}).Count(&totalUser)
if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
return
}
var user = model.User{}
var res *gorm.DB
var userVo vo.User
@@ -103,6 +111,7 @@ func (h *UserHandler) Save(c *gin.Context) {
res = h.DB.Select("username", "status", "vip", "power", "chat_roles_json", "chat_models_json", "expired_time").Updates(&user)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -148,6 +157,7 @@ func (h *UserHandler) Save(c *gin.Context) {
}
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败")
return
}

View File

@@ -96,7 +96,7 @@ func (h *ChatRoleHandler) UpdateRole(c *gin.Context) {
res := h.DB.Model(&model.User{}).Where("id = ?", user.Id).UpdateColumn("chat_roles_json", utils.JsonEncode(data.Keys))
if res.Error != nil {
logger.Error("添加应用失败:", err)
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -17,11 +17,9 @@ import (
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"html/template"
"io"
"strings"
"time"
"unicode/utf8"
)
// 微软 Azure 模型消息发送实现
@@ -101,104 +99,12 @@ func (h *ChatHandler) sendAzureMessage(
// 消息发送成功
if len(contents) > 0 {
if message.Role == "" {
message.Role = "assistant"
}
message.Content = strings.Join(contents, "")
useMsg := types.Message{Role: "user", Content: prompt}
// 更新上下文消息,如果是调用函数则不需要更新上下文
if h.App.SysConfig.EnableContext {
chatCtx = append(chatCtx, useMsg) // 提问消息
chatCtx = append(chatCtx, message) // 回复消息
h.App.ChatContexts.Put(session.ChatId, chatCtx)
}
// 追加聊天记录
// for prompt
promptToken, err := utils.CalcTokens(prompt, req.Model)
if err != nil {
logger.Error(err)
}
historyUserMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.PromptMsg,
Icon: userVo.Avatar,
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
res := h.DB.Save(&historyUserMsg)
if res.Error != nil {
logger.Error("failed to save prompt history message: ", res.Error)
}
// 计算本次对话消耗的总 token 数量
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
replyTokens += getTotalTokens(req)
historyReplyMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.ReplyMsg,
Icon: role.Icon,
Content: message.Content,
Tokens: replyTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
res = h.DB.Create(&historyReplyMsg)
if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error)
}
// 更新用户算力
h.subUserPower(userVo, session, promptToken, replyTokens)
// 保存当前会话
var chatItem model.ChatItem
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
if res.Error != nil {
chatItem.ChatId = session.ChatId
chatItem.UserId = session.UserId
chatItem.RoleId = role.Id
chatItem.ModelId = session.Model.Id
if utf8.RuneCountInString(prompt) > 30 {
chatItem.Title = string([]rune(prompt)[:30]) + "..."
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.DB.Create(&chatItem)
}
h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt)
}
} else {
body, err := io.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("error with reading response: %v", err)
}
var res types.ApiError
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("error with decode response: %v", err)
}
if strings.Contains(res.Error.Message, "maximum context length") {
logger.Error(res.Error.Message)
h.App.ChatContexts.Delete(session.ChatId)
return h.sendMessage(ctx, session, role, prompt, ws)
} else {
return fmt.Errorf("请求 Azure API 失败:%v", res.Error)
}
body, _ := io.ReadAll(response.Body)
return fmt.Errorf("请求大模型 API 失败:%s", body)
}
return nil

View File

@@ -17,12 +17,10 @@ import (
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"html/template"
"io"
"net/http"
"strings"
"time"
"unicode/utf8"
)
type baiduResp struct {
@@ -130,99 +128,11 @@ func (h *ChatHandler) sendBaiduMessage(
// 消息发送成功
if len(contents) > 0 {
if message.Role == "" {
message.Role = "assistant"
}
message.Content = strings.Join(contents, "")
useMsg := types.Message{Role: "user", Content: prompt}
// 更新上下文消息,如果是调用函数则不需要更新上下文
if h.App.SysConfig.EnableContext {
chatCtx = append(chatCtx, useMsg) // 提问消息
chatCtx = append(chatCtx, message) // 回复消息
h.App.ChatContexts.Put(session.ChatId, chatCtx)
}
// 追加聊天记录
// for prompt
promptToken, err := utils.CalcTokens(prompt, req.Model)
if err != nil {
logger.Error(err)
}
historyUserMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.PromptMsg,
Icon: userVo.Avatar,
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
res := h.DB.Save(&historyUserMsg)
if res.Error != nil {
logger.Error("failed to save prompt history message: ", res.Error)
}
// for reply
// 计算本次对话消耗的总 token 数量
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens := replyTokens + getTotalTokens(req)
historyReplyMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.ReplyMsg,
Icon: role.Icon,
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
res = h.DB.Create(&historyReplyMsg)
if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error)
}
// 更新用户算力
h.subUserPower(userVo, session, promptToken, replyTokens)
// 保存当前会话
var chatItem model.ChatItem
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
if res.Error != nil {
chatItem.ChatId = session.ChatId
chatItem.UserId = session.UserId
chatItem.RoleId = role.Id
chatItem.ModelId = session.Model.Id
if utf8.RuneCountInString(prompt) > 30 {
chatItem.Title = string([]rune(prompt)[:30]) + "..."
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.DB.Create(&chatItem)
}
h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt)
}
} else {
body, err := io.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("error with reading response: %v", err)
}
var res struct {
Code int `json:"error_code"`
Msg string `json:"error_msg"`
}
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("error with decode response: %v", err)
}
utils.ReplyMessage(ws, "请求百度文心大模型 API 失败:"+res.Msg)
body, _ := io.ReadAll(response.Body)
return fmt.Errorf("请求大模型 API 失败:%s", body)
}
return nil

View File

@@ -17,16 +17,19 @@ import (
"geekai/core/types"
"geekai/handler"
logger2 "geekai/logger"
"geekai/service"
"geekai/service/oss"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"html/template"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"unicode/utf8"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
@@ -38,15 +41,17 @@ var logger = logger2.GetLogger()
type ChatHandler struct {
handler.BaseHandler
redis *redis.Client
uploadManager *oss.UploaderManager
redis *redis.Client
uploadManager *oss.UploaderManager
licenseService *service.LicenseService
}
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager) *ChatHandler {
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService) *ChatHandler {
return &ChatHandler{
BaseHandler: handler.BaseHandler{App: app, DB: db},
redis: redis,
uploadManager: manager,
BaseHandler: handler.BaseHandler{App: app, DB: db},
redis: redis,
uploadManager: manager,
licenseService: licenseService,
}
}
@@ -119,7 +124,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
MaxContext: chatModel.MaxContext,
Temperature: chatModel.Temperature,
KeyId: chatModel.KeyId,
Platform: types.Platform(chatModel.Platform)}
Platform: chatModel.Platform}
logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username)
// 保存会话连接
@@ -215,11 +220,11 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
Stream: true,
}
switch session.Model.Platform {
case types.Azure, types.ChatGLM, types.Baidu, types.XunFei:
case types.Azure.Value, types.ChatGLM.Value, types.Baidu.Value, types.XunFei.Value:
req.Temperature = session.Model.Temperature
req.MaxTokens = session.Model.MaxTokens
break
case types.OpenAI:
case types.OpenAI.Value:
req.Temperature = session.Model.Temperature
req.MaxTokens = session.Model.MaxTokens
// OpenAI 支持函数功能
@@ -236,8 +241,6 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
if err != nil {
continue
}
required := parameters["required"]
delete(parameters, "required")
tool := types.Tool{
Type: "function",
Function: types.Function{
@@ -246,10 +249,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
Parameters: parameters,
},
}
// Fixed: compatible for gpt4-turbo-xxx model
if !strings.HasPrefix(req.Model, "gpt-4-turbo-") {
tool.Function.Required = required
if v, ok := parameters["required"]; v == nil || !ok {
tool.Function.Parameters["required"] = []string{}
}
tools = append(tools, tool)
}
@@ -258,7 +259,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
req.Tools = tools
req.ToolChoice = "auto"
}
case types.QWen:
case types.QWen.Value:
req.Parameters = map[string]interface{}{
"max_tokens": session.Model.MaxTokens,
"temperature": session.Model.Temperature,
@@ -322,14 +323,14 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
reqMgs = append(reqMgs, m)
}
if session.Model.Platform == types.QWen {
if session.Model.Platform == types.QWen.Value {
req.Input = make(map[string]interface{})
reqMgs = append(reqMgs, types.Message{
Role: "user",
Content: prompt,
})
req.Input["messages"] = reqMgs
} else if session.Model.Platform == types.OpenAI { // extract image for gpt-vision model
} else if session.Model.Platform == types.OpenAI.Value || session.Model.Platform == types.Azure.Value { // extract image for gpt-vision model
imgURLs := utils.ExtractImgURL(prompt)
logger.Debugf("detected IMG: %+v", imgURLs)
var content interface{}
@@ -367,17 +368,17 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
logger.Debugf("%+v", req.Messages)
switch session.Model.Platform {
case types.Azure:
case types.Azure.Value:
return h.sendAzureMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
case types.OpenAI:
case types.OpenAI.Value:
return h.sendOpenAiMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
case types.ChatGLM:
case types.ChatGLM.Value:
return h.sendChatGLMMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
case types.Baidu:
case types.Baidu.Value:
return h.sendBaiduMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
case types.XunFei:
case types.XunFei.Value:
return h.sendXunFeiMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
case types.QWen:
case types.QWen.Value:
return h.sendQWenMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
}
@@ -457,27 +458,35 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi
}
// use the last unused key
if apiKey.Id == 0 {
h.DB.Debug().Where("platform", session.Model.Platform).Where("type", "chat").Where("enabled", true).Order("last_used_at ASC").First(apiKey)
h.DB.Where("platform", session.Model.Platform).Where("type", "chat").Where("enabled", true).Order("last_used_at ASC").First(apiKey)
}
if apiKey.Id == 0 {
return nil, errors.New("no available key, please import key")
}
// ONLY allow apiURL in blank list
if session.Model.Platform == types.OpenAI.Value {
err := h.licenseService.IsValidApiURL(apiKey.ApiURL)
if err != nil {
return nil, err
}
}
var apiURL string
switch session.Model.Platform {
case types.Azure:
case types.Azure.Value:
md := strings.Replace(req.Model, ".", "", 1)
apiURL = strings.Replace(apiKey.ApiURL, "{model}", md, 1)
break
case types.ChatGLM:
case types.ChatGLM.Value:
apiURL = strings.Replace(apiKey.ApiURL, "{model}", req.Model, 1)
req.Prompt = req.Messages // 使用 prompt 字段替代 message 字段
req.Messages = nil
break
case types.Baidu:
case types.Baidu.Value:
apiURL = strings.Replace(apiKey.ApiURL, "{model}", req.Model, 1)
break
case types.QWen:
case types.QWen.Value:
apiURL = apiKey.ApiURL
req.Messages = nil
break
@@ -487,7 +496,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi
// 更新 API KEY 的最后使用时间
h.DB.Model(apiKey).UpdateColumn("last_used_at", time.Now().Unix())
// 百度文心,需要串接 access_token
if session.Model.Platform == types.Baidu {
if session.Model.Platform == types.Baidu.Value {
token, err := h.getBaiduToken(apiKey.Value)
if err != nil {
return nil, err
@@ -523,22 +532,22 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, sessi
}
logger.Debugf("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s, Model: %s", session.Model.Platform, apiURL, apiKey.Value, apiKey.ProxyURL, req.Model)
switch session.Model.Platform {
case types.Azure:
case types.Azure.Value:
request.Header.Set("api-key", apiKey.Value)
break
case types.ChatGLM:
case types.ChatGLM.Value:
token, err := h.getChatGLMToken(apiKey.Value)
if err != nil {
return nil, err
}
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
break
case types.Baidu:
case types.Baidu.Value:
request.RequestURI = ""
case types.OpenAI:
case types.OpenAI.Value:
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value))
break
case types.QWen:
case types.QWen.Value:
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value))
request.Header.Set("X-DashScope-SSE", "enable")
break
@@ -572,6 +581,98 @@ func (h *ChatHandler) subUserPower(userVo vo.User, session *types.ChatSession, p
}
func (h *ChatHandler) saveChatHistory(
req types.ApiRequest,
prompt string,
contents []string,
message types.Message,
chatCtx []types.Message,
session *types.ChatSession,
role model.ChatRole,
userVo vo.User,
promptCreatedAt time.Time,
replyCreatedAt time.Time) {
if message.Role == "" {
message.Role = "assistant"
}
message.Content = strings.Join(contents, "")
useMsg := types.Message{Role: "user", Content: prompt}
// 更新上下文消息,如果是调用函数则不需要更新上下文
if h.App.SysConfig.EnableContext {
chatCtx = append(chatCtx, useMsg) // 提问消息
chatCtx = append(chatCtx, message) // 回复消息
h.App.ChatContexts.Put(session.ChatId, chatCtx)
}
// 追加聊天记录
// for prompt
promptToken, err := utils.CalcTokens(prompt, req.Model)
if err != nil {
logger.Error(err)
}
historyUserMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.PromptMsg,
Icon: userVo.Avatar,
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
res := h.DB.Save(&historyUserMsg)
if res.Error != nil {
logger.Error("failed to save prompt history message: ", res.Error)
}
// for reply
// 计算本次对话消耗的总 token 数量
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens := replyTokens + getTotalTokens(req)
historyReplyMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.ReplyMsg,
Icon: role.Icon,
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
res = h.DB.Create(&historyReplyMsg)
if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error)
}
// 更新用户算力
if session.Model.Power > 0 {
h.subUserPower(userVo, session, promptToken, replyTokens)
}
// 保存当前会话
var chatItem model.ChatItem
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
if res.Error != nil {
chatItem.ChatId = session.ChatId
chatItem.UserId = session.UserId
chatItem.RoleId = role.Id
chatItem.ModelId = session.Model.Id
if utf8.RuneCountInString(prompt) > 30 {
chatItem.Title = string([]rune(prompt)[:30]) + "..."
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.DB.Create(&chatItem)
}
}
// 将AI回复消息中生成的图片链接下载到本地
func (h *ChatHandler) extractImgUrl(text string) string {
pattern := `!\[([^\]]*)]\(([^)]+)\)`

View File

@@ -10,7 +10,6 @@ package chatimpl
import (
"bufio"
"context"
"encoding/json"
"errors"
"fmt"
"geekai/core/types"
@@ -18,11 +17,9 @@ import (
"geekai/store/vo"
"geekai/utils"
"github.com/golang-jwt/jwt/v5"
"html/template"
"io"
"strings"
"time"
"unicode/utf8"
)
// 清华大学 ChatGML 消息发送实现
@@ -108,103 +105,11 @@ func (h *ChatHandler) sendChatGLMMessage(
// 消息发送成功
if len(contents) > 0 {
if message.Role == "" {
message.Role = "assistant"
}
message.Content = strings.Join(contents, "")
useMsg := types.Message{Role: "user", Content: prompt}
// 更新上下文消息,如果是调用函数则不需要更新上下文
if h.App.SysConfig.EnableContext {
chatCtx = append(chatCtx, useMsg) // 提问消息
chatCtx = append(chatCtx, message) // 回复消息
h.App.ChatContexts.Put(session.ChatId, chatCtx)
}
// 追加聊天记录
// for prompt
promptToken, err := utils.CalcTokens(prompt, req.Model)
if err != nil {
logger.Error(err)
}
historyUserMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.PromptMsg,
Icon: userVo.Avatar,
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
res := h.DB.Save(&historyUserMsg)
if res.Error != nil {
logger.Error("failed to save prompt history message: ", res.Error)
}
// for reply
// 计算本次对话消耗的总 token 数量
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens := replyTokens + getTotalTokens(req)
historyReplyMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.ReplyMsg,
Icon: role.Icon,
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
res = h.DB.Create(&historyReplyMsg)
if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error)
}
// 更新用户算力
h.subUserPower(userVo, session, promptToken, replyTokens)
// 保存当前会话
var chatItem model.ChatItem
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
if res.Error != nil {
chatItem.ChatId = session.ChatId
chatItem.UserId = session.UserId
chatItem.RoleId = role.Id
chatItem.ModelId = session.Model.Id
if utf8.RuneCountInString(prompt) > 30 {
chatItem.Title = string([]rune(prompt)[:30]) + "..."
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.DB.Create(&chatItem)
}
h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt)
}
} else {
body, err := io.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("error with reading response: %v", err)
}
var res struct {
Code int `json:"code"`
Success bool `json:"success"`
Msg string `json:"msg"`
}
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("error with decode response: %v", err)
}
if !res.Success {
utils.ReplyMessage(ws, "请求 ChatGLM 失败:"+res.Msg)
}
body, _ := io.ReadAll(response.Body)
return fmt.Errorf("请求大模型 API 失败:%s", body)
}
return nil

View File

@@ -17,13 +17,10 @@ import (
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"html/template"
req2 "github.com/imroc/req/v3"
"io"
"strings"
"time"
"unicode/utf8"
req2 "github.com/imroc/req/v3"
)
// OPenAI 消息发送实现
@@ -145,7 +142,7 @@ func (h *ChatHandler) sendOpenAiMessage(
}
if toolCall { // 调用函数完成任务
var params map[string]interface{}
params := make(map[string]interface{})
_ = utils.JsonDecode(strings.Join(arguments, ""), &params)
logger.Debugf("函数名称: %s, 函数参数:%s", function.Name, params)
params["user_id"] = userVo.Id
@@ -178,126 +175,11 @@ func (h *ChatHandler) sendOpenAiMessage(
// 消息发送成功
if len(contents) > 0 {
if message.Role == "" {
message.Role = "assistant"
}
message.Content = strings.Join(contents, "")
useMsg := types.Message{Role: "user", Content: prompt}
// 更新上下文消息,如果是调用函数则不需要更新上下文
if h.App.SysConfig.EnableContext && toolCall == false {
chatCtx = append(chatCtx, useMsg) // 提问消息
chatCtx = append(chatCtx, message) // 回复消息
h.App.ChatContexts.Put(session.ChatId, chatCtx)
}
// 追加聊天记录
useContext := true
if toolCall {
useContext = false
}
// for prompt
promptToken, err := utils.CalcTokens(prompt, req.Model)
if err != nil {
logger.Error(err)
}
historyUserMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.PromptMsg,
Icon: userVo.Avatar,
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: useContext,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
res := h.DB.Save(&historyUserMsg)
if res.Error != nil {
logger.Error("failed to save prompt history message: ", res.Error)
}
// 计算本次对话消耗的总 token 数量
var replyTokens = 0
if toolCall { // prompt + 函数名 + 参数 token
tokens, _ := utils.CalcTokens(function.Name, req.Model)
replyTokens += tokens
tokens, _ = utils.CalcTokens(utils.InterfaceToString(arguments), req.Model)
replyTokens += tokens
} else {
replyTokens, _ = utils.CalcTokens(message.Content, req.Model)
}
replyTokens += getTotalTokens(req)
historyReplyMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.ReplyMsg,
Icon: role.Icon,
Content: h.extractImgUrl(message.Content),
Tokens: replyTokens,
UseContext: useContext,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
res = h.DB.Create(&historyReplyMsg)
if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error)
}
// 更新用户算力
h.subUserPower(userVo, session, promptToken, replyTokens)
// 保存当前会话
var chatItem model.ChatItem
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
if res.Error != nil {
chatItem.ChatId = session.ChatId
chatItem.UserId = session.UserId
chatItem.RoleId = role.Id
chatItem.ModelId = session.Model.Id
if utf8.RuneCountInString(prompt) > 30 {
chatItem.Title = string([]rune(prompt)[:30]) + "..."
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.DB.Create(&chatItem)
}
h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt)
}
} else {
body, err := io.ReadAll(response.Body)
if err != nil {
utils.ReplyMessage(ws, "请求 OpenAI API 失败:"+err.Error())
return fmt.Errorf("error with reading response: %v", err)
}
var res types.ApiError
err = json.Unmarshal(body, &res)
if err != nil {
utils.ReplyMessage(ws, "请求 OpenAI API 失败:\n"+"```\n"+string(body)+"```")
return fmt.Errorf("error with decode response: %v", err)
}
// OpenAI API 调用异常处理
if strings.Contains(res.Error.Message, "This key is associated with a deactivated account") {
utils.ReplyMessage(ws, "请求 OpenAI API 失败API KEY 所关联的账户被禁用。")
// 移除当前 API key
h.DB.Where("value = ?", apiKey).Delete(&model.ApiKey{})
} else if strings.Contains(res.Error.Message, "You exceeded your current quota") {
utils.ReplyMessage(ws, "请求 OpenAI API 失败API KEY 触发并发限制,请稍后再试。")
} else if strings.Contains(res.Error.Message, "This model's maximum context length") {
logger.Error(res.Error.Message)
utils.ReplyMessage(ws, "当前会话上下文长度超出限制,已为您清空会话上下文!")
h.App.ChatContexts.Delete(session.ChatId)
return h.sendMessage(ctx, session, role, prompt, ws)
} else {
utils.ReplyMessage(ws, "请求 OpenAI API 失败:"+res.Error.Message)
}
body, _ := io.ReadAll(response.Body)
return fmt.Errorf("请求 OpenAI API 失败:%s", body)
}
return nil

View File

@@ -10,18 +10,15 @@ package chatimpl
import (
"bufio"
"context"
"encoding/json"
"fmt"
"geekai/core/types"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"github.com/syndtr/goleveldb/leveldb/errors"
"html/template"
"io"
"strings"
"time"
"unicode/utf8"
)
type qWenResp struct {
@@ -142,100 +139,11 @@ func (h *ChatHandler) sendQWenMessage(
// 消息发送成功
if len(contents) > 0 {
if message.Role == "" {
message.Role = "assistant"
}
message.Content = strings.Join(contents, "")
useMsg := types.Message{Role: "user", Content: prompt}
// 更新上下文消息,如果是调用函数则不需要更新上下文
if h.App.SysConfig.EnableContext {
chatCtx = append(chatCtx, useMsg) // 提问消息
chatCtx = append(chatCtx, message) // 回复消息
h.App.ChatContexts.Put(session.ChatId, chatCtx)
}
// 追加聊天记录
// for prompt
promptToken, err := utils.CalcTokens(prompt, req.Model)
if err != nil {
logger.Error(err)
}
historyUserMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.PromptMsg,
Icon: userVo.Avatar,
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
res := h.DB.Save(&historyUserMsg)
if res.Error != nil {
logger.Error("failed to save prompt history message: ", res.Error)
}
// for reply
// 计算本次对话消耗的总 token 数量
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens := replyTokens + getTotalTokens(req)
historyReplyMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.ReplyMsg,
Icon: role.Icon,
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
res = h.DB.Create(&historyReplyMsg)
if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error)
}
// 更新用户算力
h.subUserPower(userVo, session, promptToken, replyTokens)
// 保存当前会话
var chatItem model.ChatItem
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
if res.Error != nil {
chatItem.ChatId = session.ChatId
chatItem.UserId = session.UserId
chatItem.RoleId = role.Id
chatItem.ModelId = session.Model.Id
if utf8.RuneCountInString(prompt) > 30 {
chatItem.Title = string([]rune(prompt)[:30]) + "..."
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.DB.Create(&chatItem)
}
h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt)
}
} else {
body, err := io.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("error with reading response: %v", err)
}
var res struct {
Code int `json:"error_code"`
Msg string `json:"error_msg"`
}
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("error with decode response: %v", err)
}
utils.ReplyMessage(ws, "请求通义千问大模型 API 失败:"+res.Msg)
body, _ := io.ReadAll(response.Body)
return fmt.Errorf("请求大模型 API 失败:%s", body)
}
return nil

View File

@@ -21,13 +21,11 @@ import (
"geekai/utils"
"github.com/gorilla/websocket"
"gorm.io/gorm"
"html/template"
"io"
"net/http"
"net/url"
"strings"
"time"
"unicode/utf8"
)
type xunFeiResp struct {
@@ -181,89 +179,10 @@ func (h *ChatHandler) sendXunFeiMessage(
}
}
// 消息发送成功
if len(contents) > 0 {
if message.Role == "" {
message.Role = "assistant"
}
message.Content = strings.Join(contents, "")
useMsg := types.Message{Role: "user", Content: prompt}
// 更新上下文消息,如果是调用函数则不需要更新上下文
if h.App.SysConfig.EnableContext {
chatCtx = append(chatCtx, useMsg) // 提问消息
chatCtx = append(chatCtx, message) // 回复消息
h.App.ChatContexts.Put(session.ChatId, chatCtx)
}
// 追加聊天记录
// for prompt
promptToken, err := utils.CalcTokens(prompt, req.Model)
if err != nil {
logger.Error(err)
}
historyUserMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.PromptMsg,
Icon: userVo.Avatar,
Content: template.HTMLEscapeString(prompt),
Tokens: promptToken,
UseContext: true,
Model: req.Model,
}
historyUserMsg.CreatedAt = promptCreatedAt
historyUserMsg.UpdatedAt = promptCreatedAt
res := h.DB.Save(&historyUserMsg)
if res.Error != nil {
logger.Error("failed to save prompt history message: ", res.Error)
}
// for reply
// 计算本次对话消耗的总 token 数量
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
totalTokens := replyTokens + getTotalTokens(req)
historyReplyMsg := model.ChatMessage{
UserId: userVo.Id,
ChatId: session.ChatId,
RoleId: role.Id,
Type: types.ReplyMsg,
Icon: role.Icon,
Content: message.Content,
Tokens: totalTokens,
UseContext: true,
Model: req.Model,
}
historyReplyMsg.CreatedAt = replyCreatedAt
historyReplyMsg.UpdatedAt = replyCreatedAt
res = h.DB.Create(&historyReplyMsg)
if res.Error != nil {
logger.Error("failed to save reply history message: ", res.Error)
}
// 更新用户算力
h.subUserPower(userVo, session, promptToken, replyTokens)
// 保存当前会话
var chatItem model.ChatItem
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
if res.Error != nil {
chatItem.ChatId = session.ChatId
chatItem.UserId = session.UserId
chatItem.RoleId = role.Id
chatItem.ModelId = session.Model.Id
if utf8.RuneCountInString(prompt) > 30 {
chatItem.Title = string([]rune(prompt)[:30]) + "..."
} else {
chatItem.Title = prompt
}
chatItem.Model = req.Model
h.DB.Create(&chatItem)
}
h.saveChatHistory(req, prompt, contents, message, chatCtx, session, role, userVo, promptCreatedAt, replyCreatedAt)
}
return nil
}

View File

@@ -9,6 +9,7 @@ package handler
import (
"geekai/core"
"geekai/service"
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
@@ -19,10 +20,11 @@ import (
type ConfigHandler struct {
BaseHandler
licenseService *service.LicenseService
}
func NewConfigHandler(app *core.AppServer, db *gorm.DB) *ConfigHandler {
return &ConfigHandler{BaseHandler: BaseHandler{App: app, DB: db}}
func NewConfigHandler(app *core.AppServer, db *gorm.DB, licenseService *service.LicenseService) *ConfigHandler {
return &ConfigHandler{BaseHandler: BaseHandler{App: app, DB: db}, licenseService: licenseService}
}
// Get 获取指定的系统配置
@@ -44,3 +46,9 @@ func (h *ConfigHandler) Get(c *gin.Context) {
resp.SUCCESS(c, value)
}
// License 获取 License 配置
func (h *ConfigHandler) License(c *gin.Context) {
license := h.licenseService.GetLicense()
resp.SUCCESS(c, license.Configs)
}

View File

@@ -253,6 +253,7 @@ func (h *DallJobHandler) Publish(c *gin.Context) {
res := h.DB.Model(&model.DallJob{Id: data.Id}).UpdateColumn("publish", true)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败")
return
}

View File

@@ -183,45 +183,29 @@ func (h *MarkMapHandler) sendMessage(client *types.WsClient, prompt string, mode
utils.ReplyChunkMessage(client, types.WsMessage{Type: types.WsEnd})
} else {
body, err := io.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("读取响应失败: %v", err)
}
var res types.ApiError
err = json.Unmarshal(body, &res)
if err != nil {
return fmt.Errorf("解析响应失败: %v", err)
}
// OpenAI API 调用异常处理
if strings.Contains(res.Error.Message, "This key is associated with a deactivated account") {
// remove key
h.DB.Where("value = ?", apiKey).Delete(&model.ApiKey{})
return errors.New("请求 OpenAI API 失败API KEY 所关联的账户被禁用。")
} else if strings.Contains(res.Error.Message, "You exceeded your current quota") {
return errors.New("请求 OpenAI API 失败API KEY 触发并发限制,请稍后再试。")
} else {
return fmt.Errorf("请求 OpenAI API 失败:%v", res.Error.Message)
}
body, _ := io.ReadAll(response.Body)
return fmt.Errorf("请求 OpenAI API 失败:%s", string(body))
}
// 扣减算力
res = h.DB.Model(&model.User{}).Where("id", userId).UpdateColumn("power", gorm.Expr("power - ?", chatModel.Power))
if res.Error == nil {
// 记录算力消费日志
var u model.User
h.DB.Where("id", userId).First(&u)
h.DB.Create(&model.PowerLog{
UserId: u.Id,
Username: u.Username,
Type: types.PowerConsume,
Amount: chatModel.Power,
Mark: types.PowerSub,
Balance: u.Power,
Model: chatModel.Value,
Remark: fmt.Sprintf("AI绘制思维导图模型名称%s, ", chatModel.Value),
CreatedAt: time.Now(),
})
if chatModel.Power > 0 {
res = h.DB.Model(&model.User{}).Where("id", userId).UpdateColumn("power", gorm.Expr("power - ?", chatModel.Power))
if res.Error == nil {
// 记录算力消费日志
var u model.User
h.DB.Where("id", userId).First(&u)
h.DB.Create(&model.PowerLog{
UserId: u.Id,
Username: u.Username,
Type: types.PowerConsume,
Amount: chatModel.Power,
Mark: types.PowerSub,
Balance: u.Power,
Model: chatModel.Value,
Remark: fmt.Sprintf("AI绘制思维导图模型名称%s, ", chatModel.Value),
CreatedAt: time.Now(),
})
}
}
return nil
@@ -235,7 +219,7 @@ func (h *MarkMapHandler) doRequest(req types.ApiRequest, chatModel model.ChatMod
}
// use the last unused key
if apiKey.Id == 0 {
res = h.DB.Where("platform", types.OpenAI).
res = h.DB.Where("platform", types.OpenAI.Value).
Where("type", "chat").
Where("enabled", true).Order("last_used_at ASC").First(apiKey)
}

View File

@@ -8,6 +8,8 @@ package handler
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"encoding/base64"
"fmt"
"geekai/core"
"geekai/core/types"
"geekai/service"
@@ -17,8 +19,6 @@ import (
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"encoding/base64"
"fmt"
"net/http"
"strings"
"time"
@@ -205,7 +205,6 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
h.pool.PushTask(types.MjTask{
Id: job.Id,
TaskId: taskId,
SessionId: data.SessionId,
Type: types.TaskType(data.TaskType),
Prompt: data.Prompt,
NegPrompt: data.NegPrompt,
@@ -283,7 +282,6 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
h.pool.PushTask(types.MjTask{
Id: job.Id,
SessionId: data.SessionId,
Type: types.TaskUpscale,
Prompt: data.Prompt,
UserId: userId,
@@ -350,7 +348,6 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
h.pool.PushTask(types.MjTask{
Id: job.Id,
SessionId: data.SessionId,
Type: types.TaskVariation,
Prompt: data.Prompt,
UserId: userId,
@@ -511,6 +508,7 @@ func (h *MidJourneyHandler) Publish(c *gin.Context) {
res := h.DB.Model(&model.MidJourneyJob{Id: data.Id}).UpdateColumn("publish", data.Action)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败")
return
}

View File

@@ -8,6 +8,9 @@ package handler
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"embed"
"encoding/base64"
"fmt"
"geekai/core"
"geekai/core/types"
"geekai/service"
@@ -15,9 +18,6 @@ import (
"geekai/store/model"
"geekai/utils"
"geekai/utils/resp"
"embed"
"encoding/base64"
"fmt"
"github.com/shopspring/decimal"
"math"
"net/http"
@@ -29,48 +29,73 @@ import (
"gorm.io/gorm"
)
const (
PayWayAlipay = "支付宝"
PayWayXunHu = "虎皮椒"
PayWayJs = "PayJS"
type PayWay struct {
Name string `json:"name"`
Value string `json:"value"`
}
var (
PayWayAlipay = PayWay{Name: "支付宝", Value: "alipay"}
PayWayXunHu = PayWay{Name: "虎皮椒", Value: "hupi"}
PayWayJs = PayWay{Name: "PayJS", Value: "payjs"}
PayWayWechat = PayWay{Name: "微信支付", Value: "wechat"}
)
// PaymentHandler 支付服务回调 handler
type PaymentHandler struct {
BaseHandler
alipayService *payment.AlipayService
huPiPayService *payment.HuPiPayService
js *payment.PayJS
snowflake *service.Snowflake
fs embed.FS
lock sync.Mutex
alipayService *payment.AlipayService
huPiPayService *payment.HuPiPayService
jsPayService *payment.JPayService
wechatPayService *payment.WechatPayService
snowflake *service.Snowflake
fs embed.FS
lock sync.Mutex
signKey string // 用来签名的随机秘钥
}
func NewPaymentHandler(
server *core.AppServer,
alipayService *payment.AlipayService,
huPiPayService *payment.HuPiPayService,
js *payment.PayJS,
jsPayService *payment.JPayService,
wechatPayService *payment.WechatPayService,
db *gorm.DB,
snowflake *service.Snowflake,
fs embed.FS) *PaymentHandler {
return &PaymentHandler{
alipayService: alipayService,
huPiPayService: huPiPayService,
js: js,
snowflake: snowflake,
fs: fs,
lock: sync.Mutex{},
alipayService: alipayService,
huPiPayService: huPiPayService,
jsPayService: jsPayService,
wechatPayService: wechatPayService,
snowflake: snowflake,
fs: fs,
lock: sync.Mutex{},
BaseHandler: BaseHandler{
App: server,
DB: db,
},
signKey: utils.RandString(32),
}
}
func (h *PaymentHandler) DoPay(c *gin.Context) {
orderNo := h.GetTrim(c, "order_no")
payWay := h.GetTrim(c, "pay_way")
t := h.GetInt(c, "t", 0)
sign := h.GetTrim(c, "sign")
signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, payWay, t, h.signKey)
newSign := utils.Sha256(signStr)
if newSign != sign {
resp.ERROR(c, "订单签名错误!")
return
}
// 检查二维码是否过期
if time.Now().Unix()-int64(t) > int64(h.App.SysConfig.OrderPayTimeout) {
resp.ERROR(c, "支付二维码已过期,请重新生成!")
return
}
if orderNo == "" {
resp.ERROR(c, types.InvalidArgs)
@@ -92,13 +117,10 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
// 更新扫码状态
h.DB.Model(&order).UpdateColumn("status", types.OrderScanned)
if payWay == "alipay" { // 支付宝
// 生成支付链接
notifyURL := h.App.Config.AlipayConfig.NotifyURL
returnURL := "" // 关闭同步回跳
amount := fmt.Sprintf("%.2f", order.Amount)
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, notifyURL, returnURL, amount, order.Subject)
if payWay == "alipay" { // 支付宝
amount := fmt.Sprintf("%.2f", order.Amount)
uri, err := h.alipayService.PayUrlMobile(order.OrderNo, amount, order.Subject)
if err != nil {
resp.ERROR(c, "error with generate pay url: "+err.Error())
return
@@ -198,14 +220,21 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
var notifyURL string
switch data.PayWay {
case "hupi":
payWay = PayWayXunHu
payWay = PayWayXunHu.Value
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
break
case "payjs":
payWay = PayWayJs
payWay = PayWayJs.Value
notifyURL = h.App.Config.JPayConfig.NotifyURL
default:
payWay = PayWayAlipay
break
case "alipay":
payWay = PayWayAlipay.Value
notifyURL = h.App.Config.AlipayConfig.NotifyURL
break
default:
payWay = PayWayWechat.Value
notifyURL = h.App.Config.WechatPayConfig.NotifyURL
}
// 创建订单
remark := types.OrderRemark{
@@ -241,7 +270,7 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
OutTradeNo: order.OrderNo,
Subject: product.Name,
}
r := h.js.Pay(params)
r := h.jsPayService.Pay(params)
if r.IsOK() {
resp.SUCCESS(c, gin.H{"order_no": order.OrderNo, "image": r.Qrcode})
return
@@ -260,6 +289,8 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
} else {
logo = "res/img/alipay.jpg"
}
} else if data.PayWay == "wechat" {
logo = "res/img/wechat-pay.jpg"
}
file, err := h.fs.Open(logo)
@@ -273,8 +304,21 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
resp.ERROR(c, err.Error())
return
}
imageURL := fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s", parse.Scheme, parse.Host, orderNo, data.PayWay)
timestamp := time.Now().Unix()
signStr := fmt.Sprintf("%s-%s-%d-%s", orderNo, data.PayWay, timestamp, h.signKey)
sign := utils.Sha256(signStr)
var imageURL string
if data.PayWay == "wechat" {
payUrl, err := h.wechatPayService.PayUrlNative(order.OrderNo, int(math.Floor(order.Amount*100)), product.Name)
if err != nil {
resp.ERROR(c, "error with generating wechat payment qrcode: "+err.Error())
return
} else {
imageURL = payUrl
}
} else {
imageURL = fmt.Sprintf("%s://%s/api/payment/doPay?order_no=%s&pay_way=%s&t=%d&sign=%s", parse.Scheme, parse.Host, orderNo, data.PayWay, timestamp, sign)
}
imgData, err := utils.GenQrcode(imageURL, 400, file)
if err != nil {
resp.ERROR(c, err.Error())
@@ -321,9 +365,11 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
var payURL string
switch data.PayWay {
case "hupi":
payWay = PayWayXunHu
payWay = PayWayXunHu.Name
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
returnURL = h.App.Config.HuPiPayConfig.ReturnURL
parse, _ := url.Parse(h.App.Config.HuPiPayConfig.ReturnURL)
baseURL := fmt.Sprintf("%s://%s", parse.Scheme, parse.Host)
params := payment.HuPiPayReq{
Version: "1.1",
TradeOrderId: orderNo,
@@ -333,16 +379,19 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
ReturnURL: returnURL,
CallbackURL: returnURL,
WapName: "极客学长",
WapUrl: baseURL,
Type: "WAP",
}
r, err := h.huPiPayService.Pay(params)
if err != nil {
logger.Error("error with generating Pay URL: ", err.Error())
resp.ERROR(c, "error with generating Pay URL: "+err.Error())
errMsg := "error with generating Pay Hupi URL: " + err.Error()
logger.Error(errMsg)
resp.ERROR(c, errMsg)
return
}
payURL = r.URL
case "payjs":
payWay = PayWayJs
payWay = PayWayJs.Name
notifyURL = h.App.Config.JPayConfig.NotifyURL
returnURL = h.App.Config.JPayConfig.ReturnURL
totalFee := decimal.NewFromFloat(product.Price).Sub(decimal.NewFromFloat(product.Discount)).Mul(decimal.NewFromInt(100)).IntPart()
@@ -352,14 +401,22 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
params.Add("body", product.Name)
params.Add("notify_url", notifyURL)
params.Add("auto", "0")
payURL = h.js.PayH5(params)
payURL = h.jsPayService.PayH5(params)
case "alipay":
payWay = PayWayAlipay
notifyURL = h.App.Config.AlipayConfig.NotifyURL
returnURL = h.App.Config.AlipayConfig.ReturnURL
payURL, err = h.alipayService.PayUrlMobile(orderNo, notifyURL, returnURL, fmt.Sprintf("%.2f", amount), product.Name)
payWay = PayWayAlipay.Name
payURL, err = h.alipayService.PayUrlMobile(orderNo, fmt.Sprintf("%.2f", amount), product.Name)
if err != nil {
resp.ERROR(c, "error with generating Pay URL: "+err.Error())
errMsg := "error with generating Alipay URL: " + err.Error()
resp.ERROR(c, errMsg)
return
}
case "wechat":
payWay = PayWayWechat.Name
payURL, err = h.wechatPayService.PayUrlH5(orderNo, int(amount*100), product.Name, c.ClientIP())
if err != nil {
errMsg := "error with generating Wechat URL: " + err.Error()
logger.Error(errMsg)
resp.ERROR(c, errMsg)
return
}
default:
@@ -471,7 +528,7 @@ func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
h.DB.Model(&model.Product{}).Where("id = ?", order.ProductId).UpdateColumn("sales", gorm.Expr("sales + ?", 1))
// 记录算力充值日志
if opt != "" {
if power > 0 {
h.DB.Create(&model.PowerLog{
UserId: user.Id,
Username: user.Username,
@@ -500,6 +557,9 @@ func (h *PaymentHandler) GetPayWays(c *gin.Context) {
if h.App.Config.JPayConfig.Enabled {
data["payjs"] = gin.H{"name": h.App.Config.JPayConfig.Name}
}
if h.App.Config.WechatPayConfig.Enabled {
data["wechat"] = gin.H{"name": "wechat"}
}
resp.SUCCESS(c, data)
}
@@ -538,7 +598,7 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
}
// TODO验证交易签名
res := h.alipayService.TradeVerify(c.Request.Form)
res := h.alipayService.TradeVerify(c.Request)
logger.Infof("验证支付结果:%+v", res)
if !res.Success() {
logger.Error("订单校验失败:", res.Message)
@@ -566,7 +626,7 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
orderNo := c.Request.Form.Get("out_trade_no")
returnCode := c.Request.Form.Get("return_code")
logger.Infof("收到订单支付回调,订单 NO%s支付结果代码%v", orderNo, returnCode)
logger.Infof("收到PayJs订单支付回调,订单 NO%s支付结果代码%v", orderNo, returnCode)
// 支付失败
if returnCode != "1" {
return
@@ -574,7 +634,7 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
// 校验订单支付状态
tradeNo := c.Request.Form.Get("payjs_order_id")
err = h.js.Check(tradeNo)
err = h.jsPayService.TradeVerify(tradeNo)
if err != nil {
logger.Error("订单校验失败:", err)
c.String(http.StatusOK, "fail")
@@ -589,3 +649,30 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
c.String(http.StatusOK, "success")
}
// WechatPayNotify 微信商户支付异步回调
func (h *PaymentHandler) WechatPayNotify(c *gin.Context) {
err := c.Request.ParseForm()
if err != nil {
c.String(http.StatusOK, "fail")
return
}
result := h.wechatPayService.TradeVerify(c.Request)
if !result.Success() {
logger.Error("订单校验失败:", err)
c.JSON(http.StatusBadRequest, gin.H{
"code": "FAIL",
"message": err.Error(),
})
return
}
err = h.notify(result.OutTradeNo, result.TradeId)
if err != nil {
c.String(http.StatusOK, "fail")
return
}
c.String(http.StatusOK, "success")
}

View File

@@ -8,13 +8,13 @@ package handler
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"fmt"
"geekai/core"
"geekai/core/types"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"math"
@@ -73,6 +73,7 @@ func (h *RewardHandler) Verify(c *gin.Context) {
res = tx.Model(&user).UpdateColumn("power", gorm.Expr("power + ?", exchange.Power))
if res.Error != nil {
tx.Rollback()
logger.Error("添加应用失败:", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}
@@ -84,6 +85,7 @@ func (h *RewardHandler) Verify(c *gin.Context) {
res = tx.Updates(&item)
if res.Error != nil {
tx.Rollback()
logger.Error("添加应用失败:", res.Error)
resp.ERROR(c, "更新数据库失败!")
return
}

View File

@@ -8,6 +8,7 @@ package handler
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"fmt"
"geekai/core"
"geekai/core/types"
"geekai/service"
@@ -18,7 +19,6 @@ import (
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"fmt"
"net/http"
"time"
@@ -168,11 +168,10 @@ func (h *SdJobHandler) Image(c *gin.Context) {
}
h.pool.PushTask(types.SdTask{
Id: int(job.Id),
SessionId: data.SessionId,
Type: types.TaskImage,
Params: params,
UserId: userId,
Id: int(job.Id),
Type: types.TaskImage,
Params: params,
UserId: userId,
})
client := h.pool.Clients.Get(uint(job.UserId))
@@ -325,6 +324,7 @@ func (h *SdJobHandler) Publish(c *gin.Context) {
res := h.DB.Model(&model.SdJob{Id: data.Id}).UpdateColumn("publish", true)
if res.Error != nil {
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败")
return
}

View File

@@ -9,9 +9,9 @@ import (
type TestHandler struct {
db *gorm.DB
snowflake *service.Snowflake
js *payment.PayJS
js *payment.JPayService
}
func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.PayJS) *TestHandler {
func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.JPayService) *TestHandler {
return &TestHandler{db: db, snowflake: snowflake, js: js}
}

View File

@@ -11,6 +11,7 @@ import (
"fmt"
"geekai/core"
"geekai/core/types"
"geekai/service"
"geekai/store/model"
"geekai/store/vo"
"geekai/utils"
@@ -28,19 +29,22 @@ import (
type UserHandler struct {
BaseHandler
searcher *xdb.Searcher
redis *redis.Client
searcher *xdb.Searcher
redis *redis.Client
licenseService *service.LicenseService
}
func NewUserHandler(
app *core.AppServer,
db *gorm.DB,
searcher *xdb.Searcher,
client *redis.Client) *UserHandler {
client *redis.Client,
licenseService *service.LicenseService) *UserHandler {
return &UserHandler{
BaseHandler: BaseHandler{DB: db, App: app},
searcher: searcher,
redis: client,
BaseHandler: BaseHandler{DB: db, App: app},
searcher: searcher,
redis: client,
licenseService: licenseService,
}
}
@@ -64,6 +68,14 @@ func (h *UserHandler) Register(c *gin.Context) {
return
}
// 检测最大注册人数
var totalUser int64
h.DB.Model(&model.User{}).Count(&totalUser)
if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
return
}
// 检查验证码
var key string
if data.RegWay == "email" || data.RegWay == "mobile" {
@@ -97,7 +109,6 @@ func (h *UserHandler) Register(c *gin.Context) {
user := model.User{
Username: data.Username,
Password: utils.GenPassword(data.Password, salt),
Nickname: fmt.Sprintf("极客学长@%d", utils.RandomNumber(6)),
Avatar: "/images/avatar/user.png",
Salt: salt,
Status: true,
@@ -106,6 +117,16 @@ func (h *UserHandler) Register(c *gin.Context) {
Power: h.App.SysConfig.InitPower,
}
// 被邀请人也获得赠送算力
if data.InviteCode != "" {
user.Power += h.App.SysConfig.InvitePower
}
if h.licenseService.GetLicense().Configs.DeCopy {
user.Username = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
} else {
user.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6))
}
res = h.DB.Create(&user)
if res.Error != nil {
resp.ERROR(c, "保存数据失败")
@@ -337,7 +358,7 @@ func (h *UserHandler) UpdatePass(c *gin.Context) {
newPass := utils.GenPassword(data.Password, user.Salt)
res := h.DB.Model(&user).UpdateColumn("password", newPass)
if res.Error != nil {
logger.Error("更新数据库失败: ", res.Error)
logger.Error("error with update database", res.Error)
resp.ERROR(c, "更新数据库失败")
return
}
@@ -418,6 +439,7 @@ func (h *UserHandler) BindUsername(c *gin.Context) {
res = h.DB.Model(&user).UpdateColumn("username", data.Username)
if res.Error != nil {
logger.Error(res.Error)
resp.ERROR(c, "更新数据库失败")
return
}

View File

@@ -171,6 +171,11 @@ func main() {
// 邮件服务
fx.Provide(service.NewSmtpService),
// License 服务
fx.Provide(service.NewLicenseService),
fx.Invoke(func(licenseService *service.LicenseService) {
licenseService.SyncLicense()
}),
// 微信机器人服务
fx.Provide(wx.NewWeChatBot),
@@ -206,7 +211,8 @@ func main() {
fx.Provide(payment.NewAlipayService),
fx.Provide(payment.NewHuPiPay),
fx.Provide(payment.NewPayJS),
fx.Provide(payment.NewJPayService),
fx.Provide(payment.NewWechatService),
fx.Provide(service.NewSnowflake),
fx.Provide(service.NewXXLJobExecutor),
fx.Invoke(func(exec *service.XXLJobExecutor, config *types.AppConfig) {
@@ -290,6 +296,7 @@ func main() {
fx.Invoke(func(s *core.AppServer, h *handler.ConfigHandler) {
group := s.Engine.Group("/api/config/")
group.GET("get", h.Get)
group.GET("license", h.License)
}),
// 管理后台控制器
@@ -297,6 +304,10 @@ func main() {
group := s.Engine.Group("/api/admin/")
group.POST("config/update", h.Update)
group.GET("config/get", h.Get)
group.POST("active", h.Active)
group.GET("config/get/license", h.GetLicense)
group.GET("config/get/app", h.GetAppConfig)
group.POST("config/update/draw", h.SaveDrawingConfig)
}),
fx.Invoke(func(s *core.AppServer, h *admin.ManagerHandler) {
group := s.Engine.Group("/api/admin/")
@@ -363,6 +374,7 @@ func main() {
group.POST("alipay/notify", h.AlipayNotify)
group.POST("hupipay/notify", h.HuPiPayNotify)
group.POST("payjs/notify", h.PayJsNotify)
group.POST("wechat/notify", h.WechatPayNotify)
}),
fx.Invoke(func(s *core.AppServer, h *admin.ProductHandler) {
group := s.Engine.Group("/api/admin/product/")

View File

@@ -109,13 +109,13 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
logger.Debugf("绘画参数:%+v", task)
prompt := task.Prompt
// translate prompt
if utils.HasChinese(task.Prompt) {
content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.RewritePromptTemplate, task.Prompt))
if err != nil {
return "", fmt.Errorf("error with translate prompt: %v", err)
if utils.HasChinese(prompt) {
content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.RewritePromptTemplate, prompt))
if err == nil {
prompt = content
logger.Debugf("重写后提示词:%s", prompt)
}
prompt = content
logger.Debugf("重写后提示词:%s", prompt)
}
var user model.User
@@ -124,62 +124,8 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
return "", errors.New("insufficient of power")
}
// get image generation API KEY
var apiKey model.ApiKey
tx := s.db.Where("platform", types.OpenAI).
Where("type", "img").
Where("enabled", true).
Order("last_used_at ASC").First(&apiKey)
if tx.Error != nil {
return "", fmt.Errorf("no available IMG api key: %v", tx.Error)
}
var res imgRes
var errRes ErrRes
if len(apiKey.ProxyURL) > 5 {
s.httpClient.SetProxyURL(apiKey.ProxyURL).R()
}
logger.Infof("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, apiKey.ProxyURL)
r, err := s.httpClient.R().SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+apiKey.Value).
SetBody(imgReq{
Model: "dall-e-3",
Prompt: prompt,
N: 1,
Size: "1024x1024",
Style: task.Style,
Quality: task.Quality,
}).
SetErrorResult(&errRes).
SetSuccessResult(&res).Post(apiKey.ApiURL)
if err != nil {
return "", fmt.Errorf("error with send request: %v", err)
}
if r.IsErrorState() {
return "", fmt.Errorf("error with send request: %v", errRes.Error)
}
// update the api key last use time
s.db.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix())
// update task progress
s.db.Model(&model.DallJob{Id: task.JobId}).UpdateColumns(map[string]interface{}{
"progress": 100,
"org_url": res.Data[0].Url,
"prompt": prompt,
})
s.notifyQueue.RPush(sd.NotifyMessage{UserId: int(task.UserId), JobId: int(task.JobId), Message: sd.Finished})
var content string
if sync {
imgURL, err := s.downloadImage(task.JobId, int(task.UserId), res.Data[0].Url)
if err != nil {
return "", fmt.Errorf("error with download image: %v", err)
}
content = fmt.Sprintf("```\n%s\n```\n下面是我为你创作的图片\n\n![](%s)\n", prompt, imgURL)
}
// 更新用户算力
tx = s.db.Model(&model.User{}).Where("id", user.Id).UpdateColumn("power", gorm.Expr("power - ?", task.Power))
tx := s.db.Model(&model.User{}).Where("id", user.Id).UpdateColumn("power", gorm.Expr("power - ?", task.Power))
// 记录算力变化日志
if tx.Error == nil && tx.RowsAffected > 0 {
var u model.User
@@ -197,6 +143,65 @@ func (s *Service) Image(task types.DallTask, sync bool) (string, error) {
})
}
// get image generation API KEY
var apiKey model.ApiKey
tx = s.db.Where("type", "img").
Where("enabled", true).
Order("last_used_at ASC").First(&apiKey)
if tx.Error != nil {
return "", fmt.Errorf("no available IMG api key: %v", tx.Error)
}
var res imgRes
var errRes ErrRes
if len(apiKey.ProxyURL) > 5 {
s.httpClient.SetProxyURL(apiKey.ProxyURL).R()
}
reqBody := imgReq{
Model: "dall-e-3",
Prompt: prompt,
N: 1,
Size: task.Size,
Style: task.Style,
Quality: task.Quality,
}
logger.Infof("Sending %s request, ApiURL:%s, API KEY:%s, BODY: %+v", apiKey.Platform, apiKey.ApiURL, apiKey.Value, reqBody)
request := s.httpClient.R().SetHeader("Content-Type", "application/json")
if apiKey.Platform == types.Azure.Value {
request = request.SetHeader("api-key", apiKey.Value)
} else {
request = request.SetHeader("Authorization", "Bearer "+apiKey.Value)
}
r, err := request.SetBody(reqBody).SetErrorResult(&errRes).SetSuccessResult(&res).Post(apiKey.ApiURL)
if err != nil {
return "", fmt.Errorf("error with send request: %v", err)
}
if r.IsErrorState() {
return "", fmt.Errorf("error with send request, status: %s, %+v", r.Status, errRes.Error)
}
// update the api key last use time
s.db.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix())
// update task progress
tx = s.db.Model(&model.DallJob{Id: task.JobId}).UpdateColumns(map[string]interface{}{
"progress": 100,
"org_url": res.Data[0].Url,
"prompt": prompt,
})
if tx.Error != nil {
return "", fmt.Errorf("err with update database: %v", tx.Error)
}
s.notifyQueue.RPush(sd.NotifyMessage{UserId: int(task.UserId), JobId: int(task.JobId), Message: sd.Finished})
var content string
if sync {
imgURL, err := s.downloadImage(task.JobId, int(task.UserId), res.Data[0].Url)
if err != nil {
return "", fmt.Errorf("error with download image: %v", err)
}
content = fmt.Sprintf("```\n%s\n```\n下面是我为你创作的图片\n\n![](%s)\n", prompt, imgURL)
}
return content, nil
}

View File

@@ -0,0 +1,197 @@
package service
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"fmt"
"geekai/core"
"geekai/core/types"
"geekai/store"
"time"
"github.com/imroc/req/v3"
)
type LicenseService struct {
config types.ApiConfig
levelDB *store.LevelDB
license *types.License
urlWhiteList []string
machineId string
}
func NewLicenseService(server *core.AppServer, levelDB *store.LevelDB) *LicenseService {
var license types.License
return &LicenseService{
config: server.Config.ApiConfig,
levelDB: levelDB,
license: &license,
machineId: "",
}
}
type License struct {
Name string `json:"name"`
License string `json:"license"`
MachineId string `json:"mid"`
ActiveAt int64 `json:"active_at"`
ExpiredAt int64 `json:"expired_at"`
UserNum int `json:"user_num"`
Configs types.LicenseConfig `json:"configs"`
}
// ActiveLicense 激活 License
func (s *LicenseService) ActiveLicense(license string, machineId string) error {
var res struct {
Code types.BizCode `json:"code"`
Message string `json:"message"`
Data License `json:"data"`
}
apiURL := fmt.Sprintf("%s/%s", s.config.ApiURL, "api/license/active")
response, err := req.C().R().
SetBody(map[string]string{"license": license, "machine_id": machineId}).
SetSuccessResult(&res).Post(apiURL)
if err != nil {
return fmt.Errorf("发送激活请求失败: %v", err)
}
if response.IsErrorState() {
return fmt.Errorf("发送激活请求失败:%v", response.Status)
}
if res.Code != types.Success {
return fmt.Errorf("激活失败:%v", res.Message)
}
s.license = &types.License{
Key: license,
MachineId: machineId,
Configs: res.Data.Configs,
ExpiredAt: res.Data.ExpiredAt,
IsActive: true,
}
err = s.levelDB.Put(types.LicenseKey, s.license)
if err != nil {
return fmt.Errorf("保存许可证书失败:%v", err)
}
return nil
}
// SyncLicense 定期同步 License
func (s *LicenseService) SyncLicense() {
go func() {
retryCounter := 0
for {
license, err := s.fetchLicense()
if err != nil {
retryCounter++
if retryCounter < 5 {
logger.Warn(err)
}
s.license.IsActive = false
} else {
s.license = license
}
urls, err := s.fetchUrlWhiteList()
if err == nil {
s.urlWhiteList = urls
}
time.Sleep(time.Second * 10)
}
}()
}
func (s *LicenseService) fetchLicense() (*types.License, error) {
//var res struct {
// Code types.BizCode `json:"code"`
// Message string `json:"message"`
// Data License `json:"data"`
//}
//apiURL := fmt.Sprintf("%s/%s", s.config.ApiURL, "api/license/check")
//response, err := req.C().R().
// SetBody(map[string]string{"license": s.license.Key, "machine_id": s.machineId}).
// SetSuccessResult(&res).Post(apiURL)
//if err != nil {
// return nil, fmt.Errorf("发送激活请求失败: %v", err)
//}
//if response.IsErrorState() {
// return nil, fmt.Errorf("激活失败:%v", response.Status)
//}
//if res.Code != types.Success {
// return nil, fmt.Errorf("激活失败:%v", res.Message)
//}
return &types.License{
Key: "abc",
MachineId: "abc",
Configs: types.LicenseConfig{
UserNum: 10000,
DeCopy: false,
},
ExpiredAt: 0,
IsActive: true,
}, nil
}
func (s *LicenseService) fetchUrlWhiteList() ([]string, error) {
var res struct {
Code types.BizCode `json:"code"`
Message string `json:"message"`
Data []string `json:"data"`
}
apiURL := fmt.Sprintf("%s/%s", s.config.ApiURL, "api/license/urls")
response, err := req.C().R().SetSuccessResult(&res).Get(apiURL)
if err != nil {
return nil, fmt.Errorf("发送请求失败: %v", err)
}
if response.IsErrorState() {
return nil, fmt.Errorf("发送请求失败:%v", response.Status)
}
if res.Code != types.Success {
return nil, fmt.Errorf("获取白名单失败:%v", res.Message)
}
return res.Data, nil
}
// GetLicense 获取许可信息
func (s *LicenseService) GetLicense() *types.License {
return s.license
}
// IsValidApiURL 判断是否合法的中转 URL
func (s *LicenseService) IsValidApiURL(uri string) error {
// 获得许可授权的直接放行
return nil
//if s.license.IsActive {
// if s.license.MachineId != s.machineId {
// return errors.New("系统使用了盗版的许可证书")
// }
//
// if time.Now().Unix() > s.license.ExpiredAt {
// return errors.New("系统许可证书已经过期")
// }
// return nil
//}
//
//if len(s.urlWhiteList) == 0 {
// urls, err := s.fetchUrlWhiteList()
// if err == nil {
// s.urlWhiteList = urls
// }
//}
//
//for _, v := range s.urlWhiteList {
// if strings.HasPrefix(uri, v) {
// return nil
// }
//}
//return fmt.Errorf("当前 API 地址 %s 不在白名单列表当中。", uri)
}

View File

@@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"geekai/core/types"
"geekai/service"
"geekai/utils"
"github.com/imroc/req/v3"
"io"
@@ -22,20 +23,30 @@ import (
// PlusClient MidJourney Plus ProxyClient
type PlusClient struct {
Config types.MjPlusConfig
apiURL string
client *req.Client
Config types.MjPlusConfig
apiURL string
client *req.Client
licenseService *service.LicenseService
}
func NewPlusClient(config types.MjPlusConfig) *PlusClient {
func NewPlusClient(config types.MjPlusConfig, licenseService *service.LicenseService) *PlusClient {
return &PlusClient{
Config: config,
apiURL: config.ApiURL,
client: req.C().SetTimeout(time.Minute).SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"),
Config: config,
apiURL: config.ApiURL,
client: req.C().SetTimeout(time.Minute).SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"),
licenseService: licenseService,
}
}
func (c *PlusClient) preCheck() error {
return c.licenseService.IsValidApiURL(c.Config.ApiURL)
}
func (c *PlusClient) Imagine(task types.MjTask) (ImageRes, error) {
if err := c.preCheck(); err != nil {
return ImageRes{}, err
}
apiURL := fmt.Sprintf("%s/mj-%s/mj/submit/imagine", c.apiURL, c.Config.Mode)
prompt := fmt.Sprintf("%s %s", task.Prompt, task.Params)
if task.NegPrompt != "" {
@@ -79,6 +90,10 @@ func (c *PlusClient) Imagine(task types.MjTask) (ImageRes, error) {
// Blend 融图
func (c *PlusClient) Blend(task types.MjTask) (ImageRes, error) {
if err := c.preCheck(); err != nil {
return ImageRes{}, err
}
apiURL := fmt.Sprintf("%s/mj-%s/mj/submit/blend", c.apiURL, c.Config.Mode)
logger.Info("API URL: ", apiURL)
body := ImageReq{
@@ -118,6 +133,10 @@ func (c *PlusClient) Blend(task types.MjTask) (ImageRes, error) {
// SwapFace 换脸
func (c *PlusClient) SwapFace(task types.MjTask) (ImageRes, error) {
if err := c.preCheck(); err != nil {
return ImageRes{}, err
}
apiURL := fmt.Sprintf("%s/mj-%s/mj/insight-face/swap", c.apiURL, c.Config.Mode)
// 生成图片 Base64 编码
if len(task.ImgArr) != 2 {
@@ -167,6 +186,10 @@ func (c *PlusClient) SwapFace(task types.MjTask) (ImageRes, error) {
// Upscale 放大指定的图片
func (c *PlusClient) Upscale(task types.MjTask) (ImageRes, error) {
if err := c.preCheck(); err != nil {
return ImageRes{}, err
}
body := map[string]string{
"customId": fmt.Sprintf("MJ::JOB::upsample::%d::%s", task.Index, task.MessageHash),
"taskId": task.MessageId,
@@ -194,6 +217,10 @@ func (c *PlusClient) Upscale(task types.MjTask) (ImageRes, error) {
// Variation 以指定的图片的视角进行变换再创作,注意需要在对应的频道中关闭 Remix 变换,否则 Variation 指令将不会生效
func (c *PlusClient) Variation(task types.MjTask) (ImageRes, error) {
if err := c.preCheck(); err != nil {
return ImageRes{}, err
}
body := map[string]string{
"customId": fmt.Sprintf("MJ::JOB::variation::%d::%s", task.Index, task.MessageHash),
"taskId": task.MessageId,

View File

@@ -11,6 +11,7 @@ import (
"fmt"
"geekai/core/types"
logger2 "geekai/logger"
"geekai/service"
"geekai/service/oss"
"geekai/service/sd"
"geekai/store"
@@ -30,11 +31,12 @@ type ServicePool struct {
db *gorm.DB
uploaderManager *oss.UploaderManager
Clients *types.LMap[uint, *types.WsClient] // UserId => Client
licenseService *service.LicenseService
}
var logger = logger2.GetLogger()
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager) *ServicePool {
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService) *ServicePool {
services := make([]*Service, 0)
taskQueue := store.NewRedisQueue("MidJourney_Task_Queue", redisCli)
notifyQueue := store.NewRedisQueue("MidJourney_Notify_Queue", redisCli)
@@ -45,6 +47,7 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
uploaderManager: manager,
db: db,
Clients: types.NewLMap[uint, *types.WsClient](),
licenseService: licenseService,
}
}
@@ -60,7 +63,7 @@ func (p *ServicePool) InitServices(plusConfigs []types.MjPlusConfig, proxyConfig
continue
}
cli := NewPlusClient(config)
cli := NewPlusClient(config, p.licenseService)
name := fmt.Sprintf("mj-plus-service-%d", k)
plusService := NewService(name, p.taskQueue, p.notifyQueue, p.db, cli)
go func() {
@@ -176,14 +179,14 @@ func (p *ServicePool) HasAvailableService() bool {
// SyncTaskProgress 异步拉取任务
func (p *ServicePool) SyncTaskProgress() {
go func() {
var items []model.MidJourneyJob
var jobs []model.MidJourneyJob
for {
res := p.db.Where("progress < ?", 100).Find(&items)
res := p.db.Where("progress < ?", 100).Find(&jobs)
if res.Error != nil {
continue
}
for _, job := range items {
for _, job := range jobs {
// 失败或者 30 分钟还没完成的任务删除并退回算力
if time.Now().Sub(job.CreatedAt) > time.Minute*30 || job.Progress == -1 {
p.db.Delete(&job)

View File

@@ -108,7 +108,13 @@ func (s *Service) Run() {
}
if err != nil || (res.Code != 1 && res.Code != 22) {
errMsg := fmt.Sprintf("%v,%s", err, res.Description)
var errMsg string
if err != nil {
errMsg = err.Error()
} else {
errMsg = fmt.Sprintf("%v,%s", err, res.Description)
}
logger.Error("绘画任务执行失败:", errMsg)
job.Progress = -1
job.ErrMsg = errMsg

View File

@@ -8,12 +8,13 @@ package payment
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"context"
"fmt"
"geekai/core/types"
logger2 "geekai/logger"
"github.com/smartwalle/alipay/v3"
"log"
"net/url"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
"net/http"
"os"
)
@@ -35,93 +36,90 @@ func NewAlipayService(appConfig *types.AppConfig) (*AlipayService, error) {
return nil, fmt.Errorf("error with read App Private key: %v", err)
}
xClient, err := alipay.New(config.AppId, priKey, !config.SandBox)
client, err := alipay.NewClient(config.AppId, priKey, !config.SandBox)
if err != nil {
return nil, fmt.Errorf("error with initialize alipay service: %v", err)
}
if err = xClient.LoadAppCertPublicKeyFromFile(config.PublicKey); err != nil {
return nil, fmt.Errorf("error with loading App PublicKey: %v", err)
}
if err = xClient.LoadAliPayRootCertFromFile(config.RootCert); err != nil {
return nil, fmt.Errorf("error with loading alipay RootCert: %v", err)
}
if err = xClient.LoadAlipayCertPublicKeyFromFile(config.AlipayPublicKey); err != nil {
return nil, fmt.Errorf("error with loading Alipay PublicKey: %v", err)
//client.DebugSwitch = gopay.DebugOn // 开启调试模式
client.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间
SetCharset(alipay.UTF8). // 设置字符编码,不设置默认 utf-8
SetSignType(alipay.RSA2). // 设置签名类型,不设置默认 RSA2
SetReturnUrl(config.ReturnURL). // 设置返回URL
SetNotifyUrl(config.NotifyURL)
if err = client.SetCertSnByPath(config.PublicKey, config.RootCert, config.AlipayPublicKey); err != nil {
return nil, fmt.Errorf("error with load payment public key: %v", err)
}
return &AlipayService{config: &config, client: xClient}, nil
return &AlipayService{config: &config, client: client}, nil
}
func (s *AlipayService) PayUrlMobile(outTradeNo string, notifyURL string, returnURL string, Amount string, subject string) (string, error) {
var p = alipay.TradeWapPay{}
p.NotifyURL = notifyURL
p.ReturnURL = returnURL
p.Subject = subject
p.OutTradeNo = outTradeNo
p.TotalAmount = Amount
p.ProductCode = "QUICK_WAP_WAY"
res, err := s.client.TradeWapPay(p)
if err != nil {
return "", err
}
return res.String(), err
func (s *AlipayService) PayUrlMobile(outTradeNo string, amount string, subject string) (string, error) {
bm := make(gopay.BodyMap)
bm.Set("subject", subject)
bm.Set("out_trade_no", outTradeNo)
bm.Set("quit_url", s.config.ReturnURL)
bm.Set("total_amount", amount)
bm.Set("product_code", "QUICK_WAP_WAY")
return s.client.TradeWapPay(context.Background(), bm)
}
func (s *AlipayService) PayUrlPc(outTradeNo string, notifyURL string, returnURL string, amount string, subject string) (string, error) {
var p = alipay.TradePagePay{}
p.NotifyURL = notifyURL
p.ReturnURL = returnURL
p.Subject = subject
p.OutTradeNo = outTradeNo
p.TotalAmount = amount
p.ProductCode = "FAST_INSTANT_TRADE_PAY"
res, err := s.client.TradePagePay(p)
if err != nil {
return "", nil
}
return res.String(), err
func (s *AlipayService) PayUrlPc(outTradeNo string, amount string, subject string) (string, error) {
bm := make(gopay.BodyMap)
bm.Set("subject", subject)
bm.Set("out_trade_no", outTradeNo)
bm.Set("total_amount", amount)
bm.Set("product_code", "FAST_INSTANT_TRADE_PAY")
return s.client.TradePagePay(context.Background(), bm)
}
// TradeVerify 交易验证
func (s *AlipayService) TradeVerify(reqForm url.Values) NotifyVo {
err := s.client.VerifySign(reqForm)
func (s *AlipayService) TradeVerify(request *http.Request) NotifyVo {
notifyReq, err := alipay.ParseNotifyToBodyMap(request) // c.Request 是 gin 框架的写法
if err != nil {
log.Println("异步通知验证签名发生错误", err)
return NotifyVo{
Status: 0,
Message: "异步通知验证签名发生错误",
Status: Failure,
Message: "error with parse notify request: " + err.Error(),
}
}
return s.TradeQuery(reqForm.Get("out_trade_no"))
_, err = alipay.VerifySignWithCert(s.config.AlipayPublicKey, notifyReq)
if err != nil {
return NotifyVo{
Status: Failure,
Message: "error with verify sign: " + err.Error(),
}
}
return s.TradeQuery(request.Form.Get("out_trade_no"))
}
func (s *AlipayService) TradeQuery(outTradeNo string) NotifyVo {
var p = alipay.TradeQuery{}
p.OutTradeNo = outTradeNo
rsp, err := s.client.TradeQuery(p)
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", outTradeNo)
//查询订单
rsp, err := s.client.TradeQuery(context.Background(), bm)
if err != nil {
return NotifyVo{
Status: 0,
Status: Failure,
Message: "异步查询验证订单信息发生错误" + outTradeNo + err.Error(),
}
}
if rsp.IsSuccess() == true && rsp.TradeStatus == "TRADE_SUCCESS" {
if rsp.Response.TradeStatus == "TRADE_SUCCESS" {
return NotifyVo{
Status: 1,
OutTradeNo: rsp.OutTradeNo,
TradeNo: rsp.TradeNo,
Amount: rsp.TotalAmount,
Subject: rsp.Subject,
Status: Success,
OutTradeNo: rsp.Response.OutTradeNo,
TradeId: rsp.Response.TradeNo,
Amount: rsp.Response.TotalAmount,
Subject: rsp.Response.Subject,
Message: "OK",
}
} else {
return NotifyVo{
Status: 0,
Status: Failure,
Message: "异步查询验证订单信息发生错误" + outTradeNo,
}
}
@@ -134,16 +132,3 @@ func readKey(filename string) (string, error) {
}
return string(data), nil
}
type NotifyVo struct {
Status int
OutTradeNo string
TradeNo string
Amount string
Message string
Subject string
}
func (v NotifyVo) Success() bool {
return v.Status == 1
}

View File

@@ -49,6 +49,8 @@ type HuPiPayReq struct {
CallbackURL string `json:"callback_url"`
Time string `json:"time"`
NonceStr string `json:"nonce_str"`
Type string `json:"type"`
WapUrl string `json:"wap_url"`
}
type HuPiResp struct {

View File

@@ -21,12 +21,12 @@ import (
"strings"
)
type PayJS struct {
type JPayService struct {
config *types.JPayConfig
}
func NewPayJS(appConfig *types.AppConfig) *PayJS {
return &PayJS{
func NewJPayService(appConfig *types.AppConfig) *JPayService {
return &JPayService{
config: &appConfig.JPayConfig,
}
}
@@ -53,7 +53,7 @@ func (r JPayReps) IsOK() bool {
return r.ReturnMsg == "SUCCESS"
}
func (js *PayJS) Pay(param JPayReq) JPayReps {
func (js *JPayService) Pay(param JPayReq) JPayReps {
param.NotifyURL = js.config.NotifyURL
var p = url.Values{}
encode := utils.JsonEncode(param)
@@ -86,13 +86,13 @@ func (js *PayJS) Pay(param JPayReq) JPayReps {
return data
}
func (js *PayJS) PayH5(p url.Values) string {
func (js *JPayService) PayH5(p url.Values) string {
p.Add("mchid", js.config.AppId)
p.Add("sign", js.sign(p))
return fmt.Sprintf("%s/api/cashier?%s", js.config.ApiURL, p.Encode())
}
func (js *PayJS) sign(params url.Values) string {
func (js *JPayService) sign(params url.Values) string {
params.Del(`sign`)
var keys = make([]string, 0, 0)
for key := range params {
@@ -117,20 +117,18 @@ func (js *PayJS) sign(params url.Values) string {
return strings.ToUpper(md5res)
}
// Check 查询订单支付状态
// TradeVerify 查询订单支付状态
// @param tradeNo 支付平台交易 ID
func (js *PayJS) Check(tradeNo string) error {
func (js *JPayService) TradeVerify(tradeNo string) error {
apiURL := fmt.Sprintf("%s/api/check", js.config.ApiURL)
params := url.Values{}
params.Add("payjs_order_id", tradeNo)
params.Add("sign", js.sign(params))
data := strings.NewReader(params.Encode())
resp, err := http.Post(apiURL, "application/x-www-form-urlencoded", data)
defer resp.Body.Close()
if err != nil {
return fmt.Errorf("error with http reqeust: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {

View File

@@ -0,0 +1,19 @@
package payment
type NotifyVo struct {
Status int
OutTradeNo string // 商户订单号
TradeId string // 交易ID
Amount string // 交易金额
Message string
Subject string
}
func (v NotifyVo) Success() bool {
return v.Status == Success
}
const (
Success = 0
Failure = 1
)

View File

@@ -0,0 +1,135 @@
package payment
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"context"
"fmt"
"geekai/core/types"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/wechat/v3"
"net/http"
"time"
)
type WechatPayService struct {
config *types.WechatPayConfig
client *wechat.ClientV3
}
func NewWechatService(appConfig *types.AppConfig) (*WechatPayService, error) {
config := appConfig.WechatPayConfig
if !config.Enabled {
logger.Info("Disabled WechatPay service")
return nil, nil
}
priKey, err := readKey(config.PrivateKey)
if err != nil {
return nil, fmt.Errorf("error with read App Private key: %v", err)
}
client, err := wechat.NewClientV3(config.MchId, config.SerialNo, config.ApiV3Key, priKey)
if err != nil {
return nil, fmt.Errorf("error with initialize WechatPay service: %v", err)
}
err = client.AutoVerifySign()
if err != nil {
return nil, fmt.Errorf("error with autoVerifySign: %v", err)
}
//client.DebugSwitch = gopay.DebugOn
return &WechatPayService{config: &config, client: client}, nil
}
func (s *WechatPayService) PayUrlNative(outTradeNo string, amount int, subject string) (string, error) {
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("appid", s.config.AppId).
Set("mchid", s.config.MchId).
Set("description", subject).
Set("out_trade_no", outTradeNo).
Set("time_expire", expire).
Set("notify_url", s.config.NotifyURL).
SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", amount).
Set("currency", "CNY")
})
wxRsp, err := s.client.V3TransactionNative(context.Background(), bm)
if err != nil {
return "", fmt.Errorf("error with client v3 transaction Native: %v", err)
}
if wxRsp.Code != wechat.Success {
return "", fmt.Errorf("error status with generating pay url: %v", wxRsp.Error)
}
return wxRsp.Response.CodeUrl, nil
}
func (s *WechatPayService) PayUrlH5(outTradeNo string, amount int, subject string, ip string) (string, error) {
expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
// 初始化 BodyMap
bm := make(gopay.BodyMap)
bm.Set("appid", s.config.AppId).
Set("mchid", s.config.MchId).
Set("description", subject).
Set("out_trade_no", outTradeNo).
Set("time_expire", expire).
Set("notify_url", s.config.NotifyURL).
SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", amount).
Set("currency", "CNY")
}).
SetBodyMap("scene_info", func(bm gopay.BodyMap) {
bm.Set("payer_client_ip", ip).
SetBodyMap("h5_info", func(bm gopay.BodyMap) {
bm.Set("type", "Wap")
})
})
wxRsp, err := s.client.V3TransactionH5(context.Background(), bm)
if err != nil {
return "", fmt.Errorf("error with client v3 transaction H5: %v", err)
}
if wxRsp.Code != wechat.Success {
return "", fmt.Errorf("error with generating pay url: %v", wxRsp.Error)
}
return wxRsp.Response.H5Url, nil
}
type NotifyResponse struct {
Code string `json:"code"`
Message string `xml:"message"`
}
// TradeVerify 交易验证
func (s *WechatPayService) TradeVerify(request *http.Request) NotifyVo {
notifyReq, err := wechat.V3ParseNotify(request)
if err != nil {
return NotifyVo{Status: 1, Message: fmt.Sprintf("error with client v3 parse notify: %v", err)}
}
// TODO: 这里验签程序有 Bug一直报错crypto/rsa: verification error先暂时取消验签
//err = notifyReq.VerifySignByPK(s.client.WxPublicKey())
//if err != nil {
// return fmt.Errorf("error with client v3 verify sign: %v", err)
//}
// 解密支付密文,验证订单信息
result, err := notifyReq.DecryptPayCipherText(s.config.ApiV3Key)
if err != nil {
return NotifyVo{Status: Failure, Message: fmt.Sprintf("error with client v3 decrypt: %v", err)}
}
return NotifyVo{
Status: Success,
OutTradeNo: result.OutTradeNo,
TradeId: result.TransactionId,
Amount: fmt.Sprintf("%.2f", float64(result.Amount.Total)/100),
}
}

View File

@@ -132,7 +132,7 @@ func (p *ServicePool) CheckTaskStatus() {
continue
}
}
time.Sleep(time.Second * 10)
time.Sleep(time.Second * 5)
}
}()
}

View File

@@ -192,7 +192,7 @@ func (s *Service) Txt2Img(task types.SdTask) error {
return
}
task.Params.Seed = int64(utils.IntValue(utils.InterfaceToString(info["seed"]), -1))
s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumns(model.SdJob{ImgURL: imgURL, Params: utils.JsonEncode(task.Params)})
s.db.Model(&model.SdJob{Id: uint(task.Id)}).UpdateColumns(model.SdJob{ImgURL: imgURL, Params: utils.JsonEncode(task.Params), Prompt: task.Params.Prompt})
errChan <- nil
}()

View File

@@ -28,8 +28,8 @@ func NewSmtpService(appConfig *types.AppConfig) *SmtpService {
}
func (s *SmtpService) SendVerifyCode(to string, code int) error {
subject := "Geek-AI 注册验证码"
body := fmt.Sprintf("您正在注册 Geek-AI 助手账户,注册验证码为 %d请不要告诉他人。如非本人操作请忽略此邮件。", code)
subject := fmt.Sprintf("%s 注册验证码", s.config.AppName)
body := fmt.Sprintf("您正在注册 %s 账户,注册验证码为 %d请不要告诉他人。如非本人操作请忽略此邮件。", s.config.AppName, code)
auth := smtp.PlainAuth("", s.config.From, s.config.Password, s.config.Host)
if s.config.UseTls {

View File

@@ -106,23 +106,26 @@ func (e *XXLJobExecutor) ResetVipPower(cxt context.Context, param *xxl.RunReq) (
e.db.Model(&model.User{}).Where("id", u.Id).UpdateColumn("vip", false)
continue
}
// update user
tx := e.db.Model(&model.User{}).Where("id", u.Id).UpdateColumn("power", gorm.Expr("power + ?", config.VipMonthPower))
// 记录算力变动日志
if tx.Error == nil {
var user model.User
e.db.Where("id", u.Id).First(&user)
e.db.Create(&model.PowerLog{
UserId: u.Id,
Username: u.Username,
Type: types.PowerRecharge,
Amount: config.VipMonthPower,
Mark: types.PowerAdd,
Balance: user.Power,
Model: "系统盘点",
Remark: fmt.Sprintf("VIP会员每月算力派发%d", config.VipMonthPower),
CreatedAt: time.Now(),
})
if u.Power < config.VipMonthPower {
power := config.VipMonthPower - u.Power
// update user
tx := e.db.Model(&model.User{}).Where("id", u.Id).UpdateColumn("power", gorm.Expr("power + ?", power))
// 记录算力变动日志
if tx.Error == nil {
var user model.User
e.db.Where("id", u.Id).First(&user)
e.db.Create(&model.PowerLog{
UserId: u.Id,
Username: u.Username,
Type: types.PowerRecharge,
Amount: power,
Mark: types.PowerAdd,
Balance: user.Power,
Model: "系统盘点",
Remark: fmt.Sprintf("VIP会员每月算力派发%d", config.VipMonthPower),
CreatedAt: time.Now(),
})
}
}
}
logger.Info("月底盘点完成!")

View File

@@ -1,12 +1,9 @@
package main
import (
"geekai/utils"
"fmt"
)
func main() {
text := "https://nk.img.r9it.com/chatgpt-plus/1712709360012445.png 请简单描述一下这幅图上的内容 "
imgURL := utils.ExtractImgURL(text)
fmt.Println(imgURL)
fmt.Println(fmt.Sprintf("%v", float64(90)/100))
}

View File

@@ -54,7 +54,7 @@ type apiErrRes struct {
func OpenAIRequest(db *gorm.DB, prompt string) (string, error) {
var apiKey model.ApiKey
res := db.Where("platform = ?", types.OpenAI).Where("type = ?", "chat").Where("enabled = ?", true).First(&apiKey)
res := db.Where("platform", types.OpenAI.Value).Where("type", "chat").Where("enabled", true).First(&apiKey)
if res.Error != nil {
return "", fmt.Errorf("error with fetch OpenAI API KEY%v", res.Error)
}
@@ -74,7 +74,7 @@ func OpenAIRequest(db *gorm.DB, prompt string) (string, error) {
r, err := client.R().SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+apiKey.Value).
SetBody(types.ApiRequest{
Model: "gpt-3.5-turbo-0125",
Model: "gpt-3.5-turbo",
Temperature: 0.9,
MaxTokens: 1024,
Stream: false,
@@ -90,4 +90,4 @@ func OpenAIRequest(db *gorm.DB, prompt string) (string, error) {
db.Model(&apiKey).UpdateColumn("last_used_at", time.Now().Unix())
return response.Choices[0].Message.Content, nil
}
}

View File

@@ -20,7 +20,7 @@ docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:$version
# build docker image for geekai-web
docker rmi -f registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:$version-$arch
docker build --platform linux/amd64 -t registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api-web:$version-$arch -f dockerfile-vue ../
docker build -t registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:$version-$arch -f dockerfile-vue ../
if [ "$3" = "push" ];then
docker push registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:$version-$arch

View File

@@ -0,0 +1,849 @@
-- phpMyAdmin SQL Dump
-- version 5.2.1
-- https://www.phpmyadmin.net/
--
-- 主机: 127.0.0.1
-- 生成日期: 2024-05-29 17:40:11
-- 服务器版本: 8.0.33
-- PHP 版本: 8.1.2-1ubuntu2.17
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- 数据库: `chatgpt_plus`
--
CREATE DATABASE IF NOT EXISTS `chatgpt_plus` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE `chatgpt_plus`;
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_admin_users`
--
DROP TABLE IF EXISTS `chatgpt_admin_users`;
CREATE TABLE `chatgpt_admin_users` (
`id` int NOT NULL,
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
`salt` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码盐',
`status` tinyint(1) NOT NULL COMMENT '当前状态',
`last_login_at` int NOT NULL COMMENT '最后登录时间',
`last_login_ip` char(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '最后登录 IP',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户' ROW_FORMAT=DYNAMIC;
--
-- 转存表中的数据 `chatgpt_admin_users`
--
INSERT INTO `chatgpt_admin_users` (`id`, `username`, `password`, `salt`, `status`, `last_login_at`, `last_login_ip`, `created_at`, `updated_at`) VALUES
(1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1715756987, '::1', '2024-03-11 16:30:20', '2024-05-15 15:09:47');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_api_keys`
--
DROP TABLE IF EXISTS `chatgpt_api_keys`;
CREATE TABLE `chatgpt_api_keys` (
`id` int NOT NULL,
`platform` char(20) DEFAULT NULL COMMENT '平台',
`name` varchar(30) DEFAULT NULL COMMENT '名称',
`value` varchar(100) NOT NULL COMMENT 'API KEY value',
`type` varchar(10) NOT NULL DEFAULT 'chat' COMMENT '用途chat=>聊天img=>图片)',
`last_used_at` int NOT NULL COMMENT '最后使用时间',
`api_url` varchar(255) DEFAULT NULL COMMENT 'API 地址',
`enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
`proxy_url` varchar(100) DEFAULT NULL COMMENT '代理地址',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OpenAI API ';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_history`
--
DROP TABLE IF EXISTS `chatgpt_chat_history`;
CREATE TABLE `chatgpt_chat_history` (
`id` bigint NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
`type` varchar(10) NOT NULL COMMENT '类型prompt|reply',
`icon` varchar(100) NOT NULL COMMENT '角色图标',
`role_id` int NOT NULL COMMENT '角色 ID',
`model` varchar(30) DEFAULT NULL COMMENT '模型名称',
`content` text NOT NULL COMMENT '聊天内容',
`tokens` smallint NOT NULL COMMENT '耗费 token 数量',
`use_context` tinyint(1) NOT NULL COMMENT '是否允许作为上下文语料',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天历史记录';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_items`
--
DROP TABLE IF EXISTS `chatgpt_chat_items`;
CREATE TABLE `chatgpt_chat_items` (
`id` int NOT NULL,
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
`user_id` int NOT NULL COMMENT '用户 ID',
`role_id` int NOT NULL COMMENT '角色 ID',
`title` varchar(100) NOT NULL COMMENT '会话标题',
`model_id` int NOT NULL DEFAULT '0' COMMENT '模型 ID',
`model` varchar(30) DEFAULT NULL COMMENT '模型名称',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户会话列表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_models`
--
DROP TABLE IF EXISTS `chatgpt_chat_models`;
CREATE TABLE `chatgpt_chat_models` (
`id` int NOT NULL,
`platform` varchar(20) DEFAULT NULL COMMENT '模型平台',
`name` varchar(50) NOT NULL COMMENT '模型名称',
`value` varchar(50) NOT NULL COMMENT '模型值',
`sort_num` tinyint(1) NOT NULL COMMENT '排序数字',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用模型',
`power` tinyint NOT NULL COMMENT '消耗算力点数',
`temperature` float(3,1) NOT NULL DEFAULT '1.0' COMMENT '模型创意度',
`max_tokens` int NOT NULL DEFAULT '1024' COMMENT '最大响应长度',
`max_context` int NOT NULL DEFAULT '4096' COMMENT '最大上下文长度',
`open` tinyint(1) NOT NULL COMMENT '是否开放模型',
`key_id` int NOT NULL COMMENT '绑定API KEY ID',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI 模型表';
--
-- 转存表中的数据 `chatgpt_chat_models`
--
INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num`, `enabled`, `power`, `temperature`, `max_tokens`, `max_context`, `open`, `key_id`, `created_at`, `updated_at`) VALUES
(1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo', 1, 1, 0, 1.0, 1024, 4096, 1, 0, '2023-08-23 12:06:36', '2024-05-29 09:19:06'),
(2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 22, 1, 1, 1.0, 1024, 4096, 0, 0, '2023-08-23 12:15:30', '2024-05-29 09:19:06'),
(3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 11, 1, 1, 1.0, 2048, 32768, 1, 0, '2023-08-23 13:35:45', '2024-05-29 09:19:06'),
(7, 'Baidu', '文心一言3.0', 'eb-instant', 20, 1, 1, 1.0, 1024, 4096, 1, 0, '2023-10-11 11:29:28', '2024-05-29 09:19:06'),
(8, 'XunFei', '星火V3.5', 'generalv3.5', 10, 1, 5, 0.8, 1024, 8192, 1, 0, '2023-10-11 15:48:30', '2024-05-29 09:19:06'),
(9, 'XunFei', '星火V2.0', 'generalv2', 19, 1, 1, 1.0, 1024, 8192, 1, 0, '2023-10-11 15:48:45', '2024-05-29 09:19:06'),
(10, 'Baidu', '文心一言4.0', 'completions_pro', 21, 1, 3, 1.0, 1024, 8192, 1, 0, '2023-10-25 08:31:37', '2024-05-29 09:19:06'),
(11, 'OpenAI', 'GPT-4.0', 'gpt-4-0125-preview', 9, 1, 15, 1.0, 2048, 8192, 1, 0, '2023-10-25 08:45:15', '2024-05-29 09:19:06'),
(12, 'XunFei', '星火v3.0', 'generalv3', 18, 1, 3, 1.0, 1024, 8192, 1, 0, '2023-11-23 09:20:33', '2024-05-29 09:19:06'),
(15, 'OpenAI', 'GPT-超级模型', 'gpt-4-all', 12, 1, 30, 1.0, 4096, 32768, 0, 0, '2024-01-15 11:32:52', '2024-05-29 09:19:06'),
(16, 'OpenAI', '视频号导师', 'gpt-4-gizmo-g-QXXEBTXl7', 13, 1, 30, 1.0, 4096, 32768, 0, 0, '2024-01-15 14:46:35', '2024-05-29 09:19:06'),
(17, 'QWen', '通义千问-Turbo', 'qwen-turbo', 15, 1, 1, 1.0, 1024, 8192, 1, 0, '2024-01-19 10:42:24', '2024-05-29 09:19:06'),
(18, 'QWen', '通义千问-Plus', 'qwen-plus', 16, 1, 1, 1.0, 1024, 32768, 1, 0, '2024-01-19 10:42:49', '2024-05-29 09:19:06'),
(19, 'QWen', '通义千问-Max', 'qwen-max-1201', 17, 1, 1, 1.0, 1024, 32768, 1, 0, '2024-01-19 10:51:03', '2024-05-29 09:19:06'),
(21, 'OpenAI', '董宇辉小作文助手', 'gpt-4-gizmo-g-dse9iXvor', 14, 1, 30, 1.0, 8192, 32768, 0, 0, '2024-03-18 14:24:20', '2024-05-29 09:19:06'),
(22, 'OpenAI', 'LOGO生成神器', 'gpt-4-gizmo-g-YL87j8C7S', 8, 1, 30, 1.0, 1024, 4096, 1, 44, '2024-03-20 14:02:11', '2024-05-29 09:19:06'),
(23, 'OpenAI', '音乐生成器', 'suno-v3', 7, 1, 50, 0.8, 1024, 4096, 1, 44, '2024-03-29 15:43:40', '2024-05-29 09:19:06'),
(24, 'OpenAI', '通义千问(中转)', 'qwen-plus', 6, 1, 1, 1.0, 1024, 4096, 1, 0, '2024-04-03 12:00:46', '2024-05-29 09:19:06'),
(25, 'OpenAI', 'GPT4-TURBO', 'gpt-4-turbo', 5, 1, 15, 1.0, 2048, 8092, 1, 0, '2024-04-10 08:35:17', '2024-05-29 09:19:06'),
(26, 'QWen', '通义千问-Turbo', 'qwen-turbo', 4, 1, 2, 1.0, 1024, 8192, 1, 0, '2024-04-12 14:11:19', '2024-05-29 09:19:06'),
(27, 'QWen', '通义千问-Plus', 'qwen-plus', 3, 1, 2, 1.0, 1024, 8192, 1, 0, '2024-04-12 14:11:52', '2024-05-29 09:19:06'),
(28, 'OpenAI', 'GPT-3.5(免费)', 'gpt-3.5-turbo', 23, 1, 0, 1.0, 1024, 16384, 1, 53, '2024-04-12 15:16:43', '2024-05-29 09:19:06'),
(34, 'OpenAI', 'LLAMA3', 'llama3-8b', 24, 1, 1, 1.0, 1024, 8192, 1, 56, '2024-04-30 15:22:50', '2024-05-29 09:19:06'),
(35, 'OpenAI', 'GPT-3.5-16K', 'gpt-3.5-turbo-16k', 2, 1, 1, 1.0, 4096, 16384, 1, 0, '2024-05-10 15:20:27', '2024-05-29 09:19:06'),
(36, 'OpenAI', 'GPT-4O', 'gpt-4o', 25, 1, 15, 1.0, 4096, 16384, 1, 57, '2024-05-14 09:25:15', '2024-05-29 09:19:06'),
(38, 'OpenAI', 'Gemini-pro', 'gemini-pro-1.5', 26, 1, 10, 1.0, 2048, 8192, 1, 0, '2024-05-27 18:10:35', '2024-05-29 09:19:06'),
(39, 'Baidu', 'ERNIE-Speed-8K', 'ernie_speed', 26, 1, 1, 1.0, 1024, 8192, 1, 0, '2024-05-29 15:04:19', '2024-05-29 15:04:19');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_roles`
--
DROP TABLE IF EXISTS `chatgpt_chat_roles`;
CREATE TABLE `chatgpt_chat_roles` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '角色名称',
`marker` varchar(30) NOT NULL COMMENT '角色标识',
`context_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色语料 json',
`hello_msg` varchar(255) NOT NULL COMMENT '打招呼信息',
`icon` varchar(255) NOT NULL COMMENT '角色图标',
`enable` tinyint(1) NOT NULL COMMENT '是否被启用',
`sort_num` smallint NOT NULL DEFAULT '0' COMMENT '角色排序',
`model_id` int NOT NULL DEFAULT '0' COMMENT '绑定模型ID',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天角色表';
--
-- 转存表中的数据 `chatgpt_chat_roles`
--
INSERT INTO `chatgpt_chat_roles` (`id`, `name`, `marker`, `context_json`, `hello_msg`, `icon`, `enable`, `sort_num`, `model_id`, `created_at`, `updated_at`) VALUES
(1, '通用AI助手', 'gpt', '', '您好我是您的AI智能助手我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 0, 0, '2023-05-30 07:02:06', '2024-03-15 09:15:42'),
(24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 3, 0, '2023-05-30 14:10:24', '2023-12-29 17:46:45'),
(25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 2, 0, '2023-05-30 14:10:24', '2023-12-29 17:46:44'),
(26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 4, 0, '2023-05-30 14:10:24', '2023-12-29 17:46:45'),
(27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 1, 1, '2023-05-30 14:10:24', '2024-04-12 11:54:53'),
(28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 5, 0, '2023-05-30 14:10:24', '2023-12-29 17:46:46'),
(29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装¥1188/箱,和系列 6 瓶装¥2208/箱,贵系列 6 瓶装¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 8, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(30, '英语陪练员', 'english_trainer', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。\"},{\"role\":\"assistant\",\"content\":\"Okay, let\'s start our conversation practice! What\'s your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)\"}]', 'Okay, let\'s start our conversation practice! What\'s your name?', '/images/avatar/english_trainer.jpg', 1, 6, 0, '2023-05-30 14:10:24', '2023-12-29 17:46:47'),
(31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 7, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 9, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 10, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 11, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 12, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 13, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 14, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 15, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 16, 0, '2023-05-30 14:10:24', '2023-12-29 17:43:53');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_configs`
--
DROP TABLE IF EXISTS `chatgpt_configs`;
CREATE TABLE `chatgpt_configs` (
`id` int NOT NULL,
`marker` varchar(20) NOT NULL COMMENT '标识',
`config_json` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
--
-- 转存表中的数据 `chatgpt_configs`
--
INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES
(1, 'system', '{\"title\":\"Geek-AI 创作系统\",\"admin_title\":\"Geek-AI 控制台\",\"logo\":\"http://localhost:5678/static/upload/2024/4/1714382860986912.png\",\"init_power\":100,\"daily_power\":99,\"invite_power\":10,\"vip_month_power\":1000,\"register_ways\":[\"mobile\",\"username\",\"email\"],\"enabled_register\":true,\"reward_img\":\"http://localhost:5678/static/upload/2024/3/1710753716309668.jpg\",\"enabled_reward\":true,\"power_price\":0.1,\"order_pay_timeout\":30,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[11,7,1,10,12,19,18,17,3],\"mj_power\":30,\"mj_action_power\":10,\"sd_power\":10,\"dall_power\":15,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4,\"sd_neg_prompt\":\"nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet\",\"rand_bg\":true}'),
(3, 'notice', '{\"sd_neg_prompt\":\"\",\"rand_bg\":false,\"content\":\"## v4.0.8 更新日志\\n\\n* 功能优化:升级 mathjax 公式解析插件,修复公式因为图片访问限制而无法显示的问题\\n* 功能优化:当数据库更新失败的时候记录错误日志\\n* 功能优化:聊天输入框会随着输入内容的增多自动调整高度\\n* Bug修复修复移动端聊天页面模型切换不生效的Bug\\n* 功能优化给PC端扫码支付增加签名验证和有效期验证\\n* Bug修复修复支付码生成API权限控制的问题\\n* Bug修复模型算力设置为0时不扣减用户算力并且不记录算力消费日志\\n* 功能优化:新增随机背景配置项,可以在后台设置,首页使用 Bing 壁纸作为背景图片\\n* 功能新增H5端支持 Dalle 绘图\\n\\n注意当前站点仅为开源项目 \\u003ca style=\\\"color: #F56C6C\\\" href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003eChatPlus\\u003c/a\\u003e 的演示项目,本项目单纯就是给大家体验项目功能使用。\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作\\u003c/strong\\u003e\\n 如果觉得好用你就花几分钟自己部署一套没有API KEY 的同学可以去下面几个推荐的中转站购买:\\n1、\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e\\n2、\\u003ca href=\\\"https://api.geekai.me\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.geekai.me\\u003c/a\\u003e\\n3、 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e\\n支持MidJourneyGPTClaudeGoogle Gemmi以及国内各个厂家的大模型现在有超级优惠价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://ai.r9it.com/docs/install/errors-handle.html#%E8%B0%83%E7%94%A8%E4%B8%AD%E8%BD%AC-api-%E6%8A%A5%E9%94%99%E6%97%A0%E5%8F%AF%E7%94%A8%E6%B8%A0%E9%81%93)。GPT-3.5GPT-4DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程 \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n本项目源码地址\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_dall_jobs`
--
DROP TABLE IF EXISTS `chatgpt_dall_jobs`;
CREATE TABLE `chatgpt_dall_jobs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`prompt` varchar(2000) NOT NULL COMMENT '提示词',
`img_url` varchar(255) NOT NULL COMMENT '图片地址',
`org_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原图地址',
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
`power` smallint NOT NULL COMMENT '消耗算力',
`progress` smallint NOT NULL COMMENT '任务进度',
`err_msg` varchar(255) NOT NULL COMMENT '错误信息',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='DALLE 绘图任务表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_files`
--
DROP TABLE IF EXISTS `chatgpt_files`;
CREATE TABLE `chatgpt_files` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`name` varchar(100) NOT NULL COMMENT '文件名',
`obj_key` varchar(100) DEFAULT NULL COMMENT '文件标识',
`url` varchar(255) NOT NULL COMMENT '文件地址',
`ext` varchar(10) NOT NULL COMMENT '文件后缀',
`size` bigint NOT NULL DEFAULT '0' COMMENT '文件大小',
`created_at` datetime NOT NULL COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户文件表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_functions`
--
DROP TABLE IF EXISTS `chatgpt_functions`;
CREATE TABLE `chatgpt_functions` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '函数名称',
`label` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '函数标签',
`description` varchar(255) DEFAULT NULL COMMENT '函数描述',
`parameters` text COMMENT '函数参数JSON',
`token` varchar(255) DEFAULT NULL COMMENT 'API授权token',
`action` varchar(255) DEFAULT NULL COMMENT '函数处理 API',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='函数插件表';
--
-- 转存表中的数据 `chatgpt_functions`
--
INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `token`, `action`, `enabled`) VALUES
(1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 0),
(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 0),
(3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 0);
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_invite_codes`
--
DROP TABLE IF EXISTS `chatgpt_invite_codes`;
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='用户邀请码';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_invite_logs`
--
DROP TABLE IF EXISTS `chatgpt_invite_logs`;
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(30) 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 '邀请码',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_menus`
--
DROP TABLE IF EXISTS `chatgpt_menus`;
CREATE TABLE `chatgpt_menus` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '菜单名称',
`icon` varchar(150) NOT NULL COMMENT '菜单图标',
`url` varchar(100) NOT NULL COMMENT '地址',
`sort_num` smallint NOT NULL COMMENT '排序',
`enabled` tinyint(1) NOT NULL COMMENT '是否启用'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='前端菜单表';
--
-- 转存表中的数据 `chatgpt_menus`
--
INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES
(1, '对话聊天', '/images/menu/chat.png', '/chat', 1, 1),
(5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 2, 1),
(6, 'SD 绘画', '/images/menu/sd.png', '/sd', 3, 1),
(7, '算力日志', '/images/menu/log.png', '/powerLog', 8, 1),
(8, '应用中心', '/images/menu/app.png', '/apps', 7, 1),
(9, '画廊', '/images/menu/img-wall.png', '/images-wall', 5, 1),
(10, '会员计划', '/images/menu/member.png', '/member', 9, 1),
(11, '分享计划', '/images/menu/share.png', '/invite', 10, 1),
(12, '思维导图', '/images/menu/xmind.png', '/xmind', 6, 1),
(13, 'DALLE', '/images/menu/dalle.png', '/dalle', 4, 1),
(14, '项目文档', '/images/menu/docs.png', 'https://ai.r9it.com/docs/', 11, 1),
(16, '极客论坛', '/images/menu/bbs.png', 'https://bbs.geekai.me/', 13, 1);
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_mj_jobs`
--
DROP TABLE IF EXISTS `chatgpt_mj_jobs`;
CREATE TABLE `chatgpt_mj_jobs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID',
`type` varchar(20) DEFAULT 'image' COMMENT '任务类别',
`message_id` char(40) NOT NULL COMMENT '消息 ID',
`channel_id` char(40) DEFAULT NULL COMMENT '频道ID',
`reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID',
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
`img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL',
`org_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原始图片地址',
`hash` varchar(100) DEFAULT NULL COMMENT 'message hash',
`progress` smallint DEFAULT '0' COMMENT '任务进度',
`use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代',
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
`err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
`power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_orders`
--
DROP TABLE IF EXISTS `chatgpt_orders`;
CREATE TABLE `chatgpt_orders` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`product_id` int NOT NULL COMMENT '产品ID',
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户明',
`order_no` varchar(30) NOT NULL COMMENT '订单ID',
`trade_no` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付平台交易流水号',
`subject` varchar(100) NOT NULL COMMENT '订单产品',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态0待支付1已扫码2支付失败',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
`pay_time` int DEFAULT NULL COMMENT '支付时间',
`pay_way` varchar(20) NOT NULL COMMENT '支付方式',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='充值订单表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_power_logs`
--
DROP TABLE IF EXISTS `chatgpt_power_logs`;
CREATE TABLE `chatgpt_power_logs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`username` varchar(30) NOT NULL COMMENT '用户名',
`type` tinyint(1) NOT NULL COMMENT '类型1充值2消费3退费',
`amount` smallint NOT NULL COMMENT '算力数值',
`balance` int NOT NULL COMMENT '余额',
`model` varchar(30) NOT NULL COMMENT '模型',
`remark` varchar(255) NOT NULL COMMENT '备注',
`mark` tinyint(1) NOT NULL COMMENT '资金类型0支出1收入',
`created_at` datetime NOT NULL COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户算力消费日志';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_products`
--
DROP TABLE IF EXISTS `chatgpt_products`;
CREATE TABLE `chatgpt_products` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '名称',
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
`discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额',
`days` smallint NOT NULL DEFAULT '0' COMMENT '延长天数',
`power` int NOT NULL DEFAULT '0' COMMENT '增加算力值',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启动',
`sales` int NOT NULL DEFAULT '0' COMMENT '销量',
`sort_num` tinyint NOT NULL DEFAULT '0' COMMENT '排序',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`app_url` varchar(255) DEFAULT NULL COMMENT 'App跳转地址',
`url` varchar(255) DEFAULT NULL COMMENT '跳转地址'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
--
-- 转存表中的数据 `chatgpt_products`
--
INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `power`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`, `app_url`, `url`) VALUES
(1, '会员1个月', 1999.90, 1999.00, 30, 0, 1, 0, 0, '2023-08-28 10:48:57', '2024-04-26 16:09:06', NULL, NULL),
(2, '会员3个月', 3940.00, 30.00, 90, 0, 1, 0, 0, '2023-08-28 10:52:22', '2024-03-22 17:56:10', NULL, NULL),
(3, '会员6个月', 5990.00, 100.00, 180, 0, 1, 0, 0, '2023-08-28 10:53:39', '2024-03-22 17:56:15', NULL, NULL),
(4, '会员12个月', 9980.00, 200.00, 365, 0, 1, 0, 0, '2023-08-28 10:54:15', '2024-03-22 17:56:23', NULL, NULL),
(5, '100次点卡', 1999.00, 3.00, 0, 100, 1, 0, 0, '2023-08-28 10:55:08', '2024-03-22 17:56:37', NULL, NULL),
(6, '200次点卡', 2999.00, 10.00, 0, 200, 1, 0, 0, '1970-01-01 08:00:00', '2024-03-22 17:56:41', NULL, NULL);
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_rewards`
--
DROP TABLE IF EXISTS `chatgpt_rewards`;
CREATE TABLE `chatgpt_rewards` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`tx_id` char(36) NOT NULL COMMENT '交易 ID',
`amount` decimal(10,2) NOT NULL COMMENT '打赏金额',
`remark` varchar(80) NOT NULL COMMENT '备注',
`status` tinyint(1) NOT NULL COMMENT '核销状态0未核销1已核销',
`exchange` varchar(255) NOT NULL COMMENT '兑换详情json',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户打赏';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_sd_jobs`
--
DROP TABLE IF EXISTS `chatgpt_sd_jobs`;
CREATE TABLE `chatgpt_sd_jobs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'txt2img' COMMENT '任务类别',
`task_id` char(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务 ID',
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
`img_url` varchar(255) DEFAULT NULL COMMENT '图片URL',
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '绘画参数json',
`progress` smallint DEFAULT '0' COMMENT '任务进度',
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
`err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
`power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Stable Diffusion 任务表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_users`
--
DROP TABLE IF EXISTS `chatgpt_users`;
CREATE TABLE `chatgpt_users` (
`id` int NOT NULL,
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`nickname` varchar(30) NOT NULL COMMENT '昵称',
`password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
`avatar` varchar(100) NOT NULL COMMENT '头像',
`salt` char(12) NOT NULL COMMENT '密码盐',
`power` int NOT NULL DEFAULT '0' COMMENT '剩余算力',
`expired_time` int NOT NULL COMMENT '用户过期时间',
`status` tinyint(1) NOT NULL COMMENT '当前状态',
`chat_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天配置json',
`chat_roles_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天角色 json',
`chat_models_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'AI模型 json',
`last_login_at` int NOT NULL COMMENT '最后登录时间',
`vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否会员',
`last_login_ip` char(16) NOT NULL COMMENT '最后登录 IP',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
--
-- 转存表中的数据 `chatgpt_users`
--
INSERT INTO `chatgpt_users` (`id`, `username`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `created_at`, `updated_at`) VALUES
(4, '18575670125', '极客学长@830270', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/5/1715651569509929.png', 'ueedue5l', 4887, 1717292086, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"seller\",\"artist\",\"lu_xun\",\"girl_friend\",\"psychiatrist\",\"teacher\",\"programmer\",\"test\",\"qing_gan_da_shi\"]', '[1,11]', 1716964389, 1, '::1', '2023-06-12 16:47:17', '2024-05-29 14:33:09'),
(5, 'yangjian102621@gmail.com', '极客学长@486041', '75d1a22f33e1ffffb7943946b6b8d5177d5ecd685d3cef1b468654038b0a8c22', '/images/avatar/user.png', '2q8ugxzk', 100, 0, 1, '', '[\"gpt\",\"programmer\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '2024-04-23 09:17:26', '2024-04-23 09:17:26'),
(8, 'yangjian102623@gmail.com', '极客学长@714931', 'f8f0e0abf146569217273ea0712a0f9b6cbbe7d943a1d9bd5f91c55e6d8c05d1', '/images/avatar/user.png', 'geuddq7f', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '2024-04-26 15:19:28', '2024-04-26 15:19:28'),
(9, '1234567', '极客学长@604526', '858e2afec79e1d6364f4567f945f2310024896d9aa45dd944efa95a0c31e4d08', '/images/avatar/user.png', '00qawlos', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '2024-04-26 15:21:06', '2024-04-26 15:21:06');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_user_login_logs`
--
DROP TABLE IF EXISTS `chatgpt_user_login_logs`;
CREATE TABLE `chatgpt_user_login_logs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`username` varchar(30) NOT NULL COMMENT '用户名',
`login_ip` char(16) NOT NULL COMMENT '登录IP',
`login_address` varchar(30) NOT NULL COMMENT '登录地址',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录日志';
--
-- 转储表的索引
--
--
-- 表的索引 `chatgpt_admin_users`
--
ALTER TABLE `chatgpt_admin_users`
ADD PRIMARY KEY (`id`) USING BTREE,
ADD UNIQUE KEY `username` (`username`) USING BTREE;
--
-- 表的索引 `chatgpt_api_keys`
--
ALTER TABLE `chatgpt_api_keys`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_chat_history`
--
ALTER TABLE `chatgpt_chat_history`
ADD PRIMARY KEY (`id`),
ADD KEY `chat_id` (`chat_id`);
--
-- 表的索引 `chatgpt_chat_items`
--
ALTER TABLE `chatgpt_chat_items`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `chat_id` (`chat_id`);
--
-- 表的索引 `chatgpt_chat_models`
--
ALTER TABLE `chatgpt_chat_models`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_chat_roles`
--
ALTER TABLE `chatgpt_chat_roles`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `marker` (`marker`);
--
-- 表的索引 `chatgpt_configs`
--
ALTER TABLE `chatgpt_configs`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `marker` (`marker`);
--
-- 表的索引 `chatgpt_dall_jobs`
--
ALTER TABLE `chatgpt_dall_jobs`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_files`
--
ALTER TABLE `chatgpt_files`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_functions`
--
ALTER TABLE `chatgpt_functions`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `name` (`name`);
--
-- 表的索引 `chatgpt_invite_codes`
--
ALTER TABLE `chatgpt_invite_codes`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `code` (`code`);
--
-- 表的索引 `chatgpt_invite_logs`
--
ALTER TABLE `chatgpt_invite_logs`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_menus`
--
ALTER TABLE `chatgpt_menus`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_mj_jobs`
--
ALTER TABLE `chatgpt_mj_jobs`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `task_id` (`task_id`),
ADD KEY `message_id` (`message_id`);
--
-- 表的索引 `chatgpt_orders`
--
ALTER TABLE `chatgpt_orders`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `order_no` (`order_no`);
--
-- 表的索引 `chatgpt_power_logs`
--
ALTER TABLE `chatgpt_power_logs`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_products`
--
ALTER TABLE `chatgpt_products`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_rewards`
--
ALTER TABLE `chatgpt_rewards`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `tx_id` (`tx_id`);
--
-- 表的索引 `chatgpt_sd_jobs`
--
ALTER TABLE `chatgpt_sd_jobs`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `task_id` (`task_id`);
--
-- 表的索引 `chatgpt_users`
--
ALTER TABLE `chatgpt_users`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `username` (`username`),
ADD UNIQUE KEY `username_2` (`username`);
--
-- 表的索引 `chatgpt_user_login_logs`
--
ALTER TABLE `chatgpt_user_login_logs`
ADD PRIMARY KEY (`id`);
--
-- 在导出的表使用AUTO_INCREMENT
--
--
-- 使用表AUTO_INCREMENT `chatgpt_admin_users`
--
ALTER TABLE `chatgpt_admin_users`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=113;
--
-- 使用表AUTO_INCREMENT `chatgpt_api_keys`
--
ALTER TABLE `chatgpt_api_keys`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_history`
--
ALTER TABLE `chatgpt_chat_history`
MODIFY `id` bigint NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_items`
--
ALTER TABLE `chatgpt_chat_items`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_models`
--
ALTER TABLE `chatgpt_chat_models`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=40;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
--
ALTER TABLE `chatgpt_chat_roles`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=132;
--
-- 使用表AUTO_INCREMENT `chatgpt_configs`
--
ALTER TABLE `chatgpt_configs`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
--
-- 使用表AUTO_INCREMENT `chatgpt_dall_jobs`
--
ALTER TABLE `chatgpt_dall_jobs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_files`
--
ALTER TABLE `chatgpt_files`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_functions`
--
ALTER TABLE `chatgpt_functions`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
--
-- 使用表AUTO_INCREMENT `chatgpt_invite_codes`
--
ALTER TABLE `chatgpt_invite_codes`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_invite_logs`
--
ALTER TABLE `chatgpt_invite_logs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_menus`
--
ALTER TABLE `chatgpt_menus`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=19;
--
-- 使用表AUTO_INCREMENT `chatgpt_mj_jobs`
--
ALTER TABLE `chatgpt_mj_jobs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_orders`
--
ALTER TABLE `chatgpt_orders`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_power_logs`
--
ALTER TABLE `chatgpt_power_logs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_products`
--
ALTER TABLE `chatgpt_products`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
--
-- 使用表AUTO_INCREMENT `chatgpt_rewards`
--
ALTER TABLE `chatgpt_rewards`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_sd_jobs`
--
ALTER TABLE `chatgpt_sd_jobs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_users`
--
ALTER TABLE `chatgpt_users`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10;
--
-- 使用表AUTO_INCREMENT `chatgpt_user_login_logs`
--
ALTER TABLE `chatgpt_user_login_logs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
ALTER TABLE `chatgpt_dall_jobs` CHANGE `org_url` `org_url` VARCHAR(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '原图地址';

3
deploy/.gitignore vendored
View File

@@ -1,4 +1,5 @@
data/mysql/data
data/leveldb
logs
static/*
redis/redis
redis/redis

File diff suppressed because one or more lines are too long

View File

@@ -1,790 +0,0 @@
-- phpMyAdmin SQL Dump
-- version 5.1.3
-- https://www.phpmyadmin.net/
--
-- 主机: localhost:3307
-- 生成日期: 2024-04-07 10:30:00
-- 服务器版本: 8.0.33
-- PHP 版本: 8.1.18
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- 数据库: `chatgpt_plus`
--
CREATE DATABASE IF NOT EXISTS `chatgpt_plus` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE `chatgpt_plus`;
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_admin_users`
--
DROP TABLE IF EXISTS `chatgpt_admin_users`;
CREATE TABLE `chatgpt_admin_users` (
`id` int NOT NULL,
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
`salt` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码盐',
`status` tinyint(1) NOT NULL COMMENT '当前状态',
`last_login_at` int NOT NULL COMMENT '最后登录时间',
`last_login_ip` char(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '最后登录 IP',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户' ROW_FORMAT=DYNAMIC;
--
-- 转存表中的数据 `chatgpt_admin_users`
--
INSERT INTO `chatgpt_admin_users` (`id`, `username`, `password`, `salt`, `status`, `last_login_at`, `last_login_ip`, `created_at`, `updated_at`) VALUES
(1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1712456145, '::1', '2024-03-11 16:30:20', '2024-04-07 10:15:45'),
(108, 'test', '9ed720ce03e0a69885455271b4b3e1710bff79434f2a95d0de6406dd88cc9f79', '4b9orqjh', 0, 1710396975, '::1', '2024-03-13 16:06:43', '2024-03-21 15:15:04');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_api_keys`
--
DROP TABLE IF EXISTS `chatgpt_api_keys`;
CREATE TABLE `chatgpt_api_keys` (
`id` int NOT NULL,
`platform` char(20) DEFAULT NULL COMMENT '平台',
`name` varchar(30) DEFAULT NULL COMMENT '名称',
`value` varchar(100) NOT NULL COMMENT 'API KEY value',
`type` varchar(10) NOT NULL DEFAULT 'chat' COMMENT '用途chat=>聊天img=>图片)',
`last_used_at` int NOT NULL COMMENT '最后使用时间',
`api_url` varchar(255) DEFAULT NULL COMMENT 'API 地址',
`enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
`proxy_url` varchar(100) DEFAULT NULL COMMENT '代理地址',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OpenAI API ';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_history`
--
DROP TABLE IF EXISTS `chatgpt_chat_history`;
CREATE TABLE `chatgpt_chat_history` (
`id` bigint NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
`type` varchar(10) NOT NULL COMMENT '类型prompt|reply',
`icon` varchar(100) NOT NULL COMMENT '角色图标',
`role_id` int NOT NULL COMMENT '角色 ID',
`model` varchar(30) DEFAULT NULL COMMENT '模型名称',
`content` text NOT NULL COMMENT '聊天内容',
`tokens` smallint NOT NULL COMMENT '耗费 token 数量',
`use_context` tinyint(1) NOT NULL COMMENT '是否允许作为上下文语料',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天历史记录';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_items`
--
DROP TABLE IF EXISTS `chatgpt_chat_items`;
CREATE TABLE `chatgpt_chat_items` (
`id` int NOT NULL,
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
`user_id` int NOT NULL COMMENT '用户 ID',
`role_id` int NOT NULL COMMENT '角色 ID',
`title` varchar(100) NOT NULL COMMENT '会话标题',
`model_id` int NOT NULL DEFAULT '0' COMMENT '模型 ID',
`model` varchar(30) DEFAULT NULL COMMENT '模型名称',
`created_at` datetime NOT NULL COMMENT '创建时间',
`updated_at` datetime NOT NULL COMMENT '更新时间',
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户会话列表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_models`
--
DROP TABLE IF EXISTS `chatgpt_chat_models`;
CREATE TABLE `chatgpt_chat_models` (
`id` int NOT NULL,
`platform` varchar(20) DEFAULT NULL COMMENT '模型平台',
`name` varchar(50) NOT NULL COMMENT '模型名称',
`value` varchar(50) NOT NULL COMMENT '模型值',
`sort_num` tinyint(1) NOT NULL COMMENT '排序数字',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用模型',
`power` tinyint NOT NULL COMMENT '消耗算力点数',
`temperature` float(3,1) NOT NULL DEFAULT '1.0' COMMENT '模型创意度',
`max_tokens` int NOT NULL DEFAULT '1024' COMMENT '最大响应长度',
`max_context` int NOT NULL DEFAULT '4096' COMMENT '最大上下文长度',
`open` tinyint(1) NOT NULL COMMENT '是否开放模型',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI 模型表';
--
-- 转存表中的数据 `chatgpt_chat_models`
--
INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num`, `enabled`, `power`, `temperature`, `max_tokens`, `max_context`, `open`, `created_at`, `updated_at`) VALUES
(1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo-0125', 0, 1, 1, 1.0, 1024, 4096, 1, '2023-08-23 12:06:36', '2024-03-18 15:43:51'),
(2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 14, 1, 1, 1.0, 1024, 4096, 0, '2023-08-23 12:15:30', '2024-03-18 14:27:19'),
(3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 3, 1, 1, 1.0, 2048, 32768, 1, '2023-08-23 13:35:45', '2024-03-18 14:27:19'),
(7, 'Baidu', '文心一言3.0', 'eb-instant', 12, 1, 1, 1.0, 1024, 4096, 1, '2023-10-11 11:29:28', '2024-03-18 14:27:19'),
(8, 'XunFei', '星火V3.5', 'generalv3.5', 2, 1, 5, 0.8, 1024, 8192, 1, '2023-10-11 15:48:30', '2024-03-18 14:27:19'),
(9, 'XunFei', '星火V2.0', 'generalv2', 11, 1, 1, 1.0, 1024, 8192, 1, '2023-10-11 15:48:45', '2024-03-18 14:27:19'),
(10, 'Baidu', '文心一言4.0', 'completions_pro', 13, 1, 3, 1.0, 1024, 8192, 1, '2023-10-25 08:31:37', '2024-03-18 14:27:19'),
(11, 'OpenAI', 'GPT-4.0', 'gpt-4-0125-preview', 1, 1, 15, 1.0, 1024, 8192, 1, '2023-10-25 08:45:15', '2024-03-18 15:46:58'),
(12, 'XunFei', '星火v3.0', 'generalv3', 10, 1, 3, 1.0, 1024, 8192, 1, '2023-11-23 09:20:33', '2024-03-18 14:27:19'),
(15, 'OpenAI', 'GPT-超级模型', 'gpt-4-all', 4, 1, 30, 1.0, 4096, 32768, 0, '2024-01-15 11:32:52', '2024-03-18 14:27:19'),
(16, 'OpenAI', '视频号导师', 'gpt-4-gizmo-g-QXXEBTXl7', 5, 1, 30, 1.0, 4096, 32768, 0, '2024-01-15 14:46:35', '2024-03-18 14:29:39'),
(17, 'QWen', '通义千问-Turbo', 'qwen-turbo', 7, 1, 1, 1.0, 1024, 8192, 1, '2024-01-19 10:42:24', '2024-03-18 14:27:19'),
(18, 'QWen', '通义千问-Plus', 'qwen-plus', 8, 1, 1, 1.0, 1024, 32768, 1, '2024-01-19 10:42:49', '2024-03-18 14:27:19'),
(19, 'QWen', '通义千问-Max', 'qwen-max-1201', 9, 1, 1, 1.0, 1024, 32768, 1, '2024-01-19 10:51:03', '2024-03-18 14:27:19'),
(21, 'OpenAI', '董宇辉小作文助手', 'gpt-4-gizmo-g-dse9iXvor', 6, 1, 30, 1.0, 8192, 32768, 0, '2024-03-18 14:24:20', '2024-03-18 14:27:19'),
(22, 'OpenAI', 'LOGO生成神器', 'gpt-4-gizmo-g-YL87j8C7S', 0, 1, 30, 1.0, 1024, 4096, 1, '2024-03-20 14:02:11', '2024-03-20 14:02:18'),
(23, 'OpenAI', '音乐生成器', 'suno-v3', 0, 1, 50, 0.8, 1024, 4096, 1, '2024-03-29 15:43:40', '2024-03-29 15:45:15'),
(24, 'OpenAI', '通义千问(中转)', 'qwen-plus', 0, 1, 0, 1.0, 1024, 4096, 1, '2024-04-03 12:00:46', '2024-04-03 12:00:46');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_chat_roles`
--
DROP TABLE IF EXISTS `chatgpt_chat_roles`;
CREATE TABLE `chatgpt_chat_roles` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '角色名称',
`marker` varchar(30) NOT NULL COMMENT '角色标识',
`context_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色语料 json',
`hello_msg` varchar(255) NOT NULL COMMENT '打招呼信息',
`icon` varchar(255) NOT NULL COMMENT '角色图标',
`enable` tinyint(1) NOT NULL COMMENT '是否被启用',
`sort_num` smallint NOT NULL DEFAULT '0' COMMENT '角色排序',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天角色表';
--
-- 转存表中的数据 `chatgpt_chat_roles`
--
INSERT INTO `chatgpt_chat_roles` (`id`, `name`, `marker`, `context_json`, `hello_msg`, `icon`, `enable`, `sort_num`, `created_at`, `updated_at`) VALUES
(1, '通用AI助手', 'gpt', '', '您好我是您的AI智能助手我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 0, '2023-05-30 07:02:06', '2024-03-15 09:15:42'),
(24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 3, '2023-05-30 14:10:24', '2023-12-29 17:46:45'),
(25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 2, '2023-05-30 14:10:24', '2023-12-29 17:46:44'),
(26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 4, '2023-05-30 14:10:24', '2023-12-29 17:46:45'),
(27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 1, '2023-05-30 14:10:24', '2023-12-29 17:46:43'),
(28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 5, '2023-05-30 14:10:24', '2023-12-29 17:46:46'),
(29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装¥1188/箱,和系列 6 瓶装¥2208/箱,贵系列 6 瓶装¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 8, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(30, '英语陪练员', 'english_trainer', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。\"},{\"role\":\"assistant\",\"content\":\"Okay, let\'s start our conversation practice! What\'s your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)\"}]', 'Okay, let\'s start our conversation practice! What\'s your name?', '/images/avatar/english_trainer.jpg', 1, 6, '2023-05-30 14:10:24', '2023-12-29 17:46:47'),
(31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 7, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 9, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 10, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 11, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 12, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 13, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 14, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 15, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
(39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 16, '2023-05-30 14:10:24', '2023-12-29 17:43:53');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_configs`
--
DROP TABLE IF EXISTS `chatgpt_configs`;
CREATE TABLE `chatgpt_configs` (
`id` int NOT NULL,
`marker` varchar(20) NOT NULL COMMENT '标识',
`config_json` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
--
-- 转存表中的数据 `chatgpt_configs`
--
INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES
(1, 'system', '{\"title\":\"Geek-AI创作系统\",\"admin_title\":\"Geek-AI控制台\",\"logo\":\"/images/logo.png\",\"init_power\":100,\"daily_power\":99,\"invite_power\":10,\"vip_month_power\":1000,\"register_ways\":[\"mobile\",\"username\",\"email\"],\"enabled_register\":true,\"reward_img\":\"http://localhost:5678/static/upload/2024/3/1710753716309668.jpg\",\"enabled_reward\":true,\"power_price\":0.1,\"order_pay_timeout\":1800,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[11,7,1,10,12,19,18,17,3],\"mj_power\":20,\"mj_action_power\":10,\"sd_power\":5,\"dall_power\":15,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4}'),
(3, 'notice', '{\"content\":\"系统每日会给免费会员赠送10算力值用完请第二天再来领取。\\n## v4.0.2 更新日志\\n* 功能新增:支持前端菜单可以配置\\n* 功能优化:在登录和注册界面标题显示软件版本号\\n* 功能优化MJ 绘画支持 --sref 和 --cref 图片一致性参数\\n* 功能优化:使用 leveldb 解决 SD 绘图进度图片预览问题\\n* Bug修复解决因为图片上传使用相对路径而导致融图失败的问题\\n* 功能新增:手机端支持 Stable-Diffusion 绘画\\n* Bug修复修复管理后台 API KEY 删除失败的问题\\n\\n 如果觉得好用你就花几分钟自己部署一套没有API KEY 的同学可以去\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e 支持MidJourneyGPTClaudeGoogle Gemmi 各种表格模型) 或者 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e不支持 Midjourney 购买,现在有超级优惠,价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://ai.r9it.com/docs/install/errors-handle.html#%E8%B0%83%E7%94%A8%E4%B8%AD%E8%BD%AC-api-%E6%8A%A5%E9%94%99%E6%97%A0%E5%8F%AF%E7%94%A8%E6%B8%A0%E9%81%93)。\\nGPT-3.5GPT-4DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程 \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n\\n本项目源码地址\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_files`
--
DROP TABLE IF EXISTS `chatgpt_files`;
CREATE TABLE `chatgpt_files` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`name` varchar(100) NOT NULL COMMENT '文件名',
`obj_key` varchar(100) DEFAULT NULL COMMENT '文件标识',
`url` varchar(255) NOT NULL COMMENT '文件地址',
`ext` varchar(10) NOT NULL COMMENT '文件后缀',
`size` bigint NOT NULL DEFAULT '0' COMMENT '文件大小',
`created_at` datetime NOT NULL COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户文件表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_functions`
--
DROP TABLE IF EXISTS `chatgpt_functions`;
CREATE TABLE `chatgpt_functions` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '函数名称',
`label` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '函数标签',
`description` varchar(255) DEFAULT NULL COMMENT '函数描述',
`parameters` text COMMENT '函数参数JSON',
`token` varchar(255) DEFAULT NULL COMMENT 'API授权token',
`action` varchar(255) DEFAULT NULL COMMENT '函数处理 API',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='函数插件表';
--
-- 转存表中的数据 `chatgpt_functions`
--
INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `token`, `action`, `enabled`) VALUES
(1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 0),
(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 0),
(3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 0);
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_invite_codes`
--
DROP TABLE IF EXISTS `chatgpt_invite_codes`;
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='用户邀请码';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_invite_logs`
--
DROP TABLE IF EXISTS `chatgpt_invite_logs`;
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(30) 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 '邀请码',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_menus`
--
DROP TABLE IF EXISTS `chatgpt_menus`;
CREATE TABLE `chatgpt_menus` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '菜单名称',
`icon` varchar(150) NOT NULL COMMENT '菜单图标',
`url` varchar(100) NOT NULL COMMENT '地址',
`sort_num` smallint NOT NULL COMMENT '排序',
`enabled` tinyint(1) NOT NULL COMMENT '是否启用'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='前端菜单表';
--
-- 转存表中的数据 `chatgpt_menus`
--
INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES
(1, '对话聊天', '/images/menu/chat.png', '/chat', 0, 1),
(5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 1, 1),
(6, 'SD 绘画', '/images/menu/sd.png', '/sd', 2, 1),
(7, '算力日志', '/images/menu/log.png', '/powerLog', 5, 1),
(8, '应用中心', '/images/menu/app.png', '/apps', 3, 1),
(9, '作品展示', '/images/menu/img-wall.png', '/images-wall', 4, 1),
(10, '会员计划', '/images/menu/member.png', '/member', 6, 1),
(11, '分享计划', '/images/menu/share.png', '/invite', 7, 1);
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_mj_jobs`
--
DROP TABLE IF EXISTS `chatgpt_mj_jobs`;
CREATE TABLE `chatgpt_mj_jobs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID',
`type` varchar(20) DEFAULT 'image' COMMENT '任务类别',
`message_id` char(40) NOT NULL COMMENT '消息 ID',
`channel_id` char(40) DEFAULT NULL COMMENT '频道ID',
`reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID',
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
`img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL',
`org_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原始图片地址',
`hash` varchar(100) DEFAULT NULL COMMENT 'message hash',
`progress` smallint DEFAULT '0' COMMENT '任务进度',
`use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代',
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
`err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
`power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_orders`
--
DROP TABLE IF EXISTS `chatgpt_orders`;
CREATE TABLE `chatgpt_orders` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`product_id` int NOT NULL COMMENT '产品ID',
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户明',
`order_no` varchar(30) NOT NULL COMMENT '订单ID',
`trade_no` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付平台交易流水号',
`subject` varchar(100) NOT NULL COMMENT '订单产品',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态0待支付1已扫码2支付失败',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
`pay_time` int DEFAULT NULL COMMENT '支付时间',
`pay_way` varchar(20) NOT NULL COMMENT '支付方式',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='充值订单表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_power_logs`
--
DROP TABLE IF EXISTS `chatgpt_power_logs`;
CREATE TABLE `chatgpt_power_logs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`username` varchar(30) NOT NULL COMMENT '用户名',
`type` tinyint(1) NOT NULL COMMENT '类型1充值2消费3退费',
`amount` smallint NOT NULL COMMENT '算力数值',
`balance` int NOT NULL COMMENT '余额',
`model` varchar(30) NOT NULL COMMENT '模型',
`remark` varchar(255) NOT NULL COMMENT '备注',
`mark` tinyint(1) NOT NULL COMMENT '资金类型0支出1收入',
`created_at` datetime NOT NULL COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户算力消费日志';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_products`
--
DROP TABLE IF EXISTS `chatgpt_products`;
CREATE TABLE `chatgpt_products` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '名称',
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
`discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额',
`days` smallint NOT NULL DEFAULT '0' COMMENT '延长天数',
`power` int NOT NULL DEFAULT '0' COMMENT '增加算力值',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启动',
`sales` int NOT NULL DEFAULT '0' COMMENT '销量',
`sort_num` tinyint NOT NULL DEFAULT '0' COMMENT '排序',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`app_url` varchar(255) DEFAULT NULL COMMENT 'App跳转地址',
`url` varchar(255) DEFAULT NULL COMMENT '跳转地址'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_rewards`
--
DROP TABLE IF EXISTS `chatgpt_rewards`;
CREATE TABLE `chatgpt_rewards` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`tx_id` char(36) NOT NULL COMMENT '交易 ID',
`amount` decimal(10,2) NOT NULL COMMENT '打赏金额',
`remark` varchar(80) NOT NULL COMMENT '备注',
`status` tinyint(1) NOT NULL COMMENT '核销状态0未核销1已核销',
`exchange` varchar(255) NOT NULL COMMENT '兑换详情json',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户打赏';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_sd_jobs`
--
DROP TABLE IF EXISTS `chatgpt_sd_jobs`;
CREATE TABLE `chatgpt_sd_jobs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'txt2img' COMMENT '任务类别',
`task_id` char(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务 ID',
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
`img_url` varchar(255) DEFAULT NULL COMMENT '图片URL',
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '绘画参数json',
`progress` smallint DEFAULT '0' COMMENT '任务进度',
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
`err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
`power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Stable Diffusion 任务表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_users`
--
DROP TABLE IF EXISTS `chatgpt_users`;
CREATE TABLE `chatgpt_users` (
`id` int NOT NULL,
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`nickname` varchar(30) NOT NULL COMMENT '昵称',
`password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
`avatar` varchar(100) NOT NULL COMMENT '头像',
`salt` char(12) NOT NULL COMMENT '密码盐',
`power` int NOT NULL DEFAULT '0' COMMENT '剩余算力',
`expired_time` int NOT NULL COMMENT '用户过期时间',
`status` tinyint(1) NOT NULL COMMENT '当前状态',
`chat_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天配置json',
`chat_roles_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天角色 json',
`chat_models_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'AI模型 json',
`last_login_at` int NOT NULL COMMENT '最后登录时间',
`vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否会员',
`last_login_ip` char(16) NOT NULL COMMENT '最后登录 IP',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
-- --------------------------------------------------------
--
-- 转存表中的数据 `chatgpt_users`
--
INSERT INTO `chatgpt_users` (`id`, `username`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `created_at`, `updated_at`) VALUES
(4, '18575670125', '极客学长@830270', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/2/1708682650912429.png', 'ueedue5l', 9384, 1717292086, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"programmer\",\"seller\"]', '[1,11]', 1711698298, 1, '::1', '2023-06-12 16:47:17', '2024-03-29 15:44:58');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_user_login_logs`
--
DROP TABLE IF EXISTS `chatgpt_user_login_logs`;
CREATE TABLE `chatgpt_user_login_logs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`username` varchar(30) NOT NULL COMMENT '用户名',
`login_ip` char(16) NOT NULL COMMENT '登录IP',
`login_address` varchar(30) NOT NULL COMMENT '登录地址',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录日志';
--
-- 转储表的索引
--
--
-- 表的索引 `chatgpt_admin_users`
--
ALTER TABLE `chatgpt_admin_users`
ADD PRIMARY KEY (`id`) USING BTREE,
ADD UNIQUE KEY `username` (`username`) USING BTREE;
--
-- 表的索引 `chatgpt_api_keys`
--
ALTER TABLE `chatgpt_api_keys`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_chat_history`
--
ALTER TABLE `chatgpt_chat_history`
ADD PRIMARY KEY (`id`),
ADD KEY `chat_id` (`chat_id`);
--
-- 表的索引 `chatgpt_chat_items`
--
ALTER TABLE `chatgpt_chat_items`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `chat_id` (`chat_id`);
--
-- 表的索引 `chatgpt_chat_models`
--
ALTER TABLE `chatgpt_chat_models`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_chat_roles`
--
ALTER TABLE `chatgpt_chat_roles`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `marker` (`marker`);
--
-- 表的索引 `chatgpt_configs`
--
ALTER TABLE `chatgpt_configs`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `marker` (`marker`);
--
-- 表的索引 `chatgpt_files`
--
ALTER TABLE `chatgpt_files`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_functions`
--
ALTER TABLE `chatgpt_functions`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `name` (`name`);
--
-- 表的索引 `chatgpt_invite_codes`
--
ALTER TABLE `chatgpt_invite_codes`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `code` (`code`);
--
-- 表的索引 `chatgpt_invite_logs`
--
ALTER TABLE `chatgpt_invite_logs`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_menus`
--
ALTER TABLE `chatgpt_menus`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_mj_jobs`
--
ALTER TABLE `chatgpt_mj_jobs`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `task_id` (`task_id`),
ADD KEY `message_id` (`message_id`);
--
-- 表的索引 `chatgpt_orders`
--
ALTER TABLE `chatgpt_orders`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `order_no` (`order_no`);
--
-- 表的索引 `chatgpt_power_logs`
--
ALTER TABLE `chatgpt_power_logs`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_products`
--
ALTER TABLE `chatgpt_products`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `chatgpt_rewards`
--
ALTER TABLE `chatgpt_rewards`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `tx_id` (`tx_id`);
--
-- 表的索引 `chatgpt_sd_jobs`
--
ALTER TABLE `chatgpt_sd_jobs`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `task_id` (`task_id`);
--
-- 表的索引 `chatgpt_users`
--
ALTER TABLE `chatgpt_users`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `username` (`username`),
ADD UNIQUE KEY `username_2` (`username`);
--
-- 表的索引 `chatgpt_user_login_logs`
--
ALTER TABLE `chatgpt_user_login_logs`
ADD PRIMARY KEY (`id`);
--
-- 在导出的表使用AUTO_INCREMENT
--
--
-- 使用表AUTO_INCREMENT `chatgpt_admin_users`
--
ALTER TABLE `chatgpt_admin_users`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=113;
--
-- 使用表AUTO_INCREMENT `chatgpt_api_keys`
--
ALTER TABLE `chatgpt_api_keys`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_history`
--
ALTER TABLE `chatgpt_chat_history`
MODIFY `id` bigint NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_items`
--
ALTER TABLE `chatgpt_chat_items`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_models`
--
ALTER TABLE `chatgpt_chat_models`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=25;
--
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
--
ALTER TABLE `chatgpt_chat_roles`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=130;
--
-- 使用表AUTO_INCREMENT `chatgpt_configs`
--
ALTER TABLE `chatgpt_configs`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
--
-- 使用表AUTO_INCREMENT `chatgpt_files`
--
ALTER TABLE `chatgpt_files`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_functions`
--
ALTER TABLE `chatgpt_functions`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
--
-- 使用表AUTO_INCREMENT `chatgpt_invite_codes`
--
ALTER TABLE `chatgpt_invite_codes`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_invite_logs`
--
ALTER TABLE `chatgpt_invite_logs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_menus`
--
ALTER TABLE `chatgpt_menus`
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12;
--
-- 使用表AUTO_INCREMENT `chatgpt_mj_jobs`
--
ALTER TABLE `chatgpt_mj_jobs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_orders`
--
ALTER TABLE `chatgpt_orders`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_power_logs`
--
ALTER TABLE `chatgpt_power_logs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_products`
--
ALTER TABLE `chatgpt_products`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_rewards`
--
ALTER TABLE `chatgpt_rewards`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_sd_jobs`
--
ALTER TABLE `chatgpt_sd_jobs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_users`
--
ALTER TABLE `chatgpt_users`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `chatgpt_user_login_logs`
--
ALTER TABLE `chatgpt_user_login_logs`
MODIFY `id` int NOT NULL AUTO_INCREMENT;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

View File

@@ -49,9 +49,10 @@ services:
volumes:
- ./conf/mj-proxy:/home/spring/config
# 后端 API 程序
geekai-api:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.0.6-amd64
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.0.9-amd64
container_name: geekai-api
restart: always
depends_on:
@@ -73,7 +74,7 @@ services:
# 前端应用
geekai-web:
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.0.6-amd64
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.0.9-amd64
container_name: geekai-web
restart: always
depends_on:

View File

@@ -1,9 +1,9 @@
VUE_APP_API_HOST=http://localhost:5678
VUE_APP_WS_HOST=ws://localhost:5678
VUE_APP_API_HOST=http://172.22.11.69:5678
VUE_APP_WS_HOST=ws://172.22.11.69:5678
VUE_APP_USER=18575670125
VUE_APP_PASS=12345678
VUE_APP_ADMIN_USER=admin
VUE_APP_ADMIN_PASS=admin123
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
VUE_APP_TITLE="Geek-AI 创作系统"
VUE_APP_VERSION=v4.0.7
VUE_APP_VERSION=v4.0.9

View File

@@ -2,4 +2,4 @@ VUE_APP_API_HOST=
VUE_APP_WS_HOST=
VUE_APP_KEY_PREFIX=ChatPLUS_
VUE_APP_TITLE="Geek-AI 创作系统"
VUE_APP_VERSION=v4.0.7
VUE_APP_VERSION=v4.0.9

493
web/package-lock.json generated
View File

@@ -1,11 +1,11 @@
{
"name": "chatgpt-plus-web",
"name": "geekai-web",
"version": "0.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "chatgpt-plus-web",
"name": "geekai-web",
"version": "0.1.0",
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
@@ -14,14 +14,13 @@
"compressorjs": "^1.2.1",
"core-js": "^3.8.3",
"echarts": "^5.5.0",
"element-plus": "^2.3.0",
"element-plus": "^2.4.0",
"good-storage": "^1.1.1",
"highlight.js": "^11.7.0",
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"markdown-it-latex2img": "^0.0.6",
"markdown-it-mathjax": "^2.0.0",
"markdown-it-mathjax3": "^4.3.2",
"markmap-common": "^0.16.0",
"markmap-lib": "^0.16.1",
"markmap-toolbar": "^0.17.0",
@@ -1739,9 +1738,9 @@
}
},
"node_modules/@element-plus/icons-vue": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.1.0.tgz",
"integrity": "sha512-PSBn3elNoanENc1vnCfh+3WA9fimRC7n+fWkf3rE5jvv+aBohNHABC/KAR5KWPecxWxDTVT1ERpRbOMRcOV/vA==",
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
"peerDependencies": {
"vue": "^3.2.0"
}
@@ -3586,7 +3585,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.1.tgz",
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"dev": true,
"engines": {
"node": ">=6"
}
@@ -4974,7 +4972,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz",
"integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
"dev": true,
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.0.1",
@@ -5833,7 +5830,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz",
"integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
"dev": true,
"dependencies": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
@@ -5849,7 +5845,6 @@
"version": "4.3.1",
"resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz",
"integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
"dev": true,
"dependencies": {
"domelementtype": "^2.2.0"
},
@@ -5861,7 +5856,6 @@
"version": "2.8.0",
"resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz",
"integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
"dev": true,
"dependencies": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
@@ -5935,12 +5929,12 @@
"dev": true
},
"node_modules/element-plus": {
"version": "2.3.9",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.9.tgz",
"integrity": "sha512-TIOLnPl4cnoCPXqK3QYh+jpkthUBQnAM21O7o3Lhbse8v9pfrRXRTaBJtoEKnYNa8GZ4lZptUfH0PeZgDCNLUg==",
"version": "2.7.6",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.7.6.tgz",
"integrity": "sha512-36sw1K23hYjgeooR10U6CiCaCp2wvOqwoFurADZVlekeQ9v5U1FhJCFGEXO6i/kZBBMwsE1c9fxjLs9LENw2Rg==",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6",
"@element-plus/icons-vue": "^2.3.1",
"@floating-ui/dom": "^1.0.1",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
"@types/lodash": "^4.14.182",
@@ -6030,8 +6024,7 @@
"node_modules/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
"dev": true
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
},
"node_modules/error-ex": {
"version": "1.3.2",
@@ -6066,6 +6059,17 @@
"node": ">=6"
}
},
"node_modules/escape-goat": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/escape-goat/-/escape-goat-3.0.0.tgz",
"integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
@@ -6459,6 +6463,14 @@
"node": ">= 8"
}
},
"node_modules/esm": {
"version": "3.2.25",
"resolved": "https://registry.npmmirror.com/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
"engines": {
"node": ">=6"
}
},
"node_modules/espree": {
"version": "7.3.1",
"resolved": "https://registry.npmmirror.com/espree/-/espree-7.3.1.tgz",
@@ -7287,7 +7299,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz",
"integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
"dev": true,
"dependencies": {
"domelementtype": "^2.0.1",
"domhandler": "^4.0.0",
@@ -7804,6 +7815,72 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/juice": {
"version": "8.1.0",
"resolved": "https://registry.npmmirror.com/juice/-/juice-8.1.0.tgz",
"integrity": "sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA==",
"dependencies": {
"cheerio": "1.0.0-rc.10",
"commander": "^6.1.0",
"mensch": "^0.3.4",
"slick": "^1.12.2",
"web-resource-inliner": "^6.0.1"
},
"bin": {
"juice": "bin/juice"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/juice/node_modules/cheerio": {
"version": "1.0.0-rc.10",
"resolved": "https://registry.npmmirror.com/cheerio/-/cheerio-1.0.0-rc.10.tgz",
"integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==",
"dependencies": {
"cheerio-select": "^1.5.0",
"dom-serializer": "^1.3.2",
"domhandler": "^4.2.0",
"htmlparser2": "^6.1.0",
"parse5": "^6.0.1",
"parse5-htmlparser2-tree-adapter": "^6.0.1",
"tslib": "^2.2.0"
},
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/juice/node_modules/cheerio-select": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/cheerio-select/-/cheerio-select-1.6.0.tgz",
"integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==",
"dependencies": {
"css-select": "^4.3.0",
"css-what": "^6.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.3.1",
"domutils": "^2.8.0"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/juice/node_modules/commander": {
"version": "6.2.1",
"resolved": "https://registry.npmmirror.com/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
"engines": {
"node": ">= 6"
}
},
"node_modules/juice/node_modules/parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
"node_modules/katex": {
"version": "0.16.10",
"resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.10.tgz",
@@ -8254,15 +8331,14 @@
"markdown-it": "bin/markdown-it.js"
}
},
"node_modules/markdown-it-latex2img": {
"version": "0.0.6",
"resolved": "https://registry.npmmirror.com/markdown-it-latex2img/-/markdown-it-latex2img-0.0.6.tgz",
"integrity": "sha512-plzX+YmYv28UnM172ZDKexyquec/GMit2K84g4x60u0YRFybhDQl8W0aTCHD8gJbhYVJRTWIDaL9YB0cvTijYQ=="
},
"node_modules/markdown-it-mathjax": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/markdown-it-mathjax/-/markdown-it-mathjax-2.0.0.tgz",
"integrity": "sha512-Fafv7TnMENccWYTNjMZzV4BzONPxpK9Mknr1iMEK6m7PI5a5UTCOFctPzx7Nhv81fFzYEY8WHDkSu9n43fTV9g=="
"node_modules/markdown-it-mathjax3": {
"version": "4.3.2",
"resolved": "https://registry.npmmirror.com/markdown-it-mathjax3/-/markdown-it-mathjax3-4.3.2.tgz",
"integrity": "sha512-TX3GW5NjmupgFtMJGRauioMbbkGsOXAAt1DZ/rzzYmTHqzkO1rNAdiMD4NiruurToPApn2kYy76x02QN26qr2w==",
"dependencies": {
"juice": "^8.0.0",
"mathjax-full": "^3.2.0"
}
},
"node_modules/markdown-it/node_modules/argparse": {
"version": "2.0.1",
@@ -8364,6 +8440,17 @@
"markmap-common": "*"
}
},
"node_modules/mathjax-full": {
"version": "3.2.2",
"resolved": "https://registry.npmmirror.com/mathjax-full/-/mathjax-full-3.2.2.tgz",
"integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==",
"dependencies": {
"esm": "^3.2.25",
"mhchemparser": "^4.1.0",
"mj-context-menu": "^0.6.1",
"speech-rule-engine": "^4.0.6"
}
},
"node_modules/md-editor-v3": {
"version": "2.11.3",
"resolved": "https://registry.npmjs.org/md-editor-v3/-/md-editor-v3-2.11.3.tgz",
@@ -8409,6 +8496,11 @@
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
"node_modules/mensch": {
"version": "0.3.4",
"resolved": "https://registry.npmmirror.com/mensch/-/mensch-0.3.4.tgz",
"integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -8457,6 +8549,11 @@
"node": ">= 0.6"
}
},
"node_modules/mhchemparser": {
"version": "4.2.1",
"resolved": "https://registry.npmmirror.com/mhchemparser/-/mhchemparser-4.2.1.tgz",
"integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ=="
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz",
@@ -8611,6 +8708,11 @@
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
},
"node_modules/mj-context-menu": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz",
@@ -8720,7 +8822,6 @@
"version": "2.6.7",
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"dev": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
@@ -9141,7 +9242,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
"integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
"dev": true,
"dependencies": {
"parse5": "^6.0.1"
}
@@ -9149,8 +9249,7 @@
"node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
"dev": true
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
},
"node_modules/parseurl": {
"version": "1.3.3",
@@ -10780,6 +10879,14 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/slick": {
"version": "1.12.2",
"resolved": "https://registry.npmmirror.com/slick/-/slick-1.12.2.tgz",
"integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==",
"engines": {
"node": "*"
}
},
"node_modules/sockjs": {
"version": "0.3.24",
"resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz",
@@ -10896,6 +11003,27 @@
"wbuf": "^1.7.3"
}
},
"node_modules/speech-rule-engine": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz",
"integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==",
"dependencies": {
"commander": "9.2.0",
"wicked-good-xpath": "1.3.0",
"xmldom-sre": "0.1.31"
},
"bin": {
"sre": "bin/sre"
}
},
"node_modules/speech-rule-engine/node_modules/commander": {
"version": "9.2.0",
"resolved": "https://registry.npmmirror.com/commander/-/commander-9.2.0.tgz",
"integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==",
"engines": {
"node": "^12.20.0 || >=14"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -11371,8 +11499,7 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"dev": true
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tslib": {
"version": "2.4.0",
@@ -11542,6 +11669,14 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
"node_modules/valid-data-url": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/valid-data-url/-/valid-data-url-3.0.1.tgz",
"integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==",
"engines": {
"node": ">=10"
}
},
"node_modules/validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -11829,11 +11964,65 @@
"defaults": "^1.0.3"
}
},
"node_modules/web-resource-inliner": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz",
"integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==",
"dependencies": {
"ansi-colors": "^4.1.1",
"escape-goat": "^3.0.0",
"htmlparser2": "^5.0.0",
"mime": "^2.4.6",
"node-fetch": "^2.6.0",
"valid-data-url": "^3.0.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/web-resource-inliner/node_modules/domhandler": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-3.3.0.tgz",
"integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
"dependencies": {
"domelementtype": "^2.0.1"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/web-resource-inliner/node_modules/htmlparser2": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-5.0.1.tgz",
"integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==",
"dependencies": {
"domelementtype": "^2.0.1",
"domhandler": "^3.3.0",
"domutils": "^2.4.2",
"entities": "^2.0.0"
},
"funding": {
"url": "https://github.com/fb55/htmlparser2?sponsor=1"
}
},
"node_modules/web-resource-inliner/node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"dev": true
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/webpack": {
"version": "5.90.3",
@@ -12243,7 +12432,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dev": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -12266,6 +12454,11 @@
"resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
},
"node_modules/wicked-good-xpath": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
"integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw=="
},
"node_modules/wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-2.0.0.tgz",
@@ -12352,6 +12545,14 @@
}
}
},
"node_modules/xmldom-sre": {
"version": "0.1.31",
"resolved": "https://registry.npmmirror.com/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
"integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==",
"engines": {
"node": ">=0.1"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
@@ -13672,9 +13873,9 @@
"integrity": "sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw=="
},
"@element-plus/icons-vue": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.1.0.tgz",
"integrity": "sha512-PSBn3elNoanENc1vnCfh+3WA9fimRC7n+fWkf3rE5jvv+aBohNHABC/KAR5KWPecxWxDTVT1ERpRbOMRcOV/vA==",
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
"requires": {}
},
"@eslint/eslintrc": {
@@ -15283,8 +15484,7 @@
"ansi-colors": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.1.tgz",
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"dev": true
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA=="
},
"ansi-escapes": {
"version": "3.2.0",
@@ -16429,7 +16629,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz",
"integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
"dev": true,
"requires": {
"boolbase": "^1.0.0",
"css-what": "^6.0.1",
@@ -17068,7 +17267,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz",
"integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
"dev": true,
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.2.0",
@@ -17084,7 +17282,6 @@
"version": "4.3.1",
"resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz",
"integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
"dev": true,
"requires": {
"domelementtype": "^2.2.0"
}
@@ -17093,7 +17290,6 @@
"version": "2.8.0",
"resolved": "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz",
"integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
"dev": true,
"requires": {
"dom-serializer": "^1.0.1",
"domelementtype": "^2.2.0",
@@ -17163,12 +17359,12 @@
"dev": true
},
"element-plus": {
"version": "2.3.9",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.3.9.tgz",
"integrity": "sha512-TIOLnPl4cnoCPXqK3QYh+jpkthUBQnAM21O7o3Lhbse8v9pfrRXRTaBJtoEKnYNa8GZ4lZptUfH0PeZgDCNLUg==",
"version": "2.7.6",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.7.6.tgz",
"integrity": "sha512-36sw1K23hYjgeooR10U6CiCaCp2wvOqwoFurADZVlekeQ9v5U1FhJCFGEXO6i/kZBBMwsE1c9fxjLs9LENw2Rg==",
"requires": {
"@ctrl/tinycolor": "^3.4.1",
"@element-plus/icons-vue": "^2.0.6",
"@element-plus/icons-vue": "^2.3.1",
"@floating-ui/dom": "^1.0.1",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
"@types/lodash": "^4.14.182",
@@ -17244,8 +17440,7 @@
"entities": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
"dev": true
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
},
"error-ex": {
"version": "1.3.2",
@@ -17277,6 +17472,11 @@
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
"dev": true
},
"escape-goat": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/escape-goat/-/escape-goat-3.0.0.tgz",
"integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw=="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
@@ -17575,6 +17775,11 @@
}
}
},
"esm": {
"version": "3.2.25",
"resolved": "https://registry.npmmirror.com/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
},
"espree": {
"version": "7.3.1",
"resolved": "https://registry.npmmirror.com/espree/-/espree-7.3.1.tgz",
@@ -18245,7 +18450,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz",
"integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
"dev": true,
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^4.0.0",
@@ -18641,6 +18845,56 @@
"universalify": "^2.0.0"
}
},
"juice": {
"version": "8.1.0",
"resolved": "https://registry.npmmirror.com/juice/-/juice-8.1.0.tgz",
"integrity": "sha512-FLzurJrx5Iv1e7CfBSZH68dC04EEvXvvVvPYB7Vx1WAuhCp1ZPIMtqxc+WTWxVkpTIC2Ach/GAv0rQbtGf6YMA==",
"requires": {
"cheerio": "1.0.0-rc.10",
"commander": "^6.1.0",
"mensch": "^0.3.4",
"slick": "^1.12.2",
"web-resource-inliner": "^6.0.1"
},
"dependencies": {
"cheerio": {
"version": "1.0.0-rc.10",
"resolved": "https://registry.npmmirror.com/cheerio/-/cheerio-1.0.0-rc.10.tgz",
"integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==",
"requires": {
"cheerio-select": "^1.5.0",
"dom-serializer": "^1.3.2",
"domhandler": "^4.2.0",
"htmlparser2": "^6.1.0",
"parse5": "^6.0.1",
"parse5-htmlparser2-tree-adapter": "^6.0.1",
"tslib": "^2.2.0"
}
},
"cheerio-select": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/cheerio-select/-/cheerio-select-1.6.0.tgz",
"integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==",
"requires": {
"css-select": "^4.3.0",
"css-what": "^6.0.1",
"domelementtype": "^2.2.0",
"domhandler": "^4.3.1",
"domutils": "^2.8.0"
}
},
"commander": {
"version": "6.2.1",
"resolved": "https://registry.npmmirror.com/commander/-/commander-6.2.1.tgz",
"integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="
},
"parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
}
}
},
"katex": {
"version": "0.16.10",
"resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.10.tgz",
@@ -19018,15 +19272,14 @@
}
}
},
"markdown-it-latex2img": {
"version": "0.0.6",
"resolved": "https://registry.npmmirror.com/markdown-it-latex2img/-/markdown-it-latex2img-0.0.6.tgz",
"integrity": "sha512-plzX+YmYv28UnM172ZDKexyquec/GMit2K84g4x60u0YRFybhDQl8W0aTCHD8gJbhYVJRTWIDaL9YB0cvTijYQ=="
},
"markdown-it-mathjax": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/markdown-it-mathjax/-/markdown-it-mathjax-2.0.0.tgz",
"integrity": "sha512-Fafv7TnMENccWYTNjMZzV4BzONPxpK9Mknr1iMEK6m7PI5a5UTCOFctPzx7Nhv81fFzYEY8WHDkSu9n43fTV9g=="
"markdown-it-mathjax3": {
"version": "4.3.2",
"resolved": "https://registry.npmmirror.com/markdown-it-mathjax3/-/markdown-it-mathjax3-4.3.2.tgz",
"integrity": "sha512-TX3GW5NjmupgFtMJGRauioMbbkGsOXAAt1DZ/rzzYmTHqzkO1rNAdiMD4NiruurToPApn2kYy76x02QN26qr2w==",
"requires": {
"juice": "^8.0.0",
"mathjax-full": "^3.2.0"
}
},
"markmap-common": {
"version": "0.16.0",
@@ -19099,6 +19352,17 @@
"d3-flextree": "^2.1.2"
}
},
"mathjax-full": {
"version": "3.2.2",
"resolved": "https://registry.npmmirror.com/mathjax-full/-/mathjax-full-3.2.2.tgz",
"integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==",
"requires": {
"esm": "^3.2.25",
"mhchemparser": "^4.1.0",
"mj-context-menu": "^0.6.1",
"speech-rule-engine": "^4.0.6"
}
},
"md-editor-v3": {
"version": "2.11.3",
"resolved": "https://registry.npmjs.org/md-editor-v3/-/md-editor-v3-2.11.3.tgz",
@@ -19136,6 +19400,11 @@
"resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
},
"mensch": {
"version": "0.3.4",
"resolved": "https://registry.npmmirror.com/mensch/-/mensch-0.3.4.tgz",
"integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
@@ -19177,6 +19446,11 @@
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"dev": true
},
"mhchemparser": {
"version": "4.2.1",
"resolved": "https://registry.npmmirror.com/mhchemparser/-/mhchemparser-4.2.1.tgz",
"integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ=="
},
"micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz",
@@ -19297,6 +19571,11 @@
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
},
"mj-context-menu": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
},
"mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz",
@@ -19388,7 +19667,6 @@
"version": "2.6.7",
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"dev": true,
"requires": {
"whatwg-url": "^5.0.0"
}
@@ -19715,7 +19993,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz",
"integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==",
"dev": true,
"requires": {
"parse5": "^6.0.1"
},
@@ -19723,8 +20000,7 @@
"parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
"dev": true
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
}
}
},
@@ -20941,6 +21217,11 @@
}
}
},
"slick": {
"version": "1.12.2",
"resolved": "https://registry.npmmirror.com/slick/-/slick-1.12.2.tgz",
"integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A=="
},
"sockjs": {
"version": "0.3.24",
"resolved": "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz",
@@ -21049,6 +21330,23 @@
"wbuf": "^1.7.3"
}
},
"speech-rule-engine": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz",
"integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==",
"requires": {
"commander": "9.2.0",
"wicked-good-xpath": "1.3.0",
"xmldom-sre": "0.1.31"
},
"dependencies": {
"commander": {
"version": "9.2.0",
"resolved": "https://registry.npmmirror.com/commander/-/commander-9.2.0.tgz",
"integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w=="
}
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -21418,8 +21716,7 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"dev": true
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"tslib": {
"version": "2.4.0",
@@ -21550,6 +21847,11 @@
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true
},
"valid-data-url": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/valid-data-url/-/valid-data-url-3.0.1.tgz",
"integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA=="
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -21786,11 +22088,49 @@
"defaults": "^1.0.3"
}
},
"web-resource-inliner": {
"version": "6.0.1",
"resolved": "https://registry.npmmirror.com/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz",
"integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==",
"requires": {
"ansi-colors": "^4.1.1",
"escape-goat": "^3.0.0",
"htmlparser2": "^5.0.0",
"mime": "^2.4.6",
"node-fetch": "^2.6.0",
"valid-data-url": "^3.0.0"
},
"dependencies": {
"domhandler": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-3.3.0.tgz",
"integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
"requires": {
"domelementtype": "^2.0.1"
}
},
"htmlparser2": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-5.0.1.tgz",
"integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==",
"requires": {
"domelementtype": "^2.0.1",
"domhandler": "^3.3.0",
"domutils": "^2.4.2",
"entities": "^2.0.0"
}
},
"mime": {
"version": "2.6.0",
"resolved": "https://registry.npmmirror.com/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="
}
}
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"dev": true
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"webpack": {
"version": "5.90.3",
@@ -22109,7 +22449,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dev": true,
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -22129,6 +22468,11 @@
"resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
},
"wicked-good-xpath": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
"integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw=="
},
"wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-2.0.0.tgz",
@@ -22191,6 +22535,11 @@
"dev": true,
"requires": {}
},
"xmldom-sre": {
"version": "0.1.31",
"resolved": "https://registry.npmmirror.com/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
"integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw=="
},
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",

View File

@@ -1,5 +1,5 @@
{
"name": "chatgpt-plus-web",
"name": "geekai-web",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -20,8 +20,7 @@
"json-bigint": "^1.0.0",
"lodash": "^4.17.21",
"markdown-it": "^13.0.1",
"markdown-it-latex2img": "^0.0.6",
"markdown-it-mathjax": "^2.0.0",
"markdown-it-mathjax3": "^4.3.2",
"markmap-common": "^0.16.0",
"markmap-lib": "^0.16.1",
"markmap-toolbar": "^0.17.0",

View File

@@ -13,8 +13,9 @@
.item {
display flex
flex-flow row
border 1px solid #3c3c3c
border 1px solid rgb(80,80,80)
padding 10px
background rgba(60,60,60 0.5)
.image {
width 80px

View File

@@ -129,15 +129,44 @@ $borderColor = #4676d0;
--el-main-padding: 0;
margin: 0;
.chat-box {
.chat-container {
min-width: 0;
flex: 1;
background-color: var(--el-bg-color)
color var(--el-text-color-primary)
.chat-config {
height 30px
padding 10px 30px
display flex
justify-content center
justify-items center
border-bottom 1px solid #d9d9e3
.role-select-label {
color #ffffff
}
.el-select {
max-width 150px;
margin-right 10px;
}
.role-select {
max-width 130px;
}
.el-button {
.el-icon {
margin-right 5px;
}
}
}
#container {
overflow: hidden;
width: 100%;
position relative
::-webkit-scrollbar {
width: 0;
@@ -165,68 +194,97 @@ $borderColor = #4676d0;
}
.input-box {
background-color: #ffffff
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
padding 0 15px;
position absolute
bottom 0
width 100%
.tool-item {
margin-right 15px
border-radius: 6px;
color: #19c37d;
.input-box-inner {
display flex
justify-content center
justify-items center
padding 6px
cursor pointer
background #F2F2F2
background-color: #ffffff
justify-content: center;
align-items: center;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
padding 0 15px;
&:hover {
background #D5FAD3
}
.tool-item {
margin-right 15px
border-radius: 6px;
color: #19c37d;
display flex
justify-content center
justify-items center
padding 6px
cursor pointer
background #F2F2F2
.iconfont {
font-size: 24px;
}
}
&:hover {
background #D5FAD3
}
.input-container {
width 100%
margin: 0;
border: none;
padding: 10px 0;
display flex
justify-content center
position relative
.el-textarea {
.el-textarea__inner::-webkit-scrollbar {
width: 0;
height: 0;
.iconfont {
font-size: 24px;
}
}
.select-file {
position absolute;
right 48px;
top 20px;
}
.input-body {
width 100%
margin: 0;
border: none;
padding: 10px 0;
display flex
justify-content center
position relative
.send-btn {
position absolute;
right 12px;
top 20px;
.hide-div {
white-space: pre-wrap; /* */
visibility: hidden; /* div */
position: absolute; /* */
line-height: 24px
font-size 14px
word-wrap: break-word; /* */
overflow-wrap: break-word; /* */
}
.input-border {
display flex
width 100%
overflow hidden
border: 2px solid #21AA93
border-radius 10px
padding 10px
.prompt-input::-webkit-scrollbar {
width: 0;
height: 0;
}
.prompt-input {
width 100%
line-height: 24px
border none
font-size 14px
background none
resize: none
white-space: pre-wrap; /* */
word-wrap: break-word; /* */
overflow-wrap: break-word; /* */
}
.send-btn {
width 32px
margin-left 10px
.el-button {
padding 8px 5px;
border-radius 6px;
font-size 20px;
}
}
.el-button {
padding 8px 5px;
border-radius 6px;
font-size 20px;
}
}
}
}
}

View File

@@ -21,8 +21,8 @@
cursor pointer
.el-image {
width 40px
height 40px
width 48px
height 48px
}
}
@@ -86,7 +86,7 @@
.navigator {
display flex
flex-flow column
width 50px
width 60px
padding 10px 1px
border-right: 1px solid #3c3c3c
background-color: #1E1F22
@@ -97,12 +97,14 @@
li {
margin-bottom 15px
display flex
flex-flow column
a {
color #DADBDC
border-radius 10px
width 40px
height 40px
width 48px
height 48px
display flex
justify-content center
align-items center
@@ -125,9 +127,12 @@
.title {
font-size: 12px
padding-top: 5px
padding-top: 6px
color: #e5e7eb;
text-align: center;
white-space: nowrap; /* */
overflow: hidden; /* */
text-overflow: unset; /* 使 */
}
.active {

View File

@@ -50,6 +50,7 @@
display flex
cursor pointer
margin-bottom: 10px;
border 1px solid #383838
&:hover {
background-color #585858
@@ -61,60 +62,9 @@
margin-bottom 5px
}
.shape {
width 16px
height 16px
margin-bottom 5px
border 1px solid #C4C4C4
border-radius 3px
}
.shape.size9-16 {
width 9px
height 16px
}
.shape.size16-9 {
height 9px
width 16px
position relative
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
.text {
margin-left 5px
margin-top 2px
}
}
@@ -122,10 +72,7 @@
.grid-content.active {
color #47fff1
background-color #585858
.shape {
border 1px solid #47fff1
}
border 1px solid #47fff1
}
.model {

View File

@@ -221,7 +221,7 @@
</template>
<script setup>
import {ref, watch} from "vue"
import {nextTick, onUnmounted, ref, watch} from "vue"
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {setUserToken} from "@/store/session";
@@ -339,7 +339,8 @@ const submitRegister = () => {
}
const close = function () {
emits('hide', false);
emits('hide', false)
login.value = true
}
</script>

View File

@@ -92,9 +92,9 @@ const items = [
},
{
icon: 'role',
index: '/admin/role',
title: '角色管理',
icon: 'menu',
index: '/admin/app',
title: '应用管理',
},
{
icon: 'api-key',

View File

@@ -6,19 +6,20 @@
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import {createRouter, createWebHistory} from "vue-router";
import {ref} from "vue";
import {httpGet} from "@/utils/http";
const routes = [
{
name: 'Index',
path: '/',
meta: {title: process.env.VUE_APP_TITLE},
meta: {title: "首页"},
component: () => import('@/views/Index.vue'),
},
{
name: 'home',
path: '/home',
redirect: '/chat',
meta: {title: '首页'},
component: () => import('@/views/Home.vue'),
children: [
{
@@ -46,7 +47,7 @@ const routes = [
component: () => import('@/views/Member.vue'),
},
{
name: 'chat-role',
name: 'chat-app',
path: '/apps',
meta: {title: '应用中心'},
component: () => import('@/views/ChatApps.vue'),
@@ -139,10 +140,10 @@ const routes = [
component: () => import('@/views/admin/Users.vue'),
},
{
path: '/admin/role',
name: 'admin-role',
meta: {title: '角色管理'},
component: () => import('@/views/admin/Roles.vue'),
path: '/admin/app',
name: 'admin-app',
meta: {title: '应用管理'},
component: () => import('@/views/admin/Apps.vue'),
},
{
path: '/admin/apikey',
@@ -273,11 +274,22 @@ const router = createRouter({
routes: routes,
})
const active = ref(false)
const title = ref('')
httpGet("/api/config/license").then(res => {
active.value = res.data.de_copy
}).catch(() => {})
httpGet("/api/config/get?key=system").then(res => {
title.value = res.data.title
}).catch(()=>{})
let prevRoute = null
// dynamic change the title when router change
router.beforeEach((to, from, next) => {
if (to.meta.title) {
if (!active.value) {
document.title = `${to.meta.title} | ${process.env.VUE_APP_TITLE}`
} else {
document.title = `${to.meta.title} | ${title.value}`
}
prevRoute = from
next()

View File

@@ -222,15 +222,29 @@ export function processContent(content) {
return texts.join("\n")
}
export function escapeHTML(html) {
return html.replace(/&/g, "&amp;")
export function processPrompt(prompt) {
prompt = prompt.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const linkRegex = /(https?:\/\/\S+)/g;
const links = prompt.match(linkRegex);
if (links) {
for (let link of links) {
if (isImage(link)) {
const index = prompt.indexOf(link)
if (prompt.substring(index - 1, 2) !== "]") {
prompt = prompt.replace(link, "\n![](" + link + ")\n")
}
}
}
}
return prompt
}
// 判断是否为 iphone 设备
export function isIphone() {
return /iPhone/i.test(navigator.userAgent) && !/iPad/i.test(navigator.userAgent);
// 判断是否为微信浏览器
export function isWeChatBrowser() {
return /MicroMessenger/i.test( navigator.userAgent);
}
export function showLoginDialog(router) {

View File

@@ -65,9 +65,44 @@
</div>
</el-aside>
<el-main v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.3)">
<div class="chat-box" :style="{height: mainWinHeight+'px'}">
<div class="chat-container">
<div class="chat-config">
<el-select v-model="roleId" filterable placeholder="角色" @change="_newChat"
class="role-select"
style="width:150px">
<el-option
v-for="item in roles"
:key="item.id"
:label="item.name"
:value="item.id"
>
<div class="role-option">
<el-image :src="item.icon"></el-image>
<span>{{ item.name }}</span>
</div>
</el-option>
</el-select>
<el-select v-model="modelID" filterable placeholder="模型" @change="_newChat"
:disabled="disableModel"
style="width:150px">
<el-option
v-for="item in models"
:key="item.id"
:label="item.name"
:value="item.id"
>
<span>{{ item.name }}</span>
<el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{
item.power
}}算力
</el-tag>
</el-option>
</el-select>
</div>
<div>
<div id="container">
<div id="container" :style="{height: mainWinHeight+'px'}">
<div class="chat-box" id="chat-box" :style="{height: chatBoxHeight+'px'}">
<div v-if="showHello">
<welcome @send="autofillPrompt"/>
@@ -84,61 +119,8 @@
</div>
</div><!-- end chat box -->
<el-affix position="bottom" :offset="0">
<div class="input-box">
<span class="tool-item">
<el-popover
:width="300"
trigger="click"
placement="top-start"
>
<template #reference>
<div>
<el-tooltip effect="dark" content="模型选择">
<i class="iconfont icon-model"></i>
</el-tooltip>
</div>
</template>
<template #default>
<div class="chat-config">
<el-select v-model="roleId" filterable placeholder="角色" @change="_newChat"
class="role-select"
style="width:150px">
<el-option
v-for="item in roles"
:key="item.id"
:label="item.name"
:value="item.id"
>
<div class="role-option">
<el-image :src="item.icon"></el-image>
<span>{{ item.name }}</span>
</div>
</el-option>
</el-select>
<el-select v-model="modelID" filterable placeholder="模型" @change="_newChat"
:disabled="disableModel"
style="width:150px">
<el-option
v-for="item in models"
:key="item.id"
:label="item.name"
:value="item.id"
>
<span>{{ item.name }}</span>
<el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{
item.power
}}算力
</el-tag>
</el-option>
</el-select>
</div>
</template>
</el-popover>
</span>
<div class="input-box">
<div class="input-box-inner">
<span class="tool-item" @click="ElMessage.info('暂时不支持语音输入')">
<el-tooltip class="box-item" effect="dark" content="语音输入">
<i class="iconfont icon-mic-bold"></i>
@@ -151,32 +133,34 @@
</el-tooltip>
</span>
<div class="input-container">
<el-input
ref="textInput"
v-model="prompt"
v-on:keydown="inputKeyDown"
autofocus
type="textarea"
:rows="2"
style="--el-input-focus-border-color:#21AA93;
border: 1px solid #21AA93;--el-input-border-color:#21AA93;
border-radius: 5px; --el-input-hover-border-color:#21AA93;"
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
/>
<span class="send-btn">
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
<el-icon>
<VideoPause/>
</el-icon>
</el-button>
<el-button @click="sendMessage" color="#19c37d" style="color:#ffffff" v-else>
<el-icon><Promotion/></el-icon>
</el-button>
</span>
<div class="input-body">
<div ref="textHeightRef" class="hide-div">{{prompt}}</div>
<div class="input-border">
<textarea
ref="inputRef"
class="prompt-input"
:rows="row"
v-model="prompt"
@keydown="onInput"
@input="onInput"
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
autofocus>
</textarea>
<span class="send-btn">
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
<el-icon>
<VideoPause/>
</el-icon>
</el-button>
<el-button @click="sendMessage" color="#19c37d" style="color:#ffffff" v-else>
<el-icon><Promotion/></el-icon>
</el-button>
</span>
</div>
</div>
</div><!-- end input box -->
</el-affix>
</div>
</div><!-- end container -->
</div><!-- end loading -->
</div>
@@ -207,7 +191,15 @@ import ChatPrompt from "@/components/ChatPrompt.vue";
import ChatReply from "@/components/ChatReply.vue";
import {Delete, Edit, More, Plus, Promotion, Search, Share, VideoPause} from '@element-plus/icons-vue'
import 'highlight.js/styles/a11y-dark.css'
import {dateFormat, escapeHTML, isMobile, processContent, randString, removeArrayItem, UUID} from "@/utils/libs";
import {
dateFormat,
isMobile,
processContent,
processPrompt,
randString,
removeArrayItem,
UUID
} from "@/utils/libs";
import {ElMessage, ElMessageBox} from "element-plus";
import hl from "highlight.js";
import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
@@ -237,11 +229,13 @@ const roleId = ref(0)
const newChatItem = ref(null);
const isLogin = ref(false)
const showHello = ref(true)
const textInput = ref(null)
const inputRef = ref(null)
const textHeightRef = ref(null)
const showNotice = ref(false)
const notice = ref("")
const noticeKey = ref("SYSTEM_NOTICE")
const store = useSharedStore();
const row = ref(1)
if (isMobile()) {
router.replace("/mobile/chat")
@@ -372,8 +366,8 @@ const getRoleById = function (rid) {
}
const resizeElement = function () {
chatBoxHeight.value = window.innerHeight - 50 - 82 - 38;
mainWinHeight.value = window.innerHeight - 50;
chatBoxHeight.value = window.innerHeight - 101 - 82 - 38;
mainWinHeight.value = window.innerHeight - 101;
leftBoxHeight.value = window.innerHeight - 90 - 45 - 82;
};
@@ -517,8 +511,7 @@ const removeChat = function (chat) {
}
const latexPlugin = require('markdown-it-latex2img')
const mathjaxPlugin = require('markdown-it-mathjax')
const mathjaxPlugin = require('markdown-it-mathjax3')
const md = require('markdown-it')({
breaks: true,
html: true,
@@ -543,7 +536,6 @@ const md = require('markdown-it')({
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
}
});
md.use(latexPlugin)
md.use(mathjaxPlugin)
// 创建 socket 连接
@@ -705,8 +697,21 @@ const enableInput = () => {
showStopGenerate.value = false;
}
// 登录输入框输入事件处理
const inputKeyDown = function (e) {
const onInput = (e) => {
// 根据输入的内容自动计算输入框的行数
const lineHeight = parseFloat(window.getComputedStyle(inputRef.value).lineHeight)
textHeightRef.value.style.width = inputRef.value.clientWidth + 'px'; // 设定宽度和 textarea 相同
const lines = Math.floor(textHeightRef.value.clientHeight / lineHeight);
inputRef.value.scrollTo(0, inputRef.value.scrollHeight)
if (prompt.value.length < 10) {
row.value = 1
} else if (lines <= 7){
row.value = lines
} else {
row.value = 7
}
// 输入回车自动提交
if (e.keyCode === 13) {
if (e.ctrlKey) { // Ctrl + Enter 换行
prompt.value += "\n";
@@ -720,7 +725,7 @@ const inputKeyDown = function (e) {
// 自动填充 prompt
const autofillPrompt = (text) => {
prompt.value = text
textInput.value.focus()
inputRef.value.focus()
// sendMessage()
}
// 发送消息
@@ -743,7 +748,7 @@ const sendMessage = function () {
type: "prompt",
id: randString(32),
icon: loginUser.value.avatar,
content: md.render(escapeHTML(processContent(prompt.value))),
content: md.render(processPrompt(prompt.value)),
created_at: new Date().getTime() / 1000,
});
@@ -929,29 +934,4 @@ const insertURL = (url) => {
}
}
}
.chat-config {
display flex
flex-direction row
padding-top 10px;
.role-select-label {
color #ffffff
}
.el-select {
max-width 150px;
margin-right 10px;
}
.role-select {
max-width 130px;
}
.el-button {
.el-icon {
margin-right 5px;
}
}
}
</style>

View File

@@ -167,11 +167,6 @@
<i class="iconfont icon-loading"></i>
<span>正在下载图片</span>
</div>
<div class="image-slot">
<el-icon>
<Picture/>
</el-icon>
</div>
</template>
</el-image>

View File

@@ -11,8 +11,8 @@
</div>
<div class="navbar">
<el-tooltip
v-if="!licenseConfig.de_copy"
class="box-item"
effect="light"
content="部署文档"
@@ -23,6 +23,7 @@
</el-tooltip>
<el-tooltip
v-if="!licenseConfig.de_copy"
class="box-item"
effect="light"
content="项目源码"
@@ -45,19 +46,21 @@
<span class="username">{{ loginUser.nickname }}</span>
</el-dropdown-item>
<el-dropdown-item>
<i class="iconfont icon-book"></i>
<a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
用户手册
</a>
</el-dropdown-item>
<div v-if="!licenseConfig.de_copy">
<el-dropdown-item>
<i class="iconfont icon-book"></i>
<a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
用户手册
</a>
</el-dropdown-item>
<el-dropdown-item>
<i class="iconfont icon-github"></i>
<a href="https://ai.r9it.com/docs/" target="_blank">
Geek-AI {{ version }}
</a>
</el-dropdown-item>
<el-dropdown-item>
<i class="iconfont icon-github"></i>
<a href="https://ai.r9it.com/docs/" target="_blank">
Geek-AI {{ version }}
</a>
</el-dropdown-item>
</div>
<el-divider style="margin: 2px 0"/>
<el-dropdown-item @click="logout">
<i class="iconfont icon-logout"></i>
@@ -85,6 +88,7 @@
<el-image :src="item.icon" style="width: 30px;height: 30px"/>
</a>
</el-tooltip>
<span :class="item.url === curPath ? 'title active' : 'title'">{{ item.name }}</span>
</li>
<el-popover
@@ -139,6 +143,7 @@ import {removeUserToken} from "@/store/session";
import LoginDialog from "@/components/LoginDialog.vue";
import {useSharedStore} from "@/store/sharedata";
import ConfigDialog from "@/components/ConfigDialog.vue";
import {showMessageError} from "@/utils/dialog";
const router = useRouter();
const logo = ref('/images/logo.png');
@@ -151,6 +156,7 @@ const loginUser = ref({})
const version = ref(process.env.VUE_APP_VERSION)
const routerViewKey = ref(0)
const showConfigDialog = ref(false)
const licenseConfig = ref({})
const store = useSharedStore();
const show = ref(false)
@@ -181,7 +187,7 @@ onMounted(() => {
httpGet("/api/menu/list").then(res => {
mainNavs.value = res.data
// 根据窗口的高度计算应该显示多少菜单
const rows = Math.floor((window.innerHeight - 90) / 60)
const rows = Math.floor((window.innerHeight - 100) / 90)
if (res.data.length > rows) {
mainNavs.value = res.data.slice(0, rows)
moreNavs.value = res.data.slice(rows)
@@ -190,6 +196,12 @@ onMounted(() => {
ElMessage.error("获取系统菜单失败:" + e.message)
})
httpGet("/api/config/license").then(res => {
licenseConfig.value = res.data
}).catch(e => {
showMessageError("获取 License 配置:" + e.message)
})
init()
})

View File

@@ -515,6 +515,13 @@
正在加载图片
</div>
</template>
<template #error>
<div class="image-slot">
<el-icon>
<Picture/>
</el-icon>
</div>
</template>
</el-image>
<el-image v-else>
<template #error>
@@ -522,11 +529,6 @@
<i class="iconfont icon-loading"></i>
<span>正在下载图片</span>
</div>
<div class="image-slot">
<el-icon>
<Picture/>
</el-icon>
</div>
</template>
</el-image>

View File

@@ -308,11 +308,7 @@
</template>
<template #error>
<div class="image-slot">
<el-icon>
<Picture/>
</el-icon>
</div>
<div class="image-slot"></div>
</template>
</el-image>

View File

@@ -1,6 +1,6 @@
<template>
<div class="index-page" :style="{height: winHeight+'px'}">
<div class="bg"></div>
<div :class="bgClass"></div>
<div class="menu-box">
<el-menu
mode="horizontal"
@@ -11,7 +11,8 @@
<div class="title">{{ title }}</div>
</div>
<div class="menu-item">
<a href="https://ai.r9it.com/docs/install/" target="_blank">
<span v-if="!licenseConfig.de_copy">
<a href="https://ai.r9it.com/docs/install/" target="_blank">
<el-button type="primary" round>
<i class="iconfont icon-book"></i>
<span>部署文档</span>
@@ -24,8 +25,12 @@
<span>项目源码</span>
</el-button>
</a>
<el-button @click="router.push('/login')" round>登录</el-button>
<el-button @click="router.push('/register')" round>注册</el-button>
</span>
<span v-if="!isLogin">
<el-button @click="router.push('/login')" round>登录</el-button>
<el-button @click="router.push('/register')" round>注册</el-button>
</span>
</div>
</el-menu>
</div>
@@ -52,7 +57,7 @@
<!-- <div id="animation-container"></div>-->
</div>
<div class="footer">
<div class="footer" v-if="!licenseConfig.de_copy">
<footer-bar />
</div>
</div>
@@ -67,6 +72,7 @@ import FooterBar from "@/components/FooterBar.vue";
import {httpGet} from "@/utils/http";
import {ElMessage} from "element-plus";
import {isMobile} from "@/utils/libs";
import {checkSession} from "@/action/session";
const router = useRouter()
@@ -77,74 +83,36 @@ if (isMobile()) {
const title = ref("Geek-AI 创作系统")
const logo = ref("/images/logo.png")
const slogan = ref("我辈之人,先干为敬,陪您先把 AI 用起来")
const licenseConfig = ref({})
// const size = Math.max(window.innerWidth * 0.5, window.innerHeight * 0.8)
const winHeight = window.innerHeight - 150
const bgClass = ref('fixed-bg')
const isLogin = ref(false)
onMounted(() => {
httpGet("/api/config/get?key=system").then(res => {
title.value = res.data.title
logo.value = res.data.logo
if (res.data.rand_bg) {
bgClass.value = "rand-bg"
}
if (res.data.slogan) {
slogan.value = res.data.slogan
}
}).catch(e => {
ElMessage.error("获取系统配置失败:" + e.message)
})
init()
})
const init = () => {
// // 创建场景
// // 创建场景
// const scene = new THREE.Scene();
//
// // 创建相机
// const camera = new THREE.PerspectiveCamera(30, 1, 0.1, 1000);
// camera.position.z = 3.88;
//
// // 创建渲染器
// const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
// renderer.setSize(size, size);
// renderer.setClearColor(0x000000, 0);
// const container = document.getElementById('animation-container');
// container.appendChild(renderer.domElement);
//
// // 加载地球纹理
// const loader = new THREE.TextureLoader();
// loader.load(
// '/images/land_ocean_ice_cloud_2048.jpg',
// function (texture) {
// // 创建地球球体
// const geometry = new THREE.SphereGeometry(1, 32, 32);
// const material = new THREE.MeshPhongMaterial({
// map: texture,
// bumpMap: texture, // 使用同一张纹理作为凹凸贴图
// bumpScale: 0.05, // 调整凹凸贴图的影响程度
// specularMap: texture, // 高光贴图
// specular: new THREE.Color('#01193B'), // 高光颜色
// });
// const earth = new THREE.Mesh(geometry, material);
// scene.add(earth);
//
// // 添加环境光和点光源
// const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
// scene.add(ambientLight);
// const pointLight = new THREE.PointLight(0xffffff, 0.8);
// pointLight.position.set(5, 5, 5);
// scene.add(pointLight);
//
// // 创建动画
// const animate = function () {
// requestAnimationFrame(animate);
//
// // 使地球自转和公转
// earth.rotation.y += 0.0006;
//
// renderer.render(scene, camera);
// };
//
// // 执行动画
// animate();
// }
// );
}
httpGet("/api/config/license").then(res => {
licenseConfig.value = res.data
}).catch(e => {
ElMessage.error("获取 License 配置:" + e.message)
})
checkSession().then(() => {
isLogin.value = true
}).catch(()=>{})
})
</script>
<style lang="stylus" scoped>
@@ -158,14 +126,25 @@ const init = () => {
align-items baseline
padding-top 150px
.bg {
.fixed-bg {
position absolute
top 0
left 0
width 100vw
height 100vh
background-image url("~@/assets/img/ai-bg.jpg")
//filter: blur(8px);
background-size: cover;
background-position: center;
}
.rand-bg {
position absolute
top 0
left 0
width 100vw
height 100vh
background-image url("https://api.dujin.org/bing/1920.php")
filter: blur(8px);
background-size: cover;
background-position: center;
}

View File

@@ -5,9 +5,8 @@
<h2>会员推广计划</h2>
<div class="share-box">
<div class="info">
我们非常欢迎您把此应用分享给您身边的朋友分享成功注册后您将获得 <strong>{{ inviteChatCalls }}</strong>
次对话额度以及
<strong>{{ inviteImgCalls }}</strong> 次AI绘画额度作为奖励
我们非常欢迎您把此应用分享给您身边的朋友分享成功注册后您和被邀请人都将获得 <strong>{{ invitePower }}</strong>
算力额度作为奖励
你可以保存下面的二维码或者直接复制分享您的专属推广链接发送给微信好友
</div>
@@ -99,8 +98,7 @@ import {useSharedStore} from "@/store/sharedata";
const inviteURL = ref("")
const qrImg = ref("/images/wx.png")
const inviteChatCalls = ref(0)
const inviteImgCalls = ref(0)
const invitePower = ref(0)
const hits = ref(0)
const regNum = ref(0)
const rate = ref(0)
@@ -144,8 +142,7 @@ const initData = () => {
})
httpGet("/api/config/get?key=system").then(res => {
inviteChatCalls.value = res.data["invite_chat_calls"]
inviteImgCalls.value = res.data["invite_img_calls"]
invitePower.value = res.data["invite_power"]
}).catch(e => {
ElMessage.error("获取系统配置失败:" + e.message)
})

View File

@@ -48,7 +48,7 @@
<reset-pass @hide="showResetPass = false" :show="showResetPass"/>
<footer class="footer">
<footer class="footer" v-if="!licenseConfig.de_copy">
<footer-bar/>
</footer>
</div>
@@ -74,6 +74,7 @@ const username = ref(process.env.VUE_APP_USER);
const password = ref(process.env.VUE_APP_PASS);
const showResetPass = ref(false)
const logo = ref("/images/logo.png")
const licenseConfig = ref({})
// 获取系统配置
httpGet("/api/config/get?key=system").then(res => {
@@ -83,6 +84,11 @@ httpGet("/api/config/get?key=system").then(res => {
showMessageError("获取系统配置失败:" + e.message)
})
httpGet("/api/config/license").then(res => {
licenseConfig.value = res.data
}).catch(e => {
showMessageError("获取 License 配置:" + e.message)
})
checkSession().then(() => {
if (isMobile()) {

View File

@@ -76,6 +76,9 @@
<el-button type="success" @click="PayJs(scope.item)" size="small" v-if="payWays['payjs']">
<span><i class="iconfont icon-wechat-pay"></i> 微信</span>
</el-button>
<el-button type="success" @click="wechatPay(scope.item)" size="small" v-if="payWays['wechat']">
<i class="iconfont icon-wechat-pay"></i> 微信
</el-button>
</div>
</div>
</div>
@@ -237,9 +240,11 @@ onMounted(() => {
// refresh payment qrcode
const refreshPayCode = () => {
if (curPay.value === 'alipay') {
alipay()
alipay(curPayProduct.value)
} else if (curPay.value === 'hupi') {
huPiPay()
huPiPay(curPayProduct.value)
} else if (curPay.value === 'payjs') {
PayJs(curPayProduct.value)
}
}
@@ -312,6 +317,21 @@ const PayJs = (row) => {
genPayQrcode()
}
const wechatPay = (row) => {
payName.value = '微信'
curPay.value = "wechat"
amount.value = (row.price - row.discount).toFixed(2)
if (!isLogin.value) {
store.setShowLoginDialog(true)
return
}
if (row) {
curPayProduct.value = row
}
genPayQrcode()
}
const queryOrder = (orderNo) => {
httpPost("/api/payment/query", {order_no: orderNo}).then(res => {
if (res.data.status === 1) {

View File

@@ -160,7 +160,7 @@
</el-result>
</div>
<footer class="footer">
<footer class="footer" v-if="!licenseConfig.de_copy">
<footer-bar/>
</footer>
</div>
@@ -199,6 +199,7 @@ const enableUser = ref(false)
const enableRegister = ref(false)
const activeName = ref("mobile")
const wxImg = ref("/images/wx.png")
const licenseConfig = ref({})
httpGet("/api/config/get?key=system").then(res => {
if (res.data) {
@@ -225,6 +226,12 @@ httpGet("/api/config/get?key=system").then(res => {
ElMessage.error("获取系统配置失败:" + e.message)
})
httpGet("/api/config/license").then(res => {
licenseConfig.value = res.data
}).catch(e => {
showMessageError("获取 License 配置:" + e.message)
})
// 注册操作
const submitRegister = () => {
if (data.value.username === '') {

View File

@@ -119,12 +119,12 @@ const mjModels = ref([
{name: "急速Turbo", value: "turbo"},
])
httpGet("/api/admin/config/get/draw").then(res => {
httpGet("/api/admin/config/get/app").then(res => {
sdConfigs.value = res.data.sd
mjPlusConfigs.value = res.data.mj_plus
mjProxyConfigs.value = res.data.mj_proxy
}).catch(e =>{
ElMessage.error("获取AI绘画配置失败:"+e.message)
ElMessage.error("获取配置失败:"+e.message)
})
const addConfig = (configs) => {

View File

@@ -2,6 +2,24 @@
<div class="container list" v-loading="loading">
<div class="handle-box">
<el-select v-model="query.platform" placeholder="平台" class="handle-input">
<el-option
v-for="item in platforms"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
<el-select v-model="query.type" placeholder="类型" class="handle-input">
<el-option
v-for="item in types"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
<el-button :icon="Search" @click="fetchData">搜索</el-button>
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
<a href="https://api.chat-plus.net" target="_blank" style="margin-left: 10px">
<el-button type="success" :icon="ShoppingCart" @click="add" plain>购买API-KEY</el-button>
@@ -87,7 +105,7 @@
<el-input v-model="item.name" autocomplete="off"/>
</el-form-item>
<el-form-item label="用途" prop="type">
<el-select v-model="item.type" placeholder="请选择用途" @change="changePlatform">
<el-select v-model="item.type" placeholder="请选择用途" @change="changeType">
<el-option v-for="item in types" :value="item.value" :label="item.name" :key="item.value">{{
item.name
}}
@@ -99,7 +117,8 @@
</el-form-item>
<el-form-item label="API URL" prop="api_url">
<el-input v-model="item.api_url" autocomplete="off"
placeholder="如果你用了第三方的 API 中转这里填写中转地址"/>
placeholder="必须填土完整的 Chat API URLhttps://api.openai.com/v1/chat/completions"/>
<div class="info">如果你使用了第三方中转这里就填写中转地址</div>
</el-form-item>
<el-form-item label="代理地址:" prop="proxy_url">
@@ -126,11 +145,12 @@ import {onMounted, onUnmounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {dateFormat, removeArrayItem, substr} from "@/utils/libs";
import {DocumentCopy, Plus, ShoppingCart} from "@element-plus/icons-vue";
import {DocumentCopy, Plus, ShoppingCart, Search} from "@element-plus/icons-vue";
import ClipboardJS from "clipboard";
// 变量定义
const items = ref([])
const query = ref({type: '',platform:''})
const item = ref({})
const showDialog = ref(false)
const rules = reactive({
@@ -142,39 +162,7 @@ const rules = reactive({
const loading = ref(true)
const formRef = ref(null)
const title = ref("")
const platforms = ref([
{
name: "OpenAI/中转ChatGPT",
value: "OpenAI",
api_url: "https://api.chat-plus.net/v1/chat/completions",
img_url: "https://api.chat-plus.net/v1/images/generations"
},
{
name: "【讯飞】星火大模型",
value: "XunFei",
api_url: "wss://spark-api.xf-yun.com/{version}/chat"
},
{
name: "【清华智普】ChatGLM",
value: "ChatGLM",
api_url: "https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke"
},
{
name: "【百度】文心一言",
value: "Baidu",
api_url: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}"
},
{
name: "【微软】Azure",
value: "Azure",
api_url: "https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15"
},
{
name: "【阿里】千义通问",
value: "QWen",
api_url: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"
},
])
const platforms = ref([])
const types = ref([
{name: "聊天", value: "chat"},
{name: "绘画", value: "img"},
@@ -191,6 +179,14 @@ onMounted(() => {
clipboard.value.on('error', () => {
ElMessage.error('复制失败!');
})
httpGet("/api/admin/config/get/app").then(res => {
platforms.value = res.data.platforms
}).catch(e =>{
ElMessage.error("获取配置失败:"+e.message)
})
fetchData()
})
onUnmounted(() => {
@@ -198,19 +194,22 @@ onUnmounted(() => {
})
// 获取数据
httpGet('/api/admin/apikey/list').then((res) => {
if (res.data) {
// 初始化数据
const arr = res.data;
for (let i = 0; i < arr.length; i++) {
arr[i].last_used_at = dateFormat(arr[i].last_used_at)
const fetchData = () => {
httpGet('/api/admin/apikey/list', query.value).then((res) => {
if (res.data) {
// 初始化数据
const arr = res.data;
for (let i = 0; i < arr.length; i++) {
arr[i].last_used_at = dateFormat(arr[i].last_used_at)
}
items.value = arr
}
items.value = arr
}
loading.value = false
}).catch(() => {
ElMessage.error("获取数据失败");
})
loading.value = false
}).catch(() => {
ElMessage.error("获取数据失败");
})
}
const add = function () {
showDialog.value = true
@@ -263,21 +262,24 @@ const set = (filed, row) => {
})
}
const changePlatform = () => {
let platform = null
const selectedPlatform = ref(null)
const changePlatform = (value) => {
console.log(value)
for (let v of platforms.value) {
if (v.value === item.value.platform) {
platform = v
break
if (v.value === value) {
selectedPlatform.value = v
item.value.api_url = v.chat_url
}
}
if (platform !== null) {
if (item.value.type === "img" && platform.img_url) {
item.value.api_url = platform.img_url
} else {
item.value.api_url = platform.api_url
}
}
const changeType = (value) => {
if (selectedPlatform.value) {
if(value === 'img') {
item.value.api_url = selectedPlatform.value.img_url
} else {
item.value.api_url = selectedPlatform.value.chat_url
}
}
}
</script>
@@ -286,6 +288,10 @@ const changePlatform = () => {
.list {
.handle-box {
margin-bottom 20px
.handle-input {
max-width 150px;
margin-right 10px;
}
}
.copy-key {
@@ -306,7 +312,9 @@ const changePlatform = () => {
.el-form {
.el-form-item__content {
.info {
color #999999
}
.el-icon {
padding-left: 10px;
}

View File

@@ -9,25 +9,25 @@
<template #default="props">
<div>
<el-table :data="props.row.context" :border="childBorder">
<el-table-column label="对话角色" prop="role" width="120"/>
<el-table-column label="对话应用" prop="role" width="120"/>
<el-table-column label="对话内容" prop="content"/>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column label="角色名称" prop="name">
<el-table-column label="应用名称" prop="name">
<template #default="scope">
<span class="sort" :data-id="scope.row.id">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column label="角色标识" prop="key"/>
<el-table-column label="应用标识" prop="key"/>
<el-table-column label="绑定模型" prop="model_name"/>
<el-table-column label="启用状态">
<template #default="scope">
<el-switch v-model="scope.row['enable']" @change="roleSet('enable',scope.row)"/>
</template>
</el-table-column>
<el-table-column label="角色图标" prop="icon">
<el-table-column label="应用图标" prop="icon">
<template #default="scope">
<el-image :src="scope.row.icon" style="width: 45px; height: 45px; border-radius: 50%"/>
</template>
@@ -36,7 +36,7 @@
<el-table-column label="操作" width="150" align="right">
<template #default="scope">
<el-button size="small" type="primary" @click="rowEdit(scope.$index, scope.row)">编辑</el-button>
<el-popconfirm title="确定要删除当前角色吗?" @confirm="removeRole(scope.row)" :width="200">
<el-popconfirm title="确定要删除当前应用吗?" @confirm="removeRole(scope.row)" :width="200">
<template #reference>
<el-button size="small" type="danger">删除</el-button>
</template>
@@ -53,21 +53,21 @@
width="50%"
>
<el-form :model="role" label-width="120px" ref="formRef" label-position="left" :rules="rules">
<el-form-item label="角色名称:" prop="name">
<el-form-item label="应用名称:" prop="name">
<el-input
v-model="role.name"
autocomplete="off"
/>
</el-form-item>
<el-form-item label="角色标志:" prop="key">
<el-form-item label="应用标志:" prop="key">
<el-input
v-model="role.key"
autocomplete="off"
/>
</el-form-item>
<el-form-item label="角色图标:" prop="icon">
<el-form-item label="应用图标:" prop="icon">
<el-input v-model="role.icon">
<template #append>
<el-upload
@@ -107,7 +107,7 @@
<el-form-item label="上下文信息:" prop="context">
<template #default>
<el-table :data="role.context" :border="childBorder" size="small">
<el-table-column label="对话角色" width="120">
<el-table-column label="对话应用" width="120">
<template #default="scope">
<el-input
v-model="scope.row.role"
@@ -133,6 +133,8 @@
<template #default="scope">
<div class="context-msg-content">
<el-input
type="textarea"
:rows="2"
v-model="scope.row.content"
autocomplete="off"
/>
@@ -181,8 +183,8 @@ const loading = ref(true)
const rules = reactive({
name: [{required: true, message: '请输入用户名', trigger: 'blur',}],
key: [{required: true, message: '请输入角色标识', trigger: 'blur',}],
icon: [{required: true, message: '请输入角色图标', trigger: 'blur',}],
key: [{required: true, message: '请输入应用标识', trigger: 'blur',}],
icon: [{required: true, message: '请输入应用图标', trigger: 'blur',}],
sort: [
{required: true, message: '请输入排序数字', trigger: 'blur'},
{type: 'number', message: '请输入有效数字'},
@@ -204,13 +206,13 @@ onMounted(() => {
})
const fetchData = () => {
//
//
httpGet('/api/admin/role/list').then((res) => {
tableData.value = res.data
sortedTableData.value = copyObj(tableData.value)
loading.value = false
}).catch(() => {
ElMessage.error("获取聊天角色失败");
ElMessage.error("获取聊天应用失败");
})
const drawBodyWrapper = document.querySelector('.el-table__body tbody')
@@ -250,14 +252,14 @@ const roleSet = (filed, row) => {
//
const curIndex = ref(0)
const rowEdit = function (index, row) {
optTitle.value = "修改角色"
optTitle.value = "修改应用"
curIndex.value = index
role.value = copyObj(row)
showDialog.value = true
}
const addRole = function () {
optTitle.value = "添加新角色"
optTitle.value = "添加新应用"
role.value = {context: []}
showDialog.value = true
}

View File

@@ -163,11 +163,8 @@
:model="item.model"
:content="item.content"/>
<chat-reply v-else-if="item.type==='reply'"
:icon="item.icon"
:org-content="item.orgContent"
:created-at="dateFormat(item['created_at'])"
:tokens="item['tokens']"
:content="item.content"/>
:read-only="true"
:data="item"/>
</div>
</div><!-- end chat box -->
</el-dialog>
@@ -291,8 +288,7 @@ const removeMessage = function (row) {
})
}
const latexPlugin = require('markdown-it-latex2img')
const mathjaxPlugin = require('markdown-it-mathjax')
const mathjaxPlugin = require('markdown-it-mathjax3')
const md = require('markdown-it')({
breaks: true,
html: true,
@@ -312,7 +308,6 @@ const md = require('markdown-it')({
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`
}
});
md.use(latexPlugin)
md.use(mathjaxPlugin)
const showContentDialog = ref(false)

View File

@@ -2,6 +2,16 @@
<div class="container model-list" v-loading="loading">
<div class="handle-box">
<el-select v-model="query.platform" placeholder="平台" class="handle-input">
<el-option
v-for="item in platforms"
:key="item.value"
:label="item.name"
:value="item.value"
/>
</el-select>
<el-button :icon="Search" @click="fetchData">搜索</el-button>
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
</div>
@@ -198,12 +208,13 @@ import {onMounted, onUnmounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {dateFormat, removeArrayItem, substr} from "@/utils/libs";
import {DocumentCopy, InfoFilled, Plus} from "@element-plus/icons-vue";
import {DocumentCopy, InfoFilled, Plus,Search} from "@element-plus/icons-vue";
import {Sortable} from "sortablejs";
import ClipboardJS from "clipboard";
// 变量定义
const items = ref([])
const query = ref({platform:''})
const item = ref({})
const showDialog = ref(false)
const title = ref("")
@@ -214,15 +225,7 @@ const rules = reactive({
})
const loading = ref(true)
const formRef = ref(null)
const platforms = ref([
{name: "OpenAIChatGPT", value: "OpenAI"},
{name: "讯飞星火大模型", value: "XunFei"},
{name: "清华智普ChatGLM", value: "ChatGLM"},
{name: "百度文心一言", value: "Baidu"},
{name: "微软Azure", value: "Azure"},
{name: "阿里通义千问", value: "QWen"},
])
const platforms = ref([])
// 获取 API KEY
const apiKeys = ref([])
@@ -234,7 +237,7 @@ httpGet('/api/admin/apikey/list?status=true&type=chat').then(res => {
// 获取数据
const fetchData = () => {
httpGet('/api/admin/model/list').then((res) => {
httpGet('/api/admin/model/list', query.value).then((res) => {
if (res.data) {
// 初始化数据
const arr = res.data;
@@ -287,6 +290,12 @@ onMounted(() => {
clipboard.value.on('error', () => {
ElMessage.error('复制失败!');
})
httpGet("/api/admin/config/get/app").then(res => {
platforms.value = res.data.platforms
}).catch(e =>{
ElMessage.error("获取配置失败"+e.message)
})
})
onUnmounted(() => {
@@ -352,6 +361,10 @@ const remove = function (row) {
.handle-box {
margin-bottom 20px
.handle-input {
max-width 150px;
margin-right 10px;
}
}
.cell {

View File

@@ -13,6 +13,9 @@
<el-form-item label="控制台标题" prop="admin_title">
<el-input v-model="system['admin_title']"/>
</el-form-item>
<el-form-item label="网站Slogan" prop="slogan">
<el-input v-model="system['slogan']"/>
</el-form-item>
<el-form-item label="网站 LOGO" prop="logo">
<el-input v-model="system['logo']" placeholder="网站LOGO图片">
<template #append>
@@ -44,6 +47,20 @@
</el-tooltip>
</el-form-item>
<el-form-item label="动态背景">
<el-switch v-model="system['rand_bg']"/>
<el-tooltip
effect="dark"
content="打开之后前端首页将使用随机壁纸作为背景图"
raw-content
placement="right"
>
<el-icon>
<InfoFilled/>
</el-icon>
</el-tooltip>
</el-form-item>
<el-form-item label="注册方式" prop="register_ways">
<el-checkbox-group v-model="system['register_ways']">
<el-checkbox value="mobile">手机注册</el-checkbox>
@@ -298,16 +315,22 @@
<el-descriptions
v-if="license.is_active"
class="margin-top"
title="授权信息"
:column="3"
title="授权信息"
:column="1"
border
>
<el-descriptions-item :span="3" :width="150">
<el-descriptions-item>
<template #label>
<div class="cell-item">License Key</div>
</template>
{{ license.key }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">机器码</div>
</template>
{{ license.machine_id }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">到期时间</div>
@@ -318,13 +341,15 @@
<template #label>
<div class="cell-item">用户人数</div>
</template>
{{ license.user_num }}
{{ license.configs?.user_num }}
</el-descriptions-item>
<el-descriptions-item>
<template #label>
<div class="cell-item">机器码</div>
<div class="cell-item">去版权</div>
</template>
{{ license.machine_id }}
<el-icon class="selected" v-if="license.configs?.de_copy"><Select /></el-icon>
<el-icon class="closed" v-else><CloseBold /></el-icon>
<span class="text">去版权之后前端页面将不会显示版权信息和源码地址</span>
</el-descriptions-item>
</el-descriptions>
@@ -348,7 +373,7 @@ import {onMounted, reactive, ref} from "vue";
import {httpGet, httpPost} from "@/utils/http";
import Compressor from "compressorjs";
import {ElMessage} from "element-plus";
import {InfoFilled, UploadFilled} from "@element-plus/icons-vue";
import {InfoFilled, UploadFilled,Select,CloseBold} from "@element-plus/icons-vue";
import MdEditor from "md-editor-v3";
import 'md-editor-v3/lib/style.css';
import Menu from "@/views/admin/Menu.vue";
@@ -536,6 +561,23 @@ const onUploadImg = (files, callback) => {
.el-descriptions {
margin-bottom 20px
.el-icon {
font-size 18px
}
.selected {
color #0bc15f
}
.closed {
color #da0d54
}
.text {
margin-left 10px
font-size 12px
color #999999
position: relative;
top -5px
}
}
.el-alert {

View File

@@ -354,6 +354,8 @@ const doResetPass = () => {
.pagination {
padding 20px 0
display flex
justify-content right
width 100%
}
.el-select {

View File

@@ -62,6 +62,7 @@ const model = ref('')
const finished = ref(false)
const error = ref(false)
const mathjaxPlugin = require('markdown-it-mathjax3')
const md = require('markdown-it')({
breaks: true,
html: true,
@@ -86,6 +87,7 @@ const md = require('markdown-it')({
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
}
});
md.use(mathjaxPlugin)
const onLoad = () => {
httpGet('/api/chat/history?chat_id=' + chatId).then(res => {
@@ -127,7 +129,7 @@ const onLoad = () => {
};
</script>
<style lang="stylus">
<style scoped lang="stylus">
.chat-export-mobile {
height 100vh
@@ -144,7 +146,7 @@ const onLoad = () => {
.message-list-box {
background #F5F5F5;
padding-bottom: 10px
padding-bottom: 50px
.van-cell {
background none

View File

@@ -225,18 +225,6 @@ const newChat = (item) => {
}
showPicker.value = false
const options = item.selectedOptions
// setChatConfig({
// role: {
// id: options[0].value,
// name: options[0].text,
// icon: options[0].icon,
// helloMsg: options[0].helloMsg
// },
// model: options[1].value,
// modelValue: getModelValue(options[1].value),
// title: '新建会话',
// chatId: 0
// })
router.push({
name: "mobile-chat-session",
params: {role_id: options[0].value, model_id: options[1].value, title: '新建会话', chat_id: 0}
@@ -244,25 +232,6 @@ const newChat = (item) => {
}
const changeChat = (chat) => {
// let role = {}
// for (let i = 0; i < roles.value.length; i++) {
// if (roles.value[i].value === chat.role_id) {
// role = roles.value[i]
// break
// }
// }
// setChatConfig({
// role: {
// id: chat.role_id,
// name: role.text,
// icon: role.icon
// },
// model: chat.model_id,
// modelValue: getModelValue(chat.model_id),
// title: chat.title,
// chatId: chat.chat_id,
// helloMsg: chat.hello_msg,
// })
router.push(`/mobile/chat/session?chat_id=${chat.chat_id}`)
}

View File

@@ -182,7 +182,7 @@ httpGet('/api/model/list').then(res => {
models.value[i].mValue = models.value[i].value
models.value[i].value = models.value[i].id
}
modelValue.value = getModelValue(modelId.value)
modelValue.value = getModelName(modelId.value)
// 加载角色列表
httpGet(`/api/role/list`).then((res) => {
roles.value = res.data;
@@ -236,6 +236,7 @@ const newChat = (item) => {
const options = item.selectedOptions
roleId.value = options[0].value
modelId.value = options[1].value
modelValue.value = getModelName(modelId.value)
chatId.value = ""
chatData.value = []
role.value = getRoleById(roleId.value)
@@ -248,8 +249,7 @@ const loading = ref(false)
const finished = ref(false)
const error = ref(false)
const latexPlugin = require('markdown-it-latex2img')
const mathjaxPlugin = require('markdown-it-mathjax')
const mathjaxPlugin = require('markdown-it-mathjax3')
const md = require('markdown-it')({
breaks: true,
html: true,
@@ -274,7 +274,6 @@ const md = require('markdown-it')({
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
}
});
md.use(latexPlugin)
md.use(mathjaxPlugin)
@@ -537,7 +536,7 @@ const shareChat = (option) => {
showToast({message: "当前会话已经导出,请通过浏览器或者微信的自带分享功能分享给好友", duration: 5000})
router.push({
path: "/mobile/chat/export",
query: {title: title, chat_id: chatId, role: role.value.name, model: modelValue}
query: {title: title.value, chat_id: chatId.value, role: role.value.name, model: modelValue.value}
})
} else if (option.icon === "link") {
document.getElementById('copy-link-btn').click();
@@ -553,10 +552,10 @@ const getRoleById = function (rid) {
return null;
}
const getModelValue = (model_id) => {
const getModelName = (model_id) => {
for (let i = 0; i < models.value.length; i++) {
if (models.value[i].id === model_id) {
return models.value[i].mValue
return models.value[i].text
}
}
return ""

View File

@@ -8,7 +8,7 @@
<image-sd/>
</van-tab>
<van-tab title="DALL" name="dall">
<van-empty description="功能正在开发中"/>
<image-dall />
</van-tab>
</van-tabs>
</div>
@@ -18,6 +18,7 @@
import {ref} from "vue";
import ImageMj from "@/views/mobile/pages/ImageMj.vue";
import ImageSd from "@/views/mobile/pages/ImageSd.vue";
import ImageDall from "@/views/mobile/pages/ImageDall.vue";
const activeName = ref("mj")
</script>
@@ -30,4 +31,4 @@ const activeName = ref("mj")
}
}
}
</style>
</style>

View File

@@ -1,9 +1,7 @@
<template>
<div class="index container">
<h2 class="title">{{title}}</h2>
<van-notice-bar left-icon="info-o" :scrollable="true">
你有多少想象力AI就有多大创造力我辈之人先干为敬陪您先把 AI 用起来
</van-notice-bar>
<van-notice-bar left-icon="info-o" :scrollable="true">{{slogan}}}</van-notice-bar>
<div class="content">
<van-grid :column-num="3" :gutter="10" border>
@@ -90,8 +88,18 @@ const isLogin = ref(false)
const apps = ref([])
const loading = ref(false)
const roles = ref([])
const slogan = ref('你有多大想象力AI就有多大创造力')
onMounted(() => {
httpGet("/api/config/get?key=system").then(res => {
title.value = res.data.title
if (res.data.slogan) {
slogan.value = res.data.slogan
}
}).catch(e => {
ElMessage.error("获取系统配置失败:" + e.message)
})
checkSession().then((user) => {
isLogin.value = true
roles.value = user.chat_roles

View File

@@ -69,6 +69,9 @@
<van-button type="success" @click="pay('payjs',item)" size="small" v-if="payWays['payjs']">
<span><i class="iconfont icon-wechat-pay"></i> 微信</span>
</van-button>
<van-button type="primary" @click="pay('wechat',item)" size="small" v-if="payWays['wechat']">
<i class="iconfont icon-wechat-pay"></i> 微信
</van-button>
</div>
</h4>
@@ -151,10 +154,10 @@
<script setup>
import {onMounted, ref} from "vue";
import {showFailToast, showNotify, showSuccessToast} from "vant";
import {showFailToast, showNotify, showSuccessToast, showToast} from "vant";
import {httpGet, httpPost} from "@/utils/http";
import Compressor from 'compressorjs';
import {dateFormat, showLoginDialog} from "@/utils/libs";
import {dateFormat, isWeChatBrowser, showLoginDialog} from "@/utils/libs";
import {ElMessage} from "element-plus";
import {checkSession} from "@/action/session";
import {useRouter} from "vue-router";
@@ -296,8 +299,11 @@ const pay = (payWay, item) => {
product_id: item.id,
user_id: userId.value
}).then(res => {
// console.log(res.data)
location.href = res.data
if (isWeChatBrowser() && payWay === 'wechat') {
showFailToast("请在系统自带浏览器打开支付页面,或者在 PC 端进行扫码支付")
} else {
location.href = res.data
}
}).catch(e => {
showFailToast("生成支付订单失败:" + e.message)
})

View File

@@ -0,0 +1,473 @@
<template>
<div class="mobile-sd">
<van-form @submit="generate">
<van-cell-group inset>
<div>
<van-field
v-model="quality"
is-link
label="图片质量"
placeholder="选择图片质量"
@click="showQualityPicker = true"
/>
<van-popup v-model:show="showQualityPicker" position="bottom" teleport="#app">
<van-picker
:columns="qualities"
@cancel="showQualityPicker = false"
@confirm="qualityConfirm"
/>
</van-popup>
</div>
<div>
<van-field
v-model="size"
is-link
label="图片尺寸"
placeholder="选择图片尺寸"
@click="showSizePicker = true"
/>
<van-popup v-model:show="showSizePicker" position="bottom" teleport="#app">
<van-picker
:columns="sizes"
@cancel="showSizePicker = false"
@confirm="sizeConfirm"
/>
</van-popup>
</div>
<div>
<van-field
v-model="style"
is-link
label="图片样式"
placeholder="选择图片样式"
@click="showStylePicker = true"
/>
<van-popup v-model:show="showStylePicker" position="bottom" teleport="#app">
<van-picker
:columns="styles"
@cancel="showStylePicker = false"
@confirm="styleConfirm"
/>
</van-popup>
</div>
<van-field
v-model="params.prompt"
rows="3"
autosize
type="textarea"
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"
/>
<div class="text-line pt-6">
<el-tag>绘图消耗{{ dallPower }}算力当前算力{{ power }}</el-tag>
</div>
<div class="text-line">
<van-button round block type="primary" native-type="submit">
立即生成
</van-button>
</div>
</van-cell-group>
</van-form>
<h3>任务列表</h3>
<div class="running-job-list">
<van-empty v-if="runningJobs.length ===0"
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
image-size="80"
description="暂无记录"
/>
<van-grid :gutter="10" :column-num="3" v-else>
<van-grid-item v-for="item in runningJobs">
<div v-if="item.progress > 0">
<van-image :src="item['img_url']">
<template v-slot:error>加载失败</template>
</van-image>
<div class="progress">
<van-circle
v-model:current-rate="item.progress"
:rate="item.progress"
:speed="100"
:text="item.progress+'%'"
:stroke-width="60"
size="90px"
/>
</div>
</div>
<div v-else class="task-in-queue">
<span class="icon"><i class="iconfont icon-quick-start"></i></span>
<span class="text">排队中</span>
</div>
</van-grid-item>
</van-grid>
</div>
<h3>创作记录</h3>
<div class="finish-job-list">
<van-empty v-if="finishedJobs.length ===0"
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
image-size="80"
description="暂无记录"
/>
<van-list v-else
v-model:error="error"
v-model:loading="loading"
:finished="finished"
error-text="请求失败点击重新加载"
finished-text="没有更多了"
@load="onLoad"
>
<van-grid :gutter="10" :column-num="2">
<van-grid-item v-for="item in finishedJobs">
<div class="job-item">
<van-image
:src="item['img_url']"
:class="item['can_opt'] ? '' : 'upscale'"
lazy-load
@click="imageView(item)"
fit="cover">
<template v-slot:loading>
<van-loading type="spinner" size="20"/>
</template>
</van-image>
<div class="remove">
<el-button type="danger" :icon="Delete" @click="removeImage($event, item)" circle/>
<el-button type="warning" v-if="item.publish" @click="publishImage($event,item, false)"
circle>
<i class="iconfont icon-cancel-share"></i>
</el-button>
<el-button type="success" v-else @click="publishImage($event, item, true)" circle>
<i class="iconfont icon-share-bold"></i>
</el-button>
<el-button type="primary" @click="showPrompt(item)" circle>
<i class="iconfont icon-prompt"></i>
</el-button>
</div>
</div>
</van-grid-item>
</van-grid>
</van-list>
</div>
<button style="display: none" class="copy-prompt-dall" :data-clipboard-text="prompt" id="copy-btn-dall">复制</button>
</div>
</template>
<script setup>
import {onMounted, onUnmounted, ref} from "vue"
import {Delete} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http";
import Clipboard from "clipboard";
import {checkSession} from "@/action/session";
import {useRouter} from "vue-router";
import {getSessionId} from "@/store/session";
import {
showConfirmDialog,
showDialog,
showFailToast,
showImagePreview,
showNotify,
showSuccessToast,
showToast
} from "vant";
import {showLoginDialog} from "@/utils/libs";
const listBoxHeight = ref(window.innerHeight - 40)
const mjBoxHeight = ref(window.innerHeight - 150)
const item = ref({})
const isLogin = ref(false)
const activeColspan = ref([""])
window.onresize = () => {
listBoxHeight.value = window.innerHeight - 40
mjBoxHeight.value = window.innerHeight - 150
}
const qualities = [
{text: "标准", value: "standard"},
{text: "高清", value: "hd"},
]
const sizes = [
{text:"1024x1024",value:"1024x1024"},
{text:"1792x1024",value:"1792x1024"},
{text: "1024x1792",value:"1024x1792"},
]
const styles = [
{text: "生动", value: "vivid"},
{text: "自然", value: "natural"}
]
const params = ref({
quality: qualities[0].value,
size: sizes[0].value,
style: styles[0].value,
prompt: ""
})
const quality = ref(qualities[0].text)
const size = ref(sizes[0].text)
const style = ref(styles[0].text)
const showQualityPicker = ref(false)
const showStylePicker = ref(false)
const showSizePicker = ref(false)
const runningJobs = ref([])
const finishedJobs = ref([])
const router = useRouter()
const power = ref(0)
const dallPower = ref(0) // 画一张 DALL 图片消耗算力
const socket = ref(null)
const userId = ref(0)
const heartbeatHandle = ref(null)
const connect = () => {
let host = process.env.VUE_APP_WS_HOST
if (host === '') {
if (location.protocol === 'https:') {
host = 'wss://' + location.host;
} else {
host = 'ws://' + location.host;
}
}
// 心跳函数
const sendHeartbeat = () => {
clearTimeout(heartbeatHandle.value)
new Promise((resolve, reject) => {
if (socket.value !== null) {
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
}
resolve("success")
}).then(() => {
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
});
}
const _socket = new WebSocket(host + `/api/dall/client?user_id=${userId.value}`);
_socket.addEventListener('open', () => {
socket.value = _socket;
// 发送心跳消息
sendHeartbeat()
});
_socket.addEventListener('message', event => {
if (event.data instanceof Blob) {
fetchRunningJobs()
finished.value = false
page.value = 1
fetchFinishJobs(page.value)
}
});
_socket.addEventListener('close', () => {
if (socket.value !== null) {
connect()
}
});
}
const clipboard = ref(null)
const prompt = ref('')
onMounted(() => {
initData()
clipboard.value = new Clipboard(".copy-prompt-dall");
clipboard.value.on('success', () => {
showNotify({type: 'success', message: '复制成功', duration: 1000})
})
clipboard.value.on('error', () => {
showNotify({type: 'danger', message: '复制失败', duration: 2000})
})
httpGet("/api/config/get?key=system").then(res => {
dallPower.value = res.data.dall_power
}).catch(e => {
showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
})
})
onUnmounted(() => {
clipboard.value.destroy()
if (socket.value !== null) {
socket.value.close()
socket.value = null
}
})
const initData = () => {
checkSession().then(user => {
power.value = user['power']
userId.value = user.id
isLogin.value = true
fetchRunningJobs()
fetchFinishJobs(1)
connect()
}).catch(() => {
loading.value = false
});
}
const fetchRunningJobs = () => {
// 获取运行中的任务
httpGet(`/api/dall/jobs?status=0`).then(res => {
const jobs = res.data
const _jobs = []
for (let i = 0; i < jobs.length; i++) {
if (jobs[i].progress === -1) {
showNotify({
message: `任务ID${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
type: 'danger',
})
power.value += dallPower.value
continue
}
_jobs.push(jobs[i])
}
runningJobs.value = _jobs
}).catch(e => {
showNotify({type: "danger", message: "获取任务失败:" + e.message})
})
}
const loading = ref(false)
const finished = ref(false)
const error = ref(false)
const page = ref(0)
const pageSize = ref(10)
// 获取已完成的任务
const fetchFinishJobs = (page) => {
loading.value = true
httpGet(`/api/dall/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
if (res.data.length < pageSize.value) {
finished.value = true
}
if (page === 1) {
finishedJobs.value = res.data
} else {
finishedJobs.value = finishedJobs.value.concat(res.data)
}
loading.value = false
}).catch(e => {
loading.value = false
showNotify({type: "danger", message: "获取任务失败:" + e.message})
})
}
const onLoad = () => {
page.value += 1
fetchFinishJobs(page.value)
}
// 创建绘图任务
const promptRef = ref(null)
const generate = () => {
if (!isLogin.value) {
return showLoginDialog(router)
}
if (params.value.prompt === '') {
promptRef.value.focus()
return showToast("请输入绘画提示词!")
}
if (!params.value.seed) {
params.value.seed = -1
}
params.value.session_id = getSessionId()
httpPost("/api/dall/image", params.value).then(() => {
showSuccessToast("绘画任务推送成功,请耐心等待任务执行...")
power.value -= dallPower.value
}).catch(e => {
showFailToast("任务推送失败:" + e.message)
})
}
const showPrompt = (item) => {
prompt.value = item.prompt
showConfirmDialog({
title: "绘画提示词",
message: item.prompt,
confirmButtonText: "复制",
cancelButtonText: "关闭",
}).then(() => {
document.querySelector('#copy-btn-dall').click()
}).catch(() => {
});
}
const removeImage = (event, item) => {
event.stopPropagation()
showConfirmDialog({
title: '标题',
message:
'此操作将会删除任务和图片,继续操作码?',
}).then(() => {
httpPost("/api/dall/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
showSuccessToast("任务删除成功")
}).catch(e => {
showFailToast("任务删除失败:" + e.message)
})
}).catch(() => {
showToast("您取消了操作")
});
}
// 发布图片到作品墙
const publishImage = (event, item, action) => {
event.stopPropagation()
let text = "图片发布"
if (action === false) {
text = "取消发布"
}
httpPost("/api/dall/publish", {id: item.id, action: action}).then(() => {
showSuccessToast(text + "成功")
item.publish = action
}).catch(e => {
showFailToast(text + "失败:" + e.message)
})
}
const imageView = (item) => {
showImagePreview([item['img_url']]);
}
const qualityConfirm = (item) => {
params.value.quality = item.selectedOptions[0].value;
quality.value = item.selectedOptions[0].text
showQualityPicker.value = false
}
const styleConfirm = (item) => {
params.value.style = item.selectedOptions[0].value;
style.value = item.selectedOptions[0].text
showStylePicker.value = false
}
const sizeConfirm =(item) => {
params.value.size = item.selectedOptions[0].value
size.value=item.selectedOptions[0].text
showSizePicker.value =false
}
const showInfo = (message) => {
showDialog({
title: "参数说明",
message: message,
}).then(() => {
// on close
});
}
</script>
<style lang="stylus">
@import "@/assets/css/mobile/image-sd.styl"
</style>

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