diff --git a/.agent/docs/architecture/可视化模块合同.md b/.agent/docs/architecture/可视化模块合同.md index da6dd3a..0d5b6f5 100644 --- a/.agent/docs/architecture/可视化模块合同.md +++ b/.agent/docs/architecture/可视化模块合同.md @@ -89,7 +89,7 @@ 1. 落库表分两类:**运行注册 / 运行回执 / 质量评判(98)与模型调用(97)已建**,但写路径未接通,视图暂空;候选 / 用户决策 / raw 全文 / 规划冻结(99/100/101)尚未建。没有它们 §4.2 的视图就是空的。 2. `db/ddl/96` 授权快照表**决定不启用**(领域索引 §9,单用户本地不做多租户授权),DDL 留存不 apply。 -3. 看板首版已建成(总览 / 作品 / 知识库 / 运行记录四空间,只读 HTTP + §4.1 部分视图 + 待落库占位格);待建 = 运行记录的分维列表与运行详情下钻、智能体空间(读 102 登记表)、作品/章工作区标签、沿来源下钻链。页面与信息架构见 §10。 +3. 看板首版已建成(总览 / 作品 / 知识库 / 智能体 / 运行记录五空间,只读 HTTP + §4.1 部分视图 + 待落库占位格;智能体空间读 102 登记表);待建 = 运行详情下钻、作品/章工作区标签、沿来源下钻链。页面与信息架构见 §10。 ## 10. 页面与信息架构(产品形态 SoT) @@ -196,7 +196,7 @@ **智能体页的 ⚠ 来源(A 方案,2026-07-30 拍板)**:角色画像 / 可配置项 / 技能合同在 Git 侧(`.claude/agents/`、`.claude/skills/`),库里没有。看板只读库,故新增**智能体/技能登记表**(`example_agent_role` + `example_skill`,设计见落库设计稿),把角色、模型归属、可配置项、技能读写合同**登记入库**——智能体配置也纳入留痕,看板只读这张表。 -**分阶段点亮(认)**:首版实现为四空间(总览 / 作品 / 知识库 / 运行记录;运行数据暂列运行记录空间,智能体空间待 102 登记表建好后上)。作品工作区**先上 [章节]**(正文已有),其余标签(大纲/细纲/设定/叙事状态、章工作区的细纲与出场卡)随对应表落库(规划表、候选表,落库设计稿第 3/2 步)逐格点亮;点亮前显示"待落库"占位,不假装已有。 +**分阶段点亮(认)**:首版实现为五空间(总览 / 作品 / 知识库 / 智能体 / 运行记录;智能体空间读 102 登记表 `example_agent_role`/`example_skill`,登记表由 `db/scripts/sync_agent_registry.py` 从 Git 侧同步,Git 仍是配置权威)。作品工作区**先上 [章节]**(正文已有),其余标签(大纲/细纲/设定/叙事状态、章工作区的细纲与出场卡)随对应表落库(规划表、候选表,落库设计稿第 3/2 步)逐格点亮;点亮前显示"待落库"占位,不假装已有。 ### 10.2 UI 形态 diff --git a/dashboard/server.py b/dashboard/server.py index de56117..57d0150 100644 --- a/dashboard/server.py +++ b/dashboard/server.py @@ -205,6 +205,7 @@ NAV = [ ("/", "总览 · 健康", "数据权威 08"), ("/works", "作品", "作品领域 01"), ("/knowledge", "知识库", "实体 02 · 范式 03"), + ("/agents", "智能体", "Agent 07"), ("/runs", "运行记录", "流程 05 · 质量 06"), ] @@ -499,6 +500,90 @@ def view_runs_page(): return layout("运行记录", body, "/runs") +# ── 智能体(07:读 102 登记表 example_agent_role / example_skill;权威在 Git 侧,登记脚本同步)──── +def _fmt_list(v): + """JSONB 列表 → 逗号串;非列表原样转义。""" + if isinstance(v, list): + return esc(", ".join(str(x) for x in v)) if v else '∅' + return esc(v) + + +def view_agents(): + _, roles = q("SELECT role, display_name, model, tools FROM example_agent_role " + "WHERE deleted=false ORDER BY role") + _, skills = q("SELECT skill_name, purpose, model_used FROM example_skill " + "WHERE deleted=false ORDER BY skill_name") + role_rows = "".join( + f"{esc(r[1] or r[0])} " + f"{esc(r[0])}" + f"{esc(r[2])}{_fmt_list(r[3])}" for r in roles) + skill_rows = "".join( + f"{esc(s[0])}" + f"{esc((s[1] or '')[:56])}{esc(s[2])}" for s in skills) + body = (f"

智能体角色与技能 · 读 102 登记表(权威在 Git 侧,登记脚本同步)· Agent 07

" + f"
角色({len(roles)})
" + f"{role_rows}
角色模型工具
" + f"
技能({len(skills)})
" + f"{skill_rows}
技能用途用模型
") + return layout("智能体", body, "/agents") + + +def view_agent_role(role): + _, rr = q("SELECT role, display_name, model, effort, budget_usd, tools, responsibility, source_ref, synced_at " + "FROM example_agent_role WHERE role=%s AND deleted=false", (role,)) + if not rr: + return layout("角色", "

角色不存在

", "/agents") + role_, disp, model, effort, budget, tools, resp, src, synced = rr[0] + info = (f"
{esc(disp or role_)} {esc(role_)}
" + f"" + f"" + f"" + f"" + f"" + f"" + f"
模型归属{esc(model)}
工具{_fmt_list(tools)}
可配置项effort {esc(effort)} · 预算 {esc(budget)}(暂空待补)
出处{esc(src)} · 同步于 {esc(synced)}
职责{esc(resp)}
") + _, runs = q("SELECT run_id, stage_kind, revision, create_time FROM example_run_receipt " + "WHERE adapter_role=%s ORDER BY create_time DESC LIMIT 20", (role,)) + runs_html = ("
本角色运行(回执,最近 20)
" + "" + + "".join(f"" for r in runs) + + "
run_id阶段revision时间
{esc(r[0])}{esc(r[1])}{esc(r[2])}{esc(r[3])}
") if runs else empty_gap("本角色暂无运行回执", "创作链落库后,该角色的运行会显示在这里。") + _, calls = q("SELECT call_time, requested_model_id, out_tokens, cost_usd FROM example_llm_call " + "WHERE caller=%s ORDER BY call_time DESC LIMIT 20", (role,)) + calls_html = ("
本角色模型调用(最近 20)
" + "" + + "".join(f"" for c in calls) + + "
时间模型输出token成本
{esc(c[0])}{esc(c[1])}{fmt_num(c[2])}${float(c[3] or 0):.5f}
") if calls else empty_gap("本角色暂无模型调用", "") + _, quals = q("SELECT q.judge_kind, q.dimension, q.conclusion, q.failure_class FROM example_quality_result q " + "JOIN example_run_receipt r ON r.id=q.receipt_id WHERE r.adapter_role=%s ORDER BY q.create_time DESC LIMIT 20", (role,)) + quals_html = ("
本角色质量评判(最近 20)
" + "" + + "".join(f"" for u in quals) + + "
类型维度结论失败类别
{esc(u[0])}{esc(u[1])}{chip(u[2])}{esc(u[3])}
") if quals else empty_gap("本角色暂无质量评判", "") + body = (f"
智能体 / {esc(disp or role_)}
" + f"

{esc(disp or role_)}

{info}

运行与产出

{runs_html}{quals_html}{calls_html}") + return layout(f"角色 · {disp or role_}", body, "/agents") + + +def view_skill(name): + _, ss = q("SELECT skill_name, purpose, consumer, reads, writes, red_lines, model_used, source_ref, synced_at " + "FROM example_skill WHERE skill_name=%s AND deleted=false", (name,)) + if not ss: + return layout("技能", "

技能不存在

", "/agents") + sn, purpose, consumer, reads, writes, red_lines, model_used, src, synced = ss[0] + body = (f"
智能体 / 技能 / {esc(sn)}
" + f"

{esc(sn)}

" + f"
" + f"" + f"" + f"" + f"" + f"" + f"" + f"
用途{esc(purpose)}
给谁用{esc(consumer) if consumer else '待补'}
用模型{esc(model_used) if model_used else '确定性工具(不用模型)'}
涉及表{_fmt_list(reads)}
自动抽取,读/写方向待人工核
红线{esc(red_lines) if red_lines else '待补'}
出处{esc(src)} · 同步于 {esc(synced)}
") + return layout(f"技能 · {sn}", body, "/agents") + + # ── 路由 ───────────────────────────────────────────────────────────────────── _ROUTES = [ (re.compile(r"^/$"), lambda m: view_home()), @@ -506,6 +591,9 @@ _ROUTES = [ (re.compile(r"^/works/(\d+)$"), lambda m: view_work(int(m.group(1)))), (re.compile(r"^/works/(\d+)/ch/(\d+)$"), lambda m: view_chapter(int(m.group(1)), int(m.group(2)))), (re.compile(r"^/knowledge$"), lambda m: view_knowledge()), + (re.compile(r"^/agents$"), lambda m: view_agents()), + (re.compile(r"^/agents/role/([\w-]+)$"), lambda m: view_agent_role(m.group(1))), + (re.compile(r"^/agents/skill/([\w-]+)$"), lambda m: view_skill(m.group(1))), (re.compile(r"^/runs$"), lambda m: view_runs_page()), ]