Skip to content

Commit 22e4053

Browse files
committed
optimize: change frontend interaction logic and startup bash script
1 parent 0422229 commit 22e4053

8 files changed

Lines changed: 149 additions & 98 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ venv/
4848
env/
4949
backend/.venv/
5050
.cache/
51+
.uv-cache/
5152
.hypothesis/
5253

5354
# Project-specific build artifacts
@@ -103,4 +104,3 @@ solutions/
103104
tmp/
104105
.claude/
105106
openai.json
106-
scripts/dev.sh

backend/config/magic-pdf.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"formula-config": {
2121
"mfd_model": "yolo_v8_mfd",
2222
"mfr_model": "unimernet_small",
23-
"enable": false
23+
"enable": true
2424
},
2525
"llm-aided-config": null,
2626
"latex-delimiter-config": null

frontend/src/app/academic/page.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ export default function AcademicSearchPage() {
440440
title="学术搜索"
441441
subtitle="输入你的科学问题或研究主题,选择适合的检索模式,即刻探索匹配的论文成果。"
442442
contentContainerClassName="items-stretch justify-center overflow-y-auto pb-0"
443-
contentClassName="w-full flex-1 px-4 pt-6 pb-0"
443+
contentClassName="w-full flex-1 px-4 pt-6 pb-40"
444444
forceCollapse={collapseSidebar}
445445
>
446446
{mode === "ai" && currentUser && (
@@ -469,10 +469,10 @@ export default function AcademicSearchPage() {
469469
<div className="mb-4">
470470
<button
471471
type="button"
472-
onClick={() => router.back()}
472+
onClick={() => router.push("/home")}
473473
ref={backButtonRef}
474474
className="inline-flex h-10 w-10 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-700 shadow-sm transition hover:border-slate-300 hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-200 dark:hover:border-slate-500"
475-
aria-label="返回"
475+
aria-label="返回首页"
476476
>
477477
<svg
478478
xmlns="http://www.w3.org/2000/svg"
@@ -582,8 +582,8 @@ export default function AcademicSearchPage() {
582582
</form>
583583
</div>
584584
) : (
585-
<div className="flex flex-col gap-10">
586-
<div className="w-full">
585+
<div className="flex flex-col gap-10 relative pb-28">
586+
<div className="w-full max-h-[calc(100vh-220px)] overflow-y-auto pr-1">
587587
<div className="mb-4 flex flex-wrap items-center gap-2 text-xs text-slate-500">
588588
<span className="font-semibold text-slate-600">数据源</span>
589589
{SOURCE_OPTIONS.map((option) => {
@@ -674,15 +674,27 @@ export default function AcademicSearchPage() {
674674
)}
675675
</div>
676676

677-
<form onSubmit={handleSubmit} className="mt-2 w-full max-w-3xl self-center">
677+
<form
678+
onSubmit={handleSubmit}
679+
className="mt-auto w-full max-w-3xl self-center sticky bottom-0 z-30 bg-transparent"
680+
>
678681
<div className="relative">
679682
<textarea
680683
value={question}
681684
onChange={(event) => setQuestion(event.target.value)}
682685
placeholder="描述你的研究问题、关键词或阅读意图…"
683-
rows={4}
684-
className="w-full resize-none rounded-3xl border border-slate-200 bg-white/95 px-5 pb-16 pr-16 pt-4 text-sm leading-relaxed text-slate-900 placeholder:text-slate-500 shadow-[0_10px_50px_-25px_rgba(15,23,42,0.4)] transition focus:border-blue-400 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-200 disabled:opacity-60 dark:border-slate-600 dark:bg-slate-900/80 dark:text-slate-100 dark:placeholder:text-slate-400"
686+
rows={3}
687+
className="academic-ai-input w-full resize-none rounded-3xl border border-slate-200 bg-white/95 px-5 pb-12 pr-16 pt-3 text-sm leading-relaxed text-slate-900 placeholder:text-slate-500 shadow-[0_10px_40px_-25px_rgba(15,23,42,0.5)] transition focus:border-blue-400 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-200 disabled:opacity-60 dark:border-slate-600 dark:bg-slate-900/85 dark:text-slate-100 dark:placeholder:text-slate-400"
685688
disabled={loading}
689+
onKeyDown={(e) => {
690+
if (e.key === 'Enter' && !e.shiftKey) {
691+
e.preventDefault();
692+
const form = e.currentTarget.form;
693+
if (form) {
694+
handleSubmit({ preventDefault: () => {}, currentTarget: form } as FormEvent<HTMLFormElement>);
695+
}
696+
}
697+
}}
686698
/>
687699

688700
<div className="absolute left-5 bottom-4 flex items-center gap-2">

frontend/src/app/smart-reading/page.tsx

Lines changed: 74 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useRouter, useSearchParams } from "next/navigation";
4-
import { useEffect, useState, useRef, useCallback } from "react";
4+
import { useEffect, useState, useRef, useCallback, useMemo } from "react";
55
import { UploadCloud, BookOpenCheck, XCircle, AlertTriangle } from "lucide-react";
66

77
import 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 = /transport destroyed/i.test(error);
215217
const isBenignCallbackError = /cannot resolve callback/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>

frontend/src/components/note-editor/NoteEditorPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,14 +364,14 @@ const NoteEditorPanel = forwardRef<
364364

365365
{/* Typora式Markdown编辑器 */}
366366
<div
367-
className="flex-1 min-h-0 overflow-auto rounded-2xl border border-slate-100 bg-white/90 shadow-inner dark:border-slate-800 dark:bg-slate-900/40"
367+
className="flex-1 min-h-[800px] overflow-auto rounded-2xl border border-slate-100 bg-white/90 shadow-inner dark:border-slate-800 dark:bg-slate-900/40"
368368
>
369369
<MDEditor
370370
key={`note-editor-${isDarkMode ? 'dark' : 'light'}-${noteId ?? 'new'}`}
371371
value={content}
372372
onChange={(val) => setContent(val || "")}
373373
preview="edit"
374-
height={480}
374+
height={1400}
375375
visibleDragbar={false}
376376
className="note-md-editor"
377377
textareaProps={{

frontend/src/components/pdf-reader/PDFRegionHighlight.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ export default function PDFRegionHighlight({
210210
const isSelected = selectedIndex === box.index;
211211
const isImageType = ["figure", "table", "formula", "equation"].includes(box.item.type);
212212
const isCaption = box.item.metadata?.is_caption === true;
213-
const showExtractButton = box.item.type === "text" || isCaption;
213+
const isEquationLike = box.item.type === "equation" || box.item.type === "formula";
214+
const showExtractButton = box.item.type === "text" || isCaption || isEquationLike;
214215
const hasContent = box.item.text || isImageType;
215216

216217
return (
@@ -241,8 +242,12 @@ export default function PDFRegionHighlight({
241242
<button
242243
onClick={(e) => {
243244
e.stopPropagation();
245+
const bboxText =
246+
box.item.bbox && box.item.bbox.length === 4
247+
? `bbox=(${box.item.bbox.join(",")})`
248+
: "";
244249
const extractText = isImageType
245-
? `[${box.item.type}] 第${box.item.page}页 - ${box.item.text || box.item.caption || "无描述"}`
250+
? `[${box.item.type}] 第${box.item.page}${bboxText} - ${box.item.text || box.item.caption || "无描述"}`
246251
: box.item.text || "";
247252
onExtractText(extractText);
248253
}}

0 commit comments

Comments
 (0)