Skip to content

Commit 5503622

Browse files
committed
feat: add timeout error handling in Chat
1 parent b86f08e commit 5503622

2 files changed

Lines changed: 62 additions & 0 deletions

File tree

src/components/Chat.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ const getEventKey = (event: StreamEvent | Message, idx: number): string => {
168168
return `message-${'id' in event ? (event as any).id : idx}`
169169
}
170170

171+
const TIMEOUT_ERROR_MESSAGE =
172+
'Your connection was interrupted after 30 seconds, possibly due to a Pomerium proxy timeout.'
173+
171174
export function Chat() {
172175
const hasMounted = useHasMounted()
173176
const messagesEndRef = useRef<HTMLDivElement>(null)
@@ -181,6 +184,8 @@ export function Chat() {
181184
const [useWebSearch, setUseWebSearch] = useState(false)
182185
const { selectedModel, setSelectedModel } = useModel()
183186
const { user } = useUser()
187+
const [requestId, setRequestId] = useState<string | null>(null)
188+
const [timedOut, setTimedOut] = useState(false)
184189

185190
const handleModelChange = (newModel: string) => {
186191
setSelectedModel(newModel)
@@ -321,10 +326,15 @@ export function Chat() {
321326
},
322327
])
323328
setStreaming(false)
329+
setTimedOut(false)
324330
}, [])
325331

326332
const handleResponse = useCallback(
327333
(response: Response) => {
334+
// Extract x-request-id header for timeout troubleshooting
335+
const xRequestId = response.headers.get('x-request-id')
336+
setRequestId(xRequestId)
337+
328338
if (!response.ok) {
329339
console.error(
330340
'Chat response error:',
@@ -339,10 +349,12 @@ export function Chat() {
339349
},
340350
])
341351
setStreaming(false)
352+
setTimedOut(false)
342353
return
343354
}
344355

345356
setStreaming(true)
357+
setTimedOut(false)
346358

347359
// Clone the response to handle our custom streaming while letting useChat handle its own
348360
const reader = response.clone().body?.getReader()
@@ -359,12 +371,14 @@ export function Chat() {
359371
},
360372
])
361373
setStreaming(false)
374+
setTimedOut(false)
362375
return
363376
}
364377

365378
const decoder = new TextDecoder()
366379
let buffer = ''
367380
let assistantId: string | null = null
381+
let receivedCompletion = false
368382

369383
const processChunk = (line: string) => {
370384
if (line.startsWith('e:')) {
@@ -407,6 +421,11 @@ export function Chat() {
407421
if (toolState.type === 'tool_call_completed') {
408422
return
409423
}
424+
// Handle stream_done event (signals end of stream)
425+
if (toolState.type === 'stream_done') {
426+
receivedCompletion = true
427+
return
428+
}
410429

411430
// Handle reasoning summary streaming
412431
if (toolState.type === 'reasoning_summary_delta') {
@@ -766,6 +785,10 @@ export function Chat() {
766785
// Flush any remaining text buffer
767786
flushTextBuffer()
768787
setStreaming(false)
788+
// If stream ended but we did not receive a completion event, treat as timeout
789+
if (!receivedCompletion) {
790+
setTimedOut(true)
791+
}
769792
return
770793
}
771794

@@ -848,6 +871,7 @@ export function Chat() {
848871

849872
const handleSendMessage = useCallback(
850873
(prompt: string) => {
874+
setTimedOut(false)
851875
if (!hasStartedChat) {
852876
setHasStartedChat(true)
853877
}
@@ -1048,6 +1072,40 @@ export function Chat() {
10481072
}
10491073
})}
10501074
{streaming && <BotThinking />}
1075+
{timedOut && (
1076+
<BotError
1077+
key="timeout-error"
1078+
message={
1079+
<div className="grid gap-2">
1080+
<p>{TIMEOUT_ERROR_MESSAGE}</p>
1081+
<p>
1082+
See the Pomerium{' '}
1083+
<a
1084+
href="https://www.pomerium.com/docs/reference/routes/timeouts"
1085+
target="_blank"
1086+
rel="noopener noreferrer"
1087+
className="underline"
1088+
>
1089+
Timeouts Settings documentation
1090+
</a>{' '}
1091+
for more information. .
1092+
</p>
1093+
{requestId && (
1094+
<>
1095+
<hr />
1096+
<dl>
1097+
<dt>Request ID</dt>
1098+
<dd>{requestId}</dd>
1099+
</dl>
1100+
<p>
1101+
Use this ID to search Pomerium logs for more details.
1102+
</p>
1103+
</>
1104+
)}
1105+
</div>
1106+
}
1107+
/>
1108+
)}
10511109
<div ref={messagesEndRef} />
10521110
</div>
10531111
</div>

src/lib/streaming.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,10 @@ export function streamText(
357357

358358
// Flush any remaining content
359359
flush()
360+
// Emit a final done event to signal successful completion
361+
controller.enqueue(
362+
encoder.encode(`t:${JSON.stringify({ type: 'stream_done' })}\n`),
363+
)
360364
controller.close()
361365
} catch (error: unknown) {
362366
console.error('Error during streamed response:', error)

0 commit comments

Comments
 (0)