merge v4.1.0 and fixed conflicts

This commit is contained in:
RockYang 2024-10-08 17:54:08 +08:00
commit b2f57aa483
90 changed files with 3185 additions and 3137 deletions

View File

@ -1,4 +1,15 @@
# 更新日志
## v4.1.0
* bug修复修复移动端修改聊天标题不生效的问题
* Bug修复修复用户注册不显示用户名的问题
* Bug修复修复管理后台拖动排序不生效的问题
* 功能优化:允许用户设置自定义首页背景图片
* 功能新增:**支持AI解读 PDF, Word, Excel等文件**
* 功能优化:优化聊天界面的用户上传文件的列表样式
* 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换
* 功能新增:支持微信扫码登录,未注册用户微信扫码后会自动注册并登录。移动使用微信浏览器打开可以实现无感登录。
## v4.0.9
* 环境升级:升级 Golang 到 go1.22.4
* 功能增加:接入微信商户号支付渠道

View File

@ -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 = "" # 支付成功同步回调地址

View File

@ -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/") ||

View File

@ -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 模型
}

View File

@ -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"` // 前端首页背景图片
}

View File

@ -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"` // 启用高清修复

View File

@ -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"
)

View File

@ -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
)

View File

@ -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=

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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助手由于未知原因已经停止输出内容。")

View File

@ -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 {

View File

@ -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, "更新数据库失败")

View File

@ -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 {

View File

@ -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, "更新数据库失败")

View File

@ -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})
}

View File

@ -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})
}
// 异步通知回调公共逻辑

View File

@ -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, "更新数据库失败")

View File

@ -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)
}
}

View File

@ -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

View 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)

View File

@ -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() {

View File

@ -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)
}

View File

@ -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 会员
}

View File

@ -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"` // 第三方登录平台
}

View File

@ -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
View 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
}

View File

@ -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"})
}
}

View File

@ -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

View File

@ -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
}

View File

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

View File

@ -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支持MidJourneyGPTClaudeGoogle Gemmi以及国内各个厂家的大模型现在有超级优惠价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://ai.r9it.com/docs/install/errors-handle.html#%E8%B0%83%E7%94%A8%E4%B8%AD%E8%BD%AC-api-%E6%8A%A5%E9%94%99%E6%97%A0%E5%8F%AF%E7%94%A8%E6%B8%A0%E9%81%93)。GPT-3.5GPT-4DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程 \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n本项目源码地址\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}');
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_dall_jobs`
--
DROP TABLE IF EXISTS `chatgpt_dall_jobs`;
CREATE TABLE `chatgpt_dall_jobs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`prompt` varchar(2000) NOT NULL COMMENT '提示词',
`img_url` varchar(255) NOT NULL COMMENT '图片地址',
`org_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原图地址',
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
`power` smallint NOT NULL COMMENT '消耗算力',
`progress` smallint NOT NULL COMMENT '任务进度',
`err_msg` varchar(255) NOT NULL COMMENT '错误信息',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='DALLE 绘图任务表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_files`
--
DROP TABLE IF EXISTS `chatgpt_files`;
CREATE TABLE `chatgpt_files` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`name` varchar(100) NOT NULL COMMENT '文件名',
`obj_key` varchar(100) DEFAULT NULL COMMENT '文件标识',
`url` varchar(255) NOT NULL COMMENT '文件地址',
`ext` varchar(10) NOT NULL COMMENT '文件后缀',
`size` bigint NOT NULL DEFAULT '0' COMMENT '文件大小',
`created_at` datetime NOT NULL COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户文件表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_functions`
--
DROP TABLE IF EXISTS `chatgpt_functions`;
CREATE TABLE `chatgpt_functions` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '函数名称',
`label` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '函数标签',
`description` varchar(255) DEFAULT NULL COMMENT '函数描述',
`parameters` text COMMENT '函数参数JSON',
`token` varchar(255) DEFAULT NULL COMMENT 'API授权token',
`action` varchar(255) DEFAULT NULL COMMENT '函数处理 API',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='函数插件表';
--
-- 转存表中的数据 `chatgpt_functions`
--
INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `token`, `action`, `enabled`) VALUES
(1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 0),
(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 0),
(3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 0);
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_invite_codes`
--
DROP TABLE IF EXISTS `chatgpt_invite_codes`;
CREATE TABLE `chatgpt_invite_codes` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`code` char(8) NOT NULL COMMENT '邀请码',
`hits` int NOT NULL COMMENT '点击次数',
`reg_num` smallint NOT NULL COMMENT '注册数量',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户邀请码';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_invite_logs`
--
DROP TABLE IF EXISTS `chatgpt_invite_logs`;
CREATE TABLE `chatgpt_invite_logs` (
`id` int NOT NULL,
`inviter_id` int NOT NULL COMMENT '邀请人ID',
`user_id` int NOT NULL COMMENT '注册用户ID',
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`invite_code` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邀请码',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_menus`
--
DROP TABLE IF EXISTS `chatgpt_menus`;
CREATE TABLE `chatgpt_menus` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '菜单名称',
`icon` varchar(150) NOT NULL COMMENT '菜单图标',
`url` varchar(100) NOT NULL COMMENT '地址',
`sort_num` smallint NOT NULL COMMENT '排序',
`enabled` tinyint(1) NOT NULL COMMENT '是否启用'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='前端菜单表';
--
-- 转存表中的数据 `chatgpt_menus`
--
INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES
(1, '对话聊天', '/images/menu/chat.png', '/chat', 1, 1),
(5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 2, 1),
(6, 'SD 绘画', '/images/menu/sd.png', '/sd', 3, 1),
(7, '算力日志', '/images/menu/log.png', '/powerLog', 8, 1),
(8, '应用中心', '/images/menu/app.png', '/apps', 7, 1),
(9, '画廊', '/images/menu/img-wall.png', '/images-wall', 5, 1),
(10, '会员计划', '/images/menu/member.png', '/member', 9, 1),
(11, '分享计划', '/images/menu/share.png', '/invite', 10, 1),
(12, '思维导图', '/images/menu/xmind.png', '/xmind', 6, 1),
(13, 'DALLE', '/images/menu/dalle.png', '/dalle', 4, 1),
(14, '项目文档', '/images/menu/docs.png', 'https://ai.r9it.com/docs/', 11, 1),
(16, '极客论坛', '/images/menu/bbs.png', 'https://bbs.geekai.me/', 13, 1);
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_mj_jobs`
--
DROP TABLE IF EXISTS `chatgpt_mj_jobs`;
CREATE TABLE `chatgpt_mj_jobs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户 ID',
`task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID',
`type` varchar(20) DEFAULT 'image' COMMENT '任务类别',
`message_id` char(40) NOT NULL COMMENT '消息 ID',
`channel_id` char(40) DEFAULT NULL COMMENT '频道ID',
`reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID',
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
`img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL',
`org_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原始图片地址',
`hash` varchar(100) DEFAULT NULL COMMENT 'message hash',
`progress` smallint DEFAULT '0' COMMENT '任务进度',
`use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代',
`publish` tinyint(1) NOT NULL COMMENT '是否发布',
`err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
`power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
`created_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_orders`
--
DROP TABLE IF EXISTS `chatgpt_orders`;
CREATE TABLE `chatgpt_orders` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`product_id` int NOT NULL COMMENT '产品ID',
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户明',
`order_no` varchar(30) NOT NULL COMMENT '订单ID',
`trade_no` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付平台交易流水号',
`subject` varchar(100) NOT NULL COMMENT '订单产品',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态0待支付1已扫码2支付成功',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
`pay_time` int DEFAULT NULL COMMENT '支付时间',
`pay_way` varchar(20) NOT NULL COMMENT '支付方式',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='充值订单表';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_power_logs`
--
DROP TABLE IF EXISTS `chatgpt_power_logs`;
CREATE TABLE `chatgpt_power_logs` (
`id` int NOT NULL,
`user_id` int NOT NULL COMMENT '用户ID',
`username` varchar(30) NOT NULL COMMENT '用户名',
`type` tinyint(1) NOT NULL COMMENT '类型1充值2消费3退费',
`amount` smallint NOT NULL COMMENT '算力数值',
`balance` int NOT NULL COMMENT '余额',
`model` varchar(30) NOT NULL COMMENT '模型',
`remark` varchar(255) NOT NULL COMMENT '备注',
`mark` tinyint(1) NOT NULL COMMENT '资金类型0支出1收入',
`created_at` datetime NOT NULL COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户算力消费日志';
-- --------------------------------------------------------
--
-- 表的结构 `chatgpt_products`
--
DROP TABLE IF EXISTS `chatgpt_products`;
CREATE TABLE `chatgpt_products` (
`id` int NOT NULL,
`name` varchar(30) NOT NULL COMMENT '名称',
`price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
`discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额',
`days` smallint NOT NULL DEFAULT '0' COMMENT '延长天数',
`power` int NOT NULL DEFAULT '0' COMMENT '增加算力值',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启动',
`sales` int NOT NULL DEFAULT '0' COMMENT '销量',
`sort_num` tinyint NOT NULL DEFAULT '0' COMMENT '排序',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`app_url` varchar(255) DEFAULT NULL COMMENT 'App跳转地址',
`url` varchar(255) DEFAULT NULL COMMENT '跳转地址'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
--
-- 转存表中的数据 `chatgpt_products`
--
INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `power`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`, `app_url`, `url`) VALUES
(1, '会员1个月', 1999.90, 1999.00, 30, 0, 1, 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 */;

View 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支付成功';

View File

@ -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"

View File

@ -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:

View File

@ -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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Before

Width:  |  Height:  |  Size: 388 KiB

After

Width:  |  Height:  |  Size: 388 KiB

View File

@ -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
}
}
}

View File

@ -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;
}

View File

@ -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

View 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;
}
}
}

View 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;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

View File

@ -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, '&lt;/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>

View File

@ -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>

View 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>

View 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>

View File

@ -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;

View File

@ -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
}
}
}

View File

@ -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')

View 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>

View File

@ -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

View File

@ -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',

View File

@ -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);
}
}
});

View File

@ -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];
}

View File

@ -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)
})

View File

@ -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, "&amp;")
return prompt.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const linkRegex = /(https?:\/\/\S+)/g;
const links = prompt.match(linkRegex);
if (links) {
for (let link of links) {
if (isImage(link)) {
const index = prompt.indexOf(link)
if (prompt.substring(index - 1, 2) !== "]") {
prompt = prompt.replace(link, "\n![](" + link + ")\n")
}
}
}
}
return prompt
}
// 判断是否为微信浏览器
@ -258,3 +213,4 @@ export function showLoginDialog(router) {
// on cancel
});
}

View File

@ -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
}
}
}

View File

@ -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>

View File

@ -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

View File

@ -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('注销失败!');
})

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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>

View 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>

View File

@ -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)

View File

@ -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 => {

View File

@ -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'"

View File

@ -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)

View File

@ -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
}

View File

@ -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 => {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)

View File

@ -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 => {

View File

@ -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 => {

View File

@ -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 => {