MoUI Studio 是一个中英双语的可视化编程环境:同一份程序有三种同源视图—— 易语言式的窗体设计器、Scratch 式的积木编排、中英双关键字的 代码编辑器。自然语言交给 AI,但它只能产出结构化提案(可预览、 可 diff、可拒绝),结构上不存在"生成任意代码直接运行"的路径。产物可以 一键导出为独立 MoUI 应用(Web / macOS 双端)。
git clone https://github.com/wzzc-dev/moui_studio.git
cd moui_studio
moon build web_wasm --target wasm-gc
# ES module 不能用 file:// 打开,需要起一个静态服务:
python3 -m http.server 8766 --directory web_wasm
# 浏览器打开 http://127.0.0.1:8766 即见 IDEweb_wasm/index.html 会自己探测 _build/ 下的 wasm 产物,debug 与
--release 两种 profile 都能直接用。
macOS 原生入口:moon run macos_skia --target native
┌─ 无边框顶带(仅 native 沉浸式)────────────────────────────────┐
│ [系统信号灯] 按住此条拖动窗口 │
├─ 顶栏 ─────────────────────────────────────────────────────────┤
│ 运行 · 单步 · 编译 MoonBit 撤销/重做 · 语言 · 搜索 · 设置 │
├────┬──────────┬────────────────────────────┬──────────────────┤
│图标│ 左栏 │ 中心工作区 │ 右栏 │
│栏 │ 程序结构 │ 实时应用 │ 设计态 │ 属性/状态/数据/ │
│ │ 本地数据 │ ──────────────────────── │ 动作/依赖 │
│ │ ──────── │ 积木(分类 · 选中描边) │ ──────────────── │
│ │ 最近修改 │ ──────────────────────── │ 编辑积木(选中时)│
│ │ │ 代码(行号 ┼ IR 映射竖条) │ 快速操作 │
│ │ │ │ 运行时监视 │
├────┴──────────┴────────────────────────────┴──────────────────┤
│ 运行审计 │ Console warn 0 · err 0 │
└───────────────────────────────────────────────────────────────┘
- 切语言:顶栏
English/中文(代码与界面一起切换:如果…则…结束↔if…then…end) - 换模板:空舞台按钮(问候 / 点名册 / 空白程序)或
⌘K命令面板 (四模板 + 新建空白程序 + 打开/保存/导出)——教案里的口算训练营、 班级小卖部从命令面板可达 - 工作区:图标栏切换可视化 / 代码 / 空舞台;再点当前项收起成空舞台
- 积木视图:画布左侧是竖排分类轨(色点 + 名称,选中项带强调边)——分类 不占画布纵向空间;画布支持同层拖拽重排;窗格脚注说明画布底部是为 AI 层 预留的避让通道;长语句按块宽以省略号收尾。 语句在右栏改(IDEA 式「选中在画布、编辑在检查器」):选中一块积木后 右栏顶部出现「编辑积木」卡(种类徽章 + 语句输入 + 应用/复制/删除), 链条永远是一条连续的链——不再因选中被切成三段。旧版把编辑卡嵌在链条 中间,块一多上下两段就只剩几行,读到的现象是「点积木后内容显示不全」
- 代码视图:左侧行号 + 代码,右侧 IR 映射竖条;悬停代码行或色块即 双向高亮对应路径(离开还原,与点击的驻留高亮互不干扰)(按行号对齐的分类色块, 点一下高亮对应积木;与「点代码行高亮积木」是同一状态的两个入口)
- 左栏:程序结构树(可搜索、可折叠)、本地数据(从 IR 派生的文件名/表头预览)、
最近修改(区分「你」与「Agent」)
- 创建入口在各分组头的
+(不是面板底部的全局按钮块):窗体 / 控件 / 变量 / 事件子程序 四组有+,点开是内联菜单;本地数据没有——它的 条目从列表/表格控件的行派生,没有独立创建语义,所以不给一个点了没用的 按钮。创建入口贴着它创建的东西,与右栏「动作」页签建子程序、积木分类轨 建语句是同一范式 - 控件组的菜单与
ControlKind枚举等长:按钮 / 标签 / 输入框 / 选择框 / 列表框 / 表格 / 下拉框 / 进度条 / 图片。变量此前没有任何新建入口(只来自 AI 提案或手打代码), 现在变量组有「+ 新建变量」,改名会同步改写所有引用它的语句 - 树上的每一类行都可点选:窗体 → 窗体属性(标题可改)、控件 → 控件属性、 事件子程序 → 代码/积木、变量 → 变量属性(改名/初值/删除)、数据条目 → 只读来源与内容预览。选中是单选:点一处会清掉其他两条槽 (控件 / 子程序 / 非控件对象),右栏永远只显示当前那一个对象的属性
- 创建入口在各分组头的
- 沉浸式(native):
macos_skia入口开transparent_titlebar—— 系统标题栏透明、 内容延伸到窗口顶端;系统信号灯照常可用(关闭/最小化/缩放都是真的,app 不 重画它们),app 只让出左侧 78pt 并把这条顶带登记为拖拽区。Web 恒关闭 - 右栏:属性/状态/数据/动作/依赖五个页签;选中积木时页签体顶部是编辑积木卡
(见上);快速操作是设计稿口径的单选控件对齐
(左
x=24、居中x=(640-w)/2、右x=640-w-24)+ 复制 + 删除 + AI 优化; 运行时监视列出被监视变量(未运行时显示空态) - 底栏:运行审计时间线(编译 / 启动 / 副作用 / 错误,副作用卡就地确认) 与 Console 原始日志两页签;点审计条目可跳到对应积木
- 命令面板:
⌘K过滤 Studio 命令(每条都是既有消息的封装,不引入新状态通道) - AI 浮动层:默认居中贴底,可切左/右/停靠右栏,可收成药丸; 浮动时自动让出避让带,不会压住窗格内容
- AI 会话线程:每次生成提案推一张 diff 线程卡(事件驱动,不是摆设),
折叠条显示「Agent 待审计 N」,采纳/拒绝后卡片标记已应用/已拒绝;
线程超过 3 条给「查看全部会话 →」直达 AI 历史;收成药丸后药丸本身显示
Agent 待审计 N——收起状态下这是唯一的待办读数 - AI 输入条工具行:锚点读数(底部居中 / 左下角 / 右下角 / 停靠 · Context 侧栏)
- 切换锚点 / 停靠右栏 / 收起三个带名字的按钮(停靠态自动改显「浮出」)
- AI 输入条:模式段 修改 / 生成 / 解释 / 修复 决定「发送」走哪条既有通道 (解释=本地确定性讲解、修复=草稿回到 IR 规范渲染、生成=清空后走同一条提案 校验链、修改=直接提案)——模式只改入口路由,不新增执行路径;右侧是发送
- 空舞台(三个工作区都收起)时输入框升格为 hero:加宽居中偏上,并列出三条 示例指令;点击示例只把文字填进输入框,按不按发送由你决定
- 运行:点「运行」,按钮真的能点;
提交数据会让程序停下等你确认。 运行页里的舞台控件逐个事件真的会重新执行:随机数每次给出新值 (种子跨事件连续推进),所以连点同一个按钮每次都能看到不同结果—— 两轨(解释/真编译)从同一个种子起算,逐位可比 - 停止:点「停止」回到可视化工作区继续编辑(不是停在空白运行页)
- 真编译:点「编译 MoonBit」在
TMPDIR/moui_studio-compile/<app>里真跑moon update / check / build,七步进度逐格可见(准备工作目录 → 解析依赖 → 类型检查 → 构建 Web 产物 → 构建 native 产物 → 启动产物自检 → 收集产物, 已完成点满、进行中高亮);产物是磁盘上的真实文件 - 产物卡:native GUI 应用(可执行窗口程序)排在第一位,带
运行产物(一键开窗)/ 复制路径 / 在文件夹中打开 三个动作;
长路径做中间省略(保头保尾,文件名永远读得到)并给出真实字节数。
native_smoke是无头启动自检(打印标记就退出,不开窗),单独标注, 不会被误当成要运行的应用 - 「运行产物」是脱离式启动:进程 reparent 到系统 init,Studio 退出或 被关掉都不会带走正在用的导出应用(实测:杀掉 Studio 后窗口仍在)
- 导出应用视口自适应:窗口按项目声明尺寸开,舞台在可用空间内等比缩放
(
fit_scale,下限 0.4),所以任何窗口尺寸下设计稿都完整可见、不会被裁。 首屏是用户设计的表单 + 一行状态条,变量/消息/审计收进默认收起的 「变量与日志」面板(导出的是应用,不是调试台)
| 样例 | 教什么 | 教案 |
|---|---|---|
| 问候小程序 | 三视图与运行闭环(最小起点) | samples/hello/README.md |
| 班级点名册 | 随机数、列表框取行 | samples/roll_call/README.md |
| 口算训练营 | 分支、变量、算术 | samples/drill/README.md |
| 班级小卖部 | 跨事件状态、外发闸门 | samples/store/README.md |
- AI 恒走真实 OpenAI 兼容 provider:没有离线假模型。未配置密钥时 composer 下方给出接通引导(不弹假回复);endpoint/model/密钥在设置面板里热改。
- 真实模型默认指向阶跃星辰(
https://api.stepfun.com/step_plan/v1,step-5-preview)。 - 凭据放在 gitignored 的
moui_studio/.config.json(可直接在 json 里换 key):
{ "provider": { "endpoint": "...", "model": "...", "api_key": "..." } }- Web 入口(浏览器)没有 HTTP 通道:AI 输入面显式降级为「需要 native 版本」 引导,不出假按钮。
- 新建示例项目 = 落盘本地文件夹(空舞台按钮 / ⌘K 的模板与「新建」命令):
选一个空文件夹,Studio 写入
moon.mod+ 可编译的 MoonBit 应用骨架 (内嵌领域内核,moon build即过)+.studio/program.jsonIR 快照。 关掉 Studio 再打开时激活该工程即可恢复程序,AI 可以继续在上面改 (比如「把按钮字号调大」——提案采纳后 handlers.mbt 与内嵌 IR 直接 写回工程文件夹)。
- DSL 规范(双语关键字、语句、内建、预算):docs/dsl-spec.md
- 程序 IR 与
.studio.json格式:docs/ir-schema.md
domain/ir/ 程序 IR + 不变量 + 预算(零依赖)
domain/studio_lang/ 双语 DSL:关键字表/词法/解析/打印/指令机解释器
domain/codec/ .studio.json 编解码 + 版本门
domain/proposals/ AI 提案契约 + 校验链 + diff + 内置模板样例
domain/blocks/ 语句 → 积木规格表
kernel/ 插件内核:服务容器 / 事件总线 / 贡献点
kernel/plugin_contract/ WASM 插件契约(清单 / 视图协议 / 宿主 API 面)
chrome/ 共享视觉原子(主题 + 尺寸常量 + ChipTone)
plugins/blocks/ 积木几何 + 绘制计划 + 洞/值命中(零 Model/Msg)
plugins/project/ 工程域:行模型 + ProjectState + ProjectMsg
plugins/run/ 预览数据层 + 运行态结构 + 预览视图贡献
plugins/ai/ AI 会话切片 + 几何/视图贡献
plugins/markdown/ plugins/terminal/
services/export/ 独立应用导出(内核快照 + bundle 生成)
services/model_provider/ OpenAI 兼容协议(纯)
services/provider_native/ native async worker + .config.json 读取
services/plugin_runtime/ WASM 插件运行时(native,wasmoon 宿主 + 配额)
services/plugin_runtime_web/ WASM 插件运行时(web,浏览器原生 wasm)
app/ TEA shell + 组合根 + 壳级组装视图 + 中英 catalog
web_wasm/ macos_skia/ 薄组合根
samples/plugin_fixture/ 最小 WASM 插件(参考实现 + E2E 用)
tools/sync_kernel/ tools/emit_bundle/
Studio 可以加载运行时插件:第三方代码以 WebAssembly 形式被加载, 贡献命令、面板与界面。
- 架构、清单格式、视图协议、宿主 API 白名单、安全边界: docs/plugin-system.md
⚠️ 插件运行时不是安全边界(wasmoon 自述 experimental + insecure)。 只安装可信来源的插件。
本仓库是独立模块(wzzc-dev/moui_studio),包路径不带
moui_studio/ 前缀——那是它作为 MoUI 主仓库 submodule 时的写法。
moon check --target native # 0 错误 0 警告
moon fmt --check
# domain 是多个包(不是单包路径),一次传完
moon test domain/ir domain/studio_lang domain/codec domain/blocks \
domain/proposals --target native
moon test kernel chrome plugins/ai plugins/markdown plugins/project \
plugins/run plugins/terminal plugins/blocks --target native
moon test services/compiled_runtime services/diff services/compile_native \
services/export --target native
moon test app --target native # 474 个测试
moon test app --target wasm-gc
# WASM 插件系统:契约 + 运行时 + E2E(真实加载一个 .wasm 插件)
sh scripts/build_plugin.sh # 先造 fixture 的 wasm 产物
moon test kernel/plugin_contract services/plugin_runtime --target native
# 导出内核漂移门:改了 domain 忘了同步 → 红
moon run tools/sync_kernel --target native -- --check
moon build web_wasm --target wasm-gc
tools/sync_kernel的路径表按母仓库根书写(带moui_studio/前缀), 但工具会按当前根自动解析:在本仓库根跑moon run tools/sync_kernel, 在母仓库根跑moon run moui_studio/tools/sync_kernel,两者等价。
上面这一套就是 .github/workflows/ci.yml 的全部内容——push / PR 自动跑,
单独 clone 本仓库也有回归网。不在本仓库 CI 里的两件事:i18n catalog
生成检查(生成器是母仓库的 tools/moui/generate_i18n_catalogs 工具,漂移由
母仓库 pr profile 把关)与真机 / 真 Skia 冒烟(路径触发,非每日门)。
(中文说明见上。)
MoUI Studio is a bilingual visual programming IDE. One program, three
same-source views: an E-language-style form designer, Scratch-style
typed blocks, and a code editor whose keywords follow the UI language
(如果…则…结束 ↔ if…then…end). AI may only produce structured proposals
(previewable, diffable, rejectable) — there is no path from "generate
arbitrary code" to "run it". Projects export to standalone MoUI apps
(Web wasm-gc / macOS Skia).
Try it in five minutes (Web, zero install):
git clone https://github.com/wzzc-dev/moui_studio.git
cd moui_studio
moon build web_wasm --target wasm-gc- Switch language from the top bar (
English/中文); the DSL keywords switch with it. - New sample project = a real folder on disk: pick an empty folder from
the empty stage or the
⌘Kpalette (all four samples plus a blank program). Studio writesmoon.mod, a compiling MoonBit app skeleton (vendored domain kernel —moon buildpasses), and a.studio/program.jsonIR snapshot. Re-open later by activating the project; the AI can keep editing it (e.g. "make the button font bigger" — accepting the proposal rewriteshandlers.mbtand the embedded IR right in that folder). - AI always uses a real OpenAI-compatible provider — there is no offline fake model. Without a key the composer shows a connect hint; Web has no HTTP channel, so its AI surface degrades to a "needs the native app" note.
- Run: buttons really work, and
submit_datapauses the program until you confirm — nothing is sent before you do.
macOS entry:
moon run macos_skia --target nativeProvider configuration (default StepFun step-5-preview):
Credentials live only in the gitignored moui_studio/.config.json:
{ "provider": { "endpoint": "...", "model": "...", "api_key": "..." } }Specs: DSL spec · IR & .studio.json schema.