diff --git a/api/handler/chat_handler.go b/api/handler/chat_handler.go index df4b4e28..0aad7821 100644 --- a/api/handler/chat_handler.go +++ b/api/handler/chat_handler.go @@ -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("%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("%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, ""), ¶ms) -// 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 -// } diff --git a/web/src/components/ChatReply.vue b/web/src/components/ChatReply.vue index 2aecee88..58fd6cda 100644 --- a/web/src/components/ChatReply.vue +++ b/web/src/components/ChatReply.vue @@ -131,6 +131,7 @@ const props = defineProps({ data: { type: Object, default: { + type: 'text', icon: '', content: { text: '', diff --git a/web/src/views/ChatPlus.vue b/web/src/views/ChatPlus.vue index 625003c8..dcf0eb53 100644 --- a/web/src/views/ChatPlus.vue +++ b/web/src/views/ChatPlus.vue @@ -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 = `
${data.body}
` + } 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')