refactor(chat): replace effect-based auth redirect with render navigation and remove unused idle-finish hook

This commit is contained in:
ibelick
2026-02-15 09:28:39 +01:00
parent b47d3cfb77
commit e31a2e0a57
3 changed files with 22 additions and 119 deletions
+16 -22
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate } from '@tanstack/react-router'
import { Navigate, useNavigate } from '@tanstack/react-router'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
@@ -38,7 +38,7 @@ import { useChatSessions } from './hooks/use-chat-sessions'
import { useChatStream } from './hooks/use-chat-stream'
import { useChatPendingSend } from './hooks/use-chat-pending-send'
import { useChatGenerationGuard } from './hooks/use-chat-generation-guard'
import { useChatErrorState } from './hooks/use-chat-error-state'
import { shouldRedirectToConnect } from './hooks/use-chat-error-state'
import { useChatRedirect } from './hooks/use-chat-redirect'
import type { AttachmentFile } from '@/components/attachment-button'
import type { ChatComposerHelpers } from './components/chat-composer'
@@ -66,7 +66,6 @@ export function ChatScreen({
const queryClient = useQueryClient()
const [sending, setSending] = useState(false)
const [creatingSession, setCreatingSession] = useState(false)
const [error, setError] = useState<string | null>(null)
const [isRedirecting, setIsRedirecting] = useState(false)
const { headerRef, composerRef, mainRef, pinGroupMinHeight, headerHeight } =
useChatMeasurements()
@@ -144,7 +143,6 @@ export function ChatScreen({
}, [gatewayStatusQuery])
const isSidebarCollapsed = uiQuery.data.isSidebarCollapsed
const handleActiveSessionDelete = useCallback(() => {
setError(null)
setIsRedirecting(true)
navigate({ to: '/new', replace: true })
}, [navigate])
@@ -253,7 +251,6 @@ export function ChatScreen({
setPendingGeneration(true)
setSending(true)
setError(null)
setWaitingForResponse(true)
setPinToTop(true)
@@ -304,7 +301,6 @@ export function ChatScreen({
},
)
}
setError(`Failed to send message. ${messageText}`)
setPendingGeneration(false)
setWaitingForResponse(false)
setPinToTop(false)
@@ -383,7 +379,7 @@ export function ChatScreen({
replace: true,
})
})
.catch((err: unknown) => {
.catch(() => {
removeHistoryMessageByClientId(
queryClient,
'new',
@@ -392,9 +388,6 @@ export function ChatScreen({
optimisticId,
)
helpers.setValue(body)
setError(
`Failed to create session. ${err instanceof Error ? err.message : String(err)}`,
)
setPendingGeneration(false)
setWaitingForResponse(false)
setPinToTop(false)
@@ -460,6 +453,15 @@ export function ChatScreen({
const showGatewayNotice =
showGatewayDown &&
gatewayStatusQuery.errorUpdatedAt > gatewayStatusMountRef.current
const redirectToConnect = shouldRedirectToConnect({
isRedirecting,
shouldRedirectToNew,
sessionsReady: sessionsQuery.isSuccess,
activeExists,
sessionsError,
historyError,
gatewayStatusError,
})
const historyEmpty = !historyLoading && displayMessages.length === 0
const gatewayNotice = useMemo(() => {
if (!showGatewayNotice) return null
@@ -512,18 +514,6 @@ export function ChatScreen({
},
})
useChatErrorState({
error,
setError,
isRedirecting,
shouldRedirectToNew,
sessionsReady: sessionsQuery.isSuccess,
activeExists,
sessionsError,
historyError,
gatewayStatusError,
})
useChatRedirect({
activeFriendlyId,
isNewChat,
@@ -567,6 +557,10 @@ export function ChatScreen({
/>
)
if (redirectToConnect) {
return <Navigate to="/connect" replace />
}
return (
<div className="h-screen bg-surface text-primary-900">
<div
@@ -1,11 +1,6 @@
import { useEffect } from 'react'
import { useNavigate } from '@tanstack/react-router'
import { isMissingGatewayAuth } from '../utils'
type UseChatErrorStateInput = {
error: string | null
setError: (value: string | null) => void
isRedirecting: boolean
shouldRedirectToNew: boolean
sessionsReady: boolean
@@ -15,9 +10,7 @@ type UseChatErrorStateInput = {
gatewayStatusError: string | null
}
export function useChatErrorState({
error,
setError,
export function shouldRedirectToConnect({
isRedirecting,
shouldRedirectToNew,
sessionsReady,
@@ -26,49 +19,9 @@ export function useChatErrorState({
historyError,
gatewayStatusError,
}: UseChatErrorStateInput) {
const navigate = useNavigate()
useEffect(() => {
if (isRedirecting) {
if (error) setError(null)
return
}
if (shouldRedirectToNew) {
if (error) setError(null)
return
}
if (sessionsReady && !activeExists) {
if (error) setError(null)
return
}
const messageText = sessionsError ?? historyError ?? gatewayStatusError
if (!messageText) {
if (error?.startsWith('Failed to load')) {
setError(null)
}
return
}
if (isMissingGatewayAuth(messageText)) {
navigate({ to: '/connect', replace: true })
}
const message = sessionsError
? `Failed to load sessions. ${sessionsError}`
: historyError
? `Failed to load history. ${historyError}`
: gatewayStatusError
? `Gateway unavailable. ${gatewayStatusError}`
: null
if (message) setError(message)
}, [
activeExists,
error,
gatewayStatusError,
historyError,
isRedirecting,
navigate,
sessionsError,
sessionsReady,
setError,
shouldRedirectToNew,
])
if (isRedirecting || shouldRedirectToNew) return false
if (sessionsReady && !activeExists) return false
const messageText = sessionsError ?? historyError ?? gatewayStatusError
if (!messageText) return false
return isMissingGatewayAuth(messageText)
}
@@ -1,44 +0,0 @@
import { useEffect, useRef } from 'react'
import { textFromMessage } from '../utils'
import { setPendingGeneration } from '../pending-send'
import type { GatewayMessage } from '../types'
type UseChatIdleFinishInput = {
historyMessages: Array<GatewayMessage>
streamStop: () => void
setWaitingForResponse: (value: boolean) => void
}
export function useChatIdleFinish({
historyMessages,
streamStop,
setWaitingForResponse,
}: UseChatIdleFinishInput) {
const lastAssistantSignature = useRef('')
const streamIdleTimer = useRef<number | null>(null)
useEffect(() => {
if (historyMessages.length === 0) return
const latestMessage = historyMessages[historyMessages.length - 1]
if (latestMessage.role !== 'assistant') return
const signature = `${historyMessages.length}:${textFromMessage(latestMessage).slice(-64)}`
if (signature !== lastAssistantSignature.current) {
lastAssistantSignature.current = signature
if (streamIdleTimer.current) {
window.clearTimeout(streamIdleTimer.current)
}
streamIdleTimer.current = window.setTimeout(() => {
streamStop()
setPendingGeneration(false)
setWaitingForResponse(false)
}, 12000)
}
return () => {
if (streamIdleTimer.current) {
window.clearTimeout(streamIdleTimer.current)
streamIdleTimer.current = null
}
}
}, [historyMessages, setWaitingForResponse, streamStop])
}