Megérkezett a ThunderPhone 2.0.Önkiszolgáló használat már 2 cent/perctől.Olvassa el a bejelentést

Widget

Headless Hook

Hozzon létre teljesen egyedi hangalapú felhasználói felületet a useThunderPhone React hookkal

A useThunderPhone hook teljes körű irányítást biztosít a felhasználói felület felett, miközben a ThunderPhone kezeli a hangmunkamenetet, a hangirányítást és a kapcsolat állapotát. Akkor használja, ha teljesen egyedi felhasználói felületre van szüksége -- saját gombokkal, elrendezésekkel, animációkkal és arculattal --, miközben a ThunderPhone a háttérben mindent kezel.

Mikor használja a Headless hookot

Az előre elkészített ThunderPhoneWidget komponens a legtöbb használati esetet lefedi, de használja a headless hookot, ha a következőkre van szüksége:

  • Teljesen egyedi hívási felületre, amely illeszkedik az alkalmazása tervezési rendszeréhez
  • Valós idejű hangszintek által vezérelt, hangra reagáló vizualizációkra (hullámformák, gömbök, pulzáló jelzők)
  • Egyedi hívási folyamatokra, például hívás előtti űrlapokra, hívás utáni felmérésekre vagy a hang melletti beágyazott chatre
  • Integrációra meglévő komponenskönyvtárba (Material UI, Chakra, Radix stb.)

Telepítés

npm install @thunderphone/widget

Alapszintű használat

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

Beállítások

Ezeket a beállításokat adja át a useThunderPhone hooknak a UseThunderPhoneOptions használatával:

BeállításTípusKötelezőAlapértelmezettLeírás
publishableKeystringIgen--Publikálható API-kulcs (pk_live_...). Az AI-ügynököt a kulcs widget-konfigurációja alapján automatikusan határozza meg.
apiBasestringNem'https://api.thunderphone.com/v1'API-alap URL felülírása.
languagestringNem--Munkamenetenkénti nyelvi felülírás -- nyelvkód vagy területi beállítás, például en, es vagy fr-FR. Ha nincs beállítva, az AI-ügynök konfigurált nyelve érvényes.
voicestringNem--Munkamenetenkénti hangfelülírás -- hangnév, például maria. Ha nincs beállítva, az AI-ügynök konfigurált hangja érvényes.
contextstringNem--Az AI-ügynöknek átadott, munkamenetenkénti tényszerű oldal- vagy webhelykontextus. A szerveroldalon 12 000 karakterre csonkolva.
onConnect() => voidNem--A hangmunkamenet kapcsolódásakor hívódik meg.
onDisconnect() => voidNem--A munkamenet befejeződésekor hívódik meg.
onError(error) => voidNem--Hiba esetén hívódik meg. A hiba error (kód) és message mezőket tartalmaz.
ringtoneboolean | stringNemfalseCsengetési hang lejátszása kapcsolódás közben. Az alapértelmezett csengetési hanghoz true, egyéni hanghoz pedig URL-karakterlánc használható.

Visszatérési érték

A hook egy UseThunderPhoneReturn objektumot ad vissza:

TulajdonságTípusLeírás
state'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'Az aktuális kapcsolati állapot.
connect() => voidHangalapú munkamenet indítása.
disconnect() => voidAz aktuális munkamenet befejezése.
toggleMute() => voidA mikrofon némításának be- vagy kikapcsolása.
isMutedbooleanA mikrofon jelenleg némítva van-e.
errorstring | undefinedHibaüzenet, amikor az állapot 'error'.
agentNamestring | undefinedA csatlakoztatott ügynök megjelenített neve.
audioLevelnumberElavult -- mindig 0. Visszafelé kompatibilitás miatt megtartott statikus helyőrző; soha nem frissül. Ehelyett olvassa az audioLevelRef.current értékét.
audioLevelRefReact.RefObject<number>Egy módosítható ref, amely a valós idejű hangszintet (0--1) tartalmazza -- az ügynök hangja és a látogató mikrofonja közül a hangosabbat --, és minden animációs képkockán frissül, a React renderelési ciklusán kívül. A zökkenőmentes, akadásmentes animációkhoz olvassa az audioLevelRef.current értékét a requestAnimationFrame ciklusokon belül, vagy mintavételezze időközönként, amikor az értékre React-állapotban van szüksége.
audioReactNodeLáthatatlan elem, amely kezeli a hangkapcsolatot -- renderelni kell.

Hangra reagáló felhasználói felület

Az audioLevelRef ref képkockasebességű hangszinteket biztosít React-újrarenderelés kiváltása nélkül, ezért ideális sima hullámforma-vizualizációk, pulzáló gömbök vagy bármely, a beszélgetéshez kötött animáció vezérlésére. A szint azt tükrözi, amelyik hangosabb: az ügynök hangja vagy a látogató mikrofonja.

Hullámforma-példa

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

Pulzáló gömb példa

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

Beszédjelző példa

A hangerővel változó, React által renderelt felhasználói felületekhez -- például egy küszöbérték-alapú „beszél” jelvényhez -- adott időközönként olvassa ki az audioLevelRef.current értékét, és tárolja az eredményt állapotban:

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

Állapotgép

A state tulajdonság az alábbi életciklust követi:

idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
                  \
                   --> error (stays until connect() is called again)
ÁllapotLeírás
idleNincs aktív munkamenet. Készen áll a connect() hívására.
connectingA munkamenet létrehozása folyamatban van. Ebben az állapotban tiltsa le a hívás gombot.
connectedA hangalapú munkamenet aktív. A felhasználó az ügynökkel beszél.
disconnectedA munkamenet szabályosan befejeződött. 1,5 másodperc után automatikusan visszavált idle állapotba.
errorHiba történt. Az üzenetért ellenőrizze a phone.error értékét. Az állapot nem törlődik magától -- a connect() ismételt hívása új próbálkozást indít, és visszaállítja a hibát.

Példák

Némításvezérléssel

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

Csengőhanggal

Játsszon le csengőhangot a csatlakozás során telefonhívás szimulálásához:

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

A csengőhang a connecting állapot alatt ismétlődik, és elhalkul, amikor az ügynök csatlakozik. A beépített alapértelmezett csengőhanghoz adjon át true értéket, saját hangfájl használatához pedig URL-karakterláncot.

Esemény-visszahívásokkal

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

Teljesen egyedi felület

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

Tippek

A phone.audio mindig legyen renderelve

A phone.audio elem láthatatlan, de kötelező. Helyezze el bárhol a JSX-ben -- nem renderel látható DOM-ot, de belsőleg kezeli a WebRTC-hangkapcsolatot.

Kapcsolódás közben tiltsa le a gombot

A connecting állapot 1–3 másodpercig tarthat. Ebben az állapotban tiltsa le a hívásgombot, hogy megelőzze az ismétlődő kapcsolódási kísérleteket.

Kezelje elegánsan a hibaállapotot

Ha az állapot error, jelenítse meg a felhasználónak a phone.error értékét, és hagyja engedélyezve a hívásgombot. A hook nem lép ki magától az error állapotból -- a connect() újbóli meghívása új kísérletet indít, és törli az előző hibát.

Használjon visszahívásokat a mellékhatásokhoz

Az onConnect, onDisconnect és onError visszahívások ideálisak elemzésekhez, naplózáshoz vagy más alkalmazáslogika aktiválásához az állapot lekérdezése nélkül.

Olvassa az audio szinteket az audioLevelRef értékéből

Az audioLevelRef az egyetlen élő hangszintforrás. A hullámformákhoz hasonló folyamatos animációkhoz olvassa az audioLevelRef.current értékét a requestAnimationFrame használatával (egy ref olvasása nem okoz újrarenderelést), vagy mintavételezze időközönként, és tárolja az eredményt állapotban a React által renderelt felhasználói felülethez. Az audioLevel szám elavult, és mindig 0 -- ne építsen rá logikát.