mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-12-27 10:35:58 +08:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35fedbe817 | ||
|
|
827acdd3f9 | ||
|
|
6c76086916 | ||
|
|
373370fde5 | ||
|
|
2165ba3406 | ||
|
|
b0e02b43fc | ||
|
|
2107c13b3d | ||
|
|
5f41aecc8d | ||
|
|
6840a13370 | ||
|
|
8f1e28c0ab | ||
|
|
7903eed284 | ||
|
|
0d49ea0d41 | ||
|
|
2ee4db5e48 | ||
|
|
48c4789505 |
@@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
### 管理后台
|
### 管理后台
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
@@ -100,7 +102,8 @@ ChatGPT 的服务。
|
|||||||
|
|
||||||
## Docker 快速部署
|
## Docker 快速部署
|
||||||
|
|
||||||
> 鉴于最新不少网友反馈在部署的时候遇到一些问题,大部分问题都是相同的,所以我这边做了一个视频教程 [五分钟部署自己的 ChatGPT 服务](https://www.bilibili.com/video/BV1H14y1B7Qw/)。
|
>
|
||||||
|
鉴于最新不少网友反馈在部署的时候遇到一些问题,大部分问题都是相同的,所以我这边做了一个视频教程 [五分钟部署自己的 ChatGPT 服务](https://www.bilibili.com/video/BV1H14y1B7Qw/)。
|
||||||
> 习惯看视频教程的朋友可以去看视频教程,视频的语速比较慢,建议 2 倍速观看。
|
> 习惯看视频教程的朋友可以去看视频教程,视频的语速比较慢,建议 2 倍速观看。
|
||||||
|
|
||||||
V3.0.0 版本以后已经支持使用容器部署了,跳过所有的繁琐的环境准备,一条命令就可以轻松部署上线。
|
V3.0.0 版本以后已经支持使用容器部署了,跳过所有的繁琐的环境准备,一条命令就可以轻松部署上线。
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ linux:
|
|||||||
.PHONY: linux
|
.PHONY: linux
|
||||||
|
|
||||||
darwin:
|
darwin:
|
||||||
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -o bin/$(NAME)-amd64-darwin main.go
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/$(NAME)-amd64-darwin main.go
|
||||||
.PHONY: darwin
|
.PHONY: darwin
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@@ -34,13 +34,10 @@ type AppServer struct {
|
|||||||
ChatClients *types.LMap[string, *types.WsClient] // map[sessionId]Websocket 连接集合
|
ChatClients *types.LMap[string, *types.WsClient] // map[sessionId]Websocket 连接集合
|
||||||
ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
|
ReqCancelFunc *types.LMap[string, context.CancelFunc] // HttpClient 请求取消 handle function
|
||||||
Functions map[string]function.Function
|
Functions map[string]function.Function
|
||||||
|
MjTaskClients *types.LMap[string, *types.WsClient]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(
|
func NewServer(appConfig *types.AppConfig, functions map[string]function.Function) *AppServer {
|
||||||
appConfig *types.AppConfig,
|
|
||||||
funZaoBao function.FuncZaoBao,
|
|
||||||
funZhiHu function.FuncHeadlines,
|
|
||||||
funWeibo function.FuncWeiboHot) *AppServer {
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
gin.DefaultWriter = io.Discard
|
gin.DefaultWriter = io.Discard
|
||||||
return &AppServer{
|
return &AppServer{
|
||||||
@@ -51,11 +48,8 @@ func NewServer(
|
|||||||
ChatSession: types.NewLMap[string, types.ChatSession](),
|
ChatSession: types.NewLMap[string, types.ChatSession](),
|
||||||
ChatClients: types.NewLMap[string, *types.WsClient](),
|
ChatClients: types.NewLMap[string, *types.WsClient](),
|
||||||
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
|
ReqCancelFunc: types.NewLMap[string, context.CancelFunc](),
|
||||||
Functions: map[string]function.Function{
|
MjTaskClients: types.NewLMap[string, *types.WsClient](),
|
||||||
types.FuncZaoBao: funZaoBao,
|
Functions: functions,
|
||||||
types.FuncWeibo: funWeibo,
|
|
||||||
types.FuncHeadLine: funZhiHu,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +151,7 @@ func corsMiddleware() gin.HandlerFunc {
|
|||||||
c.Header("Access-Control-Allow-Origin", origin)
|
c.Header("Access-Control-Allow-Origin", origin)
|
||||||
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
||||||
//允许跨域设置可以返回其他子段,可以自定义字段
|
//允许跨域设置可以返回其他子段,可以自定义字段
|
||||||
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, ChatGPT-TOKEN, ADMIN-SESSION-TOKEN")
|
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type")
|
||||||
// 允许浏览器(客户端)可以解析的头部 (重要)
|
// 允许浏览器(客户端)可以解析的头部 (重要)
|
||||||
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
|
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
|
||||||
//设置缓存时间
|
//设置缓存时间
|
||||||
@@ -186,6 +180,8 @@ func authorizeMiddleware(s *AppServer) gin.HandlerFunc {
|
|||||||
if c.Request.URL.Path == "/api/user/login" ||
|
if c.Request.URL.Path == "/api/user/login" ||
|
||||||
c.Request.URL.Path == "/api/admin/login" ||
|
c.Request.URL.Path == "/api/admin/login" ||
|
||||||
c.Request.URL.Path == "/api/user/register" ||
|
c.Request.URL.Path == "/api/user/register" ||
|
||||||
|
c.Request.URL.Path == "/api/reward/notify" ||
|
||||||
|
c.Request.URL.Path == "/api/mj/notify" ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/api/captcha/") ||
|
||||||
strings.HasPrefix(c.Request.URL.Path, "/static/") ||
|
strings.HasPrefix(c.Request.URL.Path, "/static/") ||
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ func NewDefaultConfig() *types.AppConfig {
|
|||||||
HttpOnly: false,
|
HttpOnly: false,
|
||||||
SameSite: http.SameSiteLaxMode,
|
SameSite: http.SameSiteLaxMode,
|
||||||
},
|
},
|
||||||
ApiConfig: types.ChatPlusApiConfig{},
|
ApiConfig: types.ChatPlusApiConfig{},
|
||||||
StartWechatBot: false,
|
ExtConfig: types.ChatPlusExtConfig{Token: utils.RandString(32)},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ type ChatSession struct {
|
|||||||
Model string `json:"model"` // GPT 模型
|
Model string `json:"model"` // GPT 模型
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MjTask struct {
|
||||||
|
ChatId string
|
||||||
|
MessageId string
|
||||||
|
MessageHash string
|
||||||
|
UserId uint
|
||||||
|
RoleId uint
|
||||||
|
Icon string
|
||||||
|
}
|
||||||
|
|
||||||
type ApiError struct {
|
type ApiError struct {
|
||||||
Error struct {
|
Error struct {
|
||||||
Message string
|
Message string
|
||||||
@@ -53,6 +62,7 @@ type ApiError struct {
|
|||||||
|
|
||||||
const PromptMsg = "prompt" // prompt message
|
const PromptMsg = "prompt" // prompt message
|
||||||
const ReplyMsg = "reply" // reply message
|
const ReplyMsg = "reply" // reply message
|
||||||
|
const MjMsg = "mj"
|
||||||
|
|
||||||
var ModelToTokens = map[string]int{
|
var ModelToTokens = map[string]int{
|
||||||
"gpt-3.5-turbo": 4096,
|
"gpt-3.5-turbo": 4096,
|
||||||
@@ -60,3 +70,5 @@ var ModelToTokens = map[string]int{
|
|||||||
"gpt-4": 8192,
|
"gpt-4": 8192,
|
||||||
"gpt-4-32k": 32768,
|
"gpt-4-32k": 32768,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TaskStorePrefix = "/tasks/"
|
||||||
|
|||||||
@@ -6,18 +6,14 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrConClosed = errors.New("connection closed")
|
var ErrConClosed = errors.New("connection Closed")
|
||||||
|
|
||||||
type Client interface {
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WsClient websocket client
|
// WsClient websocket client
|
||||||
type WsClient struct {
|
type WsClient struct {
|
||||||
Conn *websocket.Conn
|
Conn *websocket.Conn
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
mt int
|
mt int
|
||||||
closed bool
|
Closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWsClient(conn *websocket.Conn) *WsClient {
|
func NewWsClient(conn *websocket.Conn) *WsClient {
|
||||||
@@ -25,7 +21,7 @@ func NewWsClient(conn *websocket.Conn) *WsClient {
|
|||||||
Conn: conn,
|
Conn: conn,
|
||||||
lock: sync.Mutex{},
|
lock: sync.Mutex{},
|
||||||
mt: 2, // fixed bug for 'Invalid UTF-8 in text frame'
|
mt: 2, // fixed bug for 'Invalid UTF-8 in text frame'
|
||||||
closed: false,
|
Closed: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +29,7 @@ func (wc *WsClient) Send(message []byte) error {
|
|||||||
wc.lock.Lock()
|
wc.lock.Lock()
|
||||||
defer wc.lock.Unlock()
|
defer wc.lock.Unlock()
|
||||||
|
|
||||||
if wc.closed {
|
if wc.Closed {
|
||||||
return ErrConClosed
|
return ErrConClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +37,7 @@ func (wc *WsClient) Send(message []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (wc *WsClient) Receive() (int, []byte, error) {
|
func (wc *WsClient) Receive() (int, []byte, error) {
|
||||||
if wc.closed {
|
if wc.Closed {
|
||||||
return 0, nil, ErrConClosed
|
return 0, nil, ErrConClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +48,10 @@ func (wc *WsClient) Close() {
|
|||||||
wc.lock.Lock()
|
wc.lock.Lock()
|
||||||
defer wc.lock.Unlock()
|
defer wc.lock.Unlock()
|
||||||
|
|
||||||
if wc.closed {
|
if wc.Closed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = wc.Conn.Close()
|
_ = wc.Conn.Close()
|
||||||
wc.closed = true
|
wc.Closed = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AppConfig struct {
|
type AppConfig struct {
|
||||||
Path string `toml:"-"`
|
Path string `toml:"-"`
|
||||||
Listen string
|
Listen string
|
||||||
Session Session
|
Session Session
|
||||||
ProxyURL string
|
ProxyURL string
|
||||||
MysqlDns string // mysql 连接地址
|
MysqlDns string // mysql 连接地址
|
||||||
Manager Manager // 后台管理员账户信息
|
Manager Manager // 后台管理员账户信息
|
||||||
StaticDir string // 静态资源目录
|
StaticDir string // 静态资源目录
|
||||||
StaticUrl string // 静态资源 URL
|
StaticUrl string // 静态资源 URL
|
||||||
Redis RedisConfig // redis 连接信息
|
Redis RedisConfig // redis 连接信息
|
||||||
ApiConfig ChatPlusApiConfig // ChatPlus API authorization configs
|
ApiConfig ChatPlusApiConfig // ChatPlus API authorization configs
|
||||||
AesEncryptKey string
|
AesEncryptKey string
|
||||||
SmsConfig AliYunSmsConfig // AliYun send message service config
|
SmsConfig AliYunSmsConfig // AliYun send message service config
|
||||||
StartWechatBot bool // 是否启动微信机器人
|
ExtConfig ChatPlusExtConfig // ChatPlus extensions callback api config
|
||||||
EnabledMsgService bool // 是否启用短信服务
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChatPlusApiConfig struct {
|
type ChatPlusApiConfig struct {
|
||||||
@@ -28,6 +27,11 @@ type ChatPlusApiConfig struct {
|
|||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ChatPlusExtConfig struct {
|
||||||
|
ApiURL string
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
type AliYunSmsConfig struct {
|
type AliYunSmsConfig struct {
|
||||||
AccessKey string
|
AccessKey string
|
||||||
AccessSecret string
|
AccessSecret string
|
||||||
@@ -85,9 +89,10 @@ type ChatConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SystemConfig struct {
|
type SystemConfig struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
AdminTitle string `json:"admin_title"`
|
AdminTitle string `json:"admin_title"`
|
||||||
Models []string `json:"models"`
|
Models []string `json:"models"`
|
||||||
UserInitCalls int `json:"user_init_calls"` // 新用户注册默认总送多少次调用
|
UserInitCalls int `json:"user_init_calls"` // 新用户注册默认总送多少次调用
|
||||||
EnabledRegister bool `json:"enabled_register"`
|
EnabledRegister bool `json:"enabled_register"`
|
||||||
|
EnabledMsgService bool `json:"enabled_msg_service"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ type Property struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FuncZaoBao = "zao_bao" // 每日早报
|
FuncZaoBao = "zao_bao" // 每日早报
|
||||||
FuncHeadLine = "headline" // 今日头条
|
FuncHeadLine = "headline" // 今日头条
|
||||||
FuncWeibo = "weibo_hot" // 微博热搜
|
FuncWeibo = "weibo_hot" // 微博热搜
|
||||||
|
FuncMidJourney = "mid_journey" // MJ 绘画
|
||||||
)
|
)
|
||||||
|
|
||||||
var InnerFunctions = []Function{
|
var InnerFunctions = []Function{
|
||||||
@@ -73,4 +74,27 @@ var InnerFunctions = []Function{
|
|||||||
Required: []string{},
|
Required: []string{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
Name: FuncMidJourney,
|
||||||
|
Description: "AI 绘画工具,使用 MJ MidJourney API 进行 AI 绘画",
|
||||||
|
Parameters: Parameters{
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]Property{
|
||||||
|
"prompt": {
|
||||||
|
Type: "string",
|
||||||
|
Description: "绘画内容描述,提示词,如果该参数中有中文的话,则需要翻译成英文",
|
||||||
|
},
|
||||||
|
"ar": {
|
||||||
|
Type: "string",
|
||||||
|
Description: "图片长宽比,如 --ar 4:3",
|
||||||
|
},
|
||||||
|
"niji": {
|
||||||
|
Type: "string",
|
||||||
|
Description: "动漫模型版本,例如 --niji 5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Required: []string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ type MKey interface {
|
|||||||
string | int
|
string | int
|
||||||
}
|
}
|
||||||
type MValue interface {
|
type MValue interface {
|
||||||
*WsClient | ChatSession | context.CancelFunc | []interface{}
|
*WsClient | ChatSession | context.CancelFunc | []interface{} | MjTask
|
||||||
}
|
}
|
||||||
type LMap[K MKey, T MValue] struct {
|
type LMap[K MKey, T MValue] struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ type BizVo struct {
|
|||||||
|
|
||||||
// WsMessage Websocket message
|
// WsMessage Websocket message
|
||||||
type WsMessage struct {
|
type WsMessage struct {
|
||||||
Type WsMsgType `json:"type"` // 消息类别,start, end
|
Type WsMsgType `json:"type"` // 消息类别,start, end, img
|
||||||
Content string `json:"content"`
|
Content interface{} `json:"content"`
|
||||||
}
|
}
|
||||||
type WsMsgType string
|
type WsMsgType string
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ const (
|
|||||||
WsStart = WsMsgType("start")
|
WsStart = WsMsgType("start")
|
||||||
WsMiddle = WsMsgType("middle")
|
WsMiddle = WsMsgType("middle")
|
||||||
WsEnd = WsMsgType("end")
|
WsEnd = WsMsgType("end")
|
||||||
|
WsMjImg = WsMsgType("mj")
|
||||||
)
|
)
|
||||||
|
|
||||||
type BizCode int
|
type BizCode int
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.1.0
|
github.com/BurntSushi/toml v1.1.0
|
||||||
github.com/aliyun/alibaba-cloud-sdk-go v1.62.405
|
github.com/aliyun/alibaba-cloud-sdk-go v1.62.405
|
||||||
github.com/eatmoreapple/openwechat v1.2.1
|
|
||||||
github.com/gin-contrib/sessions v0.0.5
|
github.com/gin-contrib/sessions v0.0.5
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/imroc/req/v3 v3.37.2
|
github.com/imroc/req/v3 v3.37.2
|
||||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230415042440-a5e3d8259ae0
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20230415042440-a5e3d8259ae0
|
||||||
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480
|
github.com/pkoukk/tiktoken-go v0.1.1-0.20230418101013-cae809389480
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
|
||||||
github.com/syndtr/goleveldb v1.0.0
|
github.com/syndtr/goleveldb v1.0.0
|
||||||
go.uber.org/zap v1.23.0
|
go.uber.org/zap v1.23.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
|
github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0=
|
||||||
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/eatmoreapple/openwechat v1.2.1 h1:ez4oqF/Y2NSEX/DbPV8lvj7JlfkYqvieeo4awx5lzfU=
|
|
||||||
github.com/eatmoreapple/openwechat v1.2.1/go.mod h1:61HOzTyvLobGdgWhL68jfGNwTJEv0mhQ1miCXQrvWU8=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
@@ -140,8 +138,6 @@ github.com/quic-go/quic-go v0.35.1 h1:b0kzj6b/cQAf05cT0CkQubHM31wiA+xH3IBkxP62po
|
|||||||
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
github.com/quic-go/quic-go v0.35.1/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
|
||||||
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
||||||
github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
|
github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ func NewDashboardHandler(app *core.AppServer, db *gorm.DB) *DashboardHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type statsVo struct {
|
type statsVo struct {
|
||||||
Users int64 `json:"users"`
|
Users int64 `json:"users"`
|
||||||
Chats int64 `json:"chats"`
|
Chats int64 `json:"chats"`
|
||||||
Tokens int64 `json:"tokens"`
|
Tokens int64 `json:"tokens"`
|
||||||
|
Rewards float64 `json:"rewards"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *DashboardHandler) Stats(c *gin.Context) {
|
func (h *DashboardHandler) Stats(c *gin.Context) {
|
||||||
@@ -47,9 +48,16 @@ func (h *DashboardHandler) Stats(c *gin.Context) {
|
|||||||
|
|
||||||
// tokens took stats
|
// tokens took stats
|
||||||
var tokenCount int64
|
var tokenCount int64
|
||||||
res = h.db.Model(&model.HistoryMessage{}).Select("sum(tokens) as tokens_total").Where("created_at > ?", zeroTime).Scan(&tokenCount)
|
res = h.db.Model(&model.HistoryMessage{}).Select("sum(tokens) as total").Where("created_at > ?", zeroTime).Scan(&tokenCount)
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
stats.Tokens = tokenCount
|
stats.Tokens = tokenCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reward revenue
|
||||||
|
var amount float64
|
||||||
|
res = h.db.Model(&model.Reward{}).Select("sum(amount) as total").Where("created_at > ?", zeroTime).Scan(&amount)
|
||||||
|
if res.Error == nil {
|
||||||
|
stats.Rewards = amount
|
||||||
|
}
|
||||||
resp.SUCCESS(c, stats)
|
resp.SUCCESS(c, stats)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"chatplus/core"
|
"chatplus/core"
|
||||||
"chatplus/core/types"
|
"chatplus/core/types"
|
||||||
|
"chatplus/store"
|
||||||
"chatplus/store/model"
|
"chatplus/store/model"
|
||||||
"chatplus/store/vo"
|
"chatplus/store/vo"
|
||||||
"chatplus/utils"
|
"chatplus/utils"
|
||||||
@@ -29,11 +30,12 @@ const ErrorMsg = "抱歉,AI 助手开小差了,请稍后再试。"
|
|||||||
|
|
||||||
type ChatHandler struct {
|
type ChatHandler struct {
|
||||||
BaseHandler
|
BaseHandler
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
|
leveldb *store.LevelDB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChatHandler(app *core.AppServer, db *gorm.DB) *ChatHandler {
|
func NewChatHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB) *ChatHandler {
|
||||||
handler := ChatHandler{db: db}
|
handler := ChatHandler{db: db, leveldb: levelDB}
|
||||||
handler.App = app
|
handler.App = app
|
||||||
return &handler
|
return &handler
|
||||||
}
|
}
|
||||||
@@ -88,7 +90,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
var chatRole model.ChatRole
|
var chatRole model.ChatRole
|
||||||
res = h.db.First(&chatRole, roleId)
|
res = h.db.First(&chatRole, roleId)
|
||||||
if res.Error != nil || !chatRole.Enable {
|
if res.Error != nil || !chatRole.Enable {
|
||||||
replyMessage(client, "当前聊天角色不存在或者未启用,连接已关闭!!!")
|
utils.ReplyMessage(client, "当前聊天角色不存在或者未启用,连接已关闭!!!")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -98,7 +100,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
h.db.Where("marker", "chat").First(&config)
|
h.db.Where("marker", "chat").First(&config)
|
||||||
err = utils.JsonDecode(config.Config, &chatConfig)
|
err = utils.JsonDecode(config.Config, &chatConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
replyMessage(client, "加载系统配置失败,连接已关闭!!!")
|
utils.ReplyMessage(client, "加载系统配置失败,连接已关闭!!!")
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -116,7 +118,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Info("Receive a message: ", string(message))
|
logger.Info("Receive a message: ", string(message))
|
||||||
//replyMessage(client, "这是一条测试消息!")
|
//utils.ReplyMessage(client, "这是一条测试消息!")
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
h.App.ReqCancelFunc.Put(sessionId, cancel)
|
h.App.ReqCancelFunc.Put(sessionId, cancel)
|
||||||
// 回复消息
|
// 回复消息
|
||||||
@@ -124,7 +126,7 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
} else {
|
} else {
|
||||||
replyChunkMessage(client, types.WsMessage{Type: types.WsEnd})
|
utils.ReplyChunkMessage(client, types.WsMessage{Type: types.WsEnd})
|
||||||
logger.Info("回答完毕: " + string(message))
|
logger.Info("回答完毕: " + string(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,13 +135,13 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 将消息发送给 ChatGPT 并获取结果,通过 WebSocket 推送到客户端
|
// 将消息发送给 ChatGPT 并获取结果,通过 WebSocket 推送到客户端
|
||||||
func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession, role model.ChatRole, prompt string, ws types.Client) error {
|
func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession, role model.ChatRole, prompt string, ws *types.WsClient) error {
|
||||||
promptCreatedAt := time.Now() // 记录提问时间
|
promptCreatedAt := time.Now() // 记录提问时间
|
||||||
|
|
||||||
var user model.User
|
var user model.User
|
||||||
res := h.db.Model(&model.User{}).First(&user, session.UserId)
|
res := h.db.Model(&model.User{}).First(&user, session.UserId)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
replyMessage(ws, "非法用户,请联系管理员!")
|
utils.ReplyMessage(ws, "非法用户,请联系管理员!")
|
||||||
return res.Error
|
return res.Error
|
||||||
}
|
}
|
||||||
var userVo vo.User
|
var userVo vo.User
|
||||||
@@ -150,20 +152,20 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession
|
|||||||
}
|
}
|
||||||
|
|
||||||
if userVo.Status == false {
|
if userVo.Status == false {
|
||||||
replyMessage(ws, "您的账号已经被禁用,如果疑问,请联系管理员!")
|
utils.ReplyMessage(ws, "您的账号已经被禁用,如果疑问,请联系管理员!")
|
||||||
replyMessage(ws, "")
|
utils.ReplyMessage(ws, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if userVo.Calls <= 0 && userVo.ChatConfig.ApiKey == "" {
|
if userVo.Calls <= 0 && userVo.ChatConfig.ApiKey == "" {
|
||||||
replyMessage(ws, "您的对话次数已经用尽,请联系管理员或者点击左下角菜单加入众筹获得100次对话!")
|
utils.ReplyMessage(ws, "您的对话次数已经用尽,请联系管理员或者点击左下角菜单加入众筹获得100次对话!")
|
||||||
replyMessage(ws, "")
|
utils.ReplyMessage(ws, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if userVo.ExpiredTime > 0 && userVo.ExpiredTime <= time.Now().Unix() {
|
if userVo.ExpiredTime > 0 && userVo.ExpiredTime <= time.Now().Unix() {
|
||||||
replyMessage(ws, "您的账号已经过期,请联系管理员!")
|
utils.ReplyMessage(ws, "您的账号已经过期,请联系管理员!")
|
||||||
replyMessage(ws, "")
|
utils.ReplyMessage(ws, "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var req = types.ApiRequest{
|
var req = types.ApiRequest{
|
||||||
@@ -238,14 +240,14 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession
|
|||||||
logger.Info("用户取消了请求:", prompt)
|
logger.Info("用户取消了请求:", prompt)
|
||||||
return nil
|
return nil
|
||||||
} else if strings.Contains(err.Error(), "no available key") {
|
} else if strings.Contains(err.Error(), "no available key") {
|
||||||
replyMessage(ws, "抱歉😔😔😔,系统已经没有可用的 API KEY🔑,您可以导入自己的 API KEY🔑 继续使用!🙏🙏🙏")
|
utils.ReplyMessage(ws, "抱歉😔😔😔,系统已经没有可用的 API KEY🔑,您可以导入自己的 API KEY🔑 继续使用!🙏🙏🙏")
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
replyMessage(ws, ErrorMsg)
|
utils.ReplyMessage(ws, ErrorMsg)
|
||||||
replyMessage(ws, "")
|
utils.ReplyMessage(ws, "")
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
@@ -280,8 +282,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession
|
|||||||
err = json.Unmarshal([]byte(line[6:]), &responseBody)
|
err = json.Unmarshal([]byte(line[6:]), &responseBody)
|
||||||
if err != nil || len(responseBody.Choices) == 0 { // 数据解析出错
|
if err != nil || len(responseBody.Choices) == 0 { // 数据解析出错
|
||||||
logger.Error(err, line)
|
logger.Error(err, line)
|
||||||
replyMessage(ws, ErrorMsg)
|
utils.ReplyMessage(ws, ErrorMsg)
|
||||||
replyMessage(ws, "")
|
utils.ReplyMessage(ws, "")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,8 +297,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession
|
|||||||
functionCall = true
|
functionCall = true
|
||||||
functionName = fun.Name
|
functionName = fun.Name
|
||||||
f := h.App.Functions[functionName]
|
f := h.App.Functions[functionName]
|
||||||
replyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
||||||
replyChunkMessage(ws, types.WsMessage{Type: types.WsMiddle, Content: fmt.Sprintf("正在调用函数 `%s` 作答 ...\n\n", f.Name())})
|
utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsMiddle, Content: fmt.Sprintf("正在调用函数 `%s` 作答 ...\n\n", f.Name())})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,14 +309,14 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession
|
|||||||
// 初始化 role
|
// 初始化 role
|
||||||
if responseBody.Choices[0].Delta.Role != "" && message.Role == "" {
|
if responseBody.Choices[0].Delta.Role != "" && message.Role == "" {
|
||||||
message.Role = responseBody.Choices[0].Delta.Role
|
message.Role = responseBody.Choices[0].Delta.Role
|
||||||
replyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
||||||
continue
|
continue
|
||||||
} else if responseBody.Choices[0].FinishReason != "" {
|
} else if responseBody.Choices[0].FinishReason != "" {
|
||||||
break // 输出完成或者输出中断了
|
break // 输出完成或者输出中断了
|
||||||
} else {
|
} else {
|
||||||
content := responseBody.Choices[0].Delta.Content
|
content := responseBody.Choices[0].Delta.Content
|
||||||
contents = append(contents, utils.InterfaceToString(content))
|
contents = append(contents, utils.InterfaceToString(content))
|
||||||
replyChunkMessage(ws, types.WsMessage{
|
utils.ReplyChunkMessage(ws, types.WsMessage{
|
||||||
Type: types.WsMiddle,
|
Type: types.WsMiddle,
|
||||||
Content: utils.InterfaceToString(responseBody.Choices[0].Delta.Content),
|
Content: utils.InterfaceToString(responseBody.Choices[0].Delta.Content),
|
||||||
})
|
})
|
||||||
@@ -322,23 +324,44 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession
|
|||||||
} // end for
|
} // end for
|
||||||
|
|
||||||
if functionCall { // 调用函数完成任务
|
if functionCall { // 调用函数完成任务
|
||||||
logger.Info(functionName)
|
logger.Info("函数名称:", functionName)
|
||||||
logger.Info(arguments)
|
var params map[string]interface{}
|
||||||
|
_ = utils.JsonDecode(strings.Join(arguments, ""), ¶ms)
|
||||||
|
logger.Info("函数参数:", params)
|
||||||
f := h.App.Functions[functionName]
|
f := h.App.Functions[functionName]
|
||||||
data, err := f.Invoke(arguments)
|
data, err := f.Invoke(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := "调用函数出错:" + err.Error()
|
msg := "调用函数出错:" + err.Error()
|
||||||
replyChunkMessage(ws, types.WsMessage{
|
utils.ReplyChunkMessage(ws, types.WsMessage{
|
||||||
Type: types.WsMiddle,
|
Type: types.WsMiddle,
|
||||||
Content: msg,
|
Content: msg,
|
||||||
})
|
})
|
||||||
contents = append(contents, msg)
|
contents = append(contents, msg)
|
||||||
} else {
|
} else {
|
||||||
replyChunkMessage(ws, types.WsMessage{
|
content := data
|
||||||
|
if functionName == types.FuncMidJourney {
|
||||||
|
key := utils.Sha256(data)
|
||||||
|
//logger.Info(data, ",", key)
|
||||||
|
// add task for MidJourney
|
||||||
|
h.App.MjTaskClients.Put(key, ws)
|
||||||
|
task := types.MjTask{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Icon: "/images/avatar/mid_journey.png",
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
}
|
||||||
|
err := h.leveldb.Put(types.TaskStorePrefix+key, task)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error with store MidJourney task: ", err)
|
||||||
|
}
|
||||||
|
content = fmt.Sprintf("绘画提示词:%s 已推送任务到 MidJourney 机器人,请耐心等待任务执行...", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.ReplyChunkMessage(ws, types.WsMessage{
|
||||||
Type: types.WsMiddle,
|
Type: types.WsMiddle,
|
||||||
Content: data,
|
Content: content,
|
||||||
})
|
})
|
||||||
contents = append(contents, data)
|
contents = append(contents, content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +453,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession
|
|||||||
} else {
|
} else {
|
||||||
totalTokens = replyToken + getTotalTokens(req)
|
totalTokens = replyToken + getTotalTokens(req)
|
||||||
}
|
}
|
||||||
//replyChunkMessage(ws, types.WsMessage{Type: types.WsMiddle, Content: fmt.Sprintf("\n\n `本轮对话共消耗 Token 数量: %d`", totalTokens+11)})
|
//utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsMiddle, Content: fmt.Sprintf("\n\n `本轮对话共消耗 Token 数量: %d`", totalTokens+11)})
|
||||||
if userVo.ChatConfig.ApiKey != "" { // 调用自己的 API KEY 不计算 token 消耗
|
if userVo.ChatConfig.ApiKey != "" { // 调用自己的 API KEY 不计算 token 消耗
|
||||||
h.db.Model(&user).UpdateColumn("tokens", gorm.Expr("tokens + ?",
|
h.db.Model(&user).UpdateColumn("tokens", gorm.Expr("tokens + ?",
|
||||||
totalTokens))
|
totalTokens))
|
||||||
@@ -468,18 +491,18 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session types.ChatSession
|
|||||||
// OpenAI API 调用异常处理
|
// OpenAI API 调用异常处理
|
||||||
// TODO: 是否考虑重发消息?
|
// TODO: 是否考虑重发消息?
|
||||||
if strings.Contains(res.Error.Message, "This key is associated with a deactivated account") {
|
if strings.Contains(res.Error.Message, "This key is associated with a deactivated account") {
|
||||||
replyMessage(ws, "请求 OpenAI API 失败:API KEY 所关联的账户被禁用。")
|
utils.ReplyMessage(ws, "请求 OpenAI API 失败:API KEY 所关联的账户被禁用。")
|
||||||
// 移除当前 API key
|
// 移除当前 API key
|
||||||
h.db.Where("value = ?", apiKey).Delete(&model.ApiKey{})
|
h.db.Where("value = ?", apiKey).Delete(&model.ApiKey{})
|
||||||
} else if strings.Contains(res.Error.Message, "You exceeded your current quota") {
|
} else if strings.Contains(res.Error.Message, "You exceeded your current quota") {
|
||||||
replyMessage(ws, "请求 OpenAI API 失败:API KEY 触发并发限制,请稍后再试。")
|
utils.ReplyMessage(ws, "请求 OpenAI API 失败:API KEY 触发并发限制,请稍后再试。")
|
||||||
} else if strings.Contains(res.Error.Message, "This model's maximum context length") {
|
} else if strings.Contains(res.Error.Message, "This model's maximum context length") {
|
||||||
logger.Error(res.Error.Message)
|
logger.Error(res.Error.Message)
|
||||||
replyMessage(ws, "当前会话上下文长度超出限制,已为您清空会话上下文!")
|
utils.ReplyMessage(ws, "当前会话上下文长度超出限制,已为您清空会话上下文!")
|
||||||
h.App.ChatContexts.Delete(session.ChatId)
|
h.App.ChatContexts.Delete(session.ChatId)
|
||||||
return h.sendMessage(ctx, session, role, prompt, ws)
|
return h.sendMessage(ctx, session, role, prompt, ws)
|
||||||
} else {
|
} else {
|
||||||
replyMessage(ws, "请求 OpenAI API 失败:"+res.Error.Message)
|
utils.ReplyMessage(ws, "请求 OpenAI API 失败:"+res.Error.Message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,26 +557,6 @@ func (h *ChatHandler) doRequest(ctx context.Context, user vo.User, apiKey *strin
|
|||||||
return client.Do(request)
|
return client.Do(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 回复客户片段端消息
|
|
||||||
func replyChunkMessage(client types.Client, message types.WsMessage) {
|
|
||||||
msg, err := json.Marshal(message)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Error for decoding json data: %v", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = client.(*types.WsClient).Send(msg)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Error for reply message: %v", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 回复客户端一条完整的消息
|
|
||||||
func replyMessage(ws types.Client, message string) {
|
|
||||||
replyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
|
||||||
replyChunkMessage(ws, types.WsMessage{Type: types.WsMiddle, Content: message})
|
|
||||||
replyChunkMessage(ws, types.WsMessage{Type: types.WsEnd})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tokens 统计 token 数量
|
// Tokens 统计 token 数量
|
||||||
func (h *ChatHandler) Tokens(c *gin.Context) {
|
func (h *ChatHandler) Tokens(c *gin.Context) {
|
||||||
text := c.Query("text")
|
text := c.Query("text")
|
||||||
|
|||||||
@@ -10,44 +10,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// List 获取会话列表
|
|
||||||
func (h *ChatHandler) List(c *gin.Context) {
|
|
||||||
userId := h.GetInt(c, "user_id", 0)
|
|
||||||
if userId == 0 {
|
|
||||||
resp.ERROR(c, "The parameter 'user_id' is needed.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var items = make([]vo.ChatItem, 0)
|
|
||||||
var chats []model.ChatItem
|
|
||||||
res := h.db.Where("user_id = ?", userId).Order("id DESC").Find(&chats)
|
|
||||||
if res.Error == nil {
|
|
||||||
var roleIds = make([]uint, 0)
|
|
||||||
for _, chat := range chats {
|
|
||||||
roleIds = append(roleIds, chat.RoleId)
|
|
||||||
}
|
|
||||||
var roles []model.ChatRole
|
|
||||||
res = h.db.Find(&roles, roleIds)
|
|
||||||
if res.Error == nil {
|
|
||||||
roleMap := make(map[uint]model.ChatRole)
|
|
||||||
for _, role := range roles {
|
|
||||||
roleMap[role.Id] = role
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, chat := range chats {
|
|
||||||
var item vo.ChatItem
|
|
||||||
err := utils.CopyObject(chat, &item)
|
|
||||||
if err == nil {
|
|
||||||
item.Id = chat.Id
|
|
||||||
item.Icon = roleMap[chat.RoleId].Icon
|
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
resp.SUCCESS(c, items)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update 更新会话标题
|
// Update 更新会话标题
|
||||||
func (h *ChatHandler) Update(c *gin.Context) {
|
func (h *ChatHandler) Update(c *gin.Context) {
|
||||||
var data struct {
|
var data struct {
|
||||||
|
|||||||
71
api/handler/chat_item_handler.go
Normal file
71
api/handler/chat_item_handler.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/store/model"
|
||||||
|
"chatplus/store/vo"
|
||||||
|
"chatplus/utils"
|
||||||
|
"chatplus/utils/resp"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// List 获取会话列表
|
||||||
|
func (h *ChatHandler) List(c *gin.Context) {
|
||||||
|
userId := h.GetInt(c, "user_id", 0)
|
||||||
|
if userId == 0 {
|
||||||
|
resp.ERROR(c, "The parameter 'user_id' is needed.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var items = make([]vo.ChatItem, 0)
|
||||||
|
var chats []model.ChatItem
|
||||||
|
res := h.db.Where("user_id = ?", userId).Order("id DESC").Find(&chats)
|
||||||
|
if res.Error == nil {
|
||||||
|
var roleIds = make([]uint, 0)
|
||||||
|
for _, chat := range chats {
|
||||||
|
roleIds = append(roleIds, chat.RoleId)
|
||||||
|
}
|
||||||
|
var roles []model.ChatRole
|
||||||
|
res = h.db.Find(&roles, roleIds)
|
||||||
|
if res.Error == nil {
|
||||||
|
roleMap := make(map[uint]model.ChatRole)
|
||||||
|
for _, role := range roles {
|
||||||
|
roleMap[role.Id] = role
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chat := range chats {
|
||||||
|
var item vo.ChatItem
|
||||||
|
err := utils.CopyObject(chat, &item)
|
||||||
|
if err == nil {
|
||||||
|
item.Id = chat.Id
|
||||||
|
item.Icon = roleMap[chat.RoleId].Icon
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
resp.SUCCESS(c, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChatHandler) Detail(c *gin.Context) {
|
||||||
|
chatId := h.GetTrim(c, "chat_id")
|
||||||
|
if utils.IsEmptyValue(chatId) {
|
||||||
|
resp.ERROR(c, "Invalid chatId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chatItem model.ChatItem
|
||||||
|
res := h.db.Where("chat_id = ?", chatId).First(&chatItem)
|
||||||
|
if res.Error != nil {
|
||||||
|
resp.ERROR(c, "No chat found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chatItemVo vo.ChatItem
|
||||||
|
err := utils.CopyObject(chatItem, &chatItemVo)
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.SUCCESS(c, chatItemVo)
|
||||||
|
}
|
||||||
213
api/handler/mj_handler.go
Normal file
213
api/handler/mj_handler.go
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/core"
|
||||||
|
"chatplus/core/types"
|
||||||
|
"chatplus/service/function"
|
||||||
|
"chatplus/store"
|
||||||
|
"chatplus/store/model"
|
||||||
|
"chatplus/utils"
|
||||||
|
"chatplus/utils/resp"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Start = TaskStatus("Started")
|
||||||
|
Running = TaskStatus("Running")
|
||||||
|
Stopped = TaskStatus("Stopped")
|
||||||
|
Finished = TaskStatus("Finished")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
ProxyURL string `json:"proxy_url"`
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MidJourneyHandler struct {
|
||||||
|
BaseHandler
|
||||||
|
leveldb *store.LevelDB
|
||||||
|
db *gorm.DB
|
||||||
|
mjFunc function.FuncMidJourney
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMidJourneyHandler(app *core.AppServer, leveldb *store.LevelDB, db *gorm.DB, functions map[string]function.Function) *MidJourneyHandler {
|
||||||
|
h := MidJourneyHandler{leveldb: leveldb, db: db, mjFunc: functions[types.FuncMidJourney].(function.FuncMidJourney)}
|
||||||
|
h.App = app
|
||||||
|
return &h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MidJourneyHandler) Notify(c *gin.Context) {
|
||||||
|
token := c.GetHeader("Authorization")
|
||||||
|
if token != h.App.Config.ExtConfig.Token {
|
||||||
|
resp.NotAuth(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
MessageId string `json:"message_id"`
|
||||||
|
ReferenceId string `json:"reference_id"`
|
||||||
|
Image Image `json:"image"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
Status TaskStatus `json:"status"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil || data.Prompt == "" {
|
||||||
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("收到 MidJourney 回调请求:%+v", data)
|
||||||
|
|
||||||
|
// the job is saved
|
||||||
|
var job model.MidJourneyJob
|
||||||
|
res := h.db.Where("message_id = ?", data.MessageId).First(&job)
|
||||||
|
if res.Error == nil {
|
||||||
|
resp.SUCCESS(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Key = utils.Sha256(data.Prompt)
|
||||||
|
//logger.Info(data.Prompt, ",", key)
|
||||||
|
if data.Status == Finished {
|
||||||
|
var task types.MjTask
|
||||||
|
err := h.leveldb.Get(types.TaskStorePrefix+data.Key, &task)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error with get MidJourney task: ", err)
|
||||||
|
resp.SUCCESS(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 是否需要把图片下载到本地服务器?
|
||||||
|
|
||||||
|
message := model.HistoryMessage{
|
||||||
|
UserId: task.UserId,
|
||||||
|
ChatId: task.ChatId,
|
||||||
|
RoleId: task.RoleId,
|
||||||
|
Type: types.MjMsg,
|
||||||
|
Icon: task.Icon,
|
||||||
|
Content: utils.JsonEncode(data),
|
||||||
|
Tokens: 0,
|
||||||
|
UseContext: false,
|
||||||
|
}
|
||||||
|
res := h.db.Create(&message)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("error with save chat history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the job
|
||||||
|
job.UserId = task.UserId
|
||||||
|
job.ChatId = task.ChatId
|
||||||
|
job.MessageId = data.MessageId
|
||||||
|
job.Content = data.Content
|
||||||
|
job.Prompt = data.Prompt
|
||||||
|
job.Image = utils.JsonEncode(data.Image)
|
||||||
|
job.Hash = data.Image.Hash
|
||||||
|
job.CreatedAt = time.Now()
|
||||||
|
res = h.db.Create(&job)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("error with save MidJourney Job: ", res.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推送消息到客户端
|
||||||
|
wsClient := h.App.MjTaskClients.Get(data.Key)
|
||||||
|
if wsClient == nil { // 客户端断线,则丢弃
|
||||||
|
logger.Errorf("Client is offline: %+v", data)
|
||||||
|
resp.SUCCESS(c, "Client is offline")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Status == Finished {
|
||||||
|
utils.ReplyChunkMessage(wsClient, types.WsMessage{Type: types.WsMjImg, Content: data})
|
||||||
|
utils.ReplyChunkMessage(wsClient, types.WsMessage{Type: types.WsEnd})
|
||||||
|
// delete client
|
||||||
|
h.App.MjTaskClients.Delete(data.Key)
|
||||||
|
} else {
|
||||||
|
utils.ReplyChunkMessage(wsClient, types.WsMessage{Type: types.WsMjImg, Content: data})
|
||||||
|
}
|
||||||
|
resp.SUCCESS(c, "SUCCESS")
|
||||||
|
}
|
||||||
|
|
||||||
|
type reqVo struct {
|
||||||
|
Index int32 `json:"index"`
|
||||||
|
MessageId string `json:"message_id"`
|
||||||
|
MessageHash string `json:"message_hash"`
|
||||||
|
SessionId string `json:"session_id"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 == "" ||
|
||||||
|
data.Key == "" {
|
||||||
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wsClient := h.App.ChatClients.Get(data.SessionId)
|
||||||
|
if wsClient == nil {
|
||||||
|
resp.ERROR(c, "No Websocket client online")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.mjFunc.Upscale(function.MjUpscaleReq{
|
||||||
|
Index: data.Index,
|
||||||
|
MessageId: data.MessageId,
|
||||||
|
MessageHash: data.MessageHash,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content := fmt.Sprintf("**%s** 已推送 Upscale 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
|
||||||
|
utils.ReplyMessage(wsClient, content)
|
||||||
|
if h.App.MjTaskClients.Get(data.Key) == nil {
|
||||||
|
h.App.MjTaskClients.Put(data.Key, wsClient)
|
||||||
|
}
|
||||||
|
resp.SUCCESS(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MidJourneyHandler) Variation(c *gin.Context) {
|
||||||
|
var data reqVo
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil ||
|
||||||
|
data.SessionId == "" ||
|
||||||
|
data.Key == "" {
|
||||||
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wsClient := h.App.ChatClients.Get(data.SessionId)
|
||||||
|
if wsClient == nil {
|
||||||
|
resp.ERROR(c, "No Websocket client online")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.mjFunc.Variation(function.MjVariationReq{
|
||||||
|
Index: data.Index,
|
||||||
|
MessageId: data.MessageId,
|
||||||
|
MessageHash: data.MessageHash,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
resp.ERROR(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content := fmt.Sprintf("**%s** 已推送 Variation 任务到 MidJourney 机器人,请耐心等待任务执行...", data.Prompt)
|
||||||
|
utils.ReplyMessage(wsClient, content)
|
||||||
|
if h.App.MjTaskClients.Get(data.Key) == nil {
|
||||||
|
h.App.MjTaskClients.Put(data.Key, wsClient)
|
||||||
|
}
|
||||||
|
resp.SUCCESS(c)
|
||||||
|
}
|
||||||
@@ -21,6 +21,50 @@ func NewRewardHandler(server *core.AppServer, db *gorm.DB) *RewardHandler {
|
|||||||
return &h
|
return &h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *RewardHandler) Notify(c *gin.Context) {
|
||||||
|
token := c.GetHeader("Authorization")
|
||||||
|
if token != h.App.Config.ExtConfig.Token {
|
||||||
|
resp.NotAuth(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
TransId string `json:"trans_id"` // 微信转账交易 ID
|
||||||
|
Amount float64 `json:"amount"` // 微信转账交易金额
|
||||||
|
Remark string `json:"remark"` // 转账备注
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.Amount <= 0 {
|
||||||
|
resp.ERROR(c, "Amount should not be 0")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("收到众筹收款信息: %+v", data)
|
||||||
|
var item model.Reward
|
||||||
|
res := h.db.Where("tx_id = ?", data.TransId).First(&item)
|
||||||
|
if res.Error == nil {
|
||||||
|
resp.ERROR(c, "当前交易 ID 己经存在!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res = h.db.Create(&model.Reward{
|
||||||
|
TxId: data.TransId,
|
||||||
|
Amount: data.Amount,
|
||||||
|
Remark: data.Remark,
|
||||||
|
Status: false,
|
||||||
|
})
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Errorf("交易保存失败: %v", res.Error)
|
||||||
|
resp.ERROR(c, "交易保存失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.SUCCESS(c)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify 打赏码核销
|
// Verify 打赏码核销
|
||||||
func (h *RewardHandler) Verify(c *gin.Context) {
|
func (h *RewardHandler) Verify(c *gin.Context) {
|
||||||
var data struct {
|
var data struct {
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ const CodeStorePrefix = "/verify/codes/"
|
|||||||
|
|
||||||
type SmsHandler struct {
|
type SmsHandler struct {
|
||||||
BaseHandler
|
BaseHandler
|
||||||
db *store.LevelDB
|
leveldb *store.LevelDB
|
||||||
sms *service.AliYunSmsService
|
sms *service.AliYunSmsService
|
||||||
captcha *service.CaptchaService
|
captcha *service.CaptchaService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSmsHandler(app *core.AppServer, db *store.LevelDB, sms *service.AliYunSmsService, captcha *service.CaptchaService) *SmsHandler {
|
func NewSmsHandler(app *core.AppServer, db *store.LevelDB, sms *service.AliYunSmsService, captcha *service.CaptchaService) *SmsHandler {
|
||||||
handler := &SmsHandler{db: db, sms: sms, captcha: captcha}
|
handler := &SmsHandler{leveldb: db, sms: sms, captcha: captcha}
|
||||||
handler.App = app
|
handler.App = app
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,7 @@ func (h *SmsHandler) SendCode(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 存储验证码,等待后面注册验证
|
// 存储验证码,等待后面注册验证
|
||||||
err = h.db.Put(CodeStorePrefix+data.Mobile, code)
|
err = h.leveldb.Put(CodeStorePrefix+data.Mobile, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, "验证码保存失败")
|
resp.ERROR(c, "验证码保存失败")
|
||||||
return
|
return
|
||||||
@@ -66,5 +66,5 @@ type statusVo struct {
|
|||||||
|
|
||||||
// Status check if the message service is enabled
|
// Status check if the message service is enabled
|
||||||
func (h *SmsHandler) Status(c *gin.Context) {
|
func (h *SmsHandler) Status(c *gin.Context) {
|
||||||
resp.SUCCESS(c, statusVo{EnabledMsgService: h.App.Config.EnabledMsgService, EnabledRegister: h.App.SysConfig.EnabledRegister})
|
resp.SUCCESS(c, statusVo{EnabledMsgService: h.App.SysConfig.EnabledMsgService, EnabledRegister: h.App.SysConfig.EnabledRegister})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ type UserHandler struct {
|
|||||||
BaseHandler
|
BaseHandler
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
searcher *xdb.Searcher
|
searcher *xdb.Searcher
|
||||||
levelDB *store.LevelDB
|
leveldb *store.LevelDB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserHandler(app *core.AppServer, db *gorm.DB, searcher *xdb.Searcher, levelDB *store.LevelDB) *UserHandler {
|
func NewUserHandler(app *core.AppServer, db *gorm.DB, searcher *xdb.Searcher, levelDB *store.LevelDB) *UserHandler {
|
||||||
handler := &UserHandler{db: db, searcher: searcher, levelDB: levelDB}
|
handler := &UserHandler{db: db, searcher: searcher, leveldb: levelDB}
|
||||||
handler.App = app
|
handler.App = app
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
@@ -58,9 +58,9 @@ func (h *UserHandler) Register(c *gin.Context) {
|
|||||||
|
|
||||||
// 检查验证码
|
// 检查验证码
|
||||||
key := CodeStorePrefix + data.Mobile
|
key := CodeStorePrefix + data.Mobile
|
||||||
if h.App.Config.EnabledMsgService {
|
if h.App.SysConfig.EnabledMsgService {
|
||||||
var code int
|
var code int
|
||||||
err := h.levelDB.Get(key, &code)
|
err := h.leveldb.Get(key, &code)
|
||||||
if err != nil || code != data.Code {
|
if err != nil || code != data.Code {
|
||||||
logger.Info(code)
|
logger.Info(code)
|
||||||
resp.ERROR(c, "短信验证码错误")
|
resp.ERROR(c, "短信验证码错误")
|
||||||
@@ -117,8 +117,8 @@ func (h *UserHandler) Register(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if h.App.Config.EnabledMsgService {
|
if h.App.SysConfig.EnabledMsgService {
|
||||||
_ = h.levelDB.Delete(key) // 注册成功,删除短信验证码
|
_ = h.leveldb.Delete(key) // 注册成功,删除短信验证码
|
||||||
}
|
}
|
||||||
resp.SUCCESS(c, user)
|
resp.SUCCESS(c, user)
|
||||||
}
|
}
|
||||||
@@ -173,29 +173,8 @@ func (h *UserHandler) Login(c *gin.Context) {
|
|||||||
LoginIp: c.ClientIP(),
|
LoginIp: c.ClientIP(),
|
||||||
LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
|
LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
|
||||||
})
|
})
|
||||||
var chatConfig types.ChatConfig
|
|
||||||
err = utils.JsonDecode(user.ChatConfig, &chatConfig)
|
|
||||||
if err != nil {
|
|
||||||
resp.ERROR(c, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.SUCCESS(c, gin.H{
|
resp.SUCCESS(c, sessionId)
|
||||||
"session_id": sessionId,
|
|
||||||
"id": user.Id,
|
|
||||||
"nickname": user.Nickname,
|
|
||||||
"avatar": user.Avatar,
|
|
||||||
"username": user.Username,
|
|
||||||
"tokens": user.Tokens,
|
|
||||||
"calls": user.Calls,
|
|
||||||
"expired_time": user.ExpiredTime,
|
|
||||||
"api_key": chatConfig.ApiKey,
|
|
||||||
"model": chatConfig.Model,
|
|
||||||
"temperature": chatConfig.Temperature,
|
|
||||||
"max_tokens": chatConfig.MaxTokens,
|
|
||||||
"enable_context": chatConfig.EnableContext,
|
|
||||||
"enable_history": chatConfig.EnableHistory,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logout 注 销
|
// Logout 注 销
|
||||||
@@ -366,7 +345,7 @@ func (h *UserHandler) BindMobile(c *gin.Context) {
|
|||||||
// 检查验证码
|
// 检查验证码
|
||||||
key := CodeStorePrefix + data.Mobile
|
key := CodeStorePrefix + data.Mobile
|
||||||
var code int
|
var code int
|
||||||
err := h.levelDB.Get(key, &code)
|
err := h.leveldb.Get(key, &code)
|
||||||
if err != nil || code != data.Code {
|
if err != nil || code != data.Code {
|
||||||
resp.ERROR(c, "短信验证码错误")
|
resp.ERROR(c, "短信验证码错误")
|
||||||
return
|
return
|
||||||
@@ -384,6 +363,6 @@ func (h *UserHandler) BindMobile(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = h.levelDB.Delete(key) // 删除短信验证码
|
_ = h.leveldb.Delete(key) // 删除短信验证码
|
||||||
resp.SUCCESS(c)
|
resp.SUCCESS(c)
|
||||||
}
|
}
|
||||||
|
|||||||
30
api/main.go
30
api/main.go
@@ -6,7 +6,6 @@ import (
|
|||||||
"chatplus/handler"
|
"chatplus/handler"
|
||||||
"chatplus/handler/admin"
|
"chatplus/handler/admin"
|
||||||
logger2 "chatplus/logger"
|
logger2 "chatplus/logger"
|
||||||
"chatplus/modules/wexin"
|
|
||||||
"chatplus/service"
|
"chatplus/service"
|
||||||
"chatplus/service/function"
|
"chatplus/service/function"
|
||||||
"chatplus/store"
|
"chatplus/store"
|
||||||
@@ -104,27 +103,8 @@ func main() {
|
|||||||
return xdb.NewWithBuffer(cBuff)
|
return xdb.NewWithBuffer(cBuff)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// 创建微信机器人
|
|
||||||
fx.Provide(wexin.NewWeChatBot),
|
|
||||||
fx.Invoke(func(bot *wexin.WeChatBot) {
|
|
||||||
go func() {
|
|
||||||
err := bot.Login()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}),
|
|
||||||
|
|
||||||
// 创建函数
|
// 创建函数
|
||||||
fx.Provide(func(config *types.AppConfig) (function.FuncZaoBao, error) {
|
fx.Provide(function.NewFunctions),
|
||||||
return function.NewZaoBao(config.ApiConfig), nil
|
|
||||||
}),
|
|
||||||
fx.Provide(func(config *types.AppConfig) (function.FuncWeiboHot, error) {
|
|
||||||
return function.NewWeiboHot(config.ApiConfig), nil
|
|
||||||
}),
|
|
||||||
fx.Provide(func(config *types.AppConfig) (function.FuncHeadlines, error) {
|
|
||||||
return function.NewHeadLines(config.ApiConfig), nil
|
|
||||||
}),
|
|
||||||
|
|
||||||
// 创建控制器
|
// 创建控制器
|
||||||
fx.Provide(handler.NewChatRoleHandler),
|
fx.Provide(handler.NewChatRoleHandler),
|
||||||
@@ -134,6 +114,7 @@ func main() {
|
|||||||
fx.Provide(handler.NewSmsHandler),
|
fx.Provide(handler.NewSmsHandler),
|
||||||
fx.Provide(handler.NewRewardHandler),
|
fx.Provide(handler.NewRewardHandler),
|
||||||
fx.Provide(handler.NewCaptchaHandler),
|
fx.Provide(handler.NewCaptchaHandler),
|
||||||
|
fx.Provide(handler.NewMidJourneyHandler),
|
||||||
|
|
||||||
fx.Provide(admin.NewConfigHandler),
|
fx.Provide(admin.NewConfigHandler),
|
||||||
fx.Provide(admin.NewAdminHandler),
|
fx.Provide(admin.NewAdminHandler),
|
||||||
@@ -169,6 +150,7 @@ func main() {
|
|||||||
group := s.Engine.Group("/api/chat/")
|
group := s.Engine.Group("/api/chat/")
|
||||||
group.Any("new", h.ChatHandle)
|
group.Any("new", h.ChatHandle)
|
||||||
group.GET("list", h.List)
|
group.GET("list", h.List)
|
||||||
|
group.GET("detail", h.Detail)
|
||||||
group.POST("update", h.Update)
|
group.POST("update", h.Update)
|
||||||
group.GET("remove", h.Remove)
|
group.GET("remove", h.Remove)
|
||||||
group.GET("history", h.History)
|
group.GET("history", h.History)
|
||||||
@@ -191,8 +173,14 @@ func main() {
|
|||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.RewardHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.RewardHandler) {
|
||||||
group := s.Engine.Group("/api/reward/")
|
group := s.Engine.Group("/api/reward/")
|
||||||
|
group.POST("notify", h.Notify)
|
||||||
group.POST("verify", h.Verify)
|
group.POST("verify", h.Verify)
|
||||||
}),
|
}),
|
||||||
|
fx.Invoke(func(s *core.AppServer, h *handler.MidJourneyHandler) {
|
||||||
|
s.Engine.POST("/api/mj/notify", h.Notify)
|
||||||
|
s.Engine.POST("/api/mj/upscale", h.Upscale)
|
||||||
|
s.Engine.POST("/api/mj/variation", h.Variation)
|
||||||
|
}),
|
||||||
|
|
||||||
// 管理后台控制器
|
// 管理后台控制器
|
||||||
fx.Invoke(func(s *core.AppServer, h *admin.ConfigHandler) {
|
fx.Invoke(func(s *core.AppServer, h *admin.ConfigHandler) {
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package wexin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"chatplus/store/model"
|
|
||||||
"github.com/eatmoreapple/openwechat"
|
|
||||||
"github.com/skip2/go-qrcode"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MessageHandler 消息处理
|
|
||||||
func MessageHandler(msg *openwechat.Message, db *gorm.DB) {
|
|
||||||
sender, err := msg.Sender()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只处理微信支付的推送消息
|
|
||||||
if sender.NickName == "微信支付" ||
|
|
||||||
msg.MsgType == openwechat.MsgTypeApp ||
|
|
||||||
msg.AppMsgType == openwechat.AppMsgTypeUrl {
|
|
||||||
// 解析支付金额
|
|
||||||
message, err := parseTransactionMessage(msg.Content)
|
|
||||||
if err == nil {
|
|
||||||
transaction := extractTransaction(message)
|
|
||||||
logger.Infof("解析到收款信息:%+v", transaction)
|
|
||||||
if transaction.Amount <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var item model.Reward
|
|
||||||
res := db.Where("tx_id = ?", transaction.TransId).First(&item)
|
|
||||||
if res.Error == nil {
|
|
||||||
logger.Infof("当前交易 ID %s 己经存在!", transaction.TransId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res = db.Create(&model.Reward{
|
|
||||||
TxId: transaction.TransId,
|
|
||||||
Amount: transaction.Amount,
|
|
||||||
Remark: transaction.Remark,
|
|
||||||
Status: false,
|
|
||||||
})
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Errorf("交易保存失败,ID: %s", transaction.TransId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// QrCodeCallBack 登录扫码回调,
|
|
||||||
func QrCodeCallBack(uuid string) {
|
|
||||||
logger.Info("请使用微信扫描下面二维码登录")
|
|
||||||
q, _ := qrcode.New("https://login.weixin.qq.com/l/"+uuid, qrcode.Medium)
|
|
||||||
logger.Info(q.ToString(true))
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package wexin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Message 转账消息
|
|
||||||
type Message struct {
|
|
||||||
XMLName xml.Name `xml:"msg"`
|
|
||||||
AppMsg struct {
|
|
||||||
Des string `xml:"des"`
|
|
||||||
Url string `xml:"url"`
|
|
||||||
} `xml:"appmsg"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transaction 解析后的交易信息
|
|
||||||
type Transaction struct {
|
|
||||||
TransId string `json:"trans_id"` // 微信转账交易 ID
|
|
||||||
Amount float64 `json:"amount"` // 微信转账交易金额
|
|
||||||
Remark string `json:"remark"` // 转账备注
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析微信转账消息
|
|
||||||
func parseTransactionMessage(xmlData string) (*Message, error) {
|
|
||||||
var msg Message
|
|
||||||
if err := xml.Unmarshal([]byte(xmlData), &msg); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &msg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出交易信息
|
|
||||||
func extractTransaction(message *Message) Transaction {
|
|
||||||
var tx = Transaction{}
|
|
||||||
// 导出交易金额和备注
|
|
||||||
lines := strings.Split(message.AppMsg.Des, "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if len(line) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 解析收款金额
|
|
||||||
prefix := "收款金额¥"
|
|
||||||
if strings.HasPrefix(line, prefix) {
|
|
||||||
if value, err := strconv.ParseFloat(line[len(prefix):], 64); err == nil {
|
|
||||||
tx.Amount = value
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 解析收款备注
|
|
||||||
prefix = "付款方备注"
|
|
||||||
if strings.HasPrefix(line, prefix) {
|
|
||||||
tx.Remark = line[len(prefix):]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析交易 ID
|
|
||||||
index := strings.Index(message.AppMsg.Url, "trans_id=")
|
|
||||||
if index != -1 {
|
|
||||||
end := strings.LastIndex(message.AppMsg.Url, "&")
|
|
||||||
tx.TransId = strings.TrimSpace(message.AppMsg.Url[index+9 : end])
|
|
||||||
}
|
|
||||||
return tx
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package wexin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"chatplus/core/types"
|
|
||||||
logger2 "chatplus/logger"
|
|
||||||
"github.com/eatmoreapple/openwechat"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 微信收款机器人服务
|
|
||||||
var logger = logger2.GetLogger()
|
|
||||||
|
|
||||||
type WeChatBot struct {
|
|
||||||
bot *openwechat.Bot
|
|
||||||
db *gorm.DB
|
|
||||||
appConfig *types.AppConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWeChatBot(db *gorm.DB, config *types.AppConfig) *WeChatBot {
|
|
||||||
bot := openwechat.DefaultBot(openwechat.Desktop)
|
|
||||||
// 注册消息处理函数
|
|
||||||
bot.MessageHandler = func(msg *openwechat.Message) {
|
|
||||||
MessageHandler(msg, db)
|
|
||||||
}
|
|
||||||
// 注册登陆二维码回调
|
|
||||||
bot.UUIDCallback = QrCodeCallBack
|
|
||||||
return &WeChatBot{
|
|
||||||
bot: bot,
|
|
||||||
db: db,
|
|
||||||
appConfig: config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *WeChatBot) Login() error {
|
|
||||||
if !b.appConfig.StartWechatBot {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建热存储容器对象
|
|
||||||
reloadStorage := openwechat.NewJsonFileHotReloadStorage("storage.json")
|
|
||||||
// 执行热登录
|
|
||||||
err := b.bot.HotLogin(reloadStorage)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("login error: %v", err)
|
|
||||||
return b.bot.Login()
|
|
||||||
}
|
|
||||||
logger.Info("微信登录成功!")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
package function
|
package function
|
||||||
|
|
||||||
import "chatplus/core/types"
|
import (
|
||||||
|
"chatplus/core/types"
|
||||||
|
logger2 "chatplus/logger"
|
||||||
|
)
|
||||||
|
|
||||||
type Function interface {
|
type Function interface {
|
||||||
Invoke(...interface{}) (string, error)
|
Invoke(map[string]interface{}) (string, error)
|
||||||
Name() string
|
Name() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var logger = logger2.GetLogger()
|
||||||
|
|
||||||
type resVo struct {
|
type resVo struct {
|
||||||
Code types.BizCode `json:"code"`
|
Code types.BizCode `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
@@ -22,3 +27,12 @@ type dataItem struct {
|
|||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFunctions(config *types.AppConfig) map[string]Function {
|
||||||
|
return map[string]Function{
|
||||||
|
types.FuncZaoBao: NewZaoBao(config.ApiConfig),
|
||||||
|
types.FuncWeibo: NewWeiboHot(config.ApiConfig),
|
||||||
|
types.FuncHeadLine: NewHeadLines(config.ApiConfig),
|
||||||
|
types.FuncMidJourney: NewMidJourneyFunc(config.ExtConfig),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
117
api/service/function/mid_journey.go
Normal file
117
api/service/function/mid_journey.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package function
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/core/types"
|
||||||
|
"chatplus/utils"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/imroc/req/v3"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AI 绘画函数
|
||||||
|
|
||||||
|
type FuncMidJourney struct {
|
||||||
|
name string
|
||||||
|
config types.ChatPlusExtConfig
|
||||||
|
client *req.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMidJourneyFunc(config types.ChatPlusExtConfig) FuncMidJourney {
|
||||||
|
return FuncMidJourney{
|
||||||
|
name: "MidJourney AI 绘画",
|
||||||
|
config: config,
|
||||||
|
client: req.C().SetTimeout(30 * time.Second)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FuncMidJourney) Invoke(params map[string]interface{}) (string, error) {
|
||||||
|
if f.config.Token == "" {
|
||||||
|
return "", errors.New("无效的 API Token")
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("MJ 绘画参数:%+v", params)
|
||||||
|
prompt := utils.InterfaceToString(params["prompt"])
|
||||||
|
if !utils.IsEmptyValue(params["ar"]) {
|
||||||
|
prompt = fmt.Sprintf("%s --ar %s", prompt, params["ar"])
|
||||||
|
delete(params, "--ar")
|
||||||
|
}
|
||||||
|
if !utils.IsEmptyValue(params["niji"]) {
|
||||||
|
prompt = fmt.Sprintf("%s --niji %s", prompt, params["niji"])
|
||||||
|
delete(params, "niji")
|
||||||
|
} else {
|
||||||
|
prompt = prompt + " --v 5.2"
|
||||||
|
}
|
||||||
|
params["prompt"] = prompt
|
||||||
|
url := fmt.Sprintf("%s/api/mj/image", f.config.ApiURL)
|
||||||
|
var res types.BizVo
|
||||||
|
r, err := f.client.R().
|
||||||
|
SetHeader("Authorization", f.config.Token).
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
SetBody(params).
|
||||||
|
SetSuccessResult(&res).Post(url)
|
||||||
|
if err != nil || r.IsErrorState() {
|
||||||
|
return "", fmt.Errorf("%v%v", r.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Code != types.Success {
|
||||||
|
return "", errors.New(res.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MjUpscaleReq struct {
|
||||||
|
Index int32 `json:"index"`
|
||||||
|
MessageId string `json:"message_id"`
|
||||||
|
MessageHash string `json:"message_hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FuncMidJourney) Upscale(upReq MjUpscaleReq) error {
|
||||||
|
url := fmt.Sprintf("%s/api/mj/upscale", f.config.ApiURL)
|
||||||
|
var res types.BizVo
|
||||||
|
r, err := f.client.R().
|
||||||
|
SetHeader("Authorization", f.config.Token).
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
SetBody(upReq).
|
||||||
|
SetSuccessResult(&res).Post(url)
|
||||||
|
if err != nil || r.IsErrorState() {
|
||||||
|
return fmt.Errorf("%v%v", r.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Code != types.Success {
|
||||||
|
return errors.New(res.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type MjVariationReq struct {
|
||||||
|
Index int32 `json:"index"`
|
||||||
|
MessageId string `json:"message_id"`
|
||||||
|
MessageHash string `json:"message_hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FuncMidJourney) Variation(upReq MjVariationReq) error {
|
||||||
|
url := fmt.Sprintf("%s/api/mj/variation", f.config.ApiURL)
|
||||||
|
var res types.BizVo
|
||||||
|
r, err := f.client.R().
|
||||||
|
SetHeader("Authorization", f.config.Token).
|
||||||
|
SetHeader("Content-Type", "application/json").
|
||||||
|
SetBody(upReq).
|
||||||
|
SetSuccessResult(&res).Post(url)
|
||||||
|
if err != nil || r.IsErrorState() {
|
||||||
|
return fmt.Errorf("%v%v", r.String(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Code != types.Success {
|
||||||
|
return errors.New(res.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FuncMidJourney) Name() string {
|
||||||
|
return f.name
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Function = &FuncMidJourney{}
|
||||||
@@ -24,7 +24,7 @@ func NewHeadLines(config types.ChatPlusApiConfig) FuncHeadlines {
|
|||||||
client: req.C().SetTimeout(10 * time.Second)}
|
client: req.C().SetTimeout(10 * time.Second)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FuncHeadlines) Invoke(...interface{}) (string, error) {
|
func (f FuncHeadlines) Invoke(map[string]interface{}) (string, error) {
|
||||||
if f.config.Token == "" {
|
if f.config.Token == "" {
|
||||||
return "", errors.New("无效的 API Token")
|
return "", errors.New("无效的 API Token")
|
||||||
}
|
}
|
||||||
@@ -35,11 +35,8 @@ func (f FuncHeadlines) Invoke(...interface{}) (string, error) {
|
|||||||
SetHeader("AppId", f.config.AppId).
|
SetHeader("AppId", f.config.AppId).
|
||||||
SetHeader("Authorization", fmt.Sprintf("Bearer %s", f.config.Token)).
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", f.config.Token)).
|
||||||
SetSuccessResult(&res).Get(url)
|
SetSuccessResult(&res).Get(url)
|
||||||
if err != nil {
|
if err != nil || r.IsErrorState() {
|
||||||
return "", err
|
return "", fmt.Errorf("%v%v", err, r.Err)
|
||||||
}
|
|
||||||
if r.IsErrorState() {
|
|
||||||
return "", r.Err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.Code != types.Success {
|
if res.Code != types.Success {
|
||||||
@@ -57,3 +54,5 @@ func (f FuncHeadlines) Invoke(...interface{}) (string, error) {
|
|||||||
func (f FuncHeadlines) Name() string {
|
func (f FuncHeadlines) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Function = &FuncHeadlines{}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func NewWeiboHot(config types.ChatPlusApiConfig) FuncWeiboHot {
|
|||||||
client: req.C().SetTimeout(10 * time.Second)}
|
client: req.C().SetTimeout(10 * time.Second)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FuncWeiboHot) Invoke(...interface{}) (string, error) {
|
func (f FuncWeiboHot) Invoke(map[string]interface{}) (string, error) {
|
||||||
if f.config.Token == "" {
|
if f.config.Token == "" {
|
||||||
return "", errors.New("无效的 API Token")
|
return "", errors.New("无效的 API Token")
|
||||||
}
|
}
|
||||||
@@ -35,11 +35,8 @@ func (f FuncWeiboHot) Invoke(...interface{}) (string, error) {
|
|||||||
SetHeader("AppId", f.config.AppId).
|
SetHeader("AppId", f.config.AppId).
|
||||||
SetHeader("Authorization", fmt.Sprintf("Bearer %s", f.config.Token)).
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", f.config.Token)).
|
||||||
SetSuccessResult(&res).Get(url)
|
SetSuccessResult(&res).Get(url)
|
||||||
if err != nil {
|
if err != nil || r.IsErrorState() {
|
||||||
return "", err
|
return "", fmt.Errorf("%v%v", err, r.Err)
|
||||||
}
|
|
||||||
if r.IsErrorState() {
|
|
||||||
return "", r.Err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.Code != types.Success {
|
if res.Code != types.Success {
|
||||||
@@ -57,3 +54,5 @@ func (f FuncWeiboHot) Invoke(...interface{}) (string, error) {
|
|||||||
func (f FuncWeiboHot) Name() string {
|
func (f FuncWeiboHot) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Function = &FuncWeiboHot{}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func NewZaoBao(config types.ChatPlusApiConfig) FuncZaoBao {
|
|||||||
client: req.C().SetTimeout(10 * time.Second)}
|
client: req.C().SetTimeout(10 * time.Second)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FuncZaoBao) Invoke(...interface{}) (string, error) {
|
func (f FuncZaoBao) Invoke(map[string]interface{}) (string, error) {
|
||||||
if f.config.Token == "" {
|
if f.config.Token == "" {
|
||||||
return "", errors.New("无效的 API Token")
|
return "", errors.New("无效的 API Token")
|
||||||
}
|
}
|
||||||
@@ -35,11 +35,8 @@ func (f FuncZaoBao) Invoke(...interface{}) (string, error) {
|
|||||||
SetHeader("AppId", f.config.AppId).
|
SetHeader("AppId", f.config.AppId).
|
||||||
SetHeader("Authorization", fmt.Sprintf("Bearer %s", f.config.Token)).
|
SetHeader("Authorization", fmt.Sprintf("Bearer %s", f.config.Token)).
|
||||||
SetSuccessResult(&res).Get(url)
|
SetSuccessResult(&res).Get(url)
|
||||||
if err != nil {
|
if err != nil || r.IsErrorState() {
|
||||||
return "", err
|
return "", fmt.Errorf("%v%v", err, r.Err)
|
||||||
}
|
|
||||||
if r.IsErrorState() {
|
|
||||||
return "", r.Err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.Code != types.Success {
|
if res.Code != types.Success {
|
||||||
@@ -58,3 +55,5 @@ func (f FuncZaoBao) Invoke(...interface{}) (string, error) {
|
|||||||
func (f FuncZaoBao) Name() string {
|
func (f FuncZaoBao) Name() string {
|
||||||
return f.name
|
return f.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Function = &FuncZaoBao{}
|
||||||
|
|||||||
19
api/store/model/mj_job.go
Normal file
19
api/store/model/mj_job.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type MidJourneyJob struct {
|
||||||
|
Id uint `gorm:"primarykey;column:id"`
|
||||||
|
UserId uint
|
||||||
|
ChatId string
|
||||||
|
MessageId string
|
||||||
|
Hash string
|
||||||
|
Content string
|
||||||
|
Prompt string
|
||||||
|
Image string
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (MidJourneyJob) TableName() string {
|
||||||
|
return "chatgpt_mj_jobs"
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
fmt.Println(utils.RandString(32))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Http client 取消操作
|
// Http client 取消操作
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ func Ip2Region(searcher *xdb.Searcher, ip string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsEmptyValue(obj interface{}) bool {
|
func IsEmptyValue(obj interface{}) bool {
|
||||||
|
if obj == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
v := reflect.ValueOf(obj)
|
v := reflect.ValueOf(obj)
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case reflect.Ptr, reflect.Interface:
|
case reflect.Ptr, reflect.Interface:
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AesEncrypt 加密
|
// AesEncrypt 加密
|
||||||
@@ -68,3 +71,14 @@ func pkcs7UnPadding(data []byte) ([]byte, error) {
|
|||||||
unPadding := int(data[length-1])
|
unPadding := int(data[length-1])
|
||||||
return data[:(length - unPadding)], nil
|
return data[:(length - unPadding)], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Sha256(data string) string {
|
||||||
|
hash := sha256.New()
|
||||||
|
_, err := io.WriteString(hash, data)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
hashValue := hash.Sum(nil)
|
||||||
|
return fmt.Sprintf("%x", hashValue)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HttpGet(uri string, proxy string) ([]byte, error) {
|
|
||||||
var client *http.Client
|
|
||||||
if proxy == "" {
|
|
||||||
client = &http.Client{}
|
|
||||||
} else {
|
|
||||||
proxy, _ := url.Parse(proxy)
|
|
||||||
client = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: http.ProxyURL(proxy),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", uri, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return io.ReadAll(resp.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HttpPost(uri string, params map[string]interface{}, proxy string) ([]byte, error) {
|
|
||||||
data, err := json.Marshal(params)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var client *http.Client
|
|
||||||
if proxy == "" {
|
|
||||||
client = &http.Client{}
|
|
||||||
} else {
|
|
||||||
proxy, _ := url.Parse(proxy)
|
|
||||||
client = &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Proxy: http.ProxyURL(proxy),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", uri, bytes.NewBuffer(data))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
return io.ReadAll(resp.Body)
|
|
||||||
}
|
|
||||||
29
api/utils/websocket.go
Normal file
29
api/utils/websocket.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/core/types"
|
||||||
|
logger2 "chatplus/logger"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger = logger2.GetLogger()
|
||||||
|
|
||||||
|
// ReplyChunkMessage 回复客户片段端消息
|
||||||
|
func ReplyChunkMessage(client *types.WsClient, message types.WsMessage) {
|
||||||
|
msg, err := json.Marshal(message)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error for decoding json data: %v", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = client.Send(msg)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error for reply message: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplyMessage 回复客户端一条完整的消息
|
||||||
|
func ReplyMessage(ws *types.WsClient, message interface{}) {
|
||||||
|
ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
||||||
|
ReplyChunkMessage(ws, types.WsMessage{Type: types.WsMiddle, Content: message})
|
||||||
|
ReplyChunkMessage(ws, types.WsMessage{Type: types.WsEnd})
|
||||||
|
}
|
||||||
34
database/update-v3.0.7.sql
Normal file
34
database/update-v3.0.7.sql
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
CREATE TABLE `chatgpt_mj_jobs` (
|
||||||
|
`id` int NOT NULL,
|
||||||
|
`user_id` int NOT NULL COMMENT '用户 ID',
|
||||||
|
`chat_id` char(40) NOT NULL COMMENT '聊天会话 ID',
|
||||||
|
`message_id` char(40) NOT NULL COMMENT '消息 ID',
|
||||||
|
`hash` char(40) NOT NULL COMMENT '图片哈希',
|
||||||
|
`content` varchar(2000) NOT NULL COMMENT '消息内容',
|
||||||
|
`prompt` varchar(2000) NOT NULL COMMENT '会话提示词',
|
||||||
|
`image` text NOT NULL COMMENT '图片信息 json',
|
||||||
|
`created_at` datetime NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MidJourney 任务表';
|
||||||
|
|
||||||
|
--
|
||||||
|
-- 转储表的索引
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- 表的索引 `chatgpt_mj_jobs`
|
||||||
|
--
|
||||||
|
ALTER TABLE `chatgpt_mj_jobs`
|
||||||
|
ADD PRIMARY KEY (`id`),
|
||||||
|
ADD UNIQUE KEY `message_id` (`message_id`),
|
||||||
|
ADD UNIQUE KEY `hash` (`hash`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- 在导出的表使用AUTO_INCREMENT
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- 使用表AUTO_INCREMENT `chatgpt_mj_jobs`
|
||||||
|
--
|
||||||
|
ALTER TABLE `chatgpt_mj_jobs`
|
||||||
|
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||||
|
COMMIT;
|
||||||
@@ -20,3 +20,7 @@ docker build -t registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-go:$ve
|
|||||||
docker rmi -f registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-vue:$version
|
docker rmi -f registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-vue:$version
|
||||||
docker build --platform linux/amd64 -t registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-vue:$version -f dockerfile-vue ../
|
docker build --platform linux/amd64 -t registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-vue:$version -f dockerfile-vue ../
|
||||||
|
|
||||||
|
if [ "$2" = "push" ];then
|
||||||
|
docker push registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-vue:$version
|
||||||
|
docker push registry.cn-hangzhou.aliyuncs.com/geekmaster/chatgpt-plus-go:$version
|
||||||
|
fi
|
||||||
BIN
docs/imgs/admin_dashboard.png
Normal file
BIN
docs/imgs/admin_dashboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
BIN
web/public/images/avatar/mid_journey.png
Normal file
BIN
web/public/images/avatar/mid_journey.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4125778 */
|
font-family: "iconfont"; /* Project id 4125778 */
|
||||||
src: url('iconfont.woff2?t=1687341905766') format('woff2'),
|
src: url('iconfont.woff2?t=1691463643989') format('woff2'),
|
||||||
url('iconfont.woff?t=1687341905766') format('woff'),
|
url('iconfont.woff?t=1691463643989') format('woff'),
|
||||||
url('iconfont.ttf?t=1687341905766') format('truetype');
|
url('iconfont.ttf?t=1691463643989') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,26 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-plugin:before {
|
||||||
|
content: "\e69d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-quick-start:before {
|
||||||
|
content: "\e677";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-control:before {
|
||||||
|
content: "\e69e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-bug:before {
|
||||||
|
content: "\e645";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-export:before {
|
||||||
|
content: "\e791";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-sub-menu:before {
|
.icon-sub-menu:before {
|
||||||
content: "\e86b";
|
content: "\e86b";
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
4
web/src/assets/iconfont/iconfont.js:Zone.Identifier
Normal file
4
web/src/assets/iconfont/iconfont.js:Zone.Identifier
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[ZoneTransfer]
|
||||||
|
ZoneId=3
|
||||||
|
ReferrerUrl=https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=4125778
|
||||||
|
HostUrl=https://www.iconfont.cn/api/project/download.zip?spm=a313x.manage_type_myprojects.1998910419.d7543c303.3c973a816X8Dv0&pid=4125778&ctoken=jiQU41iUGSlzlFzLGolvuh03
|
||||||
@@ -5,6 +5,41 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "5244045",
|
||||||
|
"name": "插件",
|
||||||
|
"font_class": "plugin",
|
||||||
|
"unicode": "e69d",
|
||||||
|
"unicode_decimal": 59037
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "8893244",
|
||||||
|
"name": "高效率 copy",
|
||||||
|
"font_class": "quick-start",
|
||||||
|
"unicode": "e677",
|
||||||
|
"unicode_decimal": 58999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "16480872",
|
||||||
|
"name": "插件功能",
|
||||||
|
"font_class": "control",
|
||||||
|
"unicode": "e69e",
|
||||||
|
"unicode_decimal": 59038
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "22187612",
|
||||||
|
"name": "缺陷管理",
|
||||||
|
"font_class": "bug",
|
||||||
|
"unicode": "e645",
|
||||||
|
"unicode_decimal": 58949
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "4765958",
|
||||||
|
"name": "export",
|
||||||
|
"font_class": "export",
|
||||||
|
"unicode": "e791",
|
||||||
|
"unicode_decimal": 59281
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "6343824",
|
"icon_id": "6343824",
|
||||||
"name": "menu",
|
"name": "menu",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
236
web/src/components/ChatMidJourney.vue
Normal file
236
web/src/components/ChatMidJourney.vue
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chat-line chat-line-mj" v-loading="loading">
|
||||||
|
<div class="chat-line-inner">
|
||||||
|
<div class="chat-icon">
|
||||||
|
<img :src="icon" alt="User"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chat-item">
|
||||||
|
<div class="content">
|
||||||
|
<div class="text" v-html="data.content"></div>
|
||||||
|
<div class="images" v-if="data.image?.url !== ''">
|
||||||
|
<el-image :src="data.image?.url"
|
||||||
|
:zoom-rate="1.0"
|
||||||
|
:preview-src-list="[data.image?.url]"
|
||||||
|
:initial-index="0" lazy>
|
||||||
|
<template #placeholder>
|
||||||
|
<div class="image-slot"
|
||||||
|
:style="{height: height+'px', lineHeight:height+'px'}">
|
||||||
|
正在加载图片<span class="dot">...</span></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #error>
|
||||||
|
<div class="image-slot">
|
||||||
|
<el-icon>
|
||||||
|
<icon-picture/>
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="opt" v-if="!data['reference_id'] &&data.image?.hash !== ''">
|
||||||
|
<div class="opt-line">
|
||||||
|
<ul>
|
||||||
|
<li><a @click="upscale(1)">U1</a></li>
|
||||||
|
<li><a @click="upscale(2)">U2</a></li>
|
||||||
|
<li><a @click="upscale(3)">U3</a></li>
|
||||||
|
<li><a @click="upscale(4)">U4</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="opt-line">
|
||||||
|
<ul>
|
||||||
|
<li><a @click="variation(1)">V1</a></li>
|
||||||
|
<li><a @click="variation(2)">V2</a></li>
|
||||||
|
<li><a @click="variation(3)">V3</a></li>
|
||||||
|
<li><a @click="variation(4)">V4</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bar" v-if="createdAt !== ''">
|
||||||
|
<span class="bar-item"><el-icon><Clock/></el-icon> {{ createdAt }}</span>
|
||||||
|
<span class="bar-item">tokens: {{ tokens }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, watch} from "vue";
|
||||||
|
import {Clock} from "@element-plus/icons-vue";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
import {httpPost} from "@/utils/http";
|
||||||
|
import {getSessionId} from "@/store/session";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
content: Object,
|
||||||
|
icon: String,
|
||||||
|
createdAt: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = ref(props.content)
|
||||||
|
const tokens = ref(0)
|
||||||
|
const cacheKey = "img_placeholder_height"
|
||||||
|
const item = localStorage.getItem(cacheKey);
|
||||||
|
const loading = ref(false)
|
||||||
|
const height = ref(0)
|
||||||
|
if (item) {
|
||||||
|
height.value = parseInt(item)
|
||||||
|
}
|
||||||
|
if (data.value["image"]?.width > 0) {
|
||||||
|
height.value = 350 * data.value["image"]?.height / data.value["image"]?.width
|
||||||
|
localStorage.setItem(cacheKey, height.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.content, (newVal) => {
|
||||||
|
data.value = newVal;
|
||||||
|
});
|
||||||
|
const emits = defineEmits(['disable-input', 'disable-input']);
|
||||||
|
const upscale = (index) => {
|
||||||
|
send('/api/mj/upscale', index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const variation = (index) => {
|
||||||
|
send('/api/mj/variation', index)
|
||||||
|
}
|
||||||
|
|
||||||
|
const send = (url, index) => {
|
||||||
|
loading.value = true
|
||||||
|
emits('disable-input')
|
||||||
|
httpPost(url, {
|
||||||
|
index: index,
|
||||||
|
message_id: data.value?.["message_id"],
|
||||||
|
message_hash: data.value?.["image"]?.hash,
|
||||||
|
session_id: getSessionId(),
|
||||||
|
key: data.value?.["key"],
|
||||||
|
prompt: data.value?.["prompt"],
|
||||||
|
}).then(() => {
|
||||||
|
ElMessage.success("任务推送成功,请耐心等待任务执行...")
|
||||||
|
loading.value = false
|
||||||
|
}).catch(e => {
|
||||||
|
ElMessage.error("任务推送失败:" + e.message)
|
||||||
|
emits('disable-input')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus">
|
||||||
|
.chat-line-mj {
|
||||||
|
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-icon {
|
||||||
|
margin-right 20px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item {
|
||||||
|
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;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
p:first-child {
|
||||||
|
margin-top 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.images {
|
||||||
|
max-width 350px;
|
||||||
|
|
||||||
|
.el-image {
|
||||||
|
border-radius 10px;
|
||||||
|
|
||||||
|
.image-slot {
|
||||||
|
color #c1c1c1
|
||||||
|
width 350px
|
||||||
|
text-align center
|
||||||
|
border-radius 10px;
|
||||||
|
border 1px solid #e1e1e1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.opt {
|
||||||
|
.opt-line {
|
||||||
|
margin 6px 0
|
||||||
|
|
||||||
|
ul {
|
||||||
|
display flex
|
||||||
|
flex-flow row
|
||||||
|
padding-left 10px
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-right 10px
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding 6px 0
|
||||||
|
width 64px
|
||||||
|
text-align center
|
||||||
|
border-radius 5px
|
||||||
|
display block
|
||||||
|
cursor pointer
|
||||||
|
background-color #4E5058
|
||||||
|
color #ffffff
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color #6D6F78
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
padding 10px;
|
||||||
|
|
||||||
|
.bar-item {
|
||||||
|
background-color #f7f7f8;
|
||||||
|
color #888
|
||||||
|
padding 3px 5px;
|
||||||
|
margin-right 10px;
|
||||||
|
border-radius 5px;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
position relative
|
||||||
|
top 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -64,7 +64,7 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus">
|
||||||
.chat-line-prompt {
|
.chat-line-prompt {
|
||||||
background-color #ffffff;
|
background-color #ffffff;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
<el-form-item label="聊天记录">
|
<el-form-item label="聊天记录">
|
||||||
<el-switch v-model="form.chat_config.enable_history"/>
|
<el-switch v-model="form.chat_config.enable_history"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Model">
|
<el-form-item label="默认模型">
|
||||||
<el-select v-model="form.chat_config.model" placeholder="默认会话模型">
|
<el-select v-model="form.chat_config.model" placeholder="默认会话模型">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in models"
|
v-for="item in models"
|
||||||
@@ -51,8 +51,9 @@
|
|||||||
<el-form-item label="MaxTokens">
|
<el-form-item label="MaxTokens">
|
||||||
<el-input v-model.number="form.chat_config.max_tokens"/>
|
<el-input v-model.number="form.chat_config.max_tokens"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="Temperature">
|
<el-form-item label="创意度">
|
||||||
<el-input v-model.number="form.chat_config.temperature"/>
|
<el-slider v-model="form.chat_config.temperature" :max="2" :step="0.1"/>
|
||||||
|
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="剩余调用次数">
|
<el-form-item label="剩余调用次数">
|
||||||
<el-tag>{{ form['calls'] }}</el-tag>
|
<el-tag>{{ form['calls'] }}</el-tag>
|
||||||
@@ -80,7 +81,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
|
|
||||||
import {computed, onMounted, ref} from "vue"
|
import {computed, onMounted, reactive, ref} from "vue"
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {Plus} from "@element-plus/icons-vue";
|
import {Plus} from "@element-plus/icons-vue";
|
||||||
@@ -174,6 +175,11 @@ const close = function () {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tip {
|
||||||
|
color #c1c1c1
|
||||||
|
font-size 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
143
web/src/components/Welcome.vue
Normal file
143
web/src/components/Welcome.vue
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<template>
|
||||||
|
<div class="welcome">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">ChatGPT-PLUS</h1>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8">
|
||||||
|
<div class="grid-content">
|
||||||
|
<div class="item-title">
|
||||||
|
<div><i class="iconfont icon-quick-start"></i></div>
|
||||||
|
<div>小试牛刀</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-list">
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in samples"><a @click="send(item)">{{ item }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<div class="grid-content">
|
||||||
|
<div class="item-title">
|
||||||
|
<div><i class="iconfont icon-plugin"></i></div>
|
||||||
|
<div>插件增强</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-list">
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in plugins"><a @click="send(item.value)">{{ item.text }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<div class="grid-content">
|
||||||
|
<div class="item-title">
|
||||||
|
<div><i class="iconfont icon-control"></i></div>
|
||||||
|
<div>能力扩展</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-list">
|
||||||
|
<ul>
|
||||||
|
<li v-for="item in capabilities">{{ item }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
|
const samples = ref([
|
||||||
|
"用小学生都能听懂的术语解释什么是量子纠缠",
|
||||||
|
"能给一位6岁男孩的生日会提供一些创造性的建议吗?",
|
||||||
|
"如何用 Go 语言实现支持代理 Http client 请求?"
|
||||||
|
])
|
||||||
|
|
||||||
|
const plugins = ref([
|
||||||
|
{
|
||||||
|
value: "今日早报",
|
||||||
|
text: "今日早报:获取当天全球的热门新闻事件列表"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "微博热搜",
|
||||||
|
text: "微博热搜:新浪微博热搜榜,微博当日热搜榜单"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "今日头条",
|
||||||
|
text: "今日头条:给用户推荐当天的头条新闻,周榜热文"
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const capabilities = ref([
|
||||||
|
"轻松扮演翻译专家,程序员,AI 女友,文案高手...",
|
||||||
|
"国产大语言模型支持,GLM2 模型接入中",
|
||||||
|
"Midjourney, Stable Diffusion AI 绘画支持"
|
||||||
|
])
|
||||||
|
|
||||||
|
const emits = defineEmits(['send']);
|
||||||
|
const send = (text) => {
|
||||||
|
emits('send', text)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="stylus">
|
||||||
|
.welcome {
|
||||||
|
text-align center
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
margin-top 8vh
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width 768px;
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.25rem
|
||||||
|
line-height: 2.5rem
|
||||||
|
font-weight 600
|
||||||
|
margin-bottom: 4rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-content {
|
||||||
|
.item-title {
|
||||||
|
div {
|
||||||
|
padding 6px 10px;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-list {
|
||||||
|
ul {
|
||||||
|
padding 10px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
font-size 14px;
|
||||||
|
padding .75rem
|
||||||
|
border-radius 5px;
|
||||||
|
background-color: rgba(247, 247, 248, 1);
|
||||||
|
|
||||||
|
line-height 1.5
|
||||||
|
color #666666
|
||||||
|
|
||||||
|
a {
|
||||||
|
cursor pointer
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
}
|
||||||
|
margin-top 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -26,6 +26,12 @@ const routes = [
|
|||||||
meta: {title: 'ChatGPT-智能助手V3'},
|
meta: {title: 'ChatGPT-智能助手V3'},
|
||||||
component: () => import('@/views/ChatPlus.vue'),
|
component: () => import('@/views/ChatPlus.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'chat-export',
|
||||||
|
path: '/chat/export',
|
||||||
|
meta: {title: '导出会话记录'},
|
||||||
|
component: () => import('@/views/ChatExport.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/login',
|
path: '/admin/login',
|
||||||
name: 'admin-login',
|
name: 'admin-login',
|
||||||
|
|||||||
@@ -4,26 +4,16 @@
|
|||||||
* storage handler
|
* storage handler
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const SessionUserKey = 'LOGIN_USER';
|
const SessionUserKey = 'SESSION_ID';
|
||||||
|
|
||||||
export function getSessionId() {
|
export function getSessionId() {
|
||||||
const user = getLoginUser();
|
return sessionStorage.getItem(SessionUserKey)
|
||||||
return user ? user['session_id'] : '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeLoginUser() {
|
export function removeLoginUser() {
|
||||||
sessionStorage.removeItem(SessionUserKey)
|
sessionStorage.removeItem(SessionUserKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLoginUser() {
|
export function setSessionId(sessionId) {
|
||||||
const value = sessionStorage.getItem(SessionUserKey);
|
sessionStorage.setItem(SessionUserKey, sessionId)
|
||||||
if (value) {
|
|
||||||
return JSON.parse(value);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setLoginUser(user) {
|
|
||||||
sessionStorage.setItem(SessionUserKey, JSON.stringify(user))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import {getSessionId} from "@/store/session";
|
|
||||||
|
|
||||||
axios.defaults.timeout = 10000
|
axios.defaults.timeout = 10000
|
||||||
axios.defaults.baseURL = process.env.VUE_APP_API_HOST
|
axios.defaults.baseURL = process.env.VUE_APP_API_HOST
|
||||||
@@ -10,7 +9,7 @@ axios.defaults.headers.post['Content-Type'] = 'application/json'
|
|||||||
axios.interceptors.request.use(
|
axios.interceptors.request.use(
|
||||||
config => {
|
config => {
|
||||||
// set token
|
// set token
|
||||||
config.headers['ChatGPT-TOKEN'] = getSessionId();
|
// config.headers['ChatGPT-TOKEN'] = getSessionId();
|
||||||
return config
|
return config
|
||||||
}, error => {
|
}, error => {
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
|
|||||||
167
web/src/views/ChatExport.vue
Normal file
167
web/src/views/ChatExport.vue
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chat-export" v-loading="loading">
|
||||||
|
<div class="chat-box" id="chat-box">
|
||||||
|
<div class="title">
|
||||||
|
<h2>{{ chatTitle }}</h2>
|
||||||
|
<el-button type="success" @click="exportChat" :icon="Promotion">
|
||||||
|
导出 PDF 文档
|
||||||
|
</el-button>
|
||||||
|
</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="model"
|
||||||
|
:content="item.content"/>
|
||||||
|
<chat-reply v-else-if="item.type==='reply'"
|
||||||
|
:icon="item.icon"
|
||||||
|
:org-content="item.orgContent"
|
||||||
|
:created-at="dateFormat(item['created_at'])"
|
||||||
|
:tokens="item['tokens']"
|
||||||
|
:content="item.content"/>
|
||||||
|
<chat-mid-journey v-else-if="item.type==='mj'"
|
||||||
|
:content="item.content"
|
||||||
|
:icon="item.icon"
|
||||||
|
:created-at="dateFormat(item['created_at'])"/>
|
||||||
|
</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, ref} from "vue";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
import {httpGet} from "@/utils/http";
|
||||||
|
import 'highlight.js/styles/a11y-dark.css'
|
||||||
|
import hl from "highlight.js";
|
||||||
|
import {ElMessage} from "element-plus";
|
||||||
|
import {Promotion} from "@element-plus/icons-vue";
|
||||||
|
import ChatMidJourney from "@/components/ChatMidJourney.vue";
|
||||||
|
|
||||||
|
const chatData = ref([])
|
||||||
|
const router = useRouter()
|
||||||
|
const chatId = router.currentRoute.value.query['chat_id']
|
||||||
|
const loading = ref(true)
|
||||||
|
const chatTitle = ref('')
|
||||||
|
|
||||||
|
httpGet('/api/chat/history?chat_id=' + chatId).then(res => {
|
||||||
|
const data = res.data
|
||||||
|
if (!data) {
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const md = require('markdown-it')({breaks: true});
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
if (data[i].type === "prompt") {
|
||||||
|
chatData.value.push(data[i]);
|
||||||
|
continue;
|
||||||
|
} else if (data[i].type === "mj") {
|
||||||
|
data[i].content = JSON.parse(data[i].content)
|
||||||
|
data[i].content.content = md.render(data[i].content?.content)
|
||||||
|
chatData.value.push(data[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[i].orgContent = data[i].content;
|
||||||
|
data[i].content = md.render(data[i].content);
|
||||||
|
chatData.value.push(data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
hl.configure({ignoreUnescapedHTML: true})
|
||||||
|
const blocks = document.querySelector("#chat-box").querySelectorAll('pre code');
|
||||||
|
blocks.forEach((block) => {
|
||||||
|
hl.highlightElement(block)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
loading.value = false
|
||||||
|
}).catch(e => {
|
||||||
|
ElMessage.error('加载聊天记录失败:' + e.message);
|
||||||
|
})
|
||||||
|
|
||||||
|
httpGet('/api/chat/detail?chat_id=' + chatId).then(res => {
|
||||||
|
chatTitle.value = res.data.title
|
||||||
|
}).catch(e => {
|
||||||
|
ElMessage.error("加载会失败: " + e.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
const exportChat = () => {
|
||||||
|
window.print()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="stylus">
|
||||||
|
.chat-export {
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
|
||||||
|
.chat-box {
|
||||||
|
max-width 800px;
|
||||||
|
// 变量定义
|
||||||
|
--content-font-size: 16px;
|
||||||
|
--content-color: #c1c1c1;
|
||||||
|
|
||||||
|
font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||||
|
padding: 0 0 50px 0;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align center
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.chat-line {
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.chat-line-inner {
|
||||||
|
.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
|
||||||
|
|
||||||
|
.copy-reply {
|
||||||
|
display none
|
||||||
|
}
|
||||||
|
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -138,6 +138,10 @@
|
|||||||
新建会话
|
新建会话
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
|
<el-button type="success" @click="exportChat" plain>
|
||||||
|
<i class="iconfont icon-export"></i>
|
||||||
|
<span>导出会话</span>
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -145,7 +149,10 @@
|
|||||||
<div>
|
<div>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div class="chat-box" id="chat-box" :style="{height: chatBoxHeight+'px'}">
|
<div class="chat-box" id="chat-box" :style="{height: chatBoxHeight+'px'}">
|
||||||
<div v-for="item in chatData" :key="item.id">
|
<div v-if="showHello">
|
||||||
|
<welcome @send="autofillPrompt"/>
|
||||||
|
</div>
|
||||||
|
<div v-for="item in chatData" :key="item.id" v-else>
|
||||||
<chat-prompt
|
<chat-prompt
|
||||||
v-if="item.type==='prompt'"
|
v-if="item.type==='prompt'"
|
||||||
:icon="item.icon"
|
:icon="item.icon"
|
||||||
@@ -159,6 +166,12 @@
|
|||||||
:created-at="dateFormat(item['created_at'])"
|
:created-at="dateFormat(item['created_at'])"
|
||||||
:tokens="item['tokens']"
|
:tokens="item['tokens']"
|
||||||
:content="item.content"/>
|
:content="item.content"/>
|
||||||
|
<chat-mid-journey v-else-if="item.type==='mj'"
|
||||||
|
:content="item.content"
|
||||||
|
:icon="item.icon"
|
||||||
|
@disable-input="disableInput(true)"
|
||||||
|
@enable-input="enableInput"
|
||||||
|
:created-at="dateFormat(item['created_at'])"/>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- end chat box -->
|
</div><!-- end chat box -->
|
||||||
|
|
||||||
@@ -183,7 +196,7 @@
|
|||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<el-input
|
<el-input
|
||||||
ref="text-input"
|
ref="textInput"
|
||||||
v-model="prompt"
|
v-model="prompt"
|
||||||
v-on:keydown="inputKeyDown"
|
v-on:keydown="inputKeyDown"
|
||||||
autofocus
|
autofocus
|
||||||
@@ -269,6 +282,8 @@ import PasswordDialog from "@/components/PasswordDialog.vue";
|
|||||||
import {checkSession} from "@/action/session";
|
import {checkSession} from "@/action/session";
|
||||||
import BindMobile from "@/components/BindMobile.vue";
|
import BindMobile from "@/components/BindMobile.vue";
|
||||||
import RewardVerify from "@/components/RewardVerify.vue";
|
import RewardVerify from "@/components/RewardVerify.vue";
|
||||||
|
import Welcome from "@/components/Welcome.vue";
|
||||||
|
import ChatMidJourney from "@/components/ChatMidJourney.vue";
|
||||||
|
|
||||||
const title = ref('ChatGPT-智能助手');
|
const title = ref('ChatGPT-智能助手');
|
||||||
const logo = 'images/logo.png';
|
const logo = 'images/logo.png';
|
||||||
@@ -294,6 +309,8 @@ const showBindMobileDialog = ref(false);
|
|||||||
const showRewardDialog = ref(false);
|
const showRewardDialog = ref(false);
|
||||||
const showRewardVerifyDialog = ref(false);
|
const showRewardVerifyDialog = ref(false);
|
||||||
const isLogin = ref(false)
|
const isLogin = ref(false)
|
||||||
|
const showHello = ref(true)
|
||||||
|
const textInput = ref(null)
|
||||||
|
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
router.replace("/mobile")
|
router.replace("/mobile")
|
||||||
@@ -304,6 +321,9 @@ onMounted(() => {
|
|||||||
checkSession().then((user) => {
|
checkSession().then((user) => {
|
||||||
loginUser.value = user
|
loginUser.value = user
|
||||||
isLogin.value = true
|
isLogin.value = true
|
||||||
|
if (user.chat_config?.model !== '') {
|
||||||
|
model.value = user.chat_config.model
|
||||||
|
}
|
||||||
// 加载角色列表
|
// 加载角色列表
|
||||||
httpGet(`/api/role/list?user_id=${user.id}`).then((res) => {
|
httpGet(`/api/role/list?user_id=${user.id}`).then((res) => {
|
||||||
roles.value = res.data;
|
roles.value = res.data;
|
||||||
@@ -499,7 +519,7 @@ const connect = function (chat_id, role_id) {
|
|||||||
_socket.addEventListener('open', () => {
|
_socket.addEventListener('open', () => {
|
||||||
chatData.value = []; // 初始化聊天数据
|
chatData.value = []; // 初始化聊天数据
|
||||||
previousText.value = '';
|
previousText.value = '';
|
||||||
canSend.value = true;
|
enableInput()
|
||||||
activelyClose.value = false;
|
activelyClose.value = false;
|
||||||
|
|
||||||
if (isNewChat) { // 加载打招呼信息
|
if (isNewChat) { // 加载打招呼信息
|
||||||
@@ -529,12 +549,39 @@ const connect = function (chat_id, role_id) {
|
|||||||
icon: _role['icon'],
|
icon: _role['icon'],
|
||||||
content: ""
|
content: ""
|
||||||
});
|
});
|
||||||
} else if (data.type === 'end') { // 消息接收完毕
|
} else if (data.type === "mj") {
|
||||||
canSend.value = true;
|
disableInput(true)
|
||||||
showReGenerate.value = true;
|
const content = data.content;
|
||||||
showStopGenerate.value = false;
|
const md = require('markdown-it')({breaks: true});
|
||||||
lineBuffer.value = ''; // 清空缓冲
|
content.content = md.render(content.content)
|
||||||
|
let key = content.key
|
||||||
|
// fixed bug: 执行 Upscale 和 Variation 操作的时候覆盖之前的绘画
|
||||||
|
if (content.status === "Finished") {
|
||||||
|
key = randString(32)
|
||||||
|
enableInput()
|
||||||
|
}
|
||||||
|
// console.log(content)
|
||||||
|
// check if the message is in chatData
|
||||||
|
let flag = false
|
||||||
|
for (let i = 0; i < chatData.value.length; i++) {
|
||||||
|
if (chatData.value[i].id === content.key) {
|
||||||
|
console.log(chatData.value[i])
|
||||||
|
flag = true
|
||||||
|
chatData.value[i].content = content
|
||||||
|
chatData.value[i].id = key
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flag === false) {
|
||||||
|
chatData.value.push({
|
||||||
|
type: "mj",
|
||||||
|
id: key,
|
||||||
|
icon: "/images/avatar/mid_journey.png",
|
||||||
|
content: content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (data.type === 'end') { // 消息接收完毕
|
||||||
// 追加当前会话到会话列表
|
// 追加当前会话到会话列表
|
||||||
if (isNewChat && newChatItem.value !== null) {
|
if (isNewChat && newChatItem.value !== null) {
|
||||||
newChatItem.value['title'] = previousText.value;
|
newChatItem.value['title'] = previousText.value;
|
||||||
@@ -544,6 +591,9 @@ const connect = function (chat_id, role_id) {
|
|||||||
newChatItem.value = null; // 只追加一次
|
newChatItem.value = null; // 只追加一次
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableInput()
|
||||||
|
lineBuffer.value = ''; // 清空缓冲
|
||||||
|
|
||||||
// 获取 token
|
// 获取 token
|
||||||
const reply = chatData.value[chatData.value.length - 1]
|
const reply = chatData.value[chatData.value.length - 1]
|
||||||
httpGet(`/api/chat/tokens?text=${reply.orgContent}&model=${model.value}`).then(res => {
|
httpGet(`/api/chat/tokens?text=${reply.orgContent}&model=${model.value}`).then(res => {
|
||||||
@@ -585,7 +635,7 @@ const connect = function (chat_id, role_id) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 停止发送消息
|
// 停止发送消息
|
||||||
canSend.value = true;
|
disableInput(true)
|
||||||
socket.value = null;
|
socket.value = null;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
checkSession().then(() => {
|
checkSession().then(() => {
|
||||||
@@ -603,6 +653,18 @@ const connect = function (chat_id, role_id) {
|
|||||||
socket.value = _socket;
|
socket.value = _socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const disableInput = (force) => {
|
||||||
|
canSend.value = false;
|
||||||
|
showReGenerate.value = false;
|
||||||
|
showStopGenerate.value = !force;
|
||||||
|
}
|
||||||
|
|
||||||
|
const enableInput = () => {
|
||||||
|
canSend.value = true;
|
||||||
|
showReGenerate.value = previousText.value !== "";
|
||||||
|
showStopGenerate.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
// 登录输入框输入事件处理
|
// 登录输入框输入事件处理
|
||||||
const inputKeyDown = function (e) {
|
const inputKeyDown = function (e) {
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
@@ -614,6 +676,13 @@ const inputKeyDown = function (e) {
|
|||||||
sendMessage();
|
sendMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自动填充 prompt
|
||||||
|
const autofillPrompt = (text) => {
|
||||||
|
prompt.value = text
|
||||||
|
textInput.value.focus()
|
||||||
|
// sendMessage()
|
||||||
|
}
|
||||||
// 发送消息
|
// 发送消息
|
||||||
const sendMessage = function () {
|
const sendMessage = function () {
|
||||||
if (canSend.value === false) {
|
if (canSend.value === false) {
|
||||||
@@ -638,9 +707,8 @@ const sendMessage = function () {
|
|||||||
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
||||||
})
|
})
|
||||||
|
|
||||||
canSend.value = false;
|
showHello.value = false
|
||||||
showStopGenerate.value = true;
|
disableInput(false)
|
||||||
showReGenerate.value = false;
|
|
||||||
socket.value.send(prompt.value);
|
socket.value.send(prompt.value);
|
||||||
previousText.value = prompt.value;
|
previousText.value = prompt.value;
|
||||||
prompt.value = '';
|
prompt.value = '';
|
||||||
@@ -694,6 +762,7 @@ const loadChatHistory = function (chatId) {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
showHello.value = false
|
||||||
|
|
||||||
const md = require('markdown-it')({breaks: true});
|
const md = require('markdown-it')({breaks: true});
|
||||||
// md.use(require('markdown-it-copy')); // 代码复制功能
|
// md.use(require('markdown-it-copy')); // 代码复制功能
|
||||||
@@ -701,6 +770,11 @@ const loadChatHistory = function (chatId) {
|
|||||||
if (data[i].type === "prompt") {
|
if (data[i].type === "prompt") {
|
||||||
chatData.value.push(data[i]);
|
chatData.value.push(data[i]);
|
||||||
continue;
|
continue;
|
||||||
|
} else if (data[i].type === "mj") {
|
||||||
|
data[i].content = JSON.parse(data[i].content)
|
||||||
|
data[i].content.content = md.render(data[i].content?.content)
|
||||||
|
chatData.value.push(data[i]);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
data[i].orgContent = data[i].content;
|
data[i].orgContent = data[i].content;
|
||||||
@@ -726,18 +800,13 @@ const loadChatHistory = function (chatId) {
|
|||||||
const stopGenerate = function () {
|
const stopGenerate = function () {
|
||||||
showStopGenerate.value = false;
|
showStopGenerate.value = false;
|
||||||
httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => {
|
httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => {
|
||||||
canSend.value = true;
|
enableInput()
|
||||||
if (previousText.value !== '') {
|
|
||||||
showReGenerate.value = true;
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新生成
|
// 重新生成
|
||||||
const reGenerate = function () {
|
const reGenerate = function () {
|
||||||
canSend.value = false;
|
disableInput(false)
|
||||||
showStopGenerate.value = true;
|
|
||||||
showReGenerate.value = false;
|
|
||||||
const text = '重新生成上述问题的答案:' + previousText.value;
|
const text = '重新生成上述问题的答案:' + previousText.value;
|
||||||
// 追加消息
|
// 追加消息
|
||||||
chatData.value.push({
|
chatData.value.push({
|
||||||
@@ -769,6 +838,17 @@ const updateUser = function (data) {
|
|||||||
loginUser.value.avatar = data.avatar;
|
loginUser.value.avatar = data.avatar;
|
||||||
loginUser.value.nickname = data.nickname;
|
loginUser.value.nickname = data.nickname;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导出会话
|
||||||
|
const exportChat = () => {
|
||||||
|
if (!activeChat.value['chat_id']) {
|
||||||
|
return ElMessage.error("请先选中一个会话")
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = location.protocol + '//' + location.host + '/chat/export?chat_id=' + activeChat.value['chat_id']
|
||||||
|
// console.log(url)
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
<style scoped lang="stylus">
|
||||||
@@ -776,6 +856,7 @@ const updateUser = function (data) {
|
|||||||
$sideBgColor = #252526;
|
$sideBgColor = #252526;
|
||||||
$borderColor = #4676d0;
|
$borderColor = #4676d0;
|
||||||
#app {
|
#app {
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.common-layout {
|
.common-layout {
|
||||||
@@ -978,6 +1059,10 @@ $borderColor = #4676d0;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
margin-right 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-box {
|
.right-box {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import {onMounted, ref} from "vue";
|
|||||||
import {Lock, UserFilled} from "@element-plus/icons-vue";
|
import {Lock, UserFilled} from "@element-plus/icons-vue";
|
||||||
import {httpPost} from "@/utils/http";
|
import {httpPost} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {setLoginUser} from "@/store/session";
|
import {setSessionId} from "@/store/session";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import FooterBar from "@/components/FooterBar.vue";
|
import FooterBar from "@/components/FooterBar.vue";
|
||||||
import {isMobile} from "@/utils/libs";
|
import {isMobile} from "@/utils/libs";
|
||||||
@@ -79,7 +79,7 @@ const login = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
httpPost('/api/user/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
||||||
setLoginUser(res.data)
|
setSessionId(res.data)
|
||||||
if (isMobile()) {
|
if (isMobile()) {
|
||||||
router.push('/mobile')
|
router.push('/mobile')
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block" v-if="enableMsg">
|
<div class="block">
|
||||||
<el-input placeholder="手机号码"
|
<el-input placeholder="手机号码"
|
||||||
size="large" maxlength="11"
|
size="large" maxlength="11"
|
||||||
v-model="formData.mobile"
|
v-model="formData.mobile"
|
||||||
@@ -184,6 +184,7 @@ const register = function () {
|
|||||||
|
|
||||||
.page-inner {
|
.page-inner {
|
||||||
max-width 450px
|
max-width 450px
|
||||||
|
min-width 360px
|
||||||
height 100vh
|
height 100vh
|
||||||
display flex
|
display flex
|
||||||
justify-content center
|
justify-content center
|
||||||
@@ -191,6 +192,7 @@ const register = function () {
|
|||||||
|
|
||||||
.contain {
|
.contain {
|
||||||
padding 0 40px 20px 40px;
|
padding 0 40px 20px 40px;
|
||||||
|
width 100%
|
||||||
color #ffffff
|
color #ffffff
|
||||||
border-radius 10px;
|
border-radius 10px;
|
||||||
z-index 10
|
z-index 10
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<div class="dashboard" v-loading="loading">
|
||||||
<el-row class="mgb20" :gutter="20">
|
<el-row class="mgb20" :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="6">
|
||||||
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
<div class="grid-content grid-con-1">
|
<div class="grid-content grid-con-1">
|
||||||
<el-icon class="grid-con-icon">
|
<el-icon class="grid-con-icon">
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="6">
|
||||||
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
<div class="grid-content grid-con-2">
|
<div class="grid-content grid-con-2">
|
||||||
<el-icon class="grid-con-icon">
|
<el-icon class="grid-con-icon">
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="6">
|
||||||
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
<div class="grid-content grid-con-3">
|
<div class="grid-content grid-con-3">
|
||||||
<el-icon class="grid-con-icon">
|
<el-icon class="grid-con-icon">
|
||||||
@@ -40,6 +40,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-card shadow="hover" :body-style="{ padding: '0px' }">
|
||||||
|
<div class="grid-content grid-con-3">
|
||||||
|
<el-icon class="grid-con-icon">
|
||||||
|
<i class="iconfont icon-reward"></i>
|
||||||
|
</el-icon>
|
||||||
|
<div class="grid-cont-right">
|
||||||
|
<div class="grid-num">¥{{ stats.rewards }}</div>
|
||||||
|
<div>今日入账</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -50,12 +63,15 @@ import {ChatDotRound, TrendCharts, User} from "@element-plus/icons-vue";
|
|||||||
import {httpGet} from "@/utils/http";
|
import {httpGet} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
|
|
||||||
const stats = ref({users: 0, chats: 0, tokens: 0})
|
const stats = ref({users: 0, chats: 0, tokens: 0, rewards: 0})
|
||||||
|
const loading = ref(true)
|
||||||
|
|
||||||
httpGet('/api/admin/dashboard/stats').then((res) => {
|
httpGet('/api/admin/dashboard/stats').then((res) => {
|
||||||
stats.value.users = res.data.users
|
stats.value.users = res.data.users
|
||||||
stats.value.chats = res.data.chats
|
stats.value.chats = res.data.chats
|
||||||
stats.value.tokens = res.data.tokens
|
stats.value.tokens = res.data.tokens
|
||||||
|
stats.value.rewards = res.data.rewards
|
||||||
|
loading.value = false
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
ElMessage.error("获取统计数据失败:" + e.message)
|
ElMessage.error("获取统计数据失败:" + e.message)
|
||||||
})
|
})
|
||||||
@@ -89,6 +105,10 @@ httpGet('/api/admin/dashboard/stats').then((res) => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 100px;
|
line-height: 100px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 50px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-con-1 .grid-con-icon {
|
.grid-con-1 .grid-con-icon {
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import {onMounted, ref} from "vue";
|
|||||||
import {Lock, UserFilled} from "@element-plus/icons-vue";
|
import {Lock, UserFilled} from "@element-plus/icons-vue";
|
||||||
import {httpPost} from "@/utils/http";
|
import {httpPost} from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import {setLoginUser} from "@/store/session";
|
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import FooterBar from "@/components/FooterBar.vue";
|
import FooterBar from "@/components/FooterBar.vue";
|
||||||
|
|
||||||
@@ -65,12 +64,11 @@ const login = function () {
|
|||||||
if (username.value === '') {
|
if (username.value === '') {
|
||||||
return ElMessage.error('请输入用户名');
|
return ElMessage.error('请输入用户名');
|
||||||
}
|
}
|
||||||
if (password.value.trim() === '') {
|
if (password.value === '') {
|
||||||
return ElMessage.error('请输入密码');
|
return ElMessage.error('请输入密码');
|
||||||
}
|
}
|
||||||
|
|
||||||
httpPost('/api/admin/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
httpPost('/api/admin/login', {username: username.value.trim(), password: password.value.trim()}).then((res) => {
|
||||||
setLoginUser(res.data)
|
|
||||||
router.push("/admin")
|
router.push("/admin")
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
ElMessage.error('登录失败,' + e.message)
|
ElMessage.error('登录失败,' + e.message)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container role-list">
|
<div class="container role-list" v-loading="loading">
|
||||||
<div class="handle-box">
|
<div class="handle-box">
|
||||||
<el-button type="primary" :icon="Plus" @click="addRole">新增</el-button>
|
<el-button type="primary" :icon="Plus" @click="addRole">新增</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,6 +158,7 @@ const sortedTableData = ref([])
|
|||||||
const role = ref({context: []})
|
const role = ref({context: []})
|
||||||
const formRef = ref(null)
|
const formRef = ref(null)
|
||||||
const editRow = ref({})
|
const editRow = ref({})
|
||||||
|
const loading = ref(true)
|
||||||
|
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
name: [{required: true, message: '请输入用户名', trigger: 'blur',}],
|
name: [{required: true, message: '请输入用户名', trigger: 'blur',}],
|
||||||
@@ -174,6 +175,7 @@ const rules = reactive({
|
|||||||
httpGet('/api/admin/role/list').then((res) => {
|
httpGet('/api/admin/role/list').then((res) => {
|
||||||
tableData.value = res.data
|
tableData.value = res.data
|
||||||
sortedTableData.value = copyObj(tableData.value)
|
sortedTableData.value = copyObj(tableData.value)
|
||||||
|
loading.value = false
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
ElMessage.error("获取聊天角色失败");
|
ElMessage.error("获取聊天角色失败");
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
<el-form-item label="注册赠送次数" prop="init_calls">
|
<el-form-item label="注册赠送次数" prop="init_calls">
|
||||||
<el-input v-model.number="system['user_init_calls']" placeholder="新用户注册赠送对话次数"/>
|
<el-input v-model.number="system['user_init_calls']" placeholder="新用户注册赠送对话次数"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="开放用户注册" prop="init_calls">
|
<el-form-item label="短信验证服务" prop="enabled_msg_service">
|
||||||
|
<el-switch v-model="system['enabled_msg_service']"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="开放用户注册" prop="enabled_register">
|
||||||
<el-switch v-model="system['enabled_register']"/>
|
<el-switch v-model="system['enabled_register']"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-alert type="info" show-icon :closable="false">
|
<el-alert type="info" show-icon :closable="false">
|
||||||
@@ -85,7 +88,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {nextTick, onMounted, reactive, ref} from "vue";
|
import {onMounted, reactive, ref} from "vue";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
||||||
import {Plus} from "@element-plus/icons-vue";
|
import {Plus} from "@element-plus/icons-vue";
|
||||||
@@ -115,13 +118,11 @@ onMounted(() => {
|
|||||||
// 加载聊天配置
|
// 加载聊天配置
|
||||||
httpGet('/api/admin/config/get?key=chat').then(res => {
|
httpGet('/api/admin/config/get?key=chat').then(res => {
|
||||||
chat.value = res.data
|
chat.value = res.data
|
||||||
|
loading.value = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error("加载聊天配置失败: " + e.message)
|
ElMessage.error("加载聊天配置失败: " + e.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
|
|||||||
@@ -149,7 +149,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {nextTick, onMounted, reactive, ref} from "vue";
|
import {onMounted, reactive, ref} from "vue";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import {httpGet, httpPost} from "@/utils/http";
|
||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
||||||
import {dateFormat, disabledDate, removeArrayItem} from "@/utils/libs";
|
import {dateFormat, disabledDate, removeArrayItem} from "@/utils/libs";
|
||||||
@@ -189,10 +189,6 @@ onMounted(() => {
|
|||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
ElMessage.error("获取聊天角色失败");
|
ElMessage.error("获取聊天角色失败");
|
||||||
})
|
})
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const fetchUserList = function (page, pageSize) {
|
const fetchUserList = function (page, pageSize) {
|
||||||
@@ -210,6 +206,7 @@ const fetchUserList = function (page, pageSize) {
|
|||||||
users.value.page = res.data.page
|
users.value.page = res.data.page
|
||||||
user.value.page_size = res.data.page_size
|
user.value.page_size = res.data.page_size
|
||||||
}
|
}
|
||||||
|
loading.value = false
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
ElMessage.error('加载用户列表失败')
|
ElMessage.error('加载用户列表失败')
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user