Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 1 addition & 11 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion frontend/src/app/notes/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { BACKEND_URL } from "@/lib/config";

export default function NewNotePage() {
const router = useRouter();
const token = getAccessToken();
const [token, setToken] = useState<string | null>(null);
const [hydrated, setHydrated] = useState(false);
const [papers, setPapers] = useState<UploadedPaperDTO[]>([]);
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
Expand All @@ -30,6 +31,12 @@ export default function NewNotePage() {
[],
);

// Defer token read to client to avoid SSR/client mismatch
useEffect(() => {
setToken(getAccessToken());
setHydrated(true);
}, []);

useEffect(() => {
if (!token) return;
fetchUploadedPapers(token, { page: 1, pageSize: 50 })
Expand Down Expand Up @@ -61,6 +68,15 @@ export default function NewNotePage() {
}
}

// Prevent hydration mismatches: wait until client token is known
if (!hydrated) {
return (
<DashboardShell title="新建笔记" subtitle="加载中…">
<div className="rounded-2xl border bg-white/70 p-6 text-sm text-slate-600 shadow-sm">加载中…</div>
</DashboardShell>
);
}

if (!token) {
return (
<DashboardShell title="新建笔记" subtitle="登录后管理你的阅读笔记。">
Expand Down
18 changes: 17 additions & 1 deletion frontend/src/app/notes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ function truncate(text?: string | null, length = 140): string {

export default function NotesPage() {
const router = useRouter();
const token = getAccessToken();
const [token, setToken] = useState<string | null>(null);
const [hydrated, setHydrated] = useState(false);
const [notes, setNotes] = useState<NoteDTO[]>([]);
const [papers, setPapers] = useState<UploadedPaperDTO[]>([]);
const [loading, setLoading] = useState(true);
Expand All @@ -56,6 +57,12 @@ export default function NotesPage() {
[],
);

// Defer token read to client to avoid SSR/CSR mismatch
useEffect(() => {
setToken(getAccessToken());
setHydrated(true);
}, []);

const paperLookup = useMemo(() => {
return papers.reduce<Record<number, UploadedPaperDTO>>((acc, item) => {
acc[item.id] = item;
Expand Down Expand Up @@ -168,6 +175,15 @@ export default function NotesPage() {

const searchInputRef = useRef<HTMLInputElement>(null);

// Prevent hydration mismatch by waiting for client token
if (!hydrated) {
return (
<DashboardShell title="我的笔记" subtitle="加载中…">
<div className="rounded-2xl border bg-white/70 p-6 text-sm text-slate-600 shadow-sm">加载中…</div>
</DashboardShell>
);
}

if (!token) {
return (
<DashboardShell title="我的笔记" subtitle="登录后管理你的阅读笔记。">
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/app/smart-reading/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default function SmartReadingPage() {
}, []);

const agentChatPanelRef = useRef<{ setInputText: (text: string) => void } | null>(null);
const noteEditorPanelRef = useRef<{ appendContent: (text: string) => void } | null>(null);
const noteEditorPanelRef = useRef<{ appendContent: (text: string) => void; resetEditor: () => void } | null>(null);
const splitContainerRef = useRef<HTMLDivElement | null>(null);

const handleExtractText = useCallback((text: string) => {
Expand Down Expand Up @@ -281,11 +281,9 @@ export default function SmartReadingPage() {
setSelectedPdfUrl(null);
setUploadedPaper(null);
setUploadError(null);
setParseProgress(null);
setParseError(null);
setParsedDocument(null);
setMineruResult(null);
setIsParsing(false);
noteEditorPanelRef.current?.resetEditor?.();
}}
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"
>
Expand Down
27 changes: 26 additions & 1 deletion frontend/src/components/ai-agent/AgentChatPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
const [showHeader, setShowHeader] = useState(true);
const streamControllerRef = useRef<AbortController | null>(null);
const autoSummaryHistoryRef = useRef<Record<number, string | null>>({});
const savedConversationRef = useRef<boolean>(false);

// 暴露 setInputText 方法给父组件
useImperativeHandle(ref, () => ({
Expand Down Expand Up @@ -117,14 +118,32 @@
timestamp: new Date(msg.created_at),
}));
setMessages(historyMessages);
savedConversationRef.current = true;
}
} catch (error) {
console.error("加载对话历史失败:", error);
// 如果加载失败,不影响新对话
// 后端会在类别不匹配或不存在时返回 404;清理本地缓存以便重新创建对话
const message = error instanceof Error ? error.message : "";
if (message.includes("不存在") || message.toLowerCase().includes("not found")) {
if (paperId) {
localStorage.removeItem(`conversation_${paperId}`);
}
setConversationId(null);
setMessages([
{
id: createId(),
role: "assistant",
content: "对话不存在,已为当前文档重置。可以继续提问,我会新建对话并保存历史。",
timestamp: new Date(),
},
]);
savedConversationRef.current = false;
}
// 其他错误不影响新对话
} finally {
setIsLoadingHistory(false);
}
}, []);

Check warning on line 146 in frontend/src/components/ai-agent/AgentChatPanel.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

React Hook useCallback has a missing dependency: 'paperId'. Either include it or remove the dependency array

// 当 paperId 变化时,加载对话 ID 和历史
useEffect(() => {
Expand All @@ -134,6 +153,7 @@
const convId = parseInt(savedConvId, 10);
setConversationId(convId);
loadConversationHistory(convId);
savedConversationRef.current = true;
} else {
// 没有保存的对话,显示欢迎消息
setMessages([
Expand All @@ -146,6 +166,7 @@
]);
setConversationId(null);
setQuotedText(null);
savedConversationRef.current = false;
}
} else if (!mineruResult) {
// 重置为初始状态
Expand All @@ -159,6 +180,7 @@
]);
setConversationId(null);
setQuotedText(null);
savedConversationRef.current = false;
}
}, [paperId, mineruResult, loadConversationHistory]);

Expand Down Expand Up @@ -286,6 +308,9 @@
if (!paperId || !mineruResult) {
return;
}
if (conversationId || savedConversationRef.current) {
return;
}

const summaryIntro: AgentMessage = {
id: createId(),
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/components/note-editor/NoteEditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface NoteEditorPanelProps {
}

const NoteEditorPanel = forwardRef<
{ appendContent: (text: string) => void },
{ appendContent: (text: string) => void; resetEditor: () => void },
NoteEditorPanelProps
>(({ paperId, mineruResult }, ref) => {
const [title, setTitle] = useState("");
Expand All @@ -41,14 +41,22 @@ const NoteEditorPanel = forwardRef<
const autoSaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [isDarkMode, setIsDarkMode] = useState(false);

// 暴露appendContent方法给父组件
// 暴露 appendContent / resetEditor 方法给父组件
useImperativeHandle(ref, () => ({
appendContent: (text: string) => {
setContent((prev) => {
const separator = prev.trim() ? "\n\n---\n\n" : "";
return prev + separator + text;
});
},
resetEditor: () => {
setTitle("");
setContent("");
setNoteId(null);
setLastSaved(null);
setHasAutoFilledTitle(false);
setRelatedNotes([]);
},
}));

// 重新加载笔记列表
Expand Down
Loading