我最开始想知道的是一个很简单的问题: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 负责生成参数。一次任务会按场景经过其中一部分,并不会把所有调用机械执行一遍。

CodeGenie 页面生成与预览界面

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐