Compare commits

...
8 changed files with 88 additions and 37 deletions
+2 -1
View File
@@ -11,7 +11,8 @@
"Bash(npm test:*)",
"Bash(ls:*)",
"Bash(npx tsc:*)",
"Bash(sed:*)"
"Bash(npm run build:*)"
],
"deny": []
}
+5 -4
View File
@@ -39,7 +39,7 @@
},
"devDependencies": {
"@playwright/test": "^1.40.0",
"@types/node": "^24.2.0",
"@types/node": "^24.2.1",
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"@vitejs/plugin-react": "^4.5.0",
@@ -2386,10 +2386,11 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz",
"integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==",
"version": "24.2.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz",
"integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.10.0"
}
+1 -1
View File
@@ -65,7 +65,7 @@
},
"devDependencies": {
"@playwright/test": "^1.40.0",
"@types/node": "^24.2.0",
"@types/node": "^24.2.1",
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"@vitejs/plugin-react": "^4.5.0",
@@ -5,6 +5,7 @@ import { ConvertParameters } from './useConvertParameters';
import { detectFileExtension } from '../../../utils/fileUtils';
import { createFileFromApiResponse } from '../../../utils/fileResponseUtils';
import { useToolOperation, ToolOperationConfig } from '../shared/useToolOperation';
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
import { getEndpointUrl, isImageFormat, isWebFormat } from '../../../utils/convertUtils';
const shouldProcessFilesSeparately = (
@@ -134,14 +135,6 @@ export const useConvertOperation = () => {
buildFormData, // Not used with customProcessor but required
filePrefix: 'converted_',
customProcessor: customConvertProcessor, // Convert handles its own routing
getErrorMessage: (error) => {
if (error.response?.data && typeof error.response.data === 'string') {
return error.response.data;
}
if (error.message) {
return error.message;
}
return t("convert.errorConversion", "An error occurred while converting the file.");
}
getErrorMessage: createStandardErrorHandler(t('convert.errorConversion', 'An error occurred while converting the file.'))
});
};
@@ -103,10 +103,9 @@ export const useOCROperation = () => {
filePrefix: 'ocr_',
multiFileEndpoint: false, // Process files individually
responseHandler, // use shared flow
getErrorMessage: (error) =>
error.message?.includes('OCR tools') && error.message?.includes('not installed')
? 'OCR tools (OCRmyPDF or Tesseract) are not installed on the server. Use the standard or fat Docker image instead of ultra-lite, or install OCR tools manually.'
: createStandardErrorHandler(t('ocr.error.failed', 'OCR operation failed'))(error),
getErrorMessage: (error) => {
return createStandardErrorHandler(t('ocr.error.failed', 'OCR operation failed'))(error);
},
};
return useToolOperation(ocrConfig);
@@ -5,7 +5,7 @@ import { useFileContext } from '../../../contexts/FileContext';
import { useToolState, type ProcessingProgress } from './useToolState';
import { useToolApiCalls, type ApiCallsConfig } from './useToolApiCalls';
import { useToolResources } from './useToolResources';
import { extractErrorMessage } from '../../../utils/toolErrorHandler';
import { extractErrorMessage, type ToolError } from '../../../utils/toolErrorHandler';
import { createOperation } from '../../../utils/toolOperationTracker';
import { ResponseHandler } from '../../../utils/toolResponseProcessor';
@@ -60,7 +60,7 @@ export interface ToolOperationConfig<TParams = void> {
customProcessor?: (params: TParams, files: File[]) => Promise<File[]>;
/** Extract user-friendly error messages from API errors */
getErrorMessage?: (error: any) => string;
getErrorMessage?: (error: unknown) => string;
}
/**
@@ -204,7 +204,7 @@ export const useToolOperation = <TParams = void>(
markOperationApplied(fileId, operationId);
}
} catch (error: any) {
} catch (error: unknown) {
const errorMessage = config.getErrorMessage?.(error) || extractErrorMessage(error);
actions.setError(errorMessage);
actions.setStatus('');
+71 -14
View File
@@ -2,16 +2,77 @@
* Standardized error handling utilities for tool operations
*/
/**
* Standard error type that covers common error patterns
*/
export interface ToolError {
message?: string;
response?: {
data?: string | unknown;
status?: number;
};
}
/**
* Extract error message from JSON response data
*/
const extractFromJsonData = (data: unknown): string | null => {
if (typeof data === 'object' && data !== null) {
const obj = data as Record<string, unknown>;
// Common JSON error patterns
if (typeof obj.message === 'string' && obj.message.trim()) {
return obj.message.trim();
}
if (typeof obj.error === 'string' && obj.error.trim()) {
return obj.error.trim();
}
if (typeof obj.detail === 'string' && obj.detail.trim()) {
return obj.detail.trim();
}
}
return null;
};
/**
* Default error extractor that follows the standard pattern
*/
export const extractErrorMessage = (error: any): string => {
if (error.response?.data && typeof error.response.data === 'string') {
return error.response.data;
export const extractErrorMessage = (error: unknown): string => {
// Early return if error is null/undefined
if (!error || (typeof error !== 'object')) {
return 'Operation failed';
}
if (error.message) {
return error.message;
const typedError = error as ToolError;
// Try response.data first
if (typedError.response?.data) {
// Handle string response.data
if (typeof typedError.response.data === 'string' && typedError.response.data.trim()) {
return typedError.response.data.trim();
}
// Handle JSON response.data
const jsonMessage = extractFromJsonData(typedError.response.data);
if (jsonMessage) {
return jsonMessage;
}
// Handle Blob or other non-string data gracefully
if (typedError.response.data instanceof Blob) {
return 'Server returned an error response';
}
}
// Fallback to error.message
if (typedError.message && typedError.message.trim()) {
return typedError.message.trim();
}
// Add HTTP status context if available
if (typedError.response?.status) {
return `Server error (${typedError.response.status})`;
}
return 'Operation failed';
};
@@ -21,13 +82,9 @@ export const extractErrorMessage = (error: any): string => {
* @returns Error handler function that follows the standard pattern
*/
export const createStandardErrorHandler = (fallbackMessage: string) => {
return (error: any): string => {
if (error.response?.data && typeof error.response.data === 'string') {
return error.response.data;
}
if (error.message) {
return error.message;
}
return fallbackMessage;
return (error: unknown): string => {
const message = extractErrorMessage(error);
return message === 'Operation failed' ? fallbackMessage : message;
};
};
};
+1 -1
View File
@@ -12,7 +12,7 @@
/* Language and Environment */
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"lib": ["es2022", "dom", "dom.iterable"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react-jsx", /* Specify what JSX code is generated. */
// "libReplacement": true, /* Enable lib replacement. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */