跳到主內容

輸入和事件

GenUI 應用程式如何處理輸入和事件。

本指南介紹了使用者互動在 GenUI 包中是如何處理的,涵蓋了從初始元件互動到 AI 代理接收事件的全過程。

概述

#

在 GenUI 架構中,UI 由 AI 驅動,但使用者互動(如點選按鈕或提交表單)必須傳回給 AI 代理。這使得代理能夠更新 UI 或響應使用者輸入執行操作。

事件流轉流程如下

  1. 互動:使用者與元件進行互動;例如,使用者點選了一個按鈕。
  2. 捕獲:元件實現分發一個 UiEvent
  3. 處理:框架新增上下文(如 surfaceId 或資料模型值)並轉發該事件。
  4. 傳輸:Flutter 元件生成事件,新增相應的上下文,並透過 ContentGenerator 將其路由至 AI,隨後由其轉發給 AI 代理。

定義事件

#

協議層面

#

A2UI 協議定義了一個用於上報事件的 action 訊息。action 包含

  • name:操作名稱(由 AI 生成元件時定義)。
  • surfaceId:發生事件的 UI 介面(Surface)ID。
  • sourceComponentId:觸發事件的元件 ID。
  • context:包含與事件相關資料的 JSON 物件。
  • timestamp:事件發生的時間。

Dart 實現

#

package:genui 中,使用者事件由 UiEvent 擴充套件型別及其具體實現 UserActionEvent 表示。

以下結構定義在 lib/src/model/ui_models.dart

lib/src/model/ui_models.dart
dart
/// A data object that represents a user interaction event in the UI.
extension type UiEvent.fromMap(JsonMap _json) { ... }

/// A UI event that represents a user action.
extension type UserActionEvent.fromMap(JsonMap _json) implements UiEvent {
  UserActionEvent({
    String? surfaceId,
    required String name,
    required String sourceComponentId,
    JsonMap? context,
    // ...
  }) : ...
}

在元件中捕獲事件

#

GenUI 中的元件定義在 Catalog 中,其中包含了元件可以傳送給 AI 的事件的相關資訊。AI 隨後可以傳送有關如何回傳這些事件的資訊。當您實現自定義元件(或使用標準組件)時,需使用 CatalogItemContext 中的 dispatchEvent 方法來分發事件。

示例:按鈕實現

#

以下示例展示了 Button 元件通常如何捕獲點選並分發事件。它從屬性中檢索(由 AI 提供的)操作定義,解析上下文中的任何資料繫結,併發送事件。

dart
// Inside a CatalogItem widgetBuilder:
widgetBuilder: (itemContext) {
  // 1. Extract action data from the component properties.
  final buttonData = _ButtonData.fromMap(itemContext.data as JsonMap);
  final JsonMap actionData = buttonData.action;
  final actionName = actionData['name'] as String;

  // 2. Extract context definition (which data to send back).
  final List<Object?> contextDefinition =
      (actionData['context'] as List<Object?>?) ?? <Object?>[];

  return ElevatedButton(
    onPressed: () {
      // 3. Resolve the context values from the data model.
      final JsonMap resolvedContext = resolveContext(
        itemContext.dataContext,
        contextDefinition,
      );

      // 4. Dispatch the event.
      itemContext.dispatchEvent(
        UserActionEvent(
          name: actionName,
          sourceComponentId: itemContext.id,
          context: resolvedContext,
        ),
      );
    },
    child: /* ... */
  );
},

事件處理流水線

#

一旦呼叫了 dispatchEvent,該事件就會在 GenUI 核心層中傳輸。

Surface

#

Surface 元件(在 lib/src/core/surface.dart 中)包裝了已渲染的元件,它提供了 dispatchEvent 回撥的實現。

當呼叫 _dispatchEvent

  1. 它會自動將 surfaceId 注入到事件中,確保 AI 知道互動源自哪個介面。
  2. 它將處理委託給 SurfaceHost(由 SurfaceController 實現)。
dart
// Surface implementation details
void _dispatchEvent(UiEvent event) {
  // ...
  final Map<String, Object?> eventMap = {
    ...event.toMap(),
    surfaceIdKey: widget.surfaceId, // Inject surfaceId
  };
  final UiEvent newEvent = UserActionEvent.fromMap(eventMap);
  widget.host.handleUiEvent(newEvent);
}

SurfaceController

#

SurfaceController(在 lib/src/core/surface_controller.dart 中)是管理 UI 狀態的中心樞紐。

當呼叫 handleUiEvent 時,它會執行以下操作

  1. 驗證事件型別。
  2. 將事件包裝在協議要求的 action JSON 信封中。
  3. 在其 onSubmit 流上傳送一個 UserUiInteractionMessage
dart
// SurfaceController implementation details
@override
void handleUiEvent(UiEvent event) {
  if (event is! UserActionEvent) return;

  // Wrap in protocol 'action' envelope
  final String eventJsonString = jsonEncode({'action': event.toMap()});

  // Emit for listeners (like Conversation)
  _onSubmit.add(UserUiInteractionMessage.text(eventJsonString));
}

傳輸至 AI

#

最後一步是將事件傳送給 AI 代理。這通常由 Conversation(在 lib/src/facade/conversation.dart 中)處理。Conversation 會監聽訊息處理器發出的 onSubmit 流。

dart
// Conversation constructor
_userEventSubscription = surfaceController.onSubmit.listen(sendRequest);

當接收到事件時,sendRequest 方法會

  1. UserUiInteractionMessage 包裝回給開發者的客戶端程式碼。
  2. 自定義整合或預定義的傳輸介面卡將訊息轉發給 LLM 代理網路傳輸。

AI 代理接收此 JSON 訊息,處理使用者操作,並可能流式傳輸回新的 surfaceUpdatedataModelUpdate 訊息以修改 UI,或執行其他操作,從而完成整個互動迴圈。