import { Button } from '@/components/ui/button' import { Codicon } from '@/components/ui/codicon' import { Tip } from '@/components/ui/tooltip' import { useI18n } from '@/i18n' import { triggerHaptic } from '@/lib/haptics' import { AudioLines, Layers3, Loader2, Square, SteeringWheel } from '@/lib/icons' import { cn } from '@/lib/utils' import type { ConversationStatus } from './hooks/use-voice-conversation' import type { ChatBarState, VoiceStatus } from './types' export const ICON_BTN = 'size-(--composer-control-size) shrink-0 rounded-md' export const GHOST_ICON_BTN = cn( ICON_BTN, 'text-(--ui-text-tertiary) hover:bg-(--chrome-action-hover) hover:text-foreground' ) // Send/voice-conversation primary: solid foreground-on-background circle // (reads as black-on-white in light mode, white-on-black in dark mode) to // match the reference composer's high-contrast CTA. Keeps the pill itself // neutral and lets the action visually dominate the row. export const PRIMARY_ICON_BTN = cn( 'size-(--composer-control-primary-size,var(--composer-control-size)) shrink-0 rounded-full p-0', 'bg-foreground text-background hover:bg-foreground/90', 'disabled:bg-foreground/30 disabled:text-background disabled:opacity-100' ) interface ConversationProps { active: boolean level: number muted: boolean status: ConversationStatus onEnd: () => void onStart: () => void onStopTurn: () => void onToggleMute: () => void } export function ComposerControls({ busy, busyAction, canSteer, canSubmit, conversation, disabled, hasComposerPayload, state, voiceStatus, onDictate, onSteer }: { busy: boolean busyAction: 'queue' | 'stop' canSteer: boolean canSubmit: boolean conversation: ConversationProps disabled: boolean hasComposerPayload: boolean state: ChatBarState voiceStatus: VoiceStatus onDictate: () => void onSteer: () => void }) { const { t } = useI18n() const c = t.composer if (conversation.active) { return } const showVoicePrimary = !busy && !hasComposerPayload return (
{canSteer && ( )} {showVoicePrimary ? ( ) : ( )}
) } function ConversationPill({ disabled, level, muted, onEnd, onStopTurn, onToggleMute, status }: ConversationProps & { disabled: boolean }) { const { t } = useI18n() const c = t.composer const speaking = status === 'speaking' const listening = status === 'listening' && !muted const label = status === 'speaking' ? c.speaking : status === 'transcribing' ? c.transcribing : status === 'thinking' ? c.thinking : muted ? c.muted : c.listening return (
{listening && ( )} {label}
) } function ConversationIndicator({ level, listening, speaking }: { level: number listening: boolean speaking: boolean }) { if (speaking) { return } const bars = [0.55, 0.85, 1, 0.85, 0.55] const normalized = Math.max(0, Math.min(level, 1)) return (