Headless Hook

useThunderPhone-hook annab sulle täieliku kontrolli kasutajaliidese üle, samal ajal kui ThunderPhone haldab häälesessiooni, heli suunamist ja ühenduse olekut. Kasuta seda siis, kui soovid täielikult kohandatud kasutajaliidest — oma nuppe, paigutusi, animatsioone ja brändingut — ning ThunderPhone haldab kõike taustal.

Millal kasutada headless-hooki

Valmis ThunderPhoneWidget-komponent katab enamiku kasutusjuhtudest, kuid kasuta headless-hooki, kui vajad järgmist:


Paigaldamine

npm install @thunderphone/widget

Põhikasutus

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}
    </>
  )
}

Valikud

Anna need valikud useThunderPhone-ile UseThunderPhoneOptions kaudu:

ValikTüüpKohustuslikVaikeväärtusKirjeldus
publishableKeystringJah--Avalik API-võti (pk_live_...). Häälagent määratakse automaatselt võtme vidina konfiguratsiooni alusel.
apiBasestringEi'https://api.thunderphone.com/v1'API baas-URL-i alistamine.
languagestringEi--Seansipõhine keele alistamine — keelekood või lokaat, näiteks en, es või fr-FR. Kui see on määramata, kasutatakse häälagendi seadistatud keelt.
voicestringEi--Seansipõhine hääle alistamine — hääle nimi, näiteks maria. Kui see on määramata, kasutatakse häälagendi seadistatud häält.
contextstringEi--Häälagendile edastatav seansipõhine faktiline lehe või saidi kontekst. Serveris kärbitakse 12 000 tähemärgini.
onConnect() => voidEi--Kutsutakse välja, kui häälesessioon ühendub.
onDisconnect() => voidEi--Kutsutakse välja, kui seanss lõpeb.
onError(error) => voidEi--Kutsutakse välja vea korral. Vea väljad on error (kood) ja message.
ringtoneboolean | stringEifalseEsita ühenduse loomise ajal helinat. Vaikehelina jaoks kasuta true või kohandatud heli jaoks URL-i stringi.

Tagastusväärtus

Hook tagastab objekti UseThunderPhoneReturn:

OmadusTüüpKirjeldus
state'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'Praegune ühenduse olek.
connect() => voidKäivita häälseanss.
disconnect() => voidLõpeta praegune seanss.
toggleMute() => voidLülita mikrofoni vaigistus sisse või välja.
isMutedbooleanKas mikrofon on praegu vaigistatud.
errorstring | undefinedVeateade, kui olek on 'error'.
agentNamestring | undefinedÜhendatud agendi kuvatav nimi.
audioLevelnumberAegunud -- alati 0. Staatiline kohatäide, mis on säilitatud tagasiühilduvuse tagamiseks; seda ei värskendata kunagi. Loe selle asemel audioLevelRef.current.
audioLevelRefReact.RefObject<number>Muudetav ref, mis sisaldab reaalajas helitaset (0--1) -- agendi hääle ja külastaja mikrofoni valjemat taset -- ning mida värskendatakse igal animatsioonikaadril väljaspool Reacti renderdamistsüklit. Sujuvate, tõrgeteta animatsioonide jaoks loe audioLevelRef.current requestAnimationFrame-i tsüklites või võta väärtusest proov intervalliga, kui vajad seda Reacti olekus.
audioReactNodeNähtamatu element, mis haldab heliühendust -- see tuleb renderdada.

Helile reageeriv kasutajaliides

Ref audioLevelRef annab sulle kaadrisagedusega helitasemed ilma Reacti uuesti renderdamist käivitamata, mistõttu sobib see ideaalselt sujuvate lainekuju visualiseeringute, pulseerivate kerade või mis tahes vestlusega seotud animatsioonide juhtimiseks. Tase kajastab seda, kumb on valjem: agendi hääl või külastaja mikrofon.

Lainekuju näide

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>
  )
}

Pulseeriva kera näide

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>
  )
}

Rääkimise indikaatori näide

Reactiga renderdatava kasutajaliidese jaoks, mis muutub helitugevuse järgi — näiteks lävendipõhine „rääkimise” märk — võta audioLevelRef.current väärtusest regulaarsete ajavahemike järel näidiseid ja salvesta tulemus olekusse:

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>
  )
}

Olekuautomaat

Atribuut state järgib seda elutsüklit:

idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
                  \
                   --> error (stays until connect() is called again)
OlekKirjeldus
idleAktiivset seanssi pole. Valmis kutsuma connect().
connectingSeanssi luuakse. Keela selles olekus kõnenupud.
connectedHäälseanss on aktiivne. Kasutaja räägib agendiga.
disconnectedSeanss lõppes korrektselt. Naaseb 1,5 sekundi pärast automaatselt olekusse idle.
errorMidagi läks valesti. Kontrolli teadet phone.error kaudu. Olek ei tühjene iseenesest -- connect() uuesti kutsumine alustab uut katset ja lähtestab vea.

Näited

Vaigistuse juhtimisega

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>
  )
}

Helinaga

Esita ühenduse loomise ajal helinat, et jäljendada telefonikõnet:

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}
    </>
  )
}

Helin kordub oleku connecting ajal ja vaibub, kui häälagent ühendub. Sisseehitatud vaikehelina kasutamiseks edasta true või oma helifaili kasutamiseks URL-string.

Sündmuste tagasihelistustega

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äielikult kohandatud kasutajaliides

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>
  )
}

Näpunäited

Renderda alati phone.audio

Element phone.audio on nähtamatu, kuid vajalik. Paiguta see JSX-is ükskõik kuhu -- see ei renderda nähtavat DOM-i, kuid haldab WebRTC-audioühendust sisemiselt.

Keela nupp ühenduse loomise ajal

Olek connecting võib kesta 1–3 sekundit. Keela selles olekus helistamisnupp, et vältida ühenduse loomise korduskatseid.

Käsitle veaolekut sujuvalt

Kui olek on error, kuva kasutajale phone.error ja hoia helistamisnupp lubatuna. Hook ei lahku olekust error iseseisvalt -- connect() uuesti kutsumine alustab uut katset ja kustutab eelmise vea.

Kasuta kõrvaltoimete jaoks tagasihelistusi

Tagasihelistused onConnect, onDisconnect ja onError sobivad ideaalselt analüütikaks, logimiseks või muu rakendusloogika käivitamiseks ilma olekut küsitlemata.

Loe helitasemeid audioLevelRef-ist

audioLevelRef on ainus reaalajas helitaseme allikas. Sujuvate animatsioonide, näiteks lainekujude jaoks loe audioLevelRef.current seest requestAnimationFrame (ref-i lugemine ei põhjusta uuesti renderdamist) või võta sellest väärtus kindla intervalliga ning salvesta tulemus olekusse Reacti renderdatud kasutajaliidese jaoks. Arv audioLevel on aegunud ja alati 0 -- ära raja sellele loogikat.