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

> Izveidojiet pilnībā pielāgotu balss lietotāja saskarni, izmantojot useThunderPhone React āķi

`useThunderPhone` āķis nodrošina pilnīgu kontroli pār lietotāja saskarni, kamēr ThunderPhone pārvalda balss sesiju, audio maršrutēšanu un savienojuma stāvokli. Izmantojiet to, ja vēlaties pilnībā pielāgotu lietotāja saskarni — savas pogas, izkārtojumus, animācijas un zīmolradi — kamēr ThunderPhone visu pārējo apstrādā fonā.

## Kad izmantot bezsaskarnes āķi

Iepriekš izveidotais `ThunderPhoneWidget` komponents aptver lielāko daļu lietošanas gadījumu, taču izmantojiet bezsaskarnes āķi, ja nepieciešams:

* Pilnībā pielāgots zvana lietotāja interfeiss, kas atbilst jūsu lietotnes dizaina sistēmai
* Uz audio reaģējošas vizualizācijas (viļņu formas, lodes, pulsējoši indikatori), ko nodrošina reāllaika audio līmeņi
* Pielāgotas zvanu plūsmas, piemēram, veidlapas pirms zvana, aptaujas pēc zvana vai iekļauta tērzēšana līdzās balsij
* Integrācija esošā komponentu bibliotēkā (Material UI, Chakra, Radix u. c.)

***

## Instalēšana

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

<Note>
  Bezsaskarnes āķim **nav** jāimportē `@thunderphone/widget/style.css`, jo jūs nodrošināt savu lietotāja saskarni. Tomēr joprojām jāinstalē tā pati `@thunderphone/widget` pakotne.
</Note>

***

## Pamata lietošana

```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>
  **Jums komponentu kokā kaut kur jāatveido `phone.audio`.** Tas ir neredzams React elements, kas pārvalda pamatā esošo audio savienojumu. Ja to neiekļausiet, audio netiks atskaņots un sesija nedarbosies.
</Warning>

***

## Opcijas

Nododiet šīs opcijas `useThunderPhone`, izmantojot `UseThunderPhoneOptions`:

| Opcija           | Tips                | Obligāta | Noklusējums                         | Apraksts                                                                                                                                                               |
| ---------------- | ------------------- | -------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `publishableKey` | `string`            | Jā       | --                                  | Publiskā API atslēga (`pk_live_...`). Balss aģents tiek automātiski noteikts pēc atslēgas logrīka konfigurācijas.                                                      |
| `apiBase`        | `string`            | Nē       | `'https://api.thunderphone.com/v1'` | API bāzes URL aizstāšana.                                                                                                                                              |
| `language`       | `string`            | Nē       | --                                  | Valodas aizstāšana katrai sesijai — valodas kods vai lokalizācija, piemēram, `en`, `es` vai `fr-FR`. Ja nav iestatīts, tiek izmantota balss aģenta konfigurētā valoda. |
| `voice`          | `string`            | Nē       | --                                  | Balss aizstāšana katrai sesijai — balss nosaukums, piemēram, `maria`. Ja nav iestatīts, tiek izmantota balss aģenta konfigurētā balss.                                 |
| `context`        | `string`            | Nē       | --                                  | Balss aģentam nodotais katras sesijas faktiskais lapas vai vietnes konteksts. Servera pusē saīsināts līdz 12 000 rakstzīmēm.                                           |
| `onConnect`      | `() => void`        | Nē       | --                                  | Tiek izsaukta, kad tiek izveidots savienojums ar balss sesiju.                                                                                                         |
| `onDisconnect`   | `() => void`        | Nē       | --                                  | Tiek izsaukta, kad sesija beidzas.                                                                                                                                     |
| `onError`        | `(error) => void`   | Nē       | --                                  | Tiek izsaukta kļūdu gadījumā. Kļūdai ir `error` (koda) un `message` lauki.                                                                                             |
| `ringtone`       | `boolean \| string` | Nē       | `false`                             | Atskaņot zvana signālu savienojuma izveides laikā. `true` noklusējuma zvana signālam vai URL virkne pielāgotam audio.                                                  |

<Note>
  Āķis ir bezsaskarnes: tas **nepieņem** `ThunderPhoneWidget` izskata rekvizītus (`theme`, `primaryColor`, `title`, `position`, `className`). To nodošana rada TypeScript kļūdu — prezentācija ir pilnībā jūsu ziņā.
</Note>

***

## Atgrieztā vērtība

Āķis atgriež `UseThunderPhoneReturn` objektu:

| Rekvizīts       | Tips                                                                 | Apraksts                                                                                                                                                                                                                                                                                                                                                                                       |
| --------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state`         | `'idle' \| 'connecting' \| 'connected' \| 'disconnected' \| 'error'` | Pašreizējais savienojuma stāvoklis.                                                                                                                                                                                                                                                                                                                                                            |
| `connect`       | `() => void`                                                         | Sāciet balss sesiju.                                                                                                                                                                                                                                                                                                                                                                           |
| `disconnect`    | `() => void`                                                         | Beidziet pašreizējo sesiju.                                                                                                                                                                                                                                                                                                                                                                    |
| `toggleMute`    | `() => void`                                                         | Ieslēdziet vai izslēdziet mikrofona skaņas izslēgšanu.                                                                                                                                                                                                                                                                                                                                         |
| `isMuted`       | `boolean`                                                            | Vai mikrofona skaņa pašlaik ir izslēgta.                                                                                                                                                                                                                                                                                                                                                       |
| `error`         | `string \| undefined`                                                | Kļūdas ziņojums, ja stāvoklis ir `'error'`.                                                                                                                                                                                                                                                                                                                                                    |
| `agentName`     | `string \| undefined`                                                | Pievienotā balss aģenta parādāmais nosaukums.                                                                                                                                                                                                                                                                                                                                                  |
| `audioLevel`    | `number`                                                             | **Novecojis -- vienmēr `0`.** Statisks vietturis, kas saglabāts atpakaļsaderībai; tas nekad netiek atjaunināts. Tā vietā nolasiet `audioLevelRef.current`.                                                                                                                                                                                                                                     |
| `audioLevelRef` | `React.RefObject<number>`                                            | Mainīga atsauce, kas satur reāllaika audio līmeni (0--1) — augstāko no aģenta balss un apmeklētāja mikrofona līmeņa — un tiek atjaunināta katrā animācijas kadrā ārpus React renderēšanas cikla. Lai iegūtu plūstošas animācijas bez aizturēm, nolasiet `audioLevelRef.current` `requestAnimationFrame` ciklos, vai pārbaudiet to noteiktā intervālā, kad vērtība nepieciešama React stāvoklī. |
| `audio`         | `ReactNode`                                                          | Neredzams elements, kas apstrādā audio savienojumu — **tas ir jārenderē**.                                                                                                                                                                                                                                                                                                                     |

***

## Uz audio reaģējošs lietotāja interfeiss

Atsauce `audioLevelRef` nodrošina audio līmeņus kadru nomaiņas ātrumā, neizraisot React pārzīmēšanu, tāpēc tā ir ideāli piemērota plūstošu viļņformas vizualizāciju, pulsējošu ložu vai jebkuras ar sarunu saistītas animācijas vadībai. Līmenis atspoguļo to, kas ir skaļāks: balss aģenta balss vai apmeklētāja mikrofons.

### Viļņformas piemērs

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

### Pulsējošas lodes piemērs

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

### Runāšanas indikatora piemērs

React renderētam lietotāja interfeisam, kas mainās atkarībā no skaļuma — piemēram, slieksnī balstītai “runāšanas” nozīmītei — intervālos nolasiet `audioLevelRef.current` un saglabājiet rezultātu stāvoklī:

```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>
  Vienmēr nolasiet līmeņus no `audioLevelRef.current`. Atgrieztā objekta skaitlis `audioLevel` ir **novecojis un vienmēr ir `0`** — jebkura uz tā balstīta loģika nemanāmi nolasīs nulli.
</Warning>

***

## Stāvokļu automāts

Rekvizīts `state` ievēro šādu dzīves ciklu:

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

| Stāvoklis      | Apraksts                                                                                                                                                                                              |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idle`         | Nav aktīvas sesijas. Gatavs izsaukt `connect()`.                                                                                                                                                      |
| `connecting`   | Tiek izveidota sesija. Šajā stāvoklī atspējojiet zvana pogu.                                                                                                                                          |
| `connected`    | Balss sesija ir aktīva. Lietotājs sarunājas ar balss aģentu.                                                                                                                                          |
| `disconnected` | Sesija ir veiksmīgi beigusies. Pēc 1,5 sekundēm stāvoklis automātiski pāriet atpakaļ uz `idle`.                                                                                                       |
| `error`        | Kaut kas nogāja greizi. Pārbaudiet `phone.error`, lai skatītu ziņojumu. Stāvoklis **netiek** notīrīts automātiski — atkārtoti izsaucot `connect()`, tiek sākts jauns mēģinājums un atiestatīta kļūda. |

***

## Piemēri

### Ar skaņas izslēgšanas vadību

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

### Ar zvansignālu

Savienojuma izveides laikā atskaņojiet zvana signālu, lai imitētu tālruņa zvanu:

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

Zvansignāls tiek atkārtots stāvoklī `connecting` un pakāpeniski apklust, kad pieslēdzas balss aģents. Ievadiet `true`, lai izmantotu iebūvēto noklusējuma zvansignālu, vai URL virkni, lai izmantotu savu audio failu.

### Ar notikumu atgriezeniskajām izsaukšanas funkcijām

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

### Pilnībā pielāgota lietotāja saskarne

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

***

## Padomi

<AccordionGroup>
  <Accordion title="Vienmēr renderējiet phone.audio">
    Elements `phone.audio` ir neredzams, taču obligāts. Novietojiet to jebkur JSX kodā -- tas nerenderē redzamu DOM, bet iekšēji pārvalda WebRTC audio savienojumu.
  </Accordion>

  <Accordion title="Savienojuma izveides laikā atspējojiet pogu">
    Stāvoklis `connecting` var ilgt 1–3 sekundes. Šajā stāvoklī atspējojiet zvana pogu, lai novērstu dublētus savienojuma mēģinājumus.
  </Accordion>

  <Accordion title="Korekti apstrādājiet kļūdas stāvokli">
    Kad stāvoklis ir `error`, parādiet lietotājam `phone.error` un atstājiet zvana pogu iespējotu. Hooks pats neiziet no stāvokļa `error` -- atkārtoti izsaucot `connect()`, tiek sākts jauns mēģinājums un notīrīta iepriekšējā kļūda.
  </Accordion>

  <Accordion title="Blakusiedarbībām izmantojiet atzvanes funkcijas">
    Atzvanes funkcijas `onConnect`, `onDisconnect` un `onError` ir piemērotas analītikai, žurnālu reģistrēšanai vai citas lietojumprogrammas loģikas aktivizēšanai, neaptaujājot stāvokli.
  </Accordion>

  <Accordion title="Lasiet audio līmeņus no audioLevelRef">
    `audioLevelRef` ir vienīgais reāllaika audio līmeņa avots. Lai iegūtu vienmērīgas animācijas, piemēram, viļņu formas, lasiet `audioLevelRef.current` funkcijā `requestAnimationFrame` (ref nolasīšana neizraisa atkārtotu renderēšanu), vai nolasiet to noteiktos intervālos un saglabājiet rezultātu stāvoklī React renderētajai lietotāja saskarnei. Skaitlis `audioLevel` ir novecojis un vienmēr ir `0` -- neveidojiet uz tā loģiku.
  </Accordion>
</AccordionGroup>
