重构流式响应处理逻辑,优化服务端和客户端缓冲区管理

This commit is contained in:
Zylan
2025-03-04 19:32:54 +08:00
parent 3d0c6e0480
commit 8fda4fc507
2 changed files with 55 additions and 74 deletions

54
app.py
View File

@@ -81,8 +81,9 @@ def stream_model_response(response_generator, sid):
}, room=sid) }, room=sid)
print("Sent initial status to client") print("Sent initial status to client")
# 跟踪已发送的内容 # 维护服务端缓冲区以累积完整内容
previous_thinking_content = "" response_buffer = ""
thinking_buffer = ""
# 流式处理响应 # 流式处理响应
for response in response_generator: for response in response_generator:
@@ -99,34 +100,37 @@ def stream_model_response(response_generator, sid):
# 根据不同的状态进行处理 # 根据不同的状态进行处理
if status == 'thinking': if status == 'thinking':
# 确保只发送新增的思考内容 # 累积思考内容到缓冲区
if previous_thinking_content and content.startswith(previous_thinking_content): thinking_buffer += content
# 只发送新增部分
new_content = content[len(previous_thinking_content):]
if new_content: # 只有当有新内容时才发送
print(f"Streaming thinking content: {len(new_content)} chars")
socketio.emit('claude_response', {
'status': 'thinking',
'content': new_content
}, room=sid)
else:
# 直接发送全部内容(首次或内容不连续的情况)
print(f"Streaming thinking content (reset): {len(content)} chars")
socketio.emit('claude_response', {
'status': 'thinking',
'content': content
}, room=sid)
# 更新已发送的思考内容记录 # 发送完整的思考内容
previous_thinking_content = content print(f"Streaming thinking content: {len(thinking_buffer)} chars")
socketio.emit('claude_response', {
'status': 'thinking',
'content': thinking_buffer
}, room=sid)
elif status == 'thinking_complete':
# 直接使用完整的思考内容
thinking_buffer = content # 使用服务器提供的完整内容
print(f"Thinking complete, total length: {len(thinking_buffer)} chars")
socketio.emit('claude_response', {
'status': 'thinking_complete',
'content': thinking_buffer
}, room=sid)
elif status == 'streaming': elif status == 'streaming':
# 流式输出正常内容 # 流式输出正常内容
if content: if content:
print(f"Streaming response content: {len(content)} chars") # 累积到服务端缓冲区
response_buffer += content
# 发送完整的内容
print(f"Streaming response content: {len(response_buffer)} chars")
socketio.emit('claude_response', { socketio.emit('claude_response', {
'status': 'streaming', 'status': 'streaming',
'content': content 'content': response_buffer
}, room=sid) }, room=sid)
else: else:
@@ -134,9 +138,7 @@ def stream_model_response(response_generator, sid):
socketio.emit('claude_response', response, room=sid) socketio.emit('claude_response', response, room=sid)
# 调试信息 # 调试信息
if status == 'thinking_complete': if status == 'completed':
print(f"Thinking complete, total length: {len(content)} chars")
elif status == 'completed':
print("Response completed") print("Response completed")
elif status == 'error': elif status == 'error':
print(f"Error: {response.get('error', 'Unknown error')}") print(f"Error: {response.get('error', 'Unknown error')}")

View File

@@ -66,10 +66,6 @@ class SnapSolver {
latex: '' latex: ''
}; };
// 新增:流式输出的内存缓冲区
this.responseBuffer = "";
this.thinkingBuffer = "";
// 确保裁剪容器和其他面板初始为隐藏状态 // 确保裁剪容器和其他面板初始为隐藏状态
if (this.cropContainer) { if (this.cropContainer) {
this.cropContainer.classList.add('hidden'); this.cropContainer.classList.add('hidden');
@@ -236,9 +232,6 @@ class SnapSolver {
switch (data.status) { switch (data.status) {
case 'started': case 'started':
console.log('Analysis started'); console.log('Analysis started');
// 重置内存缓冲区
this.responseBuffer = "";
this.thinkingBuffer = "";
// 清空显示内容 // 清空显示内容
this.responseContent.innerHTML = ''; this.responseContent.innerHTML = '';
this.thinkingContent.innerHTML = ''; this.thinkingContent.innerHTML = '';
@@ -250,14 +243,11 @@ class SnapSolver {
case 'thinking': case 'thinking':
// 处理思考内容 // 处理思考内容
if (data.content) { if (data.content) {
console.log('Received thinking:', data.content); console.log('Received thinking content');
this.thinkingSection.classList.remove('hidden'); this.thinkingSection.classList.remove('hidden');
// 添加到内存缓冲区 // 直接设置完整内容而不是追加
this.thinkingBuffer += data.content; this.setElementContent(this.thinkingContent, data.content);
// 安全地更新DOM
this.updateElementContent(this.thinkingContent, this.thinkingBuffer);
// 添加打字动画效果 // 添加打字动画效果
this.thinkingContent.classList.add('thinking-typing'); this.thinkingContent.classList.add('thinking-typing');
@@ -270,9 +260,8 @@ class SnapSolver {
console.log('Thinking complete'); console.log('Thinking complete');
this.thinkingSection.classList.remove('hidden'); this.thinkingSection.classList.remove('hidden');
// 重置内存缓冲区并更新 // 设置完整内容
this.thinkingBuffer = data.content; this.setElementContent(this.thinkingContent, data.content);
this.updateElementContent(this.thinkingContent, this.thinkingBuffer);
// 移除打字动画 // 移除打字动画
this.thinkingContent.classList.remove('thinking-typing'); this.thinkingContent.classList.remove('thinking-typing');
@@ -281,13 +270,10 @@ class SnapSolver {
case 'streaming': case 'streaming':
if (data.content) { if (data.content) {
console.log('Received content:', data.content); console.log('Received content');
// 添加到内存缓冲区 // 直接设置完整内容
this.responseBuffer += data.content; this.setElementContent(this.responseContent, data.content);
// 安全地更新DOM
this.updateElementContent(this.responseContent, this.responseBuffer);
// 移除思考部分的打字动画 // 移除思考部分的打字动画
this.thinkingContent.classList.remove('thinking-typing'); this.thinkingContent.classList.remove('thinking-typing');
@@ -298,17 +284,25 @@ class SnapSolver {
console.log('Analysis completed'); console.log('Analysis completed');
this.sendToClaudeBtn.disabled = false; this.sendToClaudeBtn.disabled = false;
this.sendExtractedTextBtn.disabled = false; this.sendExtractedTextBtn.disabled = false;
this.addToHistory(this.croppedImage, this.responseBuffer, this.thinkingBuffer);
// 保存到历史记录
const responseText = this.responseContent.textContent || '';
const thinkingText = this.thinkingContent.textContent || '';
this.addToHistory(this.croppedImage, responseText, thinkingText);
window.showToast('Analysis completed successfully'); window.showToast('Analysis completed successfully');
break; break;
case 'error': case 'error':
console.error('Analysis error:', data.error); console.error('Analysis error:', data.error);
const errorMessage = data.error || 'Unknown error occurred'; const errorMessage = data.error || 'Unknown error occurred';
// 错误信息添加到缓冲区
this.responseBuffer += '\nError: ' + errorMessage; // 显示错误信息
// 更新DOM if (errorMessage) {
this.updateElementContent(this.responseContent, this.responseBuffer); const currentText = this.responseContent.textContent || '';
this.setElementContent(this.responseContent, currentText + '\nError: ' + errorMessage);
}
this.sendToClaudeBtn.disabled = false; this.sendToClaudeBtn.disabled = false;
this.sendExtractedTextBtn.disabled = false; this.sendExtractedTextBtn.disabled = false;
window.showToast('Analysis failed: ' + errorMessage, 'error'); window.showToast('Analysis failed: ' + errorMessage, 'error');
@@ -317,10 +311,8 @@ class SnapSolver {
default: default:
console.warn('Unknown response status:', data.status); console.warn('Unknown response status:', data.status);
if (data.error) { if (data.error) {
// 错误信息添加到缓冲区 const currentText = this.responseContent.textContent || '';
this.responseBuffer += '\nError: ' + data.error; this.setElementContent(this.responseContent, currentText + '\nError: ' + data.error);
// 更新DOM
this.updateElementContent(this.responseContent, this.responseBuffer);
this.sendToClaudeBtn.disabled = false; this.sendToClaudeBtn.disabled = false;
this.sendExtractedTextBtn.disabled = false; this.sendExtractedTextBtn.disabled = false;
window.showToast('Unknown error occurred', 'error'); window.showToast('Unknown error occurred', 'error');
@@ -336,25 +328,12 @@ class SnapSolver {
}); });
} }
// 新:安全更新DOM内容的辅助方法 // 新方法:安全设置DOM内容的方法替代updateElementContent
updateElementContent(element, content) { setElementContent(element, content) {
if (!element) return; if (!element) return;
// 创建文档片段以提高性能 // 直接设置内容
const fragment = document.createDocumentFragment(); element.textContent = content;
const tempDiv = document.createElement('div');
// 设置内容
tempDiv.textContent = content;
// 将所有子节点移动到文档片段
while (tempDiv.firstChild) {
fragment.appendChild(tempDiv.firstChild);
}
// 清空目标元素并添加文档片段
element.innerHTML = '';
element.appendChild(fragment);
// 自动滚动到底部 // 自动滚动到底部
element.scrollTop = element.scrollHeight; element.scrollTop = element.scrollHeight;