Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: (_) {},
Expand All @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The property host does not exist on GenUiManager. The GenUiManager instance itself is the GenUiHost that should be passed to the GenUiSurface. You can either use _genUiConversation.host which is a getter for genUiManager, or access _genUiConversation.genUiManager directly.

Suggested change
host: _genUiConversation.genUiManager.host,
host: _genUiConversation.genUiManager,

surfaceId: _surfaceId,
onEvent: _handleEvent,
);
Expand Down
6 changes: 3 additions & 3 deletions examples/travel_app/integration_test/app_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
20 changes: 12 additions & 8 deletions examples/travel_app/test/main_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>();
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);
Expand All @@ -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.
Expand Down
7 changes: 3 additions & 4 deletions packages/flutter_genui/.guides/examples/riddles.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -65,15 +65,14 @@ class _MyHomePageState extends State<MyHomePage> {
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,
Expand Down
48 changes: 24 additions & 24 deletions packages/flutter_genui/.guides/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -70,4 +70,4 @@ requests:
<key>com.apple.security.network.client</key>
<true/>
</dict>
```
```
20 changes: 10 additions & 10 deletions packages/flutter_genui/.guides/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -60,7 +60,7 @@ final aiClient = FirebaseAiClient(
);
late final _uiAgent = UiAgent(
genUiManager: genUiManager,
aiClient: aiClient,
contentGenerator: contentGenerator,
onSurfaceAdded: _onSurfaceAdded,
onSurfaceDeleted: (_) {},
);
Expand Down
10 changes: 5 additions & 5 deletions packages/flutter_genui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class YourContentGenerator implements ContentGenerator {
@override
ValueListenable<bool> get isProcessing => ValueNotifier(false); // Replace
@override
Future<void> sendRequest(Iterable<ChatMessage> messages) async { /* ... */ }
Future<void> sendRequest(ChatMessage message, {Iterable<ChatMessage>? history}) async { /* ... */ }
@override
void dispose() { /* ... */ }
}
Expand Down Expand Up @@ -116,7 +116,7 @@ class _MyAppState extends State<MyApp> {
itemCount: _surfaceIds.length,
itemBuilder: (context, index) {
return GenUiSurface(
host: _genUiConversation.host,
host: _genUiConversation.host,
surfaceId: _surfaceIds[index],
);
},
Expand Down Expand Up @@ -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;
Expand Down
29 changes: 15 additions & 14 deletions packages/flutter_genui/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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)
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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.
Expand All @@ -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.
);
Expand All @@ -150,7 +151,7 @@ provider.
void dispose() {
_textController.dispose();
_genUiConversation.dispose();
_genUiManager.dispose();

super.dispose();
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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...".

Expand Down
Loading
Loading