feat(frontend/admin): M10 后台管理面板(/admin hash 路由 + 15 大模块 + RBAC(owner/admin/viewer) + 审计 + Miuix);Docs/api-client.md v0.3 契约补全;tsc+vite build 通过
This commit is contained in:
1 parent
4c48e94659
commit
f43eb17030
26 files changed
+2253
-4
No files matched your search
@@ -0,0 +1,140 @@
|
||||
import { useState } from "react";
|
||||
import { useAppStore } from "../../store/useAppStore";
|
||||
import type { AgentInfo, AgentStatus } from "../../lib/types";
|
||||
import { Notice, PageHeader } from "../components";
|
||||
import { Button, EmptyState, Surface, Tag, type TagTone } from "../../components/primitives";
|
||||
import { sendUp } from "../../lib/ws";
|
||||
import { useAuditStore } from "../audit";
|
||||
|
||||
const statusTone: Record<AgentStatus, { label: string; tone: TagTone }> = {
|
||||
idle: { label: "空闲", tone: "neutral" },
|
||||
running: { label: "运行中", tone: "accent" },
|
||||
blocked: { label: "阻塞", tone: "warn" },
|
||||
done: { label: "完成", tone: "ok" },
|
||||
error: { label: "错误", tone: "err" },
|
||||
};
|
||||
|
||||
function AgentCard({ agent, onKill }: { agent: AgentInfo; onKill: (id: string) => void }): React.ReactNode {
|
||||
const meta = statusTone[agent.status] ?? statusTone.idle;
|
||||
return (
|
||||
<Surface className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`h-2.5 w-2.5 rounded-full ${agent.status === "running" ? "bg-[var(--mu-accent)] mu-blink" : "bg-[var(--mu-surface-5)]"}`} />
|
||||
<span className="text-sm font-medium">{agent.name}</span>
|
||||
{agent.role ? <span className="text-xs text-[var(--mu-text-lo)]">{agent.role}</span> : null}
|
||||
<Tag tone={meta.tone} className="ml-auto">{meta.label}</Tag>
|
||||
</div>
|
||||
{typeof agent.progress === "number" ? (
|
||||
<div className="h-1.5 w-full overflow-hidden rounded-[var(--mu-radius-pill)] bg-[var(--mu-surface-4)]">
|
||||
<div className="h-full rounded-[var(--mu-radius-pill)] bg-[var(--mu-accent)]" style={{ width: `${Math.min(100, Math.max(0, agent.progress))}%` }} />
|
||||
</div>
|
||||
) : null}
|
||||
{agent.thinking ? (
|
||||
<pre className="max-h-24 overflow-auto whitespace-pre-wrap rounded-[var(--mu-radius-sm)] bg-[var(--mu-surface-2)] px-2 py-1.5 text-xs text-[var(--mu-text-mid)]">{agent.thinking}</pre>
|
||||
) : null}
|
||||
{agent.tools?.map((t, i) => (
|
||||
<div key={i} className="text-xs text-[var(--mu-text-lo)]">⚙ {t.name}{t.ok === false ? " ✗" : ""}</div>
|
||||
))}
|
||||
{agent.message ? <div className="text-xs text-[var(--mu-text-mid)]">{agent.message}</div> : null}
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="ghost" onClick={() => sendUp({ type: "task_output_query", data: { taskId: agent.id } })}>
|
||||
上下文查询
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" onClick={() => onKill(agent.id)}>
|
||||
终止
|
||||
</Button>
|
||||
</div>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentsModule(): React.ReactNode {
|
||||
const agents = useAppStore((s) => s.agents);
|
||||
const flowGraph = useAppStore((s) => s.flowGraph);
|
||||
const taskLogs = useAppStore((s) => s.taskLogs);
|
||||
const clearTaskLog = useAppStore((s) => s.clearTaskLog);
|
||||
const log = useAuditStore((s) => s.log);
|
||||
const [tab, setTab] = useState<"main" | "sub">("main");
|
||||
|
||||
const subAgents = agents.filter((a) => a.id !== "main");
|
||||
const list = tab === "main" ? agents.filter((a) => a.id === "main") : subAgents;
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<PageHeader
|
||||
title="Agent 运行监控"
|
||||
subtitle="主会话专属页签 + 子代理:状态/进度/思维链/工具链/流程图/后台任务/agent 消息流"
|
||||
actions={
|
||||
<Button
|
||||
variant="soft"
|
||||
onClick={() => {
|
||||
log("agent.respawn", "ok");
|
||||
alert("重派(占位):后端 M10+ 提供子代理重派。");
|
||||
}}
|
||||
>
|
||||
重派
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<TabPill active={tab === "main"} onClick={() => setTab("main")}>主会话</TabPill>
|
||||
<TabPill active={tab === "sub"} onClick={() => setTab("sub")}>子代理 ({subAgents.length})</TabPill>
|
||||
</div>
|
||||
|
||||
{flowGraph ? (
|
||||
<Surface className="flex flex-wrap items-center gap-3">
|
||||
{flowGraph.nodes.map((n, i) => (
|
||||
<div key={n.id} className="flex items-center gap-3">
|
||||
<Tag tone={n.status === "running" ? "accent" : "neutral"}>{n.label}</Tag>
|
||||
{i < flowGraph.nodes.length - 1 ? <span className="text-[var(--mu-text-lo)]">→</span> : null}
|
||||
</div>
|
||||
))}
|
||||
</Surface>
|
||||
) : (
|
||||
<Notice>流程图占位 —— 连接 /ws/chat 后按 graph_update 事件渲染。</Notice>
|
||||
)}
|
||||
|
||||
{list.length === 0 ? (
|
||||
<EmptyState icon="🧩" title={tab === "main" ? "无主会话 Agent" : "暂无子代理"} />
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{list.map((a) => (
|
||||
<AgentCard key={a.id} agent={a} onKill={(id) => { log(`agent.kill:${id}`, "ok"); alert(`终止 ${id}(占位)`); }} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Surface className="space-y-2">
|
||||
<h3 className="text-sm font-semibold">Agent 消息流 / 后台任务日志</h3>
|
||||
{Object.keys(taskLogs).length === 0 ? (
|
||||
<div className="text-xs text-[var(--mu-text-lo)]">暂无任务日志。</div>
|
||||
) : (
|
||||
Object.entries(taskLogs).map(([id, text]) => (
|
||||
<div key={id} className="rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] p-2">
|
||||
<div className="mb-1 flex items-center gap-2 text-xs">
|
||||
<Tag tone="accent">{id}</Tag>
|
||||
<button className="ml-auto text-[var(--mu-text-lo)]" onClick={() => clearTaskLog(id)}>清空</button>
|
||||
</div>
|
||||
<pre className="max-h-40 overflow-auto whitespace-pre-wrap font-mono text-xs text-[var(--mu-text-mid)]">{text}</pre>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<Notice>子代理链上下文查询、终止/重派、流程图导出需后端 M10+ 提供。</Notice>
|
||||
</Surface>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TabPill({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }): React.ReactNode {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`rounded-[var(--mu-radius)] px-3 py-1.5 text-xs font-medium transition-all duration-200 ease-[var(--mu-spring)] ${
|
||||
active ? "bg-[var(--mu-accent)]/20 text-[var(--mu-accent-hi)]" : "bg-[var(--mu-surface-4)] text-[var(--mu-text-mid)]"
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user