Skip to content
Merged
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
55 changes: 54 additions & 1 deletion frontend/package-lock.json

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

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@tailwindcss/typography": "^0.5.19",
"@uiw/react-md-editor": "^4.0.4",
"framer-motion": "^12.23.26",
"lucide-react": "^0.552.0",
"next": "15.5.4",
"pdfjs-dist": "^3.11.174",
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/academic/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import IntelligentSummary from "@/components/academic/IntelligentSummary";
import ConversationHistory from "@/components/academic/ConversationHistory";
import FloatingFilters from "@/components/academic/FloatingFilters";
import { intelligentSearch, searchPapers, streamIntelligentSearch, type SearchMode } from "@/lib/api/academic";

Check warning on line 12 in frontend/src/app/academic/page.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

'intelligentSearch' is defined but never used
import { fetchCurrentUser, type UserDTO } from "@/lib/api-client";
import { getAccessToken } from "@/lib/auth";
import { buildBackendUrl } from "@/lib/url";
Expand Down Expand Up @@ -529,7 +529,7 @@
onChange={(event) => setQuestion(event.target.value)}
placeholder="与 AI 助手对话,提出你的研究问题…"
rows={3}
className="w-full resize-none rounded-3xl border border-slate-200 bg-transparent px-5 pb-12 pr-16 pt-3 text-sm leading-relaxed text-slate-900 placeholder:text-slate-500 transition focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-200 disabled:opacity-60 dark:border-slate-600 dark:bg-transparent dark:text-slate-100 dark:placeholder:text-slate-400"
className="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"
disabled={loading}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
Expand Down Expand Up @@ -681,7 +681,7 @@
onChange={(event) => setQuestion(event.target.value)}
placeholder="描述你的研究问题、关键词或阅读意图…"
rows={4}
className="w-full resize-none rounded-3xl border border-slate-200 bg-transparent px-5 pb-16 pr-16 pt-4 text-sm leading-relaxed text-slate-900 placeholder:text-slate-500 transition focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-200 disabled:opacity-60 dark:border-slate-600 dark:bg-transparent dark:text-slate-100 dark:placeholder:text-slate-400"
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"
disabled={loading}
/>

Expand Down
17 changes: 3 additions & 14 deletions frontend/src/app/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { AuthForm } from "@/components/auth-form";
import InsightWorkflow from "@/components/marketing/insight-workflow";

export default function AuthPage() {
return (
Expand All @@ -10,20 +11,8 @@ export default function AuthPage() {
>
<main className="flex min-h-screen items-center px-2 py-10 lg:px-4">
<section className="hidden flex-1 items-center justify-end pr-0 lg:flex xl:pr-0">
<div
className="relative h-[68vh] w-full max-w-6xl overflow-hidden rounded-[2.5rem] border border-white/20 bg-transparent shadow-[0_60px_120px_-60px_rgba(15,23,42,0.65)]"
style={{ aspectRatio: "30 / 18" }}
>
<video
className="h-full w-full object-cover"
autoPlay
loop
muted
playsInline
>
<source src="/videos/auth.mp4" type="video/mp4" />
您的浏览器不支持 HTML5 视频播放。
</video>
<div className="relative h-[72vh] w-full">
<InsightWorkflow />
</div>
</section>

Expand Down
107 changes: 73 additions & 34 deletions frontend/src/app/notes/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,61 +192,82 @@ export default function NotesPage() {
<p className="text-xs uppercase tracking-wide text-slate-500">笔记分类</p>
<p className="text-base font-semibold text-slate-900 dark:text-slate-100">文件夹</p>
</div>
<span className="rounded-lg border border-slate-200 px-2.5 py-1 text-[11px] font-medium text-slate-600 dark:border-slate-600 dark:text-slate-300">
共 {folderGroups.length}
</span>
<Link
href="/notes/new"
className="rounded-lg border border-slate-200 px-2.5 py-1 text-xs font-medium text-slate-600 transition hover:bg-slate-50 dark:border-slate-600 dark:text-slate-300 dark:hover:bg-slate-700/60"
>
+ 新建
</Link>
</div>
<p className="mt-1 text-[11px] text-slate-400 dark:text-slate-500">
共 {folderGroups.length} 个分类,支持展开查看论文及其关联笔记
</p>
{loading ? (
<div className="py-8 text-center text-sm text-slate-500 dark:text-slate-400">加载中...</div>
) : (!folderGroups.some((group) => group.papers.length > 0) && unlinkedNotes.length === 0) ? (
<div className="py-8 text-center text-sm text-slate-500 dark:text-slate-400">还没有笔记,先创建一条吧。</div>
) : (
<div className="space-y-3">
<div className="mt-4 space-y-2">
{folderGroups.map(({ folder, papers: folderPapers, noteCount }) => {
const folderKey = `folder-${folder.id}`;
const isFolderOpen = expandedGroups[folderKey] ?? true;
const folderSummary = `${folderPapers.length} 论文 / ${noteCount} 笔记`;
return (
<div
key={folderKey}
className="overflow-hidden rounded-xl border border-slate-200 bg-white/80 shadow-sm dark:border-slate-700 dark:bg-slate-800/70 dark:shadow-none"
>
<div key={folderKey} className="space-y-2">
<button
type="button"
onClick={() => toggleGroup(folderKey)}
className="flex w-full items-center justify-between px-3 py-2 text-left text-sm font-medium text-slate-800 transition hover:bg-slate-100/60 dark:text-slate-100 dark:hover:bg-slate-800/60"
className={`w-full rounded-xl border px-4 py-3 text-left transition ${
isFolderOpen
? "border-blue-500 bg-blue-50 text-blue-700 shadow-sm dark:border-blue-400 dark:bg-blue-900/40 dark:text-blue-100"
: "border-transparent bg-white/70 text-slate-700 hover:border-slate-200 hover:bg-white dark:bg-slate-800/40 dark:text-slate-200 dark:hover:border-slate-600"
}`}
>
<span className="flex items-center gap-2">
<span
className="h-2 w-2 rounded-full"
style={{ backgroundColor: folder.color ?? "#cbd5f5" }}
/>
<span className="truncate">{folder.name}</span>
</span>
<span className="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-400">
<span>
{folderPapers.length} 论文 / {noteCount} 笔记
<div className="flex items-center justify-between text-sm font-medium">
<div className="flex items-center gap-2">
<span
className="h-2.5 w-2.5 rounded-full"
style={{ backgroundColor: folder.color ?? "#cbd5f5" }}
/>
<span className="truncate">{folder.name}</span>
</div>
<span className="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-300">
{folderSummary}
<svg
className={`h-3 w-3 transition ${isFolderOpen ? "rotate-90" : ""}`}
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M6 4l6 6-6 6" />
</svg>
</span>
<span className={`transition ${isFolderOpen ? "rotate-90" : ""}`}>›</span>
</span>
</div>
</button>

{isFolderOpen && (
<div className="divide-y divide-slate-200 dark:divide-slate-800">
<div className="rounded-xl border border-slate-200 bg-white/80 p-2 shadow-sm dark:border-slate-700 dark:bg-slate-900/40 dark:shadow-none">
{folderPapers.length === 0 && (
<div className="px-3 py-2 text-xs text-slate-500 dark:text-slate-400">该文件夹暂无论文</div>
)}
{folderPapers.map(({ paper, notes: paperNotes }) => {
const paperKey = `paper-${paper.id}`;
const isPaperOpen = expandedGroups[paperKey] ?? false;
return (
<div key={paperKey} className="bg-white/60 dark:bg-slate-800/40">
<div
key={paperKey}
className="rounded-lg border border-transparent bg-white/60 dark:bg-slate-800/40"
>
<button
type="button"
onClick={() => toggleGroup(paperKey)}
className="flex w-full items-center justify-between px-3 py-2 text-left text-sm text-slate-800 transition hover:bg-slate-100/60 dark:text-slate-100 dark:hover:bg-slate-800/60"
className="flex w-full items-center justify-between px-3 py-2 text-left text-xs font-medium text-slate-800 transition hover:bg-blue-50 dark:text-slate-100 dark:hover:bg-slate-800/60"
>
<span className="truncate">{paper.original_filename}</span>
<span className="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-400">
<span className="flex items-center gap-2 text-[11px] text-slate-500 dark:text-slate-400">
<span>{paperNotes.length}</span>
<span className={`transition ${isPaperOpen ? "rotate-90" : ""}`}>›</span>
</span>
Expand All @@ -261,7 +282,9 @@ export default function NotesPage() {
className="flex w-full items-center justify-between px-3 py-2 text-left text-xs text-slate-700 transition hover:bg-slate-50 dark:text-slate-200 dark:hover:bg-slate-800/50"
>
<span className="truncate">{note.title}</span>
<span className="text-[11px] text-slate-400 dark:text-slate-500">{formatDate(note.updated_at)}</span>
<span className="text-[11px] text-slate-400 dark:text-slate-500">
{formatDate(note.updated_at)}
</span>
</button>
))}
</div>
Expand All @@ -279,20 +302,36 @@ export default function NotesPage() {
})}

{unlinkedNotes.length > 0 && (
<div className="overflow-hidden rounded-xl border border-slate-200 bg-white/80 shadow-sm dark:border-slate-700 dark:bg-slate-800/70 dark:shadow-none">
<div className="space-y-2">
<button
type="button"
onClick={() => toggleGroup("unlinked")}
className="flex w-full items-center justify-between px-3 py-2 text-left text-sm font-medium text-slate-800 transition hover:bg-slate-100/60 dark:text-slate-100 dark:hover:bg-slate-800/60"
className={`w-full rounded-xl border px-4 py-3 text-left transition ${
expandedGroups["unlinked"]
? "border-emerald-500 bg-emerald-50 text-emerald-700 shadow-sm dark:border-emerald-400 dark:bg-emerald-900/30 dark:text-emerald-100"
: "border-transparent bg-white/70 text-slate-700 hover:border-slate-200 hover:bg-white dark:bg-slate-800/40 dark:text-slate-200 dark:hover:border-slate-600"
}`}
>
<span className="truncate">未关联论文</span>
<span className="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-400">
<span>{unlinkedNotes.length}</span>
<span className={`transition ${expandedGroups["unlinked"] ? "rotate-90" : ""}`}>›</span>
</span>
<div className="flex items-center justify-between text-sm font-medium">
<span className="truncate">未关联论文</span>
<span className="flex items-center gap-2 text-xs text-slate-500 dark:text-slate-300">
{unlinkedNotes.length}
<svg
className={`h-3 w-3 transition ${expandedGroups["unlinked"] ? "rotate-90" : ""}`}
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M6 4l6 6-6 6" />
</svg>
</span>
</div>
</button>
{expandedGroups["unlinked"] && (
<div className="divide-y divide-slate-200 dark:divide-slate-800">
<div className="rounded-xl border border-slate-200 bg-white/80 p-2 shadow-sm dark:border-slate-700 dark:bg-slate-900/40 dark:shadow-none">
{unlinkedNotes.map((note) => (
<button
key={note.id}
Expand Down
Loading
Loading