{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "voice",
  "type": "registry:component",
  "title": "Voice",
  "description": "Realtime voice session controls with connect, mute, and a status indicator.",
  "dependencies": [
    "@assistant-ui/react",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://r.assistant-ui.com/elements-voice.json",
    "button",
    "https://r.assistant-ui.com/tooltip-icon-button.json"
  ],
  "files": [
    {
      "type": "registry:component",
      "path": "components/assistant-ui/elements/voice.aui.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/voice.aui.tsx",
      "content": "\"use client\";\n\nimport { TooltipIconButton } from \"@/components/assistant-ui/elements/tooltip-icon-button\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  AuiIf,\n  useVoiceControls,\n  useVoiceState,\n  useVoiceVolume,\n} from \"@assistant-ui/react\";\nimport { MicIcon, MicOffIcon, PhoneIcon, PhoneOffIcon } from \"lucide-react\";\nimport { type FC, memo } from \"react\";\nimport {\n  VoiceOrb as VoiceOrbBase,\n  type VoiceOrbState,\n  type VoiceOrbVariant,\n} from \"./voice\";\n\nexport type { VoiceOrbState, VoiceOrbVariant } from \"./voice\";\n\nexport type VoiceOrbProps = {\n  state?: VoiceOrbState;\n  variant?: VoiceOrbVariant;\n  className?: string;\n};\n\nexport function deriveVoiceOrbState(\n  voiceState: ReturnType<typeof useVoiceState>,\n): VoiceOrbState {\n  if (!voiceState) return \"idle\";\n  if (voiceState.status.type === \"starting\") return \"connecting\";\n  if (voiceState.status.type === \"ended\") return \"idle\";\n  if (voiceState.isMuted) return \"muted\";\n  if (voiceState.mode === \"speaking\") return \"speaking\";\n  return \"listening\";\n}\nexport const VoiceOrb: FC<VoiceOrbProps> = memo(\n  ({ state: stateProp, ...rest }) => {\n    const voiceState = useVoiceState();\n    const volume = useVoiceVolume();\n    const state = stateProp ?? deriveVoiceOrbState(voiceState);\n    return <VoiceOrbBase state={state} volume={volume} {...rest} />;\n  },\n);\n\nVoiceOrb.displayName = \"VoiceOrb\";\n\nexport const VoiceControl: FC<{ className?: string }> = ({ className }) => {\n  return (\n    <div\n      className={cn(\n        \"aui-voice-control flex items-center gap-2 border-b px-4 py-2\",\n        className,\n      )}\n    >\n      <VoiceStatusDot />\n\n      <AuiIf\n        condition={(s) =>\n          s.thread.voice == null || s.thread.voice.status.type === \"ended\"\n        }\n      >\n        <VoiceConnectButton />\n      </AuiIf>\n\n      <AuiIf condition={(s) => s.thread.voice?.status.type === \"starting\"}>\n        <span className=\"aui-voice-status text-muted-foreground text-sm\">\n          Connecting...\n        </span>\n      </AuiIf>\n\n      <AuiIf condition={(s) => s.thread.voice?.status.type === \"running\"}>\n        <VoiceMuteButton />\n        <VoiceDisconnectButton />\n      </AuiIf>\n    </div>\n  );\n};\n\nexport const VoiceStatusDot: FC = () => {\n  const voiceState = useVoiceState();\n  const state = deriveVoiceOrbState(voiceState);\n\n  return (\n    <span\n      className={cn(\n        \"aui-voice-status-dot size-2.5 shrink-0 rounded-full transition-all duration-300\",\n        state === \"idle\" && \"bg-muted-foreground\",\n        state === \"connecting\" && \"animate-pulse bg-amber-500\",\n        state === \"listening\" && \"bg-green-500\",\n        state === \"speaking\" && \"bg-green-500\",\n        state === \"muted\" && \"bg-destructive\",\n      )}\n    />\n  );\n};\n\nexport const VoiceConnectButton: FC = () => {\n  const { connect } = useVoiceControls();\n  return (\n    <Button\n      variant=\"default\"\n      size=\"sm\"\n      className=\"aui-voice-connect gap-1.5 rounded-lg\"\n      onClick={() => connect()}\n    >\n      <PhoneIcon className=\"size-4\" />\n      Connect\n    </Button>\n  );\n};\n\nexport const VoiceMuteButton: FC = () => {\n  const voiceState = useVoiceState();\n  const { mute, unmute } = useVoiceControls();\n  const isMuted = voiceState?.isMuted ?? false;\n\n  return (\n    <TooltipIconButton\n      tooltip={isMuted ? \"Unmute\" : \"Mute\"}\n      className=\"aui-voice-mute\"\n      onClick={() => (isMuted ? unmute() : mute())}\n    >\n      {isMuted ? <MicOffIcon /> : <MicIcon />}\n    </TooltipIconButton>\n  );\n};\n\nexport const VoiceDisconnectButton: FC = () => {\n  const { disconnect } = useVoiceControls();\n  return (\n    <TooltipIconButton\n      tooltip=\"Disconnect\"\n      className=\"aui-voice-disconnect text-destructive hover:text-destructive\"\n      onClick={() => disconnect()}\n    >\n      <PhoneOffIcon />\n    </TooltipIconButton>\n  );\n};\n"
    }
  ]
}