用 Wixzel Voice 在 Flutter 應用程式中顯示代理的吉祥物
完成本指南後,您的 Flutter 應用程式會顯示代理的吉祥物(每個 Wixzel Voice 代理都有的動態臉孔),它會隨著進行中的通話聆聽、思考和說話。wixzel_voice_flutter 套件以 AgentMascotView 繪製吉祥物,並以 MascotRealtimeLink 讓它與通話同步;通話本身由 wixzel_voice Dart SDK 負責。預計約需一小時,大部分時間花在串接錄音器和播放器。工作階段的計費方式與同一引擎上的電話通話相同:Gemini Live 每接通一分鐘 $0.0491,四分鐘約 $0.20,沒有電話線路費用。
作者:Aqeel Shamsudheen · 發布於 · 更新於
- 所需時間
- 約一小時
- 引擎時間
- 每接通分鐘 $0.0491

開始之前需要準備什麼?
- 一個 Wixzel Voice 代理,以及一把有點數、保存在您伺服器上的 API 金鑰。任何代理都可以:每個代理都有吉祥物,預設是綠色的那一個。
- Flutter 3.27 或更新版本。
- 一台您能控制、可呼叫 API 的伺服器。範例使用 Node.js 和 TypeScript SDK。
- 一個能串流 16 位元 PCM 的錄音外掛(最好是 8 kHz 單聲道並開啟回音消除),以及能接受原始 PCM 的播放器,例如
record和flutter_pcm_sound。
如何在 Wixzel Voice 上逐步打造?
01加入套件
wixzel_voice_flutter負責繪製吉祥物;wixzel_voice是它所依賴的 Dart SDK:pubspec.yamldependencies: wixzel_voice: ^0.8.0 wixzel_voice_flutter: ^0.1.0 http: ^1.2.0這個套件依賴
sensors_plus,讓吉祥物的眼睛能跟隨手機的傾斜;也依賴visibility_detector,讓它只在顯示於畫面上時播放動畫。它不會錄製或播放音訊。02由您的伺服器提供代理和工作階段給應用程式
您的 API 金鑰絕不放進應用程式。由您的伺服器取得代理並建立一個有效一分鐘、只能使用一次的工作階段,應用程式再向您的伺服器索取這兩者:
server.mjsimport express from 'express'; import { WixzelVoice } from 'wixzel-voice'; const wixzel = new WixzelVoice({ apiKey: process.env.WIXZEL_API_KEY }); const app = express(); const AGENT_ID = process.env.AGENT_ID; // Put your own sign-in check in front of both routes. app.get('/agent', async (req, res) => { res.json(await wixzel.agents.retrieve(AGENT_ID)); }); app.post('/session', async (req, res) => { res.json(await wixzel.realtime.createSession({ agent_id: AGENT_ID, max_duration_seconds: 300 })); }); app.listen(3000);代理的 JSON 帶有
mascot.spec,也就是繪製吉祥物所用的精簡規格。建立工作階段不收費;計費從應用程式連線時開始。03繪製吉祥物
Agent.fromJson讀取伺服器傳來的資料,AgentMascotView負責繪製:agent_face.dartimport 'dart:convert'; import 'package:flutter/widgets.dart'; import 'package:http/http.dart' as http; import 'package:wixzel_voice/wixzel_voice.dart'; import 'package:wixzel_voice_flutter/wixzel_voice_flutter.dart'; const backendUrl = 'https://api.example.com'; Future<Agent> loadAgent() async { final res = await http.get(Uri.parse('$backendUrl/agent')); return Agent.fromJson(jsonDecode(res.body) as Map<String, dynamic>); } Widget agentFace(Agent agent, MascotController controller) => AgentMascotView(agent: agent, controller: controller, size: 220); // In a list, draw one frame and run no animation: Widget agentRow(Agent agent) => AgentMascotView(agent: agent, size: 40, still: true);沒有 spec 的代理會使用預設外觀。接近全黑或全白的吉祥物會站在與自己同色的舞台上,以免在深色或淺色畫面中消失;可用
stage: false關閉。點一下會播放表情,MascotController則可依名稱播放動作,例如mascot.play('hello')。04將吉祥物連結到通話
用 Dart SDK 的
RealtimeConnection連線,再交給MascotRealtimeLink。這個連結會讓吉祥物在通話連線時思考、揮手打招呼、在使用者說話時聆聽,並隨代理的音訊動嘴:talk.dartimport 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'package:http/http.dart' as http; import 'package:wixzel_voice/wixzel_voice.dart'; import 'package:wixzel_voice_flutter/wixzel_voice_flutter.dart'; /// Your audio plugins, behind a small interface. abstract class PcmPlayer { void add(Int16List samples); // 16-bit PCM, 8 kHz mono void clear(); } class MascotCall { MascotCall(this.mascot, this.player); final MascotController mascot; final PcmPlayer player; RealtimeConnection? _conn; MascotRealtimeLink? _link; StreamSubscription<Int16List>? _mic; /// [micFrames] is 16-bit PCM at 8 kHz in 160-sample (20 ms) frames. Future<void> start(Stream<Int16List> micFrames) async { final res = await http.post(Uri.parse('https://api.example.com/session')); final session = RealtimeSession.fromJson(jsonDecode(res.body) as Map<String, dynamic>); final conn = RealtimeConnection.forSession(session); _conn = conn; _link = MascotRealtimeLink(mascot, conn); conn.events.listen((event) { switch (event) { case RealtimeAudio(:final mulaw): player.add(mulawToPcm16(mulaw)); // the mouth follows this audio through the link case RealtimeAudioClear(): player.clear(); // the user spoke over the agent case RealtimeEnded(): unawaited(end()); default: } }); await conn.ready; _mic = micFrames.listen((frame) { conn.sendAudio(pcm16ToMulaw(frame)); _link?.micAudio(frame); // it leans in while the user talks }); } Future<void> end() async { await _mic?.cancel(); await _link?.dispose(); // leaves the mascot idle; does not end the call await _conn?.end(); _mic = null; _link = null; _conn = null; } }用
AgentMascotView(agent: agent, controller: mascot)顯示它,使用與通話驅動的同一個控制器。畫面關閉時請釋放(dispose)該控制器。在錄音器中開啟平台的回音消除,否則代理會透過喇叭聽到自己的聲音並打斷自己。
05若您的播放器會排程音訊,讓對嘴更精確
預設情況下,連結會假設播放器在每段音訊抵達後約 120 ms 開始播放。如果您的播放器確切知道每段音訊何時播放,請改為告訴連結:
exact_timing.dartimport 'dart:typed_data'; import 'package:wixzel_voice/wixzel_voice.dart'; import 'package:wixzel_voice_flutter/wixzel_voice_flutter.dart'; MascotRealtimeLink exactLink(MascotController mascot, RealtimeConnection conn) => MascotRealtimeLink(mascot, conn, estimatePlayback: false); // In your player, each time it schedules a chunk, on the player's own clock: void scheduled(MascotRealtimeLink link, Int16List pcm, double playAt, double now) { link.agentAudio(pcm, playAt: playAt, currentTime: now, sampleRate: 8000); }
在 Wixzel Voice 上打造這個要花多少錢?
在 gemini-live 引擎上進行 4 分鐘工作階段的引擎時間,以每接通分鐘 $0.0491 計算:每個工作階段約 $0.20。 沒有電信費用,因為沒有電話線路。
| 每月工作階段數 | 接通分鐘數 | 每月引擎費用 |
|---|---|---|
| 500 | 2,000 | $98.20 |
| 2,500 | 10,000 | $491 |
| 10,000 | 40,000 | $1,964 |
這些套件免費,並採用 MIT 授權。繪製吉祥物不收費;只有進行中的工作階段會消耗點數,依引擎的每分鐘價格按秒計費,從預付餘額扣款。
預付制,沒有訂閱,也沒有免費方案:從 $5 起儲值點數,使用時從中扣除。估算您自己的使用組合。
這個實作適用哪些 Wixzel Voice 限制?
- 這個套件不會錄製或播放音訊。兩者都由您的應用程式透過自選的外掛提供。
- 電話等級音質:雙向皆為 8 kHz 的 G.711 µ-law。
- 工作階段計入帳號的同時通話上限,預設五通。
- 需要 Flutter 3.27 或更新版本。傾斜功能需要加速度計;沒有的話,吉祥物會自己四處張望。
- 更改吉祥物外觀需要從您的伺服器呼叫 API(
agents.updateMascot),而不是從應用程式,因為這需要您的 API 金鑰。
常見問題
如何在 Flutter 中顯示 Wixzel Voice 代理的吉祥物?
wixzel_voice 和 wixzel_voice_flutter 加入 pubspec,用 Agent.fromJson 讀取伺服器取得的代理,再用 AgentMascotView(agent: agent) 繪製。傳入 MascotController 即可用程式碼驅動它。吉祥物如何與代理對嘴?
MascotRealtimeLink 會監聽 RealtimeConnection 的事件,並隨代理的音訊動嘴,時間依據它對播放器延遲的估算;若設定 estimatePlayback: false,則依您的播放器透過 agentAudio 回報的時間。它也會設定狀態:連線時思考、使用者說話時聆聽、通話結束時閒置。吉祥物看起來和網站上的一樣嗎?
網頁應用程式也有同樣的東西嗎?
wixzel-voice/mascot 中的 mountMascot(npm 上的 wixzel-voice 0.10.0)會在 canvas 上繪製吉祥物,face.connect(session) 則會開始即時通話並接好吉祥物,包括麥克風和播放。延伸閱讀
- SDK文件
Dart 和 TypeScript SDK,以及吉祥物小工具。
- 吉祥物文件
spec、更改外觀,以及從網站產生吉祥物。
- 即時:網頁與行動裝置文件
建立工作階段、通訊協定與計費。
關於 Wixzel Voice
Wixzel Voice 是一個語音 AI API,用於打造 AI 代理,透過您自己的 SIP 中繼撥打和接聽真實的電話,或在您的網頁和行動應用程式中與人對話。一把 API 金鑰和一份預付餘額即可使用所有語音引擎,依實際用量按秒計費。
更多指南
Claude 在 Claude Code 或 claude.ai 中透過您的 SIP 中繼撥打真實電話並讀回逐字稿,撥號前一律先徵求確認。
AI 接待員接聽您現有的 Twilio 號碼,並透過 API 安排預約,以 Node.js 打造。
兩個電話代理,一個說印地語、一個說馬拉雅拉姆語,使用 Sarvam 的印度語言模型,透過您的 SIP 中繼接聽或撥出電話。
一個讓使用者透過 WebSocket 與您的語音代理對話的 Flutter 應用程式,API 金鑰保存在您的伺服器上,完全不需要電話號碼。
透過四個 cURL 請求,讓 Gemini Live 代理經由您的 SIP 中繼撥打真實電話,並取得包含逐字稿與費用的通話紀錄。
由您的代理透過您的 SIP 中繼撥打一份潛在客戶名單,依同時通話上限分批進行,並讀回每通電話的狀態和逐字稿。
網站角落有一個會說話的吉祥物,訪客可從瀏覽器撥打給它,僅限您的網域並受每日上限約束,每位來電者都會列出。

