ai_ui_render 0.1.0
ai_ui_render: ^0.1.0 copied to clipboard
AI-driven JSON to Flutter UI rendering framework. Safely generate Flutter UI from AI prompts with guardrailed component catalogs, streaming support, visibility conditions, and rich action handling.
ai_ui_render #
A Flutter framework for safely generating UI from AI prompts. Render Flutter widgets from JSON with guardrailed component catalogs, streaming support, and rich action handling.
Features #
- Guardrailed Components - AI can only use components you explicitly define in your catalog
- JSON to Widget Rendering - Convert JSON tree structures into Flutter widgets
- Streaming Support - Render UI progressively as AI generates it (JSONL format)
- Visibility Conditions - Show/hide components based on data, auth state, or complex logic
- Rich Actions - Handle user interactions with confirmation dialogs and callbacks
- Data Binding - Dynamic values that resolve from your data model
- Validation - Built-in validators for forms and custom validation support
Screenshots #
Installation #
Add to your pubspec.yaml:
dependencies:
ai_ui_render: ^0.1.0
Then run:
flutter pub get
Quick Start #
1. Define a Component Catalog #
The catalog defines which components AI can generate - this is your safety guardrail:
import 'package:ai_ui_render/ai_ui_render.dart';
import 'package:flutter/material.dart';
final catalog = createCatalog('my-app')
.component(
'Text',
builder: (props, children) => Text(
props['text'] as String? ?? '',
style: TextStyle(
fontSize: (props['size'] as num?)?.toDouble() ?? 14,
fontWeight: props['bold'] == true ? FontWeight.bold : FontWeight.normal,
),
),
hasChildren: false,
description: 'Display text content',
)
.component(
'Button',
builder: (props, children) => ElevatedButton(
onPressed: () {
// Handle action
},
child: Text(props['label'] as String? ?? 'Button'),
),
hasChildren: false,
description: 'Clickable button',
)
.component(
'Card',
builder: (props, children) => Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(children: children),
),
),
description: 'Card container',
)
.build();
2. Create a Registry #
final registry = createRegistryFromCatalog(catalog);
3. Render a UI Tree #
// JSON structure (typically from AI)
final uiTree = UITree.fromJson({
'root': 'main-card',
'elements': {
'main-card': {
'key': 'main-card',
'type': 'Card',
'props': {},
'children': ['greeting', 'action-btn'],
},
'greeting': {
'key': 'greeting',
'type': 'Text',
'props': {'text': 'Hello, World!', 'size': 24, 'bold': true},
'children': [],
},
'action-btn': {
'key': 'action-btn',
'type': 'Button',
'props': {'label': 'Click Me'},
'children': [],
},
},
});
// Render it
StandaloneJsonRenderer(
tree: uiTree,
registry: registry,
)
Streaming UI Generation #
For AI-powered streaming generation:
final streamController = UIStreamController(
UIStreamOptions(
api: 'https://your-api.com/generate',
onComplete: (tree) => print('Generation complete!'),
onError: (error) => print('Error: $error'),
),
);
// Send a prompt
await streamController.send('Create a dashboard with revenue metrics');
// Use in widget
ListenableBuilder(
listenable: streamController,
builder: (context, child) {
return StandaloneJsonRenderer(
tree: streamController.tree,
registry: registry,
loading: streamController.isStreaming,
);
},
)
Visibility Conditions #
Control component visibility based on data or auth state:
// In JSON
{
'key': 'admin-panel',
'type': 'Card',
'props': {},
'visible': {'auth': 'signedIn'}, // Only show when signed in
'children': [],
}
// Complex conditions
{
'visible': {
'and': [
{'auth': 'signedIn'},
{'path': '/user/isAdmin'},
{'gt': [{'path': '/user/level'}, 5]}
]
}
}
Available conditions:
true/false- Always visible/hidden{'path': '/data/path'}- Visible when path is truthy{'auth': 'signedIn'}/{'auth': 'signedOut'}- Auth-based{'and': [...]}- All conditions must be true{'or': [...]}- Any condition must be true{'not': {...}}- Negates a condition{'eq': [a, b]},{'neq': [a, b]}- Equality checks{'gt': [a, b]},{'gte': [a, b]},{'lt': [a, b]},{'lte': [a, b]}- Comparisons
Data Binding #
Use dynamic values that resolve from your data model:
// In JSON props
{
'type': 'Text',
'props': {
'text': {'path': '/user/name'} // Resolves from data model
}
}
// Provide data when rendering
StandaloneJsonRenderer(
tree: uiTree,
registry: registry,
dataModel: {
'user': {'name': 'John Doe', 'level': 10},
},
)
Actions #
Define and handle user actions:
final catalog = createCatalog('my-app')
.action('deleteItem', description: 'Delete an item')
.action('saveForm', description: 'Save form data')
.build();
// In JSON
{
'type': 'Button',
'props': {
'label': 'Delete',
'action': {
'name': 'deleteItem',
'params': {'id': {'path': '/selectedItem/id'}},
'confirm': {
'title': 'Confirm Delete',
'message': 'Are you sure you want to delete this item?',
'variant': 'danger'
}
}
}
}
Validation #
Built-in validators for form fields:
final validators = Validators.compose([
Validators.required('Email is required'),
Validators.email('Invalid email format'),
]);
final result = validators('[email protected]');
print(result.isValid); // true
Available validators:
required- Value must not be null or emptyemail- Valid email formatminLength(n)/maxLength(n)- String length constraintsmin(n)/max(n)- Numeric constraintspattern(regex)- Regex pattern matchingurl- Valid URL format
With Providers #
For full state management integration:
JsonUIProvider(
registry: registry,
initialData: {'user': {'name': 'John'}},
authState: AuthState(isSignedIn: true),
actionHandlers: {
'deleteItem': (params) async {
// Handle delete
},
},
child: JsonRenderer(
tree: uiTree,
registry: registry,
),
)
JSON Tree Structure #
The UI tree uses a flat structure optimized for LLM generation:
{
"root": "main-element-key",
"elements": {
"main-element-key": {
"key": "main-element-key",
"type": "ComponentType",
"props": { "prop1": "value1" },
"children": ["child-key-1", "child-key-2"],
"visible": true
},
"child-key-1": {
"key": "child-key-1",
"type": "Text",
"props": { "text": "Hello" },
"children": []
}
}
}
Generate AI Prompts #
Generate a prompt describing your catalog for AI:
final prompt = generateCatalogPrompt(catalog);
// Use this prompt to instruct AI about available components
Example App #
The example directory contains a comprehensive demo application with 5 interactive pages:
1. Components Gallery #
Showcases all 25+ built-in components organized by category:
- Layout: Card, Row, Column, Container, Center, Expanded, Wrap, Spacer, Divider
- Text: Text, Heading, RichText
- Buttons: Button, IconButton
- Display: Icon, Badge, Avatar, Image, ProgressBar, Chip
- Input: TextField, Checkbox, Switch
- Feedback: Alert, Loading
2. Data Binding #
Demonstrates dynamic values that resolve from your data model with live updates.
3. Visibility Conditions #
Interactive demo of all visibility condition types with toggles to see real-time effects.
4. Actions #
Shows the action system with confirmation dialogs, parameters, and success/error handlers.
5. Streaming #
Simulates AI streaming UI generation with progressive rendering.
Running the Example #
cd example
flutter run
API Reference #
Core Classes #
| Class | Description |
|---|---|
UITree |
Flat tree structure containing all UI elements |
UIElement |
Single UI element with type, props, and children |
AiUiCatalog |
Registry of allowed components (safety guardrail) |
ComponentRegistry |
Maps component types to widget builders |
DynamicValue<T> |
Value that can be literal or path reference |
VisibilityCondition |
Condition for showing/hiding elements |
Action |
User action with params, confirm, callbacks |
Widgets #
| Widget | Description |
|---|---|
JsonRenderer |
Main renderer with Provider integration |
StandaloneJsonRenderer |
Standalone renderer without Provider |
JsonUIProvider |
Combined provider for data, visibility, actions |
UIStreamProvider |
Provider for streaming UI generation |
Functions #
| Function | Description |
|---|---|
createCatalog(name) |
Create a catalog builder |
createRegistryFromCatalog(catalog) |
Create registry from catalog |
generateCatalogPrompt(catalog) |
Generate AI prompt from catalog |
evaluateVisibility(condition, ctx) |
Evaluate visibility condition |
resolveAction(action, dataModel) |
Resolve dynamic values in action |
validateForm(data, validations) |
Validate form data |
Validators #
Validators.required([message]) // Not null or empty
Validators.email([message]) // Valid email format
Validators.minLength(n, [message]) // Min string length
Validators.maxLength(n, [message]) // Max string length
Validators.min(n, [message]) // Min numeric value
Validators.max(n, [message]) // Max numeric value
Validators.pattern(regex, [message]) // Regex pattern
Validators.url([message]) // Valid URL
Validators.compose([...]) // Combine validators
Architecture #
┌─────────────────────────────────────────────────────────┐
│ Your App │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Catalog │ │ Registry │ │ Data Model │ │
│ │ (Components)│ │ (Builders) │ │ (State) │ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ JsonRenderer / StandaloneJsonRenderer ││
│ │ ┌─────────────────────────────────────────────┐ ││
│ │ │ ElementBuilder │ ││
│ │ │ - Resolves dynamic values │ ││
│ │ │ - Evaluates visibility │ ││
│ │ │ - Builds widgets recursively │ ││
│ │ └─────────────────────────────────────────────┘ ││
│ └─────────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐│
│ │ Flutter Widgets ││
│ └─────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘
┌──────────────────────────────────┐
│ AI / LLM │
│ ┌────────────────────────────┐ │
│ │ Catalog Prompt │ │
│ │ (Generated from catalog) │ │
│ └────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────┐ │
│ │ JSON UI Tree │ │
│ │ (Streamed via JSONL) │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
MIT License - see LICENSE for details.