拆开 CodeGenie 插件:一句需求是怎样变成鸿蒙页面的
我最开始想知道的是一个很简单的问题:CodeGenie 生成鸿蒙页面时,是不是让模型直接写一整份 ArkTS?
我让Claude Code做了一次简单的逆向分析,拆解到 page_generation_client 后,答案基本清楚了。页面里那些容易写错的部分——路由、滚动容器、响应式布局、模块状态和资源引用——大多已经放在模板与组件库里。模型会做需求分析、选择模块、填写参数,但最终拼成工程的代码,还经过了一层生成器。
所以,这篇文章不讨论“哪个模型更聪明”,只沿着一条实际链路往下看:

1. 安装包里不只有一个 IntelliJ 插件
先看目录。除了 lib/ 下的大量 JAR,安装包还带了几个可以独立运行的 Node.js 子系统,以及两套 HarmonyOS 预览工程:
codegenie-plugin/
├─ lib/ IDE 集成、模型接入、本地推理等
├─ page_generation_client/ 页面生成 Agent、模板、组件和素材
├─ ui-verification/ UI 自动验证服务
├─ codebase_feature_ask/ 仓库问答 Agent
├─ lancedb_server/ 本地向量库服务
├─ embedding_model/ 本地嵌入模型
├─ previewProject/ 卡片预览工程
└─ previewProjectTemplate/ 页面预览工程模板
这些 Node.js 程序并不是安装后一直常驻,而是由 Java 侧按需拉起。以页面生成为例,可以粗略地把职责分成三部分:
| 部分 | 做的事情 |
|---|---|
| Java / IntelliJ 插件 | 登录态、模型请求、IDE 服务、JCEF、hvigor 和文件编辑器集成 |
| Node 页面生成 Agent | 需求拆解、工具调用、模块选择、参数生成和上下文维护 |
| HarmonyOS 工程与工具链 | 接收 ArkTS 与资源,完成构建、预览和设备验证 |
这样划分之后,Node 侧不必关心 IntelliJ 的编辑器 API,Java 侧也不必承接全部 Agent 流程。两边通过本地 IPC 交换消息,最后再由 DevEco Studio 已有的工程能力接住生成结果。
2. Node 负责流程,模型请求仍然回到 IDE
page_generation_client/app.js 是 webpack 打包后的文件。翻阅这部分代码时,有个现象很醒目:页面生成 Agent 没有自己维护一套常见的模型 HTTP 调用,而是通过 PluginConnector 把请求交回 Java。
把关键结构简化后,大致是这样:
class ThirdModel {
async chat(question) {
const request = {
chatId: randomUUID(),
snapshotQuestion: question,
snapshotTool: true,
reuseHistory: true,
messages: [
{ type: 'SYSTEM', text: '你是一个专业的助手。' },
{ type: 'USER', contents: [{ type: 'TEXT', text: question }] }
]
};
return (await this.pluginConnector.sendRequest(request)).content;
}
}
Node 进程还会启动 WebSocket Server。端口和 SID 由启动参数传入,Java 与 Node 复用这条连接,传输控制消息、模型请求以及工具执行结果。
我理解的考虑主要有两点。其一,登录态、配额和模型路由可以继续留在 IDE,不必在子进程里再做一遍;其二,Agent 流程可以相对独立地调整,模型接入变化时不必跟着大改。
代价是两套运行时共享了一份隐性协议。字段名、消息时序、错误格式只要有一边变化,另一边就可能无法工作。URL 中的 SID 可以起到本地连接隔离和校验作用,但不宜把它单独视为完整的鉴权方案。
3. 生成页面时,模型面对的不是空白文件
页面生成 Agent 对外提供的工具接近工程操作,而不是“生成一段代码”:
| 工具 | 用途 |
|---|---|
add_page | 根据任务描述新增主页面,并处理相关引用 |
remove_page | 删除页面 |
add_module | 向指定页面增加功能模块 |
adjust_secondary_page | 生成或调整二级页面及跳转关系 |
read_file / edit_file / delete_file | 读取和修改文件 |
list_dir / grep | 查看目录、检索代码 |
todo_write / finish | 编排任务、结束流程 |
以 add_page 为例,关键参数是 task_info,也就是用户希望做什么。Agent 不需要在这个调用里给出完整 ArkTS。后续的页面拆解、模块选择和代码组装由生成链路继续处理。
把相关类和阶段常量串起来,可以得到下面的流程:
1. 判断输入是否与 HarmonyOS 页面生成有关
2. 识别美食、旅游、购物、新闻、教育或其他领域
3. 将需求拆成首页、列表页、详情页等页面任务
4. 生成从上到下的布局描述
5. 先选模块类别,再选具体模块和布局样式
6. 生成类名、变量名并处理重名
7. 按模块参数契约生成文案和数据
8. 注册二级页面及路由分支
9. 生成 Index、页面、View、ViewModel 和资源文件
10. 根据后续对话继续加页、删页或加模块
这里值得停顿一下的是第 4 步和第 7 步:布局设计与内容填充分开了。模型先说清页面从上到下有哪些模块,再为每个模块填数据,而不是同时处理布局、语法、状态、路由和文案。
该版本中可以确认约 22 个职责较窄的模型调用点,例如 CorrelationJudgment 负责相关性判断,PageDesign 负责布局描述,ModulePick 和 ModuleCoPick 负责选模块,ModuleParasGen 负责生成参数。一次任务会按场景经过其中一部分,并不会把所有调用机械执行一遍。

4. 145 个模块,才是这份安装包里很实在的部分
component/module_v4.json 在该版本中包含 145 个预置模块。搜索、图文展示、快捷入口、订单、账号、教育、购物车等常见页面结构都能在里面找到。
一条模块记录同时带着源码、用途、布局说明、参数和路由:
{
"name": "带按钮和扫一扫功能入口的搜索模块",
"label": "搜索模块",
"usage": "搜索栏包含扫一扫入口和搜索按钮",
"layout": "通过 Row 排布图片、分割线、输入框与按钮",
"en_name": "SearchBar1",
"module_code": "@Component\nexport struct SearchBar1 { ... }",
"module_arguments": "search_placeholder(string):搜索框预设文本",
"route": [{ "file_name": "SearchPage" }],
"layout_hs_ref": "Row"
}
module_code 不是一段组件说明,而是可以参与组装的 ArkTS 源码。模块还配套 ViewModel、默认样式和资源引用。
于是,页面生成更接近下面这个过程:
理解用户需求
→ 从模块库中选择合适实现
→ 为模块生成参数
→ 将参数注入已有源码
→ 按模板拼接页面和路由
这比每次从零生成整页少了很多不确定因素。Row 怎么排、NavPathStack 怎么传、列表数据源怎么组织,都可以提前在模块里写好并测试。模型主要处理“这个需求该选哪个模块、里面放什么内容”。
如果要做类似工具,我认为这部分资产比继续堆提示词更值得投入。提示词可以告诉模型 ArkUI 的写法,却很难保证它每次都写出同样的工程结构;一份经过编译验证的模块库可以直接把结果收敛住。
5. 参数从模型回来以后,还不能直接注入
模块的参数契约采用类似下面的写法:
text(string):搜索框里的默认内容
生成器会从中解析出三类信息:在源码中定位参数的规则、用于替换的声明模板,以及提供给模型的 Schema 片段。
模型随后返回 JSON,但 JSON 解析成功并不代表数据可用。它可能把字符串数组返回成字符串,也可能把二维数组返回成一维数组。代码里因此留了归一化处理:
function normalizeField(value, type) {
if (type === 'string') return normalizeToString(value);
if (type === '[]string') return normalizeToStringList(value);
if (type === '[][]string') return normalizeToStringList2d(value);
return value;
}
多个关联数组还会统一长度,随后重新计算 Grid 等布局的行列数。这里将二维数据整理成规则形状,是为了方便后续布局计算,并不是说 ArkTS 语言不允许不规则数组。
这段实现给我的提醒很直接:模型输出校验不能停在“是不是合法 JSON”。字段类型、数组维度、元素数量、资源名称,都应该在写进工程前由确定性代码检查。否则,一个格式正确但形状错误的返回值,还是会一路传到页面上。
6. 模板和路由把各个模块接成工程
页面骨架来自 Single、Tabs、Scroll_Single、Scroll_Multi、SecondPage 等模板。生成器读取模板后替换导入、类名、页面内容和路由分支。
单页入口使用 Navigation 与 NavPathStack,简化后如下:
@Entry
@Component
struct Index {
@Provide('appPathStack') appPathStack: NavPathStack = new NavPathStack();
build() {
Navigation(this.appPathStack) {
// 页面内容
}
.navDestination(PageMap)
.hideTitleBar(true)
}
}
子模块通过 @Consume('appPathStack') 取得同一个路由栈,二级页面注册到 PageMap:
@Builder
export function PageMap(name: string) {
if (name === 'SearchPage') {
SecondPageSearch();
} else {
EmptyPagePathStack();
}
}
前面模块记录中的 route 在这里派上了用场:
模块声明 route
→ 生成器扩展 PageMap
→ 对应 NavDestination 文件进入工程
→ 模块通过 pushPathByName 发起跳转
模块、路由表和支撑文件必须一致。少任何一环,都可能导致页面虽然编译成功,点击后却进入空页。
资源也采用受控取值。该版本内置图标、实拍图和矢量资源,生成器根据代码中的 app.media.* 引用收集文件。模型可以从给定名称里挑,但不能随手编一个资源名让构建系统去碰运气。
7. 生成物先进入沙箱,不直接覆盖用户代码
生成结果首先写到独立目录:
entry/src/main/ets/generated/
├─ Index.ets
├─ pages/
├─ view/
├─ viewmodel/
└─ detail/
用户确认“保存到工程”后,代码才进入正式工程。这个隔离动作很有必要:生成结果尚未经过用户检查,不应该默认获得修改现有源码的权限。
预览由插件携带的 HarmonyOS 沙箱工程承接。相关 JAR 里可以看到这些类:
HvigorConnection 与 hvigor 构建进程通信
PreviewService 组织预览流程
ResetPreviewProjectImpl 重置沙箱工程
ArkJcefManager 管理 IDE 内嵌浏览器
PreviewerVirtualFileService 将预览接入编辑器标签页
UnixPipeServer / WinPipeServer 处理不同平台的 IPC
这说明预览接入了 DevEco Studio 的构建体系,并非仅根据 ArkTS 文本画一张示意图。沙箱入口还准备了静态“预览失败”页面。即使这次生成不可用,用户仍能看到明确提示和恢复入口,而不是只有构建堆栈。

8. UI 验证另走了一条设备链路
ui-verification/ 与页面生成 Agent 是两块独立代码。它通过 stdio 提供 MCP 风格服务,主要有三个工具:
| 工具 | 用途 |
|---|---|
verifyUI | 按自然语言测试计划在设备上执行验证 |
getLog | 根据验证 ID 查询日志 |
saveScreenshot | 保存本次验证的截图 |
一次验证会反复执行“截图—判断—操作—再截图”:
hdc 获取设备与屏幕信息
→ 校验测试计划和设备能力
→ 获取截图并选择有效帧
→ 视觉模型判断当前页面和任务进度
→ 动作模型给出下一步操作
→ uitest / uinput 在设备上执行
→ 收集新截图与 hilog
→ 汇总成功和失败步骤
设备侧操作最终对应明确的命令,例如:
# 点击与滑动
hdc shell uitest uiInput click <x> <y>
hdc shell uitest uiInput swipe <x1> <y1> <x2> <y2>
# 输入文本
hdc shell uitest uiInput text '<text>'
# 启动与停止应用
hdc shell aa start -a <ability> -b <bundle>
hdc shell aa force-stop <bundle>
# 获取设备信息
hdc shell param get const.product.devicetype
hdc shell hidumper -s DisplayManagerService -a -a
这个循环设有连续异常重试次数、总执行轮数和整体超时。提示逻辑里还有两条很具体的约束:异常后要根据最新截图重新识别,不能沿用旧坐标;上一次操作没有推进任务时,不能机械重复同一动作。
它们处理的是视觉 Agent 很容易遇到的两类故障:页面已经滚动,模型还在点旧位置;点击没有生效,模型就在原处连续重试。
该版本虽然包含获取 UI 布局树的代码,但主验证循环仍主要依靠截图和视觉模型。纯视觉方式通用性较好,对组件属性、文本溢出和点击区域等问题却不够精确。后续如果把截图与 ArkUI 组件树一起交给验证逻辑,应该能补上一部分盲区。
9. 拆解时也遇到了几处“能跑但不对”的问题
静态分析除了能看架构,也会看到一些很日常的开发痕迹。
单页路径中曾出现 page_title 占位符漏替换。残留值是合法字符串,编译器不会报错,但页面可能直接显示 page_title。这种问题仅靠构建无法发现,预览和视觉检查才有意义。
还有一些配置与实现不一致的情况:个别 JSON 配置项存在,对应 getter 却没有实际调用;工具描述写着使用 ripgrep,实现走的却是 git grep、系统 grep 和 JavaScript 正则的降级链。
提示词里也能看到编号重复、工具描述为空或与实现不符。它们不算新鲜问题,本质上仍是文档和代码漂移,只是这里的“文档”已经会直接参与系统行为。
结尾
拆完这条链路后,我对 CodeGenie 页面生成的理解反而朴素了很多:模型负责把需求映射到有限的工程选择,模块库和模板负责大部分 ArkUI 实现,生成器负责把参数变成文件,DevEco Studio 与设备负责检查结果。
其中最值得借鉴的,不是某个提示词怎么写,而是每一层都给不确定性留了出口。参数形状不对,可以先归一化;页面没生成好,可以留在沙箱;编译过了但界面不对,还有预览和设备验证;自动操作卡住,也有重试上限和用户接管入口。
如果要在鸿蒙场景做类似的 AI 编程工具,我大概会先把时间花在模块资产、模板契约和验证链路上,再考虑怎样让模型多写一点代码。工程能接住模型的输出,生成能力才有实际意义。
文章标签: HarmonyOS、鸿蒙、DevEco Studio、CodeGenie、ArkTS、ArkUI、AI 编程、Agent、hvigor、MCP
更多推荐





所有评论(0)