创建和自定义组件目录

在 A2UI 架构中,每个界面都由组件目录驱动。目录是一份正式合同,用于定义可供 AI 智能体使用的界面组件、属性架构和本地客户端函数。代理必须仅使用目录中声明的组件来构建界面,而不能生成任意代码或创建未知元素。

换句话说,目录声明组件,而代理使用这些组件来构建应用的界面。

使用 Jetpack Compose A2UI 渲染器构建 Android 应用时,您可以灵活选择提供目录的方式:

  • 基本目录:A2UI 项目定义了一个名为基本目录的标准化通用规范,其中包含按钮、文本、文本字段、卡片和列表等常见元素。AndroidX 库 androidx.compose.material3:material3-a2ui 使用原生 Material Design 3 组件,可提供此基本目录规范的开箱即用型实现。androidx.a2ui.compose:compose-ui 库还为基本目录提供了一个通用架构定义,可帮助您为自己的设计系统实现基本目录。
  • 自定义目录:对于具有自己独特设计系统的生产应用,您可以从头开始构建自定义目录。这会将代理限制为仅使用应用的精确组件、样式令牌和视觉语言。
  • 子集或混合:您可以将提供的基本目录中的特定组件实现与您自己的自定义组件相结合,也可以替换基本目录套件中的各个组件实现。

使用提供的基本目录

如需快速开始,而无需从头开始编写组件架构,您可以使用提供的 A2UI 基本目录规范实现。androidx.compose.material3:material3-a2ui 库使用 Material Design 3 组件实现了基本目录。

实例化 materialA2uiBasicCatalogV1 时,请为以下各项提供渲染器和处理程序:

  • 媒体组件,例如图片、视频和音频播放器
  • 网址打开器
  • 本地化消息格式

A2UI 库有意不捆绑外部媒体和网络依赖项,例如 Coil、Glide 或 Media3。而是提供您自己的渲染器。这样可以通过提供唯一的库来防止依赖项冲突。例如,如果您的应用已使用 Coil 进行图片加载或使用 Media3 进行播放,您可以将这些现有库直接插入到目录中。

以下示例演示了如何实例化基本目录并连接您偏好的媒体库、网址打开器和消息格式化程序:

// Instantiate the provided Basic Catalog (implemented with Material 3)
val basicCatalog = materialA2uiBasicCatalogV1(
    // Example: Wire up Coil for image loading (via AsyncImage)
    image = MaterialA2uiBasicCatalogV1Defaults.image {
            url, description, scale, modifier, onError ->
        AsyncImage(
            model = url,
            contentDescription = description,
            contentScale = scale,
            modifier = modifier,
            onError = { state -> onError(state.result.throwable) },
        )
    },

    // Example: Use ExoPlayer/Media3 for video
    video = MaterialA2uiBasicCatalogV1Defaults.video { url, modifier, onError ->
        // Custom ExoPlayer video integration here
    },

    // Example: Use an audio player
    audioPlayer = MaterialA2uiBasicCatalogV1Defaults.audioPlayer {
            url, description, modifier, onError ->
        // Custom audio integration here
    },

    // Handle outbound URLs, such as using an app navigator or context intents.
    urlOpener = { url ->
        appNavigator.openUrl(url)
    },

    // Handle localized message formatting
    messageFormatter = { pattern, locale, args ->
        MessageFormat.format(context, locale, pattern, args)
    },
    localeProvider = A2uiLocaleProvider.Default,
)

从头开始组装自定义组件目录

如果您的应用使用自定义设计体系,您可以定义自己的目录,其中包含您自己的自定义 A2uiComponent 实现。这种方法可让您完全控制向代理公开的组件架构和发出的原生 Compose 界面:

// Define a custom catalog that mirrors your app's design system
val CustomDesignSystemCatalog = A2uiCatalog(
    catalogId = "https://example.com/catalogs/my-design-system/v1/catalog.json",
    components = listOf(
        CustomButtonComponent,
        CustomCardComponent,
        CustomTextFieldComponent,
    ),
    functions = listOf(MyCustomLocalFunction()),
)

如需了解如何定义各个组件架构及其 Compose 界面渲染逻辑,请参阅实现自定义 A2UI 组件。

将部分基本目录组件与自定义组件搭配使用

您不必严格选择从头开始构建所有内容,也不必采用整个基本目录。您可以组装一个目录,将所提供的基本目录实现中的所选组件与您自己的自定义组件相结合:

// Assemble a catalog using select Basic Catalog components alongside custom components
val hybridCatalog = A2uiCatalog(
    catalogId = "https://example.com/catalogs/my-app/v1/catalog.json",
    components = listOf(
        // Use provided Basic Catalog components (built with Material 3)
        MaterialA2uiBasicCatalogV1Defaults.text,
        MaterialA2uiBasicCatalogV1Defaults.card,

        // Add proprietary components from your app's design system
        CustomChartComponent,
        CustomProductCardComponent,
    ),
    functions = createBasicCatalogFunctions(...),
)

或者,您也可以通过替换特定组件 slot 来自定义提供的基本目录套件:

// Override specific components within the Basic Catalog suite
val customizedBasicCatalog = materialA2uiBasicCatalogV1(
    // Supply required renderers (such as Coil or ExoPlayer) as shown earlier
    image = MaterialA2uiBasicCatalogV1Defaults.image(myImageRenderer),
    video = MaterialA2uiBasicCatalogV1Defaults.video(myVideoRenderer),
    audioPlayer = MaterialA2uiBasicCatalogV1Defaults
        .audioPlayer(myAudioRenderer),
    urlOpener = { url -> /* Open URL */ },
    messageFormatter = { pattern, _, _ -> pattern },
    localeProvider = A2uiLocaleProvider.Default,
    // Replaces the default button. If you use this, implement the
    // A2uiBasicCatalogV1.Button interface.
    button = MyCustomBrandButtonComponent,
    
)

管理目录版本控制和架构演变

A2UI 目录根据其 JSON 架构合同明确进行版本控制。引入破坏性架构更改时,必须进行版本升级:

// Original component (v1 catalog)
object CustomButtonComponent : A2uiComponent { ... }

// Unchanged component across versions
object CustomTextComponent : A2uiComponent { ... }

// Future breaking schema change (v2 catalog)
object CustomButtonComponentV2 : A2uiComponent { ... }

// Assembles the v1 catalog
fun customCatalogV1(
    button: A2uiComponent = CustomButtonComponent,
    text: A2uiComponent = CustomTextComponent,
): A2uiCatalog = A2uiCatalog(
    catalogId = "https://example.com/catalogs/my-app/v1/catalog.json",
    components = listOf(button, text),
)

// Assembles the v2 catalog
fun customCatalogV2(
    button: A2uiComponent = CustomButtonComponentV2,
    text: A2uiComponent = CustomTextComponent,
): A2uiCatalog = A2uiCatalog(
    catalogId = "https://example.com/catalogs/my-app/v2/catalog.json",
    components = listOf(button, text),
)

为了实现无停机时间的无缝迁移,您的客户端可以同时向消息处理器注册多个受支持的目录版本:

private val processor = A2uiMessageProcessor(
    catalogs = listOf(
        customCatalogV1(),
        customCatalogV2(),
    ),
)

在目录协商期间,代理会发现所有受支持的目录 ID,并针对每个平台选择合适的版本。

实现细节

以下部分将介绍内部目录验证和架构协商。

目录管理用户历程会介绍以下关键 API:

  • A2uiCatalog:用于定义组件目录的接口和顶级工厂函数。
  • materialA2uiBasicCatalogVX:提供标准 A2UI 基本目录规范的 Material 3 实现的版本化工厂函数(例如 materialA2uiBasicCatalogV1)。
  • A2uiReadinessEvaluator 和 asReadinessEvaluator():A2uiReadinessEvaluator 是用于评估组件就绪情况的接口。asReadinessEvaluator() 扩展函数使用目录中注册的组件来解析准备就绪状态。

A2UI 版本目录和组件架构

商品目录架构定义与特定协议版本相关联。当协议发展时,目录定义会推进其版本。 此下一个版本的组件实现可以使用更新后的渲染器 API,而较低版本仍可并行运行。