yangg 1 dienu atpakaļ
vecāks
revīzija
9642fcde49
1 mainītis faili ar 84 papildinājumiem un 14 dzēšanām
  1. 84 14
      src/views/report.vue

+ 84 - 14
src/views/report.vue

@@ -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);
+      });
     });
   };