mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-29 15:15:27 -07:00
- Hand /generate ids to the global SSE watcher so playback fires on completion. The mutation onSuccess was checking audio_path on a queued row, which is always empty — autoplay never ran. - Bind the Play As voice selection to capture_settings.default_playback_voice_id, kept in sync with the Settings → Captures and Settings → MCP pickers. Picking from the split-button dropdown writes back to settings. - Extract AudioBars from HistoryTable into a shared component; use it for the Play As generating state in place of Loader2. - Stop the active-state hover from flashing white text when the button is in its lighter accent/10 fill. - Drop the gradient avatar swatches from the Settings → Captures voice dropdown. - Backend: when the gen worker exits without writing a terminal status (e.g. SQLite lock racing the failed-status write inside its own exception handler), the cancel endpoint now flips the row to failed instead of 409-ing. Worker also force-fails on its way out as a belt-and-suspenders.
40 lines
1.2 KiB
TypeScript
40 lines
1.2 KiB
TypeScript
import { motion } from 'framer-motion';
|
|
|
|
import { cn } from '@/lib/utils/cn';
|
|
|
|
export type AudioBarsMode = 'idle' | 'generating' | 'playing';
|
|
|
|
interface AudioBarsProps {
|
|
mode: AudioBarsMode;
|
|
className?: string;
|
|
barClassName?: string;
|
|
}
|
|
|
|
export function AudioBars({ mode, className, barClassName }: AudioBarsProps) {
|
|
const activeColor = mode !== 'idle' ? 'bg-accent' : 'bg-muted-foreground/40';
|
|
return (
|
|
<div className={cn('flex items-center gap-[2px] h-5', className)}>
|
|
{[0, 1, 2, 3, 4].map((i) => (
|
|
<motion.div
|
|
key={`${mode}-${i}`}
|
|
className={cn('w-[3px] rounded-full', activeColor, barClassName)}
|
|
animate={
|
|
mode === 'generating'
|
|
? { height: ['6px', '16px', '6px'] }
|
|
: mode === 'playing'
|
|
? { height: ['8px', '14px', '4px', '12px', '8px'] }
|
|
: { height: '8px' }
|
|
}
|
|
transition={
|
|
mode === 'generating'
|
|
? { duration: 0.6, repeat: Infinity, delay: i * 0.08, ease: 'easeInOut' }
|
|
: mode === 'playing'
|
|
? { duration: 1.2, repeat: Infinity, delay: i * 0.15, ease: 'easeInOut' }
|
|
: { duration: 0.4, ease: 'easeOut' }
|
|
}
|
|
/>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|