import React, { useEffect, useState, useCallback, useMemo } from 'react' import { ArrowLeft, Plus, MessageSquare, BookOpen, Trash2, Eye, FileText, FileType, Image as ImageIcon, Search, RefreshCw } from 'lucide-react' import { KnowledgeGroup, KnowledgeFile } from '../../types' import { knowledgeBaseService } from '../../services/knowledgeBaseService' import { modelConfigService } from '../../services/modelConfigService' import { uploadService } from '../../services/uploadService' import { knowledgeGroupService } from '../../services/knowledgeGroupService' import { useToast } from '../../contexts/ToastContext' import { useConfirm } from '../../contexts/ConfirmContext' import { RawFile, IndexingConfig, ModelConfig } from '../../types' import IndexingModalWithMode from '../IndexingModalWithMode' import { PDFPreview } from '../PDFPreview' import { NotebookGlobalDragDropOverlay } from '../NotebookGlobalDragDropOverlay' import { useLanguage } from '../../contexts/LanguageContext' import { readFile, formatBytes } from '../../utils/fileUtils' import { isExtensionAllowed, isFormatSupportedForPreview } from '../../constants/fileSupport' import { motion, AnimatePresence } from 'framer-motion' interface NotebookDetailViewProps { authToken: string; notebook: KnowledgeGroup; onBack: () => void; onChatWithContext?: (context: { selectedGroups?: string[], selectedFiles?: string[] }) => void; isAdmin?: boolean; } export const NotebookDetailView: React.FC = ({ authToken, notebook, onBack, onChatWithContext, isAdmin = false }) => { const [files, setFiles] = useState([]) const [isLoading, setIsLoading] = useState(false) const { showError, showSuccess } = useToast() const { confirm } = useConfirm() const { t } = useLanguage() const [isIndexingModalOpen, setIsIndexingModalOpen] = useState(false) const [pendingFiles, setPendingFiles] = useState([]) const [shouldOpenModal, setShouldOpenModal] = useState(false) const [models, setModels] = useState([]) const [pdfPreview, setPdfPreview] = useState<{ fileId: string; fileName: string } | null>(null) const [filterName, setFilterName] = useState('') const fileInputRef = React.useRef(null) useEffect(() => { const fetchModels = async () => { try { const res = await modelConfigService.getAll(authToken) setModels(res) } catch (error) { console.error('Failed to fetch models', error) } } if (authToken) fetchModels() }, [authToken]) useEffect(() => { if (shouldOpenModal && pendingFiles.length > 0) { setIsIndexingModalOpen(true); setShouldOpenModal(false); } }, [shouldOpenModal, pendingFiles.length]); const loadData = useCallback(async () => { setIsLoading(true) try { const allFiles = await knowledgeBaseService.getAll(authToken) const notebookFiles = allFiles.filter(f => f.groups?.some(g => g.id === notebook.id)) setFiles(notebookFiles) } catch (error) { console.error(error) showError(t('errorLoadData')) } finally { setIsLoading(false) } }, [authToken, notebook.id, t, showError]) useEffect(() => { loadData() }, [loadData]) const handleFileUpload = async (fileList: FileList | File[]) => { if (!fileList || fileList.length === 0) return const errors: string[] = [] const newPendingFiles: RawFile[] = [] for (let i = 0; i < fileList.length; i++) { const file = fileList[i] try { if (file.size > 104857600) { errors.push(`${file.name} - ${t('fileSizeLimitExceeded', file.name, formatBytes(file.size), 100)}`) continue } const extension = file.name.split('.').pop() || '' if (!isExtensionAllowed(extension, 'group')) { if (!(await confirm(t('confirmUnsupportedFile', extension || t('unknown'))))) continue } const rawFile = await readFile(file) newPendingFiles.push(rawFile) } catch (error: any) { errors.push(`${file.name} - ${t('readingFailed')}`) } } if (errors.length > 0) showError(`${t('uploadErrors')}:\n${errors.join('\n')}`) if (newPendingFiles.length > 0) { setPendingFiles(prev => [...prev, ...newPendingFiles]) setShouldOpenModal(true) } } const handleConfirmIndexing = async (config: IndexingConfig) => { setIsIndexingModalOpen(false) try { for (const rawFile of pendingFiles) { const uploadRes = await uploadService.uploadFileWithConfig(rawFile.file, config, authToken) if (uploadRes && uploadRes.id) { await knowledgeGroupService.addFileToGroups(uploadRes.id, [notebook.id]) } } showSuccess(t('successUploadFile')) loadData() } catch (error: any) { showError(t('errorUploadFile', error.message || t('unknownError'))) } finally { setPendingFiles([]) } } const handleRemoveFile = async (fileId: string, fileName: string) => { if (!(await confirm(t('confirmRemoveFileFromGroup', fileName)))) return; try { await knowledgeGroupService.removeFileFromGroup(fileId, notebook.id); setFiles(prev => prev.filter(f => f.id !== fileId)); showSuccess(t('fileDeleted')); } catch (error) { showError(t('deleteFailed')); } } const filteredFiles = useMemo(() => { return files.filter(file => file.name.toLowerCase().includes(filterName.toLowerCase())); }, [files, filterName]); const getFileIcon = (file: KnowledgeFile) => { if (file.type.startsWith('image/')) return ; if (file.type === 'application/pdf') return ; return ; }; return (
e.target.files && handleFileUpload(e.target.files)} multiple className="hidden" /> {/* Header */}

{notebook.name}

{notebook.description || t('browseManageFiles')}

{isAdmin && ( )}
{/* Filter Bar */}
setFilterName(e.target.value)} className="w-full h-9 pl-9 pr-4 bg-white border border-slate-200 rounded-lg text-sm transition-all focus:ring-1 focus:ring-blue-500 focus:border-blue-500 outline-none" />
{/* Content Area */}
{isLoading ? (
) : filteredFiles.length > 0 ? (
{filteredFiles.map((file) => ( {/* Icon */}
{getFileIcon(file)}
{/* Main info */}

{file.name}

{file.status}
{/* Meta info & Actions */}
{formatBytes(file.size)} {file.name.split('.').pop()?.toUpperCase() || 'FILE'}
{isFormatSupportedForPreview(file.name) && ( )} {isAdmin && ( )}
))}
) : (

{t('noFiles')}

{t('noFilesDesc')}

)}
{ setPendingFiles([]); setIsIndexingModalOpen(false); }} files={pendingFiles} embeddingModels={models.filter(m => m.type === 'embedding')} defaultEmbeddingId={models.find(m => m.isDefault)?.id || ''} onConfirm={handleConfirmIndexing} authToken={authToken} /> {pdfPreview && ( setPdfPreview(null)} /> )}
) }