ہیڈلیس ہک

useThunderPhone ہک آپ کو یوزر انٹرفیس پر مکمل کنٹرول دیتا ہے، جبکہ ThunderPhone وائس سیشن، آڈیو روٹنگ، اور کنکشن کی حالت کو منظم کرتا ہے۔ جب آپ مکمل طور پر حسبِ ضرورت UI چاہتے ہوں — اپنے بٹن، لے آؤٹس، اینیمیشنز، اور برانڈنگ — جبکہ ThunderPhone پسِ پردہ ہر چیز سنبھالے، تو اسے استعمال کریں۔

ہیڈلیس ہک کب استعمال کریں

پہلے سے تیار ThunderPhoneWidget کمپوننٹ زیادہ تر استعمال کے معاملات کو پورا کرتا ہے، لیکن جب آپ کو درج ذیل کی ضرورت ہو تو ہیڈلیس ہک استعمال کریں:


انسٹالیشن

npm install @thunderphone/widget

بنیادی استعمال

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

اختیارات

ان اختیارات کو UseThunderPhoneOptions کے ذریعے useThunderPhone کو دیں:

اختیارقسمضروریڈیفالٹتفصیل
publishableKeystringہاں--پبلش ایبل API کلید (pk_live_...)۔ ایجنٹ کلید کی widget کنفیگریشن سے خودکار طور پر متعین ہوتا ہے۔
apiBasestringنہیں'https://api.thunderphone.com/v1'API بیس URL کا اووررائیڈ۔
languagestringنہیں--فی سیشن زبان کا اووررائیڈ — زبان کا کوڈ یا لوکیل، جیسے en، es، یا fr-FR۔ جب یہ متعین نہ ہو، تو ایجنٹ کی کنفیگر کردہ زبان لاگو ہوتی ہے۔
voicestringنہیں--فی سیشن آواز کا اووررائیڈ — آواز کا نام، جیسے maria۔ جب یہ متعین نہ ہو، تو ایجنٹ کی کنفیگر کردہ آواز لاگو ہوتی ہے۔
contextstringنہیں--ایجنٹ کو دیا جانے والا فی سیشن حقائق پر مبنی صفحہ یا سائٹ کا سیاق۔ سرور کی طرف سے 12,000 حروف تک محدود کیا جاتا ہے۔
onConnect() => voidنہیں--وائس سیشن کے کنیکٹ ہونے پر کال کیا جاتا ہے۔
onDisconnect() => voidنہیں--سیشن ختم ہونے پر کال کیا جاتا ہے۔
onError(error) => voidنہیں--خرابیوں پر کال کیا جاتا ہے۔ خرابی میں error (کوڈ) اور message فیلڈز ہوتے ہیں۔
ringtoneboolean | stringنہیںfalseکنیکٹ ہونے کے دوران رنگ ٹون چلائیں۔ ڈیفالٹ رنگ ٹون کے لیے true، یا حسبِ ضرورت آڈیو کے لیے URL اسٹرنگ استعمال کریں۔

واپسی کی قدر

ہک ایک UseThunderPhoneReturn آبجیکٹ واپس کرتا ہے:

پراپرٹیقسموضاحت
state'idle' | 'connecting' | 'connected' | 'disconnected' | 'error'موجودہ کنکشن کی حالت۔
connect() => voidوائس سیشن شروع کریں۔
disconnect() => voidموجودہ سیشن ختم کریں۔
toggleMute() => voidمائیکروفون میوٹ کو آن یا آف کریں۔
isMutedbooleanآیا مائیکروفون اس وقت میوٹ ہے۔
errorstring | undefinedجب حالت 'error' ہو تو خرابی کا پیغام۔
agentNamestring | undefinedمنسلک ایجنٹ کا ڈسپلے نام۔
audioLevelnumberمتروک -- ہمیشہ 0۔ پسماندہ مطابقت کے لیے برقرار رکھا گیا ایک جامد پلیس ہولڈر؛ یہ کبھی اپ ڈیٹ نہیں ہوتا۔ اس کے بجائے audioLevelRef.current پڑھیں۔
audioLevelRefReact.RefObject<number>ایک قابلِ تبدیلی ref جس میں حقیقی وقت کا آڈیو لیول (0 سے 1) موجود ہوتا ہے -- ایجنٹ کی آواز اور وزیٹر کے مائیکروفون میں سے جو زیادہ بلند ہو -- جو React کے رینڈر سائیکل سے باہر، ہر اینیمیشن فریم پر اپ ڈیٹ ہوتا ہے۔ ہموار، جھٹکوں سے پاک اینیمیشنز کے لیے requestAnimationFrame لوپس کے اندر audioLevelRef.current پڑھیں، یا جب React state میں قدر درکار ہو تو اسے کسی وقفے پر سیمپل کریں۔
audioReactNodeغیر مرئی عنصر جو آڈیو کنکشن سنبھالتا ہے -- اسے رینڈر کرنا لازمی ہے۔

آڈیو پر ردعمل دینے والا UI

audioLevelRef ref آپ کو React کے دوبارہ رینڈر ہونے کو متحرک کیے بغیر فریم ریٹ آڈیو لیولز فراہم کرتا ہے، جس کی وجہ سے یہ ہموار ویوفارم ویژولائزیشنز، دھڑکتے ہوئے گولوں، یا گفتگو سے منسلک کسی بھی اینیمیشن کو چلانے کے لیے موزوں ہے۔ لیول اس آواز کی عکاسی کرتا ہے جو زیادہ بلند ہو: ایجنٹ کی آواز یا وزیٹر کا مائیکروفون۔

ویوفارم کی مثال

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

دھڑکتے ہوئے گولے کی مثال

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

بولنے کے اشارے کی مثال

React سے رینڈر ہونے والے ایسے UI کے لیے جو آواز کی سطح کے ساتھ تبدیل ہوتا ہے -- مثلاً حد پر مبنی "بولنے" کا بیج -- وقفے سے audioLevelRef.current کا نمونہ لیں اور نتیجہ state میں محفوظ کریں:

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

اسٹیٹ مشین

state پراپرٹی اس لائف سائیکل کی پیروی کرتی ہے:

idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
                  \
                   --> error (stays until connect() is called again)
اسٹیٹوضاحت
idleکوئی فعال سیشن نہیں۔ connect() کال کرنے کے لیے تیار ہے۔
connectingسیشن قائم کیا جا رہا ہے۔ اس اسٹیٹ کے دوران کال بٹن کو غیر فعال کریں۔
connectedوائس سیشن فعال ہے۔ صارف ایجنٹ سے بات کر رہا ہے۔
disconnectedسیشن صاف طور پر ختم ہو گیا ہے۔ 1.5 سیکنڈ بعد خودکار طور پر واپس idle میں منتقل ہو جاتا ہے۔
errorکچھ غلط ہو گیا۔ پیغام کے لیے phone.error چیک کریں۔ اسٹیٹ خود سے صاف نہیں ہوتی -- دوبارہ connect() کال کرنے سے ایک نئی کوشش شروع ہوتی ہے اور خرابی ری سیٹ ہو جاتی ہے۔

مثالیں

میوٹ کنٹرول کے ساتھ

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

رنگ ٹون کے ساتھ

فون کال کی نقل کے لیے کنیکٹ ہونے کے دوران رنگ ہونے کی آواز چلائیں:

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

رنگ ٹون connecting حالت کے دوران مسلسل چلتی ہے اور ایجنٹ کے کنیکٹ ہونے پر آہستہ آہستہ ختم ہو جاتی ہے۔ بلٹ اِن ڈیفالٹ رنگ ٹون کے لیے true دیں، یا اپنی آڈیو فائل استعمال کرنے کے لیے URL اسٹرنگ دیں۔

ایونٹ کال بیکس کے ساتھ

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

مکمل کسٹم UI

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

تجاویز

ہمیشہ phone.audio رینڈر کریں

phone.audio عنصر نظر نہیں آتا مگر ضروری ہے۔ اسے اپنے JSX میں کہیں بھی رکھیں -- یہ کوئی نظر آنے والا DOM رینڈر نہیں کرتا، لیکن اندرونی طور پر WebRTC آڈیو کنکشن کا انتظام کرتا ہے۔

کنیکٹ ہوتے وقت بٹن غیر فعال کریں

connecting حالت 1 سے 3 سیکنڈ تک رہ سکتی ہے۔ ڈپلیکیٹ کنکشن کوششوں سے بچنے کے لیے اس حالت کے دوران کال بٹن غیر فعال کریں۔

خرابی کی حالت کو مناسب طریقے سے سنبھالیں

جب حالت error ہو تو صارف کو phone.error دکھائیں اور اپنا کال بٹن فعال رکھیں۔ hook خود سے error حالت سے باہر نہیں آتا -- connect() کو دوبارہ کال کرنے سے ایک نئی کوشش شروع ہوتی ہے اور پچھلی خرابی صاف ہو جاتی ہے۔

ضمنی اثرات کے لیے callbacks استعمال کریں

onConnect، onDisconnect، اور onError callbacks اسٹیٹ کو poll کیے بغیر analytics، logging، یا دیگر ایپلیکیشن منطق کو trigger کرنے کے لیے موزوں ہیں۔

audioLevelRef سے آڈیو لیولز پڑھیں

audioLevelRef واحد لائیو آڈیو-لیول سورس ہے۔ waveform جیسی ہموار animations کے لیے requestAnimationFrame کے اندر audioLevelRef.current پڑھیں (ref پڑھنے سے دوبارہ رینڈر نہیں ہوتے)، یا اسے کسی interval پر sample کریں اور نتیجہ React سے رینڈر ہونے والے UI کے لیے state میں محفوظ کریں۔ audioLevel نمبر متروک ہے اور ہمیشہ 0 ہوتا ہے -- اس پر منطق نہ بنائیں۔