Open in
Headless Hook
Vytvorte úplne vlastné hlasové používateľské rozhranie pomocou React hooku useThunderPhone
Hook useThunderPhone vám poskytuje úplnú kontrolu nad používateľským rozhraním, zatiaľ čo ThunderPhone spravuje hlasovú reláciu, smerovanie zvuku a stav pripojenia. Použite ho, keď chcete úplne vlastné UI -- vlastné tlačidlá, rozloženia, animácie a branding -- zatiaľ čo ThunderPhone zabezpečuje všetko na pozadí.
Kedy použiť hook bez vlastného UI
Predpripravený komponent ThunderPhoneWidget pokrýva väčšinu prípadov použitia, ale hook bez vlastného UI použite, keď potrebujete:
- Úplne vlastné UI hovoru, ktoré zodpovedá dizajnovému systému vašej aplikácie
- Vizualizácie reagujúce na zvuk (priebeh zvukovej vlny, gule, pulzujúce indikátory) riadené úrovňami zvuku v reálnom čase
- Vlastné toky hovorov, ako sú formuláre pred hovorom, prieskumy po hovore alebo vložený chat popri hlase
- Integráciu do existujúcej knižnice komponentov (Material UI, Chakra, Radix atď.)
Inštalácia
npm install @thunderphone/widgetZákladné použitie
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
Tieto možnosti odovzdajte funkcii useThunderPhone prostredníctvom UseThunderPhoneOptions:
| Možnosť | Typ | Povinné | Predvolené | Popis |
|---|---|---|---|---|
publishableKey | string | Áno | -- | Verejný kľúč API (pk_live_...). Hlasový agent sa automaticky určí z konfigurácie widgetu kľúča. |
apiBase | string | Nie | 'https://api.thunderphone.com/v1' | Prepísanie základnej URL API. |
language | string | Nie | -- | Prepísanie jazyka pre reláciu -- kód jazyka alebo miestne nastavenie, napríklad en, es alebo fr-FR. Keď nie je nastavené, použije sa nakonfigurovaný jazyk hlasového agenta. |
voice | string | Nie | -- | Prepísanie hlasu pre reláciu -- názov hlasu, napríklad maria. Keď nie je nastavené, použije sa nakonfigurovaný hlas hlasového agenta. |
context | string | Nie | -- | Vecný kontext stránky alebo webu pre reláciu odovzdaný hlasovému agentovi. Na strane servera sa skráti na 12 000 znakov. |
onConnect | () => void | Nie | -- | Volá sa po pripojení hlasovej relácie. |
onDisconnect | () => void | Nie | -- | Volá sa po ukončení relácie. |
onError | (error) => void | Nie | -- | Volá sa pri chybách. Chyba obsahuje polia error (kód) a message. |
ringtone | boolean | string | Nie | false | Prehrať vyzváňací tón počas pripájania. true použije predvolený vyzváňací tón alebo zadajte reťazec URL pre vlastný zvuk. |
Návratová hodnota
Hook vracia objekt UseThunderPhoneReturn:
| Vlastnosť | Typ | Popis |
|---|---|---|
state | 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error' | Aktuálny stav pripojenia. |
connect | () => void | Spustí hlasovú reláciu. |
disconnect | () => void | Ukončí aktuálnu reláciu. |
toggleMute | () => void | Prepne stlmenie mikrofónu. |
isMuted | boolean | Určuje, či je mikrofón momentálne stlmený. |
error | string | undefined | Chybové hlásenie, keď je stav 'error'. |
agentName | string | undefined | Zobrazovaný názov pripojeného agenta. |
audioLevel | number | Zastarané -- vždy 0. Statický zástupný údaj zachovaný kvôli spätnej kompatibilite; nikdy sa neaktualizuje. Namiesto toho čítajte audioLevelRef.current. |
audioLevelRef | React.RefObject<number> | Meniteľná referencia obsahujúca úroveň zvuku v reálnom čase (0--1) -- hlasnejšiu z hlasu agenta a mikrofónu návštevníka -- aktualizovaná pri každom animačnom snímku mimo cyklu vykresľovania Reactu. Pre plynulé animácie bez zasekávania čítajte audioLevelRef.current v slučkách requestAnimationFrame, alebo hodnotu vzorkujte v intervale, keď ju potrebujete v stave Reactu. |
audio | ReactNode | Neviditeľný prvok, ktorý spracúva zvukové pripojenie -- musí sa vykresliť. |
Používateľské rozhranie reagujúce na zvuk
Referencia audioLevelRef poskytuje úrovne zvuku pri každom snímku bez spúšťania opätovného vykresľovania v Reacte, takže je ideálna na plynulé vizualizácie priebehu zvuku, pulzujúce gule alebo akúkoľvek animáciu naviazanú na konverzáciu. Úroveň odráža hlasnejší zdroj: hlas agenta alebo mikrofón návštevníka.
Príklad priebehu zvuku
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>
)
}Príklad pulzujúcej gule
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>
)
}Príklad indikátora hovorenia
Pre používateľské rozhranie vykresľované v Reacte, ktoré sa mení podľa hlasitosti -- napríklad odznak „hovorí“ založený na prahovej hodnote -- načítavajte audioLevelRef.current v intervaloch a výsledok ukladajte do stavu:
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>
)
}Stavový automat
Vlastnosť state prechádza týmto životným cyklom:
idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
\
--> error (stays until connect() is called again)
| Stav | Popis |
|---|---|
idle | Žiadna aktívna relácia. Pripravené na volanie connect(). |
connecting | Relácia sa vytvára. Počas tohto stavu deaktivujte tlačidlo hovoru. |
connected | Hlasová relácia je aktívna. Používateľ hovorí s agentom. |
disconnected | Relácia sa úspešne skončila. Po 1,5 sekunde sa automaticky prepne späť na idle. |
error | Niečo sa pokazilo. Správu skontrolujte v phone.error. Stav sa nevymaže sám -- opätovné volanie connect() spustí nový pokus a resetuje chybu. |
Príklady
S ovládaním stlmenia
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>
)
}So zvonením
Počas pripájania prehrajte zvuk zvonenia na simuláciu telefonického hovoru:
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}
</>
)
}Zvonenie sa opakuje počas stavu connecting a po pripojení agenta postupne zoslabne. Odovzdajte true pre predvolené vstavané zvonenie alebo reťazec s adresou URL, ak chcete použiť vlastný zvukový súbor.
So spätnými volaniami udalostí
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}
</>
)
}Plne vlastné používateľské rozhranie
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>
)
}Tipy
Vždy vykreslite phone.audio
Prvok phone.audio je neviditeľný, ale povinný. Umiestnite ho kamkoľvek do svojho JSX -- nevykresľuje žiadny viditeľný DOM, ale interne spravuje zvukové pripojenie WebRTC.
Počas pripájania deaktivujte tlačidlo
Stav connecting môže trvať 1 až 3 sekundy. Počas tohto stavu deaktivujte tlačidlo hovoru, aby ste zabránili duplicitným pokusom o pripojenie.
Správne spracujte chybový stav
Keď je stav error, zobrazte používateľovi phone.error a ponechajte tlačidlo hovoru aktívne. Hook neopustí stav error sám -- opätovné volanie connect() spustí nový pokus a vymaže predchádzajúcu chybu.
Na vedľajšie účinky používajte spätné volania
Spätné volania onConnect, onDisconnect a onError sú ideálne na analytiku, zaznamenávanie alebo spúšťanie inej logiky aplikácie bez pravidelného kontrolovania stavu.
Čítajte úrovne zvuku z audioLevelRef
audioLevelRef je jediný živý zdroj úrovne zvuku. Ak chcete plynulé animácie, napríklad zvukové vlny, čítajte audioLevelRef.current v rámci requestAnimationFrame (čítanie ref nespôsobuje opätovné vykreslenie), alebo ho vzorkujte v intervale a výsledok uložte do stavu pre používateľské rozhranie vykresľované Reactom. Číslo audioLevel je zastarané a vždy má hodnotu 0 -- nestavajte na ňom logiku.