From 6141701023dacb1e5a246fe7c45c3bc13f6841ed Mon Sep 17 00:00:00 2001 From: "Shams Zakhour (ignore Sfshaza)" Date: Wed, 15 Apr 2026 15:35:59 -0700 Subject: [PATCH 1/4] Updating the genui docs to match the current version --- src/content/ai/genui/get-started.md | 114 +++++++++++++++++---------- src/content/ai/genui/input-events.md | 4 +- 2 files changed, 75 insertions(+), 43 deletions(-) diff --git a/src/content/ai/genui/get-started.md b/src/content/ai/genui/get-started.md index e3bae1af87..2fd0ce034d 100644 --- a/src/content/ai/genui/get-started.md +++ b/src/content/ai/genui/get-started.md @@ -97,23 +97,30 @@ consider using Firebase AI Logic instead. final catalogs = [catalog]; final surfaceController = SurfaceController(catalogs: catalogs); - final transportAdapter = A2uiTransportAdapter(); - transportAdapter.messageStream.listen(surfaceController.handleMessage); + + // The Conversation wires transport -> controller internally. + // Implement onSend to call your LLM and pipe chunks back. + final transportAdapter = A2uiTransportAdapter(onSend: (message) async { + // final stream = model.generateContentStream(...); + // await for (final chunk in stream) { + // transportAdapter.addChunk(chunk.text ?? ''); + // } + }); final promptBuilder = PromptBuilder.chat( catalog: catalog, - instructions: 'You are a helpful assistant.', + systemPromptFragments: ['You are a helpful assistant.'], ); final model = GenerativeModel( model: 'gemini-2.5-flash', apiKey: 'YOUR_API_KEY', // Or set GEMINI_API_KEY environment variable. - systemInstruction: Content.system(promptBuilder.systemPrompt), + systemInstruction: Content.system(promptBuilder.systemPromptJoined()), ); final conversation = Conversation( - surfaceController: surfaceController, - transportAdapter: transportAdapter, + controller: surfaceController, + transport: transportAdapter, ); ``` @@ -255,8 +262,8 @@ Follow these instructions: class _ChatScreenState extends State { final TextEditingController _textController = TextEditingController(); final SurfaceController _surfaceController = - SurfaceController(catalogs: [CoreCatalogItems.asCatalog()]); - final A2uiTransportAdapter _transportAdapter = A2uiTransportAdapter(); + SurfaceController(catalogs: [BasicCatalogItems.asCatalog()]); + late final A2uiTransportAdapter _transportAdapter; late final Conversation _uiAgent; late final A2uiAgentConnector _connector; final List _messages = []; @@ -265,16 +272,18 @@ Follow these instructions: void initState() { super.initState(); - // Connect Adapter -> Controller - _transportAdapter.messageStream.listen(_surfaceController.handleMessage); + // The Conversation wires transport -> controller internally. + _transportAdapter = A2uiTransportAdapter(onSend: (message) async { + // Implement sending to LLM if needed, or handled by connector + }); _connector = A2uiAgentConnector( // TODO: Replace with your A2A server URL. url: Uri.parse('http://localhost:8080'), ); _uiAgent = Conversation( - surfaceController: _surfaceController, - transportAdapter: _transportAdapter, + controller: _surfaceController, + transport: _transportAdapter, ); // Listen for messages from the remote agent. @@ -286,6 +295,7 @@ Follow these instructions: void dispose() { _textController.dispose(); _uiAgent.dispose(); + _transportAdapter.dispose(); _surfaceController.dispose(); _connector.dispose(); super.dispose(); @@ -414,6 +424,14 @@ To use `genui` with another agent provider, follow that provider's SDK documentation to implement a connection, and stream its results into an `A2uiTransportAdapter`. +> [!WARNING] +> `PromptBuilder.chat()` generates a system prompt that may be +> **3,000–5,000+ tokens** long, which can exceed the context window +> of on-device or small models. If you are targeting an on-device LLM, consider: +> - Writing a compact custom system prompt that covers only the A2UI +> `createSurface` → `updateComponents` flow. +> - Using `systemPromptFragments` to pass only the portions of the schema your use-case requires. + @@ -462,37 +480,49 @@ to your chosen agent provider. super.initState(); // Create a SurfaceController with a widget catalog. - // The CoreCatalogItems contain basic widgets for text, markdown, and images. - _surfaceController = SurfaceController(catalogs: [CoreCatalogItems.asCatalog()]); + // The BasicCatalogItems contain basic widgets for text, markdown, and images. + _surfaceController = SurfaceController(catalogs: [BasicCatalogItems.asCatalog()]); - _transportAdapter = A2uiTransportAdapter(); - _transportAdapter.messageStream.listen(_surfaceController.handleMessage); + // The Conversation wires transport -> controller internally. + _transportAdapter = A2uiTransportAdapter(onSend: (message) async { + // Implement sending to LLM and pipe chunks back. + }); - final catalog = CoreCatalogItems.asCatalog(); + final catalog = BasicCatalogItems.asCatalog(); final promptBuilder = PromptBuilder.chat( catalog: catalog, - instructions: ''' + systemPromptFragments: [ + ''' You are an expert in creating funny riddles. Every time I give you a word, you should generate UI that displays one new riddle related to that word. Each riddle should have both a question and an answer. - ''', + ''' + ], ); - // ... initialize your LLM Client of choice using promptBuilder.systemPrompt + // ... initialize your LLM Client of choice using promptBuilder.systemPromptJoined() // Create the Conversation to orchestrate everything. _conversation = Conversation( - surfaceController: _surfaceController, - transportAdapter: _transportAdapter, - onSurfaceAdded: _onSurfaceAdded, // Added in the next step. - onSurfaceDeleted: _onSurfaceDeleted, // Added in the next step. + controller: _surfaceController, + transport: _transportAdapter, ); + + // Listen for surface lifecycle events: + _conversation.events.listen((event) { + if (event is ConversationSurfaceAdded) { + _onSurfaceAdded(event); + } else if (event is ConversationSurfaceRemoved) { + _onSurfaceDeleted(event); + } + }); } @override void dispose() { _textController.dispose(); _conversation.dispose(); + _transportAdapter.dispose(); super.dispose(); } @@ -501,14 +531,14 @@ to your chosen agent provider. ## Send messages and display the agent's responses -Send a message to the agent using the `sendMessage` method +Send a request to the agent using the `sendRequest` method in the `Conversation` class, or by directly streaming into your LLM Client and pumping the result stream to the adapter via `_transportAdapter.addChunk`. To receive and display generated UI: - 1. Use the callbacks in `Conversation` to track the addition + 1. Listen to the `events` stream in `Conversation` to track the addition and removal of UI surfaces as they are generated. These events include a _surface ID_ for each surface. @@ -524,23 +554,23 @@ To receive and display generated UI: final _textController = TextEditingController(); final _surfaceIds = []; - // Send a message containing the user's [text] to the agent. - void _sendMessage(String text) { + // Send a request containing the user's [text] to the agent. + void _sendMessage(String text) async { if (text.trim().isEmpty) return; - // _conversation.sendMessage(text); + // await _conversation.sendRequest(ChatMessage.user(text)); } - // A callback invoked by the [Conversation] when a new + // Invoked by the events stream listener when a new // UI surface is generated. Here, the ID is stored so the // build method can create a Surface to display it. - void _onSurfaceAdded(SurfaceAdded update) { + void _onSurfaceAdded(ConversationSurfaceAdded update) { setState(() { _surfaceIds.add(update.surfaceId); }); } - // A callback invoked by Conversation when a UI surface is removed. - void _onSurfaceDeleted(SurfaceRemoved update) { + // Invoked by the events stream listener when a UI surface is removed. + void _onSurfaceDeleted(ConversationSurfaceRemoved update) { setState(() { _surfaceIds.remove(update.surfaceId); }); @@ -561,7 +591,7 @@ To receive and display generated UI: itemBuilder: (context, index) { // For each surface, create a Surface to display it. final id = _surfaceIds[index]; - return Surface(surfaceController: _conversation.surfaceController, surfaceId: id); + return Surface(surfaceContext: _surfaceController.contextFor(id)); }, ), ), @@ -682,7 +712,7 @@ To add your own widgets, use the following instructions. ```dart _surfaceController = SurfaceController( - catalogs: [CoreCatalogItems.asCatalog().copyWith([riddleCard])], + catalogs: [BasicCatalogItems.asCatalog().copyWith([riddleCard])], ); ``` @@ -695,14 +725,16 @@ To add your own widgets, use the following instructions. ```dart final promptBuilder = PromptBuilder.chat( catalog: catalog, - instructions: ''' - You are an expert in creating funny riddles. Every time I give you a word, - generate a RiddleCard that displays one new riddle related to that word. - Each riddle should have both a question and an answer. - ''', + systemPromptFragments: [ + ''' + You are an expert in creating funny riddles. Every time I give you a word, + generate a RiddleCard that displays one new riddle related to that word. + Each riddle should have both a question and an answer. + ''' + ], ); - // Pass promptBuilder.systemPrompt to your LLM Config + // Pass promptBuilder.systemPromptJoined() to your LLM Config ``` {:.steps} diff --git a/src/content/ai/genui/input-events.md b/src/content/ai/genui/input-events.md index 2dc7dfb7ba..e536fdc098 100644 --- a/src/content/ai/genui/input-events.md +++ b/src/content/ai/genui/input-events.md @@ -196,10 +196,10 @@ from the message processor. ```dart // Conversation constructor -_userEventSubscription = surfaceController.onSubmit.listen(sendMessage); +_userEventSubscription = surfaceController.onSubmit.listen(sendRequest); ``` -When an event is received, the `sendMessage` method: +When an event is received, the `sendRequest` method: 1. Wraps the `UserUiInteractionMessage` back to the developer's client code. 2. The custom integration or predefined transport adapter forwards From 219adc36c3a7142ec0a383d5d5cd415f03576ac7 Mon Sep 17 00:00:00 2001 From: "Shams Zakhour (ignore Sfshaza)" Date: Wed, 15 Apr 2026 15:41:11 -0700 Subject: [PATCH 2/4] Incorporated feedback --- src/content/ai/genui/get-started.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/content/ai/genui/get-started.md b/src/content/ai/genui/get-started.md index 2fd0ce034d..a51fbaeec2 100644 --- a/src/content/ai/genui/get-started.md +++ b/src/content/ai/genui/get-started.md @@ -98,26 +98,27 @@ consider using Firebase AI Logic instead. final surfaceController = SurfaceController(catalogs: catalogs); - // The Conversation wires transport -> controller internally. - // Implement onSend to call your LLM and pipe chunks back. - final transportAdapter = A2uiTransportAdapter(onSend: (message) async { - // final stream = model.generateContentStream(...); - // await for (final chunk in stream) { - // transportAdapter.addChunk(chunk.text ?? ''); - // } - }); - final promptBuilder = PromptBuilder.chat( catalog: catalog, systemPromptFragments: ['You are a helpful assistant.'], ); final model = GenerativeModel( - model: 'gemini-2.5-flash', + model: 'gemini-1.5-flash', apiKey: 'YOUR_API_KEY', // Or set GEMINI_API_KEY environment variable. systemInstruction: Content.system(promptBuilder.systemPromptJoined()), ); + // The Conversation wires transport -> controller internally. + // Implement onSend to call your LLM and pipe chunks back. + late final A2uiTransportAdapter transportAdapter; + transportAdapter = A2uiTransportAdapter(onSend: (message) async { + // final stream = model.generateContentStream(...); + // await for (final chunk in stream) { + // transportAdapter.addChunk(chunk.text ?? ''); + // } + }); + final conversation = Conversation( controller: surfaceController, transport: transportAdapter, @@ -557,7 +558,7 @@ To receive and display generated UI: // Send a request containing the user's [text] to the agent. void _sendMessage(String text) async { if (text.trim().isEmpty) return; - // await _conversation.sendRequest(ChatMessage.user(text)); + // await _conversation.sendRequest(ChatMessage.user(TextPart(text))); } // Invoked by the events stream listener when a new From 29f01c962815b85407456d2f83397c197981f0b8 Mon Sep 17 00:00:00 2001 From: Shams Zakhour <44418985+sfshaza2@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:54:00 -0700 Subject: [PATCH 3/4] Update src/content/ai/genui/get-started.md Co-authored-by: Parker Lougheed --- src/content/ai/genui/get-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/ai/genui/get-started.md b/src/content/ai/genui/get-started.md index a51fbaeec2..34f12c011b 100644 --- a/src/content/ai/genui/get-started.md +++ b/src/content/ai/genui/get-started.md @@ -104,7 +104,7 @@ consider using Firebase AI Logic instead. ); final model = GenerativeModel( - model: 'gemini-1.5-flash', + model: 'gemini-2.5-flash', apiKey: 'YOUR_API_KEY', // Or set GEMINI_API_KEY environment variable. systemInstruction: Content.system(promptBuilder.systemPromptJoined()), ); From 1b576d6fdf0b42473e562def315fba031ca8b443 Mon Sep 17 00:00:00 2001 From: Shams Zakhour <44418985+sfshaza2@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:54:50 -0700 Subject: [PATCH 4/4] Update src/content/ai/genui/get-started.md I can't believe that I missed "may"!!! Co-authored-by: Parker Lougheed --- src/content/ai/genui/get-started.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/content/ai/genui/get-started.md b/src/content/ai/genui/get-started.md index 34f12c011b..92df0c159d 100644 --- a/src/content/ai/genui/get-started.md +++ b/src/content/ai/genui/get-started.md @@ -425,13 +425,17 @@ To use `genui` with another agent provider, follow that provider's SDK documentation to implement a connection, and stream its results into an `A2uiTransportAdapter`. -> [!WARNING] -> `PromptBuilder.chat()` generates a system prompt that may be -> **3,000–5,000+ tokens** long, which can exceed the context window -> of on-device or small models. If you are targeting an on-device LLM, consider: -> - Writing a compact custom system prompt that covers only the A2UI -> `createSurface` → `updateComponents` flow. -> - Using `systemPromptFragments` to pass only the portions of the schema your use-case requires. +:::warning +`PromptBuilder.chat()` generates a system prompt that might be +**3,000–5,000+ tokens** long, which can +exceed the context window of on-device or small models. +If you are targeting an on-device LLM, consider: + +- Writing a compact custom system prompt that covers only the + A2UI `createSurface` to `updateComponents` flow. +- Using `systemPromptFragments` to pass only the portions of + the schema your use case requires. +:::