mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-11-09 18:53:43 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b14e141d0 | ||
|
|
9cbc6c91c4 | ||
|
|
21c3a419a5 |
@@ -67,8 +67,10 @@ var ModelToTokens = map[string]int{
|
|||||||
"gpt-3.5-turbo-16k": 16384,
|
"gpt-3.5-turbo-16k": 16384,
|
||||||
"gpt-4": 8192,
|
"gpt-4": 8192,
|
||||||
"gpt-4-32k": 32768,
|
"gpt-4-32k": 32768,
|
||||||
"chatglm_pro": 32768,
|
"chatglm_pro": 32768, // 清华智普
|
||||||
"chatglm_std": 16384,
|
"chatglm_std": 16384,
|
||||||
"chatglm_lite": 4096,
|
"chatglm_lite": 4096,
|
||||||
"ernie_bot_turbo": 8192, // 文心一言
|
"ernie_bot_turbo": 8192, // 文心一言
|
||||||
|
"general": 8192, // 科大讯飞
|
||||||
|
"general2": 8192,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ func (wc *WsClient) Send(message []byte) error {
|
|||||||
return wc.Conn.WriteMessage(wc.mt, message)
|
return wc.Conn.WriteMessage(wc.mt, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wc *WsClient) SendJson(value interface{}) error {
|
||||||
|
wc.lock.Lock()
|
||||||
|
defer wc.lock.Unlock()
|
||||||
|
|
||||||
|
if wc.Closed {
|
||||||
|
return ErrConClosed
|
||||||
|
}
|
||||||
|
return wc.Conn.WriteJSON(value)
|
||||||
|
}
|
||||||
|
|
||||||
func (wc *WsClient) Receive() (int, []byte, error) {
|
func (wc *WsClient) Receive() (int, []byte, error) {
|
||||||
if wc.Closed {
|
if wc.Closed {
|
||||||
return 0, nil, ErrConClosed
|
return 0, nil, ErrConClosed
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ type ChatConfig struct {
|
|||||||
Azure ModelAPIConfig `json:"azure"`
|
Azure ModelAPIConfig `json:"azure"`
|
||||||
ChatGML ModelAPIConfig `json:"chat_gml"`
|
ChatGML ModelAPIConfig `json:"chat_gml"`
|
||||||
Baidu ModelAPIConfig `json:"baidu"`
|
Baidu ModelAPIConfig `json:"baidu"`
|
||||||
|
XunFei ModelAPIConfig `json:"xun_fei"`
|
||||||
|
|
||||||
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
|
EnableContext bool `json:"enable_context"` // 是否开启聊天上下文
|
||||||
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
|
EnableHistory bool `json:"enable_history"` // 是否允许保存聊天记录
|
||||||
@@ -92,6 +93,7 @@ const OpenAI = Platform("OpenAI")
|
|||||||
const Azure = Platform("Azure")
|
const Azure = Platform("Azure")
|
||||||
const ChatGLM = Platform("ChatGLM")
|
const ChatGLM = Platform("ChatGLM")
|
||||||
const Baidu = Platform("Baidu")
|
const Baidu = Platform("Baidu")
|
||||||
|
const XunFei = Platform("XunFei")
|
||||||
|
|
||||||
// UserChatConfig 用户的聊天配置
|
// UserChatConfig 用户的聊天配置
|
||||||
type UserChatConfig struct {
|
type UserChatConfig struct {
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"chatplus/core/types"
|
|
||||||
"chatplus/store/model"
|
|
||||||
"chatplus/store/vo"
|
|
||||||
"chatplus/utils"
|
|
||||||
"chatplus/utils/resp"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update 更新会话标题
|
|
||||||
func (h *ChatHandler) Update(c *gin.Context) {
|
|
||||||
var data struct {
|
|
||||||
Id uint `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
}
|
|
||||||
if err := c.ShouldBindJSON(&data); err != nil {
|
|
||||||
resp.ERROR(c, types.InvalidArgs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var m = model.ChatItem{}
|
|
||||||
m.Id = data.Id
|
|
||||||
res := h.db.Model(&m).UpdateColumn("title", data.Title)
|
|
||||||
if res.Error != nil {
|
|
||||||
resp.ERROR(c, "Failed to update database")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.SUCCESS(c, types.OkMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// History 获取聊天历史记录
|
|
||||||
func (h *ChatHandler) History(c *gin.Context) {
|
|
||||||
chatId := c.Query("chat_id") // 会话 ID
|
|
||||||
var items []model.HistoryMessage
|
|
||||||
var messages = make([]vo.HistoryMessage, 0)
|
|
||||||
res := h.db.Where("chat_id = ?", chatId).Find(&items)
|
|
||||||
if res.Error != nil {
|
|
||||||
resp.ERROR(c, "No history message")
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
for _, item := range items {
|
|
||||||
var v vo.HistoryMessage
|
|
||||||
err := utils.CopyObject(item, &v)
|
|
||||||
v.CreatedAt = item.CreatedAt.Unix()
|
|
||||||
v.UpdatedAt = item.UpdatedAt.Unix()
|
|
||||||
if err == nil {
|
|
||||||
messages = append(messages, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.SUCCESS(c, messages)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear 清空所有聊天记录
|
|
||||||
func (h *ChatHandler) Clear(c *gin.Context) {
|
|
||||||
// 获取当前登录用户所有的聊天会话
|
|
||||||
user, err := utils.GetLoginUser(c, h.db)
|
|
||||||
if err != nil {
|
|
||||||
resp.NotAuth(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var chats []model.ChatItem
|
|
||||||
res := h.db.Where("user_id = ?", user.Id).Find(&chats)
|
|
||||||
if res.Error != nil {
|
|
||||||
resp.ERROR(c, "No chats found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var chatIds = make([]string, 0)
|
|
||||||
for _, chat := range chats {
|
|
||||||
chatIds = append(chatIds, chat.ChatId)
|
|
||||||
// 清空会话上下文
|
|
||||||
h.App.ChatContexts.Delete(chat.ChatId)
|
|
||||||
}
|
|
||||||
err = h.db.Transaction(func(tx *gorm.DB) error {
|
|
||||||
res := h.db.Where("user_id =?", user.Id).Delete(&model.ChatItem{})
|
|
||||||
if res.Error != nil {
|
|
||||||
return res.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
res = h.db.Where("user_id = ? AND chat_id IN ?", user.Id, chatIds).Delete(&model.HistoryMessage{})
|
|
||||||
if res.Error != nil {
|
|
||||||
return res.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 是否要删除 MidJourney 绘画记录和图片文件?
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Error with delete chats: %+v", err)
|
|
||||||
resp.ERROR(c, "Failed to remove chat from database.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp.SUCCESS(c, types.OkMsg)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package chatimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -16,7 +16,8 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 将消息发送给 Azure API 并获取结果,通过 WebSocket 推送到客户端
|
// 微软 Azure 模型消息发送实现
|
||||||
|
|
||||||
func (h *ChatHandler) sendAzureMessage(
|
func (h *ChatHandler) sendAzureMessage(
|
||||||
chatCtx []interface{},
|
chatCtx []interface{},
|
||||||
req types.ApiRequest,
|
req types.ApiRequest,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package chatimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -33,7 +33,8 @@ type baiduResp struct {
|
|||||||
} `json:"usage"`
|
} `json:"usage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将消息发送给百度文心一言大模型 API 并获取结果,通过 WebSocket 推送到客户端
|
// 百度文心一言消息发送实现
|
||||||
|
|
||||||
func (h *ChatHandler) sendBaiduMessage(
|
func (h *ChatHandler) sendBaiduMessage(
|
||||||
chatCtx []interface{},
|
chatCtx []interface{},
|
||||||
req types.ApiRequest,
|
req types.ApiRequest,
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package handler
|
package chatimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"chatplus/core"
|
"chatplus/core"
|
||||||
"chatplus/core/types"
|
"chatplus/core/types"
|
||||||
|
"chatplus/handler"
|
||||||
|
logger2 "chatplus/logger"
|
||||||
"chatplus/service/mj"
|
"chatplus/service/mj"
|
||||||
"chatplus/store"
|
"chatplus/store"
|
||||||
"chatplus/store/model"
|
"chatplus/store/model"
|
||||||
@@ -26,8 +28,10 @@ import (
|
|||||||
|
|
||||||
const ErrorMsg = "抱歉,AI 助手开小差了,请稍后再试。"
|
const ErrorMsg = "抱歉,AI 助手开小差了,请稍后再试。"
|
||||||
|
|
||||||
|
var logger = logger2.GetLogger()
|
||||||
|
|
||||||
type ChatHandler struct {
|
type ChatHandler struct {
|
||||||
BaseHandler
|
handler.BaseHandler
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
leveldb *store.LevelDB
|
leveldb *store.LevelDB
|
||||||
redis *redis.Client
|
redis *redis.Client
|
||||||
@@ -35,9 +39,14 @@ type ChatHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewChatHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB, redis *redis.Client, service *mj.Service) *ChatHandler {
|
func NewChatHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB, redis *redis.Client, service *mj.Service) *ChatHandler {
|
||||||
handler := ChatHandler{db: db, leveldb: levelDB, redis: redis, mjService: service}
|
h := ChatHandler{
|
||||||
handler.App = app
|
db: db,
|
||||||
return &handler
|
leveldb: levelDB,
|
||||||
|
redis: redis,
|
||||||
|
mjService: service,
|
||||||
|
}
|
||||||
|
h.App = app
|
||||||
|
return &h
|
||||||
}
|
}
|
||||||
|
|
||||||
var chatConfig types.ChatConfig
|
var chatConfig types.ChatConfig
|
||||||
@@ -123,7 +132,11 @@ func (h *ChatHandler) ChatHandle(c *gin.Context) {
|
|||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
client.Close()
|
client.Close()
|
||||||
h.App.ChatClients.Delete(sessionId)
|
h.App.ChatClients.Delete(sessionId)
|
||||||
h.App.ReqCancelFunc.Delete(sessionId)
|
cancelFunc := h.App.ReqCancelFunc.Get(sessionId)
|
||||||
|
if cancelFunc != nil {
|
||||||
|
cancelFunc()
|
||||||
|
h.App.ReqCancelFunc.Delete(sessionId)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +226,9 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
}
|
}
|
||||||
req.Functions = functions
|
req.Functions = functions
|
||||||
}
|
}
|
||||||
|
case types.XunFei:
|
||||||
|
req.Temperature = h.App.ChatConfig.XunFei.Temperature
|
||||||
|
req.MaxTokens = h.App.ChatConfig.XunFei.MaxTokens
|
||||||
default:
|
default:
|
||||||
utils.ReplyMessage(ws, "不支持的平台:"+session.Model.Platform+",请联系管理员!")
|
utils.ReplyMessage(ws, "不支持的平台:"+session.Model.Platform+",请联系管理员!")
|
||||||
utils.ReplyMessage(ws, "")
|
utils.ReplyMessage(ws, "")
|
||||||
@@ -249,9 +265,10 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
// loading recent chat history as chat context
|
// loading recent chat history as chat context
|
||||||
if chatConfig.ContextDeep > 0 {
|
if chatConfig.ContextDeep > 0 {
|
||||||
var historyMessages []model.HistoryMessage
|
var historyMessages []model.HistoryMessage
|
||||||
res := h.db.Where("chat_id = ? and use_context = 1", session.ChatId).Limit(chatConfig.ContextDeep).Order("created_at desc").Find(&historyMessages)
|
res := h.db.Debug().Where("chat_id = ? and use_context = 1", session.ChatId).Limit(chatConfig.ContextDeep).Order("id desc").Find(&historyMessages)
|
||||||
if res.Error == nil {
|
if res.Error == nil {
|
||||||
for _, msg := range historyMessages {
|
for i := len(historyMessages) - 1; i >= 0; i-- {
|
||||||
|
msg := historyMessages[i]
|
||||||
if tokens+msg.Tokens >= types.ModelToTokens[session.Model.Value] {
|
if tokens+msg.Tokens >= types.ModelToTokens[session.Model.Value] {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -286,6 +303,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
return h.sendChatGLMMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
|
return h.sendChatGLMMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
|
||||||
case types.Baidu:
|
case types.Baidu:
|
||||||
return h.sendBaiduMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
|
return h.sendBaiduMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
|
||||||
|
case types.XunFei:
|
||||||
|
return h.sendXunFeiMessage(chatCtx, req, userVo, ctx, session, role, prompt, ws)
|
||||||
|
|
||||||
}
|
}
|
||||||
utils.ReplyChunkMessage(ws, types.WsMessage{
|
utils.ReplyChunkMessage(ws, types.WsMessage{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package chatimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"chatplus/core/types"
|
"chatplus/core/types"
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"chatplus/utils"
|
"chatplus/utils"
|
||||||
"chatplus/utils/resp"
|
"chatplus/utils/resp"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// List 获取会话列表
|
// List 获取会话列表
|
||||||
@@ -47,6 +48,95 @@ func (h *ChatHandler) List(c *gin.Context) {
|
|||||||
resp.SUCCESS(c, items)
|
resp.SUCCESS(c, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update 更新会话标题
|
||||||
|
func (h *ChatHandler) Update(c *gin.Context) {
|
||||||
|
var data struct {
|
||||||
|
ChatId string `json:"chat_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&data); err != nil {
|
||||||
|
resp.ERROR(c, types.InvalidArgs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res := h.db.Model(&model.ChatItem{}).Where("chat_id = ?", data.ChatId).UpdateColumn("title", data.Title)
|
||||||
|
if res.Error != nil {
|
||||||
|
resp.ERROR(c, "Failed to update database")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.SUCCESS(c, types.OkMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear 清空所有聊天记录
|
||||||
|
func (h *ChatHandler) Clear(c *gin.Context) {
|
||||||
|
// 获取当前登录用户所有的聊天会话
|
||||||
|
user, err := utils.GetLoginUser(c, h.db)
|
||||||
|
if err != nil {
|
||||||
|
resp.NotAuth(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chats []model.ChatItem
|
||||||
|
res := h.db.Where("user_id = ?", user.Id).Find(&chats)
|
||||||
|
if res.Error != nil {
|
||||||
|
resp.ERROR(c, "No chats found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chatIds = make([]string, 0)
|
||||||
|
for _, chat := range chats {
|
||||||
|
chatIds = append(chatIds, chat.ChatId)
|
||||||
|
// 清空会话上下文
|
||||||
|
h.App.ChatContexts.Delete(chat.ChatId)
|
||||||
|
}
|
||||||
|
err = h.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
res := h.db.Where("user_id =?", user.Id).Delete(&model.ChatItem{})
|
||||||
|
if res.Error != nil {
|
||||||
|
return res.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
res = h.db.Where("user_id = ? AND chat_id IN ?", user.Id, chatIds).Delete(&model.HistoryMessage{})
|
||||||
|
if res.Error != nil {
|
||||||
|
return res.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 是否要删除 MidJourney 绘画记录和图片文件?
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Error with delete chats: %+v", err)
|
||||||
|
resp.ERROR(c, "Failed to remove chat from database.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.SUCCESS(c, types.OkMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// History 获取聊天历史记录
|
||||||
|
func (h *ChatHandler) History(c *gin.Context) {
|
||||||
|
chatId := c.Query("chat_id") // 会话 ID
|
||||||
|
var items []model.HistoryMessage
|
||||||
|
var messages = make([]vo.HistoryMessage, 0)
|
||||||
|
res := h.db.Where("chat_id = ?", chatId).Find(&items)
|
||||||
|
if res.Error != nil {
|
||||||
|
resp.ERROR(c, "No history message")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
for _, item := range items {
|
||||||
|
var v vo.HistoryMessage
|
||||||
|
err := utils.CopyObject(item, &v)
|
||||||
|
v.CreatedAt = item.CreatedAt.Unix()
|
||||||
|
v.UpdatedAt = item.UpdatedAt.Unix()
|
||||||
|
if err == nil {
|
||||||
|
messages = append(messages, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.SUCCESS(c, messages)
|
||||||
|
}
|
||||||
|
|
||||||
// Remove 删除会话
|
// Remove 删除会话
|
||||||
func (h *ChatHandler) Remove(c *gin.Context) {
|
func (h *ChatHandler) Remove(c *gin.Context) {
|
||||||
chatId := h.GetTrim(c, "chat_id")
|
chatId := h.GetTrim(c, "chat_id")
|
||||||
@@ -80,6 +170,7 @@ func (h *ChatHandler) Remove(c *gin.Context) {
|
|||||||
resp.SUCCESS(c, types.OkMsg)
|
resp.SUCCESS(c, types.OkMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detail 对话详情,用户导出对话
|
||||||
func (h *ChatHandler) Detail(c *gin.Context) {
|
func (h *ChatHandler) Detail(c *gin.Context) {
|
||||||
chatId := h.GetTrim(c, "chat_id")
|
chatId := h.GetTrim(c, "chat_id")
|
||||||
if utils.IsEmptyValue(chatId) {
|
if utils.IsEmptyValue(chatId) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package chatimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -17,7 +17,8 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 将消息发送给 ChatGLM API 并获取结果,通过 WebSocket 推送到客户端
|
// 清华大学 ChatGML 消息发送实现
|
||||||
|
|
||||||
func (h *ChatHandler) sendChatGLMMessage(
|
func (h *ChatHandler) sendChatGLMMessage(
|
||||||
chatCtx []interface{},
|
chatCtx []interface{},
|
||||||
req types.ApiRequest,
|
req types.ApiRequest,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package chatimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 将消息发送给 OpenAI API 并获取结果,通过 WebSocket 推送到客户端
|
// OPenAI 消息发送实现
|
||||||
func (h *ChatHandler) sendOpenAiMessage(
|
func (h *ChatHandler) sendOpenAiMessage(
|
||||||
chatCtx []interface{},
|
chatCtx []interface{},
|
||||||
req types.ApiRequest,
|
req types.ApiRequest,
|
||||||
322
api/handler/chatimpl/xunfei_handler.go
Normal file
322
api/handler/chatimpl/xunfei_handler.go
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
package chatimpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chatplus/core/types"
|
||||||
|
"chatplus/store/model"
|
||||||
|
"chatplus/store/vo"
|
||||||
|
"chatplus/utils"
|
||||||
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type xunFeiResp struct {
|
||||||
|
Header struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Sid string `json:"sid"`
|
||||||
|
Status int `json:"status"`
|
||||||
|
} `json:"header"`
|
||||||
|
Payload struct {
|
||||||
|
Choices struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Seq int `json:"seq"`
|
||||||
|
Text []struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
Index int `json:"index"`
|
||||||
|
} `json:"text"`
|
||||||
|
} `json:"choices"`
|
||||||
|
Usage struct {
|
||||||
|
Text struct {
|
||||||
|
QuestionTokens int `json:"question_tokens"`
|
||||||
|
PromptTokens int `json:"prompt_tokens"`
|
||||||
|
CompletionTokens int `json:"completion_tokens"`
|
||||||
|
TotalTokens int `json:"total_tokens"`
|
||||||
|
} `json:"text"`
|
||||||
|
} `json:"usage"`
|
||||||
|
} `json:"payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 科大讯飞消息发送实现
|
||||||
|
|
||||||
|
func (h *ChatHandler) sendXunFeiMessage(
|
||||||
|
chatCtx []interface{},
|
||||||
|
req types.ApiRequest,
|
||||||
|
userVo vo.User,
|
||||||
|
ctx context.Context,
|
||||||
|
session *types.ChatSession,
|
||||||
|
role model.ChatRole,
|
||||||
|
prompt string,
|
||||||
|
ws *types.WsClient) error {
|
||||||
|
promptCreatedAt := time.Now() // 记录提问时间
|
||||||
|
var apiKey = userVo.ChatConfig.ApiKeys[session.Model.Platform]
|
||||||
|
if apiKey == "" {
|
||||||
|
var key model.ApiKey
|
||||||
|
res := h.db.Where("platform = ?", session.Model.Platform).Order("last_used_at ASC").First(&key)
|
||||||
|
if res.Error != nil {
|
||||||
|
utils.ReplyMessage(ws, "抱歉😔😔😔,系统已经没有可用的 API KEY,请联系管理员!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 更新 API KEY 的最后使用时间
|
||||||
|
h.db.Model(&key).UpdateColumn("last_used_at", time.Now().Unix())
|
||||||
|
apiKey = key.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
d := websocket.Dialer{
|
||||||
|
HandshakeTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
key := strings.Split(apiKey, "|")
|
||||||
|
if len(key) != 3 {
|
||||||
|
utils.ReplyMessage(ws, "非法的 API KEY!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiURL string
|
||||||
|
if req.Model == "generalv2" {
|
||||||
|
apiURL = strings.Replace(h.App.ChatConfig.XunFei.ApiURL, "{version}", "v2.1", 1)
|
||||||
|
} else {
|
||||||
|
apiURL = strings.Replace(h.App.ChatConfig.XunFei.ApiURL, "{version}", "v1.1", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
wsURL, err := assembleAuthUrl(apiURL, key[1], key[2])
|
||||||
|
//握手并建立websocket 连接
|
||||||
|
conn, resp, err := d.Dial(wsURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(readResp(resp) + err.Error())
|
||||||
|
utils.ReplyMessage(ws, "请求讯飞星火模型 API 失败:"+readResp(resp)+err.Error())
|
||||||
|
return nil
|
||||||
|
} else if resp.StatusCode != 101 {
|
||||||
|
utils.ReplyMessage(ws, "请求讯飞星火模型 API 失败:"+readResp(resp)+err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := buildRequest(key[0], req)
|
||||||
|
fmt.Printf("%+v", data)
|
||||||
|
fmt.Println(apiURL)
|
||||||
|
err = conn.WriteJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
utils.ReplyMessage(ws, "发送消息失败:"+err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
replyCreatedAt := time.Now() // 记录回复时间
|
||||||
|
// 循环读取 Chunk 消息
|
||||||
|
var message = types.Message{}
|
||||||
|
var contents = make([]string, 0)
|
||||||
|
var content string
|
||||||
|
for {
|
||||||
|
_, msg, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error with read message:", err)
|
||||||
|
utils.ReplyMessage(ws, fmt.Sprintf("**数据读取失败:%s**", err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析数据
|
||||||
|
var result xunFeiResp
|
||||||
|
err = json.Unmarshal(msg, &result)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("error with parsing JSON:", err)
|
||||||
|
utils.ReplyMessage(ws, fmt.Sprintf("**解析数据行失败:%s**", err))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Header.Code != 0 {
|
||||||
|
utils.ReplyMessage(ws, fmt.Sprintf("**请求 API 返回错误:%s**", result.Header.Message))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content = result.Payload.Choices.Text[0].Content
|
||||||
|
contents = append(contents, content)
|
||||||
|
// 第一个结果
|
||||||
|
if result.Payload.Choices.Status == 0 {
|
||||||
|
utils.ReplyChunkMessage(ws, types.WsMessage{Type: types.WsStart})
|
||||||
|
}
|
||||||
|
utils.ReplyChunkMessage(ws, types.WsMessage{
|
||||||
|
Type: types.WsMiddle,
|
||||||
|
Content: utils.InterfaceToString(content),
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.Payload.Choices.Status == 2 { // 最终结果
|
||||||
|
_ = conn.Close() // 关闭连接
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
utils.ReplyMessage(ws, "**用户取消了生成指令!**")
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息发送成功
|
||||||
|
if len(contents) > 0 {
|
||||||
|
// 更新用户的对话次数
|
||||||
|
if userVo.ChatConfig.ApiKeys[session.Model.Platform] == "" {
|
||||||
|
h.db.Model(&model.User{}).Where("id = ?", userVo.Id).UpdateColumn("calls", gorm.Expr("calls - ?", 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
if message.Role == "" {
|
||||||
|
message.Role = "assistant"
|
||||||
|
}
|
||||||
|
message.Content = strings.Join(contents, "")
|
||||||
|
useMsg := types.Message{Role: "user", Content: prompt}
|
||||||
|
|
||||||
|
// 更新上下文消息,如果是调用函数则不需要更新上下文
|
||||||
|
if h.App.ChatConfig.EnableContext {
|
||||||
|
chatCtx = append(chatCtx, useMsg) // 提问消息
|
||||||
|
chatCtx = append(chatCtx, message) // 回复消息
|
||||||
|
h.App.ChatContexts.Put(session.ChatId, chatCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 追加聊天记录
|
||||||
|
if h.App.ChatConfig.EnableHistory {
|
||||||
|
// for prompt
|
||||||
|
promptToken, err := utils.CalcTokens(prompt, req.Model)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
}
|
||||||
|
historyUserMsg := model.HistoryMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.PromptMsg,
|
||||||
|
Icon: userVo.Avatar,
|
||||||
|
Content: prompt,
|
||||||
|
Tokens: promptToken,
|
||||||
|
UseContext: true,
|
||||||
|
}
|
||||||
|
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 数量
|
||||||
|
replyToken, _ := utils.CalcTokens(message.Content, req.Model)
|
||||||
|
totalTokens := replyToken + getTotalTokens(req)
|
||||||
|
historyReplyMsg := model.HistoryMessage{
|
||||||
|
UserId: userVo.Id,
|
||||||
|
ChatId: session.ChatId,
|
||||||
|
RoleId: role.Id,
|
||||||
|
Type: types.ReplyMsg,
|
||||||
|
Icon: role.Icon,
|
||||||
|
Content: message.Content,
|
||||||
|
Tokens: totalTokens,
|
||||||
|
UseContext: true,
|
||||||
|
}
|
||||||
|
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.db.Model(&model.User{}).Where("id = ?", userVo.Id).
|
||||||
|
UpdateColumn("total_tokens", gorm.Expr("total_tokens + ?", totalTokens))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存当前会话
|
||||||
|
var chatItem model.ChatItem
|
||||||
|
res := h.db.Where("chat_id = ?", session.ChatId).First(&chatItem)
|
||||||
|
if res.Error != nil {
|
||||||
|
chatItem.ChatId = session.ChatId
|
||||||
|
chatItem.UserId = session.UserId
|
||||||
|
chatItem.RoleId = role.Id
|
||||||
|
chatItem.ModelId = session.Model.Id
|
||||||
|
if utf8.RuneCountInString(prompt) > 30 {
|
||||||
|
chatItem.Title = string([]rune(prompt)[:30]) + "..."
|
||||||
|
} else {
|
||||||
|
chatItem.Title = prompt
|
||||||
|
}
|
||||||
|
h.db.Create(&chatItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建 websocket 请求实体
|
||||||
|
func buildRequest(appid string, req types.ApiRequest) map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"header": map[string]interface{}{
|
||||||
|
"app_id": appid,
|
||||||
|
},
|
||||||
|
"parameter": map[string]interface{}{
|
||||||
|
"chat": map[string]interface{}{
|
||||||
|
"domain": req.Model,
|
||||||
|
"temperature": float64(req.Temperature),
|
||||||
|
"top_k": int64(6),
|
||||||
|
"max_tokens": int64(req.MaxTokens),
|
||||||
|
"auditing": "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"payload": map[string]interface{}{
|
||||||
|
"message": map[string]interface{}{
|
||||||
|
"text": req.Messages,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建鉴权 URL
|
||||||
|
func assembleAuthUrl(hostURL string, apiKey, apiSecret string) (string, error) {
|
||||||
|
ul, err := url.Parse(hostURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
date := time.Now().UTC().Format(time.RFC1123)
|
||||||
|
signString := []string{"host: " + ul.Host, "date: " + date, "GET " + ul.Path + " HTTP/1.1"}
|
||||||
|
//拼接签名字符串
|
||||||
|
signStr := strings.Join(signString, "\n")
|
||||||
|
sha := hmacWithSha256(signStr, apiSecret)
|
||||||
|
|
||||||
|
authUrl := fmt.Sprintf("hmac username=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey,
|
||||||
|
"hmac-sha256", "host date request-line", sha)
|
||||||
|
//将请求参数使用base64编码
|
||||||
|
authorization := base64.StdEncoding.EncodeToString([]byte(authUrl))
|
||||||
|
v := url.Values{}
|
||||||
|
v.Add("host", ul.Host)
|
||||||
|
v.Add("date", date)
|
||||||
|
v.Add("authorization", authorization)
|
||||||
|
//将编码后的字符串url encode后添加到url后面
|
||||||
|
return hostURL + "?" + v.Encode(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 sha256 签名
|
||||||
|
func hmacWithSha256(data, key string) string {
|
||||||
|
mac := hmac.New(sha256.New, []byte(key))
|
||||||
|
mac.Write([]byte(data))
|
||||||
|
encodeData := mac.Sum(nil)
|
||||||
|
return base64.StdEncoding.EncodeToString(encodeData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取响应
|
||||||
|
func readResp(resp *http.Response) string {
|
||||||
|
if resp == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
b, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("code=%d,body=%s", resp.StatusCode, string(b))
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"chatplus/core/types"
|
"chatplus/core/types"
|
||||||
"chatplus/handler"
|
"chatplus/handler"
|
||||||
"chatplus/handler/admin"
|
"chatplus/handler/admin"
|
||||||
|
"chatplus/handler/chatimpl"
|
||||||
logger2 "chatplus/logger"
|
logger2 "chatplus/logger"
|
||||||
"chatplus/service"
|
"chatplus/service"
|
||||||
"chatplus/service/fun"
|
"chatplus/service/fun"
|
||||||
@@ -115,7 +116,7 @@ func main() {
|
|||||||
// 创建控制器
|
// 创建控制器
|
||||||
fx.Provide(handler.NewChatRoleHandler),
|
fx.Provide(handler.NewChatRoleHandler),
|
||||||
fx.Provide(handler.NewUserHandler),
|
fx.Provide(handler.NewUserHandler),
|
||||||
fx.Provide(handler.NewChatHandler),
|
fx.Provide(chatimpl.NewChatHandler),
|
||||||
fx.Provide(handler.NewUploadHandler),
|
fx.Provide(handler.NewUploadHandler),
|
||||||
fx.Provide(handler.NewSmsHandler),
|
fx.Provide(handler.NewSmsHandler),
|
||||||
fx.Provide(handler.NewRewardHandler),
|
fx.Provide(handler.NewRewardHandler),
|
||||||
@@ -196,7 +197,7 @@ func main() {
|
|||||||
group.POST("password", h.Password)
|
group.POST("password", h.Password)
|
||||||
group.POST("bind/mobile", h.BindMobile)
|
group.POST("bind/mobile", h.BindMobile)
|
||||||
}),
|
}),
|
||||||
fx.Invoke(func(s *core.AppServer, h *handler.ChatHandler) {
|
fx.Invoke(func(s *core.AppServer, h *chatimpl.ChatHandler) {
|
||||||
group := s.Engine.Group("/api/chat/")
|
group := s.Engine.Group("/api/chat/")
|
||||||
group.Any("new", h.ChatHandle)
|
group.Any("new", h.ChatHandle)
|
||||||
group.GET("list", h.List)
|
group.GET("list", h.List)
|
||||||
|
|||||||
@@ -1,55 +1,5 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
apiKey := "qjvqGdqpTY7qQaGBMenM7XgQ"
|
|
||||||
apiSecret := "3G1RzBGXywZv4VbYRTyAfNns1vIOAG8t"
|
|
||||||
token, err := getBaiduToken(apiKey, apiSecret)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(token)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBaiduToken(apiKey string, apiSecret string) (string, error) {
|
|
||||||
|
|
||||||
url := fmt.Sprintf("https://aip.baidubce.com/oauth/2.0/token?client_id=%s&client_secret=%s&grant_type=client_credentials", apiKey, apiSecret)
|
|
||||||
client := &http.Client{}
|
|
||||||
req, err := http.NewRequest("POST", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
req.Header.Add("Accept", "application/json")
|
|
||||||
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error with send request: %w", err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error with read response: %w", err)
|
|
||||||
}
|
|
||||||
var r map[string]interface{}
|
|
||||||
err = json.Unmarshal(body, &r)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error with parse response: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r["error"] != nil {
|
|
||||||
return "", fmt.Errorf("error with api response: %s", r["error_description"])
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s", r["access_token"]), nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,115 +2,137 @@ 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;
|
||||||
height: 100%;
|
padding: 10px;
|
||||||
padding: 10px;
|
box-sizing: border-box;
|
||||||
overflow-y: scroll;
|
/*BaseForm*/
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
@click="changeChat(chat)">
|
@click="changeChat(chat)">
|
||||||
<el-image :src="chat.icon" class="avatar"/>
|
<el-image :src="chat.icon" class="avatar"/>
|
||||||
<span class="chat-title-input" v-if="chat.edit">
|
<span class="chat-title-input" v-if="chat.edit">
|
||||||
<el-input v-model="tmpChatTitle" size="small" placeholder="请输入会话标题"/>
|
<el-input v-model="tmpChatTitle" size="small" @keydown="titleKeydown($event, chat)"
|
||||||
|
placeholder="请输入会话标题"/>
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="chat-title">{{ chat.title }}</span>
|
<span v-else class="chat-title">{{ chat.title }}</span>
|
||||||
<span class="btn btn-check" v-if="chat.edit || chat.removing">
|
<span class="btn btn-check" v-if="chat.edit || chat.removing">
|
||||||
@@ -451,16 +452,24 @@ const editChatTitle = function (event, chat) {
|
|||||||
tmpChatTitle.value = chat.title;
|
tmpChatTitle.value = chat.title;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const titleKeydown = (e, chat) => {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
e.stopPropagation();
|
||||||
|
confirm(e, chat)
|
||||||
|
}
|
||||||
|
}
|
||||||
// 确认修改
|
// 确认修改
|
||||||
const confirm = function (event, chat) {
|
const confirm = function (event, chat) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (curOpt.value === 'edit') {
|
if (curOpt.value === 'edit') {
|
||||||
if (tmpChatTitle.value === '') {
|
if (tmpChatTitle.value === '') {
|
||||||
ElMessage.error("请输入会话标题!");
|
return ElMessage.error("请输入会话标题!");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
if (!chat.chat_id) {
|
||||||
httpPost('/api/chat/update', {id: chat.id, title: tmpChatTitle.value}).then(() => {
|
return ElMessage.error("对话 ID 为空,请刷新页面再试!");
|
||||||
|
}
|
||||||
|
httpPost('/api/chat/update', {chat_id: chat.chat_id, title: tmpChatTitle.value}).then(() => {
|
||||||
chat.title = tmpChatTitle.value;
|
chat.title = tmpChatTitle.value;
|
||||||
chat.edit = false;
|
chat.edit = false;
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
|
|||||||
@@ -40,11 +40,14 @@
|
|||||||
v-model="showDialog"
|
v-model="showDialog"
|
||||||
:title="title"
|
:title="title"
|
||||||
>
|
>
|
||||||
<el-alert title="注意:如果是百度文心一言平台,需要用竖线(|)将 API Key 和 Secret Key 串接起来填入!"
|
<el-alert
|
||||||
type="warning"
|
type="warning"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
show-icon
|
show-icon
|
||||||
style="margin-bottom: 10px; font-size:14px;"/>
|
style="margin-bottom: 10px; font-size:14px;">
|
||||||
|
<p><b>注意:</b>如果是百度文心一言平台,需要用竖线(|)将 API Key 和 Secret Key 串接起来填入!</p>
|
||||||
|
<p><b>注意:</b>如果是讯飞星火大模型,需要用竖线(|)将 APPID, APIKey 和 APISecret 按照顺序串接起来填入!</p>
|
||||||
|
</el-alert>
|
||||||
<el-form :model="item" label-width="120px" ref="formRef" :rules="rules">
|
<el-form :model="item" label-width="120px" ref="formRef" :rules="rules">
|
||||||
<el-form-item label="所属平台:" prop="platform">
|
<el-form-item label="所属平台:" prop="platform">
|
||||||
<el-select v-model="item.platform" placeholder="请选择平台">
|
<el-select v-model="item.platform" placeholder="请选择平台">
|
||||||
@@ -87,10 +90,11 @@ const loading = ref(true)
|
|||||||
const formRef = ref(null)
|
const formRef = ref(null)
|
||||||
const title = ref("")
|
const title = ref("")
|
||||||
const platforms = ref([
|
const platforms = ref([
|
||||||
|
{name: "【OpenAI】ChatGPT", value: "OpenAI"},
|
||||||
|
{name: "【讯飞】星火大模型", value: "XunFei"},
|
||||||
{name: "【清华智普】ChatGLM", value: "ChatGLM"},
|
{name: "【清华智普】ChatGLM", value: "ChatGLM"},
|
||||||
{name: "【百度】文心一言", value: "Baidu"},
|
{name: "【百度】文心一言", value: "Baidu"},
|
||||||
{name: "【微软】Azure", value: "Azure"},
|
{name: "【微软】Azure", value: "Azure"},
|
||||||
{name: "【OpenAI】ChatGPT", value: "OpenAI"},
|
|
||||||
])
|
])
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
|
|||||||
@@ -95,10 +95,11 @@ const rules = reactive({
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const formRef = ref(null)
|
const formRef = ref(null)
|
||||||
const platforms = ref([
|
const platforms = ref([
|
||||||
|
{name: "【OpenAI】ChatGPT", value: "OpenAI"},
|
||||||
|
{name: "【讯飞】星火大模型", value: "XunFei"},
|
||||||
{name: "【清华智普】ChatGLM", value: "ChatGLM"},
|
{name: "【清华智普】ChatGLM", value: "ChatGLM"},
|
||||||
{name: "【百度】文心一言", value: "Baidu"},
|
{name: "【百度】文心一言", value: "Baidu"},
|
||||||
{name: "【微软】Azure", value: "Azure"},
|
{name: "【微软】Azure", value: "Azure"},
|
||||||
{name: "【OpenAI】ChatGPT", value: "OpenAI"},
|
|
||||||
])
|
])
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
<el-input-number v-model="chat['context_deep']" :min="0" :max="10"/>
|
<el-input-number v-model="chat['context_deep']" :min="0" :max="10"/>
|
||||||
<div class="tip" style="margin-top: 10px;">会话上下文深度:在老会话中继续会话,默认加载多少条聊天记录作为上下文。如果设置为
|
<div class="tip" style="margin-top: 10px;">会话上下文深度:在老会话中继续会话,默认加载多少条聊天记录作为上下文。如果设置为
|
||||||
0
|
0
|
||||||
则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数最好设置为 2 的整数倍。
|
则不加载聊天记录,仅仅使用当前角色的上下文。该配置参数最好设置需要为偶数,否则将无法兼容百度的 API。
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -143,6 +143,18 @@
|
|||||||
<el-input v-model.number="chat['baidu']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
<el-input v-model.number="chat['baidu']['max_tokens']" placeholder="回复的最大字数,最大4096"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-divider content-position="center">讯飞星火</el-divider>
|
||||||
|
<el-form-item label="API 地址" prop="xun_fei.api_url">
|
||||||
|
<el-input v-model="chat['xun_fei']['api_url']" placeholder="支持变量,{model} => 模型名称"/>
|
||||||
|
</el-form-item>
|
||||||
|
<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-form-item style="text-align: right">
|
<el-form-item style="text-align: right">
|
||||||
<el-button type="primary" @click="save('chat')">保存</el-button>
|
<el-button type="primary" @click="save('chat')">保存</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -164,6 +176,7 @@ const chat = ref({
|
|||||||
azure: {api_url: "", temperature: 1, max_tokens: 1024},
|
azure: {api_url: "", temperature: 1, max_tokens: 1024},
|
||||||
chat_gml: {api_url: "", temperature: 0.95, max_tokens: 1024},
|
chat_gml: {api_url: "", temperature: 0.95, max_tokens: 1024},
|
||||||
baidu: {api_url: "", temperature: 0.95, max_tokens: 1024},
|
baidu: {api_url: "", temperature: 0.95, max_tokens: 1024},
|
||||||
|
xun_fei: {api_url: "", temperature: 0.5, max_tokens: 1024},
|
||||||
context_deep: 0,
|
context_deep: 0,
|
||||||
enable_context: true,
|
enable_context: true,
|
||||||
enable_history: true,
|
enable_history: true,
|
||||||
@@ -195,6 +208,9 @@ onMounted(() => {
|
|||||||
if (res.data.baidu) {
|
if (res.data.baidu) {
|
||||||
chat.value.baidu = res.data.baidu
|
chat.value.baidu = res.data.baidu
|
||||||
}
|
}
|
||||||
|
if (res.data.xun_fei) {
|
||||||
|
chat.value.xun_fei = res.data.xun_fei
|
||||||
|
}
|
||||||
chat.value.context_deep = res.data.context_deep
|
chat.value.context_deep = res.data.context_deep
|
||||||
chat.value.enable_context = res.data.enable_context
|
chat.value.enable_context = res.data.enable_context
|
||||||
chat.value.enable_history = res.data.enable_history
|
chat.value.enable_history = res.data.enable_history
|
||||||
@@ -210,9 +226,6 @@ const rules = reactive({
|
|||||||
admin_title: [{required: true, message: '请输入控制台标题', trigger: 'blur',}],
|
admin_title: [{required: true, message: '请输入控制台标题', trigger: 'blur',}],
|
||||||
user_init_calls: [{required: true, message: '请输入赠送对话次数', trigger: 'blur'}],
|
user_init_calls: [{required: true, message: '请输入赠送对话次数', trigger: 'blur'}],
|
||||||
user_img_calls: [{required: true, message: '请输入赠送绘图次数', trigger: 'blur'}],
|
user_img_calls: [{required: true, message: '请输入赠送绘图次数', trigger: 'blur'}],
|
||||||
open_ai: {api_url: [{required: true, message: '请输入 API URL', trigger: 'blur'}]},
|
|
||||||
azure: {api_url: [{required: true, message: '请输入 API URL', trigger: 'blur'}]},
|
|
||||||
chat_gml: {api_url: [{required: true, message: '请输入 API URL', trigger: 'blur'}]},
|
|
||||||
})
|
})
|
||||||
const save = function (key) {
|
const save = function (key) {
|
||||||
if (key === 'system') {
|
if (key === 'system') {
|
||||||
@@ -226,6 +239,9 @@ const save = function (key) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if (key === 'chat') {
|
} else if (key === 'chat') {
|
||||||
|
if (chat.value.context_deep % 2 !== 0) {
|
||||||
|
return ElMessage.error("会话上下文深度必须为偶数!")
|
||||||
|
}
|
||||||
chatFormRef.value.validate((valid) => {
|
chatFormRef.value.validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
httpPost('/api/admin/config/update', {key: key, config: chat.value}).then(() => {
|
httpPost('/api/admin/config/update', {key: key, config: chat.value}).then(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user