輸入和事件
GenUI 應用程式如何處理輸入和事件。
本指南介紹了使用者互動在 GenUI 包中是如何處理的,涵蓋了從初始元件互動到 AI 代理接收事件的全過程。
概述
#在 GenUI 架構中,UI 由 AI 驅動,但使用者互動(如點選按鈕或提交表單)必須傳回給 AI 代理。這使得代理能夠更新 UI 或響應使用者輸入執行操作。
事件流轉流程如下
- 互動:使用者與元件進行互動;例如,使用者點選了一個按鈕。
- 捕獲:元件實現分發一個
UiEvent。 - 處理:框架新增上下文(如
surfaceId或資料模型值)並轉發該事件。 - 傳輸: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 中
/// 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 提供的)操作定義,解析上下文中的任何資料繫結,併發送事件。
// 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 時
- 它會自動將
surfaceId注入到事件中,確保 AI 知道互動源自哪個介面。 - 它將處理委託給
SurfaceHost(由SurfaceController實現)。
// 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 時,它會執行以下操作
- 驗證事件型別。
- 將事件包裝在協議要求的
actionJSON 信封中。 - 在其
onSubmit流上傳送一個UserUiInteractionMessage。
// 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 流。
// Conversation constructor
_userEventSubscription = surfaceController.onSubmit.listen(sendRequest);
當接收到事件時,sendRequest 方法會
- 將
UserUiInteractionMessage包裝回給開發者的客戶端程式碼。 - 自定義整合或預定義的傳輸介面卡將訊息轉發給 LLM 代理網路傳輸。
AI 代理接收此 JSON 訊息,處理使用者操作,並可能流式傳輸回新的 surfaceUpdate 或 dataModelUpdate 訊息以修改 UI,或執行其他操作,從而完成整個互動迴圈。