import React, { useState, useEffect } from 'react'; import { Database, ChevronLeft, ChevronRight, Plus, Calendar, Trash2, Edit, FileText, Image, X } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import apiClient from '../utils/apiClient'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; import ContentViewer from '../components/ContentViewer'; import TagDisplay from '../components/TagDisplay'; import Toast from '../components/Toast'; import ConfirmDialog from '../components/ConfirmDialog'; import FormModal from '../components/FormModal'; import StepIndicator from '../components/StepIndicator'; import SimpleSearchInput from '../components/SimpleSearchInput'; import Breadcrumb from '../components/Breadcrumb'; import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize from 'rehype-sanitize'; import exportService from '../services/exportService'; import tools from '../utils/tools'; import PageLoading from '../components/PageLoading'; import meetingCacheService from '../services/meetingCacheService'; import './KnowledgeBasePage.css'; const KnowledgeBasePage = ({ user }) => { const navigate = useNavigate(); const [kbs, setKbs] = useState([]); const [loading, setLoading] = useState(true); 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 [generating, setGenerating] = useState(false); const [taskId, setTaskId] = useState(null); const [progress, setProgress] = useState(0); const [deleteConfirmInfo, setDeleteConfirmInfo] = useState(null); const [toasts, setToasts] = useState([]); const [createStep, setCreateStep] = useState(1); // 1: 选择会议, 2: 输入提示词 const [meetingsPagination, setMeetingsPagination] = useState({ page: 1, total: 0, has_more: false }); const [loadingMeetings, setLoadingMeetings] = useState(false); const [availablePrompts, setAvailablePrompts] = useState([]); // 可用的提示词模版列表 const [selectedPromptId, setSelectedPromptId] = useState(null); // 选中的提示词模版ID // Toast helper functions const showToast = (message, type = 'info') => { const id = Date.now(); setToasts(prev => [...prev, { id, message, type }]); }; const removeToast = (id) => { setToasts(prev => prev.filter(toast => toast.id !== id)); }; useEffect(() => { fetchAllKbs(); fetchAllTagsForFilter(); // 获取标签云数据 }, []); // 当搜索或标签过滤变化时,重新加载第一页 useEffect(() => { if (showCreateForm) { fetchMeetings(1); } }, [searchQuery, selectedTags, showCreateForm]); useEffect(() => { if (taskId) { const interval = setInterval(() => { apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.TASK_STATUS(taskId))) .then(response => { const { status, progress } = response.data; setProgress(progress || 0); if (status === 'completed') { clearInterval(interval); setTaskId(null); setGenerating(false); setProgress(0); setUserPrompt(''); setSelectedMeetings([]); setShowCreateForm(false); setCreateStep(1); // 重置步骤 setSearchQuery(''); setSelectedTags([]); fetchAllKbs(); } else if (status === 'failed') { clearInterval(interval); setTaskId(null); setGenerating(false); setProgress(0); showToast('知识库生成失败,请稍后重试', 'error'); } }) .catch(error => { console.error("Error fetching task status:", error); clearInterval(interval); setTaskId(null); setGenerating(false); setProgress(0); }); }, 2000); return () => clearInterval(interval); } }, [taskId]); const fetchAllKbs = () => { setLoading(true); // 获取所有知识库(个人和共享) apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.LIST)) .then(response => { // 按创建时间倒序排序 const sortedKbs = response.data.kbs.sort((a, b) => new Date(b.created_at) - new Date(a.created_at) ); setKbs(sortedKbs); // 如果有知识库且没有选中,默认选中第一个 if (sortedKbs.length > 0 && !selectedKb) { loadKbDetail(sortedKbs[0].kb_id); } setLoading(false); }) .catch(error => { console.error("Error fetching knowledge bases:", error); setLoading(false); }); }; const fetchMeetings = async (page = 1) => { try { // 生成当前过滤器的键(不包含filterType,因为知识库这里不需要) const filterKey = meetingCacheService.generateFilterKey('all', searchQuery, selectedTags); // 先检查缓存 const cachedPage = meetingCacheService.getPage(filterKey, page); if (cachedPage) { console.log('Using cached page:', page, 'for filter:', filterKey); setMeetings(cachedPage.meetings); setMeetingsPagination(cachedPage.pagination); return; } // 没有缓存,从服务器获取 setLoadingMeetings(true); const params = { user_id: user.user_id, page: page, search: searchQuery || undefined, tags: selectedTags.length > 0 ? selectedTags.join(',') : undefined }; const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.LIST), { params }); const newMeetings = response.data.meetings || []; const newPagination = { page: response.data.page, total: response.data.total, has_more: response.data.has_more }; // 缓存当前页数据 meetingCacheService.setPage(filterKey, page, newMeetings, newPagination); setMeetings(newMeetings); setMeetingsPagination(newPagination); } catch (error) { console.error("Error fetching meetings:", error); setMeetings([]); } finally { setLoadingMeetings(false); } }; // 获取所有标签用于过滤器 const fetchAllTagsForFilter = async () => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.TAGS.LIST)); const allTags = response.data || []; // 取前6个热门标签 const topSixTags = allTags.slice(0, 6).map(tag => tag.name); setAvailableTags(topSixTags); } catch (error) { console.error("Error fetching tags:", error); } }; // 获取知识库任务的启用提示词模版列表 const fetchAvailablePrompts = async () => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.PROMPTS.ACTIVE('KNOWLEDGE_TASK'))); const promptsList = response.data.prompts || []; setAvailablePrompts(promptsList); // 自动选中默认模版 const defaultPrompt = promptsList.find(p => p.is_default); if (defaultPrompt) { setSelectedPromptId(defaultPrompt.id); } } catch (error) { console.error("Error fetching available prompts:", error); setAvailablePrompts([]); } }; const loadKbDetail = async (kbId) => { try { const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.DETAIL(kbId))); setSelectedKb(response.data); } catch (error) { console.error("Error loading knowledge base detail:", error); } }; const handleKbSelect = (kb) => { loadKbDetail(kb.kb_id); }; const handleGenerate = async () => { if (!selectedMeetings || selectedMeetings.length === 0) { showToast('请至少选择一个会议', 'warning'); return; } setGenerating(true); try { const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.CREATE), { user_prompt: userPrompt, source_meeting_ids: selectedMeetings.join(','), is_shared: false, prompt_id: selectedPromptId // 传递选中的模版ID }); setTaskId(response.data.task_id); } catch (error) { console.error("Error creating knowledge base:", error); setGenerating(false); } }; const toggleMeetingSelection = (meetingId) => { setSelectedMeetings(prev => prev.includes(meetingId) ? prev.filter(id => id !== meetingId) : [...prev, meetingId] ); }; const handlePageChange = (newPage) => { fetchMeetings(newPage); }; const handleTagToggle = (tag) => { setSelectedTags(prev => prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag] ); }; const clearFilters = () => { setSearchQuery(''); setSelectedTags([]); }; const handleOpenCreateModal = () => { setCreateStep(1); setSelectedMeetings([]); setUserPrompt(''); setSearchQuery(''); setSelectedTags([]); setSelectedPromptId(null); setShowCreateForm(true); // 获取可用的提示词模版 fetchAvailablePrompts(); }; const handleCloseCreateModal = () => { setShowCreateForm(false); setCreateStep(1); setSelectedMeetings([]); setUserPrompt(''); setSearchQuery(''); setSelectedTags([]); }; const handleNextStep = () => { if (selectedMeetings.length === 0) { showToast('请至少选择一个会议', 'warning'); return; } setCreateStep(2); }; const handlePrevStep = () => { setCreateStep(1); }; const handleDelete = async (kb) => { setDeleteConfirmInfo({ kb_id: kb.kb_id, title: kb.title }); }; const confirmDelete = async () => { try { await apiClient.delete(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.DELETE(deleteConfirmInfo.kb_id))); // 如果删除的是当前选中的,清除选中 if (selectedKb && selectedKb.kb_id === deleteConfirmInfo.kb_id) { setSelectedKb(null); } setDeleteConfirmInfo(null); fetchAllKbs(); } catch (error) { console.error("Error deleting knowledge base:", error); showToast('删除失败,请稍后重试', 'error'); setDeleteConfirmInfo(null); } }; const groupKbsByDate = (kbList) => { const todayKbs = []; const pastKbs = []; kbList.forEach(kb => { if (tools.isToday(kb.created_at)) { todayKbs.push(kb); } else { pastKbs.push(kb); } }); return { todayKbs, pastKbs }; }; // 导出知识库内容为图片 const exportSummaryToImage = async () => { try { if (!selectedKb?.content) { showToast('暂无知识库内容,请稍后再试。', 'warning'); return; } const createdAt = tools.formatDate(selectedKb.created_at); const tags = selectedKb.tags?.join('、') || ''; await exportService.exportKnowledgeBaseToImage({ title: selectedKb.title || '知识库', content: selectedKb.content, metadata: { creator: selectedKb.created_by_name || '未知', createdTime: createdAt, tags: tags, sourceMeetings: selectedKb.source_meetings?.length || 0 } }); showToast('内容已成功导出为图片', 'success'); } catch (error) { console.error('图片导出失败:', error); showToast('图片导出失败,请重试。', 'error'); } }; // 导出思维导图为图片 const exportMindMapToImage = async () => { try { if (!selectedKb?.content) { showToast('暂无内容,无法导出思维导图。', 'warning'); return; } await exportService.exportMindMapToImage({ title: selectedKb.title || '知识库' }); showToast('思维导图已成功导出为图片', 'success'); } catch (error) { console.error('思维导图导出失败:', error); showToast(error.message || '思维导图导出失败,请重试。', 'error'); } }; const isCreator = selectedKb && user && String(selectedKb.creator_id) === String(user.user_id); if (loading) { return ; } return (
{/* 左侧知识库列表 */}
{!sidebarCollapsed &&

知识库列表

}
{!sidebarCollapsed && ( )}
{!sidebarCollapsed && (
{kbs.length === 0 ? (

暂无知识库条目

) : ( (() => { const { todayKbs, pastKbs } = groupKbsByDate(kbs); return ( <> {/* 今天的知识库 */} {todayKbs.length > 0 && (
今天
{todayKbs.map(kb => (
handleKbSelect(kb)} >

{kb.title}

{String(kb.creator_id) === String(user.user_id) && (
)}
{tools.formatTime(kb.created_at)} {kb.source_meeting_count || 0} 个数据源
))}
)} {/* 之前的知识库 */} {pastKbs.length > 0 && (
之前
{pastKbs.map(kb => (
handleKbSelect(kb)} >

{kb.title}

{String(kb.creator_id) === String(user.user_id) && (
)}
{tools.formatShortDate(kb.created_at)} {kb.source_meeting_count || 0} 个数据源
))}
)} ); })() )}
)}
{/* 右侧详情区 */}
{selectedKb ? ( <>

{selectedKb.title} {selectedKb.tags && selectedKb.tags.length > 0 && ( tag.name)} size="medium" showIcon={true} className="inline-title-tags" /> )}

{selectedKb.created_by_name && ( 创建者: {selectedKb.created_by_name} )} {tools.formatDate(selectedKb.created_at)} {selectedKb.source_meetings && selectedKb.source_meetings.length > 0 && ( {selectedKb.source_meetings.length} 个数据源 )}
{/* 用户提示词 */} {selectedKb.user_prompt && (
用户提示词: {selectedKb.user_prompt}
)} {/* 数据源列表 */} {selectedKb.source_meetings && selectedKb.source_meetings.length > 0 && (

数据源列表

{selectedKb.source_meetings.map(meeting => ( {meeting.title} ))}
)} {/* 内容区域 - Tabs */}
导出图片 ) } mindmapActions={ selectedKb.content && ( ) } />
) : (

请从左侧选择一个知识库查看详情

)}
{/* 新增知识库表单弹窗 */} } actions={ <> {createStep === 1 ? ( <> ) : ( <> )} } > {/* 步骤 1: 选择会议 */} {createStep === 1 && (
{/* 紧凑的搜索和过滤区 */}
{availableTags.length > 0 && (
{availableTags.map(tag => ( ))}
)} {(searchQuery || selectedTags.length > 0) && ( )}
{loadingMeetings ? (

加载中...

) : meetings.length === 0 ? (

未找到匹配的会议

) : ( meetings.map(meeting => (
toggleMeetingSelection(meeting.meeting_id)} > { e.stopPropagation(); toggleMeetingSelection(meeting.meeting_id); }} onClick={(e) => e.stopPropagation()} />
{meeting.title}
{meeting.creator_username && ( 创建人: {meeting.creator_username} )} {meeting.created_at && ( 创建时间: {tools.formatMeetingDate(meeting.created_at)} )}
)) )}
{/* 分页按钮 */} {!loadingMeetings && meetings.length > 0 && (
第 {meetingsPagination.page} 页 · 共 {meetingsPagination.total} 条
)}
)} {/* 步骤 2: 输入提示词 */} {createStep === 2 && (
已选择会议: {selectedMeetings.length} 个
{/* 模版选择 */} {availablePrompts.length > 0 && (
)}

您可以添加额外的要求来定制知识库生成内容,例如重点关注某个主题、提取特定信息等。如不填写,系统将使用默认提示词。