perf: 优化 WebUI Chat 的流式传输性能
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,11 +36,11 @@ marked.setOptions({
|
||||
</template>
|
||||
<v-list-item-title class="conversation-title">新对话</v-list-item-title>
|
||||
<v-list-item-subtitle class="timestamp">{{ formatDate(item.updated_at)
|
||||
}}</v-list-item-subtitle>
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
|
||||
|
||||
<v-fade-transition>
|
||||
<div class="no-conversations" v-if="conversations.length === 0">
|
||||
<v-icon icon="mdi-message-text-outline" size="large" color="grey-lighten-1"></v-icon>
|
||||
@@ -57,7 +57,8 @@ marked.setOptions({
|
||||
<v-chip class="status-chip" :color="status?.llm_enabled ? 'primary' : 'grey-lighten-2'"
|
||||
variant="elevated" size="small">
|
||||
<template v-slot:prepend>
|
||||
<v-icon :icon="status?.llm_enabled ? 'mdi-check-circle' : 'mdi-alert-circle'" size="x-small"></v-icon>
|
||||
<v-icon :icon="status?.llm_enabled ? 'mdi-check-circle' : 'mdi-alert-circle'"
|
||||
size="x-small"></v-icon>
|
||||
</template>
|
||||
LLM 服务
|
||||
</v-chip>
|
||||
@@ -65,7 +66,8 @@ marked.setOptions({
|
||||
<v-chip class="status-chip" :color="status?.stt_enabled ? 'success' : 'grey-lighten-2'"
|
||||
variant="elevated" size="small">
|
||||
<template v-slot:prepend>
|
||||
<v-icon :icon="status?.stt_enabled ? 'mdi-check-circle' : 'mdi-alert-circle'" size="x-small"></v-icon>
|
||||
<v-icon :icon="status?.stt_enabled ? 'mdi-check-circle' : 'mdi-alert-circle'"
|
||||
size="x-small"></v-icon>
|
||||
</template>
|
||||
语音转文本
|
||||
</v-chip>
|
||||
@@ -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: `<img src="/api/chat/get_file?filename=${img}" style="max-width: 80%; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"/>`
|
||||
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: `<audio controls class="audio-player">
|
||||
<source src="/api/chat/get_file?filename=${audio}" type="audio/wav">
|
||||
您的浏览器不支持音频播放。
|
||||
</audio>`
|
||||
|
||||
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: `<img src="/api/chat/get_file?filename=${img}" style="max-width: 80%; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);"/>`
|
||||
}
|
||||
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: `<audio controls class="audio-player">
|
||||
<source src="/api/chat/get_file?filename=${audio}" type="audio/wav">
|
||||
您的浏览器不支持音频播放。
|
||||
</audio>`
|
||||
}
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user