Headless Hook

Kavelj useThunderPhone vam omogoča popoln nadzor nad uporabniškim vmesnikom, medtem ko ThunderPhone upravlja glasovno sejo, usmerjanje zvoka in stanje povezave. Uporabite ga, kadar želite popolnoma prilagojen uporabniški vmesnik -- lastne gumbe, postavitve, animacije in blagovno znamko -- medtem ko ThunderPhone v ozadju poskrbi za vse ostalo.

Kdaj uporabiti kavelj brez uporabniškega vmesnika

Vnaprej izdelana komponenta ThunderPhoneWidget pokriva večino primerov uporabe, vendar uporabite kavelj brez uporabniškega vmesnika, kadar potrebujete:


Namestitev

npm install @thunderphone/widget

Osnovna uporaba

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

Te možnosti posredujte funkciji useThunderPhone prek UseThunderPhoneOptions:

MožnostVrstaObveznoPrivzetoOpis
publishableKeystringDa--Javni ključ API (pk_live_...). Glasovni agent se samodejno določi iz konfiguracije pripomočka ključa.
apiBasestringNe'https://api.thunderphone.com/v1'Preglasitev osnovnega URL-ja API-ja.
languagestringNe--Preglasitev jezika za posamezno sejo -- jezikovna koda ali področna nastavitev, kot so en, es ali fr-FR. Če ni nastavljena, se uporabi konfigurirani jezik glasovnega agenta.
voicestringNe--Preglasitev glasu za posamezno sejo -- ime glasu, kot je maria. Če ni nastavljena, se uporabi konfigurirani glas glasovnega agenta.
contextstringNe--Stvarni kontekst strani ali spletnega mesta za posamezno sejo, posredovan glasovnemu agentu. Na strežniški strani je okrnjen na 12.000 znakov.
onConnect() => voidNe--Pokliče se, ko se glasovna seja poveže.
onDisconnect() => voidNe--Pokliče se, ko se seja konča.
onError(error) => voidNe--Pokliče se ob napakah. Napaka vsebuje polji error (koda) in message.
ringtoneboolean | stringNefalseMed vzpostavljanjem povezave predvaja zvonjenje. true za privzeto zvonjenje ali niz URL za zvok po meri.

Vrnjena vrednost

Kavelj vrne objekt UseThunderPhoneReturn:

LastnostVrstaOpis
state'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'Trenutno stanje povezave.
connect() => voidZačnite glasovno sejo.
disconnect() => voidKončajte trenutno sejo.
toggleMute() => voidVklopite ali izklopite utišanje mikrofona.
isMutedbooleanAli je mikrofon trenutno utišan.
errorstring | undefinedSporočilo o napaki, ko je stanje 'error'.
agentNamestring | undefinedPrikazno ime povezanega agenta.
audioLevelnumberZastarelo -- vedno 0. Statično nadomestno mesto, ohranjeno zaradi združljivosti s prejšnjimi različicami; nikoli se ne posodobi. Namesto tega preberite audioLevelRef.current.
audioLevelRefReact.RefObject<number>Spremenljiva referenca, ki vsebuje raven zvoka v realnem času (0--1) -- višjo od glasnosti agentovega glasu in mikrofona obiskovalca -- ter se posodobi v vsakem animacijskem okviru zunaj Reactovega cikla izrisovanja. Za gladke animacije brez zatikanja preberite audioLevelRef.current znotraj zank requestAnimationFrame, ali pa jo vzorčite v intervalih, kadar vrednost potrebujete v stanju Reacta.
audioReactNodeNeviden element, ki upravlja zvočno povezavo -- mora biti izrisan.

Na zvok odziven UI

Referenca audioLevelRef zagotavlja ravni zvoka s frekvenco sličic brez sprožanja ponovnih izrisov Reacta, zato je idealna za gladke vizualizacije valovnih oblik, utripajoče krogle ali katero koli animacijo, povezano s pogovorom. Raven odraža glasnejši vir: glas agenta ali mikrofon obiskovalca.

Primer valovne oblike

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

Primer utripajoče krogle

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

Primer indikatorja govora

Za UI, izrisan z Reactom, ki se spreminja glede na glasnost -- na primer oznako »govori«, ki temelji na pragu -- v intervalih vzorčite audioLevelRef.current in rezultat shranite v stanje:

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

Stroj stanj

Lastnost state sledi temu življenjskemu ciklu:

idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
                  \
                   --> error (stays until connect() is called again)
StanjeOpis
idleNi aktivne seje. Pripravljeno za klic connect().
connectingSeja se vzpostavlja. V tem stanju onemogočite gumb za klic.
connectedGlasovna seja je aktivna. Uporabnik se pogovarja z agentom.
disconnectedSeja se je pravilno končala. Po 1,5 sekunde se samodejno vrne v idle.
errorNekaj je šlo narobe. Sporočilo preverite v phone.error. Stanje se ne počisti samo -- ponovni klic connect() začne nov poskus in ponastavi napako.

Primeri

Z nadzorom utišanja

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

Z melodijo zvonjenja

Med povezovanjem predvajajte zvok zvonjenja, da simulirate telefonski klic:

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

Melodija zvonjenja se ponavlja med stanjem connecting in postopoma utihne, ko se agent poveže. Podajte true za vgrajeno privzeto melodijo zvonjenja ali niz URL-ja za uporabo lastne zvočne datoteke.

Z dogodkovnimi povratnimi klici

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

Popolnoma prilagojen uporabniški vmesnik

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

Nasveti

Vedno upodobite phone.audio

Element phone.audio je neviden, vendar obvezen. Postavite ga kamor koli v JSX -- ne upodobi vidnega DOM-a, vendar interno upravlja zvočno povezavo WebRTC.

Med povezovanjem onemogočite gumb

Stanje connecting lahko traja 1–3 sekunde. Med tem stanjem onemogočite gumb za klic, da preprečite podvojene poskuse povezovanja.

Ustrezno obravnavajte stanje napake

Ko je stanje error, uporabniku prikažite phone.error in pustite gumb za klic omogočen. Kavelj stanja error ne zapusti samodejno -- ponovni klic connect() začne nov poskus in počisti prejšnjo napako.

Za stranske učinke uporabite povratne klice

Povratni klici onConnect, onDisconnect in onError so idealni za analitiko, beleženje ali sprožanje druge logike aplikacije brez preverjanja stanja v zanki.

Ravni zvoka berite iz audioLevelRef

audioLevelRef je edini vir ravni zvoka v živo. Za gladke animacije, kot so valovne oblike, preberite audioLevelRef.current znotraj requestAnimationFrame (branje reference ne povzroči ponovnega upodabljanja) ali ga vzorčite v intervalih in rezultat shranite v stanje za uporabniški vmesnik, upodobljen z Reactom. Število audioLevel je zastarelo in je vedno 0 -- nanj ne gradite logike.