// dashboard/src/App.tsx import { useState, useEffect, useCallback } from 'react'; import axios from 'axios'; import { Sidebar } from './components/layout/Sidebar'; import { SearchPanel } from './features/search/SearchPanel'; import { LibraryPanel } from './features/library/LibraryPanel'; import { ReaderPanel } from './features/reader/ReaderPanel'; import { CitationPanel } from './features/citation/CitationPanel'; import { SettingsPanel } from './features/settings/SettingsPanel'; import type { StandardPaper, CitationNetwork, NoteRecord } from './types'; export default function App() { const [activeTab, setActiveTab] = useState<'search' | 'library' | 'reader' | 'citation' | 'settings'>('search'); // 共享数据状态 const [library, setLibrary] = useState([]); const [selectedPaper, setSelectedPaper] = useState(null); // 检索页状态 const [searchQuery, setSearchQuery] = useState(''); const [searchSource, setSearchSource] = useState<'all' | 'ads' | 'arxiv'>('all'); const [searchResults, setSearchResults] = useState([]); const [searching, setSearching] = useState(false); const [exportingList, setExportingList] = useState([]); const [bibtexContent, setBibtexContent] = useState(null); const [exporting, setExporting] = useState(false); // 读者页状态 const [englishText, setEnglishText] = useState(''); const [chineseText, setChineseText] = useState(''); const [parsing, setParsing] = useState(false); const [translating, setTranslating] = useState(false); // 引用星系数据状态 const [citationNetwork, setCitationNetwork] = useState(null); const [loadingCitations, setLoadingCitations] = useState(false); const [citationHistory, setCitationHistory] = useState([]); // 多跳历史 // 笔记系统状态 const [notes, setNotes] = useState([]); const [showNotesPanel, setShowNotesPanel] = useState(false); const [newNoteText, setNewNoteText] = useState(''); const [newNoteColor, setNewNoteColor] = useState('yellow'); const [selectedParagraphIdx, setSelectedParagraphIdx] = useState(null); const [selectedText, setSelectedText] = useState(''); // 下载进度状态 const [downloadingBibcodes, setDownloadingBibcodes] = useState>({}); // 1. 初始化时加载本地文献 useEffect(() => { fetchLibrary(); }, []); const fetchLibrary = async () => { try { const res = await axios.get('/api/library'); setLibrary(res.data); } catch (e) { console.error('加载本地文献库失败', e); } }; // 2. 检索文献 const handleSearch = async (e: React.FormEvent) => { e.preventDefault(); if (!searchQuery.trim()) return; setSearching(true); setBibtexContent(null); try { const res = await axios.get('/api/search', { params: { q: searchQuery, source: searchSource, rows: 15 } }); setSearchResults(res.data); } catch (e) { console.error('检索文献失败', e); alert('检索失败,请确认后端连接及 API 密钥配置。'); } finally { setSearching(false); } }; // 3. 触发文献双格式下载 const handleDownload = async (bibcode: string, force = false) => { setDownloadingBibcodes(prev => ({ ...prev, [bibcode]: true })); try { const res = await axios.post('/api/download', { bibcode, force }); // 更新库及检索列表中的状态 setSearchResults(prev => prev.map(p => p.bibcode === bibcode ? res.data : p)); setLibrary(prev => { if (prev.some(p => p.bibcode === bibcode)) { return prev.map(p => p.bibcode === bibcode ? res.data : p); } else { return [res.data, ...prev]; } }); if (selectedPaper?.bibcode === bibcode) { setSelectedPaper(res.data); } } catch (e) { console.error('下载文献失败', e); alert('文献下载失败,请检查 ADS 网络限制与网络代理!'); } finally { setDownloadingBibcodes(prev => ({ ...prev, [bibcode]: false })); } }; // 4. 文献解析成 Markdown (优先 HTML, 其次 PDF MinerU) const handleParse = async (bibcode: string, force = false) => { setParsing(true); try { const res = await axios.post<{ markdown: string }>('/api/parse', { bibcode, force }); setEnglishText(res.data.markdown); // 更新文献状态 setLibrary(prev => prev.map(p => p.bibcode === bibcode ? { ...p, has_markdown: true } : p)); if (selectedPaper?.bibcode === bibcode) { setSelectedPaper(prev => prev ? { ...prev, has_markdown: true } : null); } } catch (e) { console.error('文献解析失败', e); alert('文献排版解析失败,请检查是否已完成 HTML/PDF 下载,并配置了 MinerU API 节点。'); } finally { setParsing(false); } }; // 5. 对比翻译文本 (带天文学术语修正) const handleTranslate = async (bibcode: string, force = false) => { setTranslating(true); try { const res = await axios.post<{ translation: string }>('/api/translate', { bibcode, force }); setChineseText(res.data.translation); setLibrary(prev => prev.map(p => p.bibcode === bibcode ? { ...p, has_translation: true } : p)); if (selectedPaper?.bibcode === bibcode) { setSelectedPaper(prev => prev ? { ...prev, has_translation: true } : null); } } catch (e) { console.error('文献翻译失败', e); alert('翻译失败,请检查 .env 中的大模型 API 密钥与端点配置。'); } finally { setTranslating(false); } }; // 6. 加载文献引用关系网络 const loadCitations = useCallback(async (bibcode: string, reset = false) => { setLoadingCitations(true); try { const res = await axios.get('/api/citations', { params: { bibcode } }); setCitationNetwork(res.data); setCitationHistory(prev => { if (reset) { return [res.data]; } if (prev.some(net => net.bibcode === res.data.bibcode)) { return prev; } return [...prev, res.data]; }); } catch (e) { console.error('加载引用拓扑失败', e); } finally { setLoadingCitations(false); } }, []); // 7. 进入阅读器 const openReader = async (paper: StandardPaper) => { setSelectedPaper(paper); setEnglishText(''); setChineseText(''); setNotes([]); setShowNotesPanel(false); setActiveTab('reader'); // 获取详情 (包含已有原文及翻译) try { const res = await axios.get<{ paper: StandardPaper, english_content?: string, translation_content?: string }>('/api/paper', { params: { bibcode: paper.bibcode } }); if (res.data.english_content) { setEnglishText(res.data.english_content); } if (res.data.translation_content) { setChineseText(res.data.translation_content); } } catch (e) { console.error('加载文献详情失败', e); } // 加载该文献的所有笔记 try { const nRes = await axios.get('/api/notes', { params: { bibcode: paper.bibcode } }); setNotes(nRes.data); } catch (e) { console.error('加载笔记失败', e); } }; // 8. 批量导出 BibTeX const handleExportBibtex = async () => { if (exportingList.length === 0) return; setExporting(true); try { const res = await axios.post<{ bibtex: string }>('/api/export', { bibcodes: exportingList }); setBibtexContent(res.data.bibtex); } catch (e) { console.error('导出 BibTeX 失败', e); alert('导出 BibTeX 失败,请检查 ADS Token。'); } finally { setExporting(false); } }; // 批量选择引文 const toggleExportItem = (bibcode: string) => { setExportingList(prev => prev.includes(bibcode) ? prev.filter(b => b !== bibcode) : [...prev, bibcode] ); }; // 笔记相关操作 const handleCreateNote = async () => { if (!selectedPaper || selectedParagraphIdx === null || !newNoteText.trim()) return; try { const res = await axios.post('/api/notes', { bibcode: selectedPaper.bibcode, paragraph_index: selectedParagraphIdx, note_text: newNoteText.trim(), highlight_color: newNoteColor, selected_text: selectedText, }); setNotes(prev => [...prev, res.data]); setNewNoteText(''); setSelectedParagraphIdx(null); setSelectedText(''); } catch (e) { console.error('保存笔记失败', e); } }; const handleDeleteNote = async (id: number) => { try { await axios.delete('/api/notes', { params: { id } }); setNotes(prev => prev.filter(n => n.id !== id)); } catch (e) { console.error('删除笔记失败', e); } }; // 选中文本时弹出笔记添加面板 const handleTextSelection = (paragraphIdx: number) => { const sel = window.getSelection(); if (sel && sel.toString().trim().length > 3) { setSelectedText(sel.toString().trim()); setSelectedParagraphIdx(paragraphIdx); setShowNotesPanel(true); } }; return (
{/* 炫酷淡雅背景装饰 */}
{/* 导航左侧栏 */} {/* 主工作区 */}
{/* 顶部状态条 */}
后端服务连接正常
文献库: {library.length} 篇
{/* 选项卡容器 */}
{activeTab === 'search' && ( )} {activeTab === 'library' && ( )} {activeTab === 'reader' && selectedPaper && ( )} {activeTab === 'citation' && ( )} {activeTab === 'settings' && ( )}
); }