---
title: "Headless Hook"
description: "useThunderPhone React हुक के साथ पूरी तरह कस्टम वॉइस UI बनाएं"
---

`useThunderPhone` हुक आपको यूज़र इंटरफ़ेस पर पूरा नियंत्रण देता है, जबकि ThunderPhone वॉइस सेशन, ऑडियो रूटिंग और कनेक्शन स्टेट को मैनेज करता है। जब आपको पूरी तरह कस्टम UI चाहिए हो -- अपने बटन, लेआउट, एनिमेशन और ब्रांडिंग के साथ -- और ThunderPhone बैकग्राउंड में सब कुछ संभाले, तब इसका उपयोग करें।

## हेडलेस हुक का उपयोग कब करें

प्री-बिल्ट `ThunderPhoneWidget` कंपोनेंट अधिकांश उपयोग मामलों को कवर करता है, लेकिन जब आपको यह चाहिए हो, तब हेडलेस हुक का उपयोग करें:

- पूरी तरह कस्टम कॉल UI, जो आपके ऐप के डिज़ाइन सिस्टम से मेल खाता हो
- रीयल-टाइम ऑडियो लेवल से संचालित ऑडियो-रिएक्टिव विज़ुअलाइज़ेशन (वेवफॉर्म, ऑर्ब्स, पल्सिंग इंडिकेटर)
- कस्टम कॉल फ्लो, जैसे प्री-कॉल फॉर्म, पोस्ट-कॉल सर्वे या वॉइस के साथ इनलाइन चैट
- किसी मौजूदा कंपोनेंट लाइब्रेरी (Material UI, Chakra, Radix आदि) में इंटीग्रेशन

---

## इंस्टॉलेशन

```bash
npm install @thunderphone/widget
```

<Note>
  हेडलेस हुक के लिए `@thunderphone/widget/style.css` इंपोर्ट करना **ज़रूरी नहीं** है, क्योंकि आप अपना UI प्रदान कर रहे हैं। हालांकि, आपको फिर भी वही `@thunderphone/widget` पैकेज इंस्टॉल करना होगा।
</Note>

---

## बुनियादी उपयोग

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

<Warning>
  **आपको अपने कंपोनेंट ट्री में कहीं `phone.audio` रेंडर करना आवश्यक है।** यह एक अदृश्य React एलिमेंट है जो अंतर्निहित ऑडियो कनेक्शन को मैनेज करता है। यदि आप इसे छोड़ देते हैं, तो कोई ऑडियो नहीं चलेगा और सेशन काम नहीं करेगा।
</Warning>

---

## विकल्प

इन विकल्पों को `UseThunderPhoneOptions` के माध्यम से `useThunderPhone` में पास करें:

| विकल्प | प्रकार | आवश्यक | डिफ़ॉल्ट | विवरण |
|--------|------|----------|---------|-------------|
| `publishableKey` | `string` | हाँ | -- | पब्लिशेबल API key (`pk_live_...`)। एजेंट को key के विजेट कॉन्फ़िगरेशन से स्वचालित रूप से निर्धारित किया जाता है। |
| `apiBase` | `string` | नहीं | `'https://api.thunderphone.com/v1'` | API बेस URL ओवरराइड। |
| `language` | `string` | नहीं | -- | प्रति-सेशन भाषा ओवरराइड -- कोई भाषा कोड या लोकेल, जैसे `en`, `es`, या `fr-FR`। सेट न होने पर, एजेंट की कॉन्फ़िगर की गई भाषा लागू होती है। key के [ब्राउज़र ओवरराइड्स](/hi/guides/embed-web-widget) में `language` आवश्यक है। |
| `voice` | `string` | नहीं | -- | प्रति-सेशन वॉइस ओवरराइड -- कोई वॉइस नाम, जैसे `maria`। सेट न होने पर, एजेंट की कॉन्फ़िगर की गई वॉइस लागू होती है। key के [ब्राउज़र ओवरराइड्स](/hi/guides/embed-web-widget) में `voice` आवश्यक है। |
| `context` | `string` | नहीं | -- | एजेंट को भेजा गया प्रति-सेशन तथ्यात्मक पेज या साइट कॉन्टेक्स्ट। सर्वर-साइड पर 12,000 कैरेक्टर तक ट्रंकेट किया जाता है। key के [ब्राउज़र ओवरराइड्स](/hi/guides/embed-web-widget) में `context` आवश्यक है। |
| `onConnect` | `() => void` | नहीं | -- | वॉइस सेशन कनेक्ट होने पर कॉल किया जाता है। |
| `onDisconnect` | `() => void` | नहीं | -- | सेशन समाप्त होने पर कॉल किया जाता है। |
| `onError` | `(error) => void` | नहीं | -- | एरर होने पर कॉल किया जाता है। एरर में `error` (कोड) और `message` फ़ील्ड होते हैं। |
| `ringtone` | `boolean \| string` | नहीं | `false` | कनेक्ट करते समय रिंगटोन चलाएँ। डिफ़ॉल्ट रिंगटोन के लिए `true`, या कस्टम ऑडियो के लिए URL स्ट्रिंग। |

<Note>
  हुक हेडलेस है: यह `ThunderPhoneWidget` के अपीयरेंस प्रॉप्स (`theme`, `primaryColor`, `title`, `position`, `className`) स्वीकार **नहीं** करता। इन्हें पास करना TypeScript त्रुटि है -- प्रेज़ेंटेशन पूरी तरह से आपको बनाना है।
</Note>

---

## रिटर्न वैल्यू

हुक एक `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 ट्रिगर किए बिना फ्रेम-रेट ऑडियो लेवल देता है, जिससे यह स्मूद वेवफॉर्म विज़ुअलाइज़ेशन, पल्सिंग ऑर्ब्स या बातचीत से जुड़ी किसी भी एनीमेशन को ड्राइव करने के लिए आदर्श है। लेवल इनमें से जो अधिक तेज़ हो उसे दर्शाता है: एजेंट की आवाज़ या विज़िटर का माइक्रोफ़ोन।

### वेवफॉर्म उदाहरण

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

### पल्सिंग ऑर्ब उदाहरण

```tsx
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` को एक अंतराल पर सैंपल करें और परिणाम को स्टेट में स्टोर करें:

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

<Warning>
  हमेशा `audioLevelRef.current` से लेवल पढ़ें। रिटर्न ऑब्जेक्ट पर मौजूद `audioLevel` नंबर **deprecated है और हमेशा `0` रहता है** -- इस पर बनी कोई भी लॉजिक बिना किसी संकेत के शून्य पढ़ेगी।
</Warning>

---

## स्टेट मशीन

`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()` को फिर से कॉल करने पर एक नया प्रयास शुरू होता है और एरर रीसेट हो जाता है। |

---

## उदाहरण

### म्यूट कंट्रोल के साथ

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

### रिंगटोन के साथ

फ़ोन कॉल का सिमुलेशन करने के लिए कनेक्ट करते समय रिंगिंग साउंड चलाएँ:

```tsx
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 स्ट्रिंग पास करें।

### इवेंट कॉलबैक के साथ

```tsx
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

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

---

## टिप्स

<AccordionGroup>
  <Accordion title="हमेशा phone.audio रेंडर करें">
    `phone.audio` एलिमेंट अदृश्य है, लेकिन आवश्यक है। इसे अपने JSX में कहीं भी रखें -- यह कोई दृश्य DOM रेंडर नहीं करता, लेकिन अंदरूनी रूप से WebRTC ऑडियो कनेक्शन मैनेज करता है।
  </Accordion>

  <Accordion title="कनेक्ट करते समय बटन निष्क्रिय करें">
    `connecting` स्टेट 1-3 सेकंड तक रह सकती है। डुप्लिकेट कनेक्शन प्रयासों को रोकने के लिए इस स्टेट के दौरान कॉल बटन निष्क्रिय करें।
  </Accordion>

  <Accordion title="एरर स्टेट को सहजता से हैंडल करें">
    जब स्टेट `error` हो, तो उपयोगकर्ता को `phone.error` दिखाएँ और अपना कॉल बटन सक्षम रखें। हुक अपने-आप `error` स्टेट से बाहर नहीं जाता -- `connect()` को फिर से कॉल करने पर नया प्रयास शुरू होता है और पिछली एरर हट जाती है।
  </Accordion>

  <Accordion title="साइड इफेक्ट्स के लिए कॉलबैक का उपयोग करें">
    `onConnect`, `onDisconnect`, और `onError` कॉलबैक स्टेट को पोल किए बिना एनालिटिक्स, लॉगिंग या अन्य एप्लिकेशन लॉजिक ट्रिगर करने के लिए आदर्श हैं।
  </Accordion>

  <Accordion title="audioLevelRef से ऑडियो लेवल पढ़ें">
    `audioLevelRef` एकमात्र लाइव ऑडियो-लेवल स्रोत है। वेवफॉर्म जैसी स्मूथ एनिमेशन के लिए `requestAnimationFrame` के अंदर `audioLevelRef.current` पढ़ें (रेफ पढ़ने से री-रेंडर नहीं होते), या इसे किसी इंटरवल पर सैंपल करें और React-रेंडर किए गए UI के लिए परिणाम को स्टेट में स्टोर करें। `audioLevel` नंबर डिप्रिकेटेड है और हमेशा `0` रहता है -- इस पर लॉजिक न बनाएँ।
  </Accordion>
</AccordionGroup>
