diff --git a/README.md b/README.md index 067ff7876..e90199e93 100644 --- a/README.md +++ b/README.md @@ -50,16 +50,16 @@ final catalog = CoreCatalogItems.asCatalog().copyWith([ /// Initializing the library. final genUiManager = GenUiManager(catalog: catalog); -final aiClient = FirebaseAiClient( +final contentGenerator = FirebaseAiContentGenerator( + catalog: catalog, systemInstruction: ''' You are a bicycle maintenance assistant who is an expert in diagnosing issues and giving step-by-step instructions. ''', - tools: genUiManager.getTools(), ); late final _genUiConversation = GenUiConversation( genUiManager: genUiManager, - aiClient: aiClient, + contentGenerator: contentGenerator, onSurfaceAdded: _onSurfaceAdded, onSurfaceDeleted: (_) {}, onTextResponse: (_) {}, @@ -81,7 +81,7 @@ void _onSurfaceAdded(SurfaceAdded surface) { Widget build(BuildContext context) { if (type == MessageType.genUi) { return GenUiSurface( - host: _genUiConversation.host, + host: _genUiConversation.genUiManager.host, surfaceId: _surfaceId, onEvent: _handleEvent, ); diff --git a/examples/travel_app/integration_test/app_test.dart b/examples/travel_app/integration_test/app_test.dart index ae90dd1c5..b14b7e759 100644 --- a/examples/travel_app/integration_test/app_test.dart +++ b/examples/travel_app/integration_test/app_test.dart @@ -14,10 +14,10 @@ void main() { group('Initial UI test', () { testWidgets('send a request and verify the UI', (tester) async { - final mockAiClient = FakeContentGenerator(); - mockAiClient.addA2uiMessage(A2uiMessage.fromJson(_baliResponse)); + final mockContentGenerator = FakeContentGenerator(); + mockContentGenerator.addA2uiMessage(A2uiMessage.fromJson(_baliResponse)); - runApp(app.TravelApp(contentGenerator: mockAiClient)); + runApp(app.TravelApp(contentGenerator: mockContentGenerator)); await tester.pumpAndSettle(); await tester.enterText(find.byType(EditableText), 'Plan a trip to Bali'); await tester.tap(find.byIcon(Icons.send)); diff --git a/examples/travel_app/test/main_test.dart b/examples/travel_app/test/main_test.dart index 0c238ff4e..568ba97af 100644 --- a/examples/travel_app/test/main_test.dart +++ b/examples/travel_app/test/main_test.dart @@ -11,24 +11,28 @@ import 'package:travel_app/main.dart' as app; void main() { testWidgets('Can send a prompt', (WidgetTester tester) async { - final mockAiClient = FakeContentGenerator(); - await tester.pumpWidget(app.TravelApp(contentGenerator: mockAiClient)); + final mockContentGenerator = FakeContentGenerator(); + await tester.pumpWidget( + app.TravelApp(contentGenerator: mockContentGenerator), + ); await tester.enterText(find.byType(TextField), 'test prompt'); await tester.testTextInput.receiveAction(TextInputAction.send); - mockAiClient.addTextResponse('AI response'); + mockContentGenerator.addTextResponse('AI response'); await tester.pumpAndSettle(); - expect(mockAiClient.sendRequestCallCount, 1); + expect(mockContentGenerator.sendRequestCallCount, 1); expect(find.text('test prompt'), findsOneWidget); expect(find.text('AI response'), findsOneWidget); }); testWidgets('Shows spinner while thinking', (WidgetTester tester) async { - final mockAiClient = FakeContentGenerator(); + final mockContentGenerator = FakeContentGenerator(); final completer = Completer(); - mockAiClient.sendRequestCompleter = completer; - await tester.pumpWidget(app.TravelApp(contentGenerator: mockAiClient)); + mockContentGenerator.sendRequestCompleter = completer; + await tester.pumpWidget( + app.TravelApp(contentGenerator: mockContentGenerator), + ); await tester.enterText(find.byType(TextField), 'test prompt'); await tester.testTextInput.receiveAction(TextInputAction.send); @@ -42,7 +46,7 @@ void main() { // Complete the response. completer.complete(); - mockAiClient.addTextResponse('AI response'); + mockContentGenerator.addTextResponse('AI response'); await tester.pumpAndSettle(); // The spinner should be gone. diff --git a/packages/flutter_genui/.guides/examples/riddles.dart b/packages/flutter_genui/.guides/examples/riddles.dart index b3dd75787..ea009ca60 100644 --- a/packages/flutter_genui/.guides/examples/riddles.dart +++ b/packages/flutter_genui/.guides/examples/riddles.dart @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:json_schema_builder/json_schema_builder.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_genui/flutter_genui.dart'; import 'package:flutter_genui_firebase_ai/flutter_genui_firebase_ai.dart'; +import 'package:json_schema_builder/json_schema_builder.dart'; import 'package:logging/logging.dart'; import 'firebase_options.dart'; @@ -65,15 +65,14 @@ class _MyHomePageState extends State { final genUiManager = GenUiManager( catalog: CoreCatalogItems.asCatalog().copyWith([riddleCard]), ); - final contentGenerator = FirebaseContentGenerator( - apiKey: const String.fromEnvironment('GEMINI_API_KEY'), + final contentGenerator = FirebaseAiContentGenerator( + catalog: genUiManager.catalog, systemInstruction: ''' You are an expert in creating funny riddles. Every time I give you a word, you should generate a RiddleCard that displays one new riddle related to that word. Each riddle should have both a question and an answer. ''', - tools: genUiManager.getTools(), ); conversation = GenUiConversation( contentGenerator: contentGenerator, diff --git a/packages/flutter_genui/.guides/setup.md b/packages/flutter_genui/.guides/setup.md index b72df0e58..d2a3df0cf 100644 --- a/packages/flutter_genui/.guides/setup.md +++ b/packages/flutter_genui/.guides/setup.md @@ -11,7 +11,7 @@ below for your preferred provider. ### Configure Firebase AI Logic -To use the built-in `FirebaseAiClient` to connect to Gemini via Firebase AI +To use the built-in `FirebaseContentGenerator` to connect to Gemini via Firebase AI Logic, follow these instructions: 1. [Create a new Firebase project](https://support.google.com/appsheet/answer/10104995) @@ -25,37 +25,37 @@ Logic, follow these instructions: `dependencies` section. As of this writing, it's best to use pub's git dependency to refer directly to this project's source. - ```yaml - dependencies: - # ... - flutter_genui: - git: - url: https://github.com/flutter/genui.git - path: packages/flutter_genui - ref: fc7efa9c69529b655e531f3037bb12b9b241d6aa - flutter_genui_firebase_ai: - git: - url: https://github.com/flutter/genui.git - path: packages/flutter_genui_firebase_ai - ref: fc7efa9c69529b655e531f3037bb12b9b241d6aa - ``` + ```yaml + dependencies: + # ... + flutter_genui: + git: + url: https://github.com/flutter/genui.git + path: packages/flutter_genui + ref: fc7efa9c69529b655e531f3037bb12b9b241d6aa + flutter_genui_firebase_ai: + git: + url: https://github.com/flutter/genui.git + path: packages/flutter_genui_firebase_ai + ref: fc7efa9c69529b655e531f3037bb12b9b241d6aa + ``` 5. In your app's `main` method, ensure that the widget bindings are initialized, and then initialize Firebase. - ```dart - void main() async { - WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - runApp(const MyApp()); - } - ``` + ```dart + void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + runApp(const MyApp()); + } + ``` ### Configure another agent provider To use `flutter_genui` with another agent provider, you need to follow that provider's instructions to configure your app, and then create your own subclass -of `AiClient` to connect to that provider. Use `FirebaseAiClient` as an example +of `ContentGenerator` to connect to that provider. Use `FirebaseContentGenerator` as an example of how to do so. ## 2. Create the connection to an agent @@ -70,4 +70,4 @@ requests: com.apple.security.network.client -``` \ No newline at end of file +``` diff --git a/packages/flutter_genui/.guides/usage.md b/packages/flutter_genui/.guides/usage.md index 1db65ca2c..ed3f384c7 100644 --- a/packages/flutter_genui/.guides/usage.md +++ b/packages/flutter_genui/.guides/usage.md @@ -5,34 +5,34 @@ interactive generative UI to their applications. ## Key components -* **`UiAgent`**: The main entry point of the package. It manages the +- **`UiAgent`**: The main entry point of the package. It manages the conversation with the LLM, handles UI state, and orchestrates the interaction between the app, its UI, and the AI. -* **`AiClient`**: A client that manages communication with your LLM. The package - includes an (optional) `FirebaseAiClient` to communicate with Google's Gemini +- **`ContentGenerator`**: A client that manages communication with your LLM. The package + includes an (optional) `FirebaseContentGenerator` to communicate with Google's Gemini models (using Firebase AI Logic), and you can create your own subclasses for other models or LLM libraries. -* **`GenUiSurface`**: A Flutter widget that displays UI generated by the agent. +- **`GenUiSurface`**: A Flutter widget that displays UI generated by the agent. It listens for updates from `UiAgent` and dynamically renders the generated UI. -* **`Catalog`**: A catalog of Flutter widgets that the AI is allowed to +- **`Catalog`**: A catalog of Flutter widgets that the AI is allowed to generate. For each widget, you define a name, a schema that describes the data needed by the widget, and a builder function. The built-in `CoreCatalogItems` include widgets for rendering text, markdown, and images. -* **`GenUiManager`**: The core state manager for generated UI. You won't often +- **`GenUiManager`**: The core state manager for generated UI. You won't often interact directly with this class, but it's responsible for managing the flow of information and events between the agent and your UI. ## Use cases -* Incorporate graphical UI into chatbots: instead of describing a list of products in text, +- Incorporate graphical UI into chatbots: instead of describing a list of products in text, the LLM can render an interactive carousel of product widgets. Instead of asking for a user to type out answers to questions, the LLM can render sliders, checkboxes, and more. -* Create dynamically composed UIs: an agent can generate a complete form with sliders, date pickers, +- Create dynamically composed UIs: an agent can generate a complete form with sliders, date pickers, and text fields on the fly based on a user's request to "book a flight." ## Example @@ -51,7 +51,7 @@ final catalog = CoreCatalogItems.asCatalog().copyWith([ /// Initializing the library. final genUiManager = GenUiManager(catalog: catalog); -final aiClient = FirebaseAiClient( +final contentGenerator = FirebaseContentGenerator( systemInstruction: ''' You are a bicycle maintenance assistant who is an expert in diagnosing issues and giving step-by-step instructions. @@ -60,7 +60,7 @@ final aiClient = FirebaseAiClient( ); late final _uiAgent = UiAgent( genUiManager: genUiManager, - aiClient: aiClient, + contentGenerator: contentGenerator, onSurfaceAdded: _onSurfaceAdded, onSurfaceDeleted: (_) {}, ); diff --git a/packages/flutter_genui/README.md b/packages/flutter_genui/README.md index 0f10453d1..64df0c20a 100644 --- a/packages/flutter_genui/README.md +++ b/packages/flutter_genui/README.md @@ -49,7 +49,7 @@ class YourContentGenerator implements ContentGenerator { @override ValueListenable get isProcessing => ValueNotifier(false); // Replace @override - Future sendRequest(Iterable messages) async { /* ... */ } + Future sendRequest(ChatMessage message, {Iterable? history}) async { /* ... */ } @override void dispose() { /* ... */ } } @@ -116,7 +116,7 @@ class _MyAppState extends State { itemCount: _surfaceIds.length, itemBuilder: (context, index) { return GenUiSurface( - host: _genUiConversation.host, + host: _genUiConversation.host, surfaceId: _surfaceIds[index], ); }, @@ -159,9 +159,9 @@ graph TD end UserInput -- "calls sendRequest()" --> GenUiConversation; - GenUiConversation -- "manages conversation and sends prompt" --> ContentGenerator; - ContentGenerator -- "returns tool calls" --> GenUiConversation; - GenUiConversation -- "executes tools" --> GenUiManager; + GenUiConversation -- "sends prompt" --> ContentGenerator; + ContentGenerator -- "returns A2UI messages" --> GenUiConversation; + GenUiConversation -- "handles messages" --> GenUiManager; GenUiManager -- "notifies of updates" --> GenUiSurface; GenUiSurface -- "renders UI" --> UserInteraction; UserInteraction -- "creates event" --> GenUiSurface; diff --git a/packages/flutter_genui/USAGE.md b/packages/flutter_genui/USAGE.md index 135093b0d..395dfa94b 100644 --- a/packages/flutter_genui/USAGE.md +++ b/packages/flutter_genui/USAGE.md @@ -9,8 +9,8 @@ This guidance explains how to quickly get started with the conversation with the LLM, handles UI state, and orchestrates the interaction between the app, its UI, and the AI. -- **`AiClient`**: A client that manages communication with your LLM. The package - includes an (optional) `FirebaseAiClient` to communicate with Google's Gemini +- **`ContentGenerator`**: A client that manages communication with your LLM. The package + includes an (optional) `FirebaseAiContentGenerator` to communicate with Google's Gemini models (using Firebase AI Logic), and you can create your own subclasses for other models or LLM libraries. @@ -40,7 +40,7 @@ below for your preferred provider. #### Configure Firebase AI Logic -To use the built-in `FirebaseAiClient` to connect to Gemini via Firebase AI +To use the built-in `FirebaseAiContentGenerator` to connect to Gemini via Firebase AI Logic, follow these instructions: 1. [Create a new Firebase project](https://support.google.com/appsheet/answer/10104995) @@ -61,11 +61,12 @@ Logic, follow these instructions: git: url: https://github.com/flutter/genui.git path: packages/flutter_genui - ref: 6e472cf0f7416c31a1de6af9a0d1b4cc37188989 + ref: main flutter_genui_firebase_ai: git: url: https://github.com/flutter/genui.git path: packages/flutter_genui_firebase_ai + ref: main ``` 5. In your app's `main` method, ensure that the widget bindings are initialized, @@ -83,7 +84,7 @@ Logic, follow these instructions: To use `flutter_genui` with another agent provider, you need to follow that provider's instructions to configure your app, and then create your own subclass -of `AiClient` to connect to that provider. Use `FirebaseAiClient` as an example +of `ContentGenerator` to connect to that provider. Use `FirebaseAiContentGenerator` as an example of how to do so. ### 2. Create the connection to an agent @@ -105,10 +106,10 @@ provider. 1. Create a `GenUiManager`, and provide it with the catalog of widgets you want to make available to the agent. -2. Create an `AiClient`, and provide it with a system instruction and a set of +2. Create a `ContentGenerator`, and provide it with a system instruction and a set of tools (functions you want the agent to be able to invoke). You should always include those provided by `GenUiManager`, but feel free to include others. -3. Create a `GenUiConversation` using the instances of `AiClient` and `GenUiManager`. Your +3. Create a `GenUiConversation` using the instances of `ContentGenerator` and `GenUiManager`. Your app will primarily interact with this object to get things done. For example: @@ -126,9 +127,9 @@ provider. // The CoreCatalogItems contain basic widgets for text, markdown, and images. _genUiManager = GenUiManager(catalog: CoreCatalogItems.asCatalog()); - // Create an AiClient to communicate with the LLM. + // Create a ContentGenerator to communicate with the LLM. // Provide system instructions and the tools from the GenUiManager. - final aiClient = FirebaseAiClient( + final contentGenerator = FirebaseAiContentGenerator( systemInstruction: ''' 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. @@ -140,7 +141,7 @@ provider. // Create the GenUiConversation to orchestrate everything. _genUiConversation = GenUiConversation( genUiManager: _genUiManager, - aiClient: aiClient, + contentGenerator: contentGenerator, onSurfaceAdded: _onSurfaceAdded, // Added in the next step. onSurfaceDeleted: _onSurfaceDeleted, // Added in the next step. ); @@ -150,7 +151,7 @@ provider. void dispose() { _textController.dispose(); _genUiConversation.dispose(); - _genUiManager.dispose(); + super.dispose(); } } @@ -270,7 +271,7 @@ dependencies: git: url: https://github.com/flutter/genui.git path: packages/json_schema_builder - ref: 6e472cf0f7416c31a1de6af9a0d1b4cc37188989 + ``` #### Create the new widget's schema @@ -349,7 +350,7 @@ instruction to explicitly tell it how and when to do so. Provide the name from the CatalogItem when you do. ```dart -final aiClient = FirebaseAiClient( +final contentGenerator = FirebaseAiContentGenerator( systemInstruction: ''' You are an expert in creating funny riddles. Every time I give you a word, you should generate a RiddleCard that displays one new riddle related to that word. @@ -411,7 +412,7 @@ If something is unclear or missing, please The `flutter_genui` package gives the LLM a set of tools it can use to generate UI. To get the LLM to use these tools, the `systemInstruction` provided to -`AiClient` must explicitly tell it to do so. This is why the previous example +`ContentGenerator` must explicitly tell it to do so. This is why the previous example includes a system instruction for the agent with the line "Every time I give you a word, you should generate UI that displays one new riddle...". diff --git a/packages/flutter_genui/analysis_options.yaml b/packages/flutter_genui/analysis_options.yaml index 350a9eda3..f1603b4b7 100644 --- a/packages/flutter_genui/analysis_options.yaml +++ b/packages/flutter_genui/analysis_options.yaml @@ -3,5 +3,10 @@ # found in the LICENSE file. include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + exclude: + - '.guides/**' + # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/packages/flutter_genui_a2ui/DESIGN.md b/packages/flutter_genui_a2ui/DESIGN.md index 1b707bd53..61ca2a6bf 100644 --- a/packages/flutter_genui_a2ui/DESIGN.md +++ b/packages/flutter_genui_a2ui/DESIGN.md @@ -24,23 +24,23 @@ The problem is to integrate the A2A communication and A2UI message parsing from The `flutter_genui_a2ui` package will consist of the following key components: -### 1. `A2uiAiClient` (implements `AiClient` from `flutter_genui`) +### 1. `A2uiContentGenerator` (implements `ContentGenerator` from `flutter_genui`) -This will be the central class for connecting the A2A server to the `flutter_genui`'s `UiAgent`. It will implement the `AiClient` interface, but instead of performing AI inference, it will manage the A2A connection and process incoming A2UI messages. +This will be the central class for connecting the A2A server to the `flutter_genui`'s `GenUiConversation`. It will implement the `ContentGenerator` interface, but instead of performing AI inference, it will manage the A2A connection and process incoming A2UI messages. - **Dependencies:** `flutter_genui`, `a2a` (for A2A communication). - **Constructor:** - - `A2uiAiClient({required Uri serverUrl, required GenUiManager genUiManager})` + - `A2uiContentGenerator({required Uri serverUrl, required GenUiManager genUiManager})` - **Internal State:** - `A2AClient _a2aClient`: An instance of the A2A client for communication. - - `GenUiManager _genUiManager`: The `GenUiManager` instance provided by the `UiAgent`. + - `GenUiManager _genUiManager`: The `GenUiManager` instance provided by the `GenUiConversation`. - `StreamController _a2uiMessageController`: A stream controller to process incoming A2UI messages from the A2A server. - `String? _taskId`, `String? _contextId`: To manage the A2A conversation context. - - `ValueNotifier _activeRequests`: To implement the `activeRequests` getter from `AiClient`. + - `ValueNotifier _activeRequests`: To implement the `activeRequests` getter from `ContentGenerator`. - **Key Methods:** - `Future getAgentCard()`: Fetches the agent card from the A2A server, similar to `A2uiAgentConnector.getAgentCard` in the spike. - - `Future sendUserMessage(String messageText, {void Function(String)? onResponse})`: Sends a user message to the A2A server. This will be called by the `UiAgent`. - - `Future generateContent(List conversation, Schema outputSchema, {Iterable additionalTools = const []})`: This method will be the entry point for `UiAgent` to send messages. It will extract the latest user message from the `conversation` and send it to the A2A server via `_a2aClient`. It will then listen to the `_a2uiMessageController` and pass the `A2uiMessage`s to `_genUiManager.handleMessage`. The `outputSchema` will be ignored as the UI is driven by the A2UI stream, not direct AI output. + - `Future sendUserMessage(String messageText, {void Function(String)? onResponse})`: Sends a user message to the A2A server. This will be called by the `GenUiConversation`. + - `Future generateContent(List conversation, Schema outputSchema, {Iterable additionalTools = const []})`: This method will be the entry point for `GenUiConversation` to send messages. It will extract the latest user message from the `conversation` and send it to the A2A server via `_a2aClient`. It will then listen to the `_a2uiMessageController` and pass the `A2uiMessage`s to `_genUiManager.handleMessage`. The `outputSchema` will be ignored as the UI is driven by the A2UI stream, not direct AI output. - `Future generateText(List conversation, {Iterable additionalTools = const []})`: This method will also extract the latest user message and send it. It will return an empty string or a placeholder, as text responses are handled by `onResponse` in `sendUserMessage`. - `void _processA2aStream(Stream events)`: An internal method to process the raw A2A stream events, extract A2UI messages, and add them to `_a2uiMessageController`. - `void _handleUiEvent(UiEvent event)`: This method will be registered with `_genUiManager.onSubmit` to capture user interactions from the rendered UI and send them back to the A2A server via `_a2aClient.sendMessage`. @@ -53,7 +53,7 @@ A comprehensive example application will be created within the `example/` direct - A chat conversation view to display user input and text responses from the A2A server. - A fixed UI surface (using `GenUiSurface`) that renders the A2UI-generated interface. - Input field for sending messages to the A2A agent. - - Clear demonstration of how to initialize `A2uiAiClient` and `UiAgent`. + - Clear demonstration of how to initialize `A2uiContentGenerator` and `GenUiConversation`. - Basic error handling and loading indicators. ### 3. `A2uiAgentConnector` (Adapted from spike) @@ -65,7 +65,7 @@ The core logic for connecting to the A2A server and handling the raw A2A stream - Receiving A2A stream events. - Parsing A2A stream events to extract A2UI protocol messages. -This adapted class will be internal to `flutter_genui_a2ui` and will be used by `A2uiAiClient`. +This adapted class will be internal to `flutter_genui_a2ui` and will be used by `A2uiContentGenerator`. ### 4. `AgentCard` (Adapted from spike) @@ -76,25 +76,25 @@ The `AgentCard` class, representing metadata about the A2A agent, will also be * ```mermaid graph TD subgraph Flutter App - UiAgent -- "sends UserMessage" --> A2uiAiClient - A2uiAiClient -- "sends A2AMessage" --> A2A_Server["A2A Server"] - A2A_Server -- "sends A2AStreamEvent" --> A2uiAiClient - A2uiAiClient -- "processes A2uiMessage" --> GenUiManager + GenUiConversation -- "sends UserMessage" --> A2uiContentGenerator + A2uiContentGenerator -- "sends A2AMessage" --> A2A_Server["A2A Server"] + A2A_Server -- "sends A2AStreamEvent" --> A2uiContentGenerator + A2uiContentGenerator -- "processes A2uiMessage" --> GenUiManager GenUiManager -- "emits GenUiUpdate" --> GenUiSurface GenUiSurface -- "renders UI" --> User User -- "interacts with UI" --> GenUiSurface GenUiSurface -- "dispatches UiEvent" --> GenUiManager - GenUiManager -- "emits UserMessage (from UiEvent)" --> A2uiAiClient + GenUiManager -- "emits UserMessage (from UiEvent)" --> A2uiContentGenerator end subgraph `flutter_genui_a2ui` Package - A2uiAiClient + A2uiContentGenerator A2uiAgentConnector AgentCard end subgraph `flutter_genui` Package - UiAgent + GenUiConversation GenUiManager GenUiSurface Catalog @@ -105,17 +105,17 @@ graph TD A2AClient end - A2uiAiClient -- uses --> A2uiAgentConnector + A2uiContentGenerator -- uses --> A2uiAgentConnector A2uiAgentConnector -- uses --> A2AClient - A2uiAiClient -- uses --> GenUiManager - UiAgent -- uses --> A2uiAiClient - UiAgent -- uses --> GenUiManager + A2uiContentGenerator -- uses --> GenUiManager + GenUiConversation -- uses --> A2uiContentGenerator + GenUiConversation -- uses --> GenUiManager GenUiSurface -- uses --> GenUiManager ``` ## Summary of the Design -The `flutter_genui_a2ui` package will act as a specialized `AiClient` for `flutter_genui`, enabling seamless integration with A2A servers. It will encapsulate the A2A communication logic, translate incoming A2UI messages into `flutter_genui`'s internal UI update mechanisms, and forward user interactions back to the A2A server. This design promotes modularity, reusability, and adherence to the existing `flutter_genui` architecture. +The `flutter_genui_a2ui` package will act as a specialized `ContentGenerator` for `flutter_genui`, enabling seamless integration with A2A servers. It will encapsulate the A2A communication logic, translate incoming A2UI messages into `flutter_genui`'s internal UI update mechanisms, and forward user interactions back to the A2A server. This design promotes modularity, reusability, and adherence to the existing `flutter_genui` architecture. ## References to Research URLs diff --git a/packages/flutter_genui_a2ui/GEMINI.md b/packages/flutter_genui_a2ui/GEMINI.md index 649f95f04..9662b88a7 100644 --- a/packages/flutter_genui_a2ui/GEMINI.md +++ b/packages/flutter_genui_a2ui/GEMINI.md @@ -10,9 +10,9 @@ The primary purpose of this package is to facilitate the creation of generative ## Implementation Details -### `A2uiAiClient` +### `A2uiContentGenerator` -This class implements the `AiClient` interface from `flutter_genui`. It acts as the main bridge between the `UiAgent` and the A2UI server. It uses an internal `A2uiAgentConnector` to handle the low-level A2A communication. +This class implements the `ContentGenerator` interface from `flutter_genui`. It acts as the main bridge between the `GenUiConversation` and the A2UI server. It uses an internal `A2uiAgentConnector` to handle the low-level A2A communication. - **Constructor:** Takes `serverUrl` (the A2A server endpoint) and `genUiManager` (the `GenUiManager` instance from `flutter_genui`). An optional `A2AClient` can be provided for testing. - **`generateContent` and `generateText`:** These methods are overridden to send user messages to the A2A server via the `A2uiAgentConnector`. UI updates are driven by the incoming A2UI stream, not direct return values from these methods. @@ -24,7 +24,7 @@ This class is responsible for establishing and maintaining the WebSocket connect - **Constructor:** Takes the `url` of the A2A server. An optional `A2AClient` can be provided for testing. - **`getAgentCard`:** Fetches metadata about the AI agent from the server. -- **`connectAndSend`:** Establishes a connection (if not already established), sends a user message to the A2A server, and processes the incoming A2A stream, extracting A2UI messages and forwarding them to the `A2uiAiClient`. +- **`connectAndSend`:** Establishes a connection (if not already established), sends a user message to the A2A server, and processes the incoming A2A stream, extracting A2UI messages and forwarding them to the `A2uiContentGenerator`. - **`sendEvent`:** Sends user interaction events (e.g., button clicks) back to the A2A server. - **`_processA2uiMessages`:** An internal method to parse raw A2A data parts and convert them into `flutter_genui`'s `A2uiMessage` objects. @@ -34,8 +34,8 @@ A simple data class to hold metadata about the A2A agent, including its name, de ## File Layout -- `lib/flutter_genui_a2ui.dart`: Exports the public API of the package (`A2uiAiClient`, `A2uiAgentConnector`, `AgentCard`). -- `lib/src/a2ui_ai_client.dart`: Contains the `A2uiAiClient` implementation. +- `lib/flutter_genui_a2ui.dart`: Exports the public API of the package (`A2uiContentGenerator`, `A2uiAgentConnector`, `AgentCard`). +- `lib/src/a2ui_content_generator.dart`: Contains the `A2uiContentGenerator` implementation. - `lib/src/a2ui_agent_connector.dart`: Contains the `A2uiAgentConnector` and `AgentCard` implementations. -- `test/a2ui_client_test.dart`: Unit tests for `A2uiAiClient` and `A2uiAgentConnector`. +- `test/a2ui_content_generator_test.dart`: Unit tests for `A2uiContentGenerator` and `A2uiAgentConnector`. - `example/`: Contains a sample Flutter application demonstrating the usage of the `flutter_genui_a2ui` package. diff --git a/packages/flutter_genui_a2ui/IMPLEMENTATION.md b/packages/flutter_genui_a2ui/IMPLEMENTATION.md deleted file mode 100644 index 3b923aac4..000000000 --- a/packages/flutter_genui_a2ui/IMPLEMENTATION.md +++ /dev/null @@ -1,125 +0,0 @@ -# Implementation Plan: `flutter_genui_a2ui` - -This document outlines the phased implementation plan for creating the `flutter_genui_a2ui` package. - -## Journal - -This section will be updated after each phase to log actions taken, things learned, surprises, and any deviations from the plan. It will be maintained in chronological order. - -### Phase 1 Completion (October 21, 2025) - -- Created the new Flutter package in `/Users/gspencer/code/genui/packages/flutter_genui_a2ui`. -- Removed boilerplate `lib/main.dart` and `test/` directory. -- Updated `pubspec.yaml` with correct description, dependencies (`a2a`, `flutter_genui`, `logging`, `uuid`), and dev dependencies (`dart_flutter_team_lints`, `flutter_test`). Set version to `0.1.0`. -- Created `README.md`, `CHANGELOG.md`, and `LICENSE` files. -- Copied `analysis_options.yaml`. -- Ran `dart fix --apply`, `dart analyze`, and `dart format .` to ensure code quality. -- Committed the initial empty version of the package. - -### Phase 2 Completion (October 21, 2025) - -- Created `lib/src/a2ui_agent_connector.dart` and adapted `A2uiAgentConnector` and `AgentCard` classes from the `a2ui_client` spike. -- Created `lib/src/a2ui_ai_client.dart` to implement the `AiClient` interface from `flutter_genui`. - - Implemented the constructor and internal state as described in `DESIGN.md`. - - Implemented the `getAgentCard` method. - - Implemented the `generateContent` and `generateText` methods to send user messages to the A2A server. - - Implemented the internal `_processA2aStream` method to handle incoming A2A stream events and forward A2UI messages to the `GenUiManager`. - - Implemented the `_handleUiEvent` method to send user interactions back to the A2A server. -- Created `lib/flutter_genui_a2ui.dart` to export the public API of the package. -- Fixed `Logger` name collision in `lib/src/a2ui_ai_client.dart` by hiding `Logger` from `package:a2a/a2a.dart`. -- Modified `A2uiAgentConnector` and `A2uiAiClient` constructors to accept an optional `A2AClient` for testing. -- Updated `test/a2ui_client_test.dart` to inject `FakeA2AClient` via constructors and set `taskId` for relevant tests. -- Ran `dart fix --apply`, `dart analyze`, and `dart format .` to ensure code quality. -- All tests passed. - -### Phase 3 Completion (October 21, 2025) - -- Created a new Flutter application in the `example/` directory. -- Added a dependency on `flutter_genui_a2ui` using a path reference. -- Implemented the chat conversation view, including a `ListView` for messages and a `TextField` for user input. -- Implemented a fixed `GenUiSurface` to render the A2UI-generated UI. -- Initialized the `A2uiAiClient` and `UiAgent` and connected them to the UI. -- Ran `dart fix --apply`, `dart analyze`, and `dart format .` to ensure code quality. - -### Phase 4 Completion (October 21, 2025) - -- Created a comprehensive `README.md` file for the `flutter_genui_a2ui` package. -- Created a `GEMINI.md` file in the package directory that describes the package, its purpose, and implementation details of the package and the layout of the files. -- Ran `dart fix --apply`, `dart analyze`, and `dart format .` to ensure code quality. - -## Phase 1: Initial Package Creation and Setup - -In this phase, we will create the basic structure of the `flutter_genui_a2ui` package. - -- [x] Create a new Flutter package in the directory `/Users/gspencer/code/genui/packages/flutter_genui_a2ui`. -- [x] Remove the boilerplate `lib/flutter_genui_a2ui.dart` and `test/` directory. -- [x] Update the `pubspec.yaml` with the correct description, dependencies (`flutter`, `flutter_genui`, `a2a`, `logging`, `uuid`), and dev dependencies (`flutter_test`, `lints`) using the `pub` tool. Set the version to `0.1.0`. -- [x] Create a `README.md` file with a brief description of the package. -- [x] Create a `CHANGELOG.md` file with an initial entry for version `0.1.0`. -- [x] Create a `LICENSE` file with a license copied from `/Users/gspencer/code/genui/packages/flutter_genui/LICENSE` -- [x] Copy the `analysis_options.yaml` file from `/Users/gspencer/code/genui/packages/flutter_genui/analysis_options.yaml`. -- [x] Commit this initial empty version of the package to the `feature/flutter_genui_a2ui` branch. -- [x] After completing the tasks in this phase, I will: - - [x] Run `dart fix --apply` to clean up the code. - - [x] Run `dart analyze` and fix any issues. - - [x] Run `dart format .` to ensure correct formatting. - - [x] Re-read this `IMPLEMENTATION.md` file to check for any changes. - - [x] Update the "Journal" section in this file with a summary of the phase. - - [x] Use `git diff` to review the changes and present a commit message for your approval before committing. - -## Phase 2: A2A Connection and Message Handling - -In this phase, we will adapt the A2A connection logic from the `a2ui_client` spike and create the `A2uiAiClient`. - -- [x] Create `lib/src/a2ui_agent_connector.dart` and adapt the `A2uiAgentConnector` and `AgentCard` classes from the `a2ui_client` spike. -- [x] Create `lib/src/a2ui_ai_client.dart` to implement the `AiClient` interface from `flutter_genui`. - - [x] Implement the constructor and internal state as described in `DESIGN.md`. - - [x] Implement the `getAgentCard` method. - - [x] Implement the `generateContent` and `generateText` methods to send user messages to the A2A server. - - [x] Implement the internal `_processA2aStream` method to handle incoming A2A stream events and forward A2UI messages to the `GenUiManager`. - - [x] Implement the `_handleUiEvent` method to send user interactions back to the A2A server. -- [x] Create `lib/flutter_genui_a2ui.dart` to export the public API of the package. -- [x] After completing the tasks in this phase, I will: - - [x] Create comprehensive unit tests for the `A2uiAiClient` and `A2uiAgentConnector` in the `test/` directory. - - [x] Run the `dart_fix` tool to clean up the code. - - [x] Run the `analyze_files` tool and fix any issues. - - [x] Run the tests with the `run_tests` tool to ensure they all pass. - - [x] Run `dart_format` tool to ensure correct formatting. - - [x] Re-read this `IMPLEMENTATION.md` file to check for any changes. - - [x] Update the "Journal" section in this file with a summary of the phase. - - [x] Use `git_diff` tool to review the changes and present a commit message for your approval before committing. - -## Phase 3: Example Application - -In this phase, we will build the example application to demonstrate the usage of the `flutter_genui_a2ui` package. - -- [x] Create a new Flutter application in the `example/` directory. -- [x] Add a dependency on `flutter_genui_a2ui` using a path reference. -- [x] Implement the chat conversation view, including a `ListView` for messages and a `TextField` for user input. -- [x] Implement a fixed `GenUiSurface` to render the A2UI-generated UI. -- [x] Initialize the `A2uiAiClient` and `UiAgent` and connect them to the UI. -- [x] Add error handling and loading indicators. -- [x] After completing the tasks in this phase, I will: - - [x] Run the `dart_fix` tool to clean up the code. - - [x] Run the `analyze_files` tool and fix any issues. - - [x] Run the tests with the `run_tests` tool to ensure they all pass. - - [x] Run `dart_format` tool to ensure correct formatting. - - [x] Re-read this `IMPLEMENTATION.md` file to check for any changes. - - [x] Update the "Journal" section in this file with a summary of the phase. - - [x] Use `git_diff` tool to review the changes and present a commit message for your approval before committing.before committing. - -## Phase 4: Finalization and Documentation - -In this final phase, we will create the documentation for the package. - -- [x] Create a comprehensive `README.md` file for the `flutter_genui_a2ui` package, explaining its purpose, how to use it, and including a code example. -- [x] Create a `GEMINI.md` file in the package directory that describes the package, its purpose, and the implementation details of the package and the layout of the files. -- [x] Ask you to inspect the package and say if you are satisfied with it, or if any modifications are needed. -- [x] After completing the tasks in this phase, I will: - - [x] Run the `dart_fix` tool to clean up the code. - - [x] Run the `analyze_files` tool and fix any issues. - - [x] Run the tests with the `run_tests` tool to ensure they all pass. - - [x] Run `dart_format` tool to ensure correct formatting. - - [x] Re-read this `IMPLEMENTATION.md` file to check for any changes. - - [x] Update the "Journal" section in this file with a summary of the phase. - - [x] Use `git_diff` tool to review the changes and present a commit message for your approval before committing.before committing. diff --git a/packages/flutter_genui_a2ui/README.md b/packages/flutter_genui_a2ui/README.md index cbe6c4b6e..be3a18728 100644 --- a/packages/flutter_genui_a2ui/README.md +++ b/packages/flutter_genui_a2ui/README.md @@ -7,7 +7,7 @@ An integration package for `flutter_genui` and the A2UI Streaming UI Protocol. T - Connects to an A2A (Agent-to-Agent) server. - Receives and processes A2UI (Agent-to-UI) protocol messages. - Renders dynamic user interfaces using `flutter_genui`'s `GenUiSurface`. -- Provides an `A2uiAiClient` implementation for `flutter_genui`'s `UiAgent`. +- Provides an `A2uiContentGenerator` implementation for `flutter_genui`'s `GenUiConversation`. ## Getting Started @@ -27,10 +27,10 @@ Then run `flutter pub get`. To use this package, you need to: 1. Initialize a `GenUiManager` with your desired `Catalog`. -2. Create an `A2uiAiClient` instance, providing the A2A server URL and the `GenUiManager`. -3. Create a `UiAgent` instance with the `A2uiAiClient` and `GenUiManager`. +2. Create an `A2uiContentGenerator` instance, providing the A2A server URL and the `GenUiManager`. +3. Create a `GenUiConversation` instance with the `A2uiContentGenerator` and `GenUiManager`. 4. Use a `GenUiSurface` widget in your Flutter application to render the AI-generated UI. -5. Send user messages to the `UiAgent` using `sendRequest`. +5. Send user messages to the `GenUiConversation` using `sendRequest`. Here's a basic example: @@ -69,19 +69,19 @@ class _ChatScreenState extends State { final TextEditingController _textController = TextEditingController(); final GenUiManager _genUiManager = GenUiManager(catalog: CoreCatalogItems.asCatalog()); - late final A2uiAiClient _aiClient; - late final UiAgent _uiAgent; + late final A2uiContentGenerator _contentGenerator; + late final GenUiConversation _uiAgent; final List _messages = []; @override void initState() { super.initState(); - _aiClient = A2uiAiClient( + _contentGenerator = A2uiContentGenerator( serverUrl: Uri.parse('http://localhost:8080'), // Replace with your A2A server URL genUiManager: _genUiManager, ); - _uiAgent = UiAgent( - aiClient: _aiClient, + _uiAgent = GenUiConversation( + contentGenerator: _contentGenerator, genUiManager: _genUiManager, ); } @@ -91,7 +91,7 @@ class _ChatScreenState extends State { _textController.dispose(); _uiAgent.dispose(); _genUiManager.dispose(); - _aiClient.dispose(); + _contentGenerator.dispose(); super.dispose(); } diff --git a/packages/flutter_genui_firebase_ai/README.md b/packages/flutter_genui_firebase_ai/README.md index f19ebaf96..7f5a1fc79 100644 --- a/packages/flutter_genui_firebase_ai/README.md +++ b/packages/flutter_genui_firebase_ai/README.md @@ -4,7 +4,7 @@ This package provides the integration between `flutter_genui` and the Firebase A ## Features -- **FirebaseAiClient:** An implementation of `AiClient` that connects to the Firebase AI SDK. +- **FirebaseAiContentGenerator:** An implementation of `ContentGenerator` that connects to the Firebase AI SDK. - **GeminiContentConverter:** Converts between the generic `ChatMessage` and the `firebase_ai` specific `Content` classes. - **GeminiSchemaAdapter:** Adapts schemas from `json_schema_builder` to the `firebase_ai` format. @@ -12,17 +12,18 @@ This package provides the integration between `flutter_genui` and the Firebase A To use this package, you will need to have a Firebase project set up and the Firebase AI SDK configured. -Then, you can create an instance of `FirebaseAiClient` and pass it to your `GenUiConversation`: +Then, you can create an instance of `FirebaseAiContentGenerator` and pass it to your `GenUiConversation`: ```dart +final catalog = CoreCatalogItems.asCatalog(); final genUiManager = GenUiManager(catalog: catalog); -final aiClient = FirebaseAiClient( +final contentGenerator = FirebaseAiContentGenerator( + catalog: catalog, systemInstruction: 'You are a helpful assistant.', - tools: genUiManager.getTools(), ); final genUiConversation = GenUiConversation( genUiManager: genUiManager, - aiClient: aiClient, + contentGenerator: contentGenerator, ... ); ``` diff --git a/packages/flutter_genui_firebase_ai/lib/src/gemini_content_converter.dart b/packages/flutter_genui_firebase_ai/lib/src/gemini_content_converter.dart index c5dc6f272..bb2d220c7 100644 --- a/packages/flutter_genui_firebase_ai/lib/src/gemini_content_converter.dart +++ b/packages/flutter_genui_firebase_ai/lib/src/gemini_content_converter.dart @@ -8,15 +8,15 @@ import 'package:firebase_ai/firebase_ai.dart' as firebase_ai; import 'package:flutter_genui/flutter_genui.dart'; /// An exception thrown by this package. -class AiClientException implements Exception { - /// Creates an [AiClientException] with the given [message]. - AiClientException(this.message); +class ContentConverterException implements Exception { + /// Creates an [ContentConverterException] with the given [message]. + ContentConverterException(this.message); /// The message associated with the exception. final String message; @override - String toString() => '$AiClientException: $message'; + String toString() => '$ContentConverterException: $message'; } /// A class to convert between the generic `ChatMessage` and the `firebase_ai` @@ -72,7 +72,7 @@ class GeminiContentConverter { // representation. result.add(firebase_ai.TextPart('Image at ${part.url}')); } else { - throw AiClientException('ImagePart has no data.'); + throw ContentConverterException('ImagePart has no data.'); } case ToolCallPart(): result.add(firebase_ai.FunctionCall(part.toolName, part.arguments));