const DOMAIN_STEPS:{key:DomainStepKey;label:string}[]=[
{key:"basic",label:"方案基本属性"},
+ {key:"knowledge",label:"领域知识"},
{key:"assets",label:"资产设置"},
{key:"functions",label:"功能与菜单"},
{key:"opsflow",label:"运维流程"},
- {key:"knowledge",label:"领域知识"},
{key:"generate",label:"生成保存"},
{key:"expert",label:"专家确认"},
];
const [d, mt]=await Promise.all([ fetchDb(`/domain-templates/${id}`) as any, fetchDb(`/mu-tree`) as Promise<MuNode[]> ]);
setMuTreeLocal(mt);
setT(d);
- setM({domain_id:d.domain_id,name:d.name,description:d.description||"",status:d.status,version:d.version,creator:d.creator||"",supplement:d.supplement||"",style_id:d.style_id!=null?Number(d.style_id):null});
+ setM({domain_id:d.domain_id,name:d.name,description:d.description||"",status:d.status,version:d.version,creator:d.creator||"",supplement:d.supplement||"",style_id:d.style_id!=null?Number(d.style_id):null,docs:d.docs||[],flows:d.flows||[]});
setAssets((d.assets||[]).map((a:any)=>({id:a.id,device_template_id:a.device_template_id,device_name:a.device_name||"",count:Number(a.count)||0,rule:a.rule||"",sort_order:a.sort_order||0})));
setPages((d.pages||[]).map((p:any)=>({id:p.id,name:p.name,description:p.description||"",json_text:p.json_text||"",status:p.status||"pending",gen_filename:p.gen_filename||null,prompt:p.prompt||null,source:p.source||null,sort_order:p.sort_order||0})));
setMu(d.mu && d.mu.length ? d.mu : []);
{saving&&<Panel><div className="empty-state">正在保存…</div></Panel>}
</div></div>}
+ {step==="knowledge" && <KnowledgeStep id={id} docs={m.docs||[]} notify={notify} reload={()=>load(true)} confirm={confirm}/>}
+
{step==="assets" && <div className="two-column">
<div>
<div className="kpi-row">{[[assets.filter(a=>a.device_template_id).length,"设备类型","类"],[assets.reduce((n,a)=>n+Number(a.count||0),0),"设备实例","个"],[Math.max(2, assets.filter(a=>a.device_template_id).length+1),"资产层级","层"]].map(x=><div key={x[1]}><span>{x[1]}</span><b>{x[0]}</b><small>{x[2]}</small></div>)}{saving&&<div><span>保存</span><Status>保存中…</Status></div>}</div>
pageStyles={pageStyles} styleId={m.style_id??null} onSelectStyle={selectStyle} />}
{step==="opsflow" && <PlaceholderStep title="运维流程" note="运维流程编辑功能待开发,后续版本将支持在方案中配置标准运维 SOP 与告警处理流程。"/>}
- {step==="knowledge" && <PlaceholderStep title="领域知识" note="领域知识(知识文档、标准规范、参考资料)编辑功能待开发,后续版本将支持在方案中沉淀领域知识库。"/>}
- {step==="generate" && <DomainGenerateReview tName={m.name} assets={assets} effSelected={effSelected} grouped={grouped} pages={pages} onBack={()=>goStep("functions")} onConfirm={()=>notify("方案已生成并保存,可返回上一步继续调整")}/>}
+ {step==="generate" && <DomainGenerateReview tName={m.name} assets={assets} effSelected={effSelected} grouped={grouped} pages={pages} docs={m.docs||[]} onBack={()=>goStep("functions")} onKnowledge={()=>goStep("knowledge")} onConfirm={()=>notify("方案已生成并保存,可返回上一步继续调整")}/>}
{step==="expert" && <PlaceholderStep title="专家确认" note="专家确认流程待开发,后续版本将支持领域专家在线评审并签署方案。"/>}
</div>
<div className="sticky-actions"><Button secondary onClick={()=>curIdx>0?goStep(DOMAIN_STEPS[curIdx-1].key):onBack()}>{curIdx>0?"上一步":"返回列表"}</Button><div><Button secondary onClick={()=>notify("草稿已保存")}>保存草稿</Button>{curIdx<DOMAIN_STEPS.length-1 && <Button onClick={()=>goStep(DOMAIN_STEPS[curIdx+1].key)}>下一步</Button>}{curIdx===DOMAIN_STEPS.length-1 && <Button onClick={()=>notify("方案已提交专家确认")}>提交确认</Button>}</div></div>
</Panel>;
}
-function DomainGenerateReview({tName,assets,effSelected,grouped,pages,onBack,onConfirm}:{tName:string;assets:DomainAsset[];effSelected:string[];grouped:any[];pages:DomainPage[];onBack:()=>void;onConfirm:()=>void}){
+function DomainGenerateReview({tName,assets,effSelected,grouped,pages,docs,onBack,onKnowledge,onConfirm}:{tName:string;assets:DomainAsset[];effSelected:string[];grouped:any[];pages:DomainPage[];docs:any[];onBack:()=>void;onKnowledge?:()=>void;onConfirm:()=>void}){
+ const reviewRows:[string,string,string,string?,(()=>void)?][]=[
+ ["方案",tName,"","返回修改",onBack],
+ ["资产",`${assets.filter(a=>a.device_template_id).length} 类 / ${assets.reduce((n,a)=>n+Number(a.count||0),0)} 个设备`,"3 层资产结构",""],
+ ["MU 页面",`${effSelected.length} 个标准页面`,"复用并配置菜单",""],
+ ["Cube 页面",`${pages.length} 个扩展页面`,`已生成 ${pages.filter(p=>p.status==="generated").length} 个`,""],
+ ["菜单结构","MU + 综合监控",`共 ${effSelected.length+pages.length} 项`,""],
+ ["领域知识", docs.length?`${docs.length} 份(${docs.slice(0,3).map((d:any)=>d.name).join("、")}${docs.length>3?" 等":""})`:"暂无知识", docs.length?"已沉淀领域知识,将作为 AI 补全优先参考":"可上传文档 / 配置站点", "返回领域知识", onKnowledge],
+ ["运维流程","标准 SOP","(本期占位,待开发)",""],
+ ];
return <div className="generate-layout"><div>
<Panel title="生成范围确认"><div className="confirm-grid">
- {[["方案",tName,"","返回修改"],["资产",`${assets.filter(a=>a.device_template_id).length} 类 / ${assets.reduce((n,a)=>n+Number(a.count||0),0)} 个设备`,"3 层资产结构",""],["MU 页面",`${effSelected.length} 个标准页面`,"复用并配置菜单",""],["Cube 页面",`${pages.length} 个扩展页面`,`已生成 ${pages.filter(p=>p.status==="generated").length} 个`,""],["菜单结构","MU + 综合监控",`共 ${effSelected.length+pages.length} 项`,""],["领域知识","沉淀标准规范","(待开发)",""],["运维流程","标准 SOP","(待开发)",""]].map(x=><div key={x[0]}><span>{x[0]}</span><b>{x[1]}</b><small>{x[2]}</small>{x[3]&&<button onClick={onBack}>{x[3]}</button>}</div>)}
+ {reviewRows.map(r=><div key={r[0]}><span>{r[0]}</span><b>{r[1]}</b><small>{r[2]}</small>{r[3]&&<button onClick={r[4]||onBack}>{r[3]}</button>}</div>)}
</div></Panel>
<Panel title="资产层级预览"><div className="asset-tree-preview"><div className="tree-root">⌄ <b>{tName||"未命名方案"}</b></div>{assets.filter(a=>a.device_template_id).map((row,i)=><div key={i} className="tree-branch"><div className="tree-node">⌄ {row.device_name}<span className="tree-count">{row.count}</span></div>{row.count>0&&<div className="tree-leaves">{formatInstances(row.rule,row.count)}</div>}</div>)}</div></Panel>
<Panel title="菜单结构预览"><div className="menu-tree"><div className="menu-root">⌄ {tName||"未命名方案"}</div><div className="menu-section"><div className="menu-section-title">MU 标准页面</div>{effSelected.length===0?<div className="menu-leaf muted">(未选择)</div>:grouped.flatMap((g:any)=>g.items).filter((l:any)=>effSelected.includes(l.key)).map((l:any)=><div key={l.key} className="menu-leaf">· {l.parentMenu}/{l.label}</div>)}</div><div className="menu-section"><div className="menu-section-title">综合监控</div>{pages.filter(p=>p.name).map(p=><div key={p.id} className="menu-leaf cube">· {p.name}</div>)}</div></div></Panel>
+function KnowledgeStep({id,docs,notify,reload,confirm}:{id:number;docs:any[];notify:(m:string)=>void;reload:()=>void;confirm:(msg:string)=>Promise<boolean>}){
+ const [busy,setBusy]=useState(false);
+ const [urlName,setUrlName]=useState("");
+ const [urlVal,setUrlVal]=useState("");
+ const readBase64=(file:File)=>new Promise<{filename:string;mime:string;data:string}>((res,rej)=>{ const r=new FileReader(); r.onload=()=>res({filename:file.name,mime:file.type||"application/octet-stream",data:String(r.result).split(",")[1]}); r.onerror=()=>rej(r.error); r.readAsDataURL(file); });
+ const uploadFiles=async(files:FileList|null)=>{ if(!files||!files.length) return; setBusy(true); try{ for(const f of Array.from(files)){ const b=await readBase64(f); await fetchDb(`/domain-templates/${id}/docs`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({doc_type:"file",name:f.name,file:{filename:b.filename,mime:b.mime,data:b.data}})}); } notify("已上传知识文档"); reload(); }catch(e:any){ notify("上传失败:"+(e?.message||e)); } finally{ setBusy(false); } };
+ const addUrl=async()=>{ const name=urlName.trim(); const url=urlVal.trim(); if(!name){ notify("请填写站点名称"); return; } if(!/^https?:\/\//i.test(url)){ notify("请输入有效的 http(s) 链接"); return; } setBusy(true); try{ await fetchDb(`/domain-templates/${id}/docs`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({doc_type:"link",name,url})}); notify("已添加参考站点"); setUrlName(""); setUrlVal(""); reload(); }catch(e:any){ notify("添加失败:"+(e?.message||e)); } finally{ setBusy(false); } };
+ const removeDoc=async(docId:number)=>{ if(!(await confirm("确定删除该知识来源?此操作不可恢复"))) return; try{ await fetchDb(`/knowledge-docs/${docId}`,{method:"DELETE"}); notify("已删除"); reload(); }catch(e:any){ notify("删除失败:"+(e?.message||e)); } };
+ return <div className="two-column">
+ <Panel title="上传领域知识文档" tools={busy?<Status>上传中…</Status>:undefined}>
+ <p className="hint">上传与本站领域相关的文档(标准规范、设备手册、工艺说明、建设方案等)。知识文本将作为资产配置、功能菜单 AI 补全的优先参考资料,并随方案保存。支持 txt / md / json / csv / html 等文本类,其他格式将作为参考资料登记。</p>
+ <label className="upload" style={{height:96}}>
+ <b>+</b><span>{busy?"上传中…":"选择文件或拖拽到此处(可多选)"}</span>
+ <input type="file" multiple hidden accept=".txt,.md,.markdown,.json,.csv,.log,.html,.xml,.yaml,.yml,text/*" onChange={e=>uploadFiles(e.target.files)}/>
+ </label>
+ </Panel>
+ <Panel title="配置参考 URL 站点">
+ <p className="hint">填写一个领域相关的参考网站地址。AI 补全时会由服务端抓取该站点正文并注入提示词(抓取失败则仅保留链接作为参考)。</p>
+ <div style={{display:"flex",flexDirection:"column",gap:8}}>
+ <input placeholder="站点名称,如:XX 行业标准" value={urlName} onChange={e=>setUrlName(e.target.value)}/>
+ <input placeholder="https://..." value={urlVal} onChange={e=>setUrlVal(e.target.value)}/>
+ <div><Button onClick={addUrl} disabled={busy}>添加站点</Button></div>
+ </div>
+ </Panel>
+ <Panel title={`已沉淀领域知识(${docs.length})`} tools={<Button secondary onClick={reload}>刷新</Button>}>
+ {docs.length===0? <div className="empty-state">暂无领域知识,上传文档或配置站点后此处展示。</div> :
+ <table className="data-table"><thead><tr><th>名称</th><th>类型</th><th>大小</th><th>创建时间</th><th>操作</th></tr></thead>
+ <tbody>{docs.map((d:any)=><tr key={d.id}>
+ <td>{d.name}</td>
+ <td><span style={{padding:"2px 8px",borderRadius:4,fontSize:12,background:"#2a3a55",color:"#cfe8ff"}}>{d.doc_type==="link"?"URL 站点":"文档"}</span></td>
+ <td>{d.size!=null?`${Math.round(Number(d.size)/1024*10)/10} KB`:"—"}</td>
+ <td>{d.created_at||""}</td>
+ <td style={{display:"flex",gap:8}}>{d.doc_type==="file"&&<a className="text-button" href={`/api/db/knowledge-docs/${d.id}/file`} target="_blank" rel="noreferrer">下载</a>}<button className="text-button" onClick={()=>removeDoc(d.id)}>删除</button></td>
+ </tr>)}</tbody></table>}
+ </Panel>
+ </div>;
+}
+
function PageStyleLibrary({pageStyles, loadPageStyles, notify}:{pageStyles:PageStyle[]; loadPageStyles:()=>Promise<void>; notify:(m:string)=>void}){
const [editor,setEditor]=useState<{open:boolean; id?:number}>({open:false});
const [preview,setPreview]=useState<PageStyle|null>(null);
}
// ===== AI 智能补全:基于站点名称/领域/描述分析并生成「补充描述」 =====
- function buildCompletePrompt({ name = "", domain = "", description = "" }) {
- return `你是一名资深的工业站点数字化方案架构师,擅长把站点业务背景转化为大屏看板(Cube 仪表盘)可用的生成上下文。
+ // ===== 领域知识范本:从本方案已上传/配置的领域知识(文档 / URL 站点)中抽取可注入提示词的文本 =====
+ function isTextMime(mime, filename) {
+ if (mime && String(mime).toLowerCase().startsWith("text/")) return true;
+ const ext = (filename || "").toLowerCase().split(".").pop();
+ return [".txt", ".md", ".markdown", ".json", ".csv", ".log", ".html", ".xml", ".yaml", ".yml"].includes("." + ext);
+ }
+ function readKnowledgeFile(filePath, mime, filename) {
+ try {
+ if (!filePath || !existsSync(filePath)) return "";
+ if (!isTextMime(mime, filename)) return "";
+ const text = readFileSync(filePath, "utf8");
+ return text.length > 16000 ? text.slice(0, 16000) + "\n…(内容已截断,原文较长)" : text;
+ } catch { return ""; }
+ }
+ async function fetchKnowledgeUrl(url) {
+ try {
+ const ctrl = new AbortController();
+ const t = setTimeout(() => ctrl.abort(), 8000);
+ const resp = await fetch(url, { signal: ctrl.signal, redirect: "follow" });
+ clearTimeout(t);
+ const ct = resp.headers.get("content-type") || "";
+ if (!resp.ok || !ct.toLowerCase().includes("text")) return "";
+ const text = await resp.text();
+ if (!text) return "";
+ return text.length > 16000 ? text.slice(0, 16000) + "\n…(内容已截断,原文较长)" : text;
+ } catch { return ""; }
+ }
+ // ctx.id 为 domain_template_id;站点级补全无 id,自然返回空(不注入)。
+ // 优先依据已上传领域知识分析补全,再做大模型在线补充。
+ async function buildKnowledgePrompt(ctx) {
+ if (!ctx || !ctx.id) return "";
+ const docs = db.prepare("SELECT id,name,doc_type,url,file_path,mime FROM knowledge_docs WHERE domain_template_id=? ORDER BY id").all(ctx.id);
+ if (!docs.length) return "";
+ const parts = [];
+ for (const d of docs) {
+ let body = "";
+ if (d.doc_type === "file") body = readKnowledgeFile(d.file_path, d.mime, d.name);
+ else if (d.doc_type === "link") body = await fetchKnowledgeUrl(d.url);
+ if (body && body.trim()) parts.push(`【领域知识 · ${d.name}】\n${body.trim()}\n`);
+ else parts.push(`(参考来源:${d.doc_type === "link" ? d.url : d.name})`);
+ }
+ return `\n\n===== 领域知识(请优先依据以下已上传/配置的资料分析并补全,再结合通用经验做在线补充)=====\n${parts.join("\n")}\n`;
+ }
+
+ async function buildCompletePrompt({ name = "", domain = "", description = "", id } = {}) {
+ const base = `你是一名资深的工业站点数字化方案架构师,擅长把站点业务背景转化为大屏看板(Cube 仪表盘)可用的生成上下文。
请根据以下站点信息,分析该站点在监控与运营方面的特点,并撰写一段「补充描述」,用于补充站点基本情况描述,为后续 Cube 仪表盘生成提供更充分的上下文。
站点信息:
2. 思考过程结束后,另起一段,以「【补充描述】」开头,给出最终要填入的补充说明文本。该文本应是一段专业、具体、可直接作为生成提示词上下文的描述,聚焦监控目标、关键指标、设备与告警、管理诉求,避免空话套话。
请直接输出你的分析与【补充描述】。`;
+ return base + (await buildKnowledgePrompt({ id }));
}
function extractSupplement(text) {
domain: (body.domain ?? site.domain) || "",
description: (body.description ?? site.description) || "",
};
- const prompt = buildCompletePrompt(ctx);
+ const prompt = await buildCompletePrompt(ctx);
emit({ prompt });
emit({ log: `\n▶ 开始 AI 智能补全:基于站点名称 / 领域 / 描述分析并生成补充描述…\n` });
emit({ task: "AI 智能补全", status: "running" });
domain: u.searchParams.get("domain") ?? site.domain ?? "",
description: u.searchParams.get("description") ?? site.description ?? "",
};
- return respondJson(res, 200, { prompt: buildCompletePrompt(ctx) });
+ return respondJson(res, 200, { prompt: await buildCompletePrompt(ctx) });
}
// ===== AI 资产补全:基于站点信息分析并补充设备类型/数量/命名规则 =====
- function buildCompleteAssetsPrompt(site, assets) {
+ async function buildCompleteAssetsPrompt(site, assets) {
const existing = (assets || []).filter(a => a && a.type && a.type.trim()).map(a => a.type.trim());
const existingText = existing.length ? existing.join("、") : "(无,用户尚未配置)";
- return `你是一名资深的工业站点数字化方案架构师,擅长根据站点业务背景推导其监控运营所需的设备清单。
+ const base = `你是一名资深的工业站点数字化方案架构师,擅长根据站点业务背景推导其监控运营所需的设备清单。
请根据以下站点信息,推断该站点在监控与运营中可能需要的主要设备类型,并为每种设备给出建议数量与命名规则,用于补充「资产配置」列表(作为后续 Cube 大屏看板的设备维度)。
站点信息:
3. 只补充用户尚未配置的设备类型;命名规则尽量贴合设备习惯(如泵类用 beng-、冷水机组用 lsch-、换热器用 hx- 等)。
请直接输出你的分析与【资产配置】JSON。`;
+ return base + (await buildKnowledgePrompt(site));
}
function extractAssets(text) {
supplement: (body.supplement ?? site.supplement) || "",
assets: body.assets || site.assets || [],
};
- const prompt = buildCompleteAssetsPrompt(ctx, ctx.assets);
+ const prompt = await buildCompleteAssetsPrompt(ctx, ctx.assets);
emit({ prompt });
emit({ log: `\n▶ 开始 AI 资产补全:基于站点信息与现有设备,分析并补充设备类型…\n` });
emit({ task: "AI 资产补全", status: "running" });
supplement: u.searchParams.get("supplement") ?? site.supplement ?? "",
assets: site.assets || [],
};
- return respondJson(res, 200, { prompt: buildCompleteAssetsPrompt(ctx, ctx.assets) });
+ return respondJson(res, 200, { prompt: await buildCompleteAssetsPrompt(ctx, ctx.assets) });
}
// ===== AI Cube 页面补全:基于站点信息 + 资产层级,推断并补充 Cube 扩展页面 =====
- function buildCompleteCubesPrompt(site) {
+ async function buildCompleteCubesPrompt(site) {
const assets = (site.assets || []).filter(a => a && a.type && a.type.trim());
const assetText = assets.length
? assets.map(a => `- ${a.type.trim()} 实例数量:${Number(a.count) || 0}${a.rule ? `(命名规则:${a.rule.trim()})` : ""}`).join("\n")
: "(无,用户尚未配置设备资产)";
const existingCubes = (site.cubes || []).filter(c => c && c.name && c.name.trim()).map(c => c.name.trim());
const existingText = existingCubes.length ? existingCubes.join("、") : "(无,用户尚未创建)";
- return `你是一名资深的工业站点数字化大屏方案架构师,擅长根据站点业务背景与设备资产,规划所需的可视化监控页面(Cube 扩展页)。
+ const base = `你是一名资深的工业站点数字化大屏方案架构师,擅长根据站点业务背景与设备资产,规划所需的可视化监控页面(Cube 扩展页)。
请根据以下信息,推断该站点监控大屏所需的 Cube 扩展页面,并为每个页面撰写详尽的页面内容说明,用于补充「Cube 扩展页面」列表(位于用户已建页面下方)。
站点信息:
4. 仅补充用户尚未创建的页面;页面名称简洁且不超过 6 个汉字。
请直接输出你的分析与【Cube页面】JSON。`;
+ return base + (await buildKnowledgePrompt(site));
}
function extractCubes(text) {
assets: body.assets || site.assets || [],
cubes: site.cubes || [],
};
- const prompt = buildCompleteCubesPrompt(ctx);
+ const prompt = await buildCompleteCubesPrompt(ctx);
emit({ prompt });
emit({ log: `\n▶ 开始 AI Cube 补全:基于站点信息与设备资产,推断并补充 Cube 扩展页面…\n` });
emit({ task: "AI Cube 补全", status: "running" });
assets: site.assets || [],
cubes: site.cubes || [],
};
- return respondJson(res, 200, { prompt: buildCompleteCubesPrompt(ctx) });
+ return respondJson(res, 200, { prompt: await buildCompleteCubesPrompt(ctx) });
}
// 并发上限执行:最多同时跑 limit 个任务,避免无脑全并行抢占资源导致超时
const dt = db.prepare("SELECT dt.*, d.name AS domain_name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.id=?").get(tmplId);
if (!dt) return fail("领域方案不存在");
const body = await parseBody(req).catch(() => ({}));
- const ctx = { name: (body.name ?? dt.name) || "", domain: (body.domain ?? dt.domain_name) || "", description: (body.description ?? dt.description) || "" };
- const prompt = buildCompletePrompt(ctx);
+ const ctx = { id: tmplId, name: (body.name ?? dt.name) || "", domain: (body.domain ?? dt.domain_name) || "", description: (body.description ?? dt.description) || "" };
+ const prompt = await buildCompletePrompt(ctx);
emit({ prompt });
emit({ log: `\n▶ 开始 AI 智能补全:基于方案名称 / 所属领域 / 描述,生成「补充描述」…\n` });
emit({ task: "AI 智能补全", status: "running" });
const dt = db.prepare("SELECT dt.*, d.name AS domain_name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.id=?").get(tmplId);
if (!dt) return respondError(res, 404, "领域方案不存在");
const u = new URL(req.url, "http://localhost");
- const ctx = { name: u.searchParams.get("name") ?? dt.name ?? "", domain: u.searchParams.get("domain") ?? dt.domain_name ?? "", description: u.searchParams.get("description") ?? dt.description ?? "" };
- return respondJson(res, 200, { prompt: buildCompletePrompt(ctx) });
+ const ctx = { id: tmplId, name: u.searchParams.get("name") ?? dt.name ?? "", domain: u.searchParams.get("domain") ?? dt.domain_name ?? "", description: u.searchParams.get("description") ?? dt.description ?? "" };
+ return respondJson(res, 200, { prompt: await buildCompletePrompt(ctx) });
}
async function handleDomainCompleteAssets(req, res, tmplId) {
const dt = db.prepare("SELECT dt.*, d.name AS domain_name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.id=?").get(tmplId);
if (!dt) return fail("领域方案不存在");
const body = await parseBody(req).catch(() => ({}));
- const ctx = { name: (body.name ?? dt.name) || "", domain: (body.domain ?? dt.domain_name) || "", description: (body.description ?? dt.description) || "", supplement: (body.supplement ?? dt.supplement) || "", assets: body.assets || [] };
- const prompt = buildCompleteAssetsPrompt(ctx, ctx.assets);
+ const ctx = { id: tmplId, name: (body.name ?? dt.name) || "", domain: (body.domain ?? dt.domain_name) || "", description: (body.description ?? dt.description) || "", supplement: (body.supplement ?? dt.supplement) || "", assets: body.assets || [] };
+ const prompt = await buildCompleteAssetsPrompt(ctx, ctx.assets);
emit({ prompt });
emit({ log: `\n▶ 开始 AI 资产补全:分析并补充设备类型(库内无合适模板时自动创建)…\n` });
emit({ task: "AI 资产补全", status: "running" });
const dt = db.prepare("SELECT dt.*, d.name AS domain_name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.id=?").get(tmplId);
if (!dt) return respondError(res, 404, "领域方案不存在");
const u = new URL(req.url, "http://localhost");
- const ctx = { name: u.searchParams.get("name") ?? dt.name ?? "", domain: u.searchParams.get("domain") ?? dt.domain_name ?? "", description: u.searchParams.get("description") ?? dt.description ?? "", supplement: u.searchParams.get("supplement") ?? dt.supplement ?? "", assets: [] };
- return respondJson(res, 200, { prompt: buildCompleteAssetsPrompt(ctx, ctx.assets) });
+ const ctx = { id: tmplId, name: u.searchParams.get("name") ?? dt.name ?? "", domain: u.searchParams.get("domain") ?? dt.domain_name ?? "", description: u.searchParams.get("description") ?? dt.description ?? "", supplement: u.searchParams.get("supplement") ?? dt.supplement ?? "", assets: [] };
+ return respondJson(res, 200, { prompt: await buildCompleteAssetsPrompt(ctx, ctx.assets) });
}
async function handleDomainCompleteCubes(req, res, tmplId) {
const dt = db.prepare("SELECT dt.*, d.name AS domain_name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.id=?").get(tmplId);
if (!dt) return fail("领域方案不存在");
const body = await parseBody(req).catch(() => ({}));
- const ctx = { name: (body.name ?? dt.name) || "", domain: (body.domain ?? dt.domain_name) || "", description: (body.description ?? dt.description) || "", supplement: (body.supplement ?? dt.supplement) || "", assets: body.assets || [], cubes: body.cubes || [] };
- const prompt = buildCompleteCubesPrompt(ctx);
+ const ctx = { id: tmplId, name: (body.name ?? dt.name) || "", domain: (body.domain ?? dt.domain_name) || "", description: (body.description ?? dt.description) || "", supplement: (body.supplement ?? dt.supplement) || "", assets: body.assets || [], cubes: body.cubes || [] };
+ const prompt = await buildCompleteCubesPrompt(ctx);
emit({ prompt });
emit({ log: `\n▶ 开始 AI Cube 补全:推断并补充 Cube 扩展页面…\n` });
emit({ task: "AI Cube 补全", status: "running" });
const dt = db.prepare("SELECT dt.*, d.name AS domain_name FROM domain_templates dt JOIN domains d ON d.id=dt.domain_id WHERE dt.id=?").get(tmplId);
if (!dt) return respondError(res, 404, "领域方案不存在");
const u = new URL(req.url, "http://localhost");
- const ctx = { name: u.searchParams.get("name") ?? dt.name ?? "", domain: u.searchParams.get("domain") ?? dt.domain_name ?? "", description: u.searchParams.get("description") ?? dt.description ?? "", supplement: u.searchParams.get("supplement") ?? dt.supplement ?? "", assets: [], cubes: [] };
- return respondJson(res, 200, { prompt: buildCompleteCubesPrompt(ctx) });
+ const ctx = { id: tmplId, name: u.searchParams.get("name") ?? dt.name ?? "", domain: u.searchParams.get("domain") ?? dt.domain_name ?? "", description: u.searchParams.get("description") ?? dt.description ?? "", supplement: u.searchParams.get("supplement") ?? dt.supplement ?? "", assets: [], cubes: [] };
+ return respondJson(res, 200, { prompt: await buildCompleteCubesPrompt(ctx) });
}
async function handleDomainPageGenerate(req, res, tmplId, pageId) {