> ## 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

> Vytvořte plně vlastní hlasové uživatelské rozhraní pomocí React hooku useThunderPhone

Hook `useThunderPhone` vám poskytuje úplnou kontrolu nad uživatelským rozhraním, zatímco ThunderPhone spravuje hlasovou relaci, směrování zvuku a stav připojení. Použijte ho, když chcete plně vlastní uživatelské rozhraní -- vlastní tlačítka, rozvržení, animace a branding -- zatímco ThunderPhone se postará o vše na pozadí.

## Kdy použít headless hook

Předpřipravená komponenta `ThunderPhoneWidget` pokrývá většinu případů použití, ale headless hook použijte, když potřebujete:

* Zcela vlastní uživatelské rozhraní hovoru, které odpovídá designovému systému vaší aplikace
* Vizualizace reagující na zvuk (křivky zvuku, koule, pulzující indikátory) řízené úrovněmi zvuku v reálném čase
* Vlastní toky hovoru, jako jsou formuláře před hovorem, dotazníky po hovoru nebo integrovaný chat vedle hlasové komunikace
* Integraci do existující knihovny komponent (Material UI, Chakra, Radix atd.)

***

## Instalace

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

<Note>
  Headless hook **nevyžaduje** import `@thunderphone/widget/style.css`, protože poskytujete vlastní uživatelské rozhraní. Stále však musíte nainstalovat stejný balíček `@thunderphone/widget`.
</Note>

***

## Základní použití

```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>
  **Musíte vykreslit `phone.audio` někde ve stromu komponent.** Jde o neviditelný prvek Reactu, který spravuje základní zvukové připojení. Pokud jej vynecháte, nebude se přehrávat žádný zvuk a relace nebude fungovat.
</Warning>

***

## Možnosti

Tyto možnosti předejte do `useThunderPhone` prostřednictvím `UseThunderPhoneOptions`:

| Možnost          | Typ                 | Povinné | Výchozí                             | Popis                                                                                                                                                              |
| ---------------- | ------------------- | ------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `publishableKey` | `string`            | Ano     | --                                  | Publikovatelný klíč API (`pk_live_...`). Agent se automaticky určí z konfigurace widgetu daného klíče.                                                             |
| `apiBase`        | `string`            | Ne      | `'https://api.thunderphone.com/v1'` | Přepsání základní adresy URL API.                                                                                                                                  |
| `language`       | `string`            | Ne      | --                                  | Přepsání jazyka pro relaci -- kód jazyka nebo národní prostředí, například `en`, `es` nebo `fr-FR`. Pokud není nastaveno, použije se nakonfigurovaný jazyk agenta. |
| `voice`          | `string`            | Ne      | --                                  | Přepsání hlasu pro relaci -- název hlasu, například `maria`. Pokud není nastaveno, použije se nakonfigurovaný hlas agenta.                                         |
| `context`        | `string`            | Ne      | --                                  | Faktický kontext stránky nebo webu pro relaci předaný agentovi. Na straně serveru zkrácený na 12 000 znaků.                                                        |
| `onConnect`      | `() => void`        | Ne      | --                                  | Volá se při připojení hlasové relace.                                                                                                                              |
| `onDisconnect`   | `() => void`        | Ne      | --                                  | Volá se při ukončení relace.                                                                                                                                       |
| `onError`        | `(error) => void`   | Ne      | --                                  | Volá se při chybách. Chyba obsahuje pole `error` (kód) a `message`.                                                                                                |
| `ringtone`       | `boolean \| string` | Ne      | `false`                             | Přehrává vyzvánění během připojování. `true` pro výchozí vyzvánění nebo řetězec URL pro vlastní zvuk.                                                              |

<Note>
  Hook je headless: **nepřijímá** vlastnosti vzhledu `ThunderPhoneWidget` (`theme`, `primaryColor`, `title`, `position`, `className`). Jejich předání způsobí chybu TypeScriptu -- prezentaci si vytváříte zcela sami.
</Note>

***

## Návratová hodnota

Hook vrací objekt `UseThunderPhoneReturn`:

| Vlastnost       | Typ                                                                  | Popis                                                                                                                                                                                                                                                                                                                                                                                     |
| --------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state`         | `'idle' \| 'connecting' \| 'connected' \| 'disconnected' \| 'error'` | Aktuální stav připojení.                                                                                                                                                                                                                                                                                                                                                                  |
| `connect`       | `() => void`                                                         | Spustí hlasovou relaci.                                                                                                                                                                                                                                                                                                                                                                   |
| `disconnect`    | `() => void`                                                         | Ukončí aktuální relaci.                                                                                                                                                                                                                                                                                                                                                                   |
| `toggleMute`    | `() => void`                                                         | Zapne nebo vypne ztlumení mikrofonu.                                                                                                                                                                                                                                                                                                                                                      |
| `isMuted`       | `boolean`                                                            | Zda je mikrofon aktuálně ztlumený.                                                                                                                                                                                                                                                                                                                                                        |
| `error`         | `string \| undefined`                                                | Chybová zpráva, když je stav `'error'`.                                                                                                                                                                                                                                                                                                                                                   |
| `agentName`     | `string \| undefined`                                                | Zobrazovaný název připojeného agenta.                                                                                                                                                                                                                                                                                                                                                     |
| `audioLevel`    | `number`                                                             | **Zastaralé -- vždy `0`.** Statický zástupný symbol zachovaný kvůli zpětné kompatibilitě; nikdy se neaktualizuje. Místo toho čtěte `audioLevelRef.current`.                                                                                                                                                                                                                               |
| `audioLevelRef` | `React.RefObject<number>`                                            | Měnitelná reference obsahující úroveň zvuku v reálném čase (0--1) -- vyšší z hlasitosti hlasu agenta a mikrofonu návštěvníka -- aktualizovaná v každém animačním snímku mimo cyklus vykreslování Reactu. Pro plynulé animace bez zasekávání čtěte `audioLevelRef.current` uvnitř smyček `requestAnimationFrame`, nebo hodnotu vzorkujte v intervalu, když ji potřebujete ve stavu Reactu. |
| `audio`         | `ReactNode`                                                          | Neviditelný prvek, který zajišťuje zvukové připojení -- **musí být vykreslen**.                                                                                                                                                                                                                                                                                                           |

***

## Uživatelské rozhraní reagující na zvuk

Ref `audioLevelRef` poskytuje úrovně zvuku ve snímkové frekvenci bez vyvolání opětovného vykreslení Reactu, takže je ideální pro plynulé vizualizace zvukové vlny, pulzující koule nebo jakoukoli animaci navázanou na konverzaci. Úroveň odráží hlasitější ze dvou zdrojů: hlas agenta nebo mikrofon návštěvníka.

### Příklad zvukové vlny

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

### Příklad pulzující koule

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

### Příklad indikátoru mluvení

Pro uživatelské rozhraní vykreslované Reactem, které se mění podle hlasitosti – například štítek „mluví“ založený na prahové hodnotě – načítejte `audioLevelRef.current` v intervalu a ukládejte výsledek do stavu:

```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>
  Úrovně vždy čtěte z `audioLevelRef.current`. Číslo `audioLevel` v návratovém objektu je **zastaralé a vždy má hodnotu `0`** – jakákoli logika založená na něm bude bez upozornění číst nulu.
</Warning>

***

## Stavový automat

Vlastnost `state` prochází tímto životním cyklem:

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

| Stav           | Popis                                                                                                                                              |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idle`         | Žádná aktivní relace. Připraveno k volání `connect()`.                                                                                             |
| `connecting`   | Relace se navazuje. V tomto stavu zakažte tlačítko volání.                                                                                         |
| `connected`    | Hlasová relace je aktivní. Uživatel mluví s agentem.                                                                                               |
| `disconnected` | Relace byla řádně ukončena. Po 1,5 sekundě se automaticky přepne zpět do stavu `idle`.                                                             |
| `error`        | Něco se pokazilo. Zkontrolujte zprávu v `phone.error`. Stav se sám **nevymaže** -- opětovné volání `connect()` zahájí nový pokus a resetuje chybu. |

***

## Příklady

### S ovládáním ztlumení

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

### S vyzváněním

Při připojování přehrajte vyzváněcí zvuk pro simulaci telefonního hovoru:

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

Vyzvánění se během stavu `connecting` opakuje a po připojení agenta postupně utichne. Pro vestavěné výchozí vyzvánění předejte hodnotu `true`, nebo řetězec URL pro použití vlastního zvukového souboru.

### S obslužnými funkcemi událostí

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

### Plně vlastní uživatelské rozhraní

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

***

## Tipy

<AccordionGroup>
  <Accordion title="Vždy vykreslujte phone.audio">
    Prvek `phone.audio` je neviditelný, ale povinný. Umístěte jej kamkoli do JSX -- nevykresluje žádný viditelný DOM, ale interně spravuje zvukové připojení WebRTC.
  </Accordion>

  <Accordion title="Během připojování tlačítko deaktivujte">
    Stav `connecting` může trvat 1–3 sekundy. Během tohoto stavu deaktivujte tlačítko volání, abyste zabránili duplicitním pokusům o připojení.
  </Accordion>

  <Accordion title="Stav chyby zpracujte vhodně">
    Když je stav `error`, zobrazte uživateli `phone.error` a tlačítko volání ponechte aktivní. Hook stav `error` sám neopustí -- opětovné volání `connect()` zahájí nový pokus a vymaže předchozí chybu.
  </Accordion>

  <Accordion title="Pro vedlejší efekty používejte callbacky">
    Callbacky `onConnect`, `onDisconnect` a `onError` jsou ideální pro analytiku, protokolování nebo spuštění jiné logiky aplikace bez dotazování na stav.
  </Accordion>

  <Accordion title="Úrovně zvuku čtěte z audioLevelRef">
    `audioLevelRef` je jediný živý zdroj úrovně zvuku. Pro plynulé animace, například průběhy vln, čtěte `audioLevelRef.current` uvnitř `requestAnimationFrame` (čtení ref nezpůsobuje opětovné vykreslení), nebo jej vzorkujte v intervalu a výsledek ukládejte do stavu pro uživatelské rozhraní vykreslované Reactem. Číslo `audioLevel` je zastaralé a vždy má hodnotu `0` -- nestavte na něm logiku.
  </Accordion>
</AccordionGroup>
