11"use client" ;
22
33import { useRouter , useSearchParams } from "next/navigation" ;
4- import { useEffect , useState , useRef , useCallback } from "react" ;
4+ import { useEffect , useState , useRef , useCallback , useMemo } from "react" ;
55import { UploadCloud , BookOpenCheck , XCircle , AlertTriangle } from "lucide-react" ;
66
77import DashboardShell from "@/components/layout/dashboard-shell" ;
@@ -41,6 +41,7 @@ export default function SmartReadingPage() {
4141 const [ draftPaneRatio , setDraftPaneRatio ] = useState < number | null > ( null ) ;
4242 const [ renderPaused , setRenderPaused ] = useState ( false ) ;
4343 const renderResumeTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
44+ const sidePanelModeRef = useRef < "chat" | "note" > ( "chat" ) ;
4445 const scheduleRenderResume = useCallback (
4546 ( delay = 0 ) => {
4647 if ( renderResumeTimerRef . current ) {
@@ -74,21 +75,22 @@ export default function SmartReadingPage() {
7475
7576 const handleCopyToNote = useCallback (
7677 ( content : string ) => {
77- if ( sidePanelMode !== "note" ) {
78+ const mode = sidePanelModeRef . current ;
79+ if ( mode !== "note" ) {
7880 pendingNoteAppendRef . current = content ;
7981 setSidePanelMode ( "note" ) ;
8082 } else {
8183 noteEditorPanelRef . current ?. appendContent ( content ) ;
8284 }
8385 } ,
84- [ sidePanelMode ] ,
8586 ) ;
8687
8788 useEffect ( ( ) => {
8889 if ( sidePanelMode === "note" && pendingNoteAppendRef . current ) {
8990 noteEditorPanelRef . current ?. appendContent ( pendingNoteAppendRef . current ) ;
9091 pendingNoteAppendRef . current = null ;
9192 }
93+ sidePanelModeRef . current = sidePanelMode ;
9294 } , [ sidePanelMode ] ) ;
9395
9496 const handleClosePdf = useCallback ( ( ) => {
@@ -210,7 +212,7 @@ export default function SmartReadingPage() {
210212 void preloadLibraryPaper ( numericId ) ;
211213 } , [ preloadLibraryPaper , searchParams ] ) ;
212214
213- const handlePdfError = ( error : string ) => {
215+ const handlePdfError = useCallback ( ( error : string ) => {
214216 const isBenignTransportError = / t r a n s p o r t d e s t r o y e d / i. test ( error ) ;
215217 const isBenignCallbackError = / c a n n o t r e s o l v e c a l l b a c k / i. test ( error ) ;
216218 if ( isBenignTransportError || isBenignCallbackError ) {
@@ -219,68 +221,71 @@ export default function SmartReadingPage() {
219221 }
220222 console . error ( "PDF加载错误:" , error ) ;
221223 setIsParsing ( false ) ;
222- } ;
224+ } , [ ] ) ;
223225
224- const handlePdfLoad = ( ) => {
226+ const handlePdfLoad = useCallback ( ( ) => {
225227 console . log ( "PDF加载成功" ) ;
226- } ;
228+ } , [ ] ) ;
227229
228230 /**
229231 * Store the most recent MinerU payload for downstream tooling (e.g. summaries).
230232 */
231- const handleMinerUParseComplete = ( result : MinerUParseResult ) => {
233+ const handleMinerUParseComplete = useCallback ( ( result : MinerUParseResult ) => {
232234 console . log ( "MinerU 解析完成:" , result ) ;
233235 setMineruResult ( result ) ;
234- } ;
236+ } , [ ] ) ;
235237
236- const uploadControls = (
237- < div className = "flex flex-wrap items-center gap-2 text-sm text-slate-700 dark:text-slate-200" >
238- < button
239- type = "button"
240- onClick = { ( ) => fileInputRef . current ?. click ( ) }
241- disabled = { isUploading }
242- className = "flex items-center gap-2 rounded-lg border border-blue-200 bg-blue-50/80 px-3 py-2 font-medium text-blue-700 transition hover:border-blue-300 hover:bg-blue-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-blue-800/60 dark:bg-blue-900/30 dark:text-blue-300 dark:hover:border-blue-700 dark:hover:bg-blue-900/50"
243- >
244- { isUploading ? (
245- < span className = "flex items-center gap-2" >
246- < span className = "h-4 w-4 animate-spin rounded-full border-2 border-blue-600 border-t-transparent" > </ span >
247- 上传中...
248- </ span >
249- ) : (
250- < span className = "flex items-center gap-2" >
251- < UploadCloud className = "h-4 w-4" aria-hidden = "true" />
252- 上传PDF文件
253- </ span >
254- ) }
255- </ button >
256-
257- < button
258- type = "button"
259- onClick = { ( ) => router . push ( "/library" ) }
260- className = "flex items-center gap-2 rounded-lg border border-slate-200 bg-white/80 px-3 py-2 font-medium text-slate-700 transition hover:border-slate-300 hover:bg-white dark:border-slate-600 dark:bg-slate-800/70 dark:text-slate-200 dark:hover:border-slate-500"
261- >
262- < BookOpenCheck className = "h-4 w-4" aria-hidden = "true" />
263- 从文库选择
264- </ button >
265-
266- { selectedPdfUrl && (
238+ const uploadControls = useMemo (
239+ ( ) => (
240+ < div className = "flex flex-wrap items-center gap-2 text-sm text-slate-700 dark:text-slate-200" >
267241 < button
268242 type = "button"
269- onClick = { handleClosePdf }
270- className = "flex items-center gap-2 rounded-lg border border-red-200 bg-red-50/80 px-3 py-2 font-medium text-red-700 transition hover:border-red-300 hover:bg-red-100 dark:border-red-800/60 dark:bg-red-900/30 dark:text-red-300 dark:hover:border-red-700 dark:hover:bg-red-900/50"
243+ onClick = { ( ) => fileInputRef . current ?. click ( ) }
244+ disabled = { isUploading }
245+ className = "flex items-center gap-2 rounded-lg border border-blue-200 bg-blue-50/80 px-3 py-2 font-medium text-blue-700 transition hover:border-blue-300 hover:bg-blue-100 disabled:cursor-not-allowed disabled:opacity-50 dark:border-blue-800/60 dark:bg-blue-900/30 dark:text-blue-300 dark:hover:border-blue-700 dark:hover:bg-blue-900/50"
271246 >
272- < XCircle className = "h-4 w-4" aria-hidden = "true" />
273- 关闭PDF
247+ { isUploading ? (
248+ < span className = "flex items-center gap-2" >
249+ < span className = "h-4 w-4 animate-spin rounded-full border-2 border-blue-600 border-t-transparent" > </ span >
250+ 上传中...
251+ </ span >
252+ ) : (
253+ < span className = "flex items-center gap-2" >
254+ < UploadCloud className = "h-4 w-4" aria-hidden = "true" />
255+ 上传PDF文件
256+ </ span >
257+ ) }
274258 </ button >
275- ) }
276-
277- { uploadError && (
278- < span className = "flex items-center gap-1 rounded-lg border border-red-200 bg-red-50/80 px-3 py-1 text-xs text-red-700 dark:border-red-800/60 dark:bg-red-900/30 dark:text-red-300" >
279- < AlertTriangle className = "h-3.5 w-3.5" aria-hidden = "true" />
280- { uploadError }
281- </ span >
282- ) }
283- </ div >
259+
260+ < button
261+ type = "button"
262+ onClick = { ( ) => router . push ( "/library" ) }
263+ className = "flex items-center gap-2 rounded-lg border border-slate-200 bg-white/80 px-3 py-2 font-medium text-slate-700 transition hover:border-slate-300 hover:bg-white dark:border-slate-600 dark:bg-slate-800/70 dark:text-slate-200 dark:hover:border-slate-500"
264+ >
265+ < BookOpenCheck className = "h-4 w-4" aria-hidden = "true" />
266+ 从文库选择
267+ </ button >
268+
269+ { selectedPdfUrl && (
270+ < button
271+ type = "button"
272+ onClick = { handleClosePdf }
273+ className = "flex items-center gap-2 rounded-lg border border-red-200 bg-red-50/80 px-3 py-2 font-medium text-red-700 transition hover:border-red-300 hover:bg-red-100 dark:border-red-800/60 dark:bg-red-900/30 dark:text-red-300 dark:hover:border-red-700 dark:hover:bg-red-900/50"
274+ >
275+ < XCircle className = "h-4 w-4" aria-hidden = "true" />
276+ 关闭PDF
277+ </ button >
278+ ) }
279+
280+ { uploadError && (
281+ < span className = "flex items-center gap-1 rounded-lg border border-red-200 bg-red-50/80 px-3 py-1 text-xs text-red-700 dark:border-red-800/60 dark:bg-red-900/30 dark:text-red-300" >
282+ < AlertTriangle className = "h-3.5 w-3.5" aria-hidden = "true" />
283+ { uploadError }
284+ </ span >
285+ ) }
286+ </ div >
287+ ) ,
288+ [ isUploading , selectedPdfUrl , uploadError , handleClosePdf , router ] ,
284289 ) ;
285290
286291 return (
@@ -352,18 +357,18 @@ export default function SmartReadingPage() {
352357 style = { isWide ? { flexBasis : `${ pdfRatio * 100 } %` } : { width : "100%" } }
353358 >
354359 < div className = "flex h-full w-full min-h-0 flex-col border border-slate-200 bg-white shadow-sm dark:border-slate-700 dark:bg-slate-900" >
355- < PDFViewer
356- fileUrl = { selectedPdfUrl }
357- paperId = { uploadedPaper ?. id }
358- onError = { handlePdfError }
359- onLoad = { handlePdfLoad }
360- onMinerUParseComplete = { handleMinerUParseComplete }
361- toolbarExtras = { uploadControls }
362- onParsingStateChange = { setIsParsing }
363- onExtractText = { handleExtractText }
364- suspendRendering = { suspendRendering }
365- forceCancelToken = { forceCancelToken }
366- />
360+ < PDFViewer
361+ fileUrl = { selectedPdfUrl }
362+ paperId = { uploadedPaper ?. id }
363+ onError = { handlePdfError }
364+ onLoad = { handlePdfLoad }
365+ onMinerUParseComplete = { handleMinerUParseComplete }
366+ toolbarExtras = { uploadControls }
367+ onParsingStateChange = { setIsParsing }
368+ onExtractText = { handleExtractText }
369+ suspendRendering = { suspendRendering }
370+ forceCancelToken = { forceCancelToken }
371+ />
367372 </ div >
368373 </ div >
369374
@@ -418,7 +423,7 @@ export default function SmartReadingPage() {
418423 </ div >
419424 </ div >
420425 < div className = "flex-1 min-h-0 overflow-hidden" >
421- { sidePanelMode === "chat" ? (
426+ < div className = { sidePanelMode === "chat" ? "flex h-full" : "hidden" } >
422427 < AgentChatPanel
423428 ref = { agentChatPanelRef }
424429 mineruResult = { mineruResult }
@@ -427,14 +432,15 @@ export default function SmartReadingPage() {
427432 onExtractText = { handleExtractText }
428433 onCopyToNote = { handleCopyToNote }
429434 />
430- ) : (
435+ </ div >
436+ < div className = { sidePanelMode === "note" ? "flex h-full" : "hidden" } >
431437 < NoteEditorPanel
432438 ref = { noteEditorPanelRef }
433439 paperId = { uploadedPaper ?. id }
434440 mineruResult = { mineruResult }
435441 onExtractText = { handleExtractText }
436442 />
437- ) }
443+ </ div >
438444 </ div >
439445 </ div >
440446 </ div >
0 commit comments