|
|
@@ -0,0 +1,454 @@
|
|
|
+{% extends "base.html" %}
|
|
|
+
|
|
|
+{% block content %}
|
|
|
+<div class="flex h-screen overflow-hidden" id="knowledge-view">
|
|
|
+ <!-- Sidebar -->
|
|
|
+ {% include 'partials/sidebar.html' %}
|
|
|
+
|
|
|
+ <!-- Main Content -->
|
|
|
+ <div class="flex-1 flex flex-col min-w-0 overflow-hidden bg-gray-900/80 relative">
|
|
|
+ <!-- Top Header -->
|
|
|
+ <header class="h-16 flex items-center justify-between px-6 z-20 bg-gray-900/80 backdrop-blur-md border-b border-blue-900/30">
|
|
|
+ <button class="md:hidden text-gray-300 focus:outline-none" id="open-sidebar">
|
|
|
+ <i class="fas fa-bars text-xl"></i>
|
|
|
+ </button>
|
|
|
+ <h1 class="text-lg md:text-2xl font-bold tech-title truncate ml-2">样本中心管理</h1>
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
+ <div class="flex items-center space-x-2">
|
|
|
+ <div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white font-bold border border-cyan-400 shadow-md">A</div>
|
|
|
+ <span class="hidden md:inline text-sm text-gray-300">{{ current_user.username }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </header>
|
|
|
+
|
|
|
+ <!-- Main Scrollable Area -->
|
|
|
+ <main class="flex-1 overflow-y-auto p-4 md:p-6 scroll-smooth">
|
|
|
+ <div class="h-full flex flex-col gap-6">
|
|
|
+ <!-- Tabs -->
|
|
|
+ <div class="bg-gray-800 rounded-lg shadow-lg">
|
|
|
+ <div class="flex border-b border-gray-700 px-6 pt-4 gap-6">
|
|
|
+ <button class="kb-tab active px-4 py-2 text-sm font-bold rounded-t-lg transition-colors" data-tab="list">
|
|
|
+ <i class="fas fa-list mr-2"></i>知识库列表
|
|
|
+ </button>
|
|
|
+ <button class="kb-tab px-4 py-2 text-sm font-bold rounded-t-lg transition-colors text-gray-400 hover:text-white" data-tab="detail">
|
|
|
+ <i class="fas fa-info-circle mr-2"></i>知识库详情
|
|
|
+ </button>
|
|
|
+ <button class="kb-tab px-4 py-2 text-sm font-bold rounded-t-lg transition-colors text-gray-400 hover:text-white" data-tab="tasks">
|
|
|
+ <i class="fas fa-tasks mr-2"></i>入库任务
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Tab 1: 知识库列表 -->
|
|
|
+ <div id="tab-list" class="flex-1 bg-gray-800 p-6 rounded-b-lg shadow-lg flex flex-col min-h-0 tab-content">
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <label class="text-gray-400 text-sm font-bold">知识库列表</label>
|
|
|
+ <div class="flex gap-2 items-center">
|
|
|
+ <input type="text" id="kb-search" class="bg-gray-700 text-white rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 w-48" placeholder="搜索知识库...">
|
|
|
+ <button onclick="loadKnowledgeList()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm transition-colors">
|
|
|
+ <i class="fas fa-sync-alt"></i> 刷新
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex-1 overflow-auto custom-scrollbar">
|
|
|
+ <table class="w-full text-sm text-gray-300">
|
|
|
+ <thead class="text-xs text-gray-400 uppercase bg-gray-700/50 sticky top-0">
|
|
|
+ <tr>
|
|
|
+ <th class="px-4 py-3 text-left">ID</th>
|
|
|
+ <th class="px-4 py-3 text-left">名称</th>
|
|
|
+ <th class="px-4 py-3 text-center">文档数</th>
|
|
|
+ <th class="px-4 py-3 text-center">状态</th>
|
|
|
+ <th class="px-4 py-3 text-center">创建时间</th>
|
|
|
+ <th class="px-4 py-3 text-center">操作</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody id="kb-list-body">
|
|
|
+ <tr><td colspan="6" class="text-center text-gray-500 py-10">加载中...</td></tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-between items-center mt-4 pt-4 border-t border-gray-700">
|
|
|
+ <span id="kb-total" class="text-gray-500 text-sm"></span>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <button onclick="changePage(-1)" id="btn-prev" class="hidden bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded text-sm">上一页</button>
|
|
|
+ <span id="kb-page-info" class="text-gray-400 text-sm px-3 py-1"></span>
|
|
|
+ <button onclick="changePage(1)" id="btn-next" class="hidden bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded text-sm">下一页</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Tab 2: 知识库详情 -->
|
|
|
+ <div id="tab-detail" class="flex-1 bg-gray-800 p-6 rounded-b-lg shadow-lg flex flex-col min-h-0 tab-content hidden">
|
|
|
+ <div id="kb-detail-content">
|
|
|
+ <div class="text-center text-gray-500 py-20">
|
|
|
+ <i class="fas fa-info-circle text-4xl mb-4 opacity-50"></i>
|
|
|
+ <p>请先从知识库列表中选择查看详情</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Tab 3: 入库任务 -->
|
|
|
+ <div id="tab-tasks" class="flex-1 bg-gray-800 p-6 rounded-b-lg shadow-lg flex flex-col min-h-0 tab-content hidden">
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <label class="text-gray-400 text-sm font-bold">批量入库任务</label>
|
|
|
+ <button onclick="loadImportTasks()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm transition-colors">
|
|
|
+ <i class="fas fa-sync-alt"></i> 刷新
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div class="flex-1 overflow-auto custom-scrollbar">
|
|
|
+ <table class="w-full text-sm text-gray-300">
|
|
|
+ <thead class="text-xs text-gray-400 uppercase bg-gray-700/50 sticky top-0">
|
|
|
+ <tr>
|
|
|
+ <th class="px-4 py-3 text-left">任务号</th>
|
|
|
+ <th class="px-4 py-3 text-left">知识库</th>
|
|
|
+ <th class="px-4 py-3 text-center">状态</th>
|
|
|
+ <th class="px-4 py-3 text-center">进度</th>
|
|
|
+ <th class="px-4 py-3 text-center">创建时间</th>
|
|
|
+ <th class="px-4 py-3 text-center">操作</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody id="task-list-body">
|
|
|
+ <tr><td colspan="6" class="text-center text-gray-500 py-10">暂无任务</td></tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </main>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<!-- 入库弹窗 Modal -->
|
|
|
+<div id="import-modal" class="fixed inset-0 bg-black/70 z-50 hidden flex items-center justify-center">
|
|
|
+ <div class="bg-gray-800 rounded-lg shadow-2xl w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
|
|
|
+ <div class="flex justify-between items-center p-6 border-b border-gray-700">
|
|
|
+ <h2 class="text-xl font-bold text-white"><i class="fas fa-upload mr-2 text-blue-500"></i>批量入库</h2>
|
|
|
+ <button onclick="closeImportModal()" class="text-gray-400 hover:text-white"><i class="fas fa-times text-xl"></i></button>
|
|
|
+ </div>
|
|
|
+ <div class="p-6 space-y-4">
|
|
|
+ <div>
|
|
|
+ <label class="block text-gray-400 text-sm mb-1 font-bold">知识库</label>
|
|
|
+ <input type="text" id="import-kb-name" class="w-full bg-gray-700 text-white rounded p-3 focus:outline-none focus:ring-2 focus:ring-blue-500" readonly>
|
|
|
+ <input type="hidden" id="import-kb-id">
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-gray-400 text-sm mb-1 font-bold">任务号 (task_no)</label>
|
|
|
+ <input type="text" id="import-task-no" class="w-full bg-gray-700 text-white rounded p-3 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="自动生成或手动输入">
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-gray-400 text-sm mb-1 font-bold">Parents (JSON 数组)</label>
|
|
|
+ <textarea id="import-parents" class="w-full bg-gray-700 text-white rounded p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-xs" rows="8" placeholder='[{"index": 0, "parent_id": "...", "text": "..."}]'></textarea>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-gray-400 text-sm mb-1 font-bold">Children (JSON 数组,可选)</label>
|
|
|
+ <textarea id="import-children" class="w-full bg-gray-700 text-white rounded p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-xs" rows="6" placeholder='[{"index": 0, "parent_id": "...", "text": "..."}]'></textarea>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex justify-end gap-3 p-6 border-t border-gray-700">
|
|
|
+ <button onclick="closeImportModal()" class="bg-gray-600 hover:bg-gray-500 text-white px-6 py-2 rounded transition-colors">取消</button>
|
|
|
+ <button onclick="submitImport()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded transition-colors"><i class="fas fa-paper-plane mr-1"></i>提交</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<style>
|
|
|
+.kb-tab.active {
|
|
|
+ background-color: #1F2937;
|
|
|
+ color: white;
|
|
|
+ border-bottom: 2px solid #3B82F6;
|
|
|
+}
|
|
|
+.status-pending { color: #F59E0B; }
|
|
|
+.status-processing { color: #3B82F6; }
|
|
|
+.status-success, .status-completed { color: #10B981; }
|
|
|
+.status-failed { color: #EF4444; }
|
|
|
+.custom-scrollbar::-webkit-scrollbar { width: 6px; }
|
|
|
+.custom-scrollbar::-webkit-scrollbar-track { background: #1F2937; }
|
|
|
+.custom-scrollbar::-webkit-scrollbar-thumb { background: #4B5563; border-radius: 3px; }
|
|
|
+</style>
|
|
|
+
|
|
|
+<script>
|
|
|
+let currentPage = 1;
|
|
|
+let currentKbId = '';
|
|
|
+
|
|
|
+$(document).ready(function() {
|
|
|
+ $('#open-sidebar').click(function() {
|
|
|
+ $('.sidebar').toggleClass('-translate-x-full');
|
|
|
+ });
|
|
|
+ loadKnowledgeList();
|
|
|
+ loadImportTasks();
|
|
|
+
|
|
|
+ // Tab switching
|
|
|
+ $('.kb-tab').click(function() {
|
|
|
+ $('.kb-tab').removeClass('active').addClass('text-gray-400');
|
|
|
+ $(this).addClass('active').removeClass('text-gray-400');
|
|
|
+ $('.tab-content').addClass('hidden');
|
|
|
+ $('#tab-' + $(this).data('tab')).removeClass('hidden');
|
|
|
+ });
|
|
|
+
|
|
|
+ // Search
|
|
|
+ $('#kb-search').on('keyup', function(e) {
|
|
|
+ if (e.key === 'Enter') {
|
|
|
+ currentPage = 1;
|
|
|
+ loadKnowledgeList();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Auto-generate task_no
|
|
|
+ $('#import-task-no').on('focus', function() {
|
|
|
+ if (!$(this).val()) {
|
|
|
+ $(this).val('KIT-' + new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14) + '-' + Math.random().toString(36).slice(2, 6));
|
|
|
+ }
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+function loadKnowledgeList() {
|
|
|
+ const keyword = $('#kb-search').val().trim();
|
|
|
+ $.ajax({
|
|
|
+ url: '/api/v1/knowledge/bases',
|
|
|
+ method: 'GET',
|
|
|
+ data: { page: currentPage, page_size: 20 },
|
|
|
+ headers: { 'Authorization': 'Bearer ' + getJwtToken() },
|
|
|
+ success: function(resp) {
|
|
|
+ renderKnowledgeList(resp.data || resp);
|
|
|
+ },
|
|
|
+ error: function(xhr) {
|
|
|
+ $('#kb-list-body').html('<tr><td colspan="6" class="text-center text-red-400 py-10">加载失败: ' + (xhr.responseJSON?.message || '未知错误') + '</td></tr>');
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function renderKnowledgeList(data) {
|
|
|
+ const items = data.items || data;
|
|
|
+ const total = data.total || items.length;
|
|
|
+ const tbody = $('#kb-list-body');
|
|
|
+ tbody.empty();
|
|
|
+
|
|
|
+ if (!items || items.length === 0) {
|
|
|
+ tbody.html('<tr><td colspan="6" class="text-center text-gray-500 py-10">暂无知识库数据</td></tr>');
|
|
|
+ $('#kb-total').text('');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ items.forEach(function(item) {
|
|
|
+ const statusHtml = item.status === 1
|
|
|
+ ? '<span class="text-green-400"><i class="fas fa-check-circle"></i> 启用</span>'
|
|
|
+ : '<span class="text-red-400"><i class="fas fa-times-circle"></i> 禁用</span>';
|
|
|
+ const html = `<tr class="border-b border-gray-700 hover:bg-gray-700/30 transition-colors">
|
|
|
+ <td class="px-4 py-3 text-gray-400 font-mono text-xs">${item.id}</td>
|
|
|
+ <td class="px-4 py-3 text-white font-medium">${item.name}</td>
|
|
|
+ <td class="px-4 py-3 text-center">${item.document_count || 0}</td>
|
|
|
+ <td class="px-4 py-3 text-center">${statusHtml}</td>
|
|
|
+ <td class="px-4 py-3 text-center text-gray-400 text-xs">${item.created_at || ''}</td>
|
|
|
+ <td class="px-4 py-3 text-center">
|
|
|
+ <button onclick="viewKbDetail('${item.id}', '${item.name.replace(/'/g, "\\'")}')" class="text-blue-400 hover:text-blue-300 text-xs mr-3"><i class="fas fa-eye"></i> 详情</button>
|
|
|
+ <button onclick="openImportModal('${item.id}', '${item.name.replace(/'/g, "\\'")}')" class="text-green-400 hover:text-green-300 text-xs"><i class="fas fa-upload"></i> 入库</button>
|
|
|
+ </td>
|
|
|
+ </tr>`;
|
|
|
+ tbody.append(html);
|
|
|
+ });
|
|
|
+
|
|
|
+ const pageSize = data.page_size || 20;
|
|
|
+ const page = data.page || 1;
|
|
|
+ const totalPages = Math.ceil(total / pageSize);
|
|
|
+ $('#kb-total').text('共 ' + total + ' 条');
|
|
|
+ $('#kb-page-info').text('第 ' + page + ' / ' + totalPages + ' 页');
|
|
|
+ $('#btn-prev').toggleClass('hidden', page <= 1);
|
|
|
+ $('#btn-next').toggleClass('hidden', page >= totalPages);
|
|
|
+}
|
|
|
+
|
|
|
+function changePage(delta) {
|
|
|
+ currentPage += delta;
|
|
|
+ if (currentPage < 1) currentPage = 1;
|
|
|
+ loadKnowledgeList();
|
|
|
+}
|
|
|
+
|
|
|
+function viewKbDetail(kbId, kbName) {
|
|
|
+ // Switch to detail tab
|
|
|
+ $('.kb-tab').removeClass('active').addClass('text-gray-400');
|
|
|
+ $('.kb-tab[data-tab="detail"]').addClass('active').removeClass('text-gray-400');
|
|
|
+ $('.tab-content').addClass('hidden');
|
|
|
+ $('#tab-detail').removeClass('hidden');
|
|
|
+
|
|
|
+ $('#kb-detail-content').html('<div class="text-center text-gray-400 py-20"><i class="fas fa-circle-notch fa-spin text-3xl text-blue-500 mb-4"></i><p>加载中...</p></div>');
|
|
|
+
|
|
|
+ $.ajax({
|
|
|
+ url: '/api/v1/knowledge/bases/' + kbId,
|
|
|
+ method: 'GET',
|
|
|
+ headers: { 'Authorization': 'Bearer ' + getJwtToken() },
|
|
|
+ success: function(resp) {
|
|
|
+ renderKbDetail(resp.data || resp);
|
|
|
+ },
|
|
|
+ error: function(xhr) {
|
|
|
+ $('#kb-detail-content').html('<div class="text-center text-red-400 py-20">加载失败: ' + (xhr.responseJSON?.message || '未知错误') + '</div>');
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function renderKbDetail(data) {
|
|
|
+ const schemaHtml = data.metadata_schema && data.metadata_schema.length > 0
|
|
|
+ ? `<div class="mt-6"><h3 class="text-white font-bold mb-3"><i class="fas fa-table mr-2 text-blue-500"></i>元数据定义</h3>
|
|
|
+ <table class="w-full text-sm text-gray-300">
|
|
|
+ <thead class="text-xs text-gray-400 uppercase bg-gray-700/50">
|
|
|
+ <tr><th class="px-4 py-2 text-left">中文名</th><th class="px-4 py-2 text-left">英文名</th><th class="px-4 py-2 text-center">类型</th><th class="px-4 py-2 text-left">描述</th></tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>${data.metadata_schema.map(function(f) {
|
|
|
+ return '<tr class="border-b border-gray-700"><td class="px-4 py-2">' + (f.field_name_cn||'') + '</td><td class="px-4 py-2 font-mono text-xs">' + (f.field_name_en||'') + '</td><td class="px-4 py-2 text-center"><span class="bg-gray-600 px-2 py-0.5 rounded text-xs">' + (f.field_type||'') + '</span></td><td class="px-4 py-2 text-gray-400">' + (f.description||'') + '</td></tr>';
|
|
|
+ }).join('')}</tbody>
|
|
|
+ </table></div>`
|
|
|
+ : '';
|
|
|
+
|
|
|
+ $('#kb-detail-content').html(`
|
|
|
+ <div class="bg-gray-700/50 rounded-lg p-6">
|
|
|
+ <h2 class="text-xl font-bold text-white mb-4"><i class="fas fa-book mr-2 text-blue-500"></i>${data.name}</h2>
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
|
|
+ <div><span class="text-gray-400">ID:</span> <span class="text-white font-mono">${data.id}</span></div>
|
|
|
+ <div><span class="text-gray-400">文档数:</span> <span class="text-white">${data.document_count || 0}</span></div>
|
|
|
+ <div><span class="text-gray-400">父表:</span> <span class="text-white font-mono text-xs">${data.parent_table || '-'}</span></div>
|
|
|
+ <div><span class="text-gray-400">子表:</span> <span class="text-white font-mono text-xs">${data.child_table || '-'}</span></div>
|
|
|
+ <div><span class="text-gray-400">创建人:</span> <span class="text-white">${data.created_by || '-'}</span></div>
|
|
|
+ <div><span class="text-gray-400">创建时间:</span> <span class="text-white">${data.created_at || '-'}</span></div>
|
|
|
+ </div>
|
|
|
+ ${data.description ? '<p class="mt-4 text-gray-300"><span class="text-gray-400">描述:</span> ' + data.description + '</p>' : ''}
|
|
|
+ ${schemaHtml}
|
|
|
+ <div class="mt-6 flex gap-3">
|
|
|
+ <button onclick="openImportModal('${data.id}', '${data.name.replace(/'/g, "\\'")}')" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded transition-colors"><i class="fas fa-upload mr-1"></i>批量入库</button>
|
|
|
+ <button onclick="switchTab('list')" class="bg-gray-600 hover:bg-gray-500 text-white px-6 py-2 rounded transition-colors"><i class="fas fa-arrow-left mr-1"></i>返回列表</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `);
|
|
|
+}
|
|
|
+
|
|
|
+function switchTab(name) {
|
|
|
+ $('.kb-tab').removeClass('active').addClass('text-gray-400');
|
|
|
+ $('.kb-tab[data-tab="' + name + '"]').addClass('active').removeClass('text-gray-400');
|
|
|
+ $('.tab-content').addClass('hidden');
|
|
|
+ $('#tab-' + name).removeClass('hidden');
|
|
|
+}
|
|
|
+
|
|
|
+function loadImportTasks() {
|
|
|
+ $.ajax({
|
|
|
+ url: '/api/v1/knowledge/import-tasks',
|
|
|
+ method: 'GET',
|
|
|
+ data: { page: 1, page_size: 50 },
|
|
|
+ headers: { 'Authorization': 'Bearer ' + getJwtToken() },
|
|
|
+ success: function(resp) {
|
|
|
+ renderImportTasks(resp.data || resp);
|
|
|
+ },
|
|
|
+ error: function(xhr) {
|
|
|
+ $('#task-list-body').html('<tr><td colspan="6" class="text-center text-red-400 py-10">加载失败</td></tr>');
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function renderImportTasks(data) {
|
|
|
+ const items = data.items || [];
|
|
|
+ const tbody = $('#task-list-body');
|
|
|
+ tbody.empty();
|
|
|
+
|
|
|
+ if (items.length === 0) {
|
|
|
+ tbody.html('<tr><td colspan="6" class="text-center text-gray-500 py-10">暂无入库任务</td></tr>');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const statusMap = {
|
|
|
+ 'pending': '<span class="status-pending"><i class="fas fa-clock"></i> 等待中</span>',
|
|
|
+ 'processing': '<span class="status-processing"><i class="fas fa-spinner fa-spin"></i> 处理中</span>',
|
|
|
+ 'success': '<span class="status-success"><i class="fas fa-check-circle"></i> 已完成</span>',
|
|
|
+ 'completed': '<span class="status-success"><i class="fas fa-check-circle"></i> 已完成</span>',
|
|
|
+ 'failed': '<span class="status-failed"><i class="fas fa-times-circle"></i> 失败</span>',
|
|
|
+ };
|
|
|
+
|
|
|
+ items.forEach(function(item) {
|
|
|
+ const progressHtml = item.progress
|
|
|
+ ? item.progress.processed + '/' + item.progress.total
|
|
|
+ : '-';
|
|
|
+ const html = `<tr class="border-b border-gray-700 hover:bg-gray-700/30 transition-colors">
|
|
|
+ <td class="px-4 py-3 font-mono text-xs text-gray-300">${item.task_no}</td>
|
|
|
+ <td class="px-4 py-3 text-white">${item.kb_name || item.kb_id}</td>
|
|
|
+ <td class="px-4 py-3 text-center">${statusMap[item.status] || item.status}</td>
|
|
|
+ <td class="px-4 py-3 text-center text-sm">${progressHtml}</td>
|
|
|
+ <td class="px-4 py-3 text-center text-gray-400 text-xs">${item.created_at || ''}</td>
|
|
|
+ <td class="px-4 py-3 text-center">
|
|
|
+ ${item.status === 'processing' || item.status === 'pending'
|
|
|
+ ? '<button onclick="refreshTaskStatus(\'' + item.task_no + '\')" class="text-blue-400 hover:text-blue-300 text-xs"><i class="fas fa-sync-alt"></i> 刷新</button>'
|
|
|
+ : (item.error_message ? '<span class="text-red-400 text-xs" title="' + item.error_message + '"><i class="fas fa-exclamation-triangle"></i></span>' : '')}
|
|
|
+ </td>
|
|
|
+ </tr>`;
|
|
|
+ tbody.append(html);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function refreshTaskStatus(taskNo) {
|
|
|
+ $.ajax({
|
|
|
+ url: '/api/v1/knowledge/import-tasks/' + taskNo,
|
|
|
+ method: 'GET',
|
|
|
+ headers: { 'Authorization': 'Bearer ' + getJwtToken() },
|
|
|
+ success: function() {
|
|
|
+ loadImportTasks();
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function openImportModal(kbId, kbName) {
|
|
|
+ $('#import-kb-id').val(kbId);
|
|
|
+ $('#import-kb-name').val(kbName);
|
|
|
+ $('#import-task-no').val('');
|
|
|
+ $('#import-parents').val('');
|
|
|
+ $('#import-children').val('');
|
|
|
+ $('#import-modal').removeClass('hidden');
|
|
|
+}
|
|
|
+
|
|
|
+function closeImportModal() {
|
|
|
+ $('#import-modal').addClass('hidden');
|
|
|
+}
|
|
|
+
|
|
|
+function submitImport() {
|
|
|
+ const kbId = $('#import-kb-id').val();
|
|
|
+ const taskNo = $('#import-task-no').val().trim();
|
|
|
+ let parents, children;
|
|
|
+
|
|
|
+ if (!taskNo) { alert('请输入任务号'); return; }
|
|
|
+
|
|
|
+ try {
|
|
|
+ parents = JSON.parse($('#import-parents').val());
|
|
|
+ } catch(e) { alert('Parents JSON 格式错误: ' + e.message); return; }
|
|
|
+
|
|
|
+ const childrenVal = $('#import-children').val().trim();
|
|
|
+ children = childrenVal ? JSON.parse(childrenVal) : null;
|
|
|
+
|
|
|
+ $.ajax({
|
|
|
+ url: '/api/v1/knowledge/bases/' + kbId + '/batch-import',
|
|
|
+ method: 'POST',
|
|
|
+ contentType: 'application/json',
|
|
|
+ headers: { 'Authorization': 'Bearer ' + getJwtToken() },
|
|
|
+ data: JSON.stringify({
|
|
|
+ task_no: taskNo,
|
|
|
+ kb_name: $('#import-kb-name').val(),
|
|
|
+ parents: parents,
|
|
|
+ children: children,
|
|
|
+ }),
|
|
|
+ success: function(resp) {
|
|
|
+ alert('入库任务已提交!任务号: ' + taskNo);
|
|
|
+ closeImportModal();
|
|
|
+ switchTab('tasks');
|
|
|
+ loadImportTasks();
|
|
|
+ },
|
|
|
+ error: function(xhr) {
|
|
|
+ alert('提交失败: ' + (xhr.responseJSON?.message || '未知错误'));
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function getJwtToken() {
|
|
|
+ // SSO 登录后保存在 localStorage 中的 key 是 'token'
|
|
|
+ return localStorage.getItem('token') || localStorage.getItem('jwt_token') || '';
|
|
|
+}
|
|
|
+
|
|
|
+// 自动刷新任务状态(每 10 秒)
|
|
|
+setInterval(function() {
|
|
|
+ if (!$('#tab-tasks').hasClass('hidden')) {
|
|
|
+ loadImportTasks();
|
|
|
+ }
|
|
|
+}, 10000);
|
|
|
+</script>
|
|
|
+{% endblock %}
|