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

> Erstellen Sie mit dem React-Hook useThunderPhone eine vollständig individuelle Sprach-Benutzeroberfläche

Der Hook `useThunderPhone` gibt Ihnen vollständige Kontrolle über die Benutzeroberfläche, während ThunderPhone die Sprachsitzung, Audio-Routing und den Verbindungsstatus verwaltet. Verwenden Sie ihn, wenn Sie eine vollständig individuelle UI wünschen -- mit eigenen Schaltflächen, Layouts, Animationen und Branding -- während ThunderPhone alles im Hintergrund übernimmt.

## Wann Sie den Headless-Hook verwenden sollten

Die vorgefertigte Komponente `ThunderPhoneWidget` deckt die meisten Anwendungsfälle ab. Verwenden Sie den Headless-Hook jedoch, wenn Sie Folgendes benötigen:

* Eine vollständig individuelle Anruf-UI, die zum Designsystem Ihrer App passt
* Audioreaktive Visualisierungen (Wellenformen, Kugeln, pulsierende Anzeigen), die von Audiopegeln in Echtzeit gesteuert werden
* Individuelle Anrufflüsse wie Formulare vor dem Anruf, Umfragen nach dem Anruf oder Inline-Chat neben der Sprachfunktion
* Integration in eine bestehende Komponentenbibliothek (Material UI, Chakra, Radix usw.)

***

## Installation

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

<Note>
  Der Headless-Hook erfordert **nicht** den Import von `@thunderphone/widget/style.css`, da Sie Ihre eigene UI bereitstellen. Sie müssen jedoch weiterhin dasselbe Paket `@thunderphone/widget` installieren.
</Note>

***

## Grundlegende Verwendung

```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>
  **Sie müssen `phone.audio` an einer Stelle in Ihrem Komponentenbaum rendern.** Es ist ein unsichtbares React-Element, das die zugrunde liegende Audioverbindung verwaltet. Wenn Sie es weglassen, wird kein Audio wiedergegeben und die Sitzung funktioniert nicht.
</Warning>

***

## Optionen

Übergeben Sie diese Optionen über `UseThunderPhoneOptions` an `useThunderPhone`:

| Option           | Typ                 | Erforderlich | Standard                            | Beschreibung                                                                                                                                                                    |
| ---------------- | ------------------- | ------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `publishableKey` | `string`            | Ja           | --                                  | Veröffentlichbarer API-Schlüssel (`pk_live_...`). Der Sprachagent wird automatisch anhand der Widget-Konfiguration des Schlüssels bestimmt.                                     |
| `apiBase`        | `string`            | Nein         | `'https://api.thunderphone.com/v1'` | Überschreibung der API-Basis-URL.                                                                                                                                               |
| `language`       | `string`            | Nein         | --                                  | Sprachüberschreibung pro Sitzung -- ein Sprachcode oder Gebietsschema wie `en`, `es` oder `fr-FR`. Wenn keine Angabe erfolgt, gilt die konfigurierte Sprache des Sprachagenten. |
| `voice`          | `string`            | Nein         | --                                  | Stimmenüberschreibung pro Sitzung -- ein Stimmenname wie `maria`. Wenn keine Angabe erfolgt, gilt die konfigurierte Stimme des Sprachagenten.                                   |
| `context`        | `string`            | Nein         | --                                  | Faktischer Seiten- oder Website-Kontext pro Sitzung, der an den Sprachagenten übergeben wird. Serverseitig auf 12.000 Zeichen gekürzt.                                          |
| `onConnect`      | `() => void`        | Nein         | --                                  | Wird aufgerufen, wenn die Sprachsitzung verbunden wird.                                                                                                                         |
| `onDisconnect`   | `() => void`        | Nein         | --                                  | Wird aufgerufen, wenn die Sitzung endet.                                                                                                                                        |
| `onError`        | `(error) => void`   | Nein         | --                                  | Wird bei Fehlern aufgerufen. Error verfügt über die Felder `error` (Code) und `message`.                                                                                        |
| `ringtone`       | `boolean \| string` | Nein         | `false`                             | Einen Klingelton während des Verbindungsaufbaus abspielen. `true` für den Standardklingelton oder eine URL-Zeichenfolge für eigenes Audio.                                      |

<Note>
  Der Hook ist Headless: Er akzeptiert **nicht** die Darstellungs-Props von `ThunderPhoneWidget` (`theme`, `primaryColor`, `title`, `position`, `className`). Die Übergabe dieser Props führt zu einem TypeScript-Fehler -- die Darstellung erstellen Sie vollständig selbst.
</Note>

***

## Rückgabewert

Der Hook gibt ein `UseThunderPhoneReturn`-Objekt zurück:

| Eigenschaft     | Typ                                                                  | Beschreibung                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| --------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state`         | `'idle' \| 'connecting' \| 'connected' \| 'disconnected' \| 'error'` | Aktueller Verbindungsstatus.                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `connect`       | `() => void`                                                         | Startet eine Sprachsitzung.                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `disconnect`    | `() => void`                                                         | Beendet die aktuelle Sitzung.                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `toggleMute`    | `() => void`                                                         | Schaltet die Mikrofonstummschaltung ein oder aus.                                                                                                                                                                                                                                                                                                                                                                                                        |
| `isMuted`       | `boolean`                                                            | Gibt an, ob das Mikrofon derzeit stummgeschaltet ist.                                                                                                                                                                                                                                                                                                                                                                                                    |
| `error`         | `string \| undefined`                                                | Fehlermeldung, wenn der Status `'error'` ist.                                                                                                                                                                                                                                                                                                                                                                                                            |
| `agentName`     | `string \| undefined`                                                | Anzeigename des verbundenen Agenten.                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `audioLevel`    | `number`                                                             | **Veraltet -- immer `0`.** Ein statischer Platzhalter für Abwärtskompatibilität; er wird nie aktualisiert. Lesen Sie stattdessen `audioLevelRef.current`.                                                                                                                                                                                                                                                                                                |
| `audioLevelRef` | `React.RefObject<number>`                                            | Eine veränderbare Ref, die den Audiopegel in Echtzeit (0--1) enthält -- den höheren Wert aus der Stimme des Agenten und dem Mikrofon des Besuchers -- und bei jedem Animationsframe außerhalb des React-Renderzyklus aktualisiert wird. Lesen Sie `audioLevelRef.current` innerhalb von `requestAnimationFrame`-Schleifen für flüssige Animationen ohne Ruckler, oder fragen Sie den Wert in einem Intervall ab, wenn Sie ihn im React-Status benötigen. |
| `audio`         | `ReactNode`                                                          | Unsichtbares Element, das die Audioverbindung verarbeitet -- **muss gerendert werden**.                                                                                                                                                                                                                                                                                                                                                                  |

***

## Audioreaktive UI

Die Ref `audioLevelRef` liefert Ihnen Audiopegel mit Bildrate, ohne React-Neurenderings auszulösen. Dadurch eignet sie sich ideal für flüssige Wellenformvisualisierungen, pulsierende Kugeln oder jede Animation, die an die Unterhaltung gekoppelt ist. Der Pegel entspricht jeweils der lauteren Quelle: der Stimme des Agenten oder dem Mikrofon des Besuchers.

### Wellenformbeispiel

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

### Beispiel für eine pulsierende Kugel

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

### Beispiel für eine Sprechindikator

Für eine von React gerenderte UI, die sich mit der Lautstärke ändert – etwa ein schwellenwertbasierter Badge für „spricht“ –, lesen Sie `audioLevelRef.current` in einem Intervall aus und speichern Sie das Ergebnis im State:

```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>
  Lesen Sie Pegel immer aus `audioLevelRef.current`. Die Zahl `audioLevel` im Rückgabeobjekt ist **veraltet und immer `0`** – jede darauf basierende Logik liest stillschweigend null.
</Warning>

***

## Zustandsautomat

Die Eigenschaft `state` durchläuft diesen Lebenszyklus:

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

| Status         | Beschreibung                                                                                                                                                                                                              |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idle`         | Keine aktive Sitzung. Bereit zum Aufruf von `connect()`.                                                                                                                                                                  |
| `connecting`   | Die Sitzung wird hergestellt. Deaktivieren Sie während dieses Status die Anruftaste.                                                                                                                                      |
| `connected`    | Die Sprachsitzung ist aktiv. Der Benutzer spricht mit dem Agenten.                                                                                                                                                        |
| `disconnected` | Die Sitzung wurde ordnungsgemäß beendet. Wechselt nach 1,5 Sekunden automatisch zurück zu `idle`.                                                                                                                         |
| `error`        | Ein Fehler ist aufgetreten. Prüfen Sie `phone.error` auf die Meldung. Der Status wird **nicht** automatisch zurückgesetzt -- ein erneuter Aufruf von `connect()` startet einen neuen Versuch und setzt den Fehler zurück. |

***

## Beispiele

### Mit Stummschaltfunktion

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

### Mit Klingelton

Spielen Sie während des Verbindungsaufbaus einen Klingelton ab, um einen Telefonanruf zu simulieren:

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

Der Klingelton wird während des Status `connecting` wiederholt und ausgeblendet, wenn der Agent verbunden ist. Übergeben Sie `true` für den integrierten Standardklingelton oder einen URL-String, um Ihre eigene Audiodatei zu verwenden.

### Mit Ereignis-Callbacks

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

### Vollständig benutzerdefinierte UI

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

***

## Tipps

<AccordionGroup>
  <Accordion title="phone.audio immer rendern">
    Das Element `phone.audio` ist unsichtbar, aber erforderlich. Platzieren Sie es an einer beliebigen Stelle in Ihrem JSX -- es rendert kein sichtbares DOM, verwaltet jedoch intern die WebRTC-Audioverbindung.
  </Accordion>

  <Accordion title="Die Schaltfläche während der Verbindung deaktivieren">
    Der Status `connecting` kann 1–3 Sekunden dauern. Deaktivieren Sie während dieses Status die Anrufschaltfläche, um doppelte Verbindungsversuche zu verhindern.
  </Accordion>

  <Accordion title="Den Fehlerstatus angemessen behandeln">
    Wenn der Status `error` ist, zeigen Sie dem Benutzer `phone.error` an und lassen Sie Ihre Anrufschaltfläche aktiviert. Der Hook verlässt den Status `error` nicht selbstständig -- ein erneuter Aufruf von `connect()` startet einen neuen Versuch und löscht den vorherigen Fehler.
  </Accordion>

  <Accordion title="Callbacks für Seiteneffekte verwenden">
    Die Callbacks `onConnect`, `onDisconnect` und `onError` eignen sich ideal für Analysen, Protokollierung oder zum Auslösen anderer Anwendungslogik, ohne den Status abzufragen.
  </Accordion>

  <Accordion title="Audiopegel aus audioLevelRef lesen">
    `audioLevelRef` ist die einzige Live-Quelle für Audiopegel. Lesen Sie `audioLevelRef.current` innerhalb von `requestAnimationFrame` für flüssige Animationen wie Wellenformen aus (das Lesen einer Ref verursacht keine erneuten Renderings), oder fragen Sie sie in einem Intervall ab und speichern Sie das Ergebnis im Status für von React gerenderte UI. Die Zahl `audioLevel` ist veraltet und immer `0` -- bauen Sie keine Logik darauf auf.
  </Accordion>
</AccordionGroup>
