]> acesimba.cloud Git - StationBuilder.git/commitdiff
整站生成限制最多3个并发,避免资源争抢导致超时;失败仅提示手动重试
authorwang.yu <wang.yu@advantech.com>
Tue, 18 Aug 2026 03:38:33 +0000 (11:38 +0800)
committerwang.yu <wang.yu@advantech.com>
Tue, 18 Aug 2026 03:38:33 +0000 (11:38 +0800)
db-sidecar.mjs
public/design-doc.html

index d18f8de332fffa0a48fac2fab83cae988a41793e..e186bff292511c3118ff73e0441dd46f03c625e6 100644 (file)
@@ -1091,6 +1091,20 @@ ${assetText}
     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}`)) {
@@ -1121,11 +1135,12 @@ async function handleGenerateAll(req, res, 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 {
@@ -1144,9 +1159,9 @@ async function handleGenerateAll(req, res, siteId) {
       } 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);
index 893c0d909b0fa47958b4560b728eb255c9c9d60f..f564c5a602e83bc29f93e4d97f031f581c5f1be3 100644 (file)
   <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&gt;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>