import React, { useCallback, useEffect, useState } from 'react'; import { Layout, Card, Row, Col, Button, Space, Typography, Tag, Tooltip, Modal, Progress, Spin, App, Descriptions, Avatar, Divider, List, Input, Switch, FloatButton, Tabs, Steps, Checkbox, Pagination, Empty } from 'antd'; import { DatabaseOutlined, PlusOutlined, DeleteOutlined, EditOutlined, FileTextOutlined, PictureOutlined, SearchOutlined, ArrowLeftOutlined, ArrowRightOutlined, DatabaseFilled, ClockCircleOutlined, UserOutlined, CalendarOutlined, CheckCircleOutlined, InfoCircleOutlined, ThunderboltOutlined } from '@ant-design/icons'; import { Link, useNavigate } from 'react-router-dom'; import httpService from '../services/httpService'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; import ContentViewer from '../components/ContentViewer'; import ActionButton from '../components/ActionButton'; import tools from '../utils/tools'; const { Title, Text, Paragraph } = Typography; const { Sider, Content } = Layout; const { TextArea } = Input; const KnowledgeBasePage = ({ user }) => { const navigate = useNavigate(); const { message, modal } = App.useApp(); const [kbs, setKbs] = useState([]); const [selectedKb, setSelectedKb] = useState(null); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [showCreateForm, setShowCreateForm] = useState(false); // 创建流程状态 const [meetings, setMeetings] = useState([]); const [selectedMeetings, setSelectedMeetings] = useState([]); const [userPrompt, setUserPrompt] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [selectedTags, setSelectedTags] = useState([]); const [availableTags, setAvailableTags] = useState([]); const [creating, setGenerating] = useState(false); const [createStep, setCreateStep] = useState(0); const [meetingsPagination, setMeetingsPagination] = useState({ page: 1, total: 0 }); const [loadingMeetings, setLoadingMeetings] = useState(false); const [availablePrompts, setAvailablePrompts] = useState([]); const [selectedPromptId, setSelectedPromptId] = useState(null); const [taskProgress, setTaskProgress] = useState(0); const loadKbDetail = useCallback(async (id) => { try { const res = await httpService.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.DETAIL(id))); setSelectedKb(res.data); } catch { message.error('加载知识库详情失败'); } }, [message]); const fetchAllKbs = useCallback(async () => { try { const res = await httpService.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.LIST)); const sorted = (res.data.kbs || []).sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); setKbs(sorted); if (sorted.length > 0 && !selectedKb) { loadKbDetail(sorted[0].kb_id); } } catch { message.error('加载知识库列表失败'); } }, [loadKbDetail, message, selectedKb]); const fetchMeetings = useCallback(async (page = 1) => { setLoadingMeetings(true); try { const params = { user_id: user.user_id, page, search: searchQuery || undefined, tags: selectedTags.length > 0 ? selectedTags.join(',') : undefined }; const res = await httpService.get(buildApiUrl(API_ENDPOINTS.MEETINGS.LIST), { params }); setMeetings(res.data.meetings || []); setMeetingsPagination({ page: res.data.page, total: res.data.total }); } catch { message.error('获取会议列表失败'); } finally { setLoadingMeetings(false); } }, [message, searchQuery, selectedTags, user.user_id]); const fetchAvailableTags = useCallback(async () => { try { const res = await httpService.get(buildApiUrl(API_ENDPOINTS.TAGS.LIST)); setAvailableTags(res.data?.slice(0, 10) || []); } catch { setAvailableTags([]); } }, []); const fetchPrompts = useCallback(async () => { try { const res = await httpService.get(buildApiUrl(API_ENDPOINTS.PROMPTS.ACTIVE('KNOWLEDGE_TASK'))); setAvailablePrompts(res.data.prompts || []); const def = res.data.prompts?.find(p => p.is_default) || res.data.prompts?.[0]; if (def) setSelectedPromptId(def.id); } catch { setAvailablePrompts([]); } }, []); useEffect(() => { fetchAllKbs(); fetchAvailableTags(); }, [fetchAllKbs, fetchAvailableTags]); useEffect(() => { if (showCreateForm && createStep === 0) { fetchMeetings(meetingsPagination.page); } }, [createStep, fetchMeetings, meetingsPagination.page, selectedTags, searchQuery, showCreateForm]); const handleStartCreate = () => { setShowCreateForm(true); setCreateStep(0); setSelectedMeetings([]); fetchPrompts(); }; const handleGenerate = useCallback(async () => { setGenerating(true); setTaskProgress(10); try { const res = await httpService.post(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.CREATE), { user_prompt: userPrompt, source_meeting_ids: selectedMeetings.join(','), prompt_id: selectedPromptId }); const taskId = res.data.task_id; const interval = setInterval(async () => { const statusRes = await httpService.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.TASK_STATUS(taskId))); const s = statusRes.data; setTaskProgress(s.progress || 20); if (s.status === 'completed') { clearInterval(interval); setGenerating(false); setShowCreateForm(false); message.success('知识库生成成功'); fetchAllKbs(); } else if (s.status === 'failed') { clearInterval(interval); setGenerating(false); message.error('生成失败'); } }, 3000); } catch { setGenerating(false); message.error('创建知识库任务失败'); } }, [fetchAllKbs, message, selectedMeetings, selectedPromptId, userPrompt]); const handleDelete = (kb) => { modal.confirm({ title: '删除知识库', content: `确定要删除 "${kb.title}" 吗?此操作无法撤销。`, okText: '删除', okType: 'danger', onOk: async () => { await httpService.delete(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.DELETE(kb.kb_id))); if (selectedKb?.kb_id === kb.kb_id) setSelectedKb(null); fetchAllKbs(); message.success('删除成功'); } }); }; return (
{!sidebarCollapsed && 知识库条目}
(
loadKbDetail(item.kb_id)} style={{ padding: '12px 16px', cursor: 'pointer', background: selectedKb?.kb_id === item.kb_id ? '#e6f4ff' : 'transparent', borderLeft: selectedKb?.kb_id === item.kb_id ? '4px solid #1677ff' : '4px solid transparent', transition: 'all 0.3s' }} >
{item.title} {selectedKb?.kb_id === item.kb_id && ( } onClick={(e) => { e.stopPropagation(); navigate(`/knowledge-base/edit/${item.kb_id}`); }} /> } onClick={(e) => { e.stopPropagation(); handleDelete(item); }} /> )}
{tools.formatShortDate(item.created_at)} {item.source_meeting_count} 会议
)} />
{selectedKb ? ( <>
{selectedKb.title} {selectedKb.tags?.map(t => {t})} {tools.formatDate(selectedKb.created_at)} {selectedKb.created_by_name}
{selectedKb.source_meetings?.map(m => ( }>{m.title} ))} {selectedKb.user_prompt && ( {selectedKb.user_prompt} )} ) : ( )}
setShowCreateForm(false)} footer={null} width={800} > {createStep === 0 ? (
} value={searchQuery} onChange={e => setSearchQuery(e.target.value)} style={{ width: 250 }} /> {availableTags.map(t => ( setSelectedTags(checked ? [...selectedTags, t.name] : selectedTags.filter(x => x !== t.name))} > {t.name} ))}
( { setSelectedMeetings(prev => prev.includes(m.meeting_id) ? prev.filter(id => id !== m.meeting_id) : [...prev, m.meeting_id]) }} />]}> {m.creator_username} {tools.formatMeetingDate(m.created_at)}} /> )} />
setMeetingsPagination({ ...meetingsPagination, page: p })} />
) : (
选择总结模版 {availablePrompts.map(p => ( setSelectedPromptId(p.id)}> {p.name} ))} 附加要求