From 81f8be1a94232bb85179c66a15c802f47a1bf33d Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Fri, 13 Mar 2026 10:28:20 -0700 Subject: [PATCH] defer story add until TTS completes, add generating pill to story editor, fix item placement per-track --- .../Generation/FloatingGenerateBox.tsx | 27 ++--------- .../components/StoriesTab/StoryContent.tsx | 38 ++++++++++++--- app/src/lib/hooks/useGenerationProgress.ts | 47 ++++++++++++++++--- app/src/stores/generationStore.ts | 26 +++++++++- backend/stories.py | 12 ++--- 5 files changed, 108 insertions(+), 42 deletions(-) diff --git a/app/src/components/Generation/FloatingGenerateBox.tsx b/app/src/components/Generation/FloatingGenerateBox.tsx index a9b830dc..d3159486 100644 --- a/app/src/components/Generation/FloatingGenerateBox.tsx +++ b/app/src/components/Generation/FloatingGenerateBox.tsx @@ -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); } }, }); diff --git a/app/src/components/StoriesTab/StoryContent.tsx b/app/src/components/StoriesTab/StoryContent.tsx index 483e6657..89d6dbb1 100644 --- a/app/src/components/StoriesTab/StoryContent.tsx +++ b/app/src/components/StoriesTab/StoryContent.tsx @@ -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(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() {

{story.description}

)} -
+
+ + {pendingCount > 0 && ( + + +
+
+ +
+
+ + Generating {pendingCount} {pendingCount === 1 ? 'audio' : 'audios'} + + +
+ )} +