import { useState, useCallback, useRef, useEffect } from 'react'; import { UseFormReturn } from 'react-hook-form'; import { toast } from 'sonner'; import { focusGroupsApi } from '@/lib/api'; import { useCancellableGeneration } from '@/hooks/useCancellableGeneration'; import { waitForTaskResult } from '@/lib/taskPolling'; import { getSocket } from '@/services/websocketServiceNew'; interface DiscussionGuideGenerationOptions { form: UseFormReturn; } interface DiscussionGuideGenerationReturn { discussionGuide: any | null; setDiscussionGuide: (guide: any | null) => void; discussionGuideRef: React.MutableRefObject; isEditingGuide: boolean; setIsEditingGuide: (editing: boolean) => void; guideGenerationState: { isGenerating: boolean; isComplete: boolean; hasError: boolean; isCancelling: boolean; taskId: string | null; }; guideGenerationControls: { startGeneration: () => void; setTaskId: (id: string) => void; completeGeneration: () => void; failGeneration: (error: string) => void; cancelGeneration: () => void; resetGeneration: () => void; }; isGuideProgressModalOpen: boolean; setIsGuideProgressModalOpen: (open: boolean) => void; isDownloadingGuide: boolean; generateDiscussionGuide: (values: any, focusGroupId?: string) => Promise; handleSaveDiscussionGuide: (updatedGuide: any) => void; handleDownloadDiscussionGuide: () => Promise; handleEditingStateChange: (editing: boolean) => void; handleGuideProgressComplete: () => void; isJsonFormat: (guide: any) => boolean; } export function useDiscussionGuideGeneration({ form, }: DiscussionGuideGenerationOptions): DiscussionGuideGenerationReturn { const socket = getSocket(); const [guideGenerationState, guideGenerationControls] = useCancellableGeneration('discussion guide generation', socket); const [isGuideProgressModalOpen, setIsGuideProgressModalOpen] = useState(false); const [discussionGuide, setDiscussionGuide] = useState(null); const [isEditingGuide, setIsEditingGuide] = useState(false); const [isDownloadingGuide, setIsDownloadingGuide] = useState(false); // Ref to access current discussionGuide in callbacks without adding it as dependency const discussionGuideRef = useRef(discussionGuide); discussionGuideRef.current = discussionGuide; // Helper function to determine if discussion guide is JSON format const isJsonFormat = useCallback((guide: any): boolean => { return guide && typeof guide === 'object' && guide.title && guide.sections; }, []); // Function to generate a discussion guide via the API const generateDiscussionGuide = useCallback(async (values: any, focusGroupId?: string): Promise => { guideGenerationControls.startGeneration(); setIsGuideProgressModalOpen(true); try { const requestData = { name: values.focusGroupName, description: values.researchBrief, objective: values.researchBrief, topic: values.discussionTopics, duration: parseInt(values.duration), llm_model: values.llm_model, reasoning_effort: values.reasoning_effort, verbosity: values.verbosity }; const response = focusGroupId ? await focusGroupsApi.generateDiscussionGuideForGroup(focusGroupId, requestData) : await focusGroupsApi.generateDiscussionGuide(requestData); const taskId = response.data?.task_id; if (taskId) { guideGenerationControls.setTaskId(taskId); } // Backend returns 202 immediately — wait for result via HTTP polling if (response.status === 202 && taskId) { const taskResult = await waitForTaskResult(taskId); if (taskResult.status === 'completed') { const guide = taskResult.result?.discussionGuide; if (guide) { guideGenerationControls.completeGeneration(); return guide; } else { guideGenerationControls.failGeneration('No guide returned'); throw new Error('No discussion guide returned'); } } else if (taskResult.status === 'failed') { guideGenerationControls.failGeneration(taskResult.error || 'Generation failed'); throw new Error(taskResult.error || 'Generation failed'); } else { // cancelled return ''; } } // Fallback: synchronous response with guide in body if (response.data?.discussionGuide) { guideGenerationControls.completeGeneration(); return response.data.discussionGuide; } throw new Error("Failed to generate discussion guide"); } catch (error: any) { if (error.response?.status === 499) { return ''; } console.error("Error generating discussion guide:", error); guideGenerationControls.failGeneration(error.message || 'Failed to generate discussion guide'); let errorMessage = 'Unknown error occurred'; if (error?.response?.data?.error) { errorMessage = error.response.data.error; } else if (error?.message) { errorMessage = error.message; } toast.error("Failed to generate discussion guide", { description: errorMessage, action: { label: "Retry", onClick: () => generateDiscussionGuide(values, focusGroupId) } }); throw error; } }, [guideGenerationControls]); const handleGuideProgressComplete = useCallback(() => { setIsGuideProgressModalOpen(false); guideGenerationControls.resetGeneration(); }, [guideGenerationControls]); // Stable callback for saving discussion guide changes const handleSaveDiscussionGuide = useCallback((updatedGuide: any) => { if (!isEditingGuide) { setDiscussionGuide(updatedGuide); toast.success('Discussion guide updated', { description: 'Your changes have been saved.' }); } else { discussionGuideRef.current = updatedGuide; } }, [isEditingGuide]); // Handle editing state changes from DiscussionGuideViewer const handleEditingStateChange = useCallback((editing: boolean) => { setIsEditingGuide(editing); if (!editing && discussionGuideRef.current) { setDiscussionGuide(discussionGuideRef.current); } }, []); // Function to download discussion guide as markdown const handleDownloadDiscussionGuide = useCallback(async () => { if (!discussionGuideRef.current) { toast.error("No discussion guide available", { description: "Please generate a discussion guide first" }); return; } setIsDownloadingGuide(true); try { const { downloadDiscussionGuideAsMarkdown } = await import('@/utils/discussionGuideMarkdown'); const formValues = form.getValues(); downloadDiscussionGuideAsMarkdown(discussionGuideRef.current, formValues.focusGroupName); toast.success("Discussion guide downloaded", { description: "The guide has been saved to your downloads folder" }); } catch (error) { console.error('Error downloading discussion guide:', error); toast.error("Download failed", { description: "Unable to download the discussion guide. Please try again." }); } finally { setIsDownloadingGuide(false); } }, [form]); return { discussionGuide, setDiscussionGuide, discussionGuideRef, isEditingGuide, setIsEditingGuide, guideGenerationState, guideGenerationControls, isGuideProgressModalOpen, setIsGuideProgressModalOpen, isDownloadingGuide, generateDiscussionGuide, handleSaveDiscussionGuide, handleDownloadDiscussionGuide, handleEditingStateChange, handleGuideProgressComplete, isJsonFormat, }; }