Headless Hook
useThunderPhone kabliukas suteikia jums visišką naudotojo sąsajos valdymą, o ThunderPhone tvarko balso sesiją, garso nukreipimą ir ryšio būseną. Naudokite jį, kai norite visiškai pritaikytos naudotojo sąsajos -- savo mygtukų, išdėstymų, animacijų ir prekės ženklo -- o ThunderPhone viskuo pasirūpina viduje.
Kada naudoti kabliuką be sąsajos
Iš anksto sukurtas ThunderPhoneWidget komponentas apima daugumą naudojimo atvejų, tačiau rinkitės kabliuką be sąsajos, kai reikia:
- Visiškai pritaikytos skambučio naudotojo sąsajos, atitinkančios jūsų programos dizaino sistemą
- Į garsą reaguojančių vizualizacijų (bangų formų, sferų, pulsuojančių indikatorių), valdomų pagal garso lygius realiuoju laiku
- Pritaikytų skambučio eigų, pvz., formų prieš skambutį, apklausų po skambučio ar pokalbio žinutėmis greta balso
- Integravimo į esamą komponentų biblioteką (Material UI, Chakra, Radix ir kt.)
Diegimas
npm install @thunderphone/widget
Pagrindinis naudojimas
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}
</>
)
}
Parinktys
Perduokite šias parinktis į useThunderPhone naudodami UseThunderPhoneOptions:
| Parinktis | Tipas | Būtina | Numatytoji reikšmė | Aprašymas |
|---|---|---|---|---|
publishableKey | string | Taip | -- | Viešasis API raktas (pk_live_...). Agentas automatiškai nustatomas pagal rakto valdiklio konfigūraciją. |
apiBase | string | Ne | 'https://api.thunderphone.com/v1' | API bazinio URL pakeitimas. |
language | string | Ne | -- | Vienos sesijos kalbos pakeitimas -- kalbos kodas arba lokalė, pvz., en, es arba fr-FR. Jei nenustatyta, taikoma sukonfigūruota agento kalba. |
voice | string | Ne | -- | Vienos sesijos balso pakeitimas -- balso pavadinimas, pvz., maria. Jei nenustatyta, taikomas sukonfigūruotas agento balsas. |
context | string | Ne | -- | Vienos sesijos faktinis puslapio arba svetainės kontekstas, perduodamas agentui. Serveryje sutrumpinamas iki 12 000 simbolių. |
onConnect | () => void | Ne | -- | Iškviečiama prisijungus balso sesijai. |
onDisconnect | () => void | Ne | -- | Iškviečiama pasibaigus sesijai. |
onError | (error) => void | Ne | -- | Iškviečiama įvykus klaidoms. Klaida turi laukus error (kodas) ir message. |
ringtone | boolean | string | Ne | false | Leisti skambėjimo toną jungiantis. true numatytajam skambėjimo tonui arba URL eilutė pritaikytam garsui. |
Grąžinama reikšmė
Hukas grąžina UseThunderPhoneReturn objektą:
| Savybė | Tipas | Aprašymas |
|---|---|---|
state | 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error' | Dabartinė ryšio būsena. |
connect | () => void | Pradėkite balso seansą. |
disconnect | () => void | Užbaikite dabartinį seansą. |
toggleMute | () => void | Įjunkite arba išjunkite mikrofono nutildymą. |
isMuted | boolean | Ar mikrofonas šiuo metu nutildytas. |
error | string | undefined | Klaidos pranešimas, kai būsena yra 'error'. |
agentName | string | undefined | Prijungto agento rodomas pavadinimas. |
audioLevel | number | Nebenaudojama -- visada 0. Statinė vietaženklė reikšmė, palikta siekiant atgalinio suderinamumo; ji niekada neatnaujinama. Vietoje to nuskaitykite audioLevelRef.current. |
audioLevelRef | React.RefObject<number> | Keičiamas ref, kuriame pateikiamas garso lygis realiuoju laiku (0--1) -- didesnis iš agento balso ir lankytojo mikrofono lygių -- atnaujinamas kiekviename animacijos kadre, ne React atvaizdavimo cikle. Sklandžioms, netrūkčiojančioms animacijoms nuskaitykite audioLevelRef.current requestAnimationFrame cikluose arba imkite reikšmės pavyzdžius intervalais, kai jos reikia React būsenoje. |
audio | ReactNode | Nematomas elementas, valdantis garso ryšį -- privalo būti atvaizduotas. |
Į garsą reaguojanti sąsaja
Nuoroda audioLevelRef pateikia kadrų dažnio garso lygius nesukeldama React pakartotinių atvaizdavimų, todėl puikiai tinka sklandžioms bangos formos vizualizacijoms, pulsuojantiems rutuliams ar bet kuriai su pokalbiu susietai animacijai valdyti. Lygis atspindi, kuris garsas yra stipresnis: agento balsas ar lankytojo mikrofonas.
Bangos formos pavyzdys
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>
)
}
Pulsuojančio rutulio pavyzdys
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>
)
}
Kalbėjimo indikatoriaus pavyzdys
React atvaizduojamai sąsajai, kuri keičiasi pagal garsumą, pavyzdžiui, slenksčiu pagrįstai „kalbėjimo“ žymai, nuskaitykite audioLevelRef.current nustatytu intervalu ir išsaugokite rezultatą būsenoje:
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>
)
}
Būsenų automatas
Savybė state veikia pagal šį gyvavimo ciklą:
idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
\
--> error (stays until connect() is called again)
| Būsena | Aprašymas |
|---|---|
idle | Nėra aktyvios sesijos. Parengta kviesti connect(). |
connecting | Kuriama sesija. Šios būsenos metu išjunkite skambinimo mygtuką. |
connected | Balso sesija aktyvi. Naudotojas kalbasi su agentu. |
disconnected | Sesija baigėsi sėkmingai. Po 1,5 sekundės automatiškai grįžta į idle. |
error | Įvyko klaida. Pranešimą tikrinkite phone.error. Būsena savaime neišsivalo -- pakartotinai iškvietus connect() pradedamas naujas bandymas ir klaida nustatoma iš naujo. |
Pavyzdžiai
Su nutildymo valdymu
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>
)
}
Su skambėjimo signalu
Leiskite skambėjimo garsą jungimosi metu, kad imituotumėte telefono skambutį:
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}
</>
)
}
Skambėjimo signalas kartojamas būsenos connecting metu ir nutyla, kai agentas prisijungia. Perduokite true, jei norite naudoti integruotą numatytąjį skambėjimo signalą, arba URL eilutę, jei norite naudoti savo garso failą.
Su įvykių atgaliniais iškvietimais
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}
</>
)
}
Visiškai pritaikyta vartotojo sąsaja
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>
)
}
Patarimai
Visada pateikite phone.audio
Elementas phone.audio yra nematomas, tačiau būtinas. Įdėkite jį bet kur JSX kode -- jis nepateikia matomo DOM, bet viduje valdo WebRTC garso ryšį.
Jungiantis išjunkite mygtuką
Būsena connecting gali trukti 1–3 sekundes. Šios būsenos metu išjunkite skambinimo mygtuką, kad išvengtumėte pasikartojančių prisijungimo bandymų.
Tinkamai apdorokite klaidos būseną
Kai būsena yra error, parodykite naudotojui phone.error ir palikite skambinimo mygtuką įjungtą. Kabliukas savarankiškai neišeina iš error būsenos -- dar kartą iškvietus connect(), pradedamas naujas bandymas ir išvaloma ankstesnė klaida.
Šaliniams veiksmams naudokite atgalinius iškvietimus
Atgaliniai iškvietimai onConnect, onDisconnect ir onError idealiai tinka analitikai, žurnalų įrašams arba kitai programos logikai suaktyvinti, neapklausiant būsenos.
Garso lygius nuskaitykite iš audioLevelRef
audioLevelRef yra vienintelis tiesioginis garso lygio šaltinis. Kad animacijos, pvz., bangų formos, būtų sklandžios, nuskaitykite audioLevelRef.current iš requestAnimationFrame (ref nuskaitymas nesukelia pakartotinio atvaizdavimo), arba imkite jo reikšmę intervalais ir saugokite rezultatą būsenoje, skirtoje React atvaizduojamai sąsajai. Skaičius audioLevel yra pasenęs ir visada lygus 0 -- nekurdami logikos juo nesiremkite.