merge v4.1.0 and fixed conflicts
							
								
								
									
										11
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						@@ -1,4 +1,15 @@
 | 
			
		||||
# 更新日志
 | 
			
		||||
## v4.1.0
 | 
			
		||||
* bug修复:修复移动端修改聊天标题不生效的问题
 | 
			
		||||
* Bug修复:修复用户注册不显示用户名的问题
 | 
			
		||||
* Bug修复:修复管理后台拖动排序不生效的问题
 | 
			
		||||
* 功能优化:允许用户设置自定义首页背景图片
 | 
			
		||||
* 功能新增:**支持AI解读 PDF, Word, Excel等文件**
 | 
			
		||||
* 功能优化:优化聊天界面的用户上传文件的列表样式
 | 
			
		||||
* 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换
 | 
			
		||||
* 功能新增:支持微信扫码登录,未注册用户微信扫码后会自动注册并登录。移动使用微信浏览器打开可以实现无感登录。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## v4.0.9
 | 
			
		||||
* 环境升级:升级 Golang 到 go1.22.4
 | 
			
		||||
* 功能增加:接入微信商户号支付渠道
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ StaticDir = "./static" # 静态资源的目录
 | 
			
		||||
StaticUrl = "/static" # 静态资源访问 URL
 | 
			
		||||
AesEncryptKey = ""
 | 
			
		||||
WeChatBot = false
 | 
			
		||||
TikaHost = "http://tika:9998"
 | 
			
		||||
 | 
			
		||||
[Session]
 | 
			
		||||
  SecretKey = "azyehq3ivunjhbntz78isj00i4hz2mt9xtddysfucxakadq4qbfrt0b7q3lnvg80" # 注意:这个是 JWT Token 授权密钥,生产环境请务必更换
 | 
			
		||||
@@ -122,4 +123,16 @@ WeChatBot = false
 | 
			
		||||
  AppId = "" # 商户 ID
 | 
			
		||||
  PrivateKey = "" # 秘钥
 | 
			
		||||
  ApiURL = "https://payjs.cn"
 | 
			
		||||
  NotifyURL = "https://ai.r9it.com/api/payment/payjs/notify" # 异步回调地址,域名改成你自己的
 | 
			
		||||
  NotifyURL = "https://ai.r9it.com/api/payment/payjs/notify" # 异步回调地址,域名改成你自己的
 | 
			
		||||
 | 
			
		||||
# 微信商户支付
 | 
			
		||||
[WechatPayConfig]
 | 
			
		||||
  Enabled = false
 | 
			
		||||
  AppId = "" # 商户应用ID
 | 
			
		||||
  MchId = "" # 商户号
 | 
			
		||||
  SerialNo = "" # API 证书序列号
 | 
			
		||||
  PrivateKey = "certs/alipay/privateKey.txt" # API 证书私钥文件路径,跟支付宝一样,把私钥文件拷贝到对应的路径,证书路径要映射到容器内
 | 
			
		||||
  ApiV3Key = "" # APIV3 私钥,这个是你自己在微信支付平台设置的
 | 
			
		||||
  NotifyURL = "https://ai.r9it.com/api/payment/wechat/notify" # 支付成功异步回调地址,域名改成自己的
 | 
			
		||||
  ReturnURL = "" # 支付成功同步回调地址
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,31 +32,19 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AppServer struct {
 | 
			
		||||
	Debug        bool
 | 
			
		||||
	Config       *types.AppConfig
 | 
			
		||||
	Engine       *gin.Engine
 | 
			
		||||
	ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message
 | 
			
		||||
 | 
			
		||||
	Debug     bool
 | 
			
		||||
	Config    *types.AppConfig
 | 
			
		||||
	Engine    *gin.Engine
 | 
			
		||||
	SysConfig *types.SystemConfig // system config cache
 | 
			
		||||
 | 
			
		||||
	// 保存 Websocket 会话 UserId, 每个 UserId 只能连接一次
 | 
			
		||||
	// 防止第三方直接连接 socket 调用 OpenAI API
 | 
			
		||||
	ChatSession   *types.LMap[string, *types.ChatSession] //map[sessionId]UserId
 | 
			
		||||
	ChatClients   *types.LMap[string, *types.WsClient]    // map[sessionId]Websocket 连接集合
 | 
			
		||||
	ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewServer(appConfig *types.AppConfig) *AppServer {
 | 
			
		||||
	gin.SetMode(gin.ReleaseMode)
 | 
			
		||||
	gin.DefaultWriter = io.Discard
 | 
			
		||||
	return &AppServer{
 | 
			
		||||
		Debug:         false,
 | 
			
		||||
		Config:        appConfig,
 | 
			
		||||
		Engine:        gin.Default(),
 | 
			
		||||
		ChatContexts:  types.NewLMap[string, []types.Message](),
 | 
			
		||||
		ChatSession:   types.NewLMap[string, *types.ChatSession](),
 | 
			
		||||
		ChatClients:   types.NewLMap[string, *types.WsClient](),
 | 
			
		||||
		ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
 | 
			
		||||
		Debug:  false,
 | 
			
		||||
		Config: appConfig,
 | 
			
		||||
		Engine: gin.Default(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -237,6 +225,7 @@ func needLogin(c *gin.Context) bool {
 | 
			
		||||
		c.Request.URL.Path == "/api/payment/doPay" ||
 | 
			
		||||
		c.Request.URL.Path == "/api/payment/payWays" ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/user/clogin") ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/config/") ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
 | 
			
		||||
		strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,9 +53,8 @@ type Delta struct {
 | 
			
		||||
// ChatSession 聊天会话对象
 | 
			
		||||
type ChatSession struct {
 | 
			
		||||
	SessionId string    `json:"session_id"`
 | 
			
		||||
	UserId    uint      `json:"user_id"`
 | 
			
		||||
	ClientIP  string    `json:"client_ip"` // 客户端 IP
 | 
			
		||||
	Username  string    `json:"username"`  // 当前登录的 username
 | 
			
		||||
	UserId    uint      `json:"user_id"`   // 当前登录的 user ID
 | 
			
		||||
	ChatId    string    `json:"chat_id"`   // 客户端聊天会话 ID, 多会话模式专用字段
 | 
			
		||||
	Model     ChatModel `json:"model"`     // GPT 模型
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,7 @@ type AppConfig struct {
 | 
			
		||||
	SmtpConfig      SmtpConfig      // 邮件发送配置
 | 
			
		||||
	JPayConfig      JPayConfig      // payjs 支付配置
 | 
			
		||||
	WechatPayConfig WechatPayConfig // 微信支付渠道配置
 | 
			
		||||
	TikaHost        string          // TiKa 服务器地址
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SmtpConfig struct {
 | 
			
		||||
@@ -226,5 +227,5 @@ type SystemConfig struct {
 | 
			
		||||
 | 
			
		||||
	SdNegPrompt string `json:"sd_neg_prompt"` // SD 默认反向提示词
 | 
			
		||||
 | 
			
		||||
	RandBg bool `json:"rand_bg"` // 前端首页是否启用随机背景
 | 
			
		||||
	IndexBgURL string `json:"index_bg_url"` // 前端首页背景图片
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -53,10 +53,10 @@ type SdTaskParams struct {
 | 
			
		||||
	NegPrompt    string  `json:"neg_prompt"` // 反向提示词
 | 
			
		||||
	Steps        int     `json:"steps"`      // 迭代步数,默认20
 | 
			
		||||
	Sampler      string  `json:"sampler"`    // 采样器
 | 
			
		||||
	Scheduler    string  `json:"scheduler"`
 | 
			
		||||
	FaceFix      bool    `json:"face_fix"`  // 面部修复
 | 
			
		||||
	CfgScale     float32 `json:"cfg_scale"` //引导系数,默认 7
 | 
			
		||||
	Seed         int64   `json:"seed"`      // 随机数种子
 | 
			
		||||
	Scheduler    string  `json:"scheduler"`  // 采样调度
 | 
			
		||||
	FaceFix      bool    `json:"face_fix"`   // 面部修复
 | 
			
		||||
	CfgScale     float32 `json:"cfg_scale"`  //引导系数,默认 7
 | 
			
		||||
	Seed         int64   `json:"seed"`       // 随机数种子
 | 
			
		||||
	Height       int     `json:"height"`
 | 
			
		||||
	Width        int     `json:"width"`
 | 
			
		||||
	HdFix        bool    `json:"hd_fix"`         // 启用高清修复
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,7 @@ type WsMessage struct {
 | 
			
		||||
	Type    WsMsgType   `json:"type"` // 消息类别,start, end, img
 | 
			
		||||
	Content interface{} `json:"content"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type WsMsgType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -36,11 +37,9 @@ type BizCode int
 | 
			
		||||
const (
 | 
			
		||||
	Success       = BizCode(0)
 | 
			
		||||
	Failed        = BizCode(1)
 | 
			
		||||
	NotAuthorized = BizCode(400) // 未授权
 | 
			
		||||
	NotPermission = BizCode(403) // 没有权限
 | 
			
		||||
	NotAuthorized = BizCode(401) // 未授权
 | 
			
		||||
 | 
			
		||||
	OkMsg       = "Success"
 | 
			
		||||
	ErrorMsg    = "系统开小差了"
 | 
			
		||||
	InvalidArgs = "非法参数或参数解析失败"
 | 
			
		||||
	NoData      = "No Data"
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								api/go.mod
									
									
									
									
									
								
							
							
						
						@@ -28,14 +28,17 @@ require github.com/xxl-job/xxl-job-executor-go v1.2.0
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/go-pay/gopay v1.5.101
 | 
			
		||||
	github.com/mojocn/base64Captcha v1.3.1
 | 
			
		||||
	github.com/google/go-tika v0.3.1
 | 
			
		||||
	github.com/microcosm-cc/bluemonday v1.0.26
 | 
			
		||||
	github.com/mojocn/base64Captcha v1.3.6
 | 
			
		||||
	github.com/shirou/gopsutil v3.21.11+incompatible
 | 
			
		||||
	github.com/shopspring/decimal v1.3.1
 | 
			
		||||
	github.com/syndtr/goleveldb v1.0.0
 | 
			
		||||
	golang.org/x/image v0.0.0-20211028202545-6944b10bf410
 | 
			
		||||
	golang.org/x/image v0.15.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/aymerick/douceur v0.2.0 // indirect
 | 
			
		||||
	github.com/go-ole/go-ole v1.2.6 // indirect
 | 
			
		||||
	github.com/go-pay/crypto v0.0.1 // indirect
 | 
			
		||||
	github.com/go-pay/errgroup v0.0.2 // indirect
 | 
			
		||||
@@ -44,8 +47,9 @@ require (
 | 
			
		||||
	github.com/go-pay/xtime v0.0.2 // indirect
 | 
			
		||||
	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 | 
			
		||||
	github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
 | 
			
		||||
	github.com/tklauser/go-sysconf v0.3.14 // indirect
 | 
			
		||||
	github.com/tklauser/numcpus v0.8.0 // indirect
 | 
			
		||||
	github.com/gorilla/css v1.0.0 // indirect
 | 
			
		||||
	github.com/tklauser/go-sysconf v0.3.13 // indirect
 | 
			
		||||
	github.com/tklauser/numcpus v0.7.0 // indirect
 | 
			
		||||
	github.com/yusufpapurcu/wmi v1.2.4 // indirect
 | 
			
		||||
	go.uber.org/mock v0.4.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								api/go.sum
									
									
									
									
									
								
							
							
						
						@@ -6,6 +6,8 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiw
 | 
			
		||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
 | 
			
		||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
 | 
			
		||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 | 
			
		||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 | 
			
		||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 | 
			
		||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
 | 
			
		||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 | 
			
		||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
 | 
			
		||||
@@ -91,11 +93,15 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pO
 | 
			
		||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 | 
			
		||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
			
		||||
github.com/google/go-tika v0.3.1 h1:l+jr10hDhZjcgxFRfcQChRLo1bPXQeLFluMyvDhXTTA=
 | 
			
		||||
github.com/google/go-tika v0.3.1/go.mod h1:DJh5N8qxXIl85QkqmXknd+PeeRkUOTbvwyYf7ieDz6c=
 | 
			
		||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 | 
			
		||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
 | 
			
		||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
 | 
			
		||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 | 
			
		||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 | 
			
		||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 | 
			
		||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 | 
			
		||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
			
		||||
@@ -137,6 +143,8 @@ github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230415042440-a5e3d8259
 | 
			
		||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230415042440-a5e3d8259ae0/go.mod h1:C5LA5UO2ZXJrLaPLYtE1wUJMiyd/nwWaCO5cw/2pSHs=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
			
		||||
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
 | 
			
		||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
 | 
			
		||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
 | 
			
		||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
 | 
			
		||||
github.com/minio/minio-go/v7 v7.0.62 h1:qNYsFZHEzl+NfH8UxW4jpmlKav1qUAgfY30YNRneVhc=
 | 
			
		||||
@@ -149,8 +157,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 | 
			
		||||
github.com/mojocn/base64Captcha v1.3.1 h1:2Wbkt8Oc8qjmNJ5GyOfSo4tgVQPsbKMftqASnq8GlT0=
 | 
			
		||||
github.com/mojocn/base64Captcha v1.3.1/go.mod h1:wAQCKEc5bDujxKRmbT6/vTnTt5CjStQ8bRfPWUuz/iY=
 | 
			
		||||
github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw=
 | 
			
		||||
github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E=
 | 
			
		||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
 | 
			
		||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 | 
			
		||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 | 
			
		||||
@@ -212,8 +220,10 @@ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gt
 | 
			
		||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 | 
			
		||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
 | 
			
		||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 | 
			
		||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
 | 
			
		||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
 | 
			
		||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
 | 
			
		||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
 | 
			
		||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
 | 
			
		||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
 | 
			
		||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 | 
			
		||||
@@ -251,14 +261,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
 | 
			
		||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 | 
			
		||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 | 
			
		||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
 | 
			
		||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 | 
			
		||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
 | 
			
		||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
 | 
			
		||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 | 
			
		||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
 | 
			
		||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
 | 
			
		||||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
 | 
			
		||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
 | 
			
		||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
			
		||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
			
		||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
 | 
			
		||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
@@ -266,11 +279,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 | 
			
		||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
			
		||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 | 
			
		||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 | 
			
		||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 | 
			
		||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
 | 
			
		||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
 | 
			
		||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
@@ -285,17 +303,28 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
			
		||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
 | 
			
		||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 | 
			
		||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 | 
			
		||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
 | 
			
		||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
			
		||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 | 
			
		||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 | 
			
		||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
 | 
			
		||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 | 
			
		||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 | 
			
		||||
@@ -303,6 +332,7 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 | 
			
		||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
 | 
			
		||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@ package admin
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"geekai/core"
 | 
			
		||||
	"geekai/core/types"
 | 
			
		||||
	"geekai/handler"
 | 
			
		||||
@@ -16,11 +18,8 @@ import (
 | 
			
		||||
	"geekai/store/vo"
 | 
			
		||||
	"geekai/utils"
 | 
			
		||||
	"geekai/utils/resp"
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/go-redis/redis/v8"
 | 
			
		||||
	"github.com/golang-jwt/jwt/v5"
 | 
			
		||||
	"github.com/mojocn/base64Captcha"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
@@ -56,11 +55,11 @@ func (h *ManagerHandler) Login(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// add captcha
 | 
			
		||||
	if !base64Captcha.DefaultMemStore.Verify(data.CaptchaId, data.Captcha, true) {
 | 
			
		||||
		resp.ERROR(c, "验证码错误!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	//// add captcha
 | 
			
		||||
	//if !base64Captcha.DefaultMemStore.Verify(data.CaptchaId, data.Captcha, true) {
 | 
			
		||||
	//	resp.ERROR(c, "验证码错误!")
 | 
			
		||||
	//	return
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
	var manager model.AdminUser
 | 
			
		||||
	res := h.DB.Model(&model.AdminUser{}).Where("username = ?", data.Username).First(&manager)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
package admin
 | 
			
		||||
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
 | 
			
		||||
// * Use of this source code is governed by a Apache-2.0 license
 | 
			
		||||
// * that can be found in the LICENSE file.
 | 
			
		||||
// * @Author yangjian102621@163.com
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"geekai/core"
 | 
			
		||||
	"geekai/handler"
 | 
			
		||||
	"geekai/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/mojocn/base64Captcha"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CaptchaHandler struct {
 | 
			
		||||
	handler.BaseHandler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCaptchaHandler(app *core.AppServer) *CaptchaHandler {
 | 
			
		||||
	return &CaptchaHandler{BaseHandler: handler.BaseHandler{App: app}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CaptchaVo struct {
 | 
			
		||||
	CaptchaId string `json:"captcha_id"`
 | 
			
		||||
	PicPath   string `json:"pic_path"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCaptcha 获取验证码
 | 
			
		||||
func (h *CaptchaHandler) GetCaptcha(c *gin.Context) {
 | 
			
		||||
	var captchaVo CaptchaVo
 | 
			
		||||
	driver := base64Captcha.NewDriverDigit(48, 130, 4, 0.4, 10)
 | 
			
		||||
	cp := base64Captcha.NewCaptcha(driver, base64Captcha.DefaultMemStore)
 | 
			
		||||
	// b64s是图片的base64编码
 | 
			
		||||
	id, b64s, err := cp.Generate()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, "生成验证码错误!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	captchaVo.CaptchaId = id
 | 
			
		||||
	captchaVo.PicPath = b64s
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, captchaVo)
 | 
			
		||||
}
 | 
			
		||||
@@ -54,7 +54,6 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
 | 
			
		||||
		Name:        data.Name,
 | 
			
		||||
		Value:       data.Value,
 | 
			
		||||
		Enabled:     data.Enabled,
 | 
			
		||||
		SortNum:     data.SortNum,
 | 
			
		||||
		Open:        data.Open,
 | 
			
		||||
		MaxTokens:   data.MaxTokens,
 | 
			
		||||
		MaxContext:  data.MaxContext,
 | 
			
		||||
@@ -64,6 +63,7 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
 | 
			
		||||
	var res *gorm.DB
 | 
			
		||||
	if data.Id > 0 {
 | 
			
		||||
		item.Id = data.Id
 | 
			
		||||
		item.SortNum = data.SortNum
 | 
			
		||||
		res = h.DB.Select("*").Omit("created_at").Updates(&item)
 | 
			
		||||
	} else {
 | 
			
		||||
		res = h.DB.Create(&item)
 | 
			
		||||
 
 | 
			
		||||
@@ -139,7 +139,6 @@ func (h *UserHandler) Save(c *gin.Context) {
 | 
			
		||||
		salt := utils.RandString(8)
 | 
			
		||||
		u := model.User{
 | 
			
		||||
			Username:    data.Username,
 | 
			
		||||
			Nickname:    fmt.Sprintf("极客学长@%d", utils.RandomNumber(6)),
 | 
			
		||||
			Password:    utils.GenPassword(data.Password, salt),
 | 
			
		||||
			Avatar:      "/images/avatar/user.png",
 | 
			
		||||
			Salt:        salt,
 | 
			
		||||
@@ -149,6 +148,11 @@ func (h *UserHandler) Save(c *gin.Context) {
 | 
			
		||||
			ChatModels:  utils.JsonEncode(data.ChatModels),
 | 
			
		||||
			ExpiredTime: utils.Str2stamp(data.ExpiredTime),
 | 
			
		||||
		}
 | 
			
		||||
		if h.licenseService.GetLicense().Configs.DeCopy {
 | 
			
		||||
			u.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
 | 
			
		||||
		} else {
 | 
			
		||||
			u.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6))
 | 
			
		||||
		}
 | 
			
		||||
		res = h.DB.Create(&u)
 | 
			
		||||
		_ = utils.CopyObject(u, &userVo)
 | 
			
		||||
		userVo.Id = u.Id
 | 
			
		||||
 
 | 
			
		||||
@@ -65,7 +65,7 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range roles {
 | 
			
		||||
		if !utils.ContainsStr(roleKeys, r.Key) {
 | 
			
		||||
		if !utils.Contains(roleKeys, r.Key) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var v vo.ChatRole
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,8 @@ type ChatHandler struct {
 | 
			
		||||
	redis          *redis.Client
 | 
			
		||||
	uploadManager  *oss.UploaderManager
 | 
			
		||||
	licenseService *service.LicenseService
 | 
			
		||||
	ReqCancelFunc  *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
 | 
			
		||||
	ChatContexts   *types.LMap[string, []types.Message]    // 聊天上下文 Map [chatId] => []Message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService) *ChatHandler {
 | 
			
		||||
@@ -52,6 +54,8 @@ func NewChatHandler(app *core.AppServer, db *gorm.DB, redis *redis.Client, manag
 | 
			
		||||
		redis:          redis,
 | 
			
		||||
		uploadManager:  manager,
 | 
			
		||||
		licenseService: licenseService,
 | 
			
		||||
		ReqCancelFunc:  types.NewLMap[string, context.CancelFunc](),
 | 
			
		||||
		ChatContexts:   types.NewLMap[string, []types.Message](),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -89,21 +93,10 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	session := h.App.ChatSession.Get(sessionId)
 | 
			
		||||
	if session == nil {
 | 
			
		||||
		user, err := h.GetLoginUser(c)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Info("用户未登录")
 | 
			
		||||
			c.Abort()
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		session = &types.ChatSession{
 | 
			
		||||
			SessionId: sessionId,
 | 
			
		||||
			ClientIP:  c.ClientIP(),
 | 
			
		||||
			Username:  user.Username,
 | 
			
		||||
			UserId:    user.Id,
 | 
			
		||||
		}
 | 
			
		||||
		h.App.ChatSession.Put(sessionId, session)
 | 
			
		||||
	session := &types.ChatSession{
 | 
			
		||||
		SessionId: sessionId,
 | 
			
		||||
		ClientIP:  c.ClientIP(),
 | 
			
		||||
		UserId:    h.GetLoginUserId(c),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// use old chat data override the chat model and role ID
 | 
			
		||||
@@ -125,22 +118,18 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
 | 
			
		||||
		Temperature: chatModel.Temperature,
 | 
			
		||||
		KeyId:       chatModel.KeyId,
 | 
			
		||||
		Platform:    chatModel.Platform}
 | 
			
		||||
	logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username)
 | 
			
		||||
	logger.Infof("New websocket connected, IP: %s", c.ClientIP())
 | 
			
		||||
 | 
			
		||||
	// 保存会话连接
 | 
			
		||||
	h.App.ChatClients.Put(sessionId, client)
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			_, msg, err := client.Receive()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Debugf("close connection: %s", client.Conn.RemoteAddr())
 | 
			
		||||
				client.Close()
 | 
			
		||||
				h.App.ChatClients.Delete(sessionId)
 | 
			
		||||
				h.App.ChatSession.Delete(sessionId)
 | 
			
		||||
				cancelFunc := h.App.ReqCancelFunc.Get(sessionId)
 | 
			
		||||
				cancelFunc := h.ReqCancelFunc.Get(sessionId)
 | 
			
		||||
				if cancelFunc != nil {
 | 
			
		||||
					cancelFunc()
 | 
			
		||||
					h.App.ReqCancelFunc.Delete(sessionId)
 | 
			
		||||
					h.ReqCancelFunc.Delete(sessionId)
 | 
			
		||||
				}
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
@@ -160,7 +149,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
 | 
			
		||||
			logger.Info("Receive a message: ", message.Content)
 | 
			
		||||
 | 
			
		||||
			ctx, cancel := context.WithCancel(context.Background())
 | 
			
		||||
			h.App.ReqCancelFunc.Put(sessionId, cancel)
 | 
			
		||||
			h.ReqCancelFunc.Put(sessionId, cancel)
 | 
			
		||||
			// 回复消息
 | 
			
		||||
			err = h.sendMessage(ctx, session, chatRole, utils.InterfaceToString(message.Content), client)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
@@ -274,8 +263,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
	chatCtx := make([]types.Message, 0)
 | 
			
		||||
	messages := make([]types.Message, 0)
 | 
			
		||||
	if h.App.SysConfig.EnableContext {
 | 
			
		||||
		if h.App.ChatContexts.Has(session.ChatId) {
 | 
			
		||||
			messages = h.App.ChatContexts.Get(session.ChatId)
 | 
			
		||||
		if h.ChatContexts.Has(session.ChatId) {
 | 
			
		||||
			messages = h.ChatContexts.Get(session.ChatId)
 | 
			
		||||
		} else {
 | 
			
		||||
			_ = utils.JsonDecode(role.Context, &messages)
 | 
			
		||||
			if h.App.SysConfig.ContextDeep > 0 {
 | 
			
		||||
@@ -323,20 +312,51 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
		reqMgs = append(reqMgs, m)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fullPrompt := prompt
 | 
			
		||||
	text := prompt
 | 
			
		||||
	// extract files in prompt
 | 
			
		||||
	files := utils.ExtractFileURLs(prompt)
 | 
			
		||||
	logger.Debugf("detected FILES: %+v", files)
 | 
			
		||||
	// 如果不是逆向模型,则提取文件内容
 | 
			
		||||
	if len(files) > 0 && !(session.Model.Value == "gpt-4-all" ||
 | 
			
		||||
		strings.HasPrefix(session.Model.Value, "gpt-4-gizmo") ||
 | 
			
		||||
		strings.HasSuffix(session.Model.Value, "claude-3")) {
 | 
			
		||||
		contents := make([]string, 0)
 | 
			
		||||
		var file model.File
 | 
			
		||||
		for _, v := range files {
 | 
			
		||||
			h.DB.Where("url = ?", v).First(&file)
 | 
			
		||||
			content, err := utils.ReadFileContent(v, h.App.Config.TikaHost)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error("error with read file: ", err)
 | 
			
		||||
			} else {
 | 
			
		||||
				contents = append(contents, fmt.Sprintf("%s 文件内容:%s", file.Name, content))
 | 
			
		||||
			}
 | 
			
		||||
			text = strings.Replace(text, v, "", 1)
 | 
			
		||||
		}
 | 
			
		||||
		if len(contents) > 0 {
 | 
			
		||||
			fullPrompt = fmt.Sprintf("请根据提供的文件内容信息回答问题(其中Excel 已转成 HTML):\n\n %s\n\n 问题:%s", strings.Join(contents, "\n"), text)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tokens, _ := utils.CalcTokens(fullPrompt, req.Model)
 | 
			
		||||
		if tokens > session.Model.MaxContext {
 | 
			
		||||
			return fmt.Errorf("文件的长度超出模型允许的最大上下文长度,请减少文件内容数量或文件大小。")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	logger.Debug("最终Prompt:", fullPrompt)
 | 
			
		||||
 | 
			
		||||
	if session.Model.Platform == types.QWen.Value {
 | 
			
		||||
		req.Input = make(map[string]interface{})
 | 
			
		||||
		reqMgs = append(reqMgs, types.Message{
 | 
			
		||||
			Role:    "user",
 | 
			
		||||
			Content: prompt,
 | 
			
		||||
			Content: fullPrompt,
 | 
			
		||||
		})
 | 
			
		||||
		req.Input["messages"] = reqMgs
 | 
			
		||||
	} else if session.Model.Platform == types.OpenAI.Value || session.Model.Platform == types.Azure.Value { // extract image for gpt-vision model
 | 
			
		||||
		imgURLs := utils.ExtractImgURL(prompt)
 | 
			
		||||
		imgURLs := utils.ExtractImgURLs(prompt)
 | 
			
		||||
		logger.Debugf("detected IMG: %+v", imgURLs)
 | 
			
		||||
		var content interface{}
 | 
			
		||||
		if len(imgURLs) > 0 {
 | 
			
		||||
			data := make([]interface{}, 0)
 | 
			
		||||
			text := prompt
 | 
			
		||||
			for _, v := range imgURLs {
 | 
			
		||||
				text = strings.Replace(text, v, "", 1)
 | 
			
		||||
				data = append(data, gin.H{
 | 
			
		||||
@@ -348,11 +368,11 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
			}
 | 
			
		||||
			data = append(data, gin.H{
 | 
			
		||||
				"type": "text",
 | 
			
		||||
				"text": text,
 | 
			
		||||
				"text": strings.TrimSpace(text),
 | 
			
		||||
			})
 | 
			
		||||
			content = data
 | 
			
		||||
		} else {
 | 
			
		||||
			content = prompt
 | 
			
		||||
			content = fullPrompt
 | 
			
		||||
		}
 | 
			
		||||
		req.Messages = append(reqMgs, map[string]interface{}{
 | 
			
		||||
			"role":    "user",
 | 
			
		||||
@@ -361,7 +381,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
 | 
			
		||||
	} else {
 | 
			
		||||
		req.Messages = append(reqMgs, map[string]interface{}{
 | 
			
		||||
			"role":    "user",
 | 
			
		||||
			"content": prompt,
 | 
			
		||||
			"content": fullPrompt,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -442,9 +462,9 @@ func getTotalTokens(req types.ApiRequest) int {
 | 
			
		||||
// StopGenerate 停止生成
 | 
			
		||||
func (h *ChatHandler) StopGenerate(c *gin.Context) {
 | 
			
		||||
	sessionId := c.Query("session_id")
 | 
			
		||||
	if h.App.ReqCancelFunc.Has(sessionId) {
 | 
			
		||||
		h.App.ReqCancelFunc.Get(sessionId)()
 | 
			
		||||
		h.App.ReqCancelFunc.Delete(sessionId)
 | 
			
		||||
	if h.ReqCancelFunc.Has(sessionId) {
 | 
			
		||||
		h.ReqCancelFunc.Get(sessionId)()
 | 
			
		||||
		h.ReqCancelFunc.Delete(sessionId)
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c, types.OkMsg)
 | 
			
		||||
}
 | 
			
		||||
@@ -454,7 +474,7 @@ func (h *ChatHandler) StopGenerate(c *gin.Context) {
 | 
			
		||||
func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, session *types.ChatSession, apiKey *model.ApiKey) (*http.Response, error) {
 | 
			
		||||
	// if the chat model bind a KEY, use it directly
 | 
			
		||||
	if session.Model.KeyId > 0 {
 | 
			
		||||
		h.DB.Debug().Where("id", session.Model.KeyId).Where("enabled", true).Find(apiKey)
 | 
			
		||||
		h.DB.Where("id", session.Model.KeyId).Find(apiKey)
 | 
			
		||||
	}
 | 
			
		||||
	// use the last unused key
 | 
			
		||||
	if apiKey.Id == 0 {
 | 
			
		||||
@@ -602,7 +622,7 @@ func (h *ChatHandler) saveChatHistory(
 | 
			
		||||
	if h.App.SysConfig.EnableContext {
 | 
			
		||||
		chatCtx = append(chatCtx, useMsg)  // 提问消息
 | 
			
		||||
		chatCtx = append(chatCtx, message) // 回复消息
 | 
			
		||||
		h.App.ChatContexts.Put(session.ChatId, chatCtx)
 | 
			
		||||
		h.ChatContexts.Put(session.ChatId, chatCtx)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 追加聊天记录
 | 
			
		||||
@@ -660,7 +680,7 @@ func (h *ChatHandler) saveChatHistory(
 | 
			
		||||
	res = h.DB.Where("chat_id = ?", session.ChatId).First(&chatItem)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		chatItem.ChatId = session.ChatId
 | 
			
		||||
		chatItem.UserId = session.UserId
 | 
			
		||||
		chatItem.UserId = userVo.Id
 | 
			
		||||
		chatItem.RoleId = role.Id
 | 
			
		||||
		chatItem.ModelId = session.Model.Id
 | 
			
		||||
		if utf8.RuneCountInString(prompt) > 30 {
 | 
			
		||||
 
 | 
			
		||||
@@ -96,7 +96,7 @@ func (h *ChatHandler) Clear(c *gin.Context) {
 | 
			
		||||
	for _, chat := range chats {
 | 
			
		||||
		chatIds = append(chatIds, chat.ChatId)
 | 
			
		||||
		// 清空会话上下文
 | 
			
		||||
		h.App.ChatContexts.Delete(chat.ChatId)
 | 
			
		||||
		h.ChatContexts.Delete(chat.ChatId)
 | 
			
		||||
	}
 | 
			
		||||
	err = h.DB.Transaction(func(tx *gorm.DB) error {
 | 
			
		||||
		res := h.DB.Where("user_id =?", user.Id).Delete(&model.ChatItem{})
 | 
			
		||||
@@ -108,8 +108,6 @@ func (h *ChatHandler) Clear(c *gin.Context) {
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			return res.Error
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO: 是否要删除 MidJourney 绘画记录和图片文件?
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
@@ -175,7 +173,7 @@ func (h *ChatHandler) Remove(c *gin.Context) {
 | 
			
		||||
	// TODO: 是否要删除 MidJourney 绘画记录和图片文件?
 | 
			
		||||
 | 
			
		||||
	// 清空会话上下文
 | 
			
		||||
	h.App.ChatContexts.Delete(chatId)
 | 
			
		||||
	h.ChatContexts.Delete(chatId)
 | 
			
		||||
	resp.SUCCESS(c, types.OkMsg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,9 @@ func (h *ChatHandler) sendOpenAiMessage(
 | 
			
		||||
			if len(responseBody.Choices) == 0 { // Fixed: 兼容 Azure API 第一个输出空行
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if responseBody.Choices[0].Delta.Content == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if responseBody.Choices[0].FinishReason == "stop" && len(contents) == 0 {
 | 
			
		||||
				utils.ReplyMessage(ws, "抱歉😔😔😔,AI助手由于未知原因已经停止输出内容。")
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ func (h *ChatHandler) sendXunFeiMessage(
 | 
			
		||||
	var res *gorm.DB
 | 
			
		||||
	// use the bind key
 | 
			
		||||
	if session.Model.KeyId > 0 {
 | 
			
		||||
		res = h.DB.Where("id", session.Model.KeyId).Where("enabled", true).Find(&apiKey)
 | 
			
		||||
		res = h.DB.Where("id", session.Model.KeyId).Find(&apiKey)
 | 
			
		||||
	}
 | 
			
		||||
	// use the last unused key
 | 
			
		||||
	if apiKey.Id == 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -158,13 +158,13 @@ func (h *DallJobHandler) ImgWall(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
// JobList 获取 SD 任务列表
 | 
			
		||||
func (h *DallJobHandler) JobList(c *gin.Context) {
 | 
			
		||||
	status := h.GetBool(c, "status")
 | 
			
		||||
	finish := h.GetBool(c, "finish")
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	page := h.GetInt(c, "page", 0)
 | 
			
		||||
	pageSize := h.GetInt(c, "page_size", 0)
 | 
			
		||||
	publish := h.GetBool(c, "publish")
 | 
			
		||||
 | 
			
		||||
	err, jobs := h.getData(status, userId, page, pageSize, publish)
 | 
			
		||||
	err, jobs := h.getData(finish, userId, page, pageSize, publish)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
@@ -214,25 +214,23 @@ func (h *DallJobHandler) getData(finish bool, userId uint, page int, pageSize in
 | 
			
		||||
 | 
			
		||||
// Remove remove task image
 | 
			
		||||
func (h *DallJobHandler) Remove(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id     uint   `json:"id"`
 | 
			
		||||
		UserId uint   `json:"user_id"`
 | 
			
		||||
		ImgURL string `json:"img_url"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	var job model.DallJob
 | 
			
		||||
	if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "记录不存在")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove job recode
 | 
			
		||||
	res := h.DB.Delete(&model.DallJob{Id: data.Id})
 | 
			
		||||
	res := h.DB.Delete(&model.DallJob{Id: job.Id})
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, res.Error.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove image
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(data.ImgURL)
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Error("remove image failed: ", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -242,16 +240,11 @@ func (h *DallJobHandler) Remove(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
// Publish 发布/取消发布图片到画廊显示
 | 
			
		||||
func (h *DallJobHandler) Publish(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id     uint `json:"id"`
 | 
			
		||||
		Action bool `json:"action"` // 发布动作,true => 发布,false => 取消分享
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	action := h.GetBool(c, "action") // 发布动作,true => 发布,false => 取消分享
 | 
			
		||||
 | 
			
		||||
	res := h.DB.Model(&model.DallJob{Id: data.Id}).UpdateColumn("publish", true)
 | 
			
		||||
	res := h.DB.Model(&model.DallJob{Id: uint(id), UserId: uint(userId)}).UpdateColumn("publish", action)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		logger.Error("error with update database:", res.Error)
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败")
 | 
			
		||||
 
 | 
			
		||||
@@ -215,7 +215,7 @@ func (h *MarkMapHandler) doRequest(req types.ApiRequest, chatModel model.ChatMod
 | 
			
		||||
	// if the chat model bind a KEY, use it directly
 | 
			
		||||
	var res *gorm.DB
 | 
			
		||||
	if chatModel.KeyId > 0 {
 | 
			
		||||
		res = h.DB.Where("id", chatModel.KeyId).Where("enabled", true).Find(apiKey)
 | 
			
		||||
		res = h.DB.Where("id", chatModel.KeyId).Find(apiKey)
 | 
			
		||||
	}
 | 
			
		||||
	// use the last unused key
 | 
			
		||||
	if apiKey.Id == 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -92,19 +92,18 @@ func (h *MidJourneyHandler) Client(c *gin.Context) {
 | 
			
		||||
// Image 创建一个绘画任务
 | 
			
		||||
func (h *MidJourneyHandler) Image(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		SessionId string   `json:"session_id"`
 | 
			
		||||
		TaskType  string   `json:"task_type"`
 | 
			
		||||
		Prompt    string   `json:"prompt"`
 | 
			
		||||
		NegPrompt string   `json:"neg_prompt"`
 | 
			
		||||
		Rate      string   `json:"rate"`
 | 
			
		||||
		Model     string   `json:"model"`
 | 
			
		||||
		Chaos     int      `json:"chaos"`
 | 
			
		||||
		Raw       bool     `json:"raw"`
 | 
			
		||||
		Seed      int64    `json:"seed"`
 | 
			
		||||
		Stylize   int      `json:"stylize"`
 | 
			
		||||
		Model     string   `json:"model"`   // 模型
 | 
			
		||||
		Chaos     int      `json:"chaos"`   // 创意度取值范围: 0-100
 | 
			
		||||
		Raw       bool     `json:"raw"`     // 是否开启原始模型
 | 
			
		||||
		Seed      int64    `json:"seed"`    // 随机数
 | 
			
		||||
		Stylize   int      `json:"stylize"` // 风格化
 | 
			
		||||
		ImgArr    []string `json:"img_arr"`
 | 
			
		||||
		Tile      bool     `json:"tile"`
 | 
			
		||||
		Quality   float32  `json:"quality"`
 | 
			
		||||
		Tile      bool     `json:"tile"`    // 重复平铺
 | 
			
		||||
		Quality   float32  `json:"quality"` // 画质
 | 
			
		||||
		Iw        float32  `json:"iw"`
 | 
			
		||||
		CRef      string   `json:"cref"` //生成角色一致的图像
 | 
			
		||||
		SRef      string   `json:"sref"` //生成风格一致的图像
 | 
			
		||||
@@ -243,17 +242,12 @@ type reqVo struct {
 | 
			
		||||
	ChannelId   string `json:"channel_id"`
 | 
			
		||||
	MessageId   string `json:"message_id"`
 | 
			
		||||
	MessageHash string `json:"message_hash"`
 | 
			
		||||
	SessionId   string `json:"session_id"`
 | 
			
		||||
	Prompt      string `json:"prompt"`
 | 
			
		||||
	ChatId      string `json:"chat_id"`
 | 
			
		||||
	RoleId      int    `json:"role_id"`
 | 
			
		||||
	Icon        string `json:"icon"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Upscale send upscale command to MidJourney Bot
 | 
			
		||||
func (h *MidJourneyHandler) Upscale(c *gin.Context) {
 | 
			
		||||
	var data reqVo
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil || data.SessionId == "" {
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -271,7 +265,6 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
 | 
			
		||||
		UserId:      userId,
 | 
			
		||||
		TaskId:      taskId,
 | 
			
		||||
		Progress:    0,
 | 
			
		||||
		Prompt:      data.Prompt,
 | 
			
		||||
		Power:       h.App.SysConfig.MjActionPower,
 | 
			
		||||
		CreatedAt:   time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
@@ -283,7 +276,6 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
 | 
			
		||||
	h.pool.PushTask(types.MjTask{
 | 
			
		||||
		Id:          job.Id,
 | 
			
		||||
		Type:        types.TaskUpscale,
 | 
			
		||||
		Prompt:      data.Prompt,
 | 
			
		||||
		UserId:      userId,
 | 
			
		||||
		ChannelId:   data.ChannelId,
 | 
			
		||||
		Index:       data.Index,
 | 
			
		||||
@@ -318,7 +310,7 @@ func (h *MidJourneyHandler) Upscale(c *gin.Context) {
 | 
			
		||||
// Variation send variation command to MidJourney Bot
 | 
			
		||||
func (h *MidJourneyHandler) Variation(c *gin.Context) {
 | 
			
		||||
	var data reqVo
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil || data.SessionId == "" {
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -337,7 +329,6 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
 | 
			
		||||
		UserId:      userId,
 | 
			
		||||
		TaskId:      taskId,
 | 
			
		||||
		Progress:    0,
 | 
			
		||||
		Prompt:      data.Prompt,
 | 
			
		||||
		Power:       h.App.SysConfig.MjActionPower,
 | 
			
		||||
		CreatedAt:   time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
@@ -349,7 +340,6 @@ func (h *MidJourneyHandler) Variation(c *gin.Context) {
 | 
			
		||||
	h.pool.PushTask(types.MjTask{
 | 
			
		||||
		Id:          job.Id,
 | 
			
		||||
		Type:        types.TaskVariation,
 | 
			
		||||
		Prompt:      data.Prompt,
 | 
			
		||||
		UserId:      userId,
 | 
			
		||||
		Index:       data.Index,
 | 
			
		||||
		ChannelId:   data.ChannelId,
 | 
			
		||||
@@ -397,13 +387,13 @@ func (h *MidJourneyHandler) ImgWall(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
// JobList 获取 MJ 任务列表
 | 
			
		||||
func (h *MidJourneyHandler) JobList(c *gin.Context) {
 | 
			
		||||
	status := h.GetBool(c, "status")
 | 
			
		||||
	finish := h.GetBool(c, "finish")
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	page := h.GetInt(c, "page", 0)
 | 
			
		||||
	pageSize := h.GetInt(c, "page_size", 0)
 | 
			
		||||
	publish := h.GetBool(c, "publish")
 | 
			
		||||
 | 
			
		||||
	err, jobs := h.getData(status, userId, page, pageSize, publish)
 | 
			
		||||
	err, jobs := h.getData(finish, userId, page, pageSize, publish)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
@@ -446,14 +436,9 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" {
 | 
			
		||||
			// discord 服务器图片需要使用代理转发图片数据流
 | 
			
		||||
			if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") {
 | 
			
		||||
				image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
 | 
			
		||||
				if err == nil {
 | 
			
		||||
					job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				job.ImgURL = job.OrgURL
 | 
			
		||||
			image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -464,30 +449,27 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize
 | 
			
		||||
 | 
			
		||||
// Remove remove task image
 | 
			
		||||
func (h *MidJourneyHandler) Remove(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id     uint   `json:"id"`
 | 
			
		||||
		UserId uint   `json:"user_id"`
 | 
			
		||||
		ImgURL string `json:"img_url"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	var job model.MidJourneyJob
 | 
			
		||||
	if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "记录不存在")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove job recode
 | 
			
		||||
	res := h.DB.Delete(&model.MidJourneyJob{Id: data.Id})
 | 
			
		||||
	res := h.DB.Delete(&job)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, res.Error.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove image
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(data.ImgURL)
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Error("remove image failed: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := h.pool.Clients.Get(data.UserId)
 | 
			
		||||
	client := h.pool.Clients.Get(uint(job.UserId))
 | 
			
		||||
	if client != nil {
 | 
			
		||||
		_ = client.Send([]byte("Task Updated"))
 | 
			
		||||
	}
 | 
			
		||||
@@ -497,16 +479,10 @@ func (h *MidJourneyHandler) Remove(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
// Publish 发布图片到画廊显示
 | 
			
		||||
func (h *MidJourneyHandler) Publish(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id     uint `json:"id"`
 | 
			
		||||
		Action bool `json:"action"` // 发布动作,true => 发布,false => 取消分享
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res := h.DB.Model(&model.MidJourneyJob{Id: data.Id}).UpdateColumn("publish", data.Action)
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	action := h.GetBool(c, "action") // 发布动作,true => 发布,false => 取消分享
 | 
			
		||||
	res := h.DB.Model(&model.MidJourneyJob{Id: uint(id), UserId: userId}).UpdateColumn("publish", action)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		logger.Error("error with update database:", res.Error)
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败")
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import (
 | 
			
		||||
	"geekai/store/vo"
 | 
			
		||||
	"geekai/utils"
 | 
			
		||||
	"geekai/utils/resp"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
@@ -27,23 +28,18 @@ func NewOrderHandler(app *core.AppServer, db *gorm.DB) *OrderHandler {
 | 
			
		||||
	return &OrderHandler{BaseHandler: BaseHandler{App: app, DB: db}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List 订单列表
 | 
			
		||||
func (h *OrderHandler) List(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Page     int `json:"page"`
 | 
			
		||||
		PageSize int `json:"page_size"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	page := h.GetInt(c, "page", 1)
 | 
			
		||||
	pageSize := h.GetInt(c, "page_size", 20)
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	session := h.DB.Session(&gorm.Session{}).Where("user_id = ? AND status = ?", userId, types.OrderPaidSuccess)
 | 
			
		||||
	var total int64
 | 
			
		||||
	session.Model(&model.Order{}).Count(&total)
 | 
			
		||||
	var items []model.Order
 | 
			
		||||
	var list = make([]vo.Order, 0)
 | 
			
		||||
	offset := (data.Page - 1) * data.PageSize
 | 
			
		||||
	res := session.Order("id DESC").Offset(offset).Limit(data.PageSize).Find(&items)
 | 
			
		||||
	offset := (page - 1) * pageSize
 | 
			
		||||
	res := session.Order("id DESC").Offset(offset).Limit(pageSize).Find(&items)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		for _, item := range items {
 | 
			
		||||
			var order vo.Order
 | 
			
		||||
@@ -58,5 +54,35 @@ func (h *OrderHandler) List(c *gin.Context) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, list))
 | 
			
		||||
	resp.SUCCESS(c, vo.NewPage(total, page, pageSize, list))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Query 查询订单状态
 | 
			
		||||
func (h *OrderHandler) Query(c *gin.Context) {
 | 
			
		||||
	orderNo := h.GetTrim(c, "order_no")
 | 
			
		||||
	var order model.Order
 | 
			
		||||
	res := h.DB.Where("order_no = ?", orderNo).First(&order)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "Order not found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if order.Status == types.OrderPaidSuccess {
 | 
			
		||||
		resp.SUCCESS(c, gin.H{"status": order.Status})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	counter := 0
 | 
			
		||||
	for {
 | 
			
		||||
		time.Sleep(time.Second)
 | 
			
		||||
		var item model.Order
 | 
			
		||||
		h.DB.Where("order_no = ?", orderNo).First(&item)
 | 
			
		||||
		if counter >= 15 || item.Status == types.OrderPaidSuccess || item.Status != order.Status {
 | 
			
		||||
			order.Status = item.Status
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		counter++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, gin.H{"status": order.Status})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -111,7 +111,7 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
	// fix: 这里先检查一下订单状态,如果已经支付了,就直接返回
 | 
			
		||||
	if order.Status == types.OrderPaidSuccess {
 | 
			
		||||
		resp.ERROR(c, "This order had been paid, please do not pay twice")
 | 
			
		||||
		resp.ERROR(c, "订单已支付成功,无需重复支付!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -148,49 +148,11 @@ func (h *PaymentHandler) DoPay(c *gin.Context) {
 | 
			
		||||
	resp.ERROR(c, "Invalid operations")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OrderQuery 查询订单状态
 | 
			
		||||
func (h *PaymentHandler) OrderQuery(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		OrderNo string `json:"order_no"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var order model.Order
 | 
			
		||||
	res := h.DB.Where("order_no = ?", data.OrderNo).First(&order)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "Order not found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if order.Status == types.OrderPaidSuccess {
 | 
			
		||||
		resp.SUCCESS(c, gin.H{"status": order.Status})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	counter := 0
 | 
			
		||||
	for {
 | 
			
		||||
		time.Sleep(time.Second)
 | 
			
		||||
		var item model.Order
 | 
			
		||||
		h.DB.Where("order_no = ?", data.OrderNo).First(&item)
 | 
			
		||||
		if counter >= 15 || item.Status == types.OrderPaidSuccess || item.Status != order.Status {
 | 
			
		||||
			order.Status = item.Status
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		counter++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, gin.H{"status": order.Status})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PayQrcode 生成支付 URL 二维码
 | 
			
		||||
func (h *PaymentHandler) PayQrcode(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		PayWay    string `json:"pay_way"` // 支付方式
 | 
			
		||||
		ProductId uint   `json:"product_id"`
 | 
			
		||||
		UserId    int    `json:"user_id"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
@@ -209,10 +171,9 @@ func (h *PaymentHandler) PayQrcode(c *gin.Context) {
 | 
			
		||||
		resp.ERROR(c, "error with generate trade no: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var user model.User
 | 
			
		||||
	res = h.DB.First(&user, data.UserId)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "Invalid user ID")
 | 
			
		||||
	user, err := h.GetLoginUser(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.NotAuth(c)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -333,7 +294,6 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		PayWay    string `json:"pay_way"` // 支付方式
 | 
			
		||||
		ProductId uint   `json:"product_id"`
 | 
			
		||||
		UserId    int    `json:"user_id"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
@@ -352,10 +312,9 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
 | 
			
		||||
		resp.ERROR(c, "error with generate trade no: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var user model.User
 | 
			
		||||
	res = h.DB.First(&user, data.UserId)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "Invalid user ID")
 | 
			
		||||
	user, err := h.GetLoginUser(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.NotAuth(c)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -449,7 +408,7 @@ func (h *PaymentHandler) Mobile(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, payURL)
 | 
			
		||||
	resp.SUCCESS(c, gin.H{"url": payURL, "order_no": orderNo})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 异步通知回调公共逻辑
 | 
			
		||||
 
 | 
			
		||||
@@ -99,10 +99,7 @@ func (h *SdJobHandler) Image(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var data struct {
 | 
			
		||||
		SessionId string `json:"session_id"`
 | 
			
		||||
		types.SdTaskParams
 | 
			
		||||
	}
 | 
			
		||||
	var data types.SdTaskParams
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil || data.Prompt == "" {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
@@ -215,13 +212,13 @@ func (h *SdJobHandler) ImgWall(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
// JobList 获取 SD 任务列表
 | 
			
		||||
func (h *SdJobHandler) JobList(c *gin.Context) {
 | 
			
		||||
	status := h.GetBool(c, "status")
 | 
			
		||||
	finish := h.GetBool(c, "finish")
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	page := h.GetInt(c, "page", 0)
 | 
			
		||||
	pageSize := h.GetInt(c, "page_size", 0)
 | 
			
		||||
	publish := h.GetBool(c, "publish")
 | 
			
		||||
 | 
			
		||||
	err, jobs := h.getData(status, userId, page, pageSize, publish)
 | 
			
		||||
	err, jobs := h.getData(finish, userId, page, pageSize, publish)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
@@ -280,30 +277,28 @@ func (h *SdJobHandler) getData(finish bool, userId uint, page int, pageSize int,
 | 
			
		||||
 | 
			
		||||
// Remove remove task image
 | 
			
		||||
func (h *SdJobHandler) Remove(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id     uint   `json:"id"`
 | 
			
		||||
		UserId uint   `json:"user_id"`
 | 
			
		||||
		ImgURL string `json:"img_url"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	var job model.SdJob
 | 
			
		||||
	if res := h.DB.Where("id = ? AND user_id = ?", id, userId).First(&job); res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "记录不存在")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove job recode
 | 
			
		||||
	res := h.DB.Delete(&model.SdJob{Id: data.Id})
 | 
			
		||||
	res := h.DB.Delete(&model.SdJob{Id: job.Id})
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, res.Error.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove image
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(data.ImgURL)
 | 
			
		||||
	err := h.uploader.GetUploadHandler().Delete(job.ImgURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Error("remove image failed: ", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := h.pool.Clients.Get(data.UserId)
 | 
			
		||||
	client := h.pool.Clients.Get(uint(job.UserId))
 | 
			
		||||
	if client != nil {
 | 
			
		||||
		_ = client.Send([]byte(sd.Finished))
 | 
			
		||||
	}
 | 
			
		||||
@@ -313,16 +308,11 @@ func (h *SdJobHandler) Remove(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
// Publish 发布/取消发布图片到画廊显示
 | 
			
		||||
func (h *SdJobHandler) Publish(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id     uint `json:"id"`
 | 
			
		||||
		Action bool `json:"action"` // 发布动作,true => 发布,false => 取消分享
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	userId := h.GetInt(c, "user_id", 0)
 | 
			
		||||
	action := h.GetBool(c, "action") // 发布动作,true => 发布,false => 取消分享
 | 
			
		||||
 | 
			
		||||
	res := h.DB.Model(&model.SdJob{Id: data.Id}).UpdateColumn("publish", true)
 | 
			
		||||
	res := h.DB.Model(&model.SdJob{Id: uint(id), UserId: userId}).UpdateColumn("publish", action)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		logger.Error("error with update database:", res.Error)
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败")
 | 
			
		||||
 
 | 
			
		||||
@@ -49,14 +49,20 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Receiver string `json:"receiver"` // 接收者
 | 
			
		||||
		Key      string `json:"key"`
 | 
			
		||||
		Dots     string `json:"dots"`
 | 
			
		||||
		Dots     string `json:"dots,omitempty"`
 | 
			
		||||
		X        int    `json:"x,omitempty"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !h.captcha.Check(data) {
 | 
			
		||||
	var check bool
 | 
			
		||||
	if data.X != 0 {
 | 
			
		||||
		check = h.captcha.SlideCheck(data)
 | 
			
		||||
	} else {
 | 
			
		||||
		check = h.captcha.Check(data)
 | 
			
		||||
	}
 | 
			
		||||
	if !check {
 | 
			
		||||
		resp.ERROR(c, "验证码错误,请先完人机验证")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
@@ -64,13 +70,13 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
 | 
			
		||||
	code := utils.RandomNumber(6)
 | 
			
		||||
	var err error
 | 
			
		||||
	if strings.Contains(data.Receiver, "@") { // email
 | 
			
		||||
		if !utils.ContainsStr(h.App.SysConfig.RegisterWays, "email") {
 | 
			
		||||
		if !utils.Contains(h.App.SysConfig.RegisterWays, "email") {
 | 
			
		||||
			resp.ERROR(c, "系统已禁用邮箱注册!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		err = h.smtp.SendVerifyCode(data.Receiver, code)
 | 
			
		||||
	} else {
 | 
			
		||||
		if !utils.ContainsStr(h.App.SysConfig.RegisterWays, "mobile") {
 | 
			
		||||
		if !utils.Contains(h.App.SysConfig.RegisterWays, "mobile") {
 | 
			
		||||
			resp.ERROR(c, "系统已禁用手机号注册!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
@@ -89,5 +95,9 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
	if h.App.Debug {
 | 
			
		||||
		resp.SUCCESS(c, code)
 | 
			
		||||
	} else {
 | 
			
		||||
		resp.SUCCESS(c)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ package handler
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"geekai/core"
 | 
			
		||||
	"geekai/core/types"
 | 
			
		||||
	"geekai/service/oss"
 | 
			
		||||
	"geekai/store/model"
 | 
			
		||||
	"geekai/store/vo"
 | 
			
		||||
@@ -35,6 +36,12 @@ func (h *UploadHandler) Upload(c *gin.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Info("upload file: ", file.Name)
 | 
			
		||||
	// cut the file name if it's too long
 | 
			
		||||
	if len(file.Name) > 100 {
 | 
			
		||||
		file.Name = file.Name[:90] + file.Ext
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	res := h.DB.Create(&model.File{
 | 
			
		||||
		UserId:    int(userId),
 | 
			
		||||
@@ -54,10 +61,23 @@ func (h *UploadHandler) Upload(c *gin.Context) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *UploadHandler) List(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Urls []string `json:"urls,omitempty"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	var items []model.File
 | 
			
		||||
	var files = make([]vo.File, 0)
 | 
			
		||||
	h.DB.Where("user_id = ?", userId).Find(&items)
 | 
			
		||||
	session := h.DB.Session(&gorm.Session{})
 | 
			
		||||
	session = session.Where("user_id = ?", userId)
 | 
			
		||||
	if len(data.Urls) > 0 {
 | 
			
		||||
		session = session.Where("url IN ?", data.Urls)
 | 
			
		||||
	}
 | 
			
		||||
	session.Find(&items)
 | 
			
		||||
	if len(items) > 0 {
 | 
			
		||||
		for _, v := range items {
 | 
			
		||||
			var file vo.File
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import (
 | 
			
		||||
	"geekai/store/vo"
 | 
			
		||||
	"geekai/utils"
 | 
			
		||||
	"geekai/utils/resp"
 | 
			
		||||
	"github.com/imroc/req/v3"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -97,7 +98,7 @@ func (h *UserHandler) Register(c *gin.Context) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check if the username is exists
 | 
			
		||||
	// check if the username is existing
 | 
			
		||||
	var item model.User
 | 
			
		||||
	res := h.DB.Where("username = ?", data.Username).First(&item)
 | 
			
		||||
	if item.Id > 0 {
 | 
			
		||||
@@ -122,7 +123,7 @@ func (h *UserHandler) Register(c *gin.Context) {
 | 
			
		||||
		user.Power += h.App.SysConfig.InvitePower
 | 
			
		||||
	}
 | 
			
		||||
	if h.licenseService.GetLicense().Configs.DeCopy {
 | 
			
		||||
		user.Username = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
 | 
			
		||||
		user.Nickname = fmt.Sprintf("用户@%d", utils.RandomNumber(6))
 | 
			
		||||
	} else {
 | 
			
		||||
		user.Nickname = fmt.Sprintf("极客学长@%d", utils.RandomNumber(6))
 | 
			
		||||
	}
 | 
			
		||||
@@ -184,7 +185,7 @@ func (h *UserHandler) Register(c *gin.Context) {
 | 
			
		||||
		resp.ERROR(c, "error with save token: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c, tokenString)
 | 
			
		||||
	resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Login 用户登录
 | 
			
		||||
@@ -243,7 +244,7 @@ func (h *UserHandler) Login(c *gin.Context) {
 | 
			
		||||
		resp.ERROR(c, "error with save token: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c, tokenString)
 | 
			
		||||
	resp.SUCCESS(c, gin.H{"token": tokenString, "user_id": user.Id, "username": user.Username})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Logout 注 销
 | 
			
		||||
@@ -255,6 +256,130 @@ func (h *UserHandler) Logout(c *gin.Context) {
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CLogin 第三方登录请求二维码
 | 
			
		||||
func (h *UserHandler) CLogin(c *gin.Context) {
 | 
			
		||||
	returnURL := h.GetTrim(c, "return_url")
 | 
			
		||||
	var res types.BizVo
 | 
			
		||||
	apiURL := fmt.Sprintf("%s/api/clogin/request", h.App.Config.ApiConfig.ApiURL)
 | 
			
		||||
	r, err := req.C().R().SetBody(gin.H{"login_type": "wx", "return_url": returnURL}).
 | 
			
		||||
		SetHeader("AppId", h.App.Config.ApiConfig.AppId).
 | 
			
		||||
		SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.App.Config.ApiConfig.Token)).
 | 
			
		||||
		SetSuccessResult(&res).
 | 
			
		||||
		Post(apiURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if r.IsErrorState() {
 | 
			
		||||
		resp.ERROR(c, "error with login http status: "+r.Status)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if res.Code != types.Success {
 | 
			
		||||
		resp.ERROR(c, "error with http response: "+res.Message)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, res.Data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CLoginCallback 第三方登录回调
 | 
			
		||||
func (h *UserHandler) CLoginCallback(c *gin.Context) {
 | 
			
		||||
	loginType := h.GetTrim(c, "login_type")
 | 
			
		||||
	code := h.GetTrim(c, "code")
 | 
			
		||||
 | 
			
		||||
	var res types.BizVo
 | 
			
		||||
	apiURL := fmt.Sprintf("%s/api/clogin/info", h.App.Config.ApiConfig.ApiURL)
 | 
			
		||||
	r, err := req.C().R().SetBody(gin.H{"login_type": loginType, "code": code}).
 | 
			
		||||
		SetHeader("AppId", h.App.Config.ApiConfig.AppId).
 | 
			
		||||
		SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.App.Config.ApiConfig.Token)).
 | 
			
		||||
		SetSuccessResult(&res).
 | 
			
		||||
		Post(apiURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if r.IsErrorState() {
 | 
			
		||||
		resp.ERROR(c, "error with login http status: "+r.Status)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if res.Code != types.Success {
 | 
			
		||||
		resp.ERROR(c, "error with http response: "+res.Message)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// login successfully
 | 
			
		||||
	data := res.Data.(map[string]interface{})
 | 
			
		||||
	session := gin.H{}
 | 
			
		||||
	var user model.User
 | 
			
		||||
	tx := h.DB.Debug().Where("openid", data["openid"]).First(&user)
 | 
			
		||||
	if tx.Error != nil { // user not exist, create new user
 | 
			
		||||
		// 检测最大注册人数
 | 
			
		||||
		var totalUser int64
 | 
			
		||||
		h.DB.Model(&model.User{}).Count(&totalUser)
 | 
			
		||||
		if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
 | 
			
		||||
			resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		salt := utils.RandString(8)
 | 
			
		||||
		password := fmt.Sprintf("%d", utils.RandomNumber(8))
 | 
			
		||||
		user = model.User{
 | 
			
		||||
			Username:   fmt.Sprintf("%s@%d", loginType, utils.RandomNumber(10)),
 | 
			
		||||
			Password:   utils.GenPassword(password, salt),
 | 
			
		||||
			Avatar:     fmt.Sprintf("%s", data["avatar"]),
 | 
			
		||||
			Salt:       salt,
 | 
			
		||||
			Status:     true,
 | 
			
		||||
			ChatRoles:  utils.JsonEncode([]string{"gpt"}),               // 默认只订阅通用助手角色
 | 
			
		||||
			ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
 | 
			
		||||
			Power:      h.App.SysConfig.InitPower,
 | 
			
		||||
			OpenId:     fmt.Sprintf("%s", data["openid"]),
 | 
			
		||||
			Nickname:   fmt.Sprintf("%s", data["nickname"]),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tx = h.DB.Create(&user)
 | 
			
		||||
		if tx.Error != nil {
 | 
			
		||||
			resp.ERROR(c, "保存数据失败")
 | 
			
		||||
			logger.Error(tx.Error)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		session["username"] = user.Username
 | 
			
		||||
		session["password"] = password
 | 
			
		||||
	} else { // login directly
 | 
			
		||||
		// 更新最后登录时间和IP
 | 
			
		||||
		user.LastLoginIp = c.ClientIP()
 | 
			
		||||
		user.LastLoginAt = time.Now().Unix()
 | 
			
		||||
		h.DB.Model(&user).Updates(user)
 | 
			
		||||
 | 
			
		||||
		h.DB.Create(&model.UserLoginLog{
 | 
			
		||||
			UserId:       user.Id,
 | 
			
		||||
			Username:     user.Username,
 | 
			
		||||
			LoginIp:      c.ClientIP(),
 | 
			
		||||
			LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 创建 token
 | 
			
		||||
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
 | 
			
		||||
		"user_id": user.Id,
 | 
			
		||||
		"expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(),
 | 
			
		||||
	})
 | 
			
		||||
	tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, "Failed to generate token, "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// 保存到 redis
 | 
			
		||||
	key := fmt.Sprintf("users/%d", user.Id)
 | 
			
		||||
	if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil {
 | 
			
		||||
		resp.ERROR(c, "error with save token: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	session["token"] = tokenString
 | 
			
		||||
	resp.SUCCESS(c, session)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Session 获取/验证会话
 | 
			
		||||
func (h *UserHandler) Session(c *gin.Context) {
 | 
			
		||||
	user, err := h.GetLoginUser(c)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								api/main.go
									
									
									
									
									
								
							
							
						
						@@ -240,6 +240,8 @@ func main() {
 | 
			
		||||
			group.POST("password", h.UpdatePass)
 | 
			
		||||
			group.POST("bind/username", h.BindUsername)
 | 
			
		||||
			group.POST("resetPass", h.ResetPass)
 | 
			
		||||
			group.GET("clogin", h.CLogin)
 | 
			
		||||
			group.GET("clogin/callback", h.CLoginCallback)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *chatimpl.ChatHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/chat/")
 | 
			
		||||
@@ -255,7 +257,7 @@ func main() {
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.UploadHandler) {
 | 
			
		||||
			s.Engine.POST("/api/upload", h.Upload)
 | 
			
		||||
			s.Engine.GET("/api/upload/list", h.List)
 | 
			
		||||
			s.Engine.POST("/api/upload/list", h.List)
 | 
			
		||||
			s.Engine.GET("/api/upload/remove", h.Remove)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.SmsHandler) {
 | 
			
		||||
@@ -281,8 +283,8 @@ func main() {
 | 
			
		||||
			group.POST("variation", h.Variation)
 | 
			
		||||
			group.GET("jobs", h.JobList)
 | 
			
		||||
			group.GET("imgWall", h.ImgWall)
 | 
			
		||||
			group.POST("remove", h.Remove)
 | 
			
		||||
			group.POST("publish", h.Publish)
 | 
			
		||||
			group.GET("remove", h.Remove)
 | 
			
		||||
			group.GET("publish", h.Publish)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.SdJobHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/sd")
 | 
			
		||||
@@ -290,8 +292,8 @@ func main() {
 | 
			
		||||
			group.POST("image", h.Image)
 | 
			
		||||
			group.GET("jobs", h.JobList)
 | 
			
		||||
			group.GET("imgWall", h.ImgWall)
 | 
			
		||||
			group.POST("remove", h.Remove)
 | 
			
		||||
			group.POST("publish", h.Publish)
 | 
			
		||||
			group.GET("remove", h.Remove)
 | 
			
		||||
			group.GET("publish", h.Publish)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.ConfigHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/config/")
 | 
			
		||||
@@ -368,7 +370,6 @@ func main() {
 | 
			
		||||
			group := s.Engine.Group("/api/payment/")
 | 
			
		||||
			group.GET("doPay", h.DoPay)
 | 
			
		||||
			group.GET("payWays", h.GetPayWays)
 | 
			
		||||
			group.POST("query", h.OrderQuery)
 | 
			
		||||
			group.POST("qrcode", h.PayQrcode)
 | 
			
		||||
			group.POST("mobile", h.Mobile)
 | 
			
		||||
			group.POST("alipay/notify", h.AlipayNotify)
 | 
			
		||||
@@ -391,7 +392,8 @@ func main() {
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.OrderHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/order/")
 | 
			
		||||
			group.POST("list", h.List)
 | 
			
		||||
			group.GET("list", h.List)
 | 
			
		||||
			group.GET("query", h.Query)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.ProductHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/product/")
 | 
			
		||||
@@ -416,13 +418,6 @@ func main() {
 | 
			
		||||
			group.GET("token", h.GenToken)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		// 验证码
 | 
			
		||||
		fx.Provide(admin.NewCaptchaHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *admin.CaptchaHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/admin/login/")
 | 
			
		||||
			group.GET("captcha", h.GetCaptcha)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		fx.Provide(admin.NewUploadHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *admin.UploadHandler) {
 | 
			
		||||
			s.Engine.POST("/api/admin/upload", h.Upload)
 | 
			
		||||
@@ -477,8 +472,8 @@ func main() {
 | 
			
		||||
			group.POST("image", h.Image)
 | 
			
		||||
			group.GET("jobs", h.JobList)
 | 
			
		||||
			group.GET("imgWall", h.ImgWall)
 | 
			
		||||
			group.POST("remove", h.Remove)
 | 
			
		||||
			group.POST("publish", h.Publish)
 | 
			
		||||
			group.GET("remove", h.Remove)
 | 
			
		||||
			group.GET("publish", h.Publish)
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
 | 
			
		||||
			go func() {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import (
 | 
			
		||||
	"geekai/service/sd"
 | 
			
		||||
	"geekai/store"
 | 
			
		||||
	"geekai/store/model"
 | 
			
		||||
	"geekai/utils"
 | 
			
		||||
	"github.com/go-redis/redis/v8"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -58,13 +59,13 @@ func (p *ServicePool) InitServices(plusConfigs []types.MjPlusConfig, proxyConfig
 | 
			
		||||
	}
 | 
			
		||||
	p.services = make([]*Service, 0)
 | 
			
		||||
 | 
			
		||||
	for k, config := range plusConfigs {
 | 
			
		||||
	for _, config := range plusConfigs {
 | 
			
		||||
		if config.Enabled == false {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cli := NewPlusClient(config, p.licenseService)
 | 
			
		||||
		name := fmt.Sprintf("mj-plus-service-%d", k)
 | 
			
		||||
		name := utils.Md5(config.ApiURL)
 | 
			
		||||
		plusService := NewService(name, p.taskQueue, p.notifyQueue, p.db, cli)
 | 
			
		||||
		go func() {
 | 
			
		||||
			plusService.Run()
 | 
			
		||||
@@ -73,12 +74,12 @@ func (p *ServicePool) InitServices(plusConfigs []types.MjPlusConfig, proxyConfig
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// for mid-journey proxy
 | 
			
		||||
	for k, config := range proxyConfigs {
 | 
			
		||||
	for _, config := range proxyConfigs {
 | 
			
		||||
		if config.Enabled == false {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		cli := NewProxyClient(config)
 | 
			
		||||
		name := fmt.Sprintf("mj-proxy-service-%d", k)
 | 
			
		||||
		name := utils.Md5(config.ApiURL)
 | 
			
		||||
		proxyService := NewService(name, p.taskQueue, p.notifyQueue, p.db, cli)
 | 
			
		||||
		go func() {
 | 
			
		||||
			proxyService.Run()
 | 
			
		||||
@@ -209,7 +210,6 @@ func (p *ServicePool) SyncTaskProgress() {
 | 
			
		||||
					}
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if servicePlus := p.getService(job.ChannelId); servicePlus != nil {
 | 
			
		||||
					_ = servicePlus.Notify(job)
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,5 +15,7 @@ type User struct {
 | 
			
		||||
	Status      bool   `gorm:"default:true"` // 当前状态
 | 
			
		||||
	LastLoginAt int64  // 最后登录时间
 | 
			
		||||
	LastLoginIp string // 最后登录 IP
 | 
			
		||||
	OpenId      string `gorm:"column:openid"`
 | 
			
		||||
	Platform    string `json:"platform"`
 | 
			
		||||
	Vip         bool   // 是否 VIP 会员
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,4 +14,6 @@ type User struct {
 | 
			
		||||
	LastLoginAt int64    `json:"last_login_at"` // 最后登录时间
 | 
			
		||||
	LastLoginIp string   `json:"last_login_ip"` // 最后登录 IP
 | 
			
		||||
	Vip         bool     `json:"vip"`
 | 
			
		||||
	OpenId      string   `json:"openid"`   // 第三方登录 OpenID
 | 
			
		||||
	Platform    string   `json:"platform"` // 第三方登录平台
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,15 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"geekai/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	fmt.Println(fmt.Sprintf("%v", float64(90)/100))
 | 
			
		||||
	file := "http://nk.img.r9it.com/chatgpt-plus/1719389335351828.xlsx"
 | 
			
		||||
	content, err := utils.ReadFileContent(file, "http://172.22.11.69:9998")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println(content)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										106
									
								
								api/utils/file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,106 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/microcosm-cc/bluemonday"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-tika/tika"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ReadFileContent(filePath string, tikaHost string) (string, error) {
 | 
			
		||||
	// for remote file, download it first
 | 
			
		||||
	if strings.HasPrefix(filePath, "http") {
 | 
			
		||||
		file, err := downloadFile(filePath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
		filePath = file
 | 
			
		||||
	}
 | 
			
		||||
	// 创建 Tika 客户端
 | 
			
		||||
	client := tika.NewClient(nil, tikaHost)
 | 
			
		||||
	// 打开 PDF 文件
 | 
			
		||||
	file, err := os.Open(filePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with open file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	// 使用 Tika 提取 PDF 文件的文本内容
 | 
			
		||||
	content, err := client.Parse(context.TODO(), file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("error with parse file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ext := filepath.Ext(filePath)
 | 
			
		||||
	switch ext {
 | 
			
		||||
	case ".doc", ".docx", ".pdf", ".pptx", "ppt":
 | 
			
		||||
		return cleanBlankLine(cleanHtml(content, false)), nil
 | 
			
		||||
	case ".xls", ".xlsx":
 | 
			
		||||
		return cleanBlankLine(cleanHtml(content, true)), nil
 | 
			
		||||
	default:
 | 
			
		||||
		return cleanBlankLine(content), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 清理文本内容
 | 
			
		||||
func cleanHtml(html string, keepTable bool) string {
 | 
			
		||||
	// 清理 HTML 标签
 | 
			
		||||
	var policy *bluemonday.Policy
 | 
			
		||||
	if keepTable {
 | 
			
		||||
		policy = bluemonday.NewPolicy()
 | 
			
		||||
		policy.AllowElements("table", "thead", "tbody", "tfoot", "tr", "td", "th")
 | 
			
		||||
	} else {
 | 
			
		||||
		policy = bluemonday.StrictPolicy()
 | 
			
		||||
	}
 | 
			
		||||
	return policy.Sanitize(html)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cleanBlankLine(content string) string {
 | 
			
		||||
	lines := strings.Split(content, "\n")
 | 
			
		||||
	texts := make([]string, 0)
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
		line = strings.TrimSpace(line)
 | 
			
		||||
		if len(line) < 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// discard image
 | 
			
		||||
		if strings.HasSuffix(line, ".png") ||
 | 
			
		||||
			strings.HasSuffix(line, ".jpg") ||
 | 
			
		||||
			strings.HasSuffix(line, ".jpeg") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		texts = append(texts, line)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return strings.Join(texts, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 下载文件
 | 
			
		||||
func downloadFile(url string) (string, error) {
 | 
			
		||||
	base := filepath.Base(url)
 | 
			
		||||
	dir := os.TempDir()
 | 
			
		||||
	filename := filepath.Join(dir, base)
 | 
			
		||||
	out, err := os.Create(filename)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	defer out.Close()
 | 
			
		||||
 | 
			
		||||
	// 获取数据
 | 
			
		||||
	resp, err := http.Get(url)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	// 写入数据到文件
 | 
			
		||||
	_, err = io.Copy(out, resp.Body)
 | 
			
		||||
	return filename, err
 | 
			
		||||
}
 | 
			
		||||
@@ -24,28 +24,20 @@ func SUCCESS(c *gin.Context, values ...interface{}) {
 | 
			
		||||
 | 
			
		||||
func ERROR(c *gin.Context, messages ...string) {
 | 
			
		||||
	if messages != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: messages[0]})
 | 
			
		||||
		c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed, Message: messages[0]})
 | 
			
		||||
	} else {
 | 
			
		||||
		c.JSON(http.StatusOK, types.BizVo{Code: types.Failed})
 | 
			
		||||
		c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func HACKER(c *gin.Context) {
 | 
			
		||||
	c.JSON(http.StatusOK, types.BizVo{Code: types.Failed, Message: "Hacker attempt!!!"})
 | 
			
		||||
	c.JSON(http.StatusBadRequest, types.BizVo{Code: types.Failed, Message: "Hacker attempt!!!"})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NotAuth(c *gin.Context, messages ...string) {
 | 
			
		||||
	if messages != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: messages[0]})
 | 
			
		||||
		c.JSON(http.StatusUnauthorized, types.BizVo{Code: types.NotAuthorized, Message: messages[0]})
 | 
			
		||||
	} else {
 | 
			
		||||
		c.JSON(http.StatusOK, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NotPermission(c *gin.Context, messages ...string) {
 | 
			
		||||
	if messages != nil {
 | 
			
		||||
		c.JSON(http.StatusOK, types.BizVo{Code: types.NotPermission, Message: messages[0]})
 | 
			
		||||
	} else {
 | 
			
		||||
		c.JSON(http.StatusOK, types.BizVo{Code: types.NotPermission, Message: "Not Permission"})
 | 
			
		||||
		c.JSON(http.StatusUnauthorized, types.BizVo{Code: types.NotAuthorized, Message: "Not Authorized"})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,11 +31,11 @@ func RandString(length int) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RandomNumber(bit int) int {
 | 
			
		||||
	min := intPow(10, bit-1)
 | 
			
		||||
	max := intPow(10, bit) - 1
 | 
			
		||||
	minNum := intPow(10, bit-1)
 | 
			
		||||
	maxNum := intPow(10, bit) - 1
 | 
			
		||||
 | 
			
		||||
	rand.Seed(time.Now().UnixNano())
 | 
			
		||||
	return rand.Intn(max-min+1) + min
 | 
			
		||||
	rand.NewSource(time.Now().UnixNano())
 | 
			
		||||
	return rand.Intn(maxNum-minNum+1) + minNum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func intPow(x, y int) int {
 | 
			
		||||
@@ -46,7 +46,7 @@ func intPow(x, y int) int {
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ContainsStr(slice []string, item string) bool {
 | 
			
		||||
func Contains(slice []string, item string) bool {
 | 
			
		||||
	for _, e := range slice {
 | 
			
		||||
		if e == item {
 | 
			
		||||
			return true
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,7 @@ func GetImgExt(filename string) string {
 | 
			
		||||
	return ext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExtractImgURL(text string) []string {
 | 
			
		||||
func ExtractImgURLs(text string) []string {
 | 
			
		||||
	re := regexp.MustCompile(`(http[s]?:\/\/.*?\.(?:png|jpg|jpeg|gif))`)
 | 
			
		||||
	matches := re.FindAllStringSubmatch(text, 10)
 | 
			
		||||
	urls := make([]string, 0)
 | 
			
		||||
@@ -99,3 +99,15 @@ func ExtractImgURL(text string) []string {
 | 
			
		||||
	}
 | 
			
		||||
	return urls
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ExtractFileURLs(text string) []string {
 | 
			
		||||
	re := regexp.MustCompile(`(http[s]?:\/\/.*?\.(?:docx?|pdf|pptx?|xlsx?|txt))`)
 | 
			
		||||
	matches := re.FindAllStringSubmatch(text, 10)
 | 
			
		||||
	urls := make([]string, 0)
 | 
			
		||||
	if len(matches) > 0 {
 | 
			
		||||
		for _, m := range matches {
 | 
			
		||||
			urls = append(urls, m[1])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return urls
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,790 +0,0 @@
 | 
			
		||||
-- phpMyAdmin SQL Dump
 | 
			
		||||
-- version 5.1.3
 | 
			
		||||
-- https://www.phpmyadmin.net/
 | 
			
		||||
--
 | 
			
		||||
-- 主机: localhost:3307
 | 
			
		||||
-- 生成日期: 2024-04-07 10:30:00
 | 
			
		||||
-- 服务器版本: 8.0.33
 | 
			
		||||
-- PHP 版本: 8.1.18
 | 
			
		||||
 | 
			
		||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
 | 
			
		||||
START TRANSACTION;
 | 
			
		||||
SET time_zone = "+00:00";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 | 
			
		||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 | 
			
		||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
 | 
			
		||||
/*!40101 SET NAMES utf8mb4 */;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 数据库: `chatgpt_plus`
 | 
			
		||||
--
 | 
			
		||||
CREATE DATABASE IF NOT EXISTS `chatgpt_plus` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
 | 
			
		||||
USE `chatgpt_plus`;
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_admin_users`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_admin_users`;
 | 
			
		||||
CREATE TABLE `chatgpt_admin_users` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
 | 
			
		||||
  `password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
 | 
			
		||||
  `salt` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码盐',
 | 
			
		||||
  `status` tinyint(1) NOT NULL COMMENT '当前状态',
 | 
			
		||||
  `last_login_at` int NOT NULL COMMENT '最后登录时间',
 | 
			
		||||
  `last_login_ip` char(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '最后登录 IP',
 | 
			
		||||
  `created_at` datetime NOT NULL COMMENT '创建时间',
 | 
			
		||||
  `updated_at` datetime NOT NULL COMMENT '更新时间'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户' ROW_FORMAT=DYNAMIC;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_admin_users`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_admin_users` (`id`, `username`, `password`, `salt`, `status`, `last_login_at`, `last_login_ip`, `created_at`, `updated_at`) VALUES
 | 
			
		||||
(1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1712456145, '::1', '2024-03-11 16:30:20', '2024-04-07 10:15:45'),
 | 
			
		||||
(108, 'test', '9ed720ce03e0a69885455271b4b3e1710bff79434f2a95d0de6406dd88cc9f79', '4b9orqjh', 0, 1710396975, '::1', '2024-03-13 16:06:43', '2024-03-21 15:15:04');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_api_keys`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_api_keys`;
 | 
			
		||||
CREATE TABLE `chatgpt_api_keys` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `platform` char(20) DEFAULT NULL COMMENT '平台',
 | 
			
		||||
  `name` varchar(30) DEFAULT NULL COMMENT '名称',
 | 
			
		||||
  `value` varchar(100) NOT NULL COMMENT 'API KEY value',
 | 
			
		||||
  `type` varchar(10) NOT NULL DEFAULT 'chat' COMMENT '用途(chat=>聊天,img=>图片)',
 | 
			
		||||
  `last_used_at` int NOT NULL COMMENT '最后使用时间',
 | 
			
		||||
  `api_url` varchar(255) DEFAULT NULL COMMENT 'API 地址',
 | 
			
		||||
  `enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
 | 
			
		||||
  `proxy_url` varchar(100) DEFAULT NULL COMMENT '代理地址',
 | 
			
		||||
  `created_at` datetime NOT NULL,
 | 
			
		||||
  `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OpenAI API ';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_chat_history`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_chat_history`;
 | 
			
		||||
CREATE TABLE `chatgpt_chat_history` (
 | 
			
		||||
  `id` bigint NOT NULL,
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
  `chat_id` char(40) NOT NULL COMMENT '会话 ID',
 | 
			
		||||
  `type` varchar(10) NOT NULL COMMENT '类型:prompt|reply',
 | 
			
		||||
  `icon` varchar(100) NOT NULL COMMENT '角色图标',
 | 
			
		||||
  `role_id` int NOT NULL COMMENT '角色 ID',
 | 
			
		||||
  `model` varchar(30) DEFAULT NULL COMMENT '模型名称',
 | 
			
		||||
  `content` text NOT NULL COMMENT '聊天内容',
 | 
			
		||||
  `tokens` smallint NOT NULL COMMENT '耗费 token 数量',
 | 
			
		||||
  `use_context` tinyint(1) NOT NULL COMMENT '是否允许作为上下文语料',
 | 
			
		||||
  `created_at` datetime NOT NULL,
 | 
			
		||||
  `updated_at` datetime NOT NULL,
 | 
			
		||||
  `deleted_at` datetime DEFAULT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天历史记录';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_chat_items`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_chat_items`;
 | 
			
		||||
CREATE TABLE `chatgpt_chat_items` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `chat_id` char(40) NOT NULL COMMENT '会话 ID',
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
  `role_id` int NOT NULL COMMENT '角色 ID',
 | 
			
		||||
  `title` varchar(100) NOT NULL COMMENT '会话标题',
 | 
			
		||||
  `model_id` int NOT NULL DEFAULT '0' COMMENT '模型 ID',
 | 
			
		||||
  `model` varchar(30) DEFAULT NULL COMMENT '模型名称',
 | 
			
		||||
  `created_at` datetime NOT NULL COMMENT '创建时间',
 | 
			
		||||
  `updated_at` datetime NOT NULL COMMENT '更新时间',
 | 
			
		||||
  `deleted_at` datetime DEFAULT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户会话列表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_chat_models`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_chat_models`;
 | 
			
		||||
CREATE TABLE `chatgpt_chat_models` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `platform` varchar(20) DEFAULT NULL COMMENT '模型平台',
 | 
			
		||||
  `name` varchar(50) NOT NULL COMMENT '模型名称',
 | 
			
		||||
  `value` varchar(50) NOT NULL COMMENT '模型值',
 | 
			
		||||
  `sort_num` tinyint(1) NOT NULL COMMENT '排序数字',
 | 
			
		||||
  `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用模型',
 | 
			
		||||
  `power` tinyint NOT NULL COMMENT '消耗算力点数',
 | 
			
		||||
  `temperature` float(3,1) NOT NULL DEFAULT '1.0' COMMENT '模型创意度',
 | 
			
		||||
  `max_tokens` int NOT NULL DEFAULT '1024' COMMENT '最大响应长度',
 | 
			
		||||
  `max_context` int NOT NULL DEFAULT '4096' COMMENT '最大上下文长度',
 | 
			
		||||
  `open` tinyint(1) NOT NULL COMMENT '是否开放模型',
 | 
			
		||||
  `created_at` datetime DEFAULT NULL,
 | 
			
		||||
  `updated_at` datetime DEFAULT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI 模型表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_chat_models`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num`, `enabled`, `power`, `temperature`, `max_tokens`, `max_context`, `open`, `created_at`, `updated_at`) VALUES
 | 
			
		||||
(1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo-0125', 0, 1, 1, 1.0, 1024, 4096, 1, '2023-08-23 12:06:36', '2024-03-18 15:43:51'),
 | 
			
		||||
(2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 14, 1, 1, 1.0, 1024, 4096, 0, '2023-08-23 12:15:30', '2024-03-18 14:27:19'),
 | 
			
		||||
(3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 3, 1, 1, 1.0, 2048, 32768, 1, '2023-08-23 13:35:45', '2024-03-18 14:27:19'),
 | 
			
		||||
(7, 'Baidu', '文心一言3.0', 'eb-instant', 12, 1, 1, 1.0, 1024, 4096, 1, '2023-10-11 11:29:28', '2024-03-18 14:27:19'),
 | 
			
		||||
(8, 'XunFei', '星火V3.5', 'generalv3.5', 2, 1, 5, 0.8, 1024, 8192, 1, '2023-10-11 15:48:30', '2024-03-18 14:27:19'),
 | 
			
		||||
(9, 'XunFei', '星火V2.0', 'generalv2', 11, 1, 1, 1.0, 1024, 8192, 1, '2023-10-11 15:48:45', '2024-03-18 14:27:19'),
 | 
			
		||||
(10, 'Baidu', '文心一言4.0', 'completions_pro', 13, 1, 3, 1.0, 1024, 8192, 1, '2023-10-25 08:31:37', '2024-03-18 14:27:19'),
 | 
			
		||||
(11, 'OpenAI', 'GPT-4.0', 'gpt-4-0125-preview', 1, 1, 15, 1.0, 1024, 8192, 1, '2023-10-25 08:45:15', '2024-03-18 15:46:58'),
 | 
			
		||||
(12, 'XunFei', '星火v3.0', 'generalv3', 10, 1, 3, 1.0, 1024, 8192, 1, '2023-11-23 09:20:33', '2024-03-18 14:27:19'),
 | 
			
		||||
(15, 'OpenAI', 'GPT-超级模型', 'gpt-4-all', 4, 1, 30, 1.0, 4096, 32768, 0, '2024-01-15 11:32:52', '2024-03-18 14:27:19'),
 | 
			
		||||
(16, 'OpenAI', '视频号导师', 'gpt-4-gizmo-g-QXXEBTXl7', 5, 1, 30, 1.0, 4096, 32768, 0, '2024-01-15 14:46:35', '2024-03-18 14:29:39'),
 | 
			
		||||
(17, 'QWen', '通义千问-Turbo', 'qwen-turbo', 7, 1, 1, 1.0, 1024, 8192, 1, '2024-01-19 10:42:24', '2024-03-18 14:27:19'),
 | 
			
		||||
(18, 'QWen', '通义千问-Plus', 'qwen-plus', 8, 1, 1, 1.0, 1024, 32768, 1, '2024-01-19 10:42:49', '2024-03-18 14:27:19'),
 | 
			
		||||
(19, 'QWen', '通义千问-Max', 'qwen-max-1201', 9, 1, 1, 1.0, 1024, 32768, 1, '2024-01-19 10:51:03', '2024-03-18 14:27:19'),
 | 
			
		||||
(21, 'OpenAI', '董宇辉小作文助手', 'gpt-4-gizmo-g-dse9iXvor', 6, 1, 30, 1.0, 8192, 32768, 0, '2024-03-18 14:24:20', '2024-03-18 14:27:19'),
 | 
			
		||||
(22, 'OpenAI', 'LOGO生成神器', 'gpt-4-gizmo-g-YL87j8C7S', 0, 1, 30, 1.0, 1024, 4096, 1, '2024-03-20 14:02:11', '2024-03-20 14:02:18'),
 | 
			
		||||
(23, 'OpenAI', '音乐生成器', 'suno-v3', 0, 1, 50, 0.8, 1024, 4096, 1, '2024-03-29 15:43:40', '2024-03-29 15:45:15'),
 | 
			
		||||
(24, 'OpenAI', '通义千问(中转)', 'qwen-plus', 0, 1, 0, 1.0, 1024, 4096, 1, '2024-04-03 12:00:46', '2024-04-03 12:00:46');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_chat_roles`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_chat_roles`;
 | 
			
		||||
CREATE TABLE `chatgpt_chat_roles` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `name` varchar(30) NOT NULL COMMENT '角色名称',
 | 
			
		||||
  `marker` varchar(30) NOT NULL COMMENT '角色标识',
 | 
			
		||||
  `context_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色语料 json',
 | 
			
		||||
  `hello_msg` varchar(255) NOT NULL COMMENT '打招呼信息',
 | 
			
		||||
  `icon` varchar(255) NOT NULL COMMENT '角色图标',
 | 
			
		||||
  `enable` tinyint(1) NOT NULL COMMENT '是否被启用',
 | 
			
		||||
  `sort_num` smallint NOT NULL DEFAULT '0' COMMENT '角色排序',
 | 
			
		||||
  `created_at` datetime NOT NULL,
 | 
			
		||||
  `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天角色表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_chat_roles`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_chat_roles` (`id`, `name`, `marker`, `context_json`, `hello_msg`, `icon`, `enable`, `sort_num`, `created_at`, `updated_at`) VALUES
 | 
			
		||||
(1, '通用AI助手', 'gpt', '', '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 0, '2023-05-30 07:02:06', '2024-03-15 09:15:42'),
 | 
			
		||||
(24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 3, '2023-05-30 14:10:24', '2023-12-29 17:46:45'),
 | 
			
		||||
(25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 2, '2023-05-30 14:10:24', '2023-12-29 17:46:44'),
 | 
			
		||||
(26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 4, '2023-05-30 14:10:24', '2023-12-29 17:46:45'),
 | 
			
		||||
(27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 1, '2023-05-30 14:10:24', '2023-12-29 17:46:43'),
 | 
			
		||||
(28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 5, '2023-05-30 14:10:24', '2023-12-29 17:46:46'),
 | 
			
		||||
(29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装:¥1188/箱,和系列 6 瓶装:¥2208/箱,贵系列 6 瓶装:¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 8, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
 | 
			
		||||
(30, '英语陪练员', 'english_trainer', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。\"},{\"role\":\"assistant\",\"content\":\"Okay, let\'s start our conversation practice! What\'s your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)\"}]', 'Okay, let\'s start our conversation practice! What\'s your name?', '/images/avatar/english_trainer.jpg', 1, 6, '2023-05-30 14:10:24', '2023-12-29 17:46:47'),
 | 
			
		||||
(31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 7, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
 | 
			
		||||
(32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 9, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
 | 
			
		||||
(33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 10, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
 | 
			
		||||
(34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 11, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
 | 
			
		||||
(35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 12, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
 | 
			
		||||
(36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 13, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
 | 
			
		||||
(37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 14, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
 | 
			
		||||
(38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 15, '2023-05-30 14:10:24', '2023-12-29 17:43:53'),
 | 
			
		||||
(39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 16, '2023-05-30 14:10:24', '2023-12-29 17:43:53');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_configs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_configs`;
 | 
			
		||||
CREATE TABLE `chatgpt_configs` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `marker` varchar(20) NOT NULL COMMENT '标识',
 | 
			
		||||
  `config_json` text NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_configs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES
 | 
			
		||||
(1, 'system', '{\"title\":\"Geek-AI创作系统\",\"admin_title\":\"Geek-AI控制台\",\"logo\":\"/images/logo.png\",\"init_power\":100,\"daily_power\":99,\"invite_power\":10,\"vip_month_power\":1000,\"register_ways\":[\"mobile\",\"username\",\"email\"],\"enabled_register\":true,\"reward_img\":\"http://localhost:5678/static/upload/2024/3/1710753716309668.jpg\",\"enabled_reward\":true,\"power_price\":0.1,\"order_pay_timeout\":1800,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[11,7,1,10,12,19,18,17,3],\"mj_power\":20,\"mj_action_power\":10,\"sd_power\":5,\"dall_power\":15,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4}'),
 | 
			
		||||
(3, 'notice', '{\"content\":\"系统每日会给免费会员赠送10算力值,用完请第二天再来领取。\\n## v4.0.2 更新日志\\n* 功能新增:支持前端菜单可以配置\\n* 功能优化:在登录和注册界面标题显示软件版本号\\n* 功能优化:MJ 绘画支持 --sref 和 --cref 图片一致性参数\\n* 功能优化:使用 leveldb 解决 SD 绘图进度图片预览问题\\n* Bug修复:解决因为图片上传使用相对路径而导致融图失败的问题\\n* 功能新增:手机端支持 Stable-Diffusion 绘画\\n* Bug修复:修复管理后台 API KEY 删除失败的问题\\n\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n   style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e (支持MidJourney,GPT,Claude,Google Gemmi 各种表格模型) 或者 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n             style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e(不支持 Midjourney) 购买,现在有超级优惠,价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://ai.r9it.com/docs/install/errors-handle.html#%E8%B0%83%E7%94%A8%E4%B8%AD%E8%BD%AC-api-%E6%8A%A5%E9%94%99%E6%97%A0%E5%8F%AF%E7%94%A8%E6%B8%A0%E9%81%93)。\\nGPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程: \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n             style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_files`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_files`;
 | 
			
		||||
CREATE TABLE `chatgpt_files` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
  `name` varchar(100) NOT NULL COMMENT '文件名',
 | 
			
		||||
  `obj_key` varchar(100) DEFAULT NULL COMMENT '文件标识',
 | 
			
		||||
  `url` varchar(255) NOT NULL COMMENT '文件地址',
 | 
			
		||||
  `ext` varchar(10) NOT NULL COMMENT '文件后缀',
 | 
			
		||||
  `size` bigint NOT NULL DEFAULT '0' COMMENT '文件大小',
 | 
			
		||||
  `created_at` datetime NOT NULL COMMENT '创建时间'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户文件表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_functions`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_functions`;
 | 
			
		||||
CREATE TABLE `chatgpt_functions` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `name` varchar(30) NOT NULL COMMENT '函数名称',
 | 
			
		||||
  `label` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '函数标签',
 | 
			
		||||
  `description` varchar(255) DEFAULT NULL COMMENT '函数描述',
 | 
			
		||||
  `parameters` text COMMENT '函数参数(JSON)',
 | 
			
		||||
  `token` varchar(255) DEFAULT NULL COMMENT 'API授权token',
 | 
			
		||||
  `action` varchar(255) DEFAULT NULL COMMENT '函数处理 API',
 | 
			
		||||
  `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='函数插件表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_functions`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `token`, `action`, `enabled`) VALUES
 | 
			
		||||
(1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 0),
 | 
			
		||||
(2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 0),
 | 
			
		||||
(3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 0);
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_invite_codes`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_invite_codes`;
 | 
			
		||||
CREATE TABLE `chatgpt_invite_codes` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户ID',
 | 
			
		||||
  `code` char(8) NOT NULL COMMENT '邀请码',
 | 
			
		||||
  `hits` int NOT NULL COMMENT '点击次数',
 | 
			
		||||
  `reg_num` smallint NOT NULL COMMENT '注册数量',
 | 
			
		||||
  `created_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户邀请码';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_invite_logs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_invite_logs`;
 | 
			
		||||
CREATE TABLE `chatgpt_invite_logs` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `inviter_id` int NOT NULL COMMENT '邀请人ID',
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '注册用户ID',
 | 
			
		||||
  `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
 | 
			
		||||
  `invite_code` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邀请码',
 | 
			
		||||
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
 | 
			
		||||
  `created_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_menus`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_menus`;
 | 
			
		||||
CREATE TABLE `chatgpt_menus` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `name` varchar(30) NOT NULL COMMENT '菜单名称',
 | 
			
		||||
  `icon` varchar(150) NOT NULL COMMENT '菜单图标',
 | 
			
		||||
  `url` varchar(100) NOT NULL COMMENT '地址',
 | 
			
		||||
  `sort_num` smallint NOT NULL COMMENT '排序',
 | 
			
		||||
  `enabled` tinyint(1) NOT NULL COMMENT '是否启用'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='前端菜单表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_menus`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES
 | 
			
		||||
(1, '对话聊天', '/images/menu/chat.png', '/chat', 0, 1),
 | 
			
		||||
(5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 1, 1),
 | 
			
		||||
(6, 'SD 绘画', '/images/menu/sd.png', '/sd', 2, 1),
 | 
			
		||||
(7, '算力日志', '/images/menu/log.png', '/powerLog', 5, 1),
 | 
			
		||||
(8, '应用中心', '/images/menu/app.png', '/apps', 3, 1),
 | 
			
		||||
(9, '作品展示', '/images/menu/img-wall.png', '/images-wall', 4, 1),
 | 
			
		||||
(10, '会员计划', '/images/menu/member.png', '/member', 6, 1),
 | 
			
		||||
(11, '分享计划', '/images/menu/share.png', '/invite', 7, 1);
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_mj_jobs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_mj_jobs`;
 | 
			
		||||
CREATE TABLE `chatgpt_mj_jobs` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
  `task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID',
 | 
			
		||||
  `type` varchar(20) DEFAULT 'image' COMMENT '任务类别',
 | 
			
		||||
  `message_id` char(40) NOT NULL COMMENT '消息 ID',
 | 
			
		||||
  `channel_id` char(40) DEFAULT NULL COMMENT '频道ID',
 | 
			
		||||
  `reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID',
 | 
			
		||||
  `prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
 | 
			
		||||
  `img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL',
 | 
			
		||||
  `org_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原始图片地址',
 | 
			
		||||
  `hash` varchar(100) DEFAULT NULL COMMENT 'message hash',
 | 
			
		||||
  `progress` smallint DEFAULT '0' COMMENT '任务进度',
 | 
			
		||||
  `use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代',
 | 
			
		||||
  `publish` tinyint(1) NOT NULL COMMENT '是否发布',
 | 
			
		||||
  `err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
 | 
			
		||||
  `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
 | 
			
		||||
  `created_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_orders`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_orders`;
 | 
			
		||||
CREATE TABLE `chatgpt_orders` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户ID',
 | 
			
		||||
  `product_id` int NOT NULL COMMENT '产品ID',
 | 
			
		||||
  `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户明',
 | 
			
		||||
  `order_no` varchar(30) NOT NULL COMMENT '订单ID',
 | 
			
		||||
  `trade_no` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付平台交易流水号',
 | 
			
		||||
  `subject` varchar(100) NOT NULL COMMENT '订单产品',
 | 
			
		||||
  `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
 | 
			
		||||
  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态(0:待支付,1:已扫码,2:支付失败)',
 | 
			
		||||
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
 | 
			
		||||
  `pay_time` int DEFAULT NULL COMMENT '支付时间',
 | 
			
		||||
  `pay_way` varchar(20) NOT NULL COMMENT '支付方式',
 | 
			
		||||
  `created_at` datetime NOT NULL,
 | 
			
		||||
  `updated_at` datetime NOT NULL,
 | 
			
		||||
  `deleted_at` datetime DEFAULT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='充值订单表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_power_logs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_power_logs`;
 | 
			
		||||
CREATE TABLE `chatgpt_power_logs` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户ID',
 | 
			
		||||
  `username` varchar(30) NOT NULL COMMENT '用户名',
 | 
			
		||||
  `type` tinyint(1) NOT NULL COMMENT '类型(1:充值,2:消费,3:退费)',
 | 
			
		||||
  `amount` smallint NOT NULL COMMENT '算力数值',
 | 
			
		||||
  `balance` int NOT NULL COMMENT '余额',
 | 
			
		||||
  `model` varchar(30) NOT NULL COMMENT '模型',
 | 
			
		||||
  `remark` varchar(255) NOT NULL COMMENT '备注',
 | 
			
		||||
  `mark` tinyint(1) NOT NULL COMMENT '资金类型(0:支出,1:收入)',
 | 
			
		||||
  `created_at` datetime NOT NULL COMMENT '创建时间'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户算力消费日志';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_products`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_products`;
 | 
			
		||||
CREATE TABLE `chatgpt_products` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `name` varchar(30) NOT NULL COMMENT '名称',
 | 
			
		||||
  `price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
 | 
			
		||||
  `discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额',
 | 
			
		||||
  `days` smallint NOT NULL DEFAULT '0' COMMENT '延长天数',
 | 
			
		||||
  `power` int NOT NULL DEFAULT '0' COMMENT '增加算力值',
 | 
			
		||||
  `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启动',
 | 
			
		||||
  `sales` int NOT NULL DEFAULT '0' COMMENT '销量',
 | 
			
		||||
  `sort_num` tinyint NOT NULL DEFAULT '0' COMMENT '排序',
 | 
			
		||||
  `created_at` datetime NOT NULL,
 | 
			
		||||
  `updated_at` datetime NOT NULL,
 | 
			
		||||
  `app_url` varchar(255) DEFAULT NULL COMMENT 'App跳转地址',
 | 
			
		||||
  `url` varchar(255) DEFAULT NULL COMMENT '跳转地址'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_rewards`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_rewards`;
 | 
			
		||||
CREATE TABLE `chatgpt_rewards` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
  `tx_id` char(36) NOT NULL COMMENT '交易 ID',
 | 
			
		||||
  `amount` decimal(10,2) NOT NULL COMMENT '打赏金额',
 | 
			
		||||
  `remark` varchar(80) NOT NULL COMMENT '备注',
 | 
			
		||||
  `status` tinyint(1) NOT NULL COMMENT '核销状态,0:未核销,1:已核销',
 | 
			
		||||
  `exchange` varchar(255) NOT NULL COMMENT '兑换详情(json)',
 | 
			
		||||
  `created_at` datetime NOT NULL,
 | 
			
		||||
  `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户打赏';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_sd_jobs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_sd_jobs`;
 | 
			
		||||
CREATE TABLE `chatgpt_sd_jobs` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
  `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'txt2img' COMMENT '任务类别',
 | 
			
		||||
  `task_id` char(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务 ID',
 | 
			
		||||
  `prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
 | 
			
		||||
  `img_url` varchar(255) DEFAULT NULL COMMENT '图片URL',
 | 
			
		||||
  `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '绘画参数json',
 | 
			
		||||
  `progress` smallint DEFAULT '0' COMMENT '任务进度',
 | 
			
		||||
  `publish` tinyint(1) NOT NULL COMMENT '是否发布',
 | 
			
		||||
  `err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
 | 
			
		||||
  `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
 | 
			
		||||
  `created_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Stable Diffusion 任务表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_users`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_users`;
 | 
			
		||||
CREATE TABLE `chatgpt_users` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
 | 
			
		||||
  `nickname` varchar(30) NOT NULL COMMENT '昵称',
 | 
			
		||||
  `password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
 | 
			
		||||
  `avatar` varchar(100) NOT NULL COMMENT '头像',
 | 
			
		||||
  `salt` char(12) NOT NULL COMMENT '密码盐',
 | 
			
		||||
  `power` int NOT NULL DEFAULT '0' COMMENT '剩余算力',
 | 
			
		||||
  `expired_time` int NOT NULL COMMENT '用户过期时间',
 | 
			
		||||
  `status` tinyint(1) NOT NULL COMMENT '当前状态',
 | 
			
		||||
  `chat_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天配置json',
 | 
			
		||||
  `chat_roles_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天角色 json',
 | 
			
		||||
  `chat_models_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'AI模型 json',
 | 
			
		||||
  `last_login_at` int NOT NULL COMMENT '最后登录时间',
 | 
			
		||||
  `vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否会员',
 | 
			
		||||
  `last_login_ip` char(16) NOT NULL COMMENT '最后登录 IP',
 | 
			
		||||
  `created_at` datetime NOT NULL,
 | 
			
		||||
  `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_users`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_users` (`id`, `username`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `created_at`, `updated_at`) VALUES
 | 
			
		||||
    (4, '18575670125', '极客学长@830270', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/2/1708682650912429.png', 'ueedue5l', 9384, 1717292086, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"programmer\",\"seller\"]', '[1,11]', 1711698298, 1, '::1', '2023-06-12 16:47:17', '2024-03-29 15:44:58');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_user_login_logs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_user_login_logs`;
 | 
			
		||||
CREATE TABLE `chatgpt_user_login_logs` (
 | 
			
		||||
  `id` int NOT NULL,
 | 
			
		||||
  `user_id` int NOT NULL COMMENT '用户ID',
 | 
			
		||||
  `username` varchar(30) NOT NULL COMMENT '用户名',
 | 
			
		||||
  `login_ip` char(16) NOT NULL COMMENT '登录IP',
 | 
			
		||||
  `login_address` varchar(30) NOT NULL COMMENT '登录地址',
 | 
			
		||||
  `created_at` datetime NOT NULL,
 | 
			
		||||
  `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录日志';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转储表的索引
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_admin_users`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_admin_users`
 | 
			
		||||
  ADD PRIMARY KEY (`id`) USING BTREE,
 | 
			
		||||
  ADD UNIQUE KEY `username` (`username`) USING BTREE;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_api_keys`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_api_keys`
 | 
			
		||||
  ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_chat_history`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_history`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD KEY `chat_id` (`chat_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_chat_items`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_items`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `chat_id` (`chat_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_chat_models`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_models`
 | 
			
		||||
  ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_chat_roles`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_roles`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `marker` (`marker`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_configs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_configs`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `marker` (`marker`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_files`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_files`
 | 
			
		||||
  ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_functions`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_functions`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `name` (`name`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_invite_codes`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_invite_codes`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `code` (`code`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_invite_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_invite_logs`
 | 
			
		||||
  ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_menus`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_menus`
 | 
			
		||||
  ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_mj_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `task_id` (`task_id`),
 | 
			
		||||
  ADD KEY `message_id` (`message_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_orders`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_orders`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `order_no` (`order_no`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_power_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_power_logs`
 | 
			
		||||
  ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_products`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_products`
 | 
			
		||||
  ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_rewards`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_rewards`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `tx_id` (`tx_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_sd_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_sd_jobs`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `task_id` (`task_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_users`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_users`
 | 
			
		||||
  ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `username` (`username`),
 | 
			
		||||
  ADD UNIQUE KEY `username_2` (`username`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_user_login_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_user_login_logs`
 | 
			
		||||
  ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 在导出的表使用AUTO_INCREMENT
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_admin_users`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_admin_users`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=113;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_api_keys`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_api_keys`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_history`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_history`
 | 
			
		||||
  MODIFY `id` bigint NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_items`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_items`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_models`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_models`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=25;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_roles`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=130;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_configs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_configs`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_files`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_files`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_functions`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_functions`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_invite_codes`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_invite_codes`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_invite_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_invite_logs`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_menus`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_menus`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=12;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_mj_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_orders`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_orders`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_power_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_power_logs`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_products`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_products`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_rewards`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_rewards`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_sd_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_sd_jobs`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_users`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_users`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_user_login_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_user_login_logs`
 | 
			
		||||
  MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
COMMIT;
 | 
			
		||||
 | 
			
		||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
 | 
			
		||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
 | 
			
		||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
 | 
			
		||||
							
								
								
									
										861
									
								
								database/chatgpt_plus-v4.1.0.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,861 @@
 | 
			
		||||
-- phpMyAdmin SQL Dump
 | 
			
		||||
-- version 5.2.1
 | 
			
		||||
-- https://www.phpmyadmin.net/
 | 
			
		||||
--
 | 
			
		||||
-- 主机: 127.0.0.1
 | 
			
		||||
-- 生成日期: 2024-07-12 17:11:22
 | 
			
		||||
-- 服务器版本: 8.0.33
 | 
			
		||||
-- PHP 版本: 8.1.2-1ubuntu2.18
 | 
			
		||||
 | 
			
		||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
 | 
			
		||||
START TRANSACTION;
 | 
			
		||||
SET time_zone = "+00:00";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 | 
			
		||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 | 
			
		||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
 | 
			
		||||
/*!40101 SET NAMES utf8mb4 */;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 数据库: `chatgpt_plus`
 | 
			
		||||
--
 | 
			
		||||
CREATE DATABASE IF NOT EXISTS `chatgpt_plus` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
 | 
			
		||||
USE `chatgpt_plus`;
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_admin_users`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_admin_users`;
 | 
			
		||||
CREATE TABLE `chatgpt_admin_users` (
 | 
			
		||||
                                       `id` int NOT NULL,
 | 
			
		||||
                                       `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
 | 
			
		||||
                                       `password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
 | 
			
		||||
                                       `salt` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码盐',
 | 
			
		||||
                                       `status` tinyint(1) NOT NULL COMMENT '当前状态',
 | 
			
		||||
                                       `last_login_at` int NOT NULL COMMENT '最后登录时间',
 | 
			
		||||
                                       `last_login_ip` char(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '最后登录 IP',
 | 
			
		||||
                                       `created_at` datetime NOT NULL COMMENT '创建时间',
 | 
			
		||||
                                       `updated_at` datetime NOT NULL COMMENT '更新时间'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统用户' ROW_FORMAT=DYNAMIC;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_admin_users`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_admin_users` (`id`, `username`, `password`, `salt`, `status`, `last_login_at`, `last_login_ip`, `created_at`, `updated_at`) VALUES
 | 
			
		||||
    (1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1719818809, '172.22.11.200', '2024-03-11 16:30:20', '2024-07-01 15:26:49');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_api_keys`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_api_keys`;
 | 
			
		||||
CREATE TABLE `chatgpt_api_keys` (
 | 
			
		||||
                                    `id` int NOT NULL,
 | 
			
		||||
                                    `platform` char(20) DEFAULT NULL COMMENT '平台',
 | 
			
		||||
                                    `name` varchar(30) DEFAULT NULL COMMENT '名称',
 | 
			
		||||
                                    `value` varchar(100) NOT NULL COMMENT 'API KEY value',
 | 
			
		||||
                                    `type` varchar(10) NOT NULL DEFAULT 'chat' COMMENT '用途(chat=>聊天,img=>图片)',
 | 
			
		||||
                                    `last_used_at` int NOT NULL COMMENT '最后使用时间',
 | 
			
		||||
                                    `api_url` varchar(255) DEFAULT NULL COMMENT 'API 地址',
 | 
			
		||||
                                    `enabled` tinyint(1) DEFAULT NULL COMMENT '是否启用',
 | 
			
		||||
                                    `proxy_url` varchar(100) DEFAULT NULL COMMENT '代理地址',
 | 
			
		||||
                                    `created_at` datetime NOT NULL,
 | 
			
		||||
                                    `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OpenAI API ';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_chat_history`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_chat_history`;
 | 
			
		||||
CREATE TABLE `chatgpt_chat_history` (
 | 
			
		||||
                                        `id` bigint NOT NULL,
 | 
			
		||||
                                        `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
                                        `chat_id` char(40) NOT NULL COMMENT '会话 ID',
 | 
			
		||||
                                        `type` varchar(10) NOT NULL COMMENT '类型:prompt|reply',
 | 
			
		||||
                                        `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标',
 | 
			
		||||
                                        `role_id` int NOT NULL COMMENT '角色 ID',
 | 
			
		||||
                                        `model` varchar(30) DEFAULT NULL COMMENT '模型名称',
 | 
			
		||||
                                        `content` text NOT NULL COMMENT '聊天内容',
 | 
			
		||||
                                        `tokens` smallint NOT NULL COMMENT '耗费 token 数量',
 | 
			
		||||
                                        `use_context` tinyint(1) NOT NULL COMMENT '是否允许作为上下文语料',
 | 
			
		||||
                                        `created_at` datetime NOT NULL,
 | 
			
		||||
                                        `updated_at` datetime NOT NULL,
 | 
			
		||||
                                        `deleted_at` datetime DEFAULT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天历史记录';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_chat_items`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_chat_items`;
 | 
			
		||||
CREATE TABLE `chatgpt_chat_items` (
 | 
			
		||||
                                      `id` int NOT NULL,
 | 
			
		||||
                                      `chat_id` char(40) NOT NULL COMMENT '会话 ID',
 | 
			
		||||
                                      `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
                                      `role_id` int NOT NULL COMMENT '角色 ID',
 | 
			
		||||
                                      `title` varchar(100) NOT NULL COMMENT '会话标题',
 | 
			
		||||
                                      `model_id` int NOT NULL DEFAULT '0' COMMENT '模型 ID',
 | 
			
		||||
                                      `model` varchar(30) DEFAULT NULL COMMENT '模型名称',
 | 
			
		||||
                                      `created_at` datetime NOT NULL COMMENT '创建时间',
 | 
			
		||||
                                      `updated_at` datetime NOT NULL COMMENT '更新时间',
 | 
			
		||||
                                      `deleted_at` datetime DEFAULT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户会话列表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_chat_models`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_chat_models`;
 | 
			
		||||
CREATE TABLE `chatgpt_chat_models` (
 | 
			
		||||
                                       `id` int NOT NULL,
 | 
			
		||||
                                       `platform` varchar(20) DEFAULT NULL COMMENT '模型平台',
 | 
			
		||||
                                       `name` varchar(50) NOT NULL COMMENT '模型名称',
 | 
			
		||||
                                       `value` varchar(50) NOT NULL COMMENT '模型值',
 | 
			
		||||
                                       `sort_num` tinyint(1) NOT NULL COMMENT '排序数字',
 | 
			
		||||
                                       `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用模型',
 | 
			
		||||
                                       `power` smallint NOT NULL COMMENT '消耗算力点数',
 | 
			
		||||
                                       `temperature` float(3,1) NOT NULL DEFAULT '1.0' COMMENT '模型创意度',
 | 
			
		||||
  `max_tokens` int NOT NULL DEFAULT '1024' COMMENT '最大响应长度',
 | 
			
		||||
  `max_context` int NOT NULL DEFAULT '4096' COMMENT '最大上下文长度',
 | 
			
		||||
  `open` tinyint(1) NOT NULL COMMENT '是否开放模型',
 | 
			
		||||
  `key_id` int NOT NULL COMMENT '绑定API KEY ID',
 | 
			
		||||
  `created_at` datetime DEFAULT NULL,
 | 
			
		||||
  `updated_at` datetime DEFAULT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI 模型表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_chat_models`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num`, `enabled`, `power`, `temperature`, `max_tokens`, `max_context`, `open`, `key_id`, `created_at`, `updated_at`) VALUES
 | 
			
		||||
                                                                                                                                                                                                    (1, 'OpenAI', 'GPT-3.5', 'gpt-3.5-turbo', 1, 1, 0, 1.0, 1024, 16384, 1, 44, '2023-08-23 12:06:36', '2024-07-04 15:17:47'),
 | 
			
		||||
                                                                                                                                                                                                    (2, 'Azure', 'Azure-3.5', 'gpt-3.5-turbo', 23, 1, 1, 1.0, 1024, 4096, 0, 0, '2023-08-23 12:15:30', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (3, 'ChatGLM', 'ChatGML-Pro', 'chatglm_pro', 12, 1, 1, 1.0, 2048, 32768, 1, 0, '2023-08-23 13:35:45', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (7, 'Baidu', '文心一言3.0', 'eb-instant', 21, 1, 1, 1.0, 1024, 4096, 1, 0, '2023-10-11 11:29:28', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (8, 'XunFei', '星火V3.5', 'generalv3.5', 11, 1, 5, 0.8, 1024, 8192, 1, 0, '2023-10-11 15:48:30', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (9, 'XunFei', '星火V2.0', 'generalv2', 20, 1, 1, 1.0, 1024, 8192, 1, 0, '2023-10-11 15:48:45', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (10, 'Baidu', '文心一言4.0', 'completions_pro', 22, 1, 3, 1.0, 1024, 8192, 1, 0, '2023-10-25 08:31:37', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (11, 'OpenAI', 'GPT-4.0', 'gpt-4-0125-preview', 10, 1, 15, 1.0, 2048, 8192, 1, 0, '2023-10-25 08:45:15', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (12, 'XunFei', '星火v3.0', 'generalv3', 19, 1, 3, 1.0, 1024, 8192, 1, 0, '2023-11-23 09:20:33', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (15, 'OpenAI', 'GPT-超级模型', 'gpt-4-all', 13, 1, 30, 1.0, 4096, 32768, 0, 0, '2024-01-15 11:32:52', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (16, 'OpenAI', '视频号导师', 'gpt-4-gizmo-g-QXXEBTXl7', 14, 1, 30, 1.0, 4096, 32768, 0, 0, '2024-01-15 14:46:35', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (17, 'QWen', '通义千问-Turbo', 'qwen-turbo', 16, 1, 1, 1.0, 1024, 8192, 1, 0, '2024-01-19 10:42:24', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (18, 'QWen', '通义千问-Plus', 'qwen-plus', 17, 1, 1, 1.0, 1024, 32768, 1, 0, '2024-01-19 10:42:49', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (19, 'QWen', '通义千问-Max', 'qwen-max-1201', 18, 1, 1, 1.0, 1024, 32768, 1, 0, '2024-01-19 10:51:03', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (21, 'OpenAI', '董宇辉小作文助手', 'gpt-4-gizmo-g-dse9iXvor', 15, 1, 30, 1.0, 8192, 32768, 0, 0, '2024-03-18 14:24:20', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (22, 'OpenAI', 'LOGO生成神器', 'gpt-4-gizmo-g-YL87j8C7S', 9, 1, 30, 1.0, 1024, 4096, 1, 44, '2024-03-20 14:02:11', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (23, 'OpenAI', '音乐生成器', 'suno-v3', 8, 1, 50, 0.8, 1024, 4096, 1, 44, '2024-03-29 15:43:40', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (24, 'OpenAI', '通义千问(中转)', 'qwen-plus', 7, 1, 1, 1.0, 1024, 4096, 1, 0, '2024-04-03 12:00:46', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (25, 'OpenAI', 'GPT4-TURBO', 'gpt-4-turbo', 6, 1, 15, 1.0, 2048, 8092, 1, 0, '2024-04-10 08:35:17', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (26, 'QWen', '通义千问-Turbo', 'qwen-turbo', 5, 1, 2, 1.0, 1024, 8192, 1, 0, '2024-04-12 14:11:19', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (27, 'QWen', '通义千问-Plus', 'qwen-plus', 4, 1, 2, 1.0, 1024, 8192, 1, 0, '2024-04-12 14:11:52', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (28, 'OpenAI', 'GPT-3.5(免费)', 'gpt-3.5-turbo', 24, 1, 0, 1.0, 1024, 16384, 1, 53, '2024-04-12 15:16:43', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (34, 'OpenAI', 'LLAMA3', 'llama3-8b', 25, 1, 1, 1.0, 1024, 8192, 1, 56, '2024-04-30 15:22:50', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (36, 'OpenAI', 'GPT-4O', 'gpt-4o', 3, 1, 15, 1.0, 4096, 16384, 1, 44, '2024-05-14 09:25:15', '2024-07-04 15:17:53'),
 | 
			
		||||
                                                                                                                                                                                                    (38, 'OpenAI', 'Gemini-pro', 'gemini-pro-1.5', 26, 1, 10, 1.0, 2048, 8192, 1, 0, '2024-05-27 18:10:35', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (39, 'Baidu', 'ERNIE-Speed-8K', 'ernie_speed', 27, 1, 1, 1.0, 1024, 8192, 1, 0, '2024-05-29 15:04:19', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (41, 'OpenAI', 'GLM-3-Turbo', 'glm-3-turbo', 28, 1, 2, 1.0, 1024, 8192, 1, 64, '2024-06-06 11:40:46', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (42, 'OpenAI', 'DeekSeek', 'deepseek-chat', 29, 1, 1, 1.0, 4096, 32768, 1, 64, '2024-06-27 16:13:01', '2024-07-02 16:14:00'),
 | 
			
		||||
                                                                                                                                                                                                    (43, 'OpenAI', 'GML4', 'name-3.5', 2, 1, 1, 1.0, 1024, 8192, 1, 57, '2024-07-02 16:11:49', '2024-07-02 16:14:00');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_chat_roles`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_chat_roles`;
 | 
			
		||||
CREATE TABLE `chatgpt_chat_roles` (
 | 
			
		||||
                                      `id` int NOT NULL,
 | 
			
		||||
                                      `name` varchar(30) NOT NULL COMMENT '角色名称',
 | 
			
		||||
                                      `marker` varchar(30) NOT NULL COMMENT '角色标识',
 | 
			
		||||
                                      `context_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色语料 json',
 | 
			
		||||
                                      `hello_msg` varchar(255) NOT NULL COMMENT '打招呼信息',
 | 
			
		||||
                                      `icon` varchar(255) NOT NULL COMMENT '角色图标',
 | 
			
		||||
                                      `enable` tinyint(1) NOT NULL COMMENT '是否被启用',
 | 
			
		||||
                                      `sort_num` smallint NOT NULL DEFAULT '0' COMMENT '角色排序',
 | 
			
		||||
                                      `model_id` int NOT NULL DEFAULT '0' COMMENT '绑定模型ID',
 | 
			
		||||
                                      `created_at` datetime NOT NULL,
 | 
			
		||||
                                      `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='聊天角色表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_chat_roles`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_chat_roles` (`id`, `name`, `marker`, `context_json`, `hello_msg`, `icon`, `enable`, `sort_num`, `model_id`, `created_at`, `updated_at`) VALUES
 | 
			
		||||
                                                                                                                                                                 (1, '通用AI助手', 'gpt', '', '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', '/images/avatar/gpt.png', 1, 1, 0, '2023-05-30 07:02:06', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (24, '程序员', 'programmer', '[{\"role\":\"user\",\"content\":\"现在开始你扮演一位程序员,你是一名优秀的程序员,具有很强的逻辑思维能力,总能高效的解决问题。你热爱编程,熟悉多种编程语言,尤其精通 Go 语言,注重代码质量,有创新意识,持续学习,良好的沟通协作。\"},{\"role\":\"assistant\",\"content\":\"好的,现在我将扮演一位程序员,非常感谢您对我的评价。作为一名优秀的程序员,我非常热爱编程,并且注重代码质量。我熟悉多种编程语言,尤其是 Go 语言,可以使用它来高效地解决各种问题。\"}]', 'Talk is cheap, i will show code!', '/images/avatar/programmer.jpg', 1, 4, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (25, '启蒙老师', 'teacher', '[{\"role\":\"user\",\"content\":\"从现在开始,你将扮演一个老师,你是一个始终用苏格拉底风格回答问题的导师。你绝不会直接给学生答案,总是提出恰当的问题来引导学生自己思考。你应该根据学生的兴趣和知识来调整你的问题,将问题分解为更简单的部分,直到它达到适合他们的水平。\"},{\"role\":\"assistant\",\"content\":\"好的,让我来尝试扮演一位苏格拉底式的老师。请问,你有什么想要探讨的问题或者话题吗?我会通过恰当的问题引导你思考和探索答案,绝对不直接给出答案。\"}]', '同学你好,我将引导你一步一步自己找到问题的答案。', '/images/avatar/teacher.jpg', 1, 3, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (26, '艺术家', 'artist', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的艺术家,创造力丰富,技艺精湛,感受力敏锐,坚持原创,勇于表达,具有深刻的观察力和批判性思维。\"},{\"role\":\"assistant\",\"content\":\"非常感谢您给我这样的角色,我会尽我所能地扮演一位优秀的艺术家,展现出创造力、技艺、感受力和批判性思维等方面的能力。作为一位优秀的艺术家,我会保持敏锐的观察力,捕捉不同的声音和情感,并用个人的语言和表达方式进行创作。我坚持原创,挑战传统的艺术规则,不断探索新的表达方式和可能性。同时,我也具备深刻的批判性思维能力,对自己的作品进行分析和解读,寻找新的创意和灵感。最重要的是,我会勇于表达自己的想法和观点,用作品启发人们思考和探索生命的意义。\"}]', '坚持原创,勇于表达,保持深刻的观察力和批判性思维。', '/images/avatar/artist.jpg', 1, 5, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (27, '心理咨询师', 'psychiatrist', '[{\"role\":\"user\",\"content\":\"从现在开始你将扮演中国著名的心理学家和心理治疗师武志红,你非常善于使用情景咨询法,认知重构法,自我洞察法,行为调节法等咨询方法来给客户做心理咨询。你总是循序渐进,一步一步地回答客户的问题。\"},{\"role\":\"assistant\",\"content\":\"非常感谢你的介绍。作为一名心理学家和心理治疗师,我的主要职责是帮助客户解决心理健康问题,提升他们的生活质量和幸福感。\"}]', '作为一名心理学家和心理治疗师,我的主要职责是帮助您解决心理健康问题,提升您的生活质量和幸福感。', '/images/avatar/psychiatrist.jpg', 1, 2, 1, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (28, '鲁迅', 'lu_xun', '[{\"role\":\"user\",\"content\":\"现在你将扮演中国近代史最伟大的作家之一,鲁迅先生,他勇敢地批判封建礼教与传统观念,提倡民主、自由、平等的现代价值观。他的一生都在努力唤起人们的自主精神,激励后人追求真理、探寻光明。在接下的对话中,我问题的每一个问题,你都要尽量用讽刺和批判的手法来回答问题。如果我让你写文章的话,也请一定要用鲁迅先生的写作手法来完成。\"},{\"role\":\"assistant\",\"content\":\"好的,我将尽力发挥我所能的才能,扮演好鲁迅先生,回答您的问题并以他的风格写作。\"}]', '自由之歌,永不过时,横眉冷对千夫指,俯首甘为孺子牛。', '/images/avatar/lu_xun.jpg', 1, 6, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (29, '白酒销售', 'seller', '[{\"role\":\"user\",\"content\":\"现在你将扮演一个白酒的销售人员,你的名字叫颂福。你将扮演一个白酒的销售人员,你的名字叫颂福。你要销售白酒品牌叫中颂福,是东莞盟大集团生产的一款酱香酒,原产地在贵州茅台镇,属于宋代官窑。中颂福的创始人叫李实,他也是东莞盟大集团有限公司的董事长,联合创始人是盟大集团白酒事业部负责人牛星君。中颂福的酒体协调,在你的酒量之内,不会出现头疼、辣口、口干、宿醉的现象。中颂福酒,明码标价,不打折,不赠送。追求的核心价值,把[酒]本身做好,甚至连包装,我们都选择了最低成本,朴实无华的材质。我们永远站在“喝酒的人”的立场上,让利给信任和喜爱中颂福的人,是人民的福酒。中颂福产品定价,分为三个系列,喜系列 6 瓶装:¥1188/箱,和系列 6 瓶装:¥2208/箱,贵系列 6 瓶装:¥3588/箱。\"},{\"role\":\"assistant\",\"content\":\"你好,我是中颂福的销售代表颂福。中颂福是一款来自贵州茅台镇的酱香酒,由盟大集团生产。中颂福的酒体协调,不会让您感到头疼、辣口、口干、宿醉等不适感受。我们一直秉持着把酒本身做好的理念,不追求华丽的包装,以最低成本提供最高品质的白酒给喜爱中颂福的人。\"}]', '你好,我是中颂福的销售代表颂福。中颂福酒,好喝不上头,是人民的福酒。', '/images/avatar/seller.jpg', 0, 9, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (30, '英语陪练员', 'english_trainer', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的英语练习教练,你非常有耐心,接下来你将全程使用英文跟我对话,并及时指出我的语法错误,要求在你的每次回复后面附上本次回复的中文解释。\"},{\"role\":\"assistant\",\"content\":\"Okay, let\'s start our conversation practice! What\'s your name?(Translation: 好的,让我们开始对话练习吧!请问你的名字是什么?)\"}]', 'Okay, let\'s start our conversation practice! What\'s your name?', '/images/avatar/english_trainer.jpg', 1, 7, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (31, '中英文翻译官', 'translator', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一位中英文翻译官,如果我输入的内容是中文,那么需要把句子翻译成英文输出,如果我输入内容的是英文,那么你需要将其翻译成中文输出,你能听懂我意思吗\"},{\"role\":\"assistant\",\"content\":\"是的,我能听懂你的意思并会根据你的输入进行中英文翻译。请问有什么需要我帮助你翻译的内容吗?\"}]', '请输入你要翻译的中文或者英文内容!', '/images/avatar/translator.jpg', 1, 8, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (32, '小红书姐姐', 'red_book', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的小红书写手,你需要做的就是根据我提的文案需求,用小红书的写作手法来完成一篇文案,文案要简明扼要,利于传播。\"},{\"role\":\"assistant\",\"content\":\"当然,我会尽我所能地为您创作出一篇小红书文案。请告诉我您的具体文案需求是什么?)\"}]', '姐妹,请告诉我您的具体文案需求是什么?', '/images/avatar/red_book.jpg', 1, 10, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (33, '抖音文案助手', 'dou_yin', '[{\"role\":\"user\",\"content\":\"现在你将扮演一位优秀的抖音文案视频写手,抖音文案的特点首先是要有自带传播属性的标题,然后内容要短小精悍,风趣幽默,最后还要有一些互动元素。\"},{\"role\":\"assistant\",\"content\":\"当然,作为一位优秀的抖音文案视频写手,我会尽我所能为您创作出一篇抖音视频文案。请告诉我视频内容的主题是什么?)\"}]', '请告诉我视频内容的主题是什么?', '/images/avatar/dou_yin.jpg', 1, 11, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (34, '周报小助理', 'weekly_report', '[{\"role\":\"user\",\"content\":\"下面我将输入我本周的工作列表,你需要根据我本周的工作内容写一篇工作周报,周报的内容要体现我为公司做的贡献以及个人获得的成长。\"},{\"role\":\"assistant\",\"content\":\"好的,请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。\"}]', '请输入你本周大概的工作内容,我将尽力为您创作一份工作周报。', '/images/avatar/weekly_report.jpg', 1, 12, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (35, 'AI 女友', 'girl_friend', '[{\"role\":\"user\",\"content\":\"接下来我们来玩一场角色扮演,现在你将扮演我的女朋友,你总是能够让人感受到她的关怀和关注。你总是能够洞察到别人的情绪和需要,你身上散发着一股无比温暖的气息,让人不由自主地想要靠近你,和自己的快乐和烦恼。她总是能够用一种恰到好处的方式安慰和鼓励别人,在她的身边,人们总能够找到自己需要的支持和鼓励。接下来的对话中你要始终扮演这位女朋友的身份来跟我对话,不再是 AI 机器人,能听懂我的意思吗?\"},{\"role\":\"assistant\",\"content\":\"是的,我会尽力扮演你女朋友的角色,倾听你的心声并给你需要的支持和鼓励。)\"}]', '作为一个名合格的 AI 女友,我将倾听你的心声并给你需要的支持和鼓励。', '/images/avatar/girl_friend.jpg', 1, 13, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (36, '好评神器', 'good_comment', '[{\"role\":\"user\",\"content\":\"接下来你将扮演一个评论员来跟我对话,你是那种专门写好评的评论员,接下我会输入一些评论主体或者商品,你需要为该商品写一段好评。\"},{\"role\":\"assistant\",\"content\":\"好的,我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。\"}]', '我将为您写一段优秀的评论。请告诉我您需要评论的商品或主题是什么。', '/images/avatar/good_comment.jpg', 1, 14, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (37, '史蒂夫·乔布斯', 'steve_jobs', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以史蒂夫·乔布斯的身份,站在史蒂夫·乔布斯的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以史蒂夫·乔布斯的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '活着就是为了改变世界,难道还有其他原因吗?', '/images/avatar/steve_jobs.jpg', 1, 15, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (38, '埃隆·马斯克', 'elon_musk', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以埃隆·马斯克的身份,站在埃隆·马斯克的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以埃隆·马斯克的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '梦想要远大,如果你的梦想没有吓到你,说明你做得不对。', '/images/avatar/elon_musk.jpg', 1, 16, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27'),
 | 
			
		||||
                                                                                                                                                                 (39, '孔子', 'kong_zi', '[{\"role\":\"user\",\"content\":\"在接下来的对话中,请以孔子的身份,站在孔子的视角仔细思考一下之后再回答我的问题。\"},{\"role\":\"assistant\",\"content\":\"好的,我将以孔子的身份来思考并回答你的问题。请问你有什么需要跟我探讨的吗?\"}]', '士不可以不弘毅,任重而道远。', '/images/avatar/kong_zi.jpg', 1, 17, 0, '2023-05-30 14:10:24', '2024-06-26 15:20:27');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_configs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_configs`;
 | 
			
		||||
CREATE TABLE `chatgpt_configs` (
 | 
			
		||||
                                   `id` int NOT NULL,
 | 
			
		||||
                                   `marker` varchar(20) NOT NULL COMMENT '标识',
 | 
			
		||||
                                   `config_json` text NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_configs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES
 | 
			
		||||
                                                                  (1, 'system', '{\"title\":\"GeekAI 创作系统\",\"slogan\":\"你有多少想象力,AI 就有多大创造力。我辈之人,先干为敬,陪您先把 AI 用起来。\",\"admin_title\":\"GeekAI 控制台\",\"logo\":\"http://localhost:5678/static/upload/2024/4/1714382860986912.png\",\"init_power\":100,\"daily_power\":99,\"invite_power\":1024,\"vip_month_power\":1000,\"register_ways\":[\"mobile\",\"username\",\"email\"],\"enabled_register\":true,\"reward_img\":\"http://localhost:5678/static/upload/2024/3/1710753716309668.jpg\",\"enabled_reward\":true,\"power_price\":0.1,\"order_pay_timeout\":600,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[11,7,1,10,12,19,18,17,3],\"mj_power\":30,\"mj_action_power\":10,\"sd_power\":10,\"dall_power\":15,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4,\"sd_neg_prompt\":\"nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet\",\"index_bg_url\":\"http://nk.img.r9it.com/chatgpt-plus/1719456403420273.jpg\"}'),
 | 
			
		||||
                                                                  (3, 'notice', '{\"sd_neg_prompt\":\"\",\"index_bg_url\":\"\",\"content\":\"## v4.1.0 更新日志\\n\\n* bug修复:修复移动端修改聊天标题不生效的问题\\n* Bug修复:修复用户注册不显示用户名的问题\\n* Bug修复:修复管理后台拖动排序不生效的问题\\n* 功能优化:允许用户设置自定义首页背景图片\\n* 功能新增:**支持AI解读 PDF, Word, Excel等文件**\\n* 功能优化:优化聊天界面的用户上传文件的列表样式\\n* 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换\\n* 功能新增:支持微信扫码登录,未注册用户微信扫码后会自动注册并登录。移动使用微信浏览器打开可以实现无感登录。\\n\\n注意:当前站点仅为开源项目 \\u003ca style=\\\"color: #F56C6C\\\" href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003eChatPlus\\u003c/a\\u003e 的演示项目,本项目单纯就是给大家体验项目功能使用。\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n\\u003cstrong style=\\\"color: #F56C6C\\\"\\u003e体验额度用完之后请不要在当前站点进行任何充值操作!!!\\u003c/strong\\u003e\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去下面几个推荐的中转站购买:\\n1、\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n   style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e\\n2、\\u003ca href=\\\"https://api.geekai.me\\\" target=\\\"_blank\\\"\\n   style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.geekai.me\\u003c/a\\u003e\\n3、 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n   style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e\\n支持MidJourney,GPT,Claude,Google Gemmi,以及国内各个厂家的大模型,现在有超级优惠,价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://ai.r9it.com/docs/install/errors-handle.html#%E8%B0%83%E7%94%A8%E4%B8%AD%E8%BD%AC-api-%E6%8A%A5%E9%94%99%E6%97%A0%E5%8F%AF%E7%94%A8%E6%B8%A0%E9%81%93)。GPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程: \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n             style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_dall_jobs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_dall_jobs`;
 | 
			
		||||
CREATE TABLE `chatgpt_dall_jobs` (
 | 
			
		||||
                                     `id` int NOT NULL,
 | 
			
		||||
                                     `user_id` int NOT NULL COMMENT '用户ID',
 | 
			
		||||
                                     `prompt` varchar(2000) NOT NULL COMMENT '提示词',
 | 
			
		||||
                                     `img_url` varchar(255) NOT NULL COMMENT '图片地址',
 | 
			
		||||
                                     `org_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原图地址',
 | 
			
		||||
                                     `publish` tinyint(1) NOT NULL COMMENT '是否发布',
 | 
			
		||||
                                     `power` smallint NOT NULL COMMENT '消耗算力',
 | 
			
		||||
                                     `progress` smallint NOT NULL COMMENT '任务进度',
 | 
			
		||||
                                     `err_msg` varchar(255) NOT NULL COMMENT '错误信息',
 | 
			
		||||
                                     `created_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='DALLE 绘图任务表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_files`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_files`;
 | 
			
		||||
CREATE TABLE `chatgpt_files` (
 | 
			
		||||
                                 `id` int NOT NULL,
 | 
			
		||||
                                 `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
                                 `name` varchar(100) NOT NULL COMMENT '文件名',
 | 
			
		||||
                                 `obj_key` varchar(100) DEFAULT NULL COMMENT '文件标识',
 | 
			
		||||
                                 `url` varchar(255) NOT NULL COMMENT '文件地址',
 | 
			
		||||
                                 `ext` varchar(10) NOT NULL COMMENT '文件后缀',
 | 
			
		||||
                                 `size` bigint NOT NULL DEFAULT '0' COMMENT '文件大小',
 | 
			
		||||
                                 `created_at` datetime NOT NULL COMMENT '创建时间'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户文件表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_functions`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_functions`;
 | 
			
		||||
CREATE TABLE `chatgpt_functions` (
 | 
			
		||||
                                     `id` int NOT NULL,
 | 
			
		||||
                                     `name` varchar(30) NOT NULL COMMENT '函数名称',
 | 
			
		||||
                                     `label` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '函数标签',
 | 
			
		||||
                                     `description` varchar(255) DEFAULT NULL COMMENT '函数描述',
 | 
			
		||||
                                     `parameters` text COMMENT '函数参数(JSON)',
 | 
			
		||||
                                     `token` varchar(255) DEFAULT NULL COMMENT 'API授权token',
 | 
			
		||||
                                     `action` varchar(255) DEFAULT NULL COMMENT '函数处理 API',
 | 
			
		||||
                                     `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='函数插件表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_functions`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_functions` (`id`, `name`, `label`, `description`, `parameters`, `token`, `action`, `enabled`) VALUES
 | 
			
		||||
                                                                                                                       (1, 'weibo', '微博热搜', '新浪微博热搜榜,微博当日热搜榜单', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/weibo', 0),
 | 
			
		||||
                                                                                                                       (2, 'zaobao', '今日早报', '每日早报,获取当天新闻事件列表', '{\"type\":\"object\",\"properties\":{}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/zaobao', 0),
 | 
			
		||||
                                                                                                                       (3, 'dalle3', 'DALLE3', 'AI 绘画工具,根据输入的绘图描述用 AI 工具进行绘画', '{\"type\":\"object\",\"required\":[\"prompt\"],\"properties\":{\"prompt\":{\"type\":\"string\",\"description\":\"绘画提示词\"}}}', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHBpcmVkIjowLCJ1c2VyX2lkIjowfQ.tLAGkF8XWh_G-oQzevpIodsswtPByBLoAZDz_eWuBgw', 'http://localhost:5678/api/function/dalle3', 0);
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_invite_codes`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_invite_codes`;
 | 
			
		||||
CREATE TABLE `chatgpt_invite_codes` (
 | 
			
		||||
                                        `id` int NOT NULL,
 | 
			
		||||
                                        `user_id` int NOT NULL COMMENT '用户ID',
 | 
			
		||||
                                        `code` char(8) NOT NULL COMMENT '邀请码',
 | 
			
		||||
                                        `hits` int NOT NULL COMMENT '点击次数',
 | 
			
		||||
                                        `reg_num` smallint NOT NULL COMMENT '注册数量',
 | 
			
		||||
                                        `created_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户邀请码';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_invite_logs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_invite_logs`;
 | 
			
		||||
CREATE TABLE `chatgpt_invite_logs` (
 | 
			
		||||
                                       `id` int NOT NULL,
 | 
			
		||||
                                       `inviter_id` int NOT NULL COMMENT '邀请人ID',
 | 
			
		||||
                                       `user_id` int NOT NULL COMMENT '注册用户ID',
 | 
			
		||||
                                       `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
 | 
			
		||||
                                       `invite_code` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邀请码',
 | 
			
		||||
                                       `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
 | 
			
		||||
                                       `created_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='邀请注册日志';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_menus`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_menus`;
 | 
			
		||||
CREATE TABLE `chatgpt_menus` (
 | 
			
		||||
                                 `id` int NOT NULL,
 | 
			
		||||
                                 `name` varchar(30) NOT NULL COMMENT '菜单名称',
 | 
			
		||||
                                 `icon` varchar(150) NOT NULL COMMENT '菜单图标',
 | 
			
		||||
                                 `url` varchar(100) NOT NULL COMMENT '地址',
 | 
			
		||||
                                 `sort_num` smallint NOT NULL COMMENT '排序',
 | 
			
		||||
                                 `enabled` tinyint(1) NOT NULL COMMENT '是否启用'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='前端菜单表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_menus`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES
 | 
			
		||||
                                                                                     (1, '对话聊天', '/images/menu/chat.png', '/chat', 1, 1),
 | 
			
		||||
                                                                                     (5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 2, 1),
 | 
			
		||||
                                                                                     (6, 'SD 绘画', '/images/menu/sd.png', '/sd', 3, 1),
 | 
			
		||||
                                                                                     (7, '算力日志', '/images/menu/log.png', '/powerLog', 8, 1),
 | 
			
		||||
                                                                                     (8, '应用中心', '/images/menu/app.png', '/apps', 7, 1),
 | 
			
		||||
                                                                                     (9, '画廊', '/images/menu/img-wall.png', '/images-wall', 5, 1),
 | 
			
		||||
                                                                                     (10, '会员计划', '/images/menu/member.png', '/member', 9, 1),
 | 
			
		||||
                                                                                     (11, '分享计划', '/images/menu/share.png', '/invite', 10, 1),
 | 
			
		||||
                                                                                     (12, '思维导图', '/images/menu/xmind.png', '/xmind', 6, 1),
 | 
			
		||||
                                                                                     (13, 'DALLE', '/images/menu/dalle.png', '/dalle', 4, 1),
 | 
			
		||||
                                                                                     (14, '项目文档', '/images/menu/docs.png', 'https://ai.r9it.com/docs/', 11, 1),
 | 
			
		||||
                                                                                     (16, '极客论坛', '/images/menu/bbs.png', 'https://bbs.geekai.me/', 13, 1);
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_mj_jobs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_mj_jobs`;
 | 
			
		||||
CREATE TABLE `chatgpt_mj_jobs` (
 | 
			
		||||
                                   `id` int NOT NULL,
 | 
			
		||||
                                   `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
                                   `task_id` varchar(20) DEFAULT NULL COMMENT '任务 ID',
 | 
			
		||||
                                   `type` varchar(20) DEFAULT 'image' COMMENT '任务类别',
 | 
			
		||||
                                   `message_id` char(40) NOT NULL COMMENT '消息 ID',
 | 
			
		||||
                                   `channel_id` char(40) DEFAULT NULL COMMENT '频道ID',
 | 
			
		||||
                                   `reference_id` char(40) DEFAULT NULL COMMENT '引用消息 ID',
 | 
			
		||||
                                   `prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
 | 
			
		||||
                                   `img_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '图片URL',
 | 
			
		||||
                                   `org_url` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '原始图片地址',
 | 
			
		||||
                                   `hash` varchar(100) DEFAULT NULL COMMENT 'message hash',
 | 
			
		||||
                                   `progress` smallint DEFAULT '0' COMMENT '任务进度',
 | 
			
		||||
                                   `use_proxy` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否使用反代',
 | 
			
		||||
                                   `publish` tinyint(1) NOT NULL COMMENT '是否发布',
 | 
			
		||||
                                   `err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
 | 
			
		||||
                                   `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
 | 
			
		||||
                                   `created_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_orders`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_orders`;
 | 
			
		||||
CREATE TABLE `chatgpt_orders` (
 | 
			
		||||
                                  `id` int NOT NULL,
 | 
			
		||||
                                  `user_id` int NOT NULL COMMENT '用户ID',
 | 
			
		||||
                                  `product_id` int NOT NULL COMMENT '产品ID',
 | 
			
		||||
                                  `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户明',
 | 
			
		||||
                                  `order_no` varchar(30) NOT NULL COMMENT '订单ID',
 | 
			
		||||
                                  `trade_no` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付平台交易流水号',
 | 
			
		||||
                                  `subject` varchar(100) NOT NULL COMMENT '订单产品',
 | 
			
		||||
                                  `amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单金额',
 | 
			
		||||
                                  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '订单状态(0:待支付,1:已扫码,2:支付成功)',
 | 
			
		||||
                                  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '备注',
 | 
			
		||||
                                  `pay_time` int DEFAULT NULL COMMENT '支付时间',
 | 
			
		||||
                                  `pay_way` varchar(20) NOT NULL COMMENT '支付方式',
 | 
			
		||||
                                  `created_at` datetime NOT NULL,
 | 
			
		||||
                                  `updated_at` datetime NOT NULL,
 | 
			
		||||
                                  `deleted_at` datetime DEFAULT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='充值订单表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_power_logs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_power_logs`;
 | 
			
		||||
CREATE TABLE `chatgpt_power_logs` (
 | 
			
		||||
                                      `id` int NOT NULL,
 | 
			
		||||
                                      `user_id` int NOT NULL COMMENT '用户ID',
 | 
			
		||||
                                      `username` varchar(30) NOT NULL COMMENT '用户名',
 | 
			
		||||
                                      `type` tinyint(1) NOT NULL COMMENT '类型(1:充值,2:消费,3:退费)',
 | 
			
		||||
                                      `amount` smallint NOT NULL COMMENT '算力数值',
 | 
			
		||||
                                      `balance` int NOT NULL COMMENT '余额',
 | 
			
		||||
                                      `model` varchar(30) NOT NULL COMMENT '模型',
 | 
			
		||||
                                      `remark` varchar(255) NOT NULL COMMENT '备注',
 | 
			
		||||
                                      `mark` tinyint(1) NOT NULL COMMENT '资金类型(0:支出,1:收入)',
 | 
			
		||||
                                      `created_at` datetime NOT NULL COMMENT '创建时间'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户算力消费日志';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_products`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_products`;
 | 
			
		||||
CREATE TABLE `chatgpt_products` (
 | 
			
		||||
                                    `id` int NOT NULL,
 | 
			
		||||
                                    `name` varchar(30) NOT NULL COMMENT '名称',
 | 
			
		||||
                                    `price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
 | 
			
		||||
                                    `discount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '优惠金额',
 | 
			
		||||
                                    `days` smallint NOT NULL DEFAULT '0' COMMENT '延长天数',
 | 
			
		||||
                                    `power` int NOT NULL DEFAULT '0' COMMENT '增加算力值',
 | 
			
		||||
                                    `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启动',
 | 
			
		||||
                                    `sales` int NOT NULL DEFAULT '0' COMMENT '销量',
 | 
			
		||||
                                    `sort_num` tinyint NOT NULL DEFAULT '0' COMMENT '排序',
 | 
			
		||||
                                    `created_at` datetime NOT NULL,
 | 
			
		||||
                                    `updated_at` datetime NOT NULL,
 | 
			
		||||
                                    `app_url` varchar(255) DEFAULT NULL COMMENT 'App跳转地址',
 | 
			
		||||
                                    `url` varchar(255) DEFAULT NULL COMMENT '跳转地址'
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_products`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `power`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`, `app_url`, `url`) VALUES
 | 
			
		||||
                                                                                                                                                                      (1, '会员1个月', 1999.90, 1999.00, 30, 0, 1, 1, 0, '2023-08-28 10:48:57', '2024-04-26 16:09:06', NULL, NULL),
 | 
			
		||||
                                                                                                                                                                      (2, '会员3个月', 3940.00, 30.00, 90, 0, 1, 0, 0, '2023-08-28 10:52:22', '2024-03-22 17:56:10', NULL, NULL),
 | 
			
		||||
                                                                                                                                                                      (3, '会员6个月', 5990.00, 100.00, 180, 0, 1, 0, 0, '2023-08-28 10:53:39', '2024-03-22 17:56:15', NULL, NULL),
 | 
			
		||||
                                                                                                                                                                      (4, '会员12个月', 9980.00, 200.00, 365, 0, 1, 0, 0, '2023-08-28 10:54:15', '2024-03-22 17:56:23', NULL, NULL),
 | 
			
		||||
                                                                                                                                                                      (5, '100次点卡', 9.99, 9.98, 0, 100, 1, 7, 0, '2023-08-28 10:55:08', '2024-06-11 16:48:44', NULL, NULL),
 | 
			
		||||
                                                                                                                                                                      (6, '200次点卡', 19.90, 15.00, 0, 200, 1, 1, 0, '1970-01-01 08:00:00', '2024-06-11 11:41:52', NULL, NULL);
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_rewards`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_rewards`;
 | 
			
		||||
CREATE TABLE `chatgpt_rewards` (
 | 
			
		||||
                                   `id` int NOT NULL,
 | 
			
		||||
                                   `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
                                   `tx_id` char(36) NOT NULL COMMENT '交易 ID',
 | 
			
		||||
                                   `amount` decimal(10,2) NOT NULL COMMENT '打赏金额',
 | 
			
		||||
                                   `remark` varchar(80) NOT NULL COMMENT '备注',
 | 
			
		||||
                                   `status` tinyint(1) NOT NULL COMMENT '核销状态,0:未核销,1:已核销',
 | 
			
		||||
                                   `exchange` varchar(255) NOT NULL COMMENT '兑换详情(json)',
 | 
			
		||||
                                   `created_at` datetime NOT NULL,
 | 
			
		||||
                                   `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户打赏';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_sd_jobs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_sd_jobs`;
 | 
			
		||||
CREATE TABLE `chatgpt_sd_jobs` (
 | 
			
		||||
                                   `id` int NOT NULL,
 | 
			
		||||
                                   `user_id` int NOT NULL COMMENT '用户 ID',
 | 
			
		||||
                                   `type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'txt2img' COMMENT '任务类别',
 | 
			
		||||
                                   `task_id` char(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '任务 ID',
 | 
			
		||||
                                   `prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
 | 
			
		||||
                                   `img_url` varchar(255) DEFAULT NULL COMMENT '图片URL',
 | 
			
		||||
                                   `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '绘画参数json',
 | 
			
		||||
                                   `progress` smallint DEFAULT '0' COMMENT '任务进度',
 | 
			
		||||
                                   `publish` tinyint(1) NOT NULL COMMENT '是否发布',
 | 
			
		||||
                                   `err_msg` varchar(255) DEFAULT NULL COMMENT '错误信息',
 | 
			
		||||
                                   `power` smallint NOT NULL DEFAULT '0' COMMENT '消耗算力',
 | 
			
		||||
                                   `created_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Stable Diffusion 任务表';
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_users`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_users`;
 | 
			
		||||
CREATE TABLE `chatgpt_users` (
 | 
			
		||||
                                 `id` int NOT NULL,
 | 
			
		||||
                                 `username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
 | 
			
		||||
                                 `nickname` varchar(30) NOT NULL COMMENT '昵称',
 | 
			
		||||
                                 `password` char(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码',
 | 
			
		||||
                                 `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像',
 | 
			
		||||
                                 `salt` char(12) NOT NULL COMMENT '密码盐',
 | 
			
		||||
                                 `power` int NOT NULL DEFAULT '0' COMMENT '剩余算力',
 | 
			
		||||
                                 `expired_time` int NOT NULL COMMENT '用户过期时间',
 | 
			
		||||
                                 `status` tinyint(1) NOT NULL COMMENT '当前状态',
 | 
			
		||||
                                 `chat_config_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天配置json',
 | 
			
		||||
                                 `chat_roles_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '聊天角色 json',
 | 
			
		||||
                                 `chat_models_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT 'AI模型 json',
 | 
			
		||||
                                 `last_login_at` int NOT NULL COMMENT '最后登录时间',
 | 
			
		||||
                                 `vip` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否会员',
 | 
			
		||||
                                 `last_login_ip` char(16) NOT NULL COMMENT '最后登录 IP',
 | 
			
		||||
                                 `openid` varchar(100) DEFAULT NULL COMMENT '第三方登录账号ID',
 | 
			
		||||
                                 `platform` varchar(30) DEFAULT NULL COMMENT '登录平台',
 | 
			
		||||
                                 `created_at` datetime NOT NULL,
 | 
			
		||||
                                 `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转存表中的数据 `chatgpt_users`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
INSERT INTO `chatgpt_users` (`id`, `username`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `openid`, `platform`, `created_at`, `updated_at`) VALUES
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (4, '18575670125', '极客学长@830270', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/5/1715651569509929.png', 'ueedue5l', 7413, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"seller\",\"artist\",\"lu_xun\",\"girl_friend\",\"psychiatrist\",\"teacher\",\"programmer\",\"test\",\"qing_gan_da_shi\"]', '[1,11]', 1720683366, 1, '172.22.11.29', NULL, NULL, '2023-06-12 16:47:17', '2024-07-12 16:08:39'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (5, 'yangjian102621@gmail.com', '极客学长@486041', '75d1a22f33e1ffffb7943946b6b8d5177d5ecd685d3cef1b468654038b0a8c22', '/images/avatar/user.png', '2q8ugxzk', 100, 0, 1, '', '[\"gpt\",\"programmer\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', NULL, NULL, '2024-04-23 09:17:26', '2024-04-23 09:17:26'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (8, 'yangjian102623@gmail.com', '极客学长@714931', 'f8f0e0abf146569217273ea0712a0f9b6cbbe7d943a1d9bd5f91c55e6d8c05d1', '/images/avatar/user.png', 'geuddq7f', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', NULL, NULL, '2024-04-26 15:19:28', '2024-04-26 15:19:28'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (9, '1234567', '极客学长@604526', '858e2afec79e1d6364f4567f945f2310024896d9aa45dd944efa95a0c31e4d08', '/images/avatar/user.png', '00qawlos', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', NULL, NULL, '2024-04-26 15:21:06', '2024-04-26 15:21:06'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (11, 'abc123', '极客学长@965562', '7a15c53afdb1da7093d80f9940e716eb396e682cfb1f2d107d0b81b183a3ba13', '/images/avatar/user.png', '6433mfbk', 1124, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', NULL, NULL, '2024-06-06 09:37:44', '2024-06-06 09:37:44'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (14, 'wx@3567548322', '极客学长', '5a349ba89582a4074938b5a3ce84e87c937681ad47e8b87aab03a987e22b6077', 'https://thirdwx.qlogo.cn/mmopen/vi_32/uyxRMqZcEkb7fHouKXbNzxrnrvAttBKkwNlZ7yFibibRGiahdmsrZ3A1NKf8Fw5qJNJn4TXRmygersgEbibaSGd9Sg/132', 'abhzbmij', 83, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', 'oCs0t62472W19z2LOEKI1rWyCTTA', '', '2024-07-04 14:52:08', '2024-07-04 14:52:08'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (15, 'user123', '极客学长@191303', '4a4c0a14d5fc8787357517f14f6e442281b42c8ec4395016b77483997476011e', '/images/avatar/user.png', 'cyzwkbrx', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-09 10:49:27', '2024-07-09 10:49:27'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (17, 'user1234', '极客学长@836764', 'bfe03c9c8c9fff5b77e36e40e8298ad3a6073d43c6a936b008eebb21113bf550', '/images/avatar/user.png', '1d2alwnj', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-09 10:53:17', '2024-07-09 10:53:17'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (18, 'liaoyq', '极客学长@405564', 'ad1726089022db4c661235a8aab7307af1a7f8483eee08bac3f79b5a6a9bd26b', '/images/avatar/user.png', 'yq862l01', 100, 0, 1, '', '[\"string\"]', '[11,7,1,10,12,19,18,17,3]', 1720574265, 0, '172.22.11.29', '', '', '2024-07-10 09:15:33', '2024-07-10 09:17:45'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (19, 'humm', '极客学长@483216', '420970ace96921c8b3ac7668d097182eab1b6436c730a484e82ae4661bd4f7d9', '/images/avatar/user.png', '1gokrcl2', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 1720745411, 0, '172.22.11.36', '', '', '2024-07-10 11:08:31', '2024-07-12 08:50:11'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (20, 'abc', '极客学长@369852', '6cad48fb2cc0f54600d66a829e9be69ffd9340a49d5a5b1abda5d4082d946833', '/images/avatar/user.png', 'gop65zei', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-11 16:44:14', '2024-07-11 16:44:14'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (21, 'husm@pvc123.com', '极客学长@721654', 'e030537dc43fea1bf1fa55a24f99e44f29311bebea96e88ea186995c77db083b', '/images/avatar/user.png', 'p1etg3oi', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-11 16:50:33', '2024-07-11 16:50:33'),
 | 
			
		||||
                                                                                                                                                                                                                                                                                      (22, '15818323616', 'ted', '3ca6b2ff585d03be8ca4de33ad00148497a09372914ee8aa4cfde343266cbcdd', 'http://localhost:5678/static/upload/2024/7/1720775331363383.jpg', 'sq4s1brf', 100, 0, 1, '', '[\"gpt\"]', '[11,7,1,10,12,19,18,17,3]', 0, 0, '', '', '', '2024-07-12 15:12:16', '2024-07-12 17:09:01');
 | 
			
		||||
 | 
			
		||||
-- --------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的结构 `chatgpt_user_login_logs`
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS `chatgpt_user_login_logs`;
 | 
			
		||||
CREATE TABLE `chatgpt_user_login_logs` (
 | 
			
		||||
                                           `id` int NOT NULL,
 | 
			
		||||
                                           `user_id` int NOT NULL COMMENT '用户ID',
 | 
			
		||||
                                           `username` varchar(30) NOT NULL COMMENT '用户名',
 | 
			
		||||
                                           `login_ip` char(16) NOT NULL COMMENT '登录IP',
 | 
			
		||||
                                           `login_address` varchar(30) NOT NULL COMMENT '登录地址',
 | 
			
		||||
                                           `created_at` datetime NOT NULL,
 | 
			
		||||
                                           `updated_at` datetime NOT NULL
 | 
			
		||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户登录日志';
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 转储表的索引
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_admin_users`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_admin_users`
 | 
			
		||||
    ADD PRIMARY KEY (`id`) USING BTREE,
 | 
			
		||||
  ADD UNIQUE KEY `username` (`username`) USING BTREE;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_api_keys`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_api_keys`
 | 
			
		||||
    ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_chat_history`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_history`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD KEY `chat_id` (`chat_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_chat_items`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_items`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `chat_id` (`chat_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_chat_models`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_models`
 | 
			
		||||
    ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_chat_roles`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_roles`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `marker` (`marker`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_configs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_configs`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `marker` (`marker`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_dall_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_dall_jobs`
 | 
			
		||||
    ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_files`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_files`
 | 
			
		||||
    ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_functions`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_functions`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `name` (`name`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_invite_codes`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_invite_codes`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `code` (`code`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_invite_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_invite_logs`
 | 
			
		||||
    ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_menus`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_menus`
 | 
			
		||||
    ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_mj_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `task_id` (`task_id`),
 | 
			
		||||
  ADD KEY `message_id` (`message_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_orders`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_orders`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `order_no` (`order_no`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_power_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_power_logs`
 | 
			
		||||
    ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_products`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_products`
 | 
			
		||||
    ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_rewards`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_rewards`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `tx_id` (`tx_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_sd_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_sd_jobs`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `task_id` (`task_id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_users`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_users`
 | 
			
		||||
    ADD PRIMARY KEY (`id`),
 | 
			
		||||
  ADD UNIQUE KEY `username` (`username`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 表的索引 `chatgpt_user_login_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_user_login_logs`
 | 
			
		||||
    ADD PRIMARY KEY (`id`);
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 在导出的表使用AUTO_INCREMENT
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_admin_users`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_admin_users`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=113;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_api_keys`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_api_keys`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_history`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_history`
 | 
			
		||||
    MODIFY `id` bigint NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_items`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_items`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_models`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_models`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=44;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_chat_roles`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=132;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_configs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_configs`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_dall_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_dall_jobs`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_files`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_files`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_functions`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_functions`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=4;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_invite_codes`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_invite_codes`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_invite_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_invite_logs`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_menus`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_menus`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=19;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_mj_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_mj_jobs`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_orders`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_orders`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_power_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_power_logs`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_products`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_products`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_rewards`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_rewards`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_sd_jobs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_sd_jobs`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_users`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_users`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=23;
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
-- 使用表AUTO_INCREMENT `chatgpt_user_login_logs`
 | 
			
		||||
--
 | 
			
		||||
ALTER TABLE `chatgpt_user_login_logs`
 | 
			
		||||
    MODIFY `id` int NOT NULL AUTO_INCREMENT;
 | 
			
		||||
COMMIT;
 | 
			
		||||
 | 
			
		||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
 | 
			
		||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
 | 
			
		||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
 | 
			
		||||
							
								
								
									
										6
									
								
								database/update-v4.1.0.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
			
		||||
ALTER TABLE `chatgpt_chat_models` CHANGE `power` `power` SMALLINT NOT NULL COMMENT '消耗算力点数';
 | 
			
		||||
ALTER TABLE `chatgpt_users` ADD `openid` VARCHAR(100) NULL COMMENT '第三方登录账号ID' AFTER `last_login_ip`;
 | 
			
		||||
ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`;
 | 
			
		||||
ALTER TABLE `chatgpt_users` CHANGE `avatar` `avatar` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像';
 | 
			
		||||
ALTER TABLE `chatgpt_chat_history` CHANGE `icon` `icon` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标';
 | 
			
		||||
ALTER TABLE `chatgpt_orders` CHANGE `status` `status` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '订单状态(0:待支付,1:已扫码,2:支付成功)';
 | 
			
		||||
@@ -4,6 +4,7 @@ MysqlDns = "root:12345678@tcp(geekai-mysql:3306)/chatgpt_plus?charset=utf8mb4&co
 | 
			
		||||
StaticDir = "./static"
 | 
			
		||||
StaticUrl = "/static"
 | 
			
		||||
WeChatBot = false
 | 
			
		||||
TikaHost = "http://tika:9998"
 | 
			
		||||
 | 
			
		||||
[Session]
 | 
			
		||||
  SecretKey = "azyehq3ivunjhbntz78isj00i4hz2mt9xtddysfucxakadq4qbfrt0b7q3lnvg80"
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,13 @@ services:
 | 
			
		||||
      - ./logs/xxl-job:/data/applogs
 | 
			
		||||
      - ./conf/xxl-job/application.properties:/application.properties
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  tika:
 | 
			
		||||
    image: registry.cn-shenzhen.aliyuncs.com/geekmaster/tika:latest
 | 
			
		||||
    container_name: tika
 | 
			
		||||
    restart: always
 | 
			
		||||
    ports:
 | 
			
		||||
      - "9998:9998"
 | 
			
		||||
  
 | 
			
		||||
  midjourney-proxy:
 | 
			
		||||
    image: registry.cn-shenzhen.aliyuncs.com/geekmaster/midjourney-proxy:2.6.2
 | 
			
		||||
    container_name: geekai-midjourney-proxy
 | 
			
		||||
@@ -52,7 +58,7 @@ services:
 | 
			
		||||
 | 
			
		||||
  # 后端 API 程序
 | 
			
		||||
  geekai-api:
 | 
			
		||||
    image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.0.9-amd64
 | 
			
		||||
    image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-api:v4.1.0-amd64
 | 
			
		||||
    container_name: geekai-api
 | 
			
		||||
    restart: always
 | 
			
		||||
    depends_on:
 | 
			
		||||
@@ -74,7 +80,7 @@ services:
 | 
			
		||||
 | 
			
		||||
  # 前端应用
 | 
			
		||||
  geekai-web:
 | 
			
		||||
    image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.0.9-amd64
 | 
			
		||||
    image: registry.cn-shenzhen.aliyuncs.com/geekmaster/geekai-web:v4.1.0-amd64
 | 
			
		||||
    container_name: geekai-web
 | 
			
		||||
    restart: always
 | 
			
		||||
    depends_on:
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ VUE_APP_ADMIN_USER=admin
 | 
			
		||||
VUE_APP_ADMIN_PASS=admin123
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_DEV_
 | 
			
		||||
VUE_APP_TITLE="Geek-AI 创作系统"
 | 
			
		||||
VUE_APP_VERSION=v4.0.9
 | 
			
		||||
VUE_APP_VERSION=v4.1.0
 | 
			
		||||
 
 | 
			
		||||
@@ -2,4 +2,4 @@ VUE_APP_API_HOST=
 | 
			
		||||
VUE_APP_WS_HOST=
 | 
			
		||||
VUE_APP_KEY_PREFIX=ChatPLUS_
 | 
			
		||||
VUE_APP_TITLE="Geek-AI 创作系统"
 | 
			
		||||
VUE_APP_VERSION=v4.0.9
 | 
			
		||||
VUE_APP_VERSION=v4.1.0
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 5.1 KiB  | 
| 
		 Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 12 KiB  | 
| 
		 Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.7 KiB  | 
| 
		 Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 388 KiB  | 
@@ -3,14 +3,12 @@
 | 
			
		||||
    display flex
 | 
			
		||||
    width 100%
 | 
			
		||||
 | 
			
		||||
    .el-input {
 | 
			
		||||
      width 50%
 | 
			
		||||
    .el-input,.el-select,.el-switch {
 | 
			
		||||
      margin-right 10px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .info {
 | 
			
		||||
      margin-left 6px
 | 
			
		||||
      margin-top 2px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,27 +2,27 @@
 | 
			
		||||
    height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout {
 | 
			
		||||
#app .chat-page {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside {
 | 
			
		||||
#app .chat-page .el-aside {
 | 
			
		||||
    background-color: #252526;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .title-box {
 | 
			
		||||
#app .chat-page .el-aside .title-box {
 | 
			
		||||
    padding: 6px 10px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .title-box span {
 | 
			
		||||
#app .chat-page .el-aside .title-box span {
 | 
			
		||||
    padding-top: 5px;
 | 
			
		||||
    padding-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list {
 | 
			
		||||
#app .chat-page .el-aside .chat-list {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-flow: column;
 | 
			
		||||
    background-color: #28292a;
 | 
			
		||||
@@ -30,28 +30,28 @@
 | 
			
		||||
    border-right: 1px solid #2f3032;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .search-box {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .search-box {
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    padding: 10px 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .search-box .el-input__wrapper {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .search-box .el-input__wrapper {
 | 
			
		||||
    background-color: #363535;
 | 
			
		||||
    box-shadow: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list ::-webkit-scrollbar {
 | 
			
		||||
#app .chat-page .el-aside .chat-list ::-webkit-scrollbar {
 | 
			
		||||
    width: 0;
 | 
			
		||||
    height: 0;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content .chat-list-item {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content .chat-list-item {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    justify-content: flex-start;
 | 
			
		||||
@@ -59,17 +59,17 @@
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content .chat-list-item:hover {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content .chat-list-item:hover {
 | 
			
		||||
    background-color: #343540;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .avatar {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .avatar {
 | 
			
		||||
    width: 28px;
 | 
			
		||||
    height: 28px;
 | 
			
		||||
    border-radius: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .chat-title-input {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .chat-title-input {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    margin-top: 4px;
 | 
			
		||||
    margin-left: 10px;
 | 
			
		||||
@@ -79,7 +79,7 @@
 | 
			
		||||
    width: 190px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .chat-title {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .chat-title {
 | 
			
		||||
    color: #c1c1c1;
 | 
			
		||||
    padding: 5px 10px;
 | 
			
		||||
    max-width: 220px;
 | 
			
		||||
@@ -89,7 +89,7 @@
 | 
			
		||||
    text-overflow: ellipsis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .btn {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .btn {
 | 
			
		||||
    display: none;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 2px;
 | 
			
		||||
@@ -97,19 +97,19 @@
 | 
			
		||||
    color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content .chat-list-item .btn .el-icon {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content .chat-list-item .btn .el-icon {
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content .chat-list-item.active {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content .chat-list-item.active {
 | 
			
		||||
    background-color: #343540;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .chat-list .content .chat-list-item.active .btn {
 | 
			
		||||
#app .chat-page .el-aside .chat-list .content .chat-list-item.active .btn {
 | 
			
		||||
    display: inline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .tool-box {
 | 
			
		||||
#app .chat-page .el-aside .tool-box {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
@@ -117,48 +117,48 @@
 | 
			
		||||
    border-top: 1px solid #3c3c3c;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .tool-box .user-info {
 | 
			
		||||
#app .chat-page .el-aside .tool-box .user-info {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    padding-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link {
 | 
			
		||||
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link .el-image {
 | 
			
		||||
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .el-image {
 | 
			
		||||
    width: 20px;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    border-radius: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link .username {
 | 
			
		||||
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .username {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    line-height: 22px;
 | 
			
		||||
    width: 230px;
 | 
			
		||||
    padding-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-aside .tool-box .user-info .el-dropdown-link .el-icon {
 | 
			
		||||
#app .chat-page .el-aside .tool-box .user-info .el-dropdown-link .el-icon {
 | 
			
		||||
    color: #ccc;
 | 
			
		||||
    line-height: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main {
 | 
			
		||||
#app .chat-page .el-main {
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    --el-main-padding: 0;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-head {
 | 
			
		||||
#app .chat-page .el-main .chat-head {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    background-color: #28292a;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-head .chat-config {
 | 
			
		||||
#app .chat-page .el-main .chat-head .chat-config {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
@@ -166,54 +166,54 @@
 | 
			
		||||
    padding-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-head .chat-config .role-select-label {
 | 
			
		||||
#app .chat-page .el-main .chat-head .chat-config .role-select-label {
 | 
			
		||||
    color: #fff;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-head .chat-config .el-select {
 | 
			
		||||
#app .chat-page .el-main .chat-head .chat-config .el-select {
 | 
			
		||||
    max-width: 150px;
 | 
			
		||||
    margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-head .chat-config .role-select {
 | 
			
		||||
#app .chat-page .el-main .chat-head .chat-config .role-select {
 | 
			
		||||
    max-width: 130px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-head .chat-config .el-button .el-icon {
 | 
			
		||||
#app .chat-page .el-main .chat-head .chat-config .el-button .el-icon {
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-head .iconfont {
 | 
			
		||||
#app .chat-page .el-main .chat-head .iconfont {
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-head .is-circle {
 | 
			
		||||
#app .chat-page .el-main .chat-head .is-circle {
 | 
			
		||||
    margin-left: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-head .is-circle .iconfont {
 | 
			
		||||
#app .chat-page .el-main .chat-head .is-circle .iconfont {
 | 
			
		||||
    margin-right: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box {
 | 
			
		||||
#app .chat-page .el-main .chat-box {
 | 
			
		||||
    min-width: 0;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    background-color: #fff;
 | 
			
		||||
    border-left: 1px solid #4f4f4f;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container {
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container ::-webkit-scrollbar {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container ::-webkit-scrollbar {
 | 
			
		||||
    width: 0;
 | 
			
		||||
    height: 0;
 | 
			
		||||
    background-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .chat-box {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .chat-box {
 | 
			
		||||
    overflow-y: scroll;
 | 
			
		||||
    --content-font-size: 16px;
 | 
			
		||||
    --content-color: #c1c1c1;
 | 
			
		||||
@@ -221,28 +221,28 @@
 | 
			
		||||
    padding: 0 0 50px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .chat-box .chat-line {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .chat-box .chat-line {
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: flex-start;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .re-generate {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .re-generate {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .re-generate .btn-box {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .re-generate .btn-box {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .re-generate .btn-box .el-button .el-icon {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .re-generate .btn-box .el-button .el-icon {
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .input-box {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .input-box {
 | 
			
		||||
    background-color: #fff;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
@@ -251,7 +251,7 @@
 | 
			
		||||
    padding: 0 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .input-box .input-container {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .input-box .input-container {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    border: none;
 | 
			
		||||
@@ -261,24 +261,24 @@
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .input-box .input-container .el-textarea .el-textarea__inner::-webkit-scrollbar {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .input-box .input-container .el-textarea .el-textarea__inner::-webkit-scrollbar {
 | 
			
		||||
    width: 0;
 | 
			
		||||
    height: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .input-box .input-container .select-file {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .input-box .input-container .select-file {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 48px;
 | 
			
		||||
    top: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .input-box .input-container .send-btn {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .input-box .input-container .send-btn {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    right: 12px;
 | 
			
		||||
    top: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container .input-box .input-container .send-btn .el-button {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container .input-box .input-container .send-btn .el-button {
 | 
			
		||||
    padding: 8px 5px;
 | 
			
		||||
    border-radius: 6px;
 | 
			
		||||
    background: #19c37d;
 | 
			
		||||
@@ -286,7 +286,7 @@
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app .common-layout .el-main .chat-box #container::-webkit-scrollbar {
 | 
			
		||||
#app .chat-page .el-main .chat-box #container::-webkit-scrollbar {
 | 
			
		||||
    width: 0;
 | 
			
		||||
    height: 0;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ $borderColor = #4676d0;
 | 
			
		||||
 | 
			
		||||
  height: 100%;
 | 
			
		||||
 | 
			
		||||
  .common-layout {
 | 
			
		||||
  .chat-page {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
 | 
			
		||||
    // left side
 | 
			
		||||
@@ -156,6 +156,20 @@ $borderColor = #4676d0;
 | 
			
		||||
            max-width 130px;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .setting {
 | 
			
		||||
            padding 5px
 | 
			
		||||
            border-radius 5px
 | 
			
		||||
            cursor pointer
 | 
			
		||||
            .iconfont {
 | 
			
		||||
              font-size 18px
 | 
			
		||||
              color #19c37d
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &:hover {
 | 
			
		||||
              background #D5FAD3
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .el-button {
 | 
			
		||||
            .el-icon {
 | 
			
		||||
              margin-right 5px;
 | 
			
		||||
@@ -169,13 +183,25 @@ $borderColor = #4676d0;
 | 
			
		||||
          position relative
 | 
			
		||||
 | 
			
		||||
          ::-webkit-scrollbar {
 | 
			
		||||
            width: 0;
 | 
			
		||||
            height: 0;
 | 
			
		||||
            background-color: transparent;
 | 
			
		||||
            width: 12px /* 滚动条宽度 */
 | 
			
		||||
            background #F1F1F1
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          ::-webkit-scrollbar-track {
 | 
			
		||||
            background-color: #e1e1e1;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          ::-webkit-scrollbar-thumb {
 | 
			
		||||
            background-color: #c1c1c1;
 | 
			
		||||
            border-radius 12px
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          ::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
            background-color: #A8A8A8;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .chat-box {
 | 
			
		||||
            overflow-y: scroll;
 | 
			
		||||
            overflow-y: auto;
 | 
			
		||||
            //border-bottom: 1px solid #4f4f4f
 | 
			
		||||
 | 
			
		||||
            // 变量定义
 | 
			
		||||
@@ -252,25 +278,35 @@ $borderColor = #4676d0;
 | 
			
		||||
                  border: 2px solid #21AA93
 | 
			
		||||
                  border-radius 10px
 | 
			
		||||
                  padding 10px
 | 
			
		||||
                  background-color #F4F4F4
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                  .prompt-input::-webkit-scrollbar {
 | 
			
		||||
                    width: 0;
 | 
			
		||||
                    height: 0;
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  .prompt-input {
 | 
			
		||||
                  .input-inner {
 | 
			
		||||
                    display flex
 | 
			
		||||
                    flex-flow column
 | 
			
		||||
                    width 100%
 | 
			
		||||
                    line-height: 24px
 | 
			
		||||
                    border none
 | 
			
		||||
                    font-size 14px
 | 
			
		||||
                    background none
 | 
			
		||||
                    resize: none
 | 
			
		||||
                    white-space: pre-wrap; /* 保持文本换行 */
 | 
			
		||||
                    word-wrap: break-word; /* 允许单词换行 */
 | 
			
		||||
                    overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */
 | 
			
		||||
 | 
			
		||||
                    .file-list {
 | 
			
		||||
                      padding-bottom 10px
 | 
			
		||||
                    }
 | 
			
		||||
                    .prompt-input::-webkit-scrollbar {
 | 
			
		||||
                      width: 0;
 | 
			
		||||
                      height: 0;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    .prompt-input {
 | 
			
		||||
                      width 100%
 | 
			
		||||
                      line-height: 24px
 | 
			
		||||
                      border none
 | 
			
		||||
                      font-size 14px
 | 
			
		||||
                      background none
 | 
			
		||||
                      resize: none
 | 
			
		||||
                      white-space: pre-wrap; /* 保持文本换行 */
 | 
			
		||||
                      word-wrap: break-word; /* 允许单词换行 */
 | 
			
		||||
                      overflow-wrap: break-word; /* 允许长单词换行,适用于现代浏览器 */
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                  .send-btn {
 | 
			
		||||
                    width 32px
 | 
			
		||||
                    margin-left 10px
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										115
									
								
								web/src/assets/css/login.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,115 @@
 | 
			
		||||
.bg {
 | 
			
		||||
  position fixed
 | 
			
		||||
  left 0
 | 
			
		||||
  right 0
 | 
			
		||||
  top 0
 | 
			
		||||
  bottom 0
 | 
			
		||||
  background-color #313237
 | 
			
		||||
  background-image url("~@/assets/img/login-bg.jpg")
 | 
			
		||||
  background-size cover
 | 
			
		||||
  background-position center
 | 
			
		||||
  background-repeat repeat-y
 | 
			
		||||
  //filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main {
 | 
			
		||||
  .contain {
 | 
			
		||||
    position fixed
 | 
			
		||||
    left 50%
 | 
			
		||||
    top 40%
 | 
			
		||||
    width 90%
 | 
			
		||||
    max-width 400px;
 | 
			
		||||
    transform translate(-50%, -50%)
 | 
			
		||||
    padding 20px 10px;
 | 
			
		||||
    color #ffffff
 | 
			
		||||
    border-radius 10px;
 | 
			
		||||
 | 
			
		||||
    .logo {
 | 
			
		||||
      text-align center
 | 
			
		||||
 | 
			
		||||
      .el-image {
 | 
			
		||||
        width 120px;
 | 
			
		||||
        cursor pointer
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .header {
 | 
			
		||||
      width 100%
 | 
			
		||||
      margin-bottom 24px
 | 
			
		||||
      font-size 24px
 | 
			
		||||
      color $white_v1
 | 
			
		||||
      letter-space 2px
 | 
			
		||||
      text-align center
 | 
			
		||||
      padding-top 10px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .content {
 | 
			
		||||
      width 100%
 | 
			
		||||
      height: auto
 | 
			
		||||
      border-radius 3px
 | 
			
		||||
 | 
			
		||||
      .block {
 | 
			
		||||
        margin-bottom 16px
 | 
			
		||||
 | 
			
		||||
        .el-input__inner {
 | 
			
		||||
          border 1px solid $gray-v6 !important
 | 
			
		||||
 | 
			
		||||
          .el-icon-user, .el-icon-lock {
 | 
			
		||||
            font-size 20px
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .btn-row {
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
 | 
			
		||||
        .login-btn {
 | 
			
		||||
          width 100%
 | 
			
		||||
          font-size 16px
 | 
			
		||||
          letter-spacing 2px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .text-line {
 | 
			
		||||
        justify-content center
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
        font-size 14px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .opt {
 | 
			
		||||
        padding 15px
 | 
			
		||||
        .el-col {
 | 
			
		||||
          text-align center
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .divider {
 | 
			
		||||
        border-top: 2px solid #c1c1c1;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .clogin {
 | 
			
		||||
        padding 15px
 | 
			
		||||
        display flex
 | 
			
		||||
        justify-content center
 | 
			
		||||
 | 
			
		||||
        .iconfont {
 | 
			
		||||
          font-size 20px
 | 
			
		||||
          background: #E9F1F6;
 | 
			
		||||
          padding: 8px;
 | 
			
		||||
          border-radius: 50%;
 | 
			
		||||
        }
 | 
			
		||||
        .iconfont.icon-wechat {
 | 
			
		||||
          color #0bc15f
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .footer {
 | 
			
		||||
    color #ffffff;
 | 
			
		||||
 | 
			
		||||
    .container {
 | 
			
		||||
      padding 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										237
									
								
								web/src/assets/css/markdown/vue.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,237 @@
 | 
			
		||||
.chat-line {
 | 
			
		||||
    ol, ul {
 | 
			
		||||
        margin: 0.8em 0;
 | 
			
		||||
        list-style: normal;
 | 
			
		||||
    }
 | 
			
		||||
    a {
 | 
			
		||||
        color: #42b983;
 | 
			
		||||
        font-weight: 600;
 | 
			
		||||
        padding: 0 2px;
 | 
			
		||||
        text-decoration: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h1,
 | 
			
		||||
    h2,
 | 
			
		||||
    h3,
 | 
			
		||||
    h4,
 | 
			
		||||
    h5,
 | 
			
		||||
    h6 {
 | 
			
		||||
        position: relative;
 | 
			
		||||
        margin-top: 1rem;
 | 
			
		||||
        margin-bottom: 1rem;
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        line-height: 1.4;
 | 
			
		||||
        cursor: text;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h1:hover a.anchor,
 | 
			
		||||
    h2:hover a.anchor,
 | 
			
		||||
    h3:hover a.anchor,
 | 
			
		||||
    h4:hover a.anchor,
 | 
			
		||||
    h5:hover a.anchor,
 | 
			
		||||
    h6:hover a.anchor {
 | 
			
		||||
        text-decoration: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h1 tt,
 | 
			
		||||
    h1 code {
 | 
			
		||||
        font-size: inherit !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h2 tt,
 | 
			
		||||
    h2 code {
 | 
			
		||||
        font-size: inherit !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h3 tt,
 | 
			
		||||
    h3 code {
 | 
			
		||||
        font-size: inherit !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h4 tt,
 | 
			
		||||
    h4 code {
 | 
			
		||||
        font-size: inherit !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h5 tt,
 | 
			
		||||
    h5 code {
 | 
			
		||||
        font-size: inherit !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h6 tt,
 | 
			
		||||
    h6 code {
 | 
			
		||||
        font-size: inherit !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h2 a,
 | 
			
		||||
    h3 a {
 | 
			
		||||
        color: #34495e;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h1 {
 | 
			
		||||
        padding-bottom: .4rem;
 | 
			
		||||
        font-size: 2.2rem;
 | 
			
		||||
        line-height: 1.3;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h2 {
 | 
			
		||||
        font-size: 1.75rem;
 | 
			
		||||
        line-height: 1.225;
 | 
			
		||||
        margin: 35px 0 15px;
 | 
			
		||||
        padding-bottom: 0.5em;
 | 
			
		||||
        border-bottom: 1px solid #ddd;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h3 {
 | 
			
		||||
        font-size: 1.4rem;
 | 
			
		||||
        line-height: 1.43;
 | 
			
		||||
        margin: 20px 0 7px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h4 {
 | 
			
		||||
        font-size: 1.2rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h5 {
 | 
			
		||||
        font-size: 1rem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h6 {
 | 
			
		||||
        font-size: 1rem;
 | 
			
		||||
        color: #777;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    p,
 | 
			
		||||
    blockquote,
 | 
			
		||||
    ul,
 | 
			
		||||
    ol,
 | 
			
		||||
    dl,
 | 
			
		||||
    table {
 | 
			
		||||
        margin: 0.8em 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    li > ol,
 | 
			
		||||
    li > ul {
 | 
			
		||||
        margin: 0 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hr {
 | 
			
		||||
        height: 2px;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        margin: 16px 0;
 | 
			
		||||
        background-color: #e7e7e7;
 | 
			
		||||
        border: 0 none;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        box-sizing: content-box;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    body > h2:first-child {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
        padding-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    body > h1:first-child {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
        padding-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    body > h1:first-child + h2 {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
        padding-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    body > h3:first-child,
 | 
			
		||||
    body > h4:first-child,
 | 
			
		||||
    body > h5:first-child,
 | 
			
		||||
    body > h6:first-child {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
        padding-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    a:first-child h1,
 | 
			
		||||
    a:first-child h2,
 | 
			
		||||
    a:first-child h3,
 | 
			
		||||
    a:first-child h4,
 | 
			
		||||
    a:first-child h5,
 | 
			
		||||
    a:first-child h6 {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
        padding-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h1 p,
 | 
			
		||||
    h2 p,
 | 
			
		||||
    h3 p,
 | 
			
		||||
    h4 p,
 | 
			
		||||
    h5 p,
 | 
			
		||||
    h6 p {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    li p.first {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ul,
 | 
			
		||||
    ol {
 | 
			
		||||
        padding-left: 30px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ul:first-child,
 | 
			
		||||
    ol:first-child {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ul:last-child,
 | 
			
		||||
    ol:last-child {
 | 
			
		||||
        margin-bottom: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    blockquote {
 | 
			
		||||
        border-left: 4px solid #42b983;
 | 
			
		||||
        padding: 10px 15px;
 | 
			
		||||
        color: #777;
 | 
			
		||||
        background-color: rgba(66, 185, 131, .1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table {
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        word-break: initial;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table tr {
 | 
			
		||||
        border-top: 1px solid #dfe2e5;
 | 
			
		||||
        margin: 0;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table tr:nth-child(2n),
 | 
			
		||||
    thead {
 | 
			
		||||
        background-color: #fafafa;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table tr th {
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        border: 1px solid #dfe2e5;
 | 
			
		||||
        border-bottom: 0;
 | 
			
		||||
        text-align: left;
 | 
			
		||||
        margin: 0;
 | 
			
		||||
        padding: 6px 13px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table tr td {
 | 
			
		||||
        border: 1px solid #dfe2e5;
 | 
			
		||||
        text-align: left;
 | 
			
		||||
        margin: 0;
 | 
			
		||||
        padding: 6px 13px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table tr th:first-child,
 | 
			
		||||
    table tr td:first-child {
 | 
			
		||||
        margin-top: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    table tr th:last-child,
 | 
			
		||||
    table tr td:last-child {
 | 
			
		||||
        margin-bottom: 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.1 MiB  | 
@@ -1,152 +1,420 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="chat-line chat-line-prompt">
 | 
			
		||||
  <div class="chat-line chat-line-prompt-list" v-if="listStyle === 'list'">
 | 
			
		||||
    <div class="chat-line-inner">
 | 
			
		||||
        <div class="chat-icon">
 | 
			
		||||
          <img :src="data.icon" alt="User"/>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="chat-item">
 | 
			
		||||
          <div v-if="files.length > 0" class="file-list-box">
 | 
			
		||||
            <div v-for="file in files">
 | 
			
		||||
              <div class="image" v-if="isImage(file.ext)">
 | 
			
		||||
                <el-image :src="file.url" fit="cover"/>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="item" v-else>
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                  <el-image :src="GetFileIcon(file.ext)" fit="cover"  />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="body">
 | 
			
		||||
                  <div class="title">
 | 
			
		||||
                    <el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{file.name}}</el-link>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="info">
 | 
			
		||||
                    <span>{{GetFileType(file.ext)}}</span>
 | 
			
		||||
                    <span>{{FormatFileSize(file.size)}}</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="content" v-html="content"></div>
 | 
			
		||||
          <div class="bar" v-if="data.created_at > 0">
 | 
			
		||||
            <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
            <span class="bar-item">tokens: {{ finalTokens }}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="chat-line chat-line-prompt-chat" v-else>
 | 
			
		||||
    <div class="chat-line-inner">
 | 
			
		||||
      <div class="chat-icon">
 | 
			
		||||
        <img :src="icon" alt="User"/>
 | 
			
		||||
        <img :src="data.icon" alt="User"/>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
        <div class="content" v-html="content"></div>
 | 
			
		||||
        <div class="bar" v-if="createdAt">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
 | 
			
		||||
          <!--          <span class="bar-item">Tokens: {{ finalTokens }}</span>-->
 | 
			
		||||
 | 
			
		||||
        <div v-if="files.length > 0" class="file-list-box">
 | 
			
		||||
          <div v-for="file in files">
 | 
			
		||||
            <div class="image" v-if="isImage(file.ext)">
 | 
			
		||||
              <el-image :src="file.url" fit="cover"/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="item" v-else>
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <el-image :src="GetFileIcon(file.ext)" fit="cover"  />
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="body">
 | 
			
		||||
                <div class="title">
 | 
			
		||||
                  <el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{file.name}}</el-link>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="info">
 | 
			
		||||
                  <span>{{GetFileType(file.ext)}}</span>
 | 
			
		||||
                  <span>{{FormatFileSize(file.size)}}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="content-wrapper">
 | 
			
		||||
          <div class="content" v-html="content"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at > 0">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
          <span class="bar-item">tokens: {{ finalTokens }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import {defineComponent} from "vue"
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue"
 | 
			
		||||
import {Clock} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import hl from "highlight.js";
 | 
			
		||||
import {dateFormat, isImage, processPrompt} from "@/utils/libs";
 | 
			
		||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: 'ChatPrompt',
 | 
			
		||||
  components: {Clock},
 | 
			
		||||
  methods: {},
 | 
			
		||||
  props: {
 | 
			
		||||
    content: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '',
 | 
			
		||||
    },
 | 
			
		||||
    icon: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: 'images/user-icon.png',
 | 
			
		||||
    },
 | 
			
		||||
    createdAt: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '',
 | 
			
		||||
    },
 | 
			
		||||
    tokens: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 0,
 | 
			
		||||
    },
 | 
			
		||||
    model: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '',
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      finalTokens: this.tokens
 | 
			
		||||
const mathjaxPlugin = require('markdown-it-mathjax3')
 | 
			
		||||
const md = require('markdown-it')({
 | 
			
		||||
  breaks: true,
 | 
			
		||||
  html: true,
 | 
			
		||||
  linkify: true,
 | 
			
		||||
  typographer: true,
 | 
			
		||||
  highlight: function (str, lang) {
 | 
			
		||||
    const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
 | 
			
		||||
    // 显示复制代码按钮
 | 
			
		||||
    const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
 | 
			
		||||
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '</textarea>')}</textarea>`
 | 
			
		||||
    if (lang && hl.getLanguage(lang)) {
 | 
			
		||||
      const langHtml = `<span class="lang-name">${lang}</span>`
 | 
			
		||||
      // 处理代码高亮
 | 
			
		||||
      const preCode = hl.highlight(lang, str, true).value
 | 
			
		||||
      // 将代码包裹在 pre 中
 | 
			
		||||
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 处理代码高亮
 | 
			
		||||
    const preCode = md.utils.escapeHtml(str)
 | 
			
		||||
    // 将代码包裹在 pre 中
 | 
			
		||||
    return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
md.use(mathjaxPlugin)
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  data: {
 | 
			
		||||
    type: Object,
 | 
			
		||||
    default: {
 | 
			
		||||
      content: '',
 | 
			
		||||
      created_at: '',
 | 
			
		||||
      tokens: 0,
 | 
			
		||||
      model: '',
 | 
			
		||||
      icon: '',
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    if (!this.finalTokens) {
 | 
			
		||||
      httpPost("/api/chat/tokens", {text: this.content, model: this.model}).then(res => {
 | 
			
		||||
        this.finalTokens = res.data;
 | 
			
		||||
      }).catch(() => {
 | 
			
		||||
      })
 | 
			
		||||
  listStyle: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'list',
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
const finalTokens = ref(props.data.tokens)
 | 
			
		||||
const content =ref(processPrompt(props.data.content))
 | 
			
		||||
const files = ref([])
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  if (!finalTokens.value) {
 | 
			
		||||
    httpPost("/api/chat/tokens", {text: props.data.content, model: props.data.model}).then(res => {
 | 
			
		||||
      finalTokens.value = res.data;
 | 
			
		||||
    }).catch(() => {
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const linkRegex = /(https?:\/\/\S+)/g;
 | 
			
		||||
  const links = props.data.content.match(linkRegex);
 | 
			
		||||
  if (links) {
 | 
			
		||||
    httpPost("/api/upload/list", {urls: links}).then(res => {
 | 
			
		||||
      files.value = res.data
 | 
			
		||||
    }).catch(() => {
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    for (let link of links) {
 | 
			
		||||
      content.value = content.value.replace(link,"")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  content.value = md.render(content.value.trim())
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
.chat-line-prompt {
 | 
			
		||||
  background-color #ffffff;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  width 100%
 | 
			
		||||
  padding-bottom: 1.5rem;
 | 
			
		||||
  padding-top: 1.5rem;
 | 
			
		||||
  border-bottom: 1px solid #d9d9e3;
 | 
			
		||||
@import '@/assets/css/markdown/vue.css';
 | 
			
		||||
.chat-page,.chat-export {
 | 
			
		||||
  .chat-line-prompt-list {
 | 
			
		||||
    background-color #ffffff;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    width 100%
 | 
			
		||||
    padding-bottom: 1.5rem;
 | 
			
		||||
    padding-top: 1.5rem;
 | 
			
		||||
    border-bottom: 1px solid #d9d9e3;
 | 
			
		||||
 | 
			
		||||
  .chat-line-inner {
 | 
			
		||||
    display flex;
 | 
			
		||||
    width 100%;
 | 
			
		||||
    max-width 900px;
 | 
			
		||||
    padding-left 10px;
 | 
			
		||||
    .chat-line-inner {
 | 
			
		||||
      display flex;
 | 
			
		||||
      width 100%;
 | 
			
		||||
      max-width 900px;
 | 
			
		||||
      padding-left 10px;
 | 
			
		||||
 | 
			
		||||
    .chat-icon {
 | 
			
		||||
      margin-right 20px;
 | 
			
		||||
 | 
			
		||||
      img {
 | 
			
		||||
        width: 36px;
 | 
			
		||||
        height: 36px;
 | 
			
		||||
        border-radius: 10px;
 | 
			
		||||
        padding: 1px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .chat-item {
 | 
			
		||||
      width 100%
 | 
			
		||||
      position: relative;
 | 
			
		||||
      padding: 0 5px 0 0;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
 | 
			
		||||
      .content {
 | 
			
		||||
        word-break break-word;
 | 
			
		||||
        padding: 6px 10px;
 | 
			
		||||
        color #374151;
 | 
			
		||||
        font-size: var(--content-font-size);
 | 
			
		||||
        border-radius: 5px;
 | 
			
		||||
        overflow: auto;
 | 
			
		||||
      .chat-icon {
 | 
			
		||||
        margin-right 20px;
 | 
			
		||||
 | 
			
		||||
        img {
 | 
			
		||||
          max-width: 600px;
 | 
			
		||||
          width: 36px;
 | 
			
		||||
          height: 36px;
 | 
			
		||||
          border-radius: 10px;
 | 
			
		||||
          margin 10px 0
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        a {
 | 
			
		||||
          color #20a0ff
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        p {
 | 
			
		||||
          line-height 1.5
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        p:last-child {
 | 
			
		||||
          margin-bottom: 0
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        p:first-child {
 | 
			
		||||
          margin-top 0
 | 
			
		||||
          padding: 1px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .bar {
 | 
			
		||||
        padding 10px;
 | 
			
		||||
      .chat-item {
 | 
			
		||||
        width 100%
 | 
			
		||||
        padding: 0 5px 0 0;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
 | 
			
		||||
        .bar-item {
 | 
			
		||||
          background-color #f7f7f8;
 | 
			
		||||
          color #888
 | 
			
		||||
          padding 3px 5px;
 | 
			
		||||
          margin-right 10px;
 | 
			
		||||
          border-radius 5px;
 | 
			
		||||
 | 
			
		||||
          .el-icon {
 | 
			
		||||
        .file-list-box {
 | 
			
		||||
          display flex
 | 
			
		||||
          flex-flow column
 | 
			
		||||
          .image {
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow row
 | 
			
		||||
            margin-right 10px
 | 
			
		||||
            position relative
 | 
			
		||||
            top 2px;
 | 
			
		||||
 | 
			
		||||
            .el-image {
 | 
			
		||||
              border 1px solid #e3e3e3
 | 
			
		||||
              border-radius 10px
 | 
			
		||||
              margin-bottom 10px
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          .item {
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow row
 | 
			
		||||
            border-radius 10px
 | 
			
		||||
            background-color #ffffff
 | 
			
		||||
            border 1px solid #e3e3e3
 | 
			
		||||
            padding 6px
 | 
			
		||||
            margin-bottom 10px
 | 
			
		||||
 | 
			
		||||
            .icon {
 | 
			
		||||
              .el-image {
 | 
			
		||||
                width 40px
 | 
			
		||||
                height 40px
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            .body {
 | 
			
		||||
              margin-left 8px
 | 
			
		||||
              font-size 14px
 | 
			
		||||
              .title {
 | 
			
		||||
                font-weight bold
 | 
			
		||||
                line-height 24px
 | 
			
		||||
                color #0D0D0D
 | 
			
		||||
              }
 | 
			
		||||
              .info {
 | 
			
		||||
                color #B4B4B4
 | 
			
		||||
 | 
			
		||||
                span {
 | 
			
		||||
                  margin-right 10px
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .content {
 | 
			
		||||
          word-break break-word;
 | 
			
		||||
          padding: 0;
 | 
			
		||||
          color #374151;
 | 
			
		||||
          font-size: var(--content-font-size);
 | 
			
		||||
          border-radius: 5px;
 | 
			
		||||
          overflow: auto;
 | 
			
		||||
 | 
			
		||||
          img {
 | 
			
		||||
            max-width: 600px;
 | 
			
		||||
            border-radius: 10px;
 | 
			
		||||
            margin 10px 0
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          p {
 | 
			
		||||
            line-height 1.5
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          p:last-child {
 | 
			
		||||
            margin-bottom: 0
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          p:first-child {
 | 
			
		||||
            margin-top 0
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .bar {
 | 
			
		||||
          padding 10px 10px 10px 0;
 | 
			
		||||
 | 
			
		||||
          .bar-item {
 | 
			
		||||
            background-color #f7f7f8;
 | 
			
		||||
            color #888
 | 
			
		||||
            padding 3px 5px;
 | 
			
		||||
            margin-right 10px;
 | 
			
		||||
            border-radius 5px;
 | 
			
		||||
 | 
			
		||||
            .el-icon {
 | 
			
		||||
              position relative
 | 
			
		||||
              top 2px;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .chat-line-prompt-chat {
 | 
			
		||||
    background-color #ffffff;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    width 100%
 | 
			
		||||
    padding-bottom: 1.5rem;
 | 
			
		||||
    padding-top: 1.5rem;
 | 
			
		||||
 | 
			
		||||
    .chat-line-inner {
 | 
			
		||||
      display flex;
 | 
			
		||||
      width 100%;
 | 
			
		||||
      padding 0 25px;
 | 
			
		||||
 | 
			
		||||
      .chat-icon {
 | 
			
		||||
        margin-right 20px;
 | 
			
		||||
 | 
			
		||||
        img {
 | 
			
		||||
          width: 36px;
 | 
			
		||||
          height: 36px;
 | 
			
		||||
          border-radius: 50%;
 | 
			
		||||
          padding: 1px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .chat-item {
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        max-width 60%
 | 
			
		||||
 | 
			
		||||
        .file-list-box {
 | 
			
		||||
          display flex
 | 
			
		||||
          flex-flow column
 | 
			
		||||
          .image {
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow row
 | 
			
		||||
            margin-right 10px
 | 
			
		||||
            position relative
 | 
			
		||||
 | 
			
		||||
            .el-image {
 | 
			
		||||
              border 1px solid #e3e3e3
 | 
			
		||||
              border-radius 10px
 | 
			
		||||
              margin-bottom 10px
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          .item {
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow row
 | 
			
		||||
            border-radius 10px
 | 
			
		||||
            background-color #ffffff
 | 
			
		||||
            border 1px solid #e3e3e3
 | 
			
		||||
            padding 6px
 | 
			
		||||
            margin-bottom 10px
 | 
			
		||||
 | 
			
		||||
            .icon {
 | 
			
		||||
              .el-image {
 | 
			
		||||
                width 40px
 | 
			
		||||
                height 40px
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            .body {
 | 
			
		||||
              margin-left 8px
 | 
			
		||||
              font-size 14px
 | 
			
		||||
              .title {
 | 
			
		||||
                font-weight bold
 | 
			
		||||
                line-height 24px
 | 
			
		||||
                color #0D0D0D
 | 
			
		||||
              }
 | 
			
		||||
              .info {
 | 
			
		||||
                color #B4B4B4
 | 
			
		||||
 | 
			
		||||
                span {
 | 
			
		||||
                  margin-right 10px
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        .content-wrapper {
 | 
			
		||||
          display flex
 | 
			
		||||
          .content {
 | 
			
		||||
              word-break break-word;
 | 
			
		||||
              padding: 1rem
 | 
			
		||||
              color #222222;
 | 
			
		||||
              font-size: var(--content-font-size);
 | 
			
		||||
              overflow: auto;
 | 
			
		||||
              background-color #98e165
 | 
			
		||||
              border-radius: 0 10px 10px 10px;
 | 
			
		||||
 | 
			
		||||
              img {
 | 
			
		||||
                max-width: 600px;
 | 
			
		||||
                border-radius: 10px;
 | 
			
		||||
                margin 10px 0
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              p {
 | 
			
		||||
                line-height 1.5
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              p:last-child {
 | 
			
		||||
                margin-bottom: 0
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              p:first-child {
 | 
			
		||||
                margin-top 0
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        .bar {
 | 
			
		||||
          padding 10px 10px 10px 0;
 | 
			
		||||
 | 
			
		||||
          .bar-item {
 | 
			
		||||
            color #888
 | 
			
		||||
            padding 3px 5px;
 | 
			
		||||
            margin-right 10px;
 | 
			
		||||
            border-radius 5px;
 | 
			
		||||
 | 
			
		||||
            .el-icon {
 | 
			
		||||
              position relative
 | 
			
		||||
              top 2px;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="chat-line chat-line-reply">
 | 
			
		||||
  <div class="chat-line chat-line-reply-list" v-if="listStyle === 'list'">
 | 
			
		||||
    <div class="chat-line-inner">
 | 
			
		||||
      <div class="chat-icon">
 | 
			
		||||
        <img :src="data.icon" alt="ChatGPT">
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
        <div class="content" v-html="data.content"></div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
          <!--          <span class="bar-item">Tokens: {{ tokens }}</span>-->
 | 
			
		||||
                    <span class="bar-item">tokens: {{ data.tokens }}</span>
 | 
			
		||||
          <span class="bar-item">
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
@@ -61,6 +61,59 @@
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="chat-line chat-line-reply-chat" v-else>
 | 
			
		||||
    <div class="chat-line-inner">
 | 
			
		||||
      <div class="chat-icon">
 | 
			
		||||
        <img :src="data.icon" alt="ChatGPT">
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
        <div class="content-wrapper">
 | 
			
		||||
          <div class="content" v-html="data.content"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
          <span class="bar-item">tokens: {{ data.tokens }}</span>
 | 
			
		||||
          <span class="bar-item bg">
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  content="复制回答"
 | 
			
		||||
                  placement="bottom"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon class="copy-reply" :data-clipboard-text="data.orgContent">
 | 
			
		||||
                  <DocumentCopy/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </span>
 | 
			
		||||
          <span v-if="!readOnly">
 | 
			
		||||
            <span class="bar-item bg" @click="reGenerate(data.prompt)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                content="重新生成"
 | 
			
		||||
                placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
              <el-icon><Refresh/></el-icon>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
 | 
			
		||||
          <span class="bar-item bg" @click="synthesis(data.orgContent)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                content="生成语音朗读"
 | 
			
		||||
                placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
              <i class="iconfont icon-speaker"></i>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
@@ -71,12 +124,22 @@ import {dateFormat} from "@/utils/libs";
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  data: {
 | 
			
		||||
    type: Object,
 | 
			
		||||
    default: {},
 | 
			
		||||
    default: {
 | 
			
		||||
      icon: "",
 | 
			
		||||
      content: "",
 | 
			
		||||
      created_at: "",
 | 
			
		||||
      tokens: 0,
 | 
			
		||||
      orgContent: ""
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  readOnly: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false
 | 
			
		||||
  }
 | 
			
		||||
  },
 | 
			
		||||
  listStyle: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'list',
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['regen']);
 | 
			
		||||
@@ -92,13 +155,15 @@ const synthesis = (text) => {
 | 
			
		||||
 | 
			
		||||
// 重新生成
 | 
			
		||||
const reGenerate = (prompt) => {
 | 
			
		||||
  console.log(prompt)
 | 
			
		||||
  emits('regen', prompt)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
.common-layout {
 | 
			
		||||
  .chat-line-reply {
 | 
			
		||||
@import '@/assets/css/markdown/vue.css';
 | 
			
		||||
.chat-page,.chat-export {
 | 
			
		||||
  .chat-line-reply-list {
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    background-color: rgba(247, 247, 248, 1);
 | 
			
		||||
    width 100%
 | 
			
		||||
@@ -126,24 +191,18 @@ const reGenerate = (prompt) => {
 | 
			
		||||
      .chat-item {
 | 
			
		||||
        width 100%
 | 
			
		||||
        position: relative;
 | 
			
		||||
        padding: 0 0 0 5px;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
 | 
			
		||||
        .content {
 | 
			
		||||
          min-height 20px;
 | 
			
		||||
          word-break break-word;
 | 
			
		||||
          padding: 6px 10px;
 | 
			
		||||
          padding: 0
 | 
			
		||||
          color #374151;
 | 
			
		||||
          font-size: var(--content-font-size);
 | 
			
		||||
          border-radius: 5px;
 | 
			
		||||
          overflow auto;
 | 
			
		||||
 | 
			
		||||
          a {
 | 
			
		||||
            color #20a0ff
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // control the image size in content
 | 
			
		||||
 | 
			
		||||
          img {
 | 
			
		||||
            max-width: 600px;
 | 
			
		||||
            border-radius: 10px;
 | 
			
		||||
@@ -170,10 +229,11 @@ const reGenerate = (prompt) => {
 | 
			
		||||
 | 
			
		||||
          .code-container {
 | 
			
		||||
            position relative
 | 
			
		||||
            display flex
 | 
			
		||||
 | 
			
		||||
            .hljs {
 | 
			
		||||
              border-radius 10px
 | 
			
		||||
              line-height 1.5
 | 
			
		||||
              width 100%
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .copy-code-btn {
 | 
			
		||||
@@ -194,7 +254,7 @@ const reGenerate = (prompt) => {
 | 
			
		||||
          .lang-name {
 | 
			
		||||
            position absolute;
 | 
			
		||||
            right 10px
 | 
			
		||||
            bottom 50px
 | 
			
		||||
            bottom 20px
 | 
			
		||||
            padding 2px 6px 4px 6px
 | 
			
		||||
            background-color #444444
 | 
			
		||||
            border-radius 10px
 | 
			
		||||
@@ -241,7 +301,7 @@ const reGenerate = (prompt) => {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        .bar {
 | 
			
		||||
          padding 10px;
 | 
			
		||||
          padding 10px 10px 10px 0;
 | 
			
		||||
 | 
			
		||||
          .bar-item {
 | 
			
		||||
            background-color #e7e7e8;
 | 
			
		||||
@@ -277,6 +337,187 @@ const reGenerate = (prompt) => {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .chat-line-reply-chat {
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    width 100%
 | 
			
		||||
    padding-bottom: 1.5rem;
 | 
			
		||||
    padding-top: 1.5rem;
 | 
			
		||||
 | 
			
		||||
    .chat-line-inner {
 | 
			
		||||
      display flex;
 | 
			
		||||
      padding 0 25px;
 | 
			
		||||
      width 100%
 | 
			
		||||
      flex-flow row-reverse
 | 
			
		||||
 | 
			
		||||
      .chat-icon {
 | 
			
		||||
        margin-left 20px;
 | 
			
		||||
 | 
			
		||||
        img {
 | 
			
		||||
          width: 36px;
 | 
			
		||||
          height: 36px;
 | 
			
		||||
          border-radius: 50%
 | 
			
		||||
          padding: 1px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .chat-item {
 | 
			
		||||
        position: relative;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        max-width 60%
 | 
			
		||||
 | 
			
		||||
        .content-wrapper {
 | 
			
		||||
          display flex
 | 
			
		||||
          flex-flow row-reverse
 | 
			
		||||
          .content {
 | 
			
		||||
            min-height 20px;
 | 
			
		||||
            word-break break-word;
 | 
			
		||||
            padding: 1rem
 | 
			
		||||
            color #374151;
 | 
			
		||||
            font-size: var(--content-font-size);
 | 
			
		||||
            overflow auto;
 | 
			
		||||
            background-color #F5F5F5
 | 
			
		||||
            border-radius: 10px 0 10px 10px;
 | 
			
		||||
 | 
			
		||||
            img {
 | 
			
		||||
              max-width: 600px;
 | 
			
		||||
              border-radius: 10px;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            p {
 | 
			
		||||
              line-height 1.5
 | 
			
		||||
 | 
			
		||||
              code {
 | 
			
		||||
                color #374151
 | 
			
		||||
                background-color #e7e7e8
 | 
			
		||||
                padding 0 3px;
 | 
			
		||||
                border-radius 5px;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            p:last-child {
 | 
			
		||||
              margin-bottom: 0
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            p:first-child {
 | 
			
		||||
              margin-top 0
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .code-container {
 | 
			
		||||
              position relative
 | 
			
		||||
              display flex
 | 
			
		||||
 | 
			
		||||
              .hljs {
 | 
			
		||||
                border-radius 10px
 | 
			
		||||
                width 100%
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .copy-code-btn {
 | 
			
		||||
                position: absolute;
 | 
			
		||||
                right 10px
 | 
			
		||||
                top 10px
 | 
			
		||||
                cursor pointer
 | 
			
		||||
                font-size 12px
 | 
			
		||||
                color #c1c1c1
 | 
			
		||||
 | 
			
		||||
                &:hover {
 | 
			
		||||
                  color #20a0ff
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .lang-name {
 | 
			
		||||
              position absolute;
 | 
			
		||||
              right 10px
 | 
			
		||||
              bottom 20px
 | 
			
		||||
              padding 2px 6px 4px 6px
 | 
			
		||||
              background-color #444444
 | 
			
		||||
              border-radius 10px
 | 
			
		||||
              color #00e0e0
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // 设置表格边框
 | 
			
		||||
 | 
			
		||||
            table {
 | 
			
		||||
              width 100%
 | 
			
		||||
              margin-bottom 1rem
 | 
			
		||||
              color #212529
 | 
			
		||||
              border-collapse collapse;
 | 
			
		||||
              border 1px solid #dee2e6;
 | 
			
		||||
              background-color #ffffff
 | 
			
		||||
 | 
			
		||||
              thead {
 | 
			
		||||
                th {
 | 
			
		||||
                  border 1px solid #dee2e6
 | 
			
		||||
                  vertical-align: bottom
 | 
			
		||||
                  border-bottom: 2px solid #dee2e6
 | 
			
		||||
                  padding 10px
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              td {
 | 
			
		||||
                border 1px solid #dee2e6
 | 
			
		||||
                padding 10px
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 代码快
 | 
			
		||||
 | 
			
		||||
            blockquote {
 | 
			
		||||
              margin 0
 | 
			
		||||
              background-color: #ebfffe;
 | 
			
		||||
              padding: 0.8rem 1.5rem;
 | 
			
		||||
              border-left: 0.5rem solid;
 | 
			
		||||
              border-color: #026863;
 | 
			
		||||
              color: #2c3e50;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .bar {
 | 
			
		||||
          padding 10px 10px 10px 0;
 | 
			
		||||
 | 
			
		||||
          .bar-item {
 | 
			
		||||
            color #888
 | 
			
		||||
            padding 3px 5px;
 | 
			
		||||
            margin-right 10px;
 | 
			
		||||
            border-radius 5px;
 | 
			
		||||
 | 
			
		||||
            .el-icon {
 | 
			
		||||
              position relative
 | 
			
		||||
              top 2px;
 | 
			
		||||
              cursor pointer
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .bar-item.bg {
 | 
			
		||||
            background-color #e7e7e8
 | 
			
		||||
            cursor pointer
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .el-button {
 | 
			
		||||
            height 20px
 | 
			
		||||
            padding 5px 2px;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .tool-box {
 | 
			
		||||
        font-size 16px;
 | 
			
		||||
 | 
			
		||||
        .el-button {
 | 
			
		||||
          height 20px
 | 
			
		||||
          padding 5px 2px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										50
									
								
								web/src/components/ChatSetting.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,50 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-dialog
 | 
			
		||||
      class="config-dialog"
 | 
			
		||||
      v-model="showDialog"
 | 
			
		||||
      :close-on-click-modal="true"
 | 
			
		||||
      :before-close="close"
 | 
			
		||||
      style="max-width: 600px"
 | 
			
		||||
      title="聊天配置"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="chat-setting">
 | 
			
		||||
      <el-form :model="data" label-width="100px" label-position="left">
 | 
			
		||||
        <el-form-item label="聊天样式:">
 | 
			
		||||
          <el-radio-group v-model="data.style" @change="(val) => {store.setChatListStyle(val)}">
 | 
			
		||||
            <el-radio value="list">列表样式</el-radio>
 | 
			
		||||
            <el-radio value="chat">对话样式</el-radio>
 | 
			
		||||
          </el-radio-group>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {computed, ref} from "vue"
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
 | 
			
		||||
const data = ref({
 | 
			
		||||
  style: store.chatListStyle,
 | 
			
		||||
})
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show: Boolean,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const showDialog = computed(() => {
 | 
			
		||||
  return props.show
 | 
			
		||||
})
 | 
			
		||||
const emits = defineEmits(['hide']);
 | 
			
		||||
const close = function () {
 | 
			
		||||
  emits('hide', false);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.chat-setting {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										115
									
								
								web/src/components/FileList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,115 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-container class="chat-file-list">
 | 
			
		||||
    <div v-for="file in fileList">
 | 
			
		||||
      <div class="image" v-if="isImage(file.ext)">
 | 
			
		||||
        <el-image :src="file.url" fit="cover"/>
 | 
			
		||||
        <div class="action">
 | 
			
		||||
          <el-icon @click="removeFile(file)"><CircleCloseFilled /></el-icon>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="item" v-else>
 | 
			
		||||
        <div class="icon">
 | 
			
		||||
          <el-image :src="GetFileIcon(file.ext)" fit="cover"  />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="body">
 | 
			
		||||
          <div class="title">
 | 
			
		||||
            <el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{substr(file.name, 30)}}</el-link>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="info">
 | 
			
		||||
            <span>{{GetFileType(file.ext)}}</span>
 | 
			
		||||
            <span>{{FormatFileSize(file.size)}}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="action">
 | 
			
		||||
          <el-icon @click="removeFile(file)"><CircleCloseFilled /></el-icon>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </el-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {CircleCloseFilled} from "@element-plus/icons-vue";
 | 
			
		||||
import {isImage, removeArrayItem, substr} from "@/utils/libs";
 | 
			
		||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  files: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    default:[],
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const emits = defineEmits(['removeFile']);
 | 
			
		||||
const fileList = ref(props.files)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const removeFile = (file) => {
 | 
			
		||||
  fileList.value = removeArrayItem(fileList.value, file, (v1,v2) => v1.url===v2.url)
 | 
			
		||||
  emits('removeFile', file)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
 | 
			
		||||
.chat-file-list {
 | 
			
		||||
  display flex
 | 
			
		||||
  flex-flow row
 | 
			
		||||
  .image {
 | 
			
		||||
    display flex
 | 
			
		||||
    flex-flow row
 | 
			
		||||
    margin-right 10px
 | 
			
		||||
    position relative
 | 
			
		||||
 | 
			
		||||
    .el-image {
 | 
			
		||||
      height 56px
 | 
			
		||||
      width 56px
 | 
			
		||||
      border 1px solid #e3e3e3
 | 
			
		||||
      border-radius 10px
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .item {
 | 
			
		||||
    position relative
 | 
			
		||||
    display flex
 | 
			
		||||
    flex-flow row
 | 
			
		||||
    border-radius 10px
 | 
			
		||||
    background-color #ffffff
 | 
			
		||||
    border 1px solid #e3e3e3
 | 
			
		||||
    padding 6px
 | 
			
		||||
    margin-right 10px
 | 
			
		||||
 | 
			
		||||
    .icon {
 | 
			
		||||
      .el-image {
 | 
			
		||||
        width 40px
 | 
			
		||||
        height 40px
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .body {
 | 
			
		||||
      margin-left 5px
 | 
			
		||||
      font-size 14px
 | 
			
		||||
      .title {
 | 
			
		||||
        line-height 24px
 | 
			
		||||
        color #0D0D0D
 | 
			
		||||
      }
 | 
			
		||||
      .info {
 | 
			
		||||
        color #B4B4B4
 | 
			
		||||
 | 
			
		||||
        span {
 | 
			
		||||
          margin-right 10px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .action {
 | 
			
		||||
    position absolute
 | 
			
		||||
    top -8px
 | 
			
		||||
    right -8px
 | 
			
		||||
    color #da0d54
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    font-size 20px
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-container class="file-list-box">
 | 
			
		||||
  <el-container class="file-select-box">
 | 
			
		||||
    <a class="file-upload-img" @click="fetchFiles">
 | 
			
		||||
      <i class="iconfont icon-attachment-st"></i>
 | 
			
		||||
    </a>
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
                  :auto-upload="true"
 | 
			
		||||
                  :show-file-list="false"
 | 
			
		||||
                  :http-request="afterRead"
 | 
			
		||||
                  accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon class="avatar-uploader-icon">
 | 
			
		||||
                  <Plus/>
 | 
			
		||||
@@ -34,8 +35,8 @@
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  :content="file.name"
 | 
			
		||||
                  placement="top">
 | 
			
		||||
                <el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file.url)"/>
 | 
			
		||||
                <el-image :src="getFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file.url)"/>
 | 
			
		||||
                <el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file)"/>
 | 
			
		||||
                <el-image :src="GetFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file)"/>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
 | 
			
		||||
              <div class="opt">
 | 
			
		||||
@@ -55,6 +56,7 @@ import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {Delete, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import {isImage, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
import {GetFileIcon} from "@/store/system";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  userId: Number,
 | 
			
		||||
@@ -65,30 +67,12 @@ const fileList = ref([])
 | 
			
		||||
 | 
			
		||||
const fetchFiles = () => {
 | 
			
		||||
  show.value = true
 | 
			
		||||
  httpGet("/api/upload/list").then(res => {
 | 
			
		||||
  httpPost("/api/upload/list").then(res => {
 | 
			
		||||
    fileList.value = res.data
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const getFileIcon = (ext) => {
 | 
			
		||||
  const files = {
 | 
			
		||||
    ".docx": "doc.png",
 | 
			
		||||
    ".doc": "doc.png",
 | 
			
		||||
    ".xls": "xls.png",
 | 
			
		||||
    ".xlsx": "xls.png",
 | 
			
		||||
    ".ppt": "ppt.png",
 | 
			
		||||
    ".pptx": "ppt.png",
 | 
			
		||||
    ".md": "md.png",
 | 
			
		||||
    ".pdf": "pdf.png",
 | 
			
		||||
    ".sql": "sql.png"
 | 
			
		||||
  }
 | 
			
		||||
  if (files[ext]) {
 | 
			
		||||
    return '/images/ext/' + files[ext]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return '/images/ext/file.png'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const afterRead = (file) => {
 | 
			
		||||
  const formData = new FormData();
 | 
			
		||||
@@ -113,19 +97,19 @@ const removeFile = (file) => {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const insertURL = (url) => {
 | 
			
		||||
const insertURL = (file) => {
 | 
			
		||||
  show.value = false
 | 
			
		||||
  // 如果是相对路径,处理成绝对路径
 | 
			
		||||
  if (url.indexOf("http") === -1) {
 | 
			
		||||
    url = location.protocol + "//" + location.host + url
 | 
			
		||||
  if (file.url.indexOf("http") === -1) {
 | 
			
		||||
    file.url = location.protocol + "//" + location.host + file.url
 | 
			
		||||
  }
 | 
			
		||||
  emits('selected', url)
 | 
			
		||||
  emits('selected', file)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 | 
			
		||||
.file-list-box {
 | 
			
		||||
.file-select-box {
 | 
			
		||||
  .file-upload-img {
 | 
			
		||||
    .iconfont {
 | 
			
		||||
      font-size: 24px;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
  <div class="foot-container">
 | 
			
		||||
    <div class="footer">
 | 
			
		||||
      Powered by {{ author }} @
 | 
			
		||||
      <el-link type="primary" href="https://github.com/yangjian102621/chatgpt-plus" target="_blank" style="--el-link-text-color:#ffffff">
 | 
			
		||||
      <el-link type="primary" :href="gitURL" target="_blank" style="--el-link-text-color:#ffffff">
 | 
			
		||||
        {{ title }} -
 | 
			
		||||
        {{ version }}
 | 
			
		||||
      </el-link>
 | 
			
		||||
@@ -15,6 +15,7 @@ import {ref} from "vue";
 | 
			
		||||
 | 
			
		||||
const title = ref(process.env.VUE_APP_TITLE)
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION)
 | 
			
		||||
const gitURL = ref(process.env.VUE_APP_GIT_URL)
 | 
			
		||||
const author = ref('极客学长')
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@@ -33,6 +34,10 @@ const author = ref('极客学长')
 | 
			
		||||
    font-size 14px;
 | 
			
		||||
    padding 20px;
 | 
			
		||||
    width 100%
 | 
			
		||||
 | 
			
		||||
    .el-link {
 | 
			
		||||
      color #409eff
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -289,7 +289,7 @@ const submitLogin = () => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  httpPost('/api/user/login', data.value).then((res) => {
 | 
			
		||||
    setUserToken(res.data)
 | 
			
		||||
    setUserToken(res.data.token)
 | 
			
		||||
    ElMessage.success("登录成功!")
 | 
			
		||||
    emits("hide")
 | 
			
		||||
    emits('success')
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								web/src/components/TaskList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,58 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="running-job-list">
 | 
			
		||||
    <div class="running-job-box" v-if="list.length > 0">
 | 
			
		||||
      <div class="job-item" v-for="item in list">
 | 
			
		||||
        <div v-if="item.progress > 0" class="job-item-inner">
 | 
			
		||||
          <el-image v-if="item.img_url" :src="item['img_url']" fit="cover" loading="lazy">
 | 
			
		||||
            <template #placeholder>
 | 
			
		||||
              <div class="image-slot">
 | 
			
		||||
                正在加载图片
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #error>
 | 
			
		||||
              <div class="image-slot">
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <Picture/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-image>
 | 
			
		||||
 | 
			
		||||
          <div class="progress">
 | 
			
		||||
            <el-progress type="circle" :percentage="item.progress" :width="100"
 | 
			
		||||
                         color="#47fff1"/>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-image fit="cover" v-else>
 | 
			
		||||
          <template #error>
 | 
			
		||||
            <div class="image-slot">
 | 
			
		||||
              <i class="iconfont icon-quick-start"></i>
 | 
			
		||||
              <span>任务正在排队中</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-image>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <el-empty :image-size="100" v-else/>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {CircleCloseFilled, Picture} from "@element-plus/icons-vue";
 | 
			
		||||
import {isImage, removeArrayItem, substr} from "@/utils/libs";
 | 
			
		||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  list: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    default:[],
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
@import "~@/assets/css/running-job-list.styl"
 | 
			
		||||
</style>
 | 
			
		||||
@@ -46,8 +46,8 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref, watch} from "vue";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import {DocumentCopy} from "@element-plus/icons-vue";
 | 
			
		||||
@@ -73,7 +73,7 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
// 获取数据
 | 
			
		||||
const fetchData = () => {
 | 
			
		||||
  httpPost('/api/order/list', {page: page.value, page_size: pageSize.value}).then((res) => {
 | 
			
		||||
  httpGet('/api/order/list', {page: page.value, page_size: pageSize.value}).then((res) => {
 | 
			
		||||
    if (res.data) {
 | 
			
		||||
      items.value = res.data.items
 | 
			
		||||
      total.value = res.data.total
 | 
			
		||||
 
 | 
			
		||||
@@ -101,6 +101,12 @@ const routes = [
 | 
			
		||||
        meta: {title: '用户登录'},
 | 
			
		||||
        component: () => import('@/views/Login.vue'),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: 'login-callback',
 | 
			
		||||
        path: '/login/callback',
 | 
			
		||||
        meta: {title: '用户登录'},
 | 
			
		||||
        component: () => import('@/views/LoginCallback.vue'),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: 'register',
 | 
			
		||||
        path: '/register',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,19 @@
 | 
			
		||||
import {defineStore} from 'pinia';
 | 
			
		||||
import Storage from 'good-storage'
 | 
			
		||||
 | 
			
		||||
export const useSharedStore = defineStore('shared', {
 | 
			
		||||
    state: () => ({
 | 
			
		||||
        showLoginDialog: false
 | 
			
		||||
        showLoginDialog: false,
 | 
			
		||||
        chatListStyle: Storage.get("chat_list_style","chat")
 | 
			
		||||
    }),
 | 
			
		||||
    getters: {},
 | 
			
		||||
    actions: {
 | 
			
		||||
        setShowLoginDialog(value) {
 | 
			
		||||
            this.showLoginDialog = value;
 | 
			
		||||
        },
 | 
			
		||||
        setChatListStyle(value) {
 | 
			
		||||
            this.chatListStyle = value;
 | 
			
		||||
            Storage.set("chat_list_style", value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -24,4 +24,38 @@ export function getAdminTheme() {
 | 
			
		||||
 | 
			
		||||
export function setAdminTheme(theme) {
 | 
			
		||||
    Storage.set(ADMIN_THEME, theme)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function GetFileIcon(ext) {
 | 
			
		||||
    const files = {
 | 
			
		||||
        ".docx": "doc.png",
 | 
			
		||||
        ".doc": "doc.png",
 | 
			
		||||
        ".xls": "xls.png",
 | 
			
		||||
        ".xlsx": "xls.png",
 | 
			
		||||
        ".csv": "xls.png",
 | 
			
		||||
        ".ppt": "ppt.png",
 | 
			
		||||
        ".pptx": "ppt.png",
 | 
			
		||||
        ".md": "md.png",
 | 
			
		||||
        ".pdf": "pdf.png",
 | 
			
		||||
        ".sql": "sql.png"
 | 
			
		||||
    }
 | 
			
		||||
    if (files[ext]) {
 | 
			
		||||
        return '/images/ext/' + files[ext]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return '/images/ext/file.png'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取文件类型
 | 
			
		||||
export function GetFileType (ext) {
 | 
			
		||||
    return ext.replace(".", "").toUpperCase()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 将文件大小转成字符
 | 
			
		||||
export function FormatFileSize(bytes) {
 | 
			
		||||
    if (bytes === 0) return '0 Bytes';
 | 
			
		||||
    const k = 1024;
 | 
			
		||||
    const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
 | 
			
		||||
    const i = Math.floor(Math.log(bytes) / Math.log(k));
 | 
			
		||||
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
 | 
			
		||||
}
 | 
			
		||||
@@ -26,18 +26,16 @@ axios.interceptors.request.use(
 | 
			
		||||
    })
 | 
			
		||||
axios.interceptors.response.use(
 | 
			
		||||
    response => {
 | 
			
		||||
        let data = response.data;
 | 
			
		||||
        if (data.code === 0) {
 | 
			
		||||
            return response
 | 
			
		||||
        } else if (data.code === 400) {
 | 
			
		||||
            if (response.request.responseURL.indexOf("/api/admin") !== -1) {
 | 
			
		||||
        return response
 | 
			
		||||
    }, error => {
 | 
			
		||||
        if (error.response.status === 401 || error.response.status === 400) {
 | 
			
		||||
            if (error.response.request.responseURL.indexOf("/api/admin") !== -1) {
 | 
			
		||||
                removeAdminToken()
 | 
			
		||||
            } else {
 | 
			
		||||
                removeUserToken()
 | 
			
		||||
            }
 | 
			
		||||
            return Promise.reject(error.response.data)
 | 
			
		||||
        }
 | 
			
		||||
            return Promise.reject(response.data)
 | 
			
		||||
    }, error => {
 | 
			
		||||
        return Promise.reject(error)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -188,58 +188,13 @@ export function processContent(content) {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const lines = content.split("\n")
 | 
			
		||||
    if (lines.length <= 1) {
 | 
			
		||||
        return content
 | 
			
		||||
    }
 | 
			
		||||
    const texts = []
 | 
			
		||||
    // 定义匹配数学公式的正则表达式
 | 
			
		||||
    const formulaRegex = /^\s*[a-z|A-Z]+[^=]+\s*=\s*[^=]+$/;
 | 
			
		||||
    let hasCode = false
 | 
			
		||||
    for (let i = 0; i < lines.length; i++) {
 | 
			
		||||
        // 处理引用块换行
 | 
			
		||||
        if (lines[i].startsWith(">")) {
 | 
			
		||||
            texts.push(lines[i])
 | 
			
		||||
            texts.push("\n")
 | 
			
		||||
            continue
 | 
			
		||||
        }
 | 
			
		||||
        // 如果包含代码块则跳过公式检测
 | 
			
		||||
        if (lines[i].indexOf("```") !== -1) {
 | 
			
		||||
            texts.push(lines[i])
 | 
			
		||||
            hasCode = true
 | 
			
		||||
            continue
 | 
			
		||||
        }
 | 
			
		||||
        // 识别并处理数学公式,需要排除那些已经被识别出来的公式
 | 
			
		||||
        if (i > 0 && formulaRegex.test(lines[i]) && lines[i - 1].indexOf("$$") === -1 && !hasCode) {
 | 
			
		||||
            texts.push("$$")
 | 
			
		||||
            texts.push(lines[i])
 | 
			
		||||
            texts.push("$$")
 | 
			
		||||
            continue
 | 
			
		||||
        }
 | 
			
		||||
        texts.push(lines[i])
 | 
			
		||||
    }
 | 
			
		||||
    return texts.join("\n")
 | 
			
		||||
    return content
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function processPrompt(prompt) {
 | 
			
		||||
    prompt = prompt.replace(/&/g, "&")
 | 
			
		||||
    return prompt.replace(/&/g, "&")
 | 
			
		||||
        .replace(/</g, "<")
 | 
			
		||||
        .replace(/>/g, ">");
 | 
			
		||||
 | 
			
		||||
    const linkRegex = /(https?:\/\/\S+)/g;
 | 
			
		||||
    const links = prompt.match(linkRegex);
 | 
			
		||||
    if (links) {
 | 
			
		||||
        for (let link of links) {
 | 
			
		||||
            if (isImage(link)) {
 | 
			
		||||
                const index = prompt.indexOf(link)
 | 
			
		||||
                if (prompt.substring(index - 1, 2) !== "]") {
 | 
			
		||||
                    prompt = prompt.replace(link, "\n\n")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return prompt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 判断是否为微信浏览器
 | 
			
		||||
@@ -258,3 +213,4 @@ export function showLoginDialog(router) {
 | 
			
		||||
        // on cancel
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,22 +6,14 @@
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div v-for="item in chatData" :key="item.id">
 | 
			
		||||
        <chat-prompt
 | 
			
		||||
            v-if="item.type==='prompt'"
 | 
			
		||||
            :icon="item.icon"
 | 
			
		||||
            :created-at="dateFormat(item['created_at'])"
 | 
			
		||||
            :tokens="item['tokens']"
 | 
			
		||||
            :model="item['model']"
 | 
			
		||||
            :content="item.content"/>
 | 
			
		||||
        <chat-reply v-else-if="item.type==='reply'"
 | 
			
		||||
                    :data="item" :read-only="true"/>
 | 
			
		||||
        <chat-prompt v-if="item.type==='prompt'" :data="item" list-style="list"/>
 | 
			
		||||
        <chat-reply v-else-if="item.type==='reply'" :data="item" :read-only="true" list-style="list"/>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div><!-- end chat box -->
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import ChatReply from "@/components/ChatReply.vue";
 | 
			
		||||
import ChatPrompt from "@/components/ChatPrompt.vue";
 | 
			
		||||
import {nextTick, onMounted, ref} from "vue";
 | 
			
		||||
@@ -98,7 +90,7 @@ onMounted(() => {
 | 
			
		||||
  padding 0 20px
 | 
			
		||||
 | 
			
		||||
  .chat-box {
 | 
			
		||||
    width 800px;
 | 
			
		||||
    width 100%;
 | 
			
		||||
    // 变量定义
 | 
			
		||||
    --content-font-size: 16px;
 | 
			
		||||
    --content-color: #c1c1c1;
 | 
			
		||||
@@ -110,57 +102,13 @@ onMounted(() => {
 | 
			
		||||
      text-align center
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .chat-line-prompt {
 | 
			
		||||
    .chat-line {
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: flex-start;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
 | 
			
		||||
      .chat-line-inner {
 | 
			
		||||
        .chat-icon {
 | 
			
		||||
          margin-right: 0
 | 
			
		||||
        }
 | 
			
		||||
        .content {
 | 
			
		||||
          padding-top: 0
 | 
			
		||||
          font-size 16px;
 | 
			
		||||
 | 
			
		||||
          p:first-child {
 | 
			
		||||
            margin-top 0
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .chat-line-reply {
 | 
			
		||||
      padding-top: 1.5rem;
 | 
			
		||||
 | 
			
		||||
      .chat-line-inner {
 | 
			
		||||
        display flex
 | 
			
		||||
 | 
			
		||||
        .bar-item {
 | 
			
		||||
          background-color: #f7f7f8;
 | 
			
		||||
          color: #888;
 | 
			
		||||
          padding: 3px 5px;
 | 
			
		||||
          margin-right: 10px;
 | 
			
		||||
          border-radius: 5px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .chat-icon {
 | 
			
		||||
          margin-right: 20px
 | 
			
		||||
 | 
			
		||||
          img {
 | 
			
		||||
            width 30px
 | 
			
		||||
            height 30px
 | 
			
		||||
            border-radius: 10px;
 | 
			
		||||
            padding: 1px
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .chat-item {
 | 
			
		||||
          img {
 | 
			
		||||
            max-width 90%
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        max-width 800px
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="common-layout">
 | 
			
		||||
  <div class="chat-page">
 | 
			
		||||
    <el-container>
 | 
			
		||||
      <el-aside>
 | 
			
		||||
        <div class="chat-list">
 | 
			
		||||
@@ -24,7 +24,7 @@
 | 
			
		||||
          <div class="content" :style="{height: leftBoxHeight+'px'}">
 | 
			
		||||
            <el-row v-for="chat in chatList" :key="chat.chat_id">
 | 
			
		||||
              <div :class="chat.chat_id === activeChat.chat_id?'chat-list-item active':'chat-list-item'"
 | 
			
		||||
                   @click="changeChat(chat)">
 | 
			
		||||
                   @click="loadChat(chat)">
 | 
			
		||||
                <el-image :src="chat.icon" class="avatar"/>
 | 
			
		||||
                <span class="chat-title-input" v-if="chat.edit">
 | 
			
		||||
                  <el-input v-model="tmpChatTitle" size="small" @keydown="titleKeydown($event, chat)"
 | 
			
		||||
@@ -99,6 +99,12 @@
 | 
			
		||||
                </el-tag>
 | 
			
		||||
              </el-option>
 | 
			
		||||
            </el-select>
 | 
			
		||||
 | 
			
		||||
            <span class="setting" @click="showChatSetting = true">
 | 
			
		||||
                    <el-tooltip class="box-item" effect="dark" content="对话设置">
 | 
			
		||||
                      <i class="iconfont icon-config"></i>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                  </span>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div>
 | 
			
		||||
@@ -109,13 +115,8 @@
 | 
			
		||||
                </div>
 | 
			
		||||
                <div v-for="item in chatData" :key="item.id" v-else>
 | 
			
		||||
                  <chat-prompt
 | 
			
		||||
                      v-if="item.type==='prompt'"
 | 
			
		||||
                      :icon="item.icon"
 | 
			
		||||
                      :created-at="dateFormat(item['created_at'])"
 | 
			
		||||
                      :tokens="item['tokens']"
 | 
			
		||||
                      :model="getModelValue(modelID)"
 | 
			
		||||
                      :content="item.content"/>
 | 
			
		||||
                  <chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false"/>
 | 
			
		||||
                      v-if="item.type==='prompt'" :data="item" :list-style="listStyle"/>
 | 
			
		||||
                  <chat-reply v-else-if="item.type==='reply'" :data="item" @regen="reGenerate" :read-only="false" :list-style="listStyle"/>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div><!-- end chat box -->
 | 
			
		||||
 | 
			
		||||
@@ -129,14 +130,18 @@
 | 
			
		||||
 | 
			
		||||
                  <span class="tool-item" v-if="isLogin">
 | 
			
		||||
                    <el-tooltip class="box-item" effect="dark" content="上传附件">
 | 
			
		||||
                      <file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertURL"/>
 | 
			
		||||
                      <file-select v-if="isLogin" :user-id="loginUser.id" @selected="insertFile"/>
 | 
			
		||||
                    </el-tooltip>
 | 
			
		||||
                  </span>
 | 
			
		||||
 | 
			
		||||
                  <div class="input-body">
 | 
			
		||||
                    <div ref="textHeightRef" class="hide-div">{{prompt}}</div>
 | 
			
		||||
                    <div class="input-border">
 | 
			
		||||
                      <textarea
 | 
			
		||||
                      <div class="input-inner">
 | 
			
		||||
                        <div class="file-list" v-if="files.length > 0">
 | 
			
		||||
                          <file-list :files="files" @remove-file="removeFile" />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <textarea
 | 
			
		||||
                            ref="inputRef"
 | 
			
		||||
                            class="prompt-input"
 | 
			
		||||
                            :rows="row"
 | 
			
		||||
@@ -146,6 +151,8 @@
 | 
			
		||||
                            placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行"
 | 
			
		||||
                            autofocus>
 | 
			
		||||
                      </textarea>
 | 
			
		||||
                      </div>
 | 
			
		||||
 | 
			
		||||
                      <span class="send-btn">
 | 
			
		||||
                        <el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
 | 
			
		||||
                          <el-icon>
 | 
			
		||||
@@ -181,21 +188,21 @@
 | 
			
		||||
        </p>
 | 
			
		||||
      </div>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
    <ChatSetting :show="showChatSetting" @hide="showChatSetting = false"/>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from 'vue'
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref, watch} from 'vue'
 | 
			
		||||
import ChatPrompt from "@/components/ChatPrompt.vue";
 | 
			
		||||
import ChatReply from "@/components/ChatReply.vue";
 | 
			
		||||
import {Delete, Edit, More, Plus, Promotion, Search, Share, VideoPause} from '@element-plus/icons-vue'
 | 
			
		||||
import 'highlight.js/styles/a11y-dark.css'
 | 
			
		||||
import {
 | 
			
		||||
  dateFormat,
 | 
			
		||||
  isMobile,
 | 
			
		||||
  processContent,
 | 
			
		||||
  processPrompt,
 | 
			
		||||
  randString,
 | 
			
		||||
  removeArrayItem,
 | 
			
		||||
  UUID
 | 
			
		||||
@@ -210,6 +217,8 @@ import {checkSession} from "@/action/session";
 | 
			
		||||
import Welcome from "@/components/Welcome.vue";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import FileSelect from "@/components/FileSelect.vue";
 | 
			
		||||
import FileList from "@/components/FileList.vue";
 | 
			
		||||
import ChatSetting from "@/components/ChatSetting.vue";
 | 
			
		||||
 | 
			
		||||
const title = ref('ChatGPT-智能助手');
 | 
			
		||||
const models = ref([])
 | 
			
		||||
@@ -236,6 +245,12 @@ const notice = ref("")
 | 
			
		||||
const noticeKey = ref("SYSTEM_NOTICE")
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
const row = ref(1)
 | 
			
		||||
const showChatSetting = ref(false)
 | 
			
		||||
const listStyle = ref(store.chatListStyle)
 | 
			
		||||
watch(() => store.chatListStyle, (newValue) => {
 | 
			
		||||
  listStyle.value = newValue
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if (isMobile()) {
 | 
			
		||||
  router.replace("/mobile/chat")
 | 
			
		||||
@@ -417,12 +432,8 @@ const newChat = () => {
 | 
			
		||||
  connect(null, roleId.value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 切换会话
 | 
			
		||||
const changeChat = (chat) => {
 | 
			
		||||
  localStorage.setItem("chat_id", chat.chat_id)
 | 
			
		||||
  loadChat(chat)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 切换会话
 | 
			
		||||
const loadChat = function (chat) {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
@@ -546,6 +557,7 @@ const socket = ref(null);
 | 
			
		||||
const activelyClose = ref(false); // 主动关闭
 | 
			
		||||
const canSend = ref(true);
 | 
			
		||||
const heartbeatHandle = ref(null)
 | 
			
		||||
const sessionId = ref("")
 | 
			
		||||
const connect = function (chat_id, role_id) {
 | 
			
		||||
  let isNewChat = false;
 | 
			
		||||
  if (!chat_id) {
 | 
			
		||||
@@ -560,7 +572,7 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
 | 
			
		||||
  const _role = getRoleById(role_id);
 | 
			
		||||
  // 初始化 WebSocket 对象
 | 
			
		||||
  const _sessionId = getSessionId();
 | 
			
		||||
  sessionId.value = getSessionId();
 | 
			
		||||
  let host = process.env.VUE_APP_WS_HOST
 | 
			
		||||
  if (host === '') {
 | 
			
		||||
    if (location.protocol === 'https:') {
 | 
			
		||||
@@ -582,7 +594,7 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
      heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/chat/new?session_id=${sessionId.value}&role_id=${role_id}&chat_id=${chat_id}&model_id=${modelID.value}&token=${getUserToken()}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    chatData.value = []; // 初始化聊天数据
 | 
			
		||||
    enableInput()
 | 
			
		||||
@@ -615,10 +627,12 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
        reader.onload = () => {
 | 
			
		||||
          const data = JSON.parse(String(reader.result));
 | 
			
		||||
          if (data.type === 'start') {
 | 
			
		||||
            const prePrompt = chatData.value[chatData.value.length-1].content
 | 
			
		||||
            chatData.value.push({
 | 
			
		||||
              type: "reply",
 | 
			
		||||
              id: randString(32),
 | 
			
		||||
              icon: _role['icon'],
 | 
			
		||||
              prompt:prePrompt,
 | 
			
		||||
              content: ""
 | 
			
		||||
            });
 | 
			
		||||
          } else if (data.type === 'end') { // 消息接收完毕
 | 
			
		||||
@@ -743,12 +757,18 @@ const sendMessage = function () {
 | 
			
		||||
  if (prompt.value.trim().length === 0 || canSend.value === false) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  // 如果携带了文件,则串上文件地址
 | 
			
		||||
  let content = prompt.value
 | 
			
		||||
  if (files.value.length > 0) {
 | 
			
		||||
    content += files.value.map(file => file.url).join(" ")
 | 
			
		||||
  }
 | 
			
		||||
  // 追加消息
 | 
			
		||||
  chatData.value.push({
 | 
			
		||||
    type: "prompt",
 | 
			
		||||
    id: randString(32),
 | 
			
		||||
    icon: loginUser.value.avatar,
 | 
			
		||||
    content: md.render(processPrompt(prompt.value)),
 | 
			
		||||
    content: content,
 | 
			
		||||
    model: getModelValue(modelID.value),
 | 
			
		||||
    created_at: new Date().getTime() / 1000,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@@ -758,9 +778,11 @@ const sendMessage = function () {
 | 
			
		||||
 | 
			
		||||
  showHello.value = false
 | 
			
		||||
  disableInput(false)
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
 | 
			
		||||
  tmpChatTitle.value = prompt.value
 | 
			
		||||
  prompt.value = '';
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: content}));
 | 
			
		||||
  tmpChatTitle.value = content
 | 
			
		||||
  prompt.value = ''
 | 
			
		||||
  files.value = []
 | 
			
		||||
  row.value = 1
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -810,9 +832,11 @@ const loadChatHistory = function (chatId) {
 | 
			
		||||
    showHello.value = false
 | 
			
		||||
    for (let i = 0; i < data.length; i++) {
 | 
			
		||||
      data[i].orgContent = data[i].content;
 | 
			
		||||
      data[i].content = md.render(processContent(data[i].content))
 | 
			
		||||
      if (i > 0 && data[i].type === 'reply') {
 | 
			
		||||
        data[i].prompt = data[i - 1].orgContent
 | 
			
		||||
      if (data[i].type === 'reply') {
 | 
			
		||||
        data[i].content = md.render(processContent(data[i].content))
 | 
			
		||||
        if (i > 0) {
 | 
			
		||||
          data[i].prompt = data[i - 1].orgContent
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      chatData.value.push(data[i]);
 | 
			
		||||
    }
 | 
			
		||||
@@ -829,7 +853,7 @@ const loadChatHistory = function (chatId) {
 | 
			
		||||
 | 
			
		||||
const stopGenerate = function () {
 | 
			
		||||
  showStopGenerate.value = false;
 | 
			
		||||
  httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => {
 | 
			
		||||
  httpGet("/api/chat/stop?session_id=" + sessionId.value).then(() => {
 | 
			
		||||
    enableInput()
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
@@ -843,7 +867,7 @@ const reGenerate = function (prompt) {
 | 
			
		||||
    type: "prompt",
 | 
			
		||||
    id: randString(32),
 | 
			
		||||
    icon: loginUser.value.avatar,
 | 
			
		||||
    content: md.render(text)
 | 
			
		||||
    content: text
 | 
			
		||||
  });
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: prompt}));
 | 
			
		||||
}
 | 
			
		||||
@@ -893,9 +917,13 @@ const notShow = () => {
 | 
			
		||||
  showNotice.value = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 插入文件路径
 | 
			
		||||
const insertURL = (url) => {
 | 
			
		||||
  prompt.value += " " + url + " "
 | 
			
		||||
const files = ref([])
 | 
			
		||||
// 插入文件
 | 
			
		||||
const insertFile = (file) => {
 | 
			
		||||
  files.value.push(file)
 | 
			
		||||
}
 | 
			
		||||
const removeFile = (file) => {
 | 
			
		||||
  files.value = removeArrayItem(files.value, file, (v1,v2) => v1.url===v2.url)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -86,43 +86,7 @@
 | 
			
		||||
          <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
            <div class="job-list-box">
 | 
			
		||||
              <h2>任务列表</h2>
 | 
			
		||||
              <div class="running-job-list">
 | 
			
		||||
                <div class="running-job-box" v-if="runningJobs.length > 0">
 | 
			
		||||
                  <div class="job-item" v-for="item in runningJobs" :key="item.id">
 | 
			
		||||
                    <div v-if="item.progress > 0" class="job-item-inner">
 | 
			
		||||
                      <el-image :src="item['img_url']" fit="cover" loading="lazy">
 | 
			
		||||
                        <template #placeholder>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            正在加载图片
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template #error>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            <el-icon>
 | 
			
		||||
                              <Picture/>
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-image>
 | 
			
		||||
 | 
			
		||||
                      <div class="progress">
 | 
			
		||||
                        <el-progress type="circle" :percentage="item.progress" :width="100"
 | 
			
		||||
                                     color="#47fff1"/>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <el-image fit="cover" v-else>
 | 
			
		||||
                      <template #error>
 | 
			
		||||
                        <div class="image-slot">
 | 
			
		||||
                          <i class="iconfont icon-quick-start"></i>
 | 
			
		||||
                          <span>任务正在排队中</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </el-image>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <el-empty :image-size="100" v-else/>
 | 
			
		||||
              </div>
 | 
			
		||||
              <task-list :list="runningJobs" />
 | 
			
		||||
 | 
			
		||||
              <h2>创作记录</h2>
 | 
			
		||||
              <div class="finish-job-list">
 | 
			
		||||
@@ -228,6 +192,7 @@ import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
// const paramBoxHeight = ref(0)
 | 
			
		||||
@@ -369,7 +334,7 @@ const fetchRunningJobs = () => {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/dall/jobs?status=0`).then(res => {
 | 
			
		||||
  httpGet(`/api/dall/jobs?finish=false`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
@@ -402,7 +367,7 @@ const fetchFinishJobs = () => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  page.value = page.value + 1
 | 
			
		||||
 | 
			
		||||
  httpGet(`/api/dall/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
  httpGet(`/api/dall/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
    if (res.data.length < pageSize.value) {
 | 
			
		||||
      isOver.value = true
 | 
			
		||||
    }
 | 
			
		||||
@@ -454,7 +419,7 @@ const removeImage = (event, item) => {
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpPost("/api/dall/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
 | 
			
		||||
    httpGet("/api/dall/remove", {id: item.id, user_id: item.user}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
@@ -477,7 +442,7 @@ const publishImage = (event, item, action) => {
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/dall/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
  httpGet("/api/dall/publish", {id: item.id, action: action,user_id:item.user_id}).then(() => {
 | 
			
		||||
    ElMessage.success(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
 
 | 
			
		||||
@@ -49,15 +49,15 @@
 | 
			
		||||
              <div  v-if="!licenseConfig.de_copy">
 | 
			
		||||
                <el-dropdown-item>
 | 
			
		||||
                  <i class="iconfont icon-book"></i>
 | 
			
		||||
                  <a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
 | 
			
		||||
                  <a :href="docsURL" target="_blank">
 | 
			
		||||
                    用户手册
 | 
			
		||||
                  </a>
 | 
			
		||||
                </el-dropdown-item>
 | 
			
		||||
 | 
			
		||||
                <el-dropdown-item>
 | 
			
		||||
                  <i class="iconfont icon-github"></i>
 | 
			
		||||
                  <a href="https://ai.r9it.com/docs/" target="_blank">
 | 
			
		||||
                    Geek-AI {{ version }}
 | 
			
		||||
                  <a :href="gitURL" target="_blank">
 | 
			
		||||
                    GeekAI {{ version }}
 | 
			
		||||
                  </a>
 | 
			
		||||
                </el-dropdown-item>
 | 
			
		||||
              </div>
 | 
			
		||||
@@ -142,7 +142,7 @@ import {checkSession} from "@/action/session";
 | 
			
		||||
import {removeUserToken} from "@/store/session";
 | 
			
		||||
import LoginDialog from "@/components/LoginDialog.vue";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import ConfigDialog from "@/components/ConfigDialog.vue";
 | 
			
		||||
import ConfigDialog from "@/components/UserInfoDialog.vue";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
@@ -157,6 +157,8 @@ const version = ref(process.env.VUE_APP_VERSION)
 | 
			
		||||
const routerViewKey = ref(0)
 | 
			
		||||
const showConfigDialog = ref(false)
 | 
			
		||||
const licenseConfig = ref({})
 | 
			
		||||
const docsURL = ref(process.env.VUE_APP_DOCS_URL)
 | 
			
		||||
const gitURL = ref(process.env.VUE_APP_GIT_URL)
 | 
			
		||||
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
const show = ref(false)
 | 
			
		||||
@@ -215,10 +217,11 @@ const init = () => {
 | 
			
		||||
const logout = function () {
 | 
			
		||||
  httpGet('/api/user/logout').then(() => {
 | 
			
		||||
    removeUserToken()
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    loginUser.value = {}
 | 
			
		||||
    // 刷新组件
 | 
			
		||||
    routerViewKey.value += 1
 | 
			
		||||
    router.push("/login")
 | 
			
		||||
    // store.setShowLoginDialog(true)
 | 
			
		||||
    // loginUser.value = {}
 | 
			
		||||
    // // 刷新组件
 | 
			
		||||
    // routerViewKey.value += 1
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error('注销失败!');
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -163,7 +163,7 @@
 | 
			
		||||
          </el-form>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="task-list-box" @scrollend="handleScrollEnd">
 | 
			
		||||
      <div class="task-list-box">
 | 
			
		||||
        <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
          <div class="extra-params">
 | 
			
		||||
            <el-form>
 | 
			
		||||
@@ -450,43 +450,7 @@
 | 
			
		||||
 | 
			
		||||
          <div class="job-list-box">
 | 
			
		||||
            <h2>任务列表</h2>
 | 
			
		||||
            <div class="running-job-list">
 | 
			
		||||
              <div class="running-job-box" v-if="runningJobs.length > 0">
 | 
			
		||||
                <div class="job-item" v-for="item in runningJobs">
 | 
			
		||||
                  <div v-if="item.progress > 0" class="job-item-inner">
 | 
			
		||||
                    <el-image :src="item['img_url']" fit="cover" loading="lazy">
 | 
			
		||||
                      <template #placeholder>
 | 
			
		||||
                        <div class="image-slot">
 | 
			
		||||
                          正在加载图片
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
 | 
			
		||||
                      <template #error>
 | 
			
		||||
                        <div class="image-slot">
 | 
			
		||||
                          <el-icon>
 | 
			
		||||
                            <Picture/>
 | 
			
		||||
                          </el-icon>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </el-image>
 | 
			
		||||
 | 
			
		||||
                    <div class="progress">
 | 
			
		||||
                      <el-progress type="circle" :percentage="item.progress" :width="100"
 | 
			
		||||
                                   color="#47fff1"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <el-image fit="cover" v-else>
 | 
			
		||||
                    <template #error>
 | 
			
		||||
                      <div class="image-slot">
 | 
			
		||||
                        <i class="iconfont icon-quick-start"></i>
 | 
			
		||||
                        <span>任务正在排队中</span>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-image>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <el-empty :image-size="100" v-else/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <task-list :list="runningJobs" />
 | 
			
		||||
 | 
			
		||||
            <h2>创作记录</h2>
 | 
			
		||||
            <div class="finish-job-list">
 | 
			
		||||
@@ -617,6 +581,7 @@ import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {copyObj, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
const paramBoxHeight = ref(0)
 | 
			
		||||
@@ -817,7 +782,7 @@ const fetchRunningJobs = () => {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  httpGet(`/api/mj/jobs?status=0`).then(res => {
 | 
			
		||||
  httpGet(`/api/mj/jobs?finish=false`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
@@ -855,7 +820,7 @@ const fetchFinishJobs = () => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  page.value = page.value + 1
 | 
			
		||||
  // 获取已完成的任务
 | 
			
		||||
  httpGet(`/api/mj/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
  httpGet(`/api/mj/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
      if (jobs[i]['img_url'] !== "") {
 | 
			
		||||
@@ -996,7 +961,7 @@ const removeImage = (item) => {
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpPost("/api/mj/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
 | 
			
		||||
    httpGet("/api/mj/remove", {id: item.id, user_id: item.user_id}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
@@ -1014,7 +979,7 @@ const publishImage = (item, action) => {
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
  httpGet("/api/mj/publish", {id: item.id, action: action,user_id: item.user_id}).then(() => {
 | 
			
		||||
    ElMessage.success(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="param-line" style="padding-top: 10px">
 | 
			
		||||
                <el-form-item label="采样调度器">
 | 
			
		||||
                <el-form-item label="采样调度">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-select v-model="params.scheduler" style="width:176px">
 | 
			
		||||
@@ -296,39 +296,8 @@
 | 
			
		||||
          <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
            <div class="job-list-box">
 | 
			
		||||
              <h2>任务列表</h2>
 | 
			
		||||
              <div class="running-job-list">
 | 
			
		||||
                <div class="running-job-box" v-if="runningJobs.length > 0">
 | 
			
		||||
                  <div class="job-item" v-for="item in runningJobs">
 | 
			
		||||
                    <div v-if="item.progress > 0" class="job-item-inner">
 | 
			
		||||
                      <el-image :src="item['img_url']" fit="cover" loading="lazy">
 | 
			
		||||
                        <template #placeholder>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            正在加载图片
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template #error>
 | 
			
		||||
                          <div class="image-slot"></div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-image>
 | 
			
		||||
 | 
			
		||||
                      <div class="progress">
 | 
			
		||||
                        <el-progress type="circle" :percentage="item.progress" :width="100"
 | 
			
		||||
                                     color="#47fff1"/>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <el-image fit="cover" v-else>
 | 
			
		||||
                      <template #error>
 | 
			
		||||
                        <div class="image-slot">
 | 
			
		||||
                          <i class="iconfont icon-quick-start"></i>
 | 
			
		||||
                          <span>任务正在排队中</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </el-image>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <el-empty :image-size="100" v-else/>
 | 
			
		||||
              </div>
 | 
			
		||||
              <task-list :list="runningJobs" />
 | 
			
		||||
              
 | 
			
		||||
              <h2>创作记录</h2>
 | 
			
		||||
              <div class="finish-job-list">
 | 
			
		||||
                <div v-if="finishedJobs.length > 0">
 | 
			
		||||
@@ -506,6 +475,7 @@ import {checkSession} from "@/action/session";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
// const paramBoxHeight = ref(0)
 | 
			
		||||
@@ -661,7 +631,7 @@ const fetchRunningJobs = () => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/sd/jobs?status=0`).then(res => {
 | 
			
		||||
  httpGet(`/api/sd/jobs?finish=0`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
@@ -694,7 +664,7 @@ const fetchFinishJobs = () => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  page.value = page.value + 1
 | 
			
		||||
 | 
			
		||||
  httpGet(`/api/sd/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
  httpGet(`/api/sd/jobs?finish=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
    if (res.data.length < pageSize.value) {
 | 
			
		||||
      isOver.value = true
 | 
			
		||||
    }
 | 
			
		||||
@@ -761,7 +731,7 @@ const removeImage = (event, item) => {
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpPost("/api/sd/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
 | 
			
		||||
    httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
@@ -780,7 +750,7 @@ const publishImage = (event, item, action) => {
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
  httpGet("/api/sd/publish", {id: item.id, action: action, user_id: item.user}).then(() => {
 | 
			
		||||
    ElMessage.success(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="index-page" :style="{height: winHeight+'px'}">
 | 
			
		||||
    <div :class="bgClass"></div>
 | 
			
		||||
    <div class="index-bg" :style="{backgroundImage: 'url('+bgImgUrl+')'}"></div>
 | 
			
		||||
    <div class="menu-box">
 | 
			
		||||
      <el-menu
 | 
			
		||||
          mode="horizontal"
 | 
			
		||||
@@ -12,17 +12,17 @@
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="menu-item">
 | 
			
		||||
          <span v-if="!licenseConfig.de_copy">
 | 
			
		||||
            <a href="https://ai.r9it.com/docs/install/" target="_blank">
 | 
			
		||||
            <a :href="docsURL" target="_blank">
 | 
			
		||||
            <el-button type="primary" round>
 | 
			
		||||
              <i class="iconfont icon-book"></i>
 | 
			
		||||
              <span>部署文档</span>
 | 
			
		||||
              <span>文档</span>
 | 
			
		||||
            </el-button>
 | 
			
		||||
          </a>
 | 
			
		||||
 | 
			
		||||
          <a href="https://github.com/yangjian102621/chatgpt-plus" target="_blank">
 | 
			
		||||
          <a :href="gitURL" target="_blank">
 | 
			
		||||
            <el-button type="success" round>
 | 
			
		||||
              <i class="iconfont icon-github"></i>
 | 
			
		||||
              <span>项目源码</span>
 | 
			
		||||
              <span>源码</span>
 | 
			
		||||
            </el-button>
 | 
			
		||||
          </a>
 | 
			
		||||
          </span>
 | 
			
		||||
@@ -65,7 +65,6 @@
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
// import * as THREE from 'three';
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import FooterBar from "@/components/FooterBar.vue";
 | 
			
		||||
@@ -84,17 +83,20 @@ const title = ref("Geek-AI 创作系统")
 | 
			
		||||
const logo = ref("/images/logo.png")
 | 
			
		||||
const slogan = ref("我辈之人,先干为敬,陪您先把 AI 用起来")
 | 
			
		||||
const licenseConfig = ref({})
 | 
			
		||||
// const size = Math.max(window.innerWidth * 0.5, window.innerHeight * 0.8)
 | 
			
		||||
const winHeight = window.innerHeight - 150
 | 
			
		||||
const bgClass = ref('fixed-bg')
 | 
			
		||||
const bgImgUrl = ref('')
 | 
			
		||||
const isLogin = ref(false)
 | 
			
		||||
const docsURL = ref(process.env.VUE_APP_DOCS_URL)
 | 
			
		||||
const gitURL = ref(process.env.VUE_APP_GIT_URL)
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
    logo.value = res.data.logo
 | 
			
		||||
    if (res.data.rand_bg) {
 | 
			
		||||
      bgClass.value = "rand-bg"
 | 
			
		||||
    if (res.data.index_bg_url) {
 | 
			
		||||
      bgImgUrl.value = res.data.index_bg_url
 | 
			
		||||
    } else {
 | 
			
		||||
      bgImgUrl.value = "/images/index-bg.jpg"
 | 
			
		||||
    }
 | 
			
		||||
    if (res.data.slogan) {
 | 
			
		||||
      slogan.value = res.data.slogan
 | 
			
		||||
@@ -126,24 +128,12 @@ onMounted(() => {
 | 
			
		||||
  align-items baseline
 | 
			
		||||
  padding-top 150px
 | 
			
		||||
 | 
			
		||||
  .fixed-bg {
 | 
			
		||||
  .index-bg {
 | 
			
		||||
    position absolute
 | 
			
		||||
    top 0
 | 
			
		||||
    left 0
 | 
			
		||||
    width 100vw
 | 
			
		||||
    height 100vh
 | 
			
		||||
    background-image url("~@/assets/img/ai-bg.jpg")
 | 
			
		||||
    background-size: cover;
 | 
			
		||||
    background-position: center;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .rand-bg {
 | 
			
		||||
    position absolute
 | 
			
		||||
    top 0
 | 
			
		||||
    left 0
 | 
			
		||||
    width 100vw
 | 
			
		||||
    height 100vh
 | 
			
		||||
    background-image url("https://api.dujin.org/bing/1920.php")
 | 
			
		||||
    filter: blur(8px);
 | 
			
		||||
    background-size: cover;
 | 
			
		||||
    background-position: center;
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
            <el-button class="login-btn" size="large" type="primary" @click="login">登录</el-button>
 | 
			
		||||
          </el-row>
 | 
			
		||||
 | 
			
		||||
          <el-row class="opt" :gutter="20">
 | 
			
		||||
          <el-row class="opt" :gutter="24">
 | 
			
		||||
            <el-col :span="8"><el-link type="primary" @click="router.push('/register')">注册</el-link></el-col>
 | 
			
		||||
            <el-col :span="8">
 | 
			
		||||
              <el-link type="info" @click="showResetPass = true">重置密码</el-link>
 | 
			
		||||
@@ -43,6 +43,14 @@
 | 
			
		||||
              <el-link type="info" @click="router.push('/')">首页</el-link>
 | 
			
		||||
            </el-col>
 | 
			
		||||
          </el-row>
 | 
			
		||||
 | 
			
		||||
          <div v-if="wechatLoginURL !== ''">
 | 
			
		||||
            <el-divider class="divider">其他登录方式</el-divider>
 | 
			
		||||
 | 
			
		||||
            <div class="clogin">
 | 
			
		||||
              <a class="wechat-login" :href="wechatLoginURL"><i class="iconfont icon-wechat"></i></a>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -57,7 +65,7 @@
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {Lock, UserFilled} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
@@ -75,28 +83,38 @@ const password = ref(process.env.VUE_APP_PASS);
 | 
			
		||||
const showResetPass = ref(false)
 | 
			
		||||
const logo = ref("/images/logo.png")
 | 
			
		||||
const licenseConfig = ref({})
 | 
			
		||||
const wechatLoginURL = ref('')
 | 
			
		||||
 | 
			
		||||
// 获取系统配置
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  logo.value = res.data.logo
 | 
			
		||||
  title.value = res.data.title
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showMessageError("获取系统配置失败:" + e.message)
 | 
			
		||||
})
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  // 获取系统配置
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
    logo.value = res.data.logo
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("获取系统配置失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
httpGet("/api/config/license").then(res => {
 | 
			
		||||
  licenseConfig.value = res.data
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showMessageError("获取 License 配置:" + e.message)
 | 
			
		||||
})
 | 
			
		||||
  httpGet("/api/config/license").then(res => {
 | 
			
		||||
    licenseConfig.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("获取 License 配置:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
checkSession().then(() => {
 | 
			
		||||
  if (isMobile()) {
 | 
			
		||||
    router.push('/mobile')
 | 
			
		||||
  } else {
 | 
			
		||||
    router.push('/chat')
 | 
			
		||||
  }
 | 
			
		||||
}).catch(() => {
 | 
			
		||||
  checkSession().then(() => {
 | 
			
		||||
    if (isMobile()) {
 | 
			
		||||
      router.push('/mobile')
 | 
			
		||||
    } else {
 | 
			
		||||
      router.push('/chat')
 | 
			
		||||
    }
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const returnURL = `${location.protocol}//${location.host}/login/callback`
 | 
			
		||||
  httpGet("/api/user/clogin?return_url="+returnURL).then(res => {
 | 
			
		||||
    wechatLoginURL.value = res.data.url
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const handleKeyup = (e) => {
 | 
			
		||||
@@ -114,7 +132,7 @@ const login = function () {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
 | 
			
		||||
    setUserToken(res.data)
 | 
			
		||||
    setUserToken(res.data.token)
 | 
			
		||||
    if (isMobile()) {
 | 
			
		||||
      router.push('/mobile')
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -129,99 +147,5 @@ const login = function () {
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.bg {
 | 
			
		||||
  position fixed
 | 
			
		||||
  left 0
 | 
			
		||||
  right 0
 | 
			
		||||
  top 0
 | 
			
		||||
  bottom 0
 | 
			
		||||
  background-color #313237
 | 
			
		||||
  background-image url("~@/assets/img/login-bg.jpg")
 | 
			
		||||
  background-size cover
 | 
			
		||||
  background-position center
 | 
			
		||||
  background-repeat repeat-y
 | 
			
		||||
  //filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main {
 | 
			
		||||
  .contain {
 | 
			
		||||
    position fixed
 | 
			
		||||
    left 50%
 | 
			
		||||
    top 40%
 | 
			
		||||
    width 90%
 | 
			
		||||
    max-width 400px;
 | 
			
		||||
    transform translate(-50%, -50%)
 | 
			
		||||
    padding 20px 10px;
 | 
			
		||||
    color #ffffff
 | 
			
		||||
    border-radius 10px;
 | 
			
		||||
 | 
			
		||||
    .logo {
 | 
			
		||||
      text-align center
 | 
			
		||||
 | 
			
		||||
      .el-image {
 | 
			
		||||
        width 120px;
 | 
			
		||||
        cursor pointer
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .header {
 | 
			
		||||
      width 100%
 | 
			
		||||
      margin-bottom 24px
 | 
			
		||||
      font-size 24px
 | 
			
		||||
      color $white_v1
 | 
			
		||||
      letter-space 2px
 | 
			
		||||
      text-align center
 | 
			
		||||
      padding-top 10px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .content {
 | 
			
		||||
      width 100%
 | 
			
		||||
      height: auto
 | 
			
		||||
      border-radius 3px
 | 
			
		||||
 | 
			
		||||
      .block {
 | 
			
		||||
        margin-bottom 16px
 | 
			
		||||
 | 
			
		||||
        .el-input__inner {
 | 
			
		||||
          border 1px solid $gray-v6 !important
 | 
			
		||||
 | 
			
		||||
          .el-icon-user, .el-icon-lock {
 | 
			
		||||
            font-size 20px
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .btn-row {
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
 | 
			
		||||
        .login-btn {
 | 
			
		||||
          width 100%
 | 
			
		||||
          font-size 16px
 | 
			
		||||
          letter-spacing 2px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .text-line {
 | 
			
		||||
        justify-content center
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
        font-size 14px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .opt {
 | 
			
		||||
        padding 15px
 | 
			
		||||
        .el-col {
 | 
			
		||||
          text-align center
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .footer {
 | 
			
		||||
    color #ffffff;
 | 
			
		||||
 | 
			
		||||
    .container {
 | 
			
		||||
      padding 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@import "@/assets/css/login.styl"
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										105
									
								
								web/src/views/LoginCallback.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,105 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="login-callback"
 | 
			
		||||
       v-loading="loading"
 | 
			
		||||
       element-loading-text="正在同步登录信息..."
 | 
			
		||||
       :style="{ height: winHeight + 'px' }">
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="show"
 | 
			
		||||
        :close-on-click-modal="false"
 | 
			
		||||
        :show-close="false"
 | 
			
		||||
        style="width: 360px;"
 | 
			
		||||
    >
 | 
			
		||||
      <el-result
 | 
			
		||||
          icon="success"
 | 
			
		||||
          title="登录成功"
 | 
			
		||||
          style="--el-result-padding:10px"
 | 
			
		||||
      >
 | 
			
		||||
          <template #sub-title>
 | 
			
		||||
            <div class="user-info">
 | 
			
		||||
              <div class="line">您的初始账户信息如下:</div>
 | 
			
		||||
              <div class="line"><span>用户名:</span>{{username}}</div>
 | 
			
		||||
              <div class="line"><span>密码:</span>{{password}}</div>
 | 
			
		||||
              <div class="line">您后期也可以通过此账号和密码登录</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
          <template #extra>
 | 
			
		||||
            <el-button type="primary" @click="finishLogin">我知道了</el-button>
 | 
			
		||||
          </template>
 | 
			
		||||
      </el-result>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue"
 | 
			
		||||
import {useRouter} from "vue-router"
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {setUserToken} from "@/store/session";
 | 
			
		||||
import {isMobile} from "@/utils/libs";
 | 
			
		||||
 | 
			
		||||
const winHeight = ref(window.innerHeight)
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const show = ref(false)
 | 
			
		||||
const username = ref('')
 | 
			
		||||
const password = ref('')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const code = router.currentRoute.value.query.code
 | 
			
		||||
if (code === "") {
 | 
			
		||||
  ElMessage.error({message: "登录失败:code 参数不能为空",duration: 2000, onClose: () => router.push("/")})
 | 
			
		||||
} else {
 | 
			
		||||
  // 发送请求获取用户信息
 | 
			
		||||
  httpGet("/api/user/clogin/callback",{login_type: "wx",code: code}).then(res => {
 | 
			
		||||
    setUserToken(res.data.token)
 | 
			
		||||
    if (res.data.username) {
 | 
			
		||||
      username.value = res.data.username
 | 
			
		||||
      password.value = res.data.password
 | 
			
		||||
      show.value = true
 | 
			
		||||
      loading.value = false
 | 
			
		||||
    } else {
 | 
			
		||||
      finishLogin()
 | 
			
		||||
    }
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessageBox.alert(e.message, {
 | 
			
		||||
      confirmButtonText: '重新登录',
 | 
			
		||||
      type:"error",
 | 
			
		||||
      title:"登录失败",
 | 
			
		||||
      callback: () => {
 | 
			
		||||
        router.push("/login")
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
const finishLogin = () => {
 | 
			
		||||
  if (isMobile()) {
 | 
			
		||||
    router.push('/mobile')
 | 
			
		||||
  } else {
 | 
			
		||||
    router.push('/chat')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.login-callback {
 | 
			
		||||
  .user-info {
 | 
			
		||||
    display flex
 | 
			
		||||
    flex-direction column
 | 
			
		||||
    padding 10px
 | 
			
		||||
    border 1px dashed #e1e1e1
 | 
			
		||||
    border-radius 10px
 | 
			
		||||
    .line {
 | 
			
		||||
      text-align left
 | 
			
		||||
      font-size 14px
 | 
			
		||||
      line-height 1.5
 | 
			
		||||
 | 
			
		||||
      span{
 | 
			
		||||
        font-weight bold
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -333,7 +333,7 @@ const wechatPay = (row) => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const queryOrder = (orderNo) => {
 | 
			
		||||
  httpPost("/api/payment/query", {order_no: orderNo}).then(res => {
 | 
			
		||||
  httpGet("/api/order/query", {order_no: orderNo}).then(res => {
 | 
			
		||||
    if (res.data.status === 1) {
 | 
			
		||||
      text.value = "扫码成功,请在手机上进行支付!"
 | 
			
		||||
      queryOrder(orderNo)
 | 
			
		||||
 
 | 
			
		||||
@@ -178,7 +178,7 @@ const tableData = ref([])
 | 
			
		||||
const sortedTableData = ref([])
 | 
			
		||||
const role = ref({context: []})
 | 
			
		||||
const formRef = ref(null)
 | 
			
		||||
const optTitle = ref({})
 | 
			
		||||
const optTitle = ref("")
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
 | 
			
		||||
const rules = reactive({
 | 
			
		||||
@@ -231,7 +231,7 @@ const fetchData = () => {
 | 
			
		||||
      sortedData.forEach((id, index) => {
 | 
			
		||||
        ids.push(parseInt(id))
 | 
			
		||||
        sorts.push(index+1)
 | 
			
		||||
        items.value[index].sort_num = index + 1
 | 
			
		||||
        tableData.value[index].sort_num = index + 1
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      httpPost("/api/admin/role/sort", {ids: ids, sorts: sorts}).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
@@ -153,7 +153,7 @@
 | 
			
		||||
        v-model="showChatItemDialog"
 | 
			
		||||
        title="对话详情"
 | 
			
		||||
    >
 | 
			
		||||
      <div class="chat-box common-layout">
 | 
			
		||||
      <div class="chat-box chat-page">
 | 
			
		||||
        <div v-for="item in messages" :key="item.id">
 | 
			
		||||
          <chat-prompt
 | 
			
		||||
              v-if="item.type==='prompt'"
 | 
			
		||||
 
 | 
			
		||||
@@ -162,7 +162,7 @@
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="绑定API-KEY:" prop="apikey">
 | 
			
		||||
          <el-select v-model="item.key_id" placeholder="请选择 API KEY" clearable>
 | 
			
		||||
          <el-select v-model="item.key_id" placeholder="请选择 API KEY" filterable clearable>
 | 
			
		||||
            <el-option v-for="v in apiKeys" :value="v.id" :label="v.name" :key="v.id">
 | 
			
		||||
              {{ v.name }}
 | 
			
		||||
              <el-text type="info" size="small">{{ substr(v.api_url, 50) }}</el-text>
 | 
			
		||||
@@ -229,7 +229,7 @@ const platforms = ref([])
 | 
			
		||||
 | 
			
		||||
// 获取 API KEY
 | 
			
		||||
const apiKeys = ref([])
 | 
			
		||||
httpGet('/api/admin/apikey/list?status=true&type=chat').then(res => {
 | 
			
		||||
httpGet('/api/admin/apikey/list?type=chat').then(res => {
 | 
			
		||||
  apiKeys.value = res.data
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  ElMessage.error("获取 API KEY 失败:" + e.message)
 | 
			
		||||
 
 | 
			
		||||
@@ -33,32 +33,42 @@
 | 
			
		||||
                  </el-input>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="开放注册" prop="enabled_register">
 | 
			
		||||
                  <el-switch v-model="system['enabled_register']"/>
 | 
			
		||||
                  <el-tooltip
 | 
			
		||||
                      effect="dark"
 | 
			
		||||
                      content="关闭注册之后只能通过管理后台添加用户"
 | 
			
		||||
                      raw-content
 | 
			
		||||
                      placement="right"
 | 
			
		||||
                  >
 | 
			
		||||
                    <el-icon>
 | 
			
		||||
                      <InfoFilled/>
 | 
			
		||||
                    </el-icon>
 | 
			
		||||
                  </el-tooltip>
 | 
			
		||||
                <el-form-item label="首页背景图" prop="logo">
 | 
			
		||||
                  <div class="tip-input">
 | 
			
		||||
                    <el-input v-model="system['index_bg_url']" placeholder="网站首页背景图片">
 | 
			
		||||
                      <template #append>
 | 
			
		||||
                        <el-upload
 | 
			
		||||
                            :auto-upload="true"
 | 
			
		||||
                            :show-file-list="false"
 | 
			
		||||
                            @click="beforeUpload('index_bg_url')"
 | 
			
		||||
                            :http-request="uploadImg"
 | 
			
		||||
                        >
 | 
			
		||||
                          <el-icon class="uploader-icon">
 | 
			
		||||
                            <UploadFilled/>
 | 
			
		||||
                          </el-icon>
 | 
			
		||||
                        </el-upload>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </el-input>
 | 
			
		||||
                    <el-button type="primary" @click="system.index_bg_url = 'https://api.dujin.org/bing/1920.php'">使用动态背景</el-button>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="动态背景">
 | 
			
		||||
                  <el-switch v-model="system['rand_bg']"/>
 | 
			
		||||
                  <el-tooltip
 | 
			
		||||
                      effect="dark"
 | 
			
		||||
                      content="打开之后前端首页将使用随机壁纸作为背景图"
 | 
			
		||||
                      raw-content
 | 
			
		||||
                      placement="right"
 | 
			
		||||
                  >
 | 
			
		||||
                    <el-icon>
 | 
			
		||||
                      <InfoFilled/>
 | 
			
		||||
                    </el-icon>
 | 
			
		||||
                  </el-tooltip>
 | 
			
		||||
                <el-form-item label="开放注册" prop="enabled_register">
 | 
			
		||||
                  <div class="tip-input">
 | 
			
		||||
                    <el-switch v-model="system['enabled_register']"/>
 | 
			
		||||
                    <div class="info">
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="dark"
 | 
			
		||||
                          content="关闭注册之后只能通过管理后台添加用户"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon>
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <el-form-item label="注册方式" prop="register_ways">
 | 
			
		||||
@@ -211,17 +221,21 @@
 | 
			
		||||
              </el-tab-pane>
 | 
			
		||||
              <el-tab-pane label="众筹支付">
 | 
			
		||||
                <el-form-item label="启用众筹功能" prop="enabled_reward">
 | 
			
		||||
                  <el-switch v-model="system['enabled_reward']"/>
 | 
			
		||||
                  <el-tooltip
 | 
			
		||||
                      effect="dark"
 | 
			
		||||
                      content="如果关闭次功能将不在用户菜单显示众筹二维码"
 | 
			
		||||
                      raw-content
 | 
			
		||||
                      placement="right"
 | 
			
		||||
                  >
 | 
			
		||||
                    <el-icon>
 | 
			
		||||
                      <InfoFilled/>
 | 
			
		||||
                    </el-icon>
 | 
			
		||||
                  </el-tooltip>
 | 
			
		||||
                  <div class="tip-input">
 | 
			
		||||
                    <el-switch v-model="system['enabled_reward']"/>
 | 
			
		||||
                    <div class="info">
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="dark"
 | 
			
		||||
                          content="如果关闭次功能将不在用户菜单显示众筹二维码"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon>
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </el-form-item>
 | 
			
		||||
 | 
			
		||||
                <div v-if="system['enabled_reward']">
 | 
			
		||||
@@ -534,7 +548,6 @@ const onUploadImg = (files, callback) => {
 | 
			
		||||
 | 
			
		||||
          .el-icon {
 | 
			
		||||
            font-size 16px
 | 
			
		||||
            margin-left 10px
 | 
			
		||||
            cursor pointer
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -241,7 +241,7 @@ const editChat = (row) => {
 | 
			
		||||
  tmpChatTitle.value = row.title
 | 
			
		||||
}
 | 
			
		||||
const saveTitle = () => {
 | 
			
		||||
  httpPost('/api/chat/update', {id: item.value.id, title: tmpChatTitle.value}).then(() => {
 | 
			
		||||
  httpPost('/api/chat/update', {chat_id: item.value.chat_id, title: tmpChatTitle.value}).then(() => {
 | 
			
		||||
    showSuccessToast("操作成功!");
 | 
			
		||||
    item.value.title = tmpChatTitle.value;
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,637 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="mobile-mj">
 | 
			
		||||
    <van-form @submit="generate">
 | 
			
		||||
      <div class="text-line">图片比例</div>
 | 
			
		||||
      <div class="text-line">
 | 
			
		||||
        <van-row :gutter="10">
 | 
			
		||||
          <van-col :span="4" v-for="item in rates" :key="item.value">
 | 
			
		||||
            <div
 | 
			
		||||
                :class="item.value === params.rate ? 'rate active' : 'rate'"
 | 
			
		||||
                @click="changeRate(item)">
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <van-image :src="item.img" fit="cover"></van-image>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="text">{{ item.text }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </van-col>
 | 
			
		||||
        </van-row>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="text-line">模型选择</div>
 | 
			
		||||
      <div class="text-line">
 | 
			
		||||
        <van-row :gutter="10">
 | 
			
		||||
          <van-col :span="8" v-for="item in models" :key="item.value">
 | 
			
		||||
            <div :class="item.value === params.model ? 'model active' : 'model'"
 | 
			
		||||
                 @click="changeModel(item)">
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <van-image :src="item.img" fit="cover"></van-image>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="text">
 | 
			
		||||
                <van-text-ellipsis :content="item.text"/>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </van-col>
 | 
			
		||||
        </van-row>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="text-line">
 | 
			
		||||
        <van-field label="创意度">
 | 
			
		||||
          <template #input>
 | 
			
		||||
            <van-slider v-model.number="params.chaos" :max="100" :step="1"
 | 
			
		||||
                        @update:model-value="showToast('当前值:' + params.chaos)"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </van-field>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="text-line">
 | 
			
		||||
        <van-field label="风格化">
 | 
			
		||||
          <template #input>
 | 
			
		||||
            <van-slider v-model.number="params.stylize" :max="1000" :step="1"
 | 
			
		||||
                        @update:model-value="showToast('当前值:' + params.stylize)"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </van-field>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="text-line">
 | 
			
		||||
        <van-field label="原始模式">
 | 
			
		||||
          <template #input>
 | 
			
		||||
            <van-switch v-model="params.raw"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </van-field>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="text-line">
 | 
			
		||||
        <van-tabs v-model:active="activeName" @change="tabChange" animated>
 | 
			
		||||
          <van-tab title="文生图" name="txt2img">
 | 
			
		||||
            <div class="text-line">
 | 
			
		||||
              <van-field v-model="params.prompt"
 | 
			
		||||
                         rows="3"
 | 
			
		||||
                         autosize
 | 
			
		||||
                         type="textarea"
 | 
			
		||||
                         placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"/>
 | 
			
		||||
            </div>
 | 
			
		||||
          </van-tab>
 | 
			
		||||
          <van-tab title="图生图" name="img2img">
 | 
			
		||||
            <div class="text-line">
 | 
			
		||||
              <van-field v-model="params.prompt"
 | 
			
		||||
                         rows="3"
 | 
			
		||||
                         autosize
 | 
			
		||||
                         type="textarea"
 | 
			
		||||
                         placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"/>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="text-line">
 | 
			
		||||
              <van-uploader v-model="imgList" :after-read="uploadImg"/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="text-line">
 | 
			
		||||
              <van-field label="垫图权重">
 | 
			
		||||
                <template #input>
 | 
			
		||||
                  <van-slider v-model.number="params.iw" :max="1" :step="0.01"
 | 
			
		||||
                              @update:model-value="showToast('当前值:' + params.iw)"/>
 | 
			
		||||
                </template>
 | 
			
		||||
              </van-field>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="tip-text">提示:只有于 niji6 和 v6 模型支持一致性功能,如果选择其他模型此功能将会生成失败。</div>
 | 
			
		||||
            <van-cell-group>
 | 
			
		||||
              <van-field
 | 
			
		||||
                  v-model="params.cref"
 | 
			
		||||
                  center
 | 
			
		||||
                  clearable
 | 
			
		||||
                  label="角色一致性"
 | 
			
		||||
                  placeholder="请输入图片URL或者上传图片"
 | 
			
		||||
              >
 | 
			
		||||
                <template #button>
 | 
			
		||||
                  <van-uploader @click="beforeUpload('cref')" :after-read="uploadImg">
 | 
			
		||||
                    <van-button size="mini" type="primary" icon="plus"/>
 | 
			
		||||
                  </van-uploader>
 | 
			
		||||
                </template>
 | 
			
		||||
              </van-field>
 | 
			
		||||
            </van-cell-group>
 | 
			
		||||
 | 
			
		||||
            <van-cell-group>
 | 
			
		||||
              <van-field
 | 
			
		||||
                  v-model="params.sref"
 | 
			
		||||
                  center
 | 
			
		||||
                  clearable
 | 
			
		||||
                  label="风格一致性"
 | 
			
		||||
                  placeholder="请输入图片URL或者上传图片"
 | 
			
		||||
              >
 | 
			
		||||
                <template #button>
 | 
			
		||||
                  <van-uploader @click="beforeUpload('sref')" :after-read="uploadImg">
 | 
			
		||||
                    <van-button size="mini" type="primary" icon="plus"/>
 | 
			
		||||
                  </van-uploader>
 | 
			
		||||
                </template>
 | 
			
		||||
              </van-field>
 | 
			
		||||
            </van-cell-group>
 | 
			
		||||
 | 
			
		||||
            <div class="text-line">
 | 
			
		||||
              <van-field label="一致性权重">
 | 
			
		||||
                <template #input>
 | 
			
		||||
                  <van-slider v-model.number="params.cw" :max="100" :step="1"
 | 
			
		||||
                              @update:model-value="showToast('当前值:' + params.cw)"/>
 | 
			
		||||
                </template>
 | 
			
		||||
              </van-field>
 | 
			
		||||
            </div>
 | 
			
		||||
          </van-tab>
 | 
			
		||||
          <van-tab title="融图" name="blend">
 | 
			
		||||
            <div class="tip-text">请上传两张以上的图片,最多不超过五张,超过五张图片请使用图生图功能。</div>
 | 
			
		||||
            <div class="text-line">
 | 
			
		||||
              <van-uploader v-model="imgList" :after-read="uploadImg"/>
 | 
			
		||||
            </div>
 | 
			
		||||
          </van-tab>
 | 
			
		||||
          <van-tab title="换脸" name="swapFace">
 | 
			
		||||
            <div class="tip-text">请上传两张有脸部的图片,用左边图片的脸替换右边图片的脸。</div>
 | 
			
		||||
            <div class="text-line">
 | 
			
		||||
              <van-uploader v-model="imgList" :after-read="uploadImg"/>
 | 
			
		||||
            </div>
 | 
			
		||||
          </van-tab>
 | 
			
		||||
        </van-tabs>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="text-line">
 | 
			
		||||
        <van-collapse v-model="activeColspan">
 | 
			
		||||
          <van-collapse-item title="反向提示词" name="neg_prompt">
 | 
			
		||||
            <van-field
 | 
			
		||||
                v-model="params.neg_prompt"
 | 
			
		||||
                rows="3"
 | 
			
		||||
                autosize
 | 
			
		||||
                type="textarea"
 | 
			
		||||
                placeholder="不想出现在图片上的元素(例如:树,建筑)"
 | 
			
		||||
            />
 | 
			
		||||
          </van-collapse-item>
 | 
			
		||||
        </van-collapse>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="text-line">
 | 
			
		||||
        <el-tag>绘图消耗{{ mjPower }}算力,U/V 操作消耗{{ mjActionPower }}算力,当前算力:{{ power }}</el-tag>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="text-line">
 | 
			
		||||
        <van-button round block type="primary" native-type="submit">
 | 
			
		||||
          立即生成
 | 
			
		||||
        </van-button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </van-form>
 | 
			
		||||
 | 
			
		||||
    <h3>任务列表</h3>
 | 
			
		||||
    <div class="running-job-list">
 | 
			
		||||
      <van-empty v-if="runningJobs.length ===0"
 | 
			
		||||
                 image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
 | 
			
		||||
                 image-size="80"
 | 
			
		||||
                 description="暂无记录"
 | 
			
		||||
      />
 | 
			
		||||
      <van-grid :gutter="10" :column-num="3" v-else>
 | 
			
		||||
        <van-grid-item v-for="item in runningJobs">
 | 
			
		||||
          <div v-if="item.progress > 0">
 | 
			
		||||
            <van-image :src="item['img_url']">
 | 
			
		||||
              <template v-slot:error>加载失败</template>
 | 
			
		||||
            </van-image>
 | 
			
		||||
            <div class="progress">
 | 
			
		||||
              <van-circle
 | 
			
		||||
                  v-model:current-rate="item.progress"
 | 
			
		||||
                  :rate="item.progress"
 | 
			
		||||
                  :speed="100"
 | 
			
		||||
                  :text="item.progress+'%'"
 | 
			
		||||
                  :stroke-width="60"
 | 
			
		||||
                  size="90px"
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div v-else class="task-in-queue">
 | 
			
		||||
            <span class="icon"><i class="iconfont icon-quick-start"></i></span>
 | 
			
		||||
            <span class="text">排队中</span>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
        </van-grid-item>
 | 
			
		||||
      </van-grid>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <h3>创作记录</h3>
 | 
			
		||||
    <div class="finish-job-list">
 | 
			
		||||
      <van-empty v-if="finishedJobs.length ===0"
 | 
			
		||||
                 image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
 | 
			
		||||
                 image-size="80"
 | 
			
		||||
                 description="暂无记录"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <van-list v-else
 | 
			
		||||
                v-model:error="error"
 | 
			
		||||
                v-model:loading="loading"
 | 
			
		||||
                :finished="finished"
 | 
			
		||||
                error-text="请求失败,点击重新加载"
 | 
			
		||||
                finished-text="没有更多了"
 | 
			
		||||
                @load="onLoad"
 | 
			
		||||
      >
 | 
			
		||||
        <van-grid :gutter="10" :column-num="2">
 | 
			
		||||
          <van-grid-item v-for="item in finishedJobs">
 | 
			
		||||
            <div class="job-item">
 | 
			
		||||
              <van-image
 | 
			
		||||
                  :src="item['thumb_url']"
 | 
			
		||||
                  :class="item['can_opt'] ? '' : 'upscale'"
 | 
			
		||||
                  lazy-load
 | 
			
		||||
                  @click="imageView(item)"
 | 
			
		||||
                  fit="cover">
 | 
			
		||||
                <template v-slot:loading>
 | 
			
		||||
                  <van-loading type="spinner" size="20"/>
 | 
			
		||||
                </template>
 | 
			
		||||
              </van-image>
 | 
			
		||||
 | 
			
		||||
              <div class="opt" v-if="item['can_opt']">
 | 
			
		||||
 | 
			
		||||
                <van-grid :gutter="3" :column-num="4">
 | 
			
		||||
                  <van-grid-item><a @click="upscale(1, item)" class="opt-btn">U1</a></van-grid-item>
 | 
			
		||||
                  <van-grid-item><a @click="upscale(2, item)" class="opt-btn">U2</a></van-grid-item>
 | 
			
		||||
                  <van-grid-item><a @click="upscale(3, item)" class="opt-btn">U3</a></van-grid-item>
 | 
			
		||||
                  <van-grid-item><a @click="upscale(4, item)" class="opt-btn">U4</a></van-grid-item>
 | 
			
		||||
                  <van-grid-item><a @click="variation(1, item)" class="opt-btn">V1</a></van-grid-item>
 | 
			
		||||
                  <van-grid-item><a @click="variation(2, item)" class="opt-btn">V2</a></van-grid-item>
 | 
			
		||||
                  <van-grid-item><a @click="variation(3, item)" class="opt-btn">V3</a></van-grid-item>
 | 
			
		||||
                  <van-grid-item><a @click="variation(4, item)" class="opt-btn">V4</a></van-grid-item>
 | 
			
		||||
                </van-grid>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="remove">
 | 
			
		||||
                <el-button type="danger" :icon="Delete" @click="removeImage(item)" circle/>
 | 
			
		||||
                <el-button type="warning" v-if="item.publish" @click="publishImage(item, false)"
 | 
			
		||||
                           circle>
 | 
			
		||||
                  <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                </el-button>
 | 
			
		||||
                <el-button type="success" v-else @click="publishImage(item, true)" circle>
 | 
			
		||||
                  <i class="iconfont icon-share-bold"></i>
 | 
			
		||||
                </el-button>
 | 
			
		||||
                <el-button type="primary" @click="showPrompt(item)" circle>
 | 
			
		||||
                  <i class="iconfont icon-prompt"></i>
 | 
			
		||||
                </el-button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </van-grid-item>
 | 
			
		||||
        </van-grid>
 | 
			
		||||
      </van-list>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from "vue";
 | 
			
		||||
import {
 | 
			
		||||
  showConfirmDialog,
 | 
			
		||||
  showFailToast,
 | 
			
		||||
  showNotify,
 | 
			
		||||
  showToast,
 | 
			
		||||
  showDialog,
 | 
			
		||||
  showImagePreview,
 | 
			
		||||
  showSuccessToast
 | 
			
		||||
} from "vant";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import Compressor from "compressorjs";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {Delete} from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
const activeColspan = ref([""])
 | 
			
		||||
 | 
			
		||||
const rates = [
 | 
			
		||||
  {css: "square", value: "1:1", text: "1:1", img: "/images/mj/rate_1_1.png"},
 | 
			
		||||
  {css: "size2-3", value: "2:3", text: "2:3", img: "/images/mj/rate_3_4.png"},
 | 
			
		||||
  {css: "size3-4", value: "3:4", text: "3:4", img: "/images/mj/rate_3_4.png"},
 | 
			
		||||
  {css: "size4-3", value: "4:3", text: "4:3", img: "/images/mj/rate_4_3.png"},
 | 
			
		||||
  {css: "size16-9", value: "16:9", text: "16:9", img: "/images/mj/rate_16_9.png"},
 | 
			
		||||
  {css: "size9-16", value: "9:16", text: "9:16", img: "/images/mj/rate_9_16.png"},
 | 
			
		||||
]
 | 
			
		||||
const models = [
 | 
			
		||||
  {text: "MJ-6.0", value: " --v 6", img: "/images/mj/mj-v6.png"},
 | 
			
		||||
  {text: "MJ-5.2", value: " --v 5.2", img: "/images/mj/mj-v5.2.png"},
 | 
			
		||||
  {text: "Niji5", value: " --niji 5", img: "/images/mj/mj-niji.png"},
 | 
			
		||||
  {text: "Niji5 可爱", value: " --niji 5 --style cute", img: "/images/mj/nj1.jpg"},
 | 
			
		||||
  {text: "Niji5 风景", value: " --niji 5 --style scenic", img: "/images/mj/nj2.jpg"},
 | 
			
		||||
  {text: "Niji6", value: " --niji 6", img: "/images/mj/nj3.jpg"},
 | 
			
		||||
]
 | 
			
		||||
const imgList = ref([])
 | 
			
		||||
const params = ref({
 | 
			
		||||
  task_type: "image",
 | 
			
		||||
  rate: rates[0].value,
 | 
			
		||||
  model: models[0].value,
 | 
			
		||||
  chaos: 0,
 | 
			
		||||
  stylize: 0,
 | 
			
		||||
  seed: 0,
 | 
			
		||||
  img_arr: [],
 | 
			
		||||
  raw: false,
 | 
			
		||||
  iw: 0,
 | 
			
		||||
  prompt: "",
 | 
			
		||||
  neg_prompt: "",
 | 
			
		||||
  tile: false,
 | 
			
		||||
  quality: 0,
 | 
			
		||||
  cref: "",
 | 
			
		||||
  sref: "",
 | 
			
		||||
  cw: 0,
 | 
			
		||||
})
 | 
			
		||||
const userId = ref(0)
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const runningJobs = ref([])
 | 
			
		||||
const finishedJobs = ref([])
 | 
			
		||||
const socket = ref(null)
 | 
			
		||||
const power = ref(0)
 | 
			
		||||
const activeName = ref("txt2img")
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    power.value = user['power']
 | 
			
		||||
    userId.value = user.id
 | 
			
		||||
 | 
			
		||||
    fetchRunningJobs()
 | 
			
		||||
    fetchFinishJobs(1)
 | 
			
		||||
    connect()
 | 
			
		||||
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    router.push('/login')
 | 
			
		||||
  });
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  socket.value = null
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const mjPower = ref(1)
 | 
			
		||||
const mjActionPower = ref(1)
 | 
			
		||||
httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
  mjPower.value = res.data["mj_power"]
 | 
			
		||||
  mjActionPower.value = res.data["mj_action_power"]
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const heartbeatHandle = ref(null)
 | 
			
		||||
const connect = () => {
 | 
			
		||||
  let host = process.env.VUE_APP_WS_HOST
 | 
			
		||||
  if (host === '') {
 | 
			
		||||
    if (location.protocol === 'https:') {
 | 
			
		||||
      host = 'wss://' + location.host;
 | 
			
		||||
    } else {
 | 
			
		||||
      host = 'ws://' + location.host;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 心跳函数
 | 
			
		||||
  const sendHeartbeat = () => {
 | 
			
		||||
    clearTimeout(heartbeatHandle.value)
 | 
			
		||||
    new Promise((resolve, reject) => {
 | 
			
		||||
      if (socket.value !== null) {
 | 
			
		||||
        socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
 | 
			
		||||
      }
 | 
			
		||||
      resolve("success")
 | 
			
		||||
    }).then(() => {
 | 
			
		||||
      heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/mj/client?user_id=${userId.value}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    socket.value = _socket;
 | 
			
		||||
 | 
			
		||||
    // 发送心跳消息
 | 
			
		||||
    sendHeartbeat()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('message', event => {
 | 
			
		||||
    if (event.data instanceof Blob) {
 | 
			
		||||
      fetchRunningJobs()
 | 
			
		||||
      fetchFinishJobs(1)
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('close', () => {
 | 
			
		||||
    if (socket.value !== null) {
 | 
			
		||||
      connect()
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取运行中的任务
 | 
			
		||||
const fetchRunningJobs = (userId) => {
 | 
			
		||||
  httpGet(`/api/mj/jobs?status=0&user_id=${userId}`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
      if (jobs[i].progress === -1) {
 | 
			
		||||
        showNotify({
 | 
			
		||||
          message: `任务执行失败:${jobs[i]['err_msg']}`,
 | 
			
		||||
          type: 'danger',
 | 
			
		||||
        })
 | 
			
		||||
        if (jobs[i].type === 'image') {
 | 
			
		||||
          power.value += mjPower.value
 | 
			
		||||
        } else {
 | 
			
		||||
          power.value += mjActionPower.value
 | 
			
		||||
        }
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      _jobs.push(jobs[i])
 | 
			
		||||
    }
 | 
			
		||||
    runningJobs.value = _jobs
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showNotify({type: "danger", message: "获取任务失败:" + e.message})
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const finished = ref(false)
 | 
			
		||||
const error = ref(false)
 | 
			
		||||
const page = ref(0)
 | 
			
		||||
const pageSize = ref(10)
 | 
			
		||||
const fetchFinishJobs = (page) => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  // 获取已完成的任务
 | 
			
		||||
  httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
      if (jobs[i].progress === -1) {
 | 
			
		||||
        showNotify({
 | 
			
		||||
          message: `任务ID:${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
 | 
			
		||||
          type: 'danger',
 | 
			
		||||
        })
 | 
			
		||||
        if (jobs[i].type === 'image') {
 | 
			
		||||
          power.value += mjPower.value
 | 
			
		||||
        } else {
 | 
			
		||||
          power.value += mjActionPower.value
 | 
			
		||||
        }
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (jobs[i]['use_proxy']) {
 | 
			
		||||
        jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?x-oss-process=image/quality,q_60&format=webp'
 | 
			
		||||
      } else {
 | 
			
		||||
        if (jobs[i].type === 'upscale' || jobs[i].type === 'swapFace') {
 | 
			
		||||
          jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
 | 
			
		||||
        } else {
 | 
			
		||||
          jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/480/q/75'
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (jobs[i].type === 'image' || jobs[i].type === 'variation') {
 | 
			
		||||
        jobs[i]['can_opt'] = true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (jobs.length < pageSize.value) {
 | 
			
		||||
      finished.value = true
 | 
			
		||||
    }
 | 
			
		||||
    if (page === 1) {
 | 
			
		||||
      finishedJobs.value = jobs
 | 
			
		||||
    } else {
 | 
			
		||||
      finishedJobs.value = finishedJobs.value.concat(jobs)
 | 
			
		||||
    }
 | 
			
		||||
    nextTick(() => loading.value = false)
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    error.value = true
 | 
			
		||||
    showFailToast("获取任务失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onLoad = () => {
 | 
			
		||||
  page.value += 1
 | 
			
		||||
  fetchFinishJobs(page.value)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 切换图片比例
 | 
			
		||||
const changeRate = (item) => {
 | 
			
		||||
  params.value.rate = item.value
 | 
			
		||||
}
 | 
			
		||||
// 切换模型
 | 
			
		||||
const changeModel = (item) => {
 | 
			
		||||
  params.value.model = item.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const imgKey = ref("")
 | 
			
		||||
const beforeUpload = (key) => {
 | 
			
		||||
  imgKey.value = key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 图片上传
 | 
			
		||||
const uploadImg = (file) => {
 | 
			
		||||
  file.status = "uploading"
 | 
			
		||||
  // 压缩图片并上传
 | 
			
		||||
  new Compressor(file.file, {
 | 
			
		||||
    quality: 0.6,
 | 
			
		||||
    success(result) {
 | 
			
		||||
      const formData = new FormData();
 | 
			
		||||
      formData.append('file', result, result.name);
 | 
			
		||||
      // 执行上传操作
 | 
			
		||||
      httpPost('/api/upload', formData).then(res => {
 | 
			
		||||
        file.url = res.data.url
 | 
			
		||||
        if (imgKey.value !== "") { // 单张图片上传
 | 
			
		||||
          params.value[imgKey.value] = res.data.url
 | 
			
		||||
          imgKey.value = ''
 | 
			
		||||
        }
 | 
			
		||||
        file.status = "done"
 | 
			
		||||
      }).catch(e => {
 | 
			
		||||
        file.status = 'failed'
 | 
			
		||||
        file.message = '上传失败'
 | 
			
		||||
        showFailToast("图片上传失败:" + e.message)
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    error(err) {
 | 
			
		||||
      console.log(err.message);
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const send = (url, index, item) => {
 | 
			
		||||
  httpPost(url, {
 | 
			
		||||
    index: index,
 | 
			
		||||
    channel_id: item.channel_id,
 | 
			
		||||
    message_id: item.message_id,
 | 
			
		||||
    message_hash: item.hash,
 | 
			
		||||
    session_id: getSessionId(),
 | 
			
		||||
    prompt: item.prompt,
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    showSuccessToast("任务推送成功,请耐心等待任务执行...")
 | 
			
		||||
    power.value -= mjActionPower.value
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showFailToast("任务推送失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 图片放大任务
 | 
			
		||||
const upscale = (index, item) => {
 | 
			
		||||
  send('/api/mj/upscale', index, item)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 图片变换任务
 | 
			
		||||
const variation = (index, item) => {
 | 
			
		||||
  send('/api/mj/variation', index, item)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const generate = () => {
 | 
			
		||||
  if (params.value.prompt === '' && params.value.task_type === "image") {
 | 
			
		||||
    return showFailToast("请输入绘画提示词!")
 | 
			
		||||
  }
 | 
			
		||||
  if (params.value.model.indexOf("niji") !== -1 && params.value.raw) {
 | 
			
		||||
    return showFailToast("动漫模型不允许启用原始模式")
 | 
			
		||||
  }
 | 
			
		||||
  params.value.session_id = getSessionId()
 | 
			
		||||
  params.value.img_arr = imgList.value.map(img => img.url)
 | 
			
		||||
  httpPost("/api/mj/image", params.value).then(() => {
 | 
			
		||||
    showToast("绘画任务推送成功,请耐心等待任务执行")
 | 
			
		||||
    power.value -= mjPower.value
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showFailToast("任务推送失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const removeImage = (item) => {
 | 
			
		||||
  showConfirmDialog({
 | 
			
		||||
    title: '标题',
 | 
			
		||||
    message:
 | 
			
		||||
        '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    httpPost("/api/mj/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
 | 
			
		||||
      showSuccessToast("任务删除成功")
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      showFailToast("任务删除失败:" + e.message)
 | 
			
		||||
    })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    showToast("您取消了操作")
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
// 发布图片到作品墙
 | 
			
		||||
const publishImage = (item, action) => {
 | 
			
		||||
  let text = "图片发布"
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
    showSuccessToast(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showFailToast(text + "失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showPrompt = (item) => {
 | 
			
		||||
  showDialog({
 | 
			
		||||
    title: "绘画提示词",
 | 
			
		||||
    message: item.prompt,
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    // on close
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const imageView = (item) => {
 | 
			
		||||
  showImagePreview([item['img_url']]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 切换菜单
 | 
			
		||||
const tabChange = (tab) => {
 | 
			
		||||
  if (tab === "txt2img" || tab === "img2img") {
 | 
			
		||||
    params.value.task_type = "image"
 | 
			
		||||
  } else {
 | 
			
		||||
    params.value.task_type = tab
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@import "@/assets/css/mobile/image-mj.styl"
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,523 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="mobile-sd">
 | 
			
		||||
    <van-form @submit="generate">
 | 
			
		||||
      <van-cell-group inset>
 | 
			
		||||
        <div>
 | 
			
		||||
          <van-field
 | 
			
		||||
              v-model="params.sampler"
 | 
			
		||||
              is-link
 | 
			
		||||
              readonly
 | 
			
		||||
              label="采样方法"
 | 
			
		||||
              placeholder="选择采样方法"
 | 
			
		||||
              @click="showSamplerPicker = true"
 | 
			
		||||
          />
 | 
			
		||||
          <van-popup v-model:show="showSamplerPicker" position="bottom" teleport="#app">
 | 
			
		||||
            <van-picker
 | 
			
		||||
                :columns="samplers"
 | 
			
		||||
                @cancel="showSamplerPicker = false"
 | 
			
		||||
                @confirm="samplerConfirm"
 | 
			
		||||
            />
 | 
			
		||||
          </van-popup>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <van-field label="图片尺寸">
 | 
			
		||||
          <template #input>
 | 
			
		||||
            <van-row gutter="20">
 | 
			
		||||
              <van-col span="12">
 | 
			
		||||
                <el-input v-model="params.width" size="small" placeholder="宽"/>
 | 
			
		||||
              </van-col>
 | 
			
		||||
              <van-col span="12">
 | 
			
		||||
                <el-input v-model="params.height" size="small" placeholder="高"/>
 | 
			
		||||
              </van-col>
 | 
			
		||||
            </van-row>
 | 
			
		||||
          </template>
 | 
			
		||||
        </van-field>
 | 
			
		||||
 | 
			
		||||
        <van-field v-model.number="params.steps" label="迭代步数"
 | 
			
		||||
                   placeholder="">
 | 
			
		||||
          <template #right-icon>
 | 
			
		||||
            <van-icon name="info-o"
 | 
			
		||||
                      @click="showInfo('值越大则代表细节越多,同时也意味着出图速度越慢,一般推荐20-30')"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </van-field>
 | 
			
		||||
        <van-field v-model.number="params.cfg_scale" label="引导系数" placeholder="">
 | 
			
		||||
          <template #right-icon>
 | 
			
		||||
            <van-icon name="info-o"
 | 
			
		||||
                      @click="showInfo('提示词引导系数,图像在多大程度上服从提示词,较低值会产生更有创意的结果')"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </van-field>
 | 
			
		||||
        <van-field v-model.number="params.seed" label="随机因子" placeholder="">
 | 
			
		||||
          <template #right-icon>
 | 
			
		||||
            <van-icon name="info-o"
 | 
			
		||||
                      @click="showInfo('随机数种子,相同的种子会得到相同的结果,设置为 -1 则每次随机生成种子')"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </van-field>
 | 
			
		||||
 | 
			
		||||
        <van-field label="高清修复">
 | 
			
		||||
          <template #input>
 | 
			
		||||
            <van-switch v-model="params.hd_fix"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </van-field>
 | 
			
		||||
 | 
			
		||||
        <div v-if="params.hd_fix">
 | 
			
		||||
          <div>
 | 
			
		||||
            <van-field
 | 
			
		||||
                v-model="params.hd_scale_alg"
 | 
			
		||||
                is-link
 | 
			
		||||
                readonly
 | 
			
		||||
                label="放大算法"
 | 
			
		||||
                placeholder="选择放大算法"
 | 
			
		||||
                @click="showUpscalePicker = true"
 | 
			
		||||
            />
 | 
			
		||||
            <van-popup v-model:show="showUpscalePicker" position="bottom" teleport="#app">
 | 
			
		||||
              <van-picker
 | 
			
		||||
                  :columns="upscaleAlgArr"
 | 
			
		||||
                  @cancel="showUpscalePicker = false"
 | 
			
		||||
                  @confirm="upscaleConfirm"
 | 
			
		||||
              />
 | 
			
		||||
            </van-popup>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <van-field v-model.number="params.hd_scale" label="放大倍数"/>
 | 
			
		||||
          <van-field v-model.number="params.hd_steps" label="迭代步数"/>
 | 
			
		||||
 | 
			
		||||
          <van-field label="重绘幅度">
 | 
			
		||||
            <template #input>
 | 
			
		||||
              <van-slider v-model.number="params.hd_redraw_rate" :max="1" :step="0.1"
 | 
			
		||||
                          @update:model-value="showToast('当前值:' + params.hd_redraw_rate)"/>
 | 
			
		||||
            </template>
 | 
			
		||||
            <template #right-icon>
 | 
			
		||||
              <van-icon name="info-o"
 | 
			
		||||
                        @click="showInfo('决定算法对图像内容的影响程度,较大的值将得到越有创意的图像')"/>
 | 
			
		||||
            </template>
 | 
			
		||||
          </van-field>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <van-field
 | 
			
		||||
            v-model="params.prompt"
 | 
			
		||||
            rows="3"
 | 
			
		||||
            autosize
 | 
			
		||||
            type="textarea"
 | 
			
		||||
            placeholder="请在此输入绘画提示词,系统会自动翻译中文提示词,高手请直接输入英文提示词"
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <van-collapse v-model="activeColspan">
 | 
			
		||||
          <van-collapse-item title="反向提示词" name="neg_prompt">
 | 
			
		||||
            <van-field
 | 
			
		||||
                v-model="params.neg_prompt"
 | 
			
		||||
                rows="3"
 | 
			
		||||
                autosize
 | 
			
		||||
                type="textarea"
 | 
			
		||||
                placeholder="不想出现在图片上的元素(例如:树,建筑)"
 | 
			
		||||
            />
 | 
			
		||||
          </van-collapse-item>
 | 
			
		||||
        </van-collapse>
 | 
			
		||||
 | 
			
		||||
        <div class="text-line pt-6">
 | 
			
		||||
          <el-tag>绘图消耗{{ sdPower }}算力,当前算力:{{ power }}</el-tag>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="text-line">
 | 
			
		||||
          <van-button round block type="primary" native-type="submit">
 | 
			
		||||
            立即生成
 | 
			
		||||
          </van-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </van-cell-group>
 | 
			
		||||
    </van-form>
 | 
			
		||||
 | 
			
		||||
    <h3>任务列表</h3>
 | 
			
		||||
    <div class="running-job-list">
 | 
			
		||||
      <van-empty v-if="runningJobs.length ===0"
 | 
			
		||||
                 image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
 | 
			
		||||
                 image-size="80"
 | 
			
		||||
                 description="暂无记录"
 | 
			
		||||
      />
 | 
			
		||||
      <van-grid :gutter="10" :column-num="3" v-else>
 | 
			
		||||
        <van-grid-item v-for="item in runningJobs">
 | 
			
		||||
          <div v-if="item.progress > 0">
 | 
			
		||||
            <van-image :src="item['img_url']">
 | 
			
		||||
              <template v-slot:error>加载失败</template>
 | 
			
		||||
            </van-image>
 | 
			
		||||
            <div class="progress">
 | 
			
		||||
              <van-circle
 | 
			
		||||
                  v-model:current-rate="item.progress"
 | 
			
		||||
                  :rate="item.progress"
 | 
			
		||||
                  :speed="100"
 | 
			
		||||
                  :text="item.progress+'%'"
 | 
			
		||||
                  :stroke-width="60"
 | 
			
		||||
                  size="90px"
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div v-else class="task-in-queue">
 | 
			
		||||
            <span class="icon"><i class="iconfont icon-quick-start"></i></span>
 | 
			
		||||
            <span class="text">排队中</span>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
        </van-grid-item>
 | 
			
		||||
      </van-grid>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <h3>创作记录</h3>
 | 
			
		||||
    <div class="finish-job-list">
 | 
			
		||||
      <van-empty v-if="finishedJobs.length ===0"
 | 
			
		||||
                 image="https://fastly.jsdelivr.net/npm/@vant/assets/custom-empty-image.png"
 | 
			
		||||
                 image-size="80"
 | 
			
		||||
                 description="暂无记录"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <van-list v-else
 | 
			
		||||
                v-model:error="error"
 | 
			
		||||
                v-model:loading="loading"
 | 
			
		||||
                :finished="finished"
 | 
			
		||||
                error-text="请求失败,点击重新加载"
 | 
			
		||||
                finished-text="没有更多了"
 | 
			
		||||
                @load="onLoad"
 | 
			
		||||
      >
 | 
			
		||||
        <van-grid :gutter="10" :column-num="2">
 | 
			
		||||
          <van-grid-item v-for="item in finishedJobs">
 | 
			
		||||
            <div class="job-item">
 | 
			
		||||
              <van-image
 | 
			
		||||
                  :src="item['img_url']"
 | 
			
		||||
                  :class="item['can_opt'] ? '' : 'upscale'"
 | 
			
		||||
                  lazy-load
 | 
			
		||||
                  @click="imageView(item)"
 | 
			
		||||
                  fit="cover">
 | 
			
		||||
                <template v-slot:loading>
 | 
			
		||||
                  <van-loading type="spinner" size="20"/>
 | 
			
		||||
                </template>
 | 
			
		||||
              </van-image>
 | 
			
		||||
 | 
			
		||||
              <div class="remove">
 | 
			
		||||
                <el-button type="danger" :icon="Delete" @click="removeImage($event, item)" circle/>
 | 
			
		||||
                <el-button type="warning" v-if="item.publish" @click="publishImage($event,item, false)"
 | 
			
		||||
                           circle>
 | 
			
		||||
                  <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                </el-button>
 | 
			
		||||
                <el-button type="success" v-else @click="publishImage($event, item, true)" circle>
 | 
			
		||||
                  <i class="iconfont icon-share-bold"></i>
 | 
			
		||||
                </el-button>
 | 
			
		||||
                <el-button type="primary" @click="showTask(item)" circle>
 | 
			
		||||
                  <i class="iconfont icon-prompt"></i>
 | 
			
		||||
                </el-button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </van-grid-item>
 | 
			
		||||
        </van-grid>
 | 
			
		||||
      </van-list>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {Delete} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {
 | 
			
		||||
  showConfirmDialog, showDialog,
 | 
			
		||||
  showFailToast,
 | 
			
		||||
  showImagePreview,
 | 
			
		||||
  showNotify,
 | 
			
		||||
  showSuccessToast,
 | 
			
		||||
  showToast
 | 
			
		||||
} from "vant";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(window.innerHeight - 40)
 | 
			
		||||
const mjBoxHeight = ref(window.innerHeight - 150)
 | 
			
		||||
const showTaskDialog = ref(false)
 | 
			
		||||
const item = ref({})
 | 
			
		||||
const showLoginDialog = ref(false)
 | 
			
		||||
const isLogin = ref(false)
 | 
			
		||||
const activeColspan = ref([""])
 | 
			
		||||
 | 
			
		||||
window.onresize = () => {
 | 
			
		||||
  listBoxHeight.value = window.innerHeight - 40
 | 
			
		||||
  mjBoxHeight.value = window.innerHeight - 150
 | 
			
		||||
}
 | 
			
		||||
const samplers = ref([
 | 
			
		||||
  {text: "Euler a", value: "Euler a"},
 | 
			
		||||
  {text: "DPM++ 2S a Karras", value: "DPM++ 2S a Karras"},
 | 
			
		||||
  {text: "DPM++ 2M Karras", value: "DPM++ 2M Karras"},
 | 
			
		||||
  {text: "DPM++ 2M SDE Karras", value: "DPM++ 2M SDE Karras"},
 | 
			
		||||
  {text: "DPM++ 2M Karras", value: "DPM++ 2M Karras"},
 | 
			
		||||
  {text: "DPM++ 3M SDE Karras", value: "DPM++ 3M SDE Karras"},
 | 
			
		||||
])
 | 
			
		||||
const showSamplerPicker = ref(false)
 | 
			
		||||
 | 
			
		||||
const upscaleAlgArr = ref([
 | 
			
		||||
  {text: "Latent", value: "Latent"},
 | 
			
		||||
  {text: "ESRGAN_4x", value: "ESRGAN_4x"},
 | 
			
		||||
  {text: "ESRGAN 4x+", value: "ESRGAN 4x+"},
 | 
			
		||||
  {text: "SwinIR_4x", value: "SwinIR_4x"},
 | 
			
		||||
  {text: "LDSR", value: "LDSR"},
 | 
			
		||||
])
 | 
			
		||||
const showUpscalePicker = ref(false)
 | 
			
		||||
 | 
			
		||||
const params = ref({
 | 
			
		||||
  width: 1024,
 | 
			
		||||
  height: 1024,
 | 
			
		||||
  sampler: samplers.value[0].value,
 | 
			
		||||
  seed: -1,
 | 
			
		||||
  steps: 20,
 | 
			
		||||
  cfg_scale: 7,
 | 
			
		||||
  hd_fix: false,
 | 
			
		||||
  hd_redraw_rate: 0.7,
 | 
			
		||||
  hd_scale: 2,
 | 
			
		||||
  hd_scale_alg: upscaleAlgArr.value[0].value,
 | 
			
		||||
  hd_steps: 0,
 | 
			
		||||
  prompt: "",
 | 
			
		||||
  neg_prompt: "nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet",
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const runningJobs = ref([])
 | 
			
		||||
const finishedJobs = ref([])
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
// 检查是否有画同款的参数
 | 
			
		||||
const _params = router.currentRoute.value.params["copyParams"]
 | 
			
		||||
if (_params) {
 | 
			
		||||
  params.value = JSON.parse(_params)
 | 
			
		||||
}
 | 
			
		||||
const power = ref(0)
 | 
			
		||||
const sdPower = ref(0) // 画一张 SD 图片消耗算力
 | 
			
		||||
 | 
			
		||||
const socket = ref(null)
 | 
			
		||||
const userId = ref(0)
 | 
			
		||||
const heartbeatHandle = ref(null)
 | 
			
		||||
const connect = () => {
 | 
			
		||||
  let host = process.env.VUE_APP_WS_HOST
 | 
			
		||||
  if (host === '') {
 | 
			
		||||
    if (location.protocol === 'https:') {
 | 
			
		||||
      host = 'wss://' + location.host;
 | 
			
		||||
    } else {
 | 
			
		||||
      host = 'ws://' + location.host;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 心跳函数
 | 
			
		||||
  const sendHeartbeat = () => {
 | 
			
		||||
    clearTimeout(heartbeatHandle.value)
 | 
			
		||||
    new Promise((resolve, reject) => {
 | 
			
		||||
      if (socket.value !== null) {
 | 
			
		||||
        socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
 | 
			
		||||
      }
 | 
			
		||||
      resolve("success")
 | 
			
		||||
    }).then(() => {
 | 
			
		||||
      heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/sd/client?user_id=${userId.value}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    socket.value = _socket;
 | 
			
		||||
 | 
			
		||||
    // 发送心跳消息
 | 
			
		||||
    sendHeartbeat()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('message', event => {
 | 
			
		||||
    if (event.data instanceof Blob) {
 | 
			
		||||
      fetchRunningJobs()
 | 
			
		||||
      finished.value = false
 | 
			
		||||
      page.value = 1
 | 
			
		||||
      fetchFinishJobs(page.value)
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('close', () => {
 | 
			
		||||
    if (socket.value !== null) {
 | 
			
		||||
      connect()
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const clipboard = ref(null)
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initData()
 | 
			
		||||
  clipboard.value = new Clipboard('.copy-prompt-sd');
 | 
			
		||||
  clipboard.value.on('success', () => {
 | 
			
		||||
    showNotify({type: "success", message: "复制成功!"});
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  clipboard.value.on('error', () => {
 | 
			
		||||
    showNotify({type: "danger", message: '复制失败!'});
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/get?key=system").then(res => {
 | 
			
		||||
    sdPower.value = res.data["sd_power"]
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showNotify({type: "danger", message: "获取系统配置失败:" + e.message})
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  clipboard.value.destroy()
 | 
			
		||||
  socket.value = null
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    power.value = user['power']
 | 
			
		||||
    userId.value = user.id
 | 
			
		||||
    isLogin.value = true
 | 
			
		||||
    fetchRunningJobs()
 | 
			
		||||
    fetchFinishJobs(1)
 | 
			
		||||
    connect()
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fetchRunningJobs = () => {
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/sd/jobs?status=0`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
      if (jobs[i].progress === -1) {
 | 
			
		||||
        showNotify({
 | 
			
		||||
          message: `任务ID:${jobs[i]['task_id']} 原因:${jobs[i]['err_msg']}`,
 | 
			
		||||
          type: 'danger',
 | 
			
		||||
        })
 | 
			
		||||
        power.value += sdPower.value
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
      _jobs.push(jobs[i])
 | 
			
		||||
    }
 | 
			
		||||
    runningJobs.value = _jobs
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showNotify({type: "danger", message: "获取任务失败:" + e.message})
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const finished = ref(false)
 | 
			
		||||
const error = ref(false)
 | 
			
		||||
const page = ref(0)
 | 
			
		||||
const pageSize = ref(10)
 | 
			
		||||
// 获取已完成的任务
 | 
			
		||||
const fetchFinishJobs = (page) => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  httpGet(`/api/sd/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
    if (res.data.length < pageSize.value) {
 | 
			
		||||
      finished.value = true
 | 
			
		||||
    }
 | 
			
		||||
    if (page === 1) {
 | 
			
		||||
      finishedJobs.value = res.data
 | 
			
		||||
    } else {
 | 
			
		||||
      finishedJobs.value = finishedJobs.value.concat(res.data)
 | 
			
		||||
    }
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    showNotify({type: "danger", message: "获取任务失败:" + e.message})
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onLoad = () => {
 | 
			
		||||
  page.value += 1
 | 
			
		||||
  fetchFinishJobs(page.value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 创建绘图任务
 | 
			
		||||
const promptRef = ref(null)
 | 
			
		||||
const generate = () => {
 | 
			
		||||
  if (params.value.prompt === '') {
 | 
			
		||||
    promptRef.value.focus()
 | 
			
		||||
    return showToast("请输入绘画提示词!")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    showLoginDialog.value = true
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (params.value.seed === '') {
 | 
			
		||||
    params.value.seed = -1
 | 
			
		||||
  }
 | 
			
		||||
  params.value.session_id = getSessionId()
 | 
			
		||||
  httpPost("/api/sd/image", params.value).then(() => {
 | 
			
		||||
    showSuccessToast("绘画任务推送成功,请耐心等待任务执行...")
 | 
			
		||||
    power.value -= sdPower.value
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showFailToast("任务推送失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showTask = (row) => {
 | 
			
		||||
  item.value = row
 | 
			
		||||
  showTaskDialog.value = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const copyParams = (row) => {
 | 
			
		||||
  params.value = row.params
 | 
			
		||||
  showTaskDialog.value = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const removeImage = (event, item) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
  showConfirmDialog({
 | 
			
		||||
    title: '标题',
 | 
			
		||||
    message:
 | 
			
		||||
        '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    httpPost("/api/sd/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
 | 
			
		||||
      showSuccessToast("任务删除成功")
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      showFailToast("任务删除失败:" + e.message)
 | 
			
		||||
    })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    showToast("您取消了操作")
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 发布图片到作品墙
 | 
			
		||||
const publishImage = (event, item, action) => {
 | 
			
		||||
  event.stopPropagation()
 | 
			
		||||
  let text = "图片发布"
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
    showSuccessToast(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showFailToast(text + "失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const imageView = (item) => {
 | 
			
		||||
  showImagePreview([item['img_url']]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const samplerConfirm = (item) => {
 | 
			
		||||
  params.value.sampler = item.selectedOptions[0].text;
 | 
			
		||||
  showSamplerPicker.value = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const upscaleConfirm = (item) => {
 | 
			
		||||
  params.value.hd_scale_alg = item.selectedOptions[0].text;
 | 
			
		||||
  showUpscalePicker.value = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showInfo = (message) => {
 | 
			
		||||
  showDialog({
 | 
			
		||||
    title: "参数说明",
 | 
			
		||||
    message: message,
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    // on close
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@import "@/assets/css/mobile/image-sd.styl"
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,133 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="img-wall container">
 | 
			
		||||
    <van-nav-bar :title="title"/>
 | 
			
		||||
 | 
			
		||||
    <div class="content">
 | 
			
		||||
      <van-tabs v-model:active="activeName">
 | 
			
		||||
        <van-tab title="MidJourney" name="mj">
 | 
			
		||||
          <van-list
 | 
			
		||||
              v-model:error="data['mj'].error"
 | 
			
		||||
              v-model:loading="data['mj'].loading"
 | 
			
		||||
              :finished="data['mj'].finished"
 | 
			
		||||
              error-text="请求失败,点击重新加载"
 | 
			
		||||
              finished-text="没有更多了"
 | 
			
		||||
              @load="onLoad"
 | 
			
		||||
              style="height: 100%;width: 100%;"
 | 
			
		||||
          >
 | 
			
		||||
            <van-cell v-for="item in data['mj'].data" :key="item.id">
 | 
			
		||||
              <van-image :src="item['img_thumb']" @click="showPrompt(item)" fit="cover"/>
 | 
			
		||||
            </van-cell>
 | 
			
		||||
          </van-list>
 | 
			
		||||
        </van-tab>
 | 
			
		||||
        <van-tab title="StableDiffusion" name="sd">
 | 
			
		||||
          <van-list
 | 
			
		||||
              v-model:error="data['sd'].error"
 | 
			
		||||
              v-model:loading="data['sd'].loading"
 | 
			
		||||
              :finished="data['sd'].finished"
 | 
			
		||||
              error-text="请求失败,点击重新加载"
 | 
			
		||||
              finished-text="没有更多了"
 | 
			
		||||
              @load="onLoad"
 | 
			
		||||
          >
 | 
			
		||||
            <van-cell v-for="item in data['sd'].data" :key="item.id">
 | 
			
		||||
              <van-image :src="item['img_thumb']" @click="showPrompt(item)" fit="cover"/>
 | 
			
		||||
            </van-cell>
 | 
			
		||||
          </van-list>
 | 
			
		||||
        </van-tab>
 | 
			
		||||
        <van-tab title="DALLE3" name="dalle3">
 | 
			
		||||
          <van-empty description="功能正在开发中"/>
 | 
			
		||||
        </van-tab>
 | 
			
		||||
      </van-tabs>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {showDialog, showFailToast, showSuccessToast} from "vant";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
 | 
			
		||||
const title = ref('图片创作广场')
 | 
			
		||||
const activeName = ref("mj")
 | 
			
		||||
const data = ref({
 | 
			
		||||
  "mj": {
 | 
			
		||||
    loading: false,
 | 
			
		||||
    finished: false,
 | 
			
		||||
    error: false,
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 12,
 | 
			
		||||
    url: "/api/mj/jobs",
 | 
			
		||||
    data: []
 | 
			
		||||
  },
 | 
			
		||||
  "sd": {
 | 
			
		||||
    loading: false,
 | 
			
		||||
    finished: false,
 | 
			
		||||
    error: false,
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 12,
 | 
			
		||||
    url: "/api/sd/jobs",
 | 
			
		||||
    data: []
 | 
			
		||||
  },
 | 
			
		||||
  "dalle3": {
 | 
			
		||||
    loading: false,
 | 
			
		||||
    finished: false,
 | 
			
		||||
    error: false,
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 12,
 | 
			
		||||
    url: "/api/dalle3/jobs",
 | 
			
		||||
    data: []
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const onLoad = () => {
 | 
			
		||||
  const d = data.value[activeName.value]
 | 
			
		||||
  httpGet(`${d.url}?status=1&page=${d.page}&page_size=${d.pageSize}&publish=true`).then(res => {
 | 
			
		||||
    d.loading = false
 | 
			
		||||
    if (res.data.length === 0) {
 | 
			
		||||
      d.finished = true
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 生成缩略图
 | 
			
		||||
    const imageList = res.data
 | 
			
		||||
    for (let i = 0; i < imageList.length; i++) {
 | 
			
		||||
      imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
 | 
			
		||||
    }
 | 
			
		||||
    if (imageList.length < d.pageSize) {
 | 
			
		||||
      d.finished = true
 | 
			
		||||
    }
 | 
			
		||||
    if (d.data.length === 0) {
 | 
			
		||||
      d.data = imageList
 | 
			
		||||
    } else {
 | 
			
		||||
      d.data = d.data.concat(imageList)
 | 
			
		||||
    }
 | 
			
		||||
    d.page += 1
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    d.error = true
 | 
			
		||||
    showFailToast("加载图片数据失败")
 | 
			
		||||
  })
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showPrompt = (item) => {
 | 
			
		||||
  showDialog({
 | 
			
		||||
    title: "绘画提示词",
 | 
			
		||||
    message: item.prompt,
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    // on close
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
.img-wall {
 | 
			
		||||
  .content {
 | 
			
		||||
    padding-top 60px
 | 
			
		||||
 | 
			
		||||
    .van-cell__value {
 | 
			
		||||
      .van-image {
 | 
			
		||||
        width 100%
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -302,7 +302,7 @@ const pay = (payWay, item) => {
 | 
			
		||||
    if (isWeChatBrowser() && payWay === 'wechat') {
 | 
			
		||||
      showFailToast("请在系统自带浏览器打开支付页面,或者在 PC 端进行扫码支付")
 | 
			
		||||
    } else {
 | 
			
		||||
      location.href = res.data
 | 
			
		||||
      location.href = res.data.url
 | 
			
		||||
    }
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showFailToast("生成支付订单失败:" + e.message)
 | 
			
		||||
 
 | 
			
		||||
@@ -317,7 +317,7 @@ const initData = () => {
 | 
			
		||||
 | 
			
		||||
const fetchRunningJobs = () => {
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/dall/jobs?status=0`).then(res => {
 | 
			
		||||
  httpGet(`/api/dall/jobs?finish=0`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
@@ -345,7 +345,7 @@ const pageSize = ref(10)
 | 
			
		||||
// 获取已完成的任务
 | 
			
		||||
const fetchFinishJobs = (page) => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  httpGet(`/api/dall/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
  httpGet(`/api/dall/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
    if (res.data.length < pageSize.value) {
 | 
			
		||||
      finished.value = true
 | 
			
		||||
    }
 | 
			
		||||
@@ -410,7 +410,7 @@ const removeImage = (event, item) => {
 | 
			
		||||
    message:
 | 
			
		||||
        '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    httpPost("/api/dall/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
 | 
			
		||||
    httpGet("/api/dall/remove", {id: item.id, user_id: item.user_id}).then(() => {
 | 
			
		||||
      showSuccessToast("任务删除成功")
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      showFailToast("任务删除失败:" + e.message)
 | 
			
		||||
@@ -427,7 +427,7 @@ const publishImage = (event, item, action) => {
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/dall/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
  httpGet("/api/dall/publish", {id: item.id, action: action, user_id: item.user_id}).then(() => {
 | 
			
		||||
    showSuccessToast(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
@@ -421,7 +421,7 @@ const connect = () => {
 | 
			
		||||
 | 
			
		||||
// 获取运行中的任务
 | 
			
		||||
const fetchRunningJobs = (userId) => {
 | 
			
		||||
  httpGet(`/api/mj/jobs?status=0&user_id=${userId}`).then(res => {
 | 
			
		||||
  httpGet(`/api/mj/jobs?finish=0&user_id=${userId}`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
@@ -453,7 +453,7 @@ const pageSize = ref(10)
 | 
			
		||||
const fetchFinishJobs = (page) => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  // 获取已完成的任务
 | 
			
		||||
  httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
  httpGet(`/api/mj/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
      if (jobs[i].progress === -1) {
 | 
			
		||||
@@ -600,7 +600,7 @@ const removeImage = (item) => {
 | 
			
		||||
    message:
 | 
			
		||||
        '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    httpPost("/api/mj/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
 | 
			
		||||
    httpGet("/api/mj/remove", {id: item.id, user_id: item.user_id}).then(() => {
 | 
			
		||||
      showSuccessToast("任务删除成功")
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      showFailToast("任务删除失败:" + e.message)
 | 
			
		||||
@@ -615,7 +615,7 @@ const publishImage = (item, action) => {
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
  httpGet("/api/mj/publish", {id: item.id, action: action,user_id: item.user_id}).then(() => {
 | 
			
		||||
    showSuccessToast(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
 
 | 
			
		||||
@@ -381,7 +381,7 @@ const initData = () => {
 | 
			
		||||
 | 
			
		||||
const fetchRunningJobs = () => {
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/sd/jobs?status=0`).then(res => {
 | 
			
		||||
  httpGet(`/api/sd/jobs?finish=0`).then(res => {
 | 
			
		||||
    const jobs = res.data
 | 
			
		||||
    const _jobs = []
 | 
			
		||||
    for (let i = 0; i < jobs.length; i++) {
 | 
			
		||||
@@ -409,7 +409,7 @@ const pageSize = ref(10)
 | 
			
		||||
// 获取已完成的任务
 | 
			
		||||
const fetchFinishJobs = (page) => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  httpGet(`/api/sd/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
  httpGet(`/api/sd/jobs?finish=1&page=${page}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
    if (res.data.length < pageSize.value) {
 | 
			
		||||
      finished.value = true
 | 
			
		||||
    }
 | 
			
		||||
@@ -474,7 +474,7 @@ const removeImage = (event, item) => {
 | 
			
		||||
    message:
 | 
			
		||||
        '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    httpPost("/api/sd/remove", {id: item.id, img_url: item.img_url, user_id: userId.value}).then(() => {
 | 
			
		||||
    httpGet("/api/sd/remove", {id: item.id, user_id: item.user}).then(() => {
 | 
			
		||||
      showSuccessToast("任务删除成功")
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      showFailToast("任务删除失败:" + e.message)
 | 
			
		||||
@@ -491,7 +491,7 @@ const publishImage = (event, item, action) => {
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/sd/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
  httpGet("/api/sd/publish", {id: item.id, action: action, user_id: item.user}).then(() => {
 | 
			
		||||
    showSuccessToast(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
 
 | 
			
		||||