ThunderPhone 2.0 on nyt julkaistu.Ota käyttöön itse – alkaen 2¢/min.Lue lisää julkistuksesta

Widget

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

Peruskä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:

AsetusTyyppiPakollinenOletusKuvaus
publishableKeystringKyllä--Julkaistava API-avain (pk_live_...). Agentti määritetään automaattisesti avaimen widget-määrityksestä.
apiBasestringEi'https://api.thunderphone.com/v1'API:n perus-URL-osoitteen ohitus.
languagestringEi--Istuntokohtainen kieliohitus -- kielikoodi tai kieliasetus, kuten en, es tai fr-FR. Jos sitä ei ole asetettu, agentin määritettyä kieltä käytetään.
voicestringEi--Istuntokohtainen äänen ohitus -- äänen nimi, kuten maria. Jos sitä ei ole asetettu, agentin määritettyä ääntä käytetään.
contextstringEi--Agentille välitettävä istuntokohtainen faktapohjainen sivu- tai sivustokonteksti. Katkaistaan palvelinpuolella 12 000 merkkiin.
onConnect() => voidEi--Kutsutaan, kun puheistunto muodostaa yhteyden.
onDisconnect() => voidEi--Kutsutaan, kun istunto päättyy.
onError(error) => voidEi--Kutsutaan virhetilanteissa. Virheellä on error- (koodi) ja message-kentät.
ringtoneboolean | stringEifalseToista soittoääni yhteyden muodostamisen aikana. Käytä arvoa true oletussoittoäänelle tai URL-merkkijonoa mukautetulle äänelle.

Paluuarvo

Hook palauttaa UseThunderPhoneReturn-objektin:

OminaisuusTyyppiKuvaus
state'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'Nykyinen yhteystila.
connect() => voidAloita puheistunto.
disconnect() => voidLopeta nykyinen istunto.
toggleMute() => voidOta mikrofonin mykistys käyttöön tai poista se käytöstä.
isMutedbooleanOnko mikrofoni tällä hetkellä mykistetty.
errorstring | undefinedVirheilmoitus, kun tila on 'error'.
agentNamestring | undefinedYhdistetyn agentin näyttönimi.
audioLevelnumberVanhentunut -- aina 0. Staattinen paikkamerkki, joka on säilytetty taaksepäin yhteensopivuuden vuoksi; se ei koskaan päivity. Lue sen sijaan audioLevelRef.current.
audioLevelRefReact.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.
audioReactNodeNä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)
TilaKuvaus
idleEi aktiivista istuntoa. Valmis kutsumaan connect().
connectingIstuntoa muodostetaan. Poista soittopainike käytöstä tämän tilan aikana.
connectedPuheistunto on aktiivinen. Käyttäjä puhuu agentin kanssa.
disconnectedIstunto on päättynyt onnistuneesti. Siirtyy automaattisesti takaisin tilaan idle 1,5 sekunnin kuluttua.
errorJokin 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.