|
@@ -265,7 +265,7 @@
|
|
|
:key="index"
|
|
|
class="thinking-step-stream"
|
|
|
:class="[step.type, {'fade-in': true}]">
|
|
|
- {{ step.content }}
|
|
|
+ {{ step.displayContent !== undefined ? step.displayContent : step.content }}
|
|
|
<div v-if="index === currentThinkingSteps.length - 1" class="typing-indicator">
|
|
|
<span></span>
|
|
|
<span></span>
|
|
@@ -293,7 +293,7 @@
|
|
|
:key="index"
|
|
|
class="thinking-step"
|
|
|
>
|
|
|
- <div class="step-content">{{ step.displayContent || step.content }}</div>
|
|
|
+ <div class="step-content">{{ step.displayContent !== undefined ? step.displayContent : step.content }}</div>
|
|
|
<!-- <div class="step-meta">
|
|
|
<span class="step-type">{{ getStepTypeName(step.type) }}</span>
|
|
|
<span class="step-confidence">置信度: {{ (step.confidence * 100).toFixed(0) }}%</span>
|
|
@@ -1009,6 +1009,51 @@ const typingSpeed = 50; // 打字速度(毫秒/字符)
|
|
|
const isTyping = ref(false);
|
|
|
const typingTimeout = ref(null); // 添加打字超时引用
|
|
|
|
|
|
+// 逐字输出一个文本到指定对象的 display 字段
|
|
|
+const typeTextTo = (target, fullTextKey = 'content', displayKey = 'displayContent', minDelay = 15, maxDelay = 35) => {
|
|
|
+ if (!target) return Promise.resolve();
|
|
|
+ const full = target[fullTextKey] || '';
|
|
|
+ target[displayKey] = '';
|
|
|
+ let index = 0;
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ const step = () => {
|
|
|
+ if (index >= full.length) return resolve();
|
|
|
+ const ch = full[index];
|
|
|
+ target[displayKey] += ch;
|
|
|
+ index += 1;
|
|
|
+ const delay = /[\u4e00-\u9fa5]/.test(ch) ? maxDelay : minDelay;
|
|
|
+ // 等待 DOM 刷新并在下一帧再调度下一个字符,确保逐字可见
|
|
|
+ nextTick(() => {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ setTimeout(step, delay);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+ step();
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 依次为 message.thinkingSteps 中从 startIndex 开始的各步逐字输出
|
|
|
+const animateThinkingStepsForMessage = async (message, startIndex = 0) => {
|
|
|
+ try {
|
|
|
+ console.log('message',startIndex)
|
|
|
+ if (!message || !Array.isArray(message.thinkingSteps)) return;
|
|
|
+ for (let i = startIndex; i < message.thinkingSteps.length; i += 1) {
|
|
|
+ console.log('message.thinkingSteps',message.thinkingSteps)
|
|
|
+ const step = message.thinkingSteps[i];
|
|
|
+ if (!step.displayContent) step.displayContent = '';
|
|
|
+ await typeTextTo(step);
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('animateThinkingStepsForMessage error:', err);
|
|
|
+ // 回退到直接展示
|
|
|
+ for (let i = startIndex; i < (message.thinkingSteps?.length || 0); i += 1) {
|
|
|
+ const step = message.thinkingSteps[i];
|
|
|
+ if (step) step.displayContent = step.content || '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
// 添加音频播放相关的响应式变量
|
|
|
const audioPlayer = ref(null);
|
|
|
const isPlaying = ref(false);
|
|
@@ -1696,13 +1741,13 @@ const sendMessage = async () => {
|
|
|
messages.value.push(userMessage);
|
|
|
const summaryHtml=ref('')
|
|
|
// 添加临时AI消息
|
|
|
- const tempAiMessage = {
|
|
|
+ const tempAiMessage = reactive({
|
|
|
id: Date.now() + 1,
|
|
|
role: "assistant",
|
|
|
content: "思考中...",
|
|
|
displayContent: "思考中...",
|
|
|
isLoading: true,
|
|
|
- };
|
|
|
+ });
|
|
|
messages.value.push(tempAiMessage);
|
|
|
|
|
|
try {
|
|
@@ -1839,42 +1884,59 @@ const summaryHtml=ref('')
|
|
|
handleSSEMessage(currentEvent || 'message', data);
|
|
|
if(currentEvent==='thinking_process'){
|
|
|
console.log(data)
|
|
|
- // 将思考过程添加到当前AI消息中
|
|
|
+ // 将思考过程添加到当前AI消息中(启用逐字输出)
|
|
|
tempAiMessage.thinkingSteps = [
|
|
|
...(Array.isArray(data.alternative_approaches) ? data.alternative_approaches : []),
|
|
|
...(Array.isArray(data.key_insights) ? data.key_insights : []),
|
|
|
...(Array.isArray(data.reasoning_steps) ? data.reasoning_steps : [])
|
|
|
- ].map(step => ({ content: step }));
|
|
|
+ ].map(step => ({ content: step, displayContent: '' }));
|
|
|
+ // 等待渲染后再开始动画,避免首帧聚合
|
|
|
+
|
|
|
+ // nextTick(() => {
|
|
|
+ // requestAnimationFrame(() => {
|
|
|
+ // animateThinkingStepsForMessage(tempAiMessage, 0);
|
|
|
+ // });
|
|
|
+ // });
|
|
|
// 合并其他字段到思考过程
|
|
|
const __extraSteps = [];
|
|
|
if (typeof data.analysis_approach === 'string' && data.analysis_approach.trim()) {
|
|
|
- __extraSteps.push({ content: `${data.analysis_approach}` });
|
|
|
+ __extraSteps.push({ content: `${data.analysis_approach}`, displayContent: '' });
|
|
|
}
|
|
|
if (typeof data.business_impact === 'string' && data.business_impact.trim()) {
|
|
|
- __extraSteps.push({ content: `${data.business_impact}` });
|
|
|
+ __extraSteps.push({ content: `${data.business_impact}`, displayContent: '' });
|
|
|
}
|
|
|
// if (typeof data.confidence_level === 'number') {
|
|
|
// __extraSteps.push({ content: `${data.confidence_level}` });
|
|
|
// }
|
|
|
if (data.model_version != null && `${data.model_version}`.trim() !== '') {
|
|
|
- __extraSteps.push({ content: `${data.model_version}` });
|
|
|
+ __extraSteps.push({ content: `${data.model_version}`, displayContent: '' });
|
|
|
}
|
|
|
if (data.processing_time != null && `${data.processing_time}`.trim() !== '') {
|
|
|
- __extraSteps.push({ content: `${data.processing_time}` });
|
|
|
+ __extraSteps.push({ content: `${data.processing_time}`, displayContent: '' });
|
|
|
}
|
|
|
if (typeof data.query_intent === 'string' && data.query_intent.trim()) {
|
|
|
- __extraSteps.push({ content: `${data.query_intent}` });
|
|
|
+ __extraSteps.push({ content: `${data.query_intent}`, displayContent: '' });
|
|
|
}
|
|
|
if (typeof data.risk_assessment === 'string' && data.risk_assessment.trim()) {
|
|
|
- __extraSteps.push({ content: `${data.risk_assessment}` });
|
|
|
+ __extraSteps.push({ content: `${data.risk_assessment}`, displayContent: '' });
|
|
|
}
|
|
|
if (data.technical_constraints != null && `${data.technical_constraints}`.trim() !== '') {
|
|
|
- __extraSteps.push({ content: `${data.technical_constraints}` });
|
|
|
+ __extraSteps.push({ content: `${data.technical_constraints}`, displayContent: '' });
|
|
|
}
|
|
|
+ const _prevLen = tempAiMessage.thinkingSteps.length;
|
|
|
tempAiMessage.thinkingSteps = [
|
|
|
...tempAiMessage.thinkingSteps,
|
|
|
...__extraSteps
|
|
|
];
|
|
|
+ // 对新增的步骤做逐字打字(同样等待一帧)
|
|
|
+ if (__extraSteps.length > 0) {
|
|
|
+ console.log('tempAiMessage',tempAiMessage)
|
|
|
+ nextTick(() => {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ animateThinkingStepsForMessage(tempAiMessage, 0);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
if(currentEvent === 'final_summary'){
|
|
|
// 检查summary是否以"无效命令:"开头
|
|
@@ -2973,10 +3035,18 @@ const updateCollapsedState = () => {
|
|
|
|
|
|
// 添加流式思考步骤
|
|
|
const addThinkingStep = (step) => {
|
|
|
- currentThinkingSteps.value.push({
|
|
|
+ const s = {
|
|
|
type: step.type || 'default',
|
|
|
content: step.content,
|
|
|
+ displayContent: '',
|
|
|
timestamp: Date.now()
|
|
|
+ };
|
|
|
+ currentThinkingSteps.value.push(s);
|
|
|
+ // 渲染后启动逐字动画,避免瞬时显示
|
|
|
+ nextTick(() => {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ typeTextTo(s);
|
|
|
+ });
|
|
|
});
|
|
|
};
|
|
|
|