From 72bd43965fff0434dba4be4e934b4e06d91d5c83 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Fri, 12 Jun 2026 16:39:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(desktop):=20=E5=B7=A5=E4=B8=9A=E5=8C=96?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=20A=20=E2=80=94=E2=80=94=20=E8=AE=BE?= =?UTF-8?q?=E8=AE=A1=E7=B3=BB=E7=BB=9F=E5=9C=B0=E5=9F=BA=EF=BC=88primitive?= =?UTF-8?q?s=20+=20lucide=20+=20=E8=AF=AD=E4=B9=89=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把"手搓内联 class + Unicode 字符图标"换成统一组件与真实图标,为后续工业化打底。 - 依赖:装 lucide-react(描线图标,按需 tree-shake) - 令牌:tailwind.config 加语义色 brand/accent/success/warn/danger + 圆角档位; 强调色字面量(violet/cyan/emerald…)收敛到令牌,便于整体换肤 - primitives(src/ui,零重依赖自建):Button/Input/Textarea/Select/Field/Card/Panel/ Badge/Dot/Tabs/Skeleton/EmptyState/Dialog/Toast(+useToast)/cn,桶文件统一引入 - 迁移:TopBar/LeftNav/BottomDrawer + Home/Report/Runs/Kb/Placeholder/ExecTrace/ MemoryPanel/StudioView 全部换 primitives + lucide 图标;导航/能力卡/按钮告别 ▤◆▣▦ 等 Unicode 字符;错误改用全局 Toast;空状态用 EmptyState - App 包 ToastProvider 验证:tsc + vite build 通过;浏览器(Preview)走查工作台/报告页——真实图标、统一卡片/ 按钮/输入;跑报告端到端正常(执行轨迹 lucide 状态图标点亮、章节耗时/字数/检索片段、 完成弹 Toast + 下载 Word)。 Co-Authored-By: Claude Opus 4.8 --- sundynix-desktop/frontend/package-lock.json | 10 ++ sundynix-desktop/frontend/package.json | 13 +- sundynix-desktop/frontend/src/App.tsx | 3 + .../frontend/src/components/ExecTrace.tsx | 81 ++++++------ .../frontend/src/panels/MemoryPanel.tsx | 28 +++-- .../frontend/src/shell/BottomDrawer.tsx | 66 +++++----- .../frontend/src/shell/LeftNav.tsx | 62 ++++++---- .../frontend/src/shell/TopBar.tsx | 43 ++++--- .../frontend/src/studio/StudioView.tsx | 25 ++-- sundynix-desktop/frontend/src/ui/Badge.tsx | 38 ++++++ sundynix-desktop/frontend/src/ui/Button.tsx | 37 ++++++ sundynix-desktop/frontend/src/ui/Card.tsx | 36 ++++++ sundynix-desktop/frontend/src/ui/Dialog.tsx | 51 ++++++++ sundynix-desktop/frontend/src/ui/Feedback.tsx | 34 +++++ sundynix-desktop/frontend/src/ui/Input.tsx | 32 +++++ sundynix-desktop/frontend/src/ui/Tabs.tsx | 44 +++++++ sundynix-desktop/frontend/src/ui/Toast.tsx | 64 ++++++++++ sundynix-desktop/frontend/src/ui/cn.ts | 4 + sundynix-desktop/frontend/src/ui/index.ts | 10 ++ sundynix-desktop/frontend/src/views/Home.tsx | 81 ++++++------ .../frontend/src/views/KbView.tsx | 116 +++++++----------- .../frontend/src/views/Placeholder.tsx | 12 +- .../frontend/src/views/ReportView.tsx | 114 ++++++++--------- .../frontend/src/views/RunsView.tsx | 48 ++++---- sundynix-desktop/frontend/tailwind.config.js | 11 ++ 25 files changed, 715 insertions(+), 348 deletions(-) create mode 100644 sundynix-desktop/frontend/src/ui/Badge.tsx create mode 100644 sundynix-desktop/frontend/src/ui/Button.tsx create mode 100644 sundynix-desktop/frontend/src/ui/Card.tsx create mode 100644 sundynix-desktop/frontend/src/ui/Dialog.tsx create mode 100644 sundynix-desktop/frontend/src/ui/Feedback.tsx create mode 100644 sundynix-desktop/frontend/src/ui/Input.tsx create mode 100644 sundynix-desktop/frontend/src/ui/Tabs.tsx create mode 100644 sundynix-desktop/frontend/src/ui/Toast.tsx create mode 100644 sundynix-desktop/frontend/src/ui/cn.ts create mode 100644 sundynix-desktop/frontend/src/ui/index.ts diff --git a/sundynix-desktop/frontend/package-lock.json b/sundynix-desktop/frontend/package-lock.json index 8bdbeb3..10c467d 100644 --- a/sundynix-desktop/frontend/package-lock.json +++ b/sundynix-desktop/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@xyflow/react": "^12.3.0", + "lucide-react": "^1.17.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, @@ -2054,6 +2055,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", + "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", diff --git a/sundynix-desktop/frontend/package.json b/sundynix-desktop/frontend/package.json index edd59aa..e1cba83 100644 --- a/sundynix-desktop/frontend/package.json +++ b/sundynix-desktop/frontend/package.json @@ -9,18 +9,19 @@ "preview": "vite preview" }, "dependencies": { + "@xyflow/react": "^12.3.0", + "lucide-react": "^1.17.0", "react": "^19.0.0", - "react-dom": "^19.0.0", - "@xyflow/react": "^12.3.0" + "react-dom": "^19.0.0" }, "devDependencies": { "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@vitejs/plugin-react": "^4.3.0", - "typescript": "^5.6.0", - "vite": "^5.4.0", - "tailwindcss": "^3.4.0", "autoprefixer": "^10.4.0", - "postcss": "^8.4.0" + "postcss": "^8.4.0", + "tailwindcss": "^3.4.0", + "typescript": "^5.6.0", + "vite": "^5.4.0" } } diff --git a/sundynix-desktop/frontend/src/App.tsx b/sundynix-desktop/frontend/src/App.tsx index 3dc1514..fa0ea33 100644 --- a/sundynix-desktop/frontend/src/App.tsx +++ b/sundynix-desktop/frontend/src/App.tsx @@ -13,6 +13,7 @@ import { Placeholder } from "./views/Placeholder"; import { submitTask, streamTokens, streamExec, type Identity } from "./lib/api"; import type { TaskDsl } from "./lib/dsl"; import { emptyRun, type RunState } from "./lib/run"; +import { ToastProvider } from "./ui"; const PLACEHOLDERS: Partial> = { home: { title: "工作台", desc: "概览:知识库 / 文档 / 近期运行 / 待办报告 / 配额计费 + 快捷入口。" }, @@ -77,6 +78,7 @@ export default function App() { ); return ( +
{/* 顶部柔光,增加纵深 */}
+ ); } diff --git a/sundynix-desktop/frontend/src/components/ExecTrace.tsx b/sundynix-desktop/frontend/src/components/ExecTrace.tsx index 0cab1ea..a07eebb 100644 --- a/sundynix-desktop/frontend/src/components/ExecTrace.tsx +++ b/sundynix-desktop/frontend/src/components/ExecTrace.tsx @@ -1,47 +1,53 @@ +import { + Cpu, + Bookmark, + Wrench, + MessageSquareText, + Sparkles, + ListTree, + FileText, + FileOutput, + CheckCircle2, + XCircle, + Loader2, + Circle, + type LucideIcon, +} from "lucide-react"; import { deriveNodes, type NodeTrace, type RunPhase } from "../lib/run"; import type { ExecEvent } from "../lib/api"; +import { cn } from "../ui"; // 各节点类别的图标与配色(与后端 ExecEvent.kind 对应)。 -const KIND: Record = { - system: { icon: "▸", cls: "text-slate-400", name: "系统" }, - memory: { icon: "◇", cls: "text-violet-300", name: "记忆召回" }, - tool: { icon: "⚙", cls: "text-amber-300", name: "工具调用" }, - prompt: { icon: "▤", cls: "text-sky-300", name: "提示词" }, - model: { icon: "✦", cls: "text-emerald-300", name: "模型推理" }, - plan: { icon: "◷", cls: "text-cyan-300", name: "规划" }, - section: { icon: "¶", cls: "text-indigo-300", name: "章节" }, - render: { icon: "▦", cls: "text-rose-300", name: "渲染" }, +const KIND: Record = { + system: { icon: Cpu, cls: "text-slate-400", name: "系统" }, + memory: { icon: Bookmark, cls: "text-brand-400", name: "记忆召回" }, + tool: { icon: Wrench, cls: "text-warn", name: "工具调用" }, + prompt: { icon: MessageSquareText, cls: "text-sky-300", name: "提示词" }, + model: { icon: Sparkles, cls: "text-success", name: "模型推理" }, + plan: { icon: ListTree, cls: "text-accent-400", name: "规划" }, + section: { icon: FileText, cls: "text-indigo-300", name: "章节" }, + render: { icon: FileOutput, cls: "text-danger", name: "渲染" }, }; function meta(kind: string) { - return KIND[kind] ?? { icon: "•", cls: "text-slate-400", name: kind }; + return KIND[kind] ?? { icon: Circle, cls: "text-slate-400", name: kind }; } function StatusDot({ status }: { status: NodeTrace["status"] }) { - if (status === "running") - return ; - if (status === "done") return ; - if (status === "error") return ; - return ; + if (status === "running") return ; + if (status === "done") return ; + if (status === "error") return ; + return ; } // ExecTrace 把执行事件流渲染为竖向轨道:每个节点一颗灯,实时点亮 + 耗时 + 入参/产出。 -export function ExecTrace({ - events, - phase, - compact, -}: { - events: ExecEvent[]; - phase?: RunPhase; - compact?: boolean; -}) { +export function ExecTrace({ events, phase, compact }: { events: ExecEvent[]; phase?: RunPhase; compact?: boolean }) { const nodes = deriveNodes(events); if (nodes.length === 0) { return ( -
- 运行后,这里会逐节点点亮执行轨迹:记忆召回 → 工具调用(入参/产出)→ 提示词组装 → 模型推理。 -
- 报告任务则显示:规划大纲 → 各章并行检索撰写 → 渲染 Word。 +
+ 运行后逐节点点亮执行轨迹 + 记忆召回 → 工具调用 → 提示词 → 模型推理 / 报告:规划 → 各章并行 → 渲染
); } @@ -53,28 +59,27 @@ export function ExecTrace({ {nodes.length} 个节点 · 累计 {total} ms - {phase === "streaming" && ● 执行中} - {phase === "done" && ✓ 完成} - {phase === "error" && ✗ 出错} + {phase === "streaming" && ● 执行中} + {phase === "done" && ✓ 完成} + {phase === "error" && ✗ 出错}
)}
    {nodes.map((n) => { const m = meta(n.kind); + const Icon = m.icon; return (
  1. -
    +
    - {m.icon} + {n.label} - {m.name} - {n.ms != null && n.ms > 0 && ( - {n.ms} ms - )} - {n.status === "running" && 运行中…} + {m.name} + {n.ms != null && n.ms > 0 && {n.ms} ms} + {n.status === "running" && 运行中…}
    {n.detail &&

    {n.detail}

    } {n.notes.map((note, i) => ( diff --git a/sundynix-desktop/frontend/src/panels/MemoryPanel.tsx b/sundynix-desktop/frontend/src/panels/MemoryPanel.tsx index 2ccb3cb..4e0aa12 100644 --- a/sundynix-desktop/frontend/src/panels/MemoryPanel.tsx +++ b/sundynix-desktop/frontend/src/panels/MemoryPanel.tsx @@ -1,40 +1,44 @@ import { useState } from "react"; +import { Check } from "lucide-react"; import { setMemory, type Identity } from "../lib/api"; +import { Button, Input, Textarea, Field, useToast } from "../ui"; // 偏好记忆面板 —— 让用户显式登记/纠正模型对自己的记忆(→ PUT /api/v1/memory)。 export function MemoryPanel({ identity }: { identity: Identity }) { + const toast = useToast(); const [key, setKey] = useState("回答偏好"); const [value, setValue] = useState("简洁、中文、多给要点"); const [saved, setSaved] = useState>([]); - const [msg, setMsg] = useState(""); const save = async () => { if (!key.trim()) return; try { const m = await setMemory(identity, key.trim(), value.trim()); setSaved((s) => [{ key: key.trim(), value: value.trim() }, ...s.filter((x) => x.key !== key.trim())]); - setMsg(m); + toast.push("success", m); } catch (e) { - setMsg(`✗ ${(e as Error).message}`); + toast.push("error", (e as Error).message); } }; - const inputCls = "rounded-md border border-line bg-ink-800 px-2 py-1 text-sm text-slate-200 focus:border-violet-500/60 focus:outline-none"; return (
    -

    偏好记忆(让模型知道是我)

    -
    - setKey(e.target.value)} placeholder="键,如 称呼 / 回答偏好" /> -