import { useVoiceAgent, type VoiceStatus } from "@cloudflare/voice/react"; import { MicrophoneIcon, MicrophoneSlashIcon, PhoneIcon, PhoneDisconnectIcon, WaveformIcon, SpinnerGapIcon, SpeakerHighIcon, PaperPlaneRightIcon } from "@phosphor-icons/react"; import { Button, Input, Surface, Text } from "@cloudflare/kumo"; import { useEffect, useRef, useState } from "react"; import { DemoWrapper } from "../../layout/DemoWrapper"; import { ConnectionStatus } from "../../components/ConnectionStatus"; function formatTime(date: Date): string { return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); } function getStatusDisplay(status: VoiceStatus) { switch (status) { case "idle": return { text: "Ready", icon: PhoneIcon, color: "text-kumo-secondary" }; case "listening": return { text: "Listening...", icon: WaveformIcon, color: "text-kumo-success" }; case "thinking": return { text: "Thinking...", icon: SpinnerGapIcon, color: "text-kumo-warning" }; case "speaking": return { text: "Speaking...", icon: SpeakerHighIcon, color: "text-kumo-info" }; } } export function VoiceDemo() { const { status, transcript, metrics, audioLevel, isMuted, connected, error, startCall, endCall, toggleMute, sendText } = useVoiceAgent({ agent: "playground-voice-agent" }); const transcriptEndRef = useRef(null); const [textInput, setTextInput] = useState(""); useEffect(() => { transcriptEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [transcript]); const isInCall = status !== "idle"; const statusDisplay = getStatusDisplay(status); const StatusIcon = statusDisplay.icon; return ( } >
{/* Error banner */} {error && (
{error}
)} {/* Status indicator */}
{statusDisplay.text}
{isInCall && status === "listening" && (
)} {/* Latency metrics */} {metrics && (
VAD {metrics.vad_ms}ms / STT {metrics.stt_ms}ms / LLM {metrics.llm_ms}ms / TTS {metrics.tts_ms}ms / First audio{" "} {metrics.first_audio_ms}ms
)} {/* Transcript */} {transcript.length === 0 ? (
{isInCall ? "Start speaking..." : connected ? "Click Start Call to begin" : "Connecting to agent..."}
) : (
{transcript.map((msg, i) => (
{msg.text || ( ... )}
{msg.timestamp && ( {formatTime(new Date(msg.timestamp))} )}
))}
)} {/* Controls */}
{!isInCall ? ( ) : ( <> )}
{/* Text input — type to the agent */}
{ e.preventDefault(); if (textInput.trim() && connected) { sendText(textInput.trim()); setTextInput(""); } }} > setTextInput(e.target.value)} placeholder={connected ? "Type a message..." : "Connecting..."} disabled={!connected || status === "thinking"} className="flex-1" />
); }