470 lines
24 KiB
HTML
470 lines
24 KiB
HTML
<!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; }
|
||
|
||
/* 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="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 = { 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 NLU(intelligent_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>
|
||
`;
|
||
}
|
||
|
||
|
||
// ─── Render Dispatch ──────────────────────────────────────────────────────
|
||
const renderers = {
|
||
ux: renderA_UX,
|
||
tech: renderA_Tech,
|
||
};
|
||
|
||
function render() {
|
||
const { view } = S;
|
||
|
||
// 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[view]();
|
||
}
|
||
|
||
// ─── Events ───────────────────────────────────────────────────────────────
|
||
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>
|