Skip to content

Commit 61674dd

Browse files
committed
Merge remote-tracking branch 'upstream/main' into fix-padding
2 parents 88ddaae + bd4c750 commit 61674dd

File tree

76 files changed

+761
-665
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+761
-665
lines changed

CONTRIBUTING.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,10 @@ releasing a new version.
113113
[P1]: https://github.com/flutter/genui/labels?q=P1
114114
[P2]: https://github.com/flutter/genui/labels?q=P2
115115
[P3]: https://github.com/flutter/genui/labels?q=P3
116+
117+
## pubspec.lock files
118+
119+
`pubspec.lock` files are not git ignored to make the bots faster.
120+
121+
If you include `pubspec.lock` file to your PR, make sure to run `flutter pub upgrade`,
122+
when your Flutter is latest at beta channel.

README.md

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ See the [Getting started with GenUI](https://www.youtube.com/watch?v=nWr6eZKM6no
77

88
[<img src="docs/assets/genui_intro_video_still.png" alt="GenUI Intro video still" height="500">](https://www.youtube.com/watch?v=nWr6eZKM6no)
99

10-
## Status: Highly Experimental
10+
## Status: highly experimental
1111

1212
This is a highly experimental package, which means the API will change (sometimes drastically).
1313
[Feedback is very welcome](https://github.com/flutter/genui/issues/new/choose).
@@ -41,16 +41,16 @@ chatbots and next-generation agent-based user experiences.
4141
- Create dynamically composed UIs: an agent can generate a complete form with sliders, date pickers,
4242
and text fields on the fly based on a user's request to "book a flight."
4343

44-
## Look & Feel
44+
## Look & feel
4545

46-
### Interactive [Travel App Example](examples/travel_app/)
46+
### Interactive [travel app example](examples/travel_app/)
4747

4848
<img src="docs/assets/travel_app_genui_example.gif" alt="GenUI Travel App Demo" height="500">
4949

5050
_The GIF above shows how GenUI enables dynamic, interactive UI generation,_
5151
_instead of text descriptions or code from a traditional AI coding agent._
5252

53-
### Core Difference
53+
### Core difference
5454

5555
This UI is not generated in the form of code; rather, it's generated at runtime
5656
based on a widget catalog from the developers' project.
@@ -72,7 +72,7 @@ based on a widget catalog from the developers' project.
7272
- **Basic Layout:** LLM-driven basic layout generation.
7373
- **Any Model:** Integrate with any LLM that can generate structured JSON output.
7474

75-
## Connecting to an AI Agent
75+
## Connecting to an AI agent
7676

7777
The `genui` framework uses a `ContentGenerator` to communicate with a generative AI model,
7878
allowing `genui` to be backend agnostic. You can choose the implementation that best fits
@@ -84,15 +84,33 @@ See the package table below for more details on each.
8484

8585
## Packages
8686

87-
| Package | Description |
88-
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
89-
| [genui](packages/genui/) | The core framework to employ Generative UI. |
90-
| [genui_firebase_ai](packages/genui_firebase_ai/) | Provides **`FirebaseAiContentGenerator`** to connect to Gemini via Firebase AI Logic. This is the recommended approach for production apps based on client-side agents. |
91-
| [genui_google_generative_ai](packages/genui_google_generative_ai/) | Provides **`GoogleGenerativeAiContentGenerator`** for connecting to the Google Generative AI API with only an API key. Ideal for getting started quickly. |
92-
| [genui_a2ui](packages/genui_a2ui/) | Provides **`A2uiContentGenerator`** for connecting to any server that implements the [A2UI protocol](https://a2ui.org). Use this for integrating with custom agent backends. |
93-
| [json_schema_builder](packages/json_schema_builder/) | A fully featured Dart JSON Schema package with validation, used by the core framework to define widget data structures. |
94-
95-
## Getting Started
87+
| Package | Description | Version |
88+
| :--- | :--- | :--- |
89+
| [genui](packages/genui/) | The core framework to employ Generative UI. | [![pub package](https://img.shields.io/pub/v/genui.svg)](https://pub.dev/packages/genui) |
90+
| [genui_firebase_ai](packages/genui_firebase_ai/) | Provides **`FirebaseAiContentGenerator`** to connect to Gemini via Firebase AI Logic. This is the recommended approach for production apps based on client-side agents. | [![pub package](https://img.shields.io/pub/v/genui_firebase_ai.svg)](https://pub.dev/packages/genui_firebase_ai) |
91+
| [genui_google_generative_ai](packages/genui_google_generative_ai/) | Provides **`GoogleGenerativeAiContentGenerator`** for connecting to the Google Generative AI API with only an API key. Ideal for getting started quickly. | [![pub package](https://img.shields.io/pub/v/genui_google_generative_ai.svg)](https://pub.dev/packages/genui_google_generative_ai) |
92+
| [genui_a2ui](packages/genui_a2ui/) | Provides **`A2uiContentGenerator`** for connecting to any server that implements the [A2UI protocol](https://a2ui.org). Use this for integrating with custom agent backends. | [![pub package](https://img.shields.io/pub/v/genui_a2ui.svg)](https://pub.dev/packages/genui_a2ui) |
93+
| [json_schema_builder](packages/json_schema_builder/) | A fully featured Dart JSON Schema package with validation, used by the core framework to define widget data structures. | [![pub package](https://img.shields.io/pub/v/json_schema_builder.svg)](https://pub.dev/packages/json_schema_builder) |
94+
95+
### Dependencies
96+
97+
This diagram shows how packages depend on each other and how examples use them.
98+
99+
```mermaid
100+
graph TD
101+
examples/simple_chat --> genui_google_generative_ai
102+
examples/simple_chat --> genui_firebase_ai
103+
examples/travel_app --> genui_google_generative_ai
104+
examples/travel_app --> genui_firebase_ai
105+
examples/verdure --> genui_a2ui
106+
examples/custom_backend --> genui
107+
genui --> json_schema_builder
108+
genui_a2ui --> genui
109+
genui_firebase_ai --> genui
110+
genui_google_generative_ai --> genui
111+
```
112+
113+
## Getting started
96114

97115
See the [genui getting started guide](packages/genui/README.md#getting-started-with-genui).
98116

examples/catalog_gallery/lib/samples_view.dart

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class _SamplesViewState extends State<SamplesView> {
3131
List<File> _sampleFiles = [];
3232
File? _selectedFile;
3333
Sample? _selectedSample;
34-
late GenUiManager _genUiManager;
34+
late A2uiMessageProcessor _a2uiMessageProcessor;
3535
final List<String> _surfaceIds = [];
3636
int _currentSurfaceIndex = 0;
3737
StreamSubscription<GenUiUpdate>? _surfaceSubscription;
@@ -40,7 +40,7 @@ class _SamplesViewState extends State<SamplesView> {
4040
@override
4141
void initState() {
4242
super.initState();
43-
_genUiManager = GenUiManager(catalogs: [widget.catalog]);
43+
_a2uiMessageProcessor = A2uiMessageProcessor(catalogs: [widget.catalog]);
4444
_loadSamples();
4545
_setupSurfaceListener();
4646
}
@@ -49,12 +49,14 @@ class _SamplesViewState extends State<SamplesView> {
4949
void dispose() {
5050
_surfaceSubscription?.cancel();
5151
_messageSubscription?.cancel();
52-
_genUiManager.dispose();
52+
_a2uiMessageProcessor.dispose();
5353
super.dispose();
5454
}
5555

5656
void _setupSurfaceListener() {
57-
_surfaceSubscription = _genUiManager.surfaceUpdates.listen((update) {
57+
_surfaceSubscription = _a2uiMessageProcessor.surfaceUpdates.listen((
58+
update,
59+
) {
5860
if (update is SurfaceAdded) {
5961
if (!_surfaceIds.contains(update.surfaceId)) {
6062
setState(() {
@@ -107,9 +109,10 @@ class _SamplesViewState extends State<SamplesView> {
107109
_surfaceIds.clear();
108110
_currentSurfaceIndex = 0;
109111
});
110-
// Re-create GenUiManager to ensure a clean state for the new sample.
111-
_genUiManager.dispose();
112-
_genUiManager = GenUiManager(catalogs: [widget.catalog]);
112+
// Re-create A2uiMessageProcessor to ensure a clean state for the new
113+
// sample.
114+
_a2uiMessageProcessor.dispose();
115+
_a2uiMessageProcessor = A2uiMessageProcessor(catalogs: [widget.catalog]);
113116
_setupSurfaceListener();
114117

115118
try {
@@ -120,7 +123,7 @@ class _SamplesViewState extends State<SamplesView> {
120123
});
121124

122125
_messageSubscription = sample.messages.listen(
123-
_genUiManager.handleMessage,
126+
_a2uiMessageProcessor.handleMessage,
124127
onError: (Object e) {
125128
debugPrint('Error processing message: $e');
126129
if (!context.mounted) return;
@@ -226,7 +229,7 @@ class _SamplesViewState extends State<SamplesView> {
226229
? const Center(child: Text('No surfaces'))
227230
: GenUiSurface(
228231
key: ValueKey(_surfaceIds[_currentSurfaceIndex]),
229-
host: _genUiManager,
232+
host: _a2uiMessageProcessor,
230233
surfaceId: _surfaceIds[_currentSurfaceIndex],
231234
),
232235
),

examples/custom_backend/lib/main.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ class _IntegrationTesterState extends State<_IntegrationTester> {
7777
final _controller = TextEditingController(text: requestText);
7878

7979
final _protocol = Backend(uiSchema);
80-
late final GenUiManager _genUi = GenUiManager(catalogs: [_catalog]);
80+
late final A2uiMessageProcessor _a2uiMessageProcessor = A2uiMessageProcessor(
81+
catalogs: [_catalog],
82+
);
8183
String? _selectedResponse;
8284
bool _isLoading = false;
8385
String? _errorMessage;
@@ -116,7 +118,7 @@ class _IntegrationTesterState extends State<_IntegrationTester> {
116118
}
117119
_surfaceId = parsedToolCall.surfaceId;
118120
for (final A2uiMessage message in parsedToolCall.messages) {
119-
_genUi.handleMessage(message);
121+
_a2uiMessageProcessor.handleMessage(message);
120122
}
121123
print('UI received for surfaceId=${parsedToolCall.surfaceId}');
122124
setState(() => _isLoading = false);
@@ -157,7 +159,7 @@ class _IntegrationTesterState extends State<_IntegrationTester> {
157159
}
158160
return GenUiSurface(
159161
surfaceId: surfaceId,
160-
host: _genUi,
162+
host: _a2uiMessageProcessor,
161163
defaultBuilder: (_) => const Text('Fallback to defaultBuilder'),
162164
);
163165
}

examples/simple_chat/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The application's logic is contained almost entirely within `lib/main.dart`.
3232
5. **UI Rendering**:
3333
- `GenUiConversation` listens to the stream and processes the `A2uiMessage`s, invoking the appropriate callbacks like `onSurfaceAdded`.
3434
- The `_handleSurfaceAdded` callback adds a new message item to the list, containing the `surfaceId`.
35-
- The `ListView` rebuilds, and a `GenUiSurface` widget is rendered for the AI's message, dynamically building the UI based on the `UiDefinition` managed by `GenUiManager`.
35+
- The `ListView` rebuilds, and a `GenUiSurface` widget is rendered for the AI's message, dynamically building the UI based on the `UiDefinition` managed by `A2uiMessageProcessor`.
3636

3737
## Getting Started
3838

examples/simple_chat/lib/io_get_api_key.dart renamed to examples/simple_chat/lib/api_key/io_get_api_key.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ import 'dart:io';
77
/// API key for Google Generative AI (only needed if using google backend).
88
/// Get an API key from https://aistudio.google.com/app/apikey
99
/// Specify this when running the app with "-D GEMINI_API_KEY=$GEMINI_API_KEY"
10-
const String geminiApiKey = String.fromEnvironment('GEMINI_API_KEY');
10+
const String _geminiApiKey = String.fromEnvironment('GEMINI_API_KEY');
11+
12+
String? debugApiKey;
1113

1214
String getApiKey() {
13-
String apiKey = geminiApiKey.isEmpty
15+
if (debugApiKey != null) {
16+
return debugApiKey!;
17+
}
18+
String apiKey = _geminiApiKey.isEmpty
1419
? Platform.environment['GEMINI_API_KEY'] ?? ''
15-
: geminiApiKey;
20+
: _geminiApiKey;
1621
if (apiKey.isEmpty) {
1722
throw Exception(
1823
'''Gemini API key is required when using google backend. Run the app with a GEMINI_API_KEY as a Dart environment variable, for example by running with -D GEMINI_API_KEY=\$GEMINI_API_KEY or set the GEMINI_API_KEY environment variable in your shell environment.''',

examples/simple_chat/lib/web_get_api_key.dart renamed to examples/simple_chat/lib/api_key/web_get_api_key.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44

55
/// API key for Google Generative AI (only needed if using google backend).
66
/// Get an API key from https://aistudio.google.com/app/apikey
7-
const String geminiApiKey = String.fromEnvironment('GEMINI_API_KEY');
7+
const String _geminiApiKey = String.fromEnvironment('GEMINI_API_KEY');
8+
9+
String? debugApiKey;
810

911
String getApiKey() {
10-
if (geminiApiKey.isEmpty) {
12+
if (debugApiKey != null) {
13+
return debugApiKey!;
14+
}
15+
if (_geminiApiKey.isEmpty) {
1116
throw Exception(
1217
'''Gemini API key is required when using google backend. Run the app with a GEMINI_API_KEY as a Dart environment variable. You can do this by running with -D GEMINI_API_KEY=\$GEMINI_API_KEY''',
1318
);
1419
}
15-
return geminiApiKey;
20+
return _geminiApiKey;
1621
}

examples/simple_chat/lib/main.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import 'package:genui_firebase_ai/genui_firebase_ai.dart';
1111
import 'package:genui_google_generative_ai/genui_google_generative_ai.dart';
1212
import 'package:logging/logging.dart';
1313

14-
import 'configuration.dart';
15-
1614
// If you want to convert to using Firebase AI, run:
1715
//
1816
// sh tool/refresh_firebase.sh <project_id>
@@ -26,7 +24,9 @@ import 'configuration.dart';
2624

2725
// Conditionally import non-web version so we can read from shell env vars in
2826
// non-web version.
29-
import 'io_get_api_key.dart' if (dart.library.html) 'web_get_api_key.dart';
27+
import 'api_key/io_get_api_key.dart'
28+
if (dart.library.html) 'api_key/web_get_api_key.dart';
29+
import 'configuration.dart';
3030
import 'message.dart';
3131

3232
void main() async {
@@ -69,14 +69,14 @@ class _ChatScreenState extends State<ChatScreen> {
6969
final TextEditingController _textController = TextEditingController();
7070
final List<MessageController> _messages = [];
7171
late final GenUiConversation _genUiConversation;
72-
late final GenUiManager _genUiManager;
72+
late final A2uiMessageProcessor _a2uiMessageProcessor;
7373
final ScrollController _scrollController = ScrollController();
7474

7575
@override
7676
void initState() {
7777
super.initState();
7878
final Catalog catalog = CoreCatalogItems.asCatalog();
79-
_genUiManager = GenUiManager(catalogs: [catalog]);
79+
_a2uiMessageProcessor = A2uiMessageProcessor(catalogs: [catalog]);
8080

8181
final systemInstruction =
8282
'''You are a helpful assistant who chats with a user,
@@ -107,7 +107,7 @@ ${GenUiPromptFragments.basicChat}''';
107107
};
108108

109109
_genUiConversation = GenUiConversation(
110-
genUiManager: _genUiManager,
110+
a2uiMessageProcessor: _a2uiMessageProcessor,
111111
contentGenerator: contentGenerator,
112112
onSurfaceAdded: _handleSurfaceAdded,
113113
onTextResponse: _onTextResponse,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2025 The Flutter Authors.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:simple_chat/api_key/io_get_api_key.dart';
7+
import 'package:simple_chat/main.dart';
8+
9+
void main() {
10+
setUp(() {
11+
debugApiKey = 'dummy_api_key';
12+
});
13+
14+
tearDown(() {
15+
debugApiKey = null;
16+
});
17+
18+
testWidgets('Smoke test: App starts without issues', (
19+
WidgetTester tester,
20+
) async {
21+
await tester.pumpWidget(const MyApp());
22+
expect(find.byType(ChatScreen), findsOneWidget);
23+
});
24+
}

examples/travel_app/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ This example highlights several core concepts of the `genui` package:
2323
- **Dynamic UI Generation**: The entire user interface is constructed on-the-fly by the AI based on the conversation.
2424
- **Component Catalog**: The AI builds the UI from a custom, domain-specific catalog of widgets defined in `lib/src/catalog.dart`. This includes widgets like `TravelCarousel`, `ItineraryEntry`, and `OptionsFilterChipInput`.
2525
- **System Prompt Engineering**: The behavior of the AI is guided by a detailed system prompt located in `lib/src/travel_planner_page.dart`. This prompt instructs the AI on how to act like a travel agent and which widgets to use in various scenarios.
26-
- **Dynamic UI State Management**: The `GenUiConversation` and `GenUiManager` from `genui` handle the orchestration of AI interaction, state of the dynamically generated UI surfaces, and event processing.
26+
- **Dynamic UI State Management**: The `GenUiConversation` and `A2uiMessageProcessor` from `genui` handle the orchestration of AI interaction, state of the dynamically generated UI surfaces, and event processing.
2727
- **Multiple AI Backends**: The app supports switching between **Google Generative AI** (direct API) and **Firebase Vertex AI**. This is configured in `lib/src/config/configuration.dart`.
2828
- **Tool Use**: The AI uses tools like `ListHotelsTool` to fetch real-world data (mocked in this example) and present it to the user.
2929
- **Widget Catalog**: A dedicated tab allows developers to inspect all available widgets in the catalog, facilitating development and debugging.

0 commit comments

Comments
 (0)