vocall_sdk 0.1.0 copy "vocall_sdk: ^0.1.0" to clipboard
vocall_sdk: ^0.1.0 copied to clipboard

Flutter SDK for the Vocall Agent-Application Protocol (AAP). Let an AI agent see and control your app's UI — navigate screens, fill forms, click buttons, and interact via voice or text.

example/main.dart

import 'dart:async';

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

void main() {
  runApp(const VoiceTestApp());
}

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

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

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

  @override
  State<VoiceTestPage> createState() => _VoiceTestPageState();
}

class _VoiceTestPageState extends State<VoiceTestPage> {
  late final JarvisClient _jarvis;
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  final _serverUrlController = TextEditingController(text: 'ws://localhost:12900/connect');

  final List<String> _log = [];
  double _audioLevel = 0.0;
  StreamSubscription<double>? _levelSub;
  bool _connected = false;

  @override
  void initState() {
    super.initState();
    _jarvis = JarvisClient(serverUrl: _serverUrlController.text);
    _jarvis.addListener(_onClientChanged);
    _levelSub = _jarvis.audioLevelStream.listen((level) {
      setState(() => _audioLevel = level);
    });
  }

  @override
  void dispose() {
    _levelSub?.cancel();
    _jarvis.removeListener(_onClientChanged);
    _jarvis.dispose();
    _nameController.dispose();
    _emailController.dispose();
    _serverUrlController.dispose();
    super.dispose();
  }

  void _onClientChanged() {
    setState(() {});
  }

  void _addLog(String msg) {
    setState(() {
      _log.add('[${DateTime.now().toIso8601String().substring(11, 23)}] $msg');
      if (_log.length > 200) _log.removeAt(0);
    });
  }

  void _doConnect() {
    _jarvis.disconnect();
    // Create a new client with the URL from the text field
    setState(() {
      _connected = false;
      _log.clear();
    });

    _jarvis.fieldRegistry.registerField('home', 'name', _nameController);
    _jarvis.fieldRegistry.registerField('home', 'email', _emailController);

    _jarvis.connect(ManifestMessage(
      app: 'voice-test',
      screens: {
        'home': const ScreenDescriptor(
          id: 'home',
          label: 'Home',
          fields: [
            FieldDescriptor(id: 'name', type: FieldType.text, label: 'Name'),
            FieldDescriptor(id: 'email', type: FieldType.email, label: 'Email'),
          ],
          actions: [
            ActionDescriptor(id: 'submit', label: 'Submit'),
          ],
        ),
      },
    ));

    _connected = true;
    _addLog('Connecting to ${_serverUrlController.text}');
  }

  void _toggleAlwaysListening() {
    if (_jarvis.alwaysListening) {
      _jarvis.stopAlwaysListening();
      _addLog('Stopped always-listening');
    } else {
      _jarvis.startAlwaysListening();
      _addLog('Started always-listening');
    }
  }

  void _doInterrupt() {
    _jarvis.interrupt();
    _addLog('Interrupt sent');
  }

  Color _statusColor(JarvisStatus status) {
    return switch (status) {
      JarvisStatus.disconnected => Colors.grey,
      JarvisStatus.idle => Colors.blue,
      JarvisStatus.listening => Colors.green,
      JarvisStatus.recording => Colors.orange,
      JarvisStatus.thinking => Colors.purple,
      JarvisStatus.speaking => Colors.red,
      JarvisStatus.executing => Colors.teal,
    };
  }

  @override
  Widget build(BuildContext context) {
    final status = _jarvis.status;
    final partial = _jarvis.partialTranscription;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Jarvis Voice Test'),
        actions: [
          Container(
            margin: const EdgeInsets.symmetric(horizontal: 8),
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
            decoration: BoxDecoration(
              color: _statusColor(status),
              borderRadius: BorderRadius.circular(16),
            ),
            child: Text(
              status.name.toUpperCase(),
              style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12),
            ),
          ),
        ],
      ),
      body: Column(
        children: [
          // Connection bar
          Padding(
            padding: const EdgeInsets.all(8),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _serverUrlController,
                    decoration: const InputDecoration(
                      labelText: 'Server URL',
                      border: OutlineInputBorder(),
                      isDense: true,
                    ),
                    style: const TextStyle(fontSize: 13),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: _doConnect,
                  child: Text(_connected ? 'Reconnect' : 'Connect'),
                ),
              ],
            ),
          ),

          // Controls bar
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 8),
            child: Row(
              children: [
                // Mic button with level indicator
                Stack(
                  alignment: Alignment.center,
                  children: [
                    SizedBox(
                      width: 56,
                      height: 56,
                      child: CircularProgressIndicator(
                        value: _audioLevel,
                        strokeWidth: 4,
                        color: _jarvis.alwaysListening ? Colors.green : Colors.grey,
                        backgroundColor: Colors.grey.shade200,
                      ),
                    ),
                    IconButton(
                      iconSize: 28,
                      icon: Icon(
                        _jarvis.alwaysListening ? Icons.mic : Icons.mic_off,
                        color: _jarvis.alwaysListening ? Colors.green : Colors.grey,
                      ),
                      onPressed: _connected ? _toggleAlwaysListening : null,
                    ),
                  ],
                ),
                const SizedBox(width: 8),
                ElevatedButton.icon(
                  onPressed: status == JarvisStatus.speaking || status == JarvisStatus.thinking
                      ? _doInterrupt
                      : null,
                  icon: const Icon(Icons.stop),
                  label: const Text('Interrupt'),
                ),
                const SizedBox(width: 16),
                if (partial != null)
                  Expanded(
                    child: Text(
                      'STT: $partial',
                      style: TextStyle(
                        fontStyle: FontStyle.italic,
                        color: Colors.orange.shade800,
                      ),
                      overflow: TextOverflow.ellipsis,
                    ),
                  ),
              ],
            ),
          ),

          const Divider(),

          // Test fields
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _nameController,
                    decoration: const InputDecoration(
                      labelText: 'Name',
                      border: OutlineInputBorder(),
                      isDense: true,
                    ),
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: TextField(
                    controller: _emailController,
                    decoration: const InputDecoration(
                      labelText: 'Email',
                      border: OutlineInputBorder(),
                      isDense: true,
                    ),
                  ),
                ),
              ],
            ),
          ),

          const Divider(),

          // Chat messages
          Expanded(
            flex: 2,
            child: ListView.builder(
              padding: const EdgeInsets.symmetric(horizontal: 8),
              itemCount: _jarvis.messages.length,
              itemBuilder: (context, index) {
                final msg = _jarvis.messages[index];
                final isUser = msg.role == ChatRole.user;
                return Align(
                  alignment: isUser ? Alignment.centerRight : Alignment.centerLeft,
                  child: Container(
                    margin: const EdgeInsets.symmetric(vertical: 2),
                    padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
                    decoration: BoxDecoration(
                      color: isUser ? Colors.indigo.shade100 : Colors.grey.shade200,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text(msg.text, style: const TextStyle(fontSize: 14)),
                  ),
                );
              },
            ),
          ),

          const Divider(height: 1),

          // Protocol log
          Expanded(
            flex: 1,
            child: Container(
              color: Colors.grey.shade900,
              child: ListView.builder(
                padding: const EdgeInsets.all(4),
                reverse: true,
                itemCount: _log.length,
                itemBuilder: (context, index) {
                  return Text(
                    _log[_log.length - 1 - index],
                    style: const TextStyle(
                      fontFamily: 'monospace',
                      fontSize: 11,
                      color: Colors.green,
                    ),
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
0
points
99
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter SDK for the Vocall Agent-Application Protocol (AAP). Let an AI agent see and control your app's UI — navigate screens, fill forms, click buttons, and interact via voice or text.

Homepage
Repository (GitHub)
View/report issues

Topics

#ai #voice #agent #websocket #automation

License

unknown (license)

Dependencies

flutter, google_fonts, web, web_socket_channel

More

Packages that depend on vocall_sdk