/** * 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> = { 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 ( <>
{/* * 「未闭合草稿」这张卡撤了 —— 严格检查 + 保存即生效之后,不闭合的包**存不进来**, * 这个数结构上恒为 0。摆一个永远是 0 的卡位就是在占地方装样子。 * 天然的第三张是「死路」(网关一条边都判不中而卡住的流,已有 route-dead-end 事件), * 但那需要一条按事件类型聚合的查询路由 —— 没有之前不摆(同死信卡的规矩)。 */}
{abnormal > 0 ? (
nav.jump('tasks', 'todo') }]} />
) : ( 没有异常工作流 —— 在途的都还在正常走。 )}
{/* 终态分布:比例即信息 —— 五终态五色一条,不把五个百分比塞进一行小字 */}
终态分布 {mixTop.length === 0 ? ( <> — 还没有任务落终态 ) : ( <> {mixTop.map(([k, v]) => ( ))} {mixTop.map(([k, v]) => ( {TERMINAL_STATE_LABEL[k as TerminalState] ?? k} {pct(v)}% ))} )}
自主率与纠正率成对读,复合为有效自主率——单看自主率会奖励「猛冲后返工」。度量只是仪表,不载权力。 八指标与工作台的汇报同源同口径,统计窗口:近一{m?.period === 'day' ? '日' : m?.period === 'week' ? '周' : m?.period === 'year' ? '年' : '月'}。
); } /* ---------------- 知识库 ---------------- */ /** 活动记忆四层 + 候选增量的放行口 —— 记忆的终点是成为模型,那道门在这里 */ export function KnowledgeHome({ nav }: HomeProps) { const activities = useGovActivities(); const [picked, setPicked] = useState(undefined); const memory = useGovMemory(picked); return ( <> 活动记忆四层:事实层 / 模式层 / 裁决层 / 例外层。写入唯蒸馏,条目必挂证据链; 记忆不载权力——强制力只经候选增量 + 回放 + 人放行进入业务模型。
{(activities.data?.activities ?? []).map((a) => ( ))}
{(memory.data?.entries ?? []).map((e) => ( 候选 · {e.incrementState} ) : ( 已成条目 ) } onClick={() => nav.push({ k: 'mem', id: e.id })} /> ))} {picked === undefined && 选一个活动,看它这些年学到了什么。} {picked !== undefined && (memory.data?.entries ?? []).length === 0 && ( 这个活动还没有记忆条目 —— 任务落终态封卷、蒸馏钩子跑过之后,沉淀会出现在这里。 )} ); } /* ---------------- 记忆条目详情 ---------------- */ 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 记忆条目需从对应活动进入。; return ( {e.evidenceCount} 条证据 {e.incrementState !== undefined && 候选增量 · {e.incrementState}} } desc="写入唯蒸馏,条目必挂证据链;记忆不载权力——强制力只经候选增量 + 回放 + 人放行进入业务模型。" /> ); }