ThunderPhone 2.0 yayında.Kendi başınıza kullanmaya başlayın; dakikada 2¢'den başlayan fiyatlarla.Duyuruyu okuyun

Widget

Headless Hook

useThunderPhone React hook

useThunderPhone hook'u, ThunderPhone ses oturumunu, ses yönlendirmesini ve bağlantı durumunu yönetirken kullanıcı arayüzü üzerinde tam denetim sağlar. ThunderPhone arka planda her şeyi yönetirken kendi düğmeleriniz, düzenleriniz, animasyonlarınız ve markalamanızla tamamen özel bir kullanıcı arayüzü istediğinizde kullanın.

Arayüzsüz Hook Ne Zaman Kullanılmalı

Hazır ThunderPhoneWidget bileşeni çoğu kullanım durumunu kapsar, ancak aşağıdakilere ihtiyacınız olduğunda arayüzsüz hook'u kullanın:

  • Uygulamanızın tasarım sistemine uyan tamamen özel bir arama kullanıcı arayüzü
  • Gerçek zamanlı ses seviyelerine dayalı, sese duyarlı görselleştirmeler (dalga formları, küreler, titreşimli göstergeler)
  • Arama öncesi formlar, arama sonrası anketler veya sesin yanında satır içi sohbet gibi özel arama akışları
  • Mevcut bir bileşen kitaplığıyla entegrasyon (Material UI, Chakra, Radix vb.)

Kurulum

npm install @thunderphone/widget

Temel Kullanım

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

Seçenekler

Bu seçenekleri UseThunderPhoneOptions aracılığıyla useThunderPhone için iletin:

SeçenekTürZorunluVarsayılanAçıklama
publishableKeystringEvet--Yayınlanabilir API anahtarı (pk_live_...). Yapay zeka ajanı, anahtarın widget yapılandırmasından otomatik olarak belirlenir.
apiBasestringHayır'https://api.thunderphone.com/v1'API temel URL'sini geçersiz kılma.
languagestringHayır--Oturum başına dil geçersiz kılma -- en, es veya fr-FR gibi bir dil kodu ya da yerel ayar. Ayarlanmadığında, yapay zeka ajanının yapılandırılmış dili uygulanır.
voicestringHayır--Oturum başına ses geçersiz kılma -- maria gibi bir ses adı. Ayarlanmadığında, yapay zeka ajanının yapılandırılmış sesi uygulanır.
contextstringHayır--Yapay zeka ajanına iletilen, oturum başına olgusal sayfa veya site bağlamı. Sunucu tarafında 12.000 karaktere kadar kısaltılır.
onConnect() => voidHayır--Ses oturumu bağlandığında çağrılır.
onDisconnect() => voidHayır--Oturum sona erdiğinde çağrılır.
onError(error) => voidHayır--Hatalarda çağrılır. Hata, error (kod) ve message alanlarına sahiptir.
ringtoneboolean | stringHayırfalseBağlanırken zil sesi çalar. Varsayılan zil sesi için true, özel ses için ise bir URL dizesi kullanın.

Dönüş Değeri

Hook, bir UseThunderPhoneReturn nesnesi döndürür:

ÖzellikTürAçıklama
state'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'Geçerli bağlantı durumu.
connect() => voidSesli oturumu başlatın.
disconnect() => voidGeçerli oturumu sonlandırın.
toggleMute() => voidMikrofon sessize alma durumunu açıp kapatın.
isMutedbooleanMikrofonun şu anda sessize alınıp alınmadığı.
errorstring | undefinedDurum 'error' olduğunda hata mesajı.
agentNamestring | undefinedBağlı ajanın görünen adı.
audioLevelnumberKullanımdan kaldırıldı -- her zaman 0. Geriye dönük uyumluluk için korunan statik bir yer tutucudur; hiçbir zaman güncellenmez. Bunun yerine audioLevelRef.current okuyun.
audioLevelRefReact.RefObject<number>Gerçek zamanlı ses seviyesini (0--1) içeren değiştirilebilir bir ref -- ajanın sesi ile ziyaretçinin mikrofonundan hangisi daha yüksekse o -- her animasyon karesinde React'in işleme döngüsü dışında güncellenir. Akıcı, takılmasız animasyonlar için requestAnimationFrame döngülerinde audioLevelRef.current okuyun veya React durumunda değere ihtiyaç duyduğunuzda bunu belirli aralıklarla örnekleyin.
audioReactNodeSes bağlantısını yöneten görünmez öğe -- işlenmelidir.

Sese Duyarlı UI

audioLevelRef ref'i, React yeniden oluşturmalarını tetiklemeden kare hızı düzeyinde ses seviyeleri sunar; bu da onu akıcı dalga biçimi görselleştirmeleri, titreşen küreler veya konuşmaya bağlı herhangi bir animasyonu çalıştırmak için ideal kılar. Seviye, yapay zeka ajanının sesi ya da ziyaretçinin mikrofonundan hangisi daha yüksekse onu yansıtır.

Dalga Biçimi Örneği

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

Titreşen Küre Örneği

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

Konuşma Göstergesi Örneği

Ses düzeyiyle değişen React ile işlenen bir kullanıcı arayüzü için -- eşik tabanlı bir "konuşuyor" rozeti gibi -- audioLevelRef.current değerini düzenli aralıklarla örnekleyin ve sonucu durumda saklayın:

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

Durum Makinesi

state özelliği şu yaşam döngüsünü izler:

idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
                  \
                   --> error (stays until connect() is called again)
DurumAçıklama
idleEtkin oturum yok. connect() çağrılmaya hazır.
connectingOturum kuruluyor. Bu durum sırasında arama düğmesini devre dışı bırakın.
connectedSesli oturum etkin. Kullanıcı ajanla konuşuyor.
disconnectedOturum sorunsuz şekilde sona erdi. 1,5 saniye sonra otomatik olarak idle durumuna geçer.
errorBir sorun oluştu. Mesaj için phone.error değerini kontrol edin. Durum kendiliğinden temizlenmez -- connect() işlevini yeniden çağırmak yeni bir deneme başlatır ve hatayı sıfırlar.

Örnekler

Sessize Alma Denetimi ile

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

Zil Sesi ile

Telefon aramasını simüle etmek için bağlanırken bir zil sesi çalın:

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

Zil sesi, connecting durumu sırasında döngü halinde çalar ve ajan bağlandığında kademeli olarak azalır. Yerleşik varsayılan zil sesi için true, kendi ses dosyanızı kullanmak için ise bir URL dizesi iletin.

Olay Geri Çağrıları ile

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

Tam Özel Arayüz

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

İpuçları

phone.audio'yu her zaman render edin

phone.audio öğesi görünmez ancak gereklidir. JSX'inizde herhangi bir yere yerleştirin -- görünür bir DOM render etmez, ancak WebRTC ses bağlantısını dahili olarak yönetir.

Bağlanırken düğmeyi devre dışı bırakın

connecting durumu 1-3 saniye sürebilir. Yinelenen bağlantı denemelerini önlemek için bu durum sırasında arama düğmesini devre dışı bırakın.

Hata durumunu sorunsuz şekilde yönetin

Durum error olduğunda, kullanıcıya phone.error değerini gösterin ve arama düğmenizi etkin tutun. Hook, error durumundan kendiliğinden çıkmaz -- connect() işlevini yeniden çağırmak yeni bir deneme başlatır ve önceki hatayı temizler.

Yan etkiler için geri çağrıları kullanın

onConnect, onDisconnect ve onError geri çağrıları; durumu yoklamadan analitik, günlük kaydı veya diğer uygulama mantıklarını tetiklemek için idealdir.

Ses seviyelerini audioLevelRef'ten okuyun

audioLevelRef, tek canlı ses seviyesi kaynağıdır. Dalga formları gibi akıcı animasyonlar için audioLevelRef.current değerini requestAnimationFrame içinde okuyun (bir ref'i okumak yeniden render işlemine neden olmaz) veya bunu belirli aralıklarla örnekleyip sonucu React tarafından render edilen kullanıcı arayüzü için durumda saklayın. audioLevel sayısı kullanımdan kaldırılmıştır ve her zaman 0 değerindedir -- bunun üzerine mantık oluşturmayın.