Headless Hook
Rakenna täysin mukautettu puhekäyttöliittymä useThunderPhone React -hookilla
useThunderPhone-hook antaa sinulle täydellisen hallinnan käyttöliittymästä, kun ThunderPhone hallitsee puheistuntoa, äänen reititystä ja yhteystilaa. Käytä sitä, kun haluat täysin mukautetun käyttöliittymän -- omat painikkeet, asettelut, animaatiot ja brändäyksen -- samalla kun ThunderPhone hoitaa kaiken taustalla.
Milloin Headless-hookia kannattaa käyttää
Valmiiksi rakennettu ThunderPhoneWidget-komponentti kattaa useimmat käyttötapaukset, mutta käytä headless-hookia, kun tarvitset:
- Täysin mukautetun puhelukäyttöliittymän, joka vastaa sovelluksesi suunnittelujärjestelmää
- Reaaliaikaisten äänitasojen ohjaamia ääneen reagoivia visualisointeja (aaltomuotoja, palloja, sykkiviä ilmaisimia)
- Mukautettuja puheluvirtoja, kuten puhelua edeltäviä lomakkeita, puhelun jälkeisiä kyselyitä tai puheen rinnalla toimivaa upotettua keskustelua
- Integroinnin olemassa olevaan komponenttikirjastoon (Material UI, Chakra, Radix jne.)
Asennus
npm install @thunderphone/widgetPeruskäyttö
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}
</>
)
}Asetukset
Välitä nämä asetukset useThunderPhone-hookille UseThunderPhoneOptions-objektin kautta:
| Asetus | Tyyppi | Pakollinen | Oletus | Kuvaus |
|---|---|---|---|---|
publishableKey | string | Kyllä | -- | Julkaistava API-avain (pk_live_...). Agentti määritetään automaattisesti avaimen widget-määrityksestä. |
apiBase | string | Ei | 'https://api.thunderphone.com/v1' | API:n perus-URL-osoitteen ohitus. |
language | string | Ei | -- | Istuntokohtainen kieliohitus -- kielikoodi tai kieliasetus, kuten en, es tai fr-FR. Jos sitä ei ole asetettu, agentin määritettyä kieltä käytetään. |
voice | string | Ei | -- | Istuntokohtainen äänen ohitus -- äänen nimi, kuten maria. Jos sitä ei ole asetettu, agentin määritettyä ääntä käytetään. |
context | string | Ei | -- | Agentille välitettävä istuntokohtainen faktapohjainen sivu- tai sivustokonteksti. Katkaistaan palvelinpuolella 12 000 merkkiin. |
onConnect | () => void | Ei | -- | Kutsutaan, kun puheistunto muodostaa yhteyden. |
onDisconnect | () => void | Ei | -- | Kutsutaan, kun istunto päättyy. |
onError | (error) => void | Ei | -- | Kutsutaan virhetilanteissa. Virheellä on error- (koodi) ja message-kentät. |
ringtone | boolean | string | Ei | false | Toista soittoääni yhteyden muodostamisen aikana. Käytä arvoa true oletussoittoäänelle tai URL-merkkijonoa mukautetulle äänelle. |
Paluuarvo
Hook palauttaa UseThunderPhoneReturn-objektin:
| Ominaisuus | Tyyppi | Kuvaus |
|---|---|---|
state | 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error' | Nykyinen yhteystila. |
connect | () => void | Aloita puheistunto. |
disconnect | () => void | Lopeta nykyinen istunto. |
toggleMute | () => void | Ota mikrofonin mykistys käyttöön tai poista se käytöstä. |
isMuted | boolean | Onko mikrofoni tällä hetkellä mykistetty. |
error | string | undefined | Virheilmoitus, kun tila on 'error'. |
agentName | string | undefined | Yhdistetyn agentin näyttönimi. |
audioLevel | number | Vanhentunut -- aina 0. Staattinen paikkamerkki, joka on säilytetty taaksepäin yhteensopivuuden vuoksi; se ei koskaan päivity. Lue sen sijaan audioLevelRef.current. |
audioLevelRef | React.RefObject<number> | Muokattava ref, joka sisältää reaaliaikaisen äänitason (0--1) -- agentin äänen ja kävijän mikrofonin voimakkaamman tason -- ja joka päivittyy jokaisella animaatiokehityksellä Reactin renderöintisyklin ulkopuolella. Lue audioLevelRef.current requestAnimationFrame-silmukoissa sulavia, nykimättömiä animaatioita varten tai ota siitä näyte tietyin välein, kun tarvitset arvon React-tilassa. |
audio | ReactNode | Näkymätön elementti, joka käsittelee ääniyhteyden -- on renderöitävä. |
Ääneen reagoiva käyttöliittymä
audioLevelRef-ref tarjoaa kehysnopeuden mukaiset äänitasot käynnistämättä React-uudelleenrenderöintejä, joten se sopii erinomaisesti sulavien aaltomuotovisualisointien, sykkivien pallojen tai minkä tahansa keskusteluun sidotun animaation ohjaamiseen. Taso vastaa sitä, kumpi on voimakkaampi: agentin ääni vai kävijän mikrofoni.
Aaltomuotoesimerkki
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>
)
}Sykkivä pallo -esimerkki
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>
)
}Puhumisilmaisimen esimerkki
React-renderöityä äänenvoimakkuuden mukaan muuttuvaa käyttöliittymää varten -- kuten kynnysarvoon perustuvaa "puhuu"-tunnistetta -- lue audioLevelRef.current säännöllisin välein ja tallenna tulos tilaan:
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>
)
}Tilakone
state-ominaisuus noudattaa tätä elinkaarta:
idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
\
--> error (stays until connect() is called again)
| Tila | Kuvaus |
|---|---|
idle | Ei aktiivista istuntoa. Valmis kutsumaan connect(). |
connecting | Istuntoa muodostetaan. Poista soittopainike käytöstä tämän tilan aikana. |
connected | Puheistunto on aktiivinen. Käyttäjä puhuu agentin kanssa. |
disconnected | Istunto on päättynyt onnistuneesti. Siirtyy automaattisesti takaisin tilaan idle 1,5 sekunnin kuluttua. |
error | Jokin meni pieleen. Tarkista viesti kohdasta phone.error. Tila ei tyhjene itsestään -- connect()-kutsun tekeminen uudelleen aloittaa uuden yrityksen ja nollaa virheen. |
Esimerkkejä
Mykistyksen hallinnalla
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>
)
}Soittoäänellä
Toista soittoääntä yhteyden muodostamisen aikana simuloidaksesi puhelua:
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}
</>
)
}Soittoääni toistuu connecting-tilassa ja vaimenee, kun agentti yhdistyy. Välitä sisäänrakennetulle oletussoittoäänelle true tai URL-merkkijono, jos haluat käyttää omaa äänitiedostoasi.
Tapahtumakutsujen kanssa
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}
</>
)
}Täysin mukautettu käyttöliittymä
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>
)
}Vinkit
Renderöi aina phone.audio
phone.audio-elementti on näkymätön, mutta pakollinen. Sijoita se mihin tahansa JSX-koodiisi -- se ei renderöi näkyvää DOM:ää, mutta hallitsee WebRTC-audioyhteyttä sisäisesti.
Poista painike käytöstä yhdistämisen aikana
connecting-tila voi kestää 1–3 sekuntia. Poista soittopainike käytöstä tämän tilan aikana, jotta vältät päällekkäiset yhdistämisyritykset.
Käsittele virhetila hallitusti
Kun tila on error, näytä käyttäjälle phone.error ja pidä soittopainike käytössä. Hook ei poistu error-tilasta itsestään -- connect()-funktion kutsuminen uudelleen aloittaa uuden yrityksen ja tyhjentää aiemman virheen.
Käytä takaisinsoittoja sivuvaikutuksiin
onConnect-, onDisconnect- ja onError-takaisinsoitot sopivat erinomaisesti analytiikkaan, lokitukseen tai muun sovelluslogiikan käynnistämiseen ilman tilan pollausta.
Lue äänitasot audioLevelRefistä
audioLevelRef on ainoa reaaliaikainen äänitasolähde. Lue audioLevelRef.current requestAnimationFrame-kutsun sisällä sulavia animaatioita, kuten aaltomuotoja, varten (refin lukeminen ei aiheuta uudelleenrenderöintiä), tai ota siitä näyte aikavälillä ja tallenna tulos tilaan Reactilla renderöityä käyttöliittymää varten. audioLevel-numero on vanhentunut ja aina 0 -- älä rakenna logiikkaa sen varaan.