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
17 changes: 7 additions & 10 deletions frontend/src/pages/Agent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -532,18 +532,15 @@ const EmptyState = memo(function EmptyState({ onSelect }: { onSelect: (p: string
}, []);

return (
<div className="flex h-full flex-col items-center overflow-y-auto px-4 pt-12 pb-4">
<div className="bg-primary/10 mb-6 flex h-16 w-16 items-center justify-center rounded-2xl">
<Sparkles className="text-primary h-8 w-8" />
<div className="flex h-full flex-col items-center overflow-y-auto px-4 pt-8 pb-4">
<div className="bg-primary/10 mb-3 flex h-10 w-10 items-center justify-center rounded-xl">
<Sparkles className="text-primary h-5 w-5" />
</div>
<h2 className="text-ink mb-1 text-2xl font-bold">PaperMind Agent</h2>
<p className="text-ink-secondary mb-1 text-center text-sm leading-relaxed">
<h2 className="text-ink mb-0.5 text-lg font-bold">PaperMind Agent</h2>
<p className="text-ink-tertiary mb-5 max-w-md text-center text-xs leading-relaxed">
{engine === "python"
? "由 Python 引擎驱动(pm 未部署)。"
: "由 Pi agent core 驱动——与终端 pm 同一循环、同一工具集。"}
</p>
<p className="text-ink-tertiary mb-6 max-w-lg text-center text-sm leading-relaxed">
告诉我你的研究需求,我会自动规划执行步骤:搜索论文、下载、分析、生成综述。
: "由 Pi agent core 驱动,与终端 pm 同一循环、同一工具集。描述研究需求,我会自动规划:搜索 → 下载 → 分析 → 综述。"}
</p>

{/* 今日研究速览 */}
Expand All @@ -552,7 +549,7 @@ const EmptyState = memo(function EmptyState({ onSelect }: { onSelect: (p: string
{/* 统计卡片 */}
<div className="grid grid-cols-3 gap-3">
<div className="border-border bg-surface rounded-xl border p-3 text-center">
<div className="text-primary text-2xl font-bold">{today.total_papers}</div>
<div className="text-primary tnum text-xl font-bold">{today.total_papers}</div>
<div className="text-ink-tertiary text-xs">论文总量</div>
</div>
<div className="border-border bg-surface rounded-xl border p-3 text-center">
Expand Down
24 changes: 12 additions & 12 deletions frontend/src/pages/DeviceAuthorize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default function DeviceAuthorize() {
// 未认证:内嵌登录页(登录成功后回到本页授权流程)
if (!checked) {
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-[#1B1917] via-[#141210] to-[#1B1917]">
<Spinner />
</div>
);
Expand All @@ -90,25 +90,25 @@ export default function DeviceAuthorize() {
const finalized = decision !== null || info?.status === "denied" || expired;

return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-[#1B1917] via-[#141210] to-[#1B1917]">
<div className="w-full max-w-md px-4">
<div className="mb-8 text-center">
<div className="bg-primary/10 mb-4 inline-flex h-16 w-16 items-center justify-center rounded-2xl">
<MonitorSmartphone className="text-primary h-8 w-8" />
</div>
<h1 className="mb-2 text-2xl font-bold text-white">设备登录授权</h1>
<p className="text-sm text-slate-400">确认下面设备码与你终端 pm login 显示的一致</p>
<p className="text-sm text-[#A2988C]">确认下面设备码与你终端 pm login 显示的一致</p>
</div>

<div className="rounded-2xl border border-slate-700/50 bg-slate-800/50 p-6 shadow-xl backdrop-blur-sm">
<div className="rounded-2xl border border-[#2E2A25]/60 bg-[#201D1A]/70 p-6 shadow-xl backdrop-blur-sm">
{/* 设备码输入(URL 未带或需要更正时) */}
{!info && !loadError && (
<div className="space-y-3">
<input
value={userCode}
onChange={(e) => setUserCode(e.target.value.toUpperCase())}
placeholder="输入设备码,如 ABKQ-WERT"
className="w-full rounded-xl border border-slate-600 bg-slate-900/50 px-4 py-3 text-center font-mono text-lg tracking-widest text-white placeholder-slate-500 outline-none focus:border-primary"
className="w-full rounded-xl border border-[#2E2A25] bg-[#131110]/60 px-4 py-3 text-center font-mono text-lg tracking-widest text-white placeholder-[#575047] outline-none focus:border-primary"
maxLength={9}
/>
<Button
Expand All @@ -131,7 +131,7 @@ export default function DeviceAuthorize() {
setLoadError("");
setUserCode("");
}}
className="mt-2 block w-full text-xs text-slate-400 underline"
className="mt-2 block w-full text-xs text-[#A2988C] underline"
>
重新输入设备码
</button>
Expand All @@ -142,14 +142,14 @@ export default function DeviceAuthorize() {
{info && !finalized && (
<div className="space-y-5">
<div className="text-center">
<div className="rounded-xl border border-slate-600 bg-slate-900/50 px-4 py-4 font-mono text-3xl font-bold tracking-widest text-white">
<div className="rounded-xl border border-[#2E2A25] bg-[#131110]/60 px-4 py-4 font-mono text-3xl font-bold tracking-widest text-white">
{info.user_code}
</div>
<p className="mt-3 text-sm text-slate-300">
<p className="mt-3 text-sm text-[#EBE5DC]">
设备 <span className="font-medium text-white">{info.client_name}</span> 请求登录
PaperMind
</p>
<p className="mt-1 flex items-center justify-center gap-1 text-xs text-slate-500">
<p className="mt-1 flex items-center justify-center gap-1 text-xs text-[#A2988C]">
<Clock className="h-3 w-3" />
{Math.floor(info.expires_in / 60)} 分{" "}
{String(info.expires_in % 60).padStart(2, "0")} 秒后过期
Expand Down Expand Up @@ -206,14 +206,14 @@ export default function DeviceAuthorize() {
)}
{expired && (
<ResultPanel
icon={<Clock className="h-10 w-10 text-slate-400" />}
icon={<Clock className="h-10 w-10 text-[#A2988C]" />}
title="已过期"
desc="授权请求超时,请在终端重新执行 pm login"
/>
)}
</div>

<p className="mt-6 text-center text-xs text-slate-500">
<p className="mt-6 text-center text-xs text-[#A2988C]">
PaperMind · 设备码授权(类似 GitHub CLI 登录流程)
</p>
</div>
Expand All @@ -226,7 +226,7 @@ function ResultPanel({ icon, title, desc }: { icon: React.ReactNode; title: stri
<div className="py-6 text-center">
<div className="mb-3 flex justify-center">{icon}</div>
<h2 className="text-lg font-semibold text-white">{title}</h2>
<p className="mt-1 text-sm text-slate-400">{desc}</p>
<p className="mt-1 text-sm text-[#A2988C]">{desc}</p>
</div>
);
}
36 changes: 18 additions & 18 deletions frontend/src/pages/JobMonitor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { useEffect, useState, useCallback } from "react";
import { jobApi, type DurableJobItem, type DurableJobGraph } from "@/services/api";

const STATUS_COLORS: Record<string, string> = {
queued: "bg-gray-100 text-gray-600",
queued: "bg-hover text-ink-secondary",
running: "bg-blue-100 text-blue-700",
succeeded: "bg-green-100 text-green-800",
partially_succeeded: "bg-yellow-100 text-yellow-800",
failed: "bg-red-100 text-red-700",
cancelled: "bg-gray-100 text-gray-400",
cancelled: "bg-hover text-ink-tertiary",
dead_letter: "bg-red-200 text-red-900",
manual_recovery: "bg-purple-100 text-purple-800",
};
Expand Down Expand Up @@ -102,7 +102,7 @@ export default function JobMonitor() {

{/* Jobs 列表 */}
{loading && !jobs.length ? (
<p className="text-gray-500">Loading...</p>
<p className="text-ink-tertiary">Loading...</p>
) : (
<div className="space-y-2">
{jobs.map((j) => (
Expand All @@ -116,7 +116,7 @@ export default function JobMonitor() {
<div className="flex items-center gap-2">
<span
className={`px-2 py-0.5 rounded text-xs font-medium ${
STATUS_COLORS[j.status] ?? "bg-gray-100"
STATUS_COLORS[j.status] ?? "bg-hover"
}`}
>
{j.status}
Expand All @@ -133,13 +133,13 @@ export default function JobMonitor() {
{(j as any).authority === "go_core" ? "Go" : "存档"}
</span>
)}
<span className="text-xs text-gray-400 ml-auto">{j.id}</span>
<span className="text-xs text-ink-tertiary ml-auto">{j.id}</span>
</div>
{j.progress?.message && (
<p className="text-xs text-gray-600 mt-1">{j.progress.message}</p>
<p className="text-xs text-ink-secondary mt-1">{j.progress.message}</p>
)}
<div className="flex items-center gap-2 mt-1">
<div className="flex-1 bg-gray-200 rounded-full h-1.5">
<div className="flex-1 bg-active rounded-full h-1.5">
<div
className="bg-blue-500 rounded-full h-1.5"
style={{
Expand All @@ -150,7 +150,7 @@ export default function JobMonitor() {
}}
/>
</div>
<span className="text-xs text-gray-500">
<span className="text-xs text-ink-tertiary">
{j.progress?.current ?? 0}/{j.progress?.total ?? 0}
</span>
</div>
Expand All @@ -176,7 +176,7 @@ export default function JobMonitor() {
</div>
</div>
))}
{!jobs.length && <p className="text-gray-400 text-sm">No jobs found.</p>}
{!jobs.length && <p className="text-ink-tertiary text-sm">No jobs found.</p>}
</div>
)}

Expand All @@ -202,7 +202,7 @@ export default function JobMonitor() {
</span>
)}
<button
className="text-gray-400 text-sm ml-auto hover:text-gray-600"
className="text-ink-tertiary text-sm ml-auto hover:text-ink-secondary"
onClick={() => setSelected(null)}
>
✕
Expand All @@ -211,22 +211,22 @@ export default function JobMonitor() {

{/* Tasks */}
<div>
<h4 className="text-sm font-medium text-gray-600 mb-2">
<h4 className="text-sm font-medium text-ink-secondary mb-2">
Tasks ({selected.tasks.length})
</h4>
<div className="space-y-1">
{selected.tasks.map((t) => (
<div key={t.id} className="flex items-center gap-2 text-xs border rounded p-2">
<span
className={`px-1.5 py-0.5 rounded ${
STATUS_COLORS[t.status] ?? "bg-gray-100"
STATUS_COLORS[t.status] ?? "bg-hover"
}`}
>
{t.status}
</span>
<span className="font-mono">{t.capability}</span>
<span className="text-gray-400 ml-auto">attempt {t.attempt_count}</span>
<span className="text-gray-400">{t.resource_class}</span>
<span className="text-ink-tertiary ml-auto">attempt {t.attempt_count}</span>
<span className="text-ink-tertiary">{t.resource_class}</span>
{t.last_error && (
<span className="text-red-500 text-xs truncate max-w-48">{t.last_error}</span>
)}
Expand All @@ -248,17 +248,17 @@ export default function JobMonitor() {
{/* Attempts */}
{selected.attempts.length > 0 && (
<div>
<h4 className="text-sm font-medium text-gray-600 mb-2">
<h4 className="text-sm font-medium text-ink-secondary mb-2">
Attempts ({selected.attempts.length})
</h4>
<div className="space-y-1">
{selected.attempts.map((a) => (
<div key={a.id} className="flex items-center gap-2 text-xs text-gray-600">
<div key={a.id} className="flex items-center gap-2 text-xs text-ink-secondary">
<span className="font-mono">#{a.attempt_no}</span>
<span>{a.status}</span>
<span className="text-gray-400">{a.executor_id}</span>
<span className="text-ink-tertiary">{a.executor_id}</span>
{a.fencing_token > 0 && (
<span className="text-gray-300">fence:{a.fencing_token}</span>
<span className="text-ink-placeholder">fence:{a.fencing_token}</span>
)}
{a.error_message && (
<span className="text-red-500 truncate max-w-48">{a.error_message}</span>
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/pages/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,36 +59,36 @@ export default function LoginPage({ onLoginSuccess }: LoginPageProps) {
}

return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-[#1B1917] via-[#141210] to-[#1B1917]">
<div className="w-full max-w-md px-4">
{/* Logo 和标题 */}
<div className="mb-8 text-center">
<div className="bg-primary/10 mb-4 inline-flex h-16 w-16 items-center justify-center rounded-2xl">
<Lock className="text-primary h-8 w-8" />
</div>
<h1 className="mb-2 text-2xl font-bold text-white">PaperMind</h1>
<p className="text-sm text-slate-400">请输入访问密码</p>
<p className="text-sm text-[#A2988C]">请输入访问密码</p>
</div>

{/* 登录表单 */}
<form
onSubmit={handleSubmit}
className="rounded-2xl border border-slate-700/50 bg-slate-800/50 p-6 shadow-xl backdrop-blur-sm"
className="rounded-2xl border border-[#2E2A25]/60 bg-[#201D1A]/70 p-6 shadow-xl backdrop-blur-sm"
>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="访问密码"
className="focus:ring-primary w-full rounded-xl border border-slate-600 bg-slate-900/50 px-4 py-3 pr-12 text-white placeholder-slate-500 transition-all focus:border-transparent focus:ring-2 focus:outline-none"
className="focus:ring-primary w-full rounded-xl border border-[#2E2A25] bg-[#131110]/60 px-4 py-3 pr-12 text-white placeholder-[#575047] transition-all focus:border-transparent focus:ring-2 focus:outline-none"
disabled={loading || checkingAuth}
autoFocus
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute top-1/2 right-3 -translate-y-1/2 text-slate-500 transition-colors hover:text-slate-300"
className="absolute top-1/2 right-3 -translate-y-1/2 text-[#A2988C] transition-colors hover:text-[#EBE5DC]"
>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
Expand All @@ -99,7 +99,7 @@ export default function LoginPage({ onLoginSuccess }: LoginPageProps) {
<button
type="submit"
disabled={loading || checkingAuth}
className="bg-primary hover:bg-primary-hover mt-4 flex w-full items-center justify-center gap-2 rounded-xl py-3 font-medium text-white transition-colors disabled:bg-slate-600"
className="bg-primary hover:bg-primary-hover mt-4 flex w-full items-center justify-center gap-2 rounded-xl py-3 font-medium text-white transition-colors disabled:bg-[#575047]"
>
{checkingAuth ? (
<>
Expand All @@ -118,7 +118,7 @@ export default function LoginPage({ onLoginSuccess }: LoginPageProps) {
</form>

{/* 底部提示 */}
<p className="mt-6 text-center text-xs text-slate-500">
<p className="mt-6 text-center text-xs text-[#A2988C]">
PaperMind · AI 驱动的学术论文研究平台
</p>
</div>
Expand Down
Loading
Loading