diff --git a/frontend/src/hooks/useAssistantSession.ts b/frontend/src/hooks/useAssistantSession.ts index b1885c79..d4aa5bd1 100644 --- a/frontend/src/hooks/useAssistantSession.ts +++ b/frontend/src/hooks/useAssistantSession.ts @@ -73,6 +73,8 @@ export function useAssistantSession() { const dragCounterRef = useRef(0) const prefillHandledRef = useRef(false) const currentChatRef = useRef(activeChatId) + const loadingRef = useRef(false) + const initialLoadDoneRef = useRef(false) const toggleSidebarCollapse = () => { const next = !sidebarCollapsed @@ -91,20 +93,22 @@ export function useAssistantSession() { // Load chat list on mount useEffect(() => { loadChats() }, []) - // If URL has a session ID, load it + // Load session data on mount or when URL session changes. + // On initial mount, always load even if activeChatId matches urlSessionId + // (state is empty after view toggle between /assistant and /cockpit). useEffect(() => { - if (urlSessionId && urlSessionId !== activeChatId) { - selectChat(urlSessionId) + if (urlSessionId) { + if (!initialLoadDoneRef.current || urlSessionId !== activeChatId) { + selectChat(urlSessionId) + } + initialLoadDoneRef.current = true + } else if (!initialLoadDoneRef.current && activeChatId) { + // Restore session from sessionStorage on mount (when URL has no session ID) + selectChat(activeChatId) + initialLoadDoneRef.current = true } }, [urlSessionId]) // eslint-disable-line react-hooks/exhaustive-deps - // Restore session from sessionStorage on mount (when URL has no session ID) - useEffect(() => { - if (!urlSessionId && activeChatId) { - selectChat(activeChatId) - } - }, []) // eslint-disable-line react-hooks/exhaustive-deps - // Persist task lane metadata to sessionStorage useEffect(() => { try { @@ -206,6 +210,8 @@ export function useAssistantSession() { }, []) const handleNewChat = async () => { + if (loadingRef.current) return + loadingRef.current = true try { const session = await aiSessionsApi.createChatSession({ intake_type: 'free_text', @@ -228,6 +234,8 @@ export function useAssistantSession() { setActiveActions([]) } catch { toast.error('Failed to create chat') + } finally { + loadingRef.current = false } } @@ -280,7 +288,8 @@ export function useAssistantSession() { const onTriageUpdateRef = useRef<((update: TriageUpdate) => void) | null>(null) const handleSend = async () => { - if (!input.trim() || !activeChatId || loading) return + if (!input.trim() || !activeChatId || loadingRef.current) return + loadingRef.current = true const sendChatId = activeChatId const userMessage = input.trim() @@ -315,6 +324,7 @@ export function useAssistantSession() { { role: 'assistant', content: 'Sorry, something went wrong. Please try again.' }, ]) } finally { + loadingRef.current = false if (currentChatRef.current === sendChatId) { setLoading(false) requestAnimationFrame(() => inputRef.current?.focus()) @@ -333,9 +343,12 @@ export function useAssistantSession() { navigate(location.pathname, { replace: true, state: {} }) const sendPrefill = async () => { + if (loadingRef.current) return + loadingRef.current = true setShowTaskLane(false) setActiveQuestions([]) setActiveActions([]) + setLoading(true) try { const session = await aiSessionsApi.createChatSession({ @@ -355,7 +368,6 @@ export function useAssistantSession() { setChats(prev => [chatItem, ...prev]) setActiveChatId(prefillChatId) setMessages([{ role: 'user', content: prefill }]) - setLoading(true) const response = await aiSessionsApi.sendChatMessage(prefillChatId, { message: prefill, @@ -374,6 +386,7 @@ export function useAssistantSession() { } catch { toast.error('Failed to start AI conversation') } finally { + loadingRef.current = false setLoading(false) } } @@ -401,6 +414,9 @@ export function useAssistantSession() { } const handleResumeNew = async (summary: string) => { + if (loadingRef.current) return + loadingRef.current = true + setLoading(true) try { const resumePrompt = `I'm continuing a previous troubleshooting session. Here's where we left off:\n\n${summary}\n\nPlease review this context and help me continue from where we stopped.` const session = await aiSessionsApi.createChatSession({ @@ -419,7 +435,6 @@ export function useAssistantSession() { setChats(prev => [chatItem, ...prev]) setActiveChatId(session.session_id) setMessages([{ role: 'user', content: resumePrompt }]) - setLoading(true) const resumeChatId = session.session_id const response = await aiSessionsApi.sendChatMessage(resumeChatId, { message: resumePrompt }) @@ -436,6 +451,7 @@ export function useAssistantSession() { } catch { toast.error('Failed to create resume chat') } finally { + loadingRef.current = false setLoading(false) } } @@ -528,7 +544,7 @@ export function useAssistantSession() { handleFileSelect, handleRemoveUpload, retryUpload, toggleSidebarCollapse, handlePrefill, processResponse, // Refs - messagesEndRef, inputRef, fileInputRef, currentChatRef, + messagesEndRef, inputRef, fileInputRef, currentChatRef, loadingRef, // Page-specific callbacks onSessionLoadedRef, onTriageUpdateRef, // Constants diff --git a/frontend/src/pages/CockpitPage.tsx b/frontend/src/pages/CockpitPage.tsx index 63414a2d..a5370f13 100644 --- a/frontend/src/pages/CockpitPage.tsx +++ b/frontend/src/pages/CockpitPage.tsx @@ -99,20 +99,26 @@ export default function CockpitPage() { const handleEvidenceAdd = useCallback(async (text: string, status: EvidenceItem['status']) => { const newItem: EvidenceItem = { text, status } - const updated = [...triageMeta.evidence_items, newItem] - setTriageMeta(prev => ({ ...prev, evidence_items: updated })) + let updated: EvidenceItem[] = [] + setTriageMeta(prev => { + updated = [...prev.evidence_items, newItem] + return { ...prev, evidence_items: updated } + }) if (session.activeChatId) { try { await aiSessionsApi.updateTriage(session.activeChatId, { evidence_items: updated }) } catch { /* best-effort */ } } - }, [session.activeChatId, triageMeta.evidence_items]) + }, [session.activeChatId]) const handleEvidenceEdit = useCallback(async (index: number, text: string, status: EvidenceItem['status']) => { - const updated = triageMeta.evidence_items.map((item, i) => i === index ? { text, status } : item) - setTriageMeta(prev => ({ ...prev, evidence_items: updated })) + let updated: EvidenceItem[] = [] + setTriageMeta(prev => { + updated = prev.evidence_items.map((item, i) => i === index ? { text, status } : item) + return { ...prev, evidence_items: updated } + }) if (session.activeChatId) { try { await aiSessionsApi.updateTriage(session.activeChatId, { evidence_items: updated }) } catch { /* best-effort */ } } - }, [session.activeChatId, triageMeta.evidence_items]) + }, [session.activeChatId]) const handleStepComplete = useCallback((index: number) => { setCompletedSteps(prev => { diff --git a/frontend/src/pages/FlowPilotPage.tsx b/frontend/src/pages/FlowPilotPage.tsx index ece0c041..06e6b007 100644 --- a/frontend/src/pages/FlowPilotPage.tsx +++ b/frontend/src/pages/FlowPilotPage.tsx @@ -20,7 +20,7 @@ export default function FlowPilotPage() { }, []) // eslint-disable-line react-hooks/exhaustive-deps const handleTaskSubmit = async (responses: Array<{ type: string; state: string; value: string; text?: string; label?: string }>) => { - if (!session.activeChatId || session.loading) return + if (!session.activeChatId || session.loading || session.loadingRef.current) return const parts: string[] = [] for (const r of responses) {