|
| 1 | +import { useState, useCallback } from "react"; |
| 2 | +import { Resource } from "@/types/resources"; |
| 3 | +import { |
| 4 | + ActionPlan, |
| 5 | + PartialActionPlan, |
| 6 | + fetchActionPlanStreaming, |
| 7 | +} from "@/util/fetchActionPlan"; |
| 8 | + |
| 9 | +export interface UseActionPlanStreamingReturn { |
| 10 | + isGeneratingActionPlan: boolean; |
| 11 | + isStreamingActionPlan: boolean; |
| 12 | + streamingPlan: PartialActionPlan | null; |
| 13 | + actionPlan: ActionPlan | null; |
| 14 | + errorMessage: string | undefined; |
| 15 | + generateActionPlan: ( |
| 16 | + selectedResources: Resource[], |
| 17 | + userEmail: string, |
| 18 | + clientDescription: string, |
| 19 | + ) => Promise<{ |
| 20 | + actionPlan: ActionPlan | null; |
| 21 | + resultId: string; |
| 22 | + errorMessage?: string; |
| 23 | + }>; |
| 24 | + setErrorMessage: (error: string | undefined) => void; |
| 25 | + clearActionPlan: () => void; |
| 26 | +} |
| 27 | + |
| 28 | +/** |
| 29 | + * Custom hook to handle action plan streaming with SSE |
| 30 | + */ |
| 31 | +export function useActionPlanStreaming(): UseActionPlanStreamingReturn { |
| 32 | + const [isGeneratingActionPlan, setIsGeneratingActionPlan] = useState(false); |
| 33 | + const [isStreamingActionPlan, setIsStreamingActionPlan] = useState(false); |
| 34 | + const [streamingPlan, setStreamingPlan] = useState<PartialActionPlan | null>( |
| 35 | + null, |
| 36 | + ); |
| 37 | + const [actionPlan, setActionPlan] = useState<ActionPlan | null>(null); |
| 38 | + const [errorMessage, setErrorMessage] = useState<string | undefined>( |
| 39 | + undefined, |
| 40 | + ); |
| 41 | + |
| 42 | + const generateActionPlan = useCallback( |
| 43 | + async ( |
| 44 | + selectedResources: Resource[], |
| 45 | + userEmail: string, |
| 46 | + clientDescription: string, |
| 47 | + ) => { |
| 48 | + if (selectedResources.length === 0) { |
| 49 | + return { |
| 50 | + actionPlan: null, |
| 51 | + resultId: "", |
| 52 | + errorMessage: "No resources selected", |
| 53 | + }; |
| 54 | + } |
| 55 | + |
| 56 | + setIsGeneratingActionPlan(true); |
| 57 | + setIsStreamingActionPlan(true); |
| 58 | + setActionPlan(null); |
| 59 | + setStreamingPlan(null); |
| 60 | + setErrorMessage(undefined); |
| 61 | + |
| 62 | + try { |
| 63 | + const { |
| 64 | + actionPlan: finalPlan, |
| 65 | + resultId, |
| 66 | + errorMessage: planError, |
| 67 | + } = await fetchActionPlanStreaming( |
| 68 | + selectedResources, |
| 69 | + userEmail, |
| 70 | + clientDescription, |
| 71 | + // onChunk callback - receive structured partial plan |
| 72 | + (partialPlan: PartialActionPlan) => { |
| 73 | + setStreamingPlan(partialPlan); |
| 74 | + }, |
| 75 | + // onComplete callback - stop streaming UI |
| 76 | + () => { |
| 77 | + setIsStreamingActionPlan(false); |
| 78 | + }, |
| 79 | + // onError callback - clear content and show error |
| 80 | + (error: string) => { |
| 81 | + setIsStreamingActionPlan(false); |
| 82 | + setIsGeneratingActionPlan(false); |
| 83 | + setStreamingPlan(null); |
| 84 | + setActionPlan(null); |
| 85 | + setErrorMessage(error); |
| 86 | + }, |
| 87 | + ); |
| 88 | + |
| 89 | + // Handle errors from the streaming response |
| 90 | + if (planError) { |
| 91 | + setErrorMessage(planError); |
| 92 | + setStreamingPlan(null); |
| 93 | + setActionPlan(null); |
| 94 | + return { |
| 95 | + actionPlan: null, |
| 96 | + resultId: "", |
| 97 | + errorMessage: planError, |
| 98 | + }; |
| 99 | + } |
| 100 | + |
| 101 | + // Set the final parsed action plan |
| 102 | + if (finalPlan) { |
| 103 | + setActionPlan(finalPlan); |
| 104 | + setStreamingPlan(null); // Clear streaming state |
| 105 | + return { |
| 106 | + actionPlan: finalPlan, |
| 107 | + resultId, |
| 108 | + }; |
| 109 | + } else { |
| 110 | + const errorMsg = |
| 111 | + "There was an issue streaming the Action Plan. Please try again."; |
| 112 | + setErrorMessage(errorMsg); |
| 113 | + return { |
| 114 | + actionPlan: null, |
| 115 | + resultId: "", |
| 116 | + errorMessage: errorMsg, |
| 117 | + }; |
| 118 | + } |
| 119 | + } catch (error) { |
| 120 | + console.error("Error generating action plan:", error); |
| 121 | + setIsStreamingActionPlan(false); |
| 122 | + setIsGeneratingActionPlan(false); |
| 123 | + setStreamingPlan(null); |
| 124 | + setActionPlan(null); |
| 125 | + const errorMsg = |
| 126 | + "There was an issue streaming the Action Plan. Please try again."; |
| 127 | + setErrorMessage(errorMsg); |
| 128 | + return { |
| 129 | + actionPlan: null, |
| 130 | + resultId: "", |
| 131 | + errorMessage: errorMsg, |
| 132 | + }; |
| 133 | + } finally { |
| 134 | + setIsGeneratingActionPlan(false); |
| 135 | + } |
| 136 | + }, |
| 137 | + [], |
| 138 | + ); |
| 139 | + |
| 140 | + const clearActionPlan = useCallback(() => { |
| 141 | + setActionPlan(null); |
| 142 | + setStreamingPlan(null); |
| 143 | + setIsStreamingActionPlan(false); |
| 144 | + setIsGeneratingActionPlan(false); |
| 145 | + setErrorMessage(undefined); |
| 146 | + }, []); |
| 147 | + |
| 148 | + return { |
| 149 | + isGeneratingActionPlan, |
| 150 | + isStreamingActionPlan, |
| 151 | + streamingPlan, |
| 152 | + actionPlan, |
| 153 | + errorMessage, |
| 154 | + generateActionPlan, |
| 155 | + setErrorMessage, |
| 156 | + clearActionPlan, |
| 157 | + }; |
| 158 | +} |
0 commit comments