FluidUI - Zero UI Agent App
한국어 (README_KR.md) | English (README.md)
AI-driven dynamic surface assembly for Flutter.
Describe what you need in natural language, and the agent builds the UI for you.
Overview
FluidUI is a Flutter application that implements the GenUI (Generative UI) pattern with an A2UI (Agent-to-UI) protocol. Instead of pre-built screens, the app dynamically assembles native Flutter widgets based on user intent, creating a "Zero UI" experience where the interface materializes on demand.
Key Features
- Natural Language to Native UI - Describe the screen you want, get real Flutter widgets
- Widget Catalog - 8 pre-built widget types (ReservationCard, DatePicker, ActionSlider, etc.)
- High-Bandwidth Feedback Loop - Widget state changes are fed back to the agent in real-time
- Secure Architecture - All AI calls routed through a backend proxy; no API keys on the client
- Dark Mode - Full light/dark theme support via system preference
- Mock Mode - Works offline without a backend for demos and development
Architecture
User Intent (Natural Language)
|
v
+------------------+ +------------------+ +------------------+
| ChatSurfaceScreen| ----> | SurfaceController| ----> | AgentService |
| (Flutter UI) | | (State Manager) | | (HTTP to Proxy) |
+------------------+ +------------------+ +------------------+
^ | |
| v v
+------------------+ +------------------+ +------------------+
| SurfaceRenderer | | SurfaceSpec | | Backend Proxy |
| + WidgetCatalog | <---- | (JSON UI Spec) | <---- | (Node.js/Express)|
+------------------+ +------------------+ +------------------+
|
v
+------------------+
| Claude API |
+------------------+
Feedback Loop
User interacts with widget
-> onWidgetStateChanged(widgetId, newState)
-> Local state updated immediately (optimistic)
-> State sent to agent via backend proxy
-> Agent returns updated SurfaceSpec (or no-op)
-> UI re-rendered with new spec
Project Structure
lib/
main.dart # App entry point, mode selection
core/
app_theme.dart # Light/Dark theme definitions
data_model.dart # Widget state model
surface_spec.dart # UI specification (layout + widgets)
surface_controller.dart # State management & feedback loop
catalog/
widget_catalog.dart # Widget type registry
widgets/
reservation_card.dart # Reservation details card
date_picker_widget.dart # Date selection (native DatePicker)
action_slider_widget.dart # Numeric slider input
info_display.dart # Read-only info card
option_selector.dart # Single/multi select chips
text_input_widget.dart # Text input with debounce
confirm_button.dart # Action/confirm button
image_card.dart # Image display with loading state
a2ui/
a2ui_transport_adapter.dart # JSON -> SurfaceSpec parser
system_prompt.dart # LLM system prompt with catalog schema
agent/
agent_service.dart # Backend proxy HTTP client
mock_agent_service.dart # Offline mock agent
screens/
chat_surface_screen.dart # Main chat + surface UI
surface_renderer.dart # Dynamic widget tree renderer
backend/
server.js # Express proxy server
package.json # Node.js dependencies
.env.example # Environment variable template
Quick Start
Mock Mode (No backend required)
flutter pub get
flutter run
Try these prompts:
- "Restaurant reservation" / "Reservation"
- "Customer survey" / "Survey"
- "App settings" / "Settings"
Live Mode (With AI backend)
1. Start the backend proxy:
cd backend
cp .env.example .env
# Edit .env and set your ANTHROPIC_API_KEY
npm install
npm start
2. Run the Flutter app:
flutter run --dart-define=PROXY_URL=http://localhost:8080
Backend Configuration
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_API_KEY | (required) | Your Anthropic API key |
PORT | 8080 | Server port |
MODEL | claude-sonnet-4-6 | Claude model to use |
ALLOWED_ORIGINS | * | Comma-separated CORS origins |
The backend includes:
- Request validation
- In-memory rate limiting (30 req/min per IP)
- Request logging
- 30-second timeout for API calls
Widget Catalog
| Type | Description | Interactive |
|---|---|---|
ReservationCard | Reservation details with guest count controls | Yes |
DatePicker | Native date picker dialog | Yes |
ActionSlider | Numeric slider with min/max/unit | Yes |
InfoDisplay | Read-only info card with icon and color | No |
OptionSelector | Single or multi-select chip group | Yes |
TextInput | Text field with debounced state updates | Yes |
ConfirmButton | Primary/danger/outline action button | Yes |
ImageCard | Network image with loading indicator | No |
How It Works
- User types a natural language request (e.g., "Book a restaurant for 4 people")
- SurfaceController sends the intent to the agent service
- AgentService calls the backend proxy, which forwards to Claude with a system prompt containing the widget catalog schema
- Claude returns a JSON UI specification instead of text
- A2uiTransportAdapter parses the JSON into a
SurfaceSpec - SurfaceRenderer maps each widget spec to a native Flutter widget via the
WidgetCatalog - User interacts with the generated widgets; state changes flow back to the agent
Tech Stack
- Flutter 3.11+ / Dart 3.11+
- flutter_animate - Widget entrance animations
- http - Backend communication
- provider - Dependency injection ready
- Node.js / Express - Backend API proxy
- Claude API - LLM for UI generation
License
MIT