docs: 为 CLI、网页编排器和插件协议补充学习向注释

在核心类型、图执行器、stdio 插件和前端入口写清职责与对照路径,不改运行行为。
This commit is contained in:
2026-08-28 11:51:48 +08:00
parent a3cca78592
commit 950d09ddb0
38 changed files with 206 additions and 3 deletions
+1
View File
@@ -1,3 +1,4 @@
/* 编排器布局:顶栏、三栏(节点 / 画布 / 检查器)、底部日志、确认弹层。 */
:root {
--bg: #0f1419;
--panel: #171e26;
+2
View File
@@ -1,4 +1,5 @@
<!DOCTYPE html>
<!-- 单页编排器:左节点列表、中画布、右检查器、下日志。逻辑全在 /js/app.js。 -->
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
@@ -37,6 +38,7 @@
<h2>运行结果</h2>
<pre id="log">尚未运行。</pre>
</section>
<!-- 缺城市时的确认层,对应后端 status=needsDecision -->
<div id="decisionModal" class="modal hidden" aria-hidden="true">
<div class="dialog">
<h2>需要你确认</h2>
+17
View File
@@ -1,3 +1,14 @@
/**
* 可视化编排器前端(无框架)。
*
* 学习路径:
* 1. init → loadCatalog:拉系统节点和插件节点
* 2. 示例图 / 拖节点、点端口连线;图数据在 state.nodes / state.edges
* 3. runGraph POST /api/run;若 status=needsDecision 弹出确认框
* 4. 确认走 /api/run/{id}/decide;超时则 pollRun 等服务端自动采用默认方案
*
* selected / pending 只用于点击交互。发给后端的 JSON 不含 API Key,只有 credentialId。
*/
const state = {
catalog: [],
system: [],
@@ -44,6 +55,7 @@ function pickNode(list, inputName) {
return (list || []).find((item) => item.inputs?.some((p) => p.name === inputName));
}
/** 生成「抽城市 → 有城市才查天气」的示例图。连线 when=hasValidCities。 */
function exampleGraph() {
const fileInfo = pickNode(state.system, "filePath") || pickNode(state.catalog, "filePath");
const weatherInfo = pickNode(state.plugins, "cities") || pickNode(state.system, "cities") || pickNode(state.catalog, "cities");
@@ -110,6 +122,7 @@ function addNode(type) {
render();
}
/** 根据 state 重画节点 DOM;连线是 SVG,在 drawWires。 */
function render() {
canvas.innerHTML = "";
for (const node of state.nodes) {
@@ -172,6 +185,7 @@ function startDrag(e, node) {
window.addEventListener("mouseup", up);
}
/** 先点输出端口记下 pending,再点输入端口生成一条边。 */
function onPort(nodeId, port, dir) {
if (dir === "out") {
state.pending = { nodeId, port };
@@ -293,6 +307,7 @@ function renderInspector() {
};
}
/** 把画布图交给后端。可能直接完成,也可能弹出 decisionModal。 */
async function runGraph() {
closeDecision();
logEl.textContent = "运行中…";
@@ -379,6 +394,7 @@ function syncDecisionText() {
}
}
/** 倒计时到 0 后去 GET 运行结果,因为服务端会自己按默认方案继续。 */
function startDecisionTimer(deadline) {
if (decisionTimer) {
clearInterval(decisionTimer);
@@ -447,6 +463,7 @@ decisionSubmit.onclick = async () => {
}
};
/** 拉节点目录和凭据列表,刷新左侧面板。 */
async function loadCatalog() {
const [catalog, credentials] = await Promise.all([
(await fetch("/api/catalog")).json(),