Headless Hook
Izveidojiet pilnībā pielāgotu balss lietotāja saskarni, izmantojot useThunderPhone React āķi
useThunderPhone āķis nodrošina pilnīgu kontroli pār lietotāja saskarni, kamēr ThunderPhone pārvalda balss sesiju, audio maršrutēšanu un savienojuma stāvokli. Izmantojiet to, ja vēlaties pilnībā pielāgotu lietotāja saskarni — savas pogas, izkārtojumus, animācijas un zīmolradi — kamēr ThunderPhone visu pārējo apstrādā fonā.
Kad izmantot bezsaskarnes āķi
Iepriekš izveidotais ThunderPhoneWidget komponents aptver lielāko daļu lietošanas gadījumu, taču izmantojiet bezsaskarnes āķi, ja nepieciešams:
- Pilnībā pielāgots zvana lietotāja interfeiss, kas atbilst jūsu lietotnes dizaina sistēmai
- Uz audio reaģējošas vizualizācijas (viļņu formas, lodes, pulsējoši indikatori), ko nodrošina reāllaika audio līmeņi
- Pielāgotas zvanu plūsmas, piemēram, veidlapas pirms zvana, aptaujas pēc zvana vai iekļauta tērzēšana līdzās balsij
- Integrācija esošā komponentu bibliotēkā (Material UI, Chakra, Radix u. c.)
Instalēšana
npm install @thunderphone/widgetPamata lietošana
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}
</>
)
}Opcijas
Nododiet šīs opcijas useThunderPhone, izmantojot UseThunderPhoneOptions:
| Opcija | Tips | Obligāta | Noklusējums | Apraksts |
|---|---|---|---|---|
publishableKey | string | Jā | -- | Publiskā API atslēga (pk_live_...). Balss aģents tiek automātiski noteikts pēc atslēgas logrīka konfigurācijas. |
apiBase | string | Nē | 'https://api.thunderphone.com/v1' | API bāzes URL aizstāšana. |
language | string | Nē | -- | Valodas aizstāšana katrai sesijai — valodas kods vai lokalizācija, piemēram, en, es vai fr-FR. Ja nav iestatīts, tiek izmantota balss aģenta konfigurētā valoda. |
voice | string | Nē | -- | Balss aizstāšana katrai sesijai — balss nosaukums, piemēram, maria. Ja nav iestatīts, tiek izmantota balss aģenta konfigurētā balss. |
context | string | Nē | -- | Balss aģentam nodotais katras sesijas faktiskais lapas vai vietnes konteksts. Servera pusē saīsināts līdz 12 000 rakstzīmēm. |
onConnect | () => void | Nē | -- | Tiek izsaukta, kad tiek izveidots savienojums ar balss sesiju. |
onDisconnect | () => void | Nē | -- | Tiek izsaukta, kad sesija beidzas. |
onError | (error) => void | Nē | -- | Tiek izsaukta kļūdu gadījumā. Kļūdai ir error (koda) un message lauki. |
ringtone | boolean | string | Nē | false | Atskaņot zvana signālu savienojuma izveides laikā. true noklusējuma zvana signālam vai URL virkne pielāgotam audio. |
Atgrieztā vērtība
Āķis atgriež UseThunderPhoneReturn objektu:
| Rekvizīts | Tips | Apraksts |
|---|---|---|
state | 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error' | Pašreizējais savienojuma stāvoklis. |
connect | () => void | Sāciet balss sesiju. |
disconnect | () => void | Beidziet pašreizējo sesiju. |
toggleMute | () => void | Ieslēdziet vai izslēdziet mikrofona skaņas izslēgšanu. |
isMuted | boolean | Vai mikrofona skaņa pašlaik ir izslēgta. |
error | string | undefined | Kļūdas ziņojums, ja stāvoklis ir 'error'. |
agentName | string | undefined | Pievienotā balss aģenta parādāmais nosaukums. |
audioLevel | number | Novecojis -- vienmēr 0. Statisks vietturis, kas saglabāts atpakaļsaderībai; tas nekad netiek atjaunināts. Tā vietā nolasiet audioLevelRef.current. |
audioLevelRef | React.RefObject<number> | Mainīga atsauce, kas satur reāllaika audio līmeni (0--1) — augstāko no aģenta balss un apmeklētāja mikrofona līmeņa — un tiek atjaunināta katrā animācijas kadrā ārpus React renderēšanas cikla. Lai iegūtu plūstošas animācijas bez aizturēm, nolasiet audioLevelRef.current requestAnimationFrame ciklos, vai pārbaudiet to noteiktā intervālā, kad vērtība nepieciešama React stāvoklī. |
audio | ReactNode | Neredzams elements, kas apstrādā audio savienojumu — tas ir jārenderē. |
Uz audio reaģējošs lietotāja interfeiss
Atsauce audioLevelRef nodrošina audio līmeņus kadru nomaiņas ātrumā, neizraisot React pārzīmēšanu, tāpēc tā ir ideāli piemērota plūstošu viļņformas vizualizāciju, pulsējošu ložu vai jebkuras ar sarunu saistītas animācijas vadībai. Līmenis atspoguļo to, kas ir skaļāks: balss aģenta balss vai apmeklētāja mikrofons.
Viļņformas piemērs
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>
)
}Pulsējošas lodes piemērs
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>
)
}Runāšanas indikatora piemērs
React renderētam lietotāja interfeisam, kas mainās atkarībā no skaļuma — piemēram, slieksnī balstītai “runāšanas” nozīmītei — intervālos nolasiet audioLevelRef.current un saglabājiet rezultātu stāvoklī:
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>
)
}Stāvokļu automāts
Rekvizīts state ievēro šādu dzīves ciklu:
idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
\
--> error (stays until connect() is called again)
| Stāvoklis | Apraksts |
|---|---|
idle | Nav aktīvas sesijas. Gatavs izsaukt connect(). |
connecting | Tiek izveidota sesija. Šajā stāvoklī atspējojiet zvana pogu. |
connected | Balss sesija ir aktīva. Lietotājs sarunājas ar balss aģentu. |
disconnected | Sesija ir veiksmīgi beigusies. Pēc 1,5 sekundēm stāvoklis automātiski pāriet atpakaļ uz idle. |
error | Kaut kas nogāja greizi. Pārbaudiet phone.error, lai skatītu ziņojumu. Stāvoklis netiek notīrīts automātiski — atkārtoti izsaucot connect(), tiek sākts jauns mēģinājums un atiestatīta kļūda. |
Piemēri
Ar skaņas izslēgšanas vadību
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>
)
}Ar zvansignālu
Savienojuma izveides laikā atskaņojiet zvana signālu, lai imitētu tālruņa zvanu:
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}
</>
)
}Zvansignāls tiek atkārtots stāvoklī connecting un pakāpeniski apklust, kad pieslēdzas balss aģents. Ievadiet true, lai izmantotu iebūvēto noklusējuma zvansignālu, vai URL virkni, lai izmantotu savu audio failu.
Ar notikumu atgriezeniskajām izsaukšanas funkcijām
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}
</>
)
}Pilnībā pielāgota lietotāja saskarne
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>
)
}Padomi
Vienmēr renderējiet phone.audio
Elements phone.audio ir neredzams, taču obligāts. Novietojiet to jebkur JSX kodā -- tas nerenderē redzamu DOM, bet iekšēji pārvalda WebRTC audio savienojumu.
Savienojuma izveides laikā atspējojiet pogu
Stāvoklis connecting var ilgt 1–3 sekundes. Šajā stāvoklī atspējojiet zvana pogu, lai novērstu dublētus savienojuma mēģinājumus.
Korekti apstrādājiet kļūdas stāvokli
Kad stāvoklis ir error, parādiet lietotājam phone.error un atstājiet zvana pogu iespējotu. Hooks pats neiziet no stāvokļa error -- atkārtoti izsaucot connect(), tiek sākts jauns mēģinājums un notīrīta iepriekšējā kļūda.
Blakusiedarbībām izmantojiet atzvanes funkcijas
Atzvanes funkcijas onConnect, onDisconnect un onError ir piemērotas analītikai, žurnālu reģistrēšanai vai citas lietojumprogrammas loģikas aktivizēšanai, neaptaujājot stāvokli.
Lasiet audio līmeņus no audioLevelRef
audioLevelRef ir vienīgais reāllaika audio līmeņa avots. Lai iegūtu vienmērīgas animācijas, piemēram, viļņu formas, lasiet audioLevelRef.current funkcijā requestAnimationFrame (ref nolasīšana neizraisa atkārtotu renderēšanu), vai nolasiet to noteiktos intervālos un saglabājiet rezultātu stāvoklī React renderētajai lietotāja saskarnei. Skaitlis audioLevel ir novecojis un vienmēr ir 0 -- neveidojiet uz tā loģiku.