本文只讲一个很细的功能:如何在首页标题栏右上角接入智能体入口。本文中的代码全部来自项目现有实现,核心逻辑集中在 entry/src/main/ets/pages/MainMenu.ets

功能效果

这个接入点放在首页标题栏最右侧,具备三个特点:

  1. 位置固定:显示在应用标题右边
  2. 按能力显示:只有设备支持、控制器初始化成功时才渲染
  3. 生命周期完整:页面进入时初始化,页面离开时移除监听

效果

相关文件

本功能的主要实现文件只有一个:

  • entry/src/main/ets/pages/MainMenu.ets

也就是说,这次接入并没有拆到额外的服务层里,页面自己完成了状态声明、能力检测、组件渲染和事件解绑,结构比较直接,适合写成独立教程。


一、先引入智能体组件相关能力

首页文件顶部先引入 FunctionComponentFunctionController

import { common } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { FunctionComponent, FunctionController } from '@kit.AgentFrameworkKit'

这里三项分别承担不同职责:

  • FunctionComponent:真正显示在页面上的智能体入口组件
  • FunctionController:负责控制组件,并监听打开、关闭等事件
  • common.UIAbilityContext:做能力检测时需要的上下文对象

这个功能直接使用系统 Kit,不需要在项目里额外补第三方依赖。


二、页面内声明状态与控制器

MainMenu 页面里,和智能体相关的成员只有三个,逻辑很清晰:

@State isAgentSupport: boolean = false

private agentController: FunctionController | null = null
private agentId: string = 'your_agent_id_here'

这三个字段的作用分别是:

  • isAgentSupport:当前设备和环境是否支持该智能体能力
  • agentController:智能体组件控制器
  • agentId:接入的智能体标识

这里把真实 ID 换成了占位符。你在自己的项目里,只需要把 your_agent_id_here 替换成实际智能体 ID 即可。


三、在页面出现时初始化智能体能力

这个功能没有在页面创建时直接渲染,而是先做设备判断,再决定是否初始化:

aboutToAppear(): void {
  const is2In1 = is2In1Device()

  if (!is2In1) {
    this.agentController = new FunctionController()
    this.initAgentListeners()
    this.checkAgentSupport()
  } else {
    this.agentController = null
    this.isAgentSupport = false
    hilog.info(DOMAIN, TAG, 'Agent entry disabled on 2in1 device')
  }
}

这里有两个关键点:

1. 不是所有设备都直接展示

代码里明确对 2in1 做了排除处理。如果当前设备不适合该能力,就直接把入口关闭,不继续初始化。

2. 初始化顺序很重要

接入顺序是固定的:

  1. 创建 FunctionController
  2. 注册监听事件
  3. 检查能力是否支持

这样写的好处是:只有在能力确认可用后,页面才会真正显示右上角入口,避免空白占位或者点击无响应。


四、补上打开与关闭事件监听

当前实现里还给智能体组件补了两个事件监听:

private initAgentListeners(): void {
  this.agentController?.on('agentDialogOpened', this.onAgentOpenedCallback)
  this.agentController?.on('agentDialogClosed', this.onAgentClosedCallback)
}

private readonly onAgentOpenedCallback = () => {
  hilog.info(DOMAIN, TAG, 'agent dialog opened callback')
}

private readonly onAgentClosedCallback = () => {
  hilog.info(DOMAIN, TAG, 'agent dialog closed callback')
}

这部分逻辑不复杂,但很有必要。因为智能体入口往往不是一个普通按钮,它可能会拉起对话框或服务面板。接入监听后,后续如果你要统计打开次数、补埋点、做页面联动,都可以直接在这两个回调里扩展。

当前项目里这两个回调先只做日志记录,属于非常稳妥的写法:先把链路接通,再逐步增加业务逻辑。


五、渲染前先做能力检测

是否显示入口,不是靠写死布尔值,而是通过 checkAgentSupport() 动态判断:

private async checkAgentSupport(): Promise<void> {
  try {
    const context = this.getUIContext()?.getHostContext() as common.UIAbilityContext | undefined
    if (!context) {
      this.isAgentSupport = false
      hilog.warn(DOMAIN, TAG, 'UIAbilityContext is unavailable when checking agent support')
      return
    }
    if (!this.agentController) {
      this.isAgentSupport = false
      return
    }
    this.isAgentSupport = await this.agentController.isAgentSupport(context, this.agentId)

    hilog.info(DOMAIN, TAG, `Agent support: ${this.isAgentSupport}`)
  } catch (error) {
    const err = error as BusinessError
    this.isAgentSupport = false
    hilog.error(DOMAIN, TAG, `Failed to check agent support: code=${err.code}, message=${err.message}`)
  }
}

这段代码建议重点理解三件事:

1. 检测依赖页面上下文

这里不是直接调用 isAgentSupport(),而是先从页面里拿到 UIAbilityContext。如果上下文拿不到,就立即返回,并把状态设为 false

2. 检测结果直接驱动 UI

isAgentSupport@State 变量,一旦检测完成,页面会自动刷新。这样就不需要你手动控制标题栏重绘。

3. 错误场景要兜底

如果检测异常,当前实现会:

  • isAgentSupport 置为 false
  • 记录错误日志

也就是说,接入失败时页面仍然可以正常使用,只是右上角不显示智能体入口,不会影响首页其它按钮。


六、把智能体入口放到标题栏右上角

UI 布局同样写得很直接:左边是应用标题,右边是智能体入口。关键代码如下:

Row({ space: 12 }) {
  Column() {
    Text('左左右右')
      .fontSize(36)
      .fontWeight(FontWeight.Bold)
      .fontColor('#FF6B35')
  }
  .height(44)
  .justifyContent(FlexAlign.Center)
  .alignItems(HorizontalAlign.Start)
  .layoutWeight(1)

  if (this.isAgentSupport && this.agentController) {
    Column() {
      FunctionComponent({
        agentId: this.agentId,
        onError: (err: BusinessError) => {
          hilog.error(DOMAIN, TAG, `Agent component error: code=${err.code}, message=${err.message}`)
        },
        controller: this.agentController
      })
    }
    .padding(6)
    .backgroundColor(getThemeManager().isDark() ? '#1F2A3A' : '#FFFFFF')
    .borderRadius(20)
    .shadow({ radius: 6, color: '#00000014', offsetY: 2 })
  }
}
.width('90%')
.alignItems(VerticalAlign.Center)
.padding({ top: 30, bottom: 20 })

这一段可以拆成两个层面来看。

1. 布局层

  • 左侧标题区域用了 .layoutWeight(1)
  • 这样右侧智能体入口就会自然被“挤”到最右边
  • 外层 Row({ space: 12 }) 负责控制标题和入口之间的间距

所以这个右上角布局并不需要额外写 Blank(),直接用 layoutWeight(1) 就已经足够。

2. 组件层

真正的智能体接入只有一行核心:

FunctionComponent({
  agentId: this.agentId,
  onError: (err: BusinessError) => {
    hilog.error(DOMAIN, TAG, `Agent component error: code=${err.code}, message=${err.message}`)
  },
  controller: this.agentController
})

这里最重要的是三个参数:

  • agentId:指定接入哪个智能体
  • controller:把前面创建好的控制器传进去
  • onError:兜底处理组件异常

外层再用一个 Column 包起来,补上 padding、圆角和阴影,右上角入口就会更像一个完整的小卡片,而不是直接把组件生硬贴在标题旁边。


七、页面离开时移除监听

这个细节很容易漏,但当前项目已经补上了:

aboutToDisappear(): void {
  this.agentController?.off('agentDialogOpened')
  this.agentController?.off('agentDialogClosed')
}

为什么要写这一步?原因很简单:

  • 首页可能会被重复进入
  • 如果监听不解绑,重复注册后可能出现多次回调
  • 长期来看也不利于页面生命周期管理

所以完整接入不只是“显示出来”,还要把退出阶段的清理动作一起做完。


八、接入这类首页智能体入口的最小步骤

如果你也要在自己的 HarmonyOS 页面右上角放一个智能体入口,最小接入步骤就是下面这 5 步:

  1. 在页面中引入 FunctionComponentFunctionController
  2. 声明 isAgentSupportagentControlleragentId
  3. aboutToAppear() 里创建控制器并调用 checkAgentSupport()
  4. 在标题栏右侧通过 FunctionComponent 渲染入口
  5. aboutToDisappear() 里移除监听

照着当前项目这套结构接入,逻辑会比较完整,而且不会把首页写乱。


小结

首页右上角智能体接入,本质上只做了三件事:

  • 先判断能不能用:通过 isAgentSupport() 控制显隐
  • 再把组件放到标题栏右侧:通过 layoutWeight(1) 留出右侧空间
  • 最后把生命周期补完整:进入时初始化,离开时解绑事件

这类功能看起来只是页面右上角多了一个入口,但真正稳定的关键,不在“放上去”,而在于能力检测、错误兜底、事件监听和退出清理都要一起做好。当前项目里的实现已经把这条链路串通了,直接按这个结构复用即可。

Logo

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

更多推荐