import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import type { ImportPreviewResult, ImportSplitMode } from '@shared/types' import { useAppStore } from '@renderer/stores/useAppStore' import { useBookStore } from '@renderer/stores/useBookStore' import { useTabStore } from '@renderer/stores/useTabStore' import { ipcCall } from '@renderer/lib/ipc-client' const LARGE_FILE_BYTES = 20 * 1024 * 1024 function formatFileSize(bytes: number): string { if (bytes < 1024) return `${bytes} B` if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB` return `${(bytes / (1024 * 1024)).toFixed(1)} MB` } export function ImportBookModal(): React.JSX.Element | null { const { t } = useTranslation() const open = useAppStore((s) => s.importBookModalOpen) const setOpen = useAppStore((s) => s.setImportBookModalOpen) const showToast = useAppStore((s) => s.showToast) const setView = useAppStore((s) => s.setView) const { loadBooks, openBook } = useBookStore() const { openBookTab } = useTabStore() const [filePath, setFilePath] = useState('') const [fileName, setFileName] = useState('') const [bookName, setBookName] = useState('') const [category, setCategory] = useState('玄幻') const [splitMode, setSplitMode] = useState('auto') const [customRegex, setCustomRegex] = useState('') const [preview, setPreview] = useState(null) const [importing, setImporting] = useState(false) const [progress, setProgress] = useState<{ current: number; total: number } | null>(null) useEffect(() => { if (!open) return setFilePath('') setFileName('') setBookName('') setCategory('玄幻') setSplitMode('auto') setCustomRegex('') setPreview(null) setProgress(null) }, [open]) useEffect(() => { if (!open) return const unsub = window.electronAPI.onImportProgress((payload) => { setProgress(payload) }) return unsub }, [open]) const handlePickFile = async (): Promise => { const result = await ipcCall(() => window.electronAPI.import.pickFile()) if (result.canceled || !result.filePath) return setFilePath(result.filePath) const name = result.filePath.split(/[/\\]/).pop() ?? '' setFileName(name) const baseName = name.replace(/\.(txt|md|docx)$/i, '') if (!bookName.trim()) setBookName(baseName) } const handlePreview = async (): Promise => { if (!filePath) return const result = await ipcCall(() => window.electronAPI.import.preview(filePath, splitMode, customRegex || undefined) ) setPreview(result) } const handleImport = async (): Promise => { if (!filePath || !bookName.trim()) return if (preview && preview.fileSizeBytes > LARGE_FILE_BYTES) { const ok = confirm( t('import.largeFileConfirm', { size: formatFileSize(preview.fileSizeBytes) }) ) if (!ok) return } setImporting(true) setProgress(null) try { if (!preview) await handlePreview() const bookId = await ipcCall(() => window.electronAPI.import.execute({ filePath, bookName: bookName.trim(), category, splitMode, customRegex: customRegex || undefined }) ) await loadBooks() const meta = useBookStore.getState().books.find((b) => b.id === bookId) await openBook(bookId) openBookTab(bookId, meta?.name ?? bookName.trim()) setView('editor') showToast(t('import.success')) setOpen(false) } catch { showToast(t('import.failed')) } finally { setImporting(false) setProgress(null) } } if (!open) return null const previewTitles = preview?.chapters.slice(0, 5).map((ch) => ch.title) ?? [] return (