实现Agent对话,合格自动提交,不合格补充材料的能力
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* LLM 流式聊天气泡管理器
|
||||
*
|
||||
* 流式消息在处理时显示思考过程和逐字输出,结束后固化为持久消息。
|
||||
*/
|
||||
import { escapeHtml } from '../utils.js';
|
||||
import { getChatMessages, scrollToBottom } from './renderer.js';
|
||||
|
||||
/**
|
||||
* 当前活跃的 LLM 流式气泡状态
|
||||
*/
|
||||
let llmStreamState = {
|
||||
wrapper: null,
|
||||
bubble: null,
|
||||
textContent: null,
|
||||
accumulated: '',
|
||||
reasoningContent: null,
|
||||
reasoningAccumulated: '',
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理 LLM 流式事件
|
||||
*
|
||||
* @param {Object} msg - SSE 传来的 llm_stream 事件对象
|
||||
*/
|
||||
export function handleLLMStream(msg) {
|
||||
switch (msg.phase) {
|
||||
case 'start':
|
||||
_createLLMStreamBubble(msg.label || '正在分析中...');
|
||||
break;
|
||||
case 'reasoning':
|
||||
_appendLLMStreamReasoning(msg.text || '');
|
||||
break;
|
||||
case 'chunk':
|
||||
_appendLLMStreamChunk(msg.text || '');
|
||||
break;
|
||||
case 'end':
|
||||
_closeLLMStreamBubble(msg.label || '分析完成');
|
||||
break;
|
||||
case 'error':
|
||||
_errorLLMStreamBubble(msg.error || '分析失败');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 LLM 流式气泡(start 阶段)
|
||||
*/
|
||||
function _createLLMStreamBubble(label) {
|
||||
const chatMessages = getChatMessages();
|
||||
if (!chatMessages) return;
|
||||
|
||||
if (llmStreamState.wrapper) {
|
||||
_closeLLMStreamBubbleSilent();
|
||||
}
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'chat-message';
|
||||
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = 'chat-bubble processing llm-stream-bubble';
|
||||
|
||||
const labelEl = document.createElement('div');
|
||||
labelEl.className = 'llm-stream-label';
|
||||
labelEl.textContent = label;
|
||||
|
||||
const reasoningSection = document.createElement('details');
|
||||
reasoningSection.className = 'llm-reasoning-section';
|
||||
reasoningSection.open = true;
|
||||
|
||||
const reasoningSummary = document.createElement('summary');
|
||||
reasoningSummary.className = 'llm-reasoning-summary';
|
||||
reasoningSummary.textContent = '思考过程(点击折叠)';
|
||||
|
||||
const reasoningText = document.createElement('div');
|
||||
reasoningText.className = 'llm-reasoning-text';
|
||||
reasoningText.textContent = '';
|
||||
|
||||
reasoningSection.appendChild(reasoningSummary);
|
||||
reasoningSection.appendChild(reasoningText);
|
||||
|
||||
const textEl = document.createElement('div');
|
||||
textEl.className = 'llm-stream-text';
|
||||
textEl.textContent = '';
|
||||
|
||||
bubble.appendChild(labelEl);
|
||||
bubble.appendChild(reasoningSection);
|
||||
bubble.appendChild(textEl);
|
||||
wrapper.appendChild(bubble);
|
||||
chatMessages.appendChild(wrapper);
|
||||
scrollToBottom();
|
||||
|
||||
llmStreamState = {
|
||||
wrapper,
|
||||
bubble,
|
||||
textContent: textEl,
|
||||
accumulated: '',
|
||||
reasoningContent: reasoningText,
|
||||
reasoningAccumulated: '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加流式文本片段(chunk 阶段)
|
||||
*/
|
||||
function _appendLLMStreamChunk(text) {
|
||||
if (!llmStreamState.textContent) return;
|
||||
|
||||
llmStreamState.accumulated += text;
|
||||
llmStreamState.textContent.textContent = llmStreamState.accumulated;
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加思考过程片段(reasoning 阶段)
|
||||
*/
|
||||
function _appendLLMStreamReasoning(text) {
|
||||
if (!llmStreamState.reasoningContent) return;
|
||||
|
||||
llmStreamState.reasoningAccumulated += text;
|
||||
llmStreamState.reasoningContent.textContent = llmStreamState.reasoningAccumulated;
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭流式气泡(end 阶段)— 气泡保留在聊天历史中作为持久消息
|
||||
*/
|
||||
function _closeLLMStreamBubble(label) {
|
||||
if (!llmStreamState.bubble) return;
|
||||
|
||||
llmStreamState.bubble.classList.remove('processing');
|
||||
llmStreamState.bubble.classList.add('done');
|
||||
|
||||
const labelEl = llmStreamState.bubble.querySelector('.llm-stream-label');
|
||||
if (labelEl) {
|
||||
labelEl.textContent = label;
|
||||
}
|
||||
|
||||
const reasoningSection = llmStreamState.bubble.querySelector('.llm-reasoning-section');
|
||||
if (reasoningSection) {
|
||||
reasoningSection.open = false;
|
||||
}
|
||||
|
||||
scrollToBottom();
|
||||
|
||||
llmStreamState = {
|
||||
wrapper: null,
|
||||
bubble: null,
|
||||
textContent: null,
|
||||
accumulated: '',
|
||||
reasoningContent: null,
|
||||
reasoningAccumulated: '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 静默关闭流式气泡(不改变样式,用于 start 前清理)
|
||||
*/
|
||||
function _closeLLMStreamBubbleSilent() {
|
||||
if (llmStreamState.wrapper) {
|
||||
llmStreamState.wrapper.remove();
|
||||
}
|
||||
llmStreamState = {
|
||||
wrapper: null,
|
||||
bubble: null,
|
||||
textContent: null,
|
||||
accumulated: '',
|
||||
reasoningContent: null,
|
||||
reasoningAccumulated: '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误状态(error 阶段)
|
||||
*/
|
||||
function _errorLLMStreamBubble(errorMsg) {
|
||||
if (!llmStreamState.bubble) return;
|
||||
|
||||
llmStreamState.bubble.classList.remove('processing');
|
||||
llmStreamState.bubble.classList.add('error');
|
||||
|
||||
const labelEl = llmStreamState.bubble.querySelector('.llm-stream-label');
|
||||
if (labelEl) {
|
||||
labelEl.textContent = '分析失败';
|
||||
}
|
||||
|
||||
if (llmStreamState.textContent) {
|
||||
llmStreamState.textContent.textContent = escapeHtml(errorMsg);
|
||||
}
|
||||
|
||||
llmStreamState = {
|
||||
wrapper: null,
|
||||
bubble: null,
|
||||
textContent: null,
|
||||
accumulated: '',
|
||||
reasoningContent: null,
|
||||
reasoningAccumulated: '',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user