knowledge_management.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="flex h-screen overflow-hidden" id="knowledge-view">
  4. <!-- Sidebar -->
  5. {% include 'partials/sidebar.html' %}
  6. <!-- Main Content -->
  7. <div class="flex-1 flex flex-col min-w-0 overflow-hidden bg-gray-900/80 relative">
  8. <!-- Top Header -->
  9. <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">
  10. <button class="md:hidden text-gray-300 focus:outline-none" id="open-sidebar">
  11. <i class="fas fa-bars text-xl"></i>
  12. </button>
  13. <h1 class="text-lg md:text-2xl font-bold tech-title truncate ml-2">样本中心管理</h1>
  14. <div class="flex items-center space-x-4">
  15. <div class="flex items-center space-x-2">
  16. <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>
  17. <span class="hidden md:inline text-sm text-gray-300">{{ current_user.username }}</span>
  18. </div>
  19. </div>
  20. </header>
  21. <!-- Main Scrollable Area -->
  22. <main class="flex-1 overflow-y-auto p-4 md:p-6 scroll-smooth">
  23. <div class="h-full flex flex-col gap-6">
  24. <!-- Tabs -->
  25. <div class="bg-gray-800 rounded-lg shadow-lg">
  26. <div class="flex border-b border-gray-700 px-6 pt-4 gap-6">
  27. <button class="kb-tab active px-4 py-2 text-sm font-bold rounded-t-lg transition-colors" data-tab="list">
  28. <i class="fas fa-list mr-2"></i>知识库列表
  29. </button>
  30. <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">
  31. <i class="fas fa-info-circle mr-2"></i>知识库详情
  32. </button>
  33. <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">
  34. <i class="fas fa-tasks mr-2"></i>入库任务
  35. </button>
  36. </div>
  37. </div>
  38. <!-- Tab 1: 知识库列表 -->
  39. <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">
  40. <div class="flex justify-between items-center mb-4">
  41. <label class="text-gray-400 text-sm font-bold">知识库列表</label>
  42. <div class="flex gap-2 items-center">
  43. <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="搜索知识库...">
  44. <button onclick="loadKnowledgeList()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm transition-colors">
  45. <i class="fas fa-sync-alt"></i> 刷新
  46. </button>
  47. </div>
  48. </div>
  49. <div class="flex-1 overflow-auto custom-scrollbar">
  50. <table class="w-full text-sm text-gray-300">
  51. <thead class="text-xs text-gray-400 uppercase bg-gray-700/50 sticky top-0">
  52. <tr>
  53. <th class="px-4 py-3 text-left">ID</th>
  54. <th class="px-4 py-3 text-left">名称</th>
  55. <th class="px-4 py-3 text-center">文档数</th>
  56. <th class="px-4 py-3 text-center">状态</th>
  57. <th class="px-4 py-3 text-center">创建时间</th>
  58. <th class="px-4 py-3 text-center">操作</th>
  59. </tr>
  60. </thead>
  61. <tbody id="kb-list-body">
  62. <tr><td colspan="6" class="text-center text-gray-500 py-10">加载中...</td></tr>
  63. </tbody>
  64. </table>
  65. </div>
  66. <div class="flex justify-between items-center mt-4 pt-4 border-t border-gray-700">
  67. <span id="kb-total" class="text-gray-500 text-sm"></span>
  68. <div class="flex gap-2">
  69. <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>
  70. <span id="kb-page-info" class="text-gray-400 text-sm px-3 py-1"></span>
  71. <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>
  72. </div>
  73. </div>
  74. </div>
  75. <!-- Tab 2: 知识库详情 -->
  76. <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">
  77. <div id="kb-detail-content">
  78. <div class="text-center text-gray-500 py-20">
  79. <i class="fas fa-info-circle text-4xl mb-4 opacity-50"></i>
  80. <p>请先从知识库列表中选择查看详情</p>
  81. </div>
  82. </div>
  83. </div>
  84. <!-- Tab 3: 入库任务 -->
  85. <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">
  86. <div class="flex justify-between items-center mb-4">
  87. <label class="text-gray-400 text-sm font-bold">批量入库任务</label>
  88. <button onclick="loadImportTasks()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm transition-colors">
  89. <i class="fas fa-sync-alt"></i> 刷新
  90. </button>
  91. </div>
  92. <div class="flex-1 overflow-auto custom-scrollbar">
  93. <table class="w-full text-sm text-gray-300">
  94. <thead class="text-xs text-gray-400 uppercase bg-gray-700/50 sticky top-0">
  95. <tr>
  96. <th class="px-4 py-3 text-left">任务号</th>
  97. <th class="px-4 py-3 text-left">知识库</th>
  98. <th class="px-4 py-3 text-center">状态</th>
  99. <th class="px-4 py-3 text-center">进度</th>
  100. <th class="px-4 py-3 text-center">创建时间</th>
  101. <th class="px-4 py-3 text-center">操作</th>
  102. </tr>
  103. </thead>
  104. <tbody id="task-list-body">
  105. <tr><td colspan="6" class="text-center text-gray-500 py-10">暂无任务</td></tr>
  106. </tbody>
  107. </table>
  108. </div>
  109. </div>
  110. </div>
  111. </main>
  112. </div>
  113. </div>
  114. <!-- 入库弹窗 Modal -->
  115. <div id="import-modal" class="fixed inset-0 bg-black/70 z-50 hidden flex items-center justify-center">
  116. <div class="bg-gray-800 rounded-lg shadow-2xl w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
  117. <div class="flex justify-between items-center p-6 border-b border-gray-700">
  118. <h2 class="text-xl font-bold text-white"><i class="fas fa-upload mr-2 text-blue-500"></i>批量入库</h2>
  119. <button onclick="closeImportModal()" class="text-gray-400 hover:text-white"><i class="fas fa-times text-xl"></i></button>
  120. </div>
  121. <div class="p-6 space-y-4">
  122. <div>
  123. <label class="block text-gray-400 text-sm mb-1 font-bold">知识库</label>
  124. <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>
  125. <input type="hidden" id="import-kb-id">
  126. </div>
  127. <div>
  128. <label class="block text-gray-400 text-sm mb-1 font-bold">任务号 (task_no)</label>
  129. <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="自动生成或手动输入">
  130. </div>
  131. <div>
  132. <label class="block text-gray-400 text-sm mb-1 font-bold">Parents (JSON 数组)</label>
  133. <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>
  134. </div>
  135. <div>
  136. <label class="block text-gray-400 text-sm mb-1 font-bold">Children (JSON 数组,可选)</label>
  137. <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>
  138. </div>
  139. </div>
  140. <div class="flex justify-end gap-3 p-6 border-t border-gray-700">
  141. <button onclick="closeImportModal()" class="bg-gray-600 hover:bg-gray-500 text-white px-6 py-2 rounded transition-colors">取消</button>
  142. <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>
  143. </div>
  144. </div>
  145. </div>
  146. <style>
  147. .kb-tab.active {
  148. background-color: #1F2937;
  149. color: white;
  150. border-bottom: 2px solid #3B82F6;
  151. }
  152. .status-pending { color: #F59E0B; }
  153. .status-processing { color: #3B82F6; }
  154. .status-success, .status-completed { color: #10B981; }
  155. .status-partial-success { color: #F97316; }
  156. .status-failed { color: #EF4444; }
  157. .custom-scrollbar::-webkit-scrollbar { width: 6px; }
  158. .custom-scrollbar::-webkit-scrollbar-track { background: #1F2937; }
  159. .custom-scrollbar::-webkit-scrollbar-thumb { background: #4B5563; border-radius: 3px; }
  160. </style>
  161. <script>
  162. let currentPage = 1;
  163. let currentKbId = '';
  164. $(document).ready(function() {
  165. $('#open-sidebar').click(function() {
  166. $('.sidebar').toggleClass('-translate-x-full');
  167. });
  168. loadKnowledgeList();
  169. loadImportTasks();
  170. // Tab switching
  171. $('.kb-tab').click(function() {
  172. $('.kb-tab').removeClass('active').addClass('text-gray-400');
  173. $(this).addClass('active').removeClass('text-gray-400');
  174. $('.tab-content').addClass('hidden');
  175. $('#tab-' + $(this).data('tab')).removeClass('hidden');
  176. });
  177. // Search
  178. $('#kb-search').on('keyup', function(e) {
  179. if (e.key === 'Enter') {
  180. currentPage = 1;
  181. loadKnowledgeList();
  182. }
  183. });
  184. // Auto-generate task_no
  185. $('#import-task-no').on('focus', function() {
  186. if (!$(this).val()) {
  187. $(this).val('KIT-' + new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14) + '-' + Math.random().toString(36).slice(2, 6));
  188. }
  189. });
  190. });
  191. function loadKnowledgeList() {
  192. const keyword = $('#kb-search').val().trim();
  193. $.ajax({
  194. url: '/api/v1/knowledge/bases',
  195. method: 'GET',
  196. data: { page: currentPage, page_size: 20 },
  197. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  198. success: function(resp) {
  199. renderKnowledgeList(resp.data || resp);
  200. },
  201. error: function(xhr) {
  202. $('#kb-list-body').html('<tr><td colspan="6" class="text-center text-red-400 py-10">加载失败: ' + (xhr.responseJSON?.message || '未知错误') + '</td></tr>');
  203. }
  204. });
  205. }
  206. function renderKnowledgeList(data) {
  207. const items = data.items || data;
  208. const total = data.total || items.length;
  209. const tbody = $('#kb-list-body');
  210. tbody.empty();
  211. if (!items || items.length === 0) {
  212. tbody.html('<tr><td colspan="6" class="text-center text-gray-500 py-10">暂无知识库数据</td></tr>');
  213. $('#kb-total').text('');
  214. return;
  215. }
  216. items.forEach(function(item) {
  217. const statusHtml = (item.status == 1 || item.status === '1' || item.status === 'normal')
  218. ? '<span class="text-green-400"><i class="fas fa-check-circle"></i> 启用</span>'
  219. : '<span class="text-red-400"><i class="fas fa-times-circle"></i> 禁用</span>';
  220. const html = `<tr class="border-b border-gray-700 hover:bg-gray-700/30 transition-colors">
  221. <td class="px-4 py-3 text-gray-400 font-mono text-xs">${item.id}</td>
  222. <td class="px-4 py-3 text-white font-medium">${item.name}</td>
  223. <td class="px-4 py-3 text-center">${item.document_count || 0}</td>
  224. <td class="px-4 py-3 text-center">${statusHtml}</td>
  225. <td class="px-4 py-3 text-center text-gray-400 text-xs">${item.created_at || ''}</td>
  226. <td class="px-4 py-3 text-center">
  227. <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>
  228. <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>
  229. </td>
  230. </tr>`;
  231. tbody.append(html);
  232. });
  233. const pageSize = data.page_size || 20;
  234. const page = data.page || 1;
  235. const totalPages = Math.ceil(total / pageSize);
  236. $('#kb-total').text('共 ' + total + ' 条');
  237. $('#kb-page-info').text('第 ' + page + ' / ' + totalPages + ' 页');
  238. $('#btn-prev').toggleClass('hidden', page <= 1);
  239. $('#btn-next').toggleClass('hidden', page >= totalPages);
  240. }
  241. function changePage(delta) {
  242. currentPage += delta;
  243. if (currentPage < 1) currentPage = 1;
  244. loadKnowledgeList();
  245. }
  246. function viewKbDetail(kbId, kbName) {
  247. // Switch to detail tab
  248. $('.kb-tab').removeClass('active').addClass('text-gray-400');
  249. $('.kb-tab[data-tab="detail"]').addClass('active').removeClass('text-gray-400');
  250. $('.tab-content').addClass('hidden');
  251. $('#tab-detail').removeClass('hidden');
  252. $('#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>');
  253. $.ajax({
  254. url: '/api/v1/knowledge/bases/' + kbId,
  255. method: 'GET',
  256. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  257. success: function(resp) {
  258. renderKbDetail(resp.data || resp);
  259. },
  260. error: function(xhr) {
  261. $('#kb-detail-content').html('<div class="text-center text-red-400 py-20">加载失败: ' + (xhr.responseJSON?.message || '未知错误') + '</div>');
  262. }
  263. });
  264. }
  265. function renderKbDetail(data) {
  266. const schemaHtml = data.metadata_schema && data.metadata_schema.length > 0
  267. ? `<div class="mt-6"><h3 class="text-white font-bold mb-3"><i class="fas fa-table mr-2 text-blue-500"></i>元数据定义</h3>
  268. <table class="w-full text-sm text-gray-300">
  269. <thead class="text-xs text-gray-400 uppercase bg-gray-700/50">
  270. <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>
  271. </thead>
  272. <tbody>${data.metadata_schema.map(function(f) {
  273. 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>';
  274. }).join('')}</tbody>
  275. </table></div>`
  276. : '';
  277. $('#kb-detail-content').html(`
  278. <div class="bg-gray-700/50 rounded-lg p-6">
  279. <h2 class="text-xl font-bold text-white mb-4"><i class="fas fa-book mr-2 text-blue-500"></i>${data.name}</h2>
  280. <div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
  281. <div><span class="text-gray-400">ID:</span> <span class="text-white font-mono">${data.id}</span></div>
  282. <div><span class="text-gray-400">文档数:</span> <span class="text-white">${data.document_count || 0}</span></div>
  283. <div><span class="text-gray-400">父表:</span> <span class="text-white font-mono text-xs">${data.parent_table || '-'}</span></div>
  284. <div><span class="text-gray-400">子表:</span> <span class="text-white font-mono text-xs">${data.child_table || '-'}</span></div>
  285. <div><span class="text-gray-400">创建人:</span> <span class="text-white">${data.created_by || '-'}</span></div>
  286. <div><span class="text-gray-400">创建时间:</span> <span class="text-white">${data.created_at || '-'}</span></div>
  287. </div>
  288. ${data.description ? '<p class="mt-4 text-gray-300"><span class="text-gray-400">描述:</span> ' + data.description + '</p>' : ''}
  289. ${schemaHtml}
  290. <div class="mt-6 flex gap-3">
  291. <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>
  292. <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>
  293. </div>
  294. </div>
  295. `);
  296. }
  297. function switchTab(name) {
  298. $('.kb-tab').removeClass('active').addClass('text-gray-400');
  299. $('.kb-tab[data-tab="' + name + '"]').addClass('active').removeClass('text-gray-400');
  300. $('.tab-content').addClass('hidden');
  301. $('#tab-' + name).removeClass('hidden');
  302. }
  303. function loadImportTasks() {
  304. $.ajax({
  305. url: '/api/v1/knowledge/import-tasks',
  306. method: 'GET',
  307. data: { page: 1, page_size: 50 },
  308. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  309. success: function(resp) {
  310. renderImportTasks(resp.data || resp);
  311. },
  312. error: function(xhr) {
  313. $('#task-list-body').html('<tr><td colspan="6" class="text-center text-red-400 py-10">加载失败</td></tr>');
  314. }
  315. });
  316. }
  317. function renderImportTasks(data) {
  318. const items = data.items || [];
  319. const tbody = $('#task-list-body');
  320. tbody.empty();
  321. if (items.length === 0) {
  322. tbody.html('<tr><td colspan="6" class="text-center text-gray-500 py-10">暂无入库任务</td></tr>');
  323. return;
  324. }
  325. const statusMap = {
  326. 'pending': '<span class="status-pending"><i class="fas fa-clock"></i> 等待中</span>',
  327. 'processing': '<span class="status-processing"><i class="fas fa-spinner fa-spin"></i> 处理中</span>',
  328. 'success': '<span class="status-success"><i class="fas fa-check-circle"></i> 已完成</span>',
  329. 'completed': '<span class="status-success"><i class="fas fa-check-circle"></i> 已完成</span>',
  330. 'partial_success': '<span class="status-partial-success"><i class="fas fa-exclamation-circle"></i> 部分完成</span>',
  331. 'failed': '<span class="status-failed"><i class="fas fa-times-circle"></i> 失败</span>',
  332. };
  333. items.forEach(function(item) {
  334. let progressHtml = '-';
  335. if (item.progress) {
  336. const p = item.progress;
  337. if (p.succeeded !== undefined && p.failed !== undefined) {
  338. progressHtml = `<span title="成功 ${p.succeeded} / 失败 ${p.failed} / 总计 ${p.total}">${p.processed || 0}/${p.total}</span>`;
  339. } else {
  340. progressHtml = p.processed + '/' + p.total;
  341. }
  342. }
  343. const hasError = item.status === 'failed' || item.status === 'partial_success';
  344. const html = `<tr class="border-b border-gray-700 hover:bg-gray-700/30 transition-colors">
  345. <td class="px-4 py-3 font-mono text-xs text-gray-300">${item.task_no}</td>
  346. <td class="px-4 py-3 text-white">${item.kb_name || item.kb_id}</td>
  347. <td class="px-4 py-3 text-center">${statusMap[item.status] || item.status}</td>
  348. <td class="px-4 py-3 text-center text-sm">${progressHtml}</td>
  349. <td class="px-4 py-3 text-center text-gray-400 text-xs">${item.created_at || ''}</td>
  350. <td class="px-4 py-3 text-center">
  351. ${item.status === 'processing' || item.status === 'pending'
  352. ? '<button onclick="refreshTaskStatus(\'' + item.task_no + '\')" class="text-blue-400 hover:text-blue-300 text-xs"><i class="fas fa-sync-alt"></i> 刷新</button>'
  353. : (hasError && item.error_message ? '<span class="text-red-400 text-xs" title="' + item.error_message + '"><i class="fas fa-exclamation-triangle"></i></span>' : '')}
  354. </td>
  355. </tr>`;
  356. tbody.append(html);
  357. });
  358. }
  359. function refreshTaskStatus(taskNo) {
  360. $.ajax({
  361. url: '/api/v1/knowledge/import-tasks/' + taskNo,
  362. method: 'GET',
  363. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  364. success: function() {
  365. loadImportTasks();
  366. }
  367. });
  368. }
  369. function openImportModal(kbId, kbName) {
  370. $('#import-kb-id').val(kbId);
  371. $('#import-kb-name').val(kbName);
  372. $('#import-task-no').val('');
  373. $('#import-parents').val('');
  374. $('#import-children').val('');
  375. $('#import-modal').removeClass('hidden');
  376. }
  377. function closeImportModal() {
  378. $('#import-modal').addClass('hidden');
  379. }
  380. function submitImport() {
  381. const kbId = $('#import-kb-id').val();
  382. const taskNo = $('#import-task-no').val().trim();
  383. let parents, children;
  384. if (!taskNo) { alert('请输入任务号'); return; }
  385. try {
  386. parents = JSON.parse($('#import-parents').val());
  387. } catch(e) { alert('Parents JSON 格式错误: ' + e.message); return; }
  388. const childrenVal = $('#import-children').val().trim();
  389. children = childrenVal ? JSON.parse(childrenVal) : null;
  390. $.ajax({
  391. url: '/api/v1/knowledge/bases/' + kbId + '/batch-import',
  392. method: 'POST',
  393. contentType: 'application/json',
  394. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  395. data: JSON.stringify({
  396. task_no: taskNo,
  397. kb_name: $('#import-kb-name').val(),
  398. parents: parents,
  399. children: children,
  400. }),
  401. success: function(resp) {
  402. alert('入库任务已提交!任务号: ' + taskNo);
  403. closeImportModal();
  404. switchTab('tasks');
  405. loadImportTasks();
  406. },
  407. error: function(xhr) {
  408. alert('提交失败: ' + (xhr.responseJSON?.message || '未知错误'));
  409. }
  410. });
  411. }
  412. function getJwtToken() {
  413. // SSO 登录后保存在 localStorage 中的 key 是 'token'
  414. return localStorage.getItem('token') || localStorage.getItem('jwt_token') || '';
  415. }
  416. // 自动刷新任务状态(每 10 秒)
  417. setInterval(function() {
  418. if (!$('#tab-tasks').hasClass('hidden')) {
  419. loadImportTasks();
  420. }
  421. }, 10000);
  422. </script>
  423. {% endblock %}