voicebot_flutter 0.4.0
voicebot_flutter: ^0.4.0 copied to clipboard
Drop-in voice and text store assistant for Flutter apps. The same VoiceBot assistant that runs as the web widget, native in your app over WebSocket + PCM16.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:voicebot_flutter/voicebot_flutter.dart';
import 'package:voicebot_flutter/ui.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'VoiceBot SDK Example',
theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
home: const ConnectScreen(),
);
}
}
/// Step 1 — collect the backend `baseUrl` + a dev session token, then launch the
/// storefront demo that mounts the floating [VoicebotLauncher].
class ConnectScreen extends StatefulWidget {
const ConnectScreen({super.key});
@override
State<ConnectScreen> createState() => _ConnectScreenState();
}
class _ConnectScreenState extends State<ConnectScreen> {
final _baseUrl = TextEditingController(text: 'https://api.monoverse.tech');
final _token = TextEditingController();
bool _voice = true;
@override
void dispose() {
_baseUrl.dispose();
_token.dispose();
super.dispose();
}
void _launch() {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (_) => StorefrontScreen(
baseUrl: _baseUrl.text.trim(),
token: _token.text.trim(),
voice: _voice,
),
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('VoiceBot SDK Example')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Enter a backend base URL and a dev session token (JWT), then '
'open the demo. The assistant floats as a button on every screen.',
),
const SizedBox(height: 16),
TextField(
controller: _baseUrl,
decoration: const InputDecoration(
labelText: 'baseUrl',
hintText: 'https://api.monoverse.tech',
),
),
const SizedBox(height: 8),
TextField(
controller: _token,
decoration: const InputDecoration(
labelText: 'session token (dev JWT)',
),
),
SwitchListTile(
value: _voice,
onChanged: (v) => setState(() => _voice = v),
title: const Text('Voice (mic)'),
subtitle: const Text('Off = text-only (no native audio)'),
contentPadding: EdgeInsets.zero,
),
const SizedBox(height: 8),
FilledButton.icon(
onPressed: _launch,
icon: const Icon(Icons.storefront),
label: const Text('Open storefront demo'),
),
],
),
),
);
}
}
/// Step 2 — a mock storefront with the turnkey [VoicebotLauncher] dropped once
/// at the root. The launcher floats over the page, owns the session lifecycle,
/// and wires minimize≠close for you. Tool handlers + live context are wired via
/// [VoicebotLauncher.onClientReady] / [VoicebotLauncher.onSessionStarted].
class StorefrontScreen extends StatelessWidget {
const StorefrontScreen({
required this.baseUrl,
required this.token,
required this.voice,
super.key,
});
final String baseUrl;
final String token;
final bool voice;
@override
Widget build(BuildContext context) {
// Theme the kit (optional) — here a teal accent + bottom-right launcher.
const theme = VoicebotTheme(
corner: VoicebotCorner.bottomRight,
accent: Colors.teal,
labels: VoicebotLabels(title: 'Shop assistant'),
);
return Stack(
children: [
Scaffold(
appBar: AppBar(title: const Text('Demo storefront')),
body: GridView.builder(
padding: const EdgeInsets.all(12),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.8,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
),
itemCount: 6,
itemBuilder: (context, i) => Card(
child: Center(child: Text('Product ${i + 1}')),
),
),
),
// Drop ONCE near the root → floats on this whole screen.
VoicebotLauncher(
client: VoicebotClient.init(
baseUrl: baseUrl,
tokenProvider: StaticTokenProvider.value(token),
),
config: SessionConfig(lang: 'uk', enableVoice: voice),
theme: theme,
onClientReady: (client) {
// Map a navigational action to a deep link the app routes.
client.registerDeepLinkMap('open_product', (params) {
final id = params['id'] ?? params['product_id'];
return id == null ? null : '/product/$id';
});
// Map a client-executed action to a native callback.
client.registerToolHandler('add_to_cart', (args) async {
debugPrint('add_to_cart: $args');
return {'success': true};
});
},
onSessionStarted: (session) {
// Feed live, zero-PII context so the bot knows the current screen.
session.updateContext(const AppContext(screen: 'home', locale: 'uk'));
},
),
],
);
}
}