Pārlūkot izejas kodu

优化在线模型在前端输出

zkn 1 nedēļu atpakaļ
vecāks
revīzija
5ab5410139

+ 4 - 0
.gitignore

@@ -31,6 +31,10 @@ shudao-go-backend/shudao-go-backend
 shudao-go-backend/shudao-go-backend.tar.gz
 shudao-go-backend/shudao-chat-go
 shudao-go-backend/shudao-chat-go.tar.gz
+shudao-go-backend/run.bat
+shudao-vue-frontend/run.bat
+
+
 
 # Backend Copied Assets (from frontend build)
 shudao-go-backend/assets/

+ 0 - 264
shudao-vue-frontend/package-lock.json

@@ -3299,23 +3299,6 @@
       ],
       "license": "CC-BY-4.0"
     },
-    "node_modules/canvas": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmmirror.com/canvas/-/canvas-3.2.0.tgz",
-      "integrity": "sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==",
-      "dev": true,
-      "hasInstallScript": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "node-addon-api": "^7.0.0",
-        "prebuild-install": "^7.1.3"
-      },
-      "engines": {
-        "node": "^18.12.0 || >= 20.9.0"
-      }
-    },
     "node_modules/caseless": {
       "version": "0.12.0",
       "resolved": "https://registry.npmmirror.com/caseless/-/caseless-0.12.0.tgz",
@@ -3382,15 +3365,6 @@
         "node": ">=4.6.0"
       }
     },
-    "node_modules/chownr": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmmirror.com/chownr/-/chownr-1.1.4.tgz",
-      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
-      "dev": true,
-      "license": "ISC",
-      "optional": true,
-      "peer": true
-    },
     "node_modules/clipboard": {
       "version": "2.0.11",
       "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
@@ -3773,24 +3747,6 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
-    "node_modules/decompress-response": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz",
-      "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "mimic-response": "^3.1.0"
-      },
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/deep-equal": {
       "version": "1.1.2",
       "resolved": "https://registry.npmmirror.com/deep-equal/-/deep-equal-1.1.2.tgz",
@@ -3811,18 +3767,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/deep-extend": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmmirror.com/deep-extend/-/deep-extend-0.6.0.tgz",
-      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "engines": {
-        "node": ">=4.0.0"
-      }
-    },
     "node_modules/default-browser": {
       "version": "5.2.1",
       "resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.2.1.tgz",
@@ -4462,18 +4406,6 @@
         "url": "https://github.com/sindresorhus/execa?sponsor=1"
       }
     },
-    "node_modules/expand-template": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmmirror.com/expand-template/-/expand-template-2.0.3.tgz",
-      "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
-      "dev": true,
-      "license": "(MIT OR WTFPL)",
-      "optional": true,
-      "peer": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/expect-type": {
       "version": "1.3.0",
       "resolved": "https://registry.npmmirror.com/expect-type/-/expect-type-1.3.0.tgz",
@@ -4955,15 +4887,6 @@
         "assert-plus": "^1.0.0"
       }
     },
-    "node_modules/github-from-package": {
-      "version": "0.0.0",
-      "resolved": "https://registry.npmmirror.com/github-from-package/-/github-from-package-0.0.0.tgz",
-      "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true
-    },
     "node_modules/glob": {
       "version": "7.2.3",
       "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
@@ -6996,21 +6919,6 @@
         "node": ">= 0.6"
       }
     },
-    "node_modules/mimic-response": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz",
-      "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/minimalistic-assert": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -7102,15 +7010,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/mkdirp-classic": {
-      "version": "0.5.3",
-      "resolved": "https://registry.npmmirror.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
-      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true
-    },
     "node_modules/mrmime": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz",
@@ -7164,15 +7063,6 @@
       "integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw==",
       "license": "MIT"
     },
-    "node_modules/napi-build-utils": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmmirror.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
-      "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true
-    },
     "node_modules/needle": {
       "version": "3.3.1",
       "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz",
@@ -7197,45 +7087,6 @@
       "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
       "license": "ISC"
     },
-    "node_modules/node-abi": {
-      "version": "3.85.0",
-      "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.85.0.tgz",
-      "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "semver": "^7.3.5"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/node-abi/node_modules/semver": {
-      "version": "7.7.3",
-      "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
-      "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
-      "dev": true,
-      "license": "ISC",
-      "optional": true,
-      "peer": true,
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/node-addon-api": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz",
-      "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true
-    },
     "node_modules/node-fetch": {
       "version": "2.7.0",
       "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
@@ -8029,35 +7880,6 @@
         "url": "https://opencollective.com/preact"
       }
     },
-    "node_modules/prebuild-install": {
-      "version": "7.1.3",
-      "resolved": "https://registry.npmmirror.com/prebuild-install/-/prebuild-install-7.1.3.tgz",
-      "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "detect-libc": "^2.0.0",
-        "expand-template": "^2.0.3",
-        "github-from-package": "0.0.0",
-        "minimist": "^1.2.3",
-        "mkdirp-classic": "^0.5.3",
-        "napi-build-utils": "^2.0.0",
-        "node-abi": "^3.3.0",
-        "pump": "^3.0.0",
-        "rc": "^1.2.7",
-        "simple-get": "^4.0.0",
-        "tar-fs": "^2.0.0",
-        "tunnel-agent": "^0.6.0"
-      },
-      "bin": {
-        "prebuild-install": "bin.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/pretty-ms": {
       "version": "9.2.0",
       "resolved": "https://registry.npmmirror.com/pretty-ms/-/pretty-ms-9.2.0.tgz",
@@ -8268,19 +8090,6 @@
         "url": "https://github.com/sponsors/lupomontero"
       }
     },
-    "node_modules/pump": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.3.tgz",
-      "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "end-of-stream": "^1.1.0",
-        "once": "^1.3.1"
-      }
-    },
     "node_modules/punycode": {
       "version": "2.3.1",
       "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
@@ -8373,24 +8182,6 @@
         "node": ">= 12.0.0"
       }
     },
-    "node_modules/rc": {
-      "version": "1.2.8",
-      "resolved": "https://registry.npmmirror.com/rc/-/rc-1.2.8.tgz",
-      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
-      "dev": true,
-      "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "deep-extend": "^0.6.0",
-        "ini": "~1.3.0",
-        "minimist": "^1.2.0",
-        "strip-json-comments": "~2.0.1"
-      },
-      "bin": {
-        "rc": "cli.js"
-      }
-    },
     "node_modules/readable-stream": {
       "version": "2.3.8",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -8844,34 +8635,6 @@
       "license": "MIT",
       "optional": true
     },
-    "node_modules/simple-get": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-4.0.1.tgz",
-      "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "decompress-response": "^6.0.0",
-        "once": "^1.3.1",
-        "simple-concat": "^1.0.0"
-      }
-    },
     "node_modules/sirv": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/sirv/-/sirv-3.0.1.tgz",
@@ -9174,18 +8937,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/strip-json-comments": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/superjson": {
       "version": "2.2.2",
       "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz",
@@ -9238,21 +8989,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/tar-fs": {
-      "version": "2.1.4",
-      "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-2.1.4.tgz",
-      "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "chownr": "^1.1.1",
-        "mkdirp-classic": "^0.5.2",
-        "pump": "^3.0.0",
-        "tar-stream": "^2.1.4"
-      }
-    },
     "node_modules/tar-stream": {
       "version": "2.2.0",
       "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-2.2.0.tgz",

+ 38 - 76
shudao-vue-frontend/src/views/Chat.vue

@@ -1800,80 +1800,10 @@ const handleSSEMessage = (data, aiMessageIndex) => {
         // 非专业问题,只输出summary字段内容并终止流程
         const summaryContent = data.summary || '抱歉,我暂时无法回答您的问题。'
 
-        // 只设置summary,不设置content和displayContent,避免重复显示
+        // 先缓存summary作为兜底内容,但继续等待online_answer/completed事件
         aiMessage.summary = summaryContent
-        aiMessage.isTyping = false // 停止加载动画
-
-        // 保存到数据库
-        if (aiMessage.ai_message_id) {
-          updateAIMessageContent(aiMessage.ai_message_id, summaryContent, summaryContent)
-            .catch(err => console.error('回写AI消息失败:', err))
-        }
-
-        // 关闭SSE连接
-        if (sseConnection) {
-          closeSSEConnection(sseConnection)
-          sseConnection = null
-        }
-
-        // 重置发送状态
-        isSending.value = false
-        streamingReports.value.clear()
-
-        // 重置AI回复流程状态
-        isAIReplyProcessComplete.value = true
-        
-        // ===== 🎯 非专业问题也要更新历史记录和获取推荐问题 =====
-        // 更新历史记录
-        if (ai_conversation_id.value && ai_conversation_id.value !== 0) {
-          // 先清除所有高亮
-          historyData.value.forEach((item) => {
-            item.isActive = false
-          })
-          
-          // 获取第一条用户消息作为标题
-          const firstUserMessage = chatMessages.value.find(msg => msg.type === 'user')
-          const title = firstUserMessage ? firstUserMessage.content.substring(0, 20) + '...' : '新对话'
-          
-          // 检查是否已存在
-          const existingIndex = historyData.value.findIndex(item => item.id === ai_conversation_id.value)
-          
-          if (existingIndex === -1) {
-            // 不存在,在最前面插入新项
-            const newItem = {
-              id: ai_conversation_id.value,
-              title: title,
-              time: formatTime(new Date().toISOString()),
-              businessType: 0,
-              isActive: true,
-              rawData: {
-                id: ai_conversation_id.value,
-                content: firstUserMessage?.content || '',
-                updated_at: new Date().toISOString()
-              }
-            }
-            historyData.value.unshift(newItem)
-            console.log('✅ 非专业问题:已在列表最前面插入新历史记录')
-          } else {
-            // 已存在,设为高亮并移到最前面
-            const existingItem = historyData.value.splice(existingIndex, 1)[0]
-            existingItem.isActive = true
-            existingItem.time = formatTime(new Date().toISOString())
-            historyData.value.unshift(existingItem)
-            console.log('✅ 非专业问题:已将历史记录移到最前面')
-          }
-          
-          // 更新历史记录总数
-          historyTotal.value = historyData.value.length
-        }
-        
-        // 获取推荐问题
-        const lastUserMessage = chatMessages.value.filter(msg => msg.type === 'user').pop()
-        if (lastUserMessage && aiMessage.ai_message_id && summaryContent) {
-          getAIRelatedQuestions(lastUserMessage.content, summaryContent, aiMessage.ai_message_id)
-        }
-        
-        return // 终止处理
+        aiMessage._fullSummary = summaryContent
+        break
       }
 
       // 专业问题:意图识别完成,更新为查询知识库状态
@@ -1900,6 +1830,38 @@ const handleSSEMessage = (data, aiMessageIndex) => {
         })
       }
       break
+
+    case 'online_answer': {
+      aiMessage.showStats = false
+      aiMessage.summary = ''
+
+      const finalContent = data.content || ''
+      aiMessage.content = finalContent
+
+      if (!finalContent.trim()) {
+        aiMessage.displayContent = ''
+        aiMessage.isTyping = false
+        break
+      }
+
+      const processedReply = processAIResponse(finalContent)
+      const renderedReply = renderMarkdownContent(processedReply)
+
+      startTypewriterEffect(aiMessage, renderedReply, 200)
+        .catch(err => {
+          console.error('在线回答打字机效果失败:', err)
+          aiMessage.displayContent = renderedReply
+          aiMessage.isTyping = false
+        })
+      break
+    }
+
+    case 'online_error':
+      aiMessage.showStats = false
+      aiMessage.content = data.message || '在线模型服务暂不可用'
+      aiMessage.displayContent = renderMarkdownContent(processAIResponse(aiMessage.content))
+      aiMessage.isTyping = false
+      break
       
     case 'documents':
       aiMessage.totalFiles = data.total
@@ -2305,7 +2267,7 @@ const handleSSEComplete = () => {
         
         const collectedContent = message.reports && message.reports.length > 0 
           ? JSON.stringify(contentData)
-          : message.content
+          : (message.content || message._fullSummary || message.summary || '')
         
         if (collectedContent) {
           // 同时保存summary字段(作为单独字段)
@@ -2460,7 +2422,7 @@ const handleSSEInterrupted = (data) => {
         
         const collectedContent = message.reports && message.reports.length > 0 
           ? JSON.stringify(contentData)
-          : message.content
+          : (message.content || message._fullSummary || message.summary || '')
         
         if (collectedContent) {
           // 同时保存summary字段(作为单独字段)
@@ -2528,7 +2490,7 @@ const handleStopGeneration = async () => {
         
         const collectedContent = message.reports && message.reports.length > 0 
           ? JSON.stringify(contentData)
-          : message.content
+          : (message.content || message._fullSummary || message.summary || '')
         
         if (collectedContent) {
           // 同时保存summary字段(作为单独字段)

+ 34 - 77
shudao-vue-frontend/src/views/mobile/m-Chat.vue

@@ -2475,80 +2475,10 @@ const handleSSEMessage = (data, aiMessageIndex) => {
         // 非专业问题,只输出summary字段内容并终止流程
         const summaryContent = data.summary || '抱歉,我暂时无法回答您的问题。'
 
-        // 只设置summary,不设置content和displayContent,避免重复显示
+        // 先缓存summary作为兜底内容,但继续等待online_answer/completed事件
         aiMessage.summary = summaryContent
-        aiMessage.isTyping = false // 停止加载动画
-
-        // 保存到数据库
-        if (aiMessage.ai_message_id) {
-          updateAIMessageContent(aiMessage.ai_message_id, summaryContent, summaryContent)
-            .catch(err => console.error('回写AI消息失败:', err))
-        }
-
-        // 关闭SSE连接
-        if (sseConnection) {
-          closeSSEConnection(sseConnection)
-          sseConnection = null
-        }
-
-        // 重置发送状态
-        isSending.value = false
-        streamingReports.value.clear()
-
-        // 重置AI回复流程状态
-        isAIReplyProcessComplete.value = true
-        
-        // ===== 🎯 非专业问题也要更新历史记录和获取推荐问题 =====
-        // 更新历史记录
-        if (ai_conversation_id.value && ai_conversation_id.value !== 0) {
-          // 先清除所有高亮
-          historyData.value.forEach((item) => {
-            item.isActive = false
-          })
-          
-          // 获取第一条用户消息作为标题
-          const firstUserMessage = chatMessages.value.find(msg => msg.type === 'user')
-          const title = firstUserMessage ? firstUserMessage.content.substring(0, 20) + '...' : '新对话'
-          
-          // 检查是否已存在
-          const existingIndex = historyData.value.findIndex(item => item.id === ai_conversation_id.value)
-          
-          if (existingIndex === -1) {
-            // 不存在,在最前面插入新项
-            const newItem = {
-              id: ai_conversation_id.value,
-              title: title,
-              time: formatTime(new Date().toISOString()),
-              businessType: 0,
-              isActive: true,
-              rawData: {
-                id: ai_conversation_id.value,
-                content: firstUserMessage?.content || '',
-                updated_at: new Date().toISOString()
-              }
-            }
-            historyData.value.unshift(newItem)
-            console.log('✅ 非专业问题:已在列表最前面插入新历史记录')
-          } else {
-            // 已存在,设为高亮并移到最前面
-            const existingItem = historyData.value.splice(existingIndex, 1)[0]
-            existingItem.isActive = true
-            existingItem.time = formatTime(new Date().toISOString())
-            historyData.value.unshift(existingItem)
-            console.log('✅ 非专业问题:已将历史记录移到最前面')
-          }
-          
-          // 更新历史记录总数
-          historyTotal.value = historyData.value.length
-        }
-        
-        // 获取推荐问题
-        const lastUserMessage = chatMessages.value.filter(msg => msg.type === 'user').pop()
-        if (lastUserMessage && aiMessage.ai_message_id && summaryContent) {
-          getAIRelatedQuestions(lastUserMessage.content, summaryContent, aiMessage.ai_message_id)
-        }
-        
-        return // 终止处理
+        aiMessage._fullSummary = summaryContent
+        break
       }
 
       // 专业问题:意图识别完成,更新为查询知识库状态
@@ -2575,6 +2505,32 @@ const handleSSEMessage = (data, aiMessageIndex) => {
         })
       }
       break
+
+    case 'online_answer': {
+      aiMessage.showStats = false
+      aiMessage.summary = ''
+
+      const finalContent = data.content || ''
+      aiMessage.content = finalContent
+
+      if (!finalContent.trim()) {
+        aiMessage.displayContent = ''
+        aiMessage.isTyping = false
+        break
+      }
+
+      const processedReply = processAIResponse(finalContent)
+      aiMessage.displayContent = renderMarkdownContent(processedReply)
+      aiMessage.isTyping = false
+      break
+    }
+
+    case 'online_error':
+      aiMessage.showStats = false
+      aiMessage.content = data.message || '在线模型服务暂不可用'
+      aiMessage.displayContent = renderMarkdownContent(processAIResponse(aiMessage.content))
+      aiMessage.isTyping = false
+      break
       
     case 'documents':
       aiMessage.totalFiles = data.total
@@ -2986,7 +2942,7 @@ const handleSSEComplete = () => {
         
         const collectedContent = message.reports && message.reports.length > 0 
           ? JSON.stringify(contentData)
-          : message.content
+          : (message.content || message._fullSummary || message.summary || '')
         
         if (collectedContent) {
           // 同时保存summary字段(作为单独字段)
@@ -3142,7 +3098,7 @@ const handleSSEInterrupted = (data) => {
         
         const collectedContent = message.reports && message.reports.length > 0 
           ? JSON.stringify(contentData)
-          : message.content
+          : (message.content || message._fullSummary || message.summary || '')
         
         if (collectedContent) {
           // 同时保存summary字段(作为单独字段)
@@ -3201,7 +3157,7 @@ const handleStopGeneration = async () => {
         
         const collectedContent = message.reports && message.reports.length > 0 
           ? JSON.stringify(contentData)
-          : message.content
+          : (message.content || message._fullSummary || message.summary || '')
         
         if (collectedContent) {
           // 同时保存summary字段(作为单独字段)
@@ -3271,7 +3227,8 @@ const handleReportGeneratorSubmit = async (data) => {
       window_size: data.windowSize,
       n_results: 2,
       ai_conversation_id: ai_conversation_id.value,
-      is_network_search_enabled: isNetworkSearchEnabled.value
+      is_network_search_enabled: isNetworkSearchEnabled.value,
+      user_id: userId || 1 // 测试时可直接写死 1
     }
 
     console.log('📤 发起 SSE POST 请求:', {