Kaynağa Gözat

提示词工程复杂版

FanHong 5 gün önce
ebeveyn
işleme
8d54047589

+ 10 - 75
shudao-vue-frontend/src/components/CategoryTitle.vue

@@ -1,21 +1,12 @@
 <template>
   <div class="category-section">
-    <div class="category-header" @click="toggleExpand">
-      <div class="category-info">
-        <el-icon class="expand-icon" :class="{ expanded: isExpanded }">
-          <ArrowRight />
-        </el-icon>
-        <span class="category-name">{{ category }}</span>
-      </div>
-      <el-tag type="info" size="small" class="count-tag">{{ count }}个文件</el-tag>
+    <div class="category-header">
+      <span class="category-name">{{ `${number}.${category}:` }}</span>
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref, defineEmits, onMounted } from 'vue'
-import { ArrowRight } from '@element-plus/icons-vue'
-
 const props = defineProps({
   category: {
     type: String,
@@ -30,79 +21,23 @@ const props = defineProps({
     required: true
   }
 })
-
-const emit = defineEmits(['toggle'])
-const isExpanded = ref(false)
-
-const toggleExpand = () => {
-  isExpanded.value = !isExpanded.value
-  emit('toggle', { category: props.category, expanded: isExpanded.value })
-}
-
-onMounted(() => {
-  emit('toggle', { category: props.category, expanded: false })
-})
 </script>
 
 <style scoped>
 .category-section {
-  margin: 24px 0 12px 0;
+  margin: 28px 0 16px 0;
 }
 
 .category-header {
-  background: rgba(91, 141, 239, 0.15);
-  backdrop-filter: blur(10px);
-  -webkit-backdrop-filter: blur(10px);
-  border: 1px solid rgba(91, 141, 239, 0.25);
-  padding: 12px 20px;
-  border-radius: 12px;
-  cursor: pointer;
-  transition: all 0.3s ease;
-  box-shadow: 0 2px 12px rgba(91, 141, 239, 0.15);
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.category-header:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 4px 16px rgba(91, 141, 239, 0.25);
-  background: rgba(91, 141, 239, 0.2);
-  border-color: rgba(91, 141, 239, 0.35);
-}
-
-.category-info {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  color: #303133;
-  flex: 1;
-}
-
-.expand-icon {
-  font-size: 18px;
-  color: #5b8def;
-  transition: transform 0.3s ease;
-  flex-shrink: 0;
-}
-
-.expand-icon.expanded {
-  transform: rotate(90deg);
+  padding: 0 0 10px 0;
+  border-bottom: 1px solid rgba(32, 36, 44, 0.1);
 }
 
 .category-name {
-  font-size: 16px;
-  font-weight: 600;
-  color: #303133;
-  flex: 1;
-}
-
-.count-tag {
-  background: rgba(91, 141, 239, 0.1);
-  border: 1px solid rgba(91, 141, 239, 0.2);
-  color: #5b8def;
-  font-weight: 500;
-  font-size: 13px;
-  flex-shrink: 0;
+  display: block;
+  font-size: 20px;
+  font-weight: 700;
+  line-height: 1.4;
+  color: #1f2329;
 }
 </style>

+ 183 - 357
shudao-vue-frontend/src/components/FileReportCard.vue

@@ -1,131 +1,62 @@
 <template>
-  <el-card class="file-report-card" :class="statusClass" shadow="hover">
-    <!-- 文件信息头部 -->
-    <template #header>
-      <div class="card-header">
-        <div class="file-info">
-          <el-icon class="file-icon"><Document /></el-icon>
-          <div class="file-details">
-            <div class="file-name">
-              {{ report.report.display_name || report.source_file }}
-            </div>
-            <div class="file-meta">
-              <el-tag size="small" type="info">
-                文件 {{ report.file_index }}/{{ report.total_files }}
-              </el-tag>
-              <el-tag
-                v-if="report.metadata?.primary_category"
-                size="small"
-                type="success"
-              >
-                {{ report.metadata.primary_category }}
-              </el-tag>
-              <el-tag
-                v-if="report.metadata?.secondary_category"
-                size="small"
-              >
-                {{ report.metadata.secondary_category }}
-              </el-tag>
-            </div>
-            <!-- 文件来源链接 -->
-            <div v-if="sourceUrl" class="file-source-link" @click="openSourceUrl">
-              <el-icon class="link-icon"><Link /></el-icon>
-              <span class="link-text">{{ formatSourceUrl(sourceUrl) }}</span>
-            </div>
-          </div>
-        </div>
-        
-        <div class="actions">
-          <el-tooltip content="查看文件" placement="top" v-if="report.file_path">
-            <el-button
-              link
-              type="primary"
-              @click="openFile"
-            >
-              <el-icon><View /></el-icon>
-            </el-button>
-          </el-tooltip>
-          <el-tooltip content="复制报告" placement="top">
-            <el-button
-              link
-              type="primary"
-              @click="copyReport"
-            >
-              <el-icon><CopyDocument /></el-icon>
-            </el-button>
-          </el-tooltip>
-        </div>
+  <div class="file-reference-item" :class="statusClass">
+    <div class="file-header">
+      <div class="file-title-wrapper" @click="openFile" :class="{ 'clickable': report.file_path }">
+        <el-icon class="file-icon"><Document /></el-icon>
+        <span class="file-name">{{ report.report?.display_name || report.source_file }}</span>
+      </div>
+      
+      <div class="actions">
+        <el-tooltip content="复制" placement="top">
+          <el-button link type="primary" @click="copyReport" class="action-btn">
+            <el-icon><CopyDocument /></el-icon>
+          </el-button>
+        </el-tooltip>
+      </div>
+    </div>
+
+    <!-- Metadata Row -->
+    <div class="file-meta" v-if="hasMetaRow">
+      <el-tag v-if="report.metadata?.primary_category" size="small" type="info" class="meta-tag" effect="plain">
+        {{ report.metadata.primary_category }}
+      </el-tag>
+      <el-tag v-if="report.metadata?.secondary_category" size="small" type="info" class="meta-tag" effect="plain">
+        {{ report.metadata.secondary_category }}
+      </el-tag>
+      
+      <span class="meta-text" v-if="documentNumber !== '未提取'">{{ documentNumber }}</span>
+      <span class="meta-divider" v-if="documentNumber !== '未提取' && issuingUnit !== '未提取'">·</span>
+      
+      <span class="meta-text" v-if="issuingUnit !== '未提取'">{{ issuingUnit }}</span>
+      <span class="meta-divider" v-if="issuingUnit !== '未提取' && documentDate !== '未提取'">·</span>
+      
+      <span class="meta-text" v-if="documentDate !== '未提取'">{{ documentDate }}</span>
+
+      <!-- 文件来源链接 -->
+      <span class="meta-divider" v-if="sourceUrl && (documentNumber !== '未提取' || issuingUnit !== '未提取' || documentDate !== '未提取')">·</span>
+      <div v-if="sourceUrl" class="file-source-link" @click="openSourceUrl">
+        <el-icon class="link-icon"><Link /></el-icon>
+        <span class="link-text">{{ formatSourceUrl(sourceUrl) }}</span>
       </div>
-    </template>
-    
-    <!-- 报告内容 -->
-    <div class="report-content">
+    </div>
+
+    <!-- Summary -->
+    <div class="file-summary" v-if="briefSummary && briefSummary !== '暂无摘要'">
       <div v-if="report.status === 'error'" class="error-message">
         <el-alert
           title="生成报告时出错"
           type="error"
-          :description="report.report.summary"
+          :description="briefSummary"
           :closable="false"
         />
       </div>
-      
-      <div v-else>
-        <!-- 文件分析总述 -->
-        <div class="report-section">
-          <h3 class="section-title collapsible" @click="isSummaryExpanded = !isSummaryExpanded">
-            <el-icon class="collapse-icon" :class="{ collapsed: !isSummaryExpanded }">
-              <ArrowRight />
-            </el-icon>
-            <span>🔹文件分析总述</span>
-          </h3>
-          <transition name="collapse">
-            <div v-show="isSummaryExpanded" class="section-content">
-              <StreamMarkdown 
-                :content="report.report?.summary || ''" 
-                :streaming="report.status === 'streaming'"
-              />
-            </div>
-          </transition>
-        </div>
-        
-        <!-- 文件内容解读 -->
-        <div class="report-section">
-          <h3 class="section-title collapsible" @click="isAnalysisExpanded = !isAnalysisExpanded">
-            <el-icon class="collapse-icon" :class="{ collapsed: !isAnalysisExpanded }">
-              <ArrowRight />
-            </el-icon>
-            <span>🔹文件内容解读</span>
-          </h3>
-          <transition name="collapse">
-            <div v-show="isAnalysisExpanded" class="section-content">
-              <StreamMarkdown 
-                :content="report.report?.analysis || ''" 
-                :streaming="report.status === 'streaming'"
-              />
-            </div>
-          </transition>
-        </div>
-        
-        <!-- 相关条款提取 -->
-        <div v-if="report.report?.clauses" class="report-section">
-          <h3 class="section-title collapsible" @click="isClausesExpanded = !isClausesExpanded">
-            <el-icon class="collapse-icon" :class="{ collapsed: !isClausesExpanded }">
-              <ArrowRight />
-            </el-icon>
-            <span>🔹相关条款提取</span>
-          </h3>
-          <transition name="collapse">
-            <div v-show="isClausesExpanded" class="section-content">
-              <StreamMarkdown 
-                :content="report.report.clauses" 
-                :streaming="report.status === 'streaming'"
-              />
-            </div>
-          </transition>
-        </div>
-      </div>
+      <StreamMarkdown
+        v-else
+        :content="briefSummary"
+        :streaming="report.status === 'streaming'"
+      />
     </div>
-    
+
     <!-- iframe 预览弹窗 -->
     <el-dialog
       v-model="showSourcePreview"
@@ -149,13 +80,13 @@
         </div>
       </div>
     </el-dialog>
-  </el-card>
+  </div>
 </template>
 
 <script setup>
 import { computed, ref } from 'vue'
 import { ElMessage, ElDialog } from 'element-plus'
-import { Document, View, CopyDocument, Reading, List, Link, WarningFilled, ArrowRight } from '@element-plus/icons-vue'
+import { Document, CopyDocument, Link, WarningFilled } from '@element-plus/icons-vue'
 import StreamMarkdown from './StreamMarkdown.vue'
 
 const props = defineProps({
@@ -172,10 +103,14 @@ const showSourcePreview = ref(false)
 const sourcePreviewUrl = ref('')
 const sourcePreviewTitle = ref('')
 
-// 折叠状态控制
-const isSummaryExpanded = ref(true)
-const isAnalysisExpanded = ref(true)
-const isClausesExpanded = ref(true)
+const hasMetaRow = computed(() => {
+  return props.report.metadata?.primary_category || 
+         props.report.metadata?.secondary_category || 
+         documentNumber.value !== '未提取' || 
+         issuingUnit.value !== '未提取' || 
+         documentDate.value !== '未提取' ||
+         sourceUrl.value
+})
 
 const statusClass = computed(() => {
   return {
@@ -185,12 +120,61 @@ const statusClass = computed(() => {
   }
 })
 
-const getSimilarityType = (similarity) => {
-  if (similarity >= 0.8) return 'success'
-  if (similarity >= 0.6) return 'warning'
-  return 'danger'
+const readMetadataValue = (keys, fallback = '未提取') => {
+  for (const key of keys) {
+    const value = props.report?.metadata?.[key]
+    if (value !== undefined && value !== null && String(value).trim()) {
+      return String(value).trim()
+    }
+  }
+  return fallback
 }
 
+const formatDate = (value) => {
+  if (!value) {
+    return '未提取'
+  }
+  const date = new Date(value)
+  if (Number.isNaN(date.getTime())) {
+    return String(value)
+  }
+  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
+}
+
+const documentNumber = computed(() => readMetadataValue([
+  'document_number',
+  'document_no',
+  'doc_no',
+  'issue_no',
+  'serial_no',
+  'file_no',
+  '文号',
+  '发文编号'
+]))
+
+const issuingUnit = computed(() => readMetadataValue([
+  'issuing_unit',
+  'issuer',
+  'publish_unit',
+  'source_unit',
+  'department',
+  'organization',
+  'org_name',
+  '发文单位'
+]))
+
+const documentDate = computed(() => formatDate(
+  props.report?.metadata?.publish_date ||
+  props.report?.metadata?.issue_date ||
+  props.report?.metadata?.document_date ||
+  props.report?.metadata?.upload_date ||
+  props.report?.metadata?.发文日期
+))
+
+const briefSummary = computed(() => {
+  return props.report?.report?.summary || props.report?._fullContent?.summary || '暂无摘要'
+})
+
 // 获取文件来源URL
 const sourceUrl = computed(() => {
   // 调试日志:查看report对象结构
@@ -253,18 +237,12 @@ const openFile = () => {
 
 const copyReport = async () => {
   const text = `
-文件名: ${props.report.source_file}
-主要分类: ${props.report.metadata?.primary_category || '未分类'}
-场景分类: ${props.report.metadata?.secondary_category || '未分类'}
-相似度: ${(props.report.similarity * 100).toFixed(1)}%
-
-🔹文件分析总述
-${props.report.report?.summary || ''}
-
-🔹文件内容解读
-${props.report.report?.analysis || ''}
-
-${props.report.report?.clauses ? '🔹相关条款提取\n' + props.report.report.clauses : ''}
+文件名: ${props.report.report?.display_name || props.report.source_file}
+发文编号: ${documentNumber.value}
+发文单位: ${issuingUnit.value}
+发文日期: ${documentDate.value}
+摘要:
+${briefSummary.value}
   `.trim()
   
   try {
@@ -284,283 +262,123 @@ const openInNewTab = () => {
 </script>
 
 <style scoped>
-.file-report-card {
-  margin-bottom: 12px;
+.file-reference-item {
+  margin-bottom: 16px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #ebeef5;
   transition: all 0.3s ease;
-  border-radius: 8px;
-  overflow: hidden;
-}
-
-.file-report-card:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 4px 16px rgba(91, 141, 239, 0.15);
 }
 
-.status-processing {
-  border-left: none;
-}
-
-.status-completed {
-  border-left: none;
+.file-reference-item:last-child {
+  border-bottom: none;
+  margin-bottom: 8px;
+  padding-bottom: 8px;
 }
 
 .status-error {
-  border-left: 4px solid #f56c6c;
+  border-left: 3px solid #f56c6c;
+  padding-left: 12px;
 }
 
-.card-header {
+.file-header {
   display: flex;
   justify-content: space-between;
   align-items: flex-start;
+  margin-bottom: 8px;
 }
 
-.file-info {
+.file-title-wrapper {
   display: flex;
-  gap: 12px;
+  align-items: center;
+  gap: 8px;
   flex: 1;
 }
 
-.file-icon {
-  font-size: 32px;
+.file-title-wrapper.clickable {
+  cursor: pointer;
+}
+
+.file-title-wrapper.clickable:hover .file-name {
   color: #5b8def;
 }
 
-.file-details {
-  flex: 1;
+.file-icon {
+  font-size: 20px;
+  color: #5b8def;
 }
 
 .file-name {
-  font-size: 16px;
+  font-size: 15px;
   font-weight: 600;
   color: #303133;
-  margin-bottom: 8px;
-  word-break: break-all;
+  line-height: 1.4;
+  word-break: break-word;
+  transition: color 0.2s ease;
 }
 
 .file-meta {
   display: flex;
-  gap: 8px;
-  flex-wrap: wrap;
-}
-
-.file-source-link {
-  display: inline-flex;
   align-items: center;
-  gap: 6px;
-  margin-top: 8px;
-  padding: 6px 12px;
-  background-color: #F5F5F5;
-  color: #666;
-  border-radius: 6px;
-  cursor: pointer;
-  font-size: 13px;
-  transition: all 0.2s ease;
-  border: 1px solid #E8E8E8;
-  max-width: fit-content;
-}
-
-.file-source-link:hover {
-  background-color: #EAEAEA;
-  color: #333;
-  border-color: #D8D8D8;
-  transform: translateY(-1px);
-  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
-}
-
-.file-source-link .link-icon {
-  font-size: 14px;
-  color: #999;
-}
-
-.file-source-link .link-text {
-  font-weight: 500;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  max-width: 200px;
-}
-
-.actions {
-  display: flex;
+  flex-wrap: wrap;
   gap: 8px;
+  margin-bottom: 10px;
+  font-size: 13px;
+  color: #909399;
 }
 
-.report-content {
-  padding: 8px 0;
-}
-
-.report-section {
-  margin-bottom: 12px;
+.meta-tag {
+  border-radius: 4px;
 }
 
-.report-section:first-child {
-  margin-top: -10px;
+.meta-text {
+  color: #606266;
 }
 
-.report-section:last-child {
-  margin-bottom: 0;
+.meta-divider {
+  color: #dcdfe6;
 }
 
-.section-title {
-  font-size: 17px;
-  font-weight: 600;
-  color: #303133;
-  margin-top: 0;  
-  margin-bottom: 12px;
-  padding-bottom: 8px;
-  border-bottom: 2px solid #e4e7ed;
-  display: flex;
+.file-source-link {
+  display: inline-flex;
   align-items: center;
-  gap: 8px;
-}
-
-.section-title.collapsible {
+  gap: 4px;
   cursor: pointer;
-  user-select: none;
-  transition: all 0.3s ease;
-}
-
-.section-title.collapsible:hover {
-  color: #5b8def;
-}
-
-.collapse-icon {
-  font-size: 16px;
-  color: #909399;
-  transition: transform 0.3s ease;
-  flex-shrink: 0;
-}
-
-.collapse-icon.collapsed {
-  transform: rotate(0deg);
-}
-
-.section-title.collapsible:hover .collapse-icon {
   color: #5b8def;
+  transition: all 0.2s ease;
 }
 
-/* 默认展开状态,箭头向下旋转90度 */
-.collapse-icon:not(.collapsed) {
-  transform: rotate(90deg);
+.file-source-link:hover {
+  text-decoration: underline;
 }
 
-/* 折叠过渡动画 */
-.collapse-enter-active,
-.collapse-leave-active {
-  transition: opacity 0.3s ease, max-height 0.3s ease;
-  overflow: hidden;
+.file-source-link .link-icon {
+  font-size: 14px;
 }
 
-.collapse-enter-from,
-.collapse-leave-to {
+.actions {
+  display: flex;
+  gap: 4px;
+  margin-left: 12px;
   opacity: 0;
-  max-height: 0;
+  transition: opacity 0.2s ease;
 }
 
-.collapse-enter-to,
-.collapse-leave-from {
+.file-reference-item:hover .actions {
   opacity: 1;
-  max-height: 2000px;
 }
 
-.section-content {
+.file-summary {
   font-size: 14px;
-  line-height: 1.8;
-  color: #606266;
-  word-break: break-word;
-}
-
-/* 移除第一个和最后一个子元素的边距,保持一致的间距 */
-.section-content :deep(*:first-child) {
-  margin-top: 0 !important;
-}
-
-.section-content :deep(*:last-child) {
-  margin-bottom: 0 !important;
-}
-
-/* Markdown 样式 */
-.section-content :deep(h2) {
-  font-size: 16px;
-  font-weight: 600;
-  color: #303133;
-  margin: 8px 0 4px 0;
-  padding-bottom: 4px;
-  border-bottom: 1px solid #dcdfe6;
-}
-
-.section-content :deep(h3) {
-  font-size: 15px;
-  font-weight: 600;
-  color: #606266;
-  margin: 8px 0 4px 0;
-}
-
-.section-content :deep(p) {
-  margin: 8px 0;
-}
-
-.section-content :deep(strong) {
-  font-weight: 600;
-  color: #303133;
-}
-
-.section-content :deep(ul),
-.section-content :deep(ol) {
-  margin: 8px 0;
-  padding-left: 24px;
-}
-
-.section-content :deep(li) {
-  margin: 4px 0;
-}
-
-.section-content :deep(blockquote) {
-  margin: 12px 0;
-  padding: 8px 16px;
-  border-left: 4px solid #5b8def;
-  background-color: #f7f9fc;
+  line-height: 1.6;
   color: #606266;
-}
-
-.section-content :deep(code) {
-  padding: 2px 6px;
-  background-color: #f4f4f5;
-  border-radius: 3px;
-  font-family: 'Courier New', monospace;
-  font-size: 13px;
-}
-
-.section-content :deep(pre) {
-  margin: 12px 0;
+  background: #f8fafc;
   padding: 12px;
-  background-color: #f4f4f5;
-  border-radius: 4px;
-  overflow-x: auto;
-}
-
-.section-content :deep(pre code) {
-  padding: 0;
-  background-color: transparent;
-}
-
-.section-content :deep(a) {
-  color: #5b8def;
-  text-decoration: none;
-}
-
-.section-content :deep(a:hover) {
-  text-decoration: underline;
-}
-
-.section-content :deep(hr) {
-  margin: 16px 0;
-  border: none;
-  border-top: 1px solid #dcdfe6;
+  border-radius: 6px;
+  margin-top: 8px;
 }
 
 .error-message {
-  padding: 12px 0;
+  padding: 8px 0;
 }
 
 /* iframe预览样式 */
@@ -596,4 +414,12 @@ const openInNewTab = () => {
   margin-bottom: 16px;
   font-size: 16px;
 }
+
+/* Markdown 覆盖样式,使其在浅色背景框中更好看 */
+.file-summary :deep(p) {
+  margin: 0;
+}
+.file-summary :deep(p + p) {
+  margin-top: 8px;
+}
 </style>

+ 112 - 85
shudao-vue-frontend/src/components/MobileFileReportCard.vue

@@ -1,33 +1,33 @@
 <template>
-  <div class="mobile-file-report-card" @click="openFile">
-    <!-- 文件信息头部 -->
-    <div class="card-header">
+  <div class="mobile-file-reference-item" @click="openFile">
+    <div class="file-header">
       <div class="file-icon-wrapper">
         <svg class="file-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
           <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z" />
         </svg>
       </div>
-      <div class="file-details">
-        <div class="file-name">{{ report.report?.display_name || report.source_file }}</div>
-        <div class="file-meta-row">
-          <span v-if="report.metadata?.primary_category" class="category-tag">
-            <svg class="tag-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
-              <path d="M5.5,7A1.5,1.5 0 0,1 4,5.5A1.5,1.5 0 0,1 5.5,4A1.5,1.5 0 0,1 7,5.5A1.5,1.5 0 0,1 5.5,7M21.41,11.58L12.41,2.58C12.05,2.22 11.55,2 11,2H4C2.89,2 2,2.89 2,4V11C2,11.55 2.22,12.05 2.59,12.41L11.58,21.41C11.95,21.77 12.45,22 13,22C13.55,22 14.05,21.77 14.41,21.41L21.41,14.41C21.78,14.05 22,13.55 22,13C22,12.44 21.77,11.94 21.41,11.58Z" />
-            </svg>
-            {{ report.metadata.primary_category }}
-          </span>
-          <span class="view-count">
-            <svg class="view-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
-              <path d="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z" />
-            </svg>
-            {{ report.metadata?.view_count || 0 }} 次查看
-          </span>
-          <span class="view-detail">
-            查看详情 &gt;
-          </span>
-        </div>
-      </div>
-      <div class="file-date">{{ formatDate(report.metadata?.upload_date) }}</div>
+      <div class="file-name">{{ report.report?.display_name || report.source_file }}</div>
+    </div>
+
+    <div class="file-meta" v-if="hasMetaRow">
+      <span v-if="report.metadata?.primary_category" class="category-tag">
+        {{ report.metadata.primary_category }}
+      </span>
+      <span v-if="report.metadata?.secondary_category" class="category-tag">
+        {{ report.metadata.secondary_category }}
+      </span>
+      
+      <span class="meta-text" v-if="documentNumber !== '未提取'">{{ documentNumber }}</span>
+      <span class="meta-divider" v-if="documentNumber !== '未提取' && issuingUnit !== '未提取'">·</span>
+      
+      <span class="meta-text" v-if="issuingUnit !== '未提取'">{{ issuingUnit }}</span>
+      <span class="meta-divider" v-if="issuingUnit !== '未提取' && documentDate !== '未提取'">·</span>
+      
+      <span class="meta-text" v-if="documentDate !== '未提取'">{{ documentDate }}</span>
+    </div>
+
+    <div class="file-summary" v-if="briefSummary && briefSummary !== '暂无摘要'">
+      <div class="summary-text">{{ briefSummary }}</div>
     </div>
   </div>
 </template>
@@ -44,13 +44,51 @@ const props = defineProps({
 
 const emit = defineEmits(['preview-file'])
 
-const formatDate = (dateStr) => {
-  if (!dateStr) return ''
-  const date = new Date(dateStr)
-  if (isNaN(date.getTime())) return dateStr
+const readMetadataValue = (keys, fallback = '未提取') => {
+  for (const key of keys) {
+    const value = props.report?.metadata?.[key]
+    if (value !== undefined && value !== null && String(value).trim()) {
+      return String(value).trim()
+    }
+  }
+  return fallback
+}
+
+const formatDate = (value) => {
+  if (!value) return '未提取'
+  const date = new Date(value)
+  if (isNaN(date.getTime())) return String(value)
   return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
 }
 
+const documentNumber = computed(() => readMetadataValue([
+  'document_number', 'document_no', 'doc_no', 'issue_no', 'serial_no', 'file_no', '文号', '发文编号'
+]))
+
+const issuingUnit = computed(() => readMetadataValue([
+  'issuing_unit', 'issuer', 'publish_unit', 'source_unit', 'department', 'organization', 'org_name', '发文单位'
+]))
+
+const documentDate = computed(() => formatDate(
+  props.report?.metadata?.publish_date ||
+  props.report?.metadata?.issue_date ||
+  props.report?.metadata?.document_date ||
+  props.report?.metadata?.upload_date ||
+  props.report?.metadata?.发文日期
+))
+
+const briefSummary = computed(() => {
+  return props.report?.report?.summary || props.report?._fullContent?.summary || ''
+})
+
+const hasMetaRow = computed(() => {
+  return props.report.metadata?.primary_category || 
+         props.report.metadata?.secondary_category || 
+         documentNumber.value !== '未提取' || 
+         issuingUnit.value !== '未提取' || 
+         documentDate.value !== '未提取'
+})
+
 const openFile = () => {
   if (props.report.file_path) {
     const fileName = props.report.report?.display_name || props.report.source_file || '未命名文件'
@@ -63,46 +101,45 @@ const openFile = () => {
 </script>
 
 <style scoped>
-.mobile-file-report-card {
-  background: white;
-  border-radius: 12px;
-  padding: 16px;
-  margin-bottom: 12px;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+.mobile-file-reference-item {
+  margin-bottom: 16px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #f0f2f5;
   cursor: pointer;
-  transition: all 0.2s ease;
+  transition: background-color 0.2s;
+}
+
+.mobile-file-reference-item:last-child {
+  border-bottom: none;
+  margin-bottom: 8px;
+  padding-bottom: 8px;
 }
 
-.mobile-file-report-card:active {
-  background: #f8f9fa;
+.mobile-file-reference-item:active {
+  background-color: #fafafa;
 }
 
-.card-header {
+.file-header {
   display: flex;
   align-items: flex-start;
-  gap: 12px;
+  gap: 8px;
+  margin-bottom: 8px;
 }
 
 .file-icon-wrapper {
   flex-shrink: 0;
-  width: 40px;
-  height: 40px;
-  background: #fee2e2;
-  border-radius: 8px;
+  width: 24px;
+  height: 24px;
   display: flex;
   align-items: center;
   justify-content: center;
+  margin-top: 2px;
 }
 
 .file-icon {
-  width: 24px;
-  height: 24px;
-  color: #dc2626;
-}
-
-.file-details {
-  flex: 1;
-  min-width: 0;
+  width: 20px;
+  height: 20px;
+  color: #3b82f6;
 }
 
 .file-name {
@@ -110,58 +147,48 @@ const openFile = () => {
   font-weight: 600;
   color: #1f2937;
   line-height: 1.4;
-  margin-bottom: 8px;
   word-break: break-word;
 }
 
-.file-meta-row {
+.file-meta {
   display: flex;
   align-items: center;
   flex-wrap: wrap;
-  gap: 12px;
+  gap: 6px;
+  margin-bottom: 8px;
+  padding-left: 32px;
+  font-size: 12px;
+  color: #6b7280;
 }
 
 .category-tag {
   display: inline-flex;
   align-items: center;
-  gap: 4px;
-  padding: 4px 10px;
-  background: #dbeafe;
-  color: #1d4ed8;
-  border-radius: 12px;
-  font-size: 12px;
-  font-weight: 500;
+  padding: 2px 6px;
+  background: #f3f4f6;
+  color: #4b5563;
+  border-radius: 4px;
+  font-size: 11px;
 }
 
-.tag-icon {
-  width: 12px;
-  height: 12px;
+.meta-text {
+  color: #4b5563;
 }
 
-.view-count {
-  display: inline-flex;
-  align-items: center;
-  gap: 4px;
-  color: #6b7280;
-  font-size: 12px;
-}
-
-.view-icon {
-  width: 14px;
-  height: 14px;
+.meta-divider {
+  color: #d1d5db;
 }
 
-.view-detail {
-  color: #3b82f6;
-  font-size: 12px;
-  font-weight: 500;
-  margin-left: auto;
+.file-summary {
+  padding-left: 32px;
 }
 
-.file-date {
-  flex-shrink: 0;
-  color: #9ca3af;
-  font-size: 12px;
-  white-space: nowrap;
+.summary-text {
+  font-size: 13px;
+  line-height: 1.5;
+  color: #4b5563;
+  background: #f9fafb;
+  padding: 10px;
+  border-radius: 6px;
 }
 </style>

+ 12 - 7
shudao-vue-frontend/src/views/Chat.thinkingPanelOrder.test.js

@@ -20,16 +20,21 @@ const expectThinkingPanelBeforeAnswer = (source) => {
   const template = getTemplate(source)
   const responseStart = template.indexOf('<div class="ai-response-content">')
   const thinkingPanel = template.indexOf('class="thinking-panel"', responseStart)
-  const questionSummary = template.indexOf('class="question-summary"', responseStart)
-  const aiText = template.indexOf('class="ai-text"', responseStart)
+  const scrollablePanel = template.indexOf('class="thinking-panel-body thinking-panel-scrollable"', responseStart)
+  const briefAnswer = template.indexOf('class="brief-answer-card"', responseStart)
+  const reportsList = template.indexOf('class="reports-list"', responseStart)
+  const questionSummary = template.indexOf('class="question-summary result-summary-card"', responseStart)
 
   expect(responseStart).toBeGreaterThanOrEqual(0)
   expect(thinkingPanel).toBeGreaterThan(responseStart)
-  expect(questionSummary).toBeGreaterThan(responseStart)
-  expect(aiText).toBeGreaterThan(responseStart)
-  expect(thinkingPanel).toBeLessThan(questionSummary)
-  expect(thinkingPanel).toBeLessThan(aiText)
+  expect(scrollablePanel).toBeGreaterThan(thinkingPanel)
+  expect(briefAnswer).toBeGreaterThan(thinkingPanel)
+  expect(reportsList).toBeGreaterThan(briefAnswer)
+  expect(questionSummary).toBeGreaterThan(reportsList)
   expect(template.slice(thinkingPanel - 80, thinkingPanel)).toContain('v-if="message.thinkingContent"')
+  expect(template).toContain('简要回答')
+  expect(template).toContain('查询结果总结')
+  expect(template).toContain('模型思考过程')
 }
 
 describe('Chat thinking panel order', () => {
@@ -45,7 +50,7 @@ describe('Chat thinking panel order', () => {
     const source = readView('mobile/m-Chat.vue')
 
     expect(source).toContain('const appendThinkingContent =')
-    expect(source).toContain("appendThinkingContent(aiMessage, '意图分析', data.thinking_content)")
     expect(source).toContain("appendThinkingContent(aiMessage, '正式回答', data.thinking_content)")
+    expect(source).toContain("appendThinkingDelta(aiMessage, data.chunk || '')")
   })
 })

+ 180 - 31
shudao-vue-frontend/src/views/Chat.vue

@@ -244,20 +244,25 @@
                     <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-badge">{{ message.thinkingStreaming ? '思考中' : '已思考' }}</span>
                           <span class="thinking-panel-label">模型思考过程</span>
                         </div>
-                        <span class="thinking-panel-arrow">{{ message.showThinking !== false ? '收起' : '展开' }}</span>
+                        <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 v-show="message.showThinking !== false" class="thinking-panel-body thinking-panel-scrollable">
+                        <StreamMarkdown :content="getDisplayThinkingContent(message.thinkingContent)" :streaming="Boolean(message.thinkingStreaming)" />
                       </div>
                     </div>
 
-                    <!-- 问题总结 -->
-                    <div v-if="message.summary" class="question-summary">
-                      <StreamMarkdown :content="message.summary" :streaming="false" />
-                </div>
+                    <!-- 简要回答 -->
+                    <div
+                      v-if="message.displayContent && message.displayContent.length > 0 && !message.isDocument"
+                      class="brief-answer-card"
+                    >
+                      <div class="ai-markdown-content">
+                        <div v-html="message.displayContent"></div>
+                      </div>
+                    </div>
                     
                     <!-- 报告生成中的Loading动画 - 当还没有报告时显示 -->
                     <div v-if="message.isTyping && (!message.reports || message.reports.length === 0) && message.progress < 100" class="report-loading">
@@ -278,19 +283,17 @@
                           :category="report.category"
                           :number="report.number"
                           :count="report.count"
-                          @toggle="(data) => handleCategoryToggle(index, data)"
                         />
                         <!-- 文件报告 -->
                         <FileReportCard
                           v-else-if="!report.type || report.type !== 'category_title'"
-                          v-show="isCategoryExpanded(index, report.metadata?._displayCategory || report.metadata?.primary_category)"
                           :report="report"
                           @preview-file="handleFilePreview"
                         />
                       </template>
                       
                       <!-- 分类下的Loading动画 - 当有分类但还在等待报告时 -->
-                      <div v-if="message.isTyping && message.progress < 100 && hasOnlyCategoryTitles(message.reports)" class="report-loading">
+                      <div v-if="message.isTyping && hasOnlyCategoryTitles(message.reports)" class="report-loading">
                         <span class="loading-text">AI正在思考中...</span>
                       <div class="thinking-animation">
                         <span class="dot"></span>
@@ -298,6 +301,11 @@
                         <span class="dot"></span>
                       </div>
                     </div>
+
+                    <!-- 查询结果总结 -->
+                    <div v-if="message.summary" class="question-summary result-summary-card">
+                      <StreamMarkdown :content="message.summary" :streaming="false" />
+                    </div>
                     </div>
                     
                     <!-- 网络搜索总结 -->
@@ -341,16 +349,12 @@
                       </div>
                     </div>
 
-                    <!-- 原有的AI文本内容(如果没有报告数据时显示) -->
-                    <div v-if="!message.reports || message.reports.length === 0" class="ai-text">
-                    <div v-if="message.displayContent && message.displayContent.length > 0 && !message.isDocument" class="ai-markdown-content">
-                      <div v-html="message.displayContent"></div>
-                    </div>
-                  </div>
+                    <!-- 原有的AI文本容器 -->
+                    <div v-if="!message.reports || message.reports.length === 0" class="ai-text"></div>
                   </div>
                   
-                  <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary)" class="divider"></div>
-                  <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary)" class="message-actions">
+                  <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary || (message.reports && message.reports.length > 0))" class="divider"></div>
+                  <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary || (message.reports && message.reports.length > 0))" class="message-actions">
                     <div class="left-actions">
                       <button class="action-btn copy-btn" @click="copyAIMessage(message)">
                         <img :src="copyIcon" alt="复制" class="action-icon">
@@ -418,7 +422,7 @@
                 </div>
                 
                 <!-- 推荐问题Loading -->
-                <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary) && isGettingRelatedQuestions && (relatedQuestionsMessageId === message.id || relatedQuestionsMessageId === message.ai_message_id) && aiRelatedQuestions.length === 0" class="related-questions-loading">
+                <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary || (message.reports && message.reports.length > 0)) && isGettingRelatedQuestions && (relatedQuestionsMessageId === message.id || relatedQuestionsMessageId === message.ai_message_id) && aiRelatedQuestions.length === 0" class="related-questions-loading">
                   <div class="thinking-animation">
                     <span class="dot"></span>
                     <span class="dot"></span>
@@ -427,7 +431,7 @@
                 </div>
                 
                 <!-- 提问联想 -->
-                <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary) && (relatedQuestionsMessageId === message.id || relatedQuestionsMessageId === message.ai_message_id) && aiRelatedQuestions.length > 0" class="related-questions">
+                <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary || (message.reports && message.reports.length > 0)) && (relatedQuestionsMessageId === message.id || relatedQuestionsMessageId === message.ai_message_id) && aiRelatedQuestions.length > 0" class="related-questions">
                   <div 
                     v-for="(question, index) in aiRelatedQuestions" 
                     :key="index"
@@ -2175,6 +2179,8 @@ const getConversationMessages = async (conversationId) => {
           webSearchTotal: message.webSearchRaw?.total || 0,
           thinkingContent: thinkingContent,
           showThinking: Boolean(thinkingContent),
+          thinkingStreaming: false,
+          answerStreaming: false,
           // 状态管理(历史记录默认完成状态)
           showStats: totalFiles > 0,
           currentStatus: 'completed',
@@ -3003,11 +3009,11 @@ const updateMessageStatus = (aiMessage, status, customMessage = null) => {
       progress: 70
     },
     deep_thinking: {
-      message: '🤔 <span class="ai-name">蜀道安全管理AI智能助手</span>正在深度思考中,请您稍等片刻……',
+      message: '🤔 <span class="ai-name">蜀道安全管理AI智能助手</span>正在梳理回答思路,请稍等……',
       progress: 75
     },
     outputting: {
-      message: '😄 <span class="ai-name">蜀道安全管理AI智能助手</span>正在整理分析中!',
+      message: '😄 <span class="ai-name">蜀道安全管理AI智能助手</span>正在整理回答内容……',
       progress: 90
     },
     completed: {
@@ -3050,23 +3056,79 @@ const updateMessageStatus = (aiMessage, status, customMessage = null) => {
   }
 }
 
+const THINKING_FALLBACK_TEXT = '正在结合检索结果梳理回答重点,请稍等……'
+const THINKING_HEADING_REPLACEMENTS = [
+  [/^\s*\d+\.\s*Analyze the Request:?\s*$/i, '### 问题理解'],
+  [/^\s*Analyze the Request:?\s*$/i, '### 问题理解'],
+  [/^\s*\d+\.\s*Response Plan:?\s*$/i, '### 回答组织'],
+  [/^\s*Response Plan:?\s*$/i, '### 回答组织'],
+  [/^\s*\d+\.\s*Key Points:?\s*$/i, '### 回答重点'],
+  [/^\s*Key Points:?\s*$/i, '### 回答重点']
+]
+const THINKING_BLOCKLIST_PATTERNS = [
+  /^\s*Thinking Process:?\s*$/i,
+  /^\s*[-*•]?\s*Role:\s*/i,
+  /^\s*[-*•]?\s*Task:\s*/i,
+  /^\s*[-*•]?\s*Input:\s*/i,
+  /^\s*[-*•]?\s*Constraints:?\s*$/i,
+  /^\s*[-*•]?\s*Prioritize knowledge base snippets/i,
+  /^\s*[-*•]?\s*Do not fabricate information/i,
+  /^\s*[-*•]?\s*Direct answer, no meta-talk/i,
+  /^\s*[-*•]?\s*Structure:\s*/i,
+  /^\s*[-*•]?\s*Tone:\s*/i
+]
+
+const getDisplayThinkingContent = (content) => {
+  const rawContent = String(content || '')
+  if (!rawContent.trim()) return ''
+
+  const hasMetaBoilerplate = /Thinking Process:|Analyze the Request|Role:|Task:|Input:|Constraints:/i.test(rawContent)
+  const normalizedLines = rawContent
+    .split('\n')
+    .map(line => line.replace(/\r/g, ''))
+    .map(line => {
+      const trimmed = line.trim()
+      if (!trimmed) return ''
+      for (const [pattern, replacement] of THINKING_HEADING_REPLACEMENTS) {
+        if (pattern.test(trimmed)) {
+          return replacement
+        }
+      }
+      return line
+    })
+    .filter(line => {
+      const trimmed = line.trim()
+      if (!trimmed) return false
+      return !THINKING_BLOCKLIST_PATTERNS.some(pattern => pattern.test(trimmed))
+    })
+
+  const normalizedContent = normalizedLines.join('\n').trim()
+  if (normalizedContent) {
+    return normalizedContent
+  }
+  return hasMetaBoilerplate ? THINKING_FALLBACK_TEXT : rawContent.trim()
+}
+
 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.thinkingContent = normalized
+  } else if (!aiMessage.thinkingContent.includes(normalized)) {
+    aiMessage.thinkingContent = `${aiMessage.thinkingContent}${normalized}`
   }
 
   aiMessage.showThinking = true
 }
 
+const appendThinkingDelta = (aiMessage, chunk) => {
+  const normalized = chunk || ''
+  if (!normalized) return
+  aiMessage.thinkingContent = `${aiMessage.thinkingContent || ''}${normalized}`
+  aiMessage.showThinking = true
+}
+
 const toggleThinkingPanel = (message) => {
   message.showThinking = message.showThinking === false
 }
@@ -3128,7 +3190,6 @@ const handleSSEMessage = (data, aiMessageIndex) => {
 
     case 'intent':
       aiMessage.isProfessionalQuestion = data.is_professional_question !== false
-      appendThinkingContent(aiMessage, '意图分析', data.thinking_content)
 
       // 检查是否为专业问题
       if (data.is_professional_question === false) {
@@ -3169,7 +3230,67 @@ const handleSSEMessage = (data, aiMessageIndex) => {
       }
       break
 
+    case 'answer_thinking_start':
+      aiMessage.thinkingStreaming = true
+      aiMessage.showThinking = true
+      if (shouldApplyMessageProgressStatus(aiMessage, 'deep_thinking')) {
+        updateMessageStatus(aiMessage, 'deep_thinking')
+      }
+      break
+
+    case 'answer_thinking_delta':
+      appendThinkingDelta(aiMessage, data.chunk || '')
+      break
+
+    case 'answer_thinking_done':
+      aiMessage.thinkingStreaming = false
+      if (!shouldHideStatsForStreamingAnswer(aiMessage) && aiMessage.currentStatus === 'deep_thinking') {
+        updateMessageStatus(
+          aiMessage,
+          'outputting',
+          '😄 <span class="ai-name">蜀道安全管理AI智能助手</span>正在整理回答内容……'
+        )
+      }
+      break
+
+    case 'answer_content_start':
+      if (shouldHideStatsForStreamingAnswer(aiMessage)) {
+        aiMessage.showStats = false
+      } else {
+        updateMessageStatus(aiMessage, 'outputting')
+      }
+      if (shouldClearSummaryForOnlineAnswer(aiMessage)) {
+        aiMessage.summary = ''
+        aiMessage._fullSummary = ''
+      }
+      aiMessage.answerStreaming = true
+      aiMessage.isTyping = true
+      if (!aiMessage.content) {
+        aiMessage.content = ''
+      }
+      if (!aiMessage.displayContent) {
+        aiMessage.displayContent = ''
+      }
+      break
+
+    case 'answer_content_delta': {
+      const delta = data.chunk || ''
+      if (!delta) break
+      aiMessage.content = `${aiMessage.content || ''}${delta}`
+      const processedReply = processAIResponse(aiMessage.content)
+      aiMessage.displayContent = renderMarkdownContent(processedReply)
+      aiMessage.isTyping = true
+      break
+    }
+
+    case 'answer_content_done':
+      aiMessage.answerStreaming = false
+      aiMessage.isTyping = false
+      break
+
     case 'online_answer': {
+      aiMessage.answerStreaming = false
+      aiMessage.thinkingStreaming = false
       if (shouldHideStatsForStreamingAnswer(aiMessage)) {
         aiMessage.showStats = false
       } else {
@@ -3928,6 +4049,8 @@ const handleReportGeneratorSubmit = async (data) => {
     isProfessionalQuestion: null,
     thinkingContent: '',
     showThinking: true,
+    thinkingStreaming: false,
+    answerStreaming: false,
     totalFiles: 0,
     webSearchTotal: 0,
     progress: 0,
@@ -6481,13 +6604,32 @@ onActivated(async () => {
     width: 100%;
   }
   
+  .brief-answer-card,
   .question-summary {
     margin-bottom: 16px;
+    padding: 0;
+    background: transparent;
+    border: none;
+    border-radius: 0;
+  }
+
+  .response-section-title {
+    margin-bottom: 10px;
+    color: #111827;
+    font-size: 14px;
+    font-weight: 600;
+  }
+
+  .question-summary {
     font-size: 16px;
     line-height: 1.8;
     color: #606266;
   }
 
+  .result-summary-card {
+    background: transparent;
+  }
+
   .thinking-panel {
     margin-bottom: 16px;
     background: #f8fafc;
@@ -6545,6 +6687,13 @@ onActivated(async () => {
     line-height: 1.8;
     border-top: 1px solid #eef2f7;
   }
+
+  .thinking-panel-scrollable {
+    max-height: 240px;
+    overflow-y: auto;
+    overscroll-behavior: contain;
+    scrollbar-gutter: stable;
+  }
   
   .web-search-capsule-outer {
     margin-bottom: 7px;

+ 176 - 33
shudao-vue-frontend/src/views/mobile/m-Chat.vue

@@ -165,20 +165,25 @@
                   <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-badge">{{ message.thinkingStreaming ? '思考中' : '已思考' }}</span>
                         <span class="thinking-panel-label">模型思考过程</span>
                       </div>
-                      <span class="thinking-panel-arrow">{{ message.showThinking !== false ? '收起' : '展开' }}</span>
+                      <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 v-show="message.showThinking !== false" class="thinking-panel-body thinking-panel-scrollable">
+                      <StreamMarkdown :content="getDisplayThinkingContent(message.thinkingContent)" :streaming="Boolean(message.thinkingStreaming)" />
                     </div>
                   </div>
 
-                  <!-- 问题总结 -->
-                  <div v-if="message.summary" class="question-summary">
-                    <StreamMarkdown :content="message.summary" :streaming="false" />
-              </div>
+                  <!-- 简要回答 -->
+                  <div
+                    v-if="message.displayContent && message.displayContent.length > 0"
+                    class="brief-answer-card"
+                  >
+                    <div class="ai-markdown-content">
+                      <div v-html="message.displayContent"></div>
+                    </div>
+                  </div>
                   
                   <!-- 报告生成中的Loading动画 - 当还没有报告时显示 -->
                   <div v-if="message.isTyping && (!message.reports || message.reports.length === 0) && message.progress < 100" class="report-loading">
@@ -199,19 +204,17 @@
                     :category="report.category"
                     :number="report.number"
                     :count="report.count"
-                    @toggle="(data) => handleCategoryToggle(index, data)"
                   />
                   <!-- 文件报告 -->
                   <FileReportCard
                     v-else-if="!report.type || report.type !== 'category_title'"
-                    v-show="isCategoryExpanded(index, report.metadata?._displayCategory || report.metadata?.primary_category)"
                     :report="report"
                     @preview-file="handleFilePreview"
                   />
                 </template>
                 
                 <!-- 分类下的Loading动画 -->
-                <div v-if="message.isTyping && message.progress < 100 && hasOnlyCategoryTitles(message.reports)" class="report-loading">
+                <div v-if="message.isTyping && hasOnlyCategoryTitles(message.reports)" class="report-loading">
                   <span class="loading-text">AI正在思考中...</span>
                   <div class="thinking-animation">
                     <span class="dot"></span>
@@ -220,23 +223,24 @@
                 </div>
                 </div>
               </div>
+
+                  <!-- 查询结果总结 -->
+                  <div v-if="message.summary" class="question-summary result-summary-card">
+                    <StreamMarkdown :content="message.summary" :streaming="false" />
+                  </div>
               
                   <!-- 网络搜索总结 -->
                   <div v-if="message.hasWebSearchResults && message.webSearchSummary">
                     <WebSearchSummary :summary="message.webSearchSummary" />
                   </div>
                   
-                  <!-- AI文本内容(如果没有报告数据时显示) -->
-                  <div v-if="!message.reports || message.reports.length === 0" class="ai-text">
-                    <div v-if="message.displayContent && message.displayContent.length > 0" class="ai-markdown-content">
-                      <div v-html="message.displayContent"></div>
-                    </div>
-                  </div>
+                  <!-- AI文本容器 -->
+                  <div v-if="!message.reports || message.reports.length === 0" class="ai-text"></div>
                 </div>
                 
                 <!-- 操作按钮 - 在白色气泡内 -->
-                <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary)" class="divider"></div>
-                <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary)" class="message-actions">
+                <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary || (message.reports && message.reports.length > 0))" class="divider"></div>
+                <div v-show="!message.isTyping && ((message.displayContent && message.displayContent.length > 0) || message.summary || (message.reports && message.reports.length > 0))" class="message-actions">
                   <div class="left-actions">
                     <button class="action-btn copy-btn" @click="copyAIMessage(message)" title="复制">
                       <img :src="copyIcon" alt="复制" class="action-icon">
@@ -1431,6 +1435,8 @@ const getConversationMessages = async (conversationId) => {
           summary: summary, // 添加summary字段
           thinkingContent: thinkingContent,
           showThinking: Boolean(thinkingContent),
+          thinkingStreaming: false,
+          answerStreaming: false,
           totalFiles: totalFiles, // 总文件数
           completedCount: completedCount, // 完成数
           progress: progress, // 进度
@@ -2386,11 +2392,11 @@ const updateMessageStatus = (aiMessage, status, customMessage = null) => {
       progress: 70
     },
     deep_thinking: {
-      message: '正在深度思考中,请您稍等片刻……',
+      message: '正在梳理回答思路,请稍等……',
       progress: 75
     },
     outputting: {
-      message: '正在整理分析中!',
+      message: '正在整理回答内容……',
       progress: 90
     },
     completed: {
@@ -2472,23 +2478,79 @@ const startReportFieldTypewriter = (report, field, fullContent, speed = 50) => {
   })
 }
 
+const THINKING_FALLBACK_TEXT = '正在结合检索结果梳理回答重点,请稍等……'
+const THINKING_HEADING_REPLACEMENTS = [
+  [/^\s*\d+\.\s*Analyze the Request:?\s*$/i, '### 问题理解'],
+  [/^\s*Analyze the Request:?\s*$/i, '### 问题理解'],
+  [/^\s*\d+\.\s*Response Plan:?\s*$/i, '### 回答组织'],
+  [/^\s*Response Plan:?\s*$/i, '### 回答组织'],
+  [/^\s*\d+\.\s*Key Points:?\s*$/i, '### 回答重点'],
+  [/^\s*Key Points:?\s*$/i, '### 回答重点']
+]
+const THINKING_BLOCKLIST_PATTERNS = [
+  /^\s*Thinking Process:?\s*$/i,
+  /^\s*[-*•]?\s*Role:\s*/i,
+  /^\s*[-*•]?\s*Task:\s*/i,
+  /^\s*[-*•]?\s*Input:\s*/i,
+  /^\s*[-*•]?\s*Constraints:?\s*$/i,
+  /^\s*[-*•]?\s*Prioritize knowledge base snippets/i,
+  /^\s*[-*•]?\s*Do not fabricate information/i,
+  /^\s*[-*•]?\s*Direct answer, no meta-talk/i,
+  /^\s*[-*•]?\s*Structure:\s*/i,
+  /^\s*[-*•]?\s*Tone:\s*/i
+]
+
+const getDisplayThinkingContent = (content) => {
+  const rawContent = String(content || '')
+  if (!rawContent.trim()) return ''
+
+  const hasMetaBoilerplate = /Thinking Process:|Analyze the Request|Role:|Task:|Input:|Constraints:/i.test(rawContent)
+  const normalizedLines = rawContent
+    .split('\n')
+    .map(line => line.replace(/\r/g, ''))
+    .map(line => {
+      const trimmed = line.trim()
+      if (!trimmed) return ''
+      for (const [pattern, replacement] of THINKING_HEADING_REPLACEMENTS) {
+        if (pattern.test(trimmed)) {
+          return replacement
+        }
+      }
+      return line
+    })
+    .filter(line => {
+      const trimmed = line.trim()
+      if (!trimmed) return false
+      return !THINKING_BLOCKLIST_PATTERNS.some(pattern => pattern.test(trimmed))
+    })
+
+  const normalizedContent = normalizedLines.join('\n').trim()
+  if (normalizedContent) {
+    return normalizedContent
+  }
+  return hasMetaBoilerplate ? THINKING_FALLBACK_TEXT : rawContent.trim()
+}
+
 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.thinkingContent = normalized
+  } else if (!aiMessage.thinkingContent.includes(normalized)) {
+    aiMessage.thinkingContent = `${aiMessage.thinkingContent}${normalized}`
   }
 
   aiMessage.showThinking = true
 }
 
+const appendThinkingDelta = (aiMessage, chunk) => {
+  const normalized = chunk || ''
+  if (!normalized) return
+  aiMessage.thinkingContent = `${aiMessage.thinkingContent || ''}${normalized}`
+  aiMessage.showThinking = true
+}
+
 const toggleThinkingPanel = (message) => {
   message.showThinking = message.showThinking === false
 }
@@ -2538,7 +2600,6 @@ 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) {
         // 非专业问题:立即隐藏状态显示组件
@@ -2578,7 +2639,63 @@ const handleSSEMessage = (data, aiMessageIndex) => {
       }
       break
 
+    case 'answer_thinking_start':
+      aiMessage.thinkingStreaming = true
+      aiMessage.showThinking = true
+      if (shouldApplyMessageProgressStatus(aiMessage, 'deep_thinking')) {
+        updateMessageStatus(aiMessage, 'deep_thinking')
+      }
+      break
+
+    case 'answer_thinking_delta':
+      appendThinkingDelta(aiMessage, data.chunk || '')
+      break
+
+    case 'answer_thinking_done':
+      aiMessage.thinkingStreaming = false
+      if (!shouldHideStatsForStreamingAnswer(aiMessage) && aiMessage.currentStatus === 'deep_thinking') {
+        updateMessageStatus(aiMessage, 'outputting', '正在整理回答内容……')
+      }
+      break
+
+    case 'answer_content_start':
+      if (shouldHideStatsForStreamingAnswer(aiMessage)) {
+        aiMessage.showStats = false
+      } else {
+        updateMessageStatus(aiMessage, 'outputting')
+      }
+      if (shouldClearSummaryForOnlineAnswer(aiMessage)) {
+        aiMessage.summary = ''
+        aiMessage._fullSummary = ''
+      }
+      aiMessage.answerStreaming = true
+      aiMessage.isTyping = true
+      if (!aiMessage.content) {
+        aiMessage.content = ''
+      }
+      if (!aiMessage.displayContent) {
+        aiMessage.displayContent = ''
+      }
+      break
+
+    case 'answer_content_delta': {
+      const delta = data.chunk || ''
+      if (!delta) break
+      aiMessage.content = `${aiMessage.content || ''}${delta}`
+      const processedReply = processAIResponse(aiMessage.content)
+      aiMessage.displayContent = renderMarkdownContent(processedReply)
+      aiMessage.isTyping = true
+      break
+    }
+
+    case 'answer_content_done':
+      aiMessage.answerStreaming = false
+      aiMessage.isTyping = false
+      break
+
     case 'online_answer': {
+      aiMessage.answerStreaming = false
+      aiMessage.thinkingStreaming = false
       if (shouldHideStatsForStreamingAnswer(aiMessage)) {
         aiMessage.showStats = false
       } else {
@@ -3313,6 +3430,8 @@ const handleReportGeneratorSubmit = async (data) => {
     displayContent: '',
     thinkingContent: '',
     showThinking: true,
+    thinkingStreaming: false,
+    answerStreaming: false,
     timestamp: new Date().toISOString(),
     // 新增:状态管理
     currentStatus: 'querying_kb', // 当前状态
@@ -4914,17 +5033,33 @@ onActivated(async () => {
   }
   
   // 问题总结卡片
+  .brief-answer-card,
   .question-summary {
     margin-bottom: 12px;
-    padding: 12px 16px;
-    background: white;
-    border-radius: 12px;
-    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+    padding: 0;
+    background: transparent;
+    border-radius: 0;
+    box-shadow: none;
+    border: none;
+  }
+
+  .response-section-title {
+    margin-bottom: 8px;
+    color: #111827;
+    font-size: 13px;
+    font-weight: 600;
+  }
+
+  .question-summary {
     font-size: 14px;
     line-height: 1.8;
     color: #606266;
   }
 
+  .result-summary-card {
+    background: transparent;
+  }
+
   .thinking-panel {
     margin-bottom: 12px;
     background: #f8fafc;
@@ -4989,6 +5124,14 @@ onActivated(async () => {
     line-height: 1.7;
     border-top: 1px solid #eef2f7;
   }
+
+  .thinking-panel-scrollable {
+    max-height: 220px;
+    overflow-y: auto;
+    overscroll-behavior: contain;
+    -webkit-overflow-scrolling: touch;
+    scrollbar-gutter: stable;
+  }
   
   .reports-list {
     margin-top: 12px;