mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-04-28 14:04:48 +08:00
完善 SSE 功能
This commit is contained in:
@@ -52,17 +52,6 @@ type Delta struct {
|
|||||||
} `json:"function_call,omitempty"`
|
} `json:"function_call,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatSession 聊天会话对象
|
|
||||||
type ChatSession struct {
|
|
||||||
UserId uint `json:"user_id"`
|
|
||||||
ClientIP string `json:"client_ip"` // 客户端 IP
|
|
||||||
ChatId string `json:"chat_id"` // 客户端聊天会话 ID, 多会话模式专用字段
|
|
||||||
Model ChatModel `json:"model"` // GPT 模型
|
|
||||||
Start int64 `json:"start"` // 开始请求时间戳
|
|
||||||
Tools []int `json:"tools"` // 工具函数列表
|
|
||||||
Stream bool `json:"stream"` // 是否采用流式输出
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChatModel struct {
|
type ChatModel struct {
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ package handler
|
|||||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -33,7 +32,6 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
req2 "github.com/imroc/req/v3"
|
|
||||||
"github.com/sashabaranov/go-openai"
|
"github.com/sashabaranov/go-openai"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -117,17 +115,6 @@ func (h *ChatHandler) Chat(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session := &types.ChatSession{
|
|
||||||
ClientIP: c.ClientIP(),
|
|
||||||
UserId: data.UserId,
|
|
||||||
ChatId: data.ChatId,
|
|
||||||
Tools: data.Tools,
|
|
||||||
Stream: data.Stream,
|
|
||||||
Model: types.ChatModel{
|
|
||||||
KeyId: data.ModelId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用旧的聊天数据覆盖模型和角色ID
|
// 使用旧的聊天数据覆盖模型和角色ID
|
||||||
var chat model.ChatItem
|
var chat model.ChatItem
|
||||||
h.DB.Where("chat_id", data.ChatId).First(&chat)
|
h.DB.Where("chat_id", data.ChatId).First(&chat)
|
||||||
@@ -289,7 +276,7 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
|
|
||||||
logger.Debugf("聊天上下文:%+v", chatCtx)
|
logger.Debugf("聊天上下文:%+v", chatCtx)
|
||||||
}
|
}
|
||||||
reqMgs := make([]interface{}, 0)
|
reqMgs := make([]any, 0)
|
||||||
|
|
||||||
for i := len(chatCtx) - 1; i >= 0; i-- {
|
for i := len(chatCtx) - 1; i >= 0; i-- {
|
||||||
reqMgs = append(reqMgs, chatCtx[i])
|
reqMgs = append(reqMgs, chatCtx[i])
|
||||||
@@ -297,6 +284,8 @@ func (h *ChatHandler) sendMessage(ctx context.Context, session *types.ChatSessio
|
|||||||
|
|
||||||
fullPrompt := prompt
|
fullPrompt := prompt
|
||||||
text := prompt
|
text := prompt
|
||||||
|
|
||||||
|
for _, file := range session.Files {
|
||||||
// extract files in prompt
|
// extract files in prompt
|
||||||
files := utils.ExtractFileURLs(prompt)
|
files := utils.ExtractFileURLs(prompt)
|
||||||
logger.Debugf("detected FILES: %+v", files)
|
logger.Debugf("detected FILES: %+v", files)
|
||||||
@@ -688,220 +677,220 @@ func (h *ChatHandler) TextToSpeech(c *gin.Context) {
|
|||||||
c.Writer.Write(audioBytes)
|
c.Writer.Write(audioBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OPenAI 消息发送实现
|
// // OPenAI 消息发送实现
|
||||||
func (h *ChatHandler) sendOpenAiMessage(
|
// func (h *ChatHandler) sendOpenAiMessage(
|
||||||
req types.ApiRequest,
|
// req types.ApiRequest,
|
||||||
userVo vo.User,
|
// userVo vo.User,
|
||||||
ctx context.Context,
|
// ctx context.Context,
|
||||||
session *types.ChatSession,
|
// session *types.ChatSession,
|
||||||
role model.ChatRole,
|
// role model.ChatRole,
|
||||||
prompt string,
|
// prompt string,
|
||||||
c *gin.Context) error {
|
// c *gin.Context) error {
|
||||||
promptCreatedAt := time.Now() // 记录提问时间
|
// promptCreatedAt := time.Now() // 记录提问时间
|
||||||
start := time.Now()
|
// start := time.Now()
|
||||||
var apiKey = model.ApiKey{}
|
// var apiKey = model.ApiKey{}
|
||||||
response, err := h.doRequest(ctx, req, session, &apiKey)
|
// response, err := h.doRequest(ctx, req, session, &apiKey)
|
||||||
logger.Info("HTTP请求完成,耗时:", time.Since(start))
|
// logger.Info("HTTP请求完成,耗时:", time.Since(start))
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
if strings.Contains(err.Error(), "context canceled") {
|
// if strings.Contains(err.Error(), "context canceled") {
|
||||||
return fmt.Errorf("用户取消了请求:%s", prompt)
|
// return fmt.Errorf("用户取消了请求:%s", prompt)
|
||||||
} else if strings.Contains(err.Error(), "no available key") {
|
// } else if strings.Contains(err.Error(), "no available key") {
|
||||||
return errors.New("抱歉😔😔😔,系统已经没有可用的 API KEY,请联系管理员!")
|
// return errors.New("抱歉😔😔😔,系统已经没有可用的 API KEY,请联系管理员!")
|
||||||
}
|
// }
|
||||||
return err
|
// return err
|
||||||
} else {
|
// } else {
|
||||||
defer response.Body.Close()
|
// defer response.Body.Close()
|
||||||
}
|
// }
|
||||||
|
|
||||||
if response.StatusCode != 200 {
|
// if response.StatusCode != 200 {
|
||||||
body, _ := io.ReadAll(response.Body)
|
// body, _ := io.ReadAll(response.Body)
|
||||||
return fmt.Errorf("请求 OpenAI API 失败:%d, %v", response.StatusCode, string(body))
|
// return fmt.Errorf("请求 OpenAI API 失败:%d, %v", response.StatusCode, string(body))
|
||||||
}
|
// }
|
||||||
|
|
||||||
contentType := response.Header.Get("Content-Type")
|
// contentType := response.Header.Get("Content-Type")
|
||||||
if strings.Contains(contentType, "text/event-stream") {
|
// if strings.Contains(contentType, "text/event-stream") {
|
||||||
replyCreatedAt := time.Now() // 记录回复时间
|
// replyCreatedAt := time.Now() // 记录回复时间
|
||||||
// 循环读取 Chunk 消息
|
// // 循环读取 Chunk 消息
|
||||||
var message = types.Message{Role: "assistant"}
|
// var message = types.Message{Role: "assistant"}
|
||||||
var contents = make([]string, 0)
|
// var contents = make([]string, 0)
|
||||||
var function model.Function
|
// var function model.Function
|
||||||
var toolCall = false
|
// var toolCall = false
|
||||||
var arguments = make([]string, 0)
|
// var arguments = make([]string, 0)
|
||||||
var reasoning = false
|
// var reasoning = false
|
||||||
|
|
||||||
pushMessage(c, ChatEventStart, "开始响应")
|
// pushMessage(c, ChatEventStart, "开始响应")
|
||||||
scanner := bufio.NewScanner(response.Body)
|
// scanner := bufio.NewScanner(response.Body)
|
||||||
for scanner.Scan() {
|
// for scanner.Scan() {
|
||||||
line := scanner.Text()
|
// line := scanner.Text()
|
||||||
if !strings.Contains(line, "data:") || len(line) < 30 {
|
// if !strings.Contains(line, "data:") || len(line) < 30 {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
var responseBody = types.ApiResponse{}
|
// var responseBody = types.ApiResponse{}
|
||||||
err = json.Unmarshal([]byte(line[6:]), &responseBody)
|
// err = json.Unmarshal([]byte(line[6:]), &responseBody)
|
||||||
if err != nil { // 数据解析出错
|
// if err != nil { // 数据解析出错
|
||||||
return errors.New(line)
|
// return errors.New(line)
|
||||||
}
|
// }
|
||||||
if len(responseBody.Choices) == 0 { // Fixed: 兼容 Azure API 第一个输出空行
|
// if len(responseBody.Choices) == 0 { // Fixed: 兼容 Azure API 第一个输出空行
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
if responseBody.Choices[0].Delta.Content == nil &&
|
// if responseBody.Choices[0].Delta.Content == nil &&
|
||||||
responseBody.Choices[0].Delta.ToolCalls == nil &&
|
// responseBody.Choices[0].Delta.ToolCalls == nil &&
|
||||||
responseBody.Choices[0].Delta.ReasoningContent == "" {
|
// responseBody.Choices[0].Delta.ReasoningContent == "" {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
if responseBody.Choices[0].FinishReason == "stop" && len(contents) == 0 {
|
// if responseBody.Choices[0].FinishReason == "stop" && len(contents) == 0 {
|
||||||
pushMessage(c, ChatEventError, "抱歉😔😔😔,AI助手由于未知原因已经停止输出内容。")
|
// pushMessage(c, ChatEventError, "抱歉😔😔😔,AI助手由于未知原因已经停止输出内容。")
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
|
|
||||||
var tool types.ToolCall
|
// var tool types.ToolCall
|
||||||
if len(responseBody.Choices[0].Delta.ToolCalls) > 0 {
|
// if len(responseBody.Choices[0].Delta.ToolCalls) > 0 {
|
||||||
tool = responseBody.Choices[0].Delta.ToolCalls[0]
|
// tool = responseBody.Choices[0].Delta.ToolCalls[0]
|
||||||
if toolCall && tool.Function.Name == "" {
|
// if toolCall && tool.Function.Name == "" {
|
||||||
arguments = append(arguments, tool.Function.Arguments)
|
// arguments = append(arguments, tool.Function.Arguments)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 兼容 Function Call
|
// // 兼容 Function Call
|
||||||
fun := responseBody.Choices[0].Delta.FunctionCall
|
// fun := responseBody.Choices[0].Delta.FunctionCall
|
||||||
if fun.Name != "" {
|
// if fun.Name != "" {
|
||||||
tool = *new(types.ToolCall)
|
// tool = *new(types.ToolCall)
|
||||||
tool.Function.Name = fun.Name
|
// tool.Function.Name = fun.Name
|
||||||
} else if toolCall {
|
// } else if toolCall {
|
||||||
arguments = append(arguments, fun.Arguments)
|
// arguments = append(arguments, fun.Arguments)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
if !utils.IsEmptyValue(tool) {
|
// if !utils.IsEmptyValue(tool) {
|
||||||
res := h.DB.Where("name = ?", tool.Function.Name).First(&function)
|
// res := h.DB.Where("name = ?", tool.Function.Name).First(&function)
|
||||||
if res.Error == nil {
|
// if res.Error == nil {
|
||||||
toolCall = true
|
// toolCall = true
|
||||||
callMsg := fmt.Sprintf("正在调用工具 `%s` 作答 ...\n\n", function.Label)
|
// callMsg := fmt.Sprintf("正在调用工具 `%s` 作答 ...\n\n", function.Label)
|
||||||
pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
||||||
"type": "text",
|
// "type": "text",
|
||||||
"content": callMsg,
|
// "content": callMsg,
|
||||||
})
|
// })
|
||||||
contents = append(contents, callMsg)
|
// contents = append(contents, callMsg)
|
||||||
}
|
// }
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
if responseBody.Choices[0].FinishReason == "tool_calls" ||
|
// if responseBody.Choices[0].FinishReason == "tool_calls" ||
|
||||||
responseBody.Choices[0].FinishReason == "function_call" { // 函数调用完毕
|
// responseBody.Choices[0].FinishReason == "function_call" { // 函数调用完毕
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
|
|
||||||
// output stopped
|
// // output stopped
|
||||||
if responseBody.Choices[0].FinishReason != "" {
|
// if responseBody.Choices[0].FinishReason != "" {
|
||||||
break // 输出完成或者输出中断了
|
// break // 输出完成或者输出中断了
|
||||||
} else { // 正常输出结果
|
// } else { // 正常输出结果
|
||||||
// 兼容思考过程
|
// // 兼容思考过程
|
||||||
if responseBody.Choices[0].Delta.ReasoningContent != "" {
|
// if responseBody.Choices[0].Delta.ReasoningContent != "" {
|
||||||
reasoningContent := responseBody.Choices[0].Delta.ReasoningContent
|
// reasoningContent := responseBody.Choices[0].Delta.ReasoningContent
|
||||||
if !reasoning {
|
// if !reasoning {
|
||||||
reasoningContent = fmt.Sprintf("<think>%s", reasoningContent)
|
// reasoningContent = fmt.Sprintf("<think>%s", reasoningContent)
|
||||||
reasoning = true
|
// reasoning = true
|
||||||
}
|
// }
|
||||||
|
|
||||||
pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
||||||
"type": "text",
|
// "type": "text",
|
||||||
"content": reasoningContent,
|
// "content": reasoningContent,
|
||||||
})
|
// })
|
||||||
contents = append(contents, reasoningContent)
|
// contents = append(contents, reasoningContent)
|
||||||
} else if responseBody.Choices[0].Delta.Content != "" {
|
// } else if responseBody.Choices[0].Delta.Content != "" {
|
||||||
finalContent := responseBody.Choices[0].Delta.Content
|
// finalContent := responseBody.Choices[0].Delta.Content
|
||||||
if reasoning {
|
// if reasoning {
|
||||||
finalContent = fmt.Sprintf("</think>%s", responseBody.Choices[0].Delta.Content)
|
// finalContent = fmt.Sprintf("</think>%s", responseBody.Choices[0].Delta.Content)
|
||||||
reasoning = false
|
// reasoning = false
|
||||||
}
|
// }
|
||||||
contents = append(contents, utils.InterfaceToString(finalContent))
|
// contents = append(contents, utils.InterfaceToString(finalContent))
|
||||||
pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
||||||
"type": "text",
|
// "type": "text",
|
||||||
"content": finalContent,
|
// "content": finalContent,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} // end for
|
// } // end for
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
// if err := scanner.Err(); err != nil {
|
||||||
if strings.Contains(err.Error(), "context canceled") {
|
// if strings.Contains(err.Error(), "context canceled") {
|
||||||
logger.Info("用户取消了请求:", prompt)
|
// logger.Info("用户取消了请求:", prompt)
|
||||||
} else {
|
// } else {
|
||||||
logger.Error("信息读取出错:", err)
|
// logger.Error("信息读取出错:", err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if toolCall { // 调用函数完成任务
|
// if toolCall { // 调用函数完成任务
|
||||||
params := make(map[string]any)
|
// params := make(map[string]any)
|
||||||
_ = utils.JsonDecode(strings.Join(arguments, ""), ¶ms)
|
// _ = utils.JsonDecode(strings.Join(arguments, ""), ¶ms)
|
||||||
logger.Debugf("函数名称: %s, 函数参数:%s", function.Name, params)
|
// logger.Debugf("函数名称: %s, 函数参数:%s", function.Name, params)
|
||||||
params["user_id"] = userVo.Id
|
// params["user_id"] = userVo.Id
|
||||||
var apiRes types.BizVo
|
// var apiRes types.BizVo
|
||||||
r, err := req2.C().R().SetHeader("Body-Type", "application/json").
|
// r, err := req2.C().R().SetHeader("Body-Type", "application/json").
|
||||||
SetHeader("Authorization", function.Token).
|
// SetHeader("Authorization", function.Token).
|
||||||
SetBody(params).Post(function.Action)
|
// SetBody(params).Post(function.Action)
|
||||||
errMsg := ""
|
// errMsg := ""
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
errMsg = err.Error()
|
// errMsg = err.Error()
|
||||||
} else {
|
// } else {
|
||||||
all, _ := io.ReadAll(r.Body)
|
// all, _ := io.ReadAll(r.Body)
|
||||||
err = json.Unmarshal(all, &apiRes)
|
// err = json.Unmarshal(all, &apiRes)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
errMsg = err.Error()
|
// errMsg = err.Error()
|
||||||
} else if apiRes.Code != types.Success {
|
// } else if apiRes.Code != types.Success {
|
||||||
errMsg = apiRes.Message
|
// errMsg = apiRes.Message
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if errMsg != "" {
|
// if errMsg != "" {
|
||||||
errMsg = "调用函数工具出错:" + errMsg
|
// errMsg = "调用函数工具出错:" + errMsg
|
||||||
contents = append(contents, errMsg)
|
// contents = append(contents, errMsg)
|
||||||
} else {
|
// } else {
|
||||||
errMsg = utils.InterfaceToString(apiRes.Data)
|
// errMsg = utils.InterfaceToString(apiRes.Data)
|
||||||
contents = append(contents, errMsg)
|
// contents = append(contents, errMsg)
|
||||||
}
|
// }
|
||||||
pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
||||||
"type": "text",
|
// "type": "text",
|
||||||
"content": errMsg,
|
// "content": errMsg,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 消息发送成功
|
// // 消息发送成功
|
||||||
if len(contents) > 0 {
|
// if len(contents) > 0 {
|
||||||
usage := Usage{
|
// usage := Usage{
|
||||||
Prompt: prompt,
|
// Prompt: prompt,
|
||||||
Content: strings.Join(contents, ""),
|
// Content: strings.Join(contents, ""),
|
||||||
PromptTokens: 0,
|
// PromptTokens: 0,
|
||||||
CompletionTokens: 0,
|
// CompletionTokens: 0,
|
||||||
TotalTokens: 0,
|
// TotalTokens: 0,
|
||||||
}
|
// }
|
||||||
message.Content = usage.Content
|
// message.Content = usage.Content
|
||||||
h.saveChatHistory(req, usage, message, session, role, userVo, promptCreatedAt, replyCreatedAt)
|
// h.saveChatHistory(req, usage, message, session, role, userVo, promptCreatedAt, replyCreatedAt)
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
var respVo OpenAIResVo
|
// var respVo OpenAIResVo
|
||||||
body, err := io.ReadAll(response.Body)
|
// body, err := io.ReadAll(response.Body)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("读取响应失败:%v", body)
|
// return fmt.Errorf("读取响应失败:%v", body)
|
||||||
}
|
// }
|
||||||
err = json.Unmarshal(body, &respVo)
|
// err = json.Unmarshal(body, &respVo)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("解析响应失败:%v", body)
|
// return fmt.Errorf("解析响应失败:%v", body)
|
||||||
}
|
// }
|
||||||
content := respVo.Choices[0].Message.Content
|
// content := respVo.Choices[0].Message.Content
|
||||||
if strings.HasPrefix(req.Model, "o1-") {
|
// if strings.HasPrefix(req.Model, "o1-") {
|
||||||
content = fmt.Sprintf("AI思考结束,耗时:%d 秒。\n%s", time.Now().Unix()-session.Start, respVo.Choices[0].Message.Content)
|
// content = fmt.Sprintf("AI思考结束,耗时:%d 秒。\n%s", time.Now().Unix()-session.Start, respVo.Choices[0].Message.Content)
|
||||||
}
|
// }
|
||||||
pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
|
||||||
"type": "text",
|
// "type": "text",
|
||||||
"content": content,
|
// "content": content,
|
||||||
})
|
// })
|
||||||
respVo.Usage.Prompt = prompt
|
// respVo.Usage.Prompt = prompt
|
||||||
respVo.Usage.Content = content
|
// respVo.Usage.Content = content
|
||||||
h.saveChatHistory(req, respVo.Usage, respVo.Choices[0].Message, session, role, userVo, promptCreatedAt, time.Now())
|
// h.saveChatHistory(req, respVo.Usage, respVo.Choices[0].Message, session, role, userVo, promptCreatedAt, time.Now())
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,271 +1,254 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
// // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
// // * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
||||||
// // * Use of this source code is governed by a Apache-2.0 license
|
// * Use of this source code is governed by a Apache-2.0 license
|
||||||
// // * that can be found in the LICENSE file.
|
// * that can be found in the LICENSE file.
|
||||||
// // * @Author yangjian102621@163.com
|
// * @Author yangjian102621@163.com
|
||||||
// // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
// import (
|
import (
|
||||||
// "bufio"
|
"bufio"
|
||||||
// "context"
|
"context"
|
||||||
// "encoding/json"
|
"encoding/json"
|
||||||
// "errors"
|
"errors"
|
||||||
// "fmt"
|
"fmt"
|
||||||
// "geekai/core/types"
|
"geekai/core/types"
|
||||||
// "geekai/store/model"
|
"geekai/store/model"
|
||||||
// "geekai/store/vo"
|
"geekai/store/vo"
|
||||||
// "geekai/utils"
|
"geekai/utils"
|
||||||
// "io"
|
"io"
|
||||||
// "strings"
|
"strings"
|
||||||
// "time"
|
"time"
|
||||||
|
|
||||||
// req2 "github.com/imroc/req/v3"
|
"github.com/gin-gonic/gin"
|
||||||
// )
|
req2 "github.com/imroc/req/v3"
|
||||||
|
)
|
||||||
|
|
||||||
// type Usage struct {
|
type Usage struct {
|
||||||
// Prompt string `json:"prompt,omitempty"`
|
Prompt string `json:"prompt,omitempty"`
|
||||||
// Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
// PromptTokens int `json:"prompt_tokens"`
|
PromptTokens int `json:"prompt_tokens"`
|
||||||
// CompletionTokens int `json:"completion_tokens"`
|
CompletionTokens int `json:"completion_tokens"`
|
||||||
// TotalTokens int `json:"total_tokens"`
|
TotalTokens int `json:"total_tokens"`
|
||||||
// }
|
}
|
||||||
|
|
||||||
// type OpenAIResVo struct {
|
type OpenAIResVo struct {
|
||||||
// Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
// Object string `json:"object"`
|
Object string `json:"object"`
|
||||||
// Created int `json:"created"`
|
Created int `json:"created"`
|
||||||
// Model string `json:"model"`
|
Model string `json:"model"`
|
||||||
// SystemFingerprint string `json:"system_fingerprint"`
|
SystemFingerprint string `json:"system_fingerprint"`
|
||||||
// Choices []struct {
|
Choices []struct {
|
||||||
// Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
// Message struct {
|
Message struct {
|
||||||
// Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
// Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
// } `json:"message"`
|
} `json:"message"`
|
||||||
// Logprobs interface{} `json:"logprobs"`
|
Logprobs interface{} `json:"logprobs"`
|
||||||
// FinishReason string `json:"finish_reason"`
|
FinishReason string `json:"finish_reason"`
|
||||||
// } `json:"choices"`
|
} `json:"choices"`
|
||||||
// Usage Usage `json:"usage"`
|
Usage Usage `json:"usage"`
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // OPenAI 消息发送实现
|
// OPenAI 消息发送实现
|
||||||
// func (h *ChatHandler) sendOpenAiMessage(
|
func (h *ChatHandler) sendOpenAiMessage(
|
||||||
// req types.ApiRequest,
|
req types.ApiRequest,
|
||||||
// userVo vo.User,
|
userVo vo.User,
|
||||||
// ctx context.Context,
|
ctx context.Context,
|
||||||
// session *types.ChatSession,
|
session *types.ChatSession,
|
||||||
// role model.ChatRole,
|
role model.ChatRole,
|
||||||
// prompt string,
|
prompt string,
|
||||||
// messageChan chan interface{}) error {
|
c *gin.Context) error {
|
||||||
// promptCreatedAt := time.Now() // 记录提问时间
|
promptCreatedAt := time.Now() // 记录提问时间
|
||||||
// start := time.Now()
|
start := time.Now()
|
||||||
// var apiKey = model.ApiKey{}
|
var apiKey = model.ApiKey{}
|
||||||
// response, err := h.doRequest(ctx, req, session, &apiKey)
|
response, err := h.doRequest(ctx, req, session, &apiKey)
|
||||||
// logger.Info("HTTP请求完成,耗时:", time.Since(start))
|
logger.Info("HTTP请求完成,耗时:", time.Since(start))
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// if strings.Contains(err.Error(), "context canceled") {
|
if strings.Contains(err.Error(), "context canceled") {
|
||||||
// return fmt.Errorf("用户取消了请求:%s", prompt)
|
return fmt.Errorf("用户取消了请求:%s", prompt)
|
||||||
// } else if strings.Contains(err.Error(), "no available key") {
|
} else if strings.Contains(err.Error(), "no available key") {
|
||||||
// return errors.New("抱歉😔😔😔,系统已经没有可用的 API KEY,请联系管理员!")
|
return errors.New("抱歉😔😔😔,系统已经没有可用的 API KEY,请联系管理员!")
|
||||||
// }
|
}
|
||||||
// return err
|
return err
|
||||||
// } else {
|
} else {
|
||||||
// defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if response.StatusCode != 200 {
|
if response.StatusCode != 200 {
|
||||||
// body, _ := io.ReadAll(response.Body)
|
body, _ := io.ReadAll(response.Body)
|
||||||
// return fmt.Errorf("请求 OpenAI API 失败:%d, %v", response.StatusCode, string(body))
|
return fmt.Errorf("请求 OpenAI API 失败:%d, %v", response.StatusCode, string(body))
|
||||||
// }
|
}
|
||||||
|
|
||||||
// contentType := response.Header.Get("Content-Type")
|
contentType := response.Header.Get("Content-Type")
|
||||||
// if strings.Contains(contentType, "text/event-stream") {
|
if strings.Contains(contentType, "text/event-stream") {
|
||||||
// replyCreatedAt := time.Now() // 记录回复时间
|
replyCreatedAt := time.Now() // 记录回复时间
|
||||||
// // 循环读取 Chunk 消息
|
// 循环读取 Chunk 消息
|
||||||
// var message = types.Message{Role: "assistant"}
|
var message = types.Message{Role: "assistant"}
|
||||||
// var contents = make([]string, 0)
|
var contents = make([]string, 0)
|
||||||
// var function model.Function
|
var function model.Function
|
||||||
// var toolCall = false
|
var toolCall = false
|
||||||
// var arguments = make([]string, 0)
|
var arguments = make([]string, 0)
|
||||||
// var reasoning = false
|
var reasoning = false
|
||||||
|
|
||||||
// scanner := bufio.NewScanner(response.Body)
|
scanner := bufio.NewScanner(response.Body)
|
||||||
// for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
// line := scanner.Text()
|
line := scanner.Text()
|
||||||
// if !strings.Contains(line, "data:") || len(line) < 30 {
|
if !strings.Contains(line, "data:") || len(line) < 30 {
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
// var responseBody = types.ApiResponse{}
|
var responseBody = types.ApiResponse{}
|
||||||
// err = json.Unmarshal([]byte(line[6:]), &responseBody)
|
err = json.Unmarshal([]byte(line[6:]), &responseBody)
|
||||||
// if err != nil { // 数据解析出错
|
if err != nil { // 数据解析出错
|
||||||
// return errors.New(line)
|
return errors.New(line)
|
||||||
// }
|
}
|
||||||
// if len(responseBody.Choices) == 0 { // Fixed: 兼容 Azure API 第一个输出空行
|
if len(responseBody.Choices) == 0 { // Fixed: 兼容 Azure API 第一个输出空行
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
// if responseBody.Choices[0].Delta.Content == nil &&
|
if responseBody.Choices[0].Delta.Content == nil &&
|
||||||
// responseBody.Choices[0].Delta.ToolCalls == nil &&
|
responseBody.Choices[0].Delta.ToolCalls == nil &&
|
||||||
// responseBody.Choices[0].Delta.ReasoningContent == "" {
|
responseBody.Choices[0].Delta.ReasoningContent == "" {
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if responseBody.Choices[0].FinishReason == "stop" && len(contents) == 0 {
|
if responseBody.Choices[0].FinishReason == "stop" && len(contents) == 0 {
|
||||||
// messageChan <- map[string]interface{}{
|
pushMessage(c, "text", "抱歉😔😔😔,AI助手由于未知原因已经停止输出内容。")
|
||||||
// "type": "text",
|
break
|
||||||
// "body": "抱歉😔😔😔,AI助手由于未知原因已经停止输出内容。",
|
}
|
||||||
// }
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var tool types.ToolCall
|
var tool types.ToolCall
|
||||||
// if len(responseBody.Choices[0].Delta.ToolCalls) > 0 {
|
if len(responseBody.Choices[0].Delta.ToolCalls) > 0 {
|
||||||
// tool = responseBody.Choices[0].Delta.ToolCalls[0]
|
tool = responseBody.Choices[0].Delta.ToolCalls[0]
|
||||||
// if toolCall && tool.Function.Name == "" {
|
if toolCall && tool.Function.Name == "" {
|
||||||
// arguments = append(arguments, tool.Function.Arguments)
|
arguments = append(arguments, tool.Function.Arguments)
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // 兼容 Function Call
|
// 兼容 Function Call
|
||||||
// fun := responseBody.Choices[0].Delta.FunctionCall
|
fun := responseBody.Choices[0].Delta.FunctionCall
|
||||||
// if fun.Name != "" {
|
if fun.Name != "" {
|
||||||
// tool = *new(types.ToolCall)
|
tool = *new(types.ToolCall)
|
||||||
// tool.Function.Name = fun.Name
|
tool.Function.Name = fun.Name
|
||||||
// } else if toolCall {
|
} else if toolCall {
|
||||||
// arguments = append(arguments, fun.Arguments)
|
arguments = append(arguments, fun.Arguments)
|
||||||
// continue
|
continue
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if !utils.IsEmptyValue(tool) {
|
if !utils.IsEmptyValue(tool) {
|
||||||
// res := h.DB.Where("name = ?", tool.Function.Name).First(&function)
|
res := h.DB.Where("name = ?", tool.Function.Name).First(&function)
|
||||||
// if res.Error == nil {
|
if res.Error == nil {
|
||||||
// toolCall = true
|
toolCall = true
|
||||||
// callMsg := fmt.Sprintf("正在调用工具 `%s` 作答 ...\n\n", function.Label)
|
callMsg := fmt.Sprintf("正在调用工具 `%s` 作答 ...\n\n", function.Label)
|
||||||
// messageChan <- map[string]interface{}{
|
pushMessage(c, "text", callMsg)
|
||||||
// "type": "text",
|
contents = append(contents, callMsg)
|
||||||
// "body": callMsg,
|
}
|
||||||
// }
|
continue
|
||||||
// contents = append(contents, callMsg)
|
}
|
||||||
// }
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if responseBody.Choices[0].FinishReason == "tool_calls" ||
|
if responseBody.Choices[0].FinishReason == "tool_calls" ||
|
||||||
// responseBody.Choices[0].FinishReason == "function_call" { // 函数调用完毕
|
responseBody.Choices[0].FinishReason == "function_call" { // 函数调用完毕
|
||||||
// break
|
break
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // output stopped
|
// output stopped
|
||||||
// if responseBody.Choices[0].FinishReason != "" {
|
if responseBody.Choices[0].FinishReason != "" {
|
||||||
// break // 输出完成或者输出中断了
|
break // 输出完成或者输出中断了
|
||||||
// } else { // 正常输出结果
|
} else { // 正常输出结果
|
||||||
// // 兼容思考过程
|
// 兼容思考过程
|
||||||
// if responseBody.Choices[0].Delta.ReasoningContent != "" {
|
if responseBody.Choices[0].Delta.ReasoningContent != "" {
|
||||||
// reasoningContent := responseBody.Choices[0].Delta.ReasoningContent
|
reasoningContent := responseBody.Choices[0].Delta.ReasoningContent
|
||||||
// if !reasoning {
|
if !reasoning {
|
||||||
// reasoningContent = fmt.Sprintf("<think>%s", reasoningContent)
|
reasoningContent = fmt.Sprintf("<think>%s", reasoningContent)
|
||||||
// reasoning = true
|
reasoning = true
|
||||||
// }
|
}
|
||||||
|
|
||||||
// messageChan <- map[string]interface{}{
|
pushMessage(c, "text", reasoningContent)
|
||||||
// "type": "text",
|
contents = append(contents, reasoningContent)
|
||||||
// "body": reasoningContent,
|
} else if responseBody.Choices[0].Delta.Content != "" {
|
||||||
// }
|
finalContent := responseBody.Choices[0].Delta.Content
|
||||||
// contents = append(contents, reasoningContent)
|
if reasoning {
|
||||||
// } else if responseBody.Choices[0].Delta.Content != "" {
|
finalContent = fmt.Sprintf("</think>%s", responseBody.Choices[0].Delta.Content)
|
||||||
// finalContent := responseBody.Choices[0].Delta.Content
|
reasoning = false
|
||||||
// if reasoning {
|
}
|
||||||
// finalContent = fmt.Sprintf("</think>%s", responseBody.Choices[0].Delta.Content)
|
contents = append(contents, utils.InterfaceToString(finalContent))
|
||||||
// reasoning = false
|
pushMessage(c, "text", finalContent)
|
||||||
// }
|
}
|
||||||
// contents = append(contents, utils.InterfaceToString(finalContent))
|
}
|
||||||
// messageChan <- map[string]interface{}{
|
} // end for
|
||||||
// "type": "text",
|
|
||||||
// "body": finalContent,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } // end for
|
|
||||||
|
|
||||||
// if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
// if strings.Contains(err.Error(), "context canceled") {
|
if strings.Contains(err.Error(), "context canceled") {
|
||||||
// logger.Info("用户取消了请求:", prompt)
|
logger.Info("用户取消了请求:", prompt)
|
||||||
// } else {
|
} else {
|
||||||
// logger.Error("信息读取出错:", err)
|
logger.Error("信息读取出错:", err)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if toolCall { // 调用函数完成任务
|
if toolCall { // 调用函数完成任务
|
||||||
// params := make(map[string]any)
|
params := make(map[string]any)
|
||||||
// _ = utils.JsonDecode(strings.Join(arguments, ""), ¶ms)
|
_ = utils.JsonDecode(strings.Join(arguments, ""), ¶ms)
|
||||||
// logger.Debugf("函数名称: %s, 函数参数:%s", function.Name, params)
|
logger.Debugf("函数名称: %s, 函数参数:%s", function.Name, params)
|
||||||
// params["user_id"] = userVo.Id
|
params["user_id"] = userVo.Id
|
||||||
// var apiRes types.BizVo
|
var apiRes types.BizVo
|
||||||
// r, err := req2.C().R().SetHeader("Body-Type", "application/json").
|
r, err := req2.C().R().SetHeader("Body-Type", "application/json").
|
||||||
// SetHeader("Authorization", function.Token).
|
SetHeader("Authorization", function.Token).
|
||||||
// SetBody(params).Post(function.Action)
|
SetBody(params).Post(function.Action)
|
||||||
// errMsg := ""
|
errMsg := ""
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// errMsg = err.Error()
|
errMsg = err.Error()
|
||||||
// } else {
|
} else {
|
||||||
// all, _ := io.ReadAll(r.Body)
|
all, _ := io.ReadAll(r.Body)
|
||||||
// err = json.Unmarshal(all, &apiRes)
|
err = json.Unmarshal(all, &apiRes)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// errMsg = err.Error()
|
errMsg = err.Error()
|
||||||
// } else if apiRes.Code != types.Success {
|
} else if apiRes.Code != types.Success {
|
||||||
// errMsg = apiRes.Message
|
errMsg = apiRes.Message
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if errMsg != "" {
|
if errMsg != "" {
|
||||||
// errMsg = "调用函数工具出错:" + errMsg
|
errMsg = "调用函数工具出错:" + errMsg
|
||||||
// contents = append(contents, errMsg)
|
contents = append(contents, errMsg)
|
||||||
// } else {
|
} else {
|
||||||
// errMsg = utils.InterfaceToString(apiRes.Data)
|
errMsg = utils.InterfaceToString(apiRes.Data)
|
||||||
// contents = append(contents, errMsg)
|
contents = append(contents, errMsg)
|
||||||
// }
|
}
|
||||||
// messageChan <- map[string]interface{}{
|
pushMessage(c, "text", errMsg)
|
||||||
// "type": "text",
|
}
|
||||||
// "body": errMsg,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 消息发送成功
|
// 消息发送成功
|
||||||
// if len(contents) > 0 {
|
if len(contents) > 0 {
|
||||||
// usage := Usage{
|
usage := Usage{
|
||||||
// Prompt: prompt,
|
Prompt: prompt,
|
||||||
// Content: strings.Join(contents, ""),
|
Content: strings.Join(contents, ""),
|
||||||
// PromptTokens: 0,
|
PromptTokens: 0,
|
||||||
// CompletionTokens: 0,
|
CompletionTokens: 0,
|
||||||
// TotalTokens: 0,
|
TotalTokens: 0,
|
||||||
// }
|
}
|
||||||
// message.Content = usage.Content
|
message.Content = usage.Content
|
||||||
// h.saveChatHistory(req, usage, message, session, role, userVo, promptCreatedAt, replyCreatedAt)
|
h.saveChatHistory(req, usage, message, session, role, userVo, promptCreatedAt, replyCreatedAt)
|
||||||
// }
|
}
|
||||||
// } else { // 非流式输出
|
} else { // 非流式输出
|
||||||
// var respVo OpenAIResVo
|
var respVo OpenAIResVo
|
||||||
// body, err := io.ReadAll(response.Body)
|
body, err := io.ReadAll(response.Body)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return fmt.Errorf("读取响应失败:%v", body)
|
return fmt.Errorf("读取响应失败:%v", body)
|
||||||
// }
|
}
|
||||||
// err = json.Unmarshal(body, &respVo)
|
err = json.Unmarshal(body, &respVo)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return fmt.Errorf("解析响应失败:%v", body)
|
return fmt.Errorf("解析响应失败:%v", body)
|
||||||
// }
|
}
|
||||||
// content := respVo.Choices[0].Message.Content
|
content := respVo.Choices[0].Message.Content
|
||||||
// if strings.HasPrefix(req.Model, "o1-") {
|
if strings.HasPrefix(req.Model, "o1-") {
|
||||||
// content = fmt.Sprintf("AI思考结束,耗时:%d 秒。\n%s", time.Now().Unix()-session.Start, respVo.Choices[0].Message.Content)
|
content = fmt.Sprintf("AI思考结束,耗时:%d 秒。\n%s", time.Now().Unix()-session.Start, respVo.Choices[0].Message.Content)
|
||||||
// }
|
}
|
||||||
// messageChan <- map[string]interface{}{
|
pushMessage(c, "text", content)
|
||||||
// "type": "text",
|
respVo.Usage.Prompt = prompt
|
||||||
// "body": content,
|
respVo.Usage.Content = content
|
||||||
// }
|
h.saveChatHistory(req, respVo.Usage, respVo.Choices[0].Message, session, role, userVo, promptCreatedAt, time.Now())
|
||||||
// respVo.Usage.Prompt = prompt
|
}
|
||||||
// respVo.Usage.Content = content
|
|
||||||
// h.saveChatHistory(req, respVo.Usage, respVo.Choices[0].Message, session, role, userVo, promptCreatedAt, time.Now())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return nil
|
return nil
|
||||||
// }
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
.chat-page {
|
.chat-page {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
:deep (.el-message-box__message){
|
:deep(.el-message-box__message){
|
||||||
font-size: 18px !important
|
font-size: 18px !important
|
||||||
}
|
}
|
||||||
.newChat{
|
.newChat{
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ const props = defineProps({
|
|||||||
tokens: 0,
|
tokens: 0,
|
||||||
model: '',
|
model: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
|
files: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
listStyle: {
|
listStyle: {
|
||||||
@@ -146,7 +147,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
const finalTokens = ref(props.data.tokens)
|
const finalTokens = ref(props.data.tokens)
|
||||||
const content = ref(processPrompt(props.data.content))
|
const content = ref(processPrompt(props.data.content))
|
||||||
const files = ref([])
|
const files = ref(props.data.files)
|
||||||
|
|
||||||
// 定义emit事件
|
// 定义emit事件
|
||||||
const emit = defineEmits(['edit'])
|
const emit = defineEmits(['edit'])
|
||||||
@@ -159,38 +160,6 @@ const processFiles = () => {
|
|||||||
if (!props.data.content) {
|
if (!props.data.content) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取图片|文件链接
|
|
||||||
const linkRegex = /(https?:\/\/\S+)/g
|
|
||||||
const links = props.data.content.match(linkRegex)
|
|
||||||
const urlPrefix = `${window.location.protocol}//${window.location.host}`
|
|
||||||
if (links) {
|
|
||||||
// 把本地链接转换为相对路径
|
|
||||||
const _links = links.map((link) => {
|
|
||||||
if (link.startsWith(urlPrefix)) {
|
|
||||||
return link.replace(urlPrefix, '')
|
|
||||||
}
|
|
||||||
return link
|
|
||||||
})
|
|
||||||
// 合并数组并去重
|
|
||||||
const urls = [...new Set([...links, ..._links])]
|
|
||||||
httpPost('/api/upload/list', { urls: urls })
|
|
||||||
.then((res) => {
|
|
||||||
files.value = res.data.items
|
|
||||||
|
|
||||||
// for (let link of links) {
|
|
||||||
// if (isExternalImg(link, files.value)) {
|
|
||||||
// files.value.push({ url: link, ext: ".png" });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
|
|
||||||
// 替换图片|文件链接
|
|
||||||
for (let link of links) {
|
|
||||||
content.value = content.value.replace(link, '')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
content.value = md.render(content.value.trim())
|
content.value = md.render(content.value.trim())
|
||||||
}
|
}
|
||||||
const isExternalImg = (link, files) => {
|
const isExternalImg = (link, files) => {
|
||||||
|
|||||||
@@ -1,22 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
class="config-dialog"
|
class="config-dialog"
|
||||||
v-model="showDialog"
|
v-model="showDialog"
|
||||||
:close-on-click-modal="true"
|
:close-on-click-modal="true"
|
||||||
:before-close="close"
|
:before-close="close"
|
||||||
style="max-width: 600px"
|
style="max-width: 600px"
|
||||||
title="聊天配置"
|
title="聊天配置"
|
||||||
>
|
>
|
||||||
<div class="chat-setting">
|
<div class="chat-setting">
|
||||||
<el-form :model="data" label-width="100px" label-position="left">
|
<el-form :model="data" label-width="100px" label-position="left">
|
||||||
<el-form-item label="聊天样式:">
|
<el-form-item label="聊天样式:">
|
||||||
<el-radio-group v-model="data.style" @change="(val) => {store.setChatListStyle(val)}">
|
<el-radio-group
|
||||||
|
v-model="data.style"
|
||||||
|
@change="
|
||||||
|
(val) => {
|
||||||
|
store.setChatListStyle(val)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
<el-radio value="list">列表样式</el-radio>
|
<el-radio value="list">列表样式</el-radio>
|
||||||
<el-radio value="chat">对话样式</el-radio>
|
<el-radio value="chat">对话样式</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="流式输出:">
|
<el-form-item label="流式输出:">
|
||||||
<el-switch v-model="data.stream" @change="(val) => {store.setChatStream(val)}" />
|
<el-switch
|
||||||
|
v-model="data.stream"
|
||||||
|
@change="
|
||||||
|
(val) => {
|
||||||
|
store.setChatStream(val)
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="语音音色:">
|
<el-form-item label="语音音色:">
|
||||||
<el-select v-model="data.ttsModel" placeholder="请选择语音音色" @change="changeTTSModel">
|
<el-select v-model="data.ttsModel" placeholder="请选择语音音色" @change="changeTTSModel">
|
||||||
@@ -31,10 +45,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, ref, onMounted} from "vue"
|
import { computed, ref, onMounted } from 'vue'
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import { useSharedStore } from '@/store/sharedata'
|
||||||
import {httpGet} from "@/utils/http";
|
import { httpGet } from '@/utils/http'
|
||||||
const store = useSharedStore();
|
const store = useSharedStore()
|
||||||
|
|
||||||
const data = ref({
|
const data = ref({
|
||||||
style: store.chatListStyle,
|
style: store.chatListStyle,
|
||||||
@@ -44,28 +58,28 @@ const data = ref({
|
|||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: Boolean,
|
show: Boolean,
|
||||||
});
|
})
|
||||||
|
|
||||||
const showDialog = computed(() => {
|
const showDialog = computed(() => {
|
||||||
return props.show
|
return props.show
|
||||||
})
|
})
|
||||||
const emits = defineEmits(['hide']);
|
const emits = defineEmits(['hide'])
|
||||||
const close = function () {
|
const close = function () {
|
||||||
emits('hide', false);
|
emits('hide', false)
|
||||||
}
|
}
|
||||||
const models = ref([]);
|
const models = ref([])
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 获取模型列表
|
// 获取模型列表
|
||||||
httpGet("/api/model/list?type=tts").then((res) => {
|
httpGet('/api/model/list?type=tts').then((res) => {
|
||||||
models.value = res.data;
|
models.value = res.data
|
||||||
if (!data.ttsModel) {
|
if (!data.ttsModel && models.value.length > 0) {
|
||||||
store.setTtsModel(models.value[0].id);
|
store.setTtsModel(models.value[0].id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const changeTTSModel = (item) => {
|
const changeTTSModel = (item) => {
|
||||||
store.setTtsModel(item);
|
store.setTtsModel(item)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -709,11 +709,6 @@ onMounted(() => {
|
|||||||
// 初始化数据
|
// 初始化数据
|
||||||
const initData = async () => {
|
const initData = async () => {
|
||||||
try {
|
try {
|
||||||
// 获取用户信息
|
|
||||||
const user = await checkSession()
|
|
||||||
loginUser.value = user
|
|
||||||
isLogin.value = true
|
|
||||||
|
|
||||||
// 获取角色列表
|
// 获取角色列表
|
||||||
const roleRes = await httpGet('/api/app/list')
|
const roleRes = await httpGet('/api/app/list')
|
||||||
roles.value = roleRes.data
|
roles.value = roleRes.data
|
||||||
@@ -728,6 +723,11 @@ const initData = async () => {
|
|||||||
modelID.value = models.value[0].id
|
modelID.value = models.value[0].id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
const user = await checkSession()
|
||||||
|
loginUser.value = user
|
||||||
|
isLogin.value = true
|
||||||
|
|
||||||
// 获取聊天列表
|
// 获取聊天列表
|
||||||
const chatRes = await httpGet('/api/chat/list')
|
const chatRes = await httpGet('/api/chat/list')
|
||||||
allChats.value = chatRes.data
|
allChats.value = chatRes.data
|
||||||
@@ -739,7 +739,7 @@ const initData = async () => {
|
|||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
isLogin.value = false
|
isLogin.value = false
|
||||||
} else {
|
} else {
|
||||||
showMessageError('初始化数据失败:' + error.message)
|
console.warn('初始化数据失败:' + error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -762,20 +762,15 @@ const sendMessage = async function () {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果携带了文件,则串上文件地址
|
|
||||||
let content = prompt.value
|
|
||||||
if (files.value.length > 0) {
|
|
||||||
content += files.value.map((file) => file.url).join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 追加消息
|
// 追加消息
|
||||||
chatData.value.push({
|
chatData.value.push({
|
||||||
type: 'prompt',
|
type: 'prompt',
|
||||||
id: randString(32),
|
id: randString(32),
|
||||||
icon: loginUser.value.avatar,
|
icon: loginUser.value.avatar,
|
||||||
content: content,
|
content: prompt.value,
|
||||||
model: getModelValue(modelID.value),
|
model: getModelValue(modelID.value),
|
||||||
created_at: new Date().getTime() / 1000,
|
created_at: new Date().getTime() / 1000,
|
||||||
|
files: files.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 添加空回复消息
|
// 添加空回复消息
|
||||||
@@ -809,9 +804,10 @@ const sendMessage = async function () {
|
|||||||
role_id: roleId.value,
|
role_id: roleId.value,
|
||||||
model_id: modelID.value,
|
model_id: modelID.value,
|
||||||
chat_id: chatId.value,
|
chat_id: chatId.value,
|
||||||
content: content,
|
content: prompt.value,
|
||||||
tools: toolSelected.value,
|
tools: toolSelected.value,
|
||||||
stream: stream.value,
|
stream: stream.value,
|
||||||
|
files: files.value,
|
||||||
}),
|
}),
|
||||||
openWhenHidden: true,
|
openWhenHidden: true,
|
||||||
onopen(response) {
|
onopen(response) {
|
||||||
@@ -902,7 +898,7 @@ const sendMessage = async function () {
|
|||||||
ElMessage.error('发送消息失败,请重试')
|
ElMessage.error('发送消息失败,请重试')
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpChatTitle.value = content
|
tmpChatTitle.value = prompt.value
|
||||||
prompt.value = ''
|
prompt.value = ''
|
||||||
files.value = []
|
files.value = []
|
||||||
row.value = 1
|
row.value = 1
|
||||||
|
|||||||
Reference in New Issue
Block a user