return respondJson(res, 200, { prompt: buildCompleteCubesPrompt(ctx) });
}
+ // 并发上限执行:最多同时跑 limit 个任务,避免无脑全并行抢占资源导致超时
+ async function mapWithConcurrency(items, limit, fn) {
+ const results = new Array(items.length);
+ let cursor = 0;
+ const worker = async () => {
+ while (cursor < items.length) {
+ const i = cursor++;
+ results[i] = await fn(items[i], i);
+ }
+ };
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
+ return results;
+ }
+
async function handleGenerateAll(req, res, siteId) {
res.writeHead(200, { "Content-Type": "application/x-ndjson; charset=utf-8", "Cache-Control": "no-cache" });
if (genLocks.has(`site-${siteId}`)) {
streamEvent(res, { log: o.event.delta.text });
}
};
- streamEvent(res, { log: `\n▶ 开始站点生成:并行调用 Wordbuddy AI(Hy3)与 dashboard-json-generator Skill 生成 ${cubes.length} 个 Cube 页面…\n` });
+ streamEvent(res, { log: `\n▶ 开始站点生成:最多 3 个并行调用 Wordbuddy AI(Hy3)与 dashboard-json-generator Skill 生成 ${cubes.length} 个 Cube 页面…\n` });
streamEvent(res, { task: "模型准备", status: "done" });
- // ⑥ 并行生成:各 Cube 互为独立,使用 Promise.all 并发调用,显著缩短整站生成耗时。
+ // ⑥ 并发生成:各 Cube 互为独立,但限制最多 3 个同时进行,避免资源争抢导致超时。
+ const GEN_CONCURRENCY = 3;
const onLog = (t) => streamEvent(res, { log: t });
- await Promise.all(cubes.map(async (cube) => {
+ await mapWithConcurrency(cubes, GEN_CONCURRENCY, async (cube) => {
streamEvent(res, { task: cube.name, status: "running" });
streamEvent(res, { log: `\n▶ 调用 Cube Skill(dashboard-json-generator)生成「${cube.name}」…\n` });
try {
} catch (e) {
db.prepare("UPDATE cubes SET status='failed' WHERE id=?").run(cube.id);
streamEvent(res, { log: `✗ 「${cube.name}」生成失败:${e.message}\n` });
- streamEvent(res, { task: cube.name, status: "failed", message: e.message });
+ streamEvent(res, { task: cube.name, status: "failed", message: `${e.message};失败页面可在「功能与菜单」中单独点「生成」重试` });
}
- }));
+ });
streamEvent(res, { log: "\n▶ 正在打包与校验交付物…\n" });
streamEvent(res, { task: "打包与检查校验", status: "running" });
const zipPath = await createSiteZip(siteId);
<div class="flow">
<ol>
<li><b>发起</b>:在「生成确认」点「开始生成」→ 前端 <code>POST /api/db/sites/:id/generate-all</code>(携带各 Cube 的名称与内容)。</li>
- <li><b>落库与准备</b>:db-sidecar 先持久化前端传入的 Cube,再以 <code>Promise.all</code> 并行调用各 Cube 的生成(各页相互独立,互不阻塞)。</li>
+ <li><b>落库与准备</b>:db-sidecar 先持久化前端传入的 Cube,再以最多 3 个并发的 <code>mapWithConcurrency</code> 调用各 Cube 的生成(各页相互独立,互不阻塞;限制并发以避免资源争抢导致单页超时)。</li>
<li><b>调用模型</b>:每页调用 AI 生成服务(后端 <code>runCodebuddy</code> 封装)→ 生成进程以流式 JSON 输出。提示词内含可视化生成 Skill 的 Cube 结构规则(DASHBOARD_SPEC)与站点/样式上下文;DASHBOARD_SPEC 已要求模拟数据精简(时序 ≤16 点、KPI/表格样例 ≤6 行)以缩短输出。</li>
<li><b>解析与校验(含代码级自动修复)</b>:从流式输出抽取 JSON → <code>fixDashboard</code> 先做代码级自动修复(修复 <code>x+w>24</code>、面板重叠、首面板 Title 定位、KPI/Radar/Line 等 <code>data</code> 字段、缺失 <code>id/gridPos</code> 等)→ 再 <code>validateDashboard</code> 复校;仅当残留问题确无法自动修复时才再次调用模型(最多 2 次模型调用,原 3 次)。</li>
<li><b>写回</b>:生成的 JSON 存入 gen_files 并关联 Cube,状态置为 generated,同时保存组合提示词。</li>
<li>2026-08-18 新增「AI 智能补全」功能:站点基本属性页「填写建议」下方新增面板,点击「开始补全」后基于站点名称 / 所属领域 / 站点基本情况描述,由 AI 流式分析并自动写入「补充要求」字段;「查看提示词」可查看完整组合提示词(后端 <code>POST /api/db/sites/:id/complete</code> 流式、<code>GET .../complete-prompt</code> 取提示词)。</li>
<li>2026-08-18 扩展「AI 智能补全」至 Asset 资产设置页:右侧新增与基本属性同款的「填写建议 + AI 智能补全」栏;点击「开始补全」后基于站点名称 / 领域 / 描述 / 补充要求分析并补充设备类型、数量预设与命名规则,追加到用户已填项下方(不改动已有项),资产层级预览随之重算(后端 <code>POST /api/db/sites/:id/complete-assets</code> 流式、<code>GET .../complete-assets-prompt</code> 取提示词)。</li>
<li>2026-08-18 重构「功能与菜单」页:① MU 标准页面改为树状上下结构、仅保留勾选框、默认全选中;② Cube 扩展页面右侧新增「填写建议 + AI 智能补全」栏,点击「开始补全」后基于站点属性与设备资产层级由 AI 推断并补充 Cube 扩展页面(名称 + 详尽内容),追加到用户已建页面下方(不改动用户内容)(后端 <code>POST /api/db/sites/:id/complete-cubes</code> 流式、<code>GET .../complete-cubes-prompt</code> 取提示词);③ 菜单结构改为只读默认预览:MU 页面在上,所有 Cube 页面归入「综合监控」一级菜单、二级为各 Cube 名称,仅展示无编辑。</li>
+ <li>2026-08-18 修复整站生成超时:将「生成确认」页全站生成由无上限 <code>Promise.all</code> 并行改为最多 3 个并发(<code>mapWithConcurrency</code>),避免 9 个 Cube 同时抢占 CPU/模型 API 资源导致部分页面 300s 超时失败;超时阈值维持 300s 不变,单页失败不再自动重试,仅提示用户到「功能与菜单」对该页面单独点「生成」重试。</li>
</ul>
<ul>