diff --git a/frontend/src/lib/api-client.ts b/frontend/src/lib/api-client.ts index cef77e2..ef0d8e2 100644 --- a/frontend/src/lib/api-client.ts +++ b/frontend/src/lib/api-client.ts @@ -40,8 +40,10 @@ async function request(method: string, url: string, body?: any, extraIn }) if (!res.ok) { + // Read the body once to avoid "Body has already been consumed" errors on non-JSON responses + const rawBody = await res.text().catch(() => '') let details: any = undefined - try { details = await res.json() } catch { details = await res.text() } + try { details = rawBody ? JSON.parse(rawBody) : undefined } catch { details = rawBody } const status = res.status if (status === 401) throw makeError('Unauthorized', 'UNAUTHORIZED', status, details) if (status === 403) throw makeError('Forbidden', 'FORBIDDEN', status, details) @@ -114,4 +116,3 @@ export const chatbotApi = { }).then(res => res.json()) } } - diff --git a/frontend/src/lib/proxy-auth.ts b/frontend/src/lib/proxy-auth.ts index bbf7109..3b65f23 100644 --- a/frontend/src/lib/proxy-auth.ts +++ b/frontend/src/lib/proxy-auth.ts @@ -19,8 +19,10 @@ export async function proxyRequest(path: string, init?: RequestInit): Promise(response: Response, defaultMessage = 'Request failed'): Promise { if (!response.ok) { + // Read the body once to avoid "Body has already been consumed" when the upstream returns HTML errors + const rawBody = await response.text().catch(() => '') let details: any - try { details = await response.json() } catch { details = await response.text() } + try { details = rawBody ? JSON.parse(rawBody) : undefined } catch { details = rawBody } throw new Error(typeof details === 'string' ? `${defaultMessage}: ${details}` : (details?.error || defaultMessage)) } const contentType = response.headers.get('content-type') || '' @@ -28,4 +30,3 @@ export async function handleProxyResponse(response: Response, defaultMe // @ts-ignore allow non-json return (await response.text()) as T } -