Headless Hook
useThunderPhone React हुक के साथ पूरी तरह कस्टम वॉइस UI बनाएं
useThunderPhone हुक आपको यूज़र इंटरफ़ेस पर पूरा नियंत्रण देता है, जबकि ThunderPhone वॉइस सेशन, ऑडियो रूटिंग और कनेक्शन स्टेट को मैनेज करता है। जब आपको पूरी तरह कस्टम UI चाहिए हो -- अपने बटन, लेआउट, एनिमेशन और ब्रांडिंग के साथ -- और ThunderPhone बैकग्राउंड में सब कुछ संभाले, तब इसका उपयोग करें।
हेडलेस हुक का उपयोग कब करें
प्री-बिल्ट ThunderPhoneWidget कंपोनेंट अधिकांश उपयोग मामलों को कवर करता है, लेकिन जब आपको यह चाहिए हो, तब हेडलेस हुक का उपयोग करें:
- पूरी तरह कस्टम कॉल UI, जो आपके ऐप के डिज़ाइन सिस्टम से मेल खाता हो
- रीयल-टाइम ऑडियो लेवल से संचालित ऑडियो-रिएक्टिव विज़ुअलाइज़ेशन (वेवफॉर्म, ऑर्ब्स, पल्सिंग इंडिकेटर)
- कस्टम कॉल फ्लो, जैसे प्री-कॉल फॉर्म, पोस्ट-कॉल सर्वे या वॉइस के साथ इनलाइन चैट
- किसी मौजूदा कंपोनेंट लाइब्रेरी (Material UI, Chakra, Radix आदि) में इंटीग्रेशन
इंस्टॉलेशन
npm install @thunderphone/widgetबुनियादी उपयोग
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}
</>
)
}विकल्प
इन विकल्पों को UseThunderPhoneOptions के माध्यम से useThunderPhone में पास करें:
| विकल्प | प्रकार | आवश्यक | डिफ़ॉल्ट | विवरण |
|---|---|---|---|---|
publishableKey | string | हाँ | -- | पब्लिशेबल API key (pk_live_...)। एजेंट का निर्धारण key के विजेट कॉन्फ़िगरेशन से अपने आप होता है। |
apiBase | string | नहीं | 'https://api.thunderphone.com/v1' | API बेस URL ओवरराइड। |
language | string | नहीं | -- | प्रति-सेशन भाषा ओवरराइड -- भाषा कोड या लोकेल, जैसे en, es या fr-FR। सेट न होने पर, एजेंट की कॉन्फ़िगर की गई भाषा लागू होती है। |
voice | string | नहीं | -- | प्रति-सेशन वॉइस ओवरराइड -- वॉइस नाम, जैसे maria। सेट न होने पर, एजेंट की कॉन्फ़िगर की गई वॉइस लागू होती है। |
context | string | नहीं | -- | प्रति-सेशन तथ्यात्मक पेज या साइट कॉन्टेक्स्ट, जो एजेंट को पास किया जाता है। सर्वर-साइड पर 12,000 कैरेक्टर तक ट्रंकेट किया जाता है। |
onConnect | () => void | नहीं | -- | वॉइस सेशन कनेक्ट होने पर कॉल किया जाता है। |
onDisconnect | () => void | नहीं | -- | सेशन समाप्त होने पर कॉल किया जाता है। |
onError | (error) => void | नहीं | -- | त्रुटियों पर कॉल किया जाता है। Error में error (कोड) और message फ़ील्ड होते हैं। |
ringtone | boolean | string | नहीं | false | कनेक्ट करते समय रिंगटोन चलाएँ। डिफ़ॉल्ट रिंगटोन के लिए true, या कस्टम ऑडियो के लिए URL स्ट्रिंग। |
रिटर्न वैल्यू
हुक एक UseThunderPhoneReturn ऑब्जेक्ट रिटर्न करता है:
| प्रॉपर्टी | टाइप | विवरण |
|---|---|---|
state | 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error' | वर्तमान कनेक्शन स्टेट। |
connect | () => void | वॉइस सेशन शुरू करें। |
disconnect | () => void | वर्तमान सेशन समाप्त करें। |
toggleMute | () => void | माइक्रोफ़ोन म्यूट को चालू/बंद करें। |
isMuted | boolean | क्या माइक्रोफ़ोन वर्तमान में म्यूट है। |
error | string | undefined | जब स्टेट 'error' हो, तब एरर मैसेज। |
agentName | string | undefined | कनेक्टेड एजेंट का डिस्प्ले नाम। |
audioLevel | number | अप्रचलित -- हमेशा 0। बैकवर्ड्स कंपैटिबिलिटी के लिए रखा गया एक स्टैटिक प्लेसहोल्डर; यह कभी अपडेट नहीं होता। इसके बजाय audioLevelRef.current पढ़ें। |
audioLevelRef | React.RefObject<number> | एक म्यूटेबल रेफ जिसमें रियल-टाइम ऑडियो लेवल (0--1) होता है -- एजेंट की आवाज़ और विज़िटर के माइक्रोफ़ोन में से अधिक तेज़ वाला -- जो हर एनीमेशन फ्रेम पर React के रेंडर साइकल के बाहर अपडेट होता है। स्मूद, जैंक-फ्री एनीमेशन के लिए requestAnimationFrame लूप के अंदर audioLevelRef.current पढ़ें, या जब React स्टेट में वैल्यू चाहिए हो तो इसे किसी इंटरवल पर सैंपल करें। |
audio | ReactNode | ऑडियो कनेक्शन हैंडल करने वाला अदृश्य एलिमेंट -- इसे रेंडर करना आवश्यक है। |
ऑडियो-रिएक्टिव UI
audioLevelRef ref आपको React re-renders ट्रिगर किए बिना फ्रेम-रेट ऑडियो लेवल देता है, जिससे यह स्मूद वेवफॉर्म विज़ुअलाइज़ेशन, पल्सिंग ऑर्ब्स या बातचीत से जुड़ी किसी भी एनीमेशन को ड्राइव करने के लिए आदर्श है। लेवल इनमें से जो अधिक तेज़ हो उसे दर्शाता है: एजेंट की आवाज़ या विज़िटर का माइक्रोफ़ोन।
वेवफॉर्म उदाहरण
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>
)
}पल्सिंग ऑर्ब उदाहरण
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>
)
}स्पीकिंग इंडिकेटर उदाहरण
वॉल्यूम के साथ बदलने वाले React-rendered UI के लिए -- जैसे थ्रेशोल्ड-आधारित "स्पीकिंग" बैज -- audioLevelRef.current को एक अंतराल पर सैंपल करें और परिणाम को स्टेट में स्टोर करें:
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>
)
}स्टेट मशीन
state प्रॉपर्टी इस लाइफसाइकल का पालन करती है:
idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
\
--> error (stays until connect() is called again)
| स्टेट | विवरण |
|---|---|
idle | कोई सक्रिय सेशन नहीं है। connect() कॉल करने के लिए तैयार। |
connecting | सेशन स्थापित किया जा रहा है। इस स्टेट के दौरान कॉल बटन को डिसेबल करें। |
connected | वॉइस सेशन सक्रिय है। यूज़र एजेंट से बात कर रहा है। |
disconnected | सेशन साफ़ तरीके से समाप्त हो गया है। 1.5 सेकंड के बाद अपने-आप वापस idle में ट्रांज़िशन हो जाता है। |
error | कुछ गलत हो गया। मैसेज के लिए phone.error जांचें। स्टेट अपने-आप क्लियर नहीं होता -- connect() को फिर से कॉल करने पर एक नया प्रयास शुरू होता है और एरर रीसेट हो जाता है। |
उदाहरण
म्यूट कंट्रोल के साथ
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>
)
}रिंगटोन के साथ
फ़ोन कॉल का सिमुलेशन करने के लिए कनेक्ट करते समय रिंगिंग साउंड चलाएँ:
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}
</>
)
}रिंगटोन connecting स्टेट के दौरान लूप होती है और एजेंट के कनेक्ट होने पर धीरे-धीरे बंद हो जाती है। बिल्ट-इन डिफ़ॉल्ट रिंगटोन के लिए true पास करें, या अपनी ऑडियो फ़ाइल इस्तेमाल करने के लिए URL स्ट्रिंग पास करें।
इवेंट कॉलबैक के साथ
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}
</>
)
}पूरी तरह कस्टम UI
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>
)
}टिप्स
हमेशा phone.audio रेंडर करें
phone.audio एलिमेंट अदृश्य है, लेकिन आवश्यक है। इसे अपने JSX में कहीं भी रखें -- यह कोई दृश्य DOM रेंडर नहीं करता, लेकिन अंदरूनी रूप से WebRTC ऑडियो कनेक्शन मैनेज करता है।
कनेक्ट करते समय बटन निष्क्रिय करें
connecting स्टेट 1-3 सेकंड तक रह सकती है। डुप्लिकेट कनेक्शन प्रयासों को रोकने के लिए इस स्टेट के दौरान कॉल बटन निष्क्रिय करें।
एरर स्टेट को सहजता से हैंडल करें
जब स्टेट error हो, तो उपयोगकर्ता को phone.error दिखाएँ और अपना कॉल बटन सक्षम रखें। हुक अपने-आप error स्टेट से बाहर नहीं जाता -- connect() को फिर से कॉल करने पर नया प्रयास शुरू होता है और पिछली एरर हट जाती है।
साइड इफेक्ट्स के लिए कॉलबैक का उपयोग करें
onConnect, onDisconnect, और onError कॉलबैक स्टेट को पोल किए बिना एनालिटिक्स, लॉगिंग या अन्य एप्लिकेशन लॉजिक ट्रिगर करने के लिए आदर्श हैं।
audioLevelRef से ऑडियो लेवल पढ़ें
audioLevelRef एकमात्र लाइव ऑडियो-लेवल स्रोत है। वेवफॉर्म जैसी स्मूथ एनिमेशन के लिए requestAnimationFrame के अंदर audioLevelRef.current पढ़ें (रेफ पढ़ने से री-रेंडर नहीं होते), या इसे किसी इंटरवल पर सैंपल करें और React-रेंडर किए गए UI के लिए परिणाम को स्टेट में स्टोर करें। audioLevel नंबर डिप्रिकेटेड है और हमेशा 0 रहता है -- इस पर लॉजिक न बनाएँ।