Spaces:
Paused
Paused
| /** | |
| * Utility to map backend English error messages to i18n keys. | |
| */ | |
| export const ERROR_MAP: Record<string, string> = { | |
| "Notebook not found": "apiErrors.notebookNotFound", | |
| "Source not found": "apiErrors.sourceNotFound", | |
| "Transformation not found": "apiErrors.transformationNotFound", | |
| "File upload failed": "apiErrors.fileUploadFailed", | |
| "URL is required for link type": "apiErrors.urlRequired", | |
| "Content is required for text type": "apiErrors.contentRequired", | |
| "Invalid source type": "apiErrors.invalidSourceType", | |
| "Processing failed": "apiErrors.processingFailed", | |
| "Failed to queue processing": "apiErrors.failedToQueue", | |
| "sort_by must be 'created' or 'updated'": "apiErrors.invalidSortBy", | |
| "sort_order must be 'asc' or 'desc'": "apiErrors.invalidSortOrder", | |
| "Access to file denied": "apiErrors.accessDenied", | |
| "File not found on server": "apiErrors.fileNotFoundOnServer", | |
| "Missing authorization": "apiErrors.unauthorized", | |
| "Invalid password": "apiErrors.invalidPassword", | |
| "Invalid authorization header format": "apiErrors.unauthorized", | |
| "Missing authorization header": "apiErrors.unauthorized", | |
| "Vector search requires an embedding model": "apiErrors.embeddingModelRequired", | |
| "Ask feature requires an embedding model": "apiErrors.embeddingModelRequired", | |
| "Strategy model": "apiErrors.strategyModelNotFound", | |
| "Answer model": "apiErrors.answerModelNotFound", | |
| "Final answer model": "apiErrors.finalAnswerModelNotFound", | |
| "No answer generated": "apiErrors.noAnswerGenerated", | |
| }; | |
| /** | |
| * Translates a backend error message using the ERROR_MAP. | |
| * If no mapping exists, returns the fallback key or generic error key. | |
| */ | |
| export function getApiErrorKey(errorOrMessage: unknown, fallbackKey?: string): string { | |
| const message = formatApiError(errorOrMessage); | |
| if (!message) return fallbackKey || "apiErrors.genericError"; | |
| // Try exact match first | |
| if (ERROR_MAP[message]) { | |
| return ERROR_MAP[message]; | |
| } | |
| // Try partial match for dynamic messages (e.g., "File upload failed: ...") | |
| for (const [key, value] of Object.entries(ERROR_MAP)) { | |
| if (message.startsWith(key)) { | |
| return value; | |
| } | |
| } | |
| return fallbackKey || "apiErrors.genericError"; | |
| } | |
| /** | |
| * Extracts the error message, looks up i18n mapping, and falls back to the | |
| * backend-provided message when no mapping exists. This ensures user-friendly | |
| * error messages from the backend are displayed directly in the UI. | |
| */ | |
| export function getApiErrorMessage( | |
| errorOrMessage: unknown, | |
| t: (key: string) => string, | |
| fallbackKey?: string | |
| ): string { | |
| const message = formatApiError(errorOrMessage); | |
| if (!message) return fallbackKey ? t(fallbackKey) : t("apiErrors.genericError"); | |
| // Try exact match | |
| if (ERROR_MAP[message]) return t(ERROR_MAP[message]); | |
| // Try partial match for dynamic messages (e.g., "Strategy model ...") | |
| for (const [key, value] of Object.entries(ERROR_MAP)) { | |
| if (message.startsWith(key)) return t(value); | |
| } | |
| // No mapping: return backend message directly (backend is responsible for making it user-friendly) | |
| return message; | |
| } | |
| /** | |
| * Formats a raw error from the API into a user-friendly (potentially translated) string. | |
| */ | |
| export function formatApiError(error: unknown): string { | |
| if (typeof error === 'string') return error; | |
| const err = error as { response?: { data?: { detail?: string } }, detail?: string, message?: string }; | |
| const detail = err?.response?.data?.detail || err?.detail || err?.message; | |
| if (typeof detail === 'string') { | |
| return detail; // We'll handle the actual translation using the key in the hook/component | |
| } | |
| return "An unexpected error occurred"; | |
| } | |