前言
这篇文章,其实是我那篇《玩Android×AI Coding:从产品到代码,完整跑通一套 AI 软件研发工作流》里 2.4「AI 不仅能使用工具,也能创造工具」 的展开篇。
在那一节里,我提到了一件事:
当 AI 已经能参与产品、UX、视觉和工程时,我真正缺的,可能已经不是某个现成工具,而是一个更适合 AI 理解、AI 消费、AI 接力 Coding 的中间层。
于是我没有继续折腾 Figma 的付费能力,也没有继续找 Axure、墨刀的平替。
我直接让 AI 和我一起,做了一个新的工具:
AIWireframeViewer。
它不是设计稿工具,也不是页面生成器。
它是一个 JSON 驱动的低保真原型查看器。
它存在的目的只有一个:
给 AI 一种稳定的中间语言,让原型不再只是"图片"和"描述",而是一套可以被读取、校验、渲染、运行的数据。
1、为什么我要自己造这个工具
当时我已经有了这些东西:
- API 文档
- PRD
- 信息架构
- Wireframe 布局文档
- 视觉规范
- 组件映射思路
按正常流程,下一步似乎应该把这些东西继续塞给现有原型工具,生成一个可交互原型。
但我很快发现,现有工具都不是我真正想要的。
我想要的不是:
- 一个只能给人看的高保真设计稿
- 一个只能手工点击的原型链接
- 一个和代码世界完全脱节的设计文件
我真正想要的是:
一个既能让人看懂页面结构,又能让程序消费页面结构,还能继续交给 AI Coding 往下生成工程实现的中间层。
换句话说,我需要的不是"更漂亮的原型",而是"更可计算的原型"。
这就是 AIWireframeViewer 存在的原因。
1.1 它到底是什么
AIWireframeViewer 目前是一个基于 React + Vite + TypeScript 做的 JSON 驱动低保真原型查看器。
当前 V1 已经具备两种模式:
Editor:看项目工作台、页面排布、页面列表Runtime:从入口页开始运行交互、跳转、回退和历史栈
技术栈也比较克制:
ReactViteTypeScriptReact RouterAJVVitestPlaywright
它不是把页面直接写死在 React 组件里,而是通过这三类数据驱动起来:
project/manifest.jsonwireframes/*.jsonschema/*.json
也就是说,React 在这里更像一个容器。
真正描述"这个原型是什么"的,不是 JSX,而是 JSON。
1.2 我想解决的,不是"渲染页面",而是"定义中间语言"
如果只是想搞一个 Demo,其实很简单:
- 手写 8 个 React 页面
- 用路由拼起来
- 加几个按钮跳转
- 再做个侧边栏和工作台
这样当然也能演示。
但那样做出来的,充其量只是一个"原型长得像原型"的前端项目。
我想做的不是这个。
我更关心三个问题:
- 原型结构能不能脱离代码独立存在?
- 这些结构能不能被校验,而不是随便写个 JSON 就算数?
- 这套结构能不能继续交给 AI,用作后续 Coding 的输入?
所以我一开始没有直接冲着"渲染器"去,而是先定义了一层中间语言。
这个中间语言,最终分成了两层:
Project LayerWireframe Layer
2、为什么要分成 Project Layer 和 Wireframe Layer
这是整个项目里,我最想先稳定下来的边界。
2.1 Project Layer:描述整个项目
Project Layer 负责的是项目级信息,比如:
- 项目标识
- 入口页
- 页面引用列表
- 默认视口
- 编辑器状态
- Schema 路径
- Viewer 兼容信息
它的入口文件是 project/manifest.json。
最小结构大概像这样:
json
{
"projectId": "ai-wireframe-viewer-demo",
"entryPage": "home",
"schema": {
"project": "../schema/project.schema.json",
"wireframe": "../schema/wireframe.schema.json"
},
"pages": [
{
"id": "home",
"title": "首页",
"file": "../wireframes/home.json",
"canvasPosition": {
"x": 0,
"y": 0
}
}
]
}
这里有一个我刻意坚持的约束:
manifest 只保存引用,不复制页面内容。
也就是说,它负责回答的是:
- 这个项目有哪几个页面?
- 入口页是谁?
- 页面文件在哪?
- 工作台初始布局是什么?
而不是:
- 页面里面有什么组件?
- 卡片长什么样?
- 点击后去哪?
这些属于下一层。
2.2 Wireframe Layer:描述单个页面
Wireframe Layer 负责单页内容,比如:
- 页面标识
- 页面视口
- 元素树
- 坐标和尺寸
- 交互动作
- 页面导航摘要
- 元数据
这层数据放在 wireframes/*.json 里。
这样拆开的好处很直接:
- 页面可以被多个项目复用
- 项目级组织信息不会污染页面结构
- 单个页面可以独立校验和加载
- 后面想支持多项目、多入口时,结构不会塌
3、Wireframe 协议是怎么设计出来的
有了 Project Layer,接下来就轮到单页面协议。
我给每个页面定义了一份通用 JSON 结构。V1 没追求面面俱到,而是先满足"低保真原型可表达、可校验、可运行"。
顶层结构大概是这样:
json
{
"schemaVersion": "1.0.0",
"page": {
"id": "home",
"title": "首页",
"level": "primary"
},
"viewport": {
"width": 360,
"height": 800,
"unit": "dp"
},
"canvasPosition": {
"x": 0,
"y": 0
},
"coordinateSpace": "page",
"elements": [],
"navigation": {},
"metadata": {}
}
这里我做了几个比较关键的取舍。
3.1 坐标系统固定为 page
V1 里我明确规定:
coordinateSpace: "page"- 所有元素都用页面绝对坐标
- 即使元素有
children,子元素坐标也仍然相对于页面左上角
这套规则不算"高级",但非常稳定。
因为低保真原型最怕的不是能力不够,而是坐标解释不一致。
我宁愿它朴素一点,也不要一开始就把相对坐标、嵌套坐标、自动布局全揉进去。
3.2 顶层只有两个 Layer
V1 的页面只允许两个根容器:
fixed-layerscroll-layer
它们分别承载:
- 固定区:状态栏、顶部栏、底部导航
- 滚动区:正文内容
这其实是把"页面结构"显式写进协议里,而不是交给渲染器猜。
3.3 name 不再承担显示内容
这一点我后面专门补过协议修订。
我把字段职责拆开了:
name:给编辑器树和调试看text:给text元素显示正文label:给按钮、Tab、导航项、Chip 等短文本组件placeholder:给输入组件提示文案
比如一个元素会长这样:
json
{
"id": "search-submit-text",
"type": "text",
"name": "搜索提交文本",
"text": "搜索",
"x": 300,
"y": 44,
"width": 32,
"height": 24,
"fixed": true,
"scrollable": false,
"visible": true,
"coordinateSpace": "page",
"layout": {},
"style": {},
"children": [],
"interactions": []
}
看起来像小事,但这件事很重要。
因为如果 name 同时承担"节点名称"和"实际显示文案",协议很快就会变成一锅粥。
3.4 交互先收敛,不发散
V1 只支持四类动作:
navigateexternal-placeholderback-to-sourcenone
这个决定也很克制。
因为我此时需要的是"可运行原型",不是"完整状态机平台"。
先把最小交互跑通,比一开始做复杂要更重要。
4、为什么我还要加上 Schema
很多人看到 JSON 驱动,会觉得:
"那不就是写几个 JSON 文件吗?"
问题在于:
能写 JSON,不等于能稳定运行。
如果没有校验,问题会非常多:
entryPage指向一个根本不存在的页面pages[].id重复targetPage写错viewport格式不统一- 路径失效
- 显示字段缺失
- 页面导航摘要和实际交互不一致
所以我在 V1 里做了两层 Schema:
schema/project.schema.jsonschema/wireframe.schema.json
但标准 JSON Schema 还不够,因为很多规则是跨字段的,甚至跨文件的。
所以我又保留了一层补充校验逻辑,专门处理这种问题,比如:
entryPage必须存在于pages[].idpages[].id不能重复pages[].file路径必须能解析schema.project和schema.wireframe路径必须能解析- 页面跳转引用必须能闭环
整个主链路可以概括成这样:

如图所示
Schema 在这里的意义,不是"写给人看的规范",而是:
保证这些原型数据真的能被程序消费。
4.1 从 manifest 到运行时,整个链路是怎么串起来的
这也是为什么我觉得这个项目已经不只是"一个概念"。
当前仓库里,主链路已经真实存在了。
核心模块包括:
loadProjectproject-validatorschema-validatorpage-rendererinteraction-enginenavigatorhistory-stack
其中 loadProject() 干的事,基本上就是整个项目的加载总入口:
ts
export async function loadProject(): Promise<LoadedProject> {
const manifest = await readJsonAsset<ProjectManifest>(manifestPath);
const projectSchema = await readJsonAsset<unknown>(projectSchemaPath);
const wireframeSchema = await readJsonAsset<unknown>(wireframeSchemaPath);
issues.push(...validateProjectManifestSchema(manifest, projectSchema, manifestPath));
issues.push(...validateManifestSupplementalRules(manifest, manifestPath));
for (const manifestPage of manifest.pages) {
const wireframe = await readJsonAsset<Wireframe>(filePath);
issues.push(...validateWireframeSchema(wireframe, wireframeSchema, filePath));
issues.push(...validateWireframeSupplementalRules(loadedPage, manifestPage, manifest.pages));
pages.push(loadedPage);
}
if (issues.length > 0) {
throw new ProjectValidationError(sortIssues(issues));
}
return loadedProject;
}
这段逻辑背后,其实代表了我对这个项目的一个原则:
Runtime 不应该直接吞原始 JSON,而应该只消费 Loader 和 Validator 清洗过的数据。
因为只有这样,后面的渲染器和运行时才不需要一直兜底脏数据。
另外,页面入口也已经非常明确:
/editor/runtime
默认访问 / 会重定向到 /editor。
所以这个项目现在不是"停留在文档层",而是真的具备一条完整的数据消费链路。
4.2 V1 现在已经能做到什么程度
从结果看,V1 已经是可运行状态了。
目前我已经做完这些:
- 支持
Editor工作台模式 - 支持
Runtime原型运行模式 - 内置 8 个示例页面
- 支持页面跳转
- 支持来源页返回
- 支持外部占位目标提示
- 支持历史栈
- 支持
vitest单元测试 - 支持
playwrightE2E 测试
本地跑起来也很简单:
bash
pnpm install
pnpm dev
我比较满意的一点是:
这套东西已经不是"拿来展示 JSON 结构"的。
它是真的可以把一套原型数据跑成一个可交互系统。
5、AI 在这里到底扮演什么角色
虽然项目名里有 AI,但我一直不想把这个项目讲成"AI 自动生成页面代码"的故事。
我更认同的定位是:
AI 在这里参与生成结构化原型数据,而不是直接输出最终业务界面。
这条链路更像是:
布局文档 -> Prompt -> 单页 Wireframe JSON -> Manifest 注册 -> Viewer 加载
这条路的价值在于:
- 页面可以批量生成
- 生成结果可以回收到工程里
- 不是一次性聊天文本,而是结构化数据
- 可以继续做校验
- 可以继续做 Runtime 映射
- 可以继续交给下一位 AI 或下一轮 AI Coding
所以 AIWireframeViewer 在整个工作流里的定位,其实很明确:
它不是最终产物,它是 AI 软件研发工作流中的一个中间工具。
而且是一个非常关键的中间工具。
因为从这里开始,AI 消费的就不再是图片,而是一套稳定的结构化页面描述。
6、V1 为什么还故意保留了一些"不完美"
V1 肯定不是终态,我也没有打算在这一版里把所有事情一次做满。
目前我刻意保留了几个限制。
6.1 canvasPosition 仍然双写
现在它还同时存在于:
project/manifest.jsonwireframes/*.json
从长期看,它更适合正式上收到 Project Layer。
因为它本质是工作台编排信息,不是页面本体信息。
但 V1 我先让整个系统稳定跑起来,没有急着做回收重构。
6.2 Schema 和补充校验仍然分层
有些存在性规则、跨字段规则和引用闭环规则,还依赖补充校验逻辑。
这会增加一点维护成本,但可以换来更清晰的协议边界。
6.3 viewer 目前只有版本声明
现在 manifest.viewer 只有:
minimumrecommended
它已经能表达兼容门槛,但还没有完整的版本协商能力。
6.4 暂时不支持更多 Layer
V1 只有:
fixed-layerscroll-layer
还没有做 overlay、modal、toast、sheet
这不是能力缺失,而是我有意保持克制。
V1 最重要的是把主链路做稳,而不是把协议做胖。
7、这个项目接下来会往哪走
如果继续做 2.0,我最优先想推进这些事情:
- 把
canvasPosition正式上收到 Project Layer - 补齐引用闭环自动校验和校验报告
- 扩展
viewer兼容策略 - 增加更多 Layer,例如
overlay、modal - 支持多项目和更多默认设备策略
但从更大的方向看,我真正想验证的是这件事:
原型能不能从"截图 + 文案说明",变成"可以运行、可以校验、可以被程序继续消费的数据"。
如果这件事成立,那它连接的就不只是原型工具,而是整个 AI Coding 工作流里的前置输入层。
也就是说,AIWireframeViewer 的价值,不在于替代 Figma。
它真正的价值在于:
为 AI 提供一套稳定的中间语言。
结尾
回头看这件事,我最在意的从来不是"我做了一个 Viewer"。
我更在意的是,我终于把这条链路打通了:
Project -> Wireframe -> Schema -> Loader -> Renderer -> Runtime
从那篇总文章的 2.4 到这个独立项目,真正重要的不是"AI 又写了一个工具"。
而是我开始更确信一件事:
当 AI 已经能参与研发时,我们要做的不只是让它写代码,更重要的是给它创造一套更适合协作的输入和中间层。
AIWireframeViewer,就是我在这个方向上的第一步。
该项目已上传至 Github AIWireframeViewer
欢迎交流。