Voice Mode

Asistanınızla konuşun. Bulutlu bir gökyüzünden ve ferrofluid damlasından dithered piksellere, ASCII'ye, bir CRT gezegenine, yarım ton noktalara, tek bir halkaya ve yumuşak bir aurayaya uzanan sese duyarlı sekiz stil, ayrıca dört duyarlı nokta. Sessize alma, kesme ve altyazılı tam ekran oturum, sohbet içi ses hapı, ses seçici ve dinleyen, konuşmanızın bitmesini bekleyen ve yanıtlayan bir tarayıcı motoru.

Voice Mode, bir asistanla konuşmak için görsel ve kontrollerdir. Sekiz shader stilinden (Sky, Dither, ASCII, CRT, Ferrofluid, Halftone, Ring veya Aura) ya da dört düz noktadan birini seçin. Her biri sesle birlikte hareket ederken dinler, düşünür ve yanıt verir; tek renkli stiller açık veya koyu temanızı izler.

Ses seviyeleri yumuşatılır ve öğrenilmiş bir gürültü tabanına göre ölçülür; böylece sessiz bir oda sessiz görünür ve hareket yumuşak kalır. Görsel ekran dışındayken veya sekme gizliyken oluşturma durur, azaltılmış harekette ise tüm stiller sabit kalır.

VoiceSession, telefon genişliğinde tam ekran bir düzen sunar; altyazılar, bir durum satırı ve başparmakla ulaşılabilen sessize alma ve bitirme düğmeleri içerir. Sözünü kesmek için görsele dokunun, Space tuşuna basın veya konuşmaya başlayın. VoiceComposer, sohbet içinde yazma alanının yerine geçen kompakt bir sürümdür.

useBrowserVoice, tarayıcının kendi konuşma tanıması ve konuşması üzerinde eksiksiz bir döngü çalıştırır: yanıtlamadan önce bir duraklamayı bekler, sessiz kalıp yanıtları altyazı olarak gösterebilir ve hoparlörlerden geri gelen kendi sözlerini yok sayar. Gerçek zamanlı ses API'leri için mikrofon ve yanıt akışlarını useAudioLevels'a verin ve durumu oturumun olaylarından ayarlayın.

  1. Pro registry'yi components.json dosyasına ekleyin

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Token'ınızı ekleyin

    Hesap sayfanızda bir token oluşturun ve .env.local dosyasına HEXTAUI_PRO_TOKEN olarak ekleyin.

  3. Bloğu ekleyin

    pnpm dlx shadcn@latest add @hextaui-pro/voice-mode

Tarayıcının sesiyle

useBrowserVoice mikrofonla ve tarayıcının konuşma tanımasıyla dinler, bir duraklamayı bekler, söylediklerinizle respond'u çağırır ve tarayıcının konuşmasıyla geri konuşur. Modelinizin yanıtını respond'dan döndürün.

"use client"

import { useBrowserVoice } from "@/components/blocks/voice-mode/use-browser-voice"
import { VoiceSession } from "@/components/blocks/voice-mode/voice-session"

export function VoiceChat({ onClose }: { onClose: () => void }) {
  const voice = useBrowserVoice({
    pauseMs: 900,
    respond: async (text, turns, signal) => {
      const response = await fetch("/api/voice", {
        method: "POST",
        body: JSON.stringify({ text, turns }),
        signal,
      })
      const { reply } = await response.json()
      return reply
    },
  })

  return (
    <VoiceSession
      state={voice.state}
      levels={voice.levels}
      muted={voice.muted}
      onMutedChange={voice.setMuted}
      onInterrupt={voice.interrupt}
      onEnd={() => {
        voice.end()
        onClose()
      }}
      captions={voice.captions}
      showCaptions
      error={voice.error}
    />
  )
}

Gerçek zamanlı bir ses API'siyle

WebRTC üzerinden konuşmadan konuşmaya çalışan modellerde, durumu oturumun olaylarından yönetin ve her iki ses akışını da useAudioLevels'a verin; böylece siz konuşurken küre sizi, model yanıtlarken modeli takip eder.

"use client"

import * as React from "react"

import { useAudioLevels } from "@/components/blocks/voice-mode/audio"
import type { VoiceState } from "@/components/blocks/voice-mode/voice-orb"
import { VoiceSession } from "@/components/blocks/voice-mode/voice-session"

export function RealtimeVoice({ token }: { token: string }) {
  const [state, setState] = React.useState<VoiceState>("connecting")
  const [mic, setMic] = React.useState<MediaStream | null>(null)
  const [reply, setReply] = React.useState<MediaStream | null>(null)
  const [muted, setMuted] = React.useState(false)
  const peer = React.useRef<RTCPeerConnection | null>(null)
  const listening = useAudioLevels(mic)
  const speaking = useAudioLevels(reply)

  React.useEffect(() => {
    const connection = new RTCPeerConnection()
    peer.current = connection
    connection.ontrack = (event) => setReply(event.streams[0])
    const events = connection.createDataChannel("oai-events")
    events.onmessage = (message) => {
      const event = JSON.parse(message.data)
      if (event.type === "input_audio_buffer.speech_started") setState("listening")
      if (event.type === "input_audio_buffer.committed") setState("thinking")
      if (event.type === "output_audio_buffer.started") setState("speaking")
      if (event.type === "output_audio_buffer.stopped") setState("listening")
    }
    navigator.mediaDevices.getUserMedia({ audio: true }).then(async (stream) => {
      setMic(stream)
      stream.getTracks().forEach((track) => connection.addTrack(track, stream))
      const offer = await connection.createOffer()
      await connection.setLocalDescription(offer)
      const answer = await fetch("https://api.openai.com/v1/realtime/calls", {
        method: "POST",
        body: offer.sdp,
        headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/sdp" },
      })
      await connection.setRemoteDescription({ type: "answer", sdp: await answer.text() })
      setState("listening")
    })
    return () => connection.close()
  }, [token])

  React.useEffect(() => {
    mic?.getAudioTracks().forEach((track) => (track.enabled = !muted))
  }, [mic, muted])

  return (
    <VoiceSession
      state={state}
      levels={state === "speaking" ? speaking : listening}
      muted={muted}
      onMutedChange={setMuted}
      onEnd={() => peer.current?.close()}
    />
  )
}

Sohbetin içinde

VoiceComposer, bir ses oturumu sürerken yazma alanının yerine geçer; küçük bir küre, durum, bir zamanlayıcı, sessize alma ve End içerir. Sözleriniz ve yanıtlar metin olarak konuşma dizisine akabilir.

"use client"

import { useBrowserVoice } from "@/components/blocks/voice-mode/use-browser-voice"
import { VoiceComposer } from "@/components/blocks/voice-mode/voice-session"

export function ChatVoice({ respond }: { respond: (text: string) => Promise<string> }) {
  const voice = useBrowserVoice({ respond: (text) => respond(text) })

  if (voice.state === "idle") {
    return <button onClick={() => void voice.start()}>Start voice</button>
  }

  return (
    <VoiceComposer
      state={voice.state}
      levels={voice.levels}
      muted={voice.muted}
      onMutedChange={voice.setMuted}
      onInterrupt={voice.interrupt}
      onEnd={voice.end}
      startedAt={voice.startedAt}
    />
  )
}

Yapı

Dıştan içe doğru birleştirdiğiniz parçalar.

ParçaAçıklama
VoiceVisualkind'a göre herhangi bir stil: sky, dither, ascii, crt, ferrofluid, halftone, ring, aura veya bars.
VoiceOrbShader küresi. Durumu, sessize almayı ve bir levels ref'ini alır.
VoiceBarsAynı prop'lara sahip dört noktalı görsel.
VoiceShaderPaylaşılan WebGL motoru. Aynı girdilerle (durum, yumuşatılmış seviye, dört bant ve üç renk) yeni bir stil oluşturmak için kendi fragment shader'ınızı verin.
VoiceSessionTam ekran düzen: eylemler, görsel, altyazılar, durum, sessize alma ve bitirme.
VoiceComposerSohbetin içinde kullanılan kompakt ses hapı.
VoicePickerRenkli kürelerle sesleri listeleyen bir popover.
useBrowserVoiceTarayıcı API'leri üzerinde eksiksiz bir ses döngüsü: dinle, konuşma sırasının bittiğini algıla, yanıtla, konuş, sözünü kes.
useAudioLevelsHerhangi bir MediaStream veya medya öğesinden bir seviye ve dört bandı, yeniden oluşturma yapmadan bir ref'e okur.

VoiceVisual

Ayrıca tüm div prop'larını kabul eder. Boyutunu bir genişlik sınıfıyla verin; kare kalır.

PropTürVarsayılan
kindHangi stilin oluşturulacağı.
"sky" | "dither" | "ascii" | "crt" | "ferrofluid" | "halftone" | "ring" | "aura" | "bars""sky"
colorsShader için üç renk. Hex, herhangi bir CSS rengi veya currentColor. Her stil kendi renklerini getirir.
[string, string, string]–
paletteVoicePicker seslerinin kullandığı gökyüzü renkleri.
{ deep, sky, cloud }–
stateDinleme, düşünme ve konuşma hareketini yönetir.
VoiceState–
levelsCanlı ses.
RefObject<VoiceLevels>–
mutedRengi soldurur ve tepki vermeyi bırakır. Halka kesikli olur.
booleanfalse

VoiceOrb

Ayrıca tüm div prop'larını kabul eder. Boyutunu bir genişlik sınıfıyla verin; kare kalır.

PropTürVarsayılan
stateÇalkalanma, dönüş ve dalgalanmaları yönetir.
"idle" | "connecting" | "listening" | "thinking" | "speaking" | "error""idle"
levelsCanlı ses, her karede okunur. Genellikle useAudioLevels veya useBrowserVoice'tan gelir.
RefObject<VoiceLevels>–
mutedRengi soldurur ve tepki vermeyi bırakır.
booleanfalse
paletteÜç hex renk.
{ deep, sky, cloud }blue sky
PropTürVarsayılan
stateKonuşma durumu.
VoiceState–
levelsGörsele iletilir.
RefObject<VoiceLevels>–
variantHangi stilin gösterileceği.
VoiceVisualKind"sky"
colorsStilin renklerini geçersiz kılar.
[string, string, string]–
mutedSessize alma durumu.
booleanfalse
onMutedChangeSessize alma düğmesini ve M kısayolunu gösterir.
(muted: boolean) => void–
onInterruptDüşünürken veya konuşurken görseli bir düğmeye dönüştürür; Space tuşu da çalışır.
() => void–
onEndBitirme düğmesini ve Escape kısayolunu gösterir.
() => void–
captionsHer iki taraftan en son sözler.
{ user, assistant }–
showCaptionsAltyazıları görselin altında gösterin.
booleanfalse
onShowCaptionsChangeAltyazı düğmesini ve C kısayolunu gösterir.
(show: boolean) => void–
statusDurum etiketinin yerine geçer.
ReactNode–
footnoteDüğmelerin arasında, örneğin bir zamanlayıcı veya kalan dakika.
ReactNode–
errorDurumun yerine gösterilir.
string | null–
actionsSağ üstteki kontroller, örneğin VoicePicker.
ReactNode–
paletteKüre renkleri.
VoicePalette–
PropTürVarsayılan
stateKonuşma durumu.
VoiceState–
levelsKüçük küreye iletilir.
RefObject<VoiceLevels>–
onEndSesi sonlandırır ve yazma alanını geri getirir.
() => void–
muted / onMutedChangeSessize alma düğmesi.
boolean / (muted) => void–
onInterruptSözünü kesmek için küreye dokunun.
() => void–
startedAtZamanlayıcı için milisaniye cinsinden başlangıç zamanı.
number | null–

useBrowserVoice(options)

{ state, muted, setMuted, start, end, interrupt, levels, turns, captions, error, startedAt } döndürür.

PropTürVarsayılan
respondBir cümleyi bitirdiğinizde çağrılır. Yanıtı döndürün; sözünüz kesildiğinde signal iptal edilir.
(text, turns, signal) => Promise<string>–
pauseMsSıranızın bittiği kabul edilmeden önceki sessizlik süresi.
number900
bargeInKonuşmanın yanıtın sözünü kesmesine izin verin.
booleantrue
voiceOutputYanıtları sesli okur. false olduğunda yanıtlar altyazı olarak görünür ve küre canlı kalmaya devam eder.
booleantrue
voiceTercih edilen tarayıcı sesleri ve okuma biçimi.
{ names?, pitch?, rate? }–
greetingOturum başladığında söylenir.
string–
langTanıma ve konuşma dili.
string"en-US"

useAudioLevels(source, target?)

Her karede güncellenen { level, bands } ref'ini döndürür.

PropTürVarsayılan
sourceMikrofon, uzak WebRTC akışı veya bir audio öğesi.
MediaStream | HTMLMediaElement | null–
targetYeni bir ref yerine mevcut bir ref'e yazın.
RefObject<VoiceLevels>–
TuşAction
SpaceDüşünürken veya konuşurken sözünü keser.
MMikrofonunuzu sessize alır veya sesini açar.
CAltyazıları gösterir veya gizler.
EscSes oturumunu sonlandırır.
TabSırasıyla altyazı düğmesi, ses seçici, sözü kesilebiliyorsa küre, sessize alma ve bitirme arasında geçiş yapar.
  • Oturum etiketli bir bölgedir ve durum satırı kibar (polite) bir canlı bölgedir; bu nedenle “Listening” ve “Thinking” gibi durum değişiklikleri duyurulur.
  • Görsel, sözü kesilebildiği sürece “Interrupt” etiketli gerçek bir düğmedir; aksi halde devre dışıdır.
  • Sessize alma aria-pressed kullanır ve her kontrolün kısayolunu belirten bir tooltip'i vardır.
  • Altyazılar söylenen her şeye metin alternatifi sunar ve yanıtlar ses olmadan da gösterilebilir.
  • Bir alana yazı yazılırken kısayollar yok sayılır, Escape hariç.
  • Azaltılmış harekette küre ve noktalar sabit kalır, yalnızca duruma göre değişir.
  • WebGL olmadığında her shader stili sabit bir gradyana geri döner; noktalar her yerde çalışır.

Şunlarla geliştirildi

Voice Mode bloğunun yapıldığı ücretsiz HextaUI bileşenleri. Her biri tek başına yüklenir.

Kod

9 dosya, components/blocks/voice-mode konumuna eklendi.