Pārlūkot izejas kodu

增加历史问答页面

xueyiming 1 nedēļu atpakaļ
vecāks
revīzija
73e868c8fa

+ 2 - 1
package-lock.json

@@ -11,6 +11,7 @@
         "@element-plus/icons-vue": "^2.3.2",
         "axios": "^1.12.0",
         "core-js": "^3.8.3",
+        "dayjs": "^1.11.18",
         "element-plus": "^2.11.2",
         "marked": "^16.3.0",
         "vue": "^3.2.13",
@@ -5288,7 +5289,7 @@
     },
     "node_modules/dayjs": {
       "version": "1.11.18",
-      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
+      "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.18.tgz",
       "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
       "license": "MIT"
     },

+ 1 - 0
package.json

@@ -11,6 +11,7 @@
     "@element-plus/icons-vue": "^2.3.2",
     "axios": "^1.12.0",
     "core-js": "^3.8.3",
+    "dayjs": "^1.11.18",
     "element-plus": "^2.11.2",
     "marked": "^16.3.0",
     "vue": "^3.2.13",

+ 3 - 0
src/components/AppNavbar.vue

@@ -50,6 +50,9 @@ export default defineComponent({
       if (route.path.startsWith('/knowledge/content')) {
         return '/'; // 知识库页面时,设置为高亮 "知识库"
       }
+      if (route.path.startsWith('/qanda/history')) {
+        return '/qanda'; // 知识库页面时,设置为高亮 "知识库"
+      }
       return route.path;
     });
 

+ 2 - 1
src/config.ts

@@ -1 +1,2 @@
-export const API_BASE_URL = "http://61.48.133.26:8001/api";
+// export const API_BASE_URL = "http://61.48.133.26:8001/api";
+export const API_BASE_URL = "http://127.0.0.1:8001/api";

+ 6 - 0
src/router/index.ts

@@ -3,6 +3,7 @@ import KnowledgeBase from "@/views/KnowledgeBase.vue";
 import KnowledgeContent from "@/views/KnowledgeContent.vue";
 import SearchPage from "@/views/SearchPage.vue";
 import QAndA from "@/views/QAndA.vue";
+import QAndAHistory from "@/views/QAndAHistory.vue";
 
 
 
@@ -27,6 +28,11 @@ const routes: Array<RouteRecordRaw> = [
     name: 'QAndA',
     component: QAndA,
   },
+  {
+    path: '/qanda/history',
+    name: 'QAndAHistory',
+    component: QAndAHistory,
+  },
 ]
 
 const router = createRouter({

+ 20 - 17
src/views/KnowledgeContent.vue

@@ -4,17 +4,6 @@
     <div class="app-header">
       <div class="header-content">
         <div class="header-main">
-          <!-- 返回按钮 -->
-          <div class="header-back">
-            <el-button
-                class="back-btn"
-                @click="goBack"
-                text
-            >
-              <span class="back-icon">←</span>
-              <span style="color: white">返回知识库</span>
-            </el-button>
-          </div>
           <div class="header-titles">
             <h1 class="app-title">知识库内容管理</h1>
             <p class="app-subtitle">管理知识库中的文档和内容</p>
@@ -30,6 +19,17 @@
             <span class="stat-label">本页</span>
           </div>
         </div>
+
+        <div class="header-back">
+          <el-button
+              class="back-btn"
+              @click="goBack"
+              text
+          >
+            <span class="btn-icon">⬅️</span>
+            <span style="color: white;margin-left: 5px">返回知识库</span>
+          </el-button>
+        </div>
       </div>
     </div>
 
@@ -603,18 +603,21 @@ export default defineComponent({
 }
 
 .back-btn {
+  background: rgba(255, 255, 255, 0.2);
+  border: 1px solid rgba(255, 255, 255, 0.3);
   color: white;
-  font-weight: 500;
-  padding: 8px 16px;
   border-radius: 12px;
+  padding: 10px 20px;
+  font-weight: 600;
   transition: all 0.3s ease;
-  background: rgba(255, 255, 255, 0.1);
-  border: 1px solid rgba(255, 255, 255, 0.2);
+  backdrop-filter: blur(10px);
+  margin-left: 50px;
 }
 
 .back-btn:hover {
-  background: rgb(255, 255, 255);
-  transform: translateX(-2px);
+  background: rgba(255, 255, 255, 0.3);
+  transform: translateY(-2px);
+  box-shadow: 0 8px 25px rgba(255, 255, 255, 0.2);
 }
 
 .back-icon {

+ 38 - 0
src/views/QAndA.vue

@@ -20,6 +20,16 @@
             <span class="stat-label">已选择</span>
           </div>
         </div>
+        <!-- 在 header-content 内的 header-stats 后面添加 -->
+        <div class="header-actions">
+          <el-button
+              class="history-btn"
+              @click="$router.push('/qanda/history')"
+          >
+            <span class="btn-icon">📚</span>
+            查看历史问题
+          </el-button>
+        </div>
       </div>
     </div>
 
@@ -1808,6 +1818,34 @@ onMounted(() => {
   box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3);
 }
 
+/* 在样式部分添加 */
+.header-actions {
+  display: flex;
+  align-items: center;
+}
+
+.history-btn {
+  background: rgba(255, 255, 255, 0.2);
+  border: 1px solid rgba(255, 255, 255, 0.3);
+  color: white;
+  border-radius: 12px;
+  padding: 10px 20px;
+  font-weight: 600;
+  transition: all 0.3s ease;
+  backdrop-filter: blur(10px);
+  margin-left: 50px;
+}
+
+.history-btn:hover {
+  background: rgba(255, 255, 255, 0.3);
+  transform: translateY(-2px);
+  box-shadow: 0 8px 25px rgba(255, 255, 255, 0.2);
+}
+
+.btn-icon {
+  margin-right: 6px;
+}
+
 /* 响应式设计 */
 @media (max-width: 1200px) {
   .main-layout {

+ 1378 - 0
src/views/QAndAHistory.vue

@@ -0,0 +1,1378 @@
+<template>
+  <div class="history-container">
+    <!-- 顶部导航 -->
+    <div class="history-header">
+      <div class="header-content">
+        <div class="header-main">
+          <h1 class="history-title">📚 历史问题记录</h1>
+          <p class="history-subtitle">查看之前的提问和回答记录</p>
+        </div>
+        <div class="header-actions">
+          <el-button
+              class="back-btn"
+              @click="$router.push('/qanda')"
+          >
+            <span class="btn-icon">⬅️</span>
+            返回问答
+          </el-button>
+        </div>
+      </div>
+    </div>
+
+    <div class="history-content">
+      <!-- 统计卡片 -->
+      <div class="stats-cards">
+        <div class="stat-card">
+          <div class="stat-icon">📝</div>
+          <div class="stat-info">
+            <div class="stat-number">{{ totalCount }}</div>
+            <div class="stat-label">总问题数</div>
+          </div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-icon">📄</div>
+          <div class="stat-info">
+            <div class="stat-number">{{ totalPages }}</div>
+            <div class="stat-label">总页数</div>
+          </div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-icon">🕒</div>
+          <div class="stat-info">
+            <div class="stat-number">{{ pageSize }}</div>
+            <div class="stat-label">每页数量</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 搜索和筛选 -->
+<!--      <div class="filter-section">-->
+<!--        <div class="search-input">-->
+<!--          <el-input-->
+<!--              v-model="searchQuery"-->
+<!--              placeholder="搜索问题内容..."-->
+<!--              size="large"-->
+<!--              clearable-->
+<!--              @input="handleSearch"-->
+<!--          >-->
+<!--            <template #prefix>-->
+<!--              <span class="search-icon">🔍</span>-->
+<!--            </template>-->
+<!--          </el-input>-->
+<!--        </div>-->
+<!--        <div class="filter-actions">-->
+<!--          <el-button-->
+<!--              class="refresh-btn"-->
+<!--              @click="refreshData"-->
+<!--              :loading="loading"-->
+<!--          >-->
+<!--            <span class="btn-icon">🔄</span>-->
+<!--            刷新-->
+<!--          </el-button>-->
+<!--        </div>-->
+<!--      </div>-->
+
+      <!-- 加载状态 -->
+      <div v-if="loading" class="loading-section">
+        <div class="loading-content">
+          <div class="loading-spinner"></div>
+          <p>加载历史记录中...</p>
+        </div>
+      </div>
+
+      <!-- 历史记录列表 -->
+      <div v-else class="history-list">
+        <div v-if="historyList.length === 0" class="empty-state">
+          <div class="empty-content">
+            <div class="empty-icon">📚</div>
+            <h3>暂无历史记录</h3>
+            <p>您还没有任何提问记录,快去首页提问吧!</p>
+            <el-button
+                type="primary"
+                @click="$router.push('/')"
+                class="go-home-btn"
+            >
+              <span class="btn-icon">🚀</span>
+              开始提问
+            </el-button>
+          </div>
+        </div>
+
+        <div v-else class="history-items">
+          <div
+              v-for="item in filteredHistory"
+              :key="item.create_time"
+              class="history-item"
+          >
+            <div class="item-header">
+              <div class="question-content">
+                <h3 class="question-title">{{ item.query }}</h3>
+                <div class="question-meta">
+                  <span class="meta-item">
+                    <span class="meta-icon">🕒</span>
+                    {{ formatTime(item.create_time) }}
+                  </span>
+                </div>
+              </div>
+              <div class="item-actions">
+                <el-button
+                    class="detail-btn"
+                    @click="toggleItemDetail(item)"
+                    :class="{ active: expandedItems.includes(item.create_time) }"
+                >
+                  <span class="btn-icon">
+                    {{ expandedItems.includes(item.create_time) ? '📖' : '📄' }}
+                  </span>
+                  {{ expandedItems.includes(item.create_time) ? '收起详情' : '查看详情' }}
+                </el-button>
+              </div>
+            </div>
+
+            <!-- 展开的详情 -->
+            <div
+                v-if="expandedItems.includes(item.create_time)"
+                class="item-detail"
+            >
+              <!-- 最终结果 -->
+              <div class="detail-section" v-if="item.final_result">
+                <div class="section-header">
+                  <div class="section-icon">💡</div>
+                  <h4>最终结果</h4>
+                </div>
+                <div class="section-content">
+                  <div v-html="parseMarkdown(item.final_result)" class="content-text"></div>
+                </div>
+              </div>
+
+              <!-- RAG搜索结果 -->
+              <div class="detail-section" v-if="item.chat_res">
+                <div class="section-header">
+                  <div class="section-icon">🔍</div>
+                  <h4>RAG搜索结果</h4>
+                </div>
+                <div class="section-content">
+                  <div v-html="parseMarkdown(item.chat_res)" class="content-text"></div>
+                </div>
+              </div>
+
+              <!-- LLM搜索结果 -->
+              <div class="detail-section" v-if="item.ai_answer">
+                <div class="section-header">
+                  <div class="section-icon">🤖</div>
+                  <h4>LLM搜索结果</h4>
+                </div>
+                <div class="section-content">
+                  <div v-html="parseMarkdown(item.ai_answer)" class="content-text"></div>
+                </div>
+              </div>
+
+              <!-- 搜索结果统计 -->
+              <div class="detail-section" v-if="item.search_res">
+                <div class="section-header">
+                  <div class="section-icon">📚</div>
+                  <h4>参考内容</h4>
+                  <span class="result-count">
+                    {{ getSearchResults(item.search_res).length }} 条相关内容
+                  </span>
+                </div>
+                <div class="search-results-preview">
+                  <div
+                      v-for="(result, index) in getDisplayedResults(item)"
+                      :key="index"
+                      class="result-preview"
+                      @click="handleResultDetails(result)"
+                  >
+                    <div class="result-header">
+                      <span class="result-index">#{{ index + 1 }}</span>
+                      <span class="result-score" :class="getScoreClass(result.score)">
+                        {{ (result.score * 100).toFixed(1) }}%
+                      </span>
+                    </div>
+                    <p class="result-content">{{ result.contentSummary || result.content.substring(0, 120) }}...</p>
+                    <span class="result-source">{{ result.datasetName }}</span>
+                    <div class="result-footer">
+                      <span class="view-details">
+                        <span class="view-icon">🔍</span>
+                        查看详情
+                      </span>
+                    </div>
+                  </div>
+
+                  <!-- 展开/收起按钮 -->
+                  <div
+                      v-if="getSearchResults(item.search_res).length > 3"
+                      class="more-results"
+                  >
+                    <el-button
+                        link
+                        type="primary"
+                        @click="toggleResultsExpansion(item)"
+                        class="expand-btn"
+                    >
+                      <span class="btn-icon">
+                        {{ item.isResultsExpanded ? '📂' : '📁' }}
+                      </span>
+                      {{ item.isResultsExpanded ? '收起' : `展开剩余 ${getSearchResults(item.search_res).length - 3} 条内容` }}
+                    </el-button>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 分页 -->
+        <div v-if="historyList.length > 0" class="pagination-section">
+          <el-pagination
+              v-model:current-page="currentPage"
+              v-model:page-size="pageSize"
+              :total="totalCount"
+              :page-sizes="[10, 20, 50, 100]"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+              class="history-pagination"
+          />
+        </div>
+      </div>
+    </div>
+
+    <!-- 搜索结果详情弹窗 -->
+    <el-dialog
+        v-model="resultDialogVisible"
+        width="85%"
+        class="content-dialog"
+        :close-on-click-modal="false"
+    >
+      <template #header>
+        <div class="dialog-header">
+          <div class="dialog-title">
+            <div class="title-icon">📄</div>
+            <div class="title-content">
+              <h3>{{ selectedResult.contentSummary }}</h3>
+              <div class="title-meta">
+                <span class="meta-badge knowledge-badge">
+                  <span class="badge-icon">📚</span>
+                  {{ selectedResult.datasetName }}
+                </span>
+                <span class="meta-badge score-badge" :class="getScoreClass(selectedResult.score)">
+                  <span class="badge-icon">🎯</span>
+                  相关度: {{ (selectedResult.score * 100).toFixed(1) }}%
+                </span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </template>
+
+      <div class="dialog-content">
+        <div class="content-tabs">
+          <div
+              class="tab-item"
+              :class="{ active: activeTab === 'summary' }"
+              @click="activeTab = 'summary'"
+          >
+            <span class="tab-icon">📝</span>
+            摘要内容
+          </div>
+          <div
+              class="tab-item"
+              :class="{ active: activeTab === 'original' }"
+              @click="activeTab = 'original'"
+          >
+            <span class="tab-icon">📖</span>
+            完整原文
+          </div>
+        </div>
+
+        <div class="tab-content">
+          <div v-show="activeTab === 'summary'" class="content-section summary-section">
+            <div class="section-header">
+              <h4>内容摘要</h4>
+              <el-button
+                  text
+                  class="copy-btn"
+                  @click="copyToClipboard(selectedResult.content)"
+              >
+                📋 复制内容
+              </el-button>
+            </div>
+            <div class="summary-content">
+              {{ selectedResult.content }}
+            </div>
+          </div>
+
+          <div v-show="activeTab === 'original'" class="content-section original-section">
+            <div class="section-header">
+              <h4>完整原文</h4>
+              <el-button
+                  text
+                  class="copy-btn"
+                  @click="copyToClipboard(originalContent)"
+              >
+                📋 复制原文
+              </el-button>
+            </div>
+            <div class="original-content">
+              <pre>{{ originalContent }}</pre>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <div class="footer-actions">
+            <el-button
+                class="action-btn secondary"
+                @click="resultDialogVisible = false"
+            >
+              取消
+            </el-button>
+            <el-button
+                type="primary"
+                class="action-btn primary"
+                @click="resultDialogVisible = false"
+            >
+              关闭
+            </el-button>
+          </div>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, computed } from 'vue';
+import { ElMessage } from 'element-plus';
+import { marked } from 'marked';
+import { API_BASE_URL } from "@/config";
+import dayjs from 'dayjs';
+
+
+// 响应式数据
+const historyList = ref([]);
+const loading = ref(false);
+const currentPage = ref(1);
+const pageSize = ref(10);
+const totalCount = ref(0);
+const totalPages = ref(0);
+const searchQuery = ref('');
+const expandedItems = ref([]);
+const resultDialogVisible = ref(false);
+const selectedResult = ref({});
+const originalContent = ref('');
+const activeTab = ref('summary');
+
+// 计算属性
+const filteredHistory = computed(() => {
+  if (!searchQuery.value) {
+    return historyList.value;
+  }
+  const query = searchQuery.value.toLowerCase();
+  return historyList.value.filter(item =>
+      item.query.toLowerCase().includes(query) ||
+      (item.final_result && item.final_result.toLowerCase().includes(query)) ||
+      (item.chat_res && item.chat_res.toLowerCase().includes(query)) ||
+      (item.ai_answer && item.ai_answer.toLowerCase().includes(query))
+  );
+});
+
+// 方法
+const parseMarkdown = (content) => {
+  if (!content) return '';
+  const markdownSymbols = /[#*+\-`>!]/;
+  const isMarkdown = markdownSymbols.test(content);
+  return isMarkdown ? marked(content) : content;
+};
+
+const formatTime = (timeString) => {
+  try {
+    // 处理 GMT 时间格式 "Tue, 30 Sep 2025 14:02:21 GMT"
+    if (timeString.includes('GMT')) {
+      // 使用 UTC 方法获取时间组件,避免时区转换
+      const date = new Date(timeString);
+
+      if (!isNaN(date.getTime())) {
+        // 使用 UTC 相关的方法获取时间组件
+        const year = date.getUTCFullYear();
+        const month = String(date.getUTCMonth() + 1).padStart(2, '0');
+        const day = String(date.getUTCDate()).padStart(2, '0');
+        const hours = String(date.getUTCHours()).padStart(2, '0');
+        const minutes = String(date.getUTCMinutes()).padStart(2, '0');
+        const seconds = String(date.getUTCSeconds()).padStart(2, '0');
+
+        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+      }
+    }
+
+    // 对于其他格式,使用原来的逻辑
+    const date = new Date(timeString);
+    if (!isNaN(date.getTime())) {
+      return date.toLocaleString('zh-CN', {
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit',
+        hour: '2-digit',
+        minute: '2-digit',
+        second: '2-digit'
+      });
+    }
+
+    console.warn('无法解析的时间格式:', timeString);
+    return timeString;
+
+  } catch (error) {
+    console.error('时间格式化错误:', error);
+    return timeString;
+  }
+};
+
+const getScoreClass = (score) => {
+  if (score >= 0.8) return 'high';
+  if (score >= 0.6) return 'medium';
+  return 'low';
+};
+
+const getSearchResults = (searchRes) => {
+  try {
+    return JSON.parse(searchRes) || [];
+  } catch (error) {
+    console.error('解析search_res失败:', error);
+    return [];
+  }
+};
+
+const getDisplayedResults = (item) => {
+  const results = getSearchResults(item.search_res);
+  if (item.isResultsExpanded) {
+    return results;
+  }
+  return results.slice(0, 3);
+};
+
+const processHistoryItem = (item) => {
+  // 确保所有字段都有值,如果没有则从其他字段获取
+  return {
+    ...item,
+    final_result: item.final_result || item.chat_res || item.ai_answer || '',
+    chat_res: item.chat_res || '',
+    ai_answer: item.ai_answer || '',
+    // 添加展开状态
+    isResultsExpanded: false
+  };
+};
+
+const toggleItemDetail = (item) => {
+  const index = expandedItems.value.indexOf(item.create_time);
+  if (index === -1) {
+    expandedItems.value.push(item.create_time);
+  } else {
+    expandedItems.value.splice(index, 1);
+  }
+};
+
+const toggleResultsExpansion = (item) => {
+  item.isResultsExpanded = !item.isResultsExpanded;
+};
+
+const handleResultDetails = async (result) => {
+  selectedResult.value = result;
+  resultDialogVisible.value = true;
+  activeTab.value = 'summary';
+
+  try {
+    const response = await fetch(`${API_BASE_URL}/content/get?docId=${result.docId}`);
+    const data = await response.json();
+    if (data.status_code === 200) {
+      originalContent.value = data.data.text;
+    } else {
+      ElMessage.error('获取原文内容失败');
+    }
+  } catch (error) {
+    ElMessage.error('请求原文内容失败');
+  }
+};
+
+const copyToClipboard = async (text) => {
+  try {
+    await navigator.clipboard.writeText(text);
+    ElMessage.success('已复制到剪贴板');
+  } catch (err) {
+    ElMessage.error('复制失败');
+  }
+};
+
+const handleSearch = () => {
+  currentPage.value = 1;
+};
+
+const handleSizeChange = (newSize) => {
+  pageSize.value = newSize;
+  currentPage.value = 1;
+  fetchHistory();
+};
+
+const handleCurrentChange = (newPage) => {
+  currentPage.value = newPage;
+  fetchHistory();
+};
+
+const refreshData = () => {
+  currentPage.value = 1;
+  fetchHistory();
+};
+
+const fetchHistory = async () => {
+  loading.value = true;
+  try {
+    const response = await fetch(
+        `${API_BASE_URL}/chat/history?page=${currentPage.value}&pageSize=${pageSize.value}`
+    );
+    const data = await response.json();
+
+    if (data.status_code === 200) {
+      // 处理每个历史记录项,确保字段正确
+      historyList.value = (data.data.entities || []).map(processHistoryItem);
+      totalCount.value = data.data.total_count || 0;
+      totalPages.value = data.data.total_pages || 0;
+    } else {
+      ElMessage.error('获取历史记录失败');
+    }
+  } catch (error) {
+    ElMessage.error('请求失败,请稍后重试');
+    console.error('Fetch history error:', error);
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 生命周期
+onMounted(() => {
+  fetchHistory();
+});
+</script>
+
+<style scoped>
+/* 保持原有的所有样式不变 */
+.history-container {
+  min-height: 100vh;
+  background: linear-gradient(135deg, rgba(102, 126, 234, 0.81) 0%, rgba(118, 75, 162, 0.77) 100%);
+  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+
+.history-header {
+  background: rgba(255, 255, 255, 0.1);
+  backdrop-filter: blur(20px);
+  padding: 20px 0;
+  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+.header-content {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 0 24px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.header-main {
+  flex: 1;
+}
+
+.history-title {
+  color: white;
+  font-size: 2.25rem;
+  font-weight: 800;
+  margin: 0;
+  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
+  background: linear-gradient(135deg, #fff 0%, #e2e8f0 100%);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+.history-subtitle {
+  color: rgba(255, 255, 255, 0.8);
+  font-size: 1rem;
+  margin: 8px 0 0 0;
+  font-weight: 500;
+}
+
+.header-actions {
+  display: flex;
+  gap: 12px;
+}
+
+.back-btn {
+  background: rgba(255, 255, 255, 0.2);
+  border: 1px solid rgba(255, 255, 255, 0.3);
+  color: white;
+  border-radius: 12px;
+  padding: 10px 20px;
+  font-weight: 600;
+  transition: all 0.3s ease;
+  backdrop-filter: blur(10px);
+}
+
+.back-btn:hover {
+  background: rgba(255, 255, 255, 0.3);
+  transform: translateY(-2px);
+  box-shadow: 0 8px 25px rgba(255, 255, 255, 0.2);
+}
+
+.history-content {
+  max-width: 1200px;
+  margin: 0 auto;
+  padding: 24px;
+}
+
+.stats-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 20px;
+  margin-bottom: 24px;
+}
+
+.stat-card {
+  background: white;
+  border-radius: 16px;
+  padding: 24px;
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+  transition: transform 0.3s ease;
+}
+
+.stat-card:hover {
+  transform: translateY(-4px);
+}
+
+.stat-icon {
+  font-size: 2.5rem;
+  width: 60px;
+  height: 60px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 12px;
+  color: white;
+}
+
+.stat-info {
+  flex: 1;
+}
+
+.stat-number {
+  font-size: 2rem;
+  font-weight: 800;
+  color: #2d3748;
+  line-height: 1;
+  margin-bottom: 4px;
+}
+
+.stat-label {
+  color: #718096;
+  font-size: 0.9rem;
+  font-weight: 600;
+}
+
+.filter-section {
+  background: white;
+  border-radius: 16px;
+  padding: 20px;
+  margin-bottom: 24px;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+  display: flex;
+  gap: 16px;
+  align-items: center;
+}
+
+.search-input {
+  flex: 1;
+}
+
+.filter-actions {
+  display: flex;
+  gap: 12px;
+}
+
+.refresh-btn {
+  border-radius: 12px;
+  padding: 10px 20px;
+  font-weight: 600;
+}
+
+.loading-section {
+  background: white;
+  border-radius: 16px;
+  padding: 60px 32px;
+  text-align: center;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+}
+
+.loading-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16px;
+}
+
+.loading-spinner {
+  width: 40px;
+  height: 40px;
+  border: 4px solid #e2e8f0;
+  border-top: 4px solid #4299e1;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+.history-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.empty-state {
+  background: white;
+  border-radius: 24px;
+  padding: 80px 32px;
+  text-align: center;
+  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
+}
+
+.empty-content {
+  max-width: 400px;
+  margin: 0 auto;
+}
+
+.empty-icon {
+  font-size: 4rem;
+  margin-bottom: 20px;
+}
+
+.empty-state h3 {
+  margin: 0 0 12px 0;
+  color: #2d3748;
+  font-size: 1.5rem;
+  font-weight: 700;
+}
+
+.empty-state p {
+  margin: 0 0 24px 0;
+  color: #718096;
+  font-size: 1rem;
+  line-height: 1.5;
+}
+
+.go-home-btn {
+  border-radius: 12px;
+  padding: 12px 24px;
+  font-weight: 600;
+}
+
+.history-items {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+
+.history-item {
+  background: white;
+  border-radius: 16px;
+  overflow: hidden;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+  transition: all 0.3s ease;
+  border: 1px solid #e2e8f0;
+}
+
+.history-item:hover {
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
+  transform: translateY(-2px);
+}
+
+.item-header {
+  padding: 24px;
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  gap: 16px;
+  cursor: pointer;
+}
+
+.question-content {
+  flex: 1;
+  min-width: 0;
+}
+
+.question-title {
+  margin: 0 0 12px 0;
+  color: #2d3748;
+  font-size: 1.1rem;
+  font-weight: 600;
+  line-height: 1.4;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.question-meta {
+  display: flex;
+  gap: 16px;
+  align-items: center;
+}
+
+.meta-item {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  color: #718096;
+  font-size: 0.85rem;
+}
+
+.meta-icon {
+  font-size: 0.8rem;
+}
+
+.item-actions {
+  flex-shrink: 0;
+}
+
+.detail-btn {
+  border-radius: 12px;
+  padding: 8px 16px;
+  font-weight: 600;
+  transition: all 0.3s ease;
+}
+
+.detail-btn.active {
+  background: #4299e1;
+  color: white;
+}
+
+.item-detail {
+  border-top: 1px solid #e2e8f0;
+  padding: 0;
+}
+
+.detail-section {
+  padding: 24px;
+  border-bottom: 1px solid #f7fafc;
+}
+
+.detail-section:last-child {
+  border-bottom: none;
+}
+
+.section-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 16px;
+}
+
+.section-icon {
+  font-size: 1.5rem;
+  width: 40px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f7fafc;
+  border-radius: 10px;
+}
+
+.section-header h4 {
+  margin: 0;
+  color: #2d3748;
+  font-size: 1.1rem;
+  font-weight: 600;
+  flex: 1;
+}
+
+.result-count {
+  background: #4299e1;
+  color: white;
+  padding: 4px 10px;
+  border-radius: 12px;
+  font-size: 0.8rem;
+  font-weight: 600;
+}
+
+.section-content {
+  color: #4a5568;
+  line-height: 1.6;
+}
+
+.content-text {
+  line-height: 1.7;
+}
+
+.content-text :deep(h1),
+.content-text :deep(h2),
+.content-text :deep(h3) {
+  color: #2d3748;
+  margin-top: 1.5em;
+  margin-bottom: 0.5em;
+}
+
+.content-text :deep(p) {
+  margin-bottom: 1em;
+}
+
+.content-text :deep(ul),
+.content-text :deep(ol) {
+  margin: 1em 0;
+  padding-left: 1.5em;
+}
+
+.content-text :deep(li) {
+  margin-bottom: 0.5em;
+}
+
+.content-text :deep(code) {
+  background: #f7fafc;
+  padding: 2px 6px;
+  border-radius: 4px;
+  font-family: 'Monaco', 'Consolas', monospace;
+  color: #e53e3e;
+  font-size: 0.9em;
+}
+
+.content-text :deep(pre) {
+  background: #2d3748;
+  color: #e2e8f0;
+  padding: 16px;
+  border-radius: 8px;
+  overflow-x: auto;
+  margin: 1em 0;
+  font-size: 0.9em;
+}
+
+.search-results-preview {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.result-preview {
+  background: #f7fafc;
+  border-radius: 12px;
+  padding: 16px;
+  border: 1px solid #e2e8f0;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  position: relative;
+}
+
+.result-preview:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+  border-color: #4299e1;
+}
+
+.result-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.result-index {
+  background: #4299e1;
+  color: white;
+  padding: 2px 8px;
+  border-radius: 8px;
+  font-size: 0.8rem;
+  font-weight: 700;
+}
+
+.result-score {
+  padding: 2px 8px;
+  border-radius: 8px;
+  font-size: 0.8rem;
+  font-weight: 700;
+  color: white;
+}
+
+.result-score.high {
+  background: #48bb78;
+}
+
+.result-score.medium {
+  background: #ed8936;
+}
+
+.result-score.low {
+  background: #e53e3e;
+}
+
+.result-content {
+  margin: 0 0 8px 0;
+  color: #4a5568;
+  font-size: 0.9rem;
+  line-height: 1.5;
+}
+
+.result-source {
+  background: #e2e8f0;
+  color: #4a5568;
+  padding: 2px 8px;
+  border-radius: 8px;
+  font-size: 0.8rem;
+  margin-bottom: 8px;
+  display: inline-block;
+}
+
+.result-footer {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 8px;
+}
+
+.view-details {
+  color: #4299e1;
+  font-weight: 600;
+  font-size: 0.9rem;
+  transition: color 0.3s ease;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.view-icon {
+  font-size: 0.8rem;
+}
+
+.result-preview:hover .view-details {
+  color: #2b6cb0;
+}
+
+.more-results {
+  text-align: center;
+  padding: 12px;
+}
+
+.expand-btn {
+  font-weight: 600;
+  color: #4299e1;
+}
+
+.expand-btn:hover {
+  color: #2b6cb0;
+}
+
+.pagination-section {
+  background: white;
+  border-radius: 16px;
+  padding: 20px;
+  margin-top: 24px;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+  display: flex;
+  justify-content: center;
+}
+
+.history-pagination {
+  margin: 0;
+}
+
+.btn-icon {
+  margin-right: 6px;
+}
+
+/* 弹窗样式 - 从主页面复制并调整 */
+.content-dialog :deep(.el-dialog) {
+  border-radius: 24px;
+  overflow: hidden;
+  box-shadow: 0 25px 50px rgba(0, 0, 0, 0.2);
+}
+
+.content-dialog :deep(.el-dialog__header) {
+  padding: 0;
+  margin: 0;
+}
+
+.content-dialog :deep(.el-dialog__headerbtn) {
+  top: 24px;
+  right: 24px;
+  width: 32px;
+  height: 32px;
+  border-radius: 8px;
+  background: #f7fafc;
+}
+
+.content-dialog :deep(.el-dialog__headerbtn:hover) {
+  background: #e2e8f0;
+}
+
+.dialog-header {
+  padding: 0;
+}
+
+.dialog-title {
+  display: flex;
+  align-items: flex-start;
+  gap: 16px;
+  padding: 32px 32px 0;
+}
+
+.title-icon {
+  font-size: 2.5rem;
+  width: 60px;
+  height: 60px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 16px;
+  color: white;
+  flex-shrink: 0;
+}
+
+.title-content {
+  flex: 1;
+  min-width: 0;
+}
+
+.title-content h3 {
+  margin: 0 0 12px 0;
+  color: #2d3748;
+  font-size: 1.5rem;
+  font-weight: 700;
+  line-height: 1.4;
+}
+
+.title-meta {
+  display: flex;
+  gap: 12px;
+  flex-wrap: wrap;
+}
+
+.meta-badge {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 6px 12px;
+  border-radius: 12px;
+  font-size: 0.85rem;
+  font-weight: 500;
+}
+
+.knowledge-badge {
+  background: #ebf8ff;
+  color: #2b6cb0;
+  border: 1px solid #bee3f8;
+}
+
+.score-badge.high {
+  background: #f0fff4;
+  color: #2f855a;
+  border: 1px solid #c6f6d5;
+}
+
+.score-badge.medium {
+  background: #fffaf0;
+  color: #c05621;
+  border: 1px solid #feebc8;
+}
+
+.score-badge.low {
+  background: #fff5f5;
+  color: #c53030;
+  border: 1px solid #fed7d7;
+}
+
+.badge-icon {
+  font-size: 0.8rem;
+}
+
+.dialog-content {
+  padding: 0;
+}
+
+.content-tabs {
+  display: flex;
+  padding: 0 32px;
+  border-bottom: 1px solid #e2e8f0;
+  margin-top: 24px;
+}
+
+.tab-item {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 20px;
+  cursor: pointer;
+  border-bottom: 2px solid transparent;
+  color: #718096;
+  font-weight: 500;
+  transition: all 0.3s ease;
+}
+
+.tab-item:hover {
+  color: #4299e1;
+}
+
+.tab-item.active {
+  color: #4299e1;
+  border-bottom-color: #4299e1;
+}
+
+.tab-icon {
+  font-size: 1.1rem;
+}
+
+.tab-content {
+  padding: 0 32px 32px;
+}
+
+.content-section {
+  margin-top: 24px;
+}
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.section-header h4 {
+  margin: 0;
+  color: #2d3748;
+  font-size: 1.2rem;
+  font-weight: 600;
+}
+
+.copy-btn {
+  font-size: 0.9rem;
+  padding: 6px 12px;
+  border-radius: 8px;
+}
+
+.summary-content {
+  background: #f7fafc;
+  padding: 20px;
+  border-radius: 12px;
+  color: #4a5568;
+  line-height: 1.6;
+  font-size: 0.95rem;
+  border: 1px solid #e2e8f0;
+}
+
+.original-content {
+  background: #f7fafc;
+  border-radius: 12px;
+  border: 1px solid #e2e8f0;
+  max-height: 500px;
+  overflow-y: auto;
+}
+
+.original-content pre {
+  margin: 0;
+  padding: 20px;
+  color: #4a5568;
+  line-height: 1.6;
+  font-size: 0.9rem;
+  white-space: pre-wrap;
+  word-wrap: break-word;
+}
+
+.dialog-footer {
+  padding: 0 32px 24px;
+}
+
+.footer-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+.action-btn {
+  border-radius: 12px;
+  padding: 10px 24px;
+  font-weight: 500;
+  transition: all 0.3s ease;
+}
+
+.action-btn.secondary {
+  border: 1px solid #e2e8f0;
+  background: white;
+  color: #718096;
+}
+
+.action-btn.secondary:hover {
+  background: #f7fafc;
+  border-color: #cbd5e0;
+}
+
+.action-btn.primary {
+  background: #4299e1;
+  border: 1px solid #4299e1;
+  color: white;
+}
+
+.action-btn.primary:hover {
+  background: #3182ce;
+  border-color: #3182ce;
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3);
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .header-content {
+    flex-direction: column;
+    gap: 16px;
+    text-align: center;
+  }
+
+  .history-title {
+    font-size: 1.75rem;
+  }
+
+  .history-content {
+    padding: 16px;
+  }
+
+  .stats-cards {
+    grid-template-columns: 1fr;
+  }
+
+  .filter-section {
+    flex-direction: column;
+    align-items: stretch;
+  }
+
+  .item-header {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 12px;
+  }
+
+  .item-actions {
+    align-self: flex-end;
+  }
+
+  .section-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 8px;
+  }
+
+  .content-dialog :deep(.el-dialog) {
+    width: 95% !important;
+    margin: 20px auto;
+  }
+}
+</style>