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/widgetTemel 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çenek | Tür | Zorunlu | Varsayılan | Açıklama |
|---|---|---|---|---|
publishableKey | string | Evet | -- | Yayınlanabilir API anahtarı (pk_live_...). Yapay zeka ajanı, anahtarın widget yapılandırmasından otomatik olarak belirlenir. |
apiBase | string | Hayır | 'https://api.thunderphone.com/v1' | API temel URL'sini geçersiz kılma. |
language | string | Hayı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. |
voice | string | Hayı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. |
context | string | Hayı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 | () => void | Hayır | -- | Ses oturumu bağlandığında çağrılır. |
onDisconnect | () => void | Hayır | -- | Oturum sona erdiğinde çağrılır. |
onError | (error) => void | Hayır | -- | Hatalarda çağrılır. Hata, error (kod) ve message alanlarına sahiptir. |
ringtone | boolean | string | Hayır | false | Bağ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:
| Özellik | Tür | Açıklama |
|---|---|---|
state | 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error' | Geçerli bağlantı durumu. |
connect | () => void | Sesli oturumu başlatın. |
disconnect | () => void | Geçerli oturumu sonlandırın. |
toggleMute | () => void | Mikrofon sessize alma durumunu açıp kapatın. |
isMuted | boolean | Mikrofonun şu anda sessize alınıp alınmadığı. |
error | string | undefined | Durum 'error' olduğunda hata mesajı. |
agentName | string | undefined | Bağlı ajanın görünen adı. |
audioLevel | number | Kullanı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. |
audioLevelRef | React.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. |
audio | ReactNode | Ses 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)
| Durum | Açıklama |
|---|---|
idle | Etkin oturum yok. connect() çağrılmaya hazır. |
connecting | Oturum kuruluyor. Bu durum sırasında arama düğmesini devre dışı bırakın. |
connected | Sesli oturum etkin. Kullanıcı ajanla konuşuyor. |
disconnected | Oturum sorunsuz şekilde sona erdi. 1,5 saniye sonra otomatik olarak idle durumuna geçer. |
error | Bir 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.