|
|
@@ -161,6 +161,20 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- 思考过程 -->
|
|
|
+ <div v-if="message.thinkingContent" class="thinking-panel">
|
|
|
+ <button class="thinking-panel-header" @click="toggleThinkingPanel(message)">
|
|
|
+ <div class="thinking-panel-title">
|
|
|
+ <span class="thinking-panel-badge">{{ message.showThinking !== false ? '已思考' : '思考过程' }}</span>
|
|
|
+ <span class="thinking-panel-label">模型思考过程</span>
|
|
|
+ </div>
|
|
|
+ <span class="thinking-panel-arrow">{{ message.showThinking !== false ? '收起' : '展开' }}</span>
|
|
|
+ </button>
|
|
|
+ <div v-show="message.showThinking !== false" class="thinking-panel-body">
|
|
|
+ <StreamMarkdown :content="message.thinkingContent" :streaming="false" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- 问题总结 -->
|
|
|
<div v-if="message.summary" class="question-summary">
|
|
|
<StreamMarkdown :content="message.summary" :streaming="false" />
|
|
|
@@ -176,9 +190,9 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 报告列表 -->
|
|
|
+ <!-- 报告列表 -->
|
|
|
<div v-if="message.reports && message.reports.length > 0" class="reports-list">
|
|
|
- <template v-for="(report, rIndex) in message.reports" :key="`${report.source_file}-${report.file_index}-${rIndex}`">
|
|
|
+ <template v-for="(report, rIndex) in dedupeReportsByFileAndScene(message.reports)" :key="`${report.source_file}-${report.file_index}-${rIndex}`">
|
|
|
<!-- 类别标题 -->
|
|
|
<CategoryTitle
|
|
|
v-if="report.type === 'category_title'"
|
|
|
@@ -472,6 +486,7 @@ import { renderMarkdown } from '@/utils/markdown'
|
|
|
import { stopSSEStream, updateAIMessageContent } from '@/utils/api.js'
|
|
|
import {
|
|
|
buildAIMessageUpdatePayload,
|
|
|
+ dedupeReportsByFileAndScene,
|
|
|
extractRelatedQuestions,
|
|
|
hydratePersistedReports,
|
|
|
normalizeReportsForPersistence,
|
|
|
@@ -1264,6 +1279,7 @@ const getConversationMessages = async (conversationId) => {
|
|
|
let displayContent = userContent || ''
|
|
|
let reports = []
|
|
|
let summary = message.summary || '' // 从后端恢复summary字段
|
|
|
+ let thinkingContent = message.thinkingContent || message.thinking_content || ''
|
|
|
|
|
|
if (message.type === 'ai') {
|
|
|
try {
|
|
|
@@ -1289,6 +1305,19 @@ const getConversationMessages = async (conversationId) => {
|
|
|
if (parsedContent.summary) {
|
|
|
summary = parsedContent.summary
|
|
|
}
|
|
|
+ if (parsedContent.thinkingContent) {
|
|
|
+ thinkingContent = parsedContent.thinkingContent
|
|
|
+ }
|
|
|
+ } else if (parsedContent.answer || parsedContent.content || parsedContent.thinkingContent) {
|
|
|
+ if (parsedContent.thinkingContent) {
|
|
|
+ thinkingContent = parsedContent.thinkingContent
|
|
|
+ }
|
|
|
+ const answerContent = parsedContent.answer || parsedContent.content || ''
|
|
|
+ const processedContent = String(answerContent)
|
|
|
+ .replace(/\\n/g, '\n')
|
|
|
+ .replace(/\\t/g, '\t')
|
|
|
+ .replace(/\\r/g, '\r')
|
|
|
+ displayContent = renderMarkdownContent(processedContent)
|
|
|
} else if (Array.isArray(parsedContent)) {
|
|
|
// 旧格式,直接是reports数组
|
|
|
reports = hydratePersistedReports(parsedContent)
|
|
|
@@ -1338,6 +1367,8 @@ const getConversationMessages = async (conversationId) => {
|
|
|
displayContent: displayContent,
|
|
|
reports: reports, // 添加reports数组
|
|
|
summary: summary, // 添加summary字段
|
|
|
+ thinkingContent: thinkingContent,
|
|
|
+ showThinking: Boolean(thinkingContent),
|
|
|
totalFiles: totalFiles, // 总文件数
|
|
|
completedCount: completedCount, // 完成数
|
|
|
progress: progress, // 进度
|
|
|
@@ -2347,6 +2378,27 @@ const startReportFieldTypewriter = (report, field, fullContent, speed = 50) => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+const appendThinkingContent = (aiMessage, sectionTitle, content) => {
|
|
|
+ const normalized = (content || '').trim()
|
|
|
+ if (!normalized) return
|
|
|
+
|
|
|
+ const section = sectionTitle
|
|
|
+ ? `### ${sectionTitle}\n\n${normalized}`
|
|
|
+ : normalized
|
|
|
+
|
|
|
+ if (!aiMessage.thinkingContent) {
|
|
|
+ aiMessage.thinkingContent = section
|
|
|
+ } else if (!aiMessage.thinkingContent.includes(section)) {
|
|
|
+ aiMessage.thinkingContent = `${aiMessage.thinkingContent}\n\n---\n\n${section}`
|
|
|
+ }
|
|
|
+
|
|
|
+ aiMessage.showThinking = true
|
|
|
+}
|
|
|
+
|
|
|
+const toggleThinkingPanel = (message) => {
|
|
|
+ message.showThinking = message.showThinking === false
|
|
|
+}
|
|
|
+
|
|
|
// SSE消息处理函数
|
|
|
const handleSSEMessage = (data, aiMessageIndex) => {
|
|
|
const aiMessage = chatMessages.value[aiMessageIndex]
|
|
|
@@ -2392,6 +2444,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
|
|
|
switch (data.type) {
|
|
|
case 'intent':
|
|
|
aiMessage.isProfessionalQuestion = data.is_professional_question !== false
|
|
|
+ appendThinkingContent(aiMessage, '意图分析', data.thinking_content)
|
|
|
// 检查是否为专业问题
|
|
|
if (data.is_professional_question === false) {
|
|
|
// 非专业问题:立即隐藏状态显示组件
|
|
|
@@ -2437,6 +2490,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
|
|
|
aiMessage.summary = ''
|
|
|
aiMessage._fullSummary = ''
|
|
|
}
|
|
|
+ appendThinkingContent(aiMessage, '正式回答', data.thinking_content)
|
|
|
|
|
|
const finalContent = data.content || ''
|
|
|
aiMessage.content = finalContent
|
|
|
@@ -3149,6 +3203,8 @@ const handleReportGeneratorSubmit = async (data) => {
|
|
|
isTyping: true,
|
|
|
content: '',
|
|
|
displayContent: '',
|
|
|
+ thinkingContent: '',
|
|
|
+ showThinking: true,
|
|
|
timestamp: new Date().toISOString(),
|
|
|
// 新增:状态管理
|
|
|
currentStatus: 'querying_kb', // 当前状态
|
|
|
@@ -4732,6 +4788,71 @@ onActivated(async () => {
|
|
|
line-height: 1.8;
|
|
|
color: #606266;
|
|
|
}
|
|
|
+
|
|
|
+ .thinking-panel {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ background: #f8fafc;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-panel-header {
|
|
|
+ width: 100%;
|
|
|
+ padding: 10px 12px;
|
|
|
+ border: none;
|
|
|
+ background: transparent;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 8px;
|
|
|
+ cursor: pointer;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-panel-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-panel-badge {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 999px;
|
|
|
+ background: #e8f0ff;
|
|
|
+ color: #3b82f6;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-panel-label {
|
|
|
+ color: #374151;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-panel-arrow {
|
|
|
+ flex-shrink: 0;
|
|
|
+ color: #6b7280;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .thinking-panel-body {
|
|
|
+ padding: 0 12px 12px;
|
|
|
+ color: #4b5563;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.7;
|
|
|
+ border-top: 1px solid #eef2f7;
|
|
|
+ }
|
|
|
|
|
|
.reports-list {
|
|
|
margin-top: 12px;
|