266 lines
9.9 KiB
JavaScript
266 lines
9.9 KiB
JavaScript
const flows = {
|
|
agent: {
|
|
kicker: 'AGENT REQUEST / 7 HOPS',
|
|
description:
|
|
'浏览器的已登录请求由 Web 转换成兼容 OpenAI 的流式调用;Runtime 独立完成上下文构建、模型调用、工具循环和证据校验。',
|
|
nodes: ['browser', 'web', 'runtime', 'provider', 'gateway', 'workspace', 'store']
|
|
},
|
|
identity: {
|
|
kicker: 'IDENTITY / SHORT-LIVED & SERVER-SIDE',
|
|
description:
|
|
'Web 先验证登录用户并签发短期身份;Runtime 验证一次后,在每次 Gateway 工具调用前重新签发,避免长任务复用过期令牌。',
|
|
nodes: ['browser', 'web', 'runtime', 'gateway', 'workspace']
|
|
},
|
|
tool: {
|
|
kicker: 'TOOL EXECUTION / POLICY BEFORE DOCKER',
|
|
description:
|
|
'Runtime 校验并调度工具,Gateway 再执行身份、路径和资源策略,最后才通过 SSH Docker 进入该用户唯一的工作区。',
|
|
nodes: ['runtime', 'gateway', 'workspace', 'store']
|
|
},
|
|
file: {
|
|
kicker: 'FILE DELIVERY / AUTHENTICATED STREAM',
|
|
description:
|
|
'用户在工作区抽屉发起浏览或下载;Web 验证会话后请求 Gateway,文件或归档以流式响应返回,浏览器从不接触内部服务密钥。',
|
|
nodes: ['browser', 'web', 'gateway', 'workspace']
|
|
}
|
|
};
|
|
|
|
const stages = {
|
|
context: {
|
|
kicker: 'CONTEXT / recent-visible-v1',
|
|
title: '把有限上下文留给真正影响下一步的内容',
|
|
description:
|
|
'移除旧的工具详情渲染,按模型预算保留最新可见消息,再注入最多 8 条持久记忆。上下文策略和 Agent Loop 分别版本化。',
|
|
points: [
|
|
'去掉历史消息中的工具 UI 标记',
|
|
'最近消息优先,按字符预算截断',
|
|
'记忆只作为补充上下文,不直接取得执行权限'
|
|
]
|
|
},
|
|
model: {
|
|
kicker: 'MODEL / SERVER-SIDE CATALOG',
|
|
title: '模型提出下一步,平台控制它能看到和能花费什么',
|
|
description:
|
|
'Runtime 根据公开模型 ID 选择固定 provider、上下文预算、输出预算和循环上限。用户不能从前端注入密钥或任意模型参数。',
|
|
points: ['支持流式内容与工具调用', '模型和推理强度明确标注', 'provider 差异在传输层统一']
|
|
},
|
|
validate: {
|
|
kicker: 'VALIDATE / NORMALIZE BEFORE TRUST',
|
|
title: '先把模型输出变成可约束的数据,再决定是否执行',
|
|
description:
|
|
'工具名称、参数、批次数量和权限都在执行前检查。一次模型响应最多接受 8 个工具调用,未知或畸形调用会成为可见错误。',
|
|
points: ['JSON 参数规范化', '工具白名单与参数校验', '重复无进展调用守卫']
|
|
},
|
|
schedule: {
|
|
kicker: 'SCHEDULER / safe-parallel-v1',
|
|
title: '读取可以加速,写入必须保持可预测',
|
|
description:
|
|
'连续且标记为 parallel_safe 的工具并发执行;任何文件写入、命令或状态变更都会形成串行边界,结果按请求顺序返回模型。',
|
|
points: ['只读调用成组并发', '变更操作严格串行', '批次结果仍保持稳定顺序']
|
|
},
|
|
execute: {
|
|
kicker: 'EXECUTE / TOOLS + BOUNDED DELEGATION',
|
|
title: '所有能力都通过可记录、可验证的工具边界发生',
|
|
description:
|
|
'文件、命令、Git、计划和记忆都有结构化工具。Workspace 工具经 Gateway 进入用户容器;只读子任务可以受限委派。',
|
|
points: ['工作区工具与状态工具分离', '每次调用记录开始和结果事件', '子 Agent 不得再次委派']
|
|
},
|
|
evidence: {
|
|
kicker: 'EVIDENCE / COMPLETION IS A POLICY',
|
|
title: '“完成”不是一句自然语言,而是一组已经发生的事实',
|
|
description:
|
|
'如果任务要求生成文件、修改代码或给出测量报告,循环必须看到相应的写入、验证和精确结果。证据不足时会继续循环或明确失败。',
|
|
points: ['变更后必须有后续验证', '失败命令必须修复并重跑', '报告必须晚于来源或实验结果']
|
|
}
|
|
};
|
|
|
|
const statusLabels = {
|
|
baseline: '当前基线',
|
|
validated: '已验证',
|
|
backlog: '待实验'
|
|
};
|
|
|
|
let experimentData = [];
|
|
let activeExperimentFilter = 'all';
|
|
|
|
function activateFlow(flowName) {
|
|
const flow = flows[flowName];
|
|
if (!flow) return;
|
|
|
|
document.querySelectorAll('.flow-tab').forEach((button) => {
|
|
const active = button.dataset.flow === flowName;
|
|
button.classList.toggle('is-active', active);
|
|
button.setAttribute('aria-selected', String(active));
|
|
});
|
|
|
|
document.querySelectorAll('.system-node').forEach((node) => {
|
|
node.classList.toggle('is-flow-active', flow.nodes.includes(node.dataset.node));
|
|
});
|
|
|
|
document.querySelector('.system-map')?.classList.add('has-selection');
|
|
document.querySelector('#flow-kicker').textContent = flow.kicker;
|
|
document.querySelector('#flow-description').textContent = flow.description;
|
|
}
|
|
|
|
function activateStage(stageName) {
|
|
const stage = stages[stageName];
|
|
if (!stage) return;
|
|
|
|
document.querySelectorAll('.loop-stage').forEach((button) => {
|
|
const active = button.dataset.stage === stageName;
|
|
button.classList.toggle('is-active', active);
|
|
button.setAttribute('aria-selected', String(active));
|
|
});
|
|
|
|
document.querySelector('#stage-kicker').textContent = stage.kicker;
|
|
document.querySelector('#stage-title').textContent = stage.title;
|
|
document.querySelector('#stage-description').textContent = stage.description;
|
|
|
|
const list = document.querySelector('#stage-points');
|
|
list.replaceChildren(
|
|
...stage.points.map((point) => {
|
|
const item = document.createElement('li');
|
|
item.textContent = point;
|
|
return item;
|
|
})
|
|
);
|
|
}
|
|
|
|
function experimentCard(experiment) {
|
|
const article = document.createElement('article');
|
|
article.className = 'experiment-card';
|
|
|
|
const meta = document.createElement('div');
|
|
meta.className = 'experiment-meta';
|
|
|
|
const id = document.createElement('span');
|
|
id.textContent = experiment.id;
|
|
|
|
const badge = document.createElement('span');
|
|
badge.className = `status-badge status-${experiment.status}`;
|
|
badge.textContent = statusLabels[experiment.status];
|
|
meta.append(id, badge);
|
|
|
|
const title = document.createElement('h3');
|
|
title.textContent = experiment.title;
|
|
|
|
const summary = document.createElement('p');
|
|
summary.textContent = experiment.summary;
|
|
|
|
const footer = document.createElement('div');
|
|
footer.className = 'experiment-footer';
|
|
[experiment.area, experiment.version, ...(experiment.metrics || [])].filter(Boolean).forEach((text) => {
|
|
const tag = document.createElement('span');
|
|
tag.textContent = text;
|
|
footer.append(tag);
|
|
});
|
|
|
|
article.append(meta, title, summary, footer);
|
|
return article;
|
|
}
|
|
|
|
function renderExperiments() {
|
|
const query = document.querySelector('#experiment-search').value.trim().toLocaleLowerCase('zh-CN');
|
|
const filtered = experimentData.filter((experiment) => {
|
|
const statusMatches =
|
|
activeExperimentFilter === 'all' || experiment.status === activeExperimentFilter;
|
|
const haystack = [
|
|
experiment.id,
|
|
experiment.title,
|
|
experiment.summary,
|
|
experiment.area,
|
|
experiment.version,
|
|
...(experiment.metrics || [])
|
|
]
|
|
.join(' ')
|
|
.toLocaleLowerCase('zh-CN');
|
|
return statusMatches && (!query || haystack.includes(query));
|
|
});
|
|
|
|
const grid = document.querySelector('#experiment-grid');
|
|
if (filtered.length === 0) {
|
|
const empty = document.createElement('article');
|
|
empty.className = 'experiment-empty';
|
|
empty.textContent = '没有匹配的实验记录。';
|
|
grid.replaceChildren(empty);
|
|
} else {
|
|
grid.replaceChildren(...filtered.map(experimentCard));
|
|
}
|
|
|
|
document.querySelector('#experiment-count').textContent =
|
|
`${String(filtered.length).padStart(2, '0')} / ${String(experimentData.length).padStart(2, '0')} RECORDS`;
|
|
}
|
|
|
|
async function loadExperiments() {
|
|
const grid = document.querySelector('#experiment-grid');
|
|
try {
|
|
const response = await fetch('./experiments.json', { cache: 'no-cache' });
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
experimentData = await response.json();
|
|
renderExperiments();
|
|
} catch (error) {
|
|
const failure = document.createElement('article');
|
|
failure.className = 'experiment-empty';
|
|
failure.textContent = '实验台账暂时无法读取,请查看仓库中的 docs/experiments.md。';
|
|
grid.replaceChildren(failure);
|
|
document.querySelector('#experiment-count').textContent = 'DATA UNAVAILABLE';
|
|
}
|
|
}
|
|
|
|
function setActiveNavigation(sectionId) {
|
|
document.querySelectorAll('.nav-link').forEach((link) => {
|
|
link.classList.toggle('is-active', link.getAttribute('href') === `#${sectionId}`);
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll('.flow-tab').forEach((button) => {
|
|
button.addEventListener('click', () => activateFlow(button.dataset.flow));
|
|
});
|
|
|
|
document.querySelectorAll('.loop-stage').forEach((button) => {
|
|
button.addEventListener('click', () => activateStage(button.dataset.stage));
|
|
});
|
|
|
|
document.querySelectorAll('.filter-button').forEach((button) => {
|
|
button.addEventListener('click', () => {
|
|
activeExperimentFilter = button.dataset.filter;
|
|
document
|
|
.querySelectorAll('.filter-button')
|
|
.forEach((candidate) => candidate.classList.toggle('is-active', candidate === button));
|
|
renderExperiments();
|
|
});
|
|
});
|
|
|
|
document.querySelector('#experiment-search').addEventListener('input', renderExperiments);
|
|
|
|
const toggle = document.querySelector('.nav-toggle');
|
|
const sidebar = document.querySelector('.sidebar');
|
|
toggle.addEventListener('click', () => {
|
|
const isOpen = sidebar.classList.toggle('is-open');
|
|
toggle.setAttribute('aria-expanded', String(isOpen));
|
|
});
|
|
|
|
document.querySelectorAll('.nav-link').forEach((link) => {
|
|
link.addEventListener('click', () => {
|
|
sidebar.classList.remove('is-open');
|
|
toggle.setAttribute('aria-expanded', 'false');
|
|
});
|
|
});
|
|
|
|
if ('IntersectionObserver' in window) {
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
const visible = entries
|
|
.filter((entry) => entry.isIntersecting)
|
|
.sort((a, b) => b.intersectionRatio - a.intersectionRatio);
|
|
if (visible[0]) setActiveNavigation(visible[0].target.id);
|
|
},
|
|
{ rootMargin: '-20% 0px -62% 0px', threshold: [0.05, 0.3] }
|
|
);
|
|
document.querySelectorAll('main > section[id]').forEach((section) => observer.observe(section));
|
|
}
|
|
|
|
activateFlow('agent');
|
|
activateStage('context');
|
|
loadExperiments();
|