---
title: "Hook Isiyo na Kiolesura"
description: "Unda UI ya sauti iliyobinafsishwa kikamilifu kwa hook ya React useThunderPhone"
---

Hook ya `useThunderPhone` hukupa udhibiti kamili wa kiolesura cha mtumiaji huku ThunderPhone ikisimamia kipindi cha sauti, uelekezaji wa sauti na hali ya muunganisho. Itumie unapohitaji UI iliyobinafsishwa kikamilifu -- vitufe, mipangilio, uhuishaji na utambulisho wako wa chapa -- huku ThunderPhone ikishughulikia kila kitu chinichini.

## Wakati wa Kutumia Hook Isiyo na Kiolesura

Kijenzi kilichojengwa awali cha `ThunderPhoneWidget` kinatosheleza hali nyingi za matumizi, lakini tumia hook isiyo na kiolesura unapohitaji:

- UI ya simu iliyobinafsishwa kikamilifu inayolingana na mfumo wa usanifu wa app yako
- Vielelezo vinavyoitikia sauti (mawimbi ya sauti, duara, viashiria vinavyodunda) vinavyoendeshwa na viwango vya sauti vya wakati halisi
- Mitiririko maalum ya simu kama vile fomu za kabla ya simu, tafiti za baada ya simu, au gumzo la ndani sambamba na sauti
- Ujumuishaji katika maktaba ya vijenzi iliyopo (Material UI, Chakra, Radix, n.k.)

---

## Usakinishaji

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

<Note>
  Hook isiyo na kiolesura **haihitaji** kuagiza `@thunderphone/widget/style.css` kwa kuwa unatoa UI yako mwenyewe. Hata hivyo, bado lazima usakinishe kifurushi hichohicho cha `@thunderphone/widget`.
</Note>

---

## Matumizi ya Msingi

```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>
  **Lazima uonyeshe `phone.audio` mahali fulani katika mti wa vijenzi vyako.** Ni elementi ya React isiyoonekana inayosimamia muunganisho wa sauti wa msingi. Ukiiacha, hakuna sauti itakayochezwa na kipindi hakitafanya kazi.
</Warning>

---

## Chaguo

Pitisha chaguo hizi kwa `useThunderPhone` kupitia `UseThunderPhoneOptions`:

| Chaguo | Aina | Inahitajika | Chaguomsingi | Maelezo |
|--------|------|----------|---------|-------------|
| `publishableKey` | `string` | Ndiyo | -- | Ufunguo wa API unaoweza kuchapishwa (`pk_live_...`). Ejenti hutambuliwa kiotomatiki kutoka kwenye usanidi wa wijeti wa ufunguo. |
| `apiBase` | `string` | Hapana | `'https://api.thunderphone.com/v1'` | Badilisha URL ya msingi ya API. |
| `language` | `string` | Hapana | -- | Badilisha lugha kwa kila kipindi -- msimbo wa lugha au eneo kama `en`, `es`, au `fr-FR`. Isipowekwa, lugha iliyosanidiwa ya ejenti hutumika. |
| `voice` | `string` | Hapana | -- | Badilisha sauti kwa kila kipindi -- jina la sauti kama `maria`. Isipowekwa, sauti iliyosanidiwa ya ejenti hutumika. |
| `context` | `string` | Hapana | -- | Muktadha wa ukweli wa ukurasa au tovuti kwa kila kipindi unaopitishwa kwa ejenti. Hukatwa upande wa seva hadi herufi 12,000. |
| `onConnect` | `() => void` | Hapana | -- | Huitwa kipindi cha sauti kinapounganishwa. |
| `onDisconnect` | `() => void` | Hapana | -- | Huitwa kipindi kinapoisha. |
| `onError` | `(error) => void` | Hapana | -- | Huitwa kunapotokea hitilafu. Hitilafu ina sehemu za `error` (msimbo) na `message`. |
| `ringtone` | `boolean \| string` | Hapana | `false` | Cheza mlio wa simu wakati wa kuunganisha. `true` kwa mlio chaguomsingi wa simu, au mfuatano wa URL kwa sauti maalum. |

<Note>
  Hook haina kiolesura: **haikubali** prop za mwonekano za `ThunderPhoneWidget` (`theme`, `primaryColor`, `title`, `position`, `className`). Kuzipitisha ni hitilafu ya TypeScript -- mwonekano ni wako kabisa kuujenga.
</Note>

---

## Thamani ya Kurejesha

Hook hurejesha objekti ya `UseThunderPhoneReturn`:

| Sifa | Aina | Maelezo |
|----------|------|-------------|
| `state` | `'idle' \| 'connecting' \| 'connected' \| 'disconnected' \| 'error'` | Hali ya sasa ya muunganisho. |
| `connect` | `() => void` | Anzisha kipindi cha sauti. |
| `disconnect` | `() => void` | Maliza kipindi cha sasa. |
| `toggleMute` | `() => void` | Washa au zima kunyamazisha maikrofoni. |
| `isMuted` | `boolean` | Ikiwa maikrofoni imenyamazishwa kwa sasa. |
| `error` | `string \| undefined` | Ujumbe wa hitilafu wakati hali ni `'error'`. |
| `agentName` | `string \| undefined` | Jina la kuonyesha la ejenti aliyeunganishwa. |
| `audioLevel` | `number` | **Imeondolewa -- daima `0`.** Kishikilia nafasi kisichobadilika kinachohifadhiwa kwa uoanifu wa nyuma; hakisasishwi kamwe. Soma `audioLevelRef.current` badala yake. |
| `audioLevelRef` | `React.RefObject<number>` | Ref inayoweza kubadilishwa iliyo na kiwango cha sauti cha wakati halisi (0--1) -- kilicho juu zaidi kati ya sauti ya ejenti na maikrofoni ya mgeni -- inayosasishwa katika kila fremu ya uhuishaji, nje ya mzunguko wa render wa React. Soma `audioLevelRef.current` ndani ya mizunguko ya `requestAnimationFrame` kwa uhuishaji laini usio na kukwama, au ichukue kwa vipindi unapohitaji thamani katika state ya React. |
| `audio` | `ReactNode` | Kipengele kisichoonekana kinachoshughulikia muunganisho wa sauti -- **lazima kirendwe**. |

---

## Kiolesura Kinachoathiriwa na Sauti

ref ya `audioLevelRef` hukupa viwango vya sauti kwa kasi ya fremu bila kusababisha React kujitoa upya, hivyo ni bora kwa kuendesha vielelezo laini vya muundo wa mawimbi, duara zinazodunda, au uhuishaji wowote unaohusishwa na mazungumzo. Kiwango huonyesha sauti iliyo kubwa zaidi: sauti ya ejenti au maikrofoni ya mgeni.

### Mfano wa Muundo wa Mawimbi

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

### Mfano wa Duara Linalodunda

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

### Mfano wa Kiashiria cha Kuzungumza

Kwa UI inayotolewa na React ambayo hubadilika kulingana na sauti -- kama beji ya "inayozungumza" inayotegemea kizingiti -- chukua sampuli ya `audioLevelRef.current` kwa vipindi na uhifadhi matokeo katika state:

```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>
  Soma viwango kila wakati kutoka `audioLevelRef.current`. Nambari ya `audioLevel` kwenye kitu cha kurejeshwa **imepitwa na wakati na daima ni `0`** -- mantiki yoyote inayojengwa juu yake itasoma sifuri kimya kimya.
</Warning>

---

## Mashine ya Hali

Sifa ya `state` hufuata mzunguko huu wa maisha:

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

| Hali | Maelezo |
|-------|-------------|
| `idle` | Hakuna kipindi kinachoendelea. Tayari kuita `connect()`. |
| `connecting` | Kipindi kinaanzishwa. Zima kitufe cha kupiga simu wakati wa hali hii. |
| `connected` | Kipindi cha sauti kinaendelea. Mtumiaji anazungumza na ejenti. |
| `disconnected` | Kipindi kimeisha vizuri. Hubadilika kurudi kuwa `idle` kiotomatiki baada ya sekunde 1.5. |
| `error` | Kitu kimeharibika. Angalia `phone.error` kwa ujumbe. Hali hii **haijifuti** yenyewe -- kuita `connect()` tena huanzisha jaribio jipya na kuweka upya hitilafu. |

---

## Mifano

### Kwa Udhibiti wa Kunyamazisha

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

### Kwa Mlio wa Simu

Cheza sauti ya mlio wa simu wakati wa kuunganisha ili kuiga simu:

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

Mlio wa simu hurudiwa katika hali ya `connecting` na hupungua sauti ejenti anapounganishwa. Pitisha `true` kwa mlio chaguomsingi uliojengewa ndani, au mfuatano wa URL ili kutumia faili yako ya sauti.

### Kwa Callback za Matukio

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

### Kiolesura Maalum Kabisa

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

---

## Vidokezo

<AccordionGroup>
  <Accordion title="Render phone.audio kila wakati">
    Kipengele cha `phone.audio` hakionekani lakini kinahitajika. Kiweke mahali popote katika JSX yako -- hakirender DOM inayoonekana lakini kinasimamia muunganisho wa sauti wa WebRTC ndani kwa ndani.
  </Accordion>

  <Accordion title="Zima kitufe wakati wa kuunganisha">
    Hali ya `connecting` inaweza kudumu sekunde 1-3. Zima kitufe cha simu wakati wa hali hii ili kuzuia majaribio ya muunganisho yanayojirudia.
  </Accordion>

  <Accordion title="Shughulikia hali ya hitilafu kwa ustadi">
    Hali inapokuwa `error`, onyesha `phone.error` kwa mtumiaji na uache kitufe chako cha simu kikiwa kimewashwa. Hook haitoki kwenye hali ya `error` yenyewe -- kuita `connect()` tena huanzisha jaribio jipya na kufuta hitilafu ya awali.
  </Accordion>

  <Accordion title="Tumia callbacks kwa athari za kando">
    Callbacks za `onConnect`, `onDisconnect`, na `onError` zinafaa kwa uchanganuzi, uandishi wa kumbukumbu, au kuanzisha mantiki nyingine ya programu bila kufuatilia hali mara kwa mara.
  </Accordion>

  <Accordion title="Soma viwango vya sauti kutoka audioLevelRef">
    `audioLevelRef` ndicho chanzo pekee cha viwango vya sauti vya moja kwa moja. Soma `audioLevelRef.current` ndani ya `requestAnimationFrame` kwa uhuishaji laini kama maumbo ya mawimbi (kusoma ref hakusababishi render upya), au ichukue sampuli kwa vipindi na uhifadhi matokeo katika state kwa kiolesura kinachorendwa na React. Nambari ya `audioLevel` imepitwa na wakati na daima ni `0` -- usijenge mantiki juu yake.
  </Accordion>
</AccordionGroup>
