ThunderPhone 2.0 ir klāt.Sāciet uzreiz — no 2 centiem minūtē.Lasīt paziņojumu

Widget

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/widget

Pamata 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:

OpcijaTipsObligātaNoklusējumsApraksts
publishableKeystring--Publiskā API atslēga (pk_live_...). Balss aģents tiek automātiski noteikts pēc atslēgas logrīka konfigurācijas.
apiBasestring'https://api.thunderphone.com/v1'API bāzes URL aizstāšana.
languagestring--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.
voicestring--Balss aizstāšana katrai sesijai — balss nosaukums, piemēram, maria. Ja nav iestatīts, tiek izmantota balss aģenta konfigurētā balss.
contextstring--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--Tiek izsaukta, kad tiek izveidots savienojums ar balss sesiju.
onDisconnect() => void--Tiek izsaukta, kad sesija beidzas.
onError(error) => void--Tiek izsaukta kļūdu gadījumā. Kļūdai ir error (koda) un message lauki.
ringtoneboolean | stringfalseAtskaņ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ītsTipsApraksts
state'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'Pašreizējais savienojuma stāvoklis.
connect() => voidSāciet balss sesiju.
disconnect() => voidBeidziet pašreizējo sesiju.
toggleMute() => voidIeslēdziet vai izslēdziet mikrofona skaņas izslēgšanu.
isMutedbooleanVai mikrofona skaņa pašlaik ir izslēgta.
errorstring | undefinedKļūdas ziņojums, ja stāvoklis ir 'error'.
agentNamestring | undefinedPievienotā balss aģenta parādāmais nosaukums.
audioLevelnumberNovecojis -- vienmēr 0. Statisks vietturis, kas saglabāts atpakaļsaderībai; tas nekad netiek atjaunināts. Tā vietā nolasiet audioLevelRef.current.
audioLevelRefReact.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ī.
audioReactNodeNeredzams 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āvoklisApraksts
idleNav aktīvas sesijas. Gatavs izsaukt connect().
connectingTiek izveidota sesija. Šajā stāvoklī atspējojiet zvana pogu.
connectedBalss sesija ir aktīva. Lietotājs sarunājas ar balss aģentu.
disconnectedSesija ir veiksmīgi beigusies. Pēc 1,5 sekundēm stāvoklis automātiski pāriet atpakaļ uz idle.
errorKaut 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.