修复聊天页面报错重连的 bug

This commit is contained in:
RockYang
2025-08-11 20:29:19 +08:00
parent e7db720986
commit ad52613738
3 changed files with 20 additions and 222 deletions

View File

@@ -460,7 +460,7 @@ func (h *ChatHandler) StopGenerate(c *gin.Context) {
func (h *ChatHandler) doRequest(ctx context.Context, req types.ApiRequest, input ChatInput, apiKey *model.ApiKey) (*http.Response, error) {
// if the chat model bind a KEY, use it directly
if input.ChatModel.KeyId > 0 {
h.DB.Where("id", input.ChatModel.KeyId).Find(apiKey)
h.DB.Where("id", input.ChatModel.KeyId).Where("enabled", true).Find(apiKey)
} else { // use the last unused key
h.DB.Where("type", "chat").Where("enabled", true).Order("last_used_at ASC").First(apiKey)
}
@@ -725,221 +725,3 @@ func (h *ChatHandler) TextToSpeech(c *gin.Context) {
logger.Error("写入音频数据到响应失败:", err)
}
}
// // OPenAI 消息发送实现
// func (h *ChatHandler) sendOpenAiMessage(
// req types.ApiRequest,
// userVo vo.User,
// ctx context.Context,
// session *types.ChatSession,
// role model.ChatRole,
// prompt string,
// c *gin.Context) error {
// promptCreatedAt := time.Now() // 记录提问时间
// start := time.Now()
// var apiKey = model.ApiKey{}
// response, err := h.doRequest(ctx, req, session, &apiKey)
// logger.Info("HTTP请求完成耗时", time.Since(start))
// if err != nil {
// if strings.Contains(err.Error(), "context canceled") {
// return fmt.Errorf("用户取消了请求:%s", prompt)
// } else if strings.Contains(err.Error(), "no available key") {
// return errors.New("抱歉😔😔😔,系统已经没有可用的 API KEY请联系管理员")
// }
// return err
// } else {
// defer response.Body.Close()
// }
// if response.StatusCode != 200 {
// body, _ := io.ReadAll(response.Body)
// return fmt.Errorf("请求 OpenAI API 失败:%d, %v", response.StatusCode, string(body))
// }
// contentType := response.Header.Get("Prompt-Type")
// if strings.Contains(contentType, "text/event-stream") {
// replyCreatedAt := time.Now() // 记录回复时间
// // 循环读取 Chunk 消息
// var message = types.Message{Role: "assistant"}
// var contents = make([]string, 0)
// var function model.Function
// var toolCall = false
// var arguments = make([]string, 0)
// var reasoning = false
// pushMessage(c, ChatEventStart, "开始响应")
// scanner := bufio.NewScanner(response.Body)
// for scanner.Scan() {
// line := scanner.Text()
// if !strings.Contains(line, "data:") || len(line) < 30 {
// continue
// }
// var responseBody = types.ApiResponse{}
// err = json.Unmarshal([]byte(line[6:]), &responseBody)
// if err != nil { // 数据解析出错
// return errors.New(line)
// }
// if len(responseBody.Choices) == 0 { // Fixed: 兼容 Azure API 第一个输出空行
// continue
// }
// if responseBody.Choices[0].Delta.Prompt == nil &&
// responseBody.Choices[0].Delta.ToolCalls == nil &&
// responseBody.Choices[0].Delta.ReasoningContent == "" {
// continue
// }
// if responseBody.Choices[0].FinishReason == "stop" && len(contents) == 0 {
// pushMessage(c, ChatEventError, "抱歉😔😔😔AI助手由于未知原因已经停止输出内容。")
// break
// }
// var tool types.ToolCall
// if len(responseBody.Choices[0].Delta.ToolCalls) > 0 {
// tool = responseBody.Choices[0].Delta.ToolCalls[0]
// if toolCall && tool.Function.Name == "" {
// arguments = append(arguments, tool.Function.Arguments)
// continue
// }
// }
// // 兼容 Function Call
// fun := responseBody.Choices[0].Delta.FunctionCall
// if fun.Name != "" {
// tool = *new(types.ToolCall)
// tool.Function.Name = fun.Name
// } else if toolCall {
// arguments = append(arguments, fun.Arguments)
// continue
// }
// if !utils.IsEmptyValue(tool) {
// res := h.DB.Where("name = ?", tool.Function.Name).First(&function)
// if res.Error == nil {
// toolCall = true
// callMsg := fmt.Sprintf("正在调用工具 `%s` 作答 ...\n\n", function.Label)
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": callMsg,
// })
// contents = append(contents, callMsg)
// }
// continue
// }
// if responseBody.Choices[0].FinishReason == "tool_calls" ||
// responseBody.Choices[0].FinishReason == "function_call" { // 函数调用完毕
// break
// }
// // output stopped
// if responseBody.Choices[0].FinishReason != "" {
// break // 输出完成或者输出中断了
// } else { // 正常输出结果
// // 兼容思考过程
// if responseBody.Choices[0].Delta.ReasoningContent != "" {
// reasoningContent := responseBody.Choices[0].Delta.ReasoningContent
// if !reasoning {
// reasoningContent = fmt.Sprintf("<think>%s", reasoningContent)
// reasoning = true
// }
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": reasoningContent,
// })
// contents = append(contents, reasoningContent)
// } else if responseBody.Choices[0].Delta.Prompt != "" {
// finalContent := responseBody.Choices[0].Delta.Prompt
// if reasoning {
// finalContent = fmt.Sprintf("</think>%s", responseBody.Choices[0].Delta.Prompt)
// reasoning = false
// }
// contents = append(contents, utils.InterfaceToString(finalContent))
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": finalContent,
// })
// }
// }
// } // end for
// if err := scanner.Err(); err != nil {
// if strings.Contains(err.Error(), "context canceled") {
// logger.Info("用户取消了请求:", prompt)
// } else {
// logger.Error("信息读取出错:", err)
// }
// }
// if toolCall { // 调用函数完成任务
// params := make(map[string]any)
// _ = utils.JsonDecode(strings.Join(arguments, ""), &params)
// logger.Debugf("函数名称: %s, 函数参数:%s", function.Name, params)
// params["user_id"] = userVo.Id
// var apiRes types.BizVo
// r, err := req2.C().R().SetHeader("Body-Type", "application/json").
// SetHeader("Authorization", function.Token).
// SetBody(params).Post(function.Action)
// errMsg := ""
// if err != nil {
// errMsg = err.Error()
// } else {
// all, _ := io.ReadAll(r.Body)
// err = json.Unmarshal(all, &apiRes)
// if err != nil {
// errMsg = err.Error()
// } else if apiRes.Code != types.Success {
// errMsg = apiRes.Message
// }
// }
// if errMsg != "" {
// errMsg = "调用函数工具出错:" + errMsg
// contents = append(contents, errMsg)
// } else {
// errMsg = utils.InterfaceToString(apiRes.Data)
// contents = append(contents, errMsg)
// }
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": errMsg,
// })
// }
// // 消息发送成功
// if len(contents) > 0 {
// usage := Usage{
// Prompt: prompt,
// Prompt: strings.Join(contents, ""),
// PromptTokens: 0,
// CompletionTokens: 0,
// TotalTokens: 0,
// }
// message.Prompt = usage.Prompt
// h.saveChatHistory(req, usage, message, session, role, userVo, promptCreatedAt, replyCreatedAt)
// }
// } else {
// var respVo OpenAIResVo
// body, err := io.ReadAll(response.Body)
// if err != nil {
// return fmt.Errorf("读取响应失败:%v", body)
// }
// err = json.Unmarshal(body, &respVo)
// if err != nil {
// return fmt.Errorf("解析响应失败:%v", body)
// }
// content := respVo.Choices[0].Message.Prompt
// if strings.HasPrefix(req.Model, "o1-") {
// content = fmt.Sprintf("AI思考结束耗时%d 秒。\n%s", time.Now().Unix()-session.Start, respVo.Choices[0].Message.Prompt)
// }
// pushMessage(c, ChatEventMessageDelta, map[string]interface{}{
// "type": "text",
// "content": content,
// })
// respVo.Usage.Prompt = prompt
// respVo.Usage.Prompt = content
// h.saveChatHistory(req, respVo.Usage, respVo.Choices[0].Message, session, role, userVo, promptCreatedAt, time.Now())
// }
// return nil
// }

View File

@@ -131,6 +131,7 @@ const props = defineProps({
data: {
type: Object,
default: {
type: 'text',
icon: '',
content: {
text: '',

View File

@@ -703,18 +703,33 @@ const sendSSERequest = async (message) => {
},
body: JSON.stringify(message),
openWhenHidden: true,
// 禁用重试机制,避免连接断开后一直重试
retry: false,
// 设置重试延迟为0确保不重试
retryDelay: 0,
// 设置最大重试次数为0
maxRetries: 0,
onopen(response) {
if (response.ok && response.status === 200) {
console.log('SSE connection opened')
} else {
throw new Error(`Failed to open SSE connection: ${response.status}`)
const errorMsg = `连接失败 (状态码: ${response.status})`
ElMessage.error(errorMsg)
enableInput()
throw new Error(errorMsg)
}
},
onmessage(msg) {
try {
const data = JSON.parse(msg.data)
if (data.type === 'error') {
ElMessage.error(data.body)
// ElMessage.error(data.body)
const reply = chatData.value[chatData.value.length - 1]
if (reply) {
reply[
'content'
].text = `<div class="bg-red-50 text-red-500 p-3 rounded-md">${data.body}</div>`
}
enableInput()
return
}
@@ -779,7 +794,7 @@ const sendSSERequest = async (message) => {
onerror(err) {
console.error('SSE Error:', err)
enableInput()
ElMessage.error('连接已断开,请重试')
ElMessage.error('连接已断开,发生错误:' + err.message)
},
onclose() {
console.log('SSE connection closed')