---
title: "Headless Hook"
description: "Loo täielikult kohandatud häälkasutajaliides useThunderPhone Reacti hooki abil"
---

`useThunderPhone`-hook annab sulle täieliku kontrolli kasutajaliidese üle, samal ajal kui ThunderPhone haldab häälesessiooni, heli suunamist ja ühenduse olekut. Kasuta seda siis, kui soovid täielikult kohandatud kasutajaliidest — oma nuppe, paigutusi, animatsioone ja brändingut — ning ThunderPhone haldab kõike taustal.

## Millal kasutada headless-hooki

Valmis `ThunderPhoneWidget`-komponent katab enamiku kasutusjuhtudest, kuid kasuta headless-hooki, kui vajad järgmist:

- Täielikult kohandatud kõneliidest, mis sobib sinu rakenduse disainisüsteemiga
- Reaalajas helitasemetest juhitud helireaktiivseid visualiseeringuid (lainekujud, sfäärid, pulseerivad indikaatorid)
- Kohandatud kõnevooge, näiteks kõneeelseid vorme, kõnejärgseid küsitlusi või häälega koos kasutatavat tekstivestlust
- Integreerimist olemasolevasse komponenditeeki (Material UI, Chakra, Radix jne)

---

## Paigaldamine

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

<Note>
  Headless-hook ei nõua `@thunderphone/widget/style.css` importimist, kuna lood ise oma kasutajaliidese. Siiski pead paigaldama sama paketi `@thunderphone/widget`.
</Note>

---

## Põhikasutus

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function CustomCallButton() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })

  const handleClick = () => {
    if (phone.state === 'connected') {
      phone.disconnect()
    } else {
      phone.connect()
    }
  }

  return (
    <>
      <button onClick={handleClick} disabled={phone.state === 'connecting'}>
        {phone.state === 'connecting'
          ? 'Connecting...'
          : phone.state === 'connected'
            ? 'End call'
            : 'Start call'}
      </button>
      {phone.audio}
    </>
  )
}
```

<Warning>
  **Pead renderdama `phone.audio` kusagil oma komponendipuus.** See on nähtamatu Reacti element, mis haldab aluseks olevat heliühendust. Kui jätad selle välja, ei esitata heli ja seanss ei tööta.
</Warning>

---

## Valikud

Anna need valikud `useThunderPhone`-ile `UseThunderPhoneOptions` kaudu:

| Valik | Tüüp | Kohustuslik | Vaikeväärtus | Kirjeldus |
|--------|------|----------|---------|-------------|
| `publishableKey` | `string` | Jah | -- | Avalik API-võti (`pk_live_...`). Häälagent määratakse automaatselt võtme vidina konfiguratsiooni alusel. |
| `apiBase` | `string` | Ei | `'https://api.thunderphone.com/v1'` | API baas-URL-i alistamine. |
| `language` | `string` | Ei | -- | Seansipõhine keele alistamine — keelekood või lokaat, näiteks `en`, `es` või `fr-FR`. Kui see on määramata, kasutatakse häälagendi seadistatud keelt. |
| `voice` | `string` | Ei | -- | Seansipõhine hääle alistamine — hääle nimi, näiteks `maria`. Kui see on määramata, kasutatakse häälagendi seadistatud häält. |
| `context` | `string` | Ei | -- | Häälagendile edastatav seansipõhine faktiline lehe või saidi kontekst. Serveris kärbitakse 12 000 tähemärgini. |
| `onConnect` | `() => void` | Ei | -- | Kutsutakse välja, kui häälesessioon ühendub. |
| `onDisconnect` | `() => void` | Ei | -- | Kutsutakse välja, kui seanss lõpeb. |
| `onError` | `(error) => void` | Ei | -- | Kutsutakse välja vea korral. Vea väljad on `error` (kood) ja `message`. |
| `ringtone` | `boolean \| string` | Ei | `false` | Esita ühenduse loomise ajal helinat. Vaikehelina jaoks kasuta `true` või kohandatud heli jaoks URL-i stringi. |

<Note>
  Hook on ilma kasutajaliideseta: see **ei** aktsepteeri `ThunderPhoneWidget` välimuse prope (`theme`, `primaryColor`, `title`, `position`, `className`). Nende edastamine põhjustab TypeScripti vea — esitluskihi loomine on täielikult sinu ülesanne.
</Note>

---

## Tagastusväärtus

Hook tagastab objekti `UseThunderPhoneReturn`:

| Omadus | Tüüp | Kirjeldus |
|----------|------|-------------|
| `state` | `'idle' \| 'connecting' \| 'connected' \| 'disconnected' \| 'error'` | Praegune ühenduse olek. |
| `connect` | `() => void` | Käivita häälseanss. |
| `disconnect` | `() => void` | Lõpeta praegune seanss. |
| `toggleMute` | `() => void` | Lülita mikrofoni vaigistus sisse või välja. |
| `isMuted` | `boolean` | Kas mikrofon on praegu vaigistatud. |
| `error` | `string \| undefined` | Veateade, kui olek on `'error'`. |
| `agentName` | `string \| undefined` | Ühendatud agendi kuvatav nimi. |
| `audioLevel` | `number` | **Aegunud -- alati `0`.** Staatiline kohatäide, mis on säilitatud tagasiühilduvuse tagamiseks; seda ei värskendata kunagi. Loe selle asemel `audioLevelRef.current`. |
| `audioLevelRef` | `React.RefObject<number>` | Muudetav ref, mis sisaldab reaalajas helitaset (0--1) -- agendi hääle ja külastaja mikrofoni valjemat taset -- ning mida värskendatakse igal animatsioonikaadril väljaspool Reacti renderdamistsüklit. Sujuvate, tõrgeteta animatsioonide jaoks loe `audioLevelRef.current` `requestAnimationFrame`-i tsüklites või võta väärtusest proov intervalliga, kui vajad seda Reacti olekus. |
| `audio` | `ReactNode` | Nähtamatu element, mis haldab heliühendust -- **see tuleb renderdada**. |

---

## Helile reageeriv kasutajaliides

Ref `audioLevelRef` annab sulle kaadrisagedusega helitasemed ilma Reacti uuesti renderdamist käivitamata, mistõttu sobib see ideaalselt sujuvate lainekuju visualiseeringute, pulseerivate kerade või mis tahes vestlusega seotud animatsioonide juhtimiseks. Tase kajastab seda, kumb on valjem: agendi hääl või külastaja mikrofon.

### Lainekuju näide

```tsx
import { useRef, useEffect } from 'react'
import { useThunderPhone } from '@thunderphone/widget'

function WaveformCall() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })
  const canvasRef = useRef<HTMLCanvasElement>(null)

  useEffect(() => {
    if (phone.state !== 'connected') return
    const canvas = canvasRef.current
    if (!canvas) return
    const ctx = canvas.getContext('2d')!

    let animId: number
    const draw = () => {
      const level = phone.audioLevelRef.current ?? 0
      ctx.clearRect(0, 0, canvas.width, canvas.height)

      // Draw bars that react to audio level
      const barCount = 24
      const barWidth = canvas.width / barCount
      for (let i = 0; i < barCount; i++) {
        const distance = Math.abs(i - barCount / 2) / (barCount / 2)
        const height = level * canvas.height * (1 - distance * 0.6)
        const y = (canvas.height - height) / 2
        ctx.fillStyle = '#0ea5e9'
        ctx.fillRect(i * barWidth + 1, y, barWidth - 2, height)
      }

      animId = requestAnimationFrame(draw)
    }
    animId = requestAnimationFrame(draw)
    return () => cancelAnimationFrame(animId)
  }, [phone.state, phone.audioLevelRef])

  return (
    <div>
      {phone.state === 'connected' && (
        <canvas ref={canvasRef} width={240} height={80} />
      )}
      <button
        onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
        disabled={phone.state === 'connecting'}
      >
        {phone.state === 'connected' ? 'End call' : 'Start call'}
      </button>
      {phone.audio}
    </div>
  )
}
```

### Pulseeriva kera näide

```tsx
import { useRef, useEffect } from 'react'
import { useThunderPhone } from '@thunderphone/widget'

function PulsingOrb() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })
  const orbRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (phone.state !== 'connected') return
    let animId: number
    const animate = () => {
      const level = phone.audioLevelRef.current ?? 0
      if (orbRef.current) {
        const scale = 1 + level * 0.5
        orbRef.current.style.transform = `scale(${scale})`
        orbRef.current.style.opacity = `${0.6 + level * 0.4}`
      }
      animId = requestAnimationFrame(animate)
    }
    animId = requestAnimationFrame(animate)
    return () => cancelAnimationFrame(animId)
  }, [phone.state, phone.audioLevelRef])

  return (
    <div style={{ textAlign: 'center' }}>
      <div
        ref={orbRef}
        style={{
          width: 80,
          height: 80,
          borderRadius: '50%',
          background: '#0ea5e9',
          margin: '20px auto',
          transition: 'transform 0.05s ease-out',
        }}
      />
      <button
        onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
        disabled={phone.state === 'connecting'}
      >
        {phone.state === 'connected' ? 'End call' : 'Call'}
      </button>
      {phone.audio}
    </div>
  )
}
```

### Rääkimise indikaatori näide

Reactiga renderdatava kasutajaliidese jaoks, mis muutub helitugevuse järgi — näiteks lävendipõhine „rääkimise” märk — võta `audioLevelRef.current` väärtusest regulaarsete ajavahemike järel näidiseid ja salvesta tulemus olekusse:

```tsx
import { useEffect, useState } from 'react'
import { useThunderPhone } from '@thunderphone/widget'

function SpeakingBadge() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })
  const [speaking, setSpeaking] = useState(false)

  useEffect(() => {
    if (phone.state !== 'connected') {
      setSpeaking(false)
      return
    }
    const interval = setInterval(() => {
      setSpeaking((phone.audioLevelRef.current ?? 0) > 0.1)
    }, 100)
    return () => clearInterval(interval)
  }, [phone.state, phone.audioLevelRef])

  return (
    <div>
      {phone.state === 'connected' && (
        <span>{speaking ? 'Speaking' : 'Listening'}</span>
      )}
      <button onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}>
        {phone.state === 'connected' ? 'End call' : 'Start call'}
      </button>
      {phone.audio}
    </div>
  )
}
```

<Warning>
  Loe tasemeid alati väärtusest `audioLevelRef.current`. Tagastusobjekti arv `audioLevel` on **aegunud ja alati `0`** — sellele tuginev loogika loeb märkamatult nulli.
</Warning>

---

## Olekuautomaat

Atribuut `state` järgib seda elutsüklit:

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

| Olek | Kirjeldus |
|-------|-------------|
| `idle` | Aktiivset seanssi pole. Valmis kutsuma `connect()`. |
| `connecting` | Seanssi luuakse. Keela selles olekus kõnenupud. |
| `connected` | Häälseanss on aktiivne. Kasutaja räägib agendiga. |
| `disconnected` | Seanss lõppes korrektselt. Naaseb 1,5 sekundi pärast automaatselt olekusse `idle`. |
| `error` | Midagi läks valesti. Kontrolli teadet `phone.error` kaudu. Olek **ei** tühjene iseenesest -- `connect()` uuesti kutsumine alustab uut katset ja lähtestab vea. |

---

## Näited

### Vaigistuse juhtimisega

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function CallWithMute() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })

  return (
    <div>
      {phone.state === 'connected' && (
        <div>
          <p>Talking to {phone.agentName ?? 'Agent'}</p>
          <button onClick={phone.toggleMute}>
            {phone.isMuted ? 'Unmute' : 'Mute'}
          </button>
          <button onClick={phone.disconnect}>End call</button>
        </div>
      )}

      {phone.state !== 'connected' && (
        <button
          onClick={phone.connect}
          disabled={phone.state === 'connecting'}
        >
          {phone.state === 'connecting' ? 'Connecting...' : 'Call support'}
        </button>
      )}

      {phone.state === 'error' && (
        <p style={{ color: 'red' }}>{phone.error}</p>
      )}

      {phone.audio}
    </div>
  )
}
```

### Helinaga

Esita ühenduse loomise ajal helinat, et jäljendada telefonikõnet:

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function PhoneCallButton() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
    ringtone: true, // or a custom URL: 'https://example.com/ringtone.mp3'
  })

  return (
    <>
      <button
        onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
        disabled={phone.state === 'connecting'}
      >
        {phone.state === 'connecting'
          ? 'Ringing...'
          : phone.state === 'connected'
            ? 'Hang up'
            : 'Call'}
      </button>
      {phone.audio}
    </>
  )
}
```

Helin kordub oleku `connecting` ajal ja vaibub, kui häälagent ühendub. Sisseehitatud vaikehelina kasutamiseks edasta `true` või oma helifaili kasutamiseks URL-string.

### Sündmuste tagasihelistustega

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function TrackedCallButton() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
    onConnect: () => {
      analytics.track('call_started')
    },
    onDisconnect: () => {
      analytics.track('call_ended')
    },
    onError: (error) => {
      analytics.track('call_error', { code: error.error, message: error.message })
    },
  })

  return (
    <>
      <button
        onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
        disabled={phone.state === 'connecting'}
      >
        {phone.state === 'connected' ? 'Hang up' : 'Talk to AI'}
      </button>
      {phone.audio}
    </>
  )
}
```

### Täielikult kohandatud kasutajaliides

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function FullCustomUI() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })

  return (
    <div className="call-panel">
      <div className="call-status">
        {phone.state === 'idle' && <span>Ready</span>}
        {phone.state === 'connecting' && <span className="pulse">Connecting...</span>}
        {phone.state === 'connected' && (
          <span>On call with {phone.agentName}</span>
        )}
        {phone.state === 'error' && <span className="error">{phone.error}</span>}
      </div>

      <div className="call-controls">
        {phone.state === 'connected' ? (
          <>
            <button className="mute-btn" onClick={phone.toggleMute}>
              {phone.isMuted ? 'Unmute' : 'Mute'}
            </button>
            <button className="end-btn" onClick={phone.disconnect}>
              End
            </button>
          </>
        ) : (
          <button
            className="start-btn"
            onClick={phone.connect}
            disabled={phone.state === 'connecting'}
          >
            Start call
          </button>
        )}
      </div>

      {/* Required -- handles audio under the hood */}
      {phone.audio}
    </div>
  )
}
```

---

## Näpunäited

<AccordionGroup>
  <Accordion title="Renderda alati phone.audio">
    Element `phone.audio` on nähtamatu, kuid vajalik. Paiguta see JSX-is ükskõik kuhu -- see ei renderda nähtavat DOM-i, kuid haldab WebRTC-audioühendust sisemiselt.
  </Accordion>

  <Accordion title="Keela nupp ühenduse loomise ajal">
    Olek `connecting` võib kesta 1–3 sekundit. Keela selles olekus helistamisnupp, et vältida ühenduse loomise korduskatseid.
  </Accordion>

  <Accordion title="Käsitle veaolekut sujuvalt">
    Kui olek on `error`, kuva kasutajale `phone.error` ja hoia helistamisnupp lubatuna. Hook ei lahku olekust `error` iseseisvalt -- `connect()` uuesti kutsumine alustab uut katset ja kustutab eelmise vea.
  </Accordion>

  <Accordion title="Kasuta kõrvaltoimete jaoks tagasihelistusi">
    Tagasihelistused `onConnect`, `onDisconnect` ja `onError` sobivad ideaalselt analüütikaks, logimiseks või muu rakendusloogika käivitamiseks ilma olekut küsitlemata.
  </Accordion>

  <Accordion title="Loe helitasemeid audioLevelRef-ist">
    `audioLevelRef` on ainus reaalajas helitaseme allikas. Sujuvate animatsioonide, näiteks lainekujude jaoks loe `audioLevelRef.current` seest `requestAnimationFrame` (ref-i lugemine ei põhjusta uuesti renderdamist) või võta sellest väärtus kindla intervalliga ning salvesta tulemus olekusse Reacti renderdatud kasutajaliidese jaoks. Arv `audioLevel` on aegunud ja alati `0` -- ära raja sellele loogikat.
  </Accordion>
</AccordionGroup>
