Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fe4212684 | ||
|
|
8bdda64794 | ||
|
|
ec08c24dca | ||
|
|
a992a5b3b3 | ||
|
|
0f05970141 | ||
|
|
e5e762efcd | ||
|
|
b3d0c1ef9c | ||
|
|
397078f7ff | ||
|
|
3ad8065e20 | ||
|
|
66c7717f04 | ||
|
|
412f8ecc6c | ||
|
|
51dcf642b3 | ||
|
|
bfeea555b2 | ||
|
|
479f94c372 | ||
|
|
0140713e86 | ||
|
|
15b2ec9721 | ||
|
|
c9cd082855 | ||
|
|
d7c002890c | ||
|
|
348dd22279 | ||
|
|
3e99b4cbf6 | ||
|
|
6968da3ac7 | ||
|
|
bf1c1b84c3 | ||
|
|
c70314d930 | ||
|
|
9104ca8e49 | ||
|
|
2af33b3630 | ||
|
|
654e795545 | ||
|
|
c62ba2451e | ||
|
|
d72d1b8a99 | ||
|
|
b939d6016b | ||
|
|
36a2626ccc | ||
|
|
bd057a4cc9 | ||
|
|
dc24a8c781 | ||
|
|
59fa21779b | ||
|
|
a140671aad | ||
|
|
5fe8990fb4 | ||
|
|
12799b7159 | ||
|
|
9929746b1d | ||
|
|
d70035ff0c | ||
|
|
eec90274d8 | ||
|
|
e8fff55c42 | ||
|
|
3cf3cdd705 | ||
|
|
9801fce659 | ||
|
|
4c1f51110b |
23
CHANGELOG.md
@@ -1,4 +1,27 @@
|
||||
# 更新日志
|
||||
## v3.2.6
|
||||
* 功能优化:恢复关闭注册系统配置项,管理员可以在后台关闭用户注册,只允许内部添加账号
|
||||
* 功能优化:兼用旧版本微信收款消息解析
|
||||
* 功能优化:优化订单扫码支付状态轮询功能,当关闭二维码时取消轮询,节约网络资源
|
||||
* 功能新增:新增图片发布功能,画廊只显示用户已发布的图片
|
||||
* 功能新增:后台新增配置微信客服二维码,可以上传自己的微信客服二维码
|
||||
* 功能新增:新增网站公告,可以在管理后台自定义配置
|
||||
* 功能新增:新增阿里通义千问大模型支持
|
||||
* Bug修复:修复 MJ 放大任务失败时候 img_call 会增加的 Bug
|
||||
* 功能优化:新增虎皮椒和PayJS订单状态校验功能,增加安全性
|
||||
* Bug修复:修复微信转账交易 ID 提取失败 Bug
|
||||
* 功能优化:给所有的 websocket 连接加上心跳,解决 "close 1006 (abnormal closure): unexpected EOF" Bug
|
||||
* 功能新增:新增短信宝短信平台发送平台集成
|
||||
|
||||
|
||||
## v3.2.5
|
||||
* 功能新增:**重磅更新!!!** 新增 MidJourney-Plus API 支持,一秒配置,开箱即用,高效稳定。
|
||||
* 功能新增:**重磅更新!!!** 新增 GPT4-ALL 和 GPTs 模型支持,你只需花几块钱,可以丝滑享受 ChatGPT-Plus 会员的所有功能,无需再订阅 Plus 账号了!!!
|
||||
* 功能优化:增强 markdown 图片和引用块解析。
|
||||
* 功能新增:新增用户文件管理,目前一支持上传文件跟 GPT 进行多态对话。
|
||||
* 功能优化:function call 兼用中转 API。
|
||||
* Bug修复:修复部分已知的 Bug。
|
||||
|
||||
## v3.2.4.1
|
||||
* 功能新增:新增 PayJs 支付通道
|
||||
* Bug修复:紧急修复后台添加用户失败问题
|
||||
|
||||
@@ -73,7 +73,7 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
|
||||
**演示站不提供任何充值点卡售卖或者VIP充值服务。** 如果您体验过后觉得还不错的话,可以花两分钟用下面的一键部署脚本自己部署一套。
|
||||
|
||||
```shell
|
||||
bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v3.2.4-7b5ff48154.sh)"
|
||||
bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v3.2.5-400fea2598.sh)"
|
||||
```
|
||||
|
||||
目前仅支持 Ubuntu 和 Centos 系统。 部署成功之后可以访问下面地址
|
||||
@@ -114,11 +114,7 @@ KEY。
|
||||
|
||||
## 项目文档
|
||||
|
||||
*
|
||||
|
||||
*
|
||||
最新的部署视频教程:[https://www.bilibili.com/video/BV1ge411C7uA/](https://www.bilibili.com/video/BV1ge411C7uA/?vd_source=dee8b15703ccfcbd24a60ee9a0fabb73)
|
||||
**
|
||||
最新的部署视频教程:[https://www.bilibili.com/video/BV1Cc411t7CX/](https://www.bilibili.com/video/BV1Cc411t7CX/)
|
||||
|
||||
详细的部署和开发文档请参考 [**ChatGPT-Plus 文档**](https://ai.r9it.com/docs/)。
|
||||
|
||||
|
||||
@@ -33,6 +33,22 @@ WeChatBot = false
|
||||
Sign = ""
|
||||
CodeTempId = ""
|
||||
|
||||
[Sms] # Sms 配置,用于发送短信
|
||||
Active = "Ali" # 当前启用的短信服务,默认使用阿里云
|
||||
[Sms.SmsBao]
|
||||
Username = ""
|
||||
Password = ""
|
||||
Domain = "api.smsbao.com"
|
||||
Sign = "【极客学长】"
|
||||
CodeTemplate = "您的验证码是{code}。5分钟有效,若非本人操作,请忽略本短信。"
|
||||
[Sms.Ali]
|
||||
AccessKey = ""
|
||||
AccessSecret = ""
|
||||
Product = "Dysmsapi"
|
||||
Domain = "dysmsapi.aliyuncs.com"
|
||||
Sign = ""
|
||||
CodeTempId = ""
|
||||
|
||||
[OSS] # OSS 配置,用于存储 MJ 绘画图片
|
||||
Active = "local" # 默认使用本地文件存储引擎
|
||||
[OSS.Local]
|
||||
@@ -63,16 +79,11 @@ WeChatBot = false
|
||||
DiscordCDN = "https://mj.r9it.com:8002" # mj 图片反代地址
|
||||
DiscordGateway = "wss://mj.r9it.com:8003" # discord 机器人反代地址
|
||||
|
||||
[[MjConfigs]]
|
||||
[[MjPlusConfigs]]
|
||||
Enabled = false
|
||||
UserToken = ""
|
||||
BotToken = ""
|
||||
GuildId = ""
|
||||
ChanelId = ""
|
||||
UseCDN = false #是否使用反向代理访问,设置为true下面的设置才会生效
|
||||
DiscordAPI = "https://mj.r9it.com:8001" # discord API 反代地址
|
||||
DiscordCDN = "https://mj.r9it.com:8002" # mj 图片反代地址
|
||||
DiscordGateway = "wss://mj.r9it.com:8003" # discord 机器人反代地址
|
||||
ApiURL = "https://api.chatgpt-plus.net" # 目前暂时不支持更改
|
||||
ApiKey = "sk-xxx"
|
||||
NotifyURL = "https://ai.r9it.com/api/mj/notify" # 这里需要改成你的域名
|
||||
|
||||
[[SdConfigs]]
|
||||
Enabled = false
|
||||
@@ -80,18 +91,6 @@ WeChatBot = false
|
||||
ApiKey = ""
|
||||
Txt2ImgJsonPath = "res/sd/text2img.json"
|
||||
|
||||
[[SdConfigs]]
|
||||
Enabled = false
|
||||
ApiURL = ""
|
||||
ApiKey = ""
|
||||
Txt2ImgJsonPath = "res/sd/text2img.json"
|
||||
|
||||
[[SdConfigs]]
|
||||
Enabled = false
|
||||
ApiURL = ""
|
||||
ApiKey = ""
|
||||
Txt2ImgJsonPath = "res/text2img.json"
|
||||
|
||||
[XXLConfig] # xxl-job 配置,需要你部署 XXL-JOB 定时任务工具,用来定期清理未支付订单和清理过期 VIP,如果你没有启用支付服务,则该服务也无需启动
|
||||
Enabled = false # 是否启用 XXL JOB 服务
|
||||
ServerAddr = "http://172.22.11.47:8080/xxl-job-admin" # xxl-job-admin 管理地址
|
||||
|
||||
@@ -152,9 +152,9 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
|
||||
c.Request.URL.Path == "/api/role/list" ||
|
||||
c.Request.URL.Path == "/api/mj/jobs" ||
|
||||
c.Request.URL.Path == "/api/mj/client" ||
|
||||
c.Request.URL.Path == "/api/mj/notify" ||
|
||||
c.Request.URL.Path == "/api/invite/hits" ||
|
||||
c.Request.URL.Path == "/api/sd/jobs" ||
|
||||
c.Request.URL.Path == "/api/upload" ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
||||
|
||||
@@ -12,6 +12,9 @@ type ApiRequest struct {
|
||||
Functions []interface{} `json:"functions,omitempty"` // 兼容中转平台
|
||||
|
||||
ToolChoice string `json:"tool_choice,omitempty"`
|
||||
|
||||
Input map[string]interface{} `json:"input,omitempty"` //兼容阿里通义千问
|
||||
Parameters map[string]interface{} `json:"parameters,omitempty"` //兼容阿里通义千问
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
|
||||
@@ -5,21 +5,23 @@ import (
|
||||
)
|
||||
|
||||
type AppConfig struct {
|
||||
Path string `toml:"-"`
|
||||
Listen string
|
||||
Session Session
|
||||
ProxyURL string
|
||||
MysqlDns string // mysql 连接地址
|
||||
Manager Manager // 后台管理员账户信息
|
||||
StaticDir string // 静态资源目录
|
||||
StaticUrl string // 静态资源 URL
|
||||
Redis RedisConfig // redis 连接信息
|
||||
ApiConfig ChatPlusApiConfig // ChatPlus API authorization configs
|
||||
SmsConfig AliYunSmsConfig // AliYun send message service config
|
||||
OSS OSSConfig // OSS config
|
||||
MjConfigs []MidJourneyConfig // mj AI draw service pool
|
||||
WeChatBot bool // 是否启用微信机器人
|
||||
SdConfigs []StableDiffusionConfig // sd AI draw service pool
|
||||
Path string `toml:"-"`
|
||||
Listen string
|
||||
Session Session
|
||||
ProxyURL string
|
||||
MysqlDns string // mysql 连接地址
|
||||
Manager Manager // 后台管理员账户信息
|
||||
StaticDir string // 静态资源目录
|
||||
StaticUrl string // 静态资源 URL
|
||||
Redis RedisConfig // redis 连接信息
|
||||
ApiConfig ChatPlusApiConfig // ChatPlus API authorization configs
|
||||
SMS SMSConfig // send mobile message config
|
||||
OSS OSSConfig // OSS config
|
||||
MjConfigs []MidJourneyConfig // mj AI draw service pool
|
||||
MjPlusConfigs []MidJourneyPlusConfig // MJ plus config
|
||||
ImgCdnURL string // 图片反代加速地址
|
||||
WeChatBot bool // 是否启用微信机器人
|
||||
SdConfigs []StableDiffusionConfig // sd AI draw service pool
|
||||
|
||||
XXLConfig XXLConfig
|
||||
AlipayConfig AlipayConfig
|
||||
@@ -36,16 +38,6 @@ type SmtpConfig struct {
|
||||
Password string // 发件人邮箱密码
|
||||
}
|
||||
|
||||
// JPayConfig PayJs 支付配置
|
||||
type JPayConfig struct {
|
||||
Enabled bool
|
||||
Name string // 支付名称,默认 wechat
|
||||
AppId string // 商户 ID
|
||||
PrivateKey string // 私钥
|
||||
ApiURL string // API 网关
|
||||
NotifyURL string // 异步回调地址
|
||||
}
|
||||
|
||||
type ChatPlusApiConfig struct {
|
||||
ApiURL string
|
||||
AppId string
|
||||
@@ -60,7 +52,6 @@ type MidJourneyConfig struct {
|
||||
ChanelId string // Chanel ID
|
||||
UseCDN bool
|
||||
DiscordAPI string
|
||||
DiscordCDN string
|
||||
DiscordGateway string
|
||||
}
|
||||
|
||||
@@ -71,13 +62,11 @@ type StableDiffusionConfig struct {
|
||||
Txt2ImgJsonPath string
|
||||
}
|
||||
|
||||
type AliYunSmsConfig struct {
|
||||
AccessKey string
|
||||
AccessSecret string
|
||||
Product string
|
||||
Domain string
|
||||
Sign string // 短信签名
|
||||
CodeTempId string // 验证码短信模板 ID
|
||||
type MidJourneyPlusConfig struct {
|
||||
Enabled bool // 如果启用了 MidJourney Plus,将会自动禁用原生的MidJourney服务
|
||||
ApiURL string
|
||||
ApiKey string
|
||||
NotifyURL string // 任务进度更新回调地址
|
||||
}
|
||||
|
||||
type AlipayConfig struct {
|
||||
@@ -97,10 +86,20 @@ type HuPiPayConfig struct { //虎皮椒第四方支付配置
|
||||
Name string // 支付名称,如:wechat/alipay
|
||||
AppId string // App ID
|
||||
AppSecret string // app 密钥
|
||||
PayURL string // 支付网关
|
||||
ApiURL string // 支付网关
|
||||
NotifyURL string // 异步通知回调
|
||||
}
|
||||
|
||||
// JPayConfig PayJs 支付配置
|
||||
type JPayConfig struct {
|
||||
Enabled bool
|
||||
Name string // 支付名称,默认 wechat
|
||||
AppId string // 商户 ID
|
||||
PrivateKey string // 私钥
|
||||
ApiURL string // API 网关
|
||||
NotifyURL string // 异步回调地址
|
||||
}
|
||||
|
||||
type XXLConfig struct { // XXL 任务调度配置
|
||||
Enabled bool
|
||||
ServerAddr string
|
||||
@@ -148,6 +147,7 @@ const Azure = Platform("Azure")
|
||||
const ChatGLM = Platform("ChatGLM")
|
||||
const Baidu = Platform("Baidu")
|
||||
const XunFei = Platform("XunFei")
|
||||
const QWen = Platform("QWen")
|
||||
|
||||
// UserChatConfig 用户的聊天配置
|
||||
type UserChatConfig struct {
|
||||
@@ -172,7 +172,8 @@ type SystemConfig struct {
|
||||
VipMonthCalls int `json:"vip_month_calls"` // VIP 会员每月赠送的对话次数
|
||||
VipMonthImgCalls int `json:"vip_month_img_calls"` // VIP 会员每月赠送绘图次数
|
||||
|
||||
RegisterWays []string `json:"register_ways"` // 注册方式:支持手机,邮箱注册
|
||||
RegisterWays []string `json:"register_ways"` // 注册方式:支持手机,邮箱注册
|
||||
EnabledRegister bool `json:"enabled_register"` // 是否开放注册
|
||||
|
||||
RewardImg string `json:"reward_img"` // 众筹收款二维码地址
|
||||
EnabledReward bool `json:"enabled_reward"` // 启用众筹功能
|
||||
@@ -185,5 +186,5 @@ type SystemConfig struct {
|
||||
InviteChatCalls int `json:"invite_chat_calls"` // 邀请用户注册奖励对话次数
|
||||
InviteImgCalls int `json:"invite_img_calls"` // 邀请用户注册奖励绘图次数
|
||||
|
||||
ShowDemoNotice bool `json:"show_demo_notice"` // 显示演示站公告
|
||||
WechatCardURL string `json:"wechat_card_url"` // 微信客服地址
|
||||
}
|
||||
|
||||
26
api/core/types/sms.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package types
|
||||
|
||||
type SMSConfig struct {
|
||||
Active string
|
||||
Ali SmsConfigAli
|
||||
Bao SmsConfigBao
|
||||
}
|
||||
|
||||
// SmsConfigAli 阿里云短信平台配置
|
||||
type SmsConfigAli struct {
|
||||
AccessKey string
|
||||
AccessSecret string
|
||||
Product string
|
||||
Domain string
|
||||
Sign string // 短信签名
|
||||
CodeTempId string // 验证码短信模板 ID
|
||||
}
|
||||
|
||||
// SmsConfigBao 短信宝平台配置
|
||||
type SmsConfigBao struct {
|
||||
Username string //短信宝平台注册的用户名
|
||||
Password string //短信宝平台注册的密码
|
||||
Domain string //域名
|
||||
Sign string // 短信签名
|
||||
CodeTemplate string // 验证码短信模板 匹配
|
||||
}
|
||||
@@ -33,8 +33,9 @@ func (h *ConfigHandler) Update(c *gin.Context) {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
str := utils.JsonEncode(&data.Config)
|
||||
config := model.Config{Key: data.Key, Config: str}
|
||||
|
||||
value := utils.JsonEncode(&data.Config)
|
||||
config := model.Config{Key: data.Key, Config: value}
|
||||
res := h.db.FirstOrCreate(&config, model.Config{Key: data.Key})
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, res.Error.Error())
|
||||
@@ -42,7 +43,7 @@ func (h *ConfigHandler) Update(c *gin.Context) {
|
||||
}
|
||||
|
||||
if config.Id > 0 {
|
||||
config.Config = str
|
||||
config.Config = value
|
||||
res := h.db.Updates(&config)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, res.Error.Error())
|
||||
|
||||
@@ -26,7 +26,8 @@ import (
|
||||
)
|
||||
|
||||
const ErrorMsg = "抱歉,AI 助手开小差了,请稍后再试。"
|
||||
const ErrImg = ""
|
||||
|
||||
var ErrImg = ""
|
||||
|
||||
var logger = logger2.GetLogger()
|
||||
|
||||
@@ -45,6 +46,13 @@ func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client) *Chat
|
||||
return &h
|
||||
}
|
||||
|
||||
func (h *ChatHandler) Init() {
|
||||
// 如果后台有上传微信客服微信二维码,则覆盖
|
||||
if h.App.SysConfig.WechatCardURL != "" {
|
||||
ErrImg = fmt.Sprintf("", h.App.SysConfig.WechatCardURL)
|
||||
}
|
||||
}
|
||||
|
||||
var chatConfig types.ChatConfig
|
||||
|
||||
// ChatHandle 处理聊天 WebSocket 请求
|
||||
@@ -126,7 +134,6 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
||||
for {
|
||||
_, msg, err := client.Receive()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
client.Close()
|
||||
h.App.ChatClients.Delete(sessionId)
|
||||
cancelFunc := h.App.ReqCancelFunc.Get(sessionId)
|
||||
@@ -137,19 +144,30 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
message := string(msg)
|
||||
logger.Info("Receive a message: ", message)
|
||||
//utils.ReplyMessage(client, "这是一条测试消息!")
|
||||
var message types.WsMessage
|
||||
err = utils.JsonDecode(string(msg), &message)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 心跳消息
|
||||
if message.Type == "heartbeat" {
|
||||
logger.Debug("收到 Chat 心跳消息:", message.Content)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("Receive a message: ", message.Content)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
h.App.ReqCancelFunc.Put(sessionId, cancel)
|
||||
// 回复消息
|
||||
err = h.sendMessage(ctx, session, chatRole, message, client)
|
||||
err = h.sendMessage(ctx, session, chatRole, utils.InterfaceToString(message.Content), client)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
utils.ReplyChunkMessage(client, types.WsMessage{Type: types.WsEnd})
|
||||
} else {
|
||||
utils.ReplyChunkMessage(client, types.WsMessage{Type: types.WsEnd})
|
||||
logger.Info("回答完毕: " + string(message))
|
||||
logger.Infof("回答完毕: %v", message.Content)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -267,6 +285,10 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
||||
req.Temperature = h.App.ChatConfig.XunFei.Temperature
|
||||
req.MaxTokens = h.App.ChatConfig.XunFei.MaxTokens
|
||||
break
|
||||
case types.QWen:
|
||||
req.Input = map[string]interface{}{"messages": []map[string]string{{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}}}
|
||||
req.Parameters = map[string]interface{}{}
|
||||
break
|
||||
default:
|
||||
utils.ReplyMessage(ws, "不支持的平台:"+session.Model.Platform+",请联系管理员!")
|
||||
utils.ReplyMessage(ws, ErrImg)
|
||||
@@ -340,7 +362,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
||||
return h.sendBaiduMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
|
||||
case types.XunFei:
|
||||
return h.sendXunFeiMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
|
||||
|
||||
case types.QWen:
|
||||
return h.sendQWenMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
|
||||
}
|
||||
utils.ReplyChunkMessage(ws, types.WsMessage{
|
||||
Type: types.WsMiddle,
|
||||
@@ -434,8 +457,16 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
|
||||
case types.Baidu:
|
||||
apiURL = strings.Replace(apiKey.ApiURL, "{model}", req.Model, 1)
|
||||
break
|
||||
default:
|
||||
case types.QWen:
|
||||
apiURL = apiKey.ApiURL
|
||||
req.Messages = nil
|
||||
break
|
||||
default:
|
||||
if req.Model == "gpt-4-all" || strings.HasPrefix(req.Model, "gpt-4-gizmo-g-") {
|
||||
apiURL = "https://gpt.bemore.lol/v1/chat/completions"
|
||||
} else {
|
||||
apiURL = apiKey.ApiURL
|
||||
}
|
||||
}
|
||||
// 更新 API KEY 的最后使用时间
|
||||
h.db.Model(apiKey).UpdateColumn("last_used_at", time.Now().Unix())
|
||||
@@ -476,7 +507,7 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
|
||||
} else {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
logger.Debugf("Sending %s request, ApiURL:%s, ApiKey:%s, PROXY: %s, Model: %s", platform, apiURL, apiKey.Value, proxyURL, req.Model)
|
||||
logger.Debugf("Sending %s request, ApiURL:%s, Password:%s, PROXY: %s, Model: %s", platform, apiURL, apiKey.Value, proxyURL, req.Model)
|
||||
switch platform {
|
||||
case types.Azure:
|
||||
request.Header.Set("api-key", apiKey.Value)
|
||||
@@ -492,6 +523,11 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
|
||||
request.RequestURI = ""
|
||||
case types.OpenAI:
|
||||
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value))
|
||||
break
|
||||
case types.QWen:
|
||||
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey.Value))
|
||||
request.Header.Set("X-DashScope-SSE", "enable")
|
||||
break
|
||||
}
|
||||
return client.Do(request)
|
||||
}
|
||||
|
||||
229
api/handler/chatimpl/qwen_handler.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package chatimpl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"chatplus/core/types"
|
||||
"chatplus/store/model"
|
||||
"chatplus/store/vo"
|
||||
"chatplus/utils"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type qWenResp struct {
|
||||
Output struct {
|
||||
FinishReason string `json:"finish_reason"`
|
||||
Text string `json:"text"`
|
||||
} `json:"output"`
|
||||
Usage struct {
|
||||
TotalTokens int `json:"total_tokens"`
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
} `json:"usage"`
|
||||
RequestID string `json:"request_id"`
|
||||
}
|
||||
|
||||
// 通义千问消息发送实现
|
||||
func (h *ChatHandler) sendQWenMessage(
|
||||
chatCtx []interface{},
|
||||
req types.ApiRequest,
|
||||
userVo vo.User,
|
||||
ctx context.Context,
|
||||
session *types.ChatSession,
|
||||
role model.ChatRole,
|
||||
prompt string,
|
||||
ws *types.WsClient) error {
|
||||
promptCreatedAt := time.Now() // 记录提问时间
|
||||
start := time.Now()
|
||||
var apiKey = model.ApiKey{}
|
||||
response, err := h.doRequest(ctx, req, session.Model.Platform, &apiKey)
|
||||
logger.Info("HTTP请求完成,耗时:", time.Now().Sub(start))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "context canceled") {
|
||||
logger.Info("用户取消了请求:", prompt)
|
||||
return nil
|
||||
} else if strings.Contains(err.Error(), "no available key") {
|
||||
utils.ReplyMessage(ws, "抱歉😔😔😔,系统已经没有可用的 API KEY,请联系管理员!")
|
||||
return nil
|
||||
} else {
|
||||
logger.Error(err)
|
||||
}
|
||||
|
||||
utils.ReplyMessage(ws, ErrorMsg)
|
||||
utils.ReplyMessage(ws, ErrImg)
|
||||
return err
|
||||
} else {
|
||||
defer response.Body.Close()
|
||||
}
|
||||
contentType := response.Header.Get("Content-Type")
|
||||
if strings.Contains(contentType, "text/event-stream") {
|
||||
replyCreatedAt := time.Now() // 记录回复时间
|
||||
// 循环读取 Chunk 消息
|
||||
var message = types.Message{}
|
||||
var contents = make([]string, 0)
|
||||
scanner := bufio.NewScanner(response.Body)
|
||||
|
||||
var content, lastText, newText string
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if len(line) < 5 || strings.HasPrefix(line, "id:") ||
|
||||
strings.HasPrefix(line, "event:") || strings.HasPrefix(line, ":HTTP_STATUS/200") {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "data:") {
|
||||
content = line[5:]
|
||||
}
|
||||
// 处理代码换行
|
||||
if len(content) == 0 {
|
||||
content = "\n"
|
||||
}
|
||||
|
||||
var resp qWenResp
|
||||
err := utils.JsonDecode(content, &resp)
|
||||
if err != nil {
|
||||
logger.Error("error with parse data line: ", err)
|
||||
utils.ReplyMessage(ws, fmt.Sprintf("**解析数据行失败:%s**", err))
|
||||
break
|
||||
}
|
||||
|
||||
if len(contents) == 0 { // 发送消息头
|
||||
utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
||||
}
|
||||
|
||||
//通过比较 lastText(上一次的文本)和 currentText(当前的文本),
|
||||
//提取出新添加的文本部分。然后只将这部分新文本发送到客户端。
|
||||
//每次循环结束后,lastText 会更新为当前的完整文本,以便于下一次循环进行比较。
|
||||
currentText := resp.Output.Text
|
||||
if currentText != lastText {
|
||||
// 提取新增文本
|
||||
newText = strings.Replace(currentText, lastText, "", 1)
|
||||
utils.ReplyChunkMessage(ws, types.WsMessage{
|
||||
Type: types.WsMiddle,
|
||||
Content: utils.InterfaceToString(newText),
|
||||
})
|
||||
lastText = currentText // 更新 lastText
|
||||
}
|
||||
contents = append(contents, newText)
|
||||
|
||||
if resp.Output.FinishReason == "stop" {
|
||||
break
|
||||
}
|
||||
|
||||
} //end for
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
if strings.Contains(err.Error(), "context canceled") {
|
||||
logger.Info("用户取消了请求:", prompt)
|
||||
} else {
|
||||
logger.Error("信息读取出错:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 消息发送成功
|
||||
if len(contents) > 0 {
|
||||
// 更新用户的对话次数
|
||||
h.subUserCalls(userVo, session)
|
||||
|
||||
if message.Role == "" {
|
||||
message.Role = "assistant"
|
||||
}
|
||||
message.Content = strings.Join(contents, "")
|
||||
useMsg := types.Message{Role: "user", Content: prompt}
|
||||
|
||||
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
||||
if h.App.ChatConfig.EnableContext {
|
||||
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||
chatCtx = append(chatCtx, message) // 回复消息
|
||||
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
||||
}
|
||||
|
||||
// 追加聊天记录
|
||||
if h.App.ChatConfig.EnableHistory {
|
||||
// for prompt
|
||||
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
historyUserMsg := model.HistoryMessage{
|
||||
UserId: userVo.Id,
|
||||
ChatId: session.ChatId,
|
||||
RoleId: role.Id,
|
||||
Type: types.PromptMsg,
|
||||
Icon: userVo.Avatar,
|
||||
Content: template.HTMLEscapeString(prompt),
|
||||
Tokens: promptToken,
|
||||
UseContext: true,
|
||||
}
|
||||
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 数量
|
||||
replyToken, _ := utils.CalcTokens(message.Content, req.Model)
|
||||
totalTokens := replyToken + getTotalTokens(req)
|
||||
historyReplyMsg := model.HistoryMessage{
|
||||
UserId: userVo.Id,
|
||||
ChatId: session.ChatId,
|
||||
RoleId: role.Id,
|
||||
Type: types.ReplyMsg,
|
||||
Icon: role.Icon,
|
||||
Content: message.Content,
|
||||
Tokens: totalTokens,
|
||||
UseContext: true,
|
||||
}
|
||||
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.incUserTokenFee(userVo.Id, totalTokens)
|
||||
}
|
||||
|
||||
// 保存当前会话
|
||||
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
|
||||
}
|
||||
h.db.Create(&chatItem)
|
||||
}
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
|
||||
} else {
|
||||
request = req.C().R()
|
||||
}
|
||||
logger.Debugf("Sending %s request, ApiURL:%s, ApiKey:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, h.proxyURL)
|
||||
logger.Debugf("Sending %s request, ApiURL:%s, Password:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, h.proxyURL)
|
||||
r, err := request.SetHeader("Content-Type", "application/json").
|
||||
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
||||
SetBody(imgReq{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"chatplus/core/types"
|
||||
"chatplus/service"
|
||||
"chatplus/service/mj"
|
||||
"chatplus/service/mj/plus"
|
||||
"chatplus/service/oss"
|
||||
"chatplus/store/model"
|
||||
"chatplus/store/vo"
|
||||
@@ -12,12 +13,13 @@ import (
|
||||
"chatplus/utils/resp"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MidJourneyHandler struct {
|
||||
@@ -157,7 +159,7 @@ func (h *MidJourneyHandler) Image(c *gin.Context) {
|
||||
Prompt: prompt,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if res := h.db.Create(&job); res.Error != nil {
|
||||
if res := h.db.Create(&job); res.Error != nil || res.RowsAffected == 0 {
|
||||
resp.ERROR(c, "添加任务失败:"+res.Error.Error())
|
||||
return
|
||||
}
|
||||
@@ -203,7 +205,6 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
||||
}
|
||||
|
||||
idValue, _ := c.Get(types.LoginUserID)
|
||||
jobId := 0
|
||||
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
|
||||
taskId, _ := h.snowflake.Next(true)
|
||||
job := model.MidJourneyJob{
|
||||
@@ -215,13 +216,13 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
||||
Prompt: data.Prompt,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if res := h.db.Create(&job); res.Error != nil {
|
||||
if res := h.db.Create(&job); res.Error != nil || res.RowsAffected == 0 {
|
||||
resp.ERROR(c, "添加任务失败:"+res.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.pool.PushTask(types.MjTask{
|
||||
Id: jobId,
|
||||
Id: int(job.Id),
|
||||
SessionId: data.SessionId,
|
||||
Type: types.TaskUpscale,
|
||||
Prompt: data.Prompt,
|
||||
@@ -251,7 +252,6 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
||||
}
|
||||
|
||||
idValue, _ := c.Get(types.LoginUserID)
|
||||
jobId := 0
|
||||
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
|
||||
taskId, _ := h.snowflake.Next(true)
|
||||
job := model.MidJourneyJob{
|
||||
@@ -264,13 +264,13 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
||||
Prompt: data.Prompt,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if res := h.db.Create(&job); res.Error != nil {
|
||||
if res := h.db.Create(&job); res.Error != nil || res.RowsAffected == 0 {
|
||||
resp.ERROR(c, "添加任务失败:"+res.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.pool.PushTask(types.MjTask{
|
||||
Id: jobId,
|
||||
Id: int(job.Id),
|
||||
SessionId: data.SessionId,
|
||||
Type: types.TaskVariation,
|
||||
Prompt: data.Prompt,
|
||||
@@ -295,6 +295,7 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) {
|
||||
userId := h.GetInt(c, "user_id", 0)
|
||||
page := h.GetInt(c, "page", 0)
|
||||
pageSize := h.GetInt(c, "page_size", 0)
|
||||
publish := h.GetBool(c, "publish")
|
||||
|
||||
session := h.db.Session(&gorm.Session{})
|
||||
if status == 1 {
|
||||
@@ -305,6 +306,9 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) {
|
||||
if userId > 0 {
|
||||
session = session.Where("user_id = ?", userId)
|
||||
}
|
||||
if publish {
|
||||
session = session.Where("publish = ?", publish)
|
||||
}
|
||||
if page > 0 && pageSize > 0 {
|
||||
offset := (page - 1) * pageSize
|
||||
session = session.Offset(offset).Limit(pageSize)
|
||||
@@ -329,17 +333,11 @@ func (h *MidJourneyHandler) JobList(c *gin.Context) {
|
||||
h.db.Delete(&model.MidJourneyJob{Id: job.Id})
|
||||
}
|
||||
|
||||
if item.Progress < 100 {
|
||||
// 10 分钟还没完成的任务直接删除
|
||||
if time.Now().Sub(item.CreatedAt) > time.Minute*10 {
|
||||
h.db.Delete(&item)
|
||||
// 退回绘图次数
|
||||
h.db.Model(&model.User{}).Where("id = ?", item.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1))
|
||||
continue
|
||||
}
|
||||
|
||||
if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" {
|
||||
// 正在运行中任务使用代理访问图片
|
||||
if item.ImgURL == "" && item.OrgURL != "" {
|
||||
if h.App.Config.ImgCdnURL != "" {
|
||||
job.ImgURL = strings.ReplaceAll(job.OrgURL, "https://cdn.discordapp.com", h.App.Config.ImgCdnURL)
|
||||
} else {
|
||||
image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
|
||||
if err == nil {
|
||||
job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
|
||||
@@ -382,3 +380,44 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) {
|
||||
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
// Notify MidJourney Plus 服务任务回调处理
|
||||
func (h *MidJourneyHandler) Notify(c *gin.Context) {
|
||||
var data plus.CBReq
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
logger.Error("非法任务回调:%+v", err)
|
||||
return
|
||||
}
|
||||
err := h.pool.Notify(data)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
} else {
|
||||
userId := h.GetLoginUserId(c)
|
||||
client := h.pool.Clients.Get(userId)
|
||||
if client != nil {
|
||||
_ = client.Send([]byte("Task Updated"))
|
||||
}
|
||||
}
|
||||
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
// Publish 发布图片到画廊显示
|
||||
func (h *MidJourneyHandler) Publish(c *gin.Context) {
|
||||
var data struct {
|
||||
Id uint `json:"id"`
|
||||
Action bool `json:"action"` // 发布动作,true => 发布,false => 取消分享
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
|
||||
res := h.db.Model(&model.MidJourneyJob{Id: data.Id}).UpdateColumn("publish", data.Action)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "更新数据库失败")
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
@@ -101,30 +101,12 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
|
||||
NotifyURL: h.App.Config.HuPiPayConfig.NotifyURL,
|
||||
WapName: "极客学长",
|
||||
}
|
||||
res, err := h.huPiPayService.Pay(params)
|
||||
r, err := h.huPiPayService.Pay(params)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "error with generate pay url: "+err.Error())
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var r struct {
|
||||
Openid interface{} `json:"openid"`
|
||||
UrlQrcode string `json:"url_qrcode"`
|
||||
URL string `json:"url"`
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg,omitempty"`
|
||||
}
|
||||
err = utils.JsonDecode(res, &r)
|
||||
if err != nil {
|
||||
logger.Debugf(res)
|
||||
resp.ERROR(c, "error with decode payment result: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if r.ErrCode != 0 {
|
||||
resp.ERROR(c, "error with generate pay url: "+r.ErrMsg)
|
||||
return
|
||||
}
|
||||
c.Redirect(302, r.URL)
|
||||
}
|
||||
resp.ERROR(c, "Invalid operations")
|
||||
@@ -199,13 +181,17 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
}
|
||||
|
||||
var payWay string
|
||||
var notifyURL string
|
||||
switch data.PayWay {
|
||||
case "hupi":
|
||||
payWay = PayWayXunHu
|
||||
notifyURL = h.App.Config.HuPiPayConfig.NotifyURL
|
||||
case "payjs":
|
||||
payWay = PayWayJs
|
||||
notifyURL = h.App.Config.JPayConfig.NotifyURL
|
||||
default:
|
||||
payWay = PayWayAlipay
|
||||
notifyURL = h.App.Config.AlipayConfig.NotifyURL
|
||||
}
|
||||
// 创建订单
|
||||
remark := types.OrderRemark{
|
||||
@@ -218,7 +204,7 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
}
|
||||
order := model.Order{
|
||||
UserId: user.Id,
|
||||
Mobile: user.Username,
|
||||
Username: user.Username,
|
||||
ProductId: product.Id,
|
||||
OrderNo: orderNo,
|
||||
Subject: product.Name,
|
||||
@@ -228,7 +214,7 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
Remark: utils.JsonEncode(remark),
|
||||
}
|
||||
res = h.db.Create(&order)
|
||||
if res.Error != nil {
|
||||
if res.Error != nil || res.RowsAffected == 0 {
|
||||
resp.ERROR(c, "error with create order: "+res.Error.Error())
|
||||
return
|
||||
}
|
||||
@@ -267,7 +253,7 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
parse, err := url.Parse(h.App.Config.AlipayConfig.NotifyURL)
|
||||
parse, err := url.Parse(notifyURL)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
@@ -284,7 +270,7 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 异步通知回调公共逻辑
|
||||
func (h *PaymentHandler) notify(orderNo string) error {
|
||||
func (h *PaymentHandler) notify(orderNo string, tradeNo string) error {
|
||||
var order model.Order
|
||||
res := h.db.Where("order_no = ?", orderNo).First(&order)
|
||||
if res.Error != nil {
|
||||
@@ -293,6 +279,9 @@ func (h *PaymentHandler) notify(orderNo string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
// 已支付订单,直接返回
|
||||
if order.Status == types.OrderPaidSuccess {
|
||||
return nil
|
||||
@@ -314,30 +303,25 @@ func (h *PaymentHandler) notify(orderNo string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. 点卡:days == 0, calls > 0
|
||||
// 2. vip 套餐:days > 0, calls == 0
|
||||
if remark.Days > 0 {
|
||||
if user.ExpiredTime > time.Now().Unix() {
|
||||
if user.Vip { // 已经是 VIP 用户
|
||||
if remark.Days > 0 { // 只延期 VIP,不增加调用次数
|
||||
user.ExpiredTime = time.Unix(user.ExpiredTime, 0).AddDate(0, 0, remark.Days).Unix()
|
||||
} else {
|
||||
user.ExpiredTime = time.Now().AddDate(0, 0, remark.Days).Unix()
|
||||
} else { // 充值点卡,直接增加次数即可
|
||||
user.Calls += remark.Calls
|
||||
user.ImgCalls += remark.ImgCalls
|
||||
}
|
||||
user.Vip = true
|
||||
|
||||
} else if !user.Vip { // 充值点卡的非 VIP 用户
|
||||
user.ExpiredTime = time.Now().AddDate(0, 0, 30).Unix()
|
||||
}
|
||||
} else { // 非 VIP 用户
|
||||
if remark.Days > 0 { // vip 套餐:days > 0, calls == 0
|
||||
user.ExpiredTime = time.Now().AddDate(0, 0, remark.Days).Unix()
|
||||
user.Calls += h.App.SysConfig.VipMonthCalls
|
||||
user.ImgCalls += h.App.SysConfig.VipMonthImgCalls
|
||||
user.Vip = true
|
||||
|
||||
if remark.Calls > 0 { // 充值点卡
|
||||
user.Calls += remark.Calls
|
||||
} else {
|
||||
user.Calls += h.App.SysConfig.VipMonthCalls
|
||||
}
|
||||
|
||||
if remark.ImgCalls > 0 {
|
||||
user.ImgCalls += remark.ImgCalls
|
||||
} else {
|
||||
user.ImgCalls += h.App.SysConfig.VipMonthImgCalls
|
||||
} else { //点卡:days == 0, calls > 0
|
||||
user.Calls += remark.Calls
|
||||
user.ImgCalls += remark.ImgCalls
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
@@ -351,6 +335,7 @@ func (h *PaymentHandler) notify(orderNo string) error {
|
||||
// 更新订单状态
|
||||
order.PayTime = time.Now().Unix()
|
||||
order.Status = types.OrderPaidSuccess
|
||||
order.TradeNo = tradeNo
|
||||
res = h.db.Updates(&order)
|
||||
if res.Error != nil {
|
||||
err := fmt.Errorf("error with update order info: %v", res.Error)
|
||||
@@ -387,12 +372,15 @@ func (h *PaymentHandler) HuPiPayNotify(c *gin.Context) {
|
||||
}
|
||||
|
||||
orderNo := c.Request.Form.Get("trade_order_id")
|
||||
logger.Infof("收到订单支付回调,订单 NO:%s", orderNo)
|
||||
// TODO 是否要保存订单交易流水号
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
tradeNo := c.Request.Form.Get("open_order_id")
|
||||
logger.Infof("收到虎皮椒订单支付回调,订单 NO:%s,交易流水号:%s", orderNo, tradeNo)
|
||||
|
||||
err = h.notify(orderNo)
|
||||
if err = h.huPiPayService.Check(tradeNo); err != nil {
|
||||
logger.Error("订单校验失败:", err)
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
}
|
||||
err = h.notify(orderNo, tradeNo)
|
||||
if err != nil {
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
@@ -409,19 +397,17 @@ func (h *PaymentHandler) AlipayNotify(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO:这里最好用支付宝的公钥签名签证一下交易真假
|
||||
//res := h.alipayService.TradeVerify(c.Request.Form)
|
||||
r := h.alipayService.TradeQuery(c.Request.Form.Get("out_trade_no"))
|
||||
logger.Infof("验证支付结果:%+v", r)
|
||||
if !r.Success() {
|
||||
// TODO:验证交易签名
|
||||
res := h.alipayService.TradeVerify(c.Request.Form)
|
||||
logger.Infof("验证支付结果:%+v", res)
|
||||
if !res.Success() {
|
||||
logger.Error("订单校验失败:", res.Message)
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
}
|
||||
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
|
||||
err = h.notify(r.OutTradeNo)
|
||||
tradeNo := c.Request.Form.Get("trade_no")
|
||||
err = h.notify(res.OutTradeNo, tradeNo)
|
||||
if err != nil {
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
@@ -446,10 +432,16 @@ func (h *PaymentHandler) PayJsNotify(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
// 校验订单支付状态
|
||||
tradeNo := c.Request.Form.Get("payjs_order_id")
|
||||
err = h.js.Check(tradeNo)
|
||||
if err != nil {
|
||||
logger.Error("订单校验失败:", err)
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.notify(orderNo)
|
||||
err = h.notify(orderNo, tradeNo)
|
||||
if err != nil {
|
||||
c.String(http.StatusOK, "fail")
|
||||
return
|
||||
|
||||
@@ -145,6 +145,7 @@ func (h *SdJobHandler) JobList(c *gin.Context) {
|
||||
userId := h.GetInt(c, "user_id", 0)
|
||||
page := h.GetInt(c, "page", 0)
|
||||
pageSize := h.GetInt(c, "page_size", 0)
|
||||
publish := h.GetBool(c, "publish")
|
||||
|
||||
session := h.db.Session(&gorm.Session{})
|
||||
if status == 1 {
|
||||
@@ -155,6 +156,9 @@ func (h *SdJobHandler) JobList(c *gin.Context) {
|
||||
if userId > 0 {
|
||||
session = session.Where("user_id = ?", userId)
|
||||
}
|
||||
if publish {
|
||||
session = session.Where("publish", publish)
|
||||
}
|
||||
if page > 0 && pageSize > 0 {
|
||||
offset := (page - 1) * pageSize
|
||||
session = session.Offset(offset).Limit(pageSize)
|
||||
@@ -224,3 +228,23 @@ func (h *SdJobHandler) Remove(c *gin.Context) {
|
||||
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
// Publish 发布/取消发布图片到画廊显示
|
||||
func (h *SdJobHandler) Publish(c *gin.Context) {
|
||||
var data struct {
|
||||
Id uint `json:"id"`
|
||||
Action bool `json:"action"` // 发布动作,true => 发布,false => 取消分享
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
|
||||
res := h.db.Model(&model.SdJob{Id: data.Id}).UpdateColumn("publish", true)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "更新数据库失败")
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"chatplus/core"
|
||||
"chatplus/core/types"
|
||||
"chatplus/service"
|
||||
"chatplus/service/sms"
|
||||
"chatplus/utils"
|
||||
"chatplus/utils/resp"
|
||||
"strings"
|
||||
@@ -17,7 +18,7 @@ const CodeStorePrefix = "/verify/codes/"
|
||||
type SmsHandler struct {
|
||||
BaseHandler
|
||||
redis *redis.Client
|
||||
sms *service.AliYunSmsService
|
||||
sms *sms.ServiceManager
|
||||
smtp *service.SmtpService
|
||||
captcha *service.CaptchaService
|
||||
}
|
||||
@@ -25,7 +26,7 @@ type SmsHandler struct {
|
||||
func NewSmsHandler(
|
||||
app *core.AppServer,
|
||||
client *redis.Client,
|
||||
sms *service.AliYunSmsService,
|
||||
sms *sms.ServiceManager,
|
||||
smtp *service.SmtpService,
|
||||
captcha *service.CaptchaService) *SmsHandler {
|
||||
handler := &SmsHandler{redis: client, sms: sms, captcha: captcha, smtp: smtp}
|
||||
@@ -63,7 +64,8 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
|
||||
resp.ERROR(c, "系统已禁用手机号注册!")
|
||||
return
|
||||
}
|
||||
err = h.sms.SendVerifyCode(data.Receiver, code)
|
||||
err = h.sms.GetService().SendVerifyCode(data.Receiver, code)
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"chatplus/utils"
|
||||
"chatplus/utils/resp"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/imroc/req/v3"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -22,23 +22,176 @@ func NewTestHandler(db *gorm.DB, snowflake *service.Snowflake, js *payment.PayJS
|
||||
return &TestHandler{db: db, snowflake: snowflake, js: js}
|
||||
}
|
||||
|
||||
func (h *TestHandler) Test(c *gin.Context) {
|
||||
//h.initUserNickname(c)
|
||||
//h.initMjTaskId(c)
|
||||
type reqBody struct {
|
||||
BotType string `json:"botType"`
|
||||
Prompt string `json:"prompt"`
|
||||
Base64Array []interface{} `json:"base64Array,omitempty"`
|
||||
AccountFilter struct {
|
||||
InstanceId string `json:"instanceId"`
|
||||
Modes []interface{} `json:"modes"`
|
||||
Remix bool `json:"remix"`
|
||||
RemixAutoConsidered bool `json:"remixAutoConsidered"`
|
||||
} `json:"accountFilter,omitempty"`
|
||||
NotifyHook string `json:"notifyHook"`
|
||||
State string `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
orderId, _ := h.snowflake.Next(false)
|
||||
params := payment.JPayReq{
|
||||
TotalFee: 12345,
|
||||
OutTradeNo: orderId,
|
||||
Subject: "支付测试",
|
||||
type resBody struct {
|
||||
Code int `json:"code"`
|
||||
Description string `json:"description"`
|
||||
Properties struct {
|
||||
} `json:"properties"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
func (h *TestHandler) Test(c *gin.Context) {
|
||||
image(c)
|
||||
|
||||
}
|
||||
|
||||
func upscale(c *gin.Context) {
|
||||
apiURL := "https://api.openai1s.cn/mj/submit/action"
|
||||
token := "sk-QpBaQn9Z5vngsjJaFdDfC9Db90C845EaB5E764578a7d292a"
|
||||
body := map[string]string{
|
||||
"customId": "MJ::JOB::upsample::1::c80a8eb1-f2d1-4f40-8785-97eb99b7ba0a",
|
||||
"taskId": "1704880156226095",
|
||||
"notifyHook": "http://r9it.com:6004/api/test/mj",
|
||||
}
|
||||
r := h.js.Pay(params)
|
||||
if !r.IsOK() {
|
||||
resp.ERROR(c, r.ReturnMsg)
|
||||
var res resBody
|
||||
var resErr errRes
|
||||
r, err := req.C().R().
|
||||
SetHeader("Authorization", "Bearer "+token).
|
||||
SetBody(body).
|
||||
SetSuccessResult(&res).
|
||||
SetErrorResult(&resErr).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "请求出错:"+err.Error())
|
||||
return
|
||||
}
|
||||
resp.SUCCESS(c, r)
|
||||
|
||||
if r.IsErrorState() {
|
||||
resp.ERROR(c, "返回错误状态:"+resErr.Error.Message)
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, res)
|
||||
|
||||
}
|
||||
|
||||
type queryRes struct {
|
||||
Action string `json:"action"`
|
||||
Buttons []struct {
|
||||
CustomId string `json:"customId"`
|
||||
Emoji string `json:"emoji"`
|
||||
Label string `json:"label"`
|
||||
Style int `json:"style"`
|
||||
Type int `json:"type"`
|
||||
} `json:"buttons"`
|
||||
Description string `json:"description"`
|
||||
FailReason string `json:"failReason"`
|
||||
FinishTime int `json:"finishTime"`
|
||||
Id string `json:"id"`
|
||||
ImageUrl string `json:"imageUrl"`
|
||||
Progress string `json:"progress"`
|
||||
Prompt string `json:"prompt"`
|
||||
PromptEn string `json:"promptEn"`
|
||||
Properties struct {
|
||||
} `json:"properties"`
|
||||
StartTime int `json:"startTime"`
|
||||
State string `json:"state"`
|
||||
Status string `json:"status"`
|
||||
SubmitTime int `json:"submitTime"`
|
||||
}
|
||||
|
||||
func query(c *gin.Context) {
|
||||
apiURL := "https://api.openai1s.cn/mj/task/1704960661008372/fetch"
|
||||
token := "sk-QpBaQn9Z5vngsjJaFdDfC9Db90C845EaB5E764578a7d292a"
|
||||
var res queryRes
|
||||
r, err := req.C().R().SetHeader("Authorization", "Bearer "+token).
|
||||
SetSuccessResult(&res).
|
||||
Get(apiURL)
|
||||
|
||||
if err != nil {
|
||||
resp.ERROR(c, "请求出错:"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
resp.ERROR(c, "返回错误状态:"+r.Status)
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, res)
|
||||
}
|
||||
|
||||
type errRes struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func image(c *gin.Context) {
|
||||
apiURL := "https://api.openai1s.cn/mj-fast/mj/submit/imagine"
|
||||
token := "sk-QpBaQn9Z5vngsjJaFdDfC9Db90C845EaB5E764578a7d292a"
|
||||
body := reqBody{
|
||||
BotType: "MID_JOURNEY",
|
||||
Prompt: "一个中国美女,手上拿着一桶爆米花,脸上带着迷人的微笑,白色衣服 --s 750 --v 6",
|
||||
NotifyHook: "http://r9it.com:6004/api/test/mj",
|
||||
}
|
||||
var res resBody
|
||||
var resErr errRes
|
||||
r, err := req.C().R().
|
||||
SetHeader("Authorization", "Bearer "+token).
|
||||
SetBody(body).
|
||||
SetSuccessResult(&res).
|
||||
SetErrorResult(&resErr).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "请求出错:"+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
resp.ERROR(c, "返回错误状态:"+resErr.Error.Message)
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, res)
|
||||
}
|
||||
|
||||
type cbReq struct {
|
||||
Id string `json:"id"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
Prompt string `json:"prompt"`
|
||||
PromptEn string `json:"promptEn"`
|
||||
Description string `json:"description"`
|
||||
SubmitTime int64 `json:"submitTime"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
FinishTime int64 `json:"finishTime"`
|
||||
Progress string `json:"progress"`
|
||||
ImageUrl string `json:"imageUrl"`
|
||||
FailReason interface{} `json:"failReason"`
|
||||
Properties struct {
|
||||
FinalPrompt string `json:"finalPrompt"`
|
||||
} `json:"properties"`
|
||||
}
|
||||
|
||||
func (h *TestHandler) Mj(c *gin.Context) {
|
||||
var data cbReq
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
logger.Debugf("任务ID:%s,任务进度:%s,图片地址:%s, 最终提示词:%s", data.Id, data.Progress, data.ImageUrl, data.Properties.FinalPrompt)
|
||||
apiURL := "https://api.openai1s.cn/mj/task/" + data.Id + "/fetch"
|
||||
token := "sk-QpBaQn9Z5vngsjJaFdDfC9Db90C845EaB5E764578a7d292a"
|
||||
var res queryRes
|
||||
_, _ = req.C().R().SetHeader("Authorization", "Bearer "+token).
|
||||
SetSuccessResult(&res).
|
||||
Get(apiURL)
|
||||
|
||||
fmt.Println(res.State, ",", res.ImageUrl, ",", res.Progress)
|
||||
}
|
||||
|
||||
func (h *TestHandler) initUserNickname(c *gin.Context) {
|
||||
|
||||
@@ -3,9 +3,13 @@ package handler
|
||||
import (
|
||||
"chatplus/core"
|
||||
"chatplus/service/oss"
|
||||
"chatplus/store/model"
|
||||
"chatplus/store/vo"
|
||||
"chatplus/utils"
|
||||
"chatplus/utils/resp"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UploadHandler struct {
|
||||
@@ -21,11 +25,46 @@ func NewUploadHandler(app *core.AppServer, db *gorm.DB, manager *oss.UploaderMan
|
||||
}
|
||||
|
||||
func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
fileURL, err := h.uploaderManager.GetUploadHandler().PutFile(c, "file")
|
||||
file, err := h.uploaderManager.GetUploadHandler().PutFile(c, "file")
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, fileURL)
|
||||
userId := h.GetLoginUserId(c)
|
||||
res := h.db.Create(&model.File{
|
||||
UserId: userId,
|
||||
Name: file.Name,
|
||||
URL: file.URL,
|
||||
Ext: file.Ext,
|
||||
Size: file.Size,
|
||||
CreatedAt: time.Time{},
|
||||
})
|
||||
if res.Error != nil || res.RowsAffected == 0 {
|
||||
resp.ERROR(c, "error with update database: "+res.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, file)
|
||||
}
|
||||
|
||||
func (h *UploadHandler) List(c *gin.Context) {
|
||||
userId := h.GetLoginUserId(c)
|
||||
var items []model.File
|
||||
var files = make([]vo.File, 0)
|
||||
h.db.Debug().Where("user_id = ?", userId).Find(&items)
|
||||
if len(items) > 0 {
|
||||
for _, v := range items {
|
||||
var file vo.File
|
||||
err := utils.CopyObject(v, &file)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
file.CreatedAt = v.CreatedAt.Unix()
|
||||
files = append(files, file)
|
||||
}
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, files)
|
||||
}
|
||||
|
||||
25
api/main.go
@@ -12,6 +12,7 @@ import (
|
||||
"chatplus/service/oss"
|
||||
"chatplus/service/payment"
|
||||
"chatplus/service/sd"
|
||||
"chatplus/service/sms"
|
||||
"chatplus/service/wx"
|
||||
"chatplus/store"
|
||||
"context"
|
||||
@@ -59,11 +60,13 @@ func main() {
|
||||
}
|
||||
debug, _ := strconv.ParseBool(os.Getenv("APP_DEBUG"))
|
||||
logger.Info("Loading config file: ", configFile)
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error("Panic Error:", err)
|
||||
}
|
||||
}()
|
||||
if !debug {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error("Panic Error:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
app := fx.New(
|
||||
// 初始化配置应用配置
|
||||
@@ -135,7 +138,7 @@ func main() {
|
||||
fx.Provide(admin.NewOrderHandler),
|
||||
|
||||
// 创建服务
|
||||
fx.Provide(service.NewAliYunSmsService),
|
||||
fx.Provide(sms.NewSendServiceManager),
|
||||
fx.Provide(func(config *types.AppConfig) *service.CaptchaService {
|
||||
return service.NewCaptchaService(config.ApiConfig)
|
||||
}),
|
||||
@@ -162,6 +165,7 @@ func main() {
|
||||
if pool.HasAvailableService() {
|
||||
pool.DownloadImages()
|
||||
pool.CheckTaskNotify()
|
||||
pool.SyncTaskProgress()
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -213,6 +217,7 @@ func main() {
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) {
|
||||
s.Engine.POST("/api/upload", h.Upload)
|
||||
s.Engine.GET("/api/upload/list", h.List)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
|
||||
group := s.Engine.Group("/api/sms/")
|
||||
@@ -235,12 +240,15 @@ func main() {
|
||||
group.POST("variation", h.Variation)
|
||||
group.GET("jobs", h.JobList)
|
||||
group.POST("remove", h.Remove)
|
||||
group.POST("notify", h.Notify)
|
||||
group.POST("publish", h.Publish)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.SdJobHandler) {
|
||||
group := s.Engine.Group("/api/sd")
|
||||
group.POST("image", h.Image)
|
||||
group.GET("jobs", h.JobList)
|
||||
group.POST("remove", h.Remove)
|
||||
group.POST("publish", h.Publish)
|
||||
}),
|
||||
|
||||
// 管理后台控制器
|
||||
@@ -367,6 +375,7 @@ func main() {
|
||||
fx.Provide(handler.NewTestHandler),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.TestHandler) {
|
||||
s.Engine.GET("/api/test", h.Test)
|
||||
s.Engine.POST("/api/test/mj", h.Mj)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
||||
err := s.Run(db)
|
||||
@@ -374,7 +383,9 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}),
|
||||
|
||||
fx.Invoke(func(h *chatimpl.ChatHandler) {
|
||||
h.Init()
|
||||
}),
|
||||
// 注册生命周期回调函数
|
||||
fx.Invoke(func(lifecycle fx.Lifecycle, lc *AppLifecycle) {
|
||||
lifecycle.Append(fx.Hook{
|
||||
|
||||
@@ -33,7 +33,7 @@ func NewBot(name string, proxy string, config types.MidJourneyConfig, service *S
|
||||
// use CDN reverse proxy
|
||||
if config.UseCDN {
|
||||
discordgo.SetEndpointDiscord(config.DiscordAPI)
|
||||
discordgo.SetEndpointCDN(config.DiscordCDN)
|
||||
discordgo.SetEndpointCDN("https://cdn.discordapp.com")
|
||||
discordgo.SetEndpointStatus(config.DiscordAPI + "/api/v2/")
|
||||
bot.MjGateway = config.DiscordGateway + "/"
|
||||
} else { // use proxy
|
||||
|
||||
@@ -11,12 +11,13 @@ import (
|
||||
// MidJourney client
|
||||
|
||||
type Client struct {
|
||||
client *req.Client
|
||||
Config types.MidJourneyConfig
|
||||
apiURL string
|
||||
client *req.Client
|
||||
Config types.MidJourneyConfig
|
||||
imgCdnURL string
|
||||
apiURL string
|
||||
}
|
||||
|
||||
func NewClient(config types.MidJourneyConfig, proxy string) *Client {
|
||||
func NewClient(config types.MidJourneyConfig, proxy string, imgCdnURL string) *Client {
|
||||
client := req.C().SetTimeout(10 * time.Second)
|
||||
var apiURL string
|
||||
// set proxy URL
|
||||
@@ -29,7 +30,7 @@ func NewClient(config types.MidJourneyConfig, proxy string) *Client {
|
||||
}
|
||||
}
|
||||
|
||||
return &Client{client: client, Config: config, apiURL: apiURL}
|
||||
return &Client{client: client, Config: config, apiURL: apiURL, imgCdnURL: imgCdnURL}
|
||||
}
|
||||
|
||||
func (c *Client) Imagine(prompt string) error {
|
||||
|
||||
174
api/service/mj/plus/client.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package plus
|
||||
|
||||
import (
|
||||
"chatplus/core/types"
|
||||
logger2 "chatplus/logger"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/imroc/req/v3"
|
||||
)
|
||||
|
||||
var logger = logger2.GetLogger()
|
||||
|
||||
// Client MidJourney Plus Client
|
||||
type Client struct {
|
||||
Config types.MidJourneyPlusConfig
|
||||
}
|
||||
|
||||
func NewClient(config types.MidJourneyPlusConfig) *Client {
|
||||
return &Client{Config: config}
|
||||
}
|
||||
|
||||
type ImageReq struct {
|
||||
BotType string `json:"botType"`
|
||||
Prompt string `json:"prompt"`
|
||||
Base64Array []interface{} `json:"base64Array,omitempty"`
|
||||
AccountFilter struct {
|
||||
InstanceId string `json:"instanceId"`
|
||||
Modes []interface{} `json:"modes"`
|
||||
Remix bool `json:"remix"`
|
||||
RemixAutoConsidered bool `json:"remixAutoConsidered"`
|
||||
} `json:"accountFilter,omitempty"`
|
||||
NotifyHook string `json:"notifyHook"`
|
||||
State string `json:"state,omitempty"`
|
||||
}
|
||||
|
||||
type ImageRes struct {
|
||||
Code int `json:"code"`
|
||||
Description string `json:"description"`
|
||||
Properties struct {
|
||||
} `json:"properties"`
|
||||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
type ErrRes struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func (c *Client) Imagine(prompt string) (ImageRes, error) {
|
||||
apiURL := fmt.Sprintf("%s/mj-fast/mj/submit/imagine", c.Config.ApiURL)
|
||||
body := ImageReq{
|
||||
BotType: "MID_JOURNEY",
|
||||
Prompt: prompt,
|
||||
NotifyHook: c.Config.NotifyURL,
|
||||
}
|
||||
var res ImageRes
|
||||
var errRes ErrRes
|
||||
r, err := req.C().R().
|
||||
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
|
||||
SetBody(body).
|
||||
SetSuccessResult(&res).
|
||||
SetErrorResult(&errRes).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
errStr, _ := io.ReadAll(r.Body)
|
||||
return ImageRes{}, fmt.Errorf("请求 API 出错:%v,%v", err, string(errStr))
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
return ImageRes{}, fmt.Errorf("API 返回错误:%s", errRes.Error.Message)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Upscale 放大指定的图片
|
||||
func (c *Client) Upscale(index int, messageId string, hash string) (ImageRes, error) {
|
||||
body := map[string]string{
|
||||
"customId": fmt.Sprintf("MJ::JOB::upsample::%d::%s", index, hash),
|
||||
"taskId": messageId,
|
||||
"notifyHook": c.Config.NotifyURL,
|
||||
}
|
||||
apiURL := fmt.Sprintf("%s/mj/submit/action", c.Config.ApiURL)
|
||||
var res ImageRes
|
||||
var errRes ErrRes
|
||||
r, err := req.C().R().
|
||||
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
|
||||
SetBody(body).
|
||||
SetSuccessResult(&res).
|
||||
SetErrorResult(&errRes).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
return ImageRes{}, fmt.Errorf("请求 API 出错:%v", err)
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
return ImageRes{}, fmt.Errorf("API 返回错误:%s", errRes.Error.Message)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Variation 以指定的图片的视角进行变换再创作,注意需要在对应的频道中关闭 Remix 变换,否则 Variation 指令将不会生效
|
||||
func (c *Client) Variation(index int, messageId string, hash string) (ImageRes, error) {
|
||||
body := map[string]string{
|
||||
"customId": fmt.Sprintf("MJ::JOB::variation::%d::%s", index, hash),
|
||||
"taskId": messageId,
|
||||
"notifyHook": c.Config.NotifyURL,
|
||||
}
|
||||
apiURL := fmt.Sprintf("%s/mj/submit/action", c.Config.ApiURL)
|
||||
var res ImageRes
|
||||
var errRes ErrRes
|
||||
r, err := req.C().R().
|
||||
SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
|
||||
SetBody(body).
|
||||
SetSuccessResult(&res).
|
||||
SetErrorResult(&errRes).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
return ImageRes{}, fmt.Errorf("请求 API 出错:%v", err)
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
return ImageRes{}, fmt.Errorf("API 返回错误:%s", errRes.Error.Message)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type QueryRes struct {
|
||||
Action string `json:"action"`
|
||||
Buttons []struct {
|
||||
CustomId string `json:"customId"`
|
||||
Emoji string `json:"emoji"`
|
||||
Label string `json:"label"`
|
||||
Style int `json:"style"`
|
||||
Type int `json:"type"`
|
||||
} `json:"buttons"`
|
||||
Description string `json:"description"`
|
||||
FailReason string `json:"failReason"`
|
||||
FinishTime int `json:"finishTime"`
|
||||
Id string `json:"id"`
|
||||
ImageUrl string `json:"imageUrl"`
|
||||
Progress string `json:"progress"`
|
||||
Prompt string `json:"prompt"`
|
||||
PromptEn string `json:"promptEn"`
|
||||
Properties struct {
|
||||
} `json:"properties"`
|
||||
StartTime int `json:"startTime"`
|
||||
State string `json:"state"`
|
||||
Status string `json:"status"`
|
||||
SubmitTime int `json:"submitTime"`
|
||||
}
|
||||
|
||||
func (c *Client) QueryTask(taskId string) (QueryRes, error) {
|
||||
apiURL := fmt.Sprintf("%s/mj/task/%s/fetch", c.Config.ApiURL, taskId)
|
||||
var res QueryRes
|
||||
r, err := req.C().R().SetHeader("Authorization", "Bearer "+c.Config.ApiKey).
|
||||
SetSuccessResult(&res).
|
||||
Get(apiURL)
|
||||
|
||||
if err != nil {
|
||||
return QueryRes{}, err
|
||||
}
|
||||
|
||||
if r.IsErrorState() {
|
||||
return QueryRes{}, errors.New("error status:" + r.Status)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
167
api/service/mj/plus/service.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package plus
|
||||
|
||||
import (
|
||||
"chatplus/core/types"
|
||||
"chatplus/store"
|
||||
"chatplus/store/model"
|
||||
"chatplus/utils"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Service MJ 绘画服务
|
||||
type Service struct {
|
||||
Name string // service Name
|
||||
Client *Client // MJ Client
|
||||
taskQueue *store.RedisQueue
|
||||
notifyQueue *store.RedisQueue
|
||||
db *gorm.DB
|
||||
maxHandleTaskNum int32 // max task number current service can handle
|
||||
HandledTaskNum int32 // already handled task number
|
||||
taskStartTimes map[int]time.Time // task start time, to check if the task is timeout
|
||||
taskTimeout int64
|
||||
}
|
||||
|
||||
func NewService(name string, taskQueue *store.RedisQueue, notifyQueue *store.RedisQueue, maxTaskNum int32, timeout int64, db *gorm.DB, client *Client) *Service {
|
||||
return &Service{
|
||||
Name: name,
|
||||
db: db,
|
||||
taskQueue: taskQueue,
|
||||
notifyQueue: notifyQueue,
|
||||
Client: client,
|
||||
taskTimeout: timeout,
|
||||
maxHandleTaskNum: maxTaskNum,
|
||||
taskStartTimes: make(map[int]time.Time, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) Run() {
|
||||
logger.Infof("Starting MidJourney job consumer for %s", s.Name)
|
||||
for {
|
||||
s.checkTasks()
|
||||
if !s.canHandleTask() {
|
||||
// current service is full, can not handle more task
|
||||
// waiting for running task finish
|
||||
time.Sleep(time.Second * 3)
|
||||
continue
|
||||
}
|
||||
|
||||
var task types.MjTask
|
||||
err := s.taskQueue.LPop(&task)
|
||||
if err != nil {
|
||||
logger.Errorf("taking task with error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// if it's reference message, check if it's this channel's message
|
||||
if task.ChannelId != "" && task.ChannelId != s.Name {
|
||||
logger.Debugf("handle other service task, name: %s, channel_id: %s, drop it.", s.Name, task.ChannelId)
|
||||
s.taskQueue.RPush(task)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Infof("%s handle a new MidJourney task: %+v", s.Name, task)
|
||||
var res ImageRes
|
||||
switch task.Type {
|
||||
case types.TaskImage:
|
||||
index := strings.Index(task.Prompt, " ")
|
||||
res, err = s.Client.Imagine(task.Prompt[index+1:])
|
||||
break
|
||||
case types.TaskUpscale:
|
||||
res, err = s.Client.Upscale(task.Index, task.MessageId, task.MessageHash)
|
||||
break
|
||||
case types.TaskVariation:
|
||||
res, err = s.Client.Variation(task.Index, task.MessageId, task.MessageHash)
|
||||
}
|
||||
|
||||
if err != nil || (res.Code != 1 && res.Code != 22) {
|
||||
logger.Error("绘画任务执行失败:", err)
|
||||
// update the task progress
|
||||
s.db.Model(&model.MidJourneyJob{Id: uint(task.Id)}).UpdateColumn("progress", -1)
|
||||
// 任务失败,通知前端
|
||||
s.notifyQueue.RPush(task.UserId)
|
||||
// restore img_call quota
|
||||
if task.Type.String() != types.TaskUpscale.String() {
|
||||
s.db.Model(&model.User{}).Where("id = ?", task.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1))
|
||||
}
|
||||
|
||||
// TODO: 任务提交失败,加入队列重试
|
||||
continue
|
||||
}
|
||||
logger.Infof("任务提交成功:%+v", res)
|
||||
// lock the task until the execute timeout
|
||||
s.taskStartTimes[task.Id] = time.Now()
|
||||
atomic.AddInt32(&s.HandledTaskNum, 1)
|
||||
// 更新任务 ID/频道
|
||||
s.db.Model(&model.MidJourneyJob{}).Where("id = ?", task.Id).UpdateColumns(map[string]interface{}{
|
||||
"task_id": res.Result,
|
||||
"channel_id": s.Name,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// check if current service instance can handle more task
|
||||
func (s *Service) canHandleTask() bool {
|
||||
handledNum := atomic.LoadInt32(&s.HandledTaskNum)
|
||||
return handledNum < s.maxHandleTaskNum
|
||||
}
|
||||
|
||||
// remove the expired tasks
|
||||
func (s *Service) checkTasks() {
|
||||
for k, t := range s.taskStartTimes {
|
||||
if time.Now().Unix()-t.Unix() > s.taskTimeout {
|
||||
delete(s.taskStartTimes, k)
|
||||
atomic.AddInt32(&s.HandledTaskNum, -1)
|
||||
// delete task from database
|
||||
s.db.Delete(&model.MidJourneyJob{Id: uint(k)}, "progress < 100")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CBReq struct {
|
||||
Id string `json:"id"`
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
Prompt string `json:"prompt"`
|
||||
PromptEn string `json:"promptEn"`
|
||||
Description string `json:"description"`
|
||||
SubmitTime int64 `json:"submitTime"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
FinishTime int64 `json:"finishTime"`
|
||||
Progress string `json:"progress"`
|
||||
ImageUrl string `json:"imageUrl"`
|
||||
FailReason interface{} `json:"failReason"`
|
||||
Properties struct {
|
||||
FinalPrompt string `json:"finalPrompt"`
|
||||
} `json:"properties"`
|
||||
}
|
||||
|
||||
func (s *Service) Notify(data CBReq, job model.MidJourneyJob) error {
|
||||
|
||||
job.Progress = utils.IntValue(strings.Replace(data.Progress, "%", "", 1), 0)
|
||||
job.Prompt = data.Properties.FinalPrompt
|
||||
if data.ImageUrl != "" {
|
||||
job.OrgURL = data.ImageUrl
|
||||
}
|
||||
job.UseProxy = true
|
||||
job.MessageId = data.Id
|
||||
logger.Debugf("JOB: %+v", job)
|
||||
res := s.db.Updates(&job)
|
||||
if res.Error != nil {
|
||||
return fmt.Errorf("error with update job: %v", res.Error)
|
||||
}
|
||||
|
||||
if data.Status == "SUCCESS" {
|
||||
// release lock task
|
||||
atomic.AddInt32(&s.HandledTaskNum, -1)
|
||||
}
|
||||
|
||||
s.notifyQueue.RPush(job.UserId)
|
||||
return nil
|
||||
}
|
||||
@@ -2,11 +2,15 @@ package mj
|
||||
|
||||
import (
|
||||
"chatplus/core/types"
|
||||
"chatplus/service/mj/plus"
|
||||
"chatplus/service/oss"
|
||||
"chatplus/store"
|
||||
"chatplus/store/model"
|
||||
"chatplus/utils"
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -14,7 +18,7 @@ import (
|
||||
|
||||
// ServicePool Mj service pool
|
||||
type ServicePool struct {
|
||||
services []*Service
|
||||
services []interface{}
|
||||
taskQueue *store.RedisQueue
|
||||
notifyQueue *store.RedisQueue
|
||||
db *gorm.DB
|
||||
@@ -23,37 +27,56 @@ type ServicePool struct {
|
||||
}
|
||||
|
||||
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, appConfig *types.AppConfig) *ServicePool {
|
||||
services := make([]*Service, 0)
|
||||
services := make([]interface{}, 0)
|
||||
taskQueue := store.NewRedisQueue("MidJourney_Task_Queue", redisCli)
|
||||
notifyQueue := store.NewRedisQueue("MidJourney_Notify_Queue", redisCli)
|
||||
// create mj client and service
|
||||
for k, config := range appConfig.MjConfigs {
|
||||
|
||||
for k, config := range appConfig.MjPlusConfigs {
|
||||
if config.Enabled == false {
|
||||
continue
|
||||
}
|
||||
// create mj client
|
||||
client := NewClient(config, appConfig.ProxyURL)
|
||||
|
||||
name := fmt.Sprintf("MjService-%d", k)
|
||||
// create mj service
|
||||
service := NewService(name, taskQueue, notifyQueue, 4, 600, db, client)
|
||||
botName := fmt.Sprintf("MjBot-%d", k)
|
||||
bot, err := NewBot(botName, appConfig.ProxyURL, config, service)
|
||||
if err != nil {
|
||||
continue
|
||||
if config.ApiURL != "https://gpt.bemore.lol" && config.ApiURL != "https://api.chat-plus.net" {
|
||||
config.ApiURL = "https://api.chat-plus.net"
|
||||
}
|
||||
|
||||
err = bot.Run()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// run mj service
|
||||
client := plus.NewClient(config)
|
||||
name := fmt.Sprintf("mj-service-plus-%d", k)
|
||||
servicePlus := plus.NewService(name, taskQueue, notifyQueue, 10, 600, db, client)
|
||||
go func() {
|
||||
service.Run()
|
||||
servicePlus.Run()
|
||||
}()
|
||||
services = append(services, servicePlus)
|
||||
}
|
||||
|
||||
services = append(services, service)
|
||||
if len(services) == 0 {
|
||||
// create mj client and service
|
||||
for k, config := range appConfig.MjConfigs {
|
||||
if config.Enabled == false {
|
||||
continue
|
||||
}
|
||||
// create mj client
|
||||
client := NewClient(config, appConfig.ProxyURL, appConfig.ImgCdnURL)
|
||||
|
||||
name := fmt.Sprintf("MjService-%d", k)
|
||||
// create mj service
|
||||
service := NewService(name, taskQueue, notifyQueue, 4, 600, db, client)
|
||||
botName := fmt.Sprintf("MjBot-%d", k)
|
||||
bot, err := NewBot(botName, appConfig.ProxyURL, config, service)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
err = bot.Run()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// run mj service
|
||||
go func() {
|
||||
service.Run()
|
||||
}()
|
||||
|
||||
services = append(services, service)
|
||||
}
|
||||
}
|
||||
|
||||
return &ServicePool{
|
||||
@@ -94,7 +117,24 @@ func (p *ServicePool) DownloadImages() {
|
||||
|
||||
// download images
|
||||
for _, v := range items {
|
||||
imgURL, err := p.uploaderManager.GetUploadHandler().PutImg(v.OrgURL, true)
|
||||
if v.OrgURL == "" {
|
||||
continue
|
||||
}
|
||||
var imgURL string
|
||||
var err error
|
||||
if v.UseProxy {
|
||||
if servicePlus := p.getServicePlus(v.ChannelId); servicePlus != nil {
|
||||
task, _ := servicePlus.Client.QueryTask(v.TaskId)
|
||||
if task.ImageUrl != "" {
|
||||
imgURL, err = p.uploaderManager.GetUploadHandler().PutImg(task.ImageUrl, false)
|
||||
}
|
||||
if len(task.Buttons) > 0 {
|
||||
v.Hash = getImageHash(task.Buttons[0].CustomId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
imgURL, err = p.uploaderManager.GetUploadHandler().PutImg(v.OrgURL, true)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error("error with download image: ", err)
|
||||
continue
|
||||
@@ -125,3 +165,101 @@ func (p *ServicePool) PushTask(task types.MjTask) {
|
||||
func (p *ServicePool) HasAvailableService() bool {
|
||||
return len(p.services) > 0
|
||||
}
|
||||
|
||||
func (p *ServicePool) Notify(data plus.CBReq) error {
|
||||
logger.Infof("收到任务回调:%+v", data)
|
||||
var job model.MidJourneyJob
|
||||
res := p.db.Where("task_id = ?", data.Id).First(&job)
|
||||
if res.Error != nil {
|
||||
return fmt.Errorf("非法任务:%s", data.Id)
|
||||
}
|
||||
|
||||
// 任务已经拉取完成
|
||||
if job.Progress == 100 {
|
||||
return nil
|
||||
}
|
||||
if servicePlus := p.getServicePlus(job.ChannelId); servicePlus != nil {
|
||||
return servicePlus.Notify(data, job)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncTaskProgress 异步拉取任务
|
||||
func (p *ServicePool) SyncTaskProgress() {
|
||||
go func() {
|
||||
var items []model.MidJourneyJob
|
||||
for {
|
||||
res := p.db.Where("progress < ?", 100).Find(&items)
|
||||
if res.Error != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, v := range items {
|
||||
// 30 分钟还没完成的任务直接删除
|
||||
if time.Now().Sub(v.CreatedAt) > time.Minute*30 {
|
||||
p.db.Delete(&v)
|
||||
// 非放大任务,退回绘图次数
|
||||
if v.Type != types.TaskUpscale.String() {
|
||||
p.db.Model(&model.User{}).Where("id = ?", v.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(v.ChannelId, "mj-service-plus") {
|
||||
continue
|
||||
}
|
||||
|
||||
if servicePlus := p.getServicePlus(v.ChannelId); servicePlus != nil {
|
||||
task, err := servicePlus.Client.QueryTask(v.TaskId)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(task.Buttons) > 0 {
|
||||
v.Hash = getImageHash(task.Buttons[0].CustomId)
|
||||
}
|
||||
oldProgress := v.Progress
|
||||
v.Progress = utils.IntValue(strings.Replace(task.Progress, "%", "", 1), 0)
|
||||
v.Prompt = task.PromptEn
|
||||
if task.ImageUrl != "" {
|
||||
v.OrgURL = task.ImageUrl
|
||||
}
|
||||
v.UseProxy = true
|
||||
v.MessageId = task.Id
|
||||
|
||||
p.db.Updates(&v)
|
||||
|
||||
if task.Status == "SUCCESS" {
|
||||
// release lock task
|
||||
atomic.AddInt32(&servicePlus.HandledTaskNum, -1)
|
||||
}
|
||||
// 通知前端更新任务进度
|
||||
if oldProgress != v.Progress {
|
||||
p.notifyQueue.RPush(v.UserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *ServicePool) getServicePlus(name string) *plus.Service {
|
||||
for _, s := range p.services {
|
||||
if servicePlus, ok := s.(*plus.Service); ok {
|
||||
if servicePlus.Name == name {
|
||||
return servicePlus
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getImageHash(action string) string {
|
||||
split := strings.Split(action, "::")
|
||||
if len(split) > 5 {
|
||||
return split[4]
|
||||
}
|
||||
return split[len(split)-1]
|
||||
}
|
||||
|
||||
@@ -58,8 +58,6 @@ func (s *Service) Run() {
|
||||
// if it's reference message, check if it's this channel's message
|
||||
if task.ChannelId != "" && task.ChannelId != s.client.Config.ChanelId {
|
||||
s.taskQueue.RPush(task)
|
||||
s.db.Model(&model.MidJourneyJob{Id: uint(task.Id)}).UpdateColumn("progress", -1)
|
||||
s.notifyQueue.RPush(task.UserId)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -83,7 +81,9 @@ func (s *Service) Run() {
|
||||
s.db.Model(&model.MidJourneyJob{Id: uint(task.Id)}).UpdateColumn("progress", -1)
|
||||
s.notifyQueue.RPush(task.UserId)
|
||||
// restore img_call quota
|
||||
s.db.Model(&model.User{}).Where("id = ?", task.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1))
|
||||
if task.Type.String() != types.TaskUpscale.String() {
|
||||
s.db.Model(&model.User{}).Where("id = ?", task.UserId).UpdateColumn("img_calls", gorm.Expr("img_calls + ?", 1))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ func (s *Service) Notify(data CBReq) {
|
||||
job.OrgURL = data.Image.URL
|
||||
if s.client.Config.UseCDN {
|
||||
job.UseProxy = true
|
||||
job.ImgURL = strings.ReplaceAll(data.Image.URL, "https://cdn.discordapp.com", s.client.Config.DiscordCDN)
|
||||
job.ImgURL = strings.ReplaceAll(data.Image.URL, "https://cdn.discordapp.com", s.client.imgCdnURL)
|
||||
}
|
||||
|
||||
res = s.db.Updates(&job)
|
||||
|
||||
@@ -44,16 +44,16 @@ func NewAliYunOss(appConfig *types.AppConfig) (*AliYunOss, error) {
|
||||
|
||||
}
|
||||
|
||||
func (s AliYunOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
||||
func (s AliYunOss) PutFile(ctx *gin.Context, name string) (File, error) {
|
||||
// 解析表单
|
||||
file, err := ctx.FormFile(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return File{}, err
|
||||
}
|
||||
// 打开上传文件
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return File{}, err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
@@ -62,10 +62,15 @@ func (s AliYunOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
||||
// 上传文件
|
||||
err = s.bucket.PutObject(objectKey, src)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return File{}, err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", s.config.Domain, objectKey), nil
|
||||
return File{
|
||||
Name: file.Filename,
|
||||
URL: fmt.Sprintf("%s/%s", s.config.Domain, objectKey),
|
||||
Ext: fileExt,
|
||||
Size: file.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s AliYunOss) PutImg(imageURL string, useProxy bool) (string, error) {
|
||||
|
||||
@@ -23,23 +23,29 @@ func NewLocalStorage(config *types.AppConfig) LocalStorage {
|
||||
}
|
||||
}
|
||||
|
||||
func (s LocalStorage) PutFile(ctx *gin.Context, name string) (string, error) {
|
||||
func (s LocalStorage) PutFile(ctx *gin.Context, name string) (File, error) {
|
||||
file, err := ctx.FormFile(name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with get form: %v", err)
|
||||
return File{}, fmt.Errorf("error with get form: %v", err)
|
||||
}
|
||||
|
||||
filePath, err := utils.GenUploadPath(s.config.BasePath, file.Filename)
|
||||
path, err := utils.GenUploadPath(s.config.BasePath, file.Filename)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with generate filename: %s", err.Error())
|
||||
return File{}, fmt.Errorf("error with generate filename: %s", err.Error())
|
||||
}
|
||||
// 将文件保存到指定路径
|
||||
err = ctx.SaveUploadedFile(file, filePath)
|
||||
err = ctx.SaveUploadedFile(file, path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with save upload file: %s", err.Error())
|
||||
return File{}, fmt.Errorf("error with save upload file: %s", err.Error())
|
||||
}
|
||||
|
||||
return utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, filePath), nil
|
||||
ext := filepath.Ext(file.Filename)
|
||||
return File{
|
||||
Name: file.Filename,
|
||||
URL: utils.GenUploadUrl(s.config.BasePath, s.config.BaseURL, path),
|
||||
Ext: ext,
|
||||
Size: file.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s LocalStorage) PutImg(imageURL string, useProxy bool) (string, error) {
|
||||
|
||||
@@ -65,15 +65,15 @@ func (s MiniOss) PutImg(imageURL string, useProxy bool) (string, error) {
|
||||
return fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key), nil
|
||||
}
|
||||
|
||||
func (s MiniOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
||||
func (s MiniOss) PutFile(ctx *gin.Context, name string) (File, error) {
|
||||
file, err := ctx.FormFile(name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with get form: %v", err)
|
||||
return File{}, fmt.Errorf("error with get form: %v", err)
|
||||
}
|
||||
// Open the uploaded file
|
||||
fileReader, err := file.Open()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error opening file: %v", err)
|
||||
return File{}, fmt.Errorf("error opening file: %v", err)
|
||||
}
|
||||
defer fileReader.Close()
|
||||
|
||||
@@ -83,10 +83,15 @@ func (s MiniOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
||||
ContentType: file.Header.Get("Content-Type"),
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error uploading to MinIO: %v", err)
|
||||
return File{}, fmt.Errorf("error uploading to MinIO: %v", err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key), nil
|
||||
return File{
|
||||
Name: file.Filename,
|
||||
URL: fmt.Sprintf("%s/%s/%s", s.config.Domain, s.config.Bucket, info.Key),
|
||||
Ext: fileExt,
|
||||
Size: file.Size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s MiniOss) Delete(fileURL string) error {
|
||||
|
||||
@@ -50,16 +50,16 @@ func NewQiNiuOss(appConfig *types.AppConfig) QinNiuOss {
|
||||
}
|
||||
}
|
||||
|
||||
func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
||||
func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (File, error) {
|
||||
// 解析表单
|
||||
file, err := ctx.FormFile(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return File{}, err
|
||||
}
|
||||
// 打开上传文件
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return File{}, err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
@@ -70,10 +70,16 @@ func (s QinNiuOss) PutFile(ctx *gin.Context, name string) (string, error) {
|
||||
extra := storage.PutExtra{}
|
||||
err = s.uploader.Put(ctx, &ret, s.putPolicy.UploadToken(s.mac), key, src, file.Size, &extra)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return File{}, err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", s.config.Domain, ret.Key), nil
|
||||
return File{
|
||||
Name: file.Filename,
|
||||
URL: fmt.Sprintf("%s/%s", s.config.Domain, ret.Key),
|
||||
Ext: fileExt,
|
||||
Size: file.Size,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (s QinNiuOss) PutImg(imageURL string, useProxy bool) (string, error) {
|
||||
|
||||
@@ -2,8 +2,19 @@ package oss
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
const Local = "LOCAL"
|
||||
const Minio = "MINIO"
|
||||
const QiNiu = "QINIU"
|
||||
const AliYun = "ALIYUN"
|
||||
|
||||
type File struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
URL string `json:"url"`
|
||||
Ext string `json:"ext"`
|
||||
}
|
||||
type Uploader interface {
|
||||
PutFile(ctx *gin.Context, name string) (string, error)
|
||||
PutFile(ctx *gin.Context, name string) (File, error)
|
||||
PutImg(imageURL string, useProxy bool) (string, error)
|
||||
Delete(fileURL string) error
|
||||
}
|
||||
|
||||
@@ -9,11 +9,6 @@ type UploaderManager struct {
|
||||
handler Uploader
|
||||
}
|
||||
|
||||
const Local = "LOCAL"
|
||||
const Minio = "MINIO"
|
||||
const QiNiu = "QINIU"
|
||||
const AliYun = "ALIYUN"
|
||||
|
||||
func NewUploaderManager(config *types.AppConfig) (*UploaderManager, error) {
|
||||
active := Local
|
||||
if config.OSS.Active != "" {
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"chatplus/core/types"
|
||||
"chatplus/utils"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -17,14 +19,14 @@ import (
|
||||
type HuPiPayService struct {
|
||||
appId string
|
||||
appSecret string
|
||||
host string
|
||||
apiURL string
|
||||
}
|
||||
|
||||
func NewHuPiPay(config *types.AppConfig) *HuPiPayService {
|
||||
return &HuPiPayService{
|
||||
appId: config.HuPiPayConfig.AppId,
|
||||
appSecret: config.HuPiPayConfig.AppSecret,
|
||||
host: config.HuPiPayConfig.PayURL,
|
||||
apiURL: config.HuPiPayConfig.ApiURL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +44,16 @@ type HuPiPayReq struct {
|
||||
NonceStr string `json:"nonce_str"`
|
||||
}
|
||||
|
||||
type HuPiResp struct {
|
||||
Openid interface{} `json:"openid"`
|
||||
UrlQrcode string `json:"url_qrcode"`
|
||||
URL string `json:"url"`
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg,omitempty"`
|
||||
}
|
||||
|
||||
// Pay 执行支付请求操作
|
||||
func (s *HuPiPayService) Pay(params HuPiPayReq) (string, error) {
|
||||
func (s *HuPiPayService) Pay(params HuPiPayReq) (HuPiResp, error) {
|
||||
data := url.Values{}
|
||||
simple := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
params.AppId = s.appId
|
||||
@@ -55,38 +65,98 @@ func (s *HuPiPayService) Pay(params HuPiPayReq) (string, error) {
|
||||
for k, v := range m {
|
||||
data.Add(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
encode = utils.JsonEncode(params)
|
||||
m = make(map[string]string)
|
||||
_ = utils.JsonDecode(encode, &m)
|
||||
data.Add("hash", s.Sign(m))
|
||||
resp, err := http.PostForm(s.host, data)
|
||||
// 生成签名
|
||||
data.Add("hash", s.Sign(data))
|
||||
// 发送支付请求
|
||||
apiURL := fmt.Sprintf("%s/payment/do.html", s.apiURL)
|
||||
resp, err := http.PostForm(apiURL, data)
|
||||
if err != nil {
|
||||
return "error", err
|
||||
return HuPiResp{}, fmt.Errorf("error with requst api: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
all, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "error", err
|
||||
return HuPiResp{}, fmt.Errorf("error with reading response: %v", err)
|
||||
}
|
||||
return string(all), err
|
||||
|
||||
var res HuPiResp
|
||||
err = utils.JsonDecode(string(all), &res)
|
||||
if err != nil {
|
||||
return HuPiResp{}, fmt.Errorf("error with decode payment result: %v", err)
|
||||
}
|
||||
|
||||
if res.ErrCode != 0 {
|
||||
return HuPiResp{}, fmt.Errorf("error with generate pay url: %s", res.ErrMsg)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Sign 签名方法
|
||||
func (s *HuPiPayService) Sign(params map[string]string) string {
|
||||
var data string
|
||||
keys := make([]string, 0, 0)
|
||||
func (s *HuPiPayService) Sign(params url.Values) string {
|
||||
params.Del(`Sign`)
|
||||
var keys = make([]string, 0, 0)
|
||||
for key := range params {
|
||||
keys = append(keys, key)
|
||||
if params.Get(key) != `` {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
//拼接
|
||||
for _, k := range keys {
|
||||
data = fmt.Sprintf("%s%s=%s&", data, k, params[k])
|
||||
|
||||
var pList = make([]string, 0, 0)
|
||||
for _, key := range keys {
|
||||
var value = strings.TrimSpace(params.Get(key))
|
||||
if len(value) > 0 {
|
||||
pList = append(pList, key+"="+value)
|
||||
}
|
||||
}
|
||||
var src = strings.Join(pList, "&")
|
||||
src += s.appSecret
|
||||
|
||||
md5bs := md5.Sum([]byte(src))
|
||||
return hex.EncodeToString(md5bs[:])
|
||||
}
|
||||
|
||||
// Check 校验订单状态
|
||||
func (s *HuPiPayService) Check(tradeNo string) error {
|
||||
data := url.Values{}
|
||||
data.Add("appid", s.appId)
|
||||
data.Add("open_order_id", tradeNo)
|
||||
stamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
data.Add("time", stamp)
|
||||
data.Add("nonce_str", stamp)
|
||||
data.Add("hash", s.Sign(data))
|
||||
|
||||
apiURL := fmt.Sprintf("%s/payment/query.html", s.apiURL)
|
||||
resp, err := http.PostForm(apiURL, data)
|
||||
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 {
|
||||
return fmt.Errorf("error with reading response: %v", err)
|
||||
}
|
||||
|
||||
var r struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
Data struct {
|
||||
Status string `json:"status"`
|
||||
OpenOrderId string `json:"open_order_id"`
|
||||
} `json:"data,omitempty"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
err = utils.JsonDecode(string(body), &r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error with decode response: %v", err)
|
||||
}
|
||||
|
||||
if r.ErrCode == 0 && r.Data.Status == "OD" {
|
||||
return nil
|
||||
} else {
|
||||
logger.Debugf("%+v", r)
|
||||
return errors.New("order not paid:" + r.ErrMsg)
|
||||
}
|
||||
data = strings.Trim(data, "&")
|
||||
data = fmt.Sprintf("%s%s", data, s.appSecret)
|
||||
m := md5.New()
|
||||
m.Write([]byte(data))
|
||||
sign := fmt.Sprintf("%x", m.Sum(nil))
|
||||
return sign
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"chatplus/utils"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -36,7 +37,7 @@ type JPayReps struct {
|
||||
Qrcode string `json:"qrcode"`
|
||||
ReturnCode int `json:"return_code"`
|
||||
ReturnMsg string `json:"return_msg"`
|
||||
Sign string `json:"sign"`
|
||||
Sign string `json:"Sign"`
|
||||
TotalFee string `json:"total_fee"`
|
||||
}
|
||||
|
||||
@@ -55,10 +56,11 @@ func (js *PayJS) Pay(param JPayReq) JPayReps {
|
||||
}
|
||||
p.Add("mchid", js.config.AppId)
|
||||
|
||||
p.Add("sign", sign(p, js.config.PrivateKey))
|
||||
p.Add("Sign", js.sign(p))
|
||||
|
||||
cli := http.Client{}
|
||||
r, err := cli.PostForm(js.config.ApiURL, p)
|
||||
apiURL := fmt.Sprintf("%s/api/native", js.config.ApiURL)
|
||||
r, err := cli.PostForm(apiURL, p)
|
||||
if err != nil {
|
||||
return JPayReps{ReturnMsg: err.Error()}
|
||||
}
|
||||
@@ -76,8 +78,8 @@ func (js *PayJS) Pay(param JPayReq) JPayReps {
|
||||
return data
|
||||
}
|
||||
|
||||
func sign(params url.Values, priKey string) string {
|
||||
params.Del(`sign`)
|
||||
func (js *PayJS) sign(params url.Values) string {
|
||||
params.Del(`Sign`)
|
||||
var keys = make([]string, 0, 0)
|
||||
for key := range params {
|
||||
if params.Get(key) != `` {
|
||||
@@ -94,9 +96,45 @@ func sign(params url.Values, priKey string) string {
|
||||
}
|
||||
}
|
||||
var src = strings.Join(pList, "&")
|
||||
src += "&key=" + priKey
|
||||
src += "&key=" + js.config.PrivateKey
|
||||
|
||||
md5bs := md5.Sum([]byte(src))
|
||||
md5res := hex.EncodeToString(md5bs[:])
|
||||
return strings.ToUpper(md5res)
|
||||
}
|
||||
|
||||
// Check 查询订单支付状态
|
||||
// @param tradeNo 支付平台交易 ID
|
||||
func (js *PayJS) Check(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 {
|
||||
return fmt.Errorf("error with reading response: %v", err)
|
||||
}
|
||||
|
||||
var r struct {
|
||||
ReturnCode int `json:"return_code"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
err = utils.JsonDecode(string(body), &r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error with decode response: %v", err)
|
||||
}
|
||||
|
||||
if r.ReturnCode == 1 && r.Status == 1 {
|
||||
return nil
|
||||
} else {
|
||||
return errors.New("order not paid")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package service
|
||||
package sms
|
||||
|
||||
import (
|
||||
"chatplus/core/types"
|
||||
@@ -7,22 +7,23 @@ import (
|
||||
)
|
||||
|
||||
type AliYunSmsService struct {
|
||||
config *types.AliYunSmsConfig
|
||||
config *types.SmsConfigAli
|
||||
client *dysmsapi.Client
|
||||
}
|
||||
|
||||
func NewAliYunSmsService(config *types.AppConfig) (*AliYunSmsService, error) {
|
||||
func NewAliYunSmsService(appConfig *types.AppConfig) (*AliYunSmsService, error) {
|
||||
config := &appConfig.SMS.Ali
|
||||
// 创建阿里云短信客户端
|
||||
client, err := dysmsapi.NewClientWithAccessKey(
|
||||
"cn-hangzhou",
|
||||
config.SmsConfig.AccessKey,
|
||||
config.SmsConfig.AccessSecret)
|
||||
config.AccessKey,
|
||||
config.AccessSecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create client: %v", err)
|
||||
}
|
||||
|
||||
return &AliYunSmsService{
|
||||
config: &config.SmsConfig,
|
||||
config: config,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
@@ -46,8 +47,7 @@ func (s *AliYunSmsService) SendVerifyCode(mobile string, code int) error {
|
||||
if response.Code != "OK" {
|
||||
return fmt.Errorf("failed to send SMS:%v", response.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ SmsService = &AliYunSmsService{}
|
||||
var _ Service = &AliYunSmsService{}
|
||||
72
api/service/sms/bao.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"chatplus/core/types"
|
||||
"chatplus/utils"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BaoSmsService struct {
|
||||
config *types.SmsConfigBao
|
||||
}
|
||||
|
||||
func NewSmsBaoSmsService(appConfig *types.AppConfig) *BaoSmsService {
|
||||
config := appConfig.SMS.Bao
|
||||
if config.Domain == "" { // use default domain
|
||||
config.Domain = "api.smsbao.com"
|
||||
logger.Infof("Using default domain for SMS-BAO: %s", config.Domain)
|
||||
}
|
||||
return &BaoSmsService{
|
||||
config: &config,
|
||||
}
|
||||
}
|
||||
|
||||
var errMsg = map[string]string{
|
||||
"0": "短信发送成功",
|
||||
"-1": "参数不全",
|
||||
"-2": "服务器空间不支持,请确认支持curl或者fsocket,联系您的空间商解决或者更换空间",
|
||||
"30": "密码错误",
|
||||
"40": "账号不存在",
|
||||
"41": "余额不足",
|
||||
"42": "账户已过期",
|
||||
"43": "IP地址限制",
|
||||
"50": "内容含有敏感词",
|
||||
}
|
||||
|
||||
func (s *BaoSmsService) SendVerifyCode(mobile string, code int) error {
|
||||
|
||||
content := fmt.Sprintf("%s%s", s.config.Sign, s.config.CodeTemplate)
|
||||
content = strings.ReplaceAll(content, "{code}", strconv.Itoa(code))
|
||||
password := utils.Md5(s.config.Password)
|
||||
params := url.Values{}
|
||||
params.Set("u", s.config.Username)
|
||||
params.Set("p", password)
|
||||
params.Set("m", mobile)
|
||||
params.Set("c", content)
|
||||
|
||||
apiURL := fmt.Sprintf("https://%s/sms?%s", s.config.Domain, params.Encode())
|
||||
response, err := http.Get(apiURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result := string(body)
|
||||
logger.Debugf("send SmsBao result: %v", errMsg[result])
|
||||
|
||||
if result != "0" {
|
||||
return fmt.Errorf("failed to send SMS:%v", errMsg[result])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ Service = &BaoSmsService{}
|
||||
8
api/service/sms/service.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package sms
|
||||
|
||||
const Ali = "ALI"
|
||||
const Bao = "BAO"
|
||||
|
||||
type Service interface {
|
||||
SendVerifyCode(mobile string, code int) error
|
||||
}
|
||||
39
api/service/sms/service_manager.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"chatplus/core/types"
|
||||
logger2 "chatplus/logger"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ServiceManager struct {
|
||||
handler Service
|
||||
}
|
||||
|
||||
var logger = logger2.GetLogger()
|
||||
|
||||
func NewSendServiceManager(config *types.AppConfig) (*ServiceManager, error) {
|
||||
active := Ali
|
||||
if config.OSS.Active != "" {
|
||||
active = strings.ToUpper(config.SMS.Active)
|
||||
}
|
||||
var handler Service
|
||||
switch active {
|
||||
case Ali:
|
||||
client, err := NewAliYunSmsService(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handler = client
|
||||
break
|
||||
case Bao:
|
||||
handler = NewSmsBaoSmsService(config)
|
||||
break
|
||||
}
|
||||
|
||||
return &ServiceManager{handler: handler}, nil
|
||||
}
|
||||
|
||||
func (m *ServiceManager) GetService() Service {
|
||||
return m.handler
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package service
|
||||
|
||||
type SmsService interface {
|
||||
SendVerifyCode(mobile string, code int) error
|
||||
}
|
||||
1
api/service/wanx/types.go
Normal file
@@ -0,0 +1 @@
|
||||
package wanx
|
||||
@@ -64,12 +64,12 @@ func (b *Bot) messageHandler(msg *openwechat.Message) {
|
||||
msg.AppMsgType == openwechat.AppMsgTypeUrl {
|
||||
// 解析支付金额
|
||||
message := parseTransactionMessage(msg.Content)
|
||||
if message.Url != "" {
|
||||
transaction := extractTransaction(message)
|
||||
logger.Infof("解析到收款信息:%+v", transaction)
|
||||
transaction := extractTransaction(message)
|
||||
logger.Infof("解析到收款信息:%+v", transaction)
|
||||
if transaction.TransId != "" {
|
||||
var item model.Reward
|
||||
res := b.db.Where("tx_id = ?", transaction.TransId).First(&item)
|
||||
if res.Error == nil {
|
||||
if item.Id > 0 {
|
||||
logger.Error("当前交易 ID 己经存在!")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -39,14 +39,30 @@ func parseTransactionMessage(xmlData string) *Message {
|
||||
}
|
||||
break
|
||||
}
|
||||
if se.Name.Local == "weapp_path" && !strings.Contains(message.Url, "customerDetails.html") {
|
||||
if se.Name.Local == "weapp_path" || se.Name.Local == "url" {
|
||||
if err := decoder.DecodeElement(&value, &se); err == nil {
|
||||
message.Url = strings.TrimSpace(value)
|
||||
if strings.Contains(value, "trans_id=") {
|
||||
message.Url = value
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容旧版消息记录
|
||||
if message.Url == "" {
|
||||
var msg struct {
|
||||
XMLName xml.Name `xml:"msg"`
|
||||
AppMsg struct {
|
||||
Des string `xml:"des"`
|
||||
Url string `xml:"url"`
|
||||
} `xml:"appmsg"`
|
||||
}
|
||||
if err := xml.Unmarshal([]byte(xmlData), &msg); err == nil {
|
||||
message.Url = msg.AppMsg.Url
|
||||
}
|
||||
}
|
||||
return &message
|
||||
}
|
||||
|
||||
@@ -80,6 +96,10 @@ func extractTransaction(message *Message) Transaction {
|
||||
parse, err := url.Parse(message.Url)
|
||||
if err == nil {
|
||||
tx.TransId = parse.Query().Get("id")
|
||||
if tx.TransId == "" {
|
||||
tx.TransId = parse.Query().Get("trans_id")
|
||||
}
|
||||
}
|
||||
|
||||
return tx
|
||||
}
|
||||
|
||||
13
api/store/model/file.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type File struct {
|
||||
Id uint `gorm:"primarykey;column:id"`
|
||||
UserId uint
|
||||
Name string
|
||||
URL string
|
||||
Ext string
|
||||
Size int64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
@@ -16,6 +16,7 @@ type MidJourneyJob struct {
|
||||
Progress int
|
||||
Prompt string
|
||||
UseProxy bool // 是否使用反代加载图片
|
||||
Publish bool //是否发布图片到画廊
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@ type Order struct {
|
||||
BaseModel
|
||||
UserId uint
|
||||
ProductId uint
|
||||
Mobile string
|
||||
Username string
|
||||
OrderNo string
|
||||
TradeNo string
|
||||
Subject string
|
||||
Amount float64
|
||||
Status types.OrderStatus
|
||||
|
||||
@@ -11,6 +11,7 @@ type SdJob struct {
|
||||
Progress int
|
||||
Prompt string
|
||||
Params string
|
||||
Publish bool //是否发布图片到画廊
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
|
||||
11
api/store/vo/file.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package vo
|
||||
|
||||
type File struct {
|
||||
Id uint
|
||||
UserId uint `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Ext string `json:"ext"`
|
||||
Size int64 `json:"size"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
}
|
||||
@@ -16,5 +16,6 @@ type MidJourneyJob struct {
|
||||
Progress int `json:"progress"`
|
||||
Prompt string `json:"prompt"`
|
||||
UseProxy bool `json:"use_proxy"`
|
||||
Publish bool `json:"publish"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ type Order struct {
|
||||
BaseVo
|
||||
UserId uint `json:"user_id"`
|
||||
ProductId uint `json:"product_id"`
|
||||
Mobile string `json:"mobile"`
|
||||
Username string `json:"username"`
|
||||
OrderNo string `json:"order_no"`
|
||||
TradeNo string `json:"trade_no"`
|
||||
Subject string `json:"subject"`
|
||||
Amount float64 `json:"amount"`
|
||||
Status types.OrderStatus `json:"status"`
|
||||
|
||||
@@ -14,5 +14,6 @@ type SdJob struct {
|
||||
Params types.SdTaskParams `json:"params"`
|
||||
Progress int `json:"progress"`
|
||||
Prompt string `json:"prompt"`
|
||||
Publish bool `json:"publish"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
str := "7151109597841850368 一个漂亮的中国女孩,手上拿着一桶爆米花,脸上带着迷人的微笑,电影效果"
|
||||
index := strings.Index(str, " ")
|
||||
fmt.Println(str[index+1:])
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -82,3 +84,8 @@ func Sha256(data string) string {
|
||||
hashValue := hash.Sum(nil)
|
||||
return fmt.Sprintf("%x", hashValue)
|
||||
}
|
||||
|
||||
func Md5(data string) string {
|
||||
md5bs := md5.Sum([]byte(data))
|
||||
return hex.EncodeToString(md5bs[:])
|
||||
}
|
||||
|
||||
675
database/chatgpt_plus-v3.2.5.sql
Normal file
@@ -0,0 +1,675 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.1.3
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- 主机: localhost
|
||||
-- 生成日期: 2024-01-16 14:27:16
|
||||
-- 服务器版本: 8.0.33-0ubuntu0.22.04.2
|
||||
-- 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_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 '是否启用',
|
||||
`use_proxy` tinyint(1) 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',
|
||||
`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',
|
||||
`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 '是否启用模型',
|
||||
`weight` tinyint NOT NULL 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`, `weight`, `open`, `created_at`, `updated_at`) VALUES
|
||||
(1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo-16k', 0, 1, 1, 1, '2023-08-23 12:06:36', '2023-11-28 17:28:19'),
|
||||
(2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 12, 0, 1, 0, '2023-08-23 12:15:30', '2023-11-28 17:28:19'),
|
||||
(3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 8, 1, 1, 1, '2023-08-23 13:35:45', '2023-11-28 17:28:19'),
|
||||
(5, 'ChatGLM', 'ChatGLM-Std', 'chatglm_std', 7, 1, 1, 1, '2023-08-24 15:05:38', '2023-11-28 17:28:19'),
|
||||
(6, 'ChatGLM', 'ChatGLM-Lite', 'chatglm_lite', 6, 1, 1, 1, '2023-08-24 15:06:15', '2023-11-28 17:28:19'),
|
||||
(7, 'Baidu', '文心一言3.0', 'eb-instant', 4, 1, 1, 1, '2023-10-11 11:29:28', '2023-11-28 17:28:19'),
|
||||
(8, 'XunFei', '星火V1.5', 'general', 3, 1, 1, 1, '2023-10-11 15:48:30', '2023-11-28 17:28:19'),
|
||||
(9, 'XunFei', '星火V2.0', 'generalv2', 2, 1, 1, 1, '2023-10-11 15:48:45', '2023-11-28 17:28:19'),
|
||||
(10, 'Baidu', '文心一言4.0', 'completions_pro', 5, 1, 3, 1, '2023-10-25 08:31:37', '2023-11-28 17:28:19'),
|
||||
(11, 'OpenAI', 'GPT-4.0', 'gpt-4', 10, 1, 15, 0, '2023-10-25 08:45:15', '2023-11-28 17:28:19'),
|
||||
(12, 'XunFei', '星火v3.0', 'generalv3', 1, 1, 3, 1, '2023-11-23 09:20:33', '2023-11-28 17:28:19'),
|
||||
(13, 'OpenAI', 'GPT-3.5-1106', 'gpt-3.5-turbo-1106', 9, 1, 1, 0, '2023-11-24 14:05:40', '2023-11-28 17:28:19'),
|
||||
(14, 'OpenAI', 'GPT-4-preview', 'gpt-4-1106-preview', 11, 1, 1, 0, '2023-11-24 14:06:11', '2023-11-28 17:28:19'),
|
||||
(15, 'OpenAI', 'GPT-超级模型', 'gpt-4-all', 0, 1, 30, 0, '2024-01-15 11:32:52', '2024-01-15 11:32:52'),
|
||||
(16, 'OpenAI', 'MJ Prompt Generator (V6)', 'gpt-4-gizmo-g-tc0eHXdgb', 0, 1, 1, 1, '2024-01-15 14:46:35', '2024-01-15 14:53:16');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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', '2023-12-29 17:46:43'),
|
||||
(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', '{\"admin_title\":\"ChatPlus 控制台\",\"chat_call_price\":0.1,\"default_models\":[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"],\"enabled_alipay\":true,\"enabled_draw\":true,\"enabled_function\":true,\"enabled_msg\":true,\"enabled_msg_service\":false,\"enabled_register\":true,\"enabled_reward\":true,\"force_invite\":false,\"img_call_price\":0.2,\"init_calls\":1000,\"init_chat_calls\":10,\"init_img_calls\":5,\"invite_calls\":10,\"invite_chat_calls\":100,\"invite_img_calls\":50,\"models\":[\"gpt-3.5-turbo-16k\",\"gpt-3.5-turbo\",\"gpt-4\",\"gpt-4-32k\"],\"order_pay_info_text\":\"成为本站会员后每月有500次对话额度,50次 AI 绘画额度,限制下月1号解除,若在期间超过次数后可单独购买点卡。当月充值的点卡有效期可以延期到下个月底。\",\"order_pay_timeout\":1800,\"register_ways\":[\"email\",\"mobile\"],\"reward_img\":\"https://img.r9it.com/chatgpt-plus/1696824231905289.png\",\"show_demo_notice\":true,\"title\":\"ChatPlus AI 智能助手\",\"user_init_calls\":10,\"vip_month_calls\":500,\"vip_month_img_calls\":50}'),
|
||||
(2, 'chat', '{\"azure\":{\"api_url\":\"https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15\",\"max_tokens\":1024,\"temperature\":1},\"baidu\":{\"api_url\":\"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}\",\"max_tokens\":1024,\"temperature\":0.95},\"chat_gml\":{\"api_url\":\"https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke\",\"max_tokens\":1024,\"temperature\":0.95},\"context_deep\":4,\"dall_api_url\":\"http://89.117.18.9:8001/v1/images/generations\",\"dall_img_num\":1,\"enable_context\":true,\"enable_history\":true,\"open_ai\":{\"api_url\":\"https://api.fast-tunnel.one/v1/chat/completions\",\"max_tokens\":2048,\"temperature\":1},\"xun_fei\":{\"api_url\":\"wss://spark-api.xf-yun.com/{version}/chat\",\"max_tokens\":1024,\"temperature\":0.5}}');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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 '文件名',
|
||||
`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', 1),
|
||||
(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 1),
|
||||
(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', 1);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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 '邀请码',
|
||||
`reward_json` text NOT NULL COMMENT '邀请奖励',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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 '是否使用反代',
|
||||
`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',
|
||||
`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_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 '延长天数',
|
||||
`calls` int NOT NULL DEFAULT '0' COMMENT '调用次数',
|
||||
`img_calls` 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
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_products`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `calls`, `img_calls`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`) VALUES
|
||||
(1, '会员1个月', '19.90', '10.00', 30, 0, 0, 1, 2, 0, '2023-08-28 10:48:57', '2023-11-08 17:22:07'),
|
||||
(2, '会员3个月', '140.00', '30.00', 90, 0, 0, 1, 1, 0, '2023-08-28 10:52:22', '2023-08-31 16:24:31'),
|
||||
(3, '会员6个月', '290.00', '100.00', 180, 0, 0, 1, 1, 0, '2023-08-28 10:53:39', '2023-08-31 16:24:36'),
|
||||
(4, '会员12个月', '580.00', '200.00', 365, 0, 0, 1, 1, 0, '2023-08-28 10:54:15', '2023-08-31 16:24:42'),
|
||||
(5, '100次点卡', '10.00', '9.90', 0, 100, 10, 1, 3, 0, '2023-08-28 10:55:08', '2023-12-15 16:29:06'),
|
||||
(6, '200次点卡', '29.90', '20.00', 0, 200, 25, 1, 1, 0, '2023-12-15 16:55:12', '2023-12-15 16:55:12');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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 '任务进度',
|
||||
`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 '密码盐',
|
||||
`total_tokens` bigint NOT NULL DEFAULT '0' COMMENT '累计消耗 tokens',
|
||||
`tokens` bigint NOT NULL DEFAULT '0' COMMENT '当月消耗 tokens',
|
||||
`calls` int NOT NULL DEFAULT '0' COMMENT '剩余调用次数',
|
||||
`img_calls` 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`, `total_tokens`, `tokens`, `calls`, `img_calls`, `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://img.r9it.com/chatgpt-plus/1693981355719469.png', 'ueedue5l', 85072, 41235, 786, 11, 1788078636, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"elon_musk\",\"girl_friend\",\"lu_xun\",\"red_book\",\"psychiatrist\",\"translator\",\"weekly_report\",\"artist\",\"dou_yin\",\"english_trainer\",\"gpt\",\"kong_zi\",\"programmer\",\"seller\",\"steve_jobs\",\"teacher\"]', '[\"completions_pro\",\"eb-instant\",\"general\",\"generalv2\",\"chatglm_pro\",\"chatglm_lite\",\"chatglm_std\",\"gpt-3.5-turbo-16k\",\"gpt-4\",\"generalv3\",\"gpt-3.5-turbo-1106\",\"gpt-4-1106-preview\",\"gpt-4-all\"]', 1705297111, 1, '::1', '2023-06-12 16:47:17', '2024-01-15 13:38:32'),
|
||||
(91, '18575670126', '极客学长@204872', '5e4050b8dd403f593260395d9edeb9f273dbe92d15dfdd929c4a182e95da10c4', '/images/avatar/user.png', '6fj0otl8', 0, 0, 10, 0, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"completions_pro\",\"eb-instant\",\"general\",\"generalv2\",\"chatglm_pro\",\"chatglm_lite\",\"chatglm_std\",\"gpt-3.5-turbo-16k\"]', 1697184324, 1, '::1', '2023-10-13 16:01:56', '2023-12-29 17:30:27'),
|
||||
(98, '13888888888', '极客学长@851755', 'c988bb3e89df8eb7d8a4cf08fa9d0f2bca0a9e6831f9325279e46013bbb05e31', '/images/avatar/user.png', 'xie5vya7', 0, 0, 10, 5, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 0, 0, '', '2023-11-23 16:26:06', '2023-12-29 17:30:27'),
|
||||
(99, '13999999999', '极客学长@396023', 'bf47d517c17ed1ead6ff2542753f9b6a132e79c29e06c3710faf1a36d800a217', '/images/avatar/user.png', 'x1s66pwd', 0, 0, 10, 5, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 0, 0, '', '2023-11-23 16:27:20', '2023-12-29 17:30:27'),
|
||||
(100, '13777777777', '极客学长@292245', 'dcaf31b154432310bd700349e7de7e9dde2a3d6955a035a01fe527c7917a4f99', '/images/avatar/user.png', 'i8a53f8f', 0, 0, 10, 5, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 0, 0, '', '2023-11-23 16:55:45', '2023-12-29 17:30:27'),
|
||||
(102, 'yangjian102621@gmail.com', '极客学长@207163', 'd51cec21942737083943e5c3a8f063dea034e40622ac8bd47d771f13707e4676', '/images/avatar/user.png', 'eqezapgk', 202, 202, 80, 1, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 1704448377, 0, '::1', '2024-01-05 17:48:00', '2024-01-11 14:06:57');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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_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_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_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`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_user_login_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_user_login_logs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 在导出的表使用AUTO_INCREMENT
|
||||
--
|
||||
|
||||
--
|
||||
-- 使用表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=17;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_roles`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=129;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_configs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_configs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3;
|
||||
|
||||
--
|
||||
-- 使用表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_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_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=104;
|
||||
|
||||
--
|
||||
-- 使用表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 */;
|
||||
678
database/chatgpt_plus-v3.2.6.sql
Normal file
@@ -0,0 +1,678 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.1.3
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- 主机: localhost
|
||||
-- 生成日期: 2024-01-23 17:59:37
|
||||
-- 服务器版本: 8.0.33-0ubuntu0.22.04.2
|
||||
-- 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_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 '是否启用',
|
||||
`use_proxy` tinyint(1) 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',
|
||||
`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',
|
||||
`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 '是否启用模型',
|
||||
`weight` tinyint NOT NULL 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`, `weight`, `open`, `created_at`, `updated_at`) VALUES
|
||||
(1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo-16k', 0, 1, 1, 1, '2023-08-23 12:06:36', '2023-11-28 17:28:19'),
|
||||
(2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 12, 0, 1, 0, '2023-08-23 12:15:30', '2023-11-28 17:28:19'),
|
||||
(3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 8, 1, 1, 1, '2023-08-23 13:35:45', '2023-11-28 17:28:19'),
|
||||
(7, 'Baidu', '文心一言3.0', 'eb-instant', 4, 1, 1, 1, '2023-10-11 11:29:28', '2023-11-28 17:28:19'),
|
||||
(8, 'XunFei', '星火V1.5', 'general', 3, 1, 1, 1, '2023-10-11 15:48:30', '2023-11-28 17:28:19'),
|
||||
(9, 'XunFei', '星火V2.0', 'generalv2', 2, 1, 1, 1, '2023-10-11 15:48:45', '2023-11-28 17:28:19'),
|
||||
(10, 'Baidu', '文心一言4.0', 'completions_pro', 5, 1, 3, 1, '2023-10-25 08:31:37', '2023-11-28 17:28:19'),
|
||||
(11, 'OpenAI', 'GPT-4.0', 'gpt-4', 10, 1, 15, 0, '2023-10-25 08:45:15', '2023-11-28 17:28:19'),
|
||||
(12, 'XunFei', '星火v3.0', 'generalv3', 1, 1, 3, 1, '2023-11-23 09:20:33', '2023-11-28 17:28:19'),
|
||||
(13, 'OpenAI', 'GPT-3.5-1106', 'gpt-3.5-turbo-1106', 9, 1, 1, 0, '2023-11-24 14:05:40', '2023-11-28 17:28:19'),
|
||||
(14, 'OpenAI', 'GPT-4-preview', 'gpt-4-1106-preview', 11, 1, 1, 0, '2023-11-24 14:06:11', '2023-11-28 17:28:19'),
|
||||
(15, 'OpenAI', 'GPT-超级模型', 'gpt-4-all', 0, 1, 30, 0, '2024-01-15 11:32:52', '2024-01-15 11:32:52'),
|
||||
(16, 'OpenAI', 'MJ Prompt Generator (V6)', 'gpt-4-gizmo-g-tc0eHXdgb', 0, 1, 1, 1, '2024-01-15 14:46:35', '2024-01-15 14:53:16'),
|
||||
(17, 'QWen', '通义千问-Turbo', 'qwen-turbo', 0, 1, 1, 1, '2024-01-19 10:42:24', '2024-01-19 10:45:16'),
|
||||
(18, 'QWen', '通义千问-Plus', 'qwen-plus', 0, 1, 1, 1, '2024-01-19 10:42:49', '2024-01-19 10:51:09'),
|
||||
(19, 'QWen', '通义千问-Max', 'qwen-max-1201', 0, 1, 1, 1, '2024-01-19 10:51:03', '2024-01-19 10:51:03');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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', '2023-12-29 17:46:43'),
|
||||
(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', '{\"admin_title\":\"ChatPlus 控制台\",\"chat_call_price\":0.1,\"default_models\":[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\",\"generalv3\",\"qwen-max-1201\"],\"enabled_alipay\":true,\"enabled_draw\":true,\"enabled_function\":true,\"enabled_msg\":true,\"enabled_msg_service\":false,\"enabled_register\":true,\"enabled_reward\":true,\"force_invite\":false,\"img_call_price\":0.2,\"init_calls\":1000,\"init_chat_calls\":10,\"init_img_calls\":5,\"invite_calls\":10,\"invite_chat_calls\":100,\"invite_img_calls\":50,\"models\":[\"gpt-3.5-turbo-16k\",\"gpt-3.5-turbo\",\"gpt-4\",\"gpt-4-32k\"],\"order_pay_info_text\":\"成为本站会员后每月有500次对话额度,50次 AI 绘画额度,限制下月1号解除,若在期间超过次数后可单独购买点卡。当月充值的点卡有效期可以延期到下个月底。\",\"order_pay_timeout\":1800,\"register_ways\":[\"mobile\",\"email\"],\"reward_img\":\"https://img.r9it.com/chatgpt-plus/1696824231905289.png\",\"show_demo_notice\":true,\"title\":\"ChatPlus AI 智能助手\",\"user_init_calls\":10,\"vip_month_calls\":500,\"vip_month_img_calls\":50,\"wechat_card_url\":\"/images/wx.png\"}'),
|
||||
(2, 'chat', '{\"azure\":{\"api_url\":\"https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15\",\"max_tokens\":1024,\"temperature\":1},\"baidu\":{\"api_url\":\"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}\",\"max_tokens\":1024,\"temperature\":0.95},\"chat_gml\":{\"api_url\":\"https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke\",\"max_tokens\":1024,\"temperature\":0.95},\"context_deep\":4,\"dall_api_url\":\"http://89.117.18.9:8001/v1/images/generations\",\"dall_img_num\":1,\"enable_context\":true,\"enable_history\":true,\"open_ai\":{\"api_url\":\"https://api.fast-tunnel.one/v1/chat/completions\",\"max_tokens\":2048,\"temperature\":1},\"xun_fei\":{\"api_url\":\"wss://spark-api.xf-yun.com/{version}/chat\",\"max_tokens\":1024,\"temperature\":0.5}}'),
|
||||
(3, 'notice', '{\"content\":\"注意:当前站点仅为开源项目 \\u003ca style=\\\"color: #F56C6C\\\" href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003eChatPlus\\u003c/a\\u003e 的演示项目,本项目单纯就是给大家体验项目功能使用。\\n体验额度用完之后请不要在当前站点进行任何充值操作!!!\\n体验额度用完之后请不要在当前站点进行任何充值操作!!!\\n体验额度用完之后请不要在当前站点进行任何充值操作!!!\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e 购买,现在有超级优惠,价格远低于 OpenAI 官方。\\nGPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\nMidJourney API 购买地址:\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e\\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_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 '文件名',
|
||||
`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_eWuBg', 'http://localhost:5678/api/function/weibo', 1),
|
||||
(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 1),
|
||||
(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', 1);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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 '邀请码',
|
||||
`reward_json` text NOT NULL COMMENT '邀请奖励',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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 '是否使用反代',
|
||||
`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_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 '延长天数',
|
||||
`calls` int NOT NULL DEFAULT '0' COMMENT '调用次数',
|
||||
`img_calls` 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
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_products`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `calls`, `img_calls`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`) VALUES
|
||||
(1, '会员1个月', '19.90', '10.00', 30, 0, 0, 1, 2, 0, '2023-08-28 10:48:57', '2023-11-08 17:22:07'),
|
||||
(2, '会员3个月', '140.00', '30.00', 90, 0, 0, 1, 1, 0, '2023-08-28 10:52:22', '2023-08-31 16:24:31'),
|
||||
(3, '会员6个月', '290.00', '100.00', 180, 0, 0, 1, 1, 0, '2023-08-28 10:53:39', '2023-08-31 16:24:36'),
|
||||
(4, '会员12个月', '580.00', '200.00', 365, 0, 0, 1, 1, 0, '2023-08-28 10:54:15', '2023-08-31 16:24:42'),
|
||||
(5, '100次点卡', '10.00', '9.90', 0, 100, 10, 1, 7, 0, '2023-08-28 10:55:08', '2023-12-15 16:29:06'),
|
||||
(6, '200次点卡', '29.90', '20.00', 0, 200, 25, 1, 1, 0, '2023-12-15 16:55:12', '2023-12-15 16:55:12');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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 '任务进度',
|
||||
`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 '密码盐',
|
||||
`total_tokens` bigint NOT NULL DEFAULT '0' COMMENT '累计消耗 tokens',
|
||||
`tokens` bigint NOT NULL DEFAULT '0' COMMENT '当月消耗 tokens',
|
||||
`calls` int NOT NULL DEFAULT '0' COMMENT '剩余调用次数',
|
||||
`img_calls` 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`, `total_tokens`, `tokens`, `calls`, `img_calls`, `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://img.r9it.com/chatgpt-plus/1693981355719469.png', 'ueedue5l', 86517, 42680, 1172, 51, 1788078636, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"elon_musk\",\"girl_friend\",\"lu_xun\",\"red_book\",\"psychiatrist\",\"translator\",\"weekly_report\",\"artist\",\"dou_yin\",\"english_trainer\",\"gpt\",\"kong_zi\",\"programmer\",\"seller\",\"steve_jobs\",\"teacher\"]', '[\"completions_pro\",\"eb-instant\",\"general\",\"generalv2\",\"chatglm_pro\",\"chatglm_lite\",\"chatglm_std\",\"gpt-3.5-turbo-16k\",\"gpt-4\",\"generalv3\",\"gpt-3.5-turbo-1106\",\"gpt-4-1106-preview\",\"gpt-4-all\"]', 1705917311, 1, '::1', '2023-06-12 16:47:17', '2024-01-22 17:55:12'),
|
||||
(91, '18575670126', '极客学长@204872', '5e4050b8dd403f593260395d9edeb9f273dbe92d15dfdd929c4a182e95da10c4', '/images/avatar/user.png', '6fj0otl8', 0, 0, 10, 0, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"completions_pro\",\"eb-instant\",\"general\",\"generalv2\",\"chatglm_pro\",\"chatglm_lite\",\"chatglm_std\",\"gpt-3.5-turbo-16k\"]', 1697184324, 1, '::1', '2023-10-13 16:01:56', '2023-12-29 17:30:27'),
|
||||
(98, '13888888888', '极客学长@851755', 'c988bb3e89df8eb7d8a4cf08fa9d0f2bca0a9e6831f9325279e46013bbb05e31', '/images/avatar/user.png', 'xie5vya7', 0, 0, 10, 5, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 0, 0, '', '2023-11-23 16:26:06', '2023-12-29 17:30:27'),
|
||||
(99, '13999999999', '极客学长@396023', 'bf47d517c17ed1ead6ff2542753f9b6a132e79c29e06c3710faf1a36d800a217', '/images/avatar/user.png', 'x1s66pwd', 0, 0, 10, 5, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 0, 0, '', '2023-11-23 16:27:20', '2023-12-29 17:30:27'),
|
||||
(100, '13777777777', '极客学长@292245', 'dcaf31b154432310bd700349e7de7e9dde2a3d6955a035a01fe527c7917a4f99', '/images/avatar/user.png', 'i8a53f8f', 0, 0, 10, 5, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 0, 0, '', '2023-11-23 16:55:45', '2023-12-29 17:30:27'),
|
||||
(102, 'yangjian102621@gmail.com', '极客学长@207163', 'd51cec21942737083943e5c3a8f063dea034e40622ac8bd47d771f13707e4676', '/images/avatar/user.png', 'eqezapgk', 202, 202, 80, 1, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[\"eb-instant\",\"completions_pro\",\"generalv2\",\"general\",\"chatglm_pro\",\"gpt-3.5-turbo-16k\",\"chatglm_lite\",\"chatglm_std\"]', 1704448377, 0, '::1', '2024-01-05 17:48:00', '2024-01-11 14:06:57');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `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_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_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_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`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_user_login_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_user_login_logs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 在导出的表使用AUTO_INCREMENT
|
||||
--
|
||||
|
||||
--
|
||||
-- 使用表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=20;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_roles`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=129;
|
||||
|
||||
--
|
||||
-- 使用表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_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_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=104;
|
||||
|
||||
--
|
||||
-- 使用表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 */;
|
||||
13
database/update-v3.2.5.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
ALTER TABLE `chatgpt_orders` CHANGE `mobile` `username` VARCHAR(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户明';
|
||||
ALTER TABLE `chatgpt_invite_logs` CHANGE `username` `username` VARCHAR(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名';
|
||||
|
||||
CREATE TABLE `chatgpt_plus`.`chatgpt_files` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT ,
|
||||
`user_id` INT(11) NOT NULL COMMENT '用户 ID' ,
|
||||
`url` VARCHAR(255) NOT NULL COMMENT '文件地址' ,
|
||||
`ext` VARCHAR(10) NOT NULL COMMENT '文件后缀' ,
|
||||
`created_at` DATETIME NOT NULL COMMENT '创建时间' ,
|
||||
PRIMARY KEY (`id`)) ENGINE = InnoDB COMMENT = '用户文件表';
|
||||
|
||||
ALTER TABLE `chatgpt_files` ADD `size` BIGINT(20) NOT NULL DEFAULT '0' COMMENT '文件大小' AFTER `ext`;
|
||||
ALTER TABLE `chatgpt_files` ADD `name` VARCHAR(100) NOT NULL COMMENT '文件名' AFTER `user_id`;
|
||||
4
database/update-v3.2.6.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE `chatgpt_mj_jobs` ADD `publish` TINYINT(1) NOT NULL COMMENT '是否发布' AFTER `use_proxy`;
|
||||
ALTER TABLE `chatgpt_sd_jobs` ADD `publish` TINYINT(1) NOT NULL COMMENT '是否发布' AFTER `progress`;
|
||||
|
||||
ALTER TABLE `chatgpt_orders` ADD `trade_no` VARCHAR(60) NOT NULL COMMENT '支付平台交易流水号' AFTER `order_no`;
|
||||
@@ -4,6 +4,7 @@ MysqlDns = "root:12345678@tcp(chatgpt-plus-mysql:3306)/chatgpt_plus?charset=utf8
|
||||
StaticDir = "./static" # 静态资源的目录
|
||||
StaticUrl = "/static" # 静态资源访问 URL
|
||||
AesEncryptKey = ""
|
||||
ImgCdnURL = ""
|
||||
WeChatBot = false
|
||||
|
||||
[Session]
|
||||
@@ -33,11 +34,27 @@ WeChatBot = false
|
||||
Sign = ""
|
||||
CodeTempId = ""
|
||||
|
||||
[Sms] # Sms 配置,用于发送短信
|
||||
Active = "Ali" # 当前启用的短信服务,默认使用阿里云
|
||||
[Sms.SmsBao]
|
||||
Username = ""
|
||||
Password = ""
|
||||
Domain = "api.smsbao.com"
|
||||
Sign = "【极客学长】"
|
||||
CodeTemplate = "您的验证码是{code}。5分钟有效,若非本人操作,请忽略本短信。"
|
||||
[Sms.Ali]
|
||||
AccessKey = ""
|
||||
AccessSecret = ""
|
||||
Product = "Dysmsapi"
|
||||
Domain = "dysmsapi.aliyuncs.com"
|
||||
Sign = ""
|
||||
CodeTempId = ""
|
||||
|
||||
[OSS] # OSS 配置,用于存储 MJ 绘画图片
|
||||
Active = "local" # 默认使用本地文件存储引擎
|
||||
[OSS.Local]
|
||||
BasePath = "./static/upload" # 本地文件上传根路径
|
||||
BaseURL = "/static/upload" # 本地上传文件根 URL 如果是线上,则直接设置为 /static/upload 即可
|
||||
BaseURL = "http://localhost:5678/static/upload" # 本地上传文件根 URL 如果是线上,则直接设置为 /static/upload 即可
|
||||
[OSS.Minio]
|
||||
Endpoint = "" # 如 172.22.11.200:9000
|
||||
AccessKey = "" # 自己去 Minio 控制台去创建一个 Access Key
|
||||
@@ -63,16 +80,11 @@ WeChatBot = false
|
||||
DiscordCDN = "https://mj.r9it.com:8002" # mj 图片反代地址
|
||||
DiscordGateway = "wss://mj.r9it.com:8003" # discord 机器人反代地址
|
||||
|
||||
[[MjConfigs]]
|
||||
[[MjPlusConfigs]]
|
||||
Enabled = false
|
||||
UserToken = ""
|
||||
BotToken = ""
|
||||
GuildId = ""
|
||||
ChanelId = ""
|
||||
UseCDN = false #是否使用反向代理访问,设置为true下面的设置才会生效
|
||||
DiscordAPI = "https://mj.r9it.com:8001" # discord API 反代地址
|
||||
DiscordCDN = "https://mj.r9it.com:8002" # mj 图片反代地址
|
||||
DiscordGateway = "wss://mj.r9it.com:8003" # discord 机器人反代地址
|
||||
ApiURL = "https://api.chatgpt-plus.net" # 目前暂时不支持更改
|
||||
ApiKey = "sk-xxx"
|
||||
NotifyURL = "https://ai.r9it.com/api/mj/notify" # 这里需要改成你的域名
|
||||
|
||||
[[SdConfigs]]
|
||||
Enabled = false
|
||||
@@ -80,18 +92,6 @@ WeChatBot = false
|
||||
ApiKey = ""
|
||||
Txt2ImgJsonPath = "res/sd/text2img.json"
|
||||
|
||||
[[SdConfigs]]
|
||||
Enabled = false
|
||||
ApiURL = ""
|
||||
ApiKey = ""
|
||||
Txt2ImgJsonPath = "res/sd/text2img.json"
|
||||
|
||||
[[SdConfigs]]
|
||||
Enabled = false
|
||||
ApiURL = ""
|
||||
ApiKey = ""
|
||||
Txt2ImgJsonPath = "res/text2img.json"
|
||||
|
||||
[XXLConfig] # xxl-job 配置,需要你部署 XXL-JOB 定时任务工具,用来定期清理未支付订单和清理过期 VIP,如果你没有启用支付服务,则该服务也无需启动
|
||||
Enabled = false # 是否启用 XXL JOB 服务
|
||||
ServerAddr = "http://172.22.11.47:8080/xxl-job-admin" # xxl-job-admin 管理地址
|
||||
|
||||
@@ -18,6 +18,7 @@ http {
|
||||
tcp_nopush on;
|
||||
types_hash_max_size 2048;
|
||||
# server_tokens off;
|
||||
client_max_body_size 100M;
|
||||
|
||||
# server_names_hash_bucket_size 64;
|
||||
# server_name_in_redirect off;
|
||||
|
||||
11
web/package-lock.json
generated
@@ -19,6 +19,7 @@
|
||||
"json-bigint": "^1.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"markdown-it": "^13.0.1",
|
||||
"markdown-it-emoji": "^3.0.0",
|
||||
"md-editor-v3": "^2.2.1",
|
||||
"pinia": "^2.1.4",
|
||||
"qrcode": "^1.5.3",
|
||||
@@ -7430,6 +7431,11 @@
|
||||
"markdown-it": "bin/markdown-it.js"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it-emoji": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/markdown-it-emoji/-/markdown-it-emoji-3.0.0.tgz",
|
||||
"integrity": "sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg=="
|
||||
},
|
||||
"node_modules/markdown-it/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@@ -17361,6 +17367,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"markdown-it-emoji": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/markdown-it-emoji/-/markdown-it-emoji-3.0.0.tgz",
|
||||
"integrity": "sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg=="
|
||||
},
|
||||
"md-editor-v3": {
|
||||
"version": "2.11.3",
|
||||
"resolved": "https://registry.npmjs.org/md-editor-v3/-/md-editor-v3-2.11.3.tgz",
|
||||
|
||||
BIN
web/public/images/ext/doc.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
web/public/images/ext/file.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
web/public/images/ext/md.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
web/public/images/ext/pdf.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
web/public/images/ext/ppt.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
web/public/images/ext/sql.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
web/public/images/ext/txt.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
web/public/images/ext/xls.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
@@ -213,6 +213,11 @@
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
#app .common-layout .el-main .right-box #container .input-box .input-container .select-file {
|
||||
position: absolute;
|
||||
right: 48px;
|
||||
top: 20px;
|
||||
}
|
||||
#app .common-layout .el-main .right-box #container .input-box .input-container .send-btn {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
|
||||
@@ -279,6 +279,12 @@ $borderColor = #4676d0;
|
||||
}
|
||||
}
|
||||
|
||||
.select-file {
|
||||
position absolute;
|
||||
right 48px;
|
||||
top 20px;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
position absolute;
|
||||
right 12px;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4125778 */
|
||||
src: url('iconfont.woff2?t=1703124384910') format('woff2'),
|
||||
url('iconfont.woff?t=1703124384910') format('woff'),
|
||||
url('iconfont.ttf?t=1703124384910') format('truetype');
|
||||
src: url('iconfont.woff2?t=1705615887594') format('woff2'),
|
||||
url('iconfont.woff?t=1705615887594') format('woff'),
|
||||
url('iconfont.ttf?t=1705615887594') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,46 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-share-bold:before {
|
||||
content: "\e626";
|
||||
}
|
||||
|
||||
.icon-cancel-share:before {
|
||||
content: "\e682";
|
||||
}
|
||||
|
||||
.icon-xls:before {
|
||||
content: "\e678";
|
||||
}
|
||||
|
||||
.icon-file:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.icon-doc:before {
|
||||
content: "\e6c2";
|
||||
}
|
||||
|
||||
.icon-pdf:before {
|
||||
content: "\e6c3";
|
||||
}
|
||||
|
||||
.icon-ppt:before {
|
||||
content: "\e642";
|
||||
}
|
||||
|
||||
.icon-txt:before {
|
||||
content: "\e644";
|
||||
}
|
||||
|
||||
.icon-sql:before {
|
||||
content: "\e65b";
|
||||
}
|
||||
|
||||
.icon-md:before {
|
||||
content: "\e63a";
|
||||
}
|
||||
|
||||
.icon-loading:before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
@@ -5,6 +5,76 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "1132455",
|
||||
"name": "share-bold",
|
||||
"font_class": "share-bold",
|
||||
"unicode": "e626",
|
||||
"unicode_decimal": 58918
|
||||
},
|
||||
{
|
||||
"icon_id": "7567359",
|
||||
"name": "cancel-share",
|
||||
"font_class": "cancel-share",
|
||||
"unicode": "e682",
|
||||
"unicode_decimal": 59010
|
||||
},
|
||||
{
|
||||
"icon_id": "12600976",
|
||||
"name": "xls",
|
||||
"font_class": "xls",
|
||||
"unicode": "e678",
|
||||
"unicode_decimal": 59000
|
||||
},
|
||||
{
|
||||
"icon_id": "3750429",
|
||||
"name": "file",
|
||||
"font_class": "file",
|
||||
"unicode": "e62f",
|
||||
"unicode_decimal": 58927
|
||||
},
|
||||
{
|
||||
"icon_id": "4318810",
|
||||
"name": "doc",
|
||||
"font_class": "doc",
|
||||
"unicode": "e6c2",
|
||||
"unicode_decimal": 59074
|
||||
},
|
||||
{
|
||||
"icon_id": "4318811",
|
||||
"name": "pdf",
|
||||
"font_class": "pdf",
|
||||
"unicode": "e6c3",
|
||||
"unicode_decimal": 59075
|
||||
},
|
||||
{
|
||||
"icon_id": "4628503",
|
||||
"name": "ppt",
|
||||
"font_class": "ppt",
|
||||
"unicode": "e642",
|
||||
"unicode_decimal": 58946
|
||||
},
|
||||
{
|
||||
"icon_id": "6233095",
|
||||
"name": "txt",
|
||||
"font_class": "txt",
|
||||
"unicode": "e644",
|
||||
"unicode_decimal": 58948
|
||||
},
|
||||
{
|
||||
"icon_id": "12600910",
|
||||
"name": "sql",
|
||||
"font_class": "sql",
|
||||
"unicode": "e65b",
|
||||
"unicode_decimal": 58971
|
||||
},
|
||||
{
|
||||
"icon_id": "31260974",
|
||||
"name": "md",
|
||||
"font_class": "md",
|
||||
"unicode": "e63a",
|
||||
"unicode_decimal": 58938
|
||||
},
|
||||
{
|
||||
"icon_id": "1278349",
|
||||
"name": "loading",
|
||||
|
||||
@@ -102,6 +102,17 @@ export default defineComponent({
|
||||
border-radius: 5px;
|
||||
overflow: auto;
|
||||
|
||||
img {
|
||||
max-width: 600px;
|
||||
border-radius: 10px;
|
||||
margin 10px 0
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
color #20a0ff
|
||||
}
|
||||
|
||||
p {
|
||||
line-height 1.5
|
||||
}
|
||||
|
||||
@@ -107,6 +107,10 @@ export default defineComponent({
|
||||
border-radius: 5px;
|
||||
overflow auto;
|
||||
|
||||
a {
|
||||
color #20a0ff
|
||||
}
|
||||
|
||||
// control the image size in content
|
||||
|
||||
img {
|
||||
@@ -166,6 +170,7 @@ export default defineComponent({
|
||||
color #00e0e0
|
||||
}
|
||||
|
||||
|
||||
// 设置表格边框
|
||||
|
||||
table {
|
||||
@@ -190,6 +195,17 @@ export default defineComponent({
|
||||
padding 10px
|
||||
}
|
||||
}
|
||||
|
||||
// 代码快
|
||||
|
||||
blockquote {
|
||||
margin 0
|
||||
background-color: #ebfffe;
|
||||
padding: 0.8rem 1.5rem;
|
||||
border-left: 0.5rem solid;
|
||||
border-color: #026863;
|
||||
color: #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
169
web/src/components/FileSelect.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<el-container class="file-list-box">
|
||||
<el-tooltip class="box-item" effect="dark" content="打开文件管理中心">
|
||||
<el-button class="file-upload-img" @click="fetchFiles">
|
||||
<el-icon>
|
||||
<PictureFilled/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-dialog
|
||||
v-model="show"
|
||||
:close-on-click-modal="true"
|
||||
:show-close="true"
|
||||
:width="800"
|
||||
title="文件管理"
|
||||
>
|
||||
|
||||
<div class="file-list">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="3">
|
||||
<div class="grid-content">
|
||||
<el-upload
|
||||
class="avatar-uploader"
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="afterRead"
|
||||
>
|
||||
<el-icon class="avatar-uploader-icon">
|
||||
<Plus/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="3" v-for="file in fileList" :key="file.url">
|
||||
<div class="grid-content">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
:content="file.name"
|
||||
placement="top">
|
||||
<el-image :src="file.url" fit="fill" v-if="isImage(file.ext)" @click="insertURL(file.url)"/>
|
||||
<el-image :src="getFileIcon(file.ext)" fit="fill" v-else @click="insertURL(file.url)"/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {PictureFilled, Plus} from "@element-plus/icons-vue";
|
||||
import {isImage} from "@/utils/libs";
|
||||
|
||||
const props = defineProps({
|
||||
userId: String,
|
||||
});
|
||||
const emits = defineEmits(['selected']);
|
||||
const show = ref(false)
|
||||
const fileList = ref([])
|
||||
|
||||
const fetchFiles = () => {
|
||||
show.value = true
|
||||
httpGet("/api/upload/list").then(res => {
|
||||
fileList.value = res.data
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
const getFileIcon = (ext) => {
|
||||
const files = {
|
||||
".docx": "doc.png",
|
||||
".doc": "doc.png",
|
||||
".xls": "xls.png",
|
||||
".xlsx": "xls.png",
|
||||
".ppt": "ppt.png",
|
||||
".pptx": "ppt.png",
|
||||
".md": "md.png",
|
||||
".pdf": "pdf.png",
|
||||
".sql": "sql.png"
|
||||
}
|
||||
if (files[ext]) {
|
||||
return '/images/ext/' + files[ext]
|
||||
}
|
||||
|
||||
return '/images/ext/file.png'
|
||||
}
|
||||
|
||||
const afterRead = (file) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file.file, file.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then((res) => {
|
||||
fileList.value.unshift(res.data)
|
||||
ElMessage.success({message: "上传成功", duration: 500})
|
||||
}).catch((e) => {
|
||||
ElMessage.error('图片上传失败:' + e.message)
|
||||
})
|
||||
};
|
||||
|
||||
const insertURL = (url) => {
|
||||
show.value = false
|
||||
emits('selected', url)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
||||
.file-list-box {
|
||||
.file-upload-img {
|
||||
padding: 8px 5px;
|
||||
border-radius: 6px;
|
||||
background: #19c37d;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
|
||||
.el-dialog__body {
|
||||
//padding 0
|
||||
|
||||
.file-list {
|
||||
|
||||
.grid-content {
|
||||
margin-bottom 10px
|
||||
|
||||
.avatar-uploader {
|
||||
width 100%
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border 1px dashed #e1e1e1
|
||||
border-radius 6px
|
||||
|
||||
.el-upload {
|
||||
width 100%
|
||||
height 80px
|
||||
}
|
||||
}
|
||||
|
||||
.el-image {
|
||||
height 80px
|
||||
border 1px solid #ffffff
|
||||
border-radius 6px
|
||||
cursor pointer
|
||||
|
||||
&:hover {
|
||||
border 1px solid #20a0ff
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
color #20a0ff
|
||||
font-size 40px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -12,7 +12,7 @@
|
||||
<el-table-column label="邀请奖励">
|
||||
<template #default="scope">
|
||||
<span>对话:{{ scope.row['reward']['chat_calls'] }}次</span>,
|
||||
<span>绘图:{{ scope.row['reward']['chat_calls'] }}次</span>
|
||||
<span>绘图:{{ scope.row['reward']['img_calls'] }}次</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="afterRead"
|
||||
accept=".png,.jpg,.jpeg,.bmp"
|
||||
>
|
||||
<el-avatar v-if="user.avatar" :src="user.avatar" shape="circle" :size="100"/>
|
||||
<el-icon v-else class="avatar-uploader-icon">
|
||||
@@ -93,7 +94,7 @@ const afterRead = (file) => {
|
||||
formData.append('file', result, result.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then((res) => {
|
||||
user.value.avatar = res.data
|
||||
user.value.avatar = res.data.url
|
||||
ElMessage.success({message: "上传成功", duration: 500})
|
||||
}).catch((e) => {
|
||||
ElMessage.error('图片上传失败:' + e.message)
|
||||
|
||||
@@ -161,3 +161,8 @@ export function substr(str, length) {
|
||||
return result
|
||||
}
|
||||
|
||||
export function isImage(url) {
|
||||
const expr = /\.(jpg|jpeg|png|gif|bmp|svg)$/i;
|
||||
return expr.test(url);
|
||||
}
|
||||
|
||||
|
||||
@@ -187,6 +187,9 @@
|
||||
:rows="2"
|
||||
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
|
||||
/>
|
||||
<span class="select-file">
|
||||
<file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertURL"/>
|
||||
</span>
|
||||
<span class="send-btn">
|
||||
<el-button @click="sendMessage">
|
||||
<el-icon><Promotion/></el-icon>
|
||||
@@ -219,35 +222,19 @@
|
||||
</el-alert>
|
||||
|
||||
<div style="text-align: center;padding-top: 10px;">
|
||||
<el-image src="/images/wx.png"/>
|
||||
<el-image :src="wechatCardURL"/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showDemoNotice"
|
||||
v-model="showNotice"
|
||||
:show-close="true"
|
||||
custom-class="notice-dialog"
|
||||
title="网站公告"
|
||||
>
|
||||
<div class="notice">
|
||||
<el-text type="primary">
|
||||
注意:当前站点仅为开源项目
|
||||
<a style="color: #F56C6C" href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">ChatPlus</a>
|
||||
的演示项目,本项目单纯就是给大家体验项目功能使用。<br/>
|
||||
|
||||
体验额度用完之后请不要在当前站点进行任何充值操作!!!<br/>
|
||||
体验额度用完之后请不要在当前站点进行任何充值操作!!!<br/>
|
||||
体验额度用完之后请不要在当前站点进行任何充值操作!!!<br/>
|
||||
|
||||
如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去
|
||||
<a href="https://gpt.bemore.lol" target="_blank"
|
||||
style="font-size: 20px;color:#F56C6C">https://gpt.bemore.lol</a>
|
||||
购买,现在有超级优惠,价格远低于 OpenAI 官方。<br/>
|
||||
GPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。<br/>
|
||||
|
||||
本项目源码地址:
|
||||
<a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
|
||||
https://github.com/yangjian102621/chatgpt-plus
|
||||
</a>
|
||||
<div v-html="notice"></div>
|
||||
</el-text>
|
||||
|
||||
<p style="text-align: right">
|
||||
@@ -279,7 +266,7 @@ import {
|
||||
VideoPause
|
||||
} from '@element-plus/icons-vue'
|
||||
import 'highlight.js/styles/a11y-dark.css'
|
||||
import {dateFormat, isMobile, randString, removeArrayItem, UUID} from "@/utils/libs";
|
||||
import {dateFormat, isImage, isMobile, randString, removeArrayItem, UUID} from "@/utils/libs";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import hl from "highlight.js";
|
||||
import {getSessionId, getUserToken, removeUserToken} from "@/store/session";
|
||||
@@ -290,6 +277,7 @@ import ConfigDialog from "@/components/ConfigDialog.vue";
|
||||
import {checkSession} from "@/action/session";
|
||||
import Welcome from "@/components/Welcome.vue";
|
||||
import ChatMidJourney from "@/components/ChatMidJourney.vue";
|
||||
import FileSelect from "@/components/FileSelect.vue";
|
||||
|
||||
const title = ref('ChatGPT-智能助手');
|
||||
const models = ref([])
|
||||
@@ -312,8 +300,10 @@ const isLogin = ref(false)
|
||||
const showHello = ref(true)
|
||||
const textInput = ref(null)
|
||||
const showFeedbackDialog = ref(false)
|
||||
const showDemoNotice = ref(false)
|
||||
const showNoticeKey = ref("SHOW_DEMO_NOTICE_")
|
||||
const showNotice = ref(false)
|
||||
const notice = ref("")
|
||||
const noticeKey = ref("SYSTEM_NOTICE")
|
||||
const wechatCardURL = ref("/images/wx.png")
|
||||
|
||||
if (isMobile()) {
|
||||
router.replace("/mobile")
|
||||
@@ -360,11 +350,21 @@ onMounted(() => {
|
||||
ElMessage.error("加载会话列表失败!")
|
||||
})
|
||||
|
||||
// 获取系统配置
|
||||
httpGet("/api/admin/config/get?key=system").then(res => {
|
||||
title.value = res.data.title
|
||||
const show = localStorage.getItem(showNoticeKey.value + loginUser.value.username)
|
||||
if (!show) {
|
||||
showDemoNotice.value = res.data['show_demo_notice']
|
||||
wechatCardURL.value = res.data['wechat_card_url']
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
// 获取系统公告
|
||||
httpGet("/api/admin/config/get?key=notice").then(res => {
|
||||
notice.value = md.render(res.data['content'])
|
||||
const oldNotice = localStorage.getItem(noticeKey.value);
|
||||
// 如果公告有更新,则显示公告
|
||||
if (oldNotice !== notice.value) {
|
||||
showNotice.value = true
|
||||
}
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
@@ -511,6 +511,9 @@ const removeChat = function (event, chat) {
|
||||
|
||||
const md = require('markdown-it')({
|
||||
breaks: true,
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
highlight: function (str, lang) {
|
||||
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
|
||||
// 显示复制代码按钮
|
||||
@@ -540,6 +543,7 @@ const lineBuffer = ref(''); // 输出缓冲行
|
||||
const socket = ref(null);
|
||||
const activelyClose = ref(false); // 主动关闭
|
||||
const canSend = ref(true);
|
||||
const heartbeatHandle = ref(null)
|
||||
const connect = function (chat_id, role_id) {
|
||||
let isNewChat = false;
|
||||
if (!chat_id) {
|
||||
@@ -586,6 +590,14 @@ const connect = function (chat_id, role_id) {
|
||||
loadChatHistory(chat_id);
|
||||
}
|
||||
|
||||
// 发送心跳消息
|
||||
clearInterval(heartbeatHandle.value)
|
||||
heartbeatHandle.value = setInterval(() => {
|
||||
if (socket.value !== null) {
|
||||
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
});
|
||||
|
||||
_socket.addEventListener('message', event => {
|
||||
@@ -601,36 +613,6 @@ const connect = function (chat_id, role_id) {
|
||||
icon: _role['icon'],
|
||||
content: ""
|
||||
});
|
||||
} else if (data.type === "mj") {
|
||||
disableInput(true)
|
||||
const content = data.content;
|
||||
content.html = md.render(content.content)
|
||||
let key = content.key
|
||||
// fixed bug: 执行 Upscale 和 Variation 操作的时候覆盖之前的绘画
|
||||
if (content.status === "Finished") {
|
||||
key = randString(32)
|
||||
enableInput()
|
||||
}
|
||||
// console.log(content)
|
||||
// check if the message is in chatData
|
||||
let flag = false
|
||||
for (let i = 0; i < chatData.value.length; i++) {
|
||||
if (chatData.value[i].id === content.key) {
|
||||
flag = true
|
||||
chatData.value[i].content = content
|
||||
chatData.value[i].id = key
|
||||
break
|
||||
}
|
||||
}
|
||||
if (flag === false) {
|
||||
chatData.value.push({
|
||||
type: "mj",
|
||||
id: key,
|
||||
icon: "/images/avatar/mid_journey.png",
|
||||
content: content
|
||||
});
|
||||
}
|
||||
|
||||
} else if (data.type === 'end') { // 消息接收完毕
|
||||
// 追加当前会话到会话列表
|
||||
if (isNewChat && newChatItem.value !== null) {
|
||||
@@ -659,7 +641,7 @@ const connect = function (chat_id, role_id) {
|
||||
lineBuffer.value += data.content;
|
||||
const reply = chatData.value[chatData.value.length - 1]
|
||||
reply['orgContent'] = lineBuffer.value;
|
||||
reply['content'] = md.render(lineBuffer.value);
|
||||
reply['content'] = md.render(processContent(lineBuffer.value));
|
||||
}
|
||||
// 将聊天框的滚动条滑动到最底部
|
||||
nextTick(() => {
|
||||
@@ -735,7 +717,7 @@ const sendMessage = function () {
|
||||
type: "prompt",
|
||||
id: randString(32),
|
||||
icon: loginUser.value.avatar,
|
||||
content: md.render(prompt.value),
|
||||
content: md.render(processContent(prompt.value)),
|
||||
created_at: new Date().getTime(),
|
||||
});
|
||||
|
||||
@@ -745,7 +727,7 @@ const sendMessage = function () {
|
||||
|
||||
showHello.value = false
|
||||
disableInput(false)
|
||||
socket.value.send(prompt.value);
|
||||
socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
|
||||
previousText.value = prompt.value;
|
||||
prompt.value = '';
|
||||
return true;
|
||||
@@ -800,15 +782,8 @@ const loadChatHistory = function (chatId) {
|
||||
}
|
||||
showHello.value = false
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i].type === "mj") {
|
||||
data[i].content = JSON.parse(data[i].content)
|
||||
data[i].content.html = md.render(data[i].content?.content)
|
||||
chatData.value.push(data[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
data[i].orgContent = data[i].content;
|
||||
data[i].content = md.render(data[i].content);
|
||||
data[i].content = md.render(processContent(data[i].content))
|
||||
chatData.value.push(data[i]);
|
||||
}
|
||||
|
||||
@@ -822,6 +797,37 @@ const loadChatHistory = function (chatId) {
|
||||
})
|
||||
}
|
||||
|
||||
const processContent = (content) => {
|
||||
//process img url
|
||||
const linkRegex = /(https?:\/\/\S+)/g;
|
||||
const links = content.match(linkRegex);
|
||||
if (links) {
|
||||
for (let link of links) {
|
||||
if (isImage(link)) {
|
||||
const index = content.indexOf(link)
|
||||
if (content.substring(index - 1, 2) !== "]") {
|
||||
content = content.replace(link, "\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理引用块
|
||||
if (content.indexOf("\n") === -1) {
|
||||
return content
|
||||
}
|
||||
|
||||
const texts = content.split("\n")
|
||||
const lines = []
|
||||
for (let txt of texts) {
|
||||
lines.push(txt)
|
||||
if (txt.startsWith(">")) {
|
||||
lines.push("\n")
|
||||
}
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
const stopGenerate = function () {
|
||||
showStopGenerate.value = false;
|
||||
httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => {
|
||||
@@ -890,11 +896,24 @@ const getModelValue = (model_id) => {
|
||||
|
||||
|
||||
const notShow = () => {
|
||||
localStorage.setItem(showNoticeKey.value + loginUser.value.username, true)
|
||||
showDemoNotice.value = false
|
||||
localStorage.setItem(noticeKey.value, notice.value)
|
||||
showNotice.value = false
|
||||
}
|
||||
|
||||
// 插入文件路径
|
||||
const insertURL = (url) => {
|
||||
prompt.value += " " + url + " "
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
@import "@/assets/css/chat-plus.styl"
|
||||
</style>
|
||||
|
||||
<style lang="stylus">
|
||||
.notice-dialog {
|
||||
.el-dialog__body {
|
||||
padding 0 20px
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -424,6 +424,12 @@
|
||||
|
||||
<div class="remove">
|
||||
<el-button type="danger" :icon="Delete" @click="removeImage(scope.item)" circle/>
|
||||
<el-button type="warning" v-if="scope.item.publish" @click="publishImage(scope.item, false)" circle>
|
||||
<i class="iconfont icon-cancel-share"></i>
|
||||
</el-button>
|
||||
<el-button type="success" v-else @click="publishImage(scope.item, true)" circle>
|
||||
<i class="iconfont icon-share-bold"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -442,7 +448,8 @@
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue"
|
||||
import {
|
||||
ChromeFilled, Delete,
|
||||
ChromeFilled,
|
||||
Delete,
|
||||
DeleteFilled,
|
||||
DocumentCopy,
|
||||
InfoFilled,
|
||||
@@ -668,7 +675,7 @@ const afterRead = (file) => {
|
||||
formData.append('file', result, result.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then((res) => {
|
||||
params.value.img = res.data
|
||||
params.value.img = res.data.url
|
||||
ElMessage.success('上传成功')
|
||||
}).catch((e) => {
|
||||
ElMessage.error('上传失败:' + e.message)
|
||||
@@ -700,6 +707,7 @@ const generate = () => {
|
||||
|
||||
// 图片放大任务
|
||||
const upscale = (index, item) => {
|
||||
console.log(item)
|
||||
send('/api/mj/upscale', index, item)
|
||||
}
|
||||
|
||||
@@ -743,6 +751,20 @@ const removeImage = (item) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 发布图片到作品墙
|
||||
const publishImage = (item, action) => {
|
||||
let text = "图片发布"
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
|
||||
ElMessage.success(text + "成功")
|
||||
item.publish = action
|
||||
}).catch(e => {
|
||||
ElMessage.error(text + "失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
||||
@@ -374,6 +374,13 @@
|
||||
|
||||
<div class="remove">
|
||||
<el-button type="danger" :icon="Delete" @click="removeImage($event,scope.item)" circle/>
|
||||
<el-button type="warning" v-if="scope.item.publish" @click="publishImage($event,scope.item, false)"
|
||||
circle>
|
||||
<i class="iconfont icon-cancel-share"></i>
|
||||
</el-button>
|
||||
<el-button type="success" v-else @click="publishImage($event,scope.item, true)" circle>
|
||||
<i class="iconfont icon-share-bold"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -698,6 +705,21 @@ const removeImage = (event, item) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 发布图片到作品墙
|
||||
const publishImage = (event, item, action) => {
|
||||
event.stopPropagation()
|
||||
let text = "图片发布"
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
|
||||
ElMessage.success(text + "成功")
|
||||
item.publish = action
|
||||
}).catch(e => {
|
||||
ElMessage.error(text + "失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
||||
@@ -274,8 +274,7 @@ const getNext = () => {
|
||||
loading.value = true
|
||||
page.value = page.value + 1
|
||||
const url = imgType.value === "mj" ? "/api/mj/jobs" : "/api/sd/jobs"
|
||||
// 获取运行中的任务
|
||||
httpGet(`${url}?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
httpGet(`${url}?status=1&page=${page.value}&page_size=${pageSize.value}&publish=true`).then(res => {
|
||||
loading.value = false
|
||||
if (res.data.length === 0) {
|
||||
isOver.value = true
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<div class="info-line">
|
||||
<span class="label">有效期:</span>
|
||||
<span class="expire" v-if="scope.item.days > 0">{{ scope.item.days }}天</span>
|
||||
<span class="expire" v-else>当月有效</span>
|
||||
<span class="expire" v-else>长期有效</span>
|
||||
</div>
|
||||
|
||||
<div class="pay-way">
|
||||
@@ -124,6 +124,7 @@
|
||||
:close-on-click-modal="false"
|
||||
:show-close="true"
|
||||
:width="400"
|
||||
@close="closeOrder"
|
||||
title="充值订单支付">
|
||||
<div class="pay-container">
|
||||
<div class="count-down">
|
||||
@@ -332,6 +333,10 @@ const logout = function () {
|
||||
})
|
||||
}
|
||||
|
||||
const closeOrder = () => {
|
||||
activeOrderNo.value = ''
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
||||
@@ -154,6 +154,12 @@ httpGet("/api/admin/config/get?key=system").then(res => {
|
||||
ways.push("邮箱地址")
|
||||
}
|
||||
placeholder.value += ways.join("/")
|
||||
// 是否启用注册
|
||||
enableRegister.value = res.data['enabled_register']
|
||||
// 覆盖微信二维码
|
||||
if (res.data['wechat_card_url'] !== '') {
|
||||
wxImg.value = res.data['wechat_card_url']
|
||||
}
|
||||
}
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
|
||||
@@ -175,6 +175,11 @@ const platforms = ref([
|
||||
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 types = ref([
|
||||
{name: "聊天", value: "chat"},
|
||||
@@ -256,10 +261,10 @@ const changePlatform = () => {
|
||||
}
|
||||
}
|
||||
if (platform !== null) {
|
||||
if (item.value.type === "chat") {
|
||||
item.value.api_url = platform.api_url
|
||||
} else if (platform.img_url) {
|
||||
if (item.value.type === "img" && platform.img_url) {
|
||||
item.value.api_url = platform.img_url
|
||||
} else {
|
||||
item.value.api_url = platform.api_url
|
||||
}
|
||||
|
||||
}
|
||||
@@ -272,7 +277,7 @@ const changePlatform = () => {
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
display flex;
|
||||
display: flex;
|
||||
justify-content flex-end
|
||||
|
||||
.el-icon {
|
||||
@@ -289,7 +294,7 @@ const changePlatform = () => {
|
||||
.el-form-item__content {
|
||||
|
||||
.el-icon {
|
||||
padding-left 10px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +134,8 @@ const platforms = ref([
|
||||
{name: "【清华智普】ChatGLM", value: "ChatGLM"},
|
||||
{name: "【百度】文心一言", value: "Baidu"},
|
||||
{name: "【微软】Azure", value: "Azure"},
|
||||
{name: "【阿里】通义千问", value: "QWen"},
|
||||
|
||||
])
|
||||
|
||||
// 获取数据
|
||||
@@ -233,12 +235,12 @@ const remove = function (row) {
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@import "@/assets/css/admin/form.styl"
|
||||
@import "@/assets/css/admin/form.styl";
|
||||
.list {
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
display flex;
|
||||
display: flex;
|
||||
justify-content flex-end
|
||||
|
||||
.el-icon {
|
||||
|
||||
@@ -202,6 +202,7 @@ const fetch = () => {
|
||||
// 编辑
|
||||
const curIndex = ref(0)
|
||||
const rowEdit = function (index, row) {
|
||||
title.value = "编辑函数"
|
||||
curIndex.value = index
|
||||
item.value = copyObj(row)
|
||||
// initialize parameters
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<el-row>
|
||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto">
|
||||
<el-table-column prop="order_no" label="订单号"/>
|
||||
<el-table-column prop="mobile" label="下单用户"/>
|
||||
<el-table-column prop="username" label="下单用户"/>
|
||||
<el-table-column prop="subject" label="产品名称"/>
|
||||
<el-table-column prop="amount" label="订单金额"/>
|
||||
<el-table-column label="调用次数">
|
||||
|
||||
@@ -1,98 +1,40 @@
|
||||
<template>
|
||||
<div class="system-config" v-loading="loading">
|
||||
<div class="container">
|
||||
<el-divider content-position="center">基本设置</el-divider>
|
||||
<el-form :model="system" label-width="150px" label-position="right" ref="systemFormRef" :rules="rules">
|
||||
<el-form-item label="网站标题" prop="title">
|
||||
<el-input v-model="system['title']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="控制台标题" prop="admin_title">
|
||||
<el-input v-model="system['admin_title']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="注册赠送对话次数" prop="user_init_calls">
|
||||
<el-input v-model.number="system['init_chat_calls']" placeholder="新用户注册赠送对话次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="注册赠送绘图次数" prop="init_img_calls">
|
||||
<el-input v-model.number="system['init_img_calls']" placeholder="新用户注册赠送绘图次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="邀请赠送对话次数" prop="invite_chat_calls">
|
||||
<el-input v-model.number="system['invite_chat_calls']" placeholder="邀请新用户注册赠送对话次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="邀请赠送绘图次数" prop="invite_img_calls">
|
||||
<el-input v-model.number="system['invite_img_calls']" placeholder="邀请新用户注册赠送绘图次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="VIP每月对话次数" prop="vip_month_calls">
|
||||
<el-input v-model.number="system['vip_month_calls']" placeholder="VIP用户每月赠送对话次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="VIP每月绘图次数" prop="vip_month_img_calls">
|
||||
<el-input v-model.number="system['vip_month_img_calls']" placeholder="VIP用户每月赠送绘图次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="注册方式" prop="register_ways">
|
||||
<el-checkbox-group v-model="system['register_ways']">
|
||||
<el-checkbox label="mobile">手机注册</el-checkbox>
|
||||
<el-checkbox label="email">邮箱注册</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用众筹功能" prop="enabled_reward">
|
||||
<el-switch v-model="system['enabled_reward']"/>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="如果关闭次功能将不在用户菜单显示众筹二维码"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
<el-tabs v-model="activeName" class="demo-tabs">
|
||||
<el-tab-pane label="基本设置" name="basic">
|
||||
<div class="container">
|
||||
<el-form :model="system" label-width="150px" label-position="right" ref="systemFormRef" :rules="rules">
|
||||
<el-form-item label="网站标题" prop="title">
|
||||
<el-input v-model="system['title']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="控制台标题" prop="admin_title">
|
||||
<el-input v-model="system['admin_title']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="注册赠送对话次数" prop="user_init_calls">
|
||||
<el-input v-model.number="system['init_chat_calls']" placeholder="新用户注册赠送对话次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="注册赠送绘图次数" prop="init_img_calls">
|
||||
<el-input v-model.number="system['init_img_calls']" placeholder="新用户注册赠送绘图次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="邀请赠送对话次数" prop="invite_chat_calls">
|
||||
<el-input v-model.number="system['invite_chat_calls']" placeholder="邀请新用户注册赠送对话次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="邀请赠送绘图次数" prop="invite_img_calls">
|
||||
<el-input v-model.number="system['invite_img_calls']" placeholder="邀请新用户注册赠送绘图次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="VIP每月对话次数" prop="vip_month_calls">
|
||||
<el-input v-model.number="system['vip_month_calls']" placeholder="VIP用户每月赠送对话次数"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="VIP每月绘图次数" prop="vip_month_img_calls">
|
||||
<el-input v-model.number="system['vip_month_img_calls']" placeholder="VIP用户每月赠送绘图次数"/>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="system['enabled_reward']">
|
||||
<el-form-item label="单次对话价格" prop="chat_call_price">
|
||||
<el-input v-model="system['chat_call_price']" placeholder="众筹金额跟对话次数的兑换比例"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单次绘图价格" prop="img_call_price">
|
||||
<el-input v-model="system['img_call_price']" placeholder="众筹金额跟绘图次数的兑换比例"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="收款二维码" prop="reward_img">
|
||||
<el-input v-model="system['reward_img']" placeholder="众筹收款二维码地址">
|
||||
<template #append>
|
||||
<el-upload
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="uploadRewardImg"
|
||||
>
|
||||
<el-icon class="uploader-icon">
|
||||
<UploadFilled/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item label="显示演示公告" prop="show_demo_notice">
|
||||
<el-switch v-model="system['show_demo_notice']"/>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="是否在聊天首页显示演示 Demo 公告,这是专为作者自己开发的功能"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="订单超时时间" prop="order_pay_timeout">
|
||||
<div class="tip-input">
|
||||
<el-input v-model.number="system['order_pay_timeout']" placeholder="单位:秒"/>
|
||||
<div class="info">
|
||||
<el-form-item label="开放注册" prop="enabled_register">
|
||||
<el-switch v-model="system['enabled_register']"/>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="系统会定期清理超时未支付的订单<br/>默认值:900秒"
|
||||
content="关闭注册之后只能通过管理后台添加用户"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
@@ -100,126 +42,216 @@
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="会员充值说明" prop="order_pay_info_text">
|
||||
<el-input
|
||||
v-model="system['order_pay_info_text']"
|
||||
:autosize="{ minRows: 3, maxRows: 10 }"
|
||||
type="textarea"
|
||||
placeholder="请输入会员充值说明文字,比如介绍会员计划"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="默认AI模型" prop="default_models">
|
||||
<template #default>
|
||||
<div class="tip-input">
|
||||
<el-select
|
||||
v-model="system['default_models']"
|
||||
multiple
|
||||
:filterable="true"
|
||||
placeholder="选择AI模型,多选"
|
||||
style="width: 100%"
|
||||
<el-form-item label="注册方式" prop="register_ways">
|
||||
<el-checkbox-group v-model="system['register_ways']">
|
||||
<el-checkbox label="mobile">手机注册</el-checkbox>
|
||||
<el-checkbox label="email">邮箱注册</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用众筹功能" prop="enabled_reward">
|
||||
<el-switch v-model="system['enabled_reward']"/>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="如果关闭次功能将不在用户菜单显示众筹二维码"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in models"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="info">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="新用户注册默认开通的 AI 模型"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="system['enabled_reward']">
|
||||
<el-form-item label="单次对话价格" prop="chat_call_price">
|
||||
<el-input v-model="system['chat_call_price']" placeholder="众筹金额跟对话次数的兑换比例"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="单次绘图价格" prop="img_call_price">
|
||||
<el-input v-model="system['img_call_price']" placeholder="众筹金额跟绘图次数的兑换比例"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="收款二维码" prop="reward_img">
|
||||
<el-input v-model="system['reward_img']" placeholder="众筹收款二维码地址">
|
||||
<template #append>
|
||||
<el-upload
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
@click="beforeUpload('reward_img')"
|
||||
:http-request="uploadImg"
|
||||
>
|
||||
<el-icon class="uploader-icon">
|
||||
<UploadFilled/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="微信客服二维码" prop="wechat_card_url">
|
||||
<el-input v-model="system['wechat_card_url']" placeholder="微信客服二维码">
|
||||
<template #append>
|
||||
<el-upload
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
@click="beforeUpload('wechat_card_url')"
|
||||
:http-request="uploadImg"
|
||||
>
|
||||
<el-icon class="uploader-icon">
|
||||
<UploadFilled/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="订单超时时间" prop="order_pay_timeout">
|
||||
<div class="tip-input">
|
||||
<el-input v-model.number="system['order_pay_timeout']" placeholder="单位:秒"/>
|
||||
<div class="info">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="系统会定期清理超时未支付的订单<br/>默认值:900秒"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="会员充值说明" prop="order_pay_info_text">
|
||||
<el-input
|
||||
v-model="system['order_pay_info_text']"
|
||||
:autosize="{ minRows: 3, maxRows: 10 }"
|
||||
type="textarea"
|
||||
placeholder="请输入会员充值说明文字,比如介绍会员计划"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="默认AI模型" prop="default_models">
|
||||
<template #default>
|
||||
<div class="tip-input">
|
||||
<el-select
|
||||
v-model="system['default_models']"
|
||||
multiple
|
||||
:filterable="true"
|
||||
placeholder="选择AI模型,多选"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in models"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<div class="info">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="新用户注册默认开通的 AI 模型"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save('system')">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="模型配置" name="chat">
|
||||
<div class="container">
|
||||
<el-form :model="chat" label-position="right" label-width="150px" ref="chatFormRef" :rules="rules">
|
||||
<el-form-item label="开启聊天上下文">
|
||||
<el-switch v-model="chat['enable_context']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="保存聊天记录">
|
||||
<el-switch v-model="chat['enable_history']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="会话上下文深度">
|
||||
<div style="width:100%">
|
||||
<el-input-number v-model="chat['context_deep']" :min="0" :max="10"/>
|
||||
</div>
|
||||
<div class="tip" style="margin-top: 10px; ">会话上下文深度:在老会话中继续会话,默认加载多少条聊天记录作为上下文。如果设置为
|
||||
0
|
||||
则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数最好设置需要为偶数,否则将无法兼容百度的 API。
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">OpenAI</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['open_ai']['temperature']" :max="2" :step="0.1"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['open_ai']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">Azure</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['azure']['temperature']" :max="2" :step="0.1"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['azure']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">ChatGLM</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['chat_gml']['temperature']" :max="1" :step="0.01"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['chat_gml']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">文心一言</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['baidu']['temperature']" :max="1" :step="0.01"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['baidu']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">讯飞星火</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['xun_fei']['temperature']" :max="1" :step="0.1"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['xun_fei']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">AI绘图</el-divider>
|
||||
<el-form-item label="DALL-E3出图数量">
|
||||
<el-input v-model.number="chat['dall_img_num']" placeholder="调用 DALL E3 API 传入的出图数量"/>
|
||||
</el-form-item>
|
||||
<el-form-item style="text-align: right">
|
||||
<el-button type="primary" @click="save('chat')">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="公告配置" name="notice">
|
||||
<md-editor class="mgb20" v-model="notice" @on-upload-img="onUploadImg"/>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save('system')">保存</el-button>
|
||||
<el-button type="primary" @click="save('notice')">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-divider content-position="center">模型通用配置</el-divider>
|
||||
|
||||
<el-form :model="chat" label-position="right" label-width="150px" ref="chatFormRef" :rules="rules">
|
||||
<el-form-item label="开启聊天上下文">
|
||||
<el-switch v-model="chat['enable_context']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="保存聊天记录">
|
||||
<el-switch v-model="chat['enable_history']"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="会话上下文深度">
|
||||
<el-input-number v-model="chat['context_deep']" :min="0" :max="10"/>
|
||||
<div class="tip" style="margin-top: 10px;">会话上下文深度:在老会话中继续会话,默认加载多少条聊天记录作为上下文。如果设置为
|
||||
0
|
||||
则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数最好设置需要为偶数,否则将无法兼容百度的 API。
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">OpenAI</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['open_ai']['temperature']" :max="2" :step="0.1"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['open_ai']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">Azure</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['azure']['temperature']" :max="2" :step="0.1"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['azure']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">ChatGLM</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['chat_gml']['temperature']" :max="1" :step="0.01"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['chat_gml']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">文心一言</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['baidu']['temperature']" :max="1" :step="0.01"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['baidu']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">讯飞星火</el-divider>
|
||||
<el-form-item label="模型创意度">
|
||||
<el-slider v-model="chat['xun_fei']['temperature']" :max="1" :step="0.1"/>
|
||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="最大响应长度">
|
||||
<el-input v-model.number="chat['xun_fei']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="center">AI绘图</el-divider>
|
||||
<el-form-item label="DALL-E3出图数量">
|
||||
<el-input v-model.number="chat['dall_img_num']" placeholder="调用 DALL E3 API 传入的出图数量"/>
|
||||
</el-form-item>
|
||||
<el-form-item style="text-align: right">
|
||||
<el-button type="primary" @click="save('chat')">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -229,7 +261,10 @@ import {httpGet, httpPost} from "@/utils/http";
|
||||
import Compressor from "compressorjs";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {InfoFilled, UploadFilled} from "@element-plus/icons-vue";
|
||||
import MdEditor from "md-editor-v3";
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
|
||||
const activeName = ref('basic')
|
||||
const system = ref({models: []})
|
||||
const chat = ref({
|
||||
open_ai: {temperature: 1, max_tokens: 1024},
|
||||
@@ -246,6 +281,7 @@ const loading = ref(true)
|
||||
const systemFormRef = ref(null)
|
||||
const chatFormRef = ref(null)
|
||||
const models = ref([])
|
||||
const notice = ref("")
|
||||
|
||||
onMounted(() => {
|
||||
// 加载系统配置
|
||||
@@ -258,13 +294,20 @@ onMounted(() => {
|
||||
// 加载聊天配置
|
||||
httpGet('/api/admin/config/get?key=chat').then(res => {
|
||||
chat.value = res.data
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("加载聊天配置失败: " + e.message)
|
||||
})
|
||||
|
||||
// 加载聊天配置
|
||||
httpGet('/api/admin/config/get?key=notice').then(res => {
|
||||
notice.value = res.data['content']
|
||||
}).catch(e => {
|
||||
ElMessage.error("公告信息失败: " + e.message)
|
||||
})
|
||||
|
||||
httpGet('/api/admin/model/list').then(res => {
|
||||
models.value = res.data
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取模型失败:" + e.message)
|
||||
})
|
||||
@@ -303,11 +346,22 @@ const save = function (key) {
|
||||
})
|
||||
}
|
||||
})
|
||||
} else if (key === 'notice') {
|
||||
httpPost('/api/admin/config/update', {key: key, config: {content: notice.value, updated: true}}).then(() => {
|
||||
ElMessage.success("操作成功!")
|
||||
}).catch(e => {
|
||||
ElMessage.error("操作失败:" + e.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const configKey = ref("")
|
||||
const beforeUpload = (key) => {
|
||||
configKey.value = key
|
||||
}
|
||||
|
||||
// 图片上传
|
||||
const uploadRewardImg = (file) => {
|
||||
const uploadImg = (file) => {
|
||||
// 压缩图片并上传
|
||||
new Compressor(file.file, {
|
||||
quality: 0.6,
|
||||
@@ -316,18 +370,39 @@ const uploadRewardImg = (file) => {
|
||||
formData.append('file', result, result.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then((res) => {
|
||||
system.value['reward_img'] = res.data
|
||||
system.value[configKey.value] = res.data.url
|
||||
ElMessage.success('上传成功')
|
||||
}).catch((e) => {
|
||||
ElMessage.error('上传失败:' + e.message)
|
||||
})
|
||||
},
|
||||
error(err) {
|
||||
console.log(err.message);
|
||||
error(e) {
|
||||
ElMessage.error('上传失败:' + e.message)
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 编辑期文件上传处理
|
||||
const onUploadImg = (files, callback) => {
|
||||
Promise.all(
|
||||
files.map((file) => {
|
||||
return new Promise((rev, rej) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, file.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then((res) => rev(res)).catch((error) => rej(error));
|
||||
});
|
||||
})
|
||||
).then(res => {
|
||||
ElMessage.success({message: "上传成功", duration: 500})
|
||||
callback(res.map((item) => item.data.url));
|
||||
}).catch(e => {
|
||||
ElMessage.error('图片上传失败:' + e.message)
|
||||
})
|
||||
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@@ -336,41 +411,46 @@ const uploadRewardImg = (file) => {
|
||||
display flex
|
||||
justify-content center
|
||||
|
||||
.container {
|
||||
.el-tabs {
|
||||
width 100%
|
||||
max-width 800px;
|
||||
background-color #ffffff
|
||||
padding 10px 20px 40px 20px
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px
|
||||
|
||||
.el-form {
|
||||
.el-form-item__content {
|
||||
.container {
|
||||
.el-form {
|
||||
.el-form-item__content {
|
||||
|
||||
.tip-text {
|
||||
padding-left 10px;
|
||||
.tip-text {
|
||||
padding-left 10px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
color #c1c1c1
|
||||
font-size 12px;
|
||||
line-height 1.5;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size 16px
|
||||
margin-left 10px
|
||||
cursor pointer
|
||||
}
|
||||
|
||||
.uploader-icon {
|
||||
font-size 24px
|
||||
position relative
|
||||
top 3px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
color #c1c1c1
|
||||
font-size 12px;
|
||||
line-height 1.5;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size 16px
|
||||
margin-left 10px
|
||||
cursor pointer
|
||||
}
|
||||
|
||||
.uploader-icon {
|
||||
font-size 24px
|
||||
position relative
|
||||
top 3px
|
||||
}
|
||||
.el-alert {
|
||||
margin-bottom 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-alert {
|
||||
margin-bottom 15px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -200,6 +200,7 @@ const lineBuffer = ref(''); // 输出缓冲行
|
||||
const socket = ref(null);
|
||||
const activelyClose = ref(false); // 主动关闭
|
||||
const canSend = ref(true);
|
||||
const heartbeatHandle = ref(null)
|
||||
const connect = function (chat_id, role_id) {
|
||||
let isNewChat = false;
|
||||
if (!chat_id) {
|
||||
@@ -238,6 +239,14 @@ const connect = function (chat_id, role_id) {
|
||||
orgContent: role.helloMsg,
|
||||
})
|
||||
}
|
||||
|
||||
// 发送心跳消息
|
||||
clearInterval(heartbeatHandle.value)
|
||||
heartbeatHandle.value = setInterval(() => {
|
||||
if (socket.value !== null) {
|
||||
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
|
||||
}
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
_socket.addEventListener('message', event => {
|
||||
@@ -370,7 +379,7 @@ const sendMessage = () => {
|
||||
})
|
||||
|
||||
disableInput(false)
|
||||
socket.value.send(prompt.value);
|
||||
socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
|
||||
previousText.value = prompt.value;
|
||||
prompt.value = '';
|
||||
return true;
|
||||
|
||||