A complete AI chat application built with Flutter Web. Features clean architecture, dual-mode operation (mock/real AI), responsive design, and streaming responses.
The template works immediately with realistic mock responses - no API key needed!
- Get your free API key from OpenRouter.ai
- Add it to
lib/core/config.dart:static const String testingOnlyOpenRouterApiKey = 'your-key-here';
- Start chatting with 20+ AI models!
- Demo Mode: Works instantly with mock responses
- Real AI: Connect to 20+ models via OpenRouter
- Streaming: Real-time response generation
- Responsive: Mobile drawer + desktop sidebar
- Themes: Light/dark/system theme support
- Clean Architecture: Provider pattern with repository abstraction
🎨 UI Layer (widgets/) - Layout, messages, input components
🧠 State Layer (presentation/) - ChatProvider, ThemeProvider
📡 Service Layer (data/) - Repositories, AI services, mock responses
⚙️ Core Layer (core/) - Configuration, models
Key Components:
- ChatProvider: Manages conversations, messages, streaming
- Repository Pattern: Abstracts AI service backends (OpenRouter, Firebase, Supabase)
- MockResponsesService: Realistic demo responses with streaming simulation
Demo Mode (Default): Mock responses, no API key required
Development Mode: Real AI with API key in config or --dart-define
Production Mode: Backend proxy with server-side API keys
// In main.dart
MaterialApp(title: 'My AI Assistant')
// In AppConfig
static const String appName = 'My AI Assistant';// In AppConfig
static const List<String> availableModels = [
'openai/gpt-4o-mini',
'anthropic/claude-3.5-sonnet',
'my-custom/model-id', // Add here
];// In theme.dart
class LightColors {
static const primary = Color(0xFF6366F1); // Your brand color
}Development: Safe to use testingOnlyOpenRouterApiKey for local testing
Production: BackendProvider.direct - API keys become public!
Recommended Production Setup:
// 1. Switch to backend mode
static const BackendProvider backendProvider = BackendProvider.firebase;
// 2. Configure proxy URL
static const String firebaseProxyUrl = 'https://your-project.cloudfunctions.net/chatProxy';class PersistentChatProvider extends ChatProvider {
late final HiveBox<Conversation> _conversationBox;
@override
List<Conversation> get conversations => _conversationBox.values.toList();
}class MyAIServiceRepository implements ChatRepository {
@override
Stream<ChatEvent> streamChat({required List<Message> history, required String modelId}) async* {
// Your custom AI service integration
yield* myService.streamResponse(history).map(ResponseChunk.new);
}
}class VoicePromptInput extends PromptInputComplete {
Widget _buildVoiceButton() => IconButton(
icon: Icon(_isListening ? LucideIcons.micOff : LucideIcons.mic),
onPressed: _toggleVoiceInput,
);
}lib/
├── core/config.dart # API keys, models, backend selection
├── data/
│ ├── models.dart # Message, Conversation, ChatStatus
│ ├── repositories.dart # ChatRepository interface
│ └── services/ # OpenRouter, mock responses, thread naming
├── presentation/
│ ├── providers/ # ChatProvider, ThemeProvider
│ └── pages/chat_page.dart # Main chat interface
└── widgets/ # UI components (sidebar, messages, input)