瀏覽代碼

Merge branch 'master' into dev-xym-add-pdf

xueyiming 1 月之前
父節點
當前提交
c8cba2eb98
共有 9 個文件被更改,包括 435 次插入14 次删除
  1. 二進制
      public/favicon.ico
  2. 9 0
      public/index.html
  3. 0 1
      src/App.vue
  4. 18 7
      src/components/AppNavbar.vue
  5. 6 0
      src/config.ts
  6. 12 5
      src/router/index.ts
  7. 44 0
      src/types/DataCrawing.ts
  8. 334 0
      src/views/DataCrawling/index.vue
  9. 12 1
      vue.config.js

二進制
public/favicon.ico


+ 9 - 0
public/index.html

@@ -7,6 +7,15 @@
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <title><%= htmlWebpackPlugin.options.title %></title>
   </head>
+  <style>
+    body {
+      margin: 0;
+      padding: 0;
+    }
+    div {
+      box-sizing: border-box;
+    }
+  </style>
   <body>
     <noscript>
       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>

+ 0 - 1
src/App.vue

@@ -18,5 +18,4 @@ export default defineComponent({
 </script>
 
 <style scoped>
-
 </style>

+ 18 - 7
src/components/AppNavbar.vue

@@ -24,12 +24,16 @@
             问答
           </el-menu-item>
         </el-menu>
-<!--        <div class="navbar-actions">-->
-<!--          <div class="user-info">-->
-<!--            <span class="user-avatar">👤</span>-->
-<!--            <span class="user-name">用户</span>-->
-<!--          </div>-->
-<!--        </div>-->
+       <div class="navbar-actions">
+        <div class="user-info" @click="handleDataCrawling">
+           <span class="user-avatar"><el-icon><Coin /></el-icon></span>
+           <span class="user-name">数据爬取</span>
+         </div>
+         <!-- <div class="user-info">
+           <span class="user-avatar">👤</span>
+           <span class="user-name">用户</span>
+         </div> -->
+       </div>
       </div>
     </div>
   </div>
@@ -38,9 +42,10 @@
 <script lang="ts">
 import { defineComponent, computed } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
-
+import { Coin } from '@element-plus/icons-vue';
 export default defineComponent({
   name: 'AppNavbar',
+  components: { Coin },
   setup() {
     const route = useRoute();
     const router = useRouter();
@@ -60,9 +65,14 @@ export default defineComponent({
       router.push(index);
     };
 
+    const handleDataCrawling = () => {
+      router.push('/data-crawling');
+    };
+
     return {
       activeRoute,
       handleSelect,
+      handleDataCrawling,
     };
   },
 });
@@ -176,6 +186,7 @@ export default defineComponent({
   border: 1px solid rgba(255, 255, 255, 0.2);
   color: white;
   transition: all 0.3s ease;
+  cursor: pointer;
 }
 
 .user-info:hover {

+ 6 - 0
src/config.ts

@@ -1,2 +1,8 @@
 export const API_BASE_URL = "http://61.48.133.26:8001/api";
+
+// 开发环境使用代理,生产环境使用完整URL
+export const DATA_CRAWLING_BASE_URL = process.env.NODE_ENV === 'development' 
+  ? '/api/data-crawling' 
+  : 'http://8.219.186.16:8079';
+
 // export const API_BASE_URL = "http://127.0.0.1:8001/api";

+ 12 - 5
src/router/index.ts

@@ -1,9 +1,11 @@
 import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
-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";
+const KnowledgeBase = () => import("@/views/KnowledgeBase.vue");
+const KnowledgeContent = () => import("@/views/KnowledgeContent.vue");
+const SearchPage = () => import("@/views/SearchPage.vue");
+const QAndA = () => import("@/views/QAndA.vue");
+const QAndAHistory = () => import("@/views/QAndAHistory.vue");
+// 懒加载
+const DataCrawling = () => import("@/views/DataCrawling/index.vue");
 
 
 
@@ -33,6 +35,11 @@ const routes: Array<RouteRecordRaw> = [
     name: 'QAndAHistory',
     component: QAndAHistory,
   },
+  {
+    path: '/data-crawling',
+    name: 'DataCrawling',
+    component: DataCrawling,
+  },
 ]
 
 const router = createRouter({

+ 44 - 0
src/types/DataCrawing.ts

@@ -0,0 +1,44 @@
+export enum KnowledgeType {
+    '内容知识' = '内容知识',
+    '工具知识' = '工具知识',
+    '工具使用知识' = '工具使用知识',
+}
+
+export enum QueryTypeEnum {
+    'How' = 'How',
+    'What' = 'What',
+    'Pattern' = 'Pattern',
+}
+
+export enum NeedStoreEnum {
+    '不需要存储' = 0,
+    '需要存储' = 1,
+}
+
+export enum TaskStatusEnum {
+    '待处理' = 0,
+    '处理中' = 1,
+    '已完成' = 2,
+    '失败' = 3,
+}
+
+export interface QueryTask {
+    query: string;
+    knowledgeType: KnowledgeType;
+    task_id: string;
+    request_id?: string;
+    data?: string;
+    loading?: boolean;
+}
+
+export interface QuestionTask {
+    question: string;
+    task_id: string;
+    status: TaskStatusEnum;
+    statusText: string;
+    queries: QueryTask[];
+    knowledgeType: KnowledgeType;
+    query_type: QueryTypeEnum;
+    need_store: NeedStoreEnum;
+
+}

+ 334 - 0
src/views/DataCrawling/index.vue

@@ -0,0 +1,334 @@
+<template>
+  <div class="app-container">
+    <div class="pagination-container" v-loading.fullscreen.lock="loading">
+        <div class="tasks-container">
+            <div class="tasks">
+                <div 
+                    class="task-item" 
+                    v-for="task in questionTasks" 
+                    :key="task.task_id"
+                    :class="{ 'task-item-selected': selectedTaskId === task.task_id }"
+                    @click="handleTaskClick(task.task_id)"
+                >
+                    <div class="task-question">{{ task.question }}</div>
+                </div>
+            </div>
+            <div class="pagination">
+                <el-pagination
+                    @current-change="handleCurrentChange"
+                    :current-page="pageNumber"
+                    :page-size="pageSize"
+                    :total="total"
+                    layout="prev, pager, next"
+                />
+            </div>
+        </div>
+        <div class="task-detail-container">
+            <el-descriptions
+                class="margin-top"
+                title="任务详情"
+                :column="3"
+                border
+                v-if="selectedTask"
+            >
+                <el-descriptions-item label="任务ID">
+                    {{ selectedTask?.task_id }}
+                </el-descriptions-item>
+                <el-descriptions-item label="问题">
+                    {{ selectedTask?.question }}
+                </el-descriptions-item>
+                <el-descriptions-item label="任务状态">{{ selectedTask?.statusText }}</el-descriptions-item>
+                <el-descriptions-item label="知识类别">
+                    {{ KnowledgeType[selectedTask?.knowledgeType] || '' }}
+                </el-descriptions-item>
+                <el-descriptions-item label="内容知识子类别">
+                    {{ QueryTypeEnum[selectedTask?.query_type] || '-' }}
+                </el-descriptions-item>
+                <el-descriptions-item label="是否需要存储">
+                    {{ selectedTask?.need_store === NeedStoreEnum.需要存储 ? '是' : '否' }}
+                </el-descriptions-item>
+            </el-descriptions>
+            <div class="querys-header">Query词条</div>
+            <el-collapse accordion @change="handleCollapseChange">
+                <el-collapse-item 
+                    v-for="query in selectedQueries" 
+                    :key="query.query" 
+                    :title="query.query" 
+                    :name="query.query" 
+                    class="query-title"
+                >
+                    <div v-loading="query.loading" class="query-data-container">
+                        <div v-if="query.data" class="query-data-content">
+                            {{ query.data }}
+                        </div>
+                        <div v-else-if="!query.loading" class="query-data-empty">
+                            暂无数据
+                        </div>
+                    </div>
+                </el-collapse-item>
+            </el-collapse>
+        </div>
+    </div>
+    
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref, watch } from 'vue';
+import { ElPagination, ElDescriptions, ElCollapse, ElCollapseItem } from 'element-plus';
+import { DATA_CRAWLING_BASE_URL } from "@/config";
+import axios from 'axios';
+import { KnowledgeType, NeedStoreEnum, QueryTask, QueryTypeEnum, QuestionTask, TaskStatusEnum } from '@/types/DataCrawing';
+
+export default defineComponent({
+    name: 'DataCrawling',
+    components: {
+        ElPagination,
+        ElDescriptions,
+        ElCollapse,
+        ElCollapseItem,
+    },
+    setup() {
+        const questionTasks = ref<QuestionTask[]>([]);
+        const pageNumber = ref(1);
+        const pageSize = ref(10);
+        const total = ref(0);
+        const loading = ref(false);
+        const selectedTaskId = ref<string | null>(null);
+        const selectedTask = ref<QuestionTask | null>(null);
+        const selectedQueries = ref<QueryTask[]>([]);
+        const activeCollapseName = ref<string | number | null>(null);
+        // 监听selectedTaskId变化
+        watch(selectedTaskId, (newVal) => {
+            selectedTask.value = questionTasks.value.find(task => task.task_id === newVal) || null;
+            selectedQueries.value = selectedTask.value?.queries || [];
+            activeCollapseName.value = null; // 重置展开状态
+        });
+
+        const renderTaskStatus = (status: TaskStatusEnum) => {
+            return TaskStatusEnum[status];
+        };
+        const fetchData = async () => {
+            loading.value = true;
+            try {
+                const response = await axios.get(`${DATA_CRAWLING_BASE_URL}/tasks`, {
+                    params: {
+                        page_number: pageNumber.value,
+                        page_size: pageSize.value,
+                    },
+                });
+                console.log('Response:', response);
+                if (response && response.data) {
+                    questionTasks.value = response.data.tasks;
+                    total.value = response.data.total;
+                    selectedTaskId.value = response.data.tasks[0]?.task_id || null;
+                }
+            } catch (error) {
+                console.error('Error fetching data:', error);
+            } finally {
+                loading.value = false;
+            }
+        };
+        const handleCurrentChange = (newPage: number) => {
+            pageNumber.value = newPage;
+            fetchData();
+        };
+        const handleTaskClick = (taskId: string) => {
+            selectedTaskId.value = taskId;
+        };
+        
+        const handleCollapseChange = async (activeName: string | number | (string | number)[]) => {
+            console.log('activeName:', activeName);
+            // 查找对应的 query
+            const query = selectedQueries.value.find(q => q.query === activeName);
+            
+            // 如果这个 query 已经有数据了,就不再请求
+            if (query && !query.data && !query.loading) {
+                query.loading = true;
+                
+                try {
+                    // 第一步:请求 knowledge-query/data 接口
+                    const queryResponse = await axios.get(`${DATA_CRAWLING_BASE_URL}/knowledge-query/data`, {
+                        params: {
+                            suggest_task_id: query.task_id || selectedTaskId.value,
+                            query: query.query,
+                        },
+                    });
+                    
+                    if (queryResponse.data && queryResponse.data.request_id) {
+                        query.request_id = queryResponse.data.request_id;
+                        
+                        // 第二步:使用 request_id 请求 knowledge-store/data 接口
+                        const storeResponse = await axios.get(`${DATA_CRAWLING_BASE_URL}/knowledge-store/data`, {
+                            params: {
+                                request_id: query.request_id,
+                            },
+                        });
+                        
+                        if (storeResponse.data && storeResponse.data.data) {
+                            query.data = storeResponse.data.data;
+                        }
+                    }
+                } catch (error) {
+                    console.error('Error fetching query data:', error);
+                } finally {
+                    query.loading = false;
+                }
+            }
+        };
+        
+        fetchData();
+        return {
+            questionTasks,
+            pageNumber,
+            pageSize,
+            total,
+            handleCurrentChange,
+            loading,
+            selectedTaskId,
+            selectedTask,
+            handleTaskClick,
+            renderTaskStatus,
+            NeedStoreEnum,
+            KnowledgeType,
+            QueryTypeEnum,
+            selectedQueries,
+            handleCollapseChange,
+        };
+    },
+});
+</script>
+
+<style scoped>
+.app-container {
+    min-height: calc(100vh - 71px);
+    background: rgba(138,147,228,0.1);
+    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+}
+.pagination-container {
+    box-sizing: border-box;
+    height: calc(100vh - 90px);
+    max-height: calc(100vh - 90px);
+    display: flex;
+    justify-content: center;
+    align-items: flex-start;
+    gap: 10px;
+}
+.tasks-container {
+    display: flex;
+    box-sizing: border-box;
+    padding-top: 20px;
+    flex-direction: column;
+    justify-content: space-between;
+    align-items: flex-start;
+    width: 30%;
+    height: 100%;
+    overflow-y: auto;
+}
+
+.task-detail-container {
+    box-sizing: border-box;
+    flex: 1;
+    padding: 20px;
+    margin-top: 20px;
+    margin-right: 20px;
+    height: calc(100vh - 110px);
+    max-height: calc(100vh - 110px);
+    overflow-y: auto;
+    background: white;
+    border-radius: 8px;
+    box-shadow: 0 4px 12px rgba(138, 147, 228, 0.15);
+    border: 1px solid #e2e8f0;
+}
+
+.pagination {
+    width: 80%;
+    padding: 0 10px 0 20px;
+}
+
+.tasks {
+    box-sizing: border-box;
+    width: 100%;
+    padding: 0 5px 0 20px;
+}
+
+.task-item {
+    box-sizing: border-box;
+    padding: 16px 20px;
+    margin-bottom: 12px;
+    background: white;
+    border-radius: 8px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    border: 2px solid transparent;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+}
+
+.task-item:hover {
+    border-color: rgba(138, 147, 228, 0.4);
+    box-shadow: 0 4px 12px rgba(138, 147, 228, 0.15);
+    transform: translateY(-2px);
+}
+
+.task-item-selected {
+    background: rgba(138, 147, 228, 0.3);
+    border-color: rgb(138, 147, 228);
+    box-shadow: 0 4px 12px rgba(138, 147, 228, 0.25);
+}
+
+.task-item-selected:hover {
+    background: rgba(138, 147, 228, 0.15);
+}
+
+.task-question {
+    font-size: 16px;
+    color: #333;
+    line-height: 1.6;
+    font-weight: 500;
+}
+
+.query-data-container {
+    padding: 16px;
+    min-height: 100px;
+}
+
+.query-data-content {
+    font-size: 14px;
+    color: #333;
+    line-height: 1.8;
+    white-space: pre-wrap;
+    word-wrap: break-word;
+    background: #f8f9fa;
+    padding: 16px;
+    border-radius: 6px;
+    border-left: 3px solid rgba(138, 147, 228, 0.6);
+}
+
+.query-data-empty {
+    text-align: center;
+    color: #999;
+    font-size: 14px;
+    padding: 40px;
+}
+
+.task-detail-container :deep(.el-descriptions__title) {
+    font-size: 18px;
+    line-height: 1.6;
+    font-weight: 500;
+    color: #666;
+    margin: 10px 0 0;
+}
+
+.querys-header {
+    font-size: 18px;
+    line-height: 1.6;
+    font-weight: 500;
+    color: #666;
+    margin: 25px 0 15px 0;
+}
+
+.query-title :deep(.el-collapse-item__header) {
+    font-size: 15px;
+    color: #333;
+}
+</style>

+ 12 - 1
vue.config.js

@@ -1,4 +1,15 @@
 const { defineConfig } = require('@vue/cli-service')
 module.exports = defineConfig({
-  transpileDependencies: true
+  transpileDependencies: true,
+  devServer: {
+    proxy: {
+      '/api/data-crawling': {
+        target: 'http://8.219.186.16:8079',
+        changeOrigin: true,
+        pathRewrite: {
+          '^/api/data-crawling': ''
+        }
+      }
+    }
+  }
 })