تخطَّ إلى المحتوى
دليل

أضف وكيلًا صوتيًا إلى Flutter عبر WebSocket مع Wixzel Voice

في النهاية يتيح تطبيق Flutter لديك للمستخدم التحدث إلى وكيل صوتي بنيته، عبر WebSocket، دون SIP trunk ودون رقم هاتف. يُنشئ خادمك جلسة تُستخدم مرة واحدة، فلا يصل مفتاح API إلى التطبيق أبدًا، وتتولى حزمة wixzel_voice لـ Dart البروتوكول. توقّع نحو ساعة، معظمها لتوصيل الميكروفون ومكبّر الصوت. تُحتسب الجلسة مثل مكالمة هاتفية على المحرك نفسه: $0.0491 لكل دقيقة اتصال على محرك Gemini Live، أي نحو $0.20 لجلسة مدتها أربع دقائق، بالثانية من رصيد مسبق الدفع، دون أي تكلفة اتصالات هاتفية.

بقلم · نُشر في · حُدّث في

المدة
نحو ساعة
المحرك
gemini-live
وقت المحرك
$0.0491 لكل دقيقة متصلة

ما الذي تحتاجه قبل أن تبدأ؟

  • مفتاح API من Wixzel Voice مع رصيد، محفوظ على خادمك.
  • خادم تتحكم فيه لإنشاء الجلسات. يستخدم المثال Node.js وExpress؛ وتصلح أي لغة قادرة على إرسال طلب HTTPS.
  • تطبيق Flutter مع إضافة تسجيل تبث PCM بدقة 16 بت، ويُفضَّل بتردد 8 kHz أحادي القناة مع إلغاء الصدى، ومشغّل يقبل PCM خامًا.

كيف تبني هذا على Wixzel Voice، خطوة بخطوة؟

  1. 01أنشئ الوكيل مرة واحدة

    يستخدم هذا الوكيل Gemini Live، وهو نموذج آني واحد يستمع ويفكر ويتكلم:

    shell
    curl https://api.voice.wixzel.com/v1/agents \
      -H "Authorization: Bearer $WIXZEL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "In-app assistant",
        "system_prompt": "You are the voice assistant inside the Acme app. Answer in one or two short sentences.",
        "opening_message": "Hi, what can I help you with?",
        "voice": {
          "realtime": { "model": "google/gemini-live", "voice": "Kore" }
        }
      }'

    لا شيء في هذا الوكيل خاص بالتطبيق: يمكن للوكيل نفسه أن يجيب على رقم هاتف. احتفظ بقيمة id الخاصة به على خادمك باسم AGENT_ID.

  2. 02أنشئ جلسة على خادمك

    لا يحمل التطبيق مفتاح API أبدًا. يطلب من خادمك، ويطلب خادمك من الـ API جلسة عبر POST /v1/realtime/sessions، هنا من خلال TypeScript SDK:

    server.mjs
    import express from 'express';
    import { WixzelVoice } from 'wixzel-voice';
    
    const wixzel = new WixzelVoice({ apiKey: process.env.WIXZEL_API_KEY });
    const app = express();
    
    // Replace with your own authentication. It must set req.userId.
    function requireUser(req, res, next) {
      req.userId = 'demo-user';
      next();
    }
    
    app.post('/voice/session', requireUser, async (req, res) => {
      const session = await wixzel.realtime.createSession({
        agent_id: process.env.AGENT_ID,
        max_duration_seconds: 300,
        metadata: { user_id: req.userId },
      });
      res.json(session);
    });
    
    app.listen(3000);

    تحمل الاستجابة عنوان المقبس url، وclient_secret يعمل مرة واحدة، لمدة دقيقة، لهذا الوكيل، وaudio_format: "mulaw_8000". إنشاء الجلسة مجاني؛ ويبدأ الاحتساب عند اتصال التطبيق. يحدد max_duration_seconds الحد الأقصى للمحادثة، من 10 إلى 3600 ثانية، و600 افتراضيًا. تُعاد metadata في سجل المكالمة ولا تُرسل إلى النموذج أبدًا. لا تهم allowed_origins إلا للمتصفحات، لذا يتركها التطبيق الأصلي (native).

  3. 03اتصل من التطبيق

    أضف الـ SDK عبر flutter pub add wixzel_voice http، ثم افتح الجلسة التي أعادها خادمك:

    voice_call.dart
    import 'dart:async';
    import 'dart:convert';
    import 'dart:typed_data';
    
    import 'package:http/http.dart' as http;
    import 'package:wixzel_voice/wixzel_voice.dart';
    
    /// Whatever your playback plugin offers, behind a small interface.
    abstract class PcmPlayer {
      void add(Int16List samples);
      void clear();
    }
    
    class VoiceCall {
      VoiceCall({required this.player, required this.onLine});
    
      final PcmPlayer player;
      final void Function(String role, String text) onLine;
    
      RealtimeConnection? _conn;
      StreamSubscription<Uint8List>? _mic;
      final List<int> _pending = [];
    
      /// [micBytes] is 16-bit little-endian PCM, 8 kHz mono, from your recording plugin.
      Future<void> start(Stream<Uint8List> micBytes) async {
        final res = await http.post(Uri.parse('https://api.example.com/voice/session'));
        final status = res.statusCode;
        if (status != 200) throw Exception('Could not start a voice session: HTTP $status');
        final session = RealtimeSession.fromJson(jsonDecode(res.body) as Map<String, dynamic>);
    
        final conn = RealtimeConnection.forSession(session);
        _conn = conn;
        conn.events.listen((event) {
          switch (event) {
            case RealtimeAudio(:final mulaw):
              player.add(mulawToPcm16(mulaw));
            case RealtimeAudioClear():
              player.clear(); // the user interrupted: drop queued audio
            case RealtimeTranscript(:final role, :final text):
              onLine(role, text);
            case RealtimeError(:final code, :final message):
              onLine('error', '$code: $message');
            case RealtimeEnded(:final reason):
              onLine('system', 'ended: $reason');
              _mic?.cancel();
            default:
          }
        });
        await conn.ready;
        _mic = micBytes.listen(_send);
      }
    
      void _send(Uint8List bytes) {
        final data = ByteData.sublistView(bytes);
        for (var i = 0; i + 1 < bytes.length; i += 2) {
          _pending.add(data.getInt16(i, Endian.little));
        }
        // 160 samples is 20 ms at 8 kHz, the frame size the server expects.
        while (_pending.length >= 160) {
          final frame = Int16List.fromList(_pending.sublist(0, 160));
          _pending.removeRange(0, 160);
          _conn?.sendAudio(pcm16ToMulaw(frame));
        }
      }
    
      Future<void> end() async {
        await _mic?.cancel();
        await _conn?.end();
      }
    }

    يتولى RealtimeConnection البروتوكول فقط: لا يفتح الميكروفون ولا مكبّر الصوت، مما يبقي الـ SDK خاليًا من الاعتماد على الإضافات ويتيح لك استخدام إضافات الصوت التي لديك بالفعل. تحوّل pcm16ToMulaw وmulawToPcm16 بين PCM بدقة 16 بت وما يُرسل عبر الاتصال.

    اطلب إذن الميكروفون أولًا: RECORD_AUDIO على Android وNSMicrophoneUsageDescription على iOS.

  4. 04فعّل إلغاء الصدى، وتوقّع جودة صوت بمستوى الهاتف

    فعّل إلغاء الصدى الخاص بالمنصة في إضافة التسجيل. بدونه يسمع الوكيل نفسه عبر مكبّر الصوت ويقاطع نفسه.

    الصوت بصيغة G.711 µ-law بتردد 8 kHz في الاتجاهين، مثل خط الهاتف، لأن الجلسات تمر عبر مسار الوسائط نفسه الذي تمر به المكالمات. هذا مناسب للكلام وأضيق من الصوت عريض النطاق. سجّل بتردد 8 kHz إن سمحت الإضافة بذلك، أو أعد أخذ العينات قبل pcm16ToMulaw، وشغّل صوت الوكيل بتردد 8 kHz أحادي القناة.

  5. 05اقرأ الجلسة بعد انتهائها

    كل جلسة هي سجل مكالمة مع channel: "web":

    shell
    curl "https://api.voice.wixzel.com/v1/calls?channel=web&limit=5" \
      -H "Authorization: Bearer $WIXZEL_API_KEY"

    جلسة الويب واردة، وليس لها from أو to، وتحمل مدتها وcost_micros وmetadata الخاصة بك؛ ويضيف GET /v1/calls/{id} النص المكتوب. تنتهي بالـ webhook نفسه الذي ترسله المكالمة الهاتفية، callCompleted. لا تُسجَّل جلسات الويب.

كم يكلّف هذا البناء على Wixzel Voice؟

وقت المحرك لجلسات مدة كل منها 4 دقيقة على محرك gemini-live بسعر $0.0491 لكل دقيقة متصلة: نحو $0.20 للجلسة. لا توجد تكلفة اتصالات هاتفية، لأنه لا يوجد خط هاتفي.

تكلفة المحرك الشهرية لما يبنيه هذا الدليل
الجلسات شهريًاالدقائق المتصلةتكلفة المحرك شهريًا
5002,000$98.20
2,50010,000$491
10,00040,000$1,964

تكلف الجلسة ما تكلفه مكالمة هاتفية على المحرك نفسه، بالثانية، من الرصيد المسبق الدفع نفسه. إنشاء الجلسة مجاني، لذا فإن المستخدم الذي يفتح الشاشة ثم يرفض إذن الميكروفون لا يكلف شيئًا.

مسبق الدفع، دون اشتراك ودون خطة مجانية: أضِف رصيدًا بدءًا من $5 ويُستهلك مع الاستخدام. قدّر تكلفة استخدامك.

ما حدود Wixzel Voice التي تنطبق على هذا البناء؟

  • صوت بجودة الهاتف: G.711 µ-law بتردد 8 kHz في الاتجاهين، وليس عريض النطاق.
  • لا تلتقط Dart SDK الصوت ولا تشغّله. يوفّر تطبيقك الميكروفون ومكبّر الصوت عبر الإضافات التي يختارها.
  • تُحتسب الجلسات ضمن حد المكالمات المتزامنة للحساب، خمس افتراضيًا، إلى جانب المكالمات الهاتفية. يُرفع الحد عند الطلب.
  • تستمر الجلسة 10 دقائق افتراضيًا وساعة كحد أقصى، وينهيها صمت لمدة دقيقتين.
  • لا تُسجَّل جلسات الويب، ولا يوجد خط ثانٍ لتحويل المستخدم إلى شخص.

الأسئلة الشائعة

هل توجد واجهة API لوكيل صوتي في Flutter؟

لدى Wixzel Voice واحدة. تتضمن Dart SDK الخاصة به، wixzel_voice على pub.dev، الصنف RealtimeConnection الذي يربط تطبيق Flutter أو Dart بوكيل صوتي عبر WebSocket. يُنشئ خادمك الجلسة بمفتاح API الخاص بك؛ ولا يحمل التطبيق إلا سرًا صالحًا لدقيقة واحدة ولمرة واحدة.

هل أحتاج إلى رقم هاتف أو SIP trunk لوكيل صوتي داخل التطبيق؟

لا. تعمل الجلسات في تطبيقات الويب والهاتف المحمول عبر WebSocket دون أي شيء يحتاج إلى ربط. لا يلزم الـ trunk والرقم إلا للمكالمات من الهواتف الحقيقية وإليها.

لماذا يستمر الوكيل في مقاطعة نفسه؟

إنه يسمع صوته عبر مكبّر الصوت. فعّل إلغاء الصدى في إضافة التسجيل، أو اختبر بسماعات الرأس للتأكد.

هل يمكنني استخدام محرك مختلف في التطبيق؟

نعم. يعمل مسارا classic وsarvam عبر المقبس أيضًا: اختر classic لصوت محدد من ElevenLabs، أو sarvam للغات الهند. السعر هو سعر المحرك نفسه في الدقيقة، كما في المكالمة الهاتفية؛ ومحرك Gemini Live، بسعر $0.0491، هو المستخدم هنا.

ماذا يحدث عندما ينفد رصيد الحساب أثناء الجلسة؟

يتلقى التطبيق session.warning بالرمز low_balance وعددًا تقريبيًا للثواني المتبقية، ثم تنتهي الجلسة بالسبب insufficient_credits. الجلسة التي لا يمكن تمويلها إطلاقًا تُرفض برمز الإغلاق 4402.

للمزيد من القراءة

واجهات API للاتصالات الهاتفية القائمة على الوكلاء

عن Wixzel Voice

Wixzel Voice هو API للذكاء الاصطناعي الصوتي لبناء وكلاء ذكاء اصطناعي يجرون مكالمات هاتفية حقيقية ويردّون عليها عبر SIP trunk الخاص بك، أو يتحدثون إلى الناس في تطبيقات الويب والجوال الخاصة بك. مفتاح API واحد ورصيد مدفوع مسبقًا واحد يغطيان كل محركات الصوت، مع فوترة بالثانية حسب الاستخدام الفعلي.

أعطِ Claude خط هاتف حقيقيًا عبر خادم MCP من Wixzel Voice

Claude، في Claude Code أو claude.ai، يجري مكالمة حقيقية عبر SIP trunk الخاص بك ويقرأ لك النص المكتوب، مع طلب تأكيد قبل أي اتصال.

بناء موظف استقبال بالذكاء الاصطناعي على Twilio في Node.js مع Wixzel Voice

رقم Twilio الحالي لديك يجيب عليه موظف استقبال بالذكاء الاصطناعي يحجز المواعيد عبر الـ API، مبني بـ Node.js.

وكيل هاتفي بالهندية والمالايالامية على محرك sarvam من Wixzel Voice

وكيلان هاتفيان، أحدهما يتحدث الهندية والآخر المالايالامية، على نماذج Sarvam المخصصة للغات الهند، يجيبان على المكالمات أو يجريانها عبر SIP trunk الخاص بك.

Gemini Live في مكالمة هاتفية حقيقية عبر Wixzel Voice API

وكيل Gemini Live يتصل بهاتف حقيقي عبر SIP trunk الخاص بك من أربعة طلبات cURL، وسجل المكالمة مع النص المكتوب والتكلفة.

تشغيل حملة لتأهيل العملاء المحتملين بالذكاء الاصطناعي مع Wixzel Voice

قائمة عملاء محتملين اتصل بهم وكيلك عبر SIP trunk الخاص بك، على دفعات بحجم حد التزامن لديك، مع قراءة حالة كل مكالمة ونصها المكتوب.

أضف شخصية تعريفية ناطقة بالذكاء الاصطناعي إلى موقعك مع Wixzel Voice

شخصية تعريفية ناطقة في زاوية موقعك يتصل بها الزوار من المتصفح، مقيّدة بنطاقاتك وحدودك اليومية، مع قائمة بكل متصل.

اعرض الشخصية التعريفية لوكيلك في تطبيق Flutter مع Wixzel Voice

شاشة Flutter فيها الشخصية التعريفية لوكيلك تحرّك فمها مع صوت الوكيل أثناء مكالمة آنية، مع بقاء مفتاح API على خادمك.