From e9ec429d21ffcf288c8dad37764e0251688055c6 Mon Sep 17 00:00:00 2001 From: qyyfw Date: Tue, 7 Jul 2026 17:17:41 +0800 Subject: [PATCH] fix: resolve response format mismatches causing data loading failures in Dashboard, ImBots, Promotion, Knowledge pages --- frontend/src/pages/admin/Dashboard.tsx | 23 +- frontend/src/pages/admin/ImBots.tsx | 50 +- frontend/src/pages/admin/Knowledge.tsx | 759 +++++++++++-------------- frontend/src/pages/admin/Promotion.tsx | 14 +- 4 files changed, 385 insertions(+), 461 deletions(-) diff --git a/frontend/src/pages/admin/Dashboard.tsx b/frontend/src/pages/admin/Dashboard.tsx index 15e812f..2ff5bf2 100644 --- a/frontend/src/pages/admin/Dashboard.tsx +++ b/frontend/src/pages/admin/Dashboard.tsx @@ -131,13 +131,24 @@ const Dashboard: React.FC = () => { const [statsRes, dailyRes, logsRes, auditRes] = await Promise.all([ adminAPI.getStats() as any, (adminAPI as any).getDailyStats?.(30) || Promise.resolve({ data: [] }), - adminAPI.getRecentLogs(), - adminAPI.getRecentAudit(), + adminAPI.getRecentLogs() as any, + adminAPI.getRecentAudit() as any, ]); - if (statsRes.data) setStats(statsRes.data); - if (dailyRes.data) setDailyStats(dailyRes.data || []); - if (logsRes.data) setOperationLogs(Array.isArray(logsRes.data) ? logsRes.data : []); - if (auditRes.data) setAuditLogs(Array.isArray(auditRes.data) ? auditRes.data : []); + // Stats: backend returns raw dict (no {code,data} wrapper) — use .data or the response itself + const statsData = statsRes?.data || statsRes; + if (statsData && typeof statsData === 'object') setStats(statsData); + + // Daily stats: backend returns {code, data: {days, data: [...]}} — access nested .data.data + const dailyData = dailyRes?.data?.data || (Array.isArray(dailyRes?.data) ? dailyRes.data : []) || []; + setDailyStats(Array.isArray(dailyData) ? dailyData : []); + + // Recent logs: backend returns raw list (no wrapper) — use the response directly if it's an array + const logsData = Array.isArray(logsRes) ? logsRes : (Array.isArray(logsRes?.data) ? logsRes.data : []); + setOperationLogs(logsData); + + // Recent audit: same as logs + const auditData = Array.isArray(auditRes) ? auditRes : (Array.isArray(auditRes?.data) ? auditRes.data : []); + setAuditLogs(auditData); } catch (err) { console.error('Dashboard数据加载失败:', err); } finally { diff --git a/frontend/src/pages/admin/ImBots.tsx b/frontend/src/pages/admin/ImBots.tsx index 54b2af9..3d34d55 100644 --- a/frontend/src/pages/admin/ImBots.tsx +++ b/frontend/src/pages/admin/ImBots.tsx @@ -97,9 +97,9 @@ const ImBots: React.FC = () => { // Load agents for dropdowns const loadAgents = useCallback(async () => { try { - const res = await adminAPI.getAIApps('?type=agent&page_size=100'); - if (res.data?.success) { - setAgents(res.data.data?.items || []); + const res = await adminAPI.getAIApps('?type=agent&page_size=100') as any; + if (res?.data) { + setAgents(res.data?.items || []); } } catch (e) { // silently fail @@ -110,7 +110,7 @@ const ImBots: React.FC = () => { const loadStats = useCallback(async () => { try { const res = await adminAPI.getImBotStats(); - if (res.data?.success) setStats(res.data.data); + if ((res as any)?.success) setStats(res.data); } catch (e) { console.error('Stats error:', e); } @@ -120,7 +120,7 @@ const ImBots: React.FC = () => { const loadDashboard = useCallback(async () => { try { const res = await adminAPI.getImBotDashboard(); - if (res.data?.success) setDashboard(res.data.data); + if ((res as any)?.success) setDashboard(res.data); } catch (e) { console.error('Dashboard error:', e); } @@ -134,9 +134,9 @@ const ImBots: React.FC = () => { if (filterPlatform) endpoint += `&platform=${filterPlatform}`; if (filterStatus) endpoint += `&status=${filterStatus}`; const res = await adminAPI.getImBotChannels(endpoint); - if (res.data?.success) { - setChannels(res.data.data?.items || []); - setChannelsTotal(res.data.data?.total || 0); + if ((res as any)?.success) { + setChannels(res.data?.items || []); + setChannelsTotal(res.data?.total || 0); } } catch (e) { showToast('Failed to load channels', 'error'); @@ -149,7 +149,7 @@ const ImBots: React.FC = () => { const loadRoutes = useCallback(async () => { try { const res = await adminAPI.getImBotRoutes(); - if (res.data?.success) setRoutes(res.data.data || []); + if ((res as any)?.success) setRoutes(res.data || []); } catch (e) { showToast('Failed to load routes', 'error'); } @@ -159,8 +159,8 @@ const ImBots: React.FC = () => { const loadLangbot = useCallback(async () => { try { const res = await adminAPI.getLangbotConfig(); - if (res.data?.success) { - const configs = res.data.data || []; + if ((res as any)?.success) { + const configs = res.data || []; setLangbotConfig(configs); if (configs.length > 0) { setLangbotForm({ ...langbotForm, ...configs[0] }); @@ -175,9 +175,9 @@ const ImBots: React.FC = () => { const loadMessages = useCallback(async () => { try { const res = await adminAPI.getImBotMessages(`?page=${messagesPage}&page_size=50`); - if (res.data?.success) { - setMessages(res.data.data?.items || []); - setMessagesTotal(res.data.data?.total || 0); + if ((res as any)?.success) { + setMessages(res.data?.items || []); + setMessagesTotal(res.data?.total || 0); } } catch (e) { showToast('Failed to load messages', 'error'); @@ -188,7 +188,7 @@ const ImBots: React.FC = () => { const loadPlatforms = useCallback(async () => { try { const res = await adminAPI.getImBotPlatforms(); - if (res.data?.success) setPlatforms(res.data.data || []); + if ((res as any)?.success) setPlatforms(res.data || []); } catch (e) { // silently fail } @@ -222,8 +222,8 @@ const ImBots: React.FC = () => { const handleTestChannel = async (channelId: string) => { try { const res = await adminAPI.testImBotChannel(channelId); - if (res.data?.success) { - const data = res.data.data; + if ((res as any)?.success) { + const data = res.data; if (data.passed) { showToast('Channel test passed!'); } else { @@ -256,8 +256,8 @@ const ImBots: React.FC = () => { const handleViewChannel = async (channelId: string) => { try { const res = await adminAPI.getImBotChannelDetail(channelId); - if (res.data?.success) { - setSelectedChannel(res.data.data); + if ((res as any)?.success) { + setSelectedChannel(res.data); setShowChannelDetail(true); } } catch (e) { @@ -269,8 +269,8 @@ const ImBots: React.FC = () => { const handleGetWebhookUrl = async (platform: string) => { try { const res = await adminAPI.getImBotWebhookUrl(platform); - if (res.data?.success) { - setWebhookInfo(res.data.data); + if ((res as any)?.success) { + setWebhookInfo(res.data); setShowWebhookModal(true); } } catch (e) { @@ -328,8 +328,8 @@ const ImBots: React.FC = () => { if (setupForm.agent_id) params.append('agent_id', setupForm.agent_id); if (setupForm.credentials) params.append('credentials', setupForm.credentials); const res = await adminAPI.imBotQuickSetup('?' + params.toString()); - if (res.data?.success) { - const data = res.data.data; + if ((res as any)?.success) { + const data = res.data; setWebhookInfo({ platform: data.platform, platform_name: data.platform_name, @@ -381,8 +381,8 @@ const ImBots: React.FC = () => { }); if (langbotForm.api_key) params.append('api_key', langbotForm.api_key); const res = await adminAPI.testLangbot('?' + params.toString()); - if (res.data?.success) { - const data = res.data.data; + if ((res as any)?.success) { + const data = res.data; if (data.reachable) { showToast(`LangBot reachable! Status: ${data.status_code}`); } else { diff --git a/frontend/src/pages/admin/Knowledge.tsx b/frontend/src/pages/admin/Knowledge.tsx index aa46c1c..239cc5e 100644 --- a/frontend/src/pages/admin/Knowledge.tsx +++ b/frontend/src/pages/admin/Knowledge.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { adminAPI } from '@/api'; interface KnowledgeBaseItem { @@ -25,44 +25,40 @@ interface KnowledgeBaseItem { } interface ExternalService { - id: string; - name: string; - service_type: string; - api_url: string; - api_key?: string; - config?: any; - is_enabled: boolean; - status: string; - created_at?: string; - updated_at?: string; + id: string; name: string; service_type: string; api_url: string; + api_key?: string; config?: any; is_enabled: boolean; status: string; + created_at?: string; updated_at?: string; } interface Stats { - total_knowledge_bases: number; - total_documents: number; - total_storage_bytes: number; - total_storage_display: string; - official_kb: number; - user_kb: number; - active_kb: number; - kb_with_graph: number; - external_kb: number; - recent_kb_7d: number; - total_graph_nodes: number; - total_graph_edges: number; + total_knowledge_bases: number; total_documents: number; + total_storage_bytes: number; total_storage_display: string; + official_kb: number; user_kb: number; active_kb: number; + kb_with_graph: number; external_kb: number; recent_kb_7d: number; + total_graph_nodes: number; total_graph_edges: number; doc_status_distribution: Record; file_type_distribution: Record; } +const SVGIcon: React.FC<{ children: React.ReactNode; size?: number }> = ({ children, size = 16 }) => ( + + {children} + +); + +const formatSize = (bytes: number) => { + if (bytes >= 1024 * 1024 * 1024) return (bytes / 1024 / 1024 / 1024).toFixed(1) + ' GB'; + if (bytes >= 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + ' MB'; + if (bytes >= 1024) return (bytes / 1024).toFixed(1) + ' KB'; + return bytes + ' B'; +}; + const Knowledge: React.FC = () => { - // Data const [knowledgeBases, setKnowledgeBases] = useState([]); const [stats, setStats] = useState(null); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [pageSize] = useState(15); - - // Filters const [keyword, setKeyword] = useState(''); const [filterStatus, setFilterStatus] = useState(''); const [filterOfficial, setFilterOfficial] = useState(''); @@ -70,22 +66,12 @@ const Knowledge: React.FC = () => { // External Services const [extServices, setExtServices] = useState([]); const [showExtModal, setShowExtModal] = useState(false); - const [editingExtService, setEditingExtService] = useState(null); - const [extForm, setExtForm] = useState({ - name: '', - service_type: 'fastgpt', - api_url: '', - api_key: '', - }); + const [extForm, setExtForm] = useState({ name: '', service_type: 'fastgpt', api_url: '', api_key: '' }); // KB Modal const [showModal, setShowModal] = useState(false); const [editingKB, setEditingKB] = useState(null); - const [kbForm, setKBForm] = useState({ - name: '', - description: '', - is_official: false, - }); + const [kbForm, setKBForm] = useState({ name: '', description: '', is_official: false }); // Detail drawer const [showDetail, setShowDetail] = useState(false); @@ -93,24 +79,25 @@ const Knowledge: React.FC = () => { const [detailDocs, setDetailDocs] = useState([]); const [detailDocTotal, setDetailDocTotal] = useState(0); - // Loading + // Document preview + const [previewDoc, setPreviewDoc] = useState(null); + const [previewContent, setPreviewContent] = useState(''); + + // File upload + const [uploading, setUploading] = useState(false); + const [uploadMsg, setUploadMsg] = useState(''); + const fileInputRef = useRef(null); + const [loading, setLoading] = useState(false); useEffect(() => { - fetchStats(); - fetchKBs(); - fetchExtServices(); + fetchStats(); fetchKBs(); fetchExtServices(); }, []); - useEffect(() => { - fetchKBs(); - }, [page, keyword, filterStatus, filterOfficial]); + useEffect(() => { fetchKBs(); }, [page, keyword, filterStatus, filterOfficial]); const fetchStats = async () => { - try { - const res = await adminAPI.getAdminKnowledgeStats(); - if (res.data?.code === 0) setStats(res.data.data); - } catch (e) { console.error(e); } + try { const res = await adminAPI.getAdminKnowledgeStats(); if (res.code === 0) setStats(res.data); } catch (e) {} }; const fetchKBs = async () => { @@ -121,341 +108,259 @@ const Knowledge: React.FC = () => { if (filterStatus) params.status = filterStatus; if (filterOfficial) params.is_official = filterOfficial === 'true'; const res = await adminAPI.getAdminKnowledge(params); - if (res.data?.code === 0) { - setKnowledgeBases(res.data.data.items || []); - setTotal(res.data.data.total || 0); + if (res.code === 0) { + setKnowledgeBases(res.data.items || []); + setTotal(res.data.total || 0); } - } catch (e) { console.error(e); } + } catch (e) {} setLoading(false); }; const fetchExtServices = async () => { - try { - const res = await adminAPI.getAdminKnowledgeExtServices(); - if (res.data?.code === 0) setExtServices(res.data.data.items || []); - } catch (e) { console.error(e); } + try { const res = await adminAPI.getAdminKnowledgeExtServices(); if (res.code === 0) setExtServices(res.data.items || []); } catch (e) {} }; const handleCreateKB = async () => { try { const res = await adminAPI.createAdminKnowledge(kbForm); - if (res.data?.code === 0) { - setShowModal(false); - setKBForm({ name: '', description: '', is_official: false }); - fetchKBs(); - fetchStats(); - } - } catch (e) { console.error(e); } + if (res.code === 0) { setShowModal(false); setKBForm({ name: '', description: '', is_official: false }); fetchKBs(); fetchStats(); } + } catch (e) {} }; const handleUpdateKB = async () => { if (!editingKB) return; try { const res = await adminAPI.updateAdminKnowledge(editingKB.id, kbForm); - if (res.data?.code === 0) { - setShowModal(false); - fetchKBs(); - fetchStats(); - } - } catch (e) { console.error(e); } + if (res.code === 0) { setShowModal(false); fetchKBs(); fetchStats(); } + } catch (e) {} }; const handleDeleteKB = async (id: string) => { if (!confirm('确认删除该知识库及其所有文档?此操作不可恢复!')) return; - try { - const res = await adminAPI.deleteAdminKnowledge(id); - if (res.data?.code === 0) { - fetchKBs(); - fetchStats(); - } - } catch (e) { console.error(e); } + try { const res = await adminAPI.deleteAdminKnowledge(id); if (res.code === 0) { fetchKBs(); fetchStats(); } } catch (e) {} }; const handleToggleKB = async (id: string, isActive: boolean) => { - try { - const res = await adminAPI.toggleAdminKnowledge(id, isActive); - if (res.data?.code === 0) { - fetchKBs(); - fetchStats(); - } - } catch (e) { console.error(e); } + try { const res = await adminAPI.toggleAdminKnowledge(id, isActive); if (res.code === 0) { fetchKBs(); fetchStats(); } } catch (e) {} }; const handleViewDetail = async (kb: KnowledgeBaseItem) => { - setSelectedKB(kb); - setShowDetail(true); + setSelectedKB(kb); setShowDetail(true); try { const res = await adminAPI.getAdminKnowledgeDetail(kb.id); - if (res.data?.code === 0) { - setSelectedKB(res.data.data); - } - const docRes = await adminAPI.getAdminKnowledgeDocs(kb.id, { page: 1, page_size: 20 }); - if (docRes.data?.code === 0) { - setDetailDocs(docRes.data.data.items || []); - setDetailDocTotal(docRes.data.data.total || 0); - } - } catch (e) { console.error(e); } + if (res.code === 0) setSelectedKB(res.data); + const docRes = await adminAPI.getAdminKnowledgeDocs(kb.id, { page: 1, page_size: 50 }); + if (docRes.code === 0) { setDetailDocs(docRes.data.items || []); setDetailDocTotal(docRes.data.total || 0); } + } catch (e) {} }; - const handleCreateExtService = async () => { + // Document upload + const handleFileUpload = async (kbId: string, file: File) => { + setUploading(true); + setUploadMsg(`正在上传 ${file.name}...`); try { - const res = await adminAPI.createAdminKnowledgeExtService(extForm); - if (res.data?.code === 0) { - setShowExtModal(false); - setExtForm({ name: '', service_type: 'fastgpt', api_url: '', api_key: '' }); - fetchExtServices(); + const formData = new FormData(); + formData.append('file', file); + // Use raw fetch since adminAPI may not support FormData + const token = localStorage.getItem('aiflow_token'); + const res = await fetch(`/app/api/admin/knowledge/${kbId}/documents/upload`, { + method: 'POST', + headers: { 'Authorization': `Bearer ${token}` }, + body: formData, + }); + const data = await res.json(); + if (data.code === 0) { + setUploadMsg(`✅ ${file.name} 上传成功!`); + handleViewDetail(selectedKB!); fetchStats(); + } else { + setUploadMsg(`❌ 上传失败: ${data.message || '未知错误'}`); } - } catch (e) { console.error(e); } + } catch (e: any) { + setUploadMsg(`❌ 上传失败: ${e.message || '网络错误'}`); + } finally { + setUploading(false); + setTimeout(() => setUploadMsg(''), 5000); + } }; - const handleDeleteExtService = async (id: string) => { - if (!confirm('确认删除该外部知识库服务配置?')) return; + // Document preview + const handlePreviewDoc = async (kbId: string, doc: any) => { + setPreviewDoc(doc); + setPreviewContent('加载中...'); try { - const res = await adminAPI.deleteAdminKnowledgeExtService(id); - if (res.data?.code === 0) fetchExtServices(); - } catch (e) { console.error(e); } - }; - - const handleCheckExtStatus = async (id: string) => { - try { - const res = await adminAPI.checkAdminKnowledgeExtStatus(id); - if (res.data?.code === 0) { - alert(`连接状态: ${res.data.data.connected ? '✅ 已连接' : '❌ 未连接'}\n详情: ${res.data.data.detail || '无'}`); - fetchExtServices(); + const token = localStorage.getItem('aiflow_token'); + const res = await fetch(`/app/api/admin/knowledge/${kbId}/documents/${doc.id}/content`, { + headers: { 'Authorization': `Bearer ${token}` }, + }); + const data = await res.json(); + if (data.code === 0) { + setPreviewContent(data.data?.content || data.data?.text || '(文档内容为空)'); + } else { + setPreviewContent(data.message || '无法加载文档内容'); } - } catch (e) { console.error(e); } + } catch (e: any) { + setPreviewContent(`加载失败: ${e.message}`); + } }; const handleDeleteDoc = async (kbId: string, docId: string) => { if (!confirm('确认删除该文档?')) return; - try { - const res = await adminAPI.deleteAdminKnowledgeDoc(kbId, docId); - if (res.data?.code === 0) { - handleViewDetail(selectedKB!); - fetchStats(); - } - } catch (e) { console.error(e); } + try { const res = await adminAPI.deleteAdminKnowledgeDoc(kbId, docId); if (res.code === 0) { handleViewDetail(selectedKB!); fetchStats(); } } catch (e) {} }; - const openEditModal = (kb: KnowledgeBaseItem) => { - setEditingKB(kb); - setKBForm({ - name: kb.name, - description: kb.description || '', - is_official: kb.is_official, - }); - setShowModal(true); + const handleCreateExtService = async () => { + try { const res = await adminAPI.createAdminKnowledgeExtService(extForm); if (res.code === 0) { setShowExtModal(false); setExtForm({ name: '', service_type: 'fastgpt', api_url: '', api_key: '' }); fetchExtServices(); } } catch (e) {} }; - const openCreateModal = () => { - setEditingKB(null); - setKBForm({ name: '', description: '', is_official: false }); - setShowModal(true); + const handleDeleteExtService = async (id: string) => { + if (!confirm('确认删除该外部知识库服务配置?')) return; + try { const res = await adminAPI.deleteAdminKnowledgeExtService(id); if (res.code === 0) fetchExtServices(); } catch (e) {} }; - const totalPages = Math.ceil(total / pageSize); + const handleCheckExtStatus = async (id: string) => { + try { const res = await adminAPI.checkAdminKnowledgeExtStatus(id); if (res.code === 0) { alert(`连接状态: ${res.data.connected ? '已连接' : '未连接'}\n详情: ${res.data.detail || '无'}`); fetchExtServices(); } } catch (e) {} + }; - // Status badge const statusBadge = (status: string) => { const map: Record = { - active: { color: '#28a745', text: '活跃' }, - inactive: { color: '#6c757d', text: '禁用' }, - archived: { color: '#ffc107', text: '归档' }, - published: { color: '#28a745', text: '已发布' }, - pending: { color: '#ffc107', text: '待审核' }, + active: { color: '#16A34A', text: '活跃' }, inactive: { color: '#6B7280', text: '禁用' }, + archived: { color: '#F59E0B', text: '归档' }, published: { color: '#16A34A', text: '已发布' }, + pending: { color: '#F59E0B', text: '待审核' }, completed: { color: '#16A34A', text: '完成' }, + processing: { color: '#3B82F6', text: '处理中' }, failed: { color: '#DC2626', text: '失败' }, + uploading: { color: '#3B82F6', text: '上传中' }, }; - const s = map[status] || { color: '#6c757d', text: status }; - return {s.text}; + const s = map[status] || { color: '#6B7280', text: status }; + return {s.text}; }; const syncStatusBadge = (status: string) => { const map: Record = { - none: { color: '#adb5bd', text: '未同步' }, - syncing: { color: '#17a2b8', text: '同步中' }, - synced: { color: '#28a745', text: '已同步' }, - error: { color: '#dc3545', text: '同步错误' }, + none: { color: '#9CA3AF', text: '未同步' }, syncing: { color: '#3B82F6', text: '同步中' }, + synced: { color: '#16A34A', text: '已同步' }, error: { color: '#DC2626', text: '同步错误' }, }; - const s = map[status] || { color: '#adb5bd', text: status }; - return {s.text}; + const s = map[status] || { color: '#9CA3AF', text: status }; + return {s.text}; }; - const extTypeLabel = (type: string) => { - const map: Record = { fastgpt: 'FastGPT', dify: 'Dify', ragflow: 'RAGFlow' }; - return map[type] || type; - }; + const totalPages = Math.ceil(total / pageSize); + + const statCardDefs = stats ? [ + { label: '知识库总数', value: stats.total_knowledge_bases, color: '#6C63FF' }, + { label: '文档总数', value: stats.total_documents, color: '#219B5E' }, + { label: '存储占用', value: stats.total_storage_display, color: '#E8922C' }, + { label: '官方知识库', value: stats.official_kb, color: '#8B5CF6' }, + { label: '含图谱KB', value: stats.kb_with_graph, color: '#3B82F6' }, + { label: '外部集成', value: stats.external_kb, color: '#EC4899' }, + ] : []; return ( -
-

📚 知识库管理

+
+

知识库管理

- {/* Stats Dashboard */} + {/* Stats */} {stats && ( -
-
-
{stats.total_knowledge_bases}
-
知识库总数
-
-
-
{stats.total_documents}
-
文档总数
-
-
-
{stats.total_storage_display}
-
存储占用
-
-
-
{stats.official_kb}
-
官方知识库
-
-
-
{stats.kb_with_graph}
-
含图谱KB
-
-
-
{stats.external_kb}
-
外部集成
-
+
+ {statCardDefs.map((s, i) => ( +
+
+ {typeof s.value === 'number' ? s.value.toLocaleString() : s.value} +
+
{s.label}
+
+ ))}
)} {/* Toolbar */} -
- setKeyword(e.target.value)} - style={{ padding: '8px 12px', border: '1px solid #d9d9d9', borderRadius: '4px', width: '200px' }} - /> - setKeyword(e.target.value)} + style={inputStyle} /> + - setFilterOfficial(e.target.value)} style={selectStyle}> - - + +
- {/* Knowledge Base Table */} - - - - - - - - - - - - - - - {loading ? ( - - ) : knowledgeBases.length === 0 ? ( - - ) : knowledgeBases.map(kb => ( - - - - - - - - - - - ))} - -
名称来源文档数存储状态同步创建时间操作
加载中...
暂无知识库数据
-
{kb.name}
-
{kb.description?.substring(0, 50)}
-
- {kb.is_official ? ( - ⭐ 官方 - ) : ( - 👤 {kb.owner_name || '用户'} - )} - {kb.document_count}{kb.total_storage_mb || 0} MB{statusBadge(kb.status)}{syncStatusBadge(kb.external_sync_status)}{kb.created_at?.substring(0, 10)} - - - - -
+ {/* Table */} +
+
+ + + + + + + + {loading ? ( + + ) : knowledgeBases.length === 0 ? ( + + ) : knowledgeBases.map(kb => ( + + + + + + + + + + + ))} + +
名称来源文档数存储状态同步创建时间操作
加载中...
暂无知识库数据
+
{kb.name}
+ {kb.description &&
{kb.description.substring(0, 60)}
} +
{kb.is_official ? 官方 : {kb.owner_name || '用户'}}{kb.document_count}{kb.total_storage_mb || 0} MB{statusBadge(kb.status)}{syncStatusBadge(kb.external_sync_status)}{kb.created_at?.substring(0, 10)} + + + + +
+
+
{/* Pagination */} {totalPages > 1 && ( -
- - 第 {page} / {totalPages} 页 (共 {total} 条) - +
+ + 第 {page}/{totalPages} 页 (共 {total} 条) +
)} - {/* KB Create/Edit Modal */} + {/* KB Modal */} {showModal && ( -
-
+
setShowModal(false)}> +
e.stopPropagation()}>

{editingKB ? '编辑知识库' : '创建知识库'}

-
- - setKBForm({ ...kbForm, name: e.target.value })} - style={{ width: '100%', padding: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} /> +
+ + setKBForm({ ...kbForm, name: e.target.value })} style={fullInput} />
-
- -