genui_x 0.0.8
genui_x: ^0.0.8 copied to clipboard
Connect any AI backend — Claude, OpenAI-compatible, or custom proxy — to Google's genui (Generative UI) framework.
genui_x #
A lightweight AI backend adapter for Google's genui (Generative UI) framework.
Connect any AI backend — Anthropic Claude, OpenAI-compatible APIs, or your own proxy — to genui with no changes to the core framework.
What it does #
genui_x provides GenuiXTransport, a drop-in implementation of genui's Transport interface. It connects your AI backend to the genui rendering pipeline so the model can dynamically build Flutter UIs from your widget catalog.
How it works:
GenuiXTransportsends the full A2UI widget schema to the AI as a system prompt.- The model responds with A2UI JSON blocks (e.g.
createSurface,updateComponents) embedded in its text output. - genui's built-in
A2uiParserTransformerextracts these blocks and renders the widgets — automatically.
No tool-calling setup required. No custom parsers. Just plug in your API key.
Setup #
1. Add dependencies #
# pubspec.yaml
dependencies:
genui: ^0.8.0
genui_x: ^0.0.7
2. Create your catalog #
import 'package:genui/genui.dart';
import 'package:json_schema_builder/json_schema_builder.dart';
final myCatalog = Catalog(
[
CatalogItem(
name: 'WeatherWidget',
dataSchema: S.object(
description: 'Shows weather for a city.',
properties: {
'city': S.string(description: 'City name'),
'temperature': S.number(description: 'Temp in Celsius'),
'condition': S.string(description: 'e.g. Sunny, Rainy'),
},
required: ['city', 'temperature', 'condition'],
),
widgetBuilder: (ctx) {
final data = ctx.data as Map<String, dynamic>;
return WeatherWidget(
city: data['city'] as String,
temperature: (data['temperature'] as num).toDouble(),
condition: data['condition'] as String,
);
},
),
],
catalogId: 'com.example.my_catalog',
);
3. Wire up the conversation #
import 'package:genui/genui.dart';
import 'package:genui_x/genui_x.dart';
final transport = GenuiXTransport(
apiKey: 'sk-ant-your-key-here', // Never hardcode in production
catalog: myCatalog,
// model: 'claude-sonnet-4-6', // Optional — default is claude-haiku-4-5
// baseUrl: 'https://my-proxy', // Optional — for proxy backends
);
final controller = SurfaceController(catalogs: [myCatalog]);
final conversation = Conversation(
controller: controller,
transport: transport,
);
// Send a message
await conversation.sendRequest(ChatMessage.user('What is the weather in Tokyo?'));
// Render active surfaces in your widget tree
ValueListenableBuilder(
valueListenable: conversation.state,
builder: (context, state, _) {
return Column(
children: state.surfaces.map((id) =>
Surface(surfaceContext: controller.contextFor(id)),
).toList(),
);
},
);
API key security #
Never hardcode your API key in client-side code.
Recommended approaches:
- Pass it via
--dart-define=CLAUDE_API_KEY=sk-ant-...during development. - In production, route through your own backend proxy and set
baseUrlto your proxy URL.
You can use .env.example as a template for local development values.
Using LiteLLM or a proxy #
If your proxy uses Authorization headers or a custom path, configure the transport like this:
final transport = GenuiXTransport(
apiKey: 'your-key',
catalog: myCatalog,
baseUrl: 'https://your-proxy.example.com',
endpointPath: '/v1/messages',
apiKeyHeader: 'authorization',
apiKeyPrefix: 'Bearer ',
);
For OpenAI-style streaming endpoints, set the stream format and path:
final transport = GenuiXTransport(
apiKey: 'your-key',
catalog: myCatalog,
baseUrl: 'https://your-proxy.example.com',
endpointPath: '/v1/chat/completions',
apiKeyHeader: 'authorization',
apiKeyPrefix: 'Bearer ',
streamFormat: GenuiXStreamFormat.openai,
requestBodyOverrides: const {
'response_format': {'type': 'json_object'},
},
);
Models #
| Model | Speed | Cost | Recommended for |
|---|---|---|---|
claude-haiku-4-5-20251001 |
Fast | Low | Default, prototyping |
claude-sonnet-4-6 |
Balanced | Medium | Production quality |
claude-opus-4-6 |
Slow | High | Complex UIs |
Example #
See the example/ folder for a working chat app that renders a
WeatherWidget dynamically when the user asks about weather.
cd example
flutter run --dart-define=CLAUDE_API_KEY=sk-ant-your-key-here
Minimal example:
cd example
flutter run -t lib/minimal_main.dart --dart-define=CLAUDE_API_KEY=sk-ant-your-key-here
Transport controls #
Cancel an in-flight request #
// Stop the current stream and reset isLoading to false.
transport.cancel();
Clear conversation history #
// Start a fresh conversation without creating a new transport.
transport.clearHistory();
Loading state #
// Drive a loading indicator without manual state tracking.
ValueListenableBuilder<bool>(
valueListenable: transport.isLoading,
builder: (context, loading, _) {
return loading ? const CircularProgressIndicator() : const SizedBox.shrink();
},
);
Debug logging #
final transport = GenuiXTransport(
apiKey: 'your-key',
catalog: myCatalog,
debug: true, // prints request URL, status code, and errors to console
);
Limitations #
- Streaming only — non-streaming mode is not supported.
- Flutter Web — direct API calls to Anthropic will fail due to CORS. Use the
baseUrlparameter to route through a backend proxy. - genui alpha — genui itself is in early development; breaking changes may occur.