mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-17 16:56:38 +08:00
重构主体工作完成
This commit is contained in:
commit
e53db3582c
@ -30,8 +30,7 @@ type AppServer struct {
|
|||||||
Engine *gin.Engine
|
Engine *gin.Engine
|
||||||
ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message
|
ChatContexts *types.LMap[string, []types.Message] // 聊天上下文 Map [chatId] => []Message
|
||||||
|
|
||||||
ChatConfig *types.ChatConfig // chat config cache
|
SysConfig *types.SystemConfig // system config cache
|
||||||
SysConfig *types.SystemConfig // system config cache
|
|
||||||
|
|
||||||
// 保存 Websocket 会话 UserId, 每个 UserId 只能连接一次
|
// 保存 Websocket 会话 UserId, 每个 UserId 只能连接一次
|
||||||
// 防止第三方直接连接 socket 调用 OpenAI API
|
// 防止第三方直接连接 socket 调用 OpenAI API
|
||||||
@ -69,23 +68,13 @@ func (s *AppServer) Init(debug bool, client *redis.Client) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *AppServer) Run(db *gorm.DB) error {
|
func (s *AppServer) Run(db *gorm.DB) error {
|
||||||
// load chat config from database
|
|
||||||
var chatConfig model.Config
|
|
||||||
res := db.Where("marker", "chat").First(&chatConfig)
|
|
||||||
if res.Error != nil {
|
|
||||||
return res.Error
|
|
||||||
}
|
|
||||||
err := utils.JsonDecode(chatConfig.Config, &s.ChatConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// load system configs
|
// load system configs
|
||||||
var sysConfig model.Config
|
var sysConfig model.Config
|
||||||
res = db.Where("marker", "system").First(&sysConfig)
|
res := db.Where("marker", "system").First(&sysConfig)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
return res.Error
|
return res.Error
|
||||||
}
|
}
|
||||||
err = utils.JsonDecode(sysConfig.Config, &s.SysConfig)
|
err := utils.JsonDecode(sysConfig.Config, &s.SysConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,13 @@ type ChatSession struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ChatModel struct {
|
type ChatModel struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Platform Platform `json:"platform"`
|
Platform Platform `json:"platform"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Power int `json:"power"`
|
Power int `json:"power"`
|
||||||
|
MaxTokens int `json:"max_tokens"` // 最大响应长度
|
||||||
|
MaxContext int `json:"max_context"` // 最大上下文长度
|
||||||
|
Temperature float32 `json:"temperature"` // 模型温度
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiError struct {
|
type ApiError struct {
|
||||||
@ -72,27 +75,6 @@ type ApiError struct {
|
|||||||
const PromptMsg = "prompt" // prompt message
|
const PromptMsg = "prompt" // prompt message
|
||||||
const ReplyMsg = "reply" // reply message
|
const ReplyMsg = "reply" // reply message
|
||||||
|
|
||||||
var ModelToTokens = map[string]int{
|
|
||||||
"gpt-3.5-turbo": 4096,
|
|
||||||
"gpt-3.5-turbo-16k": 16384,
|
|
||||||
"gpt-4": 8192,
|
|
||||||
"gpt-4-32k": 32768,
|
|
||||||
"chatglm_pro": 32768, // 清华智普
|
|
||||||
"chatglm_std": 16384,
|
|
||||||
"chatglm_lite": 4096,
|
|
||||||
"ernie_bot_turbo": 8192, // 文心一言
|
|
||||||
"general": 8192, // 科大讯飞
|
|
||||||
"general2": 8192,
|
|
||||||
"general3": 8192,
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetModelMaxToken(model string) int {
|
|
||||||
if token, ok := ModelToTokens[model]; ok {
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
return 4096
|
|
||||||
}
|
|
||||||
|
|
||||||
// PowerType 算力日志类型
|
// PowerType 算力日志类型
|
||||||
type PowerType int
|
type PowerType int
|
||||||
|
|
||||||
|
@ -121,20 +121,6 @@ func (c RedisConfig) Url() string {
|
|||||||
return fmt.Sprintf("%s:%d", c.Host, c.Port)
|
return fmt.Sprintf("%s:%d", c.Host, c.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatConfig 系统默认的聊天配置
|
|
||||||
type ChatConfig struct {
|
|
||||||
OpenAI ModelAPIConfig `json:"open_ai"`
|
|
||||||
Azure ModelAPIConfig `json:"azure"`
|
|
||||||
ChatGML ModelAPIConfig `json:"chat_gml"`
|
|
||||||
Baidu ModelAPIConfig `json:"baidu"`
|
|
||||||
XunFei ModelAPIConfig `json:"xun_fei"`
|
|
||||||
|
|
||||||
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
|
|
||||||
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
|
|
||||||
ContextDeep int `json:"context_deep"` // 上下文深度
|
|
||||||
DallImgNum int `json:"dall_img_num"` // dall-e3 出图数量
|
|
||||||
}
|
|
||||||
|
|
||||||
type Platform string
|
type Platform string
|
||||||
|
|
||||||
const OpenAI = Platform("OpenAI")
|
const OpenAI = Platform("OpenAI")
|
||||||
@ -144,16 +130,6 @@ const Baidu = Platform("Baidu")
|
|||||||
const XunFei = Platform("XunFei")
|
const XunFei = Platform("XunFei")
|
||||||
const QWen = Platform("QWen")
|
const QWen = Platform("QWen")
|
||||||
|
|
||||||
// UserChatConfig 用户的聊天配置
|
|
||||||
type UserChatConfig struct {
|
|
||||||
ApiKeys map[Platform]string `json:"api_keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModelAPIConfig struct {
|
|
||||||
Temperature float32 `json:"temperature"`
|
|
||||||
MaxTokens int `json:"max_tokens"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SystemConfig struct {
|
type SystemConfig struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
AdminTitle string `json:"admin_title"`
|
AdminTitle string `json:"admin_title"`
|
||||||
@ -178,4 +154,7 @@ type SystemConfig struct {
|
|||||||
DallPower int `json:"dall_power"` // DALLE3 绘图消耗算力
|
DallPower int `json:"dall_power"` // DALLE3 绘图消耗算力
|
||||||
|
|
||||||
WechatCardURL string `json:"wechat_card_url"` // 微信客服地址
|
WechatCardURL string `json:"wechat_card_url"` // 微信客服地址
|
||||||
|
|
||||||
|
EnableContext bool `json:"enable_context"`
|
||||||
|
ContextDeep int `json:"context_deep"`
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
|
|||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
ApiURL string `json:"api_url"`
|
ApiURL string `json:"api_url"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
UseProxy bool `json:"use_proxy"`
|
ProxyURL string `json:"proxy_url"`
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
@ -48,7 +48,7 @@ func (h *ApiKeyHandler) Save(c *gin.Context) {
|
|||||||
apiKey.Type = data.Type
|
apiKey.Type = data.Type
|
||||||
apiKey.ApiURL = data.ApiURL
|
apiKey.ApiURL = data.ApiURL
|
||||||
apiKey.Enabled = data.Enabled
|
apiKey.Enabled = data.Enabled
|
||||||
apiKey.UseProxy = data.UseProxy
|
apiKey.ProxyURL = data.ProxyURL
|
||||||
apiKey.Name = data.Name
|
apiKey.Name = data.Name
|
||||||
res := h.db.Save(&apiKey)
|
res := h.db.Save(&apiKey)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
|
@ -24,14 +24,15 @@ func NewChatHandler(app *core.AppServer, db *gorm.DB) *ChatHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type chatItemVo struct {
|
type chatItemVo struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
UserId uint `json:"user_id"`
|
UserId uint `json:"user_id"`
|
||||||
ChatId string `json:"chat_id"`
|
ChatId string `json:"chat_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Model string `json:"model"`
|
Role vo.ChatRole `json:"role"`
|
||||||
Token int `json:"token"`
|
Model string `json:"model"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
Token int `json:"token"`
|
||||||
MsgNum int `json:"msg_num"` // 消息数量
|
CreatedAt int64 `json:"created_at"`
|
||||||
|
MsgNum int `json:"msg_num"` // 消息数量
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChatHandler) List(c *gin.Context) {
|
func (h *ChatHandler) List(c *gin.Context) {
|
||||||
@ -78,18 +79,23 @@ func (h *ChatHandler) List(c *gin.Context) {
|
|||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
userIds := make([]uint, 0)
|
userIds := make([]uint, 0)
|
||||||
chatIds := make([]string, 0)
|
chatIds := make([]string, 0)
|
||||||
|
roleIds := make([]uint, 0)
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
userIds = append(userIds, item.UserId)
|
userIds = append(userIds, item.UserId)
|
||||||
chatIds = append(chatIds, item.ChatId)
|
chatIds = append(chatIds, item.ChatId)
|
||||||
|
roleIds = append(roleIds, item.RoleId)
|
||||||
}
|
}
|
||||||
var messages []model.ChatMessage
|
var messages []model.ChatMessage
|
||||||
var users []model.User
|
var users []model.User
|
||||||
|
var roles []model.ChatRole
|
||||||
h.db.Where("chat_id IN ?", chatIds).Find(&messages)
|
h.db.Where("chat_id IN ?", chatIds).Find(&messages)
|
||||||
h.db.Where("id IN ?", userIds).Find(&users)
|
h.db.Where("id IN ?", userIds).Find(&users)
|
||||||
|
h.db.Where("id IN ?", roleIds).Find(&roles)
|
||||||
|
|
||||||
tokenMap := make(map[string]int)
|
tokenMap := make(map[string]int)
|
||||||
userMap := make(map[uint]string)
|
userMap := make(map[uint]string)
|
||||||
msgMap := make(map[string]int)
|
msgMap := make(map[string]int)
|
||||||
|
roleMap := make(map[uint]vo.ChatRole)
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
tokenMap[msg.ChatId] += msg.Tokens
|
tokenMap[msg.ChatId] += msg.Tokens
|
||||||
msgMap[msg.ChatId] += 1
|
msgMap[msg.ChatId] += 1
|
||||||
@ -97,6 +103,14 @@ func (h *ChatHandler) List(c *gin.Context) {
|
|||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
userMap[user.Id] = user.Username
|
userMap[user.Id] = user.Username
|
||||||
}
|
}
|
||||||
|
for _, r := range roles {
|
||||||
|
var roleVo vo.ChatRole
|
||||||
|
err := utils.CopyObject(r, &roleVo)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
roleMap[r.Id] = roleVo
|
||||||
|
}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
list = append(list, chatItemVo{
|
list = append(list, chatItemVo{
|
||||||
UserId: item.UserId,
|
UserId: item.UserId,
|
||||||
@ -106,6 +120,7 @@ func (h *ChatHandler) List(c *gin.Context) {
|
|||||||
Model: item.Model,
|
Model: item.Model,
|
||||||
Token: tokenMap[item.ChatId],
|
Token: tokenMap[item.ChatId],
|
||||||
MsgNum: msgMap[item.ChatId],
|
MsgNum: msgMap[item.ChatId],
|
||||||
|
Role: roleMap[item.RoleId],
|
||||||
CreatedAt: item.CreatedAt.Unix(),
|
CreatedAt: item.CreatedAt.Unix(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -26,15 +26,18 @@ func NewChatModelHandler(app *core.AppServer, db *gorm.DB) *ChatModelHandler {
|
|||||||
|
|
||||||
func (h *ChatModelHandler) Save(c *gin.Context) {
|
func (h *ChatModelHandler) Save(c *gin.Context) {
|
||||||
var data struct {
|
var data struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
SortNum int `json:"sort_num"`
|
SortNum int `json:"sort_num"`
|
||||||
Open bool `json:"open"`
|
Open bool `json:"open"`
|
||||||
Platform string `json:"platform"`
|
Platform string `json:"platform"`
|
||||||
Weight int `json:"weight"`
|
Power int `json:"power"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
MaxTokens int `json:"max_tokens"` // 最大响应长度
|
||||||
|
MaxContext int `json:"max_context"` // 最大上下文长度
|
||||||
|
Temperature string `json:"temperature"` // 模型温度
|
||||||
|
CreatedAt int64 `json:"created_at"`
|
||||||
}
|
}
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
@ -42,13 +45,16 @@ func (h *ChatModelHandler) Save(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item := model.ChatModel{
|
item := model.ChatModel{
|
||||||
Platform: data.Platform,
|
Platform: data.Platform,
|
||||||
Name: data.Name,
|
Name: data.Name,
|
||||||
Value: data.Value,
|
Value: data.Value,
|
||||||
Enabled: data.Enabled,
|
Enabled: data.Enabled,
|
||||||
SortNum: data.SortNum,
|
SortNum: data.SortNum,
|
||||||
Open: data.Open,
|
Open: data.Open,
|
||||||
Power: data.Weight}
|
MaxTokens: data.MaxTokens,
|
||||||
|
MaxContext: data.MaxContext,
|
||||||
|
Temperature: float32(utils.Str2Float(data.Temperature)),
|
||||||
|
Power: data.Power}
|
||||||
item.Id = data.Id
|
item.Id = data.Id
|
||||||
if item.Id > 0 {
|
if item.Id > 0 {
|
||||||
item.CreatedAt = time.Unix(data.CreatedAt, 0)
|
item.CreatedAt = time.Unix(data.CreatedAt, 0)
|
||||||
@ -145,19 +151,16 @@ func (h *ChatModelHandler) Sort(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *ChatModelHandler) Remove(c *gin.Context) {
|
func (h *ChatModelHandler) Remove(c *gin.Context) {
|
||||||
var data struct {
|
id := h.GetInt(c, "id", 0)
|
||||||
Id uint
|
if id <= 0 {
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if data.Id > 0 {
|
|
||||||
res := h.db.Where("id = ?", data.Id).Delete(&model.ChatModel{})
|
res := h.db.Where("id = ?", id).Delete(&model.ChatModel{})
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
resp.ERROR(c, "更新数据库失败!")
|
resp.ERROR(c, "更新数据库失败!")
|
||||||
return
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
resp.SUCCESS(c)
|
resp.SUCCESS(c)
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,6 @@ func (h *ConfigHandler) Update(c *gin.Context) {
|
|||||||
var err error
|
var err error
|
||||||
if data.Key == "system" {
|
if data.Key == "system" {
|
||||||
err = utils.JsonDecode(cfg.Config, &h.App.SysConfig)
|
err = utils.JsonDecode(cfg.Config, &h.App.SysConfig)
|
||||||
} else if data.Key == "chat" {
|
|
||||||
err = utils.JsonDecode(cfg.Config, &h.App.ChatConfig)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, "Failed to update config cache: "+err.Error())
|
resp.ERROR(c, "Failed to update config cache: "+err.Error())
|
||||||
|
@ -107,13 +107,6 @@ func (h *UserHandler) Save(c *gin.Context) {
|
|||||||
ChatRoles: utils.JsonEncode(data.ChatRoles),
|
ChatRoles: utils.JsonEncode(data.ChatRoles),
|
||||||
ChatModels: utils.JsonEncode(data.ChatModels),
|
ChatModels: utils.JsonEncode(data.ChatModels),
|
||||||
ExpiredTime: utils.Str2stamp(data.ExpiredTime),
|
ExpiredTime: utils.Str2stamp(data.ExpiredTime),
|
||||||
ChatConfig: utils.JsonEncode(types.UserChatConfig{
|
|
||||||
ApiKeys: map[types.Platform]string{
|
|
||||||
types.OpenAI: "",
|
|
||||||
types.Azure: "",
|
|
||||||
types.ChatGLM: "",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
res = h.db.Create(&u)
|
res = h.db.Create(&u)
|
||||||
_ = utils.CopyObject(u, &userVo)
|
_ = utils.CopyObject(u, &userVo)
|
||||||
|
@ -111,66 +111,64 @@ func (h *ChatHandler) sendAzureMessage(
|
|||||||
useMsg := types.Message{Role: "user", Content: prompt}
|
useMsg := types.Message{Role: "user", Content: prompt}
|
||||||
|
|
||||||
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
||||||
if h.App.ChatConfig.EnableContext {
|
if h.App.SysConfig.EnableContext {
|
||||||
chatCtx = append(chatCtx, useMsg) // 提问消息
|
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||||
chatCtx = append(chatCtx, message) // 回复消息
|
chatCtx = append(chatCtx, message) // 回复消息
|
||||||
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加聊天记录
|
// 追加聊天记录
|
||||||
if h.App.ChatConfig.EnableHistory {
|
// for prompt
|
||||||
// for prompt
|
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
||||||
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
if err != nil {
|
||||||
if err != nil {
|
logger.Error(err)
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
historyUserMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.PromptMsg,
|
|
||||||
Icon: userVo.Avatar,
|
|
||||||
Content: template.HTMLEscapeString(prompt),
|
|
||||||
Tokens: promptToken,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyUserMsg.CreatedAt = promptCreatedAt
|
|
||||||
historyUserMsg.UpdatedAt = promptCreatedAt
|
|
||||||
res := h.db.Save(&historyUserMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save prompt history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算本次对话消耗的总 token 数量
|
|
||||||
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
|
||||||
replyTokens += getTotalTokens(req)
|
|
||||||
|
|
||||||
historyReplyMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.ReplyMsg,
|
|
||||||
Icon: role.Icon,
|
|
||||||
Content: message.Content,
|
|
||||||
Tokens: replyTokens,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyReplyMsg.CreatedAt = replyCreatedAt
|
|
||||||
historyReplyMsg.UpdatedAt = replyCreatedAt
|
|
||||||
res = h.db.Create(&historyReplyMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save reply history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新用户算力
|
|
||||||
h.subUserPower(userVo, session, promptToken, replyTokens)
|
|
||||||
}
|
}
|
||||||
|
historyUserMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.PromptMsg,
|
||||||
|
Icon: userVo.Avatar,
|
||||||
|
Content: template.HTMLEscapeString(prompt),
|
||||||
|
Tokens: promptToken,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyUserMsg.CreatedAt = promptCreatedAt
|
||||||
|
historyUserMsg.UpdatedAt = promptCreatedAt
|
||||||
|
res := h.db.Save(&historyUserMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save prompt history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算本次对话消耗的总 token 数量
|
||||||
|
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
||||||
|
replyTokens += getTotalTokens(req)
|
||||||
|
|
||||||
|
historyReplyMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.ReplyMsg,
|
||||||
|
Icon: role.Icon,
|
||||||
|
Content: message.Content,
|
||||||
|
Tokens: replyTokens,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyReplyMsg.CreatedAt = replyCreatedAt
|
||||||
|
historyReplyMsg.UpdatedAt = replyCreatedAt
|
||||||
|
res = h.db.Create(&historyReplyMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save reply history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户算力
|
||||||
|
h.subUserPower(userVo, session, promptToken, replyTokens)
|
||||||
|
|
||||||
// 保存当前会话
|
// 保存当前会话
|
||||||
var chatItem model.ChatItem
|
var chatItem model.ChatItem
|
||||||
res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
chatItem.ChatId = session.ChatId
|
chatItem.ChatId = session.ChatId
|
||||||
chatItem.UserId = session.UserId
|
chatItem.UserId = session.UserId
|
||||||
|
@ -135,65 +135,63 @@ func (h *ChatHandler) sendBaiduMessage(
|
|||||||
useMsg := types.Message{Role: "user", Content: prompt}
|
useMsg := types.Message{Role: "user", Content: prompt}
|
||||||
|
|
||||||
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
||||||
if h.App.ChatConfig.EnableContext {
|
if h.App.SysConfig.EnableContext {
|
||||||
chatCtx = append(chatCtx, useMsg) // 提问消息
|
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||||
chatCtx = append(chatCtx, message) // 回复消息
|
chatCtx = append(chatCtx, message) // 回复消息
|
||||||
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加聊天记录
|
// 追加聊天记录
|
||||||
if h.App.ChatConfig.EnableHistory {
|
// for prompt
|
||||||
// for prompt
|
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
||||||
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
if err != nil {
|
||||||
if err != nil {
|
logger.Error(err)
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
historyUserMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.PromptMsg,
|
|
||||||
Icon: userVo.Avatar,
|
|
||||||
Content: template.HTMLEscapeString(prompt),
|
|
||||||
Tokens: promptToken,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyUserMsg.CreatedAt = promptCreatedAt
|
|
||||||
historyUserMsg.UpdatedAt = promptCreatedAt
|
|
||||||
res := h.db.Save(&historyUserMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save prompt history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for reply
|
|
||||||
// 计算本次对话消耗的总 token 数量
|
|
||||||
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
|
||||||
totalTokens := replyTokens + getTotalTokens(req)
|
|
||||||
historyReplyMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.ReplyMsg,
|
|
||||||
Icon: role.Icon,
|
|
||||||
Content: message.Content,
|
|
||||||
Tokens: totalTokens,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyReplyMsg.CreatedAt = replyCreatedAt
|
|
||||||
historyReplyMsg.UpdatedAt = replyCreatedAt
|
|
||||||
res = h.db.Create(&historyReplyMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save reply history message: ", res.Error)
|
|
||||||
}
|
|
||||||
// 更新用户算力
|
|
||||||
h.subUserPower(userVo, session, promptToken, replyTokens)
|
|
||||||
}
|
}
|
||||||
|
historyUserMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.PromptMsg,
|
||||||
|
Icon: userVo.Avatar,
|
||||||
|
Content: template.HTMLEscapeString(prompt),
|
||||||
|
Tokens: promptToken,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyUserMsg.CreatedAt = promptCreatedAt
|
||||||
|
historyUserMsg.UpdatedAt = promptCreatedAt
|
||||||
|
res := h.db.Save(&historyUserMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save prompt history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for reply
|
||||||
|
// 计算本次对话消耗的总 token 数量
|
||||||
|
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
||||||
|
totalTokens := replyTokens + getTotalTokens(req)
|
||||||
|
historyReplyMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.ReplyMsg,
|
||||||
|
Icon: role.Icon,
|
||||||
|
Content: message.Content,
|
||||||
|
Tokens: totalTokens,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyReplyMsg.CreatedAt = replyCreatedAt
|
||||||
|
historyReplyMsg.UpdatedAt = replyCreatedAt
|
||||||
|
res = h.db.Create(&historyReplyMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save reply history message: ", res.Error)
|
||||||
|
}
|
||||||
|
// 更新用户算力
|
||||||
|
h.subUserPower(userVo, session, promptToken, replyTokens)
|
||||||
|
|
||||||
// 保存当前会话
|
// 保存当前会话
|
||||||
var chatItem model.ChatItem
|
var chatItem model.ChatItem
|
||||||
res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
chatItem.ChatId = session.ChatId
|
chatItem.ChatId = session.ChatId
|
||||||
chatItem.UserId = session.UserId
|
chatItem.UserId = session.UserId
|
||||||
|
@ -57,8 +57,6 @@ func (h *ChatHandler) Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var chatConfig types.ChatConfig
|
|
||||||
|
|
||||||
// ChatHandle 处理聊天 WebSocket 请求
|
// ChatHandle 处理聊天 WebSocket 请求
|
||||||
func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
||||||
ws, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer, c.Request, nil)
|
ws, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(c.Writer, c.Request, nil)
|
||||||
@ -109,10 +107,13 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
|
|
||||||
session.ChatId = chatId
|
session.ChatId = chatId
|
||||||
session.Model = types.ChatModel{
|
session.Model = types.ChatModel{
|
||||||
Id: chatModel.Id,
|
Id: chatModel.Id,
|
||||||
Value: chatModel.Value,
|
Value: chatModel.Value,
|
||||||
Power: chatModel.Power,
|
Power: chatModel.Power,
|
||||||
Platform: types.Platform(chatModel.Platform)}
|
MaxTokens: chatModel.MaxTokens,
|
||||||
|
MaxContext: chatModel.MaxContext,
|
||||||
|
Temperature: chatModel.Temperature,
|
||||||
|
Platform: types.Platform(chatModel.Platform)}
|
||||||
logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username)
|
logger.Infof("New websocket connected, IP: %s, Username: %s", c.ClientIP(), session.Username)
|
||||||
var chatRole model.ChatRole
|
var chatRole model.ChatRole
|
||||||
res = h.db.First(&chatRole, roleId)
|
res = h.db.First(&chatRole, roleId)
|
||||||
@ -122,15 +123,6 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化聊天配置
|
|
||||||
var config model.Config
|
|
||||||
h.db.Where("marker", "chat").First(&config)
|
|
||||||
err = utils.JsonDecode(config.Config, &chatConfig)
|
|
||||||
if err != nil {
|
|
||||||
utils.ReplyMessage(client, "加载系统配置失败,连接已关闭!!!")
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.Init()
|
h.Init()
|
||||||
|
|
||||||
// 保存会话连接
|
// 保存会话连接
|
||||||
@ -213,7 +205,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if userVo.Power <= 0 && userVo.ChatConfig.ApiKeys[session.Model.Platform] == "" {
|
if userVo.Power <= 0 {
|
||||||
utils.ReplyMessage(ws, "您的对话次数已经用尽,请联系管理员或者充值点卡继续对话!")
|
utils.ReplyMessage(ws, "您的对话次数已经用尽,请联系管理员或者充值点卡继续对话!")
|
||||||
utils.ReplyMessage(ws, ErrImg)
|
utils.ReplyMessage(ws, ErrImg)
|
||||||
return nil
|
return nil
|
||||||
@ -227,7 +219,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
|
|
||||||
// 检查 prompt 长度是否超过了当前模型允许的最大上下文长度
|
// 检查 prompt 长度是否超过了当前模型允许的最大上下文长度
|
||||||
promptTokens, err := utils.CalcTokens(prompt, session.Model.Value)
|
promptTokens, err := utils.CalcTokens(prompt, session.Model.Value)
|
||||||
if promptTokens > types.GetModelMaxToken(session.Model.Value) {
|
if promptTokens > session.Model.MaxContext {
|
||||||
utils.ReplyMessage(ws, "对话内容超出了当前模型允许的最大上下文长度!")
|
utils.ReplyMessage(ws, "对话内容超出了当前模型允许的最大上下文长度!")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -237,21 +229,13 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
Stream: true,
|
Stream: true,
|
||||||
}
|
}
|
||||||
switch session.Model.Platform {
|
switch session.Model.Platform {
|
||||||
case types.Azure:
|
case types.Azure, types.ChatGLM, types.Baidu, types.XunFei:
|
||||||
req.Temperature = h.App.ChatConfig.Azure.Temperature
|
req.Temperature = session.Model.Temperature
|
||||||
req.MaxTokens = h.App.ChatConfig.Azure.MaxTokens
|
req.MaxTokens = session.Model.MaxTokens
|
||||||
break
|
|
||||||
case types.ChatGLM:
|
|
||||||
req.Temperature = h.App.ChatConfig.ChatGML.Temperature
|
|
||||||
req.MaxTokens = h.App.ChatConfig.ChatGML.MaxTokens
|
|
||||||
break
|
|
||||||
case types.Baidu:
|
|
||||||
req.Temperature = h.App.ChatConfig.OpenAI.Temperature
|
|
||||||
// TODO: 目前只支持 ERNIE-Bot-turbo 模型,如果是 ERNIE-Bot 模型则需要增加函数支持
|
|
||||||
break
|
break
|
||||||
case types.OpenAI:
|
case types.OpenAI:
|
||||||
req.Temperature = h.App.ChatConfig.OpenAI.Temperature
|
req.Temperature = session.Model.Temperature
|
||||||
req.MaxTokens = h.App.ChatConfig.OpenAI.MaxTokens
|
req.MaxTokens = session.Model.MaxTokens
|
||||||
// OpenAI 支持函数功能
|
// OpenAI 支持函数功能
|
||||||
var items []model.Function
|
var items []model.Function
|
||||||
res := h.db.Where("enabled", true).Find(&items)
|
res := h.db.Where("enabled", true).Find(&items)
|
||||||
@ -283,15 +267,13 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
req.Tools = tools
|
req.Tools = tools
|
||||||
req.ToolChoice = "auto"
|
req.ToolChoice = "auto"
|
||||||
}
|
}
|
||||||
|
|
||||||
case types.XunFei:
|
|
||||||
req.Temperature = h.App.ChatConfig.XunFei.Temperature
|
|
||||||
req.MaxTokens = h.App.ChatConfig.XunFei.MaxTokens
|
|
||||||
break
|
|
||||||
case types.QWen:
|
case types.QWen:
|
||||||
req.Input = map[string]interface{}{"messages": []map[string]string{{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}}}
|
req.Parameters = map[string]interface{}{
|
||||||
req.Parameters = map[string]interface{}{}
|
"max_tokens": session.Model.MaxTokens,
|
||||||
|
"temperature": session.Model.Temperature,
|
||||||
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
utils.ReplyMessage(ws, "不支持的平台:"+session.Model.Platform+",请联系管理员!")
|
utils.ReplyMessage(ws, "不支持的平台:"+session.Model.Platform+",请联系管理员!")
|
||||||
utils.ReplyMessage(ws, ErrImg)
|
utils.ReplyMessage(ws, ErrImg)
|
||||||
@ -301,14 +283,14 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
// 加载聊天上下文
|
// 加载聊天上下文
|
||||||
chatCtx := make([]types.Message, 0)
|
chatCtx := make([]types.Message, 0)
|
||||||
messages := make([]types.Message, 0)
|
messages := make([]types.Message, 0)
|
||||||
if h.App.ChatConfig.EnableContext {
|
if h.App.SysConfig.EnableContext {
|
||||||
if h.App.ChatContexts.Has(session.ChatId) {
|
if h.App.ChatContexts.Has(session.ChatId) {
|
||||||
messages = h.App.ChatContexts.Get(session.ChatId)
|
messages = h.App.ChatContexts.Get(session.ChatId)
|
||||||
} else {
|
} else {
|
||||||
_ = utils.JsonDecode(role.Context, &messages)
|
_ = utils.JsonDecode(role.Context, &messages)
|
||||||
if chatConfig.ContextDeep > 0 {
|
if h.App.SysConfig.ContextDeep > 0 {
|
||||||
var historyMessages []model.ChatMessage
|
var historyMessages []model.ChatMessage
|
||||||
res := h.db.Where("chat_id = ? and use_context = 1", session.ChatId).Limit(chatConfig.ContextDeep).Order("id DESC").Find(&historyMessages)
|
res := h.db.Where("chat_id = ? and use_context = 1", session.ChatId).Limit(h.App.SysConfig.ContextDeep).Order("id DESC").Find(&historyMessages)
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
for i := len(historyMessages) - 1; i >= 0; i-- {
|
for i := len(historyMessages) - 1; i >= 0; i-- {
|
||||||
msg := historyMessages[i]
|
msg := historyMessages[i]
|
||||||
@ -331,12 +313,12 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
for _, v := range messages {
|
for _, v := range messages {
|
||||||
tks, _ := utils.CalcTokens(v.Content, req.Model)
|
tks, _ := utils.CalcTokens(v.Content, req.Model)
|
||||||
// 上下文 token 超出了模型的最大上下文长度
|
// 上下文 token 超出了模型的最大上下文长度
|
||||||
if tokens+tks >= types.GetModelMaxToken(req.Model) {
|
if tokens+tks >= session.Model.MaxContext {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上下文的深度超出了模型的最大上下文深度
|
// 上下文的深度超出了模型的最大上下文深度
|
||||||
if len(chatCtx) >= h.App.ChatConfig.ContextDeep {
|
if len(chatCtx) >= h.App.SysConfig.ContextDeep {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,10 +333,17 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
reqMgs = append(reqMgs, m)
|
reqMgs = append(reqMgs, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Messages = append(reqMgs, map[string]interface{}{
|
if session.Model.Platform == types.QWen {
|
||||||
"role": "user",
|
req.Input = map[string]interface{}{"prompt": prompt}
|
||||||
"content": prompt,
|
if len(reqMgs) > 0 {
|
||||||
})
|
req.Input["messages"] = reqMgs
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
req.Messages = append(reqMgs, map[string]interface{}{
|
||||||
|
"role": "user",
|
||||||
|
"content": prompt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
switch session.Model.Platform {
|
switch session.Model.Platform {
|
||||||
case types.Azure:
|
case types.Azure:
|
||||||
@ -497,9 +486,8 @@ func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, platf
|
|||||||
request = request.WithContext(ctx)
|
request = request.WithContext(ctx)
|
||||||
request.Header.Set("Content-Type", "application/json")
|
request.Header.Set("Content-Type", "application/json")
|
||||||
var proxyURL string
|
var proxyURL string
|
||||||
if h.App.Config.ProxyURL != "" && apiKey.UseProxy { // 使用代理
|
if apiKey.ProxyURL != "" { // 使用代理
|
||||||
proxyURL = h.App.Config.ProxyURL
|
proxy, _ := url.Parse(apiKey.ProxyURL)
|
||||||
proxy, _ := url.Parse(proxyURL)
|
|
||||||
client = &http.Client{
|
client = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
Proxy: http.ProxyURL(proxy),
|
Proxy: http.ProxyURL(proxy),
|
||||||
@ -542,7 +530,7 @@ func (h *ChatHandler) subUserPower(userVo vo.User, session *types.ChatSession, p
|
|||||||
res := h.db.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("power", gorm.Expr("power - ?", power))
|
res := h.db.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("power", gorm.Expr("power - ?", power))
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
// 记录算力消费日志
|
// 记录算力消费日志
|
||||||
h.db.Debug().Create(&model.PowerLog{
|
h.db.Create(&model.PowerLog{
|
||||||
UserId: userVo.Id,
|
UserId: userVo.Id,
|
||||||
Username: userVo.Username,
|
Username: userVo.Username,
|
||||||
Type: types.PowerConsume,
|
Type: types.PowerConsume,
|
||||||
|
@ -126,7 +126,7 @@ func (h *ChatHandler) History(c *gin.Context) {
|
|||||||
chatId := c.Query("chat_id") // 会话 ID
|
chatId := c.Query("chat_id") // 会话 ID
|
||||||
var items []model.ChatMessage
|
var items []model.ChatMessage
|
||||||
var messages = make([]vo.HistoryMessage, 0)
|
var messages = make([]vo.HistoryMessage, 0)
|
||||||
res := h.db.Debug().Where("chat_id = ?", chatId).Find(&items)
|
res := h.db.Where("chat_id = ?", chatId).Find(&items)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
resp.ERROR(c, "No history message")
|
resp.ERROR(c, "No history message")
|
||||||
return
|
return
|
||||||
|
@ -114,66 +114,64 @@ func (h *ChatHandler) sendChatGLMMessage(
|
|||||||
useMsg := types.Message{Role: "user", Content: prompt}
|
useMsg := types.Message{Role: "user", Content: prompt}
|
||||||
|
|
||||||
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
||||||
if h.App.ChatConfig.EnableContext {
|
if h.App.SysConfig.EnableContext {
|
||||||
chatCtx = append(chatCtx, useMsg) // 提问消息
|
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||||
chatCtx = append(chatCtx, message) // 回复消息
|
chatCtx = append(chatCtx, message) // 回复消息
|
||||||
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加聊天记录
|
// 追加聊天记录
|
||||||
if h.App.ChatConfig.EnableHistory {
|
// for prompt
|
||||||
// for prompt
|
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
||||||
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
if err != nil {
|
||||||
if err != nil {
|
logger.Error(err)
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
historyUserMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.PromptMsg,
|
|
||||||
Icon: userVo.Avatar,
|
|
||||||
Content: template.HTMLEscapeString(prompt),
|
|
||||||
Tokens: promptToken,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyUserMsg.CreatedAt = promptCreatedAt
|
|
||||||
historyUserMsg.UpdatedAt = promptCreatedAt
|
|
||||||
res := h.db.Save(&historyUserMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save prompt history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for reply
|
|
||||||
// 计算本次对话消耗的总 token 数量
|
|
||||||
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
|
||||||
totalTokens := replyTokens + getTotalTokens(req)
|
|
||||||
historyReplyMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.ReplyMsg,
|
|
||||||
Icon: role.Icon,
|
|
||||||
Content: message.Content,
|
|
||||||
Tokens: totalTokens,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyReplyMsg.CreatedAt = replyCreatedAt
|
|
||||||
historyReplyMsg.UpdatedAt = replyCreatedAt
|
|
||||||
res = h.db.Create(&historyReplyMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save reply history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新用户算力
|
|
||||||
h.subUserPower(userVo, session, promptToken, replyTokens)
|
|
||||||
}
|
}
|
||||||
|
historyUserMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.PromptMsg,
|
||||||
|
Icon: userVo.Avatar,
|
||||||
|
Content: template.HTMLEscapeString(prompt),
|
||||||
|
Tokens: promptToken,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyUserMsg.CreatedAt = promptCreatedAt
|
||||||
|
historyUserMsg.UpdatedAt = promptCreatedAt
|
||||||
|
res := h.db.Save(&historyUserMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save prompt history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for reply
|
||||||
|
// 计算本次对话消耗的总 token 数量
|
||||||
|
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
||||||
|
totalTokens := replyTokens + getTotalTokens(req)
|
||||||
|
historyReplyMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.ReplyMsg,
|
||||||
|
Icon: role.Icon,
|
||||||
|
Content: message.Content,
|
||||||
|
Tokens: totalTokens,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyReplyMsg.CreatedAt = replyCreatedAt
|
||||||
|
historyReplyMsg.UpdatedAt = replyCreatedAt
|
||||||
|
res = h.db.Create(&historyReplyMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save reply history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户算力
|
||||||
|
h.subUserPower(userVo, session, promptToken, replyTokens)
|
||||||
|
|
||||||
// 保存当前会话
|
// 保存当前会话
|
||||||
var chatItem model.ChatItem
|
var chatItem model.ChatItem
|
||||||
res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
chatItem.ChatId = session.ChatId
|
chatItem.ChatId = session.ChatId
|
||||||
chatItem.UserId = session.UserId
|
chatItem.UserId = session.UserId
|
||||||
|
@ -180,79 +180,77 @@ func (h *ChatHandler) sendOpenAiMessage(
|
|||||||
useMsg := types.Message{Role: "user", Content: prompt}
|
useMsg := types.Message{Role: "user", Content: prompt}
|
||||||
|
|
||||||
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
||||||
if h.App.ChatConfig.EnableContext && toolCall == false {
|
if h.App.SysConfig.EnableContext && toolCall == false {
|
||||||
chatCtx = append(chatCtx, useMsg) // 提问消息
|
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||||
chatCtx = append(chatCtx, message) // 回复消息
|
chatCtx = append(chatCtx, message) // 回复消息
|
||||||
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加聊天记录
|
// 追加聊天记录
|
||||||
if h.App.ChatConfig.EnableHistory {
|
useContext := true
|
||||||
useContext := true
|
if toolCall {
|
||||||
if toolCall {
|
useContext = false
|
||||||
useContext = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// for prompt
|
|
||||||
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
historyUserMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.PromptMsg,
|
|
||||||
Icon: userVo.Avatar,
|
|
||||||
Content: template.HTMLEscapeString(prompt),
|
|
||||||
Tokens: promptToken,
|
|
||||||
UseContext: useContext,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyUserMsg.CreatedAt = promptCreatedAt
|
|
||||||
historyUserMsg.UpdatedAt = promptCreatedAt
|
|
||||||
res := h.db.Save(&historyUserMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save prompt history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算本次对话消耗的总 token 数量
|
|
||||||
var replyTokens = 0
|
|
||||||
if toolCall { // prompt + 函数名 + 参数 token
|
|
||||||
tokens, _ := utils.CalcTokens(function.Name, req.Model)
|
|
||||||
replyTokens += tokens
|
|
||||||
tokens, _ = utils.CalcTokens(utils.InterfaceToString(arguments), req.Model)
|
|
||||||
replyTokens += tokens
|
|
||||||
} else {
|
|
||||||
replyTokens, _ = utils.CalcTokens(message.Content, req.Model)
|
|
||||||
}
|
|
||||||
replyTokens += getTotalTokens(req)
|
|
||||||
|
|
||||||
historyReplyMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.ReplyMsg,
|
|
||||||
Icon: role.Icon,
|
|
||||||
Content: h.extractImgUrl(message.Content),
|
|
||||||
Tokens: replyTokens,
|
|
||||||
UseContext: useContext,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyReplyMsg.CreatedAt = replyCreatedAt
|
|
||||||
historyReplyMsg.UpdatedAt = replyCreatedAt
|
|
||||||
res = h.db.Create(&historyReplyMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save reply history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新用户算力
|
|
||||||
h.subUserPower(userVo, session, promptToken, replyTokens)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for prompt
|
||||||
|
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
historyUserMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.PromptMsg,
|
||||||
|
Icon: userVo.Avatar,
|
||||||
|
Content: template.HTMLEscapeString(prompt),
|
||||||
|
Tokens: promptToken,
|
||||||
|
UseContext: useContext,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyUserMsg.CreatedAt = promptCreatedAt
|
||||||
|
historyUserMsg.UpdatedAt = promptCreatedAt
|
||||||
|
res := h.db.Save(&historyUserMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save prompt history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算本次对话消耗的总 token 数量
|
||||||
|
var replyTokens = 0
|
||||||
|
if toolCall { // prompt + 函数名 + 参数 token
|
||||||
|
tokens, _ := utils.CalcTokens(function.Name, req.Model)
|
||||||
|
replyTokens += tokens
|
||||||
|
tokens, _ = utils.CalcTokens(utils.InterfaceToString(arguments), req.Model)
|
||||||
|
replyTokens += tokens
|
||||||
|
} else {
|
||||||
|
replyTokens, _ = utils.CalcTokens(message.Content, req.Model)
|
||||||
|
}
|
||||||
|
replyTokens += getTotalTokens(req)
|
||||||
|
|
||||||
|
historyReplyMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.ReplyMsg,
|
||||||
|
Icon: role.Icon,
|
||||||
|
Content: h.extractImgUrl(message.Content),
|
||||||
|
Tokens: replyTokens,
|
||||||
|
UseContext: useContext,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyReplyMsg.CreatedAt = replyCreatedAt
|
||||||
|
historyReplyMsg.UpdatedAt = replyCreatedAt
|
||||||
|
res = h.db.Create(&historyReplyMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save reply history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户算力
|
||||||
|
h.subUserPower(userVo, session, promptToken, replyTokens)
|
||||||
|
|
||||||
// 保存当前会话
|
// 保存当前会话
|
||||||
var chatItem model.ChatItem
|
var chatItem model.ChatItem
|
||||||
res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
chatItem.ChatId = session.ChatId
|
chatItem.ChatId = session.ChatId
|
||||||
chatItem.UserId = session.UserId
|
chatItem.UserId = session.UserId
|
||||||
|
@ -20,13 +20,16 @@ type qWenResp struct {
|
|||||||
Output struct {
|
Output struct {
|
||||||
FinishReason string `json:"finish_reason"`
|
FinishReason string `json:"finish_reason"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
} `json:"output"`
|
} `json:"output,omitempty"`
|
||||||
Usage struct {
|
Usage struct {
|
||||||
TotalTokens int `json:"total_tokens"`
|
TotalTokens int `json:"total_tokens"`
|
||||||
InputTokens int `json:"input_tokens"`
|
InputTokens int `json:"input_tokens"`
|
||||||
OutputTokens int `json:"output_tokens"`
|
OutputTokens int `json:"output_tokens"`
|
||||||
} `json:"usage"`
|
} `json:"usage,omitempty"`
|
||||||
RequestID string `json:"request_id"`
|
RequestID string `json:"request_id"`
|
||||||
|
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通义千问消息发送实现
|
// 通义千问消息发送实现
|
||||||
@ -70,6 +73,7 @@ func (h *ChatHandler) sendQWenMessage(
|
|||||||
scanner := bufio.NewScanner(response.Body)
|
scanner := bufio.NewScanner(response.Body)
|
||||||
|
|
||||||
var content, lastText, newText string
|
var content, lastText, newText string
|
||||||
|
var outPutStart = false
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
@ -77,24 +81,32 @@ func (h *ChatHandler) sendQWenMessage(
|
|||||||
strings.HasPrefix(line, "event:") || strings.HasPrefix(line, ":HTTP_STATUS/200") {
|
strings.HasPrefix(line, "event:") || strings.HasPrefix(line, ":HTTP_STATUS/200") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(line, "data:") {
|
if strings.HasPrefix(line, "data:") {
|
||||||
content = line[5:]
|
content = line[5:]
|
||||||
}
|
}
|
||||||
// 处理代码换行
|
|
||||||
if len(content) == 0 {
|
|
||||||
content = "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp qWenResp
|
var resp qWenResp
|
||||||
err := utils.JsonDecode(content, &resp)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("error with parse data line: ", err)
|
|
||||||
utils.ReplyMessage(ws, fmt.Sprintf("**解析数据行失败:%s**", err))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(contents) == 0 { // 发送消息头
|
if len(contents) == 0 { // 发送消息头
|
||||||
utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
if !outPutStart {
|
||||||
|
utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
||||||
|
outPutStart = true
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// 处理代码换行
|
||||||
|
content = "\n"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := utils.JsonDecode(content, &resp)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error with parse data line: ", content)
|
||||||
|
utils.ReplyMessage(ws, fmt.Sprintf("**解析数据行失败:%s**", err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if resp.Message != "" {
|
||||||
|
utils.ReplyMessage(ws, fmt.Sprintf("**API 返回错误:%s**", resp.Message))
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//通过比较 lastText(上一次的文本)和 currentText(当前的文本),
|
//通过比较 lastText(上一次的文本)和 currentText(当前的文本),
|
||||||
@ -135,66 +147,64 @@ func (h *ChatHandler) sendQWenMessage(
|
|||||||
useMsg := types.Message{Role: "user", Content: prompt}
|
useMsg := types.Message{Role: "user", Content: prompt}
|
||||||
|
|
||||||
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
||||||
if h.App.ChatConfig.EnableContext {
|
if h.App.SysConfig.EnableContext {
|
||||||
chatCtx = append(chatCtx, useMsg) // 提问消息
|
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||||
chatCtx = append(chatCtx, message) // 回复消息
|
chatCtx = append(chatCtx, message) // 回复消息
|
||||||
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加聊天记录
|
// 追加聊天记录
|
||||||
if h.App.ChatConfig.EnableHistory {
|
// for prompt
|
||||||
// for prompt
|
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
||||||
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
if err != nil {
|
||||||
if err != nil {
|
logger.Error(err)
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
historyUserMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.PromptMsg,
|
|
||||||
Icon: userVo.Avatar,
|
|
||||||
Content: template.HTMLEscapeString(prompt),
|
|
||||||
Tokens: promptToken,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyUserMsg.CreatedAt = promptCreatedAt
|
|
||||||
historyUserMsg.UpdatedAt = promptCreatedAt
|
|
||||||
res := h.db.Save(&historyUserMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save prompt history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for reply
|
|
||||||
// 计算本次对话消耗的总 token 数量
|
|
||||||
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
|
||||||
totalTokens := replyTokens + getTotalTokens(req)
|
|
||||||
historyReplyMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.ReplyMsg,
|
|
||||||
Icon: role.Icon,
|
|
||||||
Content: message.Content,
|
|
||||||
Tokens: totalTokens,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyReplyMsg.CreatedAt = replyCreatedAt
|
|
||||||
historyReplyMsg.UpdatedAt = replyCreatedAt
|
|
||||||
res = h.db.Create(&historyReplyMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save reply history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新用户算力
|
|
||||||
h.subUserPower(userVo, session, promptToken, replyTokens)
|
|
||||||
}
|
}
|
||||||
|
historyUserMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.PromptMsg,
|
||||||
|
Icon: userVo.Avatar,
|
||||||
|
Content: template.HTMLEscapeString(prompt),
|
||||||
|
Tokens: promptToken,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyUserMsg.CreatedAt = promptCreatedAt
|
||||||
|
historyUserMsg.UpdatedAt = promptCreatedAt
|
||||||
|
res := h.db.Save(&historyUserMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save prompt history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for reply
|
||||||
|
// 计算本次对话消耗的总 token 数量
|
||||||
|
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
||||||
|
totalTokens := replyTokens + getTotalTokens(req)
|
||||||
|
historyReplyMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.ReplyMsg,
|
||||||
|
Icon: role.Icon,
|
||||||
|
Content: message.Content,
|
||||||
|
Tokens: totalTokens,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyReplyMsg.CreatedAt = replyCreatedAt
|
||||||
|
historyReplyMsg.UpdatedAt = replyCreatedAt
|
||||||
|
res = h.db.Create(&historyReplyMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save reply history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户算力
|
||||||
|
h.subUserPower(userVo, session, promptToken, replyTokens)
|
||||||
|
|
||||||
// 保存当前会话
|
// 保存当前会话
|
||||||
var chatItem model.ChatItem
|
var chatItem model.ChatItem
|
||||||
res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
chatItem.ChatId = session.ChatId
|
chatItem.ChatId = session.ChatId
|
||||||
chatItem.UserId = session.UserId
|
chatItem.UserId = session.UserId
|
||||||
|
@ -50,9 +50,10 @@ type xunFeiResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var Model2URL = map[string]string{
|
var Model2URL = map[string]string{
|
||||||
"general": "v1.1",
|
"general": "v1.1",
|
||||||
"generalv2": "v2.1",
|
"generalv2": "v2.1",
|
||||||
"generalv3": "v3.1",
|
"generalv3": "v3.1",
|
||||||
|
"generalv3.5": "v3.5",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 科大讯飞消息发送实现
|
// 科大讯飞消息发送实现
|
||||||
@ -86,6 +87,7 @@ func (h *ChatHandler) sendXunFeiMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiURL := strings.Replace(apiKey.ApiURL, "{version}", Model2URL[req.Model], 1)
|
apiURL := strings.Replace(apiKey.ApiURL, "{version}", Model2URL[req.Model], 1)
|
||||||
|
logger.Debugf("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s, Model: %s", session.Model.Platform, apiURL, apiKey.Value, apiKey.ProxyURL, req.Model)
|
||||||
wsURL, err := assembleAuthUrl(apiURL, key[1], key[2])
|
wsURL, err := assembleAuthUrl(apiURL, key[1], key[2])
|
||||||
//握手并建立websocket 连接
|
//握手并建立websocket 连接
|
||||||
conn, resp, err := d.Dial(wsURL, nil)
|
conn, resp, err := d.Dial(wsURL, nil)
|
||||||
@ -173,66 +175,64 @@ func (h *ChatHandler) sendXunFeiMessage(
|
|||||||
useMsg := types.Message{Role: "user", Content: prompt}
|
useMsg := types.Message{Role: "user", Content: prompt}
|
||||||
|
|
||||||
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
||||||
if h.App.ChatConfig.EnableContext {
|
if h.App.SysConfig.EnableContext {
|
||||||
chatCtx = append(chatCtx, useMsg) // 提问消息
|
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||||
chatCtx = append(chatCtx, message) // 回复消息
|
chatCtx = append(chatCtx, message) // 回复消息
|
||||||
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 追加聊天记录
|
// 追加聊天记录
|
||||||
if h.App.ChatConfig.EnableHistory {
|
// for prompt
|
||||||
// for prompt
|
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
||||||
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
if err != nil {
|
||||||
if err != nil {
|
logger.Error(err)
|
||||||
logger.Error(err)
|
|
||||||
}
|
|
||||||
historyUserMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.PromptMsg,
|
|
||||||
Icon: userVo.Avatar,
|
|
||||||
Content: template.HTMLEscapeString(prompt),
|
|
||||||
Tokens: promptToken,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyUserMsg.CreatedAt = promptCreatedAt
|
|
||||||
historyUserMsg.UpdatedAt = promptCreatedAt
|
|
||||||
res := h.db.Save(&historyUserMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save prompt history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for reply
|
|
||||||
// 计算本次对话消耗的总 token 数量
|
|
||||||
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
|
||||||
totalTokens := replyTokens + getTotalTokens(req)
|
|
||||||
historyReplyMsg := model.ChatMessage{
|
|
||||||
UserId: userVo.Id,
|
|
||||||
ChatId: session.ChatId,
|
|
||||||
RoleId: role.Id,
|
|
||||||
Type: types.ReplyMsg,
|
|
||||||
Icon: role.Icon,
|
|
||||||
Content: message.Content,
|
|
||||||
Tokens: totalTokens,
|
|
||||||
UseContext: true,
|
|
||||||
Model: req.Model,
|
|
||||||
}
|
|
||||||
historyReplyMsg.CreatedAt = replyCreatedAt
|
|
||||||
historyReplyMsg.UpdatedAt = replyCreatedAt
|
|
||||||
res = h.db.Create(&historyReplyMsg)
|
|
||||||
if res.Error != nil {
|
|
||||||
logger.Error("failed to save reply history message: ", res.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新用户算力
|
|
||||||
h.subUserPower(userVo, session, promptToken, replyTokens)
|
|
||||||
}
|
}
|
||||||
|
historyUserMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.PromptMsg,
|
||||||
|
Icon: userVo.Avatar,
|
||||||
|
Content: template.HTMLEscapeString(prompt),
|
||||||
|
Tokens: promptToken,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyUserMsg.CreatedAt = promptCreatedAt
|
||||||
|
historyUserMsg.UpdatedAt = promptCreatedAt
|
||||||
|
res := h.db.Save(&historyUserMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save prompt history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for reply
|
||||||
|
// 计算本次对话消耗的总 token 数量
|
||||||
|
replyTokens, _ := utils.CalcTokens(message.Content, req.Model)
|
||||||
|
totalTokens := replyTokens + getTotalTokens(req)
|
||||||
|
historyReplyMsg := model.ChatMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.ReplyMsg,
|
||||||
|
Icon: role.Icon,
|
||||||
|
Content: message.Content,
|
||||||
|
Tokens: totalTokens,
|
||||||
|
UseContext: true,
|
||||||
|
Model: req.Model,
|
||||||
|
}
|
||||||
|
historyReplyMsg.CreatedAt = replyCreatedAt
|
||||||
|
historyReplyMsg.UpdatedAt = replyCreatedAt
|
||||||
|
res = h.db.Create(&historyReplyMsg)
|
||||||
|
if res.Error != nil {
|
||||||
|
logger.Error("failed to save reply history message: ", res.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户算力
|
||||||
|
h.subUserPower(userVo, session, promptToken, replyTokens)
|
||||||
|
|
||||||
// 保存当前会话
|
// 保存当前会话
|
||||||
var chatItem model.ChatItem
|
var chatItem model.ChatItem
|
||||||
res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
res = h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
chatItem.ChatId = session.ChatId
|
chatItem.ChatId = session.ChatId
|
||||||
chatItem.UserId = session.UserId
|
chatItem.UserId = session.UserId
|
||||||
@ -260,7 +260,7 @@ func buildRequest(appid string, req types.ApiRequest) map[string]interface{} {
|
|||||||
"parameter": map[string]interface{}{
|
"parameter": map[string]interface{}{
|
||||||
"chat": map[string]interface{}{
|
"chat": map[string]interface{}{
|
||||||
"domain": req.Model,
|
"domain": req.Model,
|
||||||
"temperature": float64(req.Temperature),
|
"temperature": req.Temperature,
|
||||||
"top_k": int64(6),
|
"top_k": int64(6),
|
||||||
"max_tokens": int64(req.MaxTokens),
|
"max_tokens": int64(req.MaxTokens),
|
||||||
"auditing": "default",
|
"auditing": "default",
|
||||||
|
@ -22,7 +22,6 @@ type FunctionHandler struct {
|
|||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
config types.ChatPlusApiConfig
|
config types.ChatPlusApiConfig
|
||||||
uploadManager *oss.UploaderManager
|
uploadManager *oss.UploaderManager
|
||||||
proxyURL string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFunctionHandler(server *core.AppServer, db *gorm.DB, config *types.AppConfig, manager *oss.UploaderManager) *FunctionHandler {
|
func NewFunctionHandler(server *core.AppServer, db *gorm.DB, config *types.AppConfig, manager *oss.UploaderManager) *FunctionHandler {
|
||||||
@ -33,7 +32,6 @@ func NewFunctionHandler(server *core.AppServer, db *gorm.DB, config *types.AppCo
|
|||||||
db: db,
|
db: db,
|
||||||
config: config.ApiConfig,
|
config: config.ApiConfig,
|
||||||
uploadManager: manager,
|
uploadManager: manager,
|
||||||
proxyURL: config.ProxyURL,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,47 +211,28 @@ func (h *FunctionHandler) Dall3(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get image generation api URL
|
|
||||||
var conf model.Config
|
|
||||||
var chatConfig types.ChatConfig
|
|
||||||
tx = h.db.Where("marker", "chat").First(&conf)
|
|
||||||
if tx.Error != nil {
|
|
||||||
resp.ERROR(c, "error with get chat configs:"+tx.Error.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := utils.JsonDecode(conf.Config, &chatConfig)
|
|
||||||
if err != nil {
|
|
||||||
resp.ERROR(c, "error with decode chat config: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// translate prompt
|
// translate prompt
|
||||||
const translatePromptTemplate = "Translate the following painting prompt words into English keyword phrases. Without any explanation, directly output the keyword phrases separated by commas. The content to be translated is: [%s]"
|
const translatePromptTemplate = "Translate the following painting prompt words into English keyword phrases. Without any explanation, directly output the keyword phrases separated by commas. The content to be translated is: [%s]"
|
||||||
pt, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, params["prompt"]), h.App.Config.ProxyURL)
|
pt, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, params["prompt"]))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.Debugf("翻译绘画提示词,原文:%s,译文:%s", prompt, pt)
|
logger.Debugf("翻译绘画提示词,原文:%s,译文:%s", prompt, pt)
|
||||||
prompt = pt
|
prompt = pt
|
||||||
}
|
}
|
||||||
imgNum := chatConfig.DallImgNum
|
|
||||||
if imgNum <= 0 {
|
|
||||||
imgNum = 1
|
|
||||||
}
|
|
||||||
var res imgRes
|
var res imgRes
|
||||||
var errRes ErrRes
|
var errRes ErrRes
|
||||||
var request *req.Request
|
var request *req.Request
|
||||||
if apiKey.UseProxy && h.proxyURL != "" {
|
if apiKey.ProxyURL != "" {
|
||||||
request = req.C().SetProxyURL(h.proxyURL).R()
|
request = req.C().SetProxyURL(apiKey.ProxyURL).R()
|
||||||
} else {
|
} else {
|
||||||
request = req.C().R()
|
request = req.C().R()
|
||||||
}
|
}
|
||||||
logger.Debugf("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, h.proxyURL)
|
logger.Debugf("Sending %s request, ApiURL:%s, API KEY:%s, PROXY: %s", apiKey.Platform, apiKey.ApiURL, apiKey.Value, apiKey.ProxyURL)
|
||||||
r, err := request.SetHeader("Content-Type", "application/json").
|
r, err := request.SetHeader("Content-Type", "application/json").
|
||||||
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
||||||
SetBody(imgReq{
|
SetBody(imgReq{
|
||||||
Model: "dall-e-3",
|
Model: "dall-e-3",
|
||||||
Prompt: prompt,
|
Prompt: prompt,
|
||||||
N: imgNum,
|
N: 1,
|
||||||
Size: "1024x1024",
|
Size: "1024x1024",
|
||||||
}).
|
}).
|
||||||
SetErrorResult(&errRes).
|
SetErrorResult(&errRes).
|
||||||
|
@ -35,7 +35,7 @@ func (h *PromptHandler) Rewrite(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(rewritePromptTemplate, data.Prompt), h.App.Config.ProxyURL)
|
content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(rewritePromptTemplate, data.Prompt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
return
|
return
|
||||||
@ -53,7 +53,7 @@ func (h *PromptHandler) Translate(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, data.Prompt), h.App.Config.ProxyURL)
|
content, err := utils.OpenAIRequest(h.db, fmt.Sprintf(translatePromptTemplate, data.Prompt))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.ERROR(c, err.Error())
|
resp.ERROR(c, err.Error())
|
||||||
return
|
return
|
||||||
|
@ -95,14 +95,7 @@ func (h *UserHandler) Register(c *gin.Context) {
|
|||||||
Status: true,
|
Status: true,
|
||||||
ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
|
ChatRoles: utils.JsonEncode([]string{"gpt"}), // 默认只订阅通用助手角色
|
||||||
ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
|
ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
|
||||||
ChatConfig: utils.JsonEncode(types.UserChatConfig{
|
Power: h.App.SysConfig.InitPower,
|
||||||
ApiKeys: map[types.Platform]string{
|
|
||||||
types.OpenAI: "",
|
|
||||||
types.Azure: "",
|
|
||||||
types.ChatGLM: "",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Power: h.App.SysConfig.InitPower,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res = h.db.Create(&user)
|
res = h.db.Create(&user)
|
||||||
@ -245,14 +238,13 @@ func (h *UserHandler) Session(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type userProfile struct {
|
type userProfile struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Nickname string `json:"nickname"`
|
Nickname string `json:"nickname"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
ChatConfig types.UserChatConfig `json:"chat_config"`
|
Power int `json:"power"`
|
||||||
Power int `json:"power"`
|
ExpiredTime int64 `json:"expired_time"`
|
||||||
ExpiredTime int64 `json:"expired_time"`
|
Vip bool `json:"vip"`
|
||||||
Vip bool `json:"vip"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UserHandler) Profile(c *gin.Context) {
|
func (h *UserHandler) Profile(c *gin.Context) {
|
||||||
|
@ -315,7 +315,7 @@ func main() {
|
|||||||
group.GET("list", h.List)
|
group.GET("list", h.List)
|
||||||
group.POST("set", h.Set)
|
group.POST("set", h.Set)
|
||||||
group.POST("sort", h.Sort)
|
group.POST("sort", h.Sort)
|
||||||
group.POST("remove", h.Remove)
|
group.GET("remove", h.Remove)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
|
fx.Invoke(func(s *core.AppServer, h *handler.PaymentHandler) {
|
||||||
group := s.Engine.Group("/api/payment/")
|
group := s.Engine.Group("/api/payment/")
|
||||||
|
@ -9,6 +9,6 @@ type ApiKey struct {
|
|||||||
Value string // API Key 的值
|
Value string // API Key 的值
|
||||||
ApiURL string // 当前 KEY 的 API 地址
|
ApiURL string // 当前 KEY 的 API 地址
|
||||||
Enabled bool // 是否启用
|
Enabled bool // 是否启用
|
||||||
UseProxy bool // 是否使用代理访问 API URL
|
ProxyURL string // 代理地址
|
||||||
LastUsedAt int64 // 最后使用时间
|
LastUsedAt int64 // 最后使用时间
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,14 @@ package model
|
|||||||
|
|
||||||
type ChatModel struct {
|
type ChatModel struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
Platform string
|
Platform string
|
||||||
Name string
|
Name string
|
||||||
Value string // API Key 的值
|
Value string // API Key 的值
|
||||||
SortNum int
|
SortNum int
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Power int // 每次对话消耗算力
|
Power int // 每次对话消耗算力
|
||||||
Open bool // 是否开放模型给所有人使用
|
Open bool // 是否开放模型给所有人使用
|
||||||
|
MaxTokens int // 最大响应长度
|
||||||
|
MaxContext int // 最大上下文长度
|
||||||
|
Temperature float32 // 模型温度
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,6 @@ type ApiKey struct {
|
|||||||
Value string `json:"value"` // API Key 的值
|
Value string `json:"value"` // API Key 的值
|
||||||
ApiURL string `json:"api_url"`
|
ApiURL string `json:"api_url"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
UseProxy bool `json:"use_proxy"`
|
ProxyURL string `json:"proxy_url"`
|
||||||
LastUsedAt int64 `json:"last_used_at"` // 最后使用时间
|
LastUsedAt int64 `json:"last_used_at"` // 最后使用时间
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,14 @@ package vo
|
|||||||
|
|
||||||
type ChatModel struct {
|
type ChatModel struct {
|
||||||
BaseVo
|
BaseVo
|
||||||
Platform string `json:"platform"`
|
Platform string `json:"platform"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
SortNum int `json:"sort_num"`
|
SortNum int `json:"sort_num"`
|
||||||
Weight int `json:"weight"`
|
Power int `json:"power"`
|
||||||
Open bool `json:"open"`
|
Open bool `json:"open"`
|
||||||
|
MaxTokens int `json:"max_tokens"` // 最大响应长度
|
||||||
|
MaxContext int `json:"max_context"` // 最大上下文长度
|
||||||
|
Temperature float32 `json:"temperature"` // 模型温度
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,5 @@ import "chatplus/core/types"
|
|||||||
type Config struct {
|
type Config struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
ChatConfig types.ChatConfig `json:"chat_config"`
|
|
||||||
SystemConfig types.SystemConfig `json:"system_config"`
|
SystemConfig types.SystemConfig `json:"system_config"`
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
package vo
|
package vo
|
||||||
|
|
||||||
import "chatplus/core/types"
|
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
BaseVo
|
BaseVo
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Nickname string `json:"nickname"`
|
Nickname string `json:"nickname"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
Salt string `json:"salt"` // 密码盐
|
Salt string `json:"salt"` // 密码盐
|
||||||
Power int `json:"power"` // 剩余算力
|
Power int `json:"power"` // 剩余算力
|
||||||
ChatConfig types.UserChatConfig `json:"chat_config"` // 聊天配置
|
ChatRoles []string `json:"chat_roles"` // 聊天角色集合
|
||||||
ChatRoles []string `json:"chat_roles"` // 聊天角色集合
|
ChatModels []string `json:"chat_models"` // AI模型集合
|
||||||
ChatModels []string `json:"chat_models"` // AI模型集合
|
ExpiredTime int64 `json:"expired_time"` // 账户到期时间
|
||||||
ExpiredTime int64 `json:"expired_time"` // 账户到期时间
|
Status bool `json:"status"` // 当前状态
|
||||||
Status bool `json:"status"` // 当前状态
|
LastLoginAt int64 `json:"last_login_at"` // 最后登录时间
|
||||||
LastLoginAt int64 `json:"last_login_at"` // 最后登录时间
|
LastLoginIp string `json:"last_login_ip"` // 最后登录 IP
|
||||||
LastLoginIp string `json:"last_login_ip"` // 最后登录 IP
|
Vip bool `json:"vip"`
|
||||||
Vip bool `json:"vip"`
|
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ type apiErrRes struct {
|
|||||||
} `json:"error"`
|
} `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func OpenAIRequest(db *gorm.DB, prompt string, proxy string) (string, error) {
|
func OpenAIRequest(db *gorm.DB, prompt string) (string, error) {
|
||||||
var apiKey model.ApiKey
|
var apiKey model.ApiKey
|
||||||
res := db.Where("platform = ?", types.OpenAI).Where("type = ?", "chat").Where("enabled = ?", true).First(&apiKey)
|
res := db.Where("platform = ?", types.OpenAI).Where("type = ?", "chat").Where("enabled = ?", true).First(&apiKey)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
@ -104,8 +104,8 @@ func OpenAIRequest(db *gorm.DB, prompt string, proxy string) (string, error) {
|
|||||||
var response apiRes
|
var response apiRes
|
||||||
var errRes apiErrRes
|
var errRes apiErrRes
|
||||||
client := req.C()
|
client := req.C()
|
||||||
if apiKey.UseProxy && proxy != "" {
|
if apiKey.ProxyURL != "" {
|
||||||
client.SetProxyURL(proxy)
|
client.SetProxyURL(apiKey.ApiURL)
|
||||||
}
|
}
|
||||||
r, err := client.R().SetHeader("Content-Type", "application/json").
|
r, err := client.R().SetHeader("Content-Type", "application/json").
|
||||||
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
SetHeader("Authorization", "Bearer "+apiKey.Value).
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
@ -59,7 +60,7 @@ func Str2stamp(str string) int64 {
|
|||||||
if len(str) == 0 {
|
if len(str) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
layout := "2006-01-02 15:04:05"
|
layout := "2006-01-02 15:04:05"
|
||||||
t, err := time.Parse(layout, str)
|
t, err := time.Parse(layout, str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -92,3 +93,11 @@ func InterfaceToString(value interface{}) string {
|
|||||||
}
|
}
|
||||||
return JsonEncode(value)
|
return JsonEncode(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Str2Float(str string) float64 {
|
||||||
|
num, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
@ -130,3 +130,8 @@ INSERT INTO `chatgpt_admin_permissions` VALUES (31, '权限配置', '', 2, 28, '
|
|||||||
INSERT INTO `chatgpt_admin_permissions` VALUES (32, '角色配置', '', 3, 28, '2024-03-14 15:29:15', '2024-03-14 15:29:15');
|
INSERT INTO `chatgpt_admin_permissions` VALUES (32, '角色配置', '', 3, 28, '2024-03-14 15:29:15', '2024-03-14 15:29:15');
|
||||||
INSERT INTO `chatgpt_admin_permissions` VALUES (33, '列表', 'api_admin_sysPermission_list', 1, 31, '2024-03-14 15:29:52', '2024-03-14 15:29:52');
|
INSERT INTO `chatgpt_admin_permissions` VALUES (33, '列表', 'api_admin_sysPermission_list', 1, 31, '2024-03-14 15:29:52', '2024-03-14 15:29:52');
|
||||||
INSERT INTO `chatgpt_admin_permissions` VALUES (34, '列表', 'api_admin_sysRole_list', 1, 32, '2024-03-14 15:30:21', '2024-03-14 15:30:21');
|
INSERT INTO `chatgpt_admin_permissions` VALUES (34, '列表', 'api_admin_sysRole_list', 1, 32, '2024-03-14 15:30:21', '2024-03-14 15:30:21');
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE `chatgpt_api_keys` CHANGE `use_proxy` `proxy_url` VARCHAR(100) NULL DEFAULT NULL COMMENT '代理地址';
|
||||||
|
-- 重置 proxy_url
|
||||||
|
UPDATE chatgpt_api_keys set proxy_url=''
|
@ -1,4 +1,4 @@
|
|||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.1'
|
||||||
|
|
||||||
settings:
|
settings:
|
||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
@ -127,103 +127,6 @@ importers:
|
|||||||
specifier: ^1.8.27
|
specifier: ^1.8.27
|
||||||
version: 1.8.27(typescript@5.3.3)
|
version: 1.8.27(typescript@5.3.3)
|
||||||
|
|
||||||
projects/mobile:
|
|
||||||
dependencies:
|
|
||||||
'@element-plus/icons-vue':
|
|
||||||
specifier: ^2.1.0
|
|
||||||
version: 2.3.1(vue@3.4.21)
|
|
||||||
axios:
|
|
||||||
specifier: ^0.27.2
|
|
||||||
version: 0.27.2
|
|
||||||
clipboard:
|
|
||||||
specifier: ^2.0.11
|
|
||||||
version: 2.0.11
|
|
||||||
compressorjs:
|
|
||||||
specifier: ^1.2.1
|
|
||||||
version: 1.2.1
|
|
||||||
core-js:
|
|
||||||
specifier: ^3.8.3
|
|
||||||
version: 3.36.0
|
|
||||||
element-plus:
|
|
||||||
specifier: ^2.3.0
|
|
||||||
version: 2.6.1(vue@3.4.21)
|
|
||||||
good-storage:
|
|
||||||
specifier: ^1.1.1
|
|
||||||
version: 1.1.1
|
|
||||||
highlight.js:
|
|
||||||
specifier: ^11.7.0
|
|
||||||
version: 11.9.0
|
|
||||||
json-bigint:
|
|
||||||
specifier: ^1.0.0
|
|
||||||
version: 1.0.0
|
|
||||||
lodash:
|
|
||||||
specifier: ^4.17.21
|
|
||||||
version: 4.17.21
|
|
||||||
markdown-it:
|
|
||||||
specifier: ^13.0.1
|
|
||||||
version: 13.0.2
|
|
||||||
markdown-it-latex2img:
|
|
||||||
specifier: ^0.0.6
|
|
||||||
version: 0.0.6
|
|
||||||
markdown-it-mathjax:
|
|
||||||
specifier: ^2.0.0
|
|
||||||
version: 2.0.0
|
|
||||||
md-editor-v3:
|
|
||||||
specifier: ^2.2.1
|
|
||||||
version: 2.11.3(vue@3.4.21)
|
|
||||||
pinia:
|
|
||||||
specifier: ^2.1.4
|
|
||||||
version: 2.1.7(typescript@5.3.3)(vue@3.4.21)
|
|
||||||
qrcode:
|
|
||||||
specifier: ^1.5.3
|
|
||||||
version: 1.5.3
|
|
||||||
qs:
|
|
||||||
specifier: ^6.11.1
|
|
||||||
version: 6.12.0
|
|
||||||
sortablejs:
|
|
||||||
specifier: ^1.15.0
|
|
||||||
version: 1.15.2
|
|
||||||
v3-waterfall:
|
|
||||||
specifier: ^1.2.1
|
|
||||||
version: 1.3.3
|
|
||||||
vant:
|
|
||||||
specifier: ^4.5.0
|
|
||||||
version: 4.8.5(vue@3.4.21)
|
|
||||||
vue:
|
|
||||||
specifier: ^3.2.13
|
|
||||||
version: 3.4.21(typescript@5.3.3)
|
|
||||||
vue-router:
|
|
||||||
specifier: ^4.0.15
|
|
||||||
version: 4.3.0(vue@3.4.21)
|
|
||||||
devDependencies:
|
|
||||||
'@babel/core':
|
|
||||||
specifier: 7.18.6
|
|
||||||
version: 7.18.6
|
|
||||||
'@babel/eslint-parser':
|
|
||||||
specifier: ^7.12.16
|
|
||||||
version: 7.23.10(@babel/core@7.18.6)(eslint@7.32.0)
|
|
||||||
'@vue/cli-plugin-babel':
|
|
||||||
specifier: ~5.0.0
|
|
||||||
version: 5.0.8(@vue/cli-service@5.0.8)(core-js@3.36.0)(vue@3.4.21)
|
|
||||||
'@vue/cli-plugin-eslint':
|
|
||||||
specifier: ~5.0.0
|
|
||||||
version: 5.0.8(@vue/cli-service@5.0.8)(eslint@7.32.0)
|
|
||||||
'@vue/cli-service':
|
|
||||||
specifier: ~5.0.0
|
|
||||||
version: 5.0.8(lodash@4.17.21)(prettier@3.2.5)(stylus-loader@7.1.3)(vue@3.4.21)
|
|
||||||
eslint:
|
|
||||||
specifier: ^7.32.0
|
|
||||||
version: 7.32.0
|
|
||||||
eslint-plugin-vue:
|
|
||||||
specifier: ^8.0.3
|
|
||||||
version: 8.7.1(eslint@7.32.0)
|
|
||||||
stylus:
|
|
||||||
specifier: ^0.58.1
|
|
||||||
version: 0.58.1
|
|
||||||
stylus-loader:
|
|
||||||
specifier: ^7.0.0
|
|
||||||
version: 7.1.3(stylus@0.58.1)(webpack@5.90.3)
|
|
||||||
|
|
||||||
projects/web:
|
projects/web:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@element-plus/icons-vue':
|
'@element-plus/icons-vue':
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"Vue.volar",
|
|
||||||
"Vue.vscode-typescript-vue-plugin",
|
|
||||||
"dbaeumer.vscode-eslint"
|
|
||||||
]
|
|
||||||
}
|
|
49
new-ui/projects/admin/src/components/CustomUploader.vue
Normal file
49
new-ui/projects/admin/src/components/CustomUploader.vue
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { Message } from "@arco-design/web-vue";
|
||||||
|
import type { UploadInstance, FileItem } from "@arco-design/web-vue";
|
||||||
|
import { uploadUrl } from "@/http/config";
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
modelValue: String,
|
||||||
|
placeholder: String,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
|
const uploadProps = computed<UploadInstance["$props"]>(() => {
|
||||||
|
const TOKEN = JSON.parse(localStorage.getItem(__AUTH_KEY))?.token;
|
||||||
|
return {
|
||||||
|
accept: "image/*",
|
||||||
|
action: uploadUrl,
|
||||||
|
name: "file",
|
||||||
|
headers: { [__AUTH_KEY]: TOKEN },
|
||||||
|
showFileList: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (_, file: FileItem) => {
|
||||||
|
if (file?.response) {
|
||||||
|
emits("update:modelValue", file?.response?.data?.url);
|
||||||
|
Message.success("上传成功");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<a-upload v-bind="uploadProps" style="width: 100%" @change="handleChange">
|
||||||
|
<template #upload-button>
|
||||||
|
<a-input-group style="width: 100%">
|
||||||
|
<a-input
|
||||||
|
:model-value="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
readonly
|
||||||
|
allow-clear
|
||||||
|
@clear.stop="emits('update:modelValue')"
|
||||||
|
/>
|
||||||
|
<a-button type="primary" style="width: 100px">
|
||||||
|
<icon-cloud />
|
||||||
|
</a-button>
|
||||||
|
</a-input-group>
|
||||||
|
</template>
|
||||||
|
</a-upload>
|
||||||
|
</template>
|
@ -1,18 +1,17 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, onActivated } from "vue";
|
import { computed, onActivated } from "vue";
|
||||||
import useAsyncTable from "./useAsyncTable";
|
import useAsyncTable from "./useAsyncTable";
|
||||||
import { useTableScroll } from "@/components/SearchTable/utils";
|
import { useTableScroll } from "@/components/SearchTable/utils";
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message, type TableColumnData } from "@arco-design/web-vue";
|
||||||
import type { TableRequest, TableOriginalProps } from "./useAsyncTable";
|
import type { TableRequest, TableOriginalProps } from "./useAsyncTable";
|
||||||
|
|
||||||
interface SimpleTable extends /* @vue-ignore */ TableOriginalProps {
|
interface SimpleTable extends /* @vue-ignore */ TableOriginalProps {
|
||||||
request: TableRequest<Record<string, unknown>>;
|
request: TableRequest<Record<string, unknown>>;
|
||||||
params?: Record<string, unknown>;
|
params?: Record<string, unknown>;
|
||||||
columns?: TableOriginalProps["columns"];
|
columns?: TableColumnData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<SimpleTable>();
|
const props = defineProps<SimpleTable>();
|
||||||
const tableContainerRef = ref<HTMLElement>();
|
|
||||||
|
|
||||||
// 表格请求参数
|
// 表格请求参数
|
||||||
const [tableConfig, getList] = useAsyncTable(props.request, props.params);
|
const [tableConfig, getList] = useAsyncTable(props.request, props.params);
|
||||||
@ -45,7 +44,7 @@ onActivated(handleSearch);
|
|||||||
...$attrs,
|
...$attrs,
|
||||||
...tableConfig,
|
...tableConfig,
|
||||||
...props,
|
...props,
|
||||||
scroll: useTableScroll(_columns || [], tableContainerRef as HTMLElement),
|
scroll: useTableScroll(_columns || []),
|
||||||
columns: _columns,
|
columns: _columns,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
import { getList, save, deleting, setStatus } from "./api";
|
import { getList, save, deleting, setStatus } from "./api";
|
||||||
import ApiKeyForm from "./ApiKeyForm.vue";
|
import ApiKeyForm from "./ApiKeyForm.vue";
|
||||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message, type TableColumnData } from "@arco-design/web-vue";
|
||||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
||||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||||
// table 配置
|
// table 配置
|
||||||
const columns = [
|
const columns: TableColumnData[] = [
|
||||||
{
|
{
|
||||||
title: "所属平台",
|
title: "所属平台",
|
||||||
dataIndex: "platform",
|
dataIndex: "platform",
|
||||||
@ -20,6 +20,11 @@ const columns = [
|
|||||||
dataIndex: "value",
|
dataIndex: "value",
|
||||||
slotName: "value",
|
slotName: "value",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "API URL",
|
||||||
|
dataIndex: "api_url",
|
||||||
|
slotName: "value",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "用途",
|
title: "用途",
|
||||||
dataIndex: "type",
|
dataIndex: "type",
|
||||||
@ -85,17 +90,26 @@ const handleStatusChange = ({ filed, value, record, reload }) => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<SimpleTable :columns="columns" :request="getList">
|
<SimpleTable :columns="columns" :request="getList">
|
||||||
|
<template #header="{ reload }">
|
||||||
|
<a-space>
|
||||||
|
<a-button @click="popup({ reload })" size="small" type="primary"
|
||||||
|
><template #icon> <icon-plus /> </template>新增
|
||||||
|
</a-button>
|
||||||
|
<a-button type="primary" status="success" href="https://gpt.bemore.lol" target="_blank">
|
||||||
|
<template #icon>
|
||||||
|
<icon-link />
|
||||||
|
</template>
|
||||||
|
购买API-KEY
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
<template #action="{ record, reload }">
|
<template #action="{ record, reload }">
|
||||||
<a-link @click="popup({ record, reload })">编辑</a-link>
|
<a-link @click="popup({ record, reload })">编辑</a-link>
|
||||||
<a-popconfirm content="确定删除?" @ok="handleDelete(record, reload)">
|
<a-popconfirm content="确定删除?" @ok="handleDelete(record, reload)">
|
||||||
<a-link status="danger">删除</a-link>
|
<a-link status="danger">删除</a-link>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
<template #header="{ reload }">
|
|
||||||
<a-button @click="popup({ reload })" size="small" type="primary"
|
|
||||||
><template #icon> <icon-plus /> </template>新增
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
<template #value="{ record, column }">
|
<template #value="{ record, column }">
|
||||||
<a-typography-text copyable ellipsis style="margin: 0">
|
<a-typography-text copyable ellipsis style="margin: 0">
|
||||||
{{ record[column.dataIndex] }}
|
{{ record[column.dataIndex] }}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-alert type="warning">
|
<a-alert type="warning">
|
||||||
<div class="warning">
|
<div class="warning">
|
||||||
{{
|
<div>注意:如果是百度文心一言平台,API-KEY 为 APIKey|SecretKey,中间用竖线(|)连接</div>
|
||||||
`注意:如果是百度文心一言平台,API-KEY 为 APIKey|SecretKey,中间用竖线(|)连接\n注意:如果是讯飞星火大模型,API-KEY 为 AppId|APIKey|APISecret,中间用竖线(|)连接`
|
<div>注意:如果是讯飞星火大模型,API-KEY 为 AppId|APIKey|APISecret,中间用竖线(|)连接</div>
|
||||||
}}
|
|
||||||
</div>
|
</div>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
<a-form
|
<a-form
|
||||||
@ -18,7 +17,12 @@
|
|||||||
:rules="[{ required: true, message: '请输入所属平台' }]"
|
:rules="[{ required: true, message: '请输入所属平台' }]"
|
||||||
:validate-trigger="['change', 'input']"
|
:validate-trigger="['change', 'input']"
|
||||||
>
|
>
|
||||||
<a-input v-model="form.platform" placeholder="请输入所属平台" />
|
<a-select
|
||||||
|
v-model="form.platform"
|
||||||
|
placeholder="请输入所属平台"
|
||||||
|
:options="platformOptions"
|
||||||
|
@change="handlePlatformChange"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="name"
|
field="name"
|
||||||
@ -35,7 +39,12 @@
|
|||||||
:rules="[{ required: true, message: '请输入用途' }]"
|
:rules="[{ required: true, message: '请输入用途' }]"
|
||||||
:validate-trigger="['change', 'input']"
|
:validate-trigger="['change', 'input']"
|
||||||
>
|
>
|
||||||
<a-select v-model="form.type" placeholder="请输入用途" :options="typeOPtions"> </a-select>
|
<a-select
|
||||||
|
v-model="form.type"
|
||||||
|
placeholder="请输入用途"
|
||||||
|
:options="typeOptions"
|
||||||
|
@change="handlePlatformChange"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="value"
|
field="value"
|
||||||
@ -55,13 +64,15 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item field="use_proxy" label="使用代理">
|
<a-form-item field="use_proxy" label="使用代理">
|
||||||
<a-switch v-model="form.use_proxy" />
|
<a-space>
|
||||||
<a-tooltip
|
<a-switch v-model="form.use_proxy" />
|
||||||
content="是否使用代理访问 API URL,OpenAI 官方API需要开启代理访问"
|
<a-tooltip
|
||||||
position="right"
|
content="是否使用代理访问 API URL,OpenAI 官方API需要开启代理访问"
|
||||||
>
|
position="right"
|
||||||
<icon-info-circle-fill />
|
>
|
||||||
</a-tooltip>
|
<icon-info-circle-fill />
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item field="enable" label="启用状态">
|
<a-form-item field="enable" label="启用状态">
|
||||||
<a-switch v-model="form.enable" />
|
<a-switch v-model="form.enable" />
|
||||||
@ -86,7 +97,7 @@ defineExpose({
|
|||||||
form,
|
form,
|
||||||
});
|
});
|
||||||
|
|
||||||
const typeOPtions = [
|
const typeOptions = [
|
||||||
{
|
{
|
||||||
label: "聊天",
|
label: "聊天",
|
||||||
value: "chart",
|
value: "chart",
|
||||||
@ -96,6 +107,48 @@ const typeOPtions = [
|
|||||||
value: "img",
|
value: "img",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const platformOptions = [
|
||||||
|
{
|
||||||
|
label: "【OpenAI】ChatGPT",
|
||||||
|
value: "OpenAI",
|
||||||
|
api_url: "https://gpt.bemore.lol/v1/chat/completions",
|
||||||
|
img_url: "https://gpt.bemore.lol/v1/images/generations",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "【讯飞】星火大模型",
|
||||||
|
value: "XunFei",
|
||||||
|
api_url: "wss://spark-api.xf-yun.com/{version}/chat",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "【清华智普】ChatGLM",
|
||||||
|
value: "ChatGLM",
|
||||||
|
api_url: "https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "【百度】文心一言",
|
||||||
|
value: "Baidu",
|
||||||
|
api_url: "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "【微软】Azure",
|
||||||
|
value: "Azure",
|
||||||
|
api_url:
|
||||||
|
"https://chat-bot-api.openai.azure.com/openai/deployments/{model}/chat/completions?api-version=2023-05-15",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "【阿里】千义通问",
|
||||||
|
value: "QWen",
|
||||||
|
api_url: "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handlePlatformChange = () => {
|
||||||
|
const obj = platformOptions.find((item) => item.value === form.value.platform);
|
||||||
|
if (obj) {
|
||||||
|
form.value.api_url = form.value.type === "img" ? obj.img_url : obj.api_url;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.content-title {
|
.content-title {
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getList, save, deleting, setStatus } from "./api";
|
import { getList, save, deleting, setStatus } from "./api";
|
||||||
import { ref } from "vue";
|
|
||||||
import ChatModelForm from "./ChatModelForm.vue";
|
import ChatModelForm from "./ChatModelForm.vue";
|
||||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message, type TableColumnData } from "@arco-design/web-vue";
|
||||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
||||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||||
// table 配置
|
// table 配置
|
||||||
const columns = [
|
const columns: TableColumnData[] = [
|
||||||
{
|
{
|
||||||
title: "所属平台",
|
title: "所属平台",
|
||||||
dataIndex: "platform",
|
dataIndex: "platform",
|
||||||
@ -49,20 +48,9 @@ const columns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 数据
|
|
||||||
const tableData = ref([]);
|
|
||||||
const getData = () => {
|
|
||||||
getList().then(({ code, data }) => {
|
|
||||||
if (code === 0) {
|
|
||||||
tableData.value = data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
getData();
|
|
||||||
|
|
||||||
// 新增编辑
|
// 新增编辑
|
||||||
const popup = useCustomFormPopup(ChatModelForm, save, {
|
const popup = useCustomFormPopup(ChatModelForm, save, {
|
||||||
popupProps: (arg) => ({ title: arg[0].record ? "编辑ApiKey" : "新增ApiKey" }),
|
popupProps: (arg) => ({ title: arg[0].record ? "编辑模型" : "新增模型" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
// 删除
|
// 删除
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
:rules="[{ required: true, message: '请输入所属平台' }]"
|
:rules="[{ required: true, message: '请输入所属平台' }]"
|
||||||
:validate-trigger="['change', 'input']"
|
:validate-trigger="['change', 'input']"
|
||||||
>
|
>
|
||||||
<a-input v-model="form.platform" placeholder="请输入所属平台" />
|
<a-select v-model="form.platform" placeholder="请输入所属平台" :options="platformOptions" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="name"
|
field="name"
|
||||||
@ -33,10 +33,12 @@
|
|||||||
:validate-trigger="['change', 'input']"
|
:validate-trigger="['change', 'input']"
|
||||||
showable
|
showable
|
||||||
>
|
>
|
||||||
<a-input-number v-model="form.weight" placeholder="请输入对话权重" />
|
<a-space>
|
||||||
<a-tooltip content="对话权重,每次对话扣减多少次对话额度" position="right">
|
<a-input-number v-model="form.weight" placeholder="请输入对话权重" />
|
||||||
<icon-info-circle-fill />
|
<a-tooltip content="对话权重,每次对话扣减多少次对话额度" position="right">
|
||||||
</a-tooltip>
|
<icon-info-circle-fill />
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item field="open" label="开放状态代理">
|
<a-form-item field="open" label="开放状态代理">
|
||||||
@ -65,15 +67,13 @@ defineExpose({
|
|||||||
form,
|
form,
|
||||||
});
|
});
|
||||||
|
|
||||||
const typeOPtions = [
|
const platformOptions = [
|
||||||
{
|
{ label: "【OpenAI】ChatGPT", value: "OpenAI" },
|
||||||
label: "聊天",
|
{ label: "【讯飞】星火大模型", value: "XunFei" },
|
||||||
value: "chart",
|
{ label: "【清华智普】ChatGLM", value: "ChatGLM" },
|
||||||
},
|
{ label: "【百度】文心一言", value: "Baidu" },
|
||||||
{
|
{ label: "【微软】Azure", value: "Azure" },
|
||||||
label: "绘图",
|
{ label: "【阿里】通义千问", value: "QWen" },
|
||||||
value: "img",
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -8,7 +8,7 @@ import app from "@/main";
|
|||||||
import { getList, message, remove } from "./api";
|
import { getList, message, remove } from "./api";
|
||||||
import ChatsLogs from "./ChatsLogs.vue";
|
import ChatsLogs from "./ChatsLogs.vue";
|
||||||
|
|
||||||
const columns: SearchTableColumns[] = [
|
const chatColumns: SearchTableColumns[] = [
|
||||||
{
|
{
|
||||||
dataIndex: "user_id",
|
dataIndex: "user_id",
|
||||||
title: "账户ID",
|
title: "账户ID",
|
||||||
@ -42,10 +42,48 @@ const columns: SearchTableColumns[] = [
|
|||||||
dataIndex: "token",
|
dataIndex: "token",
|
||||||
title: "消耗算力",
|
title: "消耗算力",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
dataIndex: "created_at",
|
||||||
|
title: "创建时间",
|
||||||
|
search: {
|
||||||
|
valueType: "range",
|
||||||
|
},
|
||||||
|
render: ({ record }) => dateFormat(record.created_at),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
fixed: "right",
|
||||||
|
slotName: "actions",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const messageColumns: SearchTableColumns[] = [
|
||||||
|
{
|
||||||
|
dataIndex: "user_id",
|
||||||
|
title: "账户ID",
|
||||||
|
search: {
|
||||||
|
valueType: "input",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "username",
|
dataIndex: "username",
|
||||||
title: "账户",
|
title: "账户",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
dataIndex: "title",
|
||||||
|
title: "标题",
|
||||||
|
search: {
|
||||||
|
valueType: "input",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: "msg_num",
|
||||||
|
title: "消息数量",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: "token",
|
||||||
|
title: "消耗算力",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "created_at",
|
dataIndex: "created_at",
|
||||||
title: "创建时间",
|
title: "创建时间",
|
||||||
@ -62,8 +100,8 @@ const columns: SearchTableColumns[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const tabsList = [
|
const tabsList = [
|
||||||
{ key: "1", title: "对话列表", api: getList, columns },
|
{ key: "1", title: "对话列表", api: getList, columns: chatColumns },
|
||||||
{ key: "2", title: "消息记录", api: message, columns },
|
{ key: "2", title: "消息记录", api: message, columns: messageColumns },
|
||||||
];
|
];
|
||||||
|
|
||||||
const activeKey = ref(tabsList[0].key);
|
const activeKey = ref(tabsList[0].key);
|
||||||
@ -101,7 +139,7 @@ const handleCheck = (record) => {
|
|||||||
content="是否删除?"
|
content="是否删除?"
|
||||||
position="left"
|
position="left"
|
||||||
type="warning"
|
type="warning"
|
||||||
:on-before-ok="() => handleRemove(record.id, reload)"
|
:on-before-ok="() => handleRemove(record.chat_id, reload)"
|
||||||
>
|
>
|
||||||
<a-link status="danger">删除</a-link>
|
<a-link status="danger">删除</a-link>
|
||||||
</a-popconfirm>
|
</a-popconfirm>
|
||||||
|
@ -8,10 +8,10 @@ export const getList = (data?: Record<string, unknown>) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const remove = (data) => {
|
export const remove = (params) => {
|
||||||
return http({
|
return http({
|
||||||
url: "/api/admin/order/remove",
|
url: "/api/admin/order/remove",
|
||||||
method: "post",
|
method: "get",
|
||||||
data
|
params
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -1,13 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getList, save, deleting, setStatus } from "./api";
|
import { getList, save, deleting, setStatus } from "./api";
|
||||||
import { ref } from "vue";
|
|
||||||
import ProductForm from "./ProductForm.vue";
|
import ProductForm from "./ProductForm.vue";
|
||||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message, type TableColumnData } from "@arco-design/web-vue";
|
||||||
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
import SimpleTable from "@/components/SimpleTable/SimpleTable.vue";
|
||||||
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
import { dateFormat } from "@chatgpt-plus/packages/utils";
|
||||||
// table 配置
|
// table 配置
|
||||||
const columns = [
|
const columns: TableColumnData[] = [
|
||||||
{
|
{
|
||||||
title: "产品名称",
|
title: "产品名称",
|
||||||
dataIndex: "name",
|
dataIndex: "name",
|
||||||
@ -59,17 +58,6 @@ const columns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 数据
|
|
||||||
const tableData = ref([]);
|
|
||||||
const getData = () => {
|
|
||||||
getList().then(({ code, data }) => {
|
|
||||||
if (code === 0) {
|
|
||||||
tableData.value = data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
getData();
|
|
||||||
|
|
||||||
// 新增编辑
|
// 新增编辑
|
||||||
const popup = useCustomFormPopup(ProductForm, save, {
|
const popup = useCustomFormPopup(ProductForm, save, {
|
||||||
popupProps: (arg) => ({ title: arg[0].record ? "编辑产品" : "新增产品" }),
|
popupProps: (arg) => ({ title: arg[0].record ? "编辑产品" : "新增产品" }),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { getList, save, deleting, setStatus } from "./api";
|
import { getList, save, deleting, setStatus } from "./api";
|
||||||
import { reactive, ref } from "vue";
|
import { reactive } from "vue";
|
||||||
import RoleForm from "./RoleForm.vue";
|
import RoleForm from "./RoleForm.vue";
|
||||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message } from "@arco-design/web-vue";
|
||||||
@ -40,17 +40,6 @@ const expandable = reactive({
|
|||||||
width: 50,
|
width: 50,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 数据
|
|
||||||
const tableData = ref([]);
|
|
||||||
const getData = () => {
|
|
||||||
getList().then(({ code, data }) => {
|
|
||||||
if (code === 0) {
|
|
||||||
tableData.value = data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
getData();
|
|
||||||
|
|
||||||
//展开行table
|
//展开行table
|
||||||
const expandColumns = [
|
const expandColumns = [
|
||||||
{
|
{
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
:rules="[{ required: true, message: '请输入角色图标' }]"
|
:rules="[{ required: true, message: '请输入角色图标' }]"
|
||||||
:validate-trigger="['change', 'input']"
|
:validate-trigger="['change', 'input']"
|
||||||
>
|
>
|
||||||
<a-input v-model="form.icon" placeholder="请输入角色图标" />
|
<CustomUploader v-model="form.icon" placeholder="请输入角色图标" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="hello_msg"
|
field="hello_msg"
|
||||||
@ -67,6 +67,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineExpose, defineProps } from "vue";
|
import { ref, defineExpose, defineProps } from "vue";
|
||||||
|
import CustomUploader from "@/components/CustomUploader.vue";
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {onMounted} from "vue";
|
import { onMounted } from "vue";
|
||||||
import {Message} from "@arco-design/web-vue";
|
import { Message } from "@arco-design/web-vue";
|
||||||
|
import CustomUploader from "@/components/CustomUploader.vue";
|
||||||
import useSubmit from "@/composables/useSubmit";
|
import useSubmit from "@/composables/useSubmit";
|
||||||
import useRequest from "@/composables/useRequest";
|
import useRequest from "@/composables/useRequest";
|
||||||
import {getConfig, modelList, save} from "./api";
|
import { getConfig, modelList, save } from "./api";
|
||||||
import SystemUploader from "./SystemUploader.vue";
|
|
||||||
|
|
||||||
const {formRef, formData: system, handleSubmit, submitting} = useSubmit({});
|
const { formRef, formData: system, handleSubmit, submitting } = useSubmit({});
|
||||||
|
|
||||||
const [getModelOptions, modelOptions, modelOptionsLoading] = useRequest(modelList);
|
const [getModelOptions, modelOptions, modelOptionsLoading] = useRequest(modelList);
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
title: [{required: true, message: "请输入网站标题"}],
|
title: [{ required: true, message: "请输入网站标题" }],
|
||||||
admin_title: [{required: true, message: "请输入控制台标题"}],
|
admin_title: [{ required: true, message: "请输入控制台标题" }],
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
await handleSubmit(
|
await handleSubmit(
|
||||||
() =>
|
() =>
|
||||||
save({
|
save({
|
||||||
key: "system",
|
key: "system",
|
||||||
config: system,
|
config: system,
|
||||||
}),
|
}),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
Message.success("保存成功");
|
Message.success("保存成功");
|
||||||
};
|
};
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
const {data} = await getConfig({key: "system"});
|
const { data } = await getConfig({ key: "system" });
|
||||||
data && Object.assign(system, data);
|
data && Object.assign(system, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,56 +41,43 @@ onMounted(async () => {
|
|||||||
<a-card :bordered="false">
|
<a-card :bordered="false">
|
||||||
<a-form ref="formRef" :model="system" :rules="rules" auto-label-width :disabled="submitting">
|
<a-form ref="formRef" :model="system" :rules="rules" auto-label-width :disabled="submitting">
|
||||||
<a-form-item label="网站标题" field="title">
|
<a-form-item label="网站标题" field="title">
|
||||||
<a-input v-model="system['title']"/>
|
<a-input v-model="system['title']" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="控制台标题" field="admin_title">
|
<a-form-item label="控制台标题" field="admin_title">
|
||||||
<a-input v-model="system['admin_title']"/>
|
<a-input v-model="system['admin_title']" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="网站Logo" field="logo">
|
<a-form-item label="注册赠送对话次数" field="user_init_calls">
|
||||||
<SystemUploader v-model="system['logo']" placeholder="推荐图片宽高比为 1:1"/>
|
<a-input v-model.number="system['init_chat_calls']" placeholder="新用户注册赠送对话次数" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="注册赠送算力" field="init_power">
|
<a-form-item label="注册赠送绘图次数" field="init_img_calls">
|
||||||
<a-input-number v-model="system['init_power']" placeholder="新用户注册赠送初始算力"/>
|
<a-input v-model.number="system['init_img_calls']" placeholder="新用户注册赠送绘图次数" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="邀请用户赠送算力" field="invite_power">
|
<a-form-item label="邀请赠送对话次数" field="invite_chat_calls">
|
||||||
<a-input-number
|
<a-input
|
||||||
v-model="system['invite_power']"
|
v-model.number="system['invite_chat_calls']"
|
||||||
placeholder="邀请新用户注册赠送算力"
|
placeholder="邀请新用户注册赠送对话次数"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="邀请赠送绘图次数" field="invite_img_calls">
|
||||||
<a-form-item label="VIP每月赠送算力" field="vip_month_power">
|
<a-input
|
||||||
<a-input-number v-model="system['vip_month_power']" placeholder="VIP用户每月赠送算力"/>
|
v-model.number="system['invite_img_calls']"
|
||||||
|
placeholder="邀请新用户注册赠送绘图次数"
|
||||||
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="MJ绘画价格" field="mj_power">
|
<a-form-item label="VIP每月对话次数" field="vip_month_calls">
|
||||||
<a-space>
|
<a-input v-model.number="system['vip_month_calls']" placeholder="VIP用户每月赠送对话次数" />
|
||||||
<a-input-number v-model="system['mj_power']" placeholder=""/>
|
|
||||||
<a-tooltip content="MidJourney 单次绘图消耗多少单位算力" position="right">
|
|
||||||
<icon-info-circle-fill size="18"/>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-space>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="SD绘画价格" field="sd_power">
|
<a-form-item label="VIP每月绘图次数" field="vip_month_img_calls">
|
||||||
<a-space>
|
<a-input
|
||||||
<a-input-number v-model="system['sd_power']" placeholder=""/>
|
v-model.number="system['vip_month_img_calls']"
|
||||||
<a-tooltip content="Stable-Diffusion 单次绘图消耗多少单位算力" position="right">
|
placeholder="VIP用户每月赠送绘图次数"
|
||||||
<icon-info-circle-fill size="18"/>
|
/>
|
||||||
</a-tooltip>
|
|
||||||
</a-space>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="DALL绘画价格" field="dall_power">
|
|
||||||
<a-space>
|
|
||||||
<a-input-number v-model="system['dall_power']" placeholder=""/>
|
|
||||||
<a-tooltip content="DALL-E-3 单次绘图消耗多少单位算力" position="right">
|
|
||||||
<icon-info-circle-fill size="18"/>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-space>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="开放注册" field="enabled_register">
|
<a-form-item label="开放注册" field="enabled_register">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-switch v-model="system['enabled_register']"/>
|
<a-switch v-model="system['enabled_register']" />
|
||||||
<a-tooltip content="关闭注册之后只能通过管理后台添加用户" position="right">
|
<a-tooltip content="关闭注册之后只能通过管理后台添加用户" position="right">
|
||||||
<icon-info-circle-fill size="18"/>
|
<icon-info-circle-fill size="18" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -102,58 +89,61 @@ onMounted(async () => {
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="启用众筹功能" field="enabled_reward">
|
<a-form-item label="启用众筹功能" field="enabled_reward">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a-switch v-model="system['enabled_reward']"/>
|
<a-switch v-model="system['enabled_reward']" />
|
||||||
<a-tooltip content="开启众筹功能允许用户使用个人微信收款码进行收款" position="right">
|
<a-tooltip content="如果关闭次功能将不在用户菜单显示众筹二维码" position="right">
|
||||||
<icon-info-circle-fill size="18"/>
|
<icon-info-circle-fill size="18" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="system['enabled_reward']">
|
<template v-if="system['enabled_reward']">
|
||||||
<a-form-item label="众筹算力单价" field="power_price">
|
<a-form-item label="单次对话价格" field="chat_call_price">
|
||||||
<a-input-number v-model="system['power_price']" placeholder="单位算力价格,如1块10个单位算力,那便填写 0.1"/>
|
<a-input v-model="system['chat_call_price']" placeholder="众筹金额跟对话次数的兑换比例" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="单次绘图价格" field="img_call_price">
|
||||||
|
<a-input v-model="system['img_call_price']" placeholder="众筹金额跟绘图次数的兑换比例" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="收款二维码" field="reward_img">
|
<a-form-item label="收款二维码" field="reward_img">
|
||||||
<SystemUploader v-model="system['reward_img']" placeholder="众筹收款二维码地址"/>
|
<CustomUploader v-model="system['reward_img']" placeholder="众筹收款二维码地址" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item label="微信客服二维码" field="wechat_card_url">
|
<a-form-item label="微信客服二维码" field="wechat_card_url">
|
||||||
<SystemUploader v-model="system['wechat_card_url']" placeholder="微信客服二维码"/>
|
<CustomUploader v-model="system['wechat_card_url']" placeholder="微信客服二维码" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="订单超时时间" field="order_pay_timeout">
|
<a-form-item label="订单超时时间" field="order_pay_timeout">
|
||||||
<a-space style="width: 100%">
|
<a-space style="width: 100%">
|
||||||
<a-input-number
|
<a-input
|
||||||
v-model="system['order_pay_timeout']"
|
v-model.number="system['order_pay_timeout']"
|
||||||
placeholder="单位:秒"
|
placeholder="单位:秒"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
<a-tooltip position="right">
|
<a-tooltip position="right">
|
||||||
<icon-info-circle-fill size="18"/>
|
<icon-info-circle-fill size="18" />
|
||||||
<template #content> 系统会定期清理超时未支付的订单<br/>默认值:900秒</template>
|
<template #content> 系统会定期清理超时未支付的订单<br />默认值:900秒 </template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="会员充值说明" field="order_pay_info_text">
|
<a-form-item label="会员充值说明" field="order_pay_info_text">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model="system['order_pay_info_text']"
|
v-model="system['order_pay_info_text']"
|
||||||
:autosize="{ minRows: 3, maxRows: 10 }"
|
:autosize="{ minRows: 3, maxRows: 10 }"
|
||||||
placeholder="请输入会员充值说明文字,比如介绍会员计划"
|
placeholder="请输入会员充值说明文字,比如介绍会员计划"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="默认AI模型" field="default_models">
|
<a-form-item label="默认AI模型" field="default_models">
|
||||||
<a-space style="width: 100%">
|
<a-space style="width: 100%">
|
||||||
<a-select
|
<a-select
|
||||||
v-model="system['default_models']"
|
v-model="system['default_models']"
|
||||||
multiple
|
multiple
|
||||||
:filterable="true"
|
:filterable="true"
|
||||||
placeholder="选择AI模型,多选"
|
placeholder="选择AI模型,多选"
|
||||||
:options="modelOptions"
|
:options="modelOptions"
|
||||||
:loading="modelOptionsLoading"
|
:loading="modelOptionsLoading"
|
||||||
:field-names="{ value: 'value', label: 'name' }"
|
:field-names="{ value: 'value', label: 'name' }"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-tooltip content="新用户注册默认开通的 AI 模型" position="right">
|
<a-tooltip content="新用户注册默认开通的 AI 模型" position="right">
|
||||||
<icon-info-circle-fill size="18"/>
|
<icon-info-circle-fill size="18" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -8,6 +8,11 @@ import { dateFormat } from "@chatgpt-plus/packages/utils";
|
|||||||
import UserPassword from "./UserPassword.vue";
|
import UserPassword from "./UserPassword.vue";
|
||||||
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
import useCustomFormPopup from "@/composables/useCustomFormPopup";
|
||||||
const columns: SearchTableColumns[] = [
|
const columns: SearchTableColumns[] = [
|
||||||
|
{
|
||||||
|
title: "用户头像",
|
||||||
|
dataIndex: "avatar",
|
||||||
|
slotName: "avatar",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "账号",
|
title: "账号",
|
||||||
dataIndex: "username",
|
dataIndex: "username",
|
||||||
@ -16,17 +21,9 @@ const columns: SearchTableColumns[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "剩余对话次数",
|
title: "剩余算力",
|
||||||
dataIndex: "calls",
|
dataIndex: "calls",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: "剩余绘图次数",
|
|
||||||
dataIndex: "img_calls",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "累计消耗tokens",
|
|
||||||
dataIndex: "total_tokens",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: "状态",
|
title: "状态",
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
@ -38,9 +35,7 @@ const columns: SearchTableColumns[] = [
|
|||||||
title: "过期时间",
|
title: "过期时间",
|
||||||
dataIndex: "expired_time",
|
dataIndex: "expired_time",
|
||||||
width: 180,
|
width: 180,
|
||||||
render: ({ record }) => {
|
slotName: "expired_time",
|
||||||
return dateFormat(record.expired_time);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "注册时间",
|
title: "注册时间",
|
||||||
@ -76,6 +71,15 @@ const handleDelete = async ({ id }: { id: string }, reload) => {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<SearchTable :request="getList" :columns="columns">
|
<SearchTable :request="getList" :columns="columns">
|
||||||
|
<template #avatar="{ record }">
|
||||||
|
<a-avatar>
|
||||||
|
<a-image :src="record.avatar" style="border-radius: 50%" />
|
||||||
|
</a-avatar>
|
||||||
|
</template>
|
||||||
|
<template #expired_time="{ record }">
|
||||||
|
<a-tag v-if="record.expired_time === 0" color="blue">长期有效</a-tag>
|
||||||
|
<template v-else>{{ dateFormat(record.expired_time) }}</template>
|
||||||
|
</template>
|
||||||
<template #actions="{ record, reload }">
|
<template #actions="{ record, reload }">
|
||||||
<a-link @click="editModal({ record, reload })">编辑</a-link>
|
<a-link @click="editModal({ record, reload })">编辑</a-link>
|
||||||
<a-popconfirm content="确定删除?" @ok="handleDelete(record, reload)">
|
<a-popconfirm content="确定删除?" @ok="handleDelete(record, reload)">
|
||||||
|
@ -20,17 +20,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
field="calls"
|
field="calls"
|
||||||
label="对话次数"
|
label="用户算力"
|
||||||
:rules="[{ required: true, message: '请输入对话次数' }]"
|
:rules="[{ required: true, message: '请输入用户算力' }]"
|
||||||
>
|
>
|
||||||
<a-input-number v-model="form.calls" placeholder="请输入对话次数" />
|
<a-input-number v-model="form.calls" placeholder="请输入用户算力" />
|
||||||
</a-form-item>
|
|
||||||
<a-form-item
|
|
||||||
field="img_calls"
|
|
||||||
label="绘图次数"
|
|
||||||
:rules="[{ required: true, message: '请输入绘图次数' }]"
|
|
||||||
>
|
|
||||||
<a-input-number v-model="form.img_calls" placeholder="请输入绘图次数" />
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item field="expired_time" label="有效期">
|
<a-form-item field="expired_time" label="有效期">
|
||||||
<a-date-picker v-model="form.expired_time" placeholder="请选择有效期" />
|
<a-date-picker v-model="form.expired_time" placeholder="请选择有效期" />
|
||||||
|
@ -2,137 +2,115 @@ html,
|
|||||||
body,
|
body,
|
||||||
#app,
|
#app,
|
||||||
.wrapper {
|
.wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home a {
|
.admin-home a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box {
|
.admin-home .content-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 250px;
|
left: 250px;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
/*padding-bottom: 30px;*/
|
padding-bottom: 30px;
|
||||||
-webkit-transition: left 0.3s ease-in-out;
|
-webkit-transition: left 0.3s ease-in-out;
|
||||||
transition: left 0.3s ease-in-out;
|
transition: left 0.3s ease-in-out;
|
||||||
background: #f0f0f0;
|
background: #f0f0f0;
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content {
|
.admin-home .content-box .content {
|
||||||
width: auto;
|
width: auto;
|
||||||
padding: 10px;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
padding: 10px;
|
||||||
/*BaseForm*/
|
overflow-y: scroll;
|
||||||
|
box-sizing: border-box;
|
||||||
|
/*BaseForm*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .container {
|
.admin-home .content-box .content .container {
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .container .handle-box {
|
.admin-home .content-box .content .container .handle-box {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .crumbs {
|
.admin-home .content-box .content .crumbs {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .el-table th {
|
.admin-home .content-box .content .el-table th {
|
||||||
background-color: #f5f7fa !important;
|
background-color: #f5f7fa !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .pagination {
|
.admin-home .content-box .content .pagination {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .plugins-tips {
|
.admin-home .content-box .content .plugins-tips {
|
||||||
padding: 20px 10px;
|
padding: 20px 10px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .el-button + .el-tooltip {
|
.admin-home .content-box .content .el-button + .el-tooltip {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .el-table tr:hover {
|
.admin-home .content-box .content .el-table tr:hover {
|
||||||
background: #f6faff;
|
background: #f6faff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .mgb20 {
|
.admin-home .content-box .content .mgb20 {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .move-enter-active,
|
.admin-home .content-box .content .move-enter-active,
|
||||||
.admin-home .content-box .content .move-leave-active {
|
.admin-home .content-box .content .move-leave-active {
|
||||||
transition: opacity 0.1s ease;
|
transition: opacity 0.1s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .move-enter-from,
|
.admin-home .content-box .content .move-enter-from,
|
||||||
.admin-home .content-box .content .move-leave-to {
|
.admin-home .content-box .content .move-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .form-box {
|
.admin-home .content-box .content .form-box {
|
||||||
width: 600px;
|
width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .form-box .line {
|
.admin-home .content-box .content .form-box .line {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .el-time-panel__content::after,
|
.admin-home .content-box .content .el-time-panel__content::after,
|
||||||
.admin-home .content-box .content .el-time-panel__content::before {
|
.admin-home .content-box .content .el-time-panel__content::before {
|
||||||
margin-top: -7px;
|
margin-top: -7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
|
.admin-home .content-box .content .el-time-spinner__wrapper .el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default) {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content [class*=" el-icon-"],
|
.admin-home .content-box .content [class*=" el-icon-"],
|
||||||
.admin-home .content-box .content [class^=el-icon-] {
|
.admin-home .content-box .content [class^=el-icon-] {
|
||||||
speak: none;
|
speak: none;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-variant: normal;
|
font-variant: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content .el-sub-menu [class^=el-icon-] {
|
.admin-home .content-box .content .el-sub-menu [class^=el-icon-] {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-box .content [hidden] {
|
.admin-home .content-box .content [hidden] {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-home .content-collapse {
|
.admin-home .content-collapse {
|
||||||
left: 65px;
|
left: 65px;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
VUE_APP_API_HOST=http://172.22.11.2:5678
|
VUE_APP_API_HOST=http://localhost:5678
|
||||||
VUE_APP_WS_HOST=ws://172.22.11.2:5678
|
VUE_APP_WS_HOST=ws://localhost:5678
|
||||||
VUE_APP_USER=18575670125
|
VUE_APP_USER=18575670125
|
||||||
VUE_APP_PASS=12345678
|
VUE_APP_PASS=12345678
|
||||||
VUE_APP_ADMIN_USER=admin
|
VUE_APP_ADMIN_USER=admin
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.el-form-item__content .tip-input .input {
|
.el-form-item__content .tip-input .el-input {
|
||||||
width: 100%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
.el-form-item__content .tip-input .info {
|
.el-form-item__content .tip-input .info {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,13 @@
|
|||||||
display flex
|
display flex
|
||||||
width 100%
|
width 100%
|
||||||
|
|
||||||
.input {
|
.el-input {
|
||||||
width 100%
|
width 50%
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
margin-left 6px
|
margin-left 6px
|
||||||
|
margin-top 2px
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -342,14 +342,14 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 获取系统配置
|
// 获取系统配置
|
||||||
httpGet("/api/admin/config/get?key=system").then(res => {
|
httpGet("/api/config/get?key=system").then(res => {
|
||||||
title.value = res.data.title
|
title.value = res.data.title
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
ElMessage.error("获取系统配置失败:" + e.message)
|
ElMessage.error("获取系统配置失败:" + e.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取系统公告
|
// 获取系统公告
|
||||||
httpGet("/api/admin/config/get?key=notice").then(res => {
|
httpGet("/api/config/get?key=notice").then(res => {
|
||||||
notice.value = md.render(res.data['content'])
|
notice.value = md.render(res.data['content'])
|
||||||
const oldNotice = localStorage.getItem(noticeKey.value);
|
const oldNotice = localStorage.getItem(noticeKey.value);
|
||||||
// 如果公告有更新,则显示公告
|
// 如果公告有更新,则显示公告
|
||||||
|
@ -129,7 +129,7 @@ onMounted(() => {
|
|||||||
ElMessage.error("获取邀请码失败:" + e.message)
|
ElMessage.error("获取邀请码失败:" + e.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
httpGet("/api/admin/config/get?key=system").then(res => {
|
httpGet("/api/config/get?key=system").then(res => {
|
||||||
inviteChatCalls.value = res.data["invite_chat_calls"]
|
inviteChatCalls.value = res.data["invite_chat_calls"]
|
||||||
inviteImgCalls.value = res.data["invite_img_calls"]
|
inviteImgCalls.value = res.data["invite_img_calls"]
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
@ -227,7 +227,7 @@ onMounted(() => {
|
|||||||
router.push("/login")
|
router.push("/login")
|
||||||
})
|
})
|
||||||
|
|
||||||
httpGet("/api/admin/config/get?key=system").then(res => {
|
httpGet("/api/config/get?key=system").then(res => {
|
||||||
rewardImg.value = res.data['reward_img']
|
rewardImg.value = res.data['reward_img']
|
||||||
enableReward.value = res.data['enabled_reward']
|
enableReward.value = res.data['enabled_reward']
|
||||||
orderPayInfoText.value = res.data['order_pay_info_text']
|
orderPayInfoText.value = res.data['order_pay_info_text']
|
||||||
|
@ -142,7 +142,7 @@ const wxImg = ref("/images/wx.png")
|
|||||||
const ways = []
|
const ways = []
|
||||||
const placeholder = ref("用户名:")
|
const placeholder = ref("用户名:")
|
||||||
|
|
||||||
httpGet("/api/admin/config/get?key=system").then(res => {
|
httpGet("/api/config/get?key=system").then(res => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
const registerWays = res.data['register_ways']
|
const registerWays = res.data['register_ways']
|
||||||
if (arrayContains(registerWays, "mobile")) {
|
if (arrayContains(registerWays, "mobile")) {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div class="handle-box">
|
<div class="handle-box">
|
||||||
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
|
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
|
||||||
<a href="https://gpt.bemore.lol" target="_blank" style="margin-left: 10px">
|
<a href="https://api.chat-plus.net" target="_blank" style="margin-left: 10px">
|
||||||
<el-button type="success" :icon="ShoppingCart" @click="add" plain>购买API-KEY</el-button>
|
<el-button type="success" :icon="ShoppingCart" @click="add" plain>购买API-KEY</el-button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -12,13 +12,20 @@
|
|||||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto">
|
<el-table :data="items" :row-key="row => row.id" table-layout="auto">
|
||||||
<el-table-column prop="platform" label="所属平台"/>
|
<el-table-column prop="platform" label="所属平台"/>
|
||||||
<el-table-column prop="name" label="名称"/>
|
<el-table-column prop="name" label="名称"/>
|
||||||
<el-table-column prop="value" label="KEY">
|
<el-table-column prop="value" label="API KEY">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tooltip class="box-item"
|
<span>{{ substr(scope.row.value, 20) }}</span>
|
||||||
effect="dark"
|
<el-icon class="copy-key" :data-clipboard-text="scope.row.value">
|
||||||
:content="scope.row.api_url"
|
<DocumentCopy/>
|
||||||
placement="top">{{ scope.row.value }}
|
</el-icon>
|
||||||
</el-tooltip>
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="api_url" label="API URL">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ substr(scope.row.api_url, 30) }}</span>
|
||||||
|
<el-icon class="copy-key" :data-clipboard-text="scope.row.api_url">
|
||||||
|
<DocumentCopy/>
|
||||||
|
</el-icon>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="type" label="用途">
|
<el-table-column prop="type" label="用途">
|
||||||
@ -27,11 +34,7 @@
|
|||||||
<el-tag v-else-if="scope.row.type === 'img'" type="success">绘图</el-tag>
|
<el-tag v-else-if="scope.row.type === 'img'" type="success">绘图</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="use_proxy" label="使用代理">
|
<el-table-column prop="proxy_url" label="代理地址"/>
|
||||||
<template #default="scope">
|
|
||||||
<el-switch v-model="scope.row['use_proxy']" @change="set('use_proxy',scope.row)"/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column label="最后使用时间">
|
<el-table-column label="最后使用时间">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@ -99,18 +102,8 @@
|
|||||||
placeholder="如果你用了第三方的 API 中转,这里填写中转地址"/>
|
placeholder="如果你用了第三方的 API 中转,这里填写中转地址"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="使用代理:" prop="use_proxy">
|
<el-form-item label="代理地址:" prop="proxy_url">
|
||||||
<el-switch v-model="item.use_proxy"/>
|
<el-input v-model="item.proxy_url" autocomplete="off"/>
|
||||||
<el-tooltip
|
|
||||||
effect="dark"
|
|
||||||
content="是否使用代理访问 API URL,OpenAI 官方API需要开启代理访问"
|
|
||||||
raw-content
|
|
||||||
placement="right"
|
|
||||||
>
|
|
||||||
<el-icon>
|
|
||||||
<InfoFilled/>
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="启用状态:" prop="enable">
|
<el-form-item label="启用状态:" prop="enable">
|
||||||
@ -129,11 +122,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {reactive, ref} from "vue";
|
import {onMounted, onUnmounted, 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 {dateFormat, disabledDate, removeArrayItem} from "@/utils/libs";
|
import {dateFormat, disabledDate, removeArrayItem, substr} from "@/utils/libs";
|
||||||
import {InfoFilled, Plus, ShoppingCart} from "@element-plus/icons-vue";
|
import {DocumentCopy, InfoFilled, Plus, ShoppingCart} from "@element-plus/icons-vue";
|
||||||
|
import ClipboardJS from "clipboard";
|
||||||
|
|
||||||
// 变量定义
|
// 变量定义
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
@ -150,10 +144,10 @@ const formRef = ref(null)
|
|||||||
const title = ref("")
|
const title = ref("")
|
||||||
const platforms = ref([
|
const platforms = ref([
|
||||||
{
|
{
|
||||||
name: "【OpenAI】ChatGPT",
|
name: "【OpenAI/中转】ChatGPT",
|
||||||
value: "OpenAI",
|
value: "OpenAI",
|
||||||
api_url: "https://gpt.bemore.lol/v1/chat/completions",
|
api_url: "https://api.chat-plus.net/v1/chat/completions",
|
||||||
img_url: "https://gpt.bemore.lol/v1/images/generations"
|
img_url: "https://api.chat-plus.net/v1/images/generations"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "【讯飞】星火大模型",
|
name: "【讯飞】星火大模型",
|
||||||
@ -186,6 +180,23 @@ const types = ref([
|
|||||||
{name: "绘画", value: "img"},
|
{name: "绘画", value: "img"},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
const clipboard = ref(null)
|
||||||
|
onMounted(() => {
|
||||||
|
clipboard.value = new ClipboardJS('.copy-key');
|
||||||
|
clipboard.value.on('success', () => {
|
||||||
|
ElMessage.success('复制成功!');
|
||||||
|
})
|
||||||
|
|
||||||
|
clipboard.value.on('error', () => {
|
||||||
|
ElMessage.error('复制失败!');
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clipboard.value.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
httpGet('/api/admin/apikey/list').then((res) => {
|
httpGet('/api/admin/apikey/list').then((res) => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
@ -269,7 +280,6 @@ const changePlatform = () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@ -285,6 +295,11 @@ const changePlatform = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-key {
|
||||||
|
margin-left 5px
|
||||||
|
cursor pointer
|
||||||
|
}
|
||||||
|
|
||||||
.el-select {
|
.el-select {
|
||||||
width: 100%
|
width: 100%
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,18 @@
|
|||||||
<el-table :data="data.chat.items" :row-key="row => row.id" table-layout="auto">
|
<el-table :data="data.chat.items" :row-key="row => row.id" table-layout="auto">
|
||||||
<el-table-column prop="user_id" label="账户ID"/>
|
<el-table-column prop="user_id" label="账户ID"/>
|
||||||
<el-table-column prop="username" label="账户"/>
|
<el-table-column prop="username" label="账户"/>
|
||||||
<el-table-column prop="title" label="标题"/>
|
<el-table-column label="图标">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-avatar :size="30" :src="scope.row.role.icon"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="角色">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ scope.row.role.name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="model" label="模型"/>
|
<el-table-column prop="model" label="模型"/>
|
||||||
|
<el-table-column prop="title" label="标题"/>
|
||||||
<el-table-column prop="msg_num" label="消息数量"/>
|
<el-table-column prop="msg_num" label="消息数量"/>
|
||||||
<el-table-column prop="token" label="消耗算力"/>
|
<el-table-column prop="token" label="消耗算力"/>
|
||||||
|
|
||||||
|
@ -14,7 +14,10 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="name" label="模型名称"/>
|
<el-table-column prop="name" label="模型名称"/>
|
||||||
<el-table-column prop="value" label="模型值"/>
|
<el-table-column prop="value" label="模型值"/>
|
||||||
<el-table-column prop="weight" label="对话权重"/>
|
<el-table-column prop="power" label="费率"/>
|
||||||
|
<el-table-column prop="max_tokens" label="最大响应长度"/>
|
||||||
|
<el-table-column prop="max_context" label="最大上下文长度"/>
|
||||||
|
<el-table-column prop="temperature" label="创意度"/>
|
||||||
<el-table-column prop="enabled" label="启用状态">
|
<el-table-column prop="enabled" label="启用状态">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-switch v-model="scope.row['enabled']" @change="modelSet('enabled',scope.row)"/>
|
<el-switch v-model="scope.row['enabled']" @change="modelSet('enabled',scope.row)"/>
|
||||||
@ -69,16 +72,15 @@
|
|||||||
<el-input v-model="item.value" autocomplete="off"/>
|
<el-input v-model="item.value" autocomplete="off"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="对话权重:" prop="weight">
|
<el-form-item label="费率:" prop="weight">
|
||||||
|
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="tip-input">
|
<div class="tip-input">
|
||||||
<el-input-number :min="1" v-model="item.weight" autocomplete="off"/>
|
<el-input-number :min="1" v-model="item.power" autocomplete="off"/>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
content="对话权重,每次对话扣减多少次对话额度"
|
content="每次对话扣减多少单位算力"
|
||||||
placement="right"
|
placement="right"
|
||||||
>
|
>
|
||||||
<el-icon>
|
<el-icon>
|
||||||
@ -90,11 +92,78 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="最长响应:" prop="max_tokens">
|
||||||
|
<el-input v-model.number="item.max_tokens" autocomplete="off" placeholder="模型最大响应长度"/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="最大上下文:" prop="max_context">
|
||||||
|
<div class="tip-input">
|
||||||
|
<el-input v-model.number="item.max_context" autocomplete="off" placeholder="模型最大上下文长度"/>
|
||||||
|
<div class="info">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
raw-content
|
||||||
|
content="gpt-3.5-turbo:4096 <br/>
|
||||||
|
gpt-3.5-turbo-16k: 16384 <br/>
|
||||||
|
gpt-4: 8192 <br/>
|
||||||
|
gpt-4-32k: 32768 <br/>
|
||||||
|
chatglm_pro: 32768 <br/>
|
||||||
|
chatglm_std: 16384 <br/>
|
||||||
|
chatglm_lite: 4096 <br/>
|
||||||
|
qwen-turbo: 8192 <br/>
|
||||||
|
qwen-plus: 32768 <br/>
|
||||||
|
文心一言: 8192 <br/>
|
||||||
|
星火1.0: 4096 <br/>
|
||||||
|
星火2.0-星火3.5: 8192"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<InfoFilled/>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="创意度:" prop="temperature">
|
||||||
|
<div class="tip-input">
|
||||||
|
<el-input v-model="item.temperature" autocomplete="off" placeholder="模型创意度"/>
|
||||||
|
<div class="info">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="OpenAI 0-2,其他模型 0-1"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<InfoFilled/>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="启用状态:" prop="enable">
|
<el-form-item label="启用状态:" prop="enable">
|
||||||
<el-switch v-model="item.enabled"/>
|
<el-switch v-model="item.enabled"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="开放状态:" prop="open">
|
<el-form-item label="开放状态:" prop="open">
|
||||||
<el-switch v-model="item.open"/>
|
<div class="tip-input">
|
||||||
|
<el-switch v-model="item.open"/>
|
||||||
|
<div class="info">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
raw-content
|
||||||
|
content="开放后,该模型将对所有用户可见。<br/> 如果模型没有启用,则当前设置无效。"
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<el-icon>
|
||||||
|
<InfoFilled/>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
@ -28,9 +28,9 @@
|
|||||||
<el-table-column prop="username" label="下单用户"/>
|
<el-table-column prop="username" label="下单用户"/>
|
||||||
<el-table-column prop="subject" label="产品名称"/>
|
<el-table-column prop="subject" label="产品名称"/>
|
||||||
<el-table-column prop="amount" label="订单金额"/>
|
<el-table-column prop="amount" label="订单金额"/>
|
||||||
<el-table-column label="调用次数">
|
<el-table-column label="充值算力">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ scope.row.remark?.calls }}</span>
|
<span>{{ scope.row.remark?.power }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
|
@ -20,8 +20,7 @@
|
|||||||
<span v-else>{{ scope.row.days }}</span>
|
<span v-else>{{ scope.row.days }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="calls" label="对话次数"/>
|
<el-table-column prop="power" label="算力"/>
|
||||||
<el-table-column prop="img_calls" label="绘图次数"/>
|
|
||||||
<el-table-column prop="sales" label="销量"/>
|
<el-table-column prop="sales" label="销量"/>
|
||||||
<el-table-column prop="enabled" label="启用状态">
|
<el-table-column prop="enabled" label="启用状态">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@ -71,12 +70,8 @@
|
|||||||
<el-input v-model.number="item.days" autocomplete="off" placeholder="会员有效期(天)"/>
|
<el-input v-model.number="item.days" autocomplete="off" placeholder="会员有效期(天)"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="对话次数:" prop="calls">
|
<el-form-item label="算力:" prop="power">
|
||||||
<el-input v-model.number="item.calls" autocomplete="off" placeholder="增加对话次数"/>
|
<el-input v-model.number="item.power" autocomplete="off" placeholder="增加算力值"/>
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="绘图次数:" prop="img_calls">
|
|
||||||
<el-input v-model.number="item.img_calls" autocomplete="off" placeholder="增加绘图次数"/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="启用状态:" prop="enable">
|
<el-form-item label="启用状态:" prop="enable">
|
||||||
|
@ -11,23 +11,30 @@
|
|||||||
<el-form-item label="控制台标题" prop="admin_title">
|
<el-form-item label="控制台标题" prop="admin_title">
|
||||||
<el-input v-model="system['admin_title']"/>
|
<el-input v-model="system['admin_title']"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="注册赠送对话次数" prop="user_init_calls">
|
<el-form-item label="网站 LOGO" prop="logo">
|
||||||
<el-input v-model.number="system['init_chat_calls']" placeholder="新用户注册赠送对话次数"/>
|
<el-input v-model="system['logo']" placeholder="网站LOGO图片">
|
||||||
|
<template #append>
|
||||||
|
<el-upload
|
||||||
|
:auto-upload="true"
|
||||||
|
:show-file-list="false"
|
||||||
|
@click="beforeUpload('logo')"
|
||||||
|
:http-request="uploadImg"
|
||||||
|
>
|
||||||
|
<el-icon class="uploader-icon">
|
||||||
|
<UploadFilled/>
|
||||||
|
</el-icon>
|
||||||
|
</el-upload>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="注册赠送绘图次数" prop="init_img_calls">
|
<el-form-item label="注册赠送算力" prop="init_power">
|
||||||
<el-input v-model.number="system['init_img_calls']" placeholder="新用户注册赠送绘图次数"/>
|
<el-input v-model.number="system['init_power']" placeholder="新用户注册赠送算力"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="邀请赠送对话次数" prop="invite_chat_calls">
|
<el-form-item label="邀请赠送算力" prop="invite_power">
|
||||||
<el-input v-model.number="system['invite_chat_calls']" placeholder="邀请新用户注册赠送对话次数"/>
|
<el-input v-model.number="system['invite_power']" placeholder="邀请新用户注册赠送算力"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="邀请赠送绘图次数" prop="invite_img_calls">
|
<el-form-item label="VIP每月赠送算力" prop="vip_month_power">
|
||||||
<el-input v-model.number="system['invite_img_calls']" placeholder="邀请新用户注册赠送绘图次数"/>
|
<el-input v-model.number="system['vip_month_power']" placeholder="VIP用户每月赠送算力"/>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="VIP每月对话次数" prop="vip_month_calls">
|
|
||||||
<el-input v-model.number="system['vip_month_calls']" placeholder="VIP用户每月赠送对话次数"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="VIP每月绘图次数" prop="vip_month_img_calls">
|
|
||||||
<el-input v-model.number="system['vip_month_img_calls']" placeholder="VIP用户每月赠送绘图次数"/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="开放注册" prop="enabled_register">
|
<el-form-item label="开放注册" prop="enabled_register">
|
||||||
@ -66,11 +73,9 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<div v-if="system['enabled_reward']">
|
<div v-if="system['enabled_reward']">
|
||||||
<el-form-item label="单次对话价格" prop="chat_call_price">
|
<el-form-item label="算力单价" prop="power_price">
|
||||||
<el-input v-model="system['chat_call_price']" placeholder="众筹金额跟对话次数的兑换比例"/>
|
<el-input v-model="system['power_price']"
|
||||||
</el-form-item>
|
placeholder="单位算力的价格,比如设置 0.1 表示捐赠1元钱可以得到10个单位算力"/>
|
||||||
<el-form-item label="单次绘图价格" prop="img_call_price">
|
|
||||||
<el-input v-model="system['img_call_price']" placeholder="众筹金额跟绘图次数的兑换比例"/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="收款二维码" prop="reward_img">
|
<el-form-item label="收款二维码" prop="reward_img">
|
||||||
<el-input v-model="system['reward_img']" placeholder="众筹收款二维码地址">
|
<el-input v-model="system['reward_img']" placeholder="众筹收款二维码地址">
|
||||||
@ -165,82 +170,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" @click="save('system')">保存</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="模型配置" name="chat">
|
|
||||||
<div class="container">
|
|
||||||
<el-form :model="chat" label-position="right" label-width="150px" ref="chatFormRef" :rules="rules">
|
|
||||||
<el-form-item label="开启聊天上下文">
|
<el-form-item label="开启聊天上下文">
|
||||||
<el-switch v-model="chat['enable_context']"/>
|
<el-switch v-model="system['enable_context']"/>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="保存聊天记录">
|
|
||||||
<el-switch v-model="chat['enable_history']"/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="会话上下文深度">
|
<el-form-item label="会话上下文深度">
|
||||||
<div style="width:100%">
|
<div style="width:100%">
|
||||||
<el-input-number v-model="chat['context_deep']" :min="0" :max="10"/>
|
<el-input-number v-model="system['context_deep']" :min="0" :max="10"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="tip" style="margin-top: 10px; ">会话上下文深度:在老会话中继续会话,默认加载多少条聊天记录作为上下文。如果设置为
|
<div class="tip" style="margin-top: 10px; ">会话上下文深度:在老会话中继续会话,默认加载多少条聊天记录作为上下文。如果设置为
|
||||||
0
|
0
|
||||||
则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数最好设置需要为偶数,否则将无法兼容百度的 API。
|
则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数最好设置需要为偶数,否则将无法兼容百度的 API。
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
<el-divider content-position="center">OpenAI</el-divider>
|
<el-button type="primary" @click="save('system')">保存</el-button>
|
||||||
<el-form-item label="模型创意度">
|
|
||||||
<el-slider v-model="chat['open_ai']['temperature']" :max="2" :step="0.1"/>
|
|
||||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="最大响应长度">
|
|
||||||
<el-input v-model.number="chat['open_ai']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-divider content-position="center">Azure</el-divider>
|
|
||||||
<el-form-item label="模型创意度">
|
|
||||||
<el-slider v-model="chat['azure']['temperature']" :max="2" :step="0.1"/>
|
|
||||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="最大响应长度">
|
|
||||||
<el-input v-model.number="chat['azure']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-divider content-position="center">ChatGLM</el-divider>
|
|
||||||
<el-form-item label="模型创意度">
|
|
||||||
<el-slider v-model="chat['chat_gml']['temperature']" :max="1" :step="0.01"/>
|
|
||||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="最大响应长度">
|
|
||||||
<el-input v-model.number="chat['chat_gml']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-divider content-position="center">文心一言</el-divider>
|
|
||||||
<el-form-item label="模型创意度">
|
|
||||||
<el-slider v-model="chat['baidu']['temperature']" :max="1" :step="0.01"/>
|
|
||||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="最大响应长度">
|
|
||||||
<el-input v-model.number="chat['baidu']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-divider content-position="center">讯飞星火</el-divider>
|
|
||||||
<el-form-item label="模型创意度">
|
|
||||||
<el-slider v-model="chat['xun_fei']['temperature']" :max="1" :step="0.1"/>
|
|
||||||
<div class="tip">值越大 AI 回答越发散,值越小回答越保守,建议保持默认值</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="最大响应长度">
|
|
||||||
<el-input v-model.number="chat['xun_fei']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-divider content-position="center">AI绘图</el-divider>
|
|
||||||
<el-form-item label="DALL-E3出图数量">
|
|
||||||
<el-input v-model.number="chat['dall_img_num']" placeholder="调用 DALL E3 API 传入的出图数量"/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item style="text-align: right">
|
|
||||||
<el-button type="primary" @click="save('chat')">保存</el-button>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,9 +17,7 @@
|
|||||||
<el-image v-if="scope.row.vip" :src="vipImg" style="height: 20px;position: relative; top:5px; left: 5px"/>
|
<el-image v-if="scope.row.vip" :src="vipImg" style="height: 20px;position: relative; top:5px; left: 5px"/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="calls" label="剩余对话次数"/>
|
<el-table-column prop="power" label="剩余算力"/>
|
||||||
<el-table-column prop="img_calls" label="剩余绘图次数"/>
|
|
||||||
<el-table-column prop="total_tokens" label="累计消耗tokens"/>
|
|
||||||
<el-table-column label="状态" width="80">
|
<el-table-column label="状态" width="80">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.status" type="success">正常</el-tag>
|
<el-tag v-if="scope.row.status" type="success">正常</el-tag>
|
||||||
@ -76,11 +74,8 @@
|
|||||||
<el-form-item v-if="add" label="密码:" prop="password">
|
<el-form-item v-if="add" label="密码:" prop="password">
|
||||||
<el-input v-model="user.password" autocomplete="off"/>
|
<el-input v-model="user.password" autocomplete="off"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="对话次数:" prop="calls">
|
<el-form-item label="剩余算力:" prop="power">
|
||||||
<el-input v-model.number="user.calls" autocomplete="off" placeholder="0"/>
|
<el-input v-model.number="user.power" autocomplete="off" placeholder="0"/>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="绘图次数:" prop="img_calls">
|
|
||||||
<el-input v-model.number="user['img_calls']" autocomplete="off" placeholder="0"/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="有效期:" prop="expired_time">
|
<el-form-item label="有效期:" prop="expired_time">
|
||||||
|
@ -149,7 +149,7 @@ onMounted(() => {
|
|||||||
showFailToast("获取产品套餐失败:" + e.message)
|
showFailToast("获取产品套餐失败:" + e.message)
|
||||||
})
|
})
|
||||||
|
|
||||||
httpGet("/api/admin/config/get?key=system").then(res => {
|
httpGet("/api/config/get?key=system").then(res => {
|
||||||
vipMonthCalls.value = res.data['vip_month_calls']
|
vipMonthCalls.value = res.data['vip_month_calls']
|
||||||
vipMonthImgCalls.value = res.data['vip_month_img_calls']
|
vipMonthImgCalls.value = res.data['vip_month_img_calls']
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
Loading…
Reference in New Issue
Block a user