架构变更(主人要求,design/07 §1/§2 已改定稿):把「9 个行为模式 = 9 个独立页面」改为 「一个共享页面 + 每条消息绑 mode,消息框按对应 mode 渲染」。 - ChatMessage 增 mode?(types.ts);useAppStore addUserMessage/beginAssistant 带 mode; useSendChat.send(text, mode) / chatStream body 带 mode(通知后端按 mode 回复)。 - MessageBubble 按 message.mode 分发到 components/messages/ 各形态渲染器 (chat 气泡 / code 等宽代码块 / home 设备卡 / docs 文档卡 / work 任务卡 / vision 视觉卡)。 - ChatInput 顶部加「行为模式」chip 切换下一条消息的 mode(默认 chat);输入框上方显示当前模式。 - ModeRouter 收敛为只渲染 ChatShell;删除 9 个旧独立 mode 页面;Nav/TopBar 的 mode 改为 「输入模式」语义;settings 归管理面板(design/10)。 同时补全上一轮 emoji→MD3 图标(约 23 文件):新增 Icon.tsx(MD3 SVG、IconName、零依赖、 离线优先),全部界面 emoji 替换为 <Icon name=... />;正文说明箭头保留。 npm run build 绿(tsc 0 错,79→71 模块,JS 284KB/gzip 86KB)。
142 lines
5.8 KiB
TypeScript
142 lines
5.8 KiB
TypeScript
import { useState } from "react";
|
||
import { useAppStore } from "../../store/useAppStore";
|
||
import { useAuditStore } from "../audit";
|
||
import { CardGrid, Notice, PageHeader, StatCard } from "../components";
|
||
import { Button, Surface, Tag } from "../../components/primitives";
|
||
import { Icon } from "../../components/Icon";
|
||
|
||
type Tab = "logs" | "trace" | "metrics" | "audit";
|
||
|
||
const TABS: ReadonlyArray<{ id: Tab; label: string }> = [
|
||
{ id: "logs", label: "实时日志" },
|
||
{ id: "trace", label: "Tracing" },
|
||
{ id: "metrics", label: "指标" },
|
||
{ id: "audit", label: "审计" },
|
||
];
|
||
|
||
export function ObservabilityModule(): React.ReactNode {
|
||
const [tab, setTab] = useState<Tab>("logs");
|
||
const deviceUpdates = useAppStore((s) => s.deviceUpdates);
|
||
const audit = useAuditStore((s) => s.records);
|
||
const clearAudit = useAuditStore((s) => s.clear);
|
||
|
||
return (
|
||
<div className="space-y-5">
|
||
<PageHeader title="可观测性" subtitle="实时日志 tail · Tracing(LLM/工具/编排) · 指标 · 审计" />
|
||
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
{TABS.map((t) => (
|
||
<button
|
||
key={t.id}
|
||
onClick={() => setTab(t.id)}
|
||
className={`rounded-[var(--mu-radius)] px-3 py-1.5 text-xs font-medium transition-all duration-200 ease-[var(--mu-spring)] ${
|
||
tab === t.id ? "bg-[var(--mu-accent)]/20 text-[var(--mu-accent-hi)]" : "bg-[var(--mu-surface-4)] text-[var(--mu-text-mid)]"
|
||
}`}
|
||
>
|
||
{t.label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{tab === "logs" ? <LogsTail lines={deviceUpdates} /> : null}
|
||
{tab === "trace" ? <TraceView /> : null}
|
||
{tab === "metrics" ? <MetricsView /> : null}
|
||
{tab === "audit" ? <AuditView records={audit} onClear={clearAudit} /> : null}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function LogsTail({ lines }: { lines: string[] }): React.ReactNode {
|
||
return (
|
||
<Surface className="space-y-3">
|
||
<div className="flex items-center gap-2">
|
||
<h3 className="text-sm font-semibold">实时日志流(WS 事件回显)</h3>
|
||
<Tag tone="ok">tail</Tag>
|
||
</div>
|
||
{lines.length === 0 ? (
|
||
<Notice>暂无实时事件。接入 /ws/chat 后,此处回显 stream / tool / device_update / reminder 等事件。</Notice>
|
||
) : (
|
||
<pre className="max-h-80 overflow-auto whitespace-pre-wrap rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 font-mono text-xs text-[var(--mu-text-mid)]">
|
||
{lines.join("\n")}
|
||
</pre>
|
||
)}
|
||
<Notice>完整后端日志分级 tail / 过滤 / 导出需后端 M10+ 提供。</Notice>
|
||
</Surface>
|
||
);
|
||
}
|
||
|
||
function TraceView(): React.ReactNode {
|
||
return (
|
||
<Surface className="space-y-3">
|
||
<h3 className="text-sm font-semibold">Tracing 视图</h3>
|
||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||
{["LLM 调用链", "工具链", "编排链"].map((x) => (
|
||
<div key={x} className="rounded-[var(--mu-radius)] border border-[var(--mu-glass-border)] bg-[var(--mu-surface-3)]/60 p-4 text-center text-xs text-[var(--mu-text-mid)]">
|
||
<div className="mb-2 text-lg">{x}</div>
|
||
{x} 占位 —— 后端 M10+ 提供 span/trace 数据
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Surface>
|
||
);
|
||
}
|
||
|
||
function MetricsView(): React.ReactNode {
|
||
return (
|
||
<div className="space-y-4">
|
||
<CardGrid>
|
||
<StatCard icon={<Icon name="gear" size={18} />} label="请求 QPS" value="—" sub="网关指标占位" tone="accent" />
|
||
<StatCard icon={<Icon name="robot" size={18} />} label="LLM 调用次数" value="—" sub="用量/费用占位" tone="neutral" />
|
||
<StatCard icon={<Icon name="agent" size={18} />} label="子代理事件" value="—" sub="编排指标占位" tone="ok" />
|
||
<StatCard icon={<Icon name="memory" size={18} />} label="工时/Token" value="—" sub="成本占位" tone="warn" />
|
||
</CardGrid>
|
||
<Notice>指标时序图 / 导出需后端 M10+ 提供。</Notice>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
interface AuditViewProps {
|
||
records: ReturnType<typeof useAuditStore.getState>["records"];
|
||
onClear: () => void;
|
||
}
|
||
|
||
function AuditView({ records, onClear }: AuditViewProps): React.ReactNode {
|
||
return (
|
||
<Surface className="space-y-3">
|
||
<div className="flex items-center gap-2">
|
||
<h3 className="text-sm font-semibold">审计日志(who / what / when / result)</h3>
|
||
<span className="text-xs text-[var(--mu-text-lo)]">管理 + 敏感操作全程落审计</span>
|
||
<span className="ml-auto">
|
||
<Button size="sm" variant="ghost" onClick={onClear}>
|
||
清空本地
|
||
</Button>
|
||
</span>
|
||
</div>
|
||
{records.length === 0 ? (
|
||
<Notice>暂无审计记录。本面板的管理/敏感操作(策略写入、主人档案编辑、API key 签发等)都会在此登记。</Notice>
|
||
) : (
|
||
<table className="w-full text-sm">
|
||
<thead>
|
||
<tr className="border-b border-[var(--mu-glass-border)] text-left text-xs uppercase tracking-wider text-[var(--mu-text-lo)]">
|
||
<th className="px-2 py-1.5">who</th>
|
||
<th className="px-2 py-1.5">what</th>
|
||
<th className="px-2 py-1.5">when</th>
|
||
<th className="px-2 py-1.5">result</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{records.map((r) => (
|
||
<tr key={r.id} className="border-b border-[var(--mu-glass-border)]/60 last:border-0">
|
||
<td className="px-2 py-2 text-xs">{r.who}</td>
|
||
<td className="px-2 py-2 text-xs font-mono">{r.what}</td>
|
||
<td className="px-2 py-2 text-xs text-[var(--mu-text-lo)]">{new Date(r.when).toLocaleString()}</td>
|
||
<td className="px-2 py-2"><Tag tone={r.result === "ok" ? "ok" : "err"}>{r.result}</Tag></td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
)}
|
||
</Surface>
|
||
);
|
||
}
|