mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-18 06:10:43 -07:00
defer story add until TTS completes, add generating pill to story editor, fix item placement per-track
This commit is contained in:
@@ -12,12 +12,12 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { getLanguageOptionsForEngine, type LanguageCode } from '@/lib/constants/languages';
|
||||
import { useGenerationForm } from '@/lib/hooks/useGenerationForm';
|
||||
import { useProfile, useProfiles } from '@/lib/hooks/useProfiles';
|
||||
import { useAddStoryItem, useStory } from '@/lib/hooks/useStories';
|
||||
import { useStory } from '@/lib/hooks/useStories';
|
||||
import { cn } from '@/lib/utils/cn';
|
||||
import { useGenerationStore } from '@/stores/generationStore';
|
||||
import { useStoryStore } from '@/stores/storyStore';
|
||||
import { useUIStore } from '@/stores/uiStore';
|
||||
import { ParalinguisticInput } from './ParalinguisticInput';
|
||||
@@ -44,8 +44,7 @@ export function FloatingGenerateBox({
|
||||
const selectedStoryId = useStoryStore((state) => state.selectedStoryId);
|
||||
const trackEditorHeight = useStoryStore((state) => state.trackEditorHeight);
|
||||
const { data: currentStory } = useStory(selectedStoryId);
|
||||
const addStoryItem = useAddStoryItem();
|
||||
const { toast } = useToast();
|
||||
const addPendingStoryAdd = useGenerationStore((s) => s.addPendingStoryAdd);
|
||||
|
||||
// Calculate if track editor is visible (on stories route with items)
|
||||
const hasTrackEditor = isStoriesRoute && currentStory && currentStory.items.length > 0;
|
||||
@@ -53,25 +52,9 @@ export function FloatingGenerateBox({
|
||||
const { form, handleSubmit, isPending } = useGenerationForm({
|
||||
onSuccess: async (generationId) => {
|
||||
setIsExpanded(false);
|
||||
// If on stories route and a story is selected, add generation to story
|
||||
// Defer the story add until TTS completes — useGenerationProgress handles it
|
||||
if (isStoriesRoute && selectedStoryId && generationId) {
|
||||
try {
|
||||
await addStoryItem.mutateAsync({
|
||||
storyId: selectedStoryId,
|
||||
data: { generation_id: generationId },
|
||||
});
|
||||
toast({
|
||||
title: 'Added to story',
|
||||
description: `Generation added to "${currentStory?.name || 'story'}"`,
|
||||
});
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: 'Failed to add to story',
|
||||
description:
|
||||
error instanceof Error ? error.message : 'Could not add generation to story',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
addPendingStoryAdd(generationId, selectedStoryId);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,8 +13,11 @@ import {
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { Download, Plus } from 'lucide-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import Loader from 'react-loaders';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
@@ -28,6 +31,7 @@ import {
|
||||
useStory,
|
||||
} from '@/lib/hooks/useStories';
|
||||
import { useStoryPlayback } from '@/lib/hooks/useStoryPlayback';
|
||||
import { useGenerationStore } from '@/stores/generationStore';
|
||||
import { useStoryStore } from '@/stores/storyStore';
|
||||
import { SortableStoryChatItem } from './StoryChatItem';
|
||||
|
||||
@@ -40,6 +44,7 @@ export function StoryContent() {
|
||||
const addStoryItem = useAddStoryItem();
|
||||
const { toast } = useToast();
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const pendingCount = useGenerationStore((s) => s.pendingGenerationIds.size);
|
||||
|
||||
// Add generation popover state
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -54,8 +59,7 @@ export function StoryContent() {
|
||||
return historyData.items.filter(
|
||||
(gen) =>
|
||||
!storyGenerationIds.has(gen.id) &&
|
||||
(gen.text.toLowerCase().includes(query) ||
|
||||
gen.profile_name.toLowerCase().includes(query)),
|
||||
(gen.text.toLowerCase().includes(query) || gen.profile_name.toLowerCase().includes(query)),
|
||||
);
|
||||
}, [historyData, story, searchQuery]);
|
||||
|
||||
@@ -267,7 +271,31 @@ export function StoryContent() {
|
||||
<p className="text-sm text-muted-foreground mt-1">{story.description}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 items-center">
|
||||
<AnimatePresence>
|
||||
{pendingCount > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9, width: 0 }}
|
||||
animate={{ opacity: 1, scale: 1, width: 'auto' }}
|
||||
exit={{ opacity: 0, scale: 0.9, width: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<Link
|
||||
to="/"
|
||||
className="flex items-center gap-2 h-8 pl-1.5 pr-3 rounded-full bg-card border border-border hover:bg-muted/50 transition-all duration-200 cursor-pointer"
|
||||
>
|
||||
<div className="shrink-0 w-10 h-5 overflow-hidden flex items-center justify-center">
|
||||
<div className="scale-[0.45]">
|
||||
<Loader type="line-scale" active />
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground whitespace-nowrap">
|
||||
Generating {pendingCount} {pendingCount === 1 ? 'audio' : 'audios'}
|
||||
</span>
|
||||
</Link>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<Popover open={isAddOpen} onOpenChange={setIsAddOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
@@ -287,9 +315,7 @@ export function StoryContent() {
|
||||
<div className="max-h-60 overflow-y-auto">
|
||||
{availableGenerations.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
{searchQuery
|
||||
? 'No matching generations found'
|
||||
: 'No available generations'}
|
||||
{searchQuery ? 'No matching generations found' : 'No available generations'}
|
||||
</div>
|
||||
) : (
|
||||
availableGenerations.map((gen) => (
|
||||
|
||||
Reference in New Issue
Block a user