Skip to content

Commit 364544f

Browse files
committed
update phase 2
1 parent ca2243b commit 364544f

87 files changed

Lines changed: 811 additions & 9455 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
import { SupportedFileTypes, InputFile, BulkProcessingOptions, BulkProcessingResult, DocumentConversionOptions } from './types';
2+
3+
/**
4+
* Supported file types and their extensions
5+
*/
6+
export const SUPPORTED_FILE_TYPES: SupportedFileTypes = {
7+
images: {
8+
'image/jpeg': ['.jpg', '.jpeg'],
9+
'image/png': ['.png'],
10+
'image/webp': ['.webp'],
11+
'image/tiff': ['.tiff', '.tif'],
12+
'image/heic': ['.heic'],
13+
'image/heif': ['.heif'],
14+
},
15+
pdf: {
16+
'application/pdf': ['.pdf'],
17+
},
18+
documents: {
19+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
20+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'],
21+
'application/vnd.ms-excel': ['.xls'],
22+
'text/csv': ['.csv'],
23+
},
24+
};
25+
26+
/**
27+
* Get file type from MIME type or filename
28+
*/
29+
export function getFileType(file: File | string): InputFile['type'] {
30+
const mimeType = typeof file === 'string' ? getMimeTypeFromFilename(file) : file.type;
31+
const filename = typeof file === 'string' ? file : file.name;
32+
33+
// Check image formats
34+
for (const [mime, extensions] of Object.entries(SUPPORTED_FILE_TYPES.images)) {
35+
if (mimeType === mime || extensions.some(ext => filename.toLowerCase().endsWith(ext))) {
36+
if (mime === 'image/heic' || mime === 'image/heif') return 'heic';
37+
if (mime === 'image/webp') return 'webp';
38+
if (mime === 'image/tiff') return 'tiff';
39+
return 'image';
40+
}
41+
}
42+
43+
// Check PDF
44+
if (mimeType === 'application/pdf' || filename.toLowerCase().endsWith('.pdf')) {
45+
return 'pdf';
46+
}
47+
48+
// Check documents
49+
for (const [mime, extensions] of Object.entries(SUPPORTED_FILE_TYPES.documents)) {
50+
if (mimeType === mime || extensions.some(ext => filename.toLowerCase().endsWith(ext))) {
51+
return 'document';
52+
}
53+
}
54+
55+
// Default to image for unknown types
56+
return 'image';
57+
}
58+
59+
/**
60+
* Get MIME type from filename extension
61+
*/
62+
function getMimeTypeFromFilename(filename: string): string {
63+
const ext = filename.toLowerCase().split('.').pop();
64+
65+
const mimeMap: Record<string, string> = {
66+
jpg: 'image/jpeg',
67+
jpeg: 'image/jpeg',
68+
png: 'image/png',
69+
webp: 'image/webp',
70+
tiff: 'image/tiff',
71+
tif: 'image/tiff',
72+
heic: 'image/heic',
73+
heif: 'image/heif',
74+
pdf: 'application/pdf',
75+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
76+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
77+
xls: 'application/vnd.ms-excel',
78+
csv: 'text/csv',
79+
};
80+
81+
return mimeMap[ext || ''] || 'application/octet-stream';
82+
}
83+
84+
/**
85+
* Check if a file type is supported
86+
*/
87+
export function isSupportedFile(file: File | string): boolean {
88+
const type = getFileType(file);
89+
return ['image', 'pdf', 'heic', 'webp', 'tiff', 'document'].includes(type);
90+
}
91+
92+
/**
93+
* Get supported file extensions as a string for file input accept attribute
94+
*/
95+
export function getSupportedExtensions(): string {
96+
const allExtensions: string[] = [];
97+
98+
// Add image extensions
99+
Object.values(SUPPORTED_FILE_TYPES.images).forEach(extensions => {
100+
allExtensions.push(...extensions);
101+
});
102+
103+
// Add PDF extensions
104+
Object.values(SUPPORTED_FILE_TYPES.pdf).forEach(extensions => {
105+
allExtensions.push(...extensions);
106+
});
107+
108+
// Add document extensions
109+
Object.values(SUPPORTED_FILE_TYPES.documents).forEach(extensions => {
110+
allExtensions.push(...extensions);
111+
});
112+
113+
return allExtensions.join(',');
114+
}
115+
116+
/**
117+
* Convert HEIC/HEIF to JPEG for processing
118+
*/
119+
export async function convertHeicToJpeg(file: File): Promise<File> {
120+
// Note: This would require a HEIC decoder library like libheif-js or heic2any
121+
// For now, return a placeholder implementation
122+
console.warn('HEIC conversion not yet implemented - requires libheif-js or similar');
123+
124+
// Placeholder: return original file (will likely fail processing)
125+
return new File([file], file.name.replace(/\.(heic|heif)$/i, '.jpg'), {
126+
type: 'image/jpeg'
127+
});
128+
}
129+
130+
/**
131+
* Convert WebP to PNG/JPEG for better compatibility
132+
*/
133+
export async function convertWebPToStandard(file: File, targetFormat: 'png' | 'jpeg' = 'png'): Promise<File> {
134+
return new Promise((resolve, reject) => {
135+
const canvas = document.createElement('canvas');
136+
const ctx = canvas.getContext('2d');
137+
const img = new Image();
138+
139+
img.onload = () => {
140+
canvas.width = img.width;
141+
canvas.height = img.height;
142+
143+
if (!ctx) {
144+
reject(new Error('Canvas context not available'));
145+
return;
146+
}
147+
148+
ctx.drawImage(img, 0, 0);
149+
150+
canvas.toBlob((blob) => {
151+
if (!blob) {
152+
reject(new Error('Failed to convert WebP'));
153+
return;
154+
}
155+
156+
const convertedFile = new File([blob],
157+
file.name.replace(/\.webp$/i, `.${targetFormat}`), {
158+
type: `image/${targetFormat}`
159+
});
160+
161+
resolve(convertedFile);
162+
}, `image/${targetFormat}`, 0.9);
163+
};
164+
165+
img.onerror = () => reject(new Error('Failed to load WebP image'));
166+
img.src = URL.createObjectURL(file);
167+
});
168+
}
169+
170+
/**
171+
* Convert TIFF to PNG for processing
172+
*/
173+
export async function convertTiffToPng(file: File): Promise<File> {
174+
// Note: This would require a TIFF decoder library like tiff.js
175+
console.warn('TIFF conversion not yet implemented - requires tiff.js or similar');
176+
177+
// Placeholder: return as PNG type but original data (will likely fail)
178+
return new File([file], file.name.replace(/\.tiff?$/i, '.png'), {
179+
type: 'image/png'
180+
});
181+
}
182+
183+
/**
184+
* Process bulk files with progress tracking
185+
*/
186+
export async function processBulkFiles(
187+
files: InputFile[],
188+
analyzeFn: (file: InputFile) => Promise<any>,
189+
applyFn: (file: InputFile, result: any) => Promise<any>,
190+
options: BulkProcessingOptions = {}
191+
): Promise<BulkProcessingResult> {
192+
const {
193+
maxConcurrency = 3,
194+
onProgress,
195+
onFileComplete,
196+
stopOnError = false
197+
} = options;
198+
199+
const startTime = Date.now();
200+
const results: (any | null)[] = [];
201+
const errors: (Error | null)[] = [];
202+
let successful = 0;
203+
let failed = 0;
204+
205+
// Process files in batches to control concurrency
206+
for (let i = 0; i < files.length; i += maxConcurrency) {
207+
const batch = files.slice(i, i + maxConcurrency);
208+
209+
const batchPromises = batch.map(async (file, batchIndex) => {
210+
const fileIndex = i + batchIndex;
211+
212+
try {
213+
onProgress?.(fileIndex, files.length, file.name || `file-${fileIndex}`);
214+
215+
// Analyze file
216+
const analyzeResult = await analyzeFn(file);
217+
218+
// Apply redactions
219+
const applyResult = await applyFn(file, analyzeResult);
220+
221+
results[fileIndex] = applyResult;
222+
errors[fileIndex] = null;
223+
successful++;
224+
225+
onFileComplete?.(file, applyResult);
226+
227+
return applyResult;
228+
} catch (error) {
229+
const err = error instanceof Error ? error : new Error(String(error));
230+
results[fileIndex] = null;
231+
errors[fileIndex] = err;
232+
failed++;
233+
234+
onFileComplete?.(file, null, err);
235+
236+
if (stopOnError) {
237+
throw err;
238+
}
239+
240+
return null;
241+
}
242+
});
243+
244+
try {
245+
await Promise.all(batchPromises);
246+
} catch (error) {
247+
if (stopOnError) {
248+
break;
249+
}
250+
}
251+
}
252+
253+
const duration = Date.now() - startTime;
254+
255+
return {
256+
successful,
257+
failed,
258+
total: files.length,
259+
results,
260+
errors,
261+
duration
262+
};
263+
}
264+
265+
/**
266+
* Convert document to PDF for processing
267+
*/
268+
export async function convertDocumentToPdf(
269+
file: File,
270+
options: DocumentConversionOptions = { targetFormat: 'pdf' }
271+
): Promise<File> {
272+
// Note: This would require document conversion libraries
273+
// For DOCX: mammoth.js + html-to-pdf
274+
// For XLSX: xlsx + html-to-pdf
275+
276+
console.warn(`Document conversion not yet implemented for ${file.type}`);
277+
console.log('Would convert:', file.name, 'with options:', options);
278+
279+
// Placeholder: return original file
280+
return file;
281+
}

packages/core-detect/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './types';
22
export { analyzeDocument } from './pipeline/analyze';
33
export { applyRedactions } from './pipeline/apply';
44
export { listPresets, getPreset, savePreset, deletePreset, type Preset } from './presets';
5+
export * from './formats';

packages/core-detect/src/types.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface InputFile {
77
/**
88
* The MIME type of the file, e.g. `image/png` or `application/pdf`.
99
*/
10-
type: 'image' | 'pdf';
10+
type: 'image' | 'pdf' | 'heic' | 'webp' | 'tiff' | 'document';
1111
/**
1212
* Optional name for the file.
1313
*/
@@ -182,3 +182,71 @@ export interface ApplyResult {
182182
/** Optional report with details about redactions */
183183
report?: any;
184184
}
185+
186+
export interface BulkProcessingOptions {
187+
/** Maximum number of files to process concurrently */
188+
maxConcurrency?: number;
189+
/** Callback for progress updates */
190+
onProgress?: (processed: number, total: number, currentFile: string) => void;
191+
/** Callback for individual file completion */
192+
onFileComplete?: (file: InputFile, result: ApplyResult | null, error?: Error) => void;
193+
/** Stop processing on first error */
194+
stopOnError?: boolean;
195+
/** Common analyze options to apply to all files */
196+
analyzeOptions?: AnalyzeOptions;
197+
/** Common apply options to apply to all files */
198+
applyOptions?: ApplyOptions;
199+
}
200+
201+
export interface BulkProcessingResult {
202+
/** Number of files successfully processed */
203+
successful: number;
204+
/** Number of files that failed processing */
205+
failed: number;
206+
/** Total number of files processed */
207+
total: number;
208+
/** Results for each file (null if failed) */
209+
results: (ApplyResult | null)[];
210+
/** Errors encountered during processing */
211+
errors: (Error | null)[];
212+
/** Processing time in milliseconds */
213+
duration: number;
214+
}
215+
216+
export interface DocumentConversionOptions {
217+
/** Target format for document conversion */
218+
targetFormat: 'pdf';
219+
/** Quality settings for conversion */
220+
quality?: number;
221+
/** Page layout options */
222+
layout?: {
223+
pageSize?: 'A4' | 'Letter' | 'Legal';
224+
orientation?: 'portrait' | 'landscape';
225+
margins?: { top: number; right: number; bottom: number; left: number };
226+
};
227+
/** Whether to preserve original formatting */
228+
preserveFormatting?: boolean;
229+
}
230+
231+
export interface SupportedFileTypes {
232+
/** Image formats and their MIME types */
233+
images: {
234+
'image/jpeg': string[];
235+
'image/png': string[];
236+
'image/webp': string[];
237+
'image/tiff': string[];
238+
'image/heic': string[];
239+
'image/heif': string[];
240+
};
241+
/** PDF formats */
242+
pdf: {
243+
'application/pdf': string[];
244+
};
245+
/** Document formats */
246+
documents: {
247+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': string[];
248+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': string[];
249+
'application/vnd.ms-excel': string[];
250+
'text/csv': string[];
251+
};
252+
}

0 commit comments

Comments
 (0)