mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-13 22:18:51 +01:00
🛠️ fix: UI/UX for Known Server-sent Errors (#11343)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
This commit is contained in:
parent
a95fea19bb
commit
9d5e80d7a3
1 changed files with 66 additions and 7 deletions
|
|
@ -4,11 +4,13 @@ import { SSE } from 'sse.js';
|
|||
import { useSetRecoilState } from 'recoil';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
apiBaseUrl,
|
||||
request,
|
||||
Constants,
|
||||
QueryKeys,
|
||||
ErrorTypes,
|
||||
apiBaseUrl,
|
||||
createPayload,
|
||||
ViolationTypes,
|
||||
LocalStorageKeys,
|
||||
removeNullishValues,
|
||||
} from 'librechat-data-provider';
|
||||
|
|
@ -334,8 +336,11 @@ export default function useResumableSSE(
|
|||
});
|
||||
|
||||
/**
|
||||
* Error event - fired on actual network failures (non-200, connection lost, etc.)
|
||||
* This should trigger reconnection with exponential backoff, except for 404 errors.
|
||||
* Error event handler - handles BOTH:
|
||||
* 1. HTTP-level errors (responseCode present) - 404, 401, network failures
|
||||
* 2. Server-sent error events (event: error with data) - known errors like ViolationTypes/ErrorTypes
|
||||
*
|
||||
* Order matters: check responseCode first since HTTP errors may also include data
|
||||
*/
|
||||
sse.addEventListener('error', async (e: MessageEvent) => {
|
||||
(startupConfig?.balance?.enabled ?? false) && balanceQuery.refetch();
|
||||
|
|
@ -347,7 +352,6 @@ export default function useResumableSSE(
|
|||
if (responseCode === 404) {
|
||||
console.log('[ResumableSSE] Stream not found (404) - job completed or expired');
|
||||
sse.close();
|
||||
// Optimistically remove from active jobs since job is gone
|
||||
removeActiveJob(currentStreamId);
|
||||
setIsSubmitting(false);
|
||||
setShowStopButton(false);
|
||||
|
|
@ -356,8 +360,6 @@ export default function useResumableSSE(
|
|||
return;
|
||||
}
|
||||
|
||||
console.log('[ResumableSSE] Stream error (network failure) - will attempt reconnect');
|
||||
|
||||
// Check for 401 and try to refresh token (same pattern as useSSE)
|
||||
if (responseCode === 401) {
|
||||
try {
|
||||
|
|
@ -366,7 +368,6 @@ export default function useResumableSSE(
|
|||
if (!newToken) {
|
||||
throw new Error('Token refresh failed.');
|
||||
}
|
||||
// Update headers on same SSE instance and retry (like useSSE)
|
||||
sse.headers = {
|
||||
Authorization: `Bearer ${newToken}`,
|
||||
};
|
||||
|
|
@ -378,6 +379,64 @@ export default function useResumableSSE(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Server-sent error event (event: error with data) - no responseCode.
|
||||
* These are known errors (ErrorTypes, ViolationTypes) that should be displayed to user.
|
||||
* Only check e.data if there's no HTTP responseCode, since HTTP errors may also have body data.
|
||||
*/
|
||||
if (!responseCode && e.data) {
|
||||
console.log('[ResumableSSE] Server-sent error event received:', e.data);
|
||||
sse.close();
|
||||
removeActiveJob(currentStreamId);
|
||||
|
||||
try {
|
||||
const errorData = JSON.parse(e.data);
|
||||
const errorString = errorData.error ?? errorData.message ?? JSON.stringify(errorData);
|
||||
|
||||
// Check if it's a known error type (ViolationTypes or ErrorTypes)
|
||||
let isKnownError = false;
|
||||
try {
|
||||
const parsed =
|
||||
typeof errorString === 'string' ? JSON.parse(errorString) : errorString;
|
||||
const errorType = parsed?.type ?? parsed?.code;
|
||||
if (errorType) {
|
||||
const violationValues = Object.values(ViolationTypes) as string[];
|
||||
const errorTypeValues = Object.values(ErrorTypes) as string[];
|
||||
isKnownError =
|
||||
violationValues.includes(errorType) || errorTypeValues.includes(errorType);
|
||||
}
|
||||
} catch {
|
||||
// Not JSON or parsing failed - treat as generic error
|
||||
}
|
||||
|
||||
console.log('[ResumableSSE] Error type check:', { isKnownError, errorString });
|
||||
|
||||
// Display the error to user via errorHandler
|
||||
errorHandler({
|
||||
data: { text: errorString } as unknown as Parameters<typeof errorHandler>[0]['data'],
|
||||
submission: currentSubmission as EventSubmission,
|
||||
});
|
||||
} catch (parseError) {
|
||||
console.error('[ResumableSSE] Failed to parse server error:', parseError);
|
||||
errorHandler({
|
||||
data: { text: e.data } as unknown as Parameters<typeof errorHandler>[0]['data'],
|
||||
submission: currentSubmission as EventSubmission,
|
||||
});
|
||||
}
|
||||
|
||||
setIsSubmitting(false);
|
||||
setShowStopButton(false);
|
||||
setStreamId(null);
|
||||
reconnectAttemptRef.current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Network failure or unknown HTTP error - attempt reconnection with backoff
|
||||
console.log('[ResumableSSE] Stream error (network failure) - will attempt reconnect', {
|
||||
responseCode,
|
||||
hasData: !!e.data,
|
||||
});
|
||||
|
||||
if (reconnectAttemptRef.current < MAX_RETRIES) {
|
||||
// Increment counter BEFORE close() so abort handler knows we're reconnecting
|
||||
reconnectAttemptRef.current++;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue