feat(desktop): 工业化升级 A —— 设计系统地基(primitives + lucide + 语义令牌)
把"手搓内联 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<string, { icon: string; cls: string; name: string }> = {
|
||||
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<string, { icon: LucideIcon; cls: string; name: string }> = {
|
||||
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 <span className="h-2.5 w-2.5 animate-pulse rounded-full bg-cyan-400 shadow-[0_0_8px_rgba(34,211,238,0.8)]" />;
|
||||
if (status === "done") return <span className="h-2.5 w-2.5 rounded-full bg-emerald-400" />;
|
||||
if (status === "error") return <span className="h-2.5 w-2.5 rounded-full bg-rose-500" />;
|
||||
return <span className="h-2.5 w-2.5 rounded-full bg-slate-600" />;
|
||||
if (status === "running") return <Loader2 className="h-4 w-4 animate-spin text-accent-400" strokeWidth={2.4} />;
|
||||
if (status === "done") return <CheckCircle2 className="h-4 w-4 text-success" strokeWidth={2.2} />;
|
||||
if (status === "error") return <XCircle className="h-4 w-4 text-danger" strokeWidth={2.2} />;
|
||||
return <Circle className="h-4 w-4 text-slate-600" strokeWidth={2} />;
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div className="flex h-full items-center justify-center px-4 py-8 text-center text-xs text-slate-600">
|
||||
运行后,这里会逐节点点亮执行轨迹:记忆召回 → 工具调用(入参/产出)→ 提示词组装 → 模型推理。
|
||||
<br />
|
||||
报告任务则显示:规划大纲 → 各章并行检索撰写 → 渲染 Word。
|
||||
<div className="flex h-full flex-col items-center justify-center gap-1 px-4 py-8 text-center text-xs text-slate-600">
|
||||
<span>运行后逐节点点亮执行轨迹</span>
|
||||
<span className="text-slate-700">记忆召回 → 工具调用 → 提示词 → 模型推理 / 报告:规划 → 各章并行 → 渲染</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -53,28 +59,27 @@ export function ExecTrace({
|
||||
<span>{nodes.length} 个节点</span>
|
||||
<span>·</span>
|
||||
<span>累计 {total} ms</span>
|
||||
{phase === "streaming" && <span className="text-cyan-400">● 执行中</span>}
|
||||
{phase === "done" && <span className="text-emerald-400">✓ 完成</span>}
|
||||
{phase === "error" && <span className="text-rose-400">✗ 出错</span>}
|
||||
{phase === "streaming" && <span className="text-accent-400">● 执行中</span>}
|
||||
{phase === "done" && <span className="text-success">✓ 完成</span>}
|
||||
{phase === "error" && <span className="text-danger">✗ 出错</span>}
|
||||
</div>
|
||||
)}
|
||||
<ol className="relative ml-2 space-y-2 border-l border-line pl-4">
|
||||
{nodes.map((n) => {
|
||||
const m = meta(n.kind);
|
||||
const Icon = m.icon;
|
||||
return (
|
||||
<li key={n.node} className="relative">
|
||||
<span className="absolute -left-[22px] top-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-ink-900">
|
||||
<StatusDot status={n.status} />
|
||||
</span>
|
||||
<div className="rounded-lg border border-line bg-ink-950/60 px-3 py-2">
|
||||
<div className="rounded-md border border-line bg-ink-950/60 px-3 py-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`text-sm leading-none ${m.cls}`}>{m.icon}</span>
|
||||
<Icon className={cn("h-3.5 w-3.5 shrink-0", m.cls)} strokeWidth={2} />
|
||||
<span className="text-xs font-medium text-slate-200">{n.label}</span>
|
||||
<span className={`rounded px-1.5 py-0.5 text-[9px] ${m.cls} bg-white/5`}>{m.name}</span>
|
||||
{n.ms != null && n.ms > 0 && (
|
||||
<span className="ml-auto font-mono text-[10px] text-slate-500">{n.ms} ms</span>
|
||||
)}
|
||||
{n.status === "running" && <span className="ml-auto text-[10px] text-cyan-400">运行中…</span>}
|
||||
<span className={cn("rounded bg-white/5 px-1.5 py-0.5 text-[9px]", m.cls)}>{m.name}</span>
|
||||
{n.ms != null && n.ms > 0 && <span className="ml-auto font-mono text-[10px] text-slate-500">{n.ms} ms</span>}
|
||||
{n.status === "running" && <span className="ml-auto text-[10px] text-accent-400">运行中…</span>}
|
||||
</div>
|
||||
{n.detail && <p className="mt-1 break-words text-[11px] leading-relaxed text-slate-400">{n.detail}</p>}
|
||||
{n.notes.map((note, i) => (
|
||||
|
||||
Reference in New Issue
Block a user