> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Headless Hook

> בנו ממשק משתמש קולי מותאם אישית במלואו באמצעות ה-Hook ‏useThunderPhone של React

ההוק `useThunderPhone` מעניק לכם שליטה מלאה בממשק המשתמש, בעוד ThunderPhone מנהל את סשן הקול, ניתוב השמע ומצב החיבור. השתמשו בו כאשר אתם רוצים ממשק משתמש מותאם אישית לחלוטין -- עם הכפתורים, הפריסות, ההנפשות והמיתוג שלכם -- בעוד ThunderPhone מטפל בכל מה שמאחורי הקלעים.

## מתי להשתמש בהוק ללא ממשק

הרכיב המובנה `ThunderPhoneWidget` מתאים לרוב תרחישי השימוש, אך השתמשו בהוק ללא ממשק כאשר אתם זקוקים ל:

* ממשק שיחה מותאם אישית לחלוטין שתואם למערכת העיצוב של האפליקציה שלכם
* המחשות חזותיות המגיבות לשמע (צורות גל, כדורים, מחוונים פועמים) המונעות על ידי רמות שמע בזמן אמת
* תהליכי שיחה מותאמים אישית, כגון טפסים לפני שיחה, סקרים לאחר שיחה או צ'אט מוטמע לצד קול
* שילוב בספריית רכיבים קיימת (Material UI, Chakra, Radix וכו')

***

## התקנה

```bash theme={null}
npm install @thunderphone/widget
```

<Note>
  ההוק ללא ממשק **אינו** מחייב ייבוא של `@thunderphone/widget/style.css`, מכיוון שאתם מספקים ממשק משתמש משלכם. עם זאת, עדיין עליכם להתקין את אותה חבילת `@thunderphone/widget`.
</Note>

***

## שימוש בסיסי

```tsx theme={null}
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>

***

## אפשרויות

העבירו את האפשרויות הבאות אל `useThunderPhone` באמצעות `UseThunderPhoneOptions`:

| אפשרות           | סוג                 | נדרש | ברירת מחדל                          | תיאור                                                                                                           |
| ---------------- | ------------------- | ---- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `publishableKey` | `string`            | כן   | --                                  | מפתח API ניתן לפרסום (`pk_live_...`). הסוכן מזוהה אוטומטית מהגדרת הווידג'ט של המפתח.                            |
| `apiBase`        | `string`            | לא   | `'https://api.thunderphone.com/v1'` | דריסת כתובת ה-URL הבסיסית של ה-API.                                                                             |
| `language`       | `string`            | לא   | --                                  | דריסת שפה לכל סשן -- קוד שפה או אזור, כגון `en`, `es` או `fr-FR`. כאשר אינו מוגדר, חלה השפה שהוגדרה עבור הסוכן. |
| `voice`          | `string`            | לא   | --                                  | דריסת קול לכל סשן -- שם קול כגון `maria`. כאשר אינו מוגדר, חל הקול שהוגדר עבור הסוכן.                           |
| `context`        | `string`            | לא   | --                                  | הקשר עובדתי של דף או אתר לכל סשן, המועבר לסוכן. נחתך בצד השרת ל-12,000 תווים.                                   |
| `onConnect`      | `() => void`        | לא   | --                                  | נקרא כאשר סשן הקול מתחבר.                                                                                       |
| `onDisconnect`   | `() => void`        | לא   | --                                  | נקרא כאשר הסשן מסתיים.                                                                                          |
| `onError`        | `(error) => void`   | לא   | --                                  | נקרא במקרה של שגיאות. לשגיאה יש שדות `error` (קוד) ו-`message`.                                                 |
| `ringtone`       | `boolean \| string` | לא   | `false`                             | השמעת צלצול במהלך ההתחברות. `true` עבור צלצול ברירת המחדל, או מחרוזת URL עבור שמע מותאם אישית.                  |

<Note>
  ההוק הוא ללא ממשק: הוא **אינו** מקבל את מאפייני המראה של `ThunderPhoneWidget` (`theme`, `primaryColor`, `title`, `position`, `className`). העברתם גורמת לשגיאת TypeScript -- הממשק החזותי כולו באחריותכם לבנות.
</Note>

***

## ערך מוחזר

ה-hook מחזיר אובייקט `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. קראו את `audioLevelRef.current` בתוך לולאות `requestAnimationFrame` כדי ליצור אנימציות חלקות ללא קפיצות, או דגמו אותו במרווח זמן כאשר אתם זקוקים לערך במצב React. |
| `audio`         | `ReactNode`                                                          | רכיב בלתי נראה המטפל בחיבור השמע -- **חובה לרנדר אותו**.                                                                                                                                                                                                                                                                                  |

***

## ממשק משתמש המגיב לשמע

ה־ref `audioLevelRef` מספק לכם רמות שמע בקצב פריימים ללא הפעלת רינדורים חוזרים של React, ולכן הוא אידיאלי להפעלת המחשות חלקות של צורת גל, כדורים פועמים או כל הנפשה המקושרת לשיחה. הרמה משקפת את המקור החזק יותר: קול הסוכן או המיקרופון של המבקר.

### דוגמה לצורת גל

```tsx theme={null}
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 theme={null}
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 ומשתנה בהתאם לעוצמת הקול — למשל תג "מדבר" המבוסס על סף — דגמו את `audioLevelRef.current` במרווח קבוע ושמרו את התוצאה במצב:

```tsx theme={null}
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` באובייקט ההחזרה הוא **מיושן ותמיד `0`** — כל לוגיקה המבוססת עליו תקרא אפס ללא הודעה.
</Warning>

***

## מכונת מצבים

המאפיין `state` פועל לפי מחזור החיים הבא:

```
idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
                  \
                   --> error (stays until connect() is called again)
```

| מצב            | תיאור                                                                                                                                             |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idle`         | אין סשן פעיל. מוכן לקריאה ל-`connect()`.                                                                                                          |
| `connecting`   | הסשן נמצא בתהליך הקמה. השביתו את לחצן השיחה במהלך מצב זה.                                                                                         |
| `connected`    | סשן הקול פעיל. המשתמש מדבר עם הסוכן.                                                                                                              |
| `disconnected` | הסשן הסתיים באופן תקין. המצב עובר בחזרה ל-`idle` באופן אוטומטי לאחר 1.5 שניות.                                                                    |
| `error`        | משהו השתבש. בדקו את `phone.error` כדי לראות את ההודעה. המצב **אינו** מתנקה מעצמו -- קריאה נוספת ל-`connect()` מתחילה ניסיון חדש ומאפסת את השגיאה. |

***

## דוגמאות

### עם בקרת השתקה

```tsx theme={null}
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 theme={null}
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 theme={null}
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}
    </>
  )
}
```

### ממשק משתמש מותאם אישית מלא

```tsx theme={null}
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` והשאירו את כפתור השיחה שלכם פעיל. ה-hook אינו יוצא ממצב `error` בעצמו -- קריאה נוספת ל-`connect()` מתחילה ניסיון חדש ומנקה את השגיאה הקודמת.
  </Accordion>

  <Accordion title="השתמשו ב-callbacks לתופעות לוואי">
    ה-callbacks `onConnect`, `onDisconnect` ו-`onError` מתאימים במיוחד לאנליטיקה, לרישום לוגים או להפעלת לוגיקה אחרת באפליקציה ללא תשאול של המצב.
  </Accordion>

  <Accordion title="קראו רמות שמע מ-audioLevelRef">
    `audioLevelRef` הוא המקור החי היחיד לרמת שמע. קראו את `audioLevelRef.current` בתוך `requestAnimationFrame` עבור הנפשות חלקות כמו צורות גל (קריאה מ-ref אינה גורמת לעיבודים מחדש), או דגמו אותו במרווחי זמן ושמרו את התוצאה במצב עבור ממשק משתמש שמרונדר על ידי React. המספר `audioLevel` הוצא משימוש ותמיד הוא `0` -- אל תבנו עליו לוגיקה.
  </Accordion>
</AccordionGroup>
