merge v4.1.0 and fixed conflicts
11
CHANGELOG.md
@ -1,4 +1,15 @@
|
||||
# 更新日志
|
||||
## v4.1.0
|
||||
* bug修复:修复移动端修改聊天标题不生效的问题
|
||||
* Bug修复:修复用户注册不显示用户名的问题
|
||||
* Bug修复:修复管理后台拖动排序不生效的问题
|
||||
* 功能优化:允许用户设置自定义首页背景图片
|
||||
* 功能新增:**支持AI解读 PDF, Word, Excel等文件**
|
||||
* 功能优化:优化聊天界面的用户上传文件的列表样式
|
||||
* 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换
|
||||
* 功能新增:支持微信扫码登录,未注册用户微信扫码后会自动注册并登录。移动使用微信浏览器打开可以实现无感登录。
|
||||
|
||||
|
||||
## v4.0.9
|
||||
* 环境升级:升级 Golang 到 go1.22.4
|
||||
* 功能增加:接入微信商户号支付渠道
|
||||
|
@ -5,6 +5,7 @@ StaticDir = "./static" # 静态资源的目录
|
||||
StaticUrl = "/static" # 静态资源访问 URL
|
||||
AesEncryptKey = ""
|
||||
WeChatBot = false
|
||||
TikaHost = "http://tika:9998"
|
||||
|
||||
[Session]
|
||||
SecretKey = "azyehq3ivunjhbntz78isj00i4hz2mt9xtddysfucxakadq4qbfrt0b7q3lnvg80" # 注意:这个是 JWT Token 授权密钥,生产环境请务必更换
|
||||
@ -122,4 +123,16 @@ WeChatBot = false
|
||||
AppId = "" # 商户 ID
|
||||
PrivateKey = "" # 秘钥
|
||||
ApiURL = "https://payjs.cn"
|
||||
NotifyURL = "https://ai.r9it.com/api/payment/payjs/notify" # 异步回调地址,域名改成你自己的
|
||||
NotifyURL = "https://ai.r9it.com/api/payment/payjs/notify" # 异步回调地址,域名改成你自己的
|
||||
|
||||
# 微信商户支付
|
||||
[WechatPayConfig]
|
||||
Enabled = false
|
||||
AppId = "" # 商户应用ID
|
||||
MchId = "" # 商户号
|
||||
SerialNo = "" # API 证书序列号
|
||||
PrivateKey = "certs/alipay/privateKey.txt" # API 证书私钥文件路径,跟支付宝一样,把私钥文件拷贝到对应的路径,证书路径要映射到容器内
|
||||
ApiV3Key = "" # APIV3 私钥,这个是你自己在微信支付平台设置的
|
||||
NotifyURL = "https://ai.r9it.com/api/payment/wechat/notify" # 支付成功异步回调地址,域名改成自己的
|
||||
ReturnURL = "" # 支付成功同步回调地址
|
||||
|
||||
|
@ -32,31 +32,19 @@ import (
|
||||
)
|
||||
|
||||
type AppServer struct {
|
||||
Debug bool
|
||||
Config *types.AppConfig
|
||||
Engine *gin.Engine
|
||||
ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message
|
||||
|
||||
Debug bool
|
||||
Config *types.AppConfig
|
||||
Engine *gin.Engine
|
||||
SysConfig *types.SystemConfig // system config cache
|
||||
|
||||
// 保存 Websocket 会话 UserId, 每个 UserId 只能连接一次
|
||||
// 防止第三方直接连接 socket 调用 OpenAI API
|
||||
ChatSession *types.LMap[string, *types.ChatSession] //map[sessionId]UserId
|
||||
ChatClients *types.LMap[string, *types.WsClient] // map[sessionId]Websocket 连接集合
|
||||
ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
|
||||
}
|
||||
|
||||
func NewServer(appConfig *types.AppConfig) *AppServer {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
gin.DefaultWriter = io.Discard
|
||||
return &AppServer{
|
||||
Debug: false,
|
||||
Config: appConfig,
|
||||
Engine: gin.Default(),
|
||||
ChatContexts: types.NewLMap[string, []types.Message](),
|
||||
ChatSession: types.NewLMap[string, *types.ChatSession](),
|
||||
ChatClients: types.NewLMap[string, *types.WsClient](),
|
||||
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
|
||||
Debug: false,
|
||||
Config: appConfig,
|
||||
Engine: gin.Default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,6 +225,7 @@ func needLogin(c *gin.Context) bool {
|
||||
c.Request.URL.Path == "/api/payment/doPay" ||
|
||||
c.Request.URL.Path == "/api/payment/payWays" ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/config/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
||||
|
@ -53,9 +53,8 @@ type Delta struct {
|
||||
// ChatSession 聊天会话对象
|
||||
type ChatSession struct {
|
||||
SessionId string `json:"session_id"`
|
||||
UserId uint `json:"user_id"`
|
||||
ClientIP string `json:"client_ip"` // 客户端 IP
|
||||
Username string `json:"username"` // 当前登录的 username
|
||||
UserId uint `json:"user_id"` // 当前登录的 user ID
|
||||
ChatId string `json:"chat_id"` // 客户端聊天会话 ID, 多会话模式专用字段
|
||||
Model ChatModel `json:"model"` // GPT 模型
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ type AppConfig struct {
|
||||
SmtpConfig SmtpConfig // 邮件发送配置
|
||||
JPayConfig JPayConfig // payjs 支付配置
|
||||
WechatPayConfig WechatPayConfig // 微信支付渠道配置
|
||||
TikaHost string // TiKa 服务器地址
|
||||
}
|
||||
|
||||
type SmtpConfig struct {
|
||||
@ -226,5 +227,5 @@ type SystemConfig struct {
|
||||
|
||||
SdNegPrompt string `json:"sd_neg_prompt"` // SD 默认反向提示词
|
||||
|
||||
RandBg bool `json:"rand_bg"` // 前端首页是否启用随机背景
|
||||
IndexBgURL string `json:"index_bg_url"` // 前端首页背景图片
|
||||
}
|
||||
|
@ -53,10 +53,10 @@ type SdTaskParams struct {
|
||||
NegPrompt string `json:"neg_prompt"` // 反向提示词
|
||||
Steps int `json:"steps"` // 迭代步数,默认20
|
||||
Sampler string `json:"sampler"` // 采样器
|
||||
Scheduler string `json:"scheduler"`
|
||||
FaceFix bool `json:"face_fix"` // 面部修复
|
||||
CfgScale float32 `json:"cfg_scale"` //引导系数,默认 7
|
||||
Seed int64 `json:"seed"` // 随机数种子
|
||||
Scheduler string `json:"scheduler"` // 采样调度
|
||||
FaceFix bool `json:"face_fix"` // 面部修复
|
||||
CfgScale float32 `json:"cfg_scale"` //引导系数,默认 7
|
||||
Seed int64 `json:"seed"` // 随机数种子
|
||||
Height int `json:"height"`
|
||||
Width int `json:"width"`
|
||||
HdFix bool `json:"hd_fix"` // 启用高清修复
|
||||
|
@ -22,6 +22,7 @@ type WsMessage struct {
|
||||
Type WsMsgType `json:"type"` // 消息类别,start, end, img
|
||||
Content interface{} `json:"content"`
|
||||
}
|
||||
|
||||
type WsMsgType string
|
||||
|
||||
const (
|
||||
@ -36,11 +37,9 @@ type BizCode int
|
||||
const (
|
||||
Success = BizCode(0)
|
||||
Failed = BizCode(1)
|
||||
NotAuthorized = BizCode(400) // 未授权
|
||||
NotPermission = BizCode(403) // 没有权限
|
||||
NotAuthorized = BizCode(401) // 未授权
|
||||
|
||||
OkMsg = "Success"
|
||||
ErrorMsg = "系统开小差了"
|
||||
InvalidArgs = "非法参数或参数解析失败"
|
||||
NoData = "No Data"
|
||||
)
|
||||
|
12
api/go.mod
@ -28,14 +28,17 @@ require github.com/xxl-job/xxl-job-executor-go v1.2.0
|
||||
|
||||
require (
|
||||
github.com/go-pay/gopay v1.5.101
|
||||
github.com/mojocn/base64Captcha v1.3.1
|
||||
github.com/google/go-tika v0.3.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.26
|
||||
github.com/mojocn/base64Captcha v1.3.6
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
||||
golang.org/x/image v0.15.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-pay/crypto v0.0.1 // indirect
|
||||
github.com/go-pay/errgroup v0.0.2 // indirect
|
||||
@ -44,8 +47,9 @@ require (
|
||||
github.com/go-pay/xtime v0.0.2 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
)
|
||||
|
40
api/go.sum
@ -6,6 +6,8 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiw
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
@ -91,11 +93,15 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-tika v0.3.1 h1:l+jr10hDhZjcgxFRfcQChRLo1bPXQeLFluMyvDhXTTA=
|
||||
github.com/google/go-tika v0.3.1/go.mod h1:DJh5N8qxXIl85QkqmXknd+PeeRkUOTbvwyYf7ieDz6c=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@ -137,6 +143,8 @@ github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230415042440-a5e3d8259
|
||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230415042440-a5e3d8259ae0/go.mod h1:C5LA5UO2ZXJrLaPLYtE1wUJMiyd/nwWaCO5cw/2pSHs=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.62 h1:qNYsFZHEzl+NfH8UxW4jpmlKav1qUAgfY30YNRneVhc=
|
||||
@ -149,8 +157,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mojocn/base64Captcha v1.3.1 h1:2Wbkt8Oc8qjmNJ5GyOfSo4tgVQPsbKMftqASnq8GlT0=
|
||||
github.com/mojocn/base64Captcha v1.3.1/go.mod h1:wAQCKEc5bDujxKRmbT6/vTnTt5CjStQ8bRfPWUuz/iY=
|
||||
github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw=
|
||||
github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
@ -212,8 +220,10 @@ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gt
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
@ -251,14 +261,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -266,11 +279,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -285,17 +303,28 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
@ -303,6 +332,7 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -8,6 +8,8 @@ package admin
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"geekai/core"
|
||||
"geekai/core/types"
|
||||
"geekai/handler"
|
||||
@ -16,11 +18,8 @@ import (
|
||||
"geekai/store/vo"
|
||||
"geekai/utils"
|
||||
"geekai/utils/resp"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -56,11 +55,11 @@ func (h *ManagerHandler) Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// add captcha
|
||||
if !base64Captcha.DefaultMemStore.Verify(data.CaptchaId, data.Captcha, true) {
|
||||
resp.ERROR(c, "验证码错误!")
|
||||
return
|
||||
}
|
||||
//// add captcha
|
||||
//if !base64Captcha.DefaultMemStore.Verify(data.CaptchaId, data.Captcha, true) {
|
||||
// resp.ERROR(c, "验证码错误!")
|
||||
// return
|
||||
//}
|
||||
|
||||
var manager model.AdminUser
|
||||
res := h.DB.Model(&model.AdminUser{}).Where("username = ?", data.Username).First(&manager)
|
||||
|
@ -1,46 +0,0 @@
|
||||
package admin
|
||||
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
||||
// * Use of this source code is governed by a Apache-2.0 license
|
||||
// * that can be found in the LICENSE file.
|
||||
// * @Author yangjian102621@163.com
|
||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
import (
|
||||
"geekai/core"
|
||||
"geekai/handler"
|
||||
"geekai/utils/resp"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
type CaptchaHandler struct {
|
||||
handler.BaseHandler
|
||||
}
|
||||
|
||||
func NewCaptchaHandler(app *core.AppServer) *CaptchaHandler {
|
||||
return &CaptchaHandler{BaseHandler: handler.BaseHandler{App: app}}
|
||||
}
|
||||
|
||||
type CaptchaVo struct {
|
||||
CaptchaId string `json:"captcha_id"`
|
||||
PicPath string `json:"pic_path"`
|
||||
}
|
||||
|
||||
// GetCaptcha 获取验证码
|
||||
func (h *CaptchaHandler) GetCaptcha(c *gin.Context) {
|
||||
var captchaVo CaptchaVo
|
||||
driver := base64Captcha.NewDriverDigit(48, 130, 4, 0.4, 10)
|
||||
cp := base64Captcha.NewCaptcha(driver, base64Captcha.DefaultMemStore)
|
||||
// b64s是图片的base64编码
|
||||
id, b64s, err := cp.Generate()
|
||||
if err != nil {
|
||||
resp.ERROR(c, "生成验证码错误!")
|
||||
return
|
||||
}
|
||||
captchaVo.CaptchaId = id
|
||||
captchaVo.PicPath = b64s
|
||||
|
||||
resp.SUCCESS(c, captchaVo)
|
||||
}
|
@ -54,7 +54,6 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
|
||||
Name: data.Name,
|
||||
Value: data.Value,
|
||||
Enabled: data.Enabled,
|
||||
SortNum: data.SortNum,
|
||||
Open: data.Open,
|
||||
MaxTokens: data.MaxTokens,
|
||||
MaxContext: data.MaxContext,
|
||||
@ -64,6 +63,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
|
||||
var res *gorm.DB
|
||||
if data.Id > 0 {
|
||||
item.Id = data.Id
|
||||
item.SortNum = data.SortNum
|
||||
res = h.DB.Select("*").Omit("created_at").Updates(&item)
|
||||
} else {
|
||||
res = h.DB.Create(&item)
|
||||
|
@ -139,7 +139,6 @@ func (h *UserHandler) Save(c *gin.Context) {
|
||||
salt := utils.RandString(8)
|
||||
u := model.User{
|
||||
Username: data.Username,
|
||||
Nickname: fmt.Sprintf("极客学长@%d", utils.RandomNumber(6)),
|
||||
Password: utils.GenPassword(data.Password, salt),
|
||||
Avatar: "/images/avatar/user.png",
|
||||
Salt: salt,
|
||||
@ -149,6 +148,11 @@ func (h *UserHandler) Save(c *gin.Context) {
|
||||
ChatModels: utils.JsonEncode(data.ChatModels),
|
||||
ExpiredTime: utils.Str2stamp(data.ExpiredTime),
|
||||
}
|
||||
if h.licenseService.GetLicense().Configs.DeCopy {
|
||||
u.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
|
||||
} else {
|
||||
u.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6))
|
||||
}
|
||||
res = h.DB.Create(&u)
|
||||
_ = utils.CopyObject(u, &userVo)
|
||||
userVo.Id = u.Id
|
||||
|
@ -65,7 +65,7 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
|
||||
}
|
||||
|
||||
for _, r := range roles {
|
||||
if !utils.ContainsStr(roleKeys, r.Key) {
|
||||
if !utils.Contains(roleKeys, r.Key) {
|
||||
continue
|
||||
}
|
||||
var v vo.ChatRole
|
||||
|
@ -44,6 +44,8 @@ type ChatHandler struct {
|
||||
redis *redis.Client
|
||||
uploadManager *oss.UploaderManager
|
||||
licenseService *service.LicenseService
|
||||
ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
|
||||
ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message
|
||||
}
|
||||
|
||||
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService) *ChatHandler {
|
||||
@ -52,6 +54,8 @@ func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manag
|
||||
redis: redis,
|
||||
uploadManager: manager,
|
||||
licenseService: licenseService,
|
||||
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
|
||||
ChatContexts: types.NewLMap[string, []types.Message](),
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,21 +93,10 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
session := h.App.ChatSession.Get(sessionId)
|
||||
if session == nil {
|
||||
user, err := h.GetLoginUser(c)
|
||||
if err != nil {
|
||||
logger.Info("用户未登录")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
session = &types.ChatSession{
|
||||
SessionId: sessionId,
|
||||
ClientIP: c.ClientIP(),
|
||||
Username: user.Username,
|
||||
UserId: user.Id,
|
||||
}
|
||||
h.App.ChatSession.Put(sessionId, session)
|
||||
session := &types.ChatSession{
|
||||
SessionId: sessionId,
|
||||
ClientIP: c.ClientIP(),
|
||||
UserId: h.GetLoginUserId(c),
|
||||
}
|
||||
|
||||
// use old chat data override the chat model and role ID
|
||||
@ -125,22 +118,18 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
||||
Temperature: chatModel.Temperature,
|
||||
KeyId: chatModel.KeyId,
|
||||
Platform: chatModel.Platform}
|
||||
logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username)
|
||||
logger.Infof("New websocket connected, IP: %s", c.ClientIP())
|
||||
|
||||
// 保存会话连接
|
||||
h.App.ChatClients.Put(sessionId, client)
|
||||
go func() {
|
||||
for {
|
||||
_, msg, err := client.Receive()
|
||||
if err != nil {
|
||||
logger.Debugf("close connection: %s", client.Conn.RemoteAddr())
|
||||
client.Close()
|
||||
h.App.ChatClients.Delete(sessionId)
|
||||
h.App.ChatSession.Delete(sessionId)
|
||||
cancelFunc := h.App.ReqCancelFunc.Get(sessionId)
|
||||
cancelFunc := h.ReqCancelFunc.Get(sessionId)
|
||||
if cancelFunc != nil {
|
||||
cancelFunc()
|
||||
h.App.ReqCancelFunc.Delete(sessionId)
|
||||
h.ReqCancelFunc.Delete(sessionId)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -160,7 +149,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
||||
logger.Info("Receive a message: ", message.Content)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
h.App.ReqCancelFunc.Put(sessionId, cancel)
|
||||
h.ReqCancelFunc.Put(sessionId, cancel)
|
||||
// 回复消息
|
||||
err = h.sendMessage(ctx, session, chatRole, utils.InterfaceToString(message.Content), client)
|
||||
if err != nil {
|
||||
@ -274,8 +263,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
||||
chatCtx := make([]types.Message, 0)
|
||||
messages := make([]types.Message, 0)
|
||||
if h.App.SysConfig.EnableContext {
|
||||
if h.App.ChatContexts.Has(session.ChatId) {
|
||||
messages = h.App.ChatContexts.Get(session.ChatId)
|
||||
if h.ChatContexts.Has(session.ChatId) {
|
||||
messages = h.ChatContexts.Get(session.ChatId)
|
||||
} else {
|
||||
_ = utils.JsonDecode(role.Context, &messages)
|
||||
if h.App.SysConfig.ContextDeep > 0 {
|
||||
@ -323,20 +312,51 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
||||
reqMgs = append(reqMgs, m)
|
||||
}
|
||||
|
||||
fullPrompt := prompt
|
||||
text := prompt
|
||||
// extract files in prompt
|
||||
files := utils.ExtractFileURLs(prompt)
|
||||
logger.Debugf("detected FILES: %+v", files)
|
||||
// 如果不是逆向模型,则提取文件内容
|
||||
if len(files) > 0 && !(session.Model.Value == "gpt-4-all" ||
|
||||
strings.HasPrefix(session.Model.Value, "gpt-4-gizmo") ||
|
||||
strings.HasSuffix(session.Model.Value, "claude-3")) {
|
||||
contents := make([]string, 0)
|
||||
var file model.File
|
||||
for _, v := range files {
|
||||
h.DB.Where("url = ?", v).First(&file)
|
||||
content, err := utils.ReadFileContent(v, h.App.Config.TikaHost)
|
||||
if err != nil {
|
||||
logger.Error("error with read file: ", err)
|
||||
} else {
|
||||
contents = append(contents, fmt.Sprintf("%s 文件内容:%s", file.Name, content))
|
||||
}
|
||||
text = strings.Replace(text, v, "", 1)
|
||||
}
|
||||
if len(contents) > 0 {
|
||||
fullPrompt = fmt.Sprintf("请根据提供的文件内容信息回答问题(其中Excel 已转成 HTML):\n\n %s\n\n 问题:%s", strings.Join(contents, "\n"), text)
|
||||
}
|
||||
|
||||
tokens, _ := utils.CalcTokens(fullPrompt, req.Model)
|
||||
if tokens > session.Model.MaxContext {
|
||||
return fmt.Errorf("文件的长度超出模型允许的最大上下文长度,请减少文件内容数量或文件大小。")
|
||||
}
|
||||
}
|
||||
logger.Debug("最终Prompt:", fullPrompt)
|
||||
|
||||
if session.Model.Platform == types.QWen.Value {
|
||||
req.Input = make(map[string]interface{})
|
||||
reqMgs = append(reqMgs, types.Message{
|
||||
Role: "user",
|
||||
Content: prompt,
|
||||
Content: fullPrompt,
|
||||
})
|
||||
req.Input["messages"] = reqMgs
|
||||
} else if session.Model.Platform == types.OpenAI.Value || session.Model.Platform == types.Azure.Value { // extract image for gpt-vision model
|
||||
imgURLs := utils.ExtractImgURL(prompt)
|
||||
imgURLs := utils.ExtractImgURLs(prompt)
|
||||
logger.Debugf("detected IMG: %+v", imgURLs)
|
||||
var content interface{}
|
||||
if len(imgURLs) > 0 {
|
||||
data := make([]interface{}, 0)
|
||||
text := prompt
|
||||
for _, v := range imgURLs {
|
||||
text = strings.Replace(text, v, "", 1)
|
||||
data = append(data, gin.H{
|
||||
@ -348,11 +368,11 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
||||
}
|
||||
data = append(data, gin.H{
|
||||
"type": "text",
|
||||
"text": text,
|
||||
"text": strings.TrimSpace(text),
|
||||
})
|
||||
content = data
|
||||
} else {
|
||||
content = prompt
|
||||
content = fullPrompt
|
||||
}
|
||||
req.Messages = append(reqMgs, map[string]interface{}{
|
||||
"role": "user",
|
||||
@ -361,7 +381,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
||||
} else {
|
||||
req.Messages = append(reqMgs, map[string]interface{}{
|
||||
"role": "user",
|
||||
"content": prompt,
|
||||
"content": fullPrompt,
|
||||
})
|
||||
}
|
||||
|
||||
@ -442,9 +462,9 @@ func getTotalTokens(req types.ApiRequest) int {
|
||||
// StopGenerate 停止生成
|
||||
func (h *ChatHandler) StopGenerate(c *gin.Context) {
|
||||
sessionId := c.Query("session_id")
|
||||
if h.App.ReqCancelFunc.Has(sessionId) {
|
||||
h.App.ReqCancelFunc.Get(sessionId)()
|
||||
h.App.ReqCancelFunc.Delete(sessionId)
|
||||
if h.ReqCancelFunc.Has(sessionId) {
|
||||
h.ReqCancelFunc.Get(sessionId)()
|
||||
h.ReqCancelFunc.Delete(sessionId)
|
||||
}
|
||||
resp.SUCCESS(c, types.OkMsg)
|
||||
}
|
||||
@ -454,7 +474,7 @@ func (h *ChatHandler) StopGenerate(c *gin.Context) {
|
||||
func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, session *types.ChatSession, apiKey *model.ApiKey) (*http.Response, error) {
|
||||
// if the chat model bind a KEY, use it directly
|
||||
if session.Model.KeyId > 0 {
|
||||
h.DB.Debug().Where("id", session.Model.KeyId).Where("enabled", true).Find(apiKey)
|
||||
h.DB.Where("id", session.Model.KeyId).Find(apiKey)
|
||||
}
|
||||
// use the last unused key
|
||||
if apiKey.Id == 0 {
|
||||
@ -602,7 +622,7 @@ func (h *ChatHandler) saveChatHistory(
|
||||
if h.App.SysConfig.EnableContext {
|
||||
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||
chatCtx = append(chatCtx, message) // 回复消息
|
||||
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
||||
h.ChatContexts.Put(session.ChatId, chatCtx)
|
||||
}
|
||||
|
||||
// 追加聊天记录
|
||||
@ -660,7 +680,7 @@ func (h *ChatHandler) saveChatHistory(
|
||||
res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||
if res.Error != nil {
|
||||
chatItem.ChatId = session.ChatId
|
||||
chatItem.UserId = session.UserId
|
||||
chatItem.UserId = userVo.Id
|
||||
chatItem.RoleId = role.Id
|
||||
chatItem.ModelId = session.Model.Id
|
||||
if utf8.RuneCountInString(prompt) > 30 {
|
||||
|
@ -96,7 +96,7 @@ func (h *ChatHandler) Clear(c *gin.Context) {
|
||||
for _, chat := range chats {
|
||||
chatIds = append(chatIds, chat.ChatId)
|
||||
// 清空会话上下文
|
||||
h.App.ChatContexts.Delete(chat.ChatId)
|
||||
h.ChatContexts.Delete(chat.ChatId)
|
||||
}
|
||||
err = h.DB.Transaction(func(tx *gorm.DB) error {
|
||||
res := h.DB.Where("user_id =?", user.Id).Delete(&model.ChatItem{})
|
||||
@ -108,8 +108,6 @@ func (h *ChatHandler) Clear(c *gin.Context) {
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
|
||||
// TODO: 是否要删除 MidJourney 绘画记录和图片文件?
|
||||
return nil
|
||||
})
|
||||
|
||||
@ -175,7 +173,7 @@ func (h *ChatHandler) Remove(c *gin.Context) {
|
||||
// TODO: 是否要删除 MidJourney 绘画记录和图片文件?
|
||||
|
||||
// 清空会话上下文
|
||||
h.App.ChatContexts.Delete(chatId)
|
||||
h.ChatContexts.Delete(chatId)
|
||||
resp.SUCCESS(c, types.OkMsg)
|
||||
}
|
||||
|
||||
|
@ -74,6 +74,9 @@ func (h *ChatHandler) sendOpenAiMessage(
|
||||
if len(responseBody.Choices) == 0 { // Fixed: 兼容 Azure API 第一个输出空行
|
||||
continue
|
||||
}
|
||||
if responseBody.Choices[0].Delta.Content == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if responseBody.Choices[0].FinishReason == "stop" && len(contents) == 0 {
|
||||
utils.ReplyMessage(ws, "抱歉😔😔😔,AI助手由于未知原因已经停止输出内容。")
|
||||
|
@ -79,7 +79,7 @@ func (h *ChatHandler) sendXunFeiMessage(
|
||||
var res *gorm.DB
|
||||
// use the bind key
|
||||
if session.Model.KeyId > 0 {
|
||||
res = h.DB.Where("id", session.Model.KeyId).Where("enabled", true).Find(&apiKey)
|
||||
res = h.DB.Where("id", session.Model.KeyId).Find(&apiKey)
|
||||
}
|
||||
// use the last unused key
|
||||
if apiKey.Id == 0 {
|
||||
|
@ -158,13 +158,13 @@ func (h *DallJobHandler) ImgWall(c *gin.Context) {
|
||||
|
||||
// JobList 获取 SD 任务列表
|
||||
func (h *DallJobHandler) JobList(c *gin.Context) {
|
||||
status := h.GetBool(c, "status")
|
||||
finish := h.GetBool(c, "finish")
|
||||
userId := h.GetLoginUserId(c)
|
||||
page := h.GetInt(c, "page", 0)
|
||||
pageSize := h.GetInt(c, "page_size", 0)
|
||||
publish := h.GetBool(c, "publish")
|
||||
|
||||
err, jobs := h.getData(status, userId, page, pageSize, publish)
|
||||
err, jobs := h.getData(finish, userId, page, pageSize, publish)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
@ -214,25 +214,23 @@ func (h *DallJobHandler) getData(finish bool, userId uint, page int, pageSize in
|
||||
|
||||
// Remove remove task image
|
||||
func (h *DallJobHandler) Remove(c *gin.Context) {
|
||||
var data struct {
|
||||
Id uint `json:"id"`
|
||||
UserId uint `json:"user_id"`
|
||||
ImgURL string `json:"img_url"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
id := h.GetInt(c, "id", 0)
|
||||
userId := h.GetInt(c, "user_id", 0)
|
||||
var job model.DallJob
|
||||
if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
|
||||
resp.ERROR(c, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// remove job recode
|
||||
res := h.DB.Delete(&model.DallJob{Id: data.Id})
|
||||
res := h.DB.Delete(&model.DallJob{Id: job.Id})
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, res.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// remove image
|
||||
err := h.uploader.GetUploadHandler().Delete(data.ImgURL)
|
||||
err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
|
||||
if err != nil {
|
||||
logger.Error("remove image failed: ", err)
|
||||
}
|
||||
@ -242,16 +240,11 @@ func (h *DallJobHandler) Remove(c *gin.Context) {
|
||||
|
||||
// Publish 发布/取消发布图片到画廊显示
|
||||
func (h *DallJobHandler) 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
|
||||
}
|
||||
id := h.GetInt(c, "id", 0)
|
||||
userId := h.GetInt(c, "user_id", 0)
|
||||
action := h.GetBool(c, "action") // 发布动作,true => 发布,false => 取消分享
|
||||
|
||||
res := h.DB.Model(&model.DallJob{Id: data.Id}).UpdateColumn("publish", true)
|
||||
res := h.DB.Model(&model.DallJob{Id: uint(id), UserId: uint(userId)}).UpdateColumn("publish", action)
|
||||
if res.Error != nil {
|
||||
logger.Error("error with update database:", res.Error)
|
||||
resp.ERROR(c, "更新数据库失败")
|
||||
|
@ -215,7 +215,7 @@ func (h *MarkMapHandler) doRequest(req types.ApiRequest, chatModel model.ChatMod
|
||||
// if the chat model bind a KEY, use it directly
|
||||
var res *gorm.DB
|
||||
if chatModel.KeyId > 0 {
|
||||
res = h.DB.Where("id", chatModel.KeyId).Where("enabled", true).Find(apiKey)
|
||||
res = h.DB.Where("id", chatModel.KeyId).Find(apiKey)
|
||||
}
|
||||
// use the last unused key
|
||||
if apiKey.Id == 0 {
|
||||
|
@ -92,19 +92,18 @@ func (h *MidJourneyHandler) Client(c *gin.Context) {
|
||||
// Image 创建一个绘画任务
|
||||
func (h *MidJourneyHandler) Image(c *gin.Context) {
|
||||
var data struct {
|
||||
SessionId string `json:"session_id"`
|
||||
TaskType string `json:"task_type"`
|
||||
Prompt string `json:"prompt"`
|
||||
NegPrompt string `json:"neg_prompt"`
|
||||
Rate string `json:"rate"`
|
||||
Model string `json:"model"`
|
||||
Chaos int `json:"chaos"`
|
||||
Raw bool `json:"raw"`
|
||||
Seed int64 `json:"seed"`
|
||||
Stylize int `json:"stylize"`
|
||||
Model string `json:"model"` // 模型
|
||||
Chaos int `json:"chaos"` // 创意度取值范围: 0-100
|
||||
Raw bool `json:"raw"` // 是否开启原始模型
|
||||
Seed int64 `json:"seed"` // 随机数
|
||||
Stylize int `json:"stylize"` // 风格化
|
||||
ImgArr []string `json:"img_arr"`
|
||||
Tile bool `json:"tile"`
|
||||
Quality float32 `json:"quality"`
|
||||
Tile bool `json:"tile"` // 重复平铺
|
||||
Quality float32 `json:"quality"` // 画质
|
||||
Iw float32 `json:"iw"`
|
||||
CRef string `json:"cref"` //生成角色一致的图像
|
||||
SRef string `json:"sref"` //生成风格一致的图像
|
||||
@ -243,17 +242,12 @@ type reqVo struct {
|
||||
ChannelId string `json:"channel_id"`
|
||||
MessageId string `json:"message_id"`
|
||||
MessageHash string `json:"message_hash"`
|
||||
SessionId string `json:"session_id"`
|
||||
Prompt string `json:"prompt"`
|
||||
ChatId string `json:"chat_id"`
|
||||
RoleId int `json:"role_id"`
|
||||
Icon string `json:"icon"`
|
||||
}
|
||||
|
||||
// Upscale send upscale command to MidJourney Bot
|
||||
func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
||||
var data reqVo
|
||||
if err := c.ShouldBindJSON(&data); err != nil || data.SessionId == "" {
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
@ -271,7 +265,6 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
||||
UserId: userId,
|
||||
TaskId: taskId,
|
||||
Progress: 0,
|
||||
Prompt: data.Prompt,
|
||||
Power: h.App.SysConfig.MjActionPower,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
@ -283,7 +276,6 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
||||
h.pool.PushTask(types.MjTask{
|
||||
Id: job.Id,
|
||||
Type: types.TaskUpscale,
|
||||
Prompt: data.Prompt,
|
||||
UserId: userId,
|
||||
ChannelId: data.ChannelId,
|
||||
Index: data.Index,
|
||||
@ -318,7 +310,7 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
|
||||
// Variation send variation command to MidJourney Bot
|
||||
func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
||||
var data reqVo
|
||||
if err := c.ShouldBindJSON(&data); err != nil || data.SessionId == "" {
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
@ -337,7 +329,6 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
||||
UserId: userId,
|
||||
TaskId: taskId,
|
||||
Progress: 0,
|
||||
Prompt: data.Prompt,
|
||||
Power: h.App.SysConfig.MjActionPower,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
@ -349,7 +340,6 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
||||
h.pool.PushTask(types.MjTask{
|
||||
Id: job.Id,
|
||||
Type: types.TaskVariation,
|
||||
Prompt: data.Prompt,
|
||||
UserId: userId,
|
||||
Index: data.Index,
|
||||
ChannelId: data.ChannelId,
|
||||
@ -397,13 +387,13 @@ func (h *MidJourneyHandler) ImgWall(c *gin.Context) {
|
||||
|
||||
// JobList 获取 MJ 任务列表
|
||||
func (h *MidJourneyHandler) JobList(c *gin.Context) {
|
||||
status := h.GetBool(c, "status")
|
||||
finish := h.GetBool(c, "finish")
|
||||
userId := h.GetLoginUserId(c)
|
||||
page := h.GetInt(c, "page", 0)
|
||||
pageSize := h.GetInt(c, "page_size", 0)
|
||||
publish := h.GetBool(c, "publish")
|
||||
|
||||
err, jobs := h.getData(status, userId, page, pageSize, publish)
|
||||
err, jobs := h.getData(finish, userId, page, pageSize, publish)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
@ -446,14 +436,9 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize
|
||||
}
|
||||
|
||||
if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" {
|
||||
// discord 服务器图片需要使用代理转发图片数据流
|
||||
if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") {
|
||||
image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
|
||||
if err == nil {
|
||||
job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
|
||||
}
|
||||
} else {
|
||||
job.ImgURL = job.OrgURL
|
||||
image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
|
||||
if err == nil {
|
||||
job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,30 +449,27 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize
|
||||
|
||||
// Remove remove task image
|
||||
func (h *MidJourneyHandler) Remove(c *gin.Context) {
|
||||
var data struct {
|
||||
Id uint `json:"id"`
|
||||
UserId uint `json:"user_id"`
|
||||
ImgURL string `json:"img_url"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
id := h.GetInt(c, "id", 0)
|
||||
userId := h.GetInt(c, "user_id", 0)
|
||||
var job model.MidJourneyJob
|
||||
if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
|
||||
resp.ERROR(c, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// remove job recode
|
||||
res := h.DB.Delete(&model.MidJourneyJob{Id: data.Id})
|
||||
res := h.DB.Delete(&job)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, res.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// remove image
|
||||
err := h.uploader.GetUploadHandler().Delete(data.ImgURL)
|
||||
err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
|
||||
if err != nil {
|
||||
logger.Error("remove image failed: ", err)
|
||||
}
|
||||
|
||||
client := h.pool.Clients.Get(data.UserId)
|
||||
client := h.pool.Clients.Get(uint(job.UserId))
|
||||
if client != nil {
|
||||
_ = client.Send([]byte("Task Updated"))
|
||||
}
|
||||
@ -497,16 +479,10 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) {
|
||||
|
||||
// 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)
|
||||
id := h.GetInt(c, "id", 0)
|
||||
userId := h.GetInt(c, "user_id", 0)
|
||||
action := h.GetBool(c, "action") // 发布动作,true => 发布,false => 取消分享
|
||||
res := h.DB.Model(&model.MidJourneyJob{Id: uint(id), UserId: userId}).UpdateColumn("publish", action)
|
||||
if res.Error != nil {
|
||||
logger.Error("error with update database:", res.Error)
|
||||
resp.ERROR(c, "更新数据库失败")
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"geekai/store/vo"
|
||||
"geekai/utils"
|
||||
"geekai/utils/resp"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
@ -27,23 +28,18 @@ func NewOrderHandler(app *core.AppServer, db *gorm.DB) *OrderHandler {
|
||||
return &OrderHandler{BaseHandler: BaseHandler{App: app, DB: db}}
|
||||
}
|
||||
|
||||
// List 订单列表
|
||||
func (h *OrderHandler) List(c *gin.Context) {
|
||||
var data struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
page := h.GetInt(c, "page", 1)
|
||||
pageSize := h.GetInt(c, "page_size", 20)
|
||||
userId := h.GetLoginUserId(c)
|
||||
session := h.DB.Session(&gorm.Session{}).Where("user_id = ? AND status = ?", userId, types.OrderPaidSuccess)
|
||||
var total int64
|
||||
session.Model(&model.Order{}).Count(&total)
|
||||
var items []model.Order
|
||||
var list = make([]vo.Order, 0)
|
||||
offset := (data.Page - 1) * data.PageSize
|
||||
res := session.Order("id DESC").Offset(offset).Limit(data.PageSize).Find(&items)
|
||||
offset := (page - 1) * pageSize
|
||||
res := session.Order("id DESC").Offset(offset).Limit(pageSize).Find(&items)
|
||||
if res.Error == nil {
|
||||
for _, item := range items {
|
||||
var order vo.Order
|
||||
@ -58,5 +54,35 @@ func (h *OrderHandler) List(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, list))
|
||||
resp.SUCCESS(c, vo.NewPage(total, page, pageSize, list))
|
||||
}
|
||||
|
||||
// Query 查询订单状态
|
||||
func (h *OrderHandler) Query(c *gin.Context) {
|
||||
orderNo := h.GetTrim(c, "order_no")
|
||||
var order model.Order
|
||||
res := h.DB.Where("order_no = ?", orderNo).First(&order)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "Order not found")
|
||||
return
|
||||
}
|
||||
|
||||
if order.Status == types.OrderPaidSuccess {
|
||||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||||
return
|
||||
}
|
||||
|
||||
counter := 0
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
var item model.Order
|
||||
h.DB.Where("order_no = ?", orderNo).First(&item)
|
||||
if counter >= 15 || item.Status == types.OrderPaidSuccess || item.Status != order.Status {
|
||||
order.Status = item.Status
|
||||
break
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
|
||||
|
||||
// fix: 这里先检查一下订单状态,如果已经支付了,就直接返回
|
||||
if order.Status == types.OrderPaidSuccess {
|
||||
resp.ERROR(c, "This order had been paid, please do not pay twice")
|
||||
resp.ERROR(c, "订单已支付成功,无需重复支付!")
|
||||
return
|
||||
}
|
||||
|
||||
@ -148,49 +148,11 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
|
||||
resp.ERROR(c, "Invalid operations")
|
||||
}
|
||||
|
||||
// OrderQuery 查询订单状态
|
||||
func (h *PaymentHandler) OrderQuery(c *gin.Context) {
|
||||
var data struct {
|
||||
OrderNo string `json:"order_no"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
|
||||
var order model.Order
|
||||
res := h.DB.Where("order_no = ?", data.OrderNo).First(&order)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "Order not found")
|
||||
return
|
||||
}
|
||||
|
||||
if order.Status == types.OrderPaidSuccess {
|
||||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||||
return
|
||||
}
|
||||
|
||||
counter := 0
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
var item model.Order
|
||||
h.DB.Where("order_no = ?", data.OrderNo).First(&item)
|
||||
if counter >= 15 || item.Status == types.OrderPaidSuccess || item.Status != order.Status {
|
||||
order.Status = item.Status
|
||||
break
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, gin.H{"status": order.Status})
|
||||
}
|
||||
|
||||
// PayQrcode 生成支付 URL 二维码
|
||||
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
var data struct {
|
||||
PayWay string `json:"pay_way"` // 支付方式
|
||||
ProductId uint `json:"product_id"`
|
||||
UserId int `json:"user_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
@ -209,10 +171,9 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
|
||||
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
||||
return
|
||||
}
|
||||
var user model.User
|
||||
res = h.DB.First(&user, data.UserId)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "Invalid user ID")
|
||||
user, err := h.GetLoginUser(c)
|
||||
if err != nil {
|
||||
resp.NotAuth(c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -333,7 +294,6 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
var data struct {
|
||||
PayWay string `json:"pay_way"` // 支付方式
|
||||
ProductId uint `json:"product_id"`
|
||||
UserId int `json:"user_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
@ -352,10 +312,9 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
resp.ERROR(c, "error with generate trade no: "+err.Error())
|
||||
return
|
||||
}
|
||||
var user model.User
|
||||
res = h.DB.First(&user, data.UserId)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "Invalid user ID")
|
||||
user, err := h.GetLoginUser(c)
|
||||
if err != nil {
|
||||
resp.NotAuth(c)
|
||||
return
|
||||
}
|
||||
|
||||
@ -449,7 +408,7 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, payURL)
|
||||
resp.SUCCESS(c, gin.H{"url": payURL, "order_no": orderNo})
|
||||
}
|
||||
|
||||
// 异步通知回调公共逻辑
|
||||
|
@ -99,10 +99,7 @@ func (h *SdJobHandler) Image(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var data struct {
|
||||
SessionId string `json:"session_id"`
|
||||
types.SdTaskParams
|
||||
}
|
||||
var data types.SdTaskParams
|
||||
if err := c.ShouldBindJSON(&data); err != nil || data.Prompt == "" {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
@ -215,13 +212,13 @@ func (h *SdJobHandler) ImgWall(c *gin.Context) {
|
||||
|
||||
// JobList 获取 SD 任务列表
|
||||
func (h *SdJobHandler) JobList(c *gin.Context) {
|
||||
status := h.GetBool(c, "status")
|
||||
finish := h.GetBool(c, "finish")
|
||||
userId := h.GetLoginUserId(c)
|
||||
page := h.GetInt(c, "page", 0)
|
||||
pageSize := h.GetInt(c, "page_size", 0)
|
||||
publish := h.GetBool(c, "publish")
|
||||
|
||||
err, jobs := h.getData(status, userId, page, pageSize, publish)
|
||||
err, jobs := h.getData(finish, userId, page, pageSize, publish)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
@ -280,30 +277,28 @@ func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int,
|
||||
|
||||
// Remove remove task image
|
||||
func (h *SdJobHandler) Remove(c *gin.Context) {
|
||||
var data struct {
|
||||
Id uint `json:"id"`
|
||||
UserId uint `json:"user_id"`
|
||||
ImgURL string `json:"img_url"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
id := h.GetInt(c, "id", 0)
|
||||
userId := h.GetInt(c, "user_id", 0)
|
||||
var job model.SdJob
|
||||
if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
|
||||
resp.ERROR(c, "记录不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// remove job recode
|
||||
res := h.DB.Delete(&model.SdJob{Id: data.Id})
|
||||
res := h.DB.Delete(&model.SdJob{Id: job.Id})
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, res.Error.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// remove image
|
||||
err := h.uploader.GetUploadHandler().Delete(data.ImgURL)
|
||||
err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
|
||||
if err != nil {
|
||||
logger.Error("remove image failed: ", err)
|
||||
}
|
||||
|
||||
client := h.pool.Clients.Get(data.UserId)
|
||||
client := h.pool.Clients.Get(uint(job.UserId))
|
||||
if client != nil {
|
||||
_ = client.Send([]byte(sd.Finished))
|
||||
}
|
||||
@ -313,16 +308,11 @@ func (h *SdJobHandler) Remove(c *gin.Context) {
|
||||
|
||||
// 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
|
||||
}
|
||||
id := h.GetInt(c, "id", 0)
|
||||
userId := h.GetInt(c, "user_id", 0)
|
||||
action := h.GetBool(c, "action") // 发布动作,true => 发布,false => 取消分享
|
||||
|
||||
res := h.DB.Model(&model.SdJob{Id: data.Id}).UpdateColumn("publish", true)
|
||||
res := h.DB.Model(&model.SdJob{Id: uint(id), UserId: userId}).UpdateColumn("publish", action)
|
||||
if res.Error != nil {
|
||||
logger.Error("error with update database:", res.Error)
|
||||
resp.ERROR(c, "更新数据库失败")
|
||||
|
@ -49,14 +49,20 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
|
||||
var data struct {
|
||||
Receiver string `json:"receiver"` // 接收者
|
||||
Key string `json:"key"`
|
||||
Dots string `json:"dots"`
|
||||
Dots string `json:"dots,omitempty"`
|
||||
X int `json:"x,omitempty"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
|
||||
if !h.captcha.Check(data) {
|
||||
var check bool
|
||||
if data.X != 0 {
|
||||
check = h.captcha.SlideCheck(data)
|
||||
} else {
|
||||
check = h.captcha.Check(data)
|
||||
}
|
||||
if !check {
|
||||
resp.ERROR(c, "验证码错误,请先完人机验证")
|
||||
return
|
||||
}
|
||||
@ -64,13 +70,13 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
|
||||
code := utils.RandomNumber(6)
|
||||
var err error
|
||||
if strings.Contains(data.Receiver, "@") { // email
|
||||
if !utils.ContainsStr(h.App.SysConfig.RegisterWays, "email") {
|
||||
if !utils.Contains(h.App.SysConfig.RegisterWays, "email") {
|
||||
resp.ERROR(c, "系统已禁用邮箱注册!")
|
||||
return
|
||||
}
|
||||
err = h.smtp.SendVerifyCode(data.Receiver, code)
|
||||
} else {
|
||||
if !utils.ContainsStr(h.App.SysConfig.RegisterWays, "mobile") {
|
||||
if !utils.Contains(h.App.SysConfig.RegisterWays, "mobile") {
|
||||
resp.ERROR(c, "系统已禁用手机号注册!")
|
||||
return
|
||||
}
|
||||
@ -89,5 +95,9 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c)
|
||||
if h.App.Debug {
|
||||
resp.SUCCESS(c, code)
|
||||
} else {
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ package handler
|
||||
|
||||
import (
|
||||
"geekai/core"
|
||||
"geekai/core/types"
|
||||
"geekai/service/oss"
|
||||
"geekai/store/model"
|
||||
"geekai/store/vo"
|
||||
@ -35,6 +36,12 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("upload file: ", file.Name)
|
||||
// cut the file name if it's too long
|
||||
if len(file.Name) > 100 {
|
||||
file.Name = file.Name[:90] + file.Ext
|
||||
}
|
||||
|
||||
userId := h.GetLoginUserId(c)
|
||||
res := h.DB.Create(&model.File{
|
||||
UserId: int(userId),
|
||||
@ -54,10 +61,23 @@ func (h *UploadHandler) Upload(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (h *UploadHandler) List(c *gin.Context) {
|
||||
var data struct {
|
||||
Urls []string `json:"urls,omitempty"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
|
||||
userId := h.GetLoginUserId(c)
|
||||
var items []model.File
|
||||
var files = make([]vo.File, 0)
|
||||
h.DB.Where("user_id = ?", userId).Find(&items)
|
||||
session := h.DB.Session(&gorm.Session{})
|
||||
session = session.Where("user_id = ?", userId)
|
||||
if len(data.Urls) > 0 {
|
||||
session = session.Where("url IN ?", data.Urls)
|
||||
}
|
||||
session.Find(&items)
|
||||
if len(items) > 0 {
|
||||
for _, v := range items {
|
||||
var file vo.File
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"geekai/store/vo"
|
||||
"geekai/utils"
|
||||
"geekai/utils/resp"
|
||||
"github.com/imroc/req/v3"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -97,7 +98,7 @@ func (h *UserHandler) Register(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// check if the username is exists
|
||||
// check if the username is existing
|
||||
var item model.User
|
||||
res := h.DB.Where("username = ?", data.Username).First(&item)
|
||||
if item.Id > 0 {
|
||||
@ -122,7 +123,7 @@ func (h *UserHandler) Register(c *gin.Context) {
|
||||
user.Power += h.App.SysConfig.InvitePower
|
||||
}
|
||||
if h.licenseService.GetLicense().Configs.DeCopy {
|
||||
user.Username = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
|
||||
user.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
|
||||
} else {
|
||||
user.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6))
|
||||
}
|
||||
@ -184,7 +185,7 @@ func (h *UserHandler) Register(c *gin.Context) {
|
||||
resp.ERROR(c, "error with save token: "+err.Error())
|
||||
return
|
||||
}
|
||||
resp.SUCCESS(c, tokenString)
|
||||
resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
@ -243,7 +244,7 @@ func (h *UserHandler) Login(c *gin.Context) {
|
||||
resp.ERROR(c, "error with save token: "+err.Error())
|
||||
return
|
||||
}
|
||||
resp.SUCCESS(c, tokenString)
|
||||
resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
|
||||
}
|
||||
|
||||
// Logout 注 销
|
||||
@ -255,6 +256,130 @@ func (h *UserHandler) Logout(c *gin.Context) {
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
// CLogin 第三方登录请求二维码
|
||||
func (h *UserHandler) CLogin(c *gin.Context) {
|
||||
returnURL := h.GetTrim(c, "return_url")
|
||||
var res types.BizVo
|
||||
apiURL := fmt.Sprintf("%s/api/clogin/request", h.App.Config.ApiConfig.ApiURL)
|
||||
r, err := req.C().R().SetBody(gin.H{"login_type": "wx", "return_url": returnURL}).
|
||||
SetHeader("AppId", h.App.Config.ApiConfig.AppId).
|
||||
SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.App.Config.ApiConfig.Token)).
|
||||
SetSuccessResult(&res).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
}
|
||||
if r.IsErrorState() {
|
||||
resp.ERROR(c, "error with login http status: "+r.Status)
|
||||
return
|
||||
}
|
||||
|
||||
if res.Code != types.Success {
|
||||
resp.ERROR(c, "error with http response: "+res.Message)
|
||||
return
|
||||
}
|
||||
|
||||
resp.SUCCESS(c, res.Data)
|
||||
}
|
||||
|
||||
// CLoginCallback 第三方登录回调
|
||||
func (h *UserHandler) CLoginCallback(c *gin.Context) {
|
||||
loginType := h.GetTrim(c, "login_type")
|
||||
code := h.GetTrim(c, "code")
|
||||
|
||||
var res types.BizVo
|
||||
apiURL := fmt.Sprintf("%s/api/clogin/info", h.App.Config.ApiConfig.ApiURL)
|
||||
r, err := req.C().R().SetBody(gin.H{"login_type": loginType, "code": code}).
|
||||
SetHeader("AppId", h.App.Config.ApiConfig.AppId).
|
||||
SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.App.Config.ApiConfig.Token)).
|
||||
SetSuccessResult(&res).
|
||||
Post(apiURL)
|
||||
if err != nil {
|
||||
resp.ERROR(c, err.Error())
|
||||
return
|
||||
}
|
||||
if r.IsErrorState() {
|
||||
resp.ERROR(c, "error with login http status: "+r.Status)
|
||||
return
|
||||
}
|
||||
|
||||
if res.Code != types.Success {
|
||||
resp.ERROR(c, "error with http response: "+res.Message)
|
||||
return
|
||||
}
|
||||
|
||||
// login successfully
|
||||
data := res.Data.(map[string]interface{})
|
||||
session := gin.H{}
|
||||
var user model.User
|
||||
tx := h.DB.Debug().Where("openid", data["openid"]).First(&user)
|
||||
if tx.Error != nil { // user not exist, create new user
|
||||
// 检测最大注册人数
|
||||
var totalUser int64
|
||||
h.DB.Model(&model.User{}).Count(&totalUser)
|
||||
if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
|
||||
resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
|
||||
return
|
||||
}
|
||||
|
||||
salt := utils.RandString(8)
|
||||
password := fmt.Sprintf("%d", utils.RandomNumber(8))
|
||||
user = model.User{
|
||||
Username: fmt.Sprintf("%s@%d", loginType, utils.RandomNumber(10)),
|
||||
Password: utils.GenPassword(password, salt),
|
||||
Avatar: fmt.Sprintf("%s", data["avatar"]),
|
||||
Salt: salt,
|
||||
Status: true,
|
||||
ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
|
||||
ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
|
||||
Power: h.App.SysConfig.InitPower,
|
||||
OpenId: fmt.Sprintf("%s", data["openid"]),
|
||||
Nickname: fmt.Sprintf("%s", data["nickname"]),
|
||||
}
|
||||
|
||||
tx = h.DB.Create(&user)
|
||||
if tx.Error != nil {
|
||||
resp.ERROR(c, "保存数据失败")
|
||||
logger.Error(tx.Error)
|
||||
return
|
||||
}
|
||||
session["username"] = user.Username
|
||||
session["password"] = password
|
||||
} else { // login directly
|
||||
// 更新最后登录时间和IP
|
||||
user.LastLoginIp = c.ClientIP()
|
||||
user.LastLoginAt = time.Now().Unix()
|
||||
h.DB.Model(&user).Updates(user)
|
||||
|
||||
h.DB.Create(&model.UserLoginLog{
|
||||
UserId: user.Id,
|
||||
Username: user.Username,
|
||||
LoginIp: c.ClientIP(),
|
||||
LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
|
||||
})
|
||||
}
|
||||
|
||||
// 创建 token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"user_id": user.Id,
|
||||
"expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(),
|
||||
})
|
||||
tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
|
||||
if err != nil {
|
||||
resp.ERROR(c, "Failed to generate token, "+err.Error())
|
||||
return
|
||||
}
|
||||
// 保存到 redis
|
||||
key := fmt.Sprintf("users/%d", user.Id)
|
||||
if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil {
|
||||
resp.ERROR(c, "error with save token: "+err.Error())
|
||||
return
|
||||
}
|
||||
session["token"] = tokenString
|
||||
resp.SUCCESS(c, session)
|
||||
}
|
||||
|
||||
// Session 获取/验证会话
|
||||
func (h *UserHandler) Session(c *gin.Context) {
|
||||
user, err := h.GetLoginUser(c)
|
||||
|
27
api/main.go
@ -240,6 +240,8 @@ func main() {
|
||||
group.POST("password", h.UpdatePass)
|
||||
group.POST("bind/username", h.BindUsername)
|
||||
group.POST("resetPass", h.ResetPass)
|
||||
group.GET("clogin", h.CLogin)
|
||||
group.GET("clogin/callback", h.CLoginCallback)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *chatimpl.ChatHandler) {
|
||||
group := s.Engine.Group("/api/chat/")
|
||||
@ -255,7 +257,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)
|
||||
s.Engine.POST("/api/upload/list", h.List)
|
||||
s.Engine.GET("/api/upload/remove", h.Remove)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
|
||||
@ -281,8 +283,8 @@ func main() {
|
||||
group.POST("variation", h.Variation)
|
||||
group.GET("jobs", h.JobList)
|
||||
group.GET("imgWall", h.ImgWall)
|
||||
group.POST("remove", h.Remove)
|
||||
group.POST("publish", h.Publish)
|
||||
group.GET("remove", h.Remove)
|
||||
group.GET("publish", h.Publish)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.SdJobHandler) {
|
||||
group := s.Engine.Group("/api/sd")
|
||||
@ -290,8 +292,8 @@ func main() {
|
||||
group.POST("image", h.Image)
|
||||
group.GET("jobs", h.JobList)
|
||||
group.GET("imgWall", h.ImgWall)
|
||||
group.POST("remove", h.Remove)
|
||||
group.POST("publish", h.Publish)
|
||||
group.GET("remove", h.Remove)
|
||||
group.GET("publish", h.Publish)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.ConfigHandler) {
|
||||
group := s.Engine.Group("/api/config/")
|
||||
@ -368,7 +370,6 @@ func main() {
|
||||
group := s.Engine.Group("/api/payment/")
|
||||
group.GET("doPay", h.DoPay)
|
||||
group.GET("payWays", h.GetPayWays)
|
||||
group.POST("query", h.OrderQuery)
|
||||
group.POST("qrcode", h.PayQrcode)
|
||||
group.POST("mobile", h.Mobile)
|
||||
group.POST("alipay/notify", h.AlipayNotify)
|
||||
@ -391,7 +392,8 @@ func main() {
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.OrderHandler) {
|
||||
group := s.Engine.Group("/api/order/")
|
||||
group.POST("list", h.List)
|
||||
group.GET("list", h.List)
|
||||
group.GET("query", h.Query)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.ProductHandler) {
|
||||
group := s.Engine.Group("/api/product/")
|
||||
@ -416,13 +418,6 @@ func main() {
|
||||
group.GET("token", h.GenToken)
|
||||
}),
|
||||
|
||||
// 验证码
|
||||
fx.Provide(admin.NewCaptchaHandler),
|
||||
fx.Invoke(func(s *core.AppServer, h *admin.CaptchaHandler) {
|
||||
group := s.Engine.Group("/api/admin/login/")
|
||||
group.GET("captcha", h.GetCaptcha)
|
||||
}),
|
||||
|
||||
fx.Provide(admin.NewUploadHandler),
|
||||
fx.Invoke(func(s *core.AppServer, h *admin.UploadHandler) {
|
||||
s.Engine.POST("/api/admin/upload", h.Upload)
|
||||
@ -477,8 +472,8 @@ func main() {
|
||||
group.POST("image", h.Image)
|
||||
group.GET("jobs", h.JobList)
|
||||
group.GET("imgWall", h.ImgWall)
|
||||
group.POST("remove", h.Remove)
|
||||
group.POST("publish", h.Publish)
|
||||
group.GET("remove", h.Remove)
|
||||
group.GET("publish", h.Publish)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
||||
go func() {
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"geekai/service/sd"
|
||||
"geekai/store"
|
||||
"geekai/store/model"
|
||||
"geekai/utils"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"strings"
|
||||
"time"
|
||||
@ -58,13 +59,13 @@ func (p *ServicePool) InitServices(plusConfigs []types.MjPlusConfig, proxyConfig
|
||||
}
|
||||
p.services = make([]*Service, 0)
|
||||
|
||||
for k, config := range plusConfigs {
|
||||
for _, config := range plusConfigs {
|
||||
if config.Enabled == false {
|
||||
continue
|
||||
}
|
||||
|
||||
cli := NewPlusClient(config, p.licenseService)
|
||||
name := fmt.Sprintf("mj-plus-service-%d", k)
|
||||
name := utils.Md5(config.ApiURL)
|
||||
plusService := NewService(name, p.taskQueue, p.notifyQueue, p.db, cli)
|
||||
go func() {
|
||||
plusService.Run()
|
||||
@ -73,12 +74,12 @@ func (p *ServicePool) InitServices(plusConfigs []types.MjPlusConfig, proxyConfig
|
||||
}
|
||||
|
||||
// for mid-journey proxy
|
||||
for k, config := range proxyConfigs {
|
||||
for _, config := range proxyConfigs {
|
||||
if config.Enabled == false {
|
||||
continue
|
||||
}
|
||||
cli := NewProxyClient(config)
|
||||
name := fmt.Sprintf("mj-proxy-service-%d", k)
|
||||
name := utils.Md5(config.ApiURL)
|
||||
proxyService := NewService(name, p.taskQueue, p.notifyQueue, p.db, cli)
|
||||
go func() {
|
||||
proxyService.Run()
|
||||
@ -209,7 +210,6 @@ func (p *ServicePool) SyncTaskProgress() {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if servicePlus := p.getService(job.ChannelId); servicePlus != nil {
|
||||
_ = servicePlus.Notify(job)
|
||||
}
|
||||
|
@ -15,5 +15,7 @@ type User struct {
|
||||
Status bool `gorm:"default:true"` // 当前状态
|
||||
LastLoginAt int64 // 最后登录时间
|
||||
LastLoginIp string // 最后登录 IP
|
||||
OpenId string `gorm:"column:openid"`
|
||||
Platform string `json:"platform"`
|
||||
Vip bool // 是否 VIP 会员
|
||||
}
|
||||
|
@ -14,4 +14,6 @@ type User struct {
|
||||
LastLoginAt int64 `json:"last_login_at"` // 最后登录时间
|
||||
LastLoginIp string `json:"last_login_ip"` // 最后登录 IP
|
||||
Vip bool `json:"vip"`
|
||||
OpenId string `json:"openid"` // 第三方登录 OpenID
|
||||
Platform string `json:"platform"` // 第三方登录平台
|
||||
}
|
||||
|
@ -2,8 +2,15 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"geekai/utils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(fmt.Sprintf("%v", float64(90)/100))
|
||||
file := "http://nk.img.r9it.com/chatgpt-plus/1719389335351828.xlsx"
|
||||
content, err := utils.ReadFileContent(file, "http://172.22.11.69:9998")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(content)
|
||||
}
|
||||
|
106
api/utils/file.go
Normal file
@ -0,0 +1,106 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-tika/tika"
|
||||
)
|
||||
|
||||
func ReadFileContent(filePath string, tikaHost string) (string, error) {
|
||||
// for remote file, download it first
|
||||
if strings.HasPrefix(filePath, "http") {
|
||||
file, err := downloadFile(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
filePath = file
|
||||
}
|
||||
// 创建 Tika 客户端
|
||||
client := tika.NewClient(nil, tikaHost)
|
||||
// 打开 PDF 文件
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with open file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 使用 Tika 提取 PDF 文件的文本内容
|
||||
content, err := client.Parse(context.TODO(), file)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error with parse file: %v", err)
|
||||
}
|
||||
|
||||
ext := filepath.Ext(filePath)
|
||||
switch ext {
|
||||
case ".doc", ".docx", ".pdf", ".pptx", "ppt":
|
||||
return cleanBlankLine(cleanHtml(content, false)), nil
|
||||
case ".xls", ".xlsx":
|
||||
return cleanBlankLine(cleanHtml(content, true)), nil
|
||||
default:
|
||||
return cleanBlankLine(content), nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 清理文本内容
|
||||
func cleanHtml(html string, keepTable bool) string {
|
||||
// 清理 HTML 标签
|
||||
var policy *bluemonday.Policy
|
||||
if keepTable {
|
||||
policy = bluemonday.NewPolicy()
|
||||
policy.AllowElements("table", "thead", "tbody", "tfoot", "tr", "td", "th")
|
||||
} else {
|
||||
policy = bluemonday.StrictPolicy()
|
||||
}
|
||||
return policy.Sanitize(html)
|
||||
}
|
||||
|
||||
func cleanBlankLine(content string) string {
|
||||
lines := strings.Split(content, "\n")
|
||||
texts := make([]string, 0)
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) < 2 {
|
||||
continue
|
||||
}
|
||||
// discard image
|
||||
if strings.HasSuffix(line, ".png") ||
|
||||
strings.HasSuffix(line, ".jpg") ||
|
||||
strings.HasSuffix(line, ".jpeg") {
|
||||
continue
|
||||
}
|
||||
texts = append(texts, line)
|
||||
}
|
||||
|
||||
return strings.Join(texts, "\n")
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
func downloadFile(url string) (string, error) {
|
||||
base := filepath.Base(url)
|
||||
dir := os.TempDir()
|
||||
filename := filepath.Join(dir, base)
|
||||
out, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// 获取数据
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 写入数据到文件
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
return filename, err
|
||||
}
|
@ -24,28 +24,20 @@ func SUCCESS(c *gin.Context, values ...interface{}) {
|
||||
|
||||
func ERROR(c *gin.Context, messages ...string) {
|
||||
if messages != nil {
|
||||
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: messages[0]})
|
||||
c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed, Message: messages[0]})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed})
|
||||
c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed})
|
||||
}
|
||||
}
|
||||
|
||||
func HACKER(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Hacker attempt!!!"})
|
||||
c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed, Message: "Hacker attempt!!!"})
|
||||
}
|
||||
|
||||
func NotAuth(c *gin.Context, messages ...string) {
|
||||
if messages != nil {
|
||||
c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: messages[0]})
|
||||
c.JSON(http.StatusUnauthorized, types.BizVo{Code: types.NotAuthorized, Message: messages[0]})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"})
|
||||
}
|
||||
}
|
||||
|
||||
func NotPermission(c *gin.Context, messages ...string) {
|
||||
if messages != nil {
|
||||
c.JSON(http.StatusOK, types.BizVo{Code: types.NotPermission, Message: messages[0]})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, types.BizVo{Code: types.NotPermission, Message: "Not Permission"})
|
||||
c.JSON(http.StatusUnauthorized, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"})
|
||||
}
|
||||
}
|
||||
|
@ -31,11 +31,11 @@ func RandString(length int) string {
|
||||
}
|
||||
|
||||
func RandomNumber(bit int) int {
|
||||
min := intPow(10, bit-1)
|
||||
max := intPow(10, bit) - 1
|
||||
minNum := intPow(10, bit-1)
|
||||
maxNum := intPow(10, bit) - 1
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return rand.Intn(max-min+1) + min
|
||||
rand.NewSource(time.Now().UnixNano())
|
||||
return rand.Intn(maxNum-minNum+1) + minNum
|
||||
}
|
||||
|
||||
func intPow(x, y int) int {
|
||||
@ -46,7 +46,7 @@ func intPow(x, y int) int {
|
||||
return result
|
||||
}
|
||||
|
||||
func ContainsStr(slice []string, item string) bool {
|
||||
func Contains(slice []string, item string) bool {
|
||||
for _, e := range slice {
|
||||
if e == item {
|
||||
return true
|
||||
|
@ -88,7 +88,7 @@ func GetImgExt(filename string) string {
|
||||
return ext
|
||||
}
|
||||
|
||||
func ExtractImgURL(text string) []string {
|
||||
func ExtractImgURLs(text string) []string {
|
||||
re := regexp.MustCompile(`(http[s]?:\/\/.*?\.(?:png|jpg|jpeg|gif))`)
|
||||
matches := re.FindAllStringSubmatch(text, 10)
|
||||
urls := make([]string, 0)
|
||||
@ -99,3 +99,15 @@ func ExtractImgURL(text string) []string {
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
||||
func ExtractFileURLs(text string) []string {
|
||||
re := regexp.MustCompile(`(http[s]?:\/\/.*?\.(?:docx?|pdf|pptx?|xlsx?|txt))`)
|
||||
matches := re.FindAllStringSubmatch(text, 10)
|
||||
urls := make([]string, 0)
|
||||
if len(matches) > 0 {
|
||||
for _, m := range matches {
|
||||
urls = append(urls, m[1])
|
||||
}
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
@ -1,790 +0,0 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.1.3
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- 主机: localhost:3307
|
||||
-- 生成日期: 2024-04-07 10:30:00
|
||||
-- 服务器版本: 8.0.33
|
||||
-- PHP 版本: 8.1.18
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
START TRANSACTION;
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- 数据库: `chatgpt_plus`
|
||||
--
|
||||
CREATE DATABASE IF NOT EXISTS `chatgpt_plus` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
USE `chatgpt_plus`;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_admin_users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_admin_users`;
|
||||
CREATE TABLE `chatgpt_admin_users` (
|
||||
`id` int NOT NULL,
|
||||
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
|
||||
`password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
|
||||
`salt` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码盐',
|
||||
`status` tinyint(1) NOT NULL COMMENT '当前状态',
|
||||
`last_login_at` int NOT NULL COMMENT '最后登录时间',
|
||||
`last_login_ip` char(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '最后登录 IP',
|
||||
`created_at` datetime NOT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime NOT NULL COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户' ROW_FORMAT=DYNAMIC;
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_admin_users`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_admin_users` (`id`, `username`, `password`, `salt`, `status`, `last_login_at`, `last_login_ip`, `created_at`, `updated_at`) VALUES
|
||||
(1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1712456145, '::1', '2024-03-11 16:30:20', '2024-04-07 10:15:45'),
|
||||
(108, 'test', '9ed720ce03e0a69885455271b4b3e1710bff79434f2a95d0de6406dd88cc9f79', '4b9orqjh', 0, 1710396975, '::1', '2024-03-13 16:06:43', '2024-03-21 15:15:04');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_api_keys`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_api_keys`;
|
||||
CREATE TABLE `chatgpt_api_keys` (
|
||||
`id` int NOT NULL,
|
||||
`platform` char(20) DEFAULT NULL COMMENT '平台',
|
||||
`name` varchar(30) DEFAULT NULL COMMENT '名称',
|
||||
`value` varchar(100) NOT NULL COMMENT 'API KEY value',
|
||||
`type` varchar(10) NOT NULL DEFAULT 'chat' COMMENT '用途(chat=>聊天,img=>图片)',
|
||||
`last_used_at` int NOT NULL COMMENT '最后使用时间',
|
||||
`api_url` varchar(255) DEFAULT NULL COMMENT 'API 地址',
|
||||
`enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
|
||||
`proxy_url` varchar(100) DEFAULT NULL COMMENT '代理地址',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OpenAI API ';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_chat_history`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_chat_history`;
|
||||
CREATE TABLE `chatgpt_chat_history` (
|
||||
`id` bigint NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
|
||||
`type` varchar(10) NOT NULL COMMENT '类型:prompt|reply',
|
||||
`icon` varchar(100) NOT NULL COMMENT '角色图标',
|
||||
`role_id` int NOT NULL COMMENT '角色 ID',
|
||||
`model` varchar(30) DEFAULT NULL COMMENT '模型名称',
|
||||
`content` text NOT NULL COMMENT '聊天内容',
|
||||
`tokens` smallint NOT NULL COMMENT '耗费 token 数量',
|
||||
`use_context` tinyint(1) NOT NULL COMMENT '是否允许作为上下文语料',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL,
|
||||
`deleted_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天历史记录';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_chat_items`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_chat_items`;
|
||||
CREATE TABLE `chatgpt_chat_items` (
|
||||
`id` int NOT NULL,
|
||||
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`role_id` int NOT NULL COMMENT '角色 ID',
|
||||
`title` varchar(100) NOT NULL COMMENT '会话标题',
|
||||
`model_id` int NOT NULL DEFAULT '0' COMMENT '模型 ID',
|
||||
`model` varchar(30) DEFAULT NULL COMMENT '模型名称',
|
||||
`created_at` datetime NOT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime NOT NULL COMMENT '更新时间',
|
||||
`deleted_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户会话列表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_chat_models`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_chat_models`;
|
||||
CREATE TABLE `chatgpt_chat_models` (
|
||||
`id` int NOT NULL,
|
||||
`platform` varchar(20) DEFAULT NULL COMMENT '模型平台',
|
||||
`name` varchar(50) NOT NULL COMMENT '模型名称',
|
||||
`value` varchar(50) NOT NULL COMMENT '模型值',
|
||||
`sort_num` tinyint(1) NOT NULL COMMENT '排序数字',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用模型',
|
||||
`power` tinyint NOT NULL COMMENT '消耗算力点数',
|
||||
`temperature` float(3,1) NOT NULL DEFAULT '1.0' COMMENT '模型创意度',
|
||||
`max_tokens` int NOT NULL DEFAULT '1024' COMMENT '最大响应长度',
|
||||
`max_context` int NOT NULL DEFAULT '4096' COMMENT '最大上下文长度',
|
||||
`open` tinyint(1) NOT NULL COMMENT '是否开放模型',
|
||||
`created_at` datetime DEFAULT NULL,
|
||||
`updated_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI 模型表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_chat_models`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num`, `enabled`, `power`, `temperature`, `max_tokens`, `max_context`, `open`, `created_at`, `updated_at`) VALUES
|
||||
(1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo-0125', 0, 1, 1, 1.0, 1024, 4096, 1, '2023-08-23 12:06:36', '2024-03-18 15:43:51'),
|
||||
(2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 14, 1, 1, 1.0, 1024, 4096, 0, '2023-08-23 12:15:30', '2024-03-18 14:27:19'),
|
||||
(3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 3, 1, 1, 1.0, 2048, 32768, 1, '2023-08-23 13:35:45', '2024-03-18 14:27:19'),
|
||||
(7, 'Baidu', '文心一言3.0', 'eb-instant', 12, 1, 1, 1.0, 1024, 4096, 1, '2023-10-11 11:29:28', '2024-03-18 14:27:19'),
|
||||
(8, 'XunFei', '星火V3.5', 'generalv3.5', 2, 1, 5, 0.8, 1024, 8192, 1, '2023-10-11 15:48:30', '2024-03-18 14:27:19'),
|
||||
(9, 'XunFei', '星火V2.0', 'generalv2', 11, 1, 1, 1.0, 1024, 8192, 1, '2023-10-11 15:48:45', '2024-03-18 14:27:19'),
|
||||
(10, 'Baidu', '文心一言4.0', 'completions_pro', 13, 1, 3, 1.0, 1024, 8192, 1, '2023-10-25 08:31:37', '2024-03-18 14:27:19'),
|
||||
(11, 'OpenAI', 'GPT-4.0', 'gpt-4-0125-preview', 1, 1, 15, 1.0, 1024, 8192, 1, '2023-10-25 08:45:15', '2024-03-18 15:46:58'),
|
||||
(12, 'XunFei', '星火v3.0', 'generalv3', 10, 1, 3, 1.0, 1024, 8192, 1, '2023-11-23 09:20:33', '2024-03-18 14:27:19'),
|
||||
(15, 'OpenAI', 'GPT-超级模型', 'gpt-4-all', 4, 1, 30, 1.0, 4096, 32768, 0, '2024-01-15 11:32:52', '2024-03-18 14:27:19'),
|
||||
(16, 'OpenAI', '视频号导师', 'gpt-4-gizmo-g-QXXEBTXl7', 5, 1, 30, 1.0, 4096, 32768, 0, '2024-01-15 14:46:35', '2024-03-18 14:29:39'),
|
||||
(17, 'QWen', '通义千问-Turbo', 'qwen-turbo', 7, 1, 1, 1.0, 1024, 8192, 1, '2024-01-19 10:42:24', '2024-03-18 14:27:19'),
|
||||
(18, 'QWen', '通义千问-Plus', 'qwen-plus', 8, 1, 1, 1.0, 1024, 32768, 1, '2024-01-19 10:42:49', '2024-03-18 14:27:19'),
|
||||
(19, 'QWen', '通义千问-Max', 'qwen-max-1201', 9, 1, 1, 1.0, 1024, 32768, 1, '2024-01-19 10:51:03', '2024-03-18 14:27:19'),
|
||||
(21, 'OpenAI', '董宇辉小作文助手', 'gpt-4-gizmo-g-dse9iXvor', 6, 1, 30, 1.0, 8192, 32768, 0, '2024-03-18 14:24:20', '2024-03-18 14:27:19'),
|
||||
(22, 'OpenAI', 'LOGO生成神器', 'gpt-4-gizmo-g-YL87j8C7S', 0, 1, 30, 1.0, 1024, 4096, 1, '2024-03-20 14:02:11', '2024-03-20 14:02:18'),
|
||||
(23, 'OpenAI', '音乐生成器', 'suno-v3', 0, 1, 50, 0.8, 1024, 4096, 1, '2024-03-29 15:43:40', '2024-03-29 15:45:15'),
|
||||
(24, 'OpenAI', '通义千问(中转)', 'qwen-plus', 0, 1, 0, 1.0, 1024, 4096, 1, '2024-04-03 12:00:46', '2024-04-03 12:00:46');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_chat_roles`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_chat_roles`;
|
||||
CREATE TABLE `chatgpt_chat_roles` (
|
||||
`id` int NOT NULL,
|
||||
`name` varchar(30) NOT NULL COMMENT '角色名称',
|
||||
`marker` varchar(30) NOT NULL COMMENT '角色标识',
|
||||
`context_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色语料 json',
|
||||
`hello_msg` varchar(255) NOT NULL COMMENT '打招呼信息',
|
||||
`icon` varchar(255) NOT NULL COMMENT '角色图标',
|
||||
`enable` tinyint(1) NOT NULL COMMENT '是否被启用',
|
||||
`sort_num` smallint NOT NULL DEFAULT '0' COMMENT '角色排序',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天角色表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_chat_roles`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_chat_roles` (`id`, `name`, `marker`, `context_json`, `hello_msg`, `icon`, `enable`, `sort_num`, `created_at`, `updated_at`) VALUES
|
||||
(1, '通用AI助手', 'gpt', '', '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 0, '2023-05-30 07:02:06', '2024-03-15 09:15:42'),
|
||||
(24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 3, '2023-05-30 14:10:24', '2023-12-29 17:46:45'),
|
||||
(25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 2, '2023-05-30 14:10:24', '2023-12-29 17:46:44'),
|
||||
(26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 4, '2023-05-30 14:10:24', '2023-12-29 17:46:45'),
|
||||
(27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 1, '2023-05-30 14:10:24', '2023-12-29 17:46:43'),
|
||||
(28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 5, '2023-05-30 14:10:24', '2023-12-29 17:46:46'),
|
||||
(29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装:¥1188/箱,和系列 6 瓶装:¥2208/箱,贵系列 6 瓶装:¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 8, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
|
||||
(30, '英语陪练员', 'english_trainer', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。\"},{\"role\":\"assistant\",\"content\":\"Okay, let\'s start our conversation practice! What\'s your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)\"}]', 'Okay, let\'s start our conversation practice! What\'s your name?', '/images/avatar/english_trainer.jpg', 1, 6, '2023-05-30 14:10:24', '2023-12-29 17:46:47'),
|
||||
(31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 7, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
|
||||
(32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 9, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
|
||||
(33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 10, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
|
||||
(34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 11, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
|
||||
(35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 12, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
|
||||
(36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 13, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
|
||||
(37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 14, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
|
||||
(38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 15, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
|
||||
(39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 16, '2023-05-30 14:10:24', '2023-12-29 17:43:53');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_configs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_configs`;
|
||||
CREATE TABLE `chatgpt_configs` (
|
||||
`id` int NOT NULL,
|
||||
`marker` varchar(20) NOT NULL COMMENT '标识',
|
||||
`config_json` text NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_configs`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES
|
||||
(1, 'system', '{\"title\":\"Geek-AI创作系统\",\"admin_title\":\"Geek-AI控制台\",\"logo\":\"/images/logo.png\",\"init_power\":100,\"daily_power\":99,\"invite_power\":10,\"vip_month_power\":1000,\"register_ways\":[\"mobile\",\"username\",\"email\"],\"enabled_register\":true,\"reward_img\":\"http://localhost:5678/static/upload/2024/3/1710753716309668.jpg\",\"enabled_reward\":true,\"power_price\":0.1,\"order_pay_timeout\":1800,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[11,7,1,10,12,19,18,17,3],\"mj_power\":20,\"mj_action_power\":10,\"sd_power\":5,\"dall_power\":15,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4}'),
|
||||
(3, 'notice', '{\"content\":\"系统每日会给免费会员赠送10算力值,用完请第二天再来领取。\\n## v4.0.2 更新日志\\n* 功能新增:支持前端菜单可以配置\\n* 功能优化:在登录和注册界面标题显示软件版本号\\n* 功能优化:MJ 绘画支持 --sref 和 --cref 图片一致性参数\\n* 功能优化:使用 leveldb 解决 SD 绘图进度图片预览问题\\n* Bug修复:解决因为图片上传使用相对路径而导致融图失败的问题\\n* 功能新增:手机端支持 Stable-Diffusion 绘画\\n* Bug修复:修复管理后台 API KEY 删除失败的问题\\n\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e (支持MidJourney,GPT,Claude,Google Gemmi 各种表格模型) 或者 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e(不支持 Midjourney) 购买,现在有超级优惠,价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://ai.r9it.com/docs/install/errors-handle.html#%E8%B0%83%E7%94%A8%E4%B8%AD%E8%BD%AC-api-%E6%8A%A5%E9%94%99%E6%97%A0%E5%8F%AF%E7%94%A8%E6%B8%A0%E9%81%93)。\\nGPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程: \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_files`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_files`;
|
||||
CREATE TABLE `chatgpt_files` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`name` varchar(100) NOT NULL COMMENT '文件名',
|
||||
`obj_key` varchar(100) DEFAULT NULL COMMENT '文件标识',
|
||||
`url` varchar(255) NOT NULL COMMENT '文件地址',
|
||||
`ext` varchar(10) NOT NULL COMMENT '文件后缀',
|
||||
`size` bigint NOT NULL DEFAULT '0' COMMENT '文件大小',
|
||||
`created_at` datetime NOT NULL COMMENT '创建时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户文件表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_functions`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_functions`;
|
||||
CREATE TABLE `chatgpt_functions` (
|
||||
`id` int NOT NULL,
|
||||
`name` varchar(30) NOT NULL COMMENT '函数名称',
|
||||
`label` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '函数标签',
|
||||
`description` varchar(255) DEFAULT NULL COMMENT '函数描述',
|
||||
`parameters` text COMMENT '函数参数(JSON)',
|
||||
`token` varchar(255) DEFAULT NULL COMMENT 'API授权token',
|
||||
`action` varchar(255) DEFAULT NULL COMMENT '函数处理 API',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='函数插件表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_functions`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `token`, `action`, `enabled`) VALUES
|
||||
(1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 0),
|
||||
(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 0),
|
||||
(3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 0);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_invite_codes`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_invite_codes`;
|
||||
CREATE TABLE `chatgpt_invite_codes` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`code` char(8) NOT NULL COMMENT '邀请码',
|
||||
`hits` int NOT NULL COMMENT '点击次数',
|
||||
`reg_num` smallint NOT NULL COMMENT '注册数量',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户邀请码';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_invite_logs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_invite_logs`;
|
||||
CREATE TABLE `chatgpt_invite_logs` (
|
||||
`id` int NOT NULL,
|
||||
`inviter_id` int NOT NULL COMMENT '邀请人ID',
|
||||
`user_id` int NOT NULL COMMENT '注册用户ID',
|
||||
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
|
||||
`invite_code` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邀请码',
|
||||
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_menus`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_menus`;
|
||||
CREATE TABLE `chatgpt_menus` (
|
||||
`id` int NOT NULL,
|
||||
`name` varchar(30) NOT NULL COMMENT '菜单名称',
|
||||
`icon` varchar(150) NOT NULL COMMENT '菜单图标',
|
||||
`url` varchar(100) NOT NULL COMMENT '地址',
|
||||
`sort_num` smallint NOT NULL COMMENT '排序',
|
||||
`enabled` tinyint(1) NOT NULL COMMENT '是否启用'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='前端菜单表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_menus`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES
|
||||
(1, '对话聊天', '/images/menu/chat.png', '/chat', 0, 1),
|
||||
(5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 1, 1),
|
||||
(6, 'SD 绘画', '/images/menu/sd.png', '/sd', 2, 1),
|
||||
(7, '算力日志', '/images/menu/log.png', '/powerLog', 5, 1),
|
||||
(8, '应用中心', '/images/menu/app.png', '/apps', 3, 1),
|
||||
(9, '作品展示', '/images/menu/img-wall.png', '/images-wall', 4, 1),
|
||||
(10, '会员计划', '/images/menu/member.png', '/member', 6, 1),
|
||||
(11, '分享计划', '/images/menu/share.png', '/invite', 7, 1);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_mj_jobs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_mj_jobs`;
|
||||
CREATE TABLE `chatgpt_mj_jobs` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID',
|
||||
`type` varchar(20) DEFAULT 'image' COMMENT '任务类别',
|
||||
`message_id` char(40) NOT NULL COMMENT '消息 ID',
|
||||
`channel_id` char(40) DEFAULT NULL COMMENT '频道ID',
|
||||
`reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID',
|
||||
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
|
||||
`img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL',
|
||||
`org_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原始图片地址',
|
||||
`hash` varchar(100) DEFAULT NULL COMMENT 'message hash',
|
||||
`progress` smallint DEFAULT '0' COMMENT '任务进度',
|
||||
`use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代',
|
||||
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
|
||||
`err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
|
||||
`power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_orders`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_orders`;
|
||||
CREATE TABLE `chatgpt_orders` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`product_id` int NOT NULL COMMENT '产品ID',
|
||||
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户明',
|
||||
`order_no` varchar(30) NOT NULL COMMENT '订单ID',
|
||||
`trade_no` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付平台交易流水号',
|
||||
`subject` varchar(100) NOT NULL COMMENT '订单产品',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态(0:待支付,1:已扫码,2:支付失败)',
|
||||
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
|
||||
`pay_time` int DEFAULT NULL COMMENT '支付时间',
|
||||
`pay_way` varchar(20) NOT NULL COMMENT '支付方式',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL,
|
||||
`deleted_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='充值订单表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_power_logs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_power_logs`;
|
||||
CREATE TABLE `chatgpt_power_logs` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`username` varchar(30) NOT NULL COMMENT '用户名',
|
||||
`type` tinyint(1) NOT NULL COMMENT '类型(1:充值,2:消费,3:退费)',
|
||||
`amount` smallint NOT NULL COMMENT '算力数值',
|
||||
`balance` int NOT NULL COMMENT '余额',
|
||||
`model` varchar(30) NOT NULL COMMENT '模型',
|
||||
`remark` varchar(255) NOT NULL COMMENT '备注',
|
||||
`mark` tinyint(1) NOT NULL COMMENT '资金类型(0:支出,1:收入)',
|
||||
`created_at` datetime NOT NULL COMMENT '创建时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户算力消费日志';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_products`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_products`;
|
||||
CREATE TABLE `chatgpt_products` (
|
||||
`id` int NOT NULL,
|
||||
`name` varchar(30) NOT NULL COMMENT '名称',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
|
||||
`discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额',
|
||||
`days` smallint NOT NULL DEFAULT '0' COMMENT '延长天数',
|
||||
`power` int NOT NULL DEFAULT '0' COMMENT '增加算力值',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启动',
|
||||
`sales` int NOT NULL DEFAULT '0' COMMENT '销量',
|
||||
`sort_num` tinyint NOT NULL DEFAULT '0' COMMENT '排序',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL,
|
||||
`app_url` varchar(255) DEFAULT NULL COMMENT 'App跳转地址',
|
||||
`url` varchar(255) DEFAULT NULL COMMENT '跳转地址'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_rewards`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_rewards`;
|
||||
CREATE TABLE `chatgpt_rewards` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`tx_id` char(36) NOT NULL COMMENT '交易 ID',
|
||||
`amount` decimal(10,2) NOT NULL COMMENT '打赏金额',
|
||||
`remark` varchar(80) NOT NULL COMMENT '备注',
|
||||
`status` tinyint(1) NOT NULL COMMENT '核销状态,0:未核销,1:已核销',
|
||||
`exchange` varchar(255) NOT NULL COMMENT '兑换详情(json)',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户打赏';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_sd_jobs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_sd_jobs`;
|
||||
CREATE TABLE `chatgpt_sd_jobs` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'txt2img' COMMENT '任务类别',
|
||||
`task_id` char(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务 ID',
|
||||
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
|
||||
`img_url` varchar(255) DEFAULT NULL COMMENT '图片URL',
|
||||
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '绘画参数json',
|
||||
`progress` smallint DEFAULT '0' COMMENT '任务进度',
|
||||
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
|
||||
`err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
|
||||
`power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Stable Diffusion 任务表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_users`;
|
||||
CREATE TABLE `chatgpt_users` (
|
||||
`id` int NOT NULL,
|
||||
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
|
||||
`nickname` varchar(30) NOT NULL COMMENT '昵称',
|
||||
`password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
|
||||
`avatar` varchar(100) NOT NULL COMMENT '头像',
|
||||
`salt` char(12) NOT NULL COMMENT '密码盐',
|
||||
`power` int NOT NULL DEFAULT '0' COMMENT '剩余算力',
|
||||
`expired_time` int NOT NULL COMMENT '用户过期时间',
|
||||
`status` tinyint(1) NOT NULL COMMENT '当前状态',
|
||||
`chat_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天配置json',
|
||||
`chat_roles_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天角色 json',
|
||||
`chat_models_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'AI模型 json',
|
||||
`last_login_at` int NOT NULL COMMENT '最后登录时间',
|
||||
`vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否会员',
|
||||
`last_login_ip` char(16) NOT NULL COMMENT '最后登录 IP',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_users`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_users` (`id`, `username`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `created_at`, `updated_at`) VALUES
|
||||
(4, '18575670125', '极客学长@830270', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/2/1708682650912429.png', 'ueedue5l', 9384, 1717292086, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"programmer\",\"seller\"]', '[1,11]', 1711698298, 1, '::1', '2023-06-12 16:47:17', '2024-03-29 15:44:58');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_user_login_logs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_user_login_logs`;
|
||||
CREATE TABLE `chatgpt_user_login_logs` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`username` varchar(30) NOT NULL COMMENT '用户名',
|
||||
`login_ip` char(16) NOT NULL COMMENT '登录IP',
|
||||
`login_address` varchar(30) NOT NULL COMMENT '登录地址',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录日志';
|
||||
|
||||
--
|
||||
-- 转储表的索引
|
||||
--
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_admin_users`
|
||||
--
|
||||
ALTER TABLE `chatgpt_admin_users`
|
||||
ADD PRIMARY KEY (`id`) USING BTREE,
|
||||
ADD UNIQUE KEY `username` (`username`) USING BTREE;
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_api_keys`
|
||||
--
|
||||
ALTER TABLE `chatgpt_api_keys`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_chat_history`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_history`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `chat_id` (`chat_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_chat_items`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_items`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `chat_id` (`chat_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_chat_models`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_models`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_chat_roles`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_roles`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `marker` (`marker`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_configs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_configs`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `marker` (`marker`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_files`
|
||||
--
|
||||
ALTER TABLE `chatgpt_files`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_functions`
|
||||
--
|
||||
ALTER TABLE `chatgpt_functions`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `name` (`name`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_invite_codes`
|
||||
--
|
||||
ALTER TABLE `chatgpt_invite_codes`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `code` (`code`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_invite_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_invite_logs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_menus`
|
||||
--
|
||||
ALTER TABLE `chatgpt_menus`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_mj_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_mj_jobs`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `task_id` (`task_id`),
|
||||
ADD KEY `message_id` (`message_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_orders`
|
||||
--
|
||||
ALTER TABLE `chatgpt_orders`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `order_no` (`order_no`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_power_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_power_logs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_products`
|
||||
--
|
||||
ALTER TABLE `chatgpt_products`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_rewards`
|
||||
--
|
||||
ALTER TABLE `chatgpt_rewards`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `tx_id` (`tx_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_sd_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_sd_jobs`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `task_id` (`task_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_users`
|
||||
--
|
||||
ALTER TABLE `chatgpt_users`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `username` (`username`),
|
||||
ADD UNIQUE KEY `username_2` (`username`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_user_login_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_user_login_logs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 在导出的表使用AUTO_INCREMENT
|
||||
--
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_admin_users`
|
||||
--
|
||||
ALTER TABLE `chatgpt_admin_users`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=113;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_api_keys`
|
||||
--
|
||||
ALTER TABLE `chatgpt_api_keys`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_history`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_history`
|
||||
MODIFY `id` bigint NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_items`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_items`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_models`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_models`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=25;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_roles`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=130;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_configs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_configs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_files`
|
||||
--
|
||||
ALTER TABLE `chatgpt_files`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_functions`
|
||||
--
|
||||
ALTER TABLE `chatgpt_functions`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_invite_codes`
|
||||
--
|
||||
ALTER TABLE `chatgpt_invite_codes`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_invite_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_invite_logs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_menus`
|
||||
--
|
||||
ALTER TABLE `chatgpt_menus`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_mj_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_mj_jobs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_orders`
|
||||
--
|
||||
ALTER TABLE `chatgpt_orders`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_power_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_power_logs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_products`
|
||||
--
|
||||
ALTER TABLE `chatgpt_products`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_rewards`
|
||||
--
|
||||
ALTER TABLE `chatgpt_rewards`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_sd_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_sd_jobs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_users`
|
||||
--
|
||||
ALTER TABLE `chatgpt_users`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_user_login_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_user_login_logs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
COMMIT;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
861
database/chatgpt_plus-v4.1.0.sql
Normal file
@ -0,0 +1,861 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.2.1
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- 主机: 127.0.0.1
|
||||
-- 生成日期: 2024-07-12 17:11:22
|
||||
-- 服务器版本: 8.0.33
|
||||
-- PHP 版本: 8.1.2-1ubuntu2.18
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
START TRANSACTION;
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- 数据库: `chatgpt_plus`
|
||||
--
|
||||
CREATE DATABASE IF NOT EXISTS `chatgpt_plus` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
USE `chatgpt_plus`;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_admin_users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_admin_users`;
|
||||
CREATE TABLE `chatgpt_admin_users` (
|
||||
`id` int NOT NULL,
|
||||
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
|
||||
`password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
|
||||
`salt` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码盐',
|
||||
`status` tinyint(1) NOT NULL COMMENT '当前状态',
|
||||
`last_login_at` int NOT NULL COMMENT '最后登录时间',
|
||||
`last_login_ip` char(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '最后登录 IP',
|
||||
`created_at` datetime NOT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime NOT NULL COMMENT '更新时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户' ROW_FORMAT=DYNAMIC;
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_admin_users`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_admin_users` (`id`, `username`, `password`, `salt`, `status`, `last_login_at`, `last_login_ip`, `created_at`, `updated_at`) VALUES
|
||||
(1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1719818809, '172.22.11.200', '2024-03-11 16:30:20', '2024-07-01 15:26:49');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_api_keys`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_api_keys`;
|
||||
CREATE TABLE `chatgpt_api_keys` (
|
||||
`id` int NOT NULL,
|
||||
`platform` char(20) DEFAULT NULL COMMENT '平台',
|
||||
`name` varchar(30) DEFAULT NULL COMMENT '名称',
|
||||
`value` varchar(100) NOT NULL COMMENT 'API KEY value',
|
||||
`type` varchar(10) NOT NULL DEFAULT 'chat' COMMENT '用途(chat=>聊天,img=>图片)',
|
||||
`last_used_at` int NOT NULL COMMENT '最后使用时间',
|
||||
`api_url` varchar(255) DEFAULT NULL COMMENT 'API 地址',
|
||||
`enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
|
||||
`proxy_url` varchar(100) DEFAULT NULL COMMENT '代理地址',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OpenAI API ';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_chat_history`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_chat_history`;
|
||||
CREATE TABLE `chatgpt_chat_history` (
|
||||
`id` bigint NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
|
||||
`type` varchar(10) NOT NULL COMMENT '类型:prompt|reply',
|
||||
`icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标',
|
||||
`role_id` int NOT NULL COMMENT '角色 ID',
|
||||
`model` varchar(30) DEFAULT NULL COMMENT '模型名称',
|
||||
`content` text NOT NULL COMMENT '聊天内容',
|
||||
`tokens` smallint NOT NULL COMMENT '耗费 token 数量',
|
||||
`use_context` tinyint(1) NOT NULL COMMENT '是否允许作为上下文语料',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL,
|
||||
`deleted_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天历史记录';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_chat_items`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_chat_items`;
|
||||
CREATE TABLE `chatgpt_chat_items` (
|
||||
`id` int NOT NULL,
|
||||
`chat_id` char(40) NOT NULL COMMENT '会话 ID',
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`role_id` int NOT NULL COMMENT '角色 ID',
|
||||
`title` varchar(100) NOT NULL COMMENT '会话标题',
|
||||
`model_id` int NOT NULL DEFAULT '0' COMMENT '模型 ID',
|
||||
`model` varchar(30) DEFAULT NULL COMMENT '模型名称',
|
||||
`created_at` datetime NOT NULL COMMENT '创建时间',
|
||||
`updated_at` datetime NOT NULL COMMENT '更新时间',
|
||||
`deleted_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户会话列表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_chat_models`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_chat_models`;
|
||||
CREATE TABLE `chatgpt_chat_models` (
|
||||
`id` int NOT NULL,
|
||||
`platform` varchar(20) DEFAULT NULL COMMENT '模型平台',
|
||||
`name` varchar(50) NOT NULL COMMENT '模型名称',
|
||||
`value` varchar(50) NOT NULL COMMENT '模型值',
|
||||
`sort_num` tinyint(1) NOT NULL COMMENT '排序数字',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用模型',
|
||||
`power` smallint NOT NULL COMMENT '消耗算力点数',
|
||||
`temperature` float(3,1) NOT NULL DEFAULT '1.0' COMMENT '模型创意度',
|
||||
`max_tokens` int NOT NULL DEFAULT '1024' COMMENT '最大响应长度',
|
||||
`max_context` int NOT NULL DEFAULT '4096' COMMENT '最大上下文长度',
|
||||
`open` tinyint(1) NOT NULL COMMENT '是否开放模型',
|
||||
`key_id` int NOT NULL COMMENT '绑定API KEY ID',
|
||||
`created_at` datetime DEFAULT NULL,
|
||||
`updated_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI 模型表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_chat_models`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num`, `enabled`, `power`, `temperature`, `max_tokens`, `max_context`, `open`, `key_id`, `created_at`, `updated_at`) VALUES
|
||||
(1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo', 1, 1, 0, 1.0, 1024, 16384, 1, 44, '2023-08-23 12:06:36', '2024-07-04 15:17:47'),
|
||||
(2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 23, 1, 1, 1.0, 1024, 4096, 0, 0, '2023-08-23 12:15:30', '2024-07-02 16:14:00'),
|
||||
(3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 12, 1, 1, 1.0, 2048, 32768, 1, 0, '2023-08-23 13:35:45', '2024-07-02 16:14:00'),
|
||||
(7, 'Baidu', '文心一言3.0', 'eb-instant', 21, 1, 1, 1.0, 1024, 4096, 1, 0, '2023-10-11 11:29:28', '2024-07-02 16:14:00'),
|
||||
(8, 'XunFei', '星火V3.5', 'generalv3.5', 11, 1, 5, 0.8, 1024, 8192, 1, 0, '2023-10-11 15:48:30', '2024-07-02 16:14:00'),
|
||||
(9, 'XunFei', '星火V2.0', 'generalv2', 20, 1, 1, 1.0, 1024, 8192, 1, 0, '2023-10-11 15:48:45', '2024-07-02 16:14:00'),
|
||||
(10, 'Baidu', '文心一言4.0', 'completions_pro', 22, 1, 3, 1.0, 1024, 8192, 1, 0, '2023-10-25 08:31:37', '2024-07-02 16:14:00'),
|
||||
(11, 'OpenAI', 'GPT-4.0', 'gpt-4-0125-preview', 10, 1, 15, 1.0, 2048, 8192, 1, 0, '2023-10-25 08:45:15', '2024-07-02 16:14:00'),
|
||||
(12, 'XunFei', '星火v3.0', 'generalv3', 19, 1, 3, 1.0, 1024, 8192, 1, 0, '2023-11-23 09:20:33', '2024-07-02 16:14:00'),
|
||||
(15, 'OpenAI', 'GPT-超级模型', 'gpt-4-all', 13, 1, 30, 1.0, 4096, 32768, 0, 0, '2024-01-15 11:32:52', '2024-07-02 16:14:00'),
|
||||
(16, 'OpenAI', '视频号导师', 'gpt-4-gizmo-g-QXXEBTXl7', 14, 1, 30, 1.0, 4096, 32768, 0, 0, '2024-01-15 14:46:35', '2024-07-02 16:14:00'),
|
||||
(17, 'QWen', '通义千问-Turbo', 'qwen-turbo', 16, 1, 1, 1.0, 1024, 8192, 1, 0, '2024-01-19 10:42:24', '2024-07-02 16:14:00'),
|
||||
(18, 'QWen', '通义千问-Plus', 'qwen-plus', 17, 1, 1, 1.0, 1024, 32768, 1, 0, '2024-01-19 10:42:49', '2024-07-02 16:14:00'),
|
||||
(19, 'QWen', '通义千问-Max', 'qwen-max-1201', 18, 1, 1, 1.0, 1024, 32768, 1, 0, '2024-01-19 10:51:03', '2024-07-02 16:14:00'),
|
||||
(21, 'OpenAI', '董宇辉小作文助手', 'gpt-4-gizmo-g-dse9iXvor', 15, 1, 30, 1.0, 8192, 32768, 0, 0, '2024-03-18 14:24:20', '2024-07-02 16:14:00'),
|
||||
(22, 'OpenAI', 'LOGO生成神器', 'gpt-4-gizmo-g-YL87j8C7S', 9, 1, 30, 1.0, 1024, 4096, 1, 44, '2024-03-20 14:02:11', '2024-07-02 16:14:00'),
|
||||
(23, 'OpenAI', '音乐生成器', 'suno-v3', 8, 1, 50, 0.8, 1024, 4096, 1, 44, '2024-03-29 15:43:40', '2024-07-02 16:14:00'),
|
||||
(24, 'OpenAI', '通义千问(中转)', 'qwen-plus', 7, 1, 1, 1.0, 1024, 4096, 1, 0, '2024-04-03 12:00:46', '2024-07-02 16:14:00'),
|
||||
(25, 'OpenAI', 'GPT4-TURBO', 'gpt-4-turbo', 6, 1, 15, 1.0, 2048, 8092, 1, 0, '2024-04-10 08:35:17', '2024-07-02 16:14:00'),
|
||||
(26, 'QWen', '通义千问-Turbo', 'qwen-turbo', 5, 1, 2, 1.0, 1024, 8192, 1, 0, '2024-04-12 14:11:19', '2024-07-02 16:14:00'),
|
||||
(27, 'QWen', '通义千问-Plus', 'qwen-plus', 4, 1, 2, 1.0, 1024, 8192, 1, 0, '2024-04-12 14:11:52', '2024-07-02 16:14:00'),
|
||||
(28, 'OpenAI', 'GPT-3.5(免费)', 'gpt-3.5-turbo', 24, 1, 0, 1.0, 1024, 16384, 1, 53, '2024-04-12 15:16:43', '2024-07-02 16:14:00'),
|
||||
(34, 'OpenAI', 'LLAMA3', 'llama3-8b', 25, 1, 1, 1.0, 1024, 8192, 1, 56, '2024-04-30 15:22:50', '2024-07-02 16:14:00'),
|
||||
(36, 'OpenAI', 'GPT-4O', 'gpt-4o', 3, 1, 15, 1.0, 4096, 16384, 1, 44, '2024-05-14 09:25:15', '2024-07-04 15:17:53'),
|
||||
(38, 'OpenAI', 'Gemini-pro', 'gemini-pro-1.5', 26, 1, 10, 1.0, 2048, 8192, 1, 0, '2024-05-27 18:10:35', '2024-07-02 16:14:00'),
|
||||
(39, 'Baidu', 'ERNIE-Speed-8K', 'ernie_speed', 27, 1, 1, 1.0, 1024, 8192, 1, 0, '2024-05-29 15:04:19', '2024-07-02 16:14:00'),
|
||||
(41, 'OpenAI', 'GLM-3-Turbo', 'glm-3-turbo', 28, 1, 2, 1.0, 1024, 8192, 1, 64, '2024-06-06 11:40:46', '2024-07-02 16:14:00'),
|
||||
(42, 'OpenAI', 'DeekSeek', 'deepseek-chat', 29, 1, 1, 1.0, 4096, 32768, 1, 64, '2024-06-27 16:13:01', '2024-07-02 16:14:00'),
|
||||
(43, 'OpenAI', 'GML4', 'name-3.5', 2, 1, 1, 1.0, 1024, 8192, 1, 57, '2024-07-02 16:11:49', '2024-07-02 16:14:00');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_chat_roles`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_chat_roles`;
|
||||
CREATE TABLE `chatgpt_chat_roles` (
|
||||
`id` int NOT NULL,
|
||||
`name` varchar(30) NOT NULL COMMENT '角色名称',
|
||||
`marker` varchar(30) NOT NULL COMMENT '角色标识',
|
||||
`context_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色语料 json',
|
||||
`hello_msg` varchar(255) NOT NULL COMMENT '打招呼信息',
|
||||
`icon` varchar(255) NOT NULL COMMENT '角色图标',
|
||||
`enable` tinyint(1) NOT NULL COMMENT '是否被启用',
|
||||
`sort_num` smallint NOT NULL DEFAULT '0' COMMENT '角色排序',
|
||||
`model_id` int NOT NULL DEFAULT '0' COMMENT '绑定模型ID',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天角色表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_chat_roles`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_chat_roles` (`id`, `name`, `marker`, `context_json`, `hello_msg`, `icon`, `enable`, `sort_num`, `model_id`, `created_at`, `updated_at`) VALUES
|
||||
(1, '通用AI助手', 'gpt', '', '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 1, 0, '2023-05-30 07:02:06', '2024-06-26 15:20:27'),
|
||||
(24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 4, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 3, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 5, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 2, 1, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 6, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装:¥1188/箱,和系列 6 瓶装:¥2208/箱,贵系列 6 瓶装:¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 9, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(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, 7, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 8, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 10, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 11, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 12, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 13, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 14, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 15, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 16, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
|
||||
(39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 17, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_configs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_configs`;
|
||||
CREATE TABLE `chatgpt_configs` (
|
||||
`id` int NOT NULL,
|
||||
`marker` varchar(20) NOT NULL COMMENT '标识',
|
||||
`config_json` text NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_configs`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES
|
||||
(1, 'system', '{\"title\":\"GeekAI 创作系统\",\"slogan\":\"你有多少想象力,AI 就有多大创造力。我辈之人,先干为敬,陪您先把 AI 用起来。\",\"admin_title\":\"GeekAI 控制台\",\"logo\":\"http://localhost:5678/static/upload/2024/4/1714382860986912.png\",\"init_power\":100,\"daily_power\":99,\"invite_power\":1024,\"vip_month_power\":1000,\"register_ways\":[\"mobile\",\"username\",\"email\"],\"enabled_register\":true,\"reward_img\":\"http://localhost:5678/static/upload/2024/3/1710753716309668.jpg\",\"enabled_reward\":true,\"power_price\":0.1,\"order_pay_timeout\":600,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[11,7,1,10,12,19,18,17,3],\"mj_power\":30,\"mj_action_power\":10,\"sd_power\":10,\"dall_power\":15,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4,\"sd_neg_prompt\":\"nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet\",\"index_bg_url\":\"http://nk.img.r9it.com/chatgpt-plus/1719456403420273.jpg\"}'),
|
||||
(3, 'notice', '{\"sd_neg_prompt\":\"\",\"index_bg_url\":\"\",\"content\":\"## v4.1.0 更新日志\\n\\n* bug修复:修复移动端修改聊天标题不生效的问题\\n* Bug修复:修复用户注册不显示用户名的问题\\n* Bug修复:修复管理后台拖动排序不生效的问题\\n* 功能优化:允许用户设置自定义首页背景图片\\n* 功能新增:**支持AI解读 PDF, Word, Excel等文件**\\n* 功能优化:优化聊天界面的用户上传文件的列表样式\\n* 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换\\n* 功能新增:支持微信扫码登录,未注册用户微信扫码后会自动注册并登录。移动使用微信浏览器打开可以实现无感登录。\\n\\n注意:当前站点仅为开源项目 \\u003ca style=\\\"color: #F56C6C\\\" href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003eChatPlus\\u003c/a\\u003e 的演示项目,本项目单纯就是给大家体验项目功能使用。\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去下面几个推荐的中转站购买:\\n1、\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e\\n2、\\u003ca href=\\\"https://api.geekai.me\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.geekai.me\\u003c/a\\u003e\\n3、 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e\\n支持MidJourney,GPT,Claude,Google Gemmi,以及国内各个厂家的大模型,现在有超级优惠,价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://ai.r9it.com/docs/install/errors-handle.html#%E8%B0%83%E7%94%A8%E4%B8%AD%E8%BD%AC-api-%E6%8A%A5%E9%94%99%E6%97%A0%E5%8F%AF%E7%94%A8%E6%B8%A0%E9%81%93)。GPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程: \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_dall_jobs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_dall_jobs`;
|
||||
CREATE TABLE `chatgpt_dall_jobs` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`prompt` varchar(2000) NOT NULL COMMENT '提示词',
|
||||
`img_url` varchar(255) NOT NULL COMMENT '图片地址',
|
||||
`org_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原图地址',
|
||||
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
|
||||
`power` smallint NOT NULL COMMENT '消耗算力',
|
||||
`progress` smallint NOT NULL COMMENT '任务进度',
|
||||
`err_msg` varchar(255) NOT NULL COMMENT '错误信息',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='DALLE 绘图任务表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_files`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_files`;
|
||||
CREATE TABLE `chatgpt_files` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`name` varchar(100) NOT NULL COMMENT '文件名',
|
||||
`obj_key` varchar(100) DEFAULT NULL COMMENT '文件标识',
|
||||
`url` varchar(255) NOT NULL COMMENT '文件地址',
|
||||
`ext` varchar(10) NOT NULL COMMENT '文件后缀',
|
||||
`size` bigint NOT NULL DEFAULT '0' COMMENT '文件大小',
|
||||
`created_at` datetime NOT NULL COMMENT '创建时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户文件表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_functions`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_functions`;
|
||||
CREATE TABLE `chatgpt_functions` (
|
||||
`id` int NOT NULL,
|
||||
`name` varchar(30) NOT NULL COMMENT '函数名称',
|
||||
`label` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '函数标签',
|
||||
`description` varchar(255) DEFAULT NULL COMMENT '函数描述',
|
||||
`parameters` text COMMENT '函数参数(JSON)',
|
||||
`token` varchar(255) DEFAULT NULL COMMENT 'API授权token',
|
||||
`action` varchar(255) DEFAULT NULL COMMENT '函数处理 API',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='函数插件表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_functions`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `token`, `action`, `enabled`) VALUES
|
||||
(1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 0),
|
||||
(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 0),
|
||||
(3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 0);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_invite_codes`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_invite_codes`;
|
||||
CREATE TABLE `chatgpt_invite_codes` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`code` char(8) NOT NULL COMMENT '邀请码',
|
||||
`hits` int NOT NULL COMMENT '点击次数',
|
||||
`reg_num` smallint NOT NULL COMMENT '注册数量',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户邀请码';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_invite_logs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_invite_logs`;
|
||||
CREATE TABLE `chatgpt_invite_logs` (
|
||||
`id` int NOT NULL,
|
||||
`inviter_id` int NOT NULL COMMENT '邀请人ID',
|
||||
`user_id` int NOT NULL COMMENT '注册用户ID',
|
||||
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
|
||||
`invite_code` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邀请码',
|
||||
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_menus`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_menus`;
|
||||
CREATE TABLE `chatgpt_menus` (
|
||||
`id` int NOT NULL,
|
||||
`name` varchar(30) NOT NULL COMMENT '菜单名称',
|
||||
`icon` varchar(150) NOT NULL COMMENT '菜单图标',
|
||||
`url` varchar(100) NOT NULL COMMENT '地址',
|
||||
`sort_num` smallint NOT NULL COMMENT '排序',
|
||||
`enabled` tinyint(1) NOT NULL COMMENT '是否启用'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='前端菜单表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_menus`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES
|
||||
(1, '对话聊天', '/images/menu/chat.png', '/chat', 1, 1),
|
||||
(5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 2, 1),
|
||||
(6, 'SD 绘画', '/images/menu/sd.png', '/sd', 3, 1),
|
||||
(7, '算力日志', '/images/menu/log.png', '/powerLog', 8, 1),
|
||||
(8, '应用中心', '/images/menu/app.png', '/apps', 7, 1),
|
||||
(9, '画廊', '/images/menu/img-wall.png', '/images-wall', 5, 1),
|
||||
(10, '会员计划', '/images/menu/member.png', '/member', 9, 1),
|
||||
(11, '分享计划', '/images/menu/share.png', '/invite', 10, 1),
|
||||
(12, '思维导图', '/images/menu/xmind.png', '/xmind', 6, 1),
|
||||
(13, 'DALLE', '/images/menu/dalle.png', '/dalle', 4, 1),
|
||||
(14, '项目文档', '/images/menu/docs.png', 'https://ai.r9it.com/docs/', 11, 1),
|
||||
(16, '极客论坛', '/images/menu/bbs.png', 'https://bbs.geekai.me/', 13, 1);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_mj_jobs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_mj_jobs`;
|
||||
CREATE TABLE `chatgpt_mj_jobs` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID',
|
||||
`type` varchar(20) DEFAULT 'image' COMMENT '任务类别',
|
||||
`message_id` char(40) NOT NULL COMMENT '消息 ID',
|
||||
`channel_id` char(40) DEFAULT NULL COMMENT '频道ID',
|
||||
`reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID',
|
||||
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
|
||||
`img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL',
|
||||
`org_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原始图片地址',
|
||||
`hash` varchar(100) DEFAULT NULL COMMENT 'message hash',
|
||||
`progress` smallint DEFAULT '0' COMMENT '任务进度',
|
||||
`use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代',
|
||||
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
|
||||
`err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
|
||||
`power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_orders`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_orders`;
|
||||
CREATE TABLE `chatgpt_orders` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`product_id` int NOT NULL COMMENT '产品ID',
|
||||
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户明',
|
||||
`order_no` varchar(30) NOT NULL COMMENT '订单ID',
|
||||
`trade_no` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付平台交易流水号',
|
||||
`subject` varchar(100) NOT NULL COMMENT '订单产品',
|
||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态(0:待支付,1:已扫码,2:支付成功)',
|
||||
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
|
||||
`pay_time` int DEFAULT NULL COMMENT '支付时间',
|
||||
`pay_way` varchar(20) NOT NULL COMMENT '支付方式',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL,
|
||||
`deleted_at` datetime DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='充值订单表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_power_logs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_power_logs`;
|
||||
CREATE TABLE `chatgpt_power_logs` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`username` varchar(30) NOT NULL COMMENT '用户名',
|
||||
`type` tinyint(1) NOT NULL COMMENT '类型(1:充值,2:消费,3:退费)',
|
||||
`amount` smallint NOT NULL COMMENT '算力数值',
|
||||
`balance` int NOT NULL COMMENT '余额',
|
||||
`model` varchar(30) NOT NULL COMMENT '模型',
|
||||
`remark` varchar(255) NOT NULL COMMENT '备注',
|
||||
`mark` tinyint(1) NOT NULL COMMENT '资金类型(0:支出,1:收入)',
|
||||
`created_at` datetime NOT NULL COMMENT '创建时间'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户算力消费日志';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_products`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_products`;
|
||||
CREATE TABLE `chatgpt_products` (
|
||||
`id` int NOT NULL,
|
||||
`name` varchar(30) NOT NULL COMMENT '名称',
|
||||
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
|
||||
`discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额',
|
||||
`days` smallint NOT NULL DEFAULT '0' COMMENT '延长天数',
|
||||
`power` int NOT NULL DEFAULT '0' COMMENT '增加算力值',
|
||||
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启动',
|
||||
`sales` int NOT NULL DEFAULT '0' COMMENT '销量',
|
||||
`sort_num` tinyint NOT NULL DEFAULT '0' COMMENT '排序',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL,
|
||||
`app_url` varchar(255) DEFAULT NULL COMMENT 'App跳转地址',
|
||||
`url` varchar(255) DEFAULT NULL COMMENT '跳转地址'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_products`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `power`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`, `app_url`, `url`) VALUES
|
||||
(1, '会员1个月', 1999.90, 1999.00, 30, 0, 1, 1, 0, '2023-08-28 10:48:57', '2024-04-26 16:09:06', NULL, NULL),
|
||||
(2, '会员3个月', 3940.00, 30.00, 90, 0, 1, 0, 0, '2023-08-28 10:52:22', '2024-03-22 17:56:10', NULL, NULL),
|
||||
(3, '会员6个月', 5990.00, 100.00, 180, 0, 1, 0, 0, '2023-08-28 10:53:39', '2024-03-22 17:56:15', NULL, NULL),
|
||||
(4, '会员12个月', 9980.00, 200.00, 365, 0, 1, 0, 0, '2023-08-28 10:54:15', '2024-03-22 17:56:23', NULL, NULL),
|
||||
(5, '100次点卡', 9.99, 9.98, 0, 100, 1, 7, 0, '2023-08-28 10:55:08', '2024-06-11 16:48:44', NULL, NULL),
|
||||
(6, '200次点卡', 19.90, 15.00, 0, 200, 1, 1, 0, '1970-01-01 08:00:00', '2024-06-11 11:41:52', NULL, NULL);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_rewards`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_rewards`;
|
||||
CREATE TABLE `chatgpt_rewards` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`tx_id` char(36) NOT NULL COMMENT '交易 ID',
|
||||
`amount` decimal(10,2) NOT NULL COMMENT '打赏金额',
|
||||
`remark` varchar(80) NOT NULL COMMENT '备注',
|
||||
`status` tinyint(1) NOT NULL COMMENT '核销状态,0:未核销,1:已核销',
|
||||
`exchange` varchar(255) NOT NULL COMMENT '兑换详情(json)',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户打赏';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_sd_jobs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_sd_jobs`;
|
||||
CREATE TABLE `chatgpt_sd_jobs` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||
`type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'txt2img' COMMENT '任务类别',
|
||||
`task_id` char(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务 ID',
|
||||
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
|
||||
`img_url` varchar(255) DEFAULT NULL COMMENT '图片URL',
|
||||
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '绘画参数json',
|
||||
`progress` smallint DEFAULT '0' COMMENT '任务进度',
|
||||
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
|
||||
`err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
|
||||
`power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
|
||||
`created_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Stable Diffusion 任务表';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_users`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_users`;
|
||||
CREATE TABLE `chatgpt_users` (
|
||||
`id` int NOT NULL,
|
||||
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
|
||||
`nickname` varchar(30) NOT NULL COMMENT '昵称',
|
||||
`password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
|
||||
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像',
|
||||
`salt` char(12) NOT NULL COMMENT '密码盐',
|
||||
`power` int NOT NULL DEFAULT '0' COMMENT '剩余算力',
|
||||
`expired_time` int NOT NULL COMMENT '用户过期时间',
|
||||
`status` tinyint(1) NOT NULL COMMENT '当前状态',
|
||||
`chat_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天配置json',
|
||||
`chat_roles_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天角色 json',
|
||||
`chat_models_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'AI模型 json',
|
||||
`last_login_at` int NOT NULL COMMENT '最后登录时间',
|
||||
`vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否会员',
|
||||
`last_login_ip` char(16) NOT NULL COMMENT '最后登录 IP',
|
||||
`openid` varchar(100) DEFAULT NULL COMMENT '第三方登录账号ID',
|
||||
`platform` varchar(30) DEFAULT NULL COMMENT '登录平台',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_users`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_users` (`id`, `username`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `openid`, `platform`, `created_at`, `updated_at`) VALUES
|
||||
(4, '18575670125', '极客学长@830270', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/5/1715651569509929.png', 'ueedue5l', 7413, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"seller\",\"artist\",\"lu_xun\",\"girl_friend\",\"psychiatrist\",\"teacher\",\"programmer\",\"test\",\"qing_gan_da_shi\"]', '[1,11]', 1720683366, 1, '172.22.11.29', NULL, NULL, '2023-06-12 16:47:17', '2024-07-12 16:08:39'),
|
||||
(5, 'yangjian102621@gmail.com', '极客学长@486041', '75d1a22f33e1ffffb7943946b6b8d5177d5ecd685d3cef1b468654038b0a8c22', '/images/avatar/user.png', '2q8ugxzk', 100, 0, 1, '', '[\"gpt\",\"programmer\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', NULL, NULL, '2024-04-23 09:17:26', '2024-04-23 09:17:26'),
|
||||
(8, 'yangjian102623@gmail.com', '极客学长@714931', 'f8f0e0abf146569217273ea0712a0f9b6cbbe7d943a1d9bd5f91c55e6d8c05d1', '/images/avatar/user.png', 'geuddq7f', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', NULL, NULL, '2024-04-26 15:19:28', '2024-04-26 15:19:28'),
|
||||
(9, '1234567', '极客学长@604526', '858e2afec79e1d6364f4567f945f2310024896d9aa45dd944efa95a0c31e4d08', '/images/avatar/user.png', '00qawlos', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', NULL, NULL, '2024-04-26 15:21:06', '2024-04-26 15:21:06'),
|
||||
(11, 'abc123', '极客学长@965562', '7a15c53afdb1da7093d80f9940e716eb396e682cfb1f2d107d0b81b183a3ba13', '/images/avatar/user.png', '6433mfbk', 1124, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', NULL, NULL, '2024-06-06 09:37:44', '2024-06-06 09:37:44'),
|
||||
(14, 'wx@3567548322', '极客学长', '5a349ba89582a4074938b5a3ce84e87c937681ad47e8b87aab03a987e22b6077', 'https://thirdwx.qlogo.cn/mmopen/vi_32/uyxRMqZcEkb7fHouKXbNzxrnrvAttBKkwNlZ7yFibibRGiahdmsrZ3A1NKf8Fw5qJNJn4TXRmygersgEbibaSGd9Sg/132', 'abhzbmij', 83, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', 'oCs0t62472W19z2LOEKI1rWyCTTA', '', '2024-07-04 14:52:08', '2024-07-04 14:52:08'),
|
||||
(15, 'user123', '极客学长@191303', '4a4c0a14d5fc8787357517f14f6e442281b42c8ec4395016b77483997476011e', '/images/avatar/user.png', 'cyzwkbrx', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-09 10:49:27', '2024-07-09 10:49:27'),
|
||||
(17, 'user1234', '极客学长@836764', 'bfe03c9c8c9fff5b77e36e40e8298ad3a6073d43c6a936b008eebb21113bf550', '/images/avatar/user.png', '1d2alwnj', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-09 10:53:17', '2024-07-09 10:53:17'),
|
||||
(18, 'liaoyq', '极客学长@405564', 'ad1726089022db4c661235a8aab7307af1a7f8483eee08bac3f79b5a6a9bd26b', '/images/avatar/user.png', 'yq862l01', 100, 0, 1, '', '[\"string\"]', '[11,7,1,10,12,19,18,17,3]', 1720574265, 0, '172.22.11.29', '', '', '2024-07-10 09:15:33', '2024-07-10 09:17:45'),
|
||||
(19, 'humm', '极客学长@483216', '420970ace96921c8b3ac7668d097182eab1b6436c730a484e82ae4661bd4f7d9', '/images/avatar/user.png', '1gokrcl2', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 1720745411, 0, '172.22.11.36', '', '', '2024-07-10 11:08:31', '2024-07-12 08:50:11'),
|
||||
(20, 'abc', '极客学长@369852', '6cad48fb2cc0f54600d66a829e9be69ffd9340a49d5a5b1abda5d4082d946833', '/images/avatar/user.png', 'gop65zei', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-11 16:44:14', '2024-07-11 16:44:14'),
|
||||
(21, 'husm@pvc123.com', '极客学长@721654', 'e030537dc43fea1bf1fa55a24f99e44f29311bebea96e88ea186995c77db083b', '/images/avatar/user.png', 'p1etg3oi', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-11 16:50:33', '2024-07-11 16:50:33'),
|
||||
(22, '15818323616', 'ted', '3ca6b2ff585d03be8ca4de33ad00148497a09372914ee8aa4cfde343266cbcdd', 'http://localhost:5678/static/upload/2024/7/1720775331363383.jpg', 'sq4s1brf', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-12 15:12:16', '2024-07-12 17:09:01');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 表的结构 `chatgpt_user_login_logs`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `chatgpt_user_login_logs`;
|
||||
CREATE TABLE `chatgpt_user_login_logs` (
|
||||
`id` int NOT NULL,
|
||||
`user_id` int NOT NULL COMMENT '用户ID',
|
||||
`username` varchar(30) NOT NULL COMMENT '用户名',
|
||||
`login_ip` char(16) NOT NULL COMMENT '登录IP',
|
||||
`login_address` varchar(30) NOT NULL COMMENT '登录地址',
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录日志';
|
||||
|
||||
--
|
||||
-- 转储表的索引
|
||||
--
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_admin_users`
|
||||
--
|
||||
ALTER TABLE `chatgpt_admin_users`
|
||||
ADD PRIMARY KEY (`id`) USING BTREE,
|
||||
ADD UNIQUE KEY `username` (`username`) USING BTREE;
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_api_keys`
|
||||
--
|
||||
ALTER TABLE `chatgpt_api_keys`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_chat_history`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_history`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `chat_id` (`chat_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_chat_items`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_items`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `chat_id` (`chat_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_chat_models`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_models`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_chat_roles`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_roles`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `marker` (`marker`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_configs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_configs`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `marker` (`marker`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_dall_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_dall_jobs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_files`
|
||||
--
|
||||
ALTER TABLE `chatgpt_files`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_functions`
|
||||
--
|
||||
ALTER TABLE `chatgpt_functions`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `name` (`name`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_invite_codes`
|
||||
--
|
||||
ALTER TABLE `chatgpt_invite_codes`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `code` (`code`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_invite_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_invite_logs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_menus`
|
||||
--
|
||||
ALTER TABLE `chatgpt_menus`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_mj_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_mj_jobs`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `task_id` (`task_id`),
|
||||
ADD KEY `message_id` (`message_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_orders`
|
||||
--
|
||||
ALTER TABLE `chatgpt_orders`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `order_no` (`order_no`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_power_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_power_logs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_products`
|
||||
--
|
||||
ALTER TABLE `chatgpt_products`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_rewards`
|
||||
--
|
||||
ALTER TABLE `chatgpt_rewards`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `tx_id` (`tx_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_sd_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_sd_jobs`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `task_id` (`task_id`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_users`
|
||||
--
|
||||
ALTER TABLE `chatgpt_users`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `username` (`username`);
|
||||
|
||||
--
|
||||
-- 表的索引 `chatgpt_user_login_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_user_login_logs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- 在导出的表使用AUTO_INCREMENT
|
||||
--
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_admin_users`
|
||||
--
|
||||
ALTER TABLE `chatgpt_admin_users`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=113;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_api_keys`
|
||||
--
|
||||
ALTER TABLE `chatgpt_api_keys`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_history`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_history`
|
||||
MODIFY `id` bigint NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_items`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_items`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_models`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_models`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=44;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_roles`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=132;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_configs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_configs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_dall_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_dall_jobs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_files`
|
||||
--
|
||||
ALTER TABLE `chatgpt_files`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_functions`
|
||||
--
|
||||
ALTER TABLE `chatgpt_functions`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_invite_codes`
|
||||
--
|
||||
ALTER TABLE `chatgpt_invite_codes`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_invite_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_invite_logs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_menus`
|
||||
--
|
||||
ALTER TABLE `chatgpt_menus`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=19;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_mj_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_mj_jobs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_orders`
|
||||
--
|
||||
ALTER TABLE `chatgpt_orders`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_power_logs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_power_logs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_products`
|
||||
--
|
||||
ALTER TABLE `chatgpt_products`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_rewards`
|
||||
--
|
||||
ALTER TABLE `chatgpt_rewards`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_sd_jobs`
|
||||
--
|
||||
ALTER TABLE `chatgpt_sd_jobs`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_users`
|
||||
--
|
||||
ALTER TABLE `chatgpt_users`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=23;
|
||||
|
||||
--
|
||||
-- 使用表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 */;
|
6
database/update-v4.1.0.sql
Normal file
@ -0,0 +1,6 @@
|
||||
ALTER TABLE `chatgpt_chat_models` CHANGE `power` `power` SMALLINT NOT NULL COMMENT '消耗算力点数';
|
||||
ALTER TABLE `chatgpt_users` ADD `openid` VARCHAR(100) NULL COMMENT '第三方登录账号ID' AFTER `last_login_ip`;
|
||||
ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`;
|
||||
ALTER TABLE `chatgpt_users` CHANGE `avatar` `avatar` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像';
|
||||
ALTER TABLE `chatgpt_chat_history` CHANGE `icon` `icon` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标';
|
||||
ALTER TABLE `chatgpt_orders` CHANGE `status` `status` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '订单状态(0:待支付,1:已扫码,2:支付成功)';
|
@ -4,6 +4,7 @@ MysqlDns = "root:12345678@tcp(geekai-mysql:3306)/chatgpt_plus?charset=utf8mb4&co
|
||||
StaticDir = "./static"
|
||||
StaticUrl = "/static"
|
||||
WeChatBot = false
|
||||
TikaHost = "http://tika:9998"
|
||||
|
||||
[Session]
|
||||
SecretKey = "azyehq3ivunjhbntz78isj00i4hz2mt9xtddysfucxakadq4qbfrt0b7q3lnvg80"
|
||||
|
@ -39,7 +39,13 @@ services:
|
||||
- ./logs/xxl-job:/data/applogs
|
||||
- ./conf/xxl-job/application.properties:/application.properties
|
||||
|
||||
|
||||
tika:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/tika:latest
|
||||
container_name: tika
|
||||
restart: always
|
||||
ports:
|
||||
- "9998:9998"
|
||||
|
||||
midjourney-proxy:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/midjourney-proxy:2.6.2
|
||||
container_name: geekai-midjourney-proxy
|
||||
@ -52,7 +58,7 @@ services:
|
||||
|
||||
# 后端 API 程序
|
||||
geekai-api:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.0.9-amd64
|
||||
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.1.0-amd64
|
||||
container_name: geekai-api
|
||||
restart: always
|
||||
depends_on:
|
||||
@ -74,7 +80,7 @@ services:
|
||||
|
||||
# 前端应用
|
||||
geekai-web:
|
||||
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.0.9-amd64
|
||||
image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.1.0-amd64
|
||||
container_name: geekai-web
|
||||
restart: always
|
||||
depends_on:
|
||||
|
@ -6,4 +6,4 @@ VUE_APP_ADMIN_USER=admin
|
||||
VUE_APP_ADMIN_PASS=admin123
|
||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
|
||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
||||
VUE_APP_VERSION=v4.0.9
|
||||
VUE_APP_VERSION=v4.1.0
|
||||
|
@ -2,4 +2,4 @@ VUE_APP_API_HOST=
|
||||
VUE_APP_WS_HOST=
|
||||
VUE_APP_KEY_PREFIX=ChatPLUS_
|
||||
VUE_APP_TITLE="Geek-AI 创作系统"
|
||||
VUE_APP_VERSION=v4.0.9
|
||||
VUE_APP_VERSION=v4.1.0
|
||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 388 KiB |
@ -3,14 +3,12 @@
|
||||
display flex
|
||||
width 100%
|
||||
|
||||
.el-input {
|
||||
width 50%
|
||||
.el-input,.el-select,.el-switch {
|
||||
margin-right 10px
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-left 6px
|
||||
margin-top 2px
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -2,27 +2,27 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app .common-layout {
|
||||
#app .chat-page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside {
|
||||
#app .chat-page .el-aside {
|
||||
background-color: #252526;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .title-box {
|
||||
#app .chat-page .el-aside .title-box {
|
||||
padding: 6px 10px;
|
||||
display: flex;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .title-box span {
|
||||
#app .chat-page .el-aside .title-box span {
|
||||
padding-top: 5px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list {
|
||||
#app .chat-page .el-aside .chat-list {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: #28292a;
|
||||
@ -30,28 +30,28 @@
|
||||
border-right: 1px solid #2f3032;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .search-box {
|
||||
#app .chat-page .el-aside .chat-list .search-box {
|
||||
flex-wrap: wrap;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .search-box .el-input__wrapper {
|
||||
#app .chat-page .el-aside .chat-list .search-box .el-input__wrapper {
|
||||
background-color: #363535;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list ::-webkit-scrollbar {
|
||||
#app .chat-page .el-aside .chat-list ::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content {
|
||||
#app .chat-page .el-aside .chat-list .content {
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content .chat-list-item {
|
||||
#app .chat-page .el-aside .chat-list .content .chat-list-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
@ -59,17 +59,17 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content .chat-list-item:hover {
|
||||
#app .chat-page .el-aside .chat-list .content .chat-list-item:hover {
|
||||
background-color: #343540;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .avatar {
|
||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .chat-title-input {
|
||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .chat-title-input {
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
margin-left: 10px;
|
||||
@ -79,7 +79,7 @@
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .chat-title {
|
||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .chat-title {
|
||||
color: #c1c1c1;
|
||||
padding: 5px 10px;
|
||||
max-width: 220px;
|
||||
@ -89,7 +89,7 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .btn {
|
||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .btn {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
@ -97,19 +97,19 @@
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .btn .el-icon {
|
||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .btn .el-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content .chat-list-item.active {
|
||||
#app .chat-page .el-aside .chat-list .content .chat-list-item.active {
|
||||
background-color: #343540;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .chat-list .content .chat-list-item.active .btn {
|
||||
#app .chat-page .el-aside .chat-list .content .chat-list-item.active .btn {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .tool-box {
|
||||
#app .chat-page .el-aside .tool-box {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
@ -117,48 +117,48 @@
|
||||
border-top: 1px solid #3c3c3c;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .tool-box .user-info {
|
||||
#app .chat-page .el-aside .tool-box .user-info {
|
||||
width: 100%;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link {
|
||||
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link .el-image {
|
||||
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .el-image {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link .username {
|
||||
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .username {
|
||||
display: flex;
|
||||
line-height: 22px;
|
||||
width: 230px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link .el-icon {
|
||||
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .el-icon {
|
||||
color: #ccc;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main {
|
||||
#app .chat-page .el-main {
|
||||
overflow: hidden;
|
||||
--el-main-padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-head {
|
||||
#app .chat-page .el-main .chat-head {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: #28292a;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-head .chat-config {
|
||||
#app .chat-page .el-main .chat-head .chat-config {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@ -166,54 +166,54 @@
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-head .chat-config .role-select-label {
|
||||
#app .chat-page .el-main .chat-head .chat-config .role-select-label {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-head .chat-config .el-select {
|
||||
#app .chat-page .el-main .chat-head .chat-config .el-select {
|
||||
max-width: 150px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-head .chat-config .role-select {
|
||||
#app .chat-page .el-main .chat-head .chat-config .role-select {
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-head .chat-config .el-button .el-icon {
|
||||
#app .chat-page .el-main .chat-head .chat-config .el-button .el-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-head .iconfont {
|
||||
#app .chat-page .el-main .chat-head .iconfont {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-head .is-circle {
|
||||
#app .chat-page .el-main .chat-head .is-circle {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-head .is-circle .iconfont {
|
||||
#app .chat-page .el-main .chat-head .is-circle .iconfont {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box {
|
||||
#app .chat-page .el-main .chat-box {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
background-color: #fff;
|
||||
border-left: 1px solid #4f4f4f;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container {
|
||||
#app .chat-page .el-main .chat-box #container {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container ::-webkit-scrollbar {
|
||||
#app .chat-page .el-main .chat-box #container ::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .chat-box {
|
||||
#app .chat-page .el-main .chat-box #container .chat-box {
|
||||
overflow-y: scroll;
|
||||
--content-font-size: 16px;
|
||||
--content-color: #c1c1c1;
|
||||
@ -221,28 +221,28 @@
|
||||
padding: 0 0 50px 0;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .chat-box .chat-line {
|
||||
#app .chat-page .el-main .chat-box #container .chat-box .chat-line {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .re-generate {
|
||||
#app .chat-page .el-main .chat-box #container .re-generate {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .re-generate .btn-box {
|
||||
#app .chat-page .el-main .chat-box #container .re-generate .btn-box {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .re-generate .btn-box .el-button .el-icon {
|
||||
#app .chat-page .el-main .chat-box #container .re-generate .btn-box .el-button .el-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .input-box {
|
||||
#app .chat-page .el-main .chat-box #container .input-box {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -251,7 +251,7 @@
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .input-box .input-container {
|
||||
#app .chat-page .el-main .chat-box #container .input-box .input-container {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border: none;
|
||||
@ -261,24 +261,24 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .input-box .input-container .el-textarea .el-textarea__inner::-webkit-scrollbar {
|
||||
#app .chat-page .el-main .chat-box #container .input-box .input-container .el-textarea .el-textarea__inner::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .input-box .input-container .select-file {
|
||||
#app .chat-page .el-main .chat-box #container .input-box .input-container .select-file {
|
||||
position: absolute;
|
||||
right: 48px;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .input-box .input-container .send-btn {
|
||||
#app .chat-page .el-main .chat-box #container .input-box .input-container .send-btn {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container .input-box .input-container .send-btn .el-button {
|
||||
#app .chat-page .el-main .chat-box #container .input-box .input-container .send-btn .el-button {
|
||||
padding: 8px 5px;
|
||||
border-radius: 6px;
|
||||
background: #19c37d;
|
||||
@ -286,7 +286,7 @@
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#app .common-layout .el-main .chat-box #container::-webkit-scrollbar {
|
||||
#app .chat-page .el-main .chat-box #container::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ $borderColor = #4676d0;
|
||||
|
||||
height: 100%;
|
||||
|
||||
.common-layout {
|
||||
.chat-page {
|
||||
height: 100%;
|
||||
|
||||
// left side
|
||||
@ -156,6 +156,20 @@ $borderColor = #4676d0;
|
||||
max-width 130px;
|
||||
}
|
||||
|
||||
.setting {
|
||||
padding 5px
|
||||
border-radius 5px
|
||||
cursor pointer
|
||||
.iconfont {
|
||||
font-size 18px
|
||||
color #19c37d
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background #D5FAD3
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
.el-icon {
|
||||
margin-right 5px;
|
||||
@ -169,13 +183,25 @@ $borderColor = #4676d0;
|
||||
position relative
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
background-color: transparent;
|
||||
width: 12px /* 滚动条宽度 */
|
||||
background #F1F1F1
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #e1e1e1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #c1c1c1;
|
||||
border-radius 12px
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #A8A8A8;
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
//border-bottom: 1px solid #4f4f4f
|
||||
|
||||
// 变量定义
|
||||
@ -252,25 +278,35 @@ $borderColor = #4676d0;
|
||||
border: 2px solid #21AA93
|
||||
border-radius 10px
|
||||
padding 10px
|
||||
background-color #F4F4F4
|
||||
|
||||
|
||||
.prompt-input::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.prompt-input {
|
||||
.input-inner {
|
||||
display flex
|
||||
flex-flow column
|
||||
width 100%
|
||||
line-height: 24px
|
||||
border none
|
||||
font-size 14px
|
||||
background none
|
||||
resize: none
|
||||
white-space: pre-wrap; /* 保持文本换行 */
|
||||
word-wrap: break-word; /* 允许单词换行 */
|
||||
overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */
|
||||
|
||||
.file-list {
|
||||
padding-bottom 10px
|
||||
}
|
||||
.prompt-input::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.prompt-input {
|
||||
width 100%
|
||||
line-height: 24px
|
||||
border none
|
||||
font-size 14px
|
||||
background none
|
||||
resize: none
|
||||
white-space: pre-wrap; /* 保持文本换行 */
|
||||
word-wrap: break-word; /* 允许单词换行 */
|
||||
overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.send-btn {
|
||||
width 32px
|
||||
margin-left 10px
|
||||
|
115
web/src/assets/css/login.styl
Normal file
@ -0,0 +1,115 @@
|
||||
.bg {
|
||||
position fixed
|
||||
left 0
|
||||
right 0
|
||||
top 0
|
||||
bottom 0
|
||||
background-color #313237
|
||||
background-image url("~@/assets/img/login-bg.jpg")
|
||||
background-size cover
|
||||
background-position center
|
||||
background-repeat repeat-y
|
||||
//filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */
|
||||
}
|
||||
|
||||
.main {
|
||||
.contain {
|
||||
position fixed
|
||||
left 50%
|
||||
top 40%
|
||||
width 90%
|
||||
max-width 400px;
|
||||
transform translate(-50%, -50%)
|
||||
padding 20px 10px;
|
||||
color #ffffff
|
||||
border-radius 10px;
|
||||
|
||||
.logo {
|
||||
text-align center
|
||||
|
||||
.el-image {
|
||||
width 120px;
|
||||
cursor pointer
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
width 100%
|
||||
margin-bottom 24px
|
||||
font-size 24px
|
||||
color $white_v1
|
||||
letter-space 2px
|
||||
text-align center
|
||||
padding-top 10px
|
||||
}
|
||||
|
||||
.content {
|
||||
width 100%
|
||||
height: auto
|
||||
border-radius 3px
|
||||
|
||||
.block {
|
||||
margin-bottom 16px
|
||||
|
||||
.el-input__inner {
|
||||
border 1px solid $gray-v6 !important
|
||||
|
||||
.el-icon-user, .el-icon-lock {
|
||||
font-size 20px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-row {
|
||||
padding-top 10px;
|
||||
|
||||
.login-btn {
|
||||
width 100%
|
||||
font-size 16px
|
||||
letter-spacing 2px
|
||||
}
|
||||
}
|
||||
|
||||
.text-line {
|
||||
justify-content center
|
||||
padding-top 10px;
|
||||
font-size 14px;
|
||||
}
|
||||
|
||||
.opt {
|
||||
padding 15px
|
||||
.el-col {
|
||||
text-align center
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-top: 2px solid #c1c1c1;
|
||||
}
|
||||
|
||||
.clogin {
|
||||
padding 15px
|
||||
display flex
|
||||
justify-content center
|
||||
|
||||
.iconfont {
|
||||
font-size 20px
|
||||
background: #E9F1F6;
|
||||
padding: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.iconfont.icon-wechat {
|
||||
color #0bc15f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
color #ffffff;
|
||||
|
||||
.container {
|
||||
padding 20px;
|
||||
}
|
||||
}
|
||||
}
|
237
web/src/assets/css/markdown/vue.css
Normal file
@ -0,0 +1,237 @@
|
||||
.chat-line {
|
||||
ol, ul {
|
||||
margin: 0.8em 0;
|
||||
list-style: normal;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
font-weight: 600;
|
||||
padding: 0 2px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
h1:hover a.anchor,
|
||||
h2:hover a.anchor,
|
||||
h3:hover a.anchor,
|
||||
h4:hover a.anchor,
|
||||
h5:hover a.anchor,
|
||||
h6:hover a.anchor {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1 tt,
|
||||
h1 code {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
h2 tt,
|
||||
h2 code {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
h3 tt,
|
||||
h3 code {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
h4 tt,
|
||||
h4 code {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
h5 tt,
|
||||
h5 code {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
h6 tt,
|
||||
h6 code {
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
h2 a,
|
||||
h3 a {
|
||||
color: #34495e;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding-bottom: .4rem;
|
||||
font-size: 2.2rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.75rem;
|
||||
line-height: 1.225;
|
||||
margin: 35px 0 15px;
|
||||
padding-bottom: 0.5em;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.43;
|
||||
margin: 20px 0 7px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
p,
|
||||
blockquote,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
table {
|
||||
margin: 0.8em 0;
|
||||
}
|
||||
|
||||
li > ol,
|
||||
li > ul {
|
||||
margin: 0 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 2px;
|
||||
padding: 0;
|
||||
margin: 16px 0;
|
||||
background-color: #e7e7e7;
|
||||
border: 0 none;
|
||||
overflow: hidden;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
body > h2:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
body > h1:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
body > h1:first-child + h2 {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
body > h3:first-child,
|
||||
body > h4:first-child,
|
||||
body > h5:first-child,
|
||||
body > h6:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
a:first-child h1,
|
||||
a:first-child h2,
|
||||
a:first-child h3,
|
||||
a:first-child h4,
|
||||
a:first-child h5,
|
||||
a:first-child h6 {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
h1 p,
|
||||
h2 p,
|
||||
h3 p,
|
||||
h4 p,
|
||||
h5 p,
|
||||
h6 p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
li p.first {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
ul:first-child,
|
||||
ol:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ul:last-child,
|
||||
ol:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #42b983;
|
||||
padding: 10px 15px;
|
||||
color: #777;
|
||||
background-color: rgba(66, 185, 131, .1);
|
||||
}
|
||||
|
||||
table {
|
||||
padding: 0;
|
||||
word-break: initial;
|
||||
}
|
||||
|
||||
table tr {
|
||||
border-top: 1px solid #dfe2e5;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table tr:nth-child(2n),
|
||||
thead {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
table tr th {
|
||||
font-weight: bold;
|
||||
border: 1px solid #dfe2e5;
|
||||
border-bottom: 0;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
padding: 6px 13px;
|
||||
}
|
||||
|
||||
table tr td {
|
||||
border: 1px solid #dfe2e5;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
padding: 6px 13px;
|
||||
}
|
||||
|
||||
table tr th:first-child,
|
||||
table tr td:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
table tr th:last-child,
|
||||
table tr td:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 4.1 MiB |
@ -1,152 +1,420 @@
|
||||
<template>
|
||||
<div class="chat-line chat-line-prompt">
|
||||
<div class="chat-line chat-line-prompt-list" v-if="listStyle === 'list'">
|
||||
<div class="chat-line-inner">
|
||||
<div class="chat-icon">
|
||||
<img :src="data.icon" alt="User"/>
|
||||
</div>
|
||||
|
||||
<div class="chat-item">
|
||||
<div v-if="files.length > 0" class="file-list-box">
|
||||
<div v-for="file in files">
|
||||
<div class="image" v-if="isImage(file.ext)">
|
||||
<el-image :src="file.url" fit="cover"/>
|
||||
</div>
|
||||
<div class="item" v-else>
|
||||
<div class="icon">
|
||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="title">
|
||||
<el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{file.name}}</el-link>
|
||||
</div>
|
||||
<div class="info">
|
||||
<span>{{GetFileType(file.ext)}}</span>
|
||||
<span>{{FormatFileSize(file.size)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" v-html="content"></div>
|
||||
<div class="bar" v-if="data.created_at > 0">
|
||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
|
||||
<span class="bar-item">tokens: {{ finalTokens }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-line chat-line-prompt-chat" v-else>
|
||||
<div class="chat-line-inner">
|
||||
<div class="chat-icon">
|
||||
<img :src="icon" alt="User"/>
|
||||
<img :src="data.icon" alt="User"/>
|
||||
</div>
|
||||
|
||||
<div class="chat-item">
|
||||
<div class="content" v-html="content"></div>
|
||||
<div class="bar" v-if="createdAt">
|
||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
||||
<!-- <span class="bar-item">Tokens: {{ finalTokens }}</span>-->
|
||||
|
||||
<div v-if="files.length > 0" class="file-list-box">
|
||||
<div v-for="file in files">
|
||||
<div class="image" v-if="isImage(file.ext)">
|
||||
<el-image :src="file.url" fit="cover"/>
|
||||
</div>
|
||||
<div class="item" v-else>
|
||||
<div class="icon">
|
||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="title">
|
||||
<el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{file.name}}</el-link>
|
||||
</div>
|
||||
<div class="info">
|
||||
<span>{{GetFileType(file.ext)}}</span>
|
||||
<span>{{FormatFileSize(file.size)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<div class="content" v-html="content"></div>
|
||||
</div>
|
||||
<div class="bar" v-if="data.created_at > 0">
|
||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
|
||||
<span class="bar-item">tokens: {{ finalTokens }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent} from "vue"
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue"
|
||||
import {Clock} from "@element-plus/icons-vue";
|
||||
import {httpPost} from "@/utils/http";
|
||||
import hl from "highlight.js";
|
||||
import {dateFormat, isImage, processPrompt} from "@/utils/libs";
|
||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ChatPrompt',
|
||||
components: {Clock},
|
||||
methods: {},
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'images/user-icon.png',
|
||||
},
|
||||
createdAt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
tokens: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
model: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
finalTokens: this.tokens
|
||||
const mathjaxPlugin = require('markdown-it-mathjax3')
|
||||
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)
|
||||
// 显示复制代码按钮
|
||||
const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
|
||||
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '</textarea>')}</textarea>`
|
||||
if (lang && hl.getLanguage(lang)) {
|
||||
const langHtml = `<span class="lang-name">${lang}</span>`
|
||||
// 处理代码高亮
|
||||
const preCode = hl.highlight(lang, str, true).value
|
||||
// 将代码包裹在 pre 中
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
|
||||
}
|
||||
|
||||
// 处理代码高亮
|
||||
const preCode = md.utils.escapeHtml(str)
|
||||
// 将代码包裹在 pre 中
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
|
||||
}
|
||||
});
|
||||
md.use(mathjaxPlugin)
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: {
|
||||
content: '',
|
||||
created_at: '',
|
||||
tokens: 0,
|
||||
model: '',
|
||||
icon: '',
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (!this.finalTokens) {
|
||||
httpPost("/api/chat/tokens", {text: this.content, model: this.model}).then(res => {
|
||||
this.finalTokens = res.data;
|
||||
}).catch(() => {
|
||||
})
|
||||
listStyle: {
|
||||
type: String,
|
||||
default: 'list',
|
||||
},
|
||||
})
|
||||
const finalTokens = ref(props.data.tokens)
|
||||
const content =ref(processPrompt(props.data.content))
|
||||
const files = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
if (!finalTokens.value) {
|
||||
httpPost("/api/chat/tokens", {text: props.data.content, model: props.data.model}).then(res => {
|
||||
finalTokens.value = res.data;
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
|
||||
const linkRegex = /(https?:\/\/\S+)/g;
|
||||
const links = props.data.content.match(linkRegex);
|
||||
if (links) {
|
||||
httpPost("/api/upload/list", {urls: links}).then(res => {
|
||||
files.value = res.data
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
for (let link of links) {
|
||||
content.value = content.value.replace(link,"")
|
||||
}
|
||||
}
|
||||
content.value = md.render(content.value.trim())
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.chat-line-prompt {
|
||||
background-color #ffffff;
|
||||
justify-content: center;
|
||||
width 100%
|
||||
padding-bottom: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-bottom: 1px solid #d9d9e3;
|
||||
@import '@/assets/css/markdown/vue.css';
|
||||
.chat-page,.chat-export {
|
||||
.chat-line-prompt-list {
|
||||
background-color #ffffff;
|
||||
justify-content: center;
|
||||
width 100%
|
||||
padding-bottom: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
border-bottom: 1px solid #d9d9e3;
|
||||
|
||||
.chat-line-inner {
|
||||
display flex;
|
||||
width 100%;
|
||||
max-width 900px;
|
||||
padding-left 10px;
|
||||
.chat-line-inner {
|
||||
display flex;
|
||||
width 100%;
|
||||
max-width 900px;
|
||||
padding-left 10px;
|
||||
|
||||
.chat-icon {
|
||||
margin-right 20px;
|
||||
|
||||
img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-item {
|
||||
width 100%
|
||||
position: relative;
|
||||
padding: 0 5px 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
.content {
|
||||
word-break break-word;
|
||||
padding: 6px 10px;
|
||||
color #374151;
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
overflow: auto;
|
||||
.chat-icon {
|
||||
margin-right 20px;
|
||||
|
||||
img {
|
||||
max-width: 600px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
margin 10px 0
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
color #20a0ff
|
||||
}
|
||||
|
||||
p {
|
||||
line-height 1.5
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
p:first-child {
|
||||
margin-top 0
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
padding 10px;
|
||||
.chat-item {
|
||||
width 100%
|
||||
padding: 0 5px 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
.bar-item {
|
||||
background-color #f7f7f8;
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
border-radius 5px;
|
||||
|
||||
.el-icon {
|
||||
.file-list-box {
|
||||
display flex
|
||||
flex-flow column
|
||||
.image {
|
||||
display flex
|
||||
flex-flow row
|
||||
margin-right 10px
|
||||
position relative
|
||||
top 2px;
|
||||
|
||||
.el-image {
|
||||
border 1px solid #e3e3e3
|
||||
border-radius 10px
|
||||
margin-bottom 10px
|
||||
}
|
||||
}
|
||||
.item {
|
||||
display flex
|
||||
flex-flow row
|
||||
border-radius 10px
|
||||
background-color #ffffff
|
||||
border 1px solid #e3e3e3
|
||||
padding 6px
|
||||
margin-bottom 10px
|
||||
|
||||
.icon {
|
||||
.el-image {
|
||||
width 40px
|
||||
height 40px
|
||||
}
|
||||
}
|
||||
.body {
|
||||
margin-left 8px
|
||||
font-size 14px
|
||||
.title {
|
||||
font-weight bold
|
||||
line-height 24px
|
||||
color #0D0D0D
|
||||
}
|
||||
.info {
|
||||
color #B4B4B4
|
||||
|
||||
span {
|
||||
margin-right 10px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
word-break break-word;
|
||||
padding: 0;
|
||||
color #374151;
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
overflow: auto;
|
||||
|
||||
img {
|
||||
max-width: 600px;
|
||||
border-radius: 10px;
|
||||
margin 10px 0
|
||||
}
|
||||
|
||||
p {
|
||||
line-height 1.5
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
p:first-child {
|
||||
margin-top 0
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
padding 10px 10px 10px 0;
|
||||
|
||||
.bar-item {
|
||||
background-color #f7f7f8;
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
border-radius 5px;
|
||||
|
||||
.el-icon {
|
||||
position relative
|
||||
top 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.chat-line-prompt-chat {
|
||||
background-color #ffffff;
|
||||
justify-content: center;
|
||||
width 100%
|
||||
padding-bottom: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
|
||||
.chat-line-inner {
|
||||
display flex;
|
||||
width 100%;
|
||||
padding 0 25px;
|
||||
|
||||
.chat-icon {
|
||||
margin-right 20px;
|
||||
|
||||
img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-item {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
max-width 60%
|
||||
|
||||
.file-list-box {
|
||||
display flex
|
||||
flex-flow column
|
||||
.image {
|
||||
display flex
|
||||
flex-flow row
|
||||
margin-right 10px
|
||||
position relative
|
||||
|
||||
.el-image {
|
||||
border 1px solid #e3e3e3
|
||||
border-radius 10px
|
||||
margin-bottom 10px
|
||||
}
|
||||
}
|
||||
.item {
|
||||
display flex
|
||||
flex-flow row
|
||||
border-radius 10px
|
||||
background-color #ffffff
|
||||
border 1px solid #e3e3e3
|
||||
padding 6px
|
||||
margin-bottom 10px
|
||||
|
||||
.icon {
|
||||
.el-image {
|
||||
width 40px
|
||||
height 40px
|
||||
}
|
||||
}
|
||||
.body {
|
||||
margin-left 8px
|
||||
font-size 14px
|
||||
.title {
|
||||
font-weight bold
|
||||
line-height 24px
|
||||
color #0D0D0D
|
||||
}
|
||||
.info {
|
||||
color #B4B4B4
|
||||
|
||||
span {
|
||||
margin-right 10px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.content-wrapper {
|
||||
display flex
|
||||
.content {
|
||||
word-break break-word;
|
||||
padding: 1rem
|
||||
color #222222;
|
||||
font-size: var(--content-font-size);
|
||||
overflow: auto;
|
||||
background-color #98e165
|
||||
border-radius: 0 10px 10px 10px;
|
||||
|
||||
img {
|
||||
max-width: 600px;
|
||||
border-radius: 10px;
|
||||
margin 10px 0
|
||||
}
|
||||
|
||||
p {
|
||||
line-height 1.5
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
p:first-child {
|
||||
margin-top 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.bar {
|
||||
padding 10px 10px 10px 0;
|
||||
|
||||
.bar-item {
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
border-radius 5px;
|
||||
|
||||
.el-icon {
|
||||
position relative
|
||||
top 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="chat-line chat-line-reply">
|
||||
<div class="chat-line chat-line-reply-list" v-if="listStyle === 'list'">
|
||||
<div class="chat-line-inner">
|
||||
<div class="chat-icon">
|
||||
<img :src="data.icon" alt="ChatGPT">
|
||||
@ -9,7 +9,7 @@
|
||||
<div class="content" v-html="data.content"></div>
|
||||
<div class="bar" v-if="data.created_at">
|
||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
|
||||
<!-- <span class="bar-item">Tokens: {{ tokens }}</span>-->
|
||||
<span class="bar-item">tokens: {{ data.tokens }}</span>
|
||||
<span class="bar-item">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
@ -61,6 +61,59 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-line chat-line-reply-chat" v-else>
|
||||
<div class="chat-line-inner">
|
||||
<div class="chat-icon">
|
||||
<img :src="data.icon" alt="ChatGPT">
|
||||
</div>
|
||||
|
||||
<div class="chat-item">
|
||||
<div class="content-wrapper">
|
||||
<div class="content" v-html="data.content"></div>
|
||||
</div>
|
||||
<div class="bar" v-if="data.created_at">
|
||||
<span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
|
||||
<span class="bar-item">tokens: {{ data.tokens }}</span>
|
||||
<span class="bar-item bg">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="复制回答"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon class="copy-reply" :data-clipboard-text="data.orgContent">
|
||||
<DocumentCopy/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span v-if="!readOnly">
|
||||
<span class="bar-item bg" @click="reGenerate(data.prompt)">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="重新生成"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-icon><Refresh/></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
|
||||
<span class="bar-item bg" @click="synthesis(data.orgContent)">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="生成语音朗读"
|
||||
placement="bottom"
|
||||
>
|
||||
<i class="iconfont icon-speaker"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@ -71,12 +124,22 @@ import {dateFormat} from "@/utils/libs";
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: {},
|
||||
default: {
|
||||
icon: "",
|
||||
content: "",
|
||||
created_at: "",
|
||||
tokens: 0,
|
||||
orgContent: ""
|
||||
},
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
listStyle: {
|
||||
type: String,
|
||||
default: 'list',
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['regen']);
|
||||
@ -92,13 +155,15 @@ const synthesis = (text) => {
|
||||
|
||||
// 重新生成
|
||||
const reGenerate = (prompt) => {
|
||||
console.log(prompt)
|
||||
emits('regen', prompt)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.common-layout {
|
||||
.chat-line-reply {
|
||||
@import '@/assets/css/markdown/vue.css';
|
||||
.chat-page,.chat-export {
|
||||
.chat-line-reply-list {
|
||||
justify-content: center;
|
||||
background-color: rgba(247, 247, 248, 1);
|
||||
width 100%
|
||||
@ -126,24 +191,18 @@ const reGenerate = (prompt) => {
|
||||
.chat-item {
|
||||
width 100%
|
||||
position: relative;
|
||||
padding: 0 0 0 5px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.content {
|
||||
min-height 20px;
|
||||
word-break break-word;
|
||||
padding: 6px 10px;
|
||||
padding: 0
|
||||
color #374151;
|
||||
font-size: var(--content-font-size);
|
||||
border-radius: 5px;
|
||||
overflow auto;
|
||||
|
||||
a {
|
||||
color #20a0ff
|
||||
}
|
||||
|
||||
// control the image size in content
|
||||
|
||||
img {
|
||||
max-width: 600px;
|
||||
border-radius: 10px;
|
||||
@ -170,10 +229,11 @@ const reGenerate = (prompt) => {
|
||||
|
||||
.code-container {
|
||||
position relative
|
||||
display flex
|
||||
|
||||
.hljs {
|
||||
border-radius 10px
|
||||
line-height 1.5
|
||||
width 100%
|
||||
}
|
||||
|
||||
.copy-code-btn {
|
||||
@ -194,7 +254,7 @@ const reGenerate = (prompt) => {
|
||||
.lang-name {
|
||||
position absolute;
|
||||
right 10px
|
||||
bottom 50px
|
||||
bottom 20px
|
||||
padding 2px 6px 4px 6px
|
||||
background-color #444444
|
||||
border-radius 10px
|
||||
@ -241,7 +301,7 @@ const reGenerate = (prompt) => {
|
||||
|
||||
|
||||
.bar {
|
||||
padding 10px;
|
||||
padding 10px 10px 10px 0;
|
||||
|
||||
.bar-item {
|
||||
background-color #e7e7e8;
|
||||
@ -277,6 +337,187 @@ const reGenerate = (prompt) => {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.chat-line-reply-chat {
|
||||
justify-content: center;
|
||||
width 100%
|
||||
padding-bottom: 1.5rem;
|
||||
padding-top: 1.5rem;
|
||||
|
||||
.chat-line-inner {
|
||||
display flex;
|
||||
padding 0 25px;
|
||||
width 100%
|
||||
flex-flow row-reverse
|
||||
|
||||
.chat-icon {
|
||||
margin-left 20px;
|
||||
|
||||
img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-item {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
max-width 60%
|
||||
|
||||
.content-wrapper {
|
||||
display flex
|
||||
flex-flow row-reverse
|
||||
.content {
|
||||
min-height 20px;
|
||||
word-break break-word;
|
||||
padding: 1rem
|
||||
color #374151;
|
||||
font-size: var(--content-font-size);
|
||||
overflow auto;
|
||||
background-color #F5F5F5
|
||||
border-radius: 10px 0 10px 10px;
|
||||
|
||||
img {
|
||||
max-width: 600px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height 1.5
|
||||
|
||||
code {
|
||||
color #374151
|
||||
background-color #e7e7e8
|
||||
padding 0 3px;
|
||||
border-radius 5px;
|
||||
}
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
p:first-child {
|
||||
margin-top 0
|
||||
}
|
||||
|
||||
.code-container {
|
||||
position relative
|
||||
display flex
|
||||
|
||||
.hljs {
|
||||
border-radius 10px
|
||||
width 100%
|
||||
}
|
||||
|
||||
.copy-code-btn {
|
||||
position: absolute;
|
||||
right 10px
|
||||
top 10px
|
||||
cursor pointer
|
||||
font-size 12px
|
||||
color #c1c1c1
|
||||
|
||||
&:hover {
|
||||
color #20a0ff
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.lang-name {
|
||||
position absolute;
|
||||
right 10px
|
||||
bottom 20px
|
||||
padding 2px 6px 4px 6px
|
||||
background-color #444444
|
||||
border-radius 10px
|
||||
color #00e0e0
|
||||
}
|
||||
|
||||
|
||||
// 设置表格边框
|
||||
|
||||
table {
|
||||
width 100%
|
||||
margin-bottom 1rem
|
||||
color #212529
|
||||
border-collapse collapse;
|
||||
border 1px solid #dee2e6;
|
||||
background-color #ffffff
|
||||
|
||||
thead {
|
||||
th {
|
||||
border 1px solid #dee2e6
|
||||
vertical-align: bottom
|
||||
border-bottom: 2px solid #dee2e6
|
||||
padding 10px
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
border 1px solid #dee2e6
|
||||
padding 10px
|
||||
}
|
||||
}
|
||||
|
||||
// 代码快
|
||||
|
||||
blockquote {
|
||||
margin 0
|
||||
background-color: #ebfffe;
|
||||
padding: 0.8rem 1.5rem;
|
||||
border-left: 0.5rem solid;
|
||||
border-color: #026863;
|
||||
color: #2c3e50;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.bar {
|
||||
padding 10px 10px 10px 0;
|
||||
|
||||
.bar-item {
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
border-radius 5px;
|
||||
|
||||
.el-icon {
|
||||
position relative
|
||||
top 2px;
|
||||
cursor pointer
|
||||
}
|
||||
}
|
||||
|
||||
.bar-item.bg {
|
||||
background-color #e7e7e8
|
||||
cursor pointer
|
||||
}
|
||||
|
||||
.el-button {
|
||||
height 20px
|
||||
padding 5px 2px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tool-box {
|
||||
font-size 16px;
|
||||
|
||||
.el-button {
|
||||
height 20px
|
||||
padding 5px 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
50
web/src/components/ChatSetting.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="config-dialog"
|
||||
v-model="showDialog"
|
||||
:close-on-click-modal="true"
|
||||
:before-close="close"
|
||||
style="max-width: 600px"
|
||||
title="聊天配置"
|
||||
>
|
||||
<div class="chat-setting">
|
||||
<el-form :model="data" label-width="100px" label-position="left">
|
||||
<el-form-item label="聊天样式:">
|
||||
<el-radio-group v-model="data.style" @change="(val) => {store.setChatListStyle(val)}">
|
||||
<el-radio value="list">列表样式</el-radio>
|
||||
<el-radio value="chat">对话样式</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed, ref} from "vue"
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
const store = useSharedStore();
|
||||
|
||||
const data = ref({
|
||||
style: store.chatListStyle,
|
||||
})
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
show: Boolean,
|
||||
});
|
||||
|
||||
const showDialog = computed(() => {
|
||||
return props.show
|
||||
})
|
||||
const emits = defineEmits(['hide']);
|
||||
const close = function () {
|
||||
emits('hide', false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.chat-setting {
|
||||
|
||||
}
|
||||
</style>
|
115
web/src/components/FileList.vue
Normal file
@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<el-container class="chat-file-list">
|
||||
<div v-for="file in fileList">
|
||||
<div class="image" v-if="isImage(file.ext)">
|
||||
<el-image :src="file.url" fit="cover"/>
|
||||
<div class="action">
|
||||
<el-icon @click="removeFile(file)"><CircleCloseFilled /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item" v-else>
|
||||
<div class="icon">
|
||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" />
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="title">
|
||||
<el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{substr(file.name, 30)}}</el-link>
|
||||
</div>
|
||||
<div class="info">
|
||||
<span>{{GetFileType(file.ext)}}</span>
|
||||
<span>{{FormatFileSize(file.size)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action">
|
||||
<el-icon @click="removeFile(file)"><CircleCloseFilled /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {CircleCloseFilled} from "@element-plus/icons-vue";
|
||||
import {isImage, removeArrayItem, substr} from "@/utils/libs";
|
||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
|
||||
|
||||
const props = defineProps({
|
||||
files: {
|
||||
type: Array,
|
||||
default:[],
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['removeFile']);
|
||||
const fileList = ref(props.files)
|
||||
|
||||
|
||||
const removeFile = (file) => {
|
||||
fileList.value = removeArrayItem(fileList.value, file, (v1,v2) => v1.url===v2.url)
|
||||
emits('removeFile', file)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
|
||||
.chat-file-list {
|
||||
display flex
|
||||
flex-flow row
|
||||
.image {
|
||||
display flex
|
||||
flex-flow row
|
||||
margin-right 10px
|
||||
position relative
|
||||
|
||||
.el-image {
|
||||
height 56px
|
||||
width 56px
|
||||
border 1px solid #e3e3e3
|
||||
border-radius 10px
|
||||
}
|
||||
}
|
||||
.item {
|
||||
position relative
|
||||
display flex
|
||||
flex-flow row
|
||||
border-radius 10px
|
||||
background-color #ffffff
|
||||
border 1px solid #e3e3e3
|
||||
padding 6px
|
||||
margin-right 10px
|
||||
|
||||
.icon {
|
||||
.el-image {
|
||||
width 40px
|
||||
height 40px
|
||||
}
|
||||
}
|
||||
.body {
|
||||
margin-left 5px
|
||||
font-size 14px
|
||||
.title {
|
||||
line-height 24px
|
||||
color #0D0D0D
|
||||
}
|
||||
.info {
|
||||
color #B4B4B4
|
||||
|
||||
span {
|
||||
margin-right 10px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
position absolute
|
||||
top -8px
|
||||
right -8px
|
||||
color #da0d54
|
||||
cursor pointer
|
||||
font-size 20px
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-container class="file-list-box">
|
||||
<el-container class="file-select-box">
|
||||
<a class="file-upload-img" @click="fetchFiles">
|
||||
<i class="iconfont icon-attachment-st"></i>
|
||||
</a>
|
||||
@ -20,6 +20,7 @@
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="afterRead"
|
||||
accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf"
|
||||
>
|
||||
<el-icon class="avatar-uploader-icon">
|
||||
<Plus/>
|
||||
@ -34,8 +35,8 @@
|
||||
effect="dark"
|
||||
:content="file.name"
|
||||
placement="top">
|
||||
<el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file.url)"/>
|
||||
<el-image :src="getFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file.url)"/>
|
||||
<el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file)"/>
|
||||
<el-image :src="GetFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file)"/>
|
||||
</el-tooltip>
|
||||
|
||||
<div class="opt">
|
||||
@ -55,6 +56,7 @@ import {ElMessage} from "element-plus";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {Delete, Plus} from "@element-plus/icons-vue";
|
||||
import {isImage, removeArrayItem} from "@/utils/libs";
|
||||
import {GetFileIcon} from "@/store/system";
|
||||
|
||||
const props = defineProps({
|
||||
userId: Number,
|
||||
@ -65,30 +67,12 @@ const fileList = ref([])
|
||||
|
||||
const fetchFiles = () => {
|
||||
show.value = true
|
||||
httpGet("/api/upload/list").then(res => {
|
||||
httpPost("/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();
|
||||
@ -113,19 +97,19 @@ const removeFile = (file) => {
|
||||
})
|
||||
}
|
||||
|
||||
const insertURL = (url) => {
|
||||
const insertURL = (file) => {
|
||||
show.value = false
|
||||
// 如果是相对路径,处理成绝对路径
|
||||
if (url.indexOf("http") === -1) {
|
||||
url = location.protocol + "//" + location.host + url
|
||||
if (file.url.indexOf("http") === -1) {
|
||||
file.url = location.protocol + "//" + location.host + file.url
|
||||
}
|
||||
emits('selected', url)
|
||||
emits('selected', file)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
||||
.file-list-box {
|
||||
.file-select-box {
|
||||
.file-upload-img {
|
||||
.iconfont {
|
||||
font-size: 24px;
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="foot-container">
|
||||
<div class="footer">
|
||||
Powered by {{ author }} @
|
||||
<el-link type="primary" href="https://github.com/yangjian102621/chatgpt-plus" target="_blank" style="--el-link-text-color:#ffffff">
|
||||
<el-link type="primary" :href="gitURL" target="_blank" style="--el-link-text-color:#ffffff">
|
||||
{{ title }} -
|
||||
{{ version }}
|
||||
</el-link>
|
||||
@ -15,6 +15,7 @@ import {ref} from "vue";
|
||||
|
||||
const title = ref(process.env.VUE_APP_TITLE)
|
||||
const version = ref(process.env.VUE_APP_VERSION)
|
||||
const gitURL = ref(process.env.VUE_APP_GIT_URL)
|
||||
const author = ref('极客学长')
|
||||
</script>
|
||||
|
||||
@ -33,6 +34,10 @@ const author = ref('极客学长')
|
||||
font-size 14px;
|
||||
padding 20px;
|
||||
width 100%
|
||||
|
||||
.el-link {
|
||||
color #409eff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,7 +289,7 @@ const submitLogin = () => {
|
||||
}
|
||||
|
||||
httpPost('/api/user/login', data.value).then((res) => {
|
||||
setUserToken(res.data)
|
||||
setUserToken(res.data.token)
|
||||
ElMessage.success("登录成功!")
|
||||
emits("hide")
|
||||
emits('success')
|
||||
|
58
web/src/components/TaskList.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="running-job-list">
|
||||
<div class="running-job-box" v-if="list.length > 0">
|
||||
<div class="job-item" v-for="item in list">
|
||||
<div v-if="item.progress > 0" class="job-item-inner">
|
||||
<el-image v-if="item.img_url" :src="item['img_url']" fit="cover" loading="lazy">
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
|
||||
<div class="progress">
|
||||
<el-progress type="circle" :percentage="item.progress" :width="100"
|
||||
color="#47fff1"/>
|
||||
</div>
|
||||
</div>
|
||||
<el-image fit="cover" v-else>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<i class="iconfont icon-quick-start"></i>
|
||||
<span>任务正在排队中</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue";
|
||||
import {CircleCloseFilled, Picture} from "@element-plus/icons-vue";
|
||||
import {isImage, removeArrayItem, substr} from "@/utils/libs";
|
||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
default:[],
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
@import "~@/assets/css/running-job-list.styl"
|
||||
</style>
|
@ -46,8 +46,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref, watch} from "vue";
|
||||
import {httpPost} from "@/utils/http";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {DocumentCopy} from "@element-plus/icons-vue";
|
||||
@ -73,7 +73,7 @@ onMounted(() => {
|
||||
|
||||
// 获取数据
|
||||
const fetchData = () => {
|
||||
httpPost('/api/order/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
||||
httpGet('/api/order/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
||||
if (res.data) {
|
||||
items.value = res.data.items
|
||||
total.value = res.data.total
|
||||
|
@ -101,6 +101,12 @@ const routes = [
|
||||
meta: {title: '用户登录'},
|
||||
component: () => import('@/views/Login.vue'),
|
||||
},
|
||||
{
|
||||
name: 'login-callback',
|
||||
path: '/login/callback',
|
||||
meta: {title: '用户登录'},
|
||||
component: () => import('@/views/LoginCallback.vue'),
|
||||
},
|
||||
{
|
||||
name: 'register',
|
||||
path: '/register',
|
||||
|
@ -1,13 +1,19 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import Storage from 'good-storage'
|
||||
|
||||
export const useSharedStore = defineStore('shared', {
|
||||
state: () => ({
|
||||
showLoginDialog: false
|
||||
showLoginDialog: false,
|
||||
chatListStyle: Storage.get("chat_list_style","chat")
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
setShowLoginDialog(value) {
|
||||
this.showLoginDialog = value;
|
||||
},
|
||||
setChatListStyle(value) {
|
||||
this.chatListStyle = value;
|
||||
Storage.set("chat_list_style", value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -24,4 +24,38 @@ export function getAdminTheme() {
|
||||
|
||||
export function setAdminTheme(theme) {
|
||||
Storage.set(ADMIN_THEME, theme)
|
||||
}
|
||||
|
||||
export function GetFileIcon(ext) {
|
||||
const files = {
|
||||
".docx": "doc.png",
|
||||
".doc": "doc.png",
|
||||
".xls": "xls.png",
|
||||
".xlsx": "xls.png",
|
||||
".csv": "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'
|
||||
}
|
||||
|
||||
// 获取文件类型
|
||||
export function GetFileType (ext) {
|
||||
return ext.replace(".", "").toUpperCase()
|
||||
}
|
||||
|
||||
// 将文件大小转成字符
|
||||
export function FormatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
@ -26,18 +26,16 @@ axios.interceptors.request.use(
|
||||
})
|
||||
axios.interceptors.response.use(
|
||||
response => {
|
||||
let data = response.data;
|
||||
if (data.code === 0) {
|
||||
return response
|
||||
} else if (data.code === 400) {
|
||||
if (response.request.responseURL.indexOf("/api/admin") !== -1) {
|
||||
return response
|
||||
}, error => {
|
||||
if (error.response.status === 401 || error.response.status === 400) {
|
||||
if (error.response.request.responseURL.indexOf("/api/admin") !== -1) {
|
||||
removeAdminToken()
|
||||
} else {
|
||||
removeUserToken()
|
||||
}
|
||||
return Promise.reject(error.response.data)
|
||||
}
|
||||
return Promise.reject(response.data)
|
||||
}, error => {
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
|
@ -188,58 +188,13 @@ export function processContent(content) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lines = content.split("\n")
|
||||
if (lines.length <= 1) {
|
||||
return content
|
||||
}
|
||||
const texts = []
|
||||
// 定义匹配数学公式的正则表达式
|
||||
const formulaRegex = /^\s*[a-z|A-Z]+[^=]+\s*=\s*[^=]+$/;
|
||||
let hasCode = false
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
// 处理引用块换行
|
||||
if (lines[i].startsWith(">")) {
|
||||
texts.push(lines[i])
|
||||
texts.push("\n")
|
||||
continue
|
||||
}
|
||||
// 如果包含代码块则跳过公式检测
|
||||
if (lines[i].indexOf("```") !== -1) {
|
||||
texts.push(lines[i])
|
||||
hasCode = true
|
||||
continue
|
||||
}
|
||||
// 识别并处理数学公式,需要排除那些已经被识别出来的公式
|
||||
if (i > 0 && formulaRegex.test(lines[i]) && lines[i - 1].indexOf("$$") === -1 && !hasCode) {
|
||||
texts.push("$$")
|
||||
texts.push(lines[i])
|
||||
texts.push("$$")
|
||||
continue
|
||||
}
|
||||
texts.push(lines[i])
|
||||
}
|
||||
return texts.join("\n")
|
||||
return content
|
||||
}
|
||||
|
||||
export function processPrompt(prompt) {
|
||||
prompt = prompt.replace(/&/g, "&")
|
||||
return prompt.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
|
||||
const linkRegex = /(https?:\/\/\S+)/g;
|
||||
const links = prompt.match(linkRegex);
|
||||
if (links) {
|
||||
for (let link of links) {
|
||||
if (isImage(link)) {
|
||||
const index = prompt.indexOf(link)
|
||||
if (prompt.substring(index - 1, 2) !== "]") {
|
||||
prompt = prompt.replace(link, "\n![](" + link + ")\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return prompt
|
||||
}
|
||||
|
||||
// 判断是否为微信浏览器
|
||||
@ -258,3 +213,4 @@ export function showLoginDialog(router) {
|
||||
// on cancel
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -6,22 +6,14 @@
|
||||
</div>
|
||||
|
||||
<div v-for="item in chatData" :key="item.id">
|
||||
<chat-prompt
|
||||
v-if="item.type==='prompt'"
|
||||
:icon="item.icon"
|
||||
:created-at="dateFormat(item['created_at'])"
|
||||
:tokens="item['tokens']"
|
||||
:model="item['model']"
|
||||
:content="item.content"/>
|
||||
<chat-reply v-else-if="item.type==='reply'"
|
||||
:data="item" :read-only="true"/>
|
||||
<chat-prompt v-if="item.type==='prompt'" :data="item" list-style="list"/>
|
||||
<chat-reply v-else-if="item.type==='reply'" :data="item" :read-only="true" list-style="list"/>
|
||||
</div>
|
||||
</div><!-- end chat box -->
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import ChatReply from "@/components/ChatReply.vue";
|
||||
import ChatPrompt from "@/components/ChatPrompt.vue";
|
||||
import {nextTick, onMounted, ref} from "vue";
|
||||
@ -98,7 +90,7 @@ onMounted(() => {
|
||||
padding 0 20px
|
||||
|
||||
.chat-box {
|
||||
width 800px;
|
||||
width 100%;
|
||||
// 变量定义
|
||||
--content-font-size: 16px;
|
||||
--content-color: #c1c1c1;
|
||||
@ -110,57 +102,13 @@ onMounted(() => {
|
||||
text-align center
|
||||
}
|
||||
|
||||
|
||||
.chat-line-prompt {
|
||||
.chat-line {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.chat-line-inner {
|
||||
.chat-icon {
|
||||
margin-right: 0
|
||||
}
|
||||
.content {
|
||||
padding-top: 0
|
||||
font-size 16px;
|
||||
|
||||
p:first-child {
|
||||
margin-top 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-line-reply {
|
||||
padding-top: 1.5rem;
|
||||
|
||||
.chat-line-inner {
|
||||
display flex
|
||||
|
||||
.bar-item {
|
||||
background-color: #f7f7f8;
|
||||
color: #888;
|
||||
padding: 3px 5px;
|
||||
margin-right: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.chat-icon {
|
||||
margin-right: 20px
|
||||
|
||||
img {
|
||||
width 30px
|
||||
height 30px
|
||||
border-radius: 10px;
|
||||
padding: 1px
|
||||
}
|
||||
}
|
||||
|
||||
.chat-item {
|
||||
img {
|
||||
max-width 90%
|
||||
}
|
||||
}
|
||||
max-width 800px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="common-layout">
|
||||
<div class="chat-page">
|
||||
<el-container>
|
||||
<el-aside>
|
||||
<div class="chat-list">
|
||||
@ -24,7 +24,7 @@
|
||||
<div class="content" :style="{height: leftBoxHeight+'px'}">
|
||||
<el-row v-for="chat in chatList" :key="chat.chat_id">
|
||||
<div :class="chat.chat_id === activeChat.chat_id?'chat-list-item active':'chat-list-item'"
|
||||
@click="changeChat(chat)">
|
||||
@click="loadChat(chat)">
|
||||
<el-image :src="chat.icon" class="avatar"/>
|
||||
<span class="chat-title-input" v-if="chat.edit">
|
||||
<el-input v-model="tmpChatTitle" size="small" @keydown="titleKeydown($event, chat)"
|
||||
@ -99,6 +99,12 @@
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<span class="setting" @click="showChatSetting = true">
|
||||
<el-tooltip class="box-item" effect="dark" content="对话设置">
|
||||
<i class="iconfont icon-config"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -109,13 +115,8 @@
|
||||
</div>
|
||||
<div v-for="item in chatData" :key="item.id" v-else>
|
||||
<chat-prompt
|
||||
v-if="item.type==='prompt'"
|
||||
:icon="item.icon"
|
||||
:created-at="dateFormat(item['created_at'])"
|
||||
:tokens="item['tokens']"
|
||||
:model="getModelValue(modelID)"
|
||||
:content="item.content"/>
|
||||
<chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false"/>
|
||||
v-if="item.type==='prompt'" :data="item" :list-style="listStyle"/>
|
||||
<chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false" :list-style="listStyle"/>
|
||||
</div>
|
||||
</div><!-- end chat box -->
|
||||
|
||||
@ -129,14 +130,18 @@
|
||||
|
||||
<span class="tool-item" v-if="isLogin">
|
||||
<el-tooltip class="box-item" effect="dark" content="上传附件">
|
||||
<file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertURL"/>
|
||||
<file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertFile"/>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
|
||||
<div class="input-body">
|
||||
<div ref="textHeightRef" class="hide-div">{{prompt}}</div>
|
||||
<div class="input-border">
|
||||
<textarea
|
||||
<div class="input-inner">
|
||||
<div class="file-list" v-if="files.length > 0">
|
||||
<file-list :files="files" @remove-file="removeFile" />
|
||||
</div>
|
||||
<textarea
|
||||
ref="inputRef"
|
||||
class="prompt-input"
|
||||
:rows="row"
|
||||
@ -146,6 +151,8 @@
|
||||
placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
|
||||
autofocus>
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
<span class="send-btn">
|
||||
<el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
|
||||
<el-icon>
|
||||
@ -181,21 +188,21 @@
|
||||
</p>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<ChatSetting :show="showChatSetting" @hide="showChatSetting = false"/>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted, ref} from 'vue'
|
||||
import {nextTick, onMounted, onUnmounted, ref, watch} from 'vue'
|
||||
import ChatPrompt from "@/components/ChatPrompt.vue";
|
||||
import ChatReply from "@/components/ChatReply.vue";
|
||||
import {Delete, Edit, More, Plus, Promotion, Search, Share, VideoPause} from '@element-plus/icons-vue'
|
||||
import 'highlight.js/styles/a11y-dark.css'
|
||||
import {
|
||||
dateFormat,
|
||||
isMobile,
|
||||
processContent,
|
||||
processPrompt,
|
||||
randString,
|
||||
removeArrayItem,
|
||||
UUID
|
||||
@ -210,6 +217,8 @@ import {checkSession} from "@/action/session";
|
||||
import Welcome from "@/components/Welcome.vue";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import FileSelect from "@/components/FileSelect.vue";
|
||||
import FileList from "@/components/FileList.vue";
|
||||
import ChatSetting from "@/components/ChatSetting.vue";
|
||||
|
||||
const title = ref('ChatGPT-智能助手');
|
||||
const models = ref([])
|
||||
@ -236,6 +245,12 @@ const notice = ref("")
|
||||
const noticeKey = ref("SYSTEM_NOTICE")
|
||||
const store = useSharedStore();
|
||||
const row = ref(1)
|
||||
const showChatSetting = ref(false)
|
||||
const listStyle = ref(store.chatListStyle)
|
||||
watch(() => store.chatListStyle, (newValue) => {
|
||||
listStyle.value = newValue
|
||||
});
|
||||
|
||||
|
||||
if (isMobile()) {
|
||||
router.replace("/mobile/chat")
|
||||
@ -417,12 +432,8 @@ const newChat = () => {
|
||||
connect(null, roleId.value)
|
||||
}
|
||||
|
||||
// 切换会话
|
||||
const changeChat = (chat) => {
|
||||
localStorage.setItem("chat_id", chat.chat_id)
|
||||
loadChat(chat)
|
||||
}
|
||||
|
||||
// 切换会话
|
||||
const loadChat = function (chat) {
|
||||
if (!isLogin.value) {
|
||||
store.setShowLoginDialog(true)
|
||||
@ -546,6 +557,7 @@ const socket = ref(null);
|
||||
const activelyClose = ref(false); // 主动关闭
|
||||
const canSend = ref(true);
|
||||
const heartbeatHandle = ref(null)
|
||||
const sessionId = ref("")
|
||||
const connect = function (chat_id, role_id) {
|
||||
let isNewChat = false;
|
||||
if (!chat_id) {
|
||||
@ -560,7 +572,7 @@ const connect = function (chat_id, role_id) {
|
||||
|
||||
const _role = getRoleById(role_id);
|
||||
// 初始化 WebSocket 对象
|
||||
const _sessionId = getSessionId();
|
||||
sessionId.value = getSessionId();
|
||||
let host = process.env.VUE_APP_WS_HOST
|
||||
if (host === '') {
|
||||
if (location.protocol === 'https:') {
|
||||
@ -582,7 +594,7 @@ const connect = function (chat_id, role_id) {
|
||||
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
|
||||
});
|
||||
}
|
||||
const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
|
||||
const _socket = new WebSocket(host + `/api/chat/new?session_id=${sessionId.value}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
|
||||
_socket.addEventListener('open', () => {
|
||||
chatData.value = []; // 初始化聊天数据
|
||||
enableInput()
|
||||
@ -615,10 +627,12 @@ const connect = function (chat_id, role_id) {
|
||||
reader.onload = () => {
|
||||
const data = JSON.parse(String(reader.result));
|
||||
if (data.type === 'start') {
|
||||
const prePrompt = chatData.value[chatData.value.length-1].content
|
||||
chatData.value.push({
|
||||
type: "reply",
|
||||
id: randString(32),
|
||||
icon: _role['icon'],
|
||||
prompt:prePrompt,
|
||||
content: ""
|
||||
});
|
||||
} else if (data.type === 'end') { // 消息接收完毕
|
||||
@ -743,12 +757,18 @@ const sendMessage = function () {
|
||||
if (prompt.value.trim().length === 0 || canSend.value === false) {
|
||||
return false;
|
||||
}
|
||||
// 如果携带了文件,则串上文件地址
|
||||
let content = prompt.value
|
||||
if (files.value.length > 0) {
|
||||
content += files.value.map(file => file.url).join(" ")
|
||||
}
|
||||
// 追加消息
|
||||
chatData.value.push({
|
||||
type: "prompt",
|
||||
id: randString(32),
|
||||
icon: loginUser.value.avatar,
|
||||
content: md.render(processPrompt(prompt.value)),
|
||||
content: content,
|
||||
model: getModelValue(modelID.value),
|
||||
created_at: new Date().getTime() / 1000,
|
||||
});
|
||||
|
||||
@ -758,9 +778,11 @@ const sendMessage = function () {
|
||||
|
||||
showHello.value = false
|
||||
disableInput(false)
|
||||
socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
|
||||
tmpChatTitle.value = prompt.value
|
||||
prompt.value = '';
|
||||
socket.value.send(JSON.stringify({type: "chat", content: content}));
|
||||
tmpChatTitle.value = content
|
||||
prompt.value = ''
|
||||
files.value = []
|
||||
row.value = 1
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -810,9 +832,11 @@ const loadChatHistory = function (chatId) {
|
||||
showHello.value = false
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i].orgContent = data[i].content;
|
||||
data[i].content = md.render(processContent(data[i].content))
|
||||
if (i > 0 && data[i].type === 'reply') {
|
||||
data[i].prompt = data[i - 1].orgContent
|
||||
if (data[i].type === 'reply') {
|
||||
data[i].content = md.render(processContent(data[i].content))
|
||||
if (i > 0) {
|
||||
data[i].prompt = data[i - 1].orgContent
|
||||
}
|
||||
}
|
||||
chatData.value.push(data[i]);
|
||||
}
|
||||
@ -829,7 +853,7 @@ const loadChatHistory = function (chatId) {
|
||||
|
||||
const stopGenerate = function () {
|
||||
showStopGenerate.value = false;
|
||||
httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => {
|
||||
httpGet("/api/chat/stop?session_id=" + sessionId.value).then(() => {
|
||||
enableInput()
|
||||
})
|
||||
}
|
||||
@ -843,7 +867,7 @@ const reGenerate = function (prompt) {
|
||||
type: "prompt",
|
||||
id: randString(32),
|
||||
icon: loginUser.value.avatar,
|
||||
content: md.render(text)
|
||||
content: text
|
||||
});
|
||||
socket.value.send(JSON.stringify({type: "chat", content: prompt}));
|
||||
}
|
||||
@ -893,9 +917,13 @@ const notShow = () => {
|
||||
showNotice.value = false
|
||||
}
|
||||
|
||||
// 插入文件路径
|
||||
const insertURL = (url) => {
|
||||
prompt.value += " " + url + " "
|
||||
const files = ref([])
|
||||
// 插入文件
|
||||
const insertFile = (file) => {
|
||||
files.value.push(file)
|
||||
}
|
||||
const removeFile = (file) => {
|
||||
files.value = removeArrayItem(files.value, file, (v1,v2) => v1.url===v2.url)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -86,43 +86,7 @@
|
||||
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
|
||||
<div class="job-list-box">
|
||||
<h2>任务列表</h2>
|
||||
<div class="running-job-list">
|
||||
<div class="running-job-box" v-if="runningJobs.length > 0">
|
||||
<div class="job-item" v-for="item in runningJobs" :key="item.id">
|
||||
<div v-if="item.progress > 0" class="job-item-inner">
|
||||
<el-image :src="item['img_url']" fit="cover" loading="lazy">
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
|
||||
<div class="progress">
|
||||
<el-progress type="circle" :percentage="item.progress" :width="100"
|
||||
color="#47fff1"/>
|
||||
</div>
|
||||
</div>
|
||||
<el-image fit="cover" v-else>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<i class="iconfont icon-quick-start"></i>
|
||||
<span>任务正在排队中</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
</div>
|
||||
<task-list :list="runningJobs" />
|
||||
|
||||
<h2>创作记录</h2>
|
||||
<div class="finish-job-list">
|
||||
@ -228,6 +192,7 @@ import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
|
||||
import Clipboard from "clipboard";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import TaskList from "@/components/TaskList.vue";
|
||||
|
||||
const listBoxHeight = ref(0)
|
||||
// const paramBoxHeight = ref(0)
|
||||
@ -369,7 +334,7 @@ const fetchRunningJobs = () => {
|
||||
return
|
||||
}
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/dall/jobs?status=0`).then(res => {
|
||||
httpGet(`/api/dall/jobs?finish=false`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
@ -402,7 +367,7 @@ const fetchFinishJobs = () => {
|
||||
loading.value = true
|
||||
page.value = page.value + 1
|
||||
|
||||
httpGet(`/api/dall/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
httpGet(`/api/dall/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
if (res.data.length < pageSize.value) {
|
||||
isOver.value = true
|
||||
}
|
||||
@ -454,7 +419,7 @@ const removeImage = (event, item) => {
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
httpPost("/api/dall/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
||||
httpGet("/api/dall/remove", {id: item.id, user_id: item.user}).then(() => {
|
||||
ElMessage.success("任务删除成功")
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
@ -477,7 +442,7 @@ const publishImage = (event, item, action) => {
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/dall/publish", {id: item.id, action: action}).then(() => {
|
||||
httpGet("/api/dall/publish", {id: item.id, action: action,user_id:item.user_id}).then(() => {
|
||||
ElMessage.success(text + "成功")
|
||||
item.publish = action
|
||||
page.value = 0
|
||||
|
@ -49,15 +49,15 @@
|
||||
<div v-if="!licenseConfig.de_copy">
|
||||
<el-dropdown-item>
|
||||
<i class="iconfont icon-book"></i>
|
||||
<a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
|
||||
<a :href="docsURL" target="_blank">
|
||||
用户手册
|
||||
</a>
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item>
|
||||
<i class="iconfont icon-github"></i>
|
||||
<a href="https://ai.r9it.com/docs/" target="_blank">
|
||||
Geek-AI {{ version }}
|
||||
<a :href="gitURL" target="_blank">
|
||||
GeekAI {{ version }}
|
||||
</a>
|
||||
</el-dropdown-item>
|
||||
</div>
|
||||
@ -142,7 +142,7 @@ import {checkSession} from "@/action/session";
|
||||
import {removeUserToken} from "@/store/session";
|
||||
import LoginDialog from "@/components/LoginDialog.vue";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import ConfigDialog from "@/components/ConfigDialog.vue";
|
||||
import ConfigDialog from "@/components/UserInfoDialog.vue";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
|
||||
const router = useRouter();
|
||||
@ -157,6 +157,8 @@ const version = ref(process.env.VUE_APP_VERSION)
|
||||
const routerViewKey = ref(0)
|
||||
const showConfigDialog = ref(false)
|
||||
const licenseConfig = ref({})
|
||||
const docsURL = ref(process.env.VUE_APP_DOCS_URL)
|
||||
const gitURL = ref(process.env.VUE_APP_GIT_URL)
|
||||
|
||||
const store = useSharedStore();
|
||||
const show = ref(false)
|
||||
@ -215,10 +217,11 @@ const init = () => {
|
||||
const logout = function () {
|
||||
httpGet('/api/user/logout').then(() => {
|
||||
removeUserToken()
|
||||
store.setShowLoginDialog(true)
|
||||
loginUser.value = {}
|
||||
// 刷新组件
|
||||
routerViewKey.value += 1
|
||||
router.push("/login")
|
||||
// store.setShowLoginDialog(true)
|
||||
// loginUser.value = {}
|
||||
// // 刷新组件
|
||||
// routerViewKey.value += 1
|
||||
}).catch(() => {
|
||||
ElMessage.error('注销失败!');
|
||||
})
|
||||
|
@ -163,7 +163,7 @@
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-list-box" @scrollend="handleScrollEnd">
|
||||
<div class="task-list-box">
|
||||
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
|
||||
<div class="extra-params">
|
||||
<el-form>
|
||||
@ -450,43 +450,7 @@
|
||||
|
||||
<div class="job-list-box">
|
||||
<h2>任务列表</h2>
|
||||
<div class="running-job-list">
|
||||
<div class="running-job-box" v-if="runningJobs.length > 0">
|
||||
<div class="job-item" v-for="item in runningJobs">
|
||||
<div v-if="item.progress > 0" class="job-item-inner">
|
||||
<el-image :src="item['img_url']" fit="cover" loading="lazy">
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
|
||||
<div class="progress">
|
||||
<el-progress type="circle" :percentage="item.progress" :width="100"
|
||||
color="#47fff1"/>
|
||||
</div>
|
||||
</div>
|
||||
<el-image fit="cover" v-else>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<i class="iconfont icon-quick-start"></i>
|
||||
<span>任务正在排队中</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
</div>
|
||||
<task-list :list="runningJobs" />
|
||||
|
||||
<h2>创作记录</h2>
|
||||
<div class="finish-job-list">
|
||||
@ -617,6 +581,7 @@ import {useRouter} from "vue-router";
|
||||
import {getSessionId} from "@/store/session";
|
||||
import {copyObj, removeArrayItem} from "@/utils/libs";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import TaskList from "@/components/TaskList.vue";
|
||||
|
||||
const listBoxHeight = ref(0)
|
||||
const paramBoxHeight = ref(0)
|
||||
@ -817,7 +782,7 @@ const fetchRunningJobs = () => {
|
||||
return
|
||||
}
|
||||
|
||||
httpGet(`/api/mj/jobs?status=0`).then(res => {
|
||||
httpGet(`/api/mj/jobs?finish=false`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
@ -855,7 +820,7 @@ const fetchFinishJobs = () => {
|
||||
loading.value = true
|
||||
page.value = page.value + 1
|
||||
// 获取已完成的任务
|
||||
httpGet(`/api/mj/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
httpGet(`/api/mj/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
const jobs = res.data
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
if (jobs[i]['img_url'] !== "") {
|
||||
@ -996,7 +961,7 @@ const removeImage = (item) => {
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
httpPost("/api/mj/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
||||
httpGet("/api/mj/remove", {id: item.id, user_id: item.user_id}).then(() => {
|
||||
ElMessage.success("任务删除成功")
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
@ -1014,7 +979,7 @@ const publishImage = (item, action) => {
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
|
||||
httpGet("/api/mj/publish", {id: item.id, action: action,user_id: item.user_id}).then(() => {
|
||||
ElMessage.success(text + "成功")
|
||||
item.publish = action
|
||||
page.value = 0
|
||||
|
@ -30,7 +30,7 @@
|
||||
</div>
|
||||
|
||||
<div class="param-line" style="padding-top: 10px">
|
||||
<el-form-item label="采样调度器">
|
||||
<el-form-item label="采样调度">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-select v-model="params.scheduler" style="width:176px">
|
||||
@ -296,39 +296,8 @@
|
||||
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
|
||||
<div class="job-list-box">
|
||||
<h2>任务列表</h2>
|
||||
<div class="running-job-list">
|
||||
<div class="running-job-box" v-if="runningJobs.length > 0">
|
||||
<div class="job-item" v-for="item in runningJobs">
|
||||
<div v-if="item.progress > 0" class="job-item-inner">
|
||||
<el-image :src="item['img_url']" fit="cover" loading="lazy">
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot"></div>
|
||||
</template>
|
||||
</el-image>
|
||||
|
||||
<div class="progress">
|
||||
<el-progress type="circle" :percentage="item.progress" :width="100"
|
||||
color="#47fff1"/>
|
||||
</div>
|
||||
</div>
|
||||
<el-image fit="cover" v-else>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<i class="iconfont icon-quick-start"></i>
|
||||
<span>任务正在排队中</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
</div>
|
||||
<task-list :list="runningJobs" />
|
||||
|
||||
<h2>创作记录</h2>
|
||||
<div class="finish-job-list">
|
||||
<div v-if="finishedJobs.length > 0">
|
||||
@ -506,6 +475,7 @@ import {checkSession} from "@/action/session";
|
||||
import {useRouter} from "vue-router";
|
||||
import {getSessionId} from "@/store/session";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import TaskList from "@/components/TaskList.vue";
|
||||
|
||||
const listBoxHeight = ref(0)
|
||||
// const paramBoxHeight = ref(0)
|
||||
@ -661,7 +631,7 @@ const fetchRunningJobs = () => {
|
||||
}
|
||||
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/sd/jobs?status=0`).then(res => {
|
||||
httpGet(`/api/sd/jobs?finish=0`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
@ -694,7 +664,7 @@ const fetchFinishJobs = () => {
|
||||
loading.value = true
|
||||
page.value = page.value + 1
|
||||
|
||||
httpGet(`/api/sd/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
httpGet(`/api/sd/jobs?finish=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
if (res.data.length < pageSize.value) {
|
||||
isOver.value = true
|
||||
}
|
||||
@ -761,7 +731,7 @@ const removeImage = (event, item) => {
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
httpPost("/api/sd/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
||||
httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => {
|
||||
ElMessage.success("任务删除成功")
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
@ -780,7 +750,7 @@ const publishImage = (event, item, action) => {
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
|
||||
httpGet("/api/sd/publish", {id: item.id, action: action, user_id: item.user}).then(() => {
|
||||
ElMessage.success(text + "成功")
|
||||
item.publish = action
|
||||
page.value = 0
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="index-page" :style="{height: winHeight+'px'}">
|
||||
<div :class="bgClass"></div>
|
||||
<div class="index-bg" :style="{backgroundImage: 'url('+bgImgUrl+')'}"></div>
|
||||
<div class="menu-box">
|
||||
<el-menu
|
||||
mode="horizontal"
|
||||
@ -12,17 +12,17 @@
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<span v-if="!licenseConfig.de_copy">
|
||||
<a href="https://ai.r9it.com/docs/install/" target="_blank">
|
||||
<a :href="docsURL" target="_blank">
|
||||
<el-button type="primary" round>
|
||||
<i class="iconfont icon-book"></i>
|
||||
<span>部署文档</span>
|
||||
<span>文档</span>
|
||||
</el-button>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
|
||||
<a :href="gitURL" target="_blank">
|
||||
<el-button type="success" round>
|
||||
<i class="iconfont icon-github"></i>
|
||||
<span>项目源码</span>
|
||||
<span>源码</span>
|
||||
</el-button>
|
||||
</a>
|
||||
</span>
|
||||
@ -65,7 +65,6 @@
|
||||
|
||||
<script setup>
|
||||
|
||||
// import * as THREE from 'three';
|
||||
import {onMounted, ref} from "vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import FooterBar from "@/components/FooterBar.vue";
|
||||
@ -84,17 +83,20 @@ const title = ref("Geek-AI 创作系统")
|
||||
const logo = ref("/images/logo.png")
|
||||
const slogan = ref("我辈之人,先干为敬,陪您先把 AI 用起来")
|
||||
const licenseConfig = ref({})
|
||||
// const size = Math.max(window.innerWidth * 0.5, window.innerHeight * 0.8)
|
||||
const winHeight = window.innerHeight - 150
|
||||
const bgClass = ref('fixed-bg')
|
||||
const bgImgUrl = ref('')
|
||||
const isLogin = ref(false)
|
||||
const docsURL = ref(process.env.VUE_APP_DOCS_URL)
|
||||
const gitURL = ref(process.env.VUE_APP_GIT_URL)
|
||||
|
||||
onMounted(() => {
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
title.value = res.data.title
|
||||
logo.value = res.data.logo
|
||||
if (res.data.rand_bg) {
|
||||
bgClass.value = "rand-bg"
|
||||
if (res.data.index_bg_url) {
|
||||
bgImgUrl.value = res.data.index_bg_url
|
||||
} else {
|
||||
bgImgUrl.value = "/images/index-bg.jpg"
|
||||
}
|
||||
if (res.data.slogan) {
|
||||
slogan.value = res.data.slogan
|
||||
@ -126,24 +128,12 @@ onMounted(() => {
|
||||
align-items baseline
|
||||
padding-top 150px
|
||||
|
||||
.fixed-bg {
|
||||
.index-bg {
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100vw
|
||||
height 100vh
|
||||
background-image url("~@/assets/img/ai-bg.jpg")
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.rand-bg {
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100vw
|
||||
height 100vh
|
||||
background-image url("https://api.dujin.org/bing/1920.php")
|
||||
filter: blur(8px);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
@ -34,7 +34,7 @@
|
||||
<el-button class="login-btn" size="large" type="primary" @click="login">登录</el-button>
|
||||
</el-row>
|
||||
|
||||
<el-row class="opt" :gutter="20">
|
||||
<el-row class="opt" :gutter="24">
|
||||
<el-col :span="8"><el-link type="primary" @click="router.push('/register')">注册</el-link></el-col>
|
||||
<el-col :span="8">
|
||||
<el-link type="info" @click="showResetPass = true">重置密码</el-link>
|
||||
@ -43,6 +43,14 @@
|
||||
<el-link type="info" @click="router.push('/')">首页</el-link>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div v-if="wechatLoginURL !== ''">
|
||||
<el-divider class="divider">其他登录方式</el-divider>
|
||||
|
||||
<div class="clogin">
|
||||
<a class="wechat-login" :href="wechatLoginURL"><i class="iconfont icon-wechat"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -57,7 +65,7 @@
|
||||
|
||||
<script setup>
|
||||
|
||||
import {ref} from "vue";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {Lock, UserFilled} from "@element-plus/icons-vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {useRouter} from "vue-router";
|
||||
@ -75,28 +83,38 @@ const password = ref(process.env.VUE_APP_PASS);
|
||||
const showResetPass = ref(false)
|
||||
const logo = ref("/images/logo.png")
|
||||
const licenseConfig = ref({})
|
||||
const wechatLoginURL = ref('')
|
||||
|
||||
// 获取系统配置
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
logo.value = res.data.logo
|
||||
title.value = res.data.title
|
||||
}).catch(e => {
|
||||
showMessageError("获取系统配置失败:" + e.message)
|
||||
})
|
||||
onMounted(() => {
|
||||
// 获取系统配置
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
logo.value = res.data.logo
|
||||
title.value = res.data.title
|
||||
}).catch(e => {
|
||||
showMessageError("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
||||
httpGet("/api/config/license").then(res => {
|
||||
licenseConfig.value = res.data
|
||||
}).catch(e => {
|
||||
showMessageError("获取 License 配置:" + e.message)
|
||||
})
|
||||
httpGet("/api/config/license").then(res => {
|
||||
licenseConfig.value = res.data
|
||||
}).catch(e => {
|
||||
showMessageError("获取 License 配置:" + e.message)
|
||||
})
|
||||
|
||||
checkSession().then(() => {
|
||||
if (isMobile()) {
|
||||
router.push('/mobile')
|
||||
} else {
|
||||
router.push('/chat')
|
||||
}
|
||||
}).catch(() => {
|
||||
checkSession().then(() => {
|
||||
if (isMobile()) {
|
||||
router.push('/mobile')
|
||||
} else {
|
||||
router.push('/chat')
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
const returnURL = `${location.protocol}//${location.host}/login/callback`
|
||||
httpGet("/api/user/clogin?return_url="+returnURL).then(res => {
|
||||
wechatLoginURL.value = res.data.url
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
})
|
||||
})
|
||||
|
||||
const handleKeyup = (e) => {
|
||||
@ -114,7 +132,7 @@ const login = function () {
|
||||
}
|
||||
|
||||
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
||||
setUserToken(res.data)
|
||||
setUserToken(res.data.token)
|
||||
if (isMobile()) {
|
||||
router.push('/mobile')
|
||||
} else {
|
||||
@ -129,99 +147,5 @@ const login = function () {
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.bg {
|
||||
position fixed
|
||||
left 0
|
||||
right 0
|
||||
top 0
|
||||
bottom 0
|
||||
background-color #313237
|
||||
background-image url("~@/assets/img/login-bg.jpg")
|
||||
background-size cover
|
||||
background-position center
|
||||
background-repeat repeat-y
|
||||
//filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */
|
||||
}
|
||||
|
||||
.main {
|
||||
.contain {
|
||||
position fixed
|
||||
left 50%
|
||||
top 40%
|
||||
width 90%
|
||||
max-width 400px;
|
||||
transform translate(-50%, -50%)
|
||||
padding 20px 10px;
|
||||
color #ffffff
|
||||
border-radius 10px;
|
||||
|
||||
.logo {
|
||||
text-align center
|
||||
|
||||
.el-image {
|
||||
width 120px;
|
||||
cursor pointer
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
width 100%
|
||||
margin-bottom 24px
|
||||
font-size 24px
|
||||
color $white_v1
|
||||
letter-space 2px
|
||||
text-align center
|
||||
padding-top 10px
|
||||
}
|
||||
|
||||
.content {
|
||||
width 100%
|
||||
height: auto
|
||||
border-radius 3px
|
||||
|
||||
.block {
|
||||
margin-bottom 16px
|
||||
|
||||
.el-input__inner {
|
||||
border 1px solid $gray-v6 !important
|
||||
|
||||
.el-icon-user, .el-icon-lock {
|
||||
font-size 20px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-row {
|
||||
padding-top 10px;
|
||||
|
||||
.login-btn {
|
||||
width 100%
|
||||
font-size 16px
|
||||
letter-spacing 2px
|
||||
}
|
||||
}
|
||||
|
||||
.text-line {
|
||||
justify-content center
|
||||
padding-top 10px;
|
||||
font-size 14px;
|
||||
}
|
||||
|
||||
.opt {
|
||||
padding 15px
|
||||
.el-col {
|
||||
text-align center
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
color #ffffff;
|
||||
|
||||
.container {
|
||||
padding 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@import "@/assets/css/login.styl"
|
||||
</style>
|
105
web/src/views/LoginCallback.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="login-callback"
|
||||
v-loading="loading"
|
||||
element-loading-text="正在同步登录信息..."
|
||||
:style="{ height: winHeight + 'px' }">
|
||||
|
||||
<el-dialog
|
||||
v-model="show"
|
||||
:close-on-click-modal="false"
|
||||
:show-close="false"
|
||||
style="width: 360px;"
|
||||
>
|
||||
<el-result
|
||||
icon="success"
|
||||
title="登录成功"
|
||||
style="--el-result-padding:10px"
|
||||
>
|
||||
<template #sub-title>
|
||||
<div class="user-info">
|
||||
<div class="line">您的初始账户信息如下:</div>
|
||||
<div class="line"><span>用户名:</span>{{username}}</div>
|
||||
<div class="line"><span>密码:</span>{{password}}</div>
|
||||
<div class="line">您后期也可以通过此账号和密码登录</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="finishLogin">我知道了</el-button>
|
||||
</template>
|
||||
</el-result>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from "vue"
|
||||
import {useRouter} from "vue-router"
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {setUserToken} from "@/store/session";
|
||||
import {isMobile} from "@/utils/libs";
|
||||
|
||||
const winHeight = ref(window.innerHeight)
|
||||
const loading = ref(true)
|
||||
const router = useRouter()
|
||||
const show = ref(false)
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
|
||||
|
||||
const code = router.currentRoute.value.query.code
|
||||
if (code === "") {
|
||||
ElMessage.error({message: "登录失败:code 参数不能为空",duration: 2000, onClose: () => router.push("/")})
|
||||
} else {
|
||||
// 发送请求获取用户信息
|
||||
httpGet("/api/user/clogin/callback",{login_type: "wx",code: code}).then(res => {
|
||||
setUserToken(res.data.token)
|
||||
if (res.data.username) {
|
||||
username.value = res.data.username
|
||||
password.value = res.data.password
|
||||
show.value = true
|
||||
loading.value = false
|
||||
} else {
|
||||
finishLogin()
|
||||
}
|
||||
}).catch(e => {
|
||||
ElMessageBox.alert(e.message, {
|
||||
confirmButtonText: '重新登录',
|
||||
type:"error",
|
||||
title:"登录失败",
|
||||
callback: () => {
|
||||
router.push("/login")
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
const finishLogin = () => {
|
||||
if (isMobile()) {
|
||||
router.push('/mobile')
|
||||
} else {
|
||||
router.push('/chat')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.login-callback {
|
||||
.user-info {
|
||||
display flex
|
||||
flex-direction column
|
||||
padding 10px
|
||||
border 1px dashed #e1e1e1
|
||||
border-radius 10px
|
||||
.line {
|
||||
text-align left
|
||||
font-size 14px
|
||||
line-height 1.5
|
||||
|
||||
span{
|
||||
font-weight bold
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -333,7 +333,7 @@ const wechatPay = (row) => {
|
||||
}
|
||||
|
||||
const queryOrder = (orderNo) => {
|
||||
httpPost("/api/payment/query", {order_no: orderNo}).then(res => {
|
||||
httpGet("/api/order/query", {order_no: orderNo}).then(res => {
|
||||
if (res.data.status === 1) {
|
||||
text.value = "扫码成功,请在手机上进行支付!"
|
||||
queryOrder(orderNo)
|
||||
|
@ -178,7 +178,7 @@ const tableData = ref([])
|
||||
const sortedTableData = ref([])
|
||||
const role = ref({context: []})
|
||||
const formRef = ref(null)
|
||||
const optTitle = ref({})
|
||||
const optTitle = ref("")
|
||||
const loading = ref(true)
|
||||
|
||||
const rules = reactive({
|
||||
@ -231,7 +231,7 @@ const fetchData = () => {
|
||||
sortedData.forEach((id, index) => {
|
||||
ids.push(parseInt(id))
|
||||
sorts.push(index+1)
|
||||
items.value[index].sort_num = index + 1
|
||||
tableData.value[index].sort_num = index + 1
|
||||
})
|
||||
|
||||
httpPost("/api/admin/role/sort", {ids: ids, sorts: sorts}).catch(e => {
|
||||
|
@ -153,7 +153,7 @@
|
||||
v-model="showChatItemDialog"
|
||||
title="对话详情"
|
||||
>
|
||||
<div class="chat-box common-layout">
|
||||
<div class="chat-box chat-page">
|
||||
<div v-for="item in messages" :key="item.id">
|
||||
<chat-prompt
|
||||
v-if="item.type==='prompt'"
|
||||
|
@ -162,7 +162,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="绑定API-KEY:" prop="apikey">
|
||||
<el-select v-model="item.key_id" placeholder="请选择 API KEY" clearable>
|
||||
<el-select v-model="item.key_id" placeholder="请选择 API KEY" filterable clearable>
|
||||
<el-option v-for="v in apiKeys" :value="v.id" :label="v.name" :key="v.id">
|
||||
{{ v.name }}
|
||||
<el-text type="info" size="small">{{ substr(v.api_url, 50) }}</el-text>
|
||||
@ -229,7 +229,7 @@ const platforms = ref([])
|
||||
|
||||
// 获取 API KEY
|
||||
const apiKeys = ref([])
|
||||
httpGet('/api/admin/apikey/list?status=true&type=chat').then(res => {
|
||||
httpGet('/api/admin/apikey/list?type=chat').then(res => {
|
||||
apiKeys.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取 API KEY 失败:" + e.message)
|
||||
|
@ -33,32 +33,42 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="开放注册" prop="enabled_register">
|
||||
<el-switch v-model="system['enabled_register']"/>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="关闭注册之后只能通过管理后台添加用户"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-form-item label="首页背景图" prop="logo">
|
||||
<div class="tip-input">
|
||||
<el-input v-model="system['index_bg_url']" placeholder="网站首页背景图片">
|
||||
<template #append>
|
||||
<el-upload
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
@click="beforeUpload('index_bg_url')"
|
||||
:http-request="uploadImg"
|
||||
>
|
||||
<el-icon class="uploader-icon">
|
||||
<UploadFilled/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button type="primary" @click="system.index_bg_url = 'https://api.dujin.org/bing/1920.php'">使用动态背景</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="动态背景">
|
||||
<el-switch v-model="system['rand_bg']"/>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="打开之后前端首页将使用随机壁纸作为背景图"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<el-form-item label="开放注册" prop="enabled_register">
|
||||
<div class="tip-input">
|
||||
<el-switch v-model="system['enabled_register']"/>
|
||||
<div class="info">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="关闭注册之后只能通过管理后台添加用户"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="注册方式" prop="register_ways">
|
||||
@ -211,17 +221,21 @@
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="众筹支付">
|
||||
<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>
|
||||
<div class="tip-input">
|
||||
<el-switch v-model="system['enabled_reward']"/>
|
||||
<div class="info">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="如果关闭次功能将不在用户菜单显示众筹二维码"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon>
|
||||
<InfoFilled/>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="system['enabled_reward']">
|
||||
@ -534,7 +548,6 @@ const onUploadImg = (files, callback) => {
|
||||
|
||||
.el-icon {
|
||||
font-size 16px
|
||||
margin-left 10px
|
||||
cursor pointer
|
||||
}
|
||||
|
||||
|
@ -241,7 +241,7 @@ const editChat = (row) => {
|
||||
tmpChatTitle.value = row.title
|
||||
}
|
||||
const saveTitle = () => {
|
||||
httpPost('/api/chat/update', {id: item.value.id, title: tmpChatTitle.value}).then(() => {
|
||||
httpPost('/api/chat/update', {chat_id: item.value.chat_id, title: tmpChatTitle.value}).then(() => {
|
||||
showSuccessToast("操作成功!");
|
||||
item.value.title = tmpChatTitle.value;
|
||||
}).catch(e => {
|
||||
|
@ -1,637 +0,0 @@
|
||||
<template>
|
||||
<div class="mobile-mj">
|
||||
<van-form @submit="generate">
|
||||
<div class="text-line">图片比例</div>
|
||||
<div class="text-line">
|
||||
<van-row :gutter="10">
|
||||
<van-col :span="4" v-for="item in rates" :key="item.value">
|
||||
<div
|
||||
:class="item.value === params.rate ? 'rate active' : 'rate'"
|
||||
@click="changeRate(item)">
|
||||
<div class="icon">
|
||||
<van-image :src="item.img" fit="cover"></van-image>
|
||||
</div>
|
||||
<div class="text">{{ item.text }}</div>
|
||||
</div>
|
||||
</van-col>
|
||||
</van-row>
|
||||
</div>
|
||||
<div class="text-line">模型选择</div>
|
||||
<div class="text-line">
|
||||
<van-row :gutter="10">
|
||||
<van-col :span="8" v-for="item in models" :key="item.value">
|
||||
<div :class="item.value === params.model ? 'model active' : 'model'"
|
||||
@click="changeModel(item)">
|
||||
<div class="icon">
|
||||
<van-image :src="item.img" fit="cover"></van-image>
|
||||
</div>
|
||||
<div class="text">
|
||||
<van-text-ellipsis :content="item.text"/>
|
||||
</div>
|
||||
</div>
|
||||
</van-col>
|
||||
</van-row>
|
||||
</div>
|
||||
<div class="text-line">
|
||||
<van-field label="创意度">
|
||||
<template #input>
|
||||
<van-slider v-model.number="params.chaos" :max="100" :step="1"
|
||||
@update:model-value="showToast('当前值:' + params.chaos)"/>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<van-field label="风格化">
|
||||
<template #input>
|
||||
<van-slider v-model.number="params.stylize" :max="1000" :step="1"
|
||||
@update:model-value="showToast('当前值:' + params.stylize)"/>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<van-field label="原始模式">
|
||||
<template #input>
|
||||
<van-switch v-model="params.raw"/>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<van-tabs v-model:active="activeName" @change="tabChange" animated>
|
||||
<van-tab title="文生图" name="txt2img">
|
||||
<div class="text-line">
|
||||
<van-field v-model="params.prompt"
|
||||
rows="3"
|
||||
autosize
|
||||
type="textarea"
|
||||
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"/>
|
||||
</div>
|
||||
</van-tab>
|
||||
<van-tab title="图生图" name="img2img">
|
||||
<div class="text-line">
|
||||
<van-field v-model="params.prompt"
|
||||
rows="3"
|
||||
autosize
|
||||
type="textarea"
|
||||
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"/>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<van-uploader v-model="imgList" :after-read="uploadImg"/>
|
||||
</div>
|
||||
<div class="text-line">
|
||||
<van-field label="垫图权重">
|
||||
<template #input>
|
||||
<van-slider v-model.number="params.iw" :max="1" :step="0.01"
|
||||
@update:model-value="showToast('当前值:' + params.iw)"/>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
|
||||
<div class="tip-text">提示:只有于 niji6 和 v6 模型支持一致性功能,如果选择其他模型此功能将会生成失败。</div>
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="params.cref"
|
||||
center
|
||||
clearable
|
||||
label="角色一致性"
|
||||
placeholder="请输入图片URL或者上传图片"
|
||||
>
|
||||
<template #button>
|
||||
<van-uploader @click="beforeUpload('cref')" :after-read="uploadImg">
|
||||
<van-button size="mini" type="primary" icon="plus"/>
|
||||
</van-uploader>
|
||||
</template>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
|
||||
<van-cell-group>
|
||||
<van-field
|
||||
v-model="params.sref"
|
||||
center
|
||||
clearable
|
||||
label="风格一致性"
|
||||
placeholder="请输入图片URL或者上传图片"
|
||||
>
|
||||
<template #button>
|
||||
<van-uploader @click="beforeUpload('sref')" :after-read="uploadImg">
|
||||
<van-button size="mini" type="primary" icon="plus"/>
|
||||
</van-uploader>
|
||||
</template>
|
||||
</van-field>
|
||||
</van-cell-group>
|
||||
|
||||
<div class="text-line">
|
||||
<van-field label="一致性权重">
|
||||
<template #input>
|
||||
<van-slider v-model.number="params.cw" :max="100" :step="1"
|
||||
@update:model-value="showToast('当前值:' + params.cw)"/>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
</van-tab>
|
||||
<van-tab title="融图" name="blend">
|
||||
<div class="tip-text">请上传两张以上的图片,最多不超过五张,超过五张图片请使用图生图功能。</div>
|
||||
<div class="text-line">
|
||||
<van-uploader v-model="imgList" :after-read="uploadImg"/>
|
||||
</div>
|
||||
</van-tab>
|
||||
<van-tab title="换脸" name="swapFace">
|
||||
<div class="tip-text">请上传两张有脸部的图片,用左边图片的脸替换右边图片的脸。</div>
|
||||
<div class="text-line">
|
||||
<van-uploader v-model="imgList" :after-read="uploadImg"/>
|
||||
</div>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<van-collapse v-model="activeColspan">
|
||||
<van-collapse-item title="反向提示词" name="neg_prompt">
|
||||
<van-field
|
||||
v-model="params.neg_prompt"
|
||||
rows="3"
|
||||
autosize
|
||||
type="textarea"
|
||||
placeholder="不想出现在图片上的元素(例如:树,建筑)"
|
||||
/>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<el-tag>绘图消耗{{ mjPower }}算力,U/V 操作消耗{{ mjActionPower }}算力,当前算力:{{ power }}</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<van-button round block type="primary" native-type="submit">
|
||||
立即生成
|
||||
</van-button>
|
||||
</div>
|
||||
</van-form>
|
||||
|
||||
<h3>任务列表</h3>
|
||||
<div class="running-job-list">
|
||||
<van-empty v-if="runningJobs.length ===0"
|
||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
||||
image-size="80"
|
||||
description="暂无记录"
|
||||
/>
|
||||
<van-grid :gutter="10" :column-num="3" v-else>
|
||||
<van-grid-item v-for="item in runningJobs">
|
||||
<div v-if="item.progress > 0">
|
||||
<van-image :src="item['img_url']">
|
||||
<template v-slot:error>加载失败</template>
|
||||
</van-image>
|
||||
<div class="progress">
|
||||
<van-circle
|
||||
v-model:current-rate="item.progress"
|
||||
:rate="item.progress"
|
||||
:speed="100"
|
||||
:text="item.progress+'%'"
|
||||
:stroke-width="60"
|
||||
size="90px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="task-in-queue">
|
||||
<span class="icon"><i class="iconfont icon-quick-start"></i></span>
|
||||
<span class="text">排队中</span>
|
||||
</div>
|
||||
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
|
||||
<h3>创作记录</h3>
|
||||
<div class="finish-job-list">
|
||||
<van-empty v-if="finishedJobs.length ===0"
|
||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
||||
image-size="80"
|
||||
description="暂无记录"
|
||||
/>
|
||||
|
||||
<van-list v-else
|
||||
v-model:error="error"
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
error-text="请求失败,点击重新加载"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<van-grid :gutter="10" :column-num="2">
|
||||
<van-grid-item v-for="item in finishedJobs">
|
||||
<div class="job-item">
|
||||
<van-image
|
||||
:src="item['thumb_url']"
|
||||
:class="item['can_opt'] ? '' : 'upscale'"
|
||||
lazy-load
|
||||
@click="imageView(item)"
|
||||
fit="cover">
|
||||
<template v-slot:loading>
|
||||
<van-loading type="spinner" size="20"/>
|
||||
</template>
|
||||
</van-image>
|
||||
|
||||
<div class="opt" v-if="item['can_opt']">
|
||||
|
||||
<van-grid :gutter="3" :column-num="4">
|
||||
<van-grid-item><a @click="upscale(1, item)" class="opt-btn">U1</a></van-grid-item>
|
||||
<van-grid-item><a @click="upscale(2, item)" class="opt-btn">U2</a></van-grid-item>
|
||||
<van-grid-item><a @click="upscale(3, item)" class="opt-btn">U3</a></van-grid-item>
|
||||
<van-grid-item><a @click="upscale(4, item)" class="opt-btn">U4</a></van-grid-item>
|
||||
<van-grid-item><a @click="variation(1, item)" class="opt-btn">V1</a></van-grid-item>
|
||||
<van-grid-item><a @click="variation(2, item)" class="opt-btn">V2</a></van-grid-item>
|
||||
<van-grid-item><a @click="variation(3, item)" class="opt-btn">V3</a></van-grid-item>
|
||||
<van-grid-item><a @click="variation(4, item)" class="opt-btn">V4</a></van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
|
||||
<div class="remove">
|
||||
<el-button type="danger" :icon="Delete" @click="removeImage(item)" circle/>
|
||||
<el-button type="warning" v-if="item.publish" @click="publishImage(item, false)"
|
||||
circle>
|
||||
<i class="iconfont icon-cancel-share"></i>
|
||||
</el-button>
|
||||
<el-button type="success" v-else @click="publishImage(item, true)" circle>
|
||||
<i class="iconfont icon-share-bold"></i>
|
||||
</el-button>
|
||||
<el-button type="primary" @click="showPrompt(item)" circle>
|
||||
<i class="iconfont icon-prompt"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</van-list>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted, ref} from "vue";
|
||||
import {
|
||||
showConfirmDialog,
|
||||
showFailToast,
|
||||
showNotify,
|
||||
showToast,
|
||||
showDialog,
|
||||
showImagePreview,
|
||||
showSuccessToast
|
||||
} from "vant";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import Compressor from "compressorjs";
|
||||
import {getSessionId} from "@/store/session";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {useRouter} from "vue-router";
|
||||
import {Delete} from "@element-plus/icons-vue";
|
||||
|
||||
const activeColspan = ref([""])
|
||||
|
||||
const rates = [
|
||||
{css: "square", value: "1:1", text: "1:1", img: "/images/mj/rate_1_1.png"},
|
||||
{css: "size2-3", value: "2:3", text: "2:3", img: "/images/mj/rate_3_4.png"},
|
||||
{css: "size3-4", value: "3:4", text: "3:4", img: "/images/mj/rate_3_4.png"},
|
||||
{css: "size4-3", value: "4:3", text: "4:3", img: "/images/mj/rate_4_3.png"},
|
||||
{css: "size16-9", value: "16:9", text: "16:9", img: "/images/mj/rate_16_9.png"},
|
||||
{css: "size9-16", value: "9:16", text: "9:16", img: "/images/mj/rate_9_16.png"},
|
||||
]
|
||||
const models = [
|
||||
{text: "MJ-6.0", value: " --v 6", img: "/images/mj/mj-v6.png"},
|
||||
{text: "MJ-5.2", value: " --v 5.2", img: "/images/mj/mj-v5.2.png"},
|
||||
{text: "Niji5", value: " --niji 5", img: "/images/mj/mj-niji.png"},
|
||||
{text: "Niji5 可爱", value: " --niji 5 --style cute", img: "/images/mj/nj1.jpg"},
|
||||
{text: "Niji5 风景", value: " --niji 5 --style scenic", img: "/images/mj/nj2.jpg"},
|
||||
{text: "Niji6", value: " --niji 6", img: "/images/mj/nj3.jpg"},
|
||||
]
|
||||
const imgList = ref([])
|
||||
const params = ref({
|
||||
task_type: "image",
|
||||
rate: rates[0].value,
|
||||
model: models[0].value,
|
||||
chaos: 0,
|
||||
stylize: 0,
|
||||
seed: 0,
|
||||
img_arr: [],
|
||||
raw: false,
|
||||
iw: 0,
|
||||
prompt: "",
|
||||
neg_prompt: "",
|
||||
tile: false,
|
||||
quality: 0,
|
||||
cref: "",
|
||||
sref: "",
|
||||
cw: 0,
|
||||
})
|
||||
const userId = ref(0)
|
||||
const router = useRouter()
|
||||
const runningJobs = ref([])
|
||||
const finishedJobs = ref([])
|
||||
const socket = ref(null)
|
||||
const power = ref(0)
|
||||
const activeName = ref("txt2img")
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then(user => {
|
||||
power.value = user['power']
|
||||
userId.value = user.id
|
||||
|
||||
fetchRunningJobs()
|
||||
fetchFinishJobs(1)
|
||||
connect()
|
||||
|
||||
}).catch(() => {
|
||||
router.push('/login')
|
||||
});
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
socket.value = null
|
||||
})
|
||||
|
||||
const mjPower = ref(1)
|
||||
const mjActionPower = ref(1)
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
mjPower.value = res.data["mj_power"]
|
||||
mjActionPower.value = res.data["mj_action_power"]
|
||||
}).catch(e => {
|
||||
showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
|
||||
})
|
||||
|
||||
const heartbeatHandle = ref(null)
|
||||
const connect = () => {
|
||||
let host = process.env.VUE_APP_WS_HOST
|
||||
if (host === '') {
|
||||
if (location.protocol === 'https:') {
|
||||
host = 'wss://' + location.host;
|
||||
} else {
|
||||
host = 'ws://' + location.host;
|
||||
}
|
||||
}
|
||||
|
||||
// 心跳函数
|
||||
const sendHeartbeat = () => {
|
||||
clearTimeout(heartbeatHandle.value)
|
||||
new Promise((resolve, reject) => {
|
||||
if (socket.value !== null) {
|
||||
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
|
||||
}
|
||||
resolve("success")
|
||||
}).then(() => {
|
||||
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
|
||||
});
|
||||
}
|
||||
|
||||
const _socket = new WebSocket(host + `/api/mj/client?user_id=${userId.value}`);
|
||||
_socket.addEventListener('open', () => {
|
||||
socket.value = _socket;
|
||||
|
||||
// 发送心跳消息
|
||||
sendHeartbeat()
|
||||
});
|
||||
|
||||
_socket.addEventListener('message', event => {
|
||||
if (event.data instanceof Blob) {
|
||||
fetchRunningJobs()
|
||||
fetchFinishJobs(1)
|
||||
}
|
||||
});
|
||||
|
||||
_socket.addEventListener('close', () => {
|
||||
if (socket.value !== null) {
|
||||
connect()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 获取运行中的任务
|
||||
const fetchRunningJobs = (userId) => {
|
||||
httpGet(`/api/mj/jobs?status=0&user_id=${userId}`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
if (jobs[i].progress === -1) {
|
||||
showNotify({
|
||||
message: `任务执行失败:${jobs[i]['err_msg']}`,
|
||||
type: 'danger',
|
||||
})
|
||||
if (jobs[i].type === 'image') {
|
||||
power.value += mjPower.value
|
||||
} else {
|
||||
power.value += mjActionPower.value
|
||||
}
|
||||
continue
|
||||
}
|
||||
_jobs.push(jobs[i])
|
||||
}
|
||||
runningJobs.value = _jobs
|
||||
}).catch(e => {
|
||||
showNotify({type: "danger", message: "获取任务失败:" + e.message})
|
||||
})
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const error = ref(false)
|
||||
const page = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const fetchFinishJobs = (page) => {
|
||||
loading.value = true
|
||||
// 获取已完成的任务
|
||||
httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
const jobs = res.data
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
if (jobs[i].progress === -1) {
|
||||
showNotify({
|
||||
message: `任务ID:${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
|
||||
type: 'danger',
|
||||
})
|
||||
if (jobs[i].type === 'image') {
|
||||
power.value += mjPower.value
|
||||
} else {
|
||||
power.value += mjActionPower.value
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (jobs[i]['use_proxy']) {
|
||||
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?x-oss-process=image/quality,q_60&format=webp'
|
||||
} else {
|
||||
if (jobs[i].type === 'upscale' || jobs[i].type === 'swapFace') {
|
||||
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
|
||||
} else {
|
||||
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/480/q/75'
|
||||
}
|
||||
}
|
||||
|
||||
if (jobs[i].type === 'image' || jobs[i].type === 'variation') {
|
||||
jobs[i]['can_opt'] = true
|
||||
}
|
||||
}
|
||||
if (jobs.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
if (page === 1) {
|
||||
finishedJobs.value = jobs
|
||||
} else {
|
||||
finishedJobs.value = finishedJobs.value.concat(jobs)
|
||||
}
|
||||
nextTick(() => loading.value = false)
|
||||
}).catch(e => {
|
||||
loading.value = false
|
||||
error.value = true
|
||||
showFailToast("获取任务失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const onLoad = () => {
|
||||
page.value += 1
|
||||
fetchFinishJobs(page.value)
|
||||
};
|
||||
|
||||
// 切换图片比例
|
||||
const changeRate = (item) => {
|
||||
params.value.rate = item.value
|
||||
}
|
||||
// 切换模型
|
||||
const changeModel = (item) => {
|
||||
params.value.model = item.value
|
||||
}
|
||||
|
||||
const imgKey = ref("")
|
||||
const beforeUpload = (key) => {
|
||||
imgKey.value = key
|
||||
}
|
||||
|
||||
// 图片上传
|
||||
const uploadImg = (file) => {
|
||||
file.status = "uploading"
|
||||
// 压缩图片并上传
|
||||
new Compressor(file.file, {
|
||||
quality: 0.6,
|
||||
success(result) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', result, result.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then(res => {
|
||||
file.url = res.data.url
|
||||
if (imgKey.value !== "") { // 单张图片上传
|
||||
params.value[imgKey.value] = res.data.url
|
||||
imgKey.value = ''
|
||||
}
|
||||
file.status = "done"
|
||||
}).catch(e => {
|
||||
file.status = 'failed'
|
||||
file.message = '上传失败'
|
||||
showFailToast("图片上传失败:" + e.message)
|
||||
})
|
||||
},
|
||||
error(err) {
|
||||
console.log(err.message);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const send = (url, index, item) => {
|
||||
httpPost(url, {
|
||||
index: index,
|
||||
channel_id: item.channel_id,
|
||||
message_id: item.message_id,
|
||||
message_hash: item.hash,
|
||||
session_id: getSessionId(),
|
||||
prompt: item.prompt,
|
||||
}).then(() => {
|
||||
showSuccessToast("任务推送成功,请耐心等待任务执行...")
|
||||
power.value -= mjActionPower.value
|
||||
}).catch(e => {
|
||||
showFailToast("任务推送失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
// 图片放大任务
|
||||
const upscale = (index, item) => {
|
||||
send('/api/mj/upscale', index, item)
|
||||
}
|
||||
|
||||
// 图片变换任务
|
||||
const variation = (index, item) => {
|
||||
send('/api/mj/variation', index, item)
|
||||
}
|
||||
|
||||
const generate = () => {
|
||||
if (params.value.prompt === '' && params.value.task_type === "image") {
|
||||
return showFailToast("请输入绘画提示词!")
|
||||
}
|
||||
if (params.value.model.indexOf("niji") !== -1 && params.value.raw) {
|
||||
return showFailToast("动漫模型不允许启用原始模式")
|
||||
}
|
||||
params.value.session_id = getSessionId()
|
||||
params.value.img_arr = imgList.value.map(img => img.url)
|
||||
httpPost("/api/mj/image", params.value).then(() => {
|
||||
showToast("绘画任务推送成功,请耐心等待任务执行")
|
||||
power.value -= mjPower.value
|
||||
}).catch(e => {
|
||||
showFailToast("任务推送失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const removeImage = (item) => {
|
||||
showConfirmDialog({
|
||||
title: '标题',
|
||||
message:
|
||||
'此操作将会删除任务和图片,继续操作码?',
|
||||
}).then(() => {
|
||||
httpPost("/api/mj/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
||||
showSuccessToast("任务删除成功")
|
||||
}).catch(e => {
|
||||
showFailToast("任务删除失败:" + e.message)
|
||||
})
|
||||
}).catch(() => {
|
||||
showToast("您取消了操作")
|
||||
});
|
||||
}
|
||||
// 发布图片到作品墙
|
||||
const publishImage = (item, action) => {
|
||||
let text = "图片发布"
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
|
||||
showSuccessToast(text + "成功")
|
||||
item.publish = action
|
||||
}).catch(e => {
|
||||
showFailToast(text + "失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const showPrompt = (item) => {
|
||||
showDialog({
|
||||
title: "绘画提示词",
|
||||
message: item.prompt,
|
||||
}).then(() => {
|
||||
// on close
|
||||
});
|
||||
}
|
||||
|
||||
const imageView = (item) => {
|
||||
showImagePreview([item['img_url']]);
|
||||
}
|
||||
|
||||
// 切换菜单
|
||||
const tabChange = (tab) => {
|
||||
if (tab === "txt2img" || tab === "img2img") {
|
||||
params.value.task_type = "image"
|
||||
} else {
|
||||
params.value.task_type = tab
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@import "@/assets/css/mobile/image-mj.styl"
|
||||
</style>
|
@ -1,523 +0,0 @@
|
||||
<template>
|
||||
<div class="mobile-sd">
|
||||
<van-form @submit="generate">
|
||||
<van-cell-group inset>
|
||||
<div>
|
||||
<van-field
|
||||
v-model="params.sampler"
|
||||
is-link
|
||||
readonly
|
||||
label="采样方法"
|
||||
placeholder="选择采样方法"
|
||||
@click="showSamplerPicker = true"
|
||||
/>
|
||||
<van-popup v-model:show="showSamplerPicker" position="bottom" teleport="#app">
|
||||
<van-picker
|
||||
:columns="samplers"
|
||||
@cancel="showSamplerPicker = false"
|
||||
@confirm="samplerConfirm"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<van-field label="图片尺寸">
|
||||
<template #input>
|
||||
<van-row gutter="20">
|
||||
<van-col span="12">
|
||||
<el-input v-model="params.width" size="small" placeholder="宽"/>
|
||||
</van-col>
|
||||
<van-col span="12">
|
||||
<el-input v-model="params.height" size="small" placeholder="高"/>
|
||||
</van-col>
|
||||
</van-row>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-field v-model.number="params.steps" label="迭代步数"
|
||||
placeholder="">
|
||||
<template #right-icon>
|
||||
<van-icon name="info-o"
|
||||
@click="showInfo('值越大则代表细节越多,同时也意味着出图速度越慢,一般推荐20-30')"/>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field v-model.number="params.cfg_scale" label="引导系数" placeholder="">
|
||||
<template #right-icon>
|
||||
<van-icon name="info-o"
|
||||
@click="showInfo('提示词引导系数,图像在多大程度上服从提示词,较低值会产生更有创意的结果')"/>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field v-model.number="params.seed" label="随机因子" placeholder="">
|
||||
<template #right-icon>
|
||||
<van-icon name="info-o"
|
||||
@click="showInfo('随机数种子,相同的种子会得到相同的结果,设置为 -1 则每次随机生成种子')"/>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<van-field label="高清修复">
|
||||
<template #input>
|
||||
<van-switch v-model="params.hd_fix"/>
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
<div v-if="params.hd_fix">
|
||||
<div>
|
||||
<van-field
|
||||
v-model="params.hd_scale_alg"
|
||||
is-link
|
||||
readonly
|
||||
label="放大算法"
|
||||
placeholder="选择放大算法"
|
||||
@click="showUpscalePicker = true"
|
||||
/>
|
||||
<van-popup v-model:show="showUpscalePicker" position="bottom" teleport="#app">
|
||||
<van-picker
|
||||
:columns="upscaleAlgArr"
|
||||
@cancel="showUpscalePicker = false"
|
||||
@confirm="upscaleConfirm"
|
||||
/>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<van-field v-model.number="params.hd_scale" label="放大倍数"/>
|
||||
<van-field v-model.number="params.hd_steps" label="迭代步数"/>
|
||||
|
||||
<van-field label="重绘幅度">
|
||||
<template #input>
|
||||
<van-slider v-model.number="params.hd_redraw_rate" :max="1" :step="0.1"
|
||||
@update:model-value="showToast('当前值:' + params.hd_redraw_rate)"/>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<van-icon name="info-o"
|
||||
@click="showInfo('决定算法对图像内容的影响程度,较大的值将得到越有创意的图像')"/>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
|
||||
<van-field
|
||||
v-model="params.prompt"
|
||||
rows="3"
|
||||
autosize
|
||||
type="textarea"
|
||||
placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"
|
||||
/>
|
||||
|
||||
<van-collapse v-model="activeColspan">
|
||||
<van-collapse-item title="反向提示词" name="neg_prompt">
|
||||
<van-field
|
||||
v-model="params.neg_prompt"
|
||||
rows="3"
|
||||
autosize
|
||||
type="textarea"
|
||||
placeholder="不想出现在图片上的元素(例如:树,建筑)"
|
||||
/>
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
|
||||
<div class="text-line pt-6">
|
||||
<el-tag>绘图消耗{{ sdPower }}算力,当前算力:{{ power }}</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="text-line">
|
||||
<van-button round block type="primary" native-type="submit">
|
||||
立即生成
|
||||
</van-button>
|
||||
</div>
|
||||
</van-cell-group>
|
||||
</van-form>
|
||||
|
||||
<h3>任务列表</h3>
|
||||
<div class="running-job-list">
|
||||
<van-empty v-if="runningJobs.length ===0"
|
||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
||||
image-size="80"
|
||||
description="暂无记录"
|
||||
/>
|
||||
<van-grid :gutter="10" :column-num="3" v-else>
|
||||
<van-grid-item v-for="item in runningJobs">
|
||||
<div v-if="item.progress > 0">
|
||||
<van-image :src="item['img_url']">
|
||||
<template v-slot:error>加载失败</template>
|
||||
</van-image>
|
||||
<div class="progress">
|
||||
<van-circle
|
||||
v-model:current-rate="item.progress"
|
||||
:rate="item.progress"
|
||||
:speed="100"
|
||||
:text="item.progress+'%'"
|
||||
:stroke-width="60"
|
||||
size="90px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="task-in-queue">
|
||||
<span class="icon"><i class="iconfont icon-quick-start"></i></span>
|
||||
<span class="text">排队中</span>
|
||||
</div>
|
||||
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</div>
|
||||
|
||||
<h3>创作记录</h3>
|
||||
<div class="finish-job-list">
|
||||
<van-empty v-if="finishedJobs.length ===0"
|
||||
image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
|
||||
image-size="80"
|
||||
description="暂无记录"
|
||||
/>
|
||||
|
||||
<van-list v-else
|
||||
v-model:error="error"
|
||||
v-model:loading="loading"
|
||||
:finished="finished"
|
||||
error-text="请求失败,点击重新加载"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<van-grid :gutter="10" :column-num="2">
|
||||
<van-grid-item v-for="item in finishedJobs">
|
||||
<div class="job-item">
|
||||
<van-image
|
||||
:src="item['img_url']"
|
||||
:class="item['can_opt'] ? '' : 'upscale'"
|
||||
lazy-load
|
||||
@click="imageView(item)"
|
||||
fit="cover">
|
||||
<template v-slot:loading>
|
||||
<van-loading type="spinner" size="20"/>
|
||||
</template>
|
||||
</van-image>
|
||||
|
||||
<div class="remove">
|
||||
<el-button type="danger" :icon="Delete" @click="removeImage($event, item)" circle/>
|
||||
<el-button type="warning" v-if="item.publish" @click="publishImage($event,item, false)"
|
||||
circle>
|
||||
<i class="iconfont icon-cancel-share"></i>
|
||||
</el-button>
|
||||
<el-button type="success" v-else @click="publishImage($event, item, true)" circle>
|
||||
<i class="iconfont icon-share-bold"></i>
|
||||
</el-button>
|
||||
<el-button type="primary" @click="showTask(item)" circle>
|
||||
<i class="iconfont icon-prompt"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-grid-item>
|
||||
</van-grid>
|
||||
</van-list>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, onUnmounted, ref} from "vue"
|
||||
import {Delete} from "@element-plus/icons-vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import Clipboard from "clipboard";
|
||||
import {checkSession} from "@/action/session";
|
||||
import {useRouter} from "vue-router";
|
||||
import {getSessionId} from "@/store/session";
|
||||
import {
|
||||
showConfirmDialog, showDialog,
|
||||
showFailToast,
|
||||
showImagePreview,
|
||||
showNotify,
|
||||
showSuccessToast,
|
||||
showToast
|
||||
} from "vant";
|
||||
|
||||
const listBoxHeight = ref(window.innerHeight - 40)
|
||||
const mjBoxHeight = ref(window.innerHeight - 150)
|
||||
const showTaskDialog = ref(false)
|
||||
const item = ref({})
|
||||
const showLoginDialog = ref(false)
|
||||
const isLogin = ref(false)
|
||||
const activeColspan = ref([""])
|
||||
|
||||
window.onresize = () => {
|
||||
listBoxHeight.value = window.innerHeight - 40
|
||||
mjBoxHeight.value = window.innerHeight - 150
|
||||
}
|
||||
const samplers = ref([
|
||||
{text: "Euler a", value: "Euler a"},
|
||||
{text: "DPM++ 2S a Karras", value: "DPM++ 2S a Karras"},
|
||||
{text: "DPM++ 2M Karras", value: "DPM++ 2M Karras"},
|
||||
{text: "DPM++ 2M SDE Karras", value: "DPM++ 2M SDE Karras"},
|
||||
{text: "DPM++ 2M Karras", value: "DPM++ 2M Karras"},
|
||||
{text: "DPM++ 3M SDE Karras", value: "DPM++ 3M SDE Karras"},
|
||||
])
|
||||
const showSamplerPicker = ref(false)
|
||||
|
||||
const upscaleAlgArr = ref([
|
||||
{text: "Latent", value: "Latent"},
|
||||
{text: "ESRGAN_4x", value: "ESRGAN_4x"},
|
||||
{text: "ESRGAN 4x+", value: "ESRGAN 4x+"},
|
||||
{text: "SwinIR_4x", value: "SwinIR_4x"},
|
||||
{text: "LDSR", value: "LDSR"},
|
||||
])
|
||||
const showUpscalePicker = ref(false)
|
||||
|
||||
const params = ref({
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
sampler: samplers.value[0].value,
|
||||
seed: -1,
|
||||
steps: 20,
|
||||
cfg_scale: 7,
|
||||
hd_fix: false,
|
||||
hd_redraw_rate: 0.7,
|
||||
hd_scale: 2,
|
||||
hd_scale_alg: upscaleAlgArr.value[0].value,
|
||||
hd_steps: 0,
|
||||
prompt: "",
|
||||
neg_prompt: "nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet",
|
||||
})
|
||||
|
||||
const runningJobs = ref([])
|
||||
const finishedJobs = ref([])
|
||||
const router = useRouter()
|
||||
// 检查是否有画同款的参数
|
||||
const _params = router.currentRoute.value.params["copyParams"]
|
||||
if (_params) {
|
||||
params.value = JSON.parse(_params)
|
||||
}
|
||||
const power = ref(0)
|
||||
const sdPower = ref(0) // 画一张 SD 图片消耗算力
|
||||
|
||||
const socket = ref(null)
|
||||
const userId = ref(0)
|
||||
const heartbeatHandle = ref(null)
|
||||
const connect = () => {
|
||||
let host = process.env.VUE_APP_WS_HOST
|
||||
if (host === '') {
|
||||
if (location.protocol === 'https:') {
|
||||
host = 'wss://' + location.host;
|
||||
} else {
|
||||
host = 'ws://' + location.host;
|
||||
}
|
||||
}
|
||||
|
||||
// 心跳函数
|
||||
const sendHeartbeat = () => {
|
||||
clearTimeout(heartbeatHandle.value)
|
||||
new Promise((resolve, reject) => {
|
||||
if (socket.value !== null) {
|
||||
socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
|
||||
}
|
||||
resolve("success")
|
||||
}).then(() => {
|
||||
heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
|
||||
});
|
||||
}
|
||||
|
||||
const _socket = new WebSocket(host + `/api/sd/client?user_id=${userId.value}`);
|
||||
_socket.addEventListener('open', () => {
|
||||
socket.value = _socket;
|
||||
|
||||
// 发送心跳消息
|
||||
sendHeartbeat()
|
||||
});
|
||||
|
||||
_socket.addEventListener('message', event => {
|
||||
if (event.data instanceof Blob) {
|
||||
fetchRunningJobs()
|
||||
finished.value = false
|
||||
page.value = 1
|
||||
fetchFinishJobs(page.value)
|
||||
}
|
||||
});
|
||||
|
||||
_socket.addEventListener('close', () => {
|
||||
if (socket.value !== null) {
|
||||
connect()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const clipboard = ref(null)
|
||||
onMounted(() => {
|
||||
initData()
|
||||
clipboard.value = new Clipboard('.copy-prompt-sd');
|
||||
clipboard.value.on('success', () => {
|
||||
showNotify({type: "success", message: "复制成功!"});
|
||||
})
|
||||
|
||||
clipboard.value.on('error', () => {
|
||||
showNotify({type: "danger", message: '复制失败!'});
|
||||
})
|
||||
|
||||
httpGet("/api/config/get?key=system").then(res => {
|
||||
sdPower.value = res.data["sd_power"]
|
||||
}).catch(e => {
|
||||
showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
socket.value = null
|
||||
})
|
||||
|
||||
|
||||
const initData = () => {
|
||||
checkSession().then(user => {
|
||||
power.value = user['power']
|
||||
userId.value = user.id
|
||||
isLogin.value = true
|
||||
fetchRunningJobs()
|
||||
fetchFinishJobs(1)
|
||||
connect()
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
});
|
||||
}
|
||||
|
||||
const fetchRunningJobs = () => {
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/sd/jobs?status=0`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
if (jobs[i].progress === -1) {
|
||||
showNotify({
|
||||
message: `任务ID:${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
|
||||
type: 'danger',
|
||||
})
|
||||
power.value += sdPower.value
|
||||
continue
|
||||
}
|
||||
_jobs.push(jobs[i])
|
||||
}
|
||||
runningJobs.value = _jobs
|
||||
}).catch(e => {
|
||||
showNotify({type: "danger", message: "获取任务失败:" + e.message})
|
||||
})
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
const error = ref(false)
|
||||
const page = ref(0)
|
||||
const pageSize = ref(10)
|
||||
// 获取已完成的任务
|
||||
const fetchFinishJobs = (page) => {
|
||||
loading.value = true
|
||||
httpGet(`/api/sd/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
if (res.data.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
if (page === 1) {
|
||||
finishedJobs.value = res.data
|
||||
} else {
|
||||
finishedJobs.value = finishedJobs.value.concat(res.data)
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
loading.value = false
|
||||
showNotify({type: "danger", message: "获取任务失败:" + e.message})
|
||||
})
|
||||
}
|
||||
|
||||
const onLoad = () => {
|
||||
page.value += 1
|
||||
fetchFinishJobs(page.value)
|
||||
}
|
||||
|
||||
// 创建绘图任务
|
||||
const promptRef = ref(null)
|
||||
const generate = () => {
|
||||
if (params.value.prompt === '') {
|
||||
promptRef.value.focus()
|
||||
return showToast("请输入绘画提示词!")
|
||||
}
|
||||
|
||||
if (!isLogin.value) {
|
||||
showLoginDialog.value = true
|
||||
return
|
||||
}
|
||||
|
||||
if (params.value.seed === '') {
|
||||
params.value.seed = -1
|
||||
}
|
||||
params.value.session_id = getSessionId()
|
||||
httpPost("/api/sd/image", params.value).then(() => {
|
||||
showSuccessToast("绘画任务推送成功,请耐心等待任务执行...")
|
||||
power.value -= sdPower.value
|
||||
}).catch(e => {
|
||||
showFailToast("任务推送失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const showTask = (row) => {
|
||||
item.value = row
|
||||
showTaskDialog.value = true
|
||||
}
|
||||
|
||||
const copyParams = (row) => {
|
||||
params.value = row.params
|
||||
showTaskDialog.value = false
|
||||
}
|
||||
|
||||
const removeImage = (event, item) => {
|
||||
event.stopPropagation()
|
||||
showConfirmDialog({
|
||||
title: '标题',
|
||||
message:
|
||||
'此操作将会删除任务和图片,继续操作码?',
|
||||
}).then(() => {
|
||||
httpPost("/api/sd/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
||||
showSuccessToast("任务删除成功")
|
||||
}).catch(e => {
|
||||
showFailToast("任务删除失败:" + e.message)
|
||||
})
|
||||
}).catch(() => {
|
||||
showToast("您取消了操作")
|
||||
});
|
||||
}
|
||||
|
||||
// 发布图片到作品墙
|
||||
const publishImage = (event, item, action) => {
|
||||
event.stopPropagation()
|
||||
let text = "图片发布"
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
|
||||
showSuccessToast(text + "成功")
|
||||
item.publish = action
|
||||
}).catch(e => {
|
||||
showFailToast(text + "失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const imageView = (item) => {
|
||||
showImagePreview([item['img_url']]);
|
||||
}
|
||||
|
||||
|
||||
const samplerConfirm = (item) => {
|
||||
params.value.sampler = item.selectedOptions[0].text;
|
||||
showSamplerPicker.value = false
|
||||
}
|
||||
|
||||
const upscaleConfirm = (item) => {
|
||||
params.value.hd_scale_alg = item.selectedOptions[0].text;
|
||||
showUpscalePicker.value = false
|
||||
}
|
||||
|
||||
const showInfo = (message) => {
|
||||
showDialog({
|
||||
title: "参数说明",
|
||||
message: message,
|
||||
}).then(() => {
|
||||
// on close
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@import "@/assets/css/mobile/image-sd.styl"
|
||||
</style>
|
@ -1,133 +0,0 @@
|
||||
<template>
|
||||
<div class="img-wall container">
|
||||
<van-nav-bar :title="title"/>
|
||||
|
||||
<div class="content">
|
||||
<van-tabs v-model:active="activeName">
|
||||
<van-tab title="MidJourney" name="mj">
|
||||
<van-list
|
||||
v-model:error="data['mj'].error"
|
||||
v-model:loading="data['mj'].loading"
|
||||
:finished="data['mj'].finished"
|
||||
error-text="请求失败,点击重新加载"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
style="height: 100%;width: 100%;"
|
||||
>
|
||||
<van-cell v-for="item in data['mj'].data" :key="item.id">
|
||||
<van-image :src="item['img_thumb']" @click="showPrompt(item)" fit="cover"/>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-tab>
|
||||
<van-tab title="StableDiffusion" name="sd">
|
||||
<van-list
|
||||
v-model:error="data['sd'].error"
|
||||
v-model:loading="data['sd'].loading"
|
||||
:finished="data['sd'].finished"
|
||||
error-text="请求失败,点击重新加载"
|
||||
finished-text="没有更多了"
|
||||
@load="onLoad"
|
||||
>
|
||||
<van-cell v-for="item in data['sd'].data" :key="item.id">
|
||||
<van-image :src="item['img_thumb']" @click="showPrompt(item)" fit="cover"/>
|
||||
</van-cell>
|
||||
</van-list>
|
||||
</van-tab>
|
||||
<van-tab title="DALLE3" name="dalle3">
|
||||
<van-empty description="功能正在开发中"/>
|
||||
</van-tab>
|
||||
</van-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {showDialog, showFailToast, showSuccessToast} from "vant";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const title = ref('图片创作广场')
|
||||
const activeName = ref("mj")
|
||||
const data = ref({
|
||||
"mj": {
|
||||
loading: false,
|
||||
finished: false,
|
||||
error: false,
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
url: "/api/mj/jobs",
|
||||
data: []
|
||||
},
|
||||
"sd": {
|
||||
loading: false,
|
||||
finished: false,
|
||||
error: false,
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
url: "/api/sd/jobs",
|
||||
data: []
|
||||
},
|
||||
"dalle3": {
|
||||
loading: false,
|
||||
finished: false,
|
||||
error: false,
|
||||
page: 1,
|
||||
pageSize: 12,
|
||||
url: "/api/dalle3/jobs",
|
||||
data: []
|
||||
}
|
||||
})
|
||||
|
||||
const onLoad = () => {
|
||||
const d = data.value[activeName.value]
|
||||
httpGet(`${d.url}?status=1&page=${d.page}&page_size=${d.pageSize}&publish=true`).then(res => {
|
||||
d.loading = false
|
||||
if (res.data.length === 0) {
|
||||
d.finished = true
|
||||
return
|
||||
}
|
||||
|
||||
// 生成缩略图
|
||||
const imageList = res.data
|
||||
for (let i = 0; i < imageList.length; i++) {
|
||||
imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
|
||||
}
|
||||
if (imageList.length < d.pageSize) {
|
||||
d.finished = true
|
||||
}
|
||||
if (d.data.length === 0) {
|
||||
d.data = imageList
|
||||
} else {
|
||||
d.data = d.data.concat(imageList)
|
||||
}
|
||||
d.page += 1
|
||||
}).catch(() => {
|
||||
d.error = true
|
||||
showFailToast("加载图片数据失败")
|
||||
})
|
||||
};
|
||||
|
||||
const showPrompt = (item) => {
|
||||
showDialog({
|
||||
title: "绘画提示词",
|
||||
message: item.prompt,
|
||||
}).then(() => {
|
||||
// on close
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.img-wall {
|
||||
.content {
|
||||
padding-top 60px
|
||||
|
||||
.van-cell__value {
|
||||
.van-image {
|
||||
width 100%
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -302,7 +302,7 @@ const pay = (payWay, item) => {
|
||||
if (isWeChatBrowser() && payWay === 'wechat') {
|
||||
showFailToast("请在系统自带浏览器打开支付页面,或者在 PC 端进行扫码支付")
|
||||
} else {
|
||||
location.href = res.data
|
||||
location.href = res.data.url
|
||||
}
|
||||
}).catch(e => {
|
||||
showFailToast("生成支付订单失败:" + e.message)
|
||||
|
@ -317,7 +317,7 @@ const initData = () => {
|
||||
|
||||
const fetchRunningJobs = () => {
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/dall/jobs?status=0`).then(res => {
|
||||
httpGet(`/api/dall/jobs?finish=0`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
@ -345,7 +345,7 @@ const pageSize = ref(10)
|
||||
// 获取已完成的任务
|
||||
const fetchFinishJobs = (page) => {
|
||||
loading.value = true
|
||||
httpGet(`/api/dall/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
httpGet(`/api/dall/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
if (res.data.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
@ -410,7 +410,7 @@ const removeImage = (event, item) => {
|
||||
message:
|
||||
'此操作将会删除任务和图片,继续操作码?',
|
||||
}).then(() => {
|
||||
httpPost("/api/dall/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
||||
httpGet("/api/dall/remove", {id: item.id, user_id: item.user_id}).then(() => {
|
||||
showSuccessToast("任务删除成功")
|
||||
}).catch(e => {
|
||||
showFailToast("任务删除失败:" + e.message)
|
||||
@ -427,7 +427,7 @@ const publishImage = (event, item, action) => {
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/dall/publish", {id: item.id, action: action}).then(() => {
|
||||
httpGet("/api/dall/publish", {id: item.id, action: action, user_id: item.user_id}).then(() => {
|
||||
showSuccessToast(text + "成功")
|
||||
item.publish = action
|
||||
}).catch(e => {
|
||||
|
@ -421,7 +421,7 @@ const connect = () => {
|
||||
|
||||
// 获取运行中的任务
|
||||
const fetchRunningJobs = (userId) => {
|
||||
httpGet(`/api/mj/jobs?status=0&user_id=${userId}`).then(res => {
|
||||
httpGet(`/api/mj/jobs?finish=0&user_id=${userId}`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
@ -453,7 +453,7 @@ const pageSize = ref(10)
|
||||
const fetchFinishJobs = (page) => {
|
||||
loading.value = true
|
||||
// 获取已完成的任务
|
||||
httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
httpGet(`/api/mj/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
const jobs = res.data
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
if (jobs[i].progress === -1) {
|
||||
@ -600,7 +600,7 @@ const removeImage = (item) => {
|
||||
message:
|
||||
'此操作将会删除任务和图片,继续操作码?',
|
||||
}).then(() => {
|
||||
httpPost("/api/mj/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
||||
httpGet("/api/mj/remove", {id: item.id, user_id: item.user_id}).then(() => {
|
||||
showSuccessToast("任务删除成功")
|
||||
}).catch(e => {
|
||||
showFailToast("任务删除失败:" + e.message)
|
||||
@ -615,7 +615,7 @@ const publishImage = (item, action) => {
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
|
||||
httpGet("/api/mj/publish", {id: item.id, action: action,user_id: item.user_id}).then(() => {
|
||||
showSuccessToast(text + "成功")
|
||||
item.publish = action
|
||||
}).catch(e => {
|
||||
|
@ -381,7 +381,7 @@ const initData = () => {
|
||||
|
||||
const fetchRunningJobs = () => {
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/sd/jobs?status=0`).then(res => {
|
||||
httpGet(`/api/sd/jobs?finish=0`).then(res => {
|
||||
const jobs = res.data
|
||||
const _jobs = []
|
||||
for (let i = 0; i < jobs.length; i++) {
|
||||
@ -409,7 +409,7 @@ const pageSize = ref(10)
|
||||
// 获取已完成的任务
|
||||
const fetchFinishJobs = (page) => {
|
||||
loading.value = true
|
||||
httpGet(`/api/sd/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
httpGet(`/api/sd/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
|
||||
if (res.data.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
@ -474,7 +474,7 @@ const removeImage = (event, item) => {
|
||||
message:
|
||||
'此操作将会删除任务和图片,继续操作码?',
|
||||
}).then(() => {
|
||||
httpPost("/api/sd/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
|
||||
httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => {
|
||||
showSuccessToast("任务删除成功")
|
||||
}).catch(e => {
|
||||
showFailToast("任务删除失败:" + e.message)
|
||||
@ -491,7 +491,7 @@ const publishImage = (event, item, action) => {
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
}
|
||||
httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
|
||||
httpGet("/api/sd/publish", {id: item.id, action: action, user_id: item.user}).then(() => {
|
||||
showSuccessToast(text + "成功")
|
||||
item.publish = action
|
||||
}).catch(e => {
|
||||
|