Headless Hook
Kavelj useThunderPhone vam omogoča popoln nadzor nad uporabniškim vmesnikom, medtem ko ThunderPhone upravlja glasovno sejo, usmerjanje zvoka in stanje povezave. Uporabite ga, kadar želite popolnoma prilagojen uporabniški vmesnik -- lastne gumbe, postavitve, animacije in blagovno znamko -- medtem ko ThunderPhone v ozadju poskrbi za vse ostalo.
Kdaj uporabiti kavelj brez uporabniškega vmesnika
Vnaprej izdelana komponenta ThunderPhoneWidget pokriva večino primerov uporabe, vendar uporabite kavelj brez uporabniškega vmesnika, kadar potrebujete:
- Popolnoma prilagojen uporabniški vmesnik za klice, ki se ujema z oblikovalskim sistemom vaše aplikacije
- Vizualizacije, odzivne na zvok (valovne oblike, krogle, pulzirajoči kazalniki), ki jih poganjajo ravni zvoka v realnem času
- Prilagojene poteke klicev, kot so obrazci pred klicem, ankete po klicu ali vdelan klepet ob glasovni komunikaciji
- Integracijo v obstoječo knjižnico komponent (Material UI, Chakra, Radix itd.)
Namestitev
npm install @thunderphone/widget
Osnovna uporaba
import { useThunderPhone } from '@thunderphone/widget'
function CustomCallButton() {
const phone = useThunderPhone({
publishableKey: 'pk_live_your_publishable_key',
})
const handleClick = () => {
if (phone.state === 'connected') {
phone.disconnect()
} else {
phone.connect()
}
}
return (
<>
<button onClick={handleClick} disabled={phone.state === 'connecting'}>
{phone.state === 'connecting'
? 'Connecting...'
: phone.state === 'connected'
? 'End call'
: 'Start call'}
</button>
{phone.audio}
</>
)
}
Možnosti
Te možnosti posredujte funkciji useThunderPhone prek UseThunderPhoneOptions:
| Možnost | Vrsta | Obvezno | Privzeto | Opis |
|---|---|---|---|---|
publishableKey | string | Da | -- | Javni ključ API (pk_live_...). Glasovni agent se samodejno določi iz konfiguracije pripomočka ključa. |
apiBase | string | Ne | 'https://api.thunderphone.com/v1' | Preglasitev osnovnega URL-ja API-ja. |
language | string | Ne | -- | Preglasitev jezika za posamezno sejo -- jezikovna koda ali področna nastavitev, kot so en, es ali fr-FR. Če ni nastavljena, se uporabi konfigurirani jezik glasovnega agenta. |
voice | string | Ne | -- | Preglasitev glasu za posamezno sejo -- ime glasu, kot je maria. Če ni nastavljena, se uporabi konfigurirani glas glasovnega agenta. |
context | string | Ne | -- | Stvarni kontekst strani ali spletnega mesta za posamezno sejo, posredovan glasovnemu agentu. Na strežniški strani je okrnjen na 12.000 znakov. |
onConnect | () => void | Ne | -- | Pokliče se, ko se glasovna seja poveže. |
onDisconnect | () => void | Ne | -- | Pokliče se, ko se seja konča. |
onError | (error) => void | Ne | -- | Pokliče se ob napakah. Napaka vsebuje polji error (koda) in message. |
ringtone | boolean | string | Ne | false | Med vzpostavljanjem povezave predvaja zvonjenje. true za privzeto zvonjenje ali niz URL za zvok po meri. |
Vrnjena vrednost
Kavelj vrne objekt UseThunderPhoneReturn:
| Lastnost | Vrsta | Opis |
|---|---|---|
state | 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error' | Trenutno stanje povezave. |
connect | () => void | Začnite glasovno sejo. |
disconnect | () => void | Končajte trenutno sejo. |
toggleMute | () => void | Vklopite ali izklopite utišanje mikrofona. |
isMuted | boolean | Ali je mikrofon trenutno utišan. |
error | string | undefined | Sporočilo o napaki, ko je stanje 'error'. |
agentName | string | undefined | Prikazno ime povezanega agenta. |
audioLevel | number | Zastarelo -- vedno 0. Statično nadomestno mesto, ohranjeno zaradi združljivosti s prejšnjimi različicami; nikoli se ne posodobi. Namesto tega preberite audioLevelRef.current. |
audioLevelRef | React.RefObject<number> | Spremenljiva referenca, ki vsebuje raven zvoka v realnem času (0--1) -- višjo od glasnosti agentovega glasu in mikrofona obiskovalca -- ter se posodobi v vsakem animacijskem okviru zunaj Reactovega cikla izrisovanja. Za gladke animacije brez zatikanja preberite audioLevelRef.current znotraj zank requestAnimationFrame, ali pa jo vzorčite v intervalih, kadar vrednost potrebujete v stanju Reacta. |
audio | ReactNode | Neviden element, ki upravlja zvočno povezavo -- mora biti izrisan. |
Na zvok odziven UI
Referenca audioLevelRef zagotavlja ravni zvoka s frekvenco sličic brez sprožanja ponovnih izrisov Reacta, zato je idealna za gladke vizualizacije valovnih oblik, utripajoče krogle ali katero koli animacijo, povezano s pogovorom. Raven odraža glasnejši vir: glas agenta ali mikrofon obiskovalca.
Primer valovne oblike
import { useRef, useEffect } from 'react'
import { useThunderPhone } from '@thunderphone/widget'
function WaveformCall() {
const phone = useThunderPhone({
publishableKey: 'pk_live_your_publishable_key',
})
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
if (phone.state !== 'connected') return
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')!
let animId: number
const draw = () => {
const level = phone.audioLevelRef.current ?? 0
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Draw bars that react to audio level
const barCount = 24
const barWidth = canvas.width / barCount
for (let i = 0; i < barCount; i++) {
const distance = Math.abs(i - barCount / 2) / (barCount / 2)
const height = level * canvas.height * (1 - distance * 0.6)
const y = (canvas.height - height) / 2
ctx.fillStyle = '#0ea5e9'
ctx.fillRect(i * barWidth + 1, y, barWidth - 2, height)
}
animId = requestAnimationFrame(draw)
}
animId = requestAnimationFrame(draw)
return () => cancelAnimationFrame(animId)
}, [phone.state, phone.audioLevelRef])
return (
<div>
{phone.state === 'connected' && (
<canvas ref={canvasRef} width={240} height={80} />
)}
<button
onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
disabled={phone.state === 'connecting'}
>
{phone.state === 'connected' ? 'End call' : 'Start call'}
</button>
{phone.audio}
</div>
)
}
Primer utripajoče krogle
import { useRef, useEffect } from 'react'
import { useThunderPhone } from '@thunderphone/widget'
function PulsingOrb() {
const phone = useThunderPhone({
publishableKey: 'pk_live_your_publishable_key',
})
const orbRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (phone.state !== 'connected') return
let animId: number
const animate = () => {
const level = phone.audioLevelRef.current ?? 0
if (orbRef.current) {
const scale = 1 + level * 0.5
orbRef.current.style.transform = `scale(${scale})`
orbRef.current.style.opacity = `${0.6 + level * 0.4}`
}
animId = requestAnimationFrame(animate)
}
animId = requestAnimationFrame(animate)
return () => cancelAnimationFrame(animId)
}, [phone.state, phone.audioLevelRef])
return (
<div style={{ textAlign: 'center' }}>
<div
ref={orbRef}
style={{
width: 80,
height: 80,
borderRadius: '50%',
background: '#0ea5e9',
margin: '20px auto',
transition: 'transform 0.05s ease-out',
}}
/>
<button
onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
disabled={phone.state === 'connecting'}
>
{phone.state === 'connected' ? 'End call' : 'Call'}
</button>
{phone.audio}
</div>
)
}
Primer indikatorja govora
Za UI, izrisan z Reactom, ki se spreminja glede na glasnost -- na primer oznako »govori«, ki temelji na pragu -- v intervalih vzorčite audioLevelRef.current in rezultat shranite v stanje:
import { useEffect, useState } from 'react'
import { useThunderPhone } from '@thunderphone/widget'
function SpeakingBadge() {
const phone = useThunderPhone({
publishableKey: 'pk_live_your_publishable_key',
})
const [speaking, setSpeaking] = useState(false)
useEffect(() => {
if (phone.state !== 'connected') {
setSpeaking(false)
return
}
const interval = setInterval(() => {
setSpeaking((phone.audioLevelRef.current ?? 0) > 0.1)
}, 100)
return () => clearInterval(interval)
}, [phone.state, phone.audioLevelRef])
return (
<div>
{phone.state === 'connected' && (
<span>{speaking ? 'Speaking' : 'Listening'}</span>
)}
<button onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}>
{phone.state === 'connected' ? 'End call' : 'Start call'}
</button>
{phone.audio}
</div>
)
}
Stroj stanj
Lastnost state sledi temu življenjskemu ciklu:
idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
\
--> error (stays until connect() is called again)
| Stanje | Opis |
|---|---|
idle | Ni aktivne seje. Pripravljeno za klic connect(). |
connecting | Seja se vzpostavlja. V tem stanju onemogočite gumb za klic. |
connected | Glasovna seja je aktivna. Uporabnik se pogovarja z agentom. |
disconnected | Seja se je pravilno končala. Po 1,5 sekunde se samodejno vrne v idle. |
error | Nekaj je šlo narobe. Sporočilo preverite v phone.error. Stanje se ne počisti samo -- ponovni klic connect() začne nov poskus in ponastavi napako. |
Primeri
Z nadzorom utišanja
import { useThunderPhone } from '@thunderphone/widget'
function CallWithMute() {
const phone = useThunderPhone({
publishableKey: 'pk_live_your_publishable_key',
})
return (
<div>
{phone.state === 'connected' && (
<div>
<p>Talking to {phone.agentName ?? 'Agent'}</p>
<button onClick={phone.toggleMute}>
{phone.isMuted ? 'Unmute' : 'Mute'}
</button>
<button onClick={phone.disconnect}>End call</button>
</div>
)}
{phone.state !== 'connected' && (
<button
onClick={phone.connect}
disabled={phone.state === 'connecting'}
>
{phone.state === 'connecting' ? 'Connecting...' : 'Call support'}
</button>
)}
{phone.state === 'error' && (
<p style={{ color: 'red' }}>{phone.error}</p>
)}
{phone.audio}
</div>
)
}
Z melodijo zvonjenja
Med povezovanjem predvajajte zvok zvonjenja, da simulirate telefonski klic:
import { useThunderPhone } from '@thunderphone/widget'
function PhoneCallButton() {
const phone = useThunderPhone({
publishableKey: 'pk_live_your_publishable_key',
ringtone: true, // or a custom URL: 'https://example.com/ringtone.mp3'
})
return (
<>
<button
onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
disabled={phone.state === 'connecting'}
>
{phone.state === 'connecting'
? 'Ringing...'
: phone.state === 'connected'
? 'Hang up'
: 'Call'}
</button>
{phone.audio}
</>
)
}
Melodija zvonjenja se ponavlja med stanjem connecting in postopoma utihne, ko se agent poveže. Podajte true za vgrajeno privzeto melodijo zvonjenja ali niz URL-ja za uporabo lastne zvočne datoteke.
Z dogodkovnimi povratnimi klici
import { useThunderPhone } from '@thunderphone/widget'
function TrackedCallButton() {
const phone = useThunderPhone({
publishableKey: 'pk_live_your_publishable_key',
onConnect: () => {
analytics.track('call_started')
},
onDisconnect: () => {
analytics.track('call_ended')
},
onError: (error) => {
analytics.track('call_error', { code: error.error, message: error.message })
},
})
return (
<>
<button
onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
disabled={phone.state === 'connecting'}
>
{phone.state === 'connected' ? 'Hang up' : 'Talk to AI'}
</button>
{phone.audio}
</>
)
}
Popolnoma prilagojen uporabniški vmesnik
import { useThunderPhone } from '@thunderphone/widget'
function FullCustomUI() {
const phone = useThunderPhone({
publishableKey: 'pk_live_your_publishable_key',
})
return (
<div className="call-panel">
<div className="call-status">
{phone.state === 'idle' && <span>Ready</span>}
{phone.state === 'connecting' && <span className="pulse">Connecting...</span>}
{phone.state === 'connected' && (
<span>On call with {phone.agentName}</span>
)}
{phone.state === 'error' && <span className="error">{phone.error}</span>}
</div>
<div className="call-controls">
{phone.state === 'connected' ? (
<>
<button className="mute-btn" onClick={phone.toggleMute}>
{phone.isMuted ? 'Unmute' : 'Mute'}
</button>
<button className="end-btn" onClick={phone.disconnect}>
End
</button>
</>
) : (
<button
className="start-btn"
onClick={phone.connect}
disabled={phone.state === 'connecting'}
>
Start call
</button>
)}
</div>
{/* Required -- handles audio under the hood */}
{phone.audio}
</div>
)
}
Nasveti
Vedno upodobite phone.audio
Element phone.audio je neviden, vendar obvezen. Postavite ga kamor koli v JSX -- ne upodobi vidnega DOM-a, vendar interno upravlja zvočno povezavo WebRTC.
Med povezovanjem onemogočite gumb
Stanje connecting lahko traja 1–3 sekunde. Med tem stanjem onemogočite gumb za klic, da preprečite podvojene poskuse povezovanja.
Ustrezno obravnavajte stanje napake
Ko je stanje error, uporabniku prikažite phone.error in pustite gumb za klic omogočen. Kavelj stanja error ne zapusti samodejno -- ponovni klic connect() začne nov poskus in počisti prejšnjo napako.
Za stranske učinke uporabite povratne klice
Povratni klici onConnect, onDisconnect in onError so idealni za analitiko, beleženje ali sprožanje druge logike aplikacije brez preverjanja stanja v zanki.
Ravni zvoka berite iz audioLevelRef
audioLevelRef je edini vir ravni zvoka v živo. Za gladke animacije, kot so valovne oblike, preberite audioLevelRef.current znotraj requestAnimationFrame (branje reference ne povzroči ponovnega upodabljanja) ali ga vzorčite v intervalih in rezultat shranite v stanje za uporabniški vmesnik, upodobljen z Reactom. Število audioLevel je zastarelo in je vedno 0 -- nanj ne gradite logike.