fix SSE cleanup, filter add popover to completed only, derive isGenerating from pending set, handle not_found status

This commit is contained in:
Jamie Pine
2026-03-13 10:57:28 -07:00
parent 509b0e71cc
commit 49ebf6222e
4 changed files with 18 additions and 21 deletions
@@ -58,6 +58,7 @@ export function StoryContent() {
const query = searchQuery.toLowerCase(); const query = searchQuery.toLowerCase();
return historyData.items.filter( return historyData.items.filter(
(gen) => (gen) =>
gen.status === 'completed' &&
!storyGenerationIds.has(gen.id) && !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)),
); );
+14 -11
View File
@@ -8,7 +8,7 @@ import { useServerStore } from '@/stores/serverStore';
interface GenerationStatusEvent { interface GenerationStatusEvent {
id: string; id: string;
status: 'generating' | 'completed' | 'failed'; status: 'generating' | 'completed' | 'failed' | 'not_found';
duration?: number; duration?: number;
error?: string; error?: string;
} }
@@ -37,6 +37,17 @@ export function useGenerationProgress() {
// Track active EventSource instances // Track active EventSource instances
const eventSourcesRef = useRef<Map<string, EventSource>>(new Map()); const eventSourcesRef = useRef<Map<string, EventSource>>(new Map());
// Unmount-only cleanup — close all SSE connections when the hook is torn down
useEffect(() => {
const sources = eventSourcesRef.current;
return () => {
for (const source of sources.values()) {
source.close();
}
sources.clear();
};
}, []);
useEffect(() => { useEffect(() => {
const currentSources = eventSourcesRef.current; const currentSources = eventSourcesRef.current;
@@ -103,7 +114,7 @@ export function useGenerationProgress() {
const genAudioUrl = apiClient.getAudioUrl(id); const genAudioUrl = apiClient.getAudioUrl(id);
setAudioWithAutoPlay(genAudioUrl, id, '', ''); setAudioWithAutoPlay(genAudioUrl, id, '', '');
} }
} else if (data.status === 'failed') { } else if (data.status === 'failed' || data.status === 'not_found') {
source.close(); source.close();
currentSources.delete(id); currentSources.delete(id);
removePendingGeneration(id); removePendingGeneration(id);
@@ -112,7 +123,7 @@ export function useGenerationProgress() {
queryClient.invalidateQueries({ queryKey: ['history'] }); queryClient.invalidateQueries({ queryKey: ['history'] });
toast({ toast({
title: 'Generation failed', title: data.status === 'not_found' ? 'Generation not found' : 'Generation failed',
description: data.error || 'An error occurred during generation', description: data.error || 'An error occurred during generation',
variant: 'destructive', variant: 'destructive',
}); });
@@ -132,14 +143,6 @@ export function useGenerationProgress() {
currentSources.set(id, source); currentSources.set(id, source);
} }
return () => {
// Cleanup on unmount
for (const source of currentSources.values()) {
source.close();
}
currentSources.clear();
};
}, [ }, [
pendingIds, pendingIds,
removePendingGeneration, removePendingGeneration,
+2 -6
View File
@@ -15,7 +15,6 @@ const POLL_INTERVAL = 30000;
*/ */
export function useRestoreActiveTasks() { export function useRestoreActiveTasks() {
const [activeDownloads, setActiveDownloads] = useState<ActiveDownloadTask[]>([]); const [activeDownloads, setActiveDownloads] = useState<ActiveDownloadTask[]>([]);
const setIsGenerating = useGenerationStore((state) => state.setIsGenerating);
const setActiveGenerationId = useGenerationStore((state) => state.setActiveGenerationId); const setActiveGenerationId = useGenerationStore((state) => state.setActiveGenerationId);
const addPendingGeneration = useGenerationStore((state) => state.addPendingGeneration); const addPendingGeneration = useGenerationStore((state) => state.addPendingGeneration);
@@ -26,18 +25,15 @@ export function useRestoreActiveTasks() {
try { try {
const tasks = await apiClient.getActiveTasks(); const tasks = await apiClient.getActiveTasks();
// Update generation state — restore pending generations (e.g., after page refresh) // Restore pending generations (e.g., after page refresh)
if (tasks.generations.length > 0) { if (tasks.generations.length > 0) {
setIsGenerating(true);
setActiveGenerationId(tasks.generations[0].task_id); setActiveGenerationId(tasks.generations[0].task_id);
for (const gen of tasks.generations) { for (const gen of tasks.generations) {
addPendingGeneration(gen.task_id); addPendingGeneration(gen.task_id);
} }
} else { } else {
// Only clear if we were tracking a generation
const currentId = useGenerationStore.getState().activeGenerationId; const currentId = useGenerationStore.getState().activeGenerationId;
if (currentId) { if (currentId) {
setIsGenerating(false);
setActiveGenerationId(null); setActiveGenerationId(null);
} }
} }
@@ -63,7 +59,7 @@ export function useRestoreActiveTasks() {
// Silently fail - server might be temporarily unavailable // Silently fail - server might be temporarily unavailable
console.debug('Failed to fetch active tasks:', error); console.debug('Failed to fetch active tasks:', error);
} }
}, [setIsGenerating, setActiveGenerationId]); }, [setActiveGenerationId, addPendingGeneration]);
useEffect(() => { useEffect(() => {
// Fetch immediately on mount // Fetch immediately on mount
+1 -4
View File
@@ -3,7 +3,7 @@ import { create } from 'zustand';
interface GenerationState { interface GenerationState {
/** IDs of generations currently in progress */ /** IDs of generations currently in progress */
pendingGenerationIds: Set<string>; pendingGenerationIds: Set<string>;
/** Whether any generation is in progress (derived convenience) */ /** Whether any generation is in progress (derived from pendingGenerationIds) */
isGenerating: boolean; isGenerating: boolean;
/** Map of generationId → storyId for deferred story additions */ /** Map of generationId → storyId for deferred story additions */
pendingStoryAdds: Map<string, string>; pendingStoryAdds: Map<string, string>;
@@ -11,8 +11,6 @@ interface GenerationState {
removePendingGeneration: (id: string) => void; removePendingGeneration: (id: string) => void;
addPendingStoryAdd: (generationId: string, storyId: string) => void; addPendingStoryAdd: (generationId: string, storyId: string) => void;
removePendingStoryAdd: (generationId: string) => string | undefined; removePendingStoryAdd: (generationId: string) => string | undefined;
/** Legacy setter for backward compat with useRestoreActiveTasks */
setIsGenerating: (generating: boolean) => void;
setActiveGenerationId: (id: string | null) => void; setActiveGenerationId: (id: string | null) => void;
activeGenerationId: string | null; activeGenerationId: string | null;
} }
@@ -56,6 +54,5 @@ export const useGenerationStore = create<GenerationState>((set, get) => ({
return storyId; return storyId;
}, },
setIsGenerating: (generating) => set({ isGenerating: generating }),
setActiveGenerationId: (id) => set({ activeGenerationId: id }), setActiveGenerationId: (id) => set({ activeGenerationId: id }),
})); }));