From b0af9f67256b4b9c93f413e7c57cf9869f043028 Mon Sep 17 00:00:00 2001 From: "wang.yu" Date: Tue, 18 Aug 2026 11:29:55 +0800 Subject: [PATCH] =?utf8?q?=E5=8A=9F=E8=83=BD=E4=B8=8E=E8=8F=9C=E5=8D=95?= =?utf8?q?=E9=A1=B5=E6=94=B9=E9=80=A0=EF=BC=9AMU=E6=A0=91=E7=8A=B6?= =?utf8?q?=E5=85=A8=E9=80=89=E3=80=81Cube=20AI=E8=A1=A5=E5=85=A8=E3=80=81?= =?utf8?q?=E8=8F=9C=E5=8D=95=E5=8F=AA=E8=AF=BB=E9=A2=84=E8=A7=88=EF=BC=8C?= =?utf8?q?=E5=B9=B6=E7=B2=BE=E7=AE=80MU=E6=A0=87=E5=87=86=E9=A1=B5?= =?utf8?q?=E4=B8=BA9=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- app/globals.css | 10 ++- app/page.tsx | 141 ++++++++++++++++++++++++++++++----------- db-sidecar.mjs | 134 +++++++++++++++++++++++++++++++-------- public/design-doc.html | 6 +- 4 files changed, 226 insertions(+), 65 deletions(-) diff --git a/app/globals.css b/app/globals.css index 49e8d15..beee644 100644 --- a/app/globals.css +++ b/app/globals.css @@ -98,7 +98,7 @@ /* ===== Functions 功能与菜单 ===== */ .mu-tree{display:flex;flex-direction:column;gap:14px} .mu-group>b{display:block;color:var(--blue2);font-size:13px;margin-bottom:6px} -.mu-items{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;padding-left:4px} +.mu-items{display:flex;flex-direction:column;gap:6px;padding:6px 0 4px 18px;border-left:1px dashed var(--line);margin-left:6px} .mu-items label{display:flex;align-items:flex-start;gap:7px;border:1px solid var(--line);border-radius:5px;padding:7px 9px;background:#fff;cursor:pointer;font-size:12px} .mu-items label:hover{border-color:#9ed3ec;background:#f4fafe} .mu-items label.disabled{opacity:.5;cursor:not-allowed;background:#f6f7f8} @@ -117,6 +117,14 @@ .menu-row input,.menu-row select{border:1px solid var(--line);border-radius:3px;height:30px;padding:0 8px;background:#fff;width:100%} .menu-row .trash{border:1px solid #f0c4bd;background:#fff;color:var(--red);border-radius:4px;height:30px;width:34px;font-size:15px} .menu-row .trash:hover{background:#fdeae6;border-color:var(--red)} +.menu-preview .menu-tree{border:1px solid var(--line);background:#fff} +.menu-preview .menu-root{padding:10px 12px;font-weight:bold;border-bottom:1px solid var(--line);background:#fafbfc} +.menu-preview .menu-section{padding:10px 12px;border-bottom:1px solid #eee} +.menu-preview .menu-section:last-child{border-bottom:0} +.menu-preview .menu-section-title{font-size:12px;color:var(--blue2);font-weight:bold;margin-bottom:7px} +.menu-preview .menu-leaf{padding:5px 0 5px 16px;font-size:12px;color:#555;border-left:2px solid #e1e7ea;margin:3px 0 3px 4px} +.menu-preview .menu-leaf.cube{border-left-color:#c9b8f0} +.menu-preview .menu-leaf.muted{color:#aaa;border-left-color:#eee} /* ===== Generate 预览 line 面板 ===== */ .line-panel{background:#fff;border:1px solid var(--line);border-radius:6px;padding:11px 13px;display:flex;flex-direction:column;gap:8px} diff --git a/app/page.tsx b/app/page.tsx index d7e1627..1ac5497 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -218,6 +218,15 @@ export default function Home(){ setModal("completePrompt"); }catch(e:any){ notify("读取提示词失败: "+e.message); } }; + const viewCompleteCubesPrompt=async()=>{ + if(!site?.id) { notify("请先创建或打开一个站点"); return; } + try{ + const qs=new URLSearchParams({name:site.name||"",domain:site.domain||"",description:site.description||"",supplement:site.supplement||""}).toString(); + const data:{prompt:string}=await fetchDb(`/sites/${site.id}/complete-cubes-prompt?${qs}`); + setPromptData({name:"AI Cube 页面补全", text:data.prompt}); + setModal("completePrompt"); + }catch(e:any){ notify("读取提示词失败: "+e.message); } + }; const openFeedback=(cube:Cube)=>{ if(!cube.id){ notify("请先生成该页面再提出修改意见"); return; } setFeedbackCube(cube); @@ -450,7 +459,7 @@ export default function Home(){ {page==="styleLibrary"&&} {page==="basic"&&} {page==="assets"&&} - {page==="functions"&&} + {page==="functions"&&} {page==="generate"&&} {page==="result"&&} {page==="preview"&&} @@ -653,9 +662,8 @@ function Assets({site,onChange,saving,notify,onViewCompletePrompt}:{site:SiteDet ; } -function Functions({site,muTree,onMuChange,onSiteChange,pageStyles,notify}:{site:SiteDetail|null;muTree:MuNode[];onMuChange:(s:string[])=>void;onSiteChange:(s:SiteDetail)=>void;pageStyles:PageStyle[];notify:(m:string)=>void}){ +function Functions({site,muTree,onMuChange,onSiteChange,pageStyles,notify,onViewCompletePrompt}:{site:SiteDetail|null;muTree:MuNode[];onMuChange:(s:string[])=>void;onSiteChange:(s:SiteDetail)=>void;pageStyles:PageStyle[];notify:(m:string)=>void;onViewCompletePrompt:()=>void}){ const [tab,setTab]=useState<"mu"|"cube"|"menu">("mu"); - const selectedMu = site?.mu || []; const leaves = useMemo(()=>buildMuLeaves(muTree),[muTree]); const grouped = useMemo(()=>{ const map = new Map(); @@ -665,9 +673,14 @@ function Functions({site,muTree,onMuChange,onSiteChange,pageStyles,notify}:{site } return Array.from(map.values()); },[leaves]); + const allMuKeys = useMemo(()=>leaves.map(l=>l.key),[leaves]); + const selectedMu = site?.mu || []; + // 默认全选中:site.mu 为空时按全部叶子键显示,首次勾选才落地保存 + const effSelected = selectedMu.length ? selectedMu : allMuKeys; if(!site) return
加载中…
; const toggleMu=(key:string)=>{ - onMuChange(selectedMu.includes(key)?selectedMu.filter(k=>k!==key):[...selectedMu,key]); + const base = selectedMu.length ? selectedMu : allMuKeys; + onMuChange(base.includes(key)? base.filter(k=>k!==key) : [...base, key]); }; const updateCube=(idx:number, patch:Partial)=>{ const next = site.cubes.map((c,i)=>i===idx?{...c,...patch}:c); @@ -719,44 +732,98 @@ function Functions({site,muTree,onMuChange,onSiteChange,pageStyles,notify}:{site notify(`预览 ${cube.name}`); }; - // menu editor state - const [menu, setMenu] = useState([]); - useEffect(()=>{ setMenu(site.menu.length?site.menu:[{parent_id:null,label:"站点监控",page_type:"",ref_id:"",sort_order:0}]); },[site?.menu]); - const saveMenu=async()=>{ + // ===== AI Cube 页面补全 ===== + const [completing,setCompleting]=useState(false); + const [completeLog,setCompleteLog]=useState(""); + const completeConsoleRef=useRef(null); + useEffect(()=>{ if(completeConsoleRef.current) completeConsoleRef.current.scrollTop=completeConsoleRef.current.scrollHeight; },[completeLog]); + const appendCubes=(items:Array<{name:string;content:string}>)=>{ + const base = site?.cubes || []; + const next = [...base, ...items.map((it,i)=>({name:it.name, content:it.content||"", status:"pending", sort_order: base.length+i}))]; + onSiteChange({...site, cubes:next}); + notify(`AI 已补充 ${items.length} 个 Cube 页面(位于您已填项下方)`); + }; + const runCompleteCubes=async()=>{ + if(!site?.id || completing) return; + setCompleting(true); setCompleteLog(""); + const watchdog=setTimeout(()=>{ setCompleting(false); notify("AI Cube 补全超时(>6 分钟),请重试"); }, 360000); try{ - await fetchDb(`/sites/${site.id}/menu`, {method:"PUT", headers:{"Content-Type":"application/json"}, body:JSON.stringify({menu})}); - onSiteChange({...site, menu}); - notify("菜单结构已保存"); - }catch(e:any){ notify("菜单保存失败: "+e.message); } + const res=await fetch(`/api/db/sites/${site.id}/complete-cubes`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:site.name,domain:site.domain,description:site.description,supplement:site.supplement,assets:site.assets})}); + if(!res.ok||!res.body) throw new Error("HTTP "+res.status); + const reader=res.body.getReader(); const dec=new TextDecoder(); let buf=""; + for(;;){ + const {done,value}=await reader.read(); if(done) break; + buf+=dec.decode(value,{stream:true}); + const lines=buf.split("\n"); buf=lines.pop()||""; + for(const line of lines){ + if(!line.trim()) continue; + try{ + const ev=JSON.parse(line); + if(ev.log) setCompleteLog(prev=>prev+ev.log); + if(Array.isArray(ev.result)) appendCubes(ev.result); + }catch{} + } + } + notify("AI Cube 页面补全已完成"); + }catch(e:any){ notify("AI Cube 页面补全失败:"+(e?.message||e)); } + finally{ clearTimeout(watchdog); setCompleting(false); } }; - return
- {tab==="mu"&&
{grouped.map(g=>
{g.parentMenu}
{g.items.map(item=>)}
)}
} - {tab==="cube"&&
{site.cubes.map((cube,i)=> - - - - - )}
页面名称 最多6字页面内容生成文件操作
updateCube(i,{name:e.target.value})}/>updateCube(i,{content:e.target.value})}/>{cube.status==="generated"? : 未生成}
} - {tab==="cube"&&
-

站点页面样式 站点级 · 全部 Cube 页面共用

-

选择一个样式库方案作为本站点所有 Cube 页面的视觉风格(生成时参考其 JSON 主题参数)。

-
- - {pageStyles.map(s=>)} + return
+ {/* MU 标准页面:树状上下结构,仅保留勾选框,默认全选中 */} + {tab==="mu"&&
{grouped.map(g=>
{g.parentMenu}
{g.items.map(item=>)}
)}
} + {/* Cube 扩展页面:左右两栏,右栏为填写建议 + AI 智能补全 */} + {tab==="cube"&&
+
+
{site.cubes.map((cube,i)=> + + + + + )}
页面名称 最多6字页面内容生成文件操作
updateCube(i,{name:e.target.value})}/>updateCube(i,{content:e.target.value})}/>{cube.status==="generated"? : 未生成}
+
+

站点页面样式 站点级 · 全部 Cube 页面共用

+

选择一个样式库方案作为本站点所有 Cube 页面的视觉风格(生成时参考其 JSON 主题参数)。

+
+ + {pageStyles.map(s=>)} +
+
+
+
+
  • 从站点业务与设备资产出发,规划需要的监控维度(综合总览、能耗能效、设备健康、报警事件、环境监测、生产产量等)。
  • 每个 Cube 页面写明:页面目标、关键指标/图表、数据来源与模拟数据构造思路。
  • 页面名称简洁,不超过 6 个汉字。
  • AI 补充的页面会追加在您已创建页面下方,不会改动您原有的内容。
+ +

基于站点名称 / 领域 / 描述 / 补充要求,以及设备资产层级,由 AI 分析并补充站点所需的 Cube 扩展页面(页面名称 + 详细内容),追加在您已建页面下方。

+
+ + +
+ {(completing||completeLog)&&
AI 智能补全
{completeLog||"(等待 AI 分析…)"}
} +
+
+
} + {/* 菜单结构:只读默认预览(MU 在上,Cube 全部归入「综合监控」一级菜单) */} + {tab==="menu"&&
+

默认菜单结构(展示用,无需编辑):MU 标准页面在上,所有 Cube 扩展页面归入「综合监控」一级菜单下。

+
+
⌄ {site.name || "未命名站点"}
+
+
MU 标准页面
+ {leaves.filter(l=>effSelected.includes(l.key)).map(l=>
· {l.parentMenu}/{l.label}
)} + {leaves.filter(l=>effSelected.includes(l.key)).length===0&&
(未选择 MU 页面)
} +
+
+
综合监控
+ {site.cubes.filter(c=>c.name&&c.name.trim()).map(c=>
· {c.name}
)} + {site.cubes.filter(c=>c.name&&c.name.trim()).length===0&&
(暂无 Cube 页面)
} +
} - {tab==="menu"&&

配置站点菜单结构,可选择引用 MU 标准页或 Cube 扩展页。

{menu.map((m,i)=>
{const next=[...menu]; next[i]={...next[i],label:e.target.value}; setMenu(next);}}/> - - {m.page_type==="mu"&&} - {m.page_type==="cube"&&} - -
)}
}
; } diff --git a/db-sidecar.mjs b/db-sidecar.mjs index 1da3dc2..d18f8de 100644 --- a/db-sidecar.mjs +++ b/db-sidecar.mjs @@ -38,37 +38,15 @@ const DEFAULT_DOMAINS = [ const MU_TREE = [ { key: "设备监控", - children: [ - { key: "设备总览", tabs: ["列表式", "卡片式"] }, - { - key: "设备详情", - tabs: [ - "设备工况", "告警记录", - { label: "维修记录", disabled: true, note: "第一阶段暂不开发" }, - { label: "保养记录", disabled: true, note: "第一阶段暂不开发" }, - { label: "巡检记录", disabled: true, note: "第一阶段暂不开发" }, - "上下线记录", "设备文档", "区域监控", - { label: "设备视频", disabled: true, note: "第一阶段暂不开发" }, - "远程快速控制", "远程控制计划", "设备轨迹", - ], - }, - ], + tabs: ["设备总览", "设备详情"], }, { key: "数据分析", - children: [ - { key: "数据分析" }, - { key: "报表分析" }, - { key: "用量分析" }, - ], + tabs: ["数据分析", "报表分析", "用量分析"], }, { key: "智能控制", - children: [ - { key: "远程控制计划" }, - { key: "执行记录" }, - { key: "控制模板" }, - ], + tabs: ["远程控制计划", "执行记录", "控制模板"], }, ]; @@ -1011,6 +989,108 @@ async function handleRefineCube(req, res, siteId, cubeId) { return respondJson(res, 200, { prompt: buildCompleteAssetsPrompt(ctx, ctx.assets) }); } + // ===== AI Cube 页面补全:基于站点信息 + 资产层级,推断并补充 Cube 扩展页面 ===== + 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 扩展页)。 +请根据以下信息,推断该站点监控大屏所需的 Cube 扩展页面,并为每个页面撰写详尽的页面内容说明,用于补充「Cube 扩展页面」列表(位于用户已建页面下方)。 + +站点信息: +- 站点名称:${site.name || "未命名站点"} +- 所属领域:${site.domain || "未指定"} +- 站点基本情况描述:${site.description || "无"} +- 补充要求:${site.supplement || "无"} +- 设备资产层级(站点 → 设备类型 → 实例): +${assetText} +- 用户已手工创建的 Cube 页面(请勿重复、不要修改,仅补充新增):${existingText} + +要求: +1. 先简要说明分析思路(思考过程):从站点业务与设备资产出发,推断需要哪些监控维度(如综合总览、能耗能效、设备健康、报警事件、环境监测、生产产量等),以及每个页面应覆盖的核心内容。 +2. 思考过程结束后,另起一段,以「【Cube页面】」开头,给出 JSON 数组(仅数组本身,不要多余文字与代码围栏),每个元素为 {"name":"页面名称(最多6个汉字)","content":"页面内容详述"}。 +3. content 需详实,至少覆盖:① 页面目标;② 关键展示要素(图表/指标卡/模块);③ 数据范围(来自哪些设备资产);④ 模拟数据构造思路(指标量级、单位、典型值)。 +4. 仅补充用户尚未创建的页面;页面名称简洁且不超过 6 个汉字。 + +请直接输出你的分析与【Cube页面】JSON。`; + } + + function extractCubes(text) { + const marker = "【Cube页面】"; + const idx = text.lastIndexOf(marker); + let body = idx >= 0 ? text.slice(idx + marker.length) : text; + body = body.replace(/```(?:json)?/gi, "").trim(); + const s = body.indexOf("["); + const e = body.lastIndexOf("]"); + if (s < 0 || e <= s) throw new Error("未在输出中找到 Cube 页面 JSON"); + const arr = JSON.parse(body.slice(s, e + 1)); + if (!Array.isArray(arr)) throw new Error("Cube 页面不是数组"); + return arr.filter(x => x && x.name).map(x => ({ + name: String(x.name).trim().slice(0, 6), + content: x.content != null ? String(x.content).trim() : "", + })); + } + + async function handleCompleteCubes(req, res, siteId) { + res.writeHead(200, { "Content-Type": "application/x-ndjson; charset=utf-8", "Cache-Control": "no-cache" }); + const emit = (obj) => streamEvent(res, obj); + const fail = (message) => { emit({ task: "系统", status: "failed", message }); res.end(); }; + if (genLocks.has(`complete-cubes-${siteId}`)) return fail("AI Cube 补全正在进行中"); + genLocks.add(`complete-cubes-${siteId}`); + try { + const site = hydrateSite(siteId); + if (!site) return fail("站点不存在"); + const body = await parseBody(req).catch(() => ({})); + const ctx = { + name: (body.name ?? site.name) || "", + domain: (body.domain ?? site.domain) || "", + description: (body.description ?? site.description) || "", + supplement: (body.supplement ?? site.supplement) || "", + assets: body.assets || site.assets || [], + cubes: site.cubes || [], + }; + const prompt = buildCompleteCubesPrompt(ctx); + emit({ prompt }); + emit({ log: `\n▶ 开始 AI Cube 补全:基于站点信息与设备资产,推断并补充 Cube 扩展页面…\n` }); + emit({ task: "AI Cube 补全", status: "running" }); + const onEvent = (o) => { + if (o && o.type === "stream_event" && o.event?.type === "content_block_delta" && o.event.delta?.type === "text_delta" && o.event.delta.text) { + emit({ log: o.event.delta.text }); + } + }; + const text = await runCodebuddy(prompt, `stationbuilder-complete-cubes-${siteId}`, onEvent); + const items = extractCubes(text); + emit({ log: `\n✓ 已生成 ${items.length} 个 Cube 页面\n` }); + emit({ result: items }); + emit({ task: "AI Cube 补全", status: "done" }); + res.end(); + } catch (e) { + emit({ log: `✗ AI Cube 补全失败:${e.message}\n` }); + emit({ task: "系统", status: "failed", message: e.message }); + res.end(); + } finally { + genLocks.delete(`complete-cubes-${siteId}`); + } + } + + async function handleCompleteCubesPrompt(req, res, siteId) { + const site = hydrateSite(siteId); + if (!site) return respondError(res, 404, "站点不存在"); + const u = new URL(req.url, "http://localhost"); + const ctx = { + name: u.searchParams.get("name") ?? site.name ?? "", + domain: u.searchParams.get("domain") ?? site.domain ?? "", + description: u.searchParams.get("description") ?? site.description ?? "", + supplement: u.searchParams.get("supplement") ?? site.supplement ?? "", + assets: site.assets || [], + cubes: site.cubes || [], + }; + return respondJson(res, 200, { prompt: buildCompleteCubesPrompt(ctx) }); + } + 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}`)) { @@ -1230,6 +1310,8 @@ async function route(req, res) { const mSiteCompletePrompt = path.match(/^\/api\/sites\/(\d+)\/complete-prompt$/); const mSiteCompleteAssets = path.match(/^\/api\/sites\/(\d+)\/complete-assets$/); const mSiteCompleteAssetsPrompt = path.match(/^\/api\/sites\/(\d+)\/complete-assets-prompt$/); + const mSiteCompleteCubes = path.match(/^\/api\/sites\/(\d+)\/complete-cubes$/); + const mSiteCompleteCubesPrompt = path.match(/^\/api\/sites\/(\d+)\/complete-cubes-prompt$/); const mCubePromptNew = path.match(/^\/api\/sites\/(\d+)\/cubes\/prompt$/); const mSiteMenu = path.match(/^\/api\/sites\/(\d+)\/menu$/); const mSiteFiles = path.match(/^\/api\/sites\/(\d+)\/files$/); @@ -1375,6 +1457,8 @@ async function route(req, res) { if (mSiteCompletePrompt) return handleCompletePrompt(req, res, Number(mSiteCompletePrompt[1])); if (mSiteCompleteAssets) return handleCompleteAssets(req, res, Number(mSiteCompleteAssets[1])); if (mSiteCompleteAssetsPrompt) return handleCompleteAssetsPrompt(req, res, Number(mSiteCompleteAssetsPrompt[1])); + if (mSiteCompleteCubes) return handleCompleteCubes(req, res, Number(mSiteCompleteCubes[1])); + if (mSiteCompleteCubesPrompt) return handleCompleteCubesPrompt(req, res, Number(mSiteCompleteCubesPrompt[1])); if (mSiteCubes) { const siteId = Number(mSiteCubes[1]); if (req.method === "GET") return respondJson(res, 200, db.prepare("SELECT c.*, c.prompt, gf.filename as gen_filename FROM cubes c LEFT JOIN gen_files gf ON gf.id=c.gen_file_id WHERE c.site_id=? ORDER BY c.sort_order,c.id").all(siteId)); diff --git a/public/design-doc.html b/public/design-doc.html index a523497..893c0d9 100644 --- a/public/design-doc.html +++ b/public/design-doc.html @@ -215,11 +215,12 @@

三个子页签:

- + - + +
元素功能数据来源交互效果
MU 标准页面(复选)选择标准页面GET /api/db/mu-tree勾选即保存(PUT)
MU 标准页面(树状复选)树状上下结构列出标准页面,仅保留勾选框GET /api/db/mu-tree默认全选中(site.mu 为空时按全部叶子键显示,首次取消才落地保存);勾选即保存(PUT)
Cube 扩展页面表格(名称/内容/生成文件/操作)定义并管理扩展页面GET/POST/PUT/DELETE /api/db/sites/:id/cubes行内编辑、生成、保存、删除、下载;可添加页面
「生成」单页按钮单独生成某个 CubePOST /api/db/sites/:id/cubes/:cid/generate触发生成(该路径为非流式,无实时日志) 与生成确认页不同
站点页面样式选择器为全站 Cube 套用视觉样式GET /api/db/page-styles;PUT /api/db/sites选择样书后应用到站点(写入 style_id),生成时参考其主题
菜单结构编辑器配置站点菜单(引用 MU / Cube)PUT /api/db/sites/:id/menu增删菜单项、选择页面类型与引用;保存菜单
Cube「AI 智能补全」面板基于站点名称 / 领域 / 描述 / 补充要求 + 设备资产层级,由 AI 推断并补充 Cube 扩展页面(页面名称 + 详细内容)POST /api/db/sites/:id/complete-cubes(流式 NDJSON);GET .../complete-cubes-prompt 取提示词「开始补全」后控制台流式输出思考过程,结束将 AI 页面追加到用户已建页面下方(不改动用户内容)并自动保存;「查看提示词」可查看完整组合提示词
菜单结构(只读默认预览)展示默认菜单结构本地计算(MU + Cube)MU 标准页面在上;所有 Cube 页面归入「综合监控」一级菜单,二级为各 Cube 页面名称;本页仅为展示,无编辑/保存 默认结构

5.6 生成确认(generate)

@@ -344,6 +345,7 @@
  • 2026-08-18 修复「下载 Markdown」导入 Azure DevOps Wiki 后仅显示到 §4.2 的问题:代码块改以 4 空格缩进式代码块导出(不再用 ``` 围栏),避免围栏闭合异常吞掉后续章节;表格单元格内将 < 与 > 转义为 HTML 实体,避免 Azure 把 <select> 当成未闭合标签吞掉后续内容。
  • 2026-08-18 新增「AI 智能补全」功能:站点基本属性页「填写建议」下方新增面板,点击「开始补全」后基于站点名称 / 所属领域 / 站点基本情况描述,由 AI 流式分析并自动写入「补充要求」字段;「查看提示词」可查看完整组合提示词(后端 POST /api/db/sites/:id/complete 流式、GET .../complete-prompt 取提示词)。
  • 2026-08-18 扩展「AI 智能补全」至 Asset 资产设置页:右侧新增与基本属性同款的「填写建议 + AI 智能补全」栏;点击「开始补全」后基于站点名称 / 领域 / 描述 / 补充要求分析并补充设备类型、数量预设与命名规则,追加到用户已填项下方(不改动已有项),资产层级预览随之重算(后端 POST /api/db/sites/:id/complete-assets 流式、GET .../complete-assets-prompt 取提示词)。
  • +
  • 2026-08-18 重构「功能与菜单」页:① MU 标准页面改为树状上下结构、仅保留勾选框、默认全选中;② Cube 扩展页面右侧新增「填写建议 + AI 智能补全」栏,点击「开始补全」后基于站点属性与设备资产层级由 AI 推断并补充 Cube 扩展页面(名称 + 详尽内容),追加到用户已建页面下方(不改动用户内容)(后端 POST /api/db/sites/:id/complete-cubes 流式、GET .../complete-cubes-prompt 取提示词);③ 菜单结构改为只读默认预览:MU 页面在上,所有 Cube 页面归入「综合监控」一级菜单、二级为各 Cube 名称,仅展示无编辑。
    • -- 2.43.0