测试台改仪表台:启动输入换成一封信;卡片化触发器与大模型;清掉 ⓘ 说教条

**测试台**
  · 启动输入不再是「调用参数」表 —— 这条流真实的起法就是客户来信,
    界面上就该是一封信(发件人 / 收件人 / 主题 / 正文 / 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
This commit is contained in:
2026-07-29 10:42:01 +08:00
co-authored by Claude Opus 5
parent 331fd25922
commit 72f90f293b
19 changed files with 526 additions and 838 deletions
@@ -5,6 +5,7 @@
* DOM 结构、类名(`g-*` + 色调 `t-*`)与文案原样保留,mock 常量换成服务端投影(README 平移规则 1/2/4)。
* 区首页只负责导航,治理动作一律落在下钻详情页的**就地确认带**(影响预览三必答)。
*/
import { Ico } from '@web-shared/Ico.tsx';
import { Card, Chip, NList, NRow, Note, PageHead, QueryGate, Rows, St } from '@web-shared/gov/primitives.tsx';
import type { Nav } from '@web-shared/gov/gov-route.ts';
import {
@@ -30,10 +31,6 @@ export function SystemHome({ tab, nav }: HomeProps) {
if (tab === 'chan') {
return (
<>
<Note>
通道实例声明<b>归属键规则</b>与默认认领岗位。归属解析三级逐级短路:会话线程 → 监听键 → 地址窗口;
歧义或失败一律判无主——<b>宁交人,不错投</b>。
</Note>
<NList>
{(channels.data?.channels ?? []).map((ch) => (
<NRow
@@ -64,31 +61,37 @@ export function SystemHome({ tab, nav }: HomeProps) {
return (
<QueryGate query={llm}>
<>
<Note>
LLM 实例是<b>名称间接层</b>:活动只见自定义模型名,Provider / URL / API-Key / Model 四元在系统侧绑定。
密钥与 endpoint 值只存治理台——不入包、不入活动、不入留痕明文。
</Note>
<NList>
{/* 卡而不是行:一个实例要一眼看全「谁家的 · 什么型号 · 密钥绑没绑」 */}
<div className="g-shelf">
{(llm.data?.instances ?? []).map((i) => (
<NRow
<button
key={i.id}
tone={i.enabled ? 'brand' : 'mute'}
ico="cpu"
title={i.name}
sub={`${i.provider} · ${i.model}`}
cols={[
{ k: 'Base URL', v: <span className="g-mono">{i.baseUrl}</span> },
{ k: '系统用途', v: i.systemUse ? '是' : '—' },
]}
right={
className={'g-tile t-' + (i.enabled ? 'brand' : 'mute')}
onClick={() => nav.push({ k: 'llm', id: i.id })}
>
<span className="g-tile-h">
<span className="g-tile-i">
<Ico name="cpu" s={16} />
</span>
<span className="g-tile-t">
<b>{i.name}</b>
<em>{i.model}</em>
</span>
{i.systemUse && <Chip tone="brand">系统用途</Chip>}
</span>
<span className="g-tile-d">{i.baseUrl}</span>
<span className="g-tile-m">
<Chip ico="server">{i.provider}</Chip>
</span>
<span className="g-tile-f">
<St tone={i.apiKeyBound ? 'ok' : 'warn'} ico={i.apiKeyBound ? 'shield-check' : 'alert-triangle'}>
{i.apiKeyBound ? '密钥已绑定' : '密钥未绑定'}
</St>
}
onClick={() => nav.push({ k: 'llm', id: i.id })}
/>
<span className="r">{i.enabled ? '启用' : '停用'}</span>
</span>
</button>
))}
</NList>
</div>
</>
</QueryGate>
);