mugib_voice 0.4.0 copy "mugib_voice: ^0.4.0" to clipboard
mugib_voice: ^0.4.0 copied to clipboard

Official Flutter SDK for Mugib voice agents. One client for VAPI and Google Gemini Live — pass apiKey and agentId only.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:mugib_voice/mugib_voice.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await MugibVoice.initialize();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Mugib Voice Example',
      theme: ThemeData(colorSchemeSeed: Colors.indigo, useMaterial3: true),
      home: const CallScreen(),
    );
  }
}

class CallScreen extends StatefulWidget {
  const CallScreen({super.key});

  @override
  State<CallScreen> createState() => _CallScreenState();
}

class _CallScreenState extends State<CallScreen> {
  static const _apiKey = 'YOUR_PROJECT_API_KEY';

  List<MugibAgentSummary> _agents = [];
  MugibAgentSummary? _selected;
  MugibVoiceClient? _call;
  MugibCallState _state = MugibCallState.idle;
  final List<MugibTranscript> _transcript = [];
  bool _muted = false;
  bool _loadingAgents = false;
  String? _agentError;

  @override
  void initState() {
    super.initState();
    _loadAgents();
  }

  Future<void> _loadAgents() async {
    setState(() {
      _loadingAgents = true;
      _agentError = null;
    });
    try {
      final list = await MugibVoice.listAgents(apiKey: _apiKey);
      setState(() {
        _agents = list;
        _selected = list.isNotEmpty ? list.first : null;
      });
    } catch (e) {
      setState(() => _agentError = '$e');
    } finally {
      setState(() => _loadingAgents = false);
    }
  }

  Future<void> _start() async {
    final agent = _selected;
    if (agent == null) return;

    setState(() => _transcript.clear());
    final call = MugibVoiceClient(apiKey: _apiKey, agentId: agent.id);
    _call = call;
    call.state.listen((s) => setState(() => _state = s));
    call.transcripts.listen((t) => setState(() => _transcript.add(t)));
    call.errors.listen((e) {
      if (!mounted) return;
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text('Error: $e')));
    });
    try {
      await call.start();
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context)
            .showSnackBar(SnackBar(content: Text('$e')));
      }
    }
  }

  Future<void> _end() async {
    await _call?.end();
    await _call?.dispose();
    _call = null;
    setState(() => _muted = false);
  }

  bool get _live =>
      _state == MugibCallState.connected ||
      _state == MugibCallState.listening ||
      _state == MugibCallState.speaking ||
      _state == MugibCallState.connecting;

  @override
  void dispose() {
    _call?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Mugib Voice')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: _loadingAgents
                ? const LinearProgressIndicator()
                : _agentError != null
                    ? Text(_agentError!, style: const TextStyle(color: Colors.red))
                    : DropdownButtonFormField<MugibAgentSummary>(
                        value: _selected,
                        decoration: const InputDecoration(
                          labelText: 'Voice agent',
                          border: OutlineInputBorder(),
                        ),
                        items: _agents
                            .map((a) => DropdownMenuItem(
                                  value: a,
                                  child: Text('${a.name} — ${a.language}'),
                                ))
                            .toList(),
                        onChanged: _live
                            ? null
                            : (v) => setState(() => _selected = v),
                      ),
          ),
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(16),
            color: _colorFor(_state).withOpacity(0.1),
            child: Text(
              _labelFor(_state),
              textAlign: TextAlign.center,
              style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.w600,
                  color: _colorFor(_state)),
            ),
          ),
          Expanded(
            child: ListView.builder(
              padding: const EdgeInsets.all(12),
              itemCount: _transcript.length,
              itemBuilder: (_, i) {
                final t = _transcript[i];
                return Align(
                  alignment:
                      t.isUser ? Alignment.centerRight : Alignment.centerLeft,
                  child: Container(
                    margin: const EdgeInsets.symmetric(vertical: 4),
                    padding: const EdgeInsets.all(10),
                    decoration: BoxDecoration(
                      color: t.isUser
                          ? Colors.indigo.shade100
                          : Colors.grey.shade200,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text(t.text),
                  ),
                );
              },
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(20),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                if (_live) ...[
                  FloatingActionButton(
                    heroTag: 'mute',
                    backgroundColor: _muted ? Colors.orange : Colors.grey,
                    onPressed: () =>
                        setState(() => _muted = _call?.toggleMute() ?? false),
                    child: Icon(_muted ? Icons.mic_off : Icons.mic),
                  ),
                  const SizedBox(width: 24),
                  FloatingActionButton.extended(
                    heroTag: 'end',
                    backgroundColor: Colors.red,
                    onPressed: _end,
                    icon: const Icon(Icons.call_end),
                    label: const Text('End'),
                  ),
                ] else
                  FloatingActionButton.extended(
                    heroTag: 'start',
                    onPressed: _selected == null ? null : _start,
                    icon: const Icon(Icons.call),
                    label: const Text('Start Call'),
                  ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  String _labelFor(MugibCallState s) => switch (s) {
        MugibCallState.idle => 'Tap to start',
        MugibCallState.connecting => 'Connecting…',
        MugibCallState.connected => 'Connected',
        MugibCallState.listening => 'Listening…',
        MugibCallState.speaking => 'Agent speaking…',
        MugibCallState.ended => 'Call ended',
        MugibCallState.error => 'Error',
      };

  Color _colorFor(MugibCallState s) => switch (s) {
        MugibCallState.speaking => Colors.indigo,
        MugibCallState.listening => Colors.green,
        MugibCallState.error => Colors.red,
        MugibCallState.ended => Colors.grey,
        _ => Colors.blueGrey,
      };
}
0
likes
150
points
--
downloads

Documentation

API reference

Publisher

unverified uploader

Official Flutter SDK for Mugib voice agents. One client for VAPI and Google Gemini Live — pass apiKey and agentId only.

Homepage
Repository (GitHub)
View/report issues

Topics

#voice #ai #vapi #websocket #sdk

License

MIT (license)

Dependencies

flutter, flutter_pcm_sound, http, permission_handler, record, vapi, web_socket_channel

More

Packages that depend on mugib_voice