pipecat 0.2.0 copy "pipecat: ^0.2.0" to clipboard
pipecat: ^0.2.0 copied to clipboard

Flutter plugin for Pipecat — build real-time voice and multimodal AI agents with type-safe Dart bindings over the native iOS and Android client SDKs.

example/lib/main.dart

import 'dart:async';

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

void main() {
  runApp(const MaterialApp(
    debugShowCheckedModeBanner: false,
    home: PipecatExample(),
  ));
}

enum MessageType {
  system,
  user,
  bot,
  functionCall,
}

class ChatMessage {
  final MessageType type;
  final String text;
  final DateTime timestamp;

  ChatMessage({
    required this.type,
    required this.text,
    DateTime? timestamp,
  }) : timestamp = timestamp ?? DateTime.now();
}

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

  @override
  State<PipecatExample> createState() => _PipecatExampleState();
}

class _PipecatExampleState extends State<PipecatExample> {
  late PipecatClient _client;
  final TextEditingController _endpointController = TextEditingController(
    text: 'https://your-pipecat-endpoint.com/connect',
  );
  final TextEditingController _messageController = TextEditingController();
  final ScrollController _scrollController = ScrollController();

  String _status = 'disconnected';
  final List<ChatMessage> _messages = [];
  bool _isConnecting = false;

  @override
  void initState() {
    super.initState();
    _client = PipecatClient(transport: const SmallWebRTCTransport());
    _setupListeners();
    _registerFunctions();
  }

  void _setupListeners() {
    _client.onConnected.listen((_) {
      _addMessage(MessageType.system, 'Connected to Pipecat');
      setState(() {
        _status = 'connected';
        _isConnecting = false;
      });
    });

    _client.onDisconnected.listen((_) {
      _addMessage(MessageType.system, 'Disconnected from Pipecat');
      setState(() {
        _status = 'disconnected';
        _isConnecting = false;
      });
    });

    _client.onTransportStateChanged.listen((state) {
      _addMessage(MessageType.system, 'Transport state: ${state.name}');
      setState(() => _status = state.name);
    });

    _client.onUserTranscript.listen((transcript) {
      if (transcript.finalStatus ?? false) {
        _addMessage(MessageType.user, transcript.text);
      }
    });

    _client.onBotTranscript.listen((text) {
      _addMessage(MessageType.bot, text);
    });

    _client.onBackendError.listen((error) {
      _addMessage(MessageType.system, 'Error: $error');
      setState(() {
        _isConnecting = false;
        _status = 'error';
      });
    });
  }

  void _registerFunctions() {
    _client.registerFunctionHandler('get_weather', (data) async {
      final args = data.args;
      String location = 'Unknown';
      if (args is ValueObject) {
        final loc = args.properties['location'];
        if (loc is ValueString) location = loc.value;
      }
      _addMessage(MessageType.functionCall, 'get_weather for $location');
      // Simulate network delay
      await Future.delayed(const Duration(seconds: 1));
      return ValueObject({
        'weather': const ValueString('sunny'),
        'temperature': const ValueString('72F'),
      });
    });
  }

  void _addMessage(MessageType type, String text) {
    if (!mounted) return;
    setState(() {
      _messages.add(ChatMessage(type: type, text: text));
    });
    // Auto-scroll to bottom
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_scrollController.hasClients) {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeOut,
        );
      }
    });
  }

  Future<void> _start() async {
    setState(() {
      _isConnecting = true;
      _messages.clear();
    });

    try {
      _addMessage(MessageType.system, 'Initializing devices...');
      await _client.initialize(enableMic: true, enableCam: false);
      await _client.initDevices();

      _addMessage(MessageType.system, 'Connecting to ${_endpointController.text}...');
      await _client.startBotAndConnect(endpoint: _endpointController.text);
    } catch (e) {
      _addMessage(MessageType.system, 'Failed to start: $e');
      setState(() => _isConnecting = false);
    }
  }

  Future<void> _stop() async {
    try {
      await _client.disconnect();
    } catch (e) {
      _addMessage(MessageType.system, 'Failed to disconnect: $e');
    }
  }

  Future<void> _sendMessage() async {
    final text = _messageController.text.trim();
    if (text.isEmpty) return;

    try {
      _messageController.clear();
      await _client.sendText(text);
      _addMessage(MessageType.user, text);
    } catch (e) {
      _addMessage(MessageType.system, 'Failed to send message: $e');
    }
  }

  @override
  void dispose() {
    _client.dispose();
    _endpointController.dispose();
    _messageController.dispose();
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Pipecat Flutter Rich Example'),
        actions: [
          Center(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16.0),
              child: Text(
                _status,
                style: const TextStyle(fontWeight: FontWeight.bold),
              ),
            ),
          ),
        ],
      ),
      body: Column(
        children: [
          _buildConnectionPanel(),
          _buildHardwarePanel(),
          Expanded(child: _buildChatPanel()),
          _buildInputPanel(),
        ],
      ),
    );
  }

  Widget _buildConnectionPanel() {
    return Card(
      margin: const EdgeInsets.all(8.0),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          children: [
            TextField(
              controller: _endpointController,
              decoration: const InputDecoration(
                labelText: 'Bot Endpoint',
                isDense: true,
              ),
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: _isConnecting || _status == 'connected' ? null : _start,
                    child: const Text('Connect'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: OutlinedButton(
                    onPressed: _status == 'connected' ? _stop : null,
                    child: const Text('Disconnect'),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildHardwarePanel() {
    return ValueListenableBuilder<HardwareState>(
      valueListenable: _client.hardwareState,
      builder: (context, state, child) {
        return Card(
          margin: const EdgeInsets.symmetric(horizontal: 8.0),
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              children: [
                Row(
                  children: [
                    Expanded(
                      child: _buildDeviceDropdown(
                        label: 'Microphone',
                        items: state.availableMics,
                        selected: state.selectedMic,
                        onChanged: (id) => _client.updateMic(id!),
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: _buildDeviceDropdown(
                        label: 'Camera',
                        items: state.availableCams,
                        selected: state.selectedCam,
                        onChanged: (id) => _client.updateCam(id!),
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    IconButton.filledTonal(
                      onPressed: () => _client.enableMic(!state.isMicEnabled),
                      icon: Icon(state.isMicEnabled ? Icons.mic : Icons.mic_off),
                      color: state.isMicEnabled ? Colors.green : Colors.red,
                      tooltip: state.isMicEnabled ? 'Mute Mic' : 'Unmute Mic',
                    ),
                    IconButton.filledTonal(
                      onPressed: () => _client.enableCam(!state.isCamEnabled),
                      icon: Icon(state.isCamEnabled ? Icons.videocam : Icons.videocam_off),
                      color: state.isCamEnabled ? Colors.green : Colors.red,
                      tooltip: state.isCamEnabled ? 'Disable Cam' : 'Enable Cam',
                    ),
                    IconButton.filledTonal(
                      onPressed: _status == 'connected' ? () => _client.disconnectBot() : null,
                      icon: const Icon(Icons.stop_circle),
                      color: Colors.orange,
                      tooltip: 'Disconnect Bot',
                    ),
                  ],
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  Widget _buildDeviceDropdown({
    required String label,
    required List<MediaDeviceInfo> items,
    required MediaDeviceInfo? selected,
    required ValueChanged<String?> onChanged,
  }) {
    return DropdownButtonFormField<String>(
      key: ValueKey(selected?.id),
      initialValue: selected?.id,
      decoration: InputDecoration(
        labelText: label,
        isDense: true,
        border: const OutlineInputBorder(),
      ),
      items: items.map((device) {
        return DropdownMenuItem(
          value: device.id,
          child: Text(
            device.label,
            overflow: TextOverflow.ellipsis,
          ),
        );
      }).toList(),
      onChanged: onChanged,
    );
  }

  Widget _buildChatPanel() {
    return ListView.builder(
      controller: _scrollController,
      padding: const EdgeInsets.all(8.0),
      itemCount: _messages.length,
      itemBuilder: (context, index) {
        final msg = _messages[index];
        return _buildChatBubble(msg);
      },
    );
  }

  Widget _buildChatBubble(ChatMessage msg) {
    Color bubbleColor;
    Alignment alignment;
    TextStyle textStyle = const TextStyle(color: Colors.black);

    switch (msg.type) {
      case MessageType.system:
        return Center(
          child: Padding(
            padding: const EdgeInsets.symmetric(vertical: 4.0),
            child: Text(
              msg.text,
              style: TextStyle(color: Colors.grey[600], fontSize: 12),
              textAlign: TextAlign.center,
            ),
          ),
        );
      case MessageType.user:
        bubbleColor = Colors.blue[100]!;
        alignment = Alignment.centerRight;
        break;
      case MessageType.bot:
        bubbleColor = Colors.green[100]!;
        alignment = Alignment.centerLeft;
        break;
      case MessageType.functionCall:
        bubbleColor = Colors.purple[100]!;
        alignment = Alignment.centerLeft;
        textStyle = const TextStyle(color: Colors.purple, fontWeight: FontWeight.bold);
        break;
    }

    return Align(
      alignment: alignment,
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
        padding: const EdgeInsets.all(12.0),
        decoration: BoxDecoration(
          color: bubbleColor,
          borderRadius: BorderRadius.circular(12),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(msg.text, style: textStyle),
            const SizedBox(height: 4),
            Text(
              '${msg.timestamp.hour}:${msg.timestamp.minute.toString().padLeft(2, '0')}',
              style: TextStyle(color: Colors.grey[600], fontSize: 10),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildInputPanel() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        children: [
          Expanded(
            child: TextField(
              controller: _messageController,
              decoration: const InputDecoration(
                hintText: 'Type a message...',
                border: OutlineInputBorder(),
              ),
              onSubmitted: (_) => _sendMessage(),
            ),
          ),
          const SizedBox(width: 8),
          IconButton.filled(
            onPressed: _status == 'connected' ? _sendMessage : null,
            icon: const Icon(Icons.send),
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
250
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter plugin for Pipecat — build real-time voice and multimodal AI agents with type-safe Dart bindings over the native iOS and Android client SDKs.

Homepage
Repository (GitHub)
View/report issues

Topics

#pipecat #voice #ai #realtime #webrtc

License

BSD-2-Clause (license)

Dependencies

flutter

More

Packages that depend on pipecat

Packages that implement pipecat