**测试台**
· 启动输入不再是「调用参数」表 —— 这条流真实的起法就是客户来信,
界面上就该是一封信(发件人 / 收件人 / 主题 / 正文 / BOM 附件)。
服务端拿**真通道的抽键正则** + **真触发器的装配映射**推出调用入参:
测试台自己不解析邮件,否则试通的是测试台而不是这条流。
抽不出必填就如实报「哪个字段没命中、通道的正则要什么」,不静默起空单。
· 一屏两大块:左选流,右两节 —— 01 输入配置、02 节点指派。
· 起单钮只剩一枚(原来页头一枚、RUN 卡里一枚)。
· 改暗色仪表台:调色板集中在 .g-tb/.g-rig 的一组变量上,
TRACE 那张树 + 甘特的结构一行没动,只是纸变成了面板。
**触发器 / 大模型** 列表改卡片:一条规则、一个实例都要一眼看全
(何时起 · 起哪条流 · 开没开 / 谁家的 · 什么型号 · 密钥绑没绑)。
触发器卡是 div 不是 button —— 卡里还坐着「改规则」那枚真按钮。
**同类卡等高**:底栏钉在卡底、说明恒占两行、卡头一行截断。
实测同一货架内 13 张工具卡、6 张活动卡、3 张模型卡各自完全等高。
**删掉 19 条 ⓘ 说教横幅**(「只列有生效版的包…」那一类)。
留下的是三种:确认带里的影响说明(点了动作才出现,是决策依据)、
warn/ok 的真反馈、以及「X 不存在」这类空态。
删掉入站邮件演练整屏 —— 它验的那两步已经并进测试台的起单路径了。
新增一条接缝测试:通道抽出来的键叫 keys.customerCode,触发器映射也得照这名字取;
哪头改了名字单还是能起,只是入参悄悄变空、流走到 G1 才莫名其妙地停。
四闸绿;契约快照随本提交入库。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
241 lines
9.4 KiB
TypeScript
241 lines
9.4 KiB
TypeScript
/**
|
||
* feature `gov-memory` —— 运营中心的两处:**经营驾驶舱**(度量八指标)与**知识库**(活动记忆四层)。
|
||
*
|
||
* 自 `~/coding/JCAI_PRD/governance/_components/Governance.tsx` 平移:
|
||
* DOM 结构、类名(`g-*` + 色调 `t-*`)与文案原样保留,mock 常量换成服务端投影(README 平移规则 1/2/4)。
|
||
*
|
||
* 原本这两件同挂在「记忆管理」一区,是两种心智硬挤在一起:
|
||
* 看指标问的是「今天跑得怎样」,管沉淀问的是「学到了什么、放不放行」。三面拆分后各自成页。
|
||
*/
|
||
import { useState } from 'react';
|
||
import { TERMINAL_STATE_LABEL } from '@contracts';
|
||
import type { TerminalState } from '@contracts';
|
||
import { Ico } from '@web-shared/Ico.tsx';
|
||
import { Chip, Clear, Kpi, NList, NRow, Note, PageHead, QueryGate, Todo } from '@web-shared/gov/primitives.tsx';
|
||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||
import {
|
||
useGovActivities,
|
||
useGovMemory,
|
||
useGovMetrics,
|
||
useGovTasks,
|
||
} from '@web-shared/gov/gov-api.ts';
|
||
|
||
export type HomeProps = { tab: string; nav: Nav };
|
||
|
||
const pct = (v: number | undefined) => Math.round((v ?? 0) * 100);
|
||
|
||
/** 终态五名的呈现色 —— 放行绿、驳回琥珀、停滞灰、耗尽红、终止墨,与状态章同族 */
|
||
const TERMINAL_TONE: Readonly<Record<string, string>> = {
|
||
released: 'var(--ok-accent)',
|
||
blocked: 'var(--warn-accent)',
|
||
stalled: '#8f959e',
|
||
exhausted: 'var(--danger-accent)',
|
||
terminated: '#41464e',
|
||
};
|
||
|
||
/* ---------------- 经营驾驶舱(运营首页) ---------------- */
|
||
|
||
/**
|
||
* 看企业今天跑得怎样 —— 先看**要不要管**(异常三卡),再看**跑得好不好**(度量八指标)。
|
||
* 异常在上:开屏第一眼该回答的是「有没有事要我处置」,不是「自主率几个点」。
|
||
*/
|
||
export function CockpitHome({ nav }: HomeProps) {
|
||
const metrics = useGovMetrics();
|
||
const tasks = useGovTasks();
|
||
const m = metrics.data;
|
||
|
||
const workflows = tasks.data?.workflows ?? [];
|
||
const taskRows = tasks.data?.tasks ?? [];
|
||
const abnormal = workflows.filter((w) => w.state === 'abnormal').length;
|
||
const running = taskRows.filter((t) => t.terminal === undefined).length;
|
||
|
||
const mix = m?.terminalMix ?? {};
|
||
const mixTop = Object.entries(mix)
|
||
.filter(([, v]) => v > 0)
|
||
.sort((a, b) => b[1] - a[1]);
|
||
|
||
return (
|
||
<QueryGate query={metrics}>
|
||
<>
|
||
<div className="g-kpis">
|
||
<Kpi
|
||
ico="alert-triangle"
|
||
tone="danger"
|
||
label="异常工作流"
|
||
value={abnormal}
|
||
sub="失败走向未消化,待人工处置"
|
||
/>
|
||
{/*
|
||
* 「未闭合草稿」这张卡撤了 —— 未闭合是**定义面**的常态(画图不检查,激活才查),
|
||
* 归流程包详情的异常看板管,不是运营指标。
|
||
* 天然的第三张是「死路」(条件出边一条都判不中而卡住的流,已有 route-dead-end 事件),
|
||
* 但那需要一条按事件类型聚合的查询路由 —— 没有之前不摆(同死信卡的规矩)。
|
||
*/}
|
||
<Kpi ico="activity" tone="brand" label="在途任务" value={running} sub="尚未落终态的任务" />
|
||
</div>
|
||
{abnormal > 0 ? (
|
||
<div className="g-todo">
|
||
<Todo
|
||
tone="danger"
|
||
ico="alert-triangle"
|
||
title={`${abnormal} 条工作流异常`}
|
||
desc="失败走向未消化 —— 去需处置逐条看它卡在哪个节点、要不要改派或叫停。"
|
||
acts={[{ label: '去处置', ico: 'flag', onClick: () => nav.jump('tasks', 'todo') }]}
|
||
/>
|
||
</div>
|
||
) : (
|
||
<Clear>没有异常工作流 —— 在途的都还在正常走。</Clear>
|
||
)}
|
||
|
||
<div className="g-kpis">
|
||
<Kpi
|
||
ico="bot"
|
||
tone="auto"
|
||
label="自主率"
|
||
value={pct(m?.autonomy)}
|
||
unit="%"
|
||
dial={pct(m?.autonomy)}
|
||
sub="机器产出 ÷(机器 + 人)"
|
||
/>
|
||
<Kpi
|
||
ico="rotate-ccw"
|
||
tone="warn"
|
||
label="纠正率"
|
||
value={pct(m?.correction)}
|
||
unit="%"
|
||
dial={pct(m?.correction)}
|
||
sub="被纠正批次 ÷ 提交批次"
|
||
/>
|
||
<Kpi
|
||
ico="gauge"
|
||
tone="brand"
|
||
label="有效自主率"
|
||
value={pct(m?.effectiveAutonomy)}
|
||
unit="%"
|
||
dial={pct(m?.effectiveAutonomy)}
|
||
sub="自主率 ×(1 − 纠正率)"
|
||
/>
|
||
<Kpi ico="hand" label="打扰率" value={(m?.interruption ?? 0).toFixed(1)} sub="每单任务调用人类工具次数" />
|
||
</div>
|
||
<div className="g-kpis">
|
||
<Kpi ico="unplug" tone="danger" label="断点率" value={pct(m?.breakpoint)} unit="%" sub="当下可为 ∩ 目标所需 = 空" />
|
||
<Kpi ico="git-commit" tone="ok" label="DMN 覆盖率" value={pct(m?.dmnCoverage)} unit="%" sub="确定性求值占判断次数" />
|
||
<Kpi
|
||
ico="coins"
|
||
label="单位成本"
|
||
value={Math.round(m?.unitCost.tokens ?? 0)}
|
||
unit="tok"
|
||
sub={`每放行一单:${(m?.unitCost.minutes ?? 0).toFixed(1)} 分钟 · 调人 ${(m?.unitCost.humanCalls ?? 0).toFixed(1)} 次`}
|
||
/>
|
||
{/* 终态分布:比例即信息 —— 五终态五色一条,不把五个百分比塞进一行小字 */}
|
||
<div className="g-kpi">
|
||
<span className="g-kpi-l">
|
||
<Ico name="flag" s={12} />
|
||
终态分布
|
||
</span>
|
||
{mixTop.length === 0 ? (
|
||
<>
|
||
<span className="g-kpi-v">—</span>
|
||
<span className="g-kpi-s">还没有任务落终态</span>
|
||
</>
|
||
) : (
|
||
<>
|
||
<span className="g-mixbar">
|
||
{mixTop.map(([k, v]) => (
|
||
<i key={k} style={{ width: `${pct(v)}%`, background: TERMINAL_TONE[k] ?? '#c9d2dd' }} />
|
||
))}
|
||
</span>
|
||
<span className="g-mixbar-legend">
|
||
{mixTop.map(([k, v]) => (
|
||
<span key={k}>
|
||
<i style={{ background: TERMINAL_TONE[k] ?? '#c9d2dd' }} />
|
||
{TERMINAL_STATE_LABEL[k as TerminalState] ?? k} <b>{pct(v)}%</b>
|
||
</span>
|
||
))}
|
||
</span>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<Note>
|
||
自主率与纠正率<b>成对读</b>,复合为有效自主率——单看自主率会奖励「猛冲后返工」。度量只是仪表,不载权力。
|
||
八指标与工作台的汇报<b>同源同口径</b>,统计窗口:近一{m?.period === 'day' ? '日' : m?.period === 'week' ? '周' : m?.period === 'year' ? '年' : '月'}。
|
||
</Note>
|
||
</>
|
||
</QueryGate>
|
||
);
|
||
}
|
||
|
||
/* ---------------- 知识库 ---------------- */
|
||
|
||
/** 活动记忆四层 + 候选增量的放行口 —— 记忆的终点是成为模型,那道门在这里 */
|
||
export function KnowledgeHome({ nav }: HomeProps) {
|
||
const activities = useGovActivities();
|
||
const [picked, setPicked] = useState<string | undefined>(undefined);
|
||
const memory = useGovMemory(picked);
|
||
|
||
return (
|
||
<>
|
||
<div className="g-picker">
|
||
{(activities.data?.activities ?? []).map((a) => (
|
||
<button
|
||
key={a.activityId}
|
||
className={'g-pick' + (picked === a.activityId ? ' on' : '')}
|
||
onClick={() => setPicked(a.activityId)}
|
||
>
|
||
{a.name}
|
||
</button>
|
||
))}
|
||
</div>
|
||
<NList>
|
||
{(memory.data?.entries ?? []).map((e) => (
|
||
<NRow
|
||
key={e.id}
|
||
tone={e.incrementState === undefined ? 'brand' : 'warn'}
|
||
ico="layers"
|
||
title={e.statement}
|
||
sub={`${e.layer} · ${e.createdAt.slice(0, 16).replace('T', ' ')}`}
|
||
cols={[{ k: '证据', v: e.evidenceCount }]}
|
||
right={
|
||
e.incrementState !== undefined ? (
|
||
<Chip tone="warn">候选 · {e.incrementState}</Chip>
|
||
) : (
|
||
<Chip tone="ok">已成条目</Chip>
|
||
)
|
||
}
|
||
onClick={() => nav.push({ k: 'mem', id: e.id })}
|
||
/>
|
||
))}
|
||
{picked === undefined && <Clear>选一个活动,看它这些年学到了什么。</Clear>}
|
||
{picked !== undefined && (memory.data?.entries ?? []).length === 0 && (
|
||
<Clear>这个活动还没有记忆条目 —— 任务落终态封卷、蒸馏钩子跑过之后,沉淀会出现在这里。</Clear>
|
||
)}
|
||
</NList>
|
||
</>
|
||
);
|
||
}
|
||
|
||
/* ---------------- 记忆条目详情 ---------------- */
|
||
|
||
export function MemPage({ id }: { id: string }) {
|
||
const activities = useGovActivities();
|
||
const first = (activities.data?.activities ?? [])[0]?.activityId;
|
||
const memory = useGovMemory(first);
|
||
const e = (memory.data?.entries ?? []).find((x) => x.id === id);
|
||
if (e === undefined) return <Note>记忆条目需从对应活动进入。</Note>;
|
||
return (
|
||
<PageHead
|
||
ico="layers"
|
||
kicker={`活动记忆 · ${e.layer}`}
|
||
title={e.statement}
|
||
chips={
|
||
<>
|
||
<Chip ico="link">{e.evidenceCount} 条证据</Chip>
|
||
{e.incrementState !== undefined && <Chip tone="warn">候选增量 · {e.incrementState}</Chip>}
|
||
</>
|
||
}
|
||
desc="写入唯蒸馏,条目必挂证据链;记忆不载权力——强制力只经候选增量 + 回放 + 人放行进入业务模型。"
|
||
/>
|
||
);
|
||
}
|