Files
ai-device/docs/architecture_overview.html
2026-06-11 16:28:00 +08:00

643 lines
33 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>工业 AI 交互画布:流程总览</title>
<style>
:root {
--bg: #f0f2f5;
--card: #fff;
--ink: #111827;
--muted: #667085;
--line: #d8dde6;
--soft: #f1f4f8;
--blue: #2563eb;
--green: #059669;
--amber: #d97706;
--violet: #7c3aed;
--dark: #111827;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--bg); color: var(--ink); font-family: "PingFang SC", "Microsoft YaHei", Arial, sans-serif; font-size: 14px; }
button { font: inherit; cursor: pointer; border: none; }
.page { max-width: 1500px; margin: 0 auto; padding: 24px 24px 60px; }
/* Hero */
.hero { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 18px; flex-wrap: wrap; }
h1 { font-size: 22px; font-weight: 800; }
.subtitle { color: var(--muted); font-size: 13px; margin-top: 5px; line-height: 1.6; }
/* Layout */
.main-grid { display: grid; grid-template-columns: 252px minmax(0, 1fr); gap: 16px; align-items: start; }
/* Left panel */
.l-panel { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 18px; }
.panel-title { font-size: 14px; font-weight: 700; margin-bottom: 12px; color: var(--ink); }
.stack { display: grid; gap: 8px; }
.layer { display: grid; grid-template-columns: 30px 1fr; gap: 10px; align-items: center; border: 1px solid var(--line); border-left: 4px solid var(--dark); border-radius: 7px; padding: 9px 12px; }
.layer[data-c="blue"] { border-left-color: #2563eb; }
.layer[data-c="cyan"] { border-left-color: #0891b2; }
.layer[data-c="violet"] { border-left-color: #7c3aed; }
.layer[data-c="green"] { border-left-color: #059669; }
.layer[data-c="amber"] { border-left-color: #d97706; }
.layer[data-c="dark"] { border-left-color: #111827; }
.ln { display: flex; height: 24px; width: 24px; align-items: center; justify-content: center; border-radius: 50%; background: var(--soft); font-weight: 800; font-size: 12px; color: #334155; }
.lname { font-size: 12px; font-weight: 700; }
.ldesc { font-size: 11px; color: var(--muted); margin-top: 2px; line-height: 1.3; }
/* Right: flow panel */
.r-panel { background: var(--card); border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
/* Controls bar */
.controls { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 14px 20px; border-bottom: 1px solid var(--line); background: #fafbfc; flex-wrap: wrap; }
.tg { display: inline-flex; gap: 3px; padding: 3px; border: 1px solid var(--line); border-radius: 9px; background: var(--card); }
.tbtn { background: transparent; color: #475467; padding: 8px 14px; border-radius: 6px; font-size: 13px; font-weight: 600; transition: all .15s; white-space: nowrap; }
.tbtn.active-flow { background: var(--dark); color: #fff; }
.tbtn.active-ux { background: #4f46e5; color: #fff; }
.tbtn.active-tech { background: #0d9488; color: #fff; }
/* View indicator bar */
.view-bar { height: 4px; width: 100%; }
.view-bar.ux { background: linear-gradient(90deg, #818cf8, #a78bfa); }
.view-bar.tech { background: linear-gradient(90deg, #0d9488, #059669); }
/* Flow body */
.flow-body { padding: 22px 26px 30px; overflow-x: auto; }
.flow-meta { margin-bottom: 18px; }
.flow-meta h2 { font-size: 17px; font-weight: 800; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.flow-meta p { font-size: 13px; color: var(--muted); margin-top: 6px; line-height: 1.6; }
.badge { display: inline-flex; align-items: center; font-size: 11px; font-weight: 600; padding: 2px 10px; border-radius: 20px; }
.badge-a { background: #dbeafe; color: #1d4ed8; }
.badge-b { background: #d1fae5; color: #065f46; }
.badge-ux { background: #ede9fe; color: #4c1d95; }
.badge-tech { background: #ccfbf1; color: #134e4a; }
.badge-plan { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
.plan-banner { margin: 0 0 16px; padding: 10px 16px; background: #fffbeb; border: 1px solid #fcd34d; border-radius: 8px; font-size: 12px; color: #78350f; line-height: 1.6; }
/* Legend */
.legend { display: flex; gap: 10px; flex-wrap: wrap; padding: 9px 14px; background: var(--soft); border-radius: 8px; margin-bottom: 18px; }
.lg { display: flex; align-items: center; gap: 5px; font-size: 11px; color: #475467; }
.lg-dot { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }
/* ═══ FLOWCHART BASE ═══ */
.fc { display: flex; flex-direction: column; align-items: center; min-width: 580px; }
/* Nodes */
.nd { border-radius: 8px; padding: 9px 16px; text-align: center; border: 1.5px solid var(--line); background: #fff; }
.nd .nt { font-size: 13px; font-weight: 700; line-height: 1.4; }
.nd .ns { font-size: 11px; color: var(--muted); margin-top: 3px; line-height: 1.35; }
/* Node type variants */
.nd-start { background: var(--dark); border: none; border-radius: 22px; min-width: 180px; }
.nd-start .nt { color: #fff; font-size: 14px; }
.nd-start .ns { color: rgba(255,255,255,0.6); }
/* UX node types */
.nd-show { background: #f5f3ff; border-color: #a78bfa; } /* 画面显示 */
.nd-show .nt { color: #4c1d95; }
.nd-do { background: #ecfdf5; border-color: #34d399; } /* 用户操作 */
.nd-do .nt { color: #065f46; }
.nd-end { background: #f0fdf4; border-color: #86efac; } /* 结果/结束 */
.nd-end .nt { color: #14532d; }
.nd-trig { background: #fff7ed; border-color: #fdba74; } /* 触发条件/场景 */
.nd-trig .nt { color: #9a3412; }
.nd-tab { background: #f0f9ff; border-color: #7dd3fc; border-style: dashed; }
.nd-tab .nt { color: #0c4a6e; }
/* Tech node types */
.nd-route { background: #fffbeb; border-color: #fbbf24; }
.nd-route .nt { color: #78350f; }
.nd-bert { background: #f5f3ff; border-color: #c4b5fd; }
.nd-bert .nt { color: #5b21b6; }
.nd-llm { background: #fff7ed; border-color: #fdba74; }
.nd-llm .nt { color: #9a3412; }
.nd-tool { background: #dbeafe; border-color: #60a5fa; }
.nd-tool .nt { color: #1e3a8a; }
.nd-kb { background: #ecfdf5; border-color: #6ee7b7; }
.nd-kb .nt { color: #064e3b; }
.nd-art { background: #faf5ff; border-color: #c4b5fd; border-style: dashed; }
.nd-art .nt { color: #5b21b6; }
.nd-snap { background: #f0fdf4; border-color: #86efac; border-style: dashed; }
.nd-snap .nt { color: #14532d; }
.nd-minor { background: var(--soft); border-color: var(--line); }
.nd-minor .nt { color: #475467; }
/* Tags inside nodes */
.tag { display: inline-block; font-size: 9px; font-weight: 700; padding: 2px 6px; border-radius: 3px; margin: 3px 2px 0; }
.t-bert { background: #ede9fe; color: #5b21b6; }
.t-llm { background: #fff7ed; color: #9a3412; }
.t-rule { background: #dcfce7; color: #166534; }
.t-tool { background: #dbeafe; color: #1e40af; }
.t-art { background: #faf5ff; color: #5b21b6; }
/* Connectors */
.dn { width: 2px; background: #c9d0db; height: 20px; flex-shrink: 0; }
.dn.tall { height: 30px; }
.arr { width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 7px solid #b0b9c8; flex-shrink: 0; }
/* Branch spread link */
.slink { position: relative; width: 100%; height: 24px; flex-shrink: 0; }
/* Branch columns */
.brow { display: flex; width: 100%; align-items: flex-start; }
.bcol { display: flex; flex-direction: column; align-items: center; flex: 1; padding: 0 7px; min-width: 0; }
/* Branch labels */
.blbl { padding: 4px 11px; border-radius: 20px; font-size: 11px; font-weight: 700; white-space: nowrap; }
.bl-b { background: #dbeafe; color: #1d4ed8; border: 1px solid #93c5fd; }
.bl-g { background: #d1fae5; color: #065f46; border: 1px solid #6ee7b7; }
.bl-a { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
.bl-v { background: #ede9fe; color: #4c1d95; border: 1px solid #a78bfa; }
.bl-gr { background: var(--soft); color: #475467; border: 1px solid var(--line); }
.bl-hit { background: #d1fae5; color: #065f46; border: 1px solid #6ee7b7; }
.bl-miss { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
/* Notes */
.fnote { font-size: 11px; color: #64748b; margin-top: 4px; text-align: center; line-height: 1.4; }
.fopt { font-size: 11px; color: #6d28d9; margin-top: 4px; text-align: center; font-style: italic; }
.fwarn { font-size: 11px; color: #b91c1c; margin-top: 4px; text-align: center; }
/* Divider */
.fc-sep { width: 100%; border: none; border-top: 1px dashed var(--line); margin: 4px 0; }
@media (max-width: 1080px) { .main-grid { grid-template-columns: 1fr; } }
</style>
</head>
<body>
<main class="page">
<header class="hero">
<div>
<h1>工业 AI 交互画布 · 操作流程与技术链路</h1>
<p class="subtitle">选择视角,分别查看"用户界面交互路径"或"背后技术判断逻辑"。语音输入经过四阶段前置拦截后再进入 BERT NLU。</p>
</div>
</header>
<section class="main-grid">
<!-- Left: Architecture Layers -->
<aside class="l-panel">
<div class="panel-title">主链路层级</div>
<div class="stack">
<div class="layer" data-c="blue">
<div class="ln">1</div>
<div><div class="lname">输入层</div><div class="ldesc">文字 / 语音(ASR) / 点击</div></div>
</div>
<div class="layer" data-c="cyan">
<div class="ln">2</div>
<div><div class="lname">前置拦截层</div><div class="ldesc">停止词 → UI语音点击 → Slot填写 → BERT</div></div>
</div>
<div class="layer" data-c="violet">
<div class="ln">3</div>
<div><div class="lname">路由层</div><div class="ldesc">decision: execute / clarify / route_to_cloud</div></div>
</div>
<div class="layer" data-c="green">
<div class="ln">4</div>
<div><div class="lname">编排层</div><div class="ldesc">工具选择 / 知识检索 / Artifact 生成</div></div>
</div>
<div class="layer" data-c="amber">
<div class="ln">5</div>
<div><div class="lname">执行层</div><div class="ldesc">PLC / HMI / 知识库调用</div></div>
</div>
<div class="layer" data-c="dark">
<div class="ln">6</div>
<div><div class="lname">画布层</div><div class="ldesc">渲染 Artifact + 状态快照</div></div>
</div>
</div>
</aside>
<!-- Right: Flow Panel -->
<section class="r-panel">
<div class="controls">
<div class="tg" id="flowTabs">
<button class="tbtn active-flow" data-flow="A">大流程 A · 工控对话</button>
<button class="tbtn" data-flow="B">大流程 B · 调机流程 <span style="font-size:10px;opacity:.7;">🚧 下一版本</span></button>
</div>
<div class="tg" id="viewTabs">
<button class="tbtn active-ux" data-view="ux">👁 交互流程</button>
<button class="tbtn" data-view="tech">⚙️ 技术流程</button>
</div>
</div>
<div class="view-bar ux" id="viewBar"></div>
<div class="flow-body" id="flowBody"></div>
</section>
</section>
</main>
<script>
// ─── State ──────────────────────────────────────────────────────────────
const S = { flow: 'A', view: 'ux' };
// ─── Helpers ──────────────────────────────────────────────────────────────
const dn = (h = 20) => `<div class="dn" style="height:${h}px"></div>`;
const arr = () => `<div class="arr"></div>`;
const da = (h = 20) => dn(h) + arr();
function nd(cls, title, sub = '', tags = []) {
const ts = tags.map(t => `<span class="tag t-${t}">${t.toUpperCase()}</span>`).join('');
return `<div class="nd ${cls}"><div class="nt">${title}</div>${sub ? `<div class="ns">${sub}</div>` : ''}${ts ? `<div>${ts}</div>` : ''}</div>`;
}
function blbl(text, cls) {
return `<div class="blbl ${cls}">${text}</div>`;
}
// Horizontal branch spread link: n = 2 or 3
function slink(n) {
if (n === 3) {
return `<div class="slink">
<div style="position:absolute;top:0;left:16.67%;right:16.67%;height:2px;background:#c9d0db;"></div>
<div style="position:absolute;top:0;left:calc(16.67% - 1px);width:2px;height:24px;background:#c9d0db;"></div>
<div style="position:absolute;top:0;left:calc(50% - 1px);width:2px;height:24px;background:#c9d0db;"></div>
<div style="position:absolute;top:0;right:calc(16.67% - 1px);width:2px;height:24px;background:#c9d0db;"></div>
</div>`;
}
return `<div class="slink">
<div style="position:absolute;top:0;left:25%;right:25%;height:2px;background:#c9d0db;"></div>
<div style="position:absolute;top:0;left:calc(25% - 1px);width:2px;height:24px;background:#c9d0db;"></div>
<div style="position:absolute;top:0;right:calc(25% - 1px);width:2px;height:24px;background:#c9d0db;"></div>
</div>`;
}
// ─── Legends ─────────────────────────────────────────────────────────────
const legendUX = `<div class="legend">
<div class="lg"><div class="lg-dot" style="background:#f5f3ff;border:1px solid #a78bfa;"></div> 界面展示内容</div>
<div class="lg"><div class="lg-dot" style="background:#ecfdf5;border:1px solid #34d399;"></div> 用户可做的操作</div>
<div class="lg"><div class="lg-dot" style="background:#f0fdf4;border:1px solid #86efac;"></div> 最终结果/结束态</div>
<div class="lg"><div class="lg-dot" style="background:#f0f9ff;border:1px solid #7dd3fc;border-style:dashed;"></div> Tab 分页展示(不覆盖主卡)</div>
</div>`;
const legendTech = `<div class="legend">
<div class="lg"><div class="lg-dot" style="background:#fffbeb;border:1px solid #fbbf24;"></div> 路由判断节点</div>
<div class="lg"><div class="lg-dot" style="background:#f5f3ff;border:1px solid #c4b5fd;"></div> BERT / NLU</div>
<div class="lg"><div class="lg-dot" style="background:#fff7ed;border:1px solid #fdba74;"></div> LLM 推断</div>
<div class="lg"><div class="lg-dot" style="background:#dbeafe;border:1px solid #60a5fa;"></div> 工具调用</div>
<div class="lg"><div class="lg-dot" style="background:#ecfdf5;border:1px solid #6ee7b7;"></div> 知识库检索</div>
<div class="lg"><div class="lg-dot" style="background:#faf5ff;border:1px solid #c4b5fd;border-style:dashed;"></div> Artifact 生成</div>
</div>`;
// ═══════════════════════════════════════════════════════════════════════════
// FLOW A · UX VIEW
// ═══════════════════════════════════════════════════════════════════════════
function renderA_UX() {
return `
<div class="flow-meta">
<h2>大流程 A · 普通对话 <span class="badge badge-a">无调机流程</span> <span class="badge badge-ux">👁 交互流程</span></h2>
<p>操作员从零开始发起一次输入。界面根据输入内容呈现三种不同的画布组件,操作员按提示进行确认或查阅。</p>
</div>
${legendUX}
<div class="fc">
${nd('nd-start', '👤 操作员输入文字或语音', '当前画布无激活流程')}
${da()}
${nd('nd-trig', '系统识别输入类型,分三种场景响应', '用户感知到的是画布出现了不同内容')}
${slink(3)}
<div class="brow">
<!-- Branch 1 -->
<div class="bcol">
${blbl('场景 1 · 操控设备', 'bl-b')}
${da()}
${nd('nd-show', '画布出现操控确认卡', '参数变更卡 或 设备动画卡')}
${da()}
${nd('nd-do', '操作员选择:确认或取消', '点击按钮 或 输入"确认/取消"')}
${da()}
${nd('nd-end', '画布显示执行结果', '成功 / 失败 / 重试')}
</div>
<!-- Branch 2 -->
<div class="bcol">
${blbl('场景 2 · 询问设备问题', 'bl-g')}
${da()}
${nd('nd-show', '画布出现知识教学卡', 'SOP / 报警处理 / 说明书内容')}
${da()}
${nd('nd-do', '操作员查阅内容', '可展开详情、查看步骤')}
${da()}
${nd('nd-end', '内容展示完毕')}
</div>
<!-- Branch 3 -->
<div class="bcol">
${blbl('场景 3 · 通用 / 无关', 'bl-gr')}
${da()}
${nd('nd-show', '对话框返回文字回答', '打招呼、天气、闲聊等内容')}
${da()}
${nd('nd-end', '回答完毕', '不影响工业主流程')}
<div class="fwarn">⚠ 非核心场景</div>
</div>
</div>
</div>
`;
}
// ═══════════════════════════════════════════════════════════════════════════
// FLOW A · TECH VIEW
// ═══════════════════════════════════════════════════════════════════════════
function renderA_Tech() {
return `
<div class="flow-meta">
<h2>大流程 A · 普通对话 <span class="badge badge-a">工控对话</span> <span class="badge badge-tech">⚙️ 技术流程</span></h2>
<p>语音输入经过四阶段前置拦截停止词→UI语音点击→Slot填写→BERT只有前三阶段全部未命中的输入才进入 BERT NLU再由 decision 字段驱动工具调用、知识检索或 LLM 作答。</p>
</div>
${legendTech}
<div class="fc">
${nd('nd-start', '语音 / 文字输入', '来自 ASR 转录文本 / 直接文字')}
${da()}
${nd('nd-route', '阶段 0 · 停止词检测', '命中 cancel 词表 → 直接生成 stop_action流程终止', ['rule'])}
<div class="fnote">词表来自 voice_aliases.yml · cancel_words静态构建</div>
${da()}
${nd('nd-route', '阶段 1 · UI 可见元素语音点击匹配', '优先级waiting_confirmation affirm/deny > 当前Artifact按钮 > 全局固定操作', ['rule'])}
${slink(2)}
<div class="brow">
<div class="bcol">
${blbl('命中 · 语音点击', 'bl-hit')}
${da()}
${nd('nd-tool', '生成 ActionEvent', 'actionId / artifactId / sourceText', ['rule'])}
${da()}
${nd('nd-snap', '画布状态机直接响应', '不调用 BERT不产生新 Artifact')}
</div>
<div class="bcol">
${blbl('未命中 · 继续', 'bl-miss')}
${da()}
${nd('nd-route', '阶段 1.5 · waiting_slot + inform 检测', 'session.status=waiting_slot AND 输入为数字/数值', ['rule'])}
${slink(2)}
<div class="brow" style="width:100%;">
<div class="bcol">
${blbl('命中 · 填槽', 'bl-hit')}
${da()}
${nd('nd-tool', 'fill_slots 接口', '直接补全当前 slot不走 BERT', ['rule'])}
</div>
<div class="bcol">
${blbl('未命中 · 进入 BERT', 'bl-miss')}
${da()}
${nd('nd-bert', '阶段 2 · BERT NLUintelligent_cabin', 'POST /api/v1/agent/chat\n返回 intent_id + decision + slots', ['bert'])}
<div class="fnote">inference 报错直接抛出,不降级</div>
</div>
</div>
</div>
</div>
${da(30)}
${nd('nd-route', 'decision 字段路由', 'execute / clarify / route_to_cloud / reject', ['rule'])}
${slink(3)}
<div class="brow">
<!-- Branch 1 -->
<div class="bcol">
${blbl('execute · 设备控制域', 'bl-b')}
${da()}
${nd('nd-route', 'domain = machine_control\nconfidence_grade = high\nintent_id = wirecut_*')}
${da()}
${nd('nd-tool', '工业工具调用', 'DBus 写参 / 设备控制指令', ['tool'])}
${da()}
${nd('nd-art', '生成 Artifact', 'ParameterChangeArtifact\nDeviceActionArtifact', ['art'])}
${da()}
${nd('nd-snap', '画布渲染 + 等待 ActionEvent')}
</div>
<!-- Branch 2 -->
<div class="bcol">
${blbl('route_to_cloud · 知识域', 'bl-g')}
${da()}
${nd('nd-route', 'domain = equipment_knowledge\n或 confidence 偏低')}
${da()}
${nd('nd-llm', 'LLM 语义兜底分析', '提取检索关键词', ['llm'])}
${da()}
${nd('nd-kb', '知识库检索', '说明书 / SOP / 报警手册', ['tool'])}
${da()}
${nd('nd-llm', 'LLM 组织检索结果', '生成教学结构', ['llm'])}
${da()}
${nd('nd-art', '生成 KnowledgeLessonArtifact', '', ['art'])}
${da()}
${nd('nd-snap', '画布渲染')}
</div>
<!-- Branch 3 -->
<div class="bcol">
${blbl('reject · smalltalk / fallback', 'bl-gr')}
${da()}
${nd('nd-route', 'domain = smalltalk\n或无法匹配工业 domain')}
${da()}
${nd('nd-llm', 'LLM 直接作答', '', ['llm'])}
${da()}
${nd('nd-minor', '文字回复,不生成 Artifact')}
<div class="fnote">不写入 ArtifactStore</div>
</div>
</div>
</div>
`;
}
// ═══════════════════════════════════════════════════════════════════════════
// FLOW B · UX VIEW (🚧 下一版本计划中,暂未实现)
// ═══════════════════════════════════════════════════════════════════════════
function renderB_UX() {
return `
<div class="flow-meta">
<h2>大流程 B · 调机流程中 <span class="badge badge-b">Guided Procedure 激活</span> <span class="badge badge-ux">👁 交互流程</span> <span class="badge badge-plan">🚧 下一版本 · 计划中</span></h2>
<p>操作员确认进入调机向导后,调机流程卡占据画布主位。后续输入优先驱动当前步骤,其他内容以 Tab 或浮层展示,调机卡不被覆盖。</p>
</div>
<div class="plan-banner">⚠️ 此流程为下一版本设计规划,当前版本暂未实现。此图仅用于交互设计参考。</div>
${legendUX}
<div class="fc">
${nd('nd-show', '调机向导卡在画布主位展示', '步骤列表 · 当前步骤高亮 · 步骤指令可见')}
${da()}
${nd('nd-trig', '操作员输入或点击', '文字 / 语音 / 直接点击步骤按钮')}
${slink(2)}
<div class="brow">
<!-- HIT -->
<div class="bcol">
${blbl('✅ 场景 1 · 操作当前步骤', 'bl-hit')}
${da()}
${nd('nd-do', '输入"已完成/下一步/确认"或直接点击', '等同于用语音/文字替代手点击')}
${da()}
${nd('nd-show', '当前步骤标记完成,流程卡更新', '进入下一步 · 调机卡不离开主位')}
${da()}
${nd('nd-end', '所有步骤完成,调机流程收尾', '显示操作汇总')}
</div>
<!-- NOT HIT -->
<div class="bcol">
${blbl('🔀 场景 2 · 其他内容输入', 'bl-miss')}
${da()}
${nd('nd-trig', '输入与当前步骤无关', '系统识别为新的操控/知识/通用内容')}
${slink(3)}
<div class="brow" style="width:100%;">
<div class="bcol">
${blbl('操控工具', 'bl-b')}
${da()}
${nd('nd-tab', 'Tab 出现工具执行结果', '调机 | 工具结果', [])}
${da()}
${nd('nd-do', '操作员查看后可确认')}
<div class="fnote">调机卡保持主位</div>
</div>
<div class="bcol">
${blbl('知识查询', 'bl-g')}
${da()}
${nd('nd-tab', 'Tab 出现知识卡', '调机 | 知识参考', [])}
${da()}
${nd('nd-do', '操作员查阅后继续流程')}
<div class="fnote">调机卡保持主位</div>
</div>
<div class="bcol">
${blbl('通用提问', 'bl-gr')}
${da()}
${nd('nd-show', '临时浮层文字回答', '不占画布主位')}
${da()}
${nd('nd-end', '回答完毕自动收起')}
<div class="fnote">调机卡保持主位</div>
</div>
</div>
</div>
</div>
</div>
`;
}
// ═══════════════════════════════════════════════════════════════════════════
// FLOW B · TECH VIEW (🚧 下一版本计划中,暂未实现)
// ═══════════════════════════════════════════════════════════════════════════
function renderB_Tech() {
return `
<div class="flow-meta">
<h2>大流程 B · 调机流程中 <span class="badge badge-b">Guided Procedure 激活</span> <span class="badge badge-tech">⚙️ 技术流程</span> <span class="badge badge-plan">🚧 下一版本 · 计划中</span></h2>
<p>GuidedProcedure Artifact 激活后,输入路由优先做 textAliases 规则匹配(类汽车语音点击),命中则直接更新状态机;未命中再走 BERT 新意图识别,结果以 Tab 渲染,不修改主 Artifact 实例。</p>
</div>
<div class="plan-banner">⚠️ 此流程为下一版本设计规划,当前版本暂未实现。此图仅用于技术设计参考。</div>
${legendTech}
<div class="fc">
${nd('nd-art', 'GuidedProcedure Artifact 激活', 'currentStepId · steps[] · actions[textAliases]')}
${da()}
${nd('nd-start', '文本输入', '来自文字 / ASR / 点击')}
${da()}
${nd('nd-route', '路由层:优先匹配当前步骤 textAliases', '规则匹配为主BERT 置信度辅助确认', ['rule', 'bert'])}
${slink(2)}
<div class="brow">
<!-- HIT -->
<div class="bcol">
${blbl('命中 · textAliases 匹配成功', 'bl-hit')}
${da()}
${nd('nd-route', '生成 ArtifactActionEvent', 'actionId / source="text" / transcript', ['rule'])}
${da()}
${nd('nd-tool', '执行步骤对应工具动作', '写入测量値 / 推进步骤状态')}
${da()}
${nd('nd-bert', 'Reducer 更新 Artifact 状态', 'currentStepId → nextStepId\nstep.status → "completed"')}
${da()}
${nd('nd-snap', '快照保存snapshotPolicy: persistent', '步骤记录存档,支持复盘')}
</div>
<!-- NOT HIT -->
<div class="bcol">
${blbl('未命中 · 进入新意图识别', 'bl-miss')}
${da()}
${nd('nd-bert', 'BERT NLU 识别新意图', '在当前调机上下文中重新分类 domain/intent', ['bert'])}
${slink(3)}
<div class="brow" style="width:100%;">
<div class="bcol">
${blbl('tool_call', 'bl-b')}
${da()}
${nd('nd-tool', '工具执行', '不修改主 Artifact', ['tool'])}
${da()}
${nd('nd-art', 'Tab 渲染结果', '主 Artifact 不受影响', ['art'])}
<div class="fnote">ArtifactStore 主实例不变</div>
</div>
<div class="bcol">
${blbl('knowledge_query', 'bl-g')}
${da()}
${nd('nd-kb', '知识库检索\n+ LLM 组织', '', ['llm'])}
${da()}
${nd('nd-art', 'Tab 渲染知识卡', '主 Artifact 不受影响', ['art'])}
<div class="fnote">ArtifactStore 主实例不变</div>
</div>
<div class="bcol">
${blbl('smalltalk', 'bl-gr')}
${da()}
${nd('nd-llm', 'LLM 直接作答', '', ['llm'])}
${da()}
${nd('nd-minor', '临时浮层,不写入\nArtifactStore')}
<div class="fnote">主 Artifact 完全不受影响</div>
</div>
</div>
</div>
</div>
</div>
`;
}
// ─── Render Dispatch ──────────────────────────────────────────────────────
const renderers = {
A: { ux: renderA_UX, tech: renderA_Tech },
B: { ux: renderB_UX, tech: renderB_Tech },
};
function render() {
const { flow, view } = S;
// Flow tabs
document.querySelectorAll('#flowTabs .tbtn').forEach(b => {
b.classList.toggle('active-flow', b.dataset.flow === flow);
});
// View tabs
document.querySelectorAll('#viewTabs .tbtn').forEach(b => {
b.classList.remove('active-ux', 'active-tech');
if (b.dataset.view === view) {
b.classList.add(view === 'ux' ? 'active-ux' : 'active-tech');
}
});
// Color bar
const bar = document.getElementById('viewBar');
bar.className = `view-bar ${view}`;
// Content
document.getElementById('flowBody').innerHTML = renderers[flow][view]();
}
// ─── Events ───────────────────────────────────────────────────────────────
document.getElementById('flowTabs').addEventListener('click', e => {
const btn = e.target.closest('.tbtn');
if (btn && btn.dataset.flow) { S.flow = btn.dataset.flow; render(); }
});
document.getElementById('viewTabs').addEventListener('click', e => {
const btn = e.target.closest('.tbtn');
if (btn && btn.dataset.view) { S.view = btn.dataset.view; render(); }
});
render();
</script>
</body>
</html>