forked from ChaiHao/BPOS-MVP
三面拆分⑫:设计台九区一级目录 —— 一区一内容,测试台收成单例仿真
设计中心从「蓝图 + 四库」摊成九区,顺序即设计的次序: 蓝图 → 触发器 → 流程包 → 活动库 → 工具箱 → 数据域 → 知识库 → 大模型 → 测试台。 触发器与大模型从「并进流程包 / 塞在系统区」里拿出来独立成区 —— 前者与流程包平级 (不入包结构、不随包版本),后者是设计者天天要碰的东西;知识库先立个诚实的空占位。 一区一内容,于是区内页签栏整条删掉(壳层在只剩一格时收起): · 流程包:总览的三枚计数并进货架顶端 · 测试台:入站邮件演练移到触发器区就地验(它验的正是那一区定的规则) · 大模型:从系统区拆出,系统区只剩通道实例 测试台一次只跑一条流。起单前先销毁上一单,刷新页面能认回在跑的那一单, 「停止仿真」把工作流连同它派出的任务、工单、实例、留痕一并拔掉 —— 不留半单挂在别人工作台上。实测:起单 → 1 单落到 AI 工作台 → 停止 → 库里零残留。 功能工具补中文名(migration 0018,读回退到逻辑名),治理台一律主显示中文名。 顺带清掉几处说教式文案(「这些是工具,不是模型 —— 模型是它们跑在上面的发动机…」那类)。 五闸:typecheck / lint / depcruise / test 绿;api:check 快照随本提交入库。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* feature `gov-archive` 唯一出口(编码规范 §9.1)。
|
||||
* 知识库 —— 企业的资料与文档。**待建**:占位页如实说明,不假装有内容。
|
||||
*/
|
||||
export { ArchiveHome } from './internal/panel.tsx';
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 知识库 —— 待建。
|
||||
*
|
||||
* 占位页只做一件事:**如实说它还没有**。不摆假数据、不摆点不动的按钮 ——
|
||||
* 空着比装着强(与死信卡、沟通记录同一条规矩)。
|
||||
*/
|
||||
import { Card, Clear } from '@web-shared/gov/primitives.tsx';
|
||||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||
|
||||
export function ArchiveHome({ nav }: { tab: string; nav: Nav }) {
|
||||
void nav;
|
||||
return (
|
||||
<Card ico="library" title="知识库" extra="待建">
|
||||
<Clear>
|
||||
这里将放企业的资料与文档 —— 规范、图纸、样例、外部材料,供活动按需引用。
|
||||
<br />
|
||||
目前还没有建:没有存储、没有检索、没有引用机制。等要用到的第一条流出现时再动手。
|
||||
</Clear>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* feature `gov-process` —— 流程包(设计态):**一条流一个包,版本收在包内部**。
|
||||
*
|
||||
* 台面三页签:总览 · 流程包 · 触发器。
|
||||
* 一区一内容:三枚计数摆在货架顶端,底下就是包货架(触发器已独立成区)。
|
||||
* 货架一流程一卡(不按版本平铺);进详情后左 1/3 是版本列表、右 2/3 是流程图,
|
||||
* 图下是节点属性,再往下才是声明式的契约细节(调用接口 · 边映射 · 触发器)。
|
||||
*
|
||||
@@ -41,7 +41,6 @@ import { FlowFigureBlock } from '@web-shared/gov/flow/FlowFigure.tsx';
|
||||
import { packageToGraph } from '@web-shared/gov/flow/pkg-to-graph.ts';
|
||||
import { mergeGraphIntoPackage } from '@web-shared/gov/flow/graph-to-pkg.ts';
|
||||
import type { FlowEdge, FlowGraph, FlowNode } from '@web-shared/gov/flow/flow-layout.ts';
|
||||
import { TriggerRulesHome } from '@features/gov-trigger';
|
||||
|
||||
export type HomeProps = { tab: string; nav: Nav };
|
||||
|
||||
@@ -206,11 +205,17 @@ function EdgeProps({ edge, onPatch }: { edge: FlowEdge; onPatch: (f: (e: FlowEdg
|
||||
|
||||
/* ---------------- 台面:总览 · 流程包 · 触发器 ---------------- */
|
||||
|
||||
export function WorkflowsHome({ tab, nav }: HomeProps) {
|
||||
// 触发器的**定义面**:谁调起这条流,与流程包一处维护(启停在运营中心)
|
||||
if (tab === 'rules') return <TriggerRulesHome nav={nav} />;
|
||||
if (tab === 'pkgs') return <FlowShelf nav={nav} />;
|
||||
return <OverviewHome />;
|
||||
/**
|
||||
* 流程包一区一内容:总览的三枚计数并进货架顶端 —— 一个页签的导航条不必存在,
|
||||
* 「先点总览再点流程包」也不是人想做的事。触发器已独立成区。
|
||||
*/
|
||||
export function WorkflowsHome({ nav }: HomeProps) {
|
||||
return (
|
||||
<>
|
||||
<OverviewHome />
|
||||
<FlowShelf nav={nav} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewHome() {
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
* feature `gov-system` 唯一出口(编码规范 §9.1)。
|
||||
* 系统管理:LLM 实例四元绑定、通道实例
|
||||
*/
|
||||
export { SystemHome, LlmPage, ChanPage } from './internal/panel.tsx';
|
||||
export { SystemHome, ModelsHome, LlmPage, ChanPage } from './internal/panel.tsx';
|
||||
|
||||
@@ -14,6 +14,15 @@ import {
|
||||
|
||||
export type HomeProps = { tab: string; nav: Nav };
|
||||
|
||||
/**
|
||||
* 大模型区 —— 模型实例的四元绑定(设计中心一级目录)。
|
||||
* 从系统区拆出来:模型是**设计者天天要碰**的东西(活动声明用途名、换档改绑定),
|
||||
* 不该和通道实例挤在「系统管理」里。
|
||||
*/
|
||||
export function ModelsHome({ nav }: HomeProps) {
|
||||
return <SystemHome tab="llm" nav={nav} />;
|
||||
}
|
||||
|
||||
export function SystemHome({ tab, nav }: HomeProps) {
|
||||
const llm = useGovLlm();
|
||||
const channels = useGovChannels();
|
||||
|
||||
@@ -2,49 +2,17 @@
|
||||
* 联调测试台 —— 把定义**试着跑一遍**。
|
||||
*
|
||||
* 测试台是**后端检测活动与流程**的地方,不是代人干活的地方:在这儿装配并起单,
|
||||
* 真实的任务照旧落到被指派人的工作台上。所以它只管两件事:
|
||||
* **把流跑起来** + **看穿它怎么跑的**。
|
||||
* 真实的任务照旧落到被指派人的工作台上。
|
||||
*
|
||||
* 一条流有两种起法,对应两个模式 —— 两条路都得能试:
|
||||
* · 仿真试跑:直接调流程包的**调用接口**起单(触发器起单走的也是这个口),带节点指派;
|
||||
* · 入站邮件演练:从**外界**推一封信进来,看归属解析怎么判、哪个触发器接、有没有起单。
|
||||
* **一次只跑一条**:测试台上的工作流生命周期完全跟着仿真状态 —— 起一单就是它,
|
||||
* 停止仿真,这条流连同它派出去的活动任务一起销毁,不留半单在别人工作台上挂着。
|
||||
*
|
||||
* 模式不做成区页签(区页签由壳层的 `TABS` 定,测试台只占一格),做成本屏的一个控件 ——
|
||||
* 它选的是「用哪种刺激」,不是「看哪个维度」。
|
||||
* (外界来信那条路在**触发器**区就地演练 —— 它验的是规则,归那边。)
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Ico } from '@web-shared/Ico.tsx';
|
||||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||
import { InboundDrill } from './inbound.tsx';
|
||||
import { SimulateRun } from './simulate.tsx';
|
||||
|
||||
type Mode = 'run' | 'mail';
|
||||
|
||||
const MODES: ReadonlyArray<{ k: Mode; label: string; ico: string }> = [
|
||||
{ k: 'run', label: '仿真试跑', ico: 'flask-conical' },
|
||||
{ k: 'mail', label: '入站邮件演练', ico: 'mail' },
|
||||
];
|
||||
|
||||
export function TestbedHome({ nav }: { tab: string; nav: Nav }): ReactNode {
|
||||
const [mode, setMode] = useState<Mode>('run');
|
||||
return (
|
||||
<>
|
||||
<div className="g-tbmode">
|
||||
<em>试法</em>
|
||||
{MODES.map((m) => (
|
||||
<button
|
||||
key={m.k}
|
||||
className={'g-tbmode-b' + (mode === m.k ? ' on' : '')}
|
||||
aria-pressed={mode === m.k}
|
||||
onClick={() => setMode(m.k)}
|
||||
>
|
||||
<Ico name={m.ico} s={12} />
|
||||
{m.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{mode === 'run' ? <SimulateRun nav={nav} /> : <InboundDrill nav={nav} />}
|
||||
</>
|
||||
);
|
||||
return <SimulateRun nav={nav} />;
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
*/
|
||||
import { useMemo, useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { ProcessNode, ProcessPackage } from '@contracts';
|
||||
import { TERMINAL_STATE_LABEL, WORKFLOW_STATE_LABEL, testRunRoute } from '@contracts';
|
||||
import { TERMINAL_STATE_LABEL, WORKFLOW_STATE_LABEL, testCurrentRoute, testRunRoute, testStopRoute } from '@contracts';
|
||||
import { Ico } from '@web-shared/Ico.tsx';
|
||||
import { call } from '@web-shared/typed-fetch.ts';
|
||||
import { useGovPackages, useGovStaff } from '@web-shared/gov/gov-api.ts';
|
||||
@@ -76,7 +76,28 @@ export function SimulateRun({ nav }: { nav: Nav }): ReactNode {
|
||||
const [subject, setSubject] = useState('测试台试跑');
|
||||
const [values, setValues] = useState<CallValues>({});
|
||||
const [assignments, setAssignments] = useState<Readonly<Record<string, string>>>({});
|
||||
const queryClient = useQueryClient();
|
||||
const [workflowId, setWorkflowId] = useState<string | undefined>(undefined);
|
||||
/*
|
||||
* **一次只跑一条**:刷新页面也认得回来 —— 不然仿真在后台跑着,界面上却像没起过,
|
||||
* 人会再起一单(而上一单的任务还在别人工作台上挂着)。
|
||||
*/
|
||||
const current = useQuery({
|
||||
queryKey: ['gov', 'testbed', 'current'],
|
||||
queryFn: () => call<{ workflowId?: string; subject?: string }>(testCurrentRoute),
|
||||
});
|
||||
const running = workflowId ?? current.data?.workflowId;
|
||||
|
||||
/** 停止仿真 = **销毁**:工作流、活动任务、工单、实例、trace 一并拔掉 */
|
||||
const stop = useMutation({
|
||||
mutationFn: (id: string) => call<{ destroyed: boolean; tasks: number }>(testStopRoute, { body: { workflowId: id } }),
|
||||
onSuccess: () => {
|
||||
setWorkflowId(undefined);
|
||||
run.reset();
|
||||
void queryClient.invalidateQueries({ queryKey: ['gov', 'testbed'] });
|
||||
void queryClient.invalidateQueries({ queryKey: ['desk'] });
|
||||
},
|
||||
});
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
|
||||
// 没选就落到第一个有生效版的包 —— 常见情形只有一个包,不该逼人先点一下
|
||||
@@ -107,16 +128,14 @@ export function SimulateRun({ nav }: { nav: Nav }): ReactNode {
|
||||
});
|
||||
};
|
||||
|
||||
if (workflowId !== undefined) {
|
||||
if (running !== undefined) {
|
||||
return (
|
||||
<Observe
|
||||
id={workflowId}
|
||||
id={running}
|
||||
staffName={staffName}
|
||||
nav={nav}
|
||||
onBack={() => {
|
||||
setWorkflowId(undefined);
|
||||
run.reset();
|
||||
}}
|
||||
stopping={stop.isPending}
|
||||
onBack={() => stop.mutate(running)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -312,11 +331,13 @@ function Observe({
|
||||
staffName,
|
||||
nav,
|
||||
onBack,
|
||||
stopping,
|
||||
}: {
|
||||
id: string;
|
||||
staffName: (sid: string) => string;
|
||||
nav: Nav;
|
||||
onBack: () => void;
|
||||
stopping: boolean;
|
||||
}): ReactNode {
|
||||
const wf = useRunWorkflow(id);
|
||||
const state = wf.data?.workflow.state;
|
||||
@@ -368,7 +389,7 @@ function Observe({
|
||||
}
|
||||
acts={[
|
||||
{ label: '刷新', ico: 'refresh-cw', onClick: refresh },
|
||||
{ label: '再装一单', ico: 'rotate-ccw', primary: true, onClick: onBack },
|
||||
{ label: stopping ? '销毁中…' : '停止仿真', ico: 'square', primary: true, onClick: onBack },
|
||||
]}
|
||||
/>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* feature `gov-tool` —— 工具箱:**系统能对世界做什么**,一屏四段。
|
||||
*
|
||||
* 功能工具 · 已注册的调用契约(读类自由调,写类登记意向)
|
||||
* LLM 工具 · 大模型驱动的系统内通用工具(**不是模型**;模型在系统区绑)
|
||||
* LLM 工具 · 大模型驱动的系统内通用工具(模型实例在**大模型**区绑)
|
||||
* 人类工具 · 工单两模式 + 对话卡三族(调人也是调工具,只是被调的是人)
|
||||
* 接入 · 导入自定义工具(与第一方同门两审)/ 连接 MCP
|
||||
*
|
||||
@@ -75,11 +75,10 @@ const HUMAN_TOOLS = [
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* LLM 工具目录 —— 系统内**由大模型驱动**的通用工具(不是模型本身)。
|
||||
* LLM 工具目录 —— 系统内**由大模型驱动**的通用工具。
|
||||
*
|
||||
* 模型是发动机,这些是装在发动机上的件:每一件都有明确的入、出与算账口径。
|
||||
* 它们不在工具目录里注册 —— 与人类工具一样,是基座给的能力,不是谁装上来的。
|
||||
* 跑在哪个模型上由系统区的四元绑定决定(活动只见逻辑名,不触厂商与密钥)。
|
||||
* 跑在哪个模型上由**大模型**区的四元绑定决定(活动只见逻辑名,不触厂商与密钥)。
|
||||
*/
|
||||
const LLM_TOOLS = [
|
||||
{
|
||||
@@ -150,8 +149,9 @@ export function ToolsHome({ nav }: HomeProps) {
|
||||
<Ico name="wrench" s={16} />
|
||||
</span>
|
||||
<span className="g-tile-t">
|
||||
<b>{t.name}</b>
|
||||
<em>v{t.version}</em>
|
||||
{/* 主名是人读的中文名,逻辑名退居副标 —— 一屏几十件工具时尤其要紧 */}
|
||||
<b>{t.displayName}</b>
|
||||
<em>{t.name} · v{t.version}</em>
|
||||
</span>
|
||||
<St tone={write ? 'warn' : 'ok'} ico={write ? 'pen-line' : 'search'}>
|
||||
{write ? '写' : '读'}
|
||||
@@ -204,11 +204,9 @@ export function ToolsHome({ nav }: HomeProps) {
|
||||
))}
|
||||
</div>
|
||||
<Note>
|
||||
这些是<b>工具</b>,不是模型 —— 模型是它们跑在上面的发动机。
|
||||
当前绑了 {(llm.data?.instances ?? []).length} 个模型实例(
|
||||
{(llm.data?.instances ?? []).map((m) => m.name).join(' · ') || '尚未绑定'});
|
||||
换厂商、换型号只改<button className="g-link" onClick={() => nav.jump('system', 'llm')}>系统区的四元绑定</button>,
|
||||
这些工具与活动一行都不动。
|
||||
已绑 {(llm.data?.instances ?? []).length} 个模型实例:
|
||||
{(llm.data?.instances ?? []).map((m) => m.name).join(' · ') || '尚未绑定'}。
|
||||
换厂商或型号去<button className="g-link" onClick={() => nav.jump('models', 'llm')}>大模型</button>改绑定。
|
||||
</Note>
|
||||
</Sec>
|
||||
|
||||
@@ -450,7 +448,7 @@ export function ToolPage({ id }: { id: string }) {
|
||||
ico="wrench"
|
||||
tone={write ? 'danger' : 'brand'}
|
||||
kicker={`工具 · ${t.name}`}
|
||||
title={t.name}
|
||||
title={t.displayName}
|
||||
chips={
|
||||
<>
|
||||
<Chip tone={write ? 'danger' : 'brand'}>{write ? '写类' : '读类'}</Chip>
|
||||
|
||||
+19
-28
@@ -1,5 +1,7 @@
|
||||
/**
|
||||
* 联调测试台 · 入站邮件演练 —— 从**外界**推一封信进来,看谁接。
|
||||
* 触发器 · 入站邮件演练 —— 从**外界**推一封信进来,看哪条规则接住它。
|
||||
*
|
||||
* 摆在触发器区里,是因为它验的正是这一区定的东西:规则写下来了,来一封真信试试对不对。
|
||||
*
|
||||
* 造一封入站邮件(可带附件)投进通道,走的是**与真通道同一条**入站归一链路
|
||||
* (`POST /api/ingest/channel/:id/mail` → 归属解析 → 统一事件流 → 三级消费)。
|
||||
@@ -17,7 +19,7 @@ import type { IngestMailOutput, InboundMail } from '@contracts';
|
||||
import { ingestMailRoute } from '@contracts';
|
||||
import { call } from '@web-shared/typed-fetch.ts';
|
||||
import { useGovChannels, useGovStaff } from '@web-shared/gov/gov-api.ts';
|
||||
import { Acts, Card, Field, Note, PageHead, Rows, Sec, St } from '@web-shared/gov/primitives.tsx';
|
||||
import { Acts, Band, Card, Field, Note, Rows, Sec, St } from '@web-shared/gov/primitives.tsx';
|
||||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||
|
||||
/*
|
||||
@@ -138,32 +140,9 @@ export function InboundDrill({ nav }: { nav: Nav }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead
|
||||
ico="flask-conical"
|
||||
tone="brand"
|
||||
kicker="联调测试台 · 事件触发"
|
||||
title="入站邮件演练"
|
||||
desc="试跑测的是「调用接口起单」那条路,这一屏测的是另一条:外界来了封信,触发器接不接、起不起单。"
|
||||
chips={
|
||||
<>
|
||||
<St tone={allGreen ? 'ok' : 'warn'} ico={allGreen ? 'circle-check' : 'alert-circle'}>
|
||||
{allGreen ? '起单条件齐' : `差 ${preflight.filter((p) => !p.ok).length} 条`}
|
||||
</St>
|
||||
<St tone="mute" ico="paperclip">
|
||||
附件 {attachments.length}
|
||||
</St>
|
||||
</>
|
||||
}
|
||||
acts={[
|
||||
{
|
||||
label: send.isPending ? '投递中…' : '投递这封信',
|
||||
ico: 'send',
|
||||
primary: true,
|
||||
disabled: send.isPending || effectiveChannel === '',
|
||||
onClick: fire,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Band ico="flask-conical" extra="定完就地验:规则写下来了,来一封真信试试对不对">
|
||||
投一封信试试
|
||||
</Band>
|
||||
|
||||
<Note>
|
||||
走的是与真通道同一条入站链路:通道归一 → 归属解析 → 统一事件流 → 三级消费(任务监听 /
|
||||
@@ -256,6 +235,18 @@ export function InboundDrill({ nav }: { nav: Nav }) {
|
||||
}))}
|
||||
/>
|
||||
</Card>
|
||||
<Acts
|
||||
align="end"
|
||||
items={[
|
||||
{
|
||||
label: send.isPending ? '投递中…' : allGreen ? '投递这封信' : `还差 ${preflight.filter((p) => !p.ok).length} 条,仍要投`,
|
||||
ico: 'send',
|
||||
primary: true,
|
||||
disabled: send.isPending || effectiveChannel === '',
|
||||
onClick: fire,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Sec>
|
||||
|
||||
{error !== undefined && (
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
St,
|
||||
} from '@web-shared/gov/primitives.tsx';
|
||||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||
import { InboundDrill } from './inbound.tsx';
|
||||
import { useGovObjects, useGovPackages, useGovTriggers } from '@web-shared/gov/gov-api.ts';
|
||||
import { domainShort } from '@web-shared/gov/domains.ts';
|
||||
|
||||
@@ -133,6 +134,8 @@ export function TriggerRulesHome({ nav }: { nav: Nav }) {
|
||||
)}
|
||||
</NList>
|
||||
)}
|
||||
{/* 定完就地验:来一封真信,看这些规则里哪条接住 */}
|
||||
<InboundDrill nav={nav} />
|
||||
</>
|
||||
</QueryGate>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user