From bcab8158ab6cb0dbd77ea5891688ea736941c109 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Sat, 28 Mar 2026 23:05:20 +0000 Subject: [PATCH] feat: add streamDocumentation SSE client for ticket notes Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/api/aiSessions.ts | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/frontend/src/api/aiSessions.ts b/frontend/src/api/aiSessions.ts index 08aae5bc..2877bbb7 100644 --- a/frontend/src/api/aiSessions.ts +++ b/frontend/src/api/aiSessions.ts @@ -99,6 +99,69 @@ export const aiSessionsApi = { return response.data }, + async streamDocumentation( + sessionId: string, + onChunk: (text: string) => void, + onDone: () => void, + onError: (error: string) => void, + ): Promise { + const token = localStorage.getItem('access_token') + const baseUrl = import.meta.env.VITE_API_URL || '' + + try { + const response = await fetch( + `${baseUrl}/api/ai-sessions/${sessionId}/documentation/stream`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ) + + if (!response.ok) { + onError(`HTTP ${response.status}`) + return + } + + const reader = response.body?.getReader() + if (!reader) { + onError('No response body') + return + } + + const decoder = new TextDecoder() + let buffer = '' + + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split('\n') + buffer = lines.pop() || '' + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6) + if (data === '[DONE]') { + onDone() + return + } + if (data.startsWith('[ERROR]')) { + onError(data.slice(8)) + return + } + onChunk(data) + } + } + } + // Stream ended without [DONE] + onDone() + } catch (err) { + onError(err instanceof Error ? err.message : 'Stream failed') + } + }, + async rateSession(sessionId: string, data: { rating: number; feedback?: string }): Promise { await apiClient.post(`/ai-sessions/${sessionId}/rate`, data) },