#!/usr/bin/env python3 """只读可视化看板(muse-example 库)—— 纯 Python 标准库 HTTP 服务。 合同:.agent/docs/architecture/可视化模块合同.md(唯一 SoT)。 布局:对齐 muse 的领域布局(作品 / 知识库 / 智能体 三空间 + 总览), 与领域索引(domains/_index.md)的领域分区一致——看板按领域看库,不按功能切。 命根子(验收按机械门查): 1. 连接只读:会话级 default_transaction_read_only=on,任何写语句数据库直接报错。 2. 代码无写语句:全文件只允许 SELECT,无 INSERT/UPDATE/DELETE/DDL(grep 即门)。 3. 不接会写的通道:不调用 decide-candidate / access-database Skill,只自己只读查库。 4. 挂了不牵连:进程崩了不影响库内正式内容与创作链。 视觉系统:dataviz 方法(已验证默认调色板,light/dark 双模式,状态色固定不主题化)。 跑法:.venv/bin/python dashboard/server.py (默认 http://127.0.0.1:8765) """ import html import json import re import traceback from collections import Counter from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from urllib.parse import parse_qs, urlencode, urlparse from muse_db import WINDOW_BUDGET_USD, WINDOW_CALL_CAP, connect HOST, PORT = "127.0.0.1", 8765 # AI 味案例的正式来源是 muse-example;离线 JSON 只作为数据库不可用时的恢复/回退证据。 AI_FLAVOR_REPORT_DIR = Path(__file__).resolve().parent / "fixtures" AI_FLAVOR_REVALIDATION_SCHEMA = "ai-flavor-revalidation-v1" AI_FLAVOR_CASE_SCHEMA = "ai-flavor-case-v1" AI_FLAVOR_TENANT_ID = 1 AI_FLAVOR_PAGE_SIZE = 40 _AI_FLAVOR_PATTERN_LABELS = { "lexical.meta_disclaimer": "元话语套式", "lexical.stock_micro_expression": "库存微表情", "lexical.abstract_atmosphere": "抽象气氛", } _AI_FLAVOR_LABELS = { "unclassified": "未分类", "sf": "当前认为应修", "snf": "表面相似但不应修", "boundary": "需上下文裁决", "regression": "修订回归", } _AI_FLAVOR_REVALIDATION_LABELS = { "verified": "来源已验证", "stale": "来源已变化", "unavailable": "来源不可得", "card_mismatch": "位置不匹配", "missing": "尚未重验", } def q(sql, params=()): """只读查询,返回 (列名, 行)。每次请求实时查库,不缓存(合同 §7)。 readonly=True 是会话级锁死:写语句会被 PostgreSQL 拒绝(机械门,不靠自觉)。 """ with connect(readonly=True) as conn: cur = conn.execute(sql, params) cols = [d.name for d in cur.description] if cur.description else [] return cols, cur.fetchall() def one(sql, params=()): """取单值。""" _, rows = q(sql, params) return rows[0][0] if rows else None def table_exists(name): return one("SELECT 1 FROM pg_tables WHERE schemaname='public' AND tablename=%s", (name,)) def table_counts(names): """一次只读往返返回一组表的活行数,缺表不阻断总览。""" requested = list(dict.fromkeys(names)) if not requested: return {} _, existing_rows = q( "SELECT tablename FROM pg_tables WHERE schemaname='public' AND tablename = ANY(%s)", (requested,), ) existing = [row[0] for row in existing_rows] if not existing: return {} _, deleted_rows = q( "SELECT table_name FROM information_schema.columns " "WHERE table_schema='public' AND column_name='deleted' AND table_name = ANY(%s)", (existing,), ) has_deleted = {row[0] for row in deleted_rows} parts = [] params = [] for name in existing: # name 来自 pg_tables 且只取请求白名单,双引号转义仍保留防御性边界。 identifier = '"' + name.replace('"', '""') + '"' where = " WHERE deleted=false" if name in has_deleted else "" parts.append(f"SELECT %s::text AS table_name, count(*)::bigint AS row_count FROM {identifier}{where}") params.append(name) _, rows = q(" UNION ALL ".join(parts), tuple(params)) return {row[0]: int(row[1]) for row in rows} # ── 视觉系统:dataviz 已验证调色板(light/dark),状态色固定不主题化 ────────────── CSS = """ :root { color-scheme: light; --plane:#f9f9f7; --surface:#fcfcfb; --ink:#0b0b0b; --ink2:#52514e; --muted:#898781; --grid:#e1e0d9; --baseline:#c3c2b7; --ring:rgba(11,11,11,.10); --s1:#2a78d6; --s2:#008300; --s3:#e87ba4; --s4:#eda100; --s5:#1baf7a; --s6:#eb6834; --s7:#4a3aa7; --s8:#e34948; --good:#0ca30c; --warn:#fab219; --serious:#ec835a; --crit:#d03b3b; --goodtxt:#006300; } @media (prefers-color-scheme: dark) { :root { color-scheme: dark; --plane:#0d0d0d; --surface:#1a1a19; --ink:#ffffff; --ink2:#c3c2b7; --muted:#898781; --grid:#2c2c2a; --baseline:#383835; --ring:rgba(255,255,255,.10); --s1:#3987e5; --s2:#008300; --s3:#d55181; --s4:#c98500; --s5:#199e70; --s6:#d95926; --s7:#9085e9; --s8:#e66767; --goodtxt:#0ca30c; } } * { box-sizing:border-box; margin:0; padding:0; } body { background:var(--plane); color:var(--ink); font:15px/1.6 system-ui,-apple-system,"Segoe UI","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif; display:flex; min-height:100vh; } nav { width:212px; flex:none; background:var(--surface); border-right:1px solid var(--grid); padding:22px 14px; position:sticky; top:0; height:100vh; } nav .brand { font-weight:700; font-size:16px; letter-spacing:.02em; padding:0 8px 16px; } nav .brand small { display:block; font-weight:400; color:var(--muted); font-size:12px; } nav a { display:block; padding:8px 10px; border-radius:6px; color:var(--ink2); text-decoration:none; font-size:14px; transition:background .12s,color .12s; } nav a:hover { background:var(--grid); color:var(--ink); } nav a.on { background:var(--grid); color:var(--ink); font-weight:600; } nav .dom { font-size:10.5px; color:var(--muted); padding:2px 10px 0; letter-spacing:.04em; } nav .ro { margin-top:24px; padding:10px; font-size:11px; color:var(--muted); border:1px dashed var(--baseline); border-radius:6px; } main { flex:1; padding:30px 38px 60px; max-width:1080px; } h1 { font-size:26px; font-weight:750; letter-spacing:-.01em; } h1 .sub { display:block; font-size:13px; font-weight:400; color:var(--muted); margin-top:4px; } h2 { font-size:16px; font-weight:650; margin:30px 0 12px; } p.note { color:var(--ink2); font-size:13.5px; margin:8px 0; } .tiles { display:grid; grid-template-columns:repeat(auto-fill,minmax(168px,1fr)); gap:12px; margin-top:18px; } .tile { background:var(--surface); border:1px solid var(--ring); border-radius:8px; padding:14px 16px; transition:transform .12s, box-shadow .12s; } .tile:hover { transform:translateY(-1px); box-shadow:0 3px 10px var(--ring); } .tile .v { font-size:28px; font-weight:720; letter-spacing:-.02em; line-height:1.15; } /* 大数字用比例数字 */ .tile .l { font-size:12.5px; color:var(--muted); margin-top:2px; } .tile .d { font-size:12px; margin-top:5px; color:var(--ink2); } .tile.zero .v { color:var(--muted); } .card { background:var(--surface); border:1px solid var(--ring); border-radius:8px; margin-top:14px; overflow:hidden; } .card > .hd { padding:12px 16px; border-bottom:1px solid var(--grid); font-weight:650; font-size:14px; } table { width:100%; border-collapse:collapse; font-size:13.5px; } th { text-align:left; color:var(--muted); font-weight:600; font-size:12px; padding:9px 14px; border-bottom:1px solid var(--grid); white-space:nowrap; } td { padding:9px 14px; border-bottom:1px solid var(--grid); color:var(--ink2); vertical-align:top; } tr:last-child td { border-bottom:none; } tbody tr { transition:background .1s; } tbody tr:hover { background:var(--plane); } td.num, th.num { text-align:right; font-variant-numeric:tabular-nums; } /* 对齐的数字列用等宽数字 */ td a { color:var(--s1); text-decoration:none; } td a:hover { text-decoration:underline; } .chip { display:inline-flex; align-items:center; gap:6px; font-size:12px; font-weight:600; padding:2px 9px; border-radius:999px; white-space:nowrap; border:1px solid transparent; } .chip .dot { width:7px; height:7px; border-radius:50%; flex:none; } .chip.good { color:var(--goodtxt); background:color-mix(in srgb,var(--good) 12%, transparent); } .chip.good .dot { background:var(--good); } .chip.warn { color:var(--ink2); background:color-mix(in srgb,var(--warn) 16%, transparent); } .chip.warn .dot { background:var(--warn); } .chip.serious { color:var(--ink2); background:color-mix(in srgb,var(--serious) 16%, transparent); } .chip.serious .dot { background:var(--serious); } .chip.crit { color:var(--crit); background:color-mix(in srgb,var(--crit) 12%, transparent); } .chip.crit .dot { background:var(--crit); } .chip.gap { color:var(--muted); border:1px dashed var(--baseline); background:transparent; } .chip.gap .dot { background:var(--baseline); } .chip code, .field-code { font-size:11px; font-weight:500; color:inherit; opacity:.78; } .enum-code { font-size:11px; color:var(--muted); white-space:nowrap; } .knowledge-grid, .planning-grid { display:grid; grid-template-columns:minmax(0,1.1fr) minmax(320px,.9fr); gap:14px; align-items:start; margin-top:14px; } .knowledge-grid > .card, .planning-grid > .card { margin-top:0; } .knowledge-filter { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:14px; } .knowledge-filter input, .knowledge-filter select, .knowledge-filter button { min-height:34px; border:1px solid var(--baseline); border-radius:5px; background:var(--surface); color:var(--ink); font:inherit; padding:5px 9px; } .knowledge-filter input { flex:1 1 220px; min-width:180px; } .knowledge-filter button { cursor:pointer; background:var(--ink); color:var(--plane); border-color:var(--ink); } .knowledge-filter button:hover { opacity:.86; } .knowledge-filter .clear { color:var(--s1); font-size:13px; text-decoration:none; } .knowledge-filter .clear:hover { text-decoration:underline; } .knowledge-card-list { display:flex; flex-direction:column; } .knowledge-card-item { display:block; padding:13px 16px; border-top:1px solid var(--grid); color:var(--ink); text-decoration:none; } .knowledge-card-item:first-child { border-top:none; } .knowledge-card-item:hover, .knowledge-card-item.selected { background:var(--plane); } .knowledge-card-item.selected { box-shadow:inset 3px 0 var(--s1); } .knowledge-card-item .title-row { display:flex; gap:8px; align-items:baseline; justify-content:space-between; } .knowledge-card-item .title { font-weight:650; overflow-wrap:anywhere; } .knowledge-card-item .meta, .knowledge-card-item .summary { color:var(--muted); font-size:12px; margin-top:4px; } .knowledge-card-item .summary { color:var(--ink2); line-height:1.5; } .planning-item { display:block; padding:13px 16px; border-top:1px solid var(--grid); color:var(--ink); text-decoration:none; } .planning-item:first-child { border-top:none; } .planning-item:hover, .planning-item.selected { background:var(--plane); } .planning-item.selected { box-shadow:inset 3px 0 var(--s1); } .planning-item .title-row { display:flex; gap:8px; align-items:baseline; justify-content:space-between; } .planning-item .title { font-weight:650; overflow-wrap:anywhere; } .planning-item .meta, .planning-item .summary { color:var(--muted); font-size:12px; margin-top:4px; } .planning-item .summary { color:var(--ink2); line-height:1.5; } .pager { display:flex; flex-wrap:wrap; align-items:center; gap:6px; padding:12px 16px; border-top:1px solid var(--grid); } .pager a, .pager span { min-width:30px; padding:4px 8px; border:1px solid var(--grid); border-radius:4px; color:var(--s1); text-align:center; text-decoration:none; font-size:12px; } .pager span.current { background:var(--ink); border-color:var(--ink); color:var(--plane); } .pager span.total { margin-right:auto; border:none; color:var(--muted); text-align:left; } .knowledge-detail { position:sticky; top:18px; } .knowledge-detail .detail-head { padding:14px 16px; border-bottom:1px solid var(--grid); } .knowledge-detail .detail-head h3 { font-size:18px; line-height:1.35; overflow-wrap:anywhere; } .knowledge-detail .detail-head .sub { color:var(--muted); font-size:12px; margin-top:4px; } .detail-table { width:100%; border-collapse:collapse; font-size:13px; } .detail-table th { width:112px; color:var(--muted); font-weight:600; text-align:left; vertical-align:top; } .detail-table th, .detail-table td { padding:9px 16px; border-bottom:1px solid var(--grid); } .detail-table tr:last-child th, .detail-table tr:last-child td { border-bottom:none; } .detail-text { white-space:pre-wrap; overflow-wrap:anywhere; color:var(--ink2); } .detail-list { margin:0; padding-left:18px; color:var(--ink2); } .detail-list li + li { margin-top:5px; } pre.data-block { margin:10px 16px 16px; padding:12px; max-height:520px; overflow:auto; background:var(--plane); border:1px solid var(--grid); border-radius:5px; color:var(--ink2); font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace; white-space:pre-wrap; overflow-wrap:anywhere; } pre.data-block code { font:inherit; color:inherit; white-space:inherit; } details.data-details { border-top:1px solid var(--grid); } details.data-details summary { cursor:pointer; padding:10px 16px; color:var(--s1); font-size:13px; } .error-panel { margin-top:14px; border:1px solid color-mix(in srgb,var(--crit) 45%,var(--ring)); border-radius:8px; background:color-mix(in srgb,var(--crit) 7%,var(--surface)); } .error-panel .hd { padding:11px 14px; border-bottom:1px solid var(--grid); font-weight:650; color:var(--crit); } .error-item { padding:11px 14px; border-bottom:1px solid var(--grid); } .error-item:last-child { border-bottom:none; } .error-item .label { color:var(--muted); font-size:12px; } .error-item .value { margin-top:3px; color:var(--ink2); overflow-wrap:anywhere; } .run-evidence details { border-top:1px solid var(--grid); } .run-evidence details:first-child { border-top:none; } .run-evidence summary { cursor:pointer; padding:11px 16px; color:var(--s1); } .run-evidence pre { margin:0 16px 16px; } @media (max-width:900px) { .knowledge-grid, .planning-grid { grid-template-columns:1fr; } .knowledge-detail { position:static; } } .meter { margin:10px 0; } .meter .track { height:10px; border-radius:5px; background:var(--grid); overflow:hidden; } .meter .fill { height:100%; border-radius:5px; background:var(--s1); transition:width .3s; } .meter .cap { font-size:12px; color:var(--muted); margin-top:4px; font-variant-numeric:tabular-nums; } .bar-row { display:flex; align-items:center; gap:12px; padding:7px 16px; font-size:13.5px; } .bar-row .name { width:150px; flex:none; color:var(--ink2); } .bar-row .track { flex:1; height:16px; background:var(--grid); border-radius:4px; overflow:hidden; } .bar-row .fill { height:100%; border-radius:4px; } .bar-row .val { width:110px; flex:none; text-align:right; font-variant-numeric:tabular-nums; color:var(--ink2); } .empty { padding:34px 16px; text-align:center; color:var(--muted); font-size:13.5px; } .empty.gap { border:1px dashed var(--baseline); border-radius:8px; margin-top:14px; background:transparent; } .empty b { color:var(--ink2); } article.prose { background:var(--surface); border:1px solid var(--ring); border-radius:8px; padding:30px 40px; margin-top:16px; max-width:760px; font-size:16.5px; line-height:2; white-space:pre-wrap; color:var(--ink); } .crumb { font-size:13px; color:var(--muted); margin-bottom:6px; } .crumb a { color:var(--s1); text-decoration:none; } footer { margin-top:44px; font-size:11.5px; color:var(--muted); border-top:1px solid var(--grid); padding-top:12px; } """ # 状态 → (角色, 图标)。状态色保留语义,图标+文字同在,绝不单靠颜色(dataviz 规矩)。 _STATUS = { "good": ("good", "✓"), "warn": ("warn", "●"), "serious": ("serious", "▲"), "crit": ("crit", "✕"), "gap": ("gap", "◌"), } _STATE_MAP = { "pending": "warn", "draft": "warn", "checking": "warn", "parsing": "warn", "running": "warn", "writing": "warn", "shadow": "warn", "confirmed": "good", "passed": "good", "done": "good", "parsed": "good", "completed": "good", "accepted": "good", "active": "good", "canonical": "good", "published": "good", "rejected": "serious", "failed": "serious", "revise": "serious", "stale": "serious", "discarded": "crit", "error": "crit", "unauthorized": "crit", "ignored": "crit", } _ENUM_LABELS = { "status": { "pending": "待确认", "draft": "草稿", "checking": "检查中", "parsing": "解析中", "running": "运行中", "writing": "写作中", "shadow": "待审", "confirmed": "已确认", "passed": "通过", "done": "完成", "parsed": "已解析", "completed": "已完成", "accepted": "已采纳", "active": "有效", "canonical": "正式", "published": "已发布", "rejected": "已拒绝", "failed": "失败", "revise": "待修订", "stale": "已过期", "discarded": "已丢弃", "error": "错误", "unauthorized": "未授权", "ignored": "已忽略", "not_built": "未建", "open": "开放", "closed": "已关闭", "migrating": "归档中", "migrated": "已归档", }, "section_type": { "setting": "设定", "outline": "大纲", "fine_outline": "细纲", "state": "叙事状态", "assembly": "装配", }, "draft_type": {"entity": "实体卡", "relation": "关系卡"}, "entity_type": { "character": "人物", "character_relation": "人物关系", "location": "地点", "faction": "势力", "item": "物品", "event": "事件", "power_system": "力量体系", "scene_pattern": "场景范式", "combat": "战斗范式", "craft": "写法范式", "emotion": "情绪范式", "trope": "套路范式", "chapter": "章节", "generation_context": "生成上下文", "narrative_state": "叙事状态", "novel_work": "作品", "outline": "大纲", "pacing": "节奏", "reference_work": "参考书", "scene": "场景", "style": "文风", "work_core": "作品核心", "world": "世界观", }, "run_type": {"production": "生产", "eval": "评测", "replay": "回放"}, "source_role": { "writer": "写手", "extractor": "章后抽卡", "planner": "规划器", "detector": "语义检测器", "judge": "质量评审器", "continuation": "续写编排", }, "agent_role": { "writer": "写手", "extractor": "章后抽卡", "planner": "规划器", "detector": "语义检测器", "judge": "质量评审器", }, "stage_kind": { "generation": "生成", "detection": "检测", "supplement": "补证", "rewrite": "改写", "accept": "接受", }, "judge_kind": { "detection": "检测", "scoring": "评分", "review": "审核", "experiment": "实验", }, "decision": {"accept": "采纳", "merge": "合并", "discard": "丢弃"}, "conclusion": {"pass": "通过", "fail": "失败", "revise": "需修订", "pending": "待评判"}, "source_type": { "chapter_extract": "章后抽取", "parse_book": "拆书解析", "upgrade_book": "作品升格", "ai_candidate": "模型候选", "manual": "人工录入", }, "trigger_source": { "user": "用户触发", "system": "系统触发", "api": "接口触发", "scheduled": "定时触发", }, "purpose": { "llm_call": "模型调用", "planning_persistence": "规划落库", "production_generation": "正文生产", "evaluation": "评测", }, "skill_name": { "access-database": "访问数据库", "assemble-context": "组装上下文", "call-content-model": "调用内容模型", "check-content-consistency": "检查内容一致性", "clean-book-text": "清洗书稿正文", "decide-candidate": "处理候选决策", "deconstruct-book": "拆解完整作品", "design-story-foundation": "设计作品前期", "embed-knowledge": "嵌入知识", "evaluate-frozen-replay": "评估冻结回放", "execute-claude-task": "执行 Claude 任务", "expand-scene": "扩写场景", "extract-chapter-knowledge": "抽取章节知识", "extract-work-knowledge": "抽取作品知识", "freeze-context": "冻结上下文", "import-book": "导入书稿", "maintain-work-extraction": "维护作品抽取", "optimize-content-quality": "优化内容质量", "plan-chapter": "规划章节", "plan-story": "规划作品", "polish-prose": "润色正文", "record-run-evidence": "记录运行证据", "review-knowledge-cards": "审核知识卡", "rewrite-selection": "改写指定范围", "score-content-quality": "评估内容质量", "search-knowledge": "检索知识", "write-next-chapter": "续写下一章", }, } _ENUM_ROLES = { "decision": {"accept": "good", "merge": "warn", "discard": "crit"}, "conclusion": {"pass": "good", "fail": "serious", "revise": "warn", "pending": "warn"}, "raw_status": {"closed": "good", "migrated": "good", "open": "warn", "migrating": "warn"}, } _REASON_LABELS = { "completed": "已完成", "failed": "失败", "stop": "异常停止", "tool_use": "工具调用结束", "process_exit_without_terminal_report": "进程退出且未上报终态", "InvalidTextRepresentation": "文本格式错误", "ExtractionContractError": "抽取合同错误", } _TEXT_TERM_LABELS = { "writer sandbox": "写手隔离环境", "semantic detector": "语义检测器", "Claude CLI": "模型命令行客户端", "writer": "写手", "detector": "检测器", "asOf": "截至章", "LLM": "大模型", "New-API": "模型接口", "MiniMax": "模型服务", "Claude": "模型", "CLI": "命令行客户端", "scenario": "场景", "continuation": "续写", "validation": "校验", "consistency_check": "一致性检查", "fine_outline_replay": "细纲回放", "full_parse": "全书解析", "quality_gate": "质量门", "writer_replay": "写手回放", "fieldAiContext": "字段上下文策略", "pgvector": "向量检索库", "embed": "向量化", "dimensions": "维度数", "judge": "评审", "prompt": "提示词", "expansion": "扩写", "extraction": "抽取", "planning": "规划", "polish": "润色", "rewrite": "改写", "fine_outline": "细纲", "review": "审核", "as_of": "截至章", "fresh": "全新", "sandbox": "隔离环境", "deadline": "截止时间", "structured_output": "结构化输出", "aiContext": "智能上下文", "fail-closed": "失败即关闭", "build_snapshot": "构建快照", "check_snapshot": "校验快照", "audit_leakage": "泄漏审计", "upgrade_book": "作品升格", "revision journal": "修订日志", "raw vault": "原始证据仓", "muse-example": "实验库", "advisory lock": "咨询锁", "git commit": "提交操作", "file_cas": "文件内容寻址存储", "CAS": "内容寻址存储", "journal": "日志", "revision": "修订号", "raw": "原始", "vault": "存储仓", "skill": "技能", "block": "正文块", "replay-eval": "回放评测", "quality-gate": "质量门", "detect": "语义检测", "restore": "恢复", "SQL": "数据库查询语言", "DML": "数据操作语言", "DDL": "数据定义语言", "txt": "文本", "Git": "版本库", "PostgreSQL": "数据库", "scripts/": "脚本目录", "MiniMax-M3": "模型", "Qwen3-Embedding-8B": "嵌入模型", } _TEXT_TERM_PATTERN = re.compile( r"(? 127 for char in value) def enum_text(value, group="status"): """枚举展示:中文语义优先,英文仅作为可复制的字符串编码。""" if value is None: return esc(value) raw = str(value) if _is_human_text(raw): return html.escape(raw) label = _ENUM_LABELS.get(group, {}).get(raw) if label is None: label = "未登记" return f"{html.escape(label)} {html.escape(raw)}" def field_label(label, code): """字段标题使用中文,数据库/协议字段名只作为编码保留。""" return f"{html.escape(label)} {html.escape(code)}" def annotated_text(value): """自由文本中的受控技术术语显示中文语义,原词保留为编码。""" if value is None: return esc(value) escaped = html.escape(str(value)) return _TEXT_TERM_PATTERN.sub( lambda match: f"{html.escape(_TEXT_TERM_LABELS[match.group(0)])} " f"{match.group(0)}", escaped, ) def short_text(value, limit=56): """截断说明时不切断英文编码,避免出现 ``fine_outl`` 这类伪字段。""" text = str(value or "") if len(text) <= limit: return text cut = text[:limit] while cut and len(cut) < len(text): next_char = text[len(cut)] last_char = cut[-1] if not (last_char.isalnum() or last_char in "_-") or not (next_char.isalnum() or next_char in "_-"): break cut = cut[:-1] return cut.rstrip() + "…" def _json_text(value): """JSONB 详情使用可读缩进,标量和空值也保持稳定文本。""" if value is None: return "" if isinstance(value, str): return value try: return json.dumps(value, ensure_ascii=False, indent=2, default=str) except (TypeError, ValueError): return str(value) def _data_block(value): text = _json_text(value) return f"
{esc(text)}
" def _content_text(value): """原始 prompt/response 若是 JSON,格式化后再展示;普通文本原样保留。""" if value is None: return "" text = str(value) try: return json.dumps(json.loads(text), ensure_ascii=False, indent=2) except (TypeError, ValueError, json.JSONDecodeError): return text _RAW_KIND_LABELS = { "prompt": "模型输入", "response": "模型输出", "stderr": "错误输出", "error": "错误信息", } def _raw_kind_label(value): raw = "" if value is None else str(value) return f"{html.escape(_RAW_KIND_LABELS.get(raw, '原始证据'))} {html.escape(raw)}" def _reason_text(value): if value is None or value == "": return "—" raw = str(value) label = _REASON_LABELS.get(raw, "未登记原因") return f"{html.escape(label)} {html.escape(raw)}" def _row_dicts(cols, rows): return [dict(zip(cols, row)) for row in rows] def _query_href(path, current, **updates): """保留当前筛选条件,生成可复制的 GET 下钻地址。""" values = { key: vals[0] for key, vals in (current or {}).items() if vals and vals[0] not in (None, "") } for key, value in updates.items(): if value in (None, ""): values.pop(key, None) else: values[key] = value query = urlencode(values) return f"{path}?{query}" if query else path def _chapter_text(chapter_id, order_no, title, *, link_work=None): """章节主键只作为编码显示,用户看到章节序号和标题。""" if order_no is None: label = "未关联章节" else: label = f"第{order_no}章{f'《{title}》' if title else ''}" if link_work is not None and order_no is not None: label = f"{html.escape(label)}" else: label = html.escape(label) if chapter_id is not None: label += f" chapter_id={html.escape(str(chapter_id))}" return label def _mapping_html(mapping): if not isinstance(mapping, dict) or not mapping: return "暂无" return ("" + "".join(f"" for key, value in mapping.items()) + "
{esc(key)}{esc(_json_text(value))}
") def display_trigger(value): raw = "" if value is None else str(value) if raw in _ENUM_LABELS["trigger_source"]: return enum_text(raw, "trigger_source") return annotated_text(value) def _operator_text(value): """运行调用方可能是角色,也可能是技能;两者都先显示中文语义。""" raw = "" if value is None else str(value) if raw in _ENUM_LABELS.get("source_role", {}): return enum_text(raw, "source_role") return enum_text(raw, "skill_name") def enum_chip(value, group="status"): raw = "" if value is None else str(value) visual = _ENUM_ROLES.get(group, {}).get(raw, raw) label = _ENUM_LABELS.get(group, {}).get(raw) if label is None: label = raw if _is_human_text(raw) else "未登记" return chip(visual, label, code=raw) def chip(state, label=None, *, code=None): """状态标签:颜色按语义角色,图标+文字同在。未识别状态归 warn(待审)。""" role = _STATE_MAP.get(str(state).lower(), "warn") if state else "gap" if state in (None, "", "not_built"): role = "gap" r, icon = _STATUS[role] raw = code if code is not None else state if label is None: label = _ENUM_LABELS.get("status", {}).get(str(raw), "未登记") suffix = "" if code is not None or (state and str(state).lower() not in _STATUS): suffix = f" {html.escape(str(raw))}" return (f'{icon} ' f'{html.escape(str(label))}{suffix}') def esc(v): if v is None: return '∅' return html.escape(str(v)) def fmt_num(v): try: return f"{int(v):,}" except (TypeError, ValueError): return esc(v) def empty_gap(title, desc): """待落库空状态:中性虚线,与"失败"红严格区分。""" return f"
{esc(title)}
{esc(desc)}
" # ── 布局:对齐 muse 领域布局(总览 / 作品 / 知识库 / 智能体)──────────────────── # 每个导航项标注对应领域(domains/_index.md 的分区),看板按领域看库。 NAV = [ ("/", "总览 · 健康", "数据权威 08"), ("/works", "作品", "作品领域 01"), ("/knowledge", "知识库", "实体 02 · 范式 03"), ("/ai-flavor", "AI 味案例", "质量 06 · 数据库证据"), ("/agents", "智能体", "智能体 07"), ("/runs", "运行记录", "流程 05 · 质量 06"), ] def layout(title, body, active="/", footer_note=None): links = [] for href, label, dom in NAV: cls = "on" if href == active else "" links.append(f'{html.escape(label)}' f'
{html.escape(dom)}
') footer_note = footer_note or "数据实时查库,无缓存 · 数字为当前库内事实 · 空白即落库缺口" return f""" {html.escape(title)} · 创作看板
{body}
""" def _latest_ai_flavor_report(): """数据库不可用时取仓库内最新的回填报告;不扫描原文。""" reports = sorted(AI_FLAVOR_REPORT_DIR.glob("backfill-inventory-*.json")) return reports[-1] if reports else None def _load_ai_flavor_report(): path = _latest_ai_flavor_report() if path is None: return None, "尚未生成离线回填报告" try: report = json.loads(path.read_text(encoding="utf-8")) except (OSError, UnicodeError, json.JSONDecodeError) as exc: return None, f"回填报告读取失败:{exc}" if not isinstance(report, dict) or report.get("schema_version") != "ai-flavor-inventory-v1": return None, "回填报告版本不受支持" if not isinstance(report.get("cards"), list) or not isinstance(report.get("works"), list): return None, "回填报告缺少 works/cards 数组" return report, None def _latest_ai_flavor_revalidation_report(): """数据库不可用时取仓内重验证回执;页面不因浏览而读取原文。""" reports = sorted(AI_FLAVOR_REPORT_DIR.glob("revalidation-*.json")) return reports[-1] if reports else None def _load_ai_flavor_revalidation_report(): path = _latest_ai_flavor_revalidation_report() if path is None: return None, "尚未生成来源重验证回执" try: report = json.loads(path.read_text(encoding="utf-8")) except (OSError, UnicodeError, json.JSONDecodeError) as exc: return None, f"重验证回执读取失败:{exc}" if not isinstance(report, dict) or report.get("schema_version") != AI_FLAVOR_REVALIDATION_SCHEMA: return None, "重验证回执版本不受支持" if not isinstance(report.get("cards"), list) or not isinstance(report.get("works"), list): return None, "重验证回执缺少 works/cards 数组" return report, None def _ai_db_available(): """只检查正式三表是否存在;查询失败由调用方转为离线回退。""" return all(table_exists(name) for name in ( "example_ai_flavor_case", "example_ai_flavor_revalidation_batch", "example_ai_flavor_revalidation", )) def _ai_db_bundle(): """从 muse-example 组装看板所需的 inventory + 最新重验证回执视图。""" if not _ai_db_available(): return None, None, "AI 味案例正式表尚未建立" _, card_rows = q( "SELECT card_id,schema_version,card_type,state,label,layer,carrier,capture_mode," "source_kind,source_license,source_sha256,excerpt_sha256,source_ref,work_ref," "source_location,surface_location,excerpt,context,observation,feedback,review,rule_candidate_ids " "FROM example_ai_flavor_case WHERE tenant_id=%s AND deleted=false " "ORDER BY work_ref, (source_location->>'char_start')::bigint, card_id", (AI_FLAVOR_TENANT_ID,), ) cards = [] for row in card_rows: (card_id, schema_version, card_type, state, label, layer, carrier, capture_mode, source_kind, source_license, source_sha256, excerpt_sha256, source_ref, work_ref, source_location, surface_location, excerpt, context, observation, feedback, review, rule_candidate_ids) = row cards.append({ "schema_version": schema_version, "id": card_id, "card_type": card_type, "state": state, "label": label, "layer": layer, "carrier": carrier, "capture_mode": capture_mode, "excerpt": excerpt or "", "context": context or "", "source": { "kind": source_kind, "license": source_license, "source_sha256": source_sha256, "excerpt_sha256": excerpt_sha256, "source_ref": source_ref or "", "work_ref": work_ref, "location": source_location or {}, "surface_location": surface_location or {}, }, "observation": observation or {}, **({"feedback": feedback} if feedback is not None else {}), **({"review": review} if review is not None else {}), "rule_candidate_ids": rule_candidate_ids or [], }) _, batch_rows = q( "SELECT id,batch_ref,report_sha256,schema_version,checked_on,source_root_ref,usable,totals,works,report_path " "FROM example_ai_flavor_revalidation_batch WHERE tenant_id=%s " "ORDER BY checked_on DESC,id DESC LIMIT 1", (AI_FLAVOR_TENANT_ID,), ) if not batch_rows: work_map = {} for card in cards: source = card["source"] key = (source.get("work_ref", ""), source.get("source_ref", "")) work = work_map.setdefault(key, { "work_ref": key[0], "source_ref": key[1], "source_sha256": source.get("source_sha256"), "card_count": 0, "pattern_counts": {}, }) work["card_count"] += 1 pattern = card.get("observation", {}).get("pattern_key") work["pattern_counts"][pattern] = work["pattern_counts"].get(pattern, 0) + 1 return { "schema_version": "ai-flavor-inventory-v1", "generated_on": None, "source_root_ref": None, "source_license": "unknown", "totals": {}, "works": list(work_map.values()), "cards": cards, "_authority": "postgresql", }, None, None (batch_id, batch_ref, report_sha256, revalidation_schema, checked_on, source_root_ref, usable, batch_totals, batch_works, report_path) = batch_rows[0] _, receipt_rows = q( "SELECT card_id,work_ref,source_ref,expected_source_sha256,actual_source_sha256,status,reason,checked_on " "FROM example_ai_flavor_revalidation WHERE tenant_id=%s AND batch_id=%s ORDER BY card_id", (AI_FLAVOR_TENANT_ID, batch_id), ) receipts = [{ "card_id": row[0], "work_ref": row[1], "source_ref": row[2], "expected_source_sha256": row[3], "actual_source_sha256": row[4], "status": row[5], "reason": row[6], "checked_on": row[7].isoformat() if row[7] else None, } for row in receipt_rows] by_id = {item["card_id"]: item for item in receipts} for card in cards: card["_revalidation"] = by_id.get(card["id"], {"status": "missing"}) work_map = {} pattern_counts = Counter() for card in cards: source = card["source"] key = (source.get("work_ref", ""), source.get("source_ref", "")) work = work_map.setdefault(key, { "work_ref": key[0], "source_ref": key[1], "source_sha256": source.get("source_sha256"), "card_count": 0, "pattern_counts": {}, }) work["card_count"] += 1 pattern = card.get("observation", {}).get("pattern_key") work["pattern_counts"][pattern] = work["pattern_counts"].get(pattern, 0) + 1 pattern_counts.update([pattern]) aggregate_totals = { "books": len(work_map), "cards": len(cards), "pattern_counts": dict(sorted(pattern_counts.items())), } report = { "schema_version": "ai-flavor-inventory-v1", "generated_on": checked_on.isoformat() if checked_on else None, "source_root_ref": source_root_ref, "source_license": next((card["source"]["license"] for card in cards), "unknown"), "totals": {**aggregate_totals, **(batch_totals or {})}, "works": list(work_map.values()), "cards": cards, "_authority": "postgresql", "_batch_id": batch_id, "_batch_ref": batch_ref, "_report_sha256": report_sha256, "_report_path": report_path, "_usable": usable, } return report, {"schema_version": revalidation_schema, "checked_on": report["generated_on"], "usable": usable, "totals": batch_totals or {}, "works": list(work_map.values()), "cards": receipts, "_authority": "postgresql", "_batch_id": batch_id, "_report_sha256": report_sha256, "_report_path": report_path}, None def _load_ai_flavor_bundle(): """数据库优先;只有数据库表/连接不可用才退回仓内证据文件。""" try: report, revalidation, error = _ai_db_bundle() if error is None and report is not None: return report, revalidation, None, "postgresql" except Exception as exc: # 看板不能阻断创作链,回退原因要显式显示 error = f"数据库读取失败:{exc}" report, report_error = _load_ai_flavor_report() revalidation, revalidation_error = _load_ai_flavor_revalidation_report() if report_error: return None, None, error or report_error, "offline" return report, revalidation, error or revalidation_error, "offline" def _ai_first(params, key, default=""): values = (params or {}).get(key, []) return values[0] if values else default def _ai_pattern_label(key): return _AI_FLAVOR_PATTERN_LABELS.get(key, "未登记模式") def _ai_revalidation_label(status): return _AI_FLAVOR_REVALIDATION_LABELS.get(status, "未登记复核状态") def _ai_revalidation_chip(status): status = status or "missing" role = {"verified": "good", "missing": "warn"}.get(status, "serious") icon = {"verified": "✓", "missing": "●"}.get(status, "▲") return (f"{icon}" f"{esc(_ai_revalidation_label(status))} {esc(status)}") def _ai_location(card): location = card.get("source", {}).get("surface_location") or card.get("source", {}).get("location") or {} line_start = location.get("line_start", "?") line_end = location.get("line_end", line_start) char_start = location.get("char_start", "?") char_end = location.get("char_end", char_start) line = f"第 {line_start} 行" if line_start == line_end else f"第 {line_start}-{line_end} 行" return f"{line} · 字符 {char_start}-{char_end}" def _ai_pager(path, params, page, pages): links = [f"共 {fmt_num(pages)} 页"] if page > 1: links.append(f"上一页") window = range(max(1, page - 2), min(pages, page + 2) + 1) for number in window: if number == page: links.append(f"{number}") else: links.append(f"{number}") if page < pages: links.append(f"下一页") return "
" + "".join(links) + "
" def _ai_card_detail(card): source = card.get("source", {}) observation = card.get("observation", {}) verification = card.get("_revalidation") or {} checks = observation.get("function_check") or [] check_html = "".join(f"
  • {esc(item)}
  • " for item in checks) label = _AI_FLAVOR_LABELS.get(card.get("label"), "未登记标签") state = card.get("state") verification_status = verification.get("status", "missing") if state == "canonical" and verification_status == "verified": usability = "可进入样例投影或规则候选评审;仍不能直接成为 active 规则" elif state in {"rejected", "archived"}: usability = "仅保留审计,不参与当前诊断、样例或规则消费" elif verification_status in {"stale", "unavailable", "card_mismatch", "missing"}: usability = "当前失效:需重新取得来源并通过重验证;历史卡和回执继续保留" else: usability = "仅供本卡/本作品人工复核与候选提示;不可注入生成上下文或作为 blocking 规则" scope = f"{source.get('work_ref') or '未绑定作品'} · {source.get('source_ref') or '未登记来源'} · {card.get('capture_mode') or '未知入口'}" return ( "
    " "

    案例卡详情

    " f"
    {esc(card.get('id'))} · 数据库当前投影 · 规则仍需评审
    " "" f"" f"" f"" f"" f"" f"" f"" f"" f"" f"" f"" f"" f"" f"" f"" "
    状态{chip(card.get('state'), 'Shadow · 待复核', code=card.get('state'))}
    来源重验证{_ai_revalidation_chip(verification.get('status'))}
    {esc(verification.get('reason', '页面未执行重验证'))}
    人工标签{esc(label)} {esc(card.get('label'))}
    当前用途{esc(usability)}
    作用域{esc(scope)}
    模式{esc(_ai_pattern_label(observation.get('pattern_key')))} {esc(observation.get('pattern_key'))}
    作品{esc(source.get('work_ref'))}
    来源位置{esc(source.get('source_ref'))}
    {esc(_ai_location(card))}
    表面命中{esc(observation.get('surface'))}
    来源许可{esc(source.get('license'))} · {'原文片段未保存' if not card.get('excerpt') else '已按授权保存片段'}
    全文 SHA-256{esc(source.get('source_sha256'))}
    命中 SHA-256{esc(source.get('excerpt_sha256'))}
    诊断{esc(observation.get('diagnosis'))}
    改动风险{esc(observation.get('risk_if_changed'))}
    建议动作{esc(observation.get('suggested_action'))}
    " f"
    功能复核项
    " f"" "
    " ) def view_ai_flavor(params=None): """展示数据库正式案例卡;数据库不可用时明确标注离线回退。""" report, revalidation, load_note, authority = _load_ai_flavor_bundle() if report is None: body = ("

    AI 味案例已有作品反向回填 · 数据库案例卡

    " + empty_gap("暂无可展示案例", load_note or "数据库和离线恢复证据均不可用")) return layout("AI 味案例", body, "/ai-flavor", "质量证据页 · 尚未形成可读数据 · 看板只读") revalidation_by_id = { item.get("card_id"): item for item in (revalidation or {}).get("cards", []) if isinstance(item, dict) and item.get("card_id") } # 只给渲染副本附加回执,不改写磁盘报告;页面本身不读取原文。 cards = [] for card in report["cards"]: rendered = dict(card) rendered["_revalidation"] = rendered.get("_revalidation") or revalidation_by_id.get(card.get("id"), {"status": "missing"}) cards.append(rendered) works = report["works"] totals = report.get("totals") or {} selected_work = _ai_first(params, "work") selected_pattern = _ai_first(params, "pattern") selected_card_id = _ai_first(params, "card") try: page = max(1, int(_ai_first(params, "page", "1"))) except ValueError: page = 1 filtered = [card for card in cards if (not selected_work or card.get("source", {}).get("work_ref") == selected_work) and (not selected_pattern or card.get("observation", {}).get("pattern_key") == selected_pattern)] filtered.sort(key=lambda card: ( card.get("source", {}).get("work_ref", ""), card.get("source", {}).get("location", {}).get("char_start", 0), card.get("id", ""), )) pages = max(1, (len(filtered) + AI_FLAVOR_PAGE_SIZE - 1) // AI_FLAVOR_PAGE_SIZE) page = min(page, pages) visible = filtered[(page - 1) * AI_FLAVOR_PAGE_SIZE:page * AI_FLAVOR_PAGE_SIZE] selected = next((card for card in cards if card.get("id") == selected_card_id), None) pattern_counts = Counter(card.get("observation", {}).get("pattern_key") for card in filtered) state_counts = Counter(card.get("state") for card in cards) verification_counts = Counter(card.get("_revalidation", {}).get("status", "missing") for card in cards) source_license = report.get("source_license", "unknown") generated_on = report.get("generated_on", "unknown") report_rel = report.get("_report_path") or report.get("_batch_ref") or "数据库当前投影" # 批次历史可能来自旧运行的本机临时路径;页面只暴露可迁移的逻辑引用。 if authority == "postgresql" and report_rel: report_rel = f"capture://ai-flavor/{Path(str(report_rel)).name}" if authority == "offline": try: report_rel = str(_latest_ai_flavor_report().relative_to(Path(__file__).resolve().parents[1])) except (AttributeError, ValueError): report_rel = "仓内离线回退文件" tiles = ("
    " f"
    {fmt_num(totals.get('books', len(works)))}
    作品
    " f"
    {fmt_num(totals.get('cards', len(cards)))}
    候选卡
    当前筛选 {fmt_num(len(filtered))}
    " f"
    {fmt_num(state_counts.get('shadow', 0))}
    Shadow · 待复核
    " f"
    {fmt_num(verification_counts.get('verified', 0))}
    来源已验证
    非已验证 {fmt_num(len(cards) - verification_counts.get('verified', 0))}
    " f"
    {esc(source_license)}
    来源许可
    " "
    ") work_rows = [] for work in works: work_ref = work.get("work_ref", "") active = "" if selected_work == work_ref else "" href = _query_href("/ai-flavor", params, work=work_ref, pattern=None, page=1, card=None) work_rows.append( f"{esc(work.get('source_ref'))}" f"
    {esc(work_ref)}
    " f"{fmt_num(work.get('card_count', 0))}" f"{', '.join(f'{esc(_ai_pattern_label(k))} {fmt_num(v)}' for k, v in (work.get('pattern_counts') or {}).items())}" f"{esc(work.get('source_sha256'))}" ) work_filter = (f"清除作品筛选" if selected_work else "") work_table = (f"
    按作品汇总({fmt_num(len(works))} 本)
    " "" f"{''.join(work_rows)}
    来源文件 / work_ref命中卡模式分布全文 SHA-256
    {work_filter}
    ") pattern_rows = [] for key, count in sorted(pattern_counts.items(), key=lambda item: (-item[1], item[0])): href = _query_href("/ai-flavor", params, pattern=key, page=1, card=None) pattern_rows.append(f"{esc(_ai_pattern_label(key))} {esc(key)}{fmt_num(count)}") pattern_table = ("
    当前筛选的模式分布
    " "" f"{''.join(pattern_rows) or ''}
    模式命中卡
    暂无
    ") card_rows = [] for card in visible: source = card.get("source", {}) observation = card.get("observation", {}) href = _query_href("/ai-flavor", params, card=card.get("id")) card_rows.append( f"{esc(card.get('id'))}" f"{esc(source.get('work_ref'))}" f"{esc(_ai_pattern_label(observation.get('pattern_key')))}
    {esc(observation.get('pattern_key'))}
    " f"{esc(observation.get('surface'))}" f"{esc(_ai_location(card))}" f"{chip(card.get('state'), 'Shadow · 待复核', code=card.get('state'))}" f"{_ai_revalidation_chip(card.get('_revalidation', {}).get('status'))}" ) cards_table = ("
    案例卡明细" f"({fmt_num(len(filtered))} 张,当前第 {page}/{pages} 页)
    " "" f"{''.join(card_rows) or ''}
    卡片 ID作品模式表面命中位置状态来源重验证
    当前筛选暂无案例
    " f"{_ai_pager('/ai-flavor', params, page, pages)}
    ") detail = _ai_card_detail(selected) if selected else empty_gap("选择一张案例卡", "点击明细表中的卡片 ID 查看来源锚点和人工复核要求。") filters = ("
    " f"" f"" "清除
    ") if revalidation: if authority == "postgresql": receipt_ref = f"batch_id={esc((revalidation or {}).get('_batch_id'))} · report_sha256={esc((revalidation or {}).get('_report_sha256'))}" else: latest = _latest_ai_flavor_revalidation_report() receipt_ref = (str(latest.relative_to(Path(__file__).resolve().parents[1])) if latest else "仓内离线回执") revalidation_note = (f"重验证 {esc(receipt_ref)} · " f"检查于 {esc((revalidation or {}).get('checked_on', 'unknown'))} · " f"来源已验证 {fmt_num(verification_counts.get('verified', 0))}/{fmt_num(len(cards))}") else: revalidation_note = f"重验证:{esc(revalidation_error or '未提供回执')}" authority_label = "PostgreSQL muse-example 正式库" if authority == "postgresql" else "离线回退(数据库当前不可读)" fallback_note = f"
    回退 {esc(load_note)}" if authority == "offline" and load_note else "" source_storage_note = "研究限定/未授权来源原文片段未保存" if source_license in {"research_only", "unauthorized"} else "授权来源可在评审后保存受控片段" meta = (f"

    来源 {esc(authority_label)} · 批次/报告 {esc(report_rel)} · 生成于 {esc(generated_on)} · " f"确定性模式命中数不是人工确认的 AI 味结论;{esc(source_storage_note)},当前状态仍须按评审门禁升级。
    " f"{revalidation_note}。来源重验证只在确认、投影或规则使用前作为门禁,打开本页不会重新读取原文。{fallback_note}

    ") body = ("

    AI 味案例已有作品反向回填 · 数据库案例卡 · 候选命中(非确认结论)

    " f"{meta}{tiles}{filters}
    {work_table}{pattern_table}{cards_table}
    {detail}
    ") return layout("AI 味案例", body, "/ai-flavor", "质量证据页 · 数据库只读渲染 · 案例卡可追溯 · 重验证回执 append-only · 规则仍须人工/回放门禁") # ── 总览(08 数据权威:落库验收面 + 健康数字 + 额度水位)────────────────────── # 落库缺口板:一切输入产出该落哪张表,建没建、有没有数(合同 §4.2 的验收面) _LEDGER = [ ("AI 味案例卡", "example_ai_flavor_case"), ("AI 味重验证批次", "example_ai_flavor_revalidation_batch"), ("AI 味逐卡回执", "example_ai_flavor_revalidation"), ("待审候选", "example_candidate"), ("用户决策", "example_user_decision"), ("运行回执", "example_run_receipt"), ("质量评判", "example_quality_result"), ("模型调用明细", "example_llm_call"), ("运行注册", "example_run"), ("原始全文", "example_raw_content"), ("规划 / 细纲", "example_planning_section"), ("上下文冻结", "example_context_freeze"), ] def _quota_block(): _, rows = q("SELECT window_key, minimax_usd, total_calls FROM example_llm_quota " "ORDER BY window_key DESC LIMIT 1") if not rows: return ("

    额度水位

    " + empty_gap("暂无额度记录", "发生模型调用后自动记录")) wk, usd, calls = rows[0] usd, calls = float(usd or 0), int(calls or 0) def meter(val, cap, text): pct = min(100.0, val / cap * 100) if cap else 0 return (f"
    " f"
    {text}(上限 {cap:,.0f})
    ") return (f"

    额度水位 额度窗口 {esc(wk)}

    " f"
    " f"{meter(usd, WINDOW_BUDGET_USD, f'模型 MiniMax 花费 ${usd:.4f} / ${WINDOW_BUDGET_USD:.0f}')}" f"{meter(calls, WINDOW_CALL_CAP, f'成功调用 {calls:,} / {WINDOW_CALL_CAP:,} 次')}
    ") def view_home(): ledger_names = [tbl for _, tbl in _LEDGER] counts = table_counts([ "muse_content_work", "muse_content_chapter", "muse_content_block", "muse_knowledge_draft", "muse_knowledge_entity", "example_reference_work", "example_clean_log", *ledger_names, ]) works = counts.get("muse_content_work", 0) chapters = counts.get("muse_content_chapter", 0) blocks = counts.get("muse_content_block", 0) drafts = counts.get("muse_knowledge_draft", 0) entities = counts.get("muse_knowledge_entity", 0) refs = counts.get("example_reference_work", 0) cleans = counts.get("example_clean_log", 0) tiles = f"""
    {fmt_num(works)}
    作品
    {fmt_num(chapters)}
    章节
    {fmt_num(blocks)}
    正文块
    {fmt_num(drafts)}
    知识卡(草稿)
    已确认 {fmt_num(entities)} 张
    {fmt_num(refs)}
    参考书
    {fmt_num(cleans)}
    清洗记录
    """ rows = [] for label, tbl in _LEDGER: if tbl not in counts: st, cnt = chip("not_built", "未建 · 待落库"), "—" else: n = counts[tbl] st = chip("active", "已建 · 有数据") if n > 0 else chip("pending", "已建 · 0 行") cnt = fmt_num(n) rows.append(f"{esc(label)}{esc(tbl)}" f"{cnt}{st}") ledger = f"""
    落库账本 —— 一切输入产出,落没落库一目了然
    {"".join(rows)}
    输入产出承接表编码行数状态

    「未建」= 表还没建(合同要求落库,属缺口);「已建 · 0 行」= 表已就位,等创作链写入。 知识卡已确认 {fmt_num(entities)} 张(草稿≠已确认,全部确认前此数为 0 是正常状态)。

    """ body = (f"

    总览 · 健康库里现在有什么、缺什么——这就是「一切落库』的验收面

    " f"{tiles}{ledger}{_quota_block()}") return layout("总览", body, "/") # ── 作品(01 作品领域:作品 → 章节 → 正文全文)──────────────────────────────── def view_works(): _, rows = q( "SELECT w.id,w.title,w.status,w.genre,COUNT(DISTINCT c.id)::bigint AS chapter_count, " "COALESCE(SUM(b.word_count),0)::bigint AS word_count " "FROM muse_content_work w " "LEFT JOIN muse_content_chapter c ON c.work_id=w.id AND c.deleted=false " "LEFT JOIN muse_content_block b ON b.chapter_id=c.id AND b.deleted=false " "WHERE w.deleted=false GROUP BY w.id,w.title,w.status,w.genre ORDER BY w.id" ) if not rows: return layout("作品", "

    作品

    " + empty_gap("库里还没有作品", "导入或创建后在此显示"), "/works") trs = "".join( f"{esc(r[1])}{chip(r[2])}" f"{esc(r[3])}{esc(r[4])}{fmt_num(r[5])}" for r in rows) body = (f"

    作品正式内容以库为准(表编码 muse_content_work)· 作品领域 01

    " f"
    " f"{trs}
    作品状态类型章数字数
    ") return layout("作品", body, "/works") def _knowledge_card_detail(wid, card): payload = card.get("draft_payload") or {} entity_id = card.get("entity_id") source = payload.get("source") if isinstance(payload.get("source"), dict) else {} target = payload.get("target") if isinstance(payload.get("target"), dict) else {} name = (card.get("entity_name") or payload.get("name") or payload.get("名称") or source.get("name") or target.get("name") or "未命名卡") card_type = card.get("entity_type") or payload.get("type") or payload.get("型") or card.get("draft_type") brief = (card.get("entity_description") or payload.get("brief") or payload.get("description") or payload.get("摘要") or "") fields = payload.get("fields") or payload.get("attributes") or {} extract_run_id = payload.get("extractRunId") or payload.get("extract_run_id") run_link = (f"{html.escape(str(extract_run_id))}" if extract_run_id else "—") chapter = _chapter_text(card.get("chapter_id"), card.get("chapter_order"), card.get("chapter_title"), link_work=wid) entity_html = "" if entity_id: entity_html = ( "
    正式实体快照" f"" f"" f"" f"
    实体编号{esc(entity_id)}
    实体状态{chip(card.get('entity_status'))}
    实体名称{esc(card.get('entity_name'))}
    实体说明{esc(card.get('entity_description'))}
    " f"{_data_block(card.get('entity_attributes'))}" "
    " ) relation_html = "" if source or target: relation_html = ("关系两端" f"{esc(source.get('name', '未命名'))} → {esc(target.get('name', '未命名'))}") return ( "
    " f"

    {esc(name)}

    {enum_text(card_type, 'entity_type')} · 卡片编号 {esc(card.get('draft_id'))}
    " "" f"" f"" f"" f"" f"" f"" f"" f"" f"{relation_html}" f"" f"" "
    卡片类型{enum_text(card.get('draft_type'), 'draft_type')}
    实体/关系类型{enum_text(card_type, 'entity_type')}
    状态{chip(card.get('status'))}
    置信度{esc(card.get('confidence'))}
    来源章节{chapter}
    抽取运行{run_link}
    更新时间{esc(card.get('update_time'))}
    来源类型{enum_text(card.get('source_type'), 'source_type')} · source_id={esc(card.get('source_id'))}
    摘要{esc(brief) if brief else '—'}
    证据原文{esc(payload.get('evidence')) if payload.get('evidence') else '—'}
    " f"
    字段
    {_mapping_html(fields)}" f"{entity_html}" f"
    查看完整卡载荷{_data_block(payload)}
    " + (f"
    查看拟议变更{_data_block(card.get('proposed_changes'))}
    " if card.get("proposed_changes") else "") + (f"
    查看当前正式快照{_data_block(card.get('current_canonical_snapshot'))}
    " if card.get("current_canonical_snapshot") else "") + "
    " ) def _work_knowledge_panel(wid, params=None): """作品级知识链下钻:可筛选卡片、右侧详情、抽取运行与向量状态。""" params = params or {} if not table_exists("muse_knowledge_draft"): return empty_gap("知识抽取 · 待落库", "知识草稿表尚未建立。") _, summary = q( """SELECT COUNT(*) FILTER (WHERE d.status='pending'), COUNT(*) FILTER (WHERE d.status='confirmed'), COUNT(*) FILTER (WHERE d.draft_type='entity' AND d.status='pending'), COUNT(*) FILTER (WHERE d.draft_type='relation' AND d.status='pending'), (SELECT COUNT(*) FROM muse_knowledge_entity e WHERE e.work_id=%s AND e.deleted=false), (SELECT COUNT(*) FROM muse_knowledge_relation r WHERE r.work_id=%s AND r.deleted=false), (SELECT COUNT(*) FROM muse_knowledge_binding b WHERE b.work_id=%s AND b.deleted=false AND b.binding_status='active'), (SELECT COUNT(*) FROM example_knowledge_embedding e WHERE e.deleted=false AND ( EXISTS (SELECT 1 FROM muse_knowledge_draft d2 WHERE d2.id=e.draft_id AND d2.work_id=%s AND d2.deleted=false) OR EXISTS (SELECT 1 FROM muse_knowledge_entity e2 WHERE e2.id=e.entity_id AND e2.work_id=%s AND e2.deleted=false) )) FROM muse_knowledge_draft d WHERE d.work_id=%s AND d.deleted=false""", (wid, wid, wid, wid, wid, wid), ) pending, confirmed_drafts, pending_entities, pending_relations, entities, relations, bindings, embeddings = summary[0] search = (params.get("q") or [""])[0].strip() type_filter = (params.get("type") or [""])[0].strip() status_filter = (params.get("status") or [""])[0].strip() chapter_filter = (params.get("chapter") or [""])[0].strip() selected_card = (params.get("card") or [""])[0].strip() try: selected_card_id = int(selected_card) if selected_card else None except ValueError: selected_card_id = None _, type_rows = q( """SELECT DISTINCT COALESCE(e.entity_type,d.draft_payload->>'type',d.draft_payload->>'型',d.draft_type) FROM muse_knowledge_draft d LEFT JOIN muse_knowledge_entity e ON e.id=d.entity_id AND e.deleted=false WHERE d.work_id=%s AND d.deleted=false ORDER BY 1""", (wid,), ) _, chapter_rows = q( "SELECT id,order_no,title FROM muse_content_chapter WHERE work_id=%s AND deleted=false ORDER BY order_no", (wid,), ) type_options = [row[0] for row in type_rows if row[0]] _, status_rows = q( "SELECT DISTINCT status FROM muse_knowledge_draft WHERE work_id=%s AND deleted=false ORDER BY status", (wid,), ) status_options = [row[0] for row in status_rows if row[0]] conditions = ["d.work_id=%s", "d.deleted=false"] query_params = [wid] if search: like = f"%{search}%" conditions.append("(d.draft_payload::text ILIKE %s OR COALESCE(e.normalized_name,'') ILIKE %s OR COALESCE(e.description,'') ILIKE %s)") query_params.extend([like, like, like]) if type_filter: conditions.append("COALESCE(e.entity_type,d.draft_payload->>'type',d.draft_payload->>'型',d.draft_type)=%s") query_params.append(type_filter) if status_filter: conditions.append("d.status=%s") query_params.append(status_filter) if chapter_filter: try: chapter_order = int(chapter_filter) except ValueError: chapter_order = None if chapter_order is not None: conditions.append("c.order_no=%s") query_params.append(chapter_order) list_cols, list_rows = q( """SELECT d.id AS draft_id,d.entity_id,d.draft_type,d.status,d.confidence,d.source_type,d.source_id, c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title, d.update_time,d.create_time, COALESCE(e.normalized_name,d.draft_payload->>'name',d.draft_payload->>'名称', d.draft_payload->'source'->>'name',d.draft_payload->'target'->>'name','未命名卡') AS entity_name, COALESCE(e.entity_type,d.draft_payload->>'type',d.draft_payload->>'型',d.draft_type) AS entity_type, COALESCE(e.description,d.draft_payload->>'brief',d.draft_payload->>'description','') AS summary, EXISTS (SELECT 1 FROM example_knowledge_embedding emb WHERE emb.deleted=false AND (emb.draft_id=d.id OR emb.entity_id=d.entity_id)) AS has_embedding FROM muse_knowledge_draft d LEFT JOIN muse_knowledge_entity e ON e.id=d.entity_id AND e.deleted=false LEFT JOIN muse_content_chapter c ON c.id=d.source_id AND c.work_id=d.work_id AND c.deleted=false WHERE """ + " AND ".join(conditions) + " ORDER BY d.update_time DESC NULLS LAST,d.create_time DESC,d.id DESC", tuple(query_params), ) rows = _row_dicts(list_cols, list_rows) detail = ("
    卡详情
    " "
    点击左侧实体/卡列表,右侧显示完整内容和来源证据。
    ") if selected_card_id is not None: detail_cols, detail_rows = q( """SELECT d.id AS draft_id,d.entity_id,d.draft_type,d.status,d.confidence,d.source_type,d.source_id, d.revision,d.create_time,d.update_time,d.draft_payload,d.proposed_changes, d.current_canonical_snapshot,d.source_snapshot_id,d.authorization_snapshot_id, d.source_status,d.source_action_policy, c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title, e.id AS entity_id_joined,e.normalized_name AS entity_name,e.entity_type AS entity_type, e.description AS entity_description,e.attributes AS entity_attributes,e.status AS entity_status, e.update_time AS entity_update_time,e.lineage_payload AS entity_lineage_payload FROM muse_knowledge_draft d LEFT JOIN muse_content_chapter c ON c.id=d.source_id AND c.work_id=d.work_id AND c.deleted=false LEFT JOIN muse_knowledge_entity e ON e.id=d.entity_id AND e.deleted=false WHERE d.id=%s AND d.work_id=%s AND d.deleted=false""", (selected_card_id, wid), ) if detail_rows: card = dict(zip(detail_cols, detail_rows[0])) card["entity_id"] = card.get("entity_id_joined") detail = _knowledge_card_detail(wid, card) else: detail = ("
    卡详情
    " "
    该卡不属于当前作品,或已经被软删除。
    ") def option(value, label, selected): return f"" type_opts = "".join(option(value, _ENUM_LABELS.get("entity_type", {}).get(value, value), type_filter) for value in type_options) status_opts = "".join(option(value, _ENUM_LABELS.get("status", {}).get(value, value), status_filter) for value in status_options) chapter_opts = "".join(option(row[1], f"第{row[1]}章《{row[2]}》" if row[2] else f"第{row[1]}章", chapter_filter) for row in chapter_rows) filter_form = ( "
    " f"" f"" f"" f"" "" f"清除条件
    " ) card_rows = [] for row in rows: selected = " selected" if row["draft_id"] == selected_card_id else "" current = dict(params or {}) href = _query_href(f"/works/{wid}", current, card=row["draft_id"]) summary_text = short_text(row["summary"], 120) if row["summary"] else "暂无摘要" source_text = _chapter_text(row["chapter_id"], row["chapter_order"], row["chapter_title"]) card_rows.append( f"" f"
    {esc(row['entity_name'])}{enum_text(row['entity_type'], 'entity_type')}
    " f"
    {enum_text(row['draft_type'], 'draft_type')} · {source_text} · 更新于 {esc(row['update_time'])}
    " f"
    {esc(summary_text)} · {chip(row['status'])}
    " ) list_body = "".join(card_rows) if card_rows else empty_gap("没有匹配的实体/卡", "调整搜索或筛选条件后重试。") list_html = ( f"
    实体/卡列表({len(rows)})默认按更新时间倒序
    " f"
    {list_body}
    " ) cards = ( f"
    " f"
    {fmt_num(pending)}
    待确认草稿
    " f"
    实体 {fmt_num(pending_entities)} · 关系 {fmt_num(pending_relations)}
    " f"
    {fmt_num(confirmed_drafts)}
    已确认草稿
    " f"
    {fmt_num(entities)}
    正式实体
    " f"
    {fmt_num(relations)}
    正式关系
    " f"
    {fmt_num(embeddings)}
    草稿向量
    " f"
    {fmt_num(bindings)}
    有效知识库绑定
    " f"
    " ) knowledge_grid = f"
    {list_html}{detail}
    " runs = _runs_for_work(wid, 30) runs_html = ( f"
    作品运行链(最近 {len(runs)})
    " f"{_RUN_COLS}{_run_rows(runs)}
    " ) if runs else empty_gap("作品暂无运行", "运行登记后会显示模型、回执和原始证据下钻入口。") return f"

    知识闭环

    抽取 → 草稿 → 向量 → 用户确认 → 正式实体/关系;看板只读,确认仍走确认通道。

    {cards}{filter_form}{knowledge_grid}{runs_html}" _SCHEMA_LABELS = { "outline": "大纲结构", "novel_work": "作品设定", "narrative_state": "叙事状态", "assembly": "装配结构", } def _planning_title(payload, section_type, target_chapter): payload = payload if isinstance(payload, dict) else {} if payload.get("title"): return str(payload["title"]) if payload.get("chapterGoal"): return f"第{target_chapter}章目标" if target_chapter is not None else "章节目标" if isinstance(payload.get("state"), dict) and payload["state"].get("currentSituation"): return "当前叙事状态" if section_type == "assembly": return "范式装配结果" return _ENUM_LABELS.get("section_type", {}).get(section_type, "规划内容") def _planning_summary(payload): payload = payload if isinstance(payload, dict) else {} candidates = [ payload.get("chapterGoal"), payload.get("overall_arc"), payload.get("core_premise"), payload.get("currentSituation"), payload.get("chapterEndHook"), payload.get("handoff"), ] state = payload.get("state") if isinstance(state, dict): candidates.insert(3, state.get("currentSituation")) return next((str(value) for value in candidates if value), "暂无摘要") def _planning_detail(wid, section): payload = section.get("payload") or {} section_type = section.get("section_type") title = _planning_title(payload, section_type, section.get("target_chapter")) summary_rows = [] summary_keys = [ ("title", "标题"), ("chapterGoal", "章节目标"), ("overall_arc", "总体弧线"), ("core_premise", "核心设定"), ("currentSituation", "当前局势"), ("chapterEndHook", "章末钩子"), ("handoff", "交接信息"), ] for key, label in summary_keys: value = payload.get(key) if value: summary_rows.append(f"{label}{esc(_json_text(value))}") state = payload.get("state") if isinstance(state, dict) and state.get("currentSituation") and not payload.get("currentSituation"): summary_rows.append(f"当前局势{esc(_json_text(state['currentSituation']))}") summary_html = ("
    规划摘要
    " + "".join(summary_rows) + "
    ") if summary_rows else "" chapter = _chapter_text(section.get("chapter_id"), section.get("chapter_order"), section.get("chapter_title"), link_work=wid) return ( "
    " f"

    {esc(title)}

    {enum_text(section_type, 'section_type')} · 规划编号 {esc(section.get('planning_id'))}
    " "" f"" f"" f"" f"" f"" f"" f"" f"" "
    规划类型{enum_text(section_type, 'section_type')}
    结构类型{_SCHEMA_LABELS.get(section.get('schema_type'), '未登记')} {esc(section.get('schema_type'))}
    目标章{chapter}
    版本号{esc(section.get('version'))}
    状态{chip(section.get('state'))}
    确认时间{esc(section.get('confirmed_at'))}
    创建时间{esc(section.get('create_time'))}
    更新时间{esc(section.get('update_time'))}
    " f"{summary_html}" f"
    查看完整规划载荷{_data_block(payload)}
    " "
    " ) def _planning_panel(wid, params=None): """规划列表:更新时间倒序、服务端筛选分页,选中项右侧展示完整 payload。""" params = params or {} if not table_exists("example_planning_section"): return empty_gap("规划 · 待落库", "规划表尚未建立(编码:100)。") keyword = (params.get("plan_q") or [""])[0].strip() type_filter = (params.get("plan_type") or [""])[0].strip() schema_filter = (params.get("plan_schema") or [""])[0].strip() state_filter = (params.get("plan_state") or [""])[0].strip() chapter_filter = (params.get("plan_chapter") or [""])[0].strip() selected_raw = (params.get("plan") or [""])[0].strip() try: selected_id = int(selected_raw) if selected_raw else None except ValueError: selected_id = None try: page = max(1, int((params.get("plan_page") or ["1"])[0])) except ValueError: page = 1 page_size = 10 _, type_rows = q("SELECT DISTINCT section_type FROM example_planning_section WHERE work_id=%s AND deleted=false ORDER BY 1", (wid,)) _, schema_rows = q("SELECT DISTINCT schema_type FROM example_planning_section WHERE work_id=%s AND deleted=false ORDER BY 1", (wid,)) _, state_rows = q("SELECT DISTINCT state FROM example_planning_section WHERE work_id=%s AND deleted=false ORDER BY 1", (wid,)) _, chapter_rows = q("SELECT order_no,title FROM muse_content_chapter WHERE work_id=%s AND deleted=false ORDER BY order_no", (wid,)) conditions = ["p.work_id=%s", "p.deleted=false"] query_params = [wid] if keyword: like = f"%{keyword}%" conditions.append("(p.payload::text ILIKE %s OR p.section_type ILIKE %s OR p.schema_type ILIKE %s)") query_params.extend([like, like, like]) if type_filter: conditions.append("p.section_type=%s") query_params.append(type_filter) if schema_filter: conditions.append("p.schema_type=%s") query_params.append(schema_filter) if state_filter: conditions.append("p.state=%s") query_params.append(state_filter) if chapter_filter == "none": conditions.append("p.target_chapter IS NULL") elif chapter_filter: try: chapter_number = int(chapter_filter) except ValueError: chapter_number = None if chapter_number is not None: conditions.append("p.target_chapter=%s") query_params.append(chapter_number) where_sql = " AND ".join(conditions) total = int(one("SELECT count(*) FROM example_planning_section p WHERE " + where_sql, tuple(query_params)) or 0) page_count = max(1, (total + page_size - 1) // page_size) page = min(page, page_count) list_cols, list_rows = q( """SELECT p.id AS planning_id,p.section_type,p.schema_type,p.target_chapter,p.version,p.state,p.confirmed_at, p.create_time,p.update_time,p.payload,c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title FROM example_planning_section p LEFT JOIN muse_content_chapter c ON c.work_id=p.work_id AND c.order_no=p.target_chapter AND c.deleted=false WHERE """ + where_sql + " ORDER BY p.update_time DESC NULLS LAST,p.create_time DESC,p.id DESC LIMIT %s OFFSET %s", tuple(query_params) + (page_size, (page - 1) * page_size), ) rows = _row_dicts(list_cols, list_rows) def option(value, label, selected): return f"" type_opts = "".join(option(row[0], _ENUM_LABELS.get("section_type", {}).get(row[0], row[0]), type_filter) for row in type_rows) schema_opts = "".join(option(row[0], _SCHEMA_LABELS.get(row[0], row[0]), schema_filter) for row in schema_rows) state_opts = "".join(option(row[0], _ENUM_LABELS.get("status", {}).get(row[0], row[0]), state_filter) for row in state_rows) chapter_opts = "".format(" selected" if chapter_filter == "none" else "") chapter_opts += "".join(option(row[0], f"第{row[0]}章《{row[1]}》" if row[1] else f"第{row[0]}章", chapter_filter) for row in chapter_rows) filter_form = ( "
    " f"" f"" f"" f"" f"" "" f"清除规划条件
    " ) items = [] for row in rows: selected = " selected" if row["planning_id"] == selected_id else "" href = _query_href(f"/works/{wid}", params, plan=row["planning_id"], plan_page=page) title = _planning_title(row["payload"], row["section_type"], row["target_chapter"]) chapter = _chapter_text(row["chapter_id"], row["chapter_order"], row["chapter_title"]) items.append( f"" f"
    {esc(title)}{enum_text(row['section_type'], 'section_type')}
    " f"
    目标:{chapter} · 版本 {esc(row['version'])} · 更新于 {esc(row['update_time'])}
    " f"
    {esc(short_text(_planning_summary(row['payload']), 130))} · {chip(row['state'])}
    " ) list_body = "".join(items) if items else empty_gap("没有匹配的规划", "调整搜索或筛选条件后重试。") pager_parts = [f"第 {page}/{page_count} 页 · 共 {total} 条"] if page > 1: pager_parts.append(f"上一页") page_start = max(1, page - 2) page_end = min(page_count, page + 2) if page_start > 1: pager_parts.append(f"1") if page_start > 2: pager_parts.append("…") pager_parts.extend( f"{number}" if number == page else f"{number}" for number in range(page_start, page_end + 1) ) if page_end < page_count: if page_end < page_count - 1: pager_parts.append("…") pager_parts.append(f"{page_count}") if page < page_count: pager_parts.append(f"下一页") pager = "
    " + "".join(pager_parts) + "
    " list_html = (f"
    规划列表({len(rows)} / {total})默认按更新时间倒序
    " f"
    {list_body}
    {pager}
    ") detail = ("
    规划详情
    " "
    点击左侧规划列表,右侧显示完整规划内容和版本信息。
    ") if selected_id is not None: detail_cols, detail_rows = q( """SELECT p.id AS planning_id,p.section_type,p.schema_type,p.target_chapter,p.version,p.state,p.confirmed_at, p.create_time,p.update_time,p.payload,c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title FROM example_planning_section p LEFT JOIN muse_content_chapter c ON c.work_id=p.work_id AND c.order_no=p.target_chapter AND c.deleted=false WHERE p.id=%s AND p.work_id=%s AND p.deleted=false""", (selected_id, wid), ) if detail_rows: detail = _planning_detail(wid, dict(zip(detail_cols, detail_rows[0]))) else: detail = ("
    规划详情
    " "
    该规划不属于当前作品,或已经被软删除。
    ") return f"{filter_form}
    {list_html}{detail}
    " def view_work(wid, params=None): _, wrows = q( "SELECT w.id,w.title,w.status,w.genre,w.summary, " "COALESCE(SUM(b.word_count),0)::bigint AS word_count, " "COUNT(DISTINCT c.id)::bigint AS chapter_count " "FROM muse_content_work w " "LEFT JOIN muse_content_chapter c ON c.work_id=w.id AND c.deleted=false " "LEFT JOIN muse_content_block b ON b.chapter_id=c.id AND b.deleted=false " "WHERE w.id=%s AND w.deleted=false GROUP BY w.id,w.title,w.status,w.genre,w.summary", (wid,), ) if not wrows: return layout("作品", "

    作品不存在

    ", "/works") wid_, title, status, genre, summary, wc, cc = wrows[0] _, chs = q("SELECT id, order_no, title, status, revision FROM muse_content_chapter " "WHERE work_id=%s AND deleted=false ORDER BY order_no", (wid,)) if chs: trs = "".join( f"{esc(c[1])}" f"{esc(c[2]) or f'第{c[1]}章'}" f"{chip(c[3])}{esc(c[4])}" for c in chs) chap = (f"
    章节({len(chs)})
    " f"" f"{trs}
    序标题状态修订
    ") else: chap = empty_gap("还没有章节", "") plan = _planning_panel(wid, params) knowledge = _work_knowledge_panel(wid, params) body = (f"
    作品 / {esc(title)}
    " f"

    {esc(title)} {chip(status)}

    " f"

    {esc(genre)} · {fmt_num(wc)} 字 · {esc(cc)} 章

    " f"

    {esc(summary)}

    {chap}

    规划

    {plan}{knowledge}") return layout(title, body, "/works") def view_chapter(wid, order_no): _, wrow = q("SELECT title FROM muse_content_work WHERE id=%s AND deleted=false", (wid,)) if not wrow: return layout("章节", "

    作品不存在

    ", "/works") wtitle = wrow[0][0] _, ch = q("SELECT id,title,order_no FROM muse_content_chapter " "WHERE work_id=%s AND order_no=%s AND deleted=false", (wid, order_no)) if not ch: return layout("章节", "

    章节不存在

    ", "/works") cid, ctitle, _ = ch[0] _, blocks = q("SELECT content_text, word_count FROM muse_content_block " "WHERE chapter_id=%s AND deleted=false ORDER BY order_no", (cid,)) text = "\n\n".join((b[0] or "") for b in blocks) body = (f"
    作品 / {esc(wtitle)} " f"/ 第{esc(order_no)}章
    " f"

    {esc(ctitle) or f'第{order_no}章'}{fmt_num(sum(b[1] or 0 for b in blocks))} 字 · 正文全文(实时查库)

    " f"
    {esc(text) if text.strip() else '(本章正文为空)'}
    ") return layout(f"{wtitle} · 第{order_no}章", body, "/works") # ── 知识库(02 实体 + 03 范式:知识卡 + 参考书·拆书·清洗)───────────────────── def _barset(rows, slots, group=None): if not rows: return "
    无数据
    " mx = max(r[1] for r in rows) total = sum(r[1] for r in rows) out = [] for i, (name, n) in enumerate(rows): color = f"var(--s{slots[i % len(slots)]})" display_name = enum_text(name, group) if group else esc(name) out.append(f"
    {display_name}" f"" f"{fmt_num(n)} · {n/total*100:.0f}%
    ") return "".join(out) def view_knowledge(params=None): params = params or {} selected = (params.get("work") or [""])[0] _, works = q("SELECT id,title FROM muse_content_work WHERE deleted=false ORDER BY id") selector = _selector("按作品:", "work", [(w[0], w[1]) for w in works], selected) if selected: try: work_id = int(selected) except ValueError: return layout("知识库", "

    作品参数非法

    ", "/knowledge") _, work = q("SELECT title FROM muse_content_work WHERE id=%s AND deleted=false", (work_id,)) if not work: return layout("知识库", "

    作品不存在

    ", "/knowledge") body = (f"
    知识库 / {esc(work[0][0])}
    " f"

    知识库作品级创作闭环 · {esc(work[0][0])}

    " f"{selector}{_work_knowledge_panel(work_id)}") return layout("知识库 · " + str(work[0][0]), body, "/knowledge") drafts = one("SELECT count(*) FROM muse_knowledge_draft WHERE deleted=false") or 0 entities = one("SELECT count(*) FROM muse_knowledge_entity WHERE deleted=false") or 0 public = one("SELECT count(*) FROM muse_knowledge_draft WHERE work_id=0 AND deleted=false") or 0 work_cards = one("SELECT count(*) FROM muse_knowledge_draft WHERE work_id<>0 AND deleted=false") or 0 # 型别看载荷字段,不看 draft_type 列(库内约定,防误导):公共卡用中文键「型」,作品卡用 type 编码。 _, pub_types = q("SELECT COALESCE(draft_payload->>'型','(无型)') t, count(*) FROM muse_knowledge_draft " "WHERE work_id=0 AND deleted=false GROUP BY 1 ORDER BY 2 DESC") _, wk_types = q("SELECT COALESCE(draft_payload->>'type','(无型)') t, count(*) FROM muse_knowledge_draft " "WHERE work_id<>0 AND deleted=false GROUP BY 1 ORDER BY 2 DESC") cards = f"""
    {fmt_num(drafts)}
    草稿卡(待确认)
    {fmt_num(entities)}
    已确认知识
    {"当前为 0 · 全部草稿待确认" if entities==0 else ""}
    {fmt_num(public)}
    公共范式卡(work_id=0)
    {fmt_num(work_cards)}
    作品级卡(work_id≠0)
    公共范式卡 · 按型分布(载荷字段 payload 的中文键「型」)
    {_barset(pub_types,[1,2,3,4,5], 'entity_type')}
    作品级卡 · 按型分布(类型字段 type)
    {_barset(wk_types,[1,2,3,4,5], 'entity_type')}

    型别看载荷字段 draft_payload,不看 draft_type 列(该列恒为实体草稿,无区分力)。 文风与节奏(style / pacing)已注册 schema,但当前 0 张卡。草稿≠已确认:确认前正式实体表为 0 是正常状态。

    """ # 参考书·拆书·清洗(知识从哪来——拆书是知识的生产线) _, refs = q("SELECT id, source_file, author, imported_chapter_count, declared_chapter_count, " "char_count, parse_status FROM example_reference_work WHERE deleted=false ORDER BY id") if refs: trs = "".join( f"{esc(r[1])}{esc(r[2])}" f"{esc(r[3])} / {esc(r[4] or '?')}{fmt_num(r[5])}" f"{chip(r[6])}" for r in refs) reftbl = (f"
    参考书档案({len(refs)})
    " f"" f"{trs}
    原文件作者导入/声明章数字符数拆书状态
    ") else: reftbl = empty_gap("还没有参考书", "导入参考书后在此显示") _, pt = q("SELECT scaffold_status, pattern_status, count(*) FROM example_parse_task " "WHERE deleted=false GROUP BY 1,2 ORDER BY 3 DESC") pt_html = ("
    拆书任务状态(章 × 两阶段)
    " + "".join(f"
    脚手架 {chip(s)} · 范式 {chip(p)}" f"{fmt_num(n)} 章
    " for s, p, n in pt) + "
    " ) if pt else "" cleans = one("SELECT count(*) FROM example_clean_log WHERE deleted=false") or 0 _, recent = q("SELECT batch, reason, model, occurrences, create_time FROM example_clean_log " "WHERE deleted=false ORDER BY create_time DESC LIMIT 10") clean_html = (f"
    清洗审计(共 {fmt_num(cleans)} 条 · 最近 10)
    " + "".join(f"
    {esc(r[0])} · {esc(r[2])}" f"{esc(r[1])}" f"{esc(r[3])} 处
    " for r in recent) + "
    " ) if recent else "" body = (f"

    知识库实体与范式(表编码 muse_knowledge_*)+ 它们的生产线(拆书·清洗)· 实体 02 · 范式 03

    " f"{selector}{cards}

    参考书 · 拆书 · 清洗

    {reftbl}{pt_html}{clean_html}") return layout("知识库", body, "/knowledge") # ── 运行记录(05 流程 + 06 质量:运行列表按作品/按角色双维 + 运行详情下钻)──────── _RUN_COLS = ("" "") def _run_description(run): """把机器运行登记拼成用户能判断用途的中文描述。""" run_id, work_id, work_title, target_chapter, trigger, terminal, started = run[:7] chapter_id, chapter_order, chapter_title = run[7:10] adapter_role, stage_kind, run_type, source_role, purpose = run[10:15] if purpose == "planning_persistence" or str(run_id).startswith("planning-persist"): kind = "规划落库" elif purpose == "production_generation" or run_type == "production" or str(run_id).startswith("run-prod"): kind = "正文生产" elif adapter_role == "extractor" or source_role == "extractor" or str(run_id).startswith("extract-knowledge"): kind = "章后抽卡" elif purpose == "evaluation" or run_type == "eval": kind = "质量评测" elif adapter_role == "planner": kind = "规划生成" else: kind = _ENUM_LABELS.get("stage_kind", {}).get(stage_kind, "运行") actor = source_role or adapter_role actor_html = f" · {enum_text(actor, 'source_role')}" if actor else "" chapter_html = "" if chapter_order is not None: chapter_html = f" · {_chapter_text(chapter_id, chapter_order, chapter_title)}" return f"{html.escape(kind)}{actor_html}{chapter_html}" def _run_rows(runs): return "".join( f"" f"" f"" for r in runs) def _runs_query(where_sql="r.deleted=false", params=(), limit=50): candidate_join = ( "LEFT JOIN LATERAL (SELECT ec.run_type,ec.source_role FROM example_candidate ec " "WHERE ec.run_id=r.run_id AND ec.deleted=false ORDER BY ec.create_time,ec.id LIMIT 1) cand ON TRUE" if table_exists("example_candidate") else "LEFT JOIN LATERAL (SELECT NULL::text AS run_type,NULL::text AS source_role) cand ON TRUE" ) raw_join = ( "LEFT JOIN LATERAL (SELECT rl.purpose FROM example_raw_lease rl " "WHERE rl.run_id=r.run_id ORDER BY rl.create_time,rl.id LIMIT 1) raw ON TRUE" if table_exists("example_raw_lease") else "LEFT JOIN LATERAL (SELECT NULL::text AS purpose) raw ON TRUE" ) receipt_join = ( "LEFT JOIN LATERAL (SELECT er.adapter_role,er.stage_kind FROM example_run_receipt er " "WHERE er.run_id=r.run_id ORDER BY er.create_time,er.id LIMIT 1) first_receipt ON TRUE" if table_exists("example_run_receipt") else "LEFT JOIN LATERAL (SELECT NULL::text AS adapter_role,NULL::text AS stage_kind) first_receipt ON TRUE" ) sql = ( "SELECT r.run_id,r.work_id,w.title AS work_title,r.target_chapter,r.trigger_source,r.terminal_state,r.started_at," "c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title," "first_receipt.adapter_role,first_receipt.stage_kind,cand.run_type,cand.source_role,raw.purpose " "FROM example_run r LEFT JOIN muse_content_work w ON w.id=r.work_id " "LEFT JOIN muse_content_chapter c ON c.work_id=r.work_id AND c.order_no=r.target_chapter AND c.deleted=false " f"{receipt_join} {candidate_join} {raw_join} WHERE {where_sql} " "ORDER BY r.started_at DESC LIMIT %s" ) _, runs = q(sql, tuple(params) + (limit,)) return runs def _all_runs(limit=50): return _runs_query(limit=limit) def _runs_for_work(work_id, limit=50): return _runs_query("r.deleted=false AND r.work_id=%s", (work_id,), limit) def _selector(label, name, options, sel): """服务端渲染的下拉筛选(GET 表单,无 JS)。options=[(值,显示名)]。""" opts = "".join(f"" for v, t in options) return (f"" f"" f"" f"") def view_runs(): runs = _all_runs() entries = ("

    按维度查看:按作品看运行 · " "按角色看运行

    ") runs_html = (f"
    运行(最近 50)
    " + _RUN_COLS + _run_rows(runs) + "
    运行编号 run_id作品运行内容目标章触发来源终态开始时间
    {esc(r[0])}{esc(r[2])} work_id={esc(r[1])}{_run_description(r)}{esc(r[3])}{display_trigger(r[4])}{chip(r[5])}{esc(r[6])}
    ") if runs else \ empty_gap("暂无运行记录", "创作链落库后,每次运行登记一行(example_run)。") if table_exists("example_candidate"): _, cands = q("SELECT id, work_id, target_chapter, candidate_version, run_type, state, source_role " "FROM example_candidate WHERE deleted=false AND state IN ('draft','checking','passed') " "ORDER BY create_time DESC LIMIT 30") cand = ("
    待审候选(活跃)
    " "" + "".join(f"" f"" for c in cands) + "
    候选编号 candidate_id作品目标章版本运行类型状态来源角色
    {esc(c[0])}{esc(c[1])}{esc(c[2])}{esc(c[3])}{enum_text(c[4], 'run_type')}{chip(c[5])}{enum_text(c[6], 'source_role')}
    ") if cands else "
    候选表已建 · 暂无待审候选
    " else: cand = empty_gap("待审候选 · 待落库", "候选表尚未建立(编码:99),模型候选正文暂无持久化登记。") if table_exists("example_user_decision"): _, decs = q("SELECT work_id, target_chapter, decision, rationale, decided_by, create_time " "FROM example_user_decision ORDER BY create_time DESC LIMIT 30") dec = ("
    用户决策(最近 30)
    " "" + "".join(f"" f"" for d in decs) + "
    作品章决策理据决策人时间
    {esc(d[0])}{esc(d[1])}{enum_chip(d[2], 'decision')}{annotated_text((d[3] or '')[:40])}{esc(d[4])}{esc(d[5])}
    ") if decs else "
    决策表已建 · 暂无决策
    " else: dec = empty_gap("用户决策 · 待落库", "决策记录表尚未建立(编码:99),接受/合并/丢弃暂以版本库提交留痕。") body = (f"

    运行记录智能体的操作与产出 · 流程 05 · 质量 06

    " f"{entries}{runs_html}

    待审候选

    {cand}

    用户决策

    {dec}") return layout("运行记录", body, "/runs") def view_runs_by_work(params): sel = (params.get("work") or [""])[0] _, works = q("SELECT id, title FROM muse_content_work WHERE deleted=false ORDER BY id") selector = _selector("选作品:", "work", [(w[0], w[1]) for w in works], sel) if sel: runs = _runs_for_work(sel, 50) else: runs = _all_runs() tbl = (_RUN_COLS + _run_rows(runs) + "") if runs else "
    暂无运行
    " body = (f"
    运行记录 / 按作品
    " f"

    按作品看运行

    {selector}
    {tbl}
    ") return layout("按作品看运行", body, "/runs") def view_runs_by_role(params): sel = (params.get("role") or [""])[0] _, roles = q("SELECT role, display_name FROM example_agent_role WHERE deleted=false ORDER BY role") selector = _selector("选角色:", "role", [(r[0], r[1] or r[0]) for r in roles], sel) if sel: runs = _runs_query( "r.deleted=false AND EXISTS (SELECT 1 FROM example_run_receipt role_receipt " "WHERE role_receipt.run_id=r.run_id AND role_receipt.adapter_role=%s)", (sel,), 50, ) else: runs = _all_runs() tbl = (_RUN_COLS + _run_rows(runs) + "") if runs else "
    该角色暂无运行
    " body = (f"
    运行记录 / 按角色
    " f"

    按角色看运行

    {selector}
    {tbl}
    ") return layout("按角色看运行", body, "/runs") def view_run_detail(run_id): _, rr = q( """SELECT r.run_id,r.work_id,r.target_chapter,r.trigger_source,r.started_at,r.finished_at,r.terminal_state, w.title AS work_title,c.id AS chapter_id,c.order_no AS chapter_order,c.title AS chapter_title FROM example_run r LEFT JOIN muse_content_work w ON w.id=r.work_id LEFT JOIN muse_content_chapter c ON c.work_id=r.work_id AND c.order_no=r.target_chapter AND c.deleted=false WHERE r.run_id=%s AND r.deleted=false""", (run_id,), ) if not rr: return layout("运行", "

    运行不存在

    ", "/runs") rid, wid, chap, trig, started, finished, state, work_title, chapter_id, chapter_order, chapter_title = rr[0] rc_cols, rc_rows = q( """SELECT id,sample_id,revision,stage_kind,adapter_role,attempt,loop_seq,candidate_version,is_error, stop_reason,terminal_reason,safe_summary,raw_content_id,requested_model_id,actual_model_id, total_cost_usd,usage,create_time FROM example_run_receipt WHERE run_id=%s ORDER BY sample_id,revision""", (run_id,), ) rc = _row_dicts(rc_cols, rc_rows) q_cols, q_rows = q( """SELECT judge_kind,dimension,scale_version,score,conclusion,failure_class,detail,raw_content_id,create_time FROM example_quality_result WHERE run_id=%s ORDER BY create_time""", (run_id,), ) qres = _row_dicts(q_cols, q_rows) call_cols, call_rows = q( """SELECT id,call_time,caller,requested_model_id,actual_model_id,in_tokens,cached_tokens,out_tokens, cost_usd,stop_reason,duration_ms,prompt_sha256,raw_content_id FROM example_llm_call WHERE run_id=%s ORDER BY call_time""", (run_id,), ) calls = _row_dicts(call_cols, call_rows) candidates = [] if table_exists("example_candidate"): candidate_cols, candidate_rows = q( "SELECT id, target_chapter, candidate_version, run_type, state, source_role, " "candidate_sha256, context_sha256 FROM example_candidate " "WHERE run_id=%s AND deleted=false ORDER BY target_chapter, candidate_version", (run_id,), ) candidates = _row_dicts(candidate_cols, candidate_rows) cand = ( "
    候选(本运行)
    " "" "" + "".join( f"" f"" f"" for c in candidates ) + "
    候选编号 candidate_id目标章版本编码运行类型状态来源角色正文哈希上下文哈希
    {esc(c['id'])}{esc(c['target_chapter'])}{esc(c['candidate_version'])}{enum_text(c['run_type'], 'run_type')}{chip(c['state'])}{enum_text(c['source_role'], 'source_role')}{esc(c['candidate_sha256'])}{esc(c['context_sha256'])}
    " if candidates else "
    候选表已建 · 该运行暂无候选
    " ) else: cand = empty_gap("待审候选 · 待落库", "候选表尚未建立(编码:99)。") if table_exists("example_user_decision") and table_exists("example_candidate"): decision_cols, decision_rows = q( "SELECT d.id,d.decision,d.rationale,d.decided_by,d.canonical_block_id,d.create_time " "FROM example_user_decision d JOIN example_candidate c ON c.id=d.candidate_id " "WHERE c.run_id=%s ORDER BY d.create_time", (run_id,), ) decisions = _row_dicts(decision_cols, decision_rows) dec = ( "
    用户决策(本运行)
    " "" "" + "".join( f"" f"" for d in decisions ) + "
    决策编号 decision_id决策理据决策人正式正文块时间
    {esc(d['id'])}{enum_chip(d['decision'], 'decision')}{annotated_text(d['rationale'])}{esc(d['decided_by'])}{esc(d['canonical_block_id'])}{esc(d['create_time'])}
    " if decisions else "
    决策表已建 · 该运行暂无决策
    " ) else: dec = empty_gap("用户决策 · 待落库", "决策表(99)或候选表未建。") raw_rows = [] if table_exists("example_raw_lease"): raw_cols, raw_data = q( "SELECT l.id,l.purpose,l.status,count(c.id),max(c.create_time) " "FROM example_raw_lease l LEFT JOIN example_raw_content c ON c.lease_id=l.id " "WHERE l.run_id=%s GROUP BY l.id,l.purpose,l.status ORDER BY l.id", (run_id,), ) raw_rows = [dict(zip(raw_cols, row)) for row in raw_data] raw_html = ( "
    原始证据(本运行)
    " "" + "".join( f"" f"" for r in raw_rows ) + "
    租约编号 lease_id用途状态内容数最近写入
    {esc(r['id'])}{enum_text(r['purpose'], 'purpose')}{enum_chip(r['status'], 'status')}{esc(r['count'])}{esc(r['max'])}
    " if raw_rows else empty_gap("原始证据 · 该运行暂无记录", "模型输入输出落库后在此显示。") ) else: raw_html = empty_gap("原始证据 · 待落库", "原始证据表尚未建立(编码:example_raw_lease)。") raw_content = [] if table_exists("example_raw_content"): raw_content_cols, raw_content_rows = q( "SELECT id,kind,role,content_sha256,content,create_time FROM example_raw_content " "WHERE run_id=%s ORDER BY create_time,id", (run_id,) ) raw_content = _row_dicts(raw_content_cols, raw_content_rows) raw_details = [] for index, content in enumerate(raw_content): open_attr = " open" if index == len(raw_content) - 1 else "" raw_details.append( f"{_raw_kind_label(content['kind'])} · 内容编号 {esc(content['id'])} · " f"{len(content['content'] or '')} 字 · 哈希 {esc(content['content_sha256'])}" f"
    {esc(_content_text(content['content']))}
    " ) raw_detail_html = ( "
    模型输入 / 输出正文
    " + "".join(raw_details) + "
    " if raw_details else empty_gap("暂无模型输入输出", "原始内容落库后在此显示完整正文。") ) if table_exists("example_context_freeze") and wid is not None and chap is not None: _, freezes = q( "SELECT id,as_of_chapter,manifest_sha256,context_sha256,token_budget,create_time " "FROM example_context_freeze WHERE work_id=%s AND target_chapter=%s " "ORDER BY create_time DESC", (wid, chap), ) freeze_html = ( "
    上下文冻结(匹配本作品/目标章)
    " "" + "".join( f"" f"" f"" for f in freezes ) + "
    冻结编号 freeze_id基线章 as_of_chapter来源清单哈希上下文哈希令牌预算时间
    {esc(f[0])}{esc(f[1])}{esc(f[2])}{esc(f[3])}{esc(f[4])}{esc(f[5])}
    " if freezes else empty_gap("上下文冻结 · 该目标章暂无记录", "上下文装配后的冻结清单落库后在此显示。") ) else: freeze_html = empty_gap("上下文冻结 · 待落库", "冻结表尚未建立(编码:100),或该运行不是章级运行。") first_receipt = rc[0] if rc else {} first_candidate = candidates[0] if candidates else {} first_raw = raw_rows[0] if raw_rows else {} run_content = _run_description(( rid, wid, work_title, chap, trig, state, started, chapter_id, chapter_order, chapter_title, first_receipt.get("adapter_role"), first_receipt.get("stage_kind"), first_candidate.get("run_type"), first_candidate.get("source_role") or first_receipt.get("adapter_role"), first_raw.get("purpose"), )) chapter_html = _chapter_text(chapter_id, chapter_order, chapter_title, link_work=wid) info = (f"
    " f"" f"" f"" f"" f"" f"" f"" f"
    {field_label('运行编号', 'run_id')}{esc(rid)}
    作品{esc(work_title)} work_id={esc(wid)}
    运行内容{run_content}
    目标章{chapter_html}
    触发来源{display_trigger(trig)}
    终态{chip(state)}
    起止{esc(started)} → {esc(finished)}
    ") rc_html = ("
    回执链(按样本 · 修订链 revision)
    " "" + "".join( f"" f"" f"" f"" f"" for r in rc) + "
    样本编号修订号 revision阶段角色尝试次数 attempt补证次数 loop_seq候选版本结果诊断
    {esc(r['sample_id'])}{esc(r['revision'])}{enum_text(r['stage_kind'], 'stage_kind')}{enum_text(r['adapter_role'], 'source_role')}{esc(r['attempt'])}{esc(r['loop_seq'])}{esc(r['candidate_version'])}{chip('failed' if r['is_error'] else 'passed')}{_reason_text(r['stop_reason'])} / {_reason_text(r['terminal_reason'])}
    ") if rc else empty_gap("暂无回执", "该运行的回执落库后在此显示(修订链编码:revision)。") q_html = ("
    质量评判
    " "" + "".join( f"" f"" f"" for u in qres) + "
    评判类型维度编号量表版本编码分数结论失败类别编码详细信息
    {enum_chip(u['judge_kind'], 'judge_kind')}{esc(u['dimension'])}{esc(u['scale_version'])}{esc(u['score'])}{enum_chip(u['conclusion'], 'conclusion')}{esc(u['failure_class'])}{('
    查看' + _data_block(u['detail']) + '
    ') if u['detail'] else '—'}
    ") if qres else empty_gap("暂无质量评判", "") call_html = ("
    模型调用
    " "" + "".join( f"" f"" f"" for c in calls) + "
    时间调用方请求模型编码实际模型编码输入令牌缓存令牌输出令牌停止原因耗时毫秒原始内容
    {esc(c['call_time'])}{_operator_text(c['caller'])}{esc(c['requested_model_id'])}{esc(c['actual_model_id'])}{fmt_num(c['in_tokens'])}{fmt_num(c['cached_tokens'])}{fmt_num(c['out_tokens'])}{_reason_text(c['stop_reason'])}{fmt_num(c['duration_ms'])}{esc(c['raw_content_id'])}
    ") if calls else empty_gap("暂无模型调用", "") error_items = [] for r in rc: abnormal_stop = r["stop_reason"] not in (None, "", "completed", "tool_use") abnormal_terminal = r["terminal_reason"] not in (None, "", "completed") if r["is_error"] or abnormal_stop or abnormal_terminal: summary = _data_block(r["safe_summary"]) if r["safe_summary"] else "—" error_items.append( f"
    回执 · 样本 {esc(r['sample_id'])} · 修订 {esc(r['revision'])}
    " f"
    停止原因:{_reason_text(r['stop_reason'])} · 终态原因:{_reason_text(r['terminal_reason'])}
    " f"
    安全摘要:{summary}
    " ) for u in qres: if u["failure_class"] or u["conclusion"] == "fail": error_items.append( f"
    质量评判 · 类型 {enum_chip(u['judge_kind'], 'judge_kind')}
    " f"
    失败类别:{esc(u['failure_class'])}
    " f"
    判定细节:{_data_block(u['detail']) if u['detail'] else '—'}
    " ) if state == "failed" and not error_items: error_items.append("
    运行终态为失败,但回执和质量表没有提供更细错误记录。
    ") error_html = ("
    错误与诊断
    " + "".join(error_items) + "
    ") if error_items else "" body = (f"
    运行记录 / {esc(rid)}
    " f"

    运行详情

    {info}{error_html}" f"

    回执链

    {rc_html}

    质量评判

    {q_html}

    模型调用

    {call_html}" f"

    候选与决策

    {cand}{dec}

    证据与上下文

    {raw_html}{raw_detail_html}{freeze_html}") return layout("运行详情", body, "/runs") # ── 智能体(07:读 102 登记表 example_agent_role / example_skill;权威在 Git 侧,登记脚本同步)──── def _fmt_list(v): """JSONB 列表 → 技术编码逗号串;非列表原样转义。""" if isinstance(v, list): return ", ".join(f"{esc(x)}" for x in v) if v else '∅' return esc(v) _TOOL_LABELS = { "Read": "读取", "Write": "写入", "Grep": "文本搜索", "Glob": "文件匹配", } def _fmt_tools(v): """工具名称使用中文语义,CLI 工具名作为编码保留。""" if not isinstance(v, list): return esc(v) if not v: return '∅' return ", ".join( f"{html.escape(_TOOL_LABELS.get(str(item), '未登记'))} " f"{esc(item)}" for item in 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 _ENUM_LABELS['agent_role'].get(r[0], '未登记'))} " f"{esc(r[0])}" f"{esc(r[2])}{_fmt_tools(r[3])}" for r in roles) skill_rows = "".join( f"{enum_text(s[0], 'skill_name')}" f"{annotated_text(short_text(s[1]))}{esc(s[2])}" for s in skills) body = (f"

    智能体角色与技能 · 读取登记表(表编码 example_agent_role / example_skill)· 智能体 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 _ENUM_LABELS['agent_role'].get(role_, '未登记'))} {esc(role_)}
    " f"" f"" f"" f"" f"" f"" f"
    模型归属{esc(model)}
    工具{_fmt_tools(tools)}
    可配置项工作强度 effort:{esc(effort)} · 预算:{esc(budget)}
    出处编码{esc(src)} · 同步于 {esc(synced)}
    职责{annotated_text(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])}{enum_text(r[1], 'stage_kind')}{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) + "
    时间模型编码输出令牌数成本(美元)
    {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) + "
    类型维度结论失败类别
    {enum_text(u[0], 'judge_kind')}{esc(u[1])}{enum_chip(u[2], 'conclusion')}{esc(u[3])}
    ") if quals else empty_gap("本角色暂无质量评判", "") body = (f"
    智能体 / {esc(disp or _ENUM_LABELS['agent_role'].get(role_, '未登记'))}
    " f"

    {esc(disp or _ENUM_LABELS['agent_role'].get(role_, '未登记'))}

    {info}

    运行与产出

    {runs_html}{quals_html}{calls_html}") return layout(f"角色 · {disp or _ENUM_LABELS['agent_role'].get(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] skill_title = _ENUM_LABELS['skill_name'].get(sn, '未登记') model_html = f"{esc(model_used)}" if model_used else "确定性工具(不用模型)" body = (f"
    智能体 / 技能 / {esc(skill_title)}
    " f"

    {esc(skill_title)} {esc(sn)}

    " f"
    " f"" f"" f"" f"" f"" f"" f"
    用途{annotated_text(purpose)}
    给谁用{annotated_text(consumer) if consumer else '待补'}
    用模型{model_html}
    涉及表编码{_fmt_list(reads)}
    自动抽取,读/写方向待人工核
    红线{annotated_text(red_lines) if red_lines else '待补'}
    出处编码{esc(src)} · 同步于 {esc(synced)}
    ") return layout(f"技能 · {skill_title}", body, "/agents") # ── 路由 ───────────────────────────────────────────────────────────────────── _ROUTES = [ (re.compile(r"^/$"), lambda m, p: view_home()), (re.compile(r"^/works$"), lambda m, p: view_works()), (re.compile(r"^/works/(\d+)$"), lambda m, p: view_work(int(m.group(1)), p)), (re.compile(r"^/works/(\d+)/ch/(\d+)$"), lambda m, p: view_chapter(int(m.group(1)), int(m.group(2)))), (re.compile(r"^/knowledge$"), lambda m, p: view_knowledge(p)), (re.compile(r"^/ai-flavor$"), lambda m, p: view_ai_flavor(p)), (re.compile(r"^/agents$"), lambda m, p: view_agents()), (re.compile(r"^/agents/role/([\w-]+)$"), lambda m, p: view_agent_role(m.group(1))), (re.compile(r"^/agents/skill/([\w-]+)$"), lambda m, p: view_skill(m.group(1))), (re.compile(r"^/runs$"), lambda m, p: view_runs()), (re.compile(r"^/runs/by-work$"), lambda m, p: view_runs_by_work(p)), (re.compile(r"^/runs/by-role$"), lambda m, p: view_runs_by_role(p)), (re.compile(r"^/runs/([\w.-]+)$"), lambda m, p: view_run_detail(m.group(1))), ] class Handler(BaseHTTPRequestHandler): def do_GET(self): parsed = urlparse(self.path) params = parse_qs(parsed.query) try: for pat, fn in _ROUTES: m = pat.match(parsed.path) if m: self._send(200, fn(m, params)) return self._send(404, layout("未找到", "

    404 · 页面不存在

    ", "")) except Exception: # 客户端给固定文案,细节写服务端 stderr(不泄露 DB 内部结构;看板挂了不牵连库) traceback.print_exc() self._send(500, layout("出错", "

    看板渲染出错

    该请求处理失败,细节记录在服务端日志。

    ", "")) def _send(self, code, body): data = body.encode("utf-8") self.send_response(code) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) def log_message(self, fmt, *args): # 静音默认访问日志 pass if __name__ == "__main__": print(f"muse 只读看板:http://{HOST}:{PORT} (Ctrl-C 退出;只读连接,不写任何数据)") ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()