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="键,如 称呼 / 回答偏好" /> -