This commit is contained in:
James Brunton 2025-09-02 17:49:10 +01:00 committed by GitHub
commit 11a72780da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 969 additions and 48 deletions

View File

@ -24,7 +24,7 @@ indent_size = 2
insert_final_newline = false insert_final_newline = false
trim_trailing_whitespace = false trim_trailing_whitespace = false
[{*.js,*.jsx,*.ts,*.tsx}] [{*.js,*.jsx,*.mjs,*.ts,*.tsx}]
indent_size = 2 indent_size = 2
[*.css] [*.css]

View File

@ -147,6 +147,8 @@ jobs:
cache-dependency-path: frontend/package-lock.json cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies - name: Install frontend dependencies
run: cd frontend && npm ci run: cd frontend && npm ci
- name: Lint frontend
run: cd frontend && npm run lint
- name: Build frontend - name: Build frontend
run: cd frontend && npm run build run: cd frontend && npm run build
- name: Run frontend tests - name: Run frontend tests

View File

@ -19,5 +19,6 @@
"yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing "yzhang.markdown-all-in-one", // Markdown All-in-One extension for enhanced Markdown editing
"stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting "stylelint.vscode-stylelint", // Stylelint extension for CSS and SCSS linting
"redhat.vscode-yaml", // YAML extension for Visual Studio Code "redhat.vscode-yaml", // YAML extension for Visual Studio Code
"dbaeumer.vscode-eslint", // ESLint extension for TypeScript linting
] ]
} }

View File

@ -0,0 +1,31 @@
// @ts-check
import eslint from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
export default defineConfig(
eslint.configs.recommended,
tseslint.configs.recommended,
{
ignores: [
"dist", // Contains 3rd party code
"public", // Contains 3rd party code
],
},
{
rules: {
"no-empty": "off", // Temporarily disabled until codebase conformant
"no-empty-pattern": "off", // Temporarily disabled until codebase conformant
"no-undef": "off", // Temporarily disabled until codebase conformant
"no-useless-escape": "off", // Temporarily disabled until codebase conformant
"prefer-const": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/ban-ts-comment": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-empty-object-type": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-explicit-any": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-require-imports": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-unused-expressions": "off", // Temporarily disabled until codebase conformant
"@typescript-eslint/no-unused-vars": "off", // Temporarily disabled until codebase conformant
},
}
);

File diff suppressed because it is too large Load Diff

View File

@ -40,6 +40,7 @@
"predev": "npm run generate-icons", "predev": "npm run generate-icons",
"dev": "npx tsc --noEmit && vite", "dev": "npx tsc --noEmit && vite",
"prebuild": "npm run generate-icons", "prebuild": "npm run generate-icons",
"lint": "npx eslint",
"build": "npx tsc --noEmit && vite build", "build": "npx tsc --noEmit && vite build",
"preview": "vite preview", "preview": "vite preview",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
@ -72,6 +73,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.34.0",
"@iconify-json/material-symbols": "^1.2.33", "@iconify-json/material-symbols": "^1.2.33",
"@iconify/utils": "^3.0.1", "@iconify/utils": "^3.0.1",
"@playwright/test": "^1.40.0", "@playwright/test": "^1.40.0",
@ -80,6 +82,7 @@
"@types/react-dom": "^19.1.5", "@types/react-dom": "^19.1.5",
"@vitejs/plugin-react": "^4.5.0", "@vitejs/plugin-react": "^4.5.0",
"@vitest/coverage-v8": "^1.0.0", "@vitest/coverage-v8": "^1.0.0",
"eslint": "^9.34.0",
"jsdom": "^23.0.0", "jsdom": "^23.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"madge": "^8.0.0", "madge": "^8.0.0",
@ -87,7 +90,8 @@
"postcss-cli": "^11.0.1", "postcss-cli": "^11.0.1",
"postcss-preset-mantine": "^1.17.0", "postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1", "postcss-simple-vars": "^7.0.1",
"typescript": "^5.8.3", "typescript": "^5.9.2",
"typescript-eslint": "^8.42.0",
"vite": "^6.3.5", "vite": "^6.3.5",
"vitest": "^1.0.0" "vitest": "^1.0.0"
} }

View File

@ -174,7 +174,7 @@ export const useToolOperation = <TParams>(
let processedFiles: File[]; let processedFiles: File[];
switch (config.toolType) { switch (config.toolType) {
case ToolType.singleFile: case ToolType.singleFile: {
// Individual file processing - separate API call per file // Individual file processing - separate API call per file
const apiCallsConfig: ApiCallsConfig<TParams> = { const apiCallsConfig: ApiCallsConfig<TParams> = {
endpoint: config.endpoint, endpoint: config.endpoint,
@ -190,8 +190,9 @@ export const useToolOperation = <TParams>(
actions.setStatus actions.setStatus
); );
break; break;
}
case ToolType.multiFile: case ToolType.multiFile: {
// Multi-file processing - single API call with all files // Multi-file processing - single API call with all files
actions.setStatus('Processing files...'); actions.setStatus('Processing files...');
const formData = config.buildFormData(params, validFiles); const formData = config.buildFormData(params, validFiles);
@ -204,7 +205,7 @@ export const useToolOperation = <TParams>(
// Use custom responseHandler for multi-file (handles ZIP extraction) // Use custom responseHandler for multi-file (handles ZIP extraction)
processedFiles = await config.responseHandler(response.data, validFiles); processedFiles = await config.responseHandler(response.data, validFiles);
} else if (response.data.type === 'application/pdf' || } else if (response.data.type === 'application/pdf' ||
(response.headers && response.headers['content-type'] === 'application/pdf')) { (response.headers && response.headers['content-type'] === 'application/pdf')) {
// Single PDF response (e.g. split with merge option) - use original filename // Single PDF response (e.g. split with merge option) - use original filename
const originalFileName = validFiles[0]?.name || 'document.pdf'; const originalFileName = validFiles[0]?.name || 'document.pdf';
const singleFile = new File([response.data], originalFileName, { type: 'application/pdf' }); const singleFile = new File([response.data], originalFileName, { type: 'application/pdf' });
@ -219,6 +220,7 @@ export const useToolOperation = <TParams>(
} }
} }
break; break;
}
case ToolType.custom: case ToolType.custom:
actions.setStatus('Processing files...'); actions.setStatus('Processing files...');
@ -243,7 +245,7 @@ export const useToolOperation = <TParams>(
// Replace input files with processed files (consumeFiles handles pinning) // Replace input files with processed files (consumeFiles handles pinning)
const inputFileIds: FileId[] = []; const inputFileIds: FileId[] = [];
const inputFileRecords: FileRecord[] = []; const inputFileRecords: FileRecord[] = [];
// Build parallel arrays of IDs and records for undo tracking // Build parallel arrays of IDs and records for undo tracking
for (const file of validFiles) { for (const file of validFiles) {
const fileId = findFileId(file); const fileId = findFileId(file);
@ -259,9 +261,9 @@ export const useToolOperation = <TParams>(
console.warn(`No file ID found for file: ${file.name}`); console.warn(`No file ID found for file: ${file.name}`);
} }
} }
const outputFileIds = await consumeFiles(inputFileIds, processedFiles); const outputFileIds = await consumeFiles(inputFileIds, processedFiles);
// Store operation data for undo (only store what we need to avoid memory bloat) // Store operation data for undo (only store what we need to avoid memory bloat)
lastOperationRef.current = { lastOperationRef.current = {
inputFiles: validFiles, // Keep original File objects for undo inputFiles: validFiles, // Keep original File objects for undo
@ -326,17 +328,17 @@ export const useToolOperation = <TParams>(
try { try {
// Undo the consume operation // Undo the consume operation
await undoConsumeFiles(inputFiles, inputFileRecords, outputFileIds); await undoConsumeFiles(inputFiles, inputFileRecords, outputFileIds);
// Clear results and operation tracking // Clear results and operation tracking
resetResults(); resetResults();
lastOperationRef.current = null; lastOperationRef.current = null;
// Show success message // Show success message
actions.setStatus(t('undoSuccess', 'Operation undone successfully')); actions.setStatus(t('undoSuccess', 'Operation undone successfully'));
} catch (error: any) { } catch (error: any) {
let errorMessage = extractErrorMessage(error); let errorMessage = extractErrorMessage(error);
// Provide more specific error messages based on error type // Provide more specific error messages based on error type
if (error.message?.includes('Mismatch between input files')) { if (error.message?.includes('Mismatch between input files')) {
errorMessage = t('undoDataMismatch', 'Cannot undo: operation data is corrupted'); errorMessage = t('undoDataMismatch', 'Cannot undo: operation data is corrupted');
@ -345,9 +347,9 @@ export const useToolOperation = <TParams>(
} else if (error.name === 'QuotaExceededError') { } else if (error.name === 'QuotaExceededError') {
errorMessage = t('undoQuotaError', 'Cannot undo: insufficient storage space'); errorMessage = t('undoQuotaError', 'Cannot undo: insufficient storage space');
} }
actions.setError(`${t('undoFailed', 'Failed to undo operation')}: ${errorMessage}`); actions.setError(`${t('undoFailed', 'Failed to undo operation')}: ${errorMessage}`);
// Don't clear the operation data if undo failed - user might want to try again // Don't clear the operation data if undo failed - user might want to try again
} }
}, [undoConsumeFiles, resetResults, actions, t]); }, [undoConsumeFiles, resetResults, actions, t]);

View File

@ -182,7 +182,7 @@ export class EnhancedPDFProcessingService {
): Promise<ProcessedFile> { ): Promise<ProcessedFile> {
const arrayBuffer = await file.arrayBuffer(); const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfWorkerManager.createDocument(arrayBuffer); const pdf = await pdfWorkerManager.createDocument(arrayBuffer);
try { try {
const totalPages = pdf.numPages; const totalPages = pdf.numPages;
@ -459,11 +459,12 @@ export class EnhancedPDFProcessingService {
case 'failed': case 'failed':
this.metrics.failedFiles++; this.metrics.failedFiles++;
break; break;
case 'cacheHit': case 'cacheHit': {
// Update cache hit rate // Update cache hit rate
const totalAttempts = this.metrics.totalFiles + 1; const totalAttempts = this.metrics.totalFiles + 1;
this.metrics.cacheHitRate = (this.metrics.cacheHitRate * this.metrics.totalFiles + 1) / totalAttempts; this.metrics.cacheHitRate = (this.metrics.cacheHitRate * this.metrics.totalFiles + 1) / totalAttempts;
break; break;
}
} }
} }

View File

@ -148,15 +148,17 @@ export class FileAnalyzer {
case 'immediate_full': case 'immediate_full':
return pageCount * baseTime; return pageCount * baseTime;
case 'priority_pages': case 'priority_pages': {
// Estimate time for priority pages (first 10) // Estimate time for priority pages (first 10)
const priorityPages = Math.min(pageCount, 10); const priorityPages = Math.min(pageCount, 10);
return priorityPages * baseTime; return priorityPages * baseTime;
}
case 'progressive_chunked': case 'progressive_chunked': {
// Estimate time for first chunk (20 pages) // Estimate time for first chunk (20 pages)
const firstChunk = Math.min(pageCount, 20); const firstChunk = Math.min(pageCount, 20);
return firstChunk * baseTime; return firstChunk * baseTime;
}
default: default:
return pageCount * baseTime; return pageCount * baseTime;

View File

@ -130,7 +130,7 @@ export class PDFExportService {
newDoc.setModificationDate(new Date()); newDoc.setModificationDate(new Date());
const pdfBytes = await newDoc.save(); const pdfBytes = await newDoc.save();
return new Blob([pdfBytes], { type: 'application/pdf' }); return new Blob([pdfBytes as BlobPart], { type: 'application/pdf' });
} }
/** /**
@ -176,7 +176,7 @@ export class PDFExportService {
newDoc.setModificationDate(new Date()); newDoc.setModificationDate(new Date());
const pdfBytes = await newDoc.save(); const pdfBytes = await newDoc.save();
return new Blob([pdfBytes], { type: 'application/pdf' }); return new Blob([pdfBytes as BlobPart], { type: 'application/pdf' });
} }