From 5f0b8161b7c430fd25b244c81f52cd00342c58bb Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 9 Apr 2025 15:22:35 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20WebUI=20Chat=20?= =?UTF-8?q?=E7=9A=84=E6=B5=81=E5=BC=8F=E4=BC=A0=E8=BE=93=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/provider/sources/openai_source.py | 10 +- astrbot/dashboard/routes/chat.py | 2 +- dashboard/src/views/ChatPage.vue | 98 +++++++++++-------- 3 files changed, 67 insertions(+), 43 deletions(-) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 9f3db42a4..8023d18d1 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -8,6 +8,7 @@ import astrbot.core.message.components as Comp from openai import AsyncOpenAI, AsyncAzureOpenAI from openai.types.chat.chat_completion import ChatCompletion + # from openai.types.chat.chat_completion_chunk import ChatCompletionChunk from openai._exceptions import NotFoundError, UnprocessableEntityError from openai.lib.streaming.chat._completions import ChatCompletionStreamState @@ -143,14 +144,19 @@ class ProviderOpenAIOfficial(Provider): state = ChatCompletionStreamState() async for chunk in stream: - state.handle_chunk(chunk) + try: + state.handle_chunk(chunk) + except Exception as e: + logger.warning("Saving chunk state error: " + str(e)) if len(chunk.choices) == 0: continue delta = chunk.choices[0].delta # 处理文本内容 if delta.content: completion_text = delta.content - llm_response.result_chain = MessageChain(chain=[Comp.Plain(completion_text)]) + llm_response.result_chain = MessageChain( + chain=[Comp.Plain(completion_text)] + ) yield llm_response final_completion = state.get_final_completion() diff --git a/astrbot/dashboard/routes/chat.py b/astrbot/dashboard/routes/chat.py index 0f4e82ea4..d767ddea4 100644 --- a/astrbot/dashboard/routes/chat.py +++ b/astrbot/dashboard/routes/chat.py @@ -190,7 +190,7 @@ class ChatRoute(Route): # 丢弃 continue yield f"data: {json.dumps(result, ensure_ascii=False)}\n\n" - await asyncio.sleep(0.15) + await asyncio.sleep(0.05) if streaming and type != "end": continue diff --git a/dashboard/src/views/ChatPage.vue b/dashboard/src/views/ChatPage.vue index b00e96d66..07eb41c22 100644 --- a/dashboard/src/views/ChatPage.vue +++ b/dashboard/src/views/ChatPage.vue @@ -36,11 +36,11 @@ marked.setOptions({ 新对话 {{ formatDate(item.updated_at) - }} + }} - +
@@ -57,7 +57,8 @@ marked.setOptions({ LLM 服务 @@ -65,7 +66,8 @@ marked.setOptions({ 语音转文本 @@ -295,50 +297,66 @@ export default { const chunk = decoder.decode(value, { stream: true }); - // data: {"type": "plain", "data": "helloworld"} - let chunk_json = JSON.parse(chunk.replace('data: ', '')); + // 可能有多行 - if (chunk_json.type === 'heartbeat') { - continue; // 心跳包 - } - if (chunk_json.type === 'error') { - console.error('Error received:', chunk_json.data); - continue; - } + let lines = chunk.split('\n\n'); - if (chunk_json.type === 'image') { - let img = chunk_json.data.replace('[IMAGE]', ''); - let bot_resp = { - type: 'bot', - message: `` + console.log('SSE数据:', lines); + + for (let i = 0; i < lines.length; i++) { + let line = lines[i].trim(); + + if (!line) { + continue; } - this.messages.push(bot_resp); - } else if (chunk_json.type === 'record') { - let audio = chunk_json.data.replace('[RECORD]', ''); - let bot_resp = { - type: 'bot', - message: `` + + console.log(line) + + // data: {"type": "plain", "data": "helloworld"} + let chunk_json = JSON.parse(line.replace('data: ', '')); + + if (chunk_json.type === 'heartbeat') { + continue; // 心跳包 } - this.messages.push(bot_resp); - } else if (chunk_json.type === 'plain') { - if (!in_streaming) { - message_obj = { + if (chunk_json.type === 'error') { + console.error('Error received:', chunk_json.data); + continue; + } + + if (chunk_json.type === 'image') { + let img = chunk_json.data.replace('[IMAGE]', ''); + let bot_resp = { type: 'bot', - message: ref(chunk_json.data), + message: `` } - this.messages.push(message_obj); - in_streaming = true; - } else { - message_obj.message.value += chunk_json.data; + this.messages.push(bot_resp); + } else if (chunk_json.type === 'record') { + let audio = chunk_json.data.replace('[RECORD]', ''); + let bot_resp = { + type: 'bot', + message: `` + } + this.messages.push(bot_resp); + } else if (chunk_json.type === 'plain') { + if (!in_streaming) { + message_obj = { + type: 'bot', + message: ref(chunk_json.data), + } + this.messages.push(message_obj); + in_streaming = true; + } else { + message_obj.message.value += chunk_json.data; + } + } else if (chunk_json.type === 'end') { + in_streaming = false; + continue; } - } else if (chunk_json.type === 'end') { - in_streaming = false; - continue; + this.scrollToBottom(); } - this.scrollToBottom(); } },