尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

OpenMAIC 交互式图示生成器(Interactive Diagram Generator)技术指南:SVG 节点连线、widget-config 数据协议与 postMessage 驱动机制

OpenMAIC 交互式图示生成器(Interactive Diagram Generator)技术指南:SVG 节点连线、widget-config 数据协议与 postMessage 驱动机制 OpenMAIC 交互式图示生成器Interactive Diagram Generator技术指南SVG 节点连线、widget-config 数据协议与 postMessage 驱动机制【免费下载链接】OpenMAICOpen Multi-Agent Interactive Classroom — Get an immersive, multi-agent learning experience in just one click项目地址: https://gitcode.com/GitHub_Trending/op/OpenMAIC本指南以 OpenMAIC 仓库中 diagram-content/system.md 这一系统提示词模板为核心深入拆解交互式图示DiagramWidget 的生成契约从嵌入式 JSON 数据协议、SVG 节点与连线的几何算法到平台通过postMessage驱动 iframe 内 Widget 的四种消息协议并结合源码验证其在实际生成管线中的落地方式。读者将掌握如何让 LLM 生成一份既满足深色高对比、首节点立即可见、移动端可用等硬性要求、又能被课堂播放引擎精确控制高亮、标注、揭晓、状态切换的自包含 HTML 图示。文档定位一份写给生成模型的系统提示词 硬性契约diagram-content是 OpenMAIC 生成包 openmaic/generation 的十余个内容模板之一与slide-content、quiz-content、simulation-content、game-content等并列负责交互式图示这一类 Widget 的内容生成。模板目录内包含两份文件system.md本文主角面向 LLM 的系统提示词规定数据 Schema、核心设计要求、连线几何算法、postMessage消息监听器硬性要求与输出格式user.md面向 LLM 的用户提示词模板注入标题、图示类型、描述、要点、节点数量约束与预设节点等变量。两者经 prompts/loader.ts 中的buildPrompt(promptId, variables)加载与插值后拼装成完整请求。PROMPT_IDS.DIAGRAM_CONTENT在 prompts/index.ts 中登记标识符为diagram-content。loader 的处理顺序是先展开{{snippet:...}}片段再按{{#if hasNodeCount}}这类条件块剔除不满足条件的段落最后做{{variable}}插值——这解释了为何 system.md 中的 JSON 示例不含{{}}占位符而 user.md 中才有{{diagramType}}、{{nodeCount}}等变量。数据协议嵌入 HTML 的widget-configsystem.md 要求生成的 HTML 以script typeapplication/json idwidget-config形式内嵌一份 JSON 配置作为整张图示的数据源{ nodes: [ { id: n1, label: Label, icon: , details: Description } ], edges: [ { from: n1, to: n2, label: next } ], revealOrder: [n1, n2] }字段语义如下字段类型说明nodes数组节点集合。id为唯一标识如n1label为节点主标题icon为 emoji 图标details为点击节点后展示的详情描述edges数组有向边集合。from/to引用nodes中的idlabel为边上的文字标注如nextrevealOrder数组节点逐步揭晓的顺序对应交互中的下一步/上一步按钮这份 JSON 并不只是给模型看的规范——生成后它会被 extractWidgetConfig 用正则从 HTML 中解析出来进入GeneratedInteractiveContent.widgetConfig作为运行时状态和动作定位的依据。解析时若 JSON 中缺少type字段会自动用路由得到的 Widget 类型如diagram补齐若 JSON 无法解析或不是对象则丢弃该配置。这一点在 diagram-node-constraints.test.ts 中有直接的单测覆盖extractWidgetConfig的seeds a missing type/drops non-object config JSON用例。预设节点与节点数量约束除了 system.md 本体user.md 还定义了两种可选的上游约束节点数量约束当hasNodeCount为真时提示词中会出现 Maximum node count: {{nodeCount}}要求未提供预设节点列表时widget-config.nodes不得超过该上限预设节点当hasPrescribedNodes为真时必须每个预设节点恰好使用一次保留其id、label、icon、details不得增删替换若存在parentId则据此推导层级边。从源码看这些变量由 generateWidgetContent 的 diagram 分支 注入diagramType默认flowchartnodeCount缺省取widgetOutline.nodes.lengthhasNodeCount仅在 outline 显式给出大于 0 的nodeCount时为真。对应测试 diagram-node-constraints.test.ts 验证了三种情形同时转发数量与预设节点、仅有数量约束、两者皆无时模板正确省略对应段落且最终提示词中不再残留{{占位符。核心设计要求七条硬性规范system.md 为生成的图示设定了七条必须满足的设计契约它们是判断一次生成合格与否的准绳基于 SVG且内嵌 JSON 配置画布以 SVG 实现配置数据嵌入widget-config脚本块首节点立即可见页面加载时第一个节点必须处于可见状态而非等待揭晓高对比度深色背景上的白色节点、浅色边标签保证课堂大屏与投影场景下的可读性连线贴合节点边缘边必须从节点边缘起止并考虑节点尺寸与箭头偏移量详见下一节算法移动端可用侧边栏/面板可折叠且不得遮挡图示主体无抖动避免点击操作与 hover 变换transform产生冲突导致布局跳动无孤立节点所有节点必须被边连通。第 5、6 条尤其值得注意折叠面板服务于移动端不遮挡图示而no jitter约束了 hover 高亮、点击放大等交互的实现方式——不要用会导致布局重排的margin/width变化而应使用transform: scale()且确保点击时不与 hover 态冲突。连线几何getEdgePoints边缘锚点算法为了让边精确地从源节点边缘出发、在目标节点边缘前收住预留箭头空间system.md 给出了可直接抄用的锚点计算函数const NODE_WIDTH 180, NODE_HEIGHT 70, ARROW_OFFSET 10; function getEdgePoints(from, to) { const dx to.x - from.x, dy to.y - from.y; let sx, sy, ex, ey; if (Math.abs(dy) Math.abs(dx)) { // Vertical sx from.x; sy dy 0 ? from.y NODE_HEIGHT/2 : from.y - NODE_HEIGHT/2; ex to.x; ey dy 0 ? to.y - NODE_HEIGHT/2 - ARROW_OFFSET : to.y NODE_HEIGHT/2 ARROW_OFFSET; } else { // Horizontal sx dx 0 ? from.x NODE_WIDTH/2 : from.x - NODE_WIDTH/2; sy from.y; ex dx 0 ? to.x - NODE_WIDTH/2 - ARROW_OFFSET : to.x NODE_WIDTH/2 ARROW_OFFSET; ey to.y; } return M ${sx} ${sy} L ${ex} ${ey}; }其几何逻辑可拆解为四步方向判定比较dy与dx的绝对值决定走垂直还是水平边。|dy| |dx|时节点在纵向上错开更大边从上下边缘进出否则从左右边缘进出起点锚定起点锚在from节点的边缘中点上——垂直情形取from.y ± NODE_HEIGHT/2dy0说明目标在下取底部水平情形取from.x ± NODE_WIDTH/2终点回退终点同样锚在to节点边缘中点但额外回退ARROW_OFFSET 10像素为箭头标记marker留出空间避免箭头尖端刺入节点内部被遮挡输出路径返回M x1 y1 L x2 y2形式的 SVG path 字符串可直接用于line或path元素。这是一套正方形包围盒 主轴优先的简化连线方案不计算精确到像素的切点而是以主方向上的边缘中点近似代码量小、视觉稳定且天然规避了节点旋转等复杂情形。生成模型在实现自定义布局时可保留该函数作为默认路径也可在保持终点回退箭头偏移这一语义的前提下替换为贝塞尔曲线。关键机制postMessage驱动 Widget 动作REQUIRED这是 system.md 中标注为CRITICAL / REQUIRED的核心部分。平台不直接操作 Widget 内部 DOM而是通过向 iframepostMessage四种消息来驱动 Widget。若生成的 HTML 未注册监听器这些动作将静默无效。四种消息类型及负载消息类型负载字段作用SET_WIDGET_STATEstate{nodeId: boolean}映射按节点 id 批量设置显隐/激活状态值为true时节点opacity:1并加active类false时降为0.35HIGHLIGHT_ELEMENTtargetCSS 选择器为目标元素添加紫色脉冲描边3 秒后自动清除ANNOTATE_ELEMENTtarget、content在目标元素上方弹出教师批注气泡4 秒后自动移除REVEAL_ELEMENTtarget将隐藏元素恢复为可见display:、opacity:1system.md 要求把以下监听器脚本放在 HTML 末尾// Add this script at the end of your HTML window.addEventListener(message, function(event) { const { type, target, state, content } event.data; switch (type) { case SET_WIDGET_STATE: // Apply state keyed by node id. Use the same convention as REVEAL_ELEMENT: // each diagram node is idnode-{id} (matching the JSON node ids). if (state) { Object.entries(state).forEach(([key, value]) { const node document.getElementById(node- key) || document.querySelector([data-node key ]); if (node) { node.style.opacity value ? 1 : 0.35; node.classList.toggle(active, !!value); } }); } break; case HIGHLIGHT_ELEMENT: // Highlight the target element with a pulsing border const highlightEl document.querySelector(target); if (highlightEl) { highlightEl.style.outline 3px solid rgba(139, 92, 246, 0.8); highlightEl.style.outlineOffset 4px; highlightEl.style.animation pulse-highlight 2s infinite; setTimeout(() { highlightEl.style.outline ; highlightEl.style.animation ; }, 3000); } break; case ANNOTATE_ELEMENT: // Show an annotation tooltip near the target element const annotateEl document.querySelector(target); if (annotateEl content) { const rect annotateEl.getBoundingClientRect(); const tooltip document.createElement(div); tooltip.className teacher-annotation; tooltip.style.cssText position:fixed; top: (rect.top - 40) px; left: rect.left px; background:rgba(139,92,246,0.95); color:white; padding:8px 12px; border-radius:8px; font-size:14px; z-index:1000; animation:fadeIn 0.3s;; tooltip.textContent content; document.body.appendChild(tooltip); setTimeout(() tooltip.remove(), 4000); } break; case REVEAL_ELEMENT: // Reveal a hidden node/element const revealEl document.querySelector(target); if (revealEl) { revealEl.style.display ; revealEl.style.opacity 1; } break; } });监听器还需要动态注入一段动画 CSSpulse-highlight脉冲描边关键帧与fadeIn气泡淡入const style document.createElement(style); style.textContent keyframes pulse-highlight { 0%, 100% { outline-color: rgba(139, 92, 246, 0.8); } 50% { outline-color: rgba(139, 92, 246, 0.4); } } keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }; document.head.appendChild(style);实现要点监听器用switch(type)分发SET_WIDGET_STATE的state键与节点 JSONid对齐HIGHLIGHT_ELEMENT、ANNOTATE_ELEMENT、REVEAL_ELEMENT的target是 CSS 选择器可指向任意元素节点、边标签等。元素命名约定可被精确寻址的前提高亮/批注/揭晓要命中具体节点就必须有稳定 id。system.md 明确约定节点组idnode-{id}如idnode-n1与 JSON 中的id严格对应——这正是SET_WIDGET_STATE中getElementById(node- key)能工作的原因边标签若需被定位使用idedge-{from}-{to}。平台侧发送端动作引擎 → iframe从源码可以完整验证这条消息链路的发送端。课堂播放/动作引擎 lib/action/engine.ts 将四种 Widget 动作翻译为对应消息widget_highlight→HIGHLIGHT_ELEMENT携带target、contentwidget_setState→SET_WIDGET_STATE携带state、contentwidget_annotation→ANNOTATE_ELEMENT携带target、contentwidget_reveal→REVEAL_ELEMENT携带target、content。这四个动作名在 DSL 契约 openmaic/dsl/src/action.ts 中注册并在生成侧由 scene-generator.ts 的INTERACTIVE_WIDGET_ACTIONS白名单与 action-parser.ts 解析。特别地action-parser 会对 LLM 漏传state的widget_setState动作自动补state: {}避免SET_WIDGET_STATE监听器解引用undefined时报错——这条守卫在 tests/action/widget-actions.test.ts 与 widget-actions-direct-pipeline.test.ts 中均有断言。播放引擎 lib/playback/engine.ts 将这四个动作作为同步动作串行执行执行完毕才推进下一个动作。消息最终通过 iframe 宿主的 InteractiveIframeHost.tsx 发送宿主为每个场景注册一个send(type, payload)回调以iframeRef.current?.contentWindow?.postMessage({ type, ...payload }, *)发出。该文件还解释了安全模型iframe 的 sandbox 刻意不包含allow-same-origin使嵌入文档处于独立null源LLM 生成的脚本无法触达宿主应用状态而targetOrigin*的 postMessage 在 null 源下依然工作——这正是生成的 HTML 必须注册 message 监听器这一要求的平台侧背景。输出要求一份自包含 HTMLsystem.md 的Output部分对模型给出三条收尾约束恰好返回一份完整的 HTML 文档不要用 markdown 代码围栏no markdown fences包裹不要重复输出。这一要求与生成管线的解析逻辑直接相关generateWidgetContent用extractHtml(response)从模型响应中提取 HTMLscene-generator.ts随后extractWidgetConfig(html, widgetType)解析内嵌widget-configpostProcessInteractiveHtml(html)interactive-post-processor.ts做后处理将$$...$$转为\[...\]、$...$转为\(...\)并向head注入 KaTeXv0.16.9样式、脚本与 auto-render MutationObserver保证图示中的数学公式可渲染。最终产出被封装为{ html, widgetType, widgetConfig }的GeneratedInteractiveContent交由课堂播放阶段的 iframe 承载渲染。在生成管线中的完整调用链将本模板放回整体流程一次图示 Widget的生成调用链可归纳为大纲outline阶段产出widgetType: diagram与widgetOutline含diagramType、nodeCount、可选nodes预设列表见 scene-generator.tsbuildPrompt(PROMPT_IDS.DIAGRAM_CONTENT, variables)依次执行片段展开、条件块裁剪、变量插值拼出 system user 两条提示词prompts/loader.ts调用aiCall(system, user)从响应中提取 HTML解析widget-config注入 KaTeX播放阶段widget_highlight/widget_setState/widget_annotation/widget_reveal动作经动作引擎翻译为HIGHLIGHT_ELEMENT/SET_WIDGET_STATE/ANNOTATE_ELEMENT/REVEAL_ELEMENT消息推入 iframe生成的 HTML 中注册的 message 监听器响应消息完成高亮、批注、揭晓与状态切换。至此从一份系统提示词到课堂上被精确驱动的交互图示整条链路完全闭环。对需要扩展自定义交互 Widget 的开发者而言本模板与上述源码共同构成了提示词契约 数据协议 消息协议 渲染宿主四层参考实现。【免费下载链接】OpenMAICOpen Multi-Agent Interactive Classroom — Get an immersive, multi-agent learning experience in just one click项目地址: https://gitcode.com/GitHub_Trending/op/OpenMAIC创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表