@amirdaraee/namewise 0.4.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +14 -7
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/rename.js +1 -1
- package/dist/cli/rename.js.map +1 -1
- package/dist/parsers/factory.d.ts +2 -1
- package/dist/parsers/factory.d.ts.map +1 -1
- package/dist/parsers/factory.js +9 -6
- package/dist/parsers/factory.js.map +1 -1
- package/dist/parsers/pdf-parser.d.ts +1 -0
- package/dist/parsers/pdf-parser.d.ts.map +1 -1
- package/dist/parsers/pdf-parser.js +20 -1
- package/dist/parsers/pdf-parser.js.map +1 -1
- package/dist/services/claude-service.d.ts.map +1 -1
- package/dist/services/claude-service.js +57 -17
- package/dist/services/claude-service.js.map +1 -1
- package/dist/services/lmstudio-service.d.ts.map +1 -1
- package/dist/services/lmstudio-service.js +7 -0
- package/dist/services/lmstudio-service.js.map +1 -1
- package/dist/services/ollama-service.d.ts +1 -0
- package/dist/services/ollama-service.d.ts.map +1 -1
- package/dist/services/ollama-service.js +54 -15
- package/dist/services/ollama-service.js.map +1 -1
- package/dist/services/openai-service.d.ts.map +1 -1
- package/dist/services/openai-service.js +57 -18
- package/dist/services/openai-service.js.map +1 -1
- package/dist/utils/pdf-to-image.d.ts +11 -0
- package/dist/utils/pdf-to-image.d.ts.map +1 -0
- package/dist/utils/pdf-to-image.js +104 -0
- package/dist/utils/pdf-to-image.js.map +1 -0
- package/eng.traineddata +0 -0
- package/package.json +5 -2
- package/src/cli/commands.ts +14 -7
- package/src/cli/rename.ts +1 -1
- package/src/parsers/factory.ts +11 -7
- package/src/parsers/pdf-parser.ts +22 -1
- package/src/services/claude-service.ts +61 -18
- package/src/services/lmstudio-service.ts +9 -0
- package/src/services/ollama-service.ts +68 -15
- package/src/services/openai-service.ts +61 -19
- package/src/utils/pdf-to-image.ts +137 -0
- package/tests/integration/end-to-end.test.ts +9 -9
- package/tests/unit/cli/commands.test.ts +9 -3
- package/tests/unit/utils/pdf-to-image.test.ts +127 -0
|
@@ -12,25 +12,64 @@ export class OpenAIService {
|
|
|
12
12
|
async generateFileName(content, originalName, namingConvention = 'kebab-case', category = 'general', fileInfo) {
|
|
13
13
|
const convention = namingConvention;
|
|
14
14
|
const fileCategory = category;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
originalName,
|
|
18
|
-
namingConvention: convention,
|
|
19
|
-
category: fileCategory,
|
|
20
|
-
fileInfo
|
|
21
|
-
});
|
|
15
|
+
// Check if this is a scanned PDF image
|
|
16
|
+
const isScannedPDF = content.startsWith('[SCANNED_PDF_IMAGE]:');
|
|
22
17
|
try {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
let response;
|
|
19
|
+
if (isScannedPDF) {
|
|
20
|
+
// Extract base64 image data
|
|
21
|
+
const imageBase64 = content.replace('[SCANNED_PDF_IMAGE]:', '');
|
|
22
|
+
const prompt = buildFileNamePrompt({
|
|
23
|
+
content: 'This is a scanned PDF document converted to an image. Please analyze the image and extract the main content to generate an appropriate filename.',
|
|
24
|
+
originalName,
|
|
25
|
+
namingConvention: convention,
|
|
26
|
+
category: fileCategory,
|
|
27
|
+
fileInfo
|
|
28
|
+
});
|
|
29
|
+
response = await this.client.chat.completions.create({
|
|
30
|
+
model: 'gpt-4o', // Use GPT-4 with vision capabilities
|
|
31
|
+
messages: [
|
|
32
|
+
{
|
|
33
|
+
role: 'user',
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: 'text',
|
|
37
|
+
text: prompt
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: 'image_url',
|
|
41
|
+
image_url: {
|
|
42
|
+
url: imageBase64
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
max_tokens: 100,
|
|
49
|
+
temperature: 0.3
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Standard text processing
|
|
54
|
+
const prompt = buildFileNamePrompt({
|
|
55
|
+
content,
|
|
56
|
+
originalName,
|
|
57
|
+
namingConvention: convention,
|
|
58
|
+
category: fileCategory,
|
|
59
|
+
fileInfo
|
|
60
|
+
});
|
|
61
|
+
response = await this.client.chat.completions.create({
|
|
62
|
+
model: 'gpt-3.5-turbo',
|
|
63
|
+
messages: [
|
|
64
|
+
{
|
|
65
|
+
role: 'user',
|
|
66
|
+
content: prompt
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
max_tokens: 100,
|
|
70
|
+
temperature: 0.3
|
|
71
|
+
});
|
|
72
|
+
}
|
|
34
73
|
const suggestedName = response.choices[0]?.message?.content?.trim() || 'untitled-document';
|
|
35
74
|
// Clean and validate the suggested name
|
|
36
75
|
return this.sanitizeFileName(suggestedName, convention);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-service.js","sourceRoot":"","sources":["../../src/services/openai-service.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,qBAAqB,EAAoB,MAAM,gCAAgC,CAAC;AAEzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,OAAO,aAAa;IACxB,IAAI,GAAG,QAAQ,CAAC;IACR,MAAM,CAAS;IAEvB,YAAY,MAAc;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC;YACvB,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,OAAe,EAAE,YAAoB,EAAE,mBAA2B,YAAY,EAAE,WAAmB,SAAS,EAAE,QAAmB;QACtJ,MAAM,UAAU,GAAG,gBAAoC,CAAC;QACxD,MAAM,YAAY,GAAG,QAAwB,CAAC;QAE9C,MAAM,MAAM,GAAG,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"openai-service.js","sourceRoot":"","sources":["../../src/services/openai-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PDFToImageOptions {
|
|
2
|
+
scale?: number;
|
|
3
|
+
format?: 'png' | 'jpeg';
|
|
4
|
+
firstPageOnly?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class PDFToImageConverter {
|
|
7
|
+
private static readonly MAX_IMAGE_SIZE_BYTES;
|
|
8
|
+
static convertFirstPageToBase64(pdfBuffer: Buffer, options?: PDFToImageOptions): Promise<string>;
|
|
9
|
+
static isScannedPDF(extractedText: string): boolean;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=pdf-to-image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf-to-image.d.ts","sourceRoot":"","sources":["../../src/utils/pdf-to-image.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,qBAAa,mBAAmB;IAE9B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAmB;WAElD,wBAAwB,CACnC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,MAAM,CAAC;IAyFlB,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO;CAapD"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { pdfToPng } from 'pdf-to-png-converter';
|
|
2
|
+
import { createCanvas, loadImage, DOMMatrix } from 'canvas';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
// Polyfill DOMMatrix for Node.js environments (required by pdf-to-png-converter)
|
|
5
|
+
if (typeof global !== 'undefined' && !global.DOMMatrix) {
|
|
6
|
+
global.DOMMatrix = DOMMatrix;
|
|
7
|
+
}
|
|
8
|
+
// Polyfill process.getBuiltinModule for Node.js < 22.3.0
|
|
9
|
+
if (typeof process !== 'undefined' && !process.getBuiltinModule) {
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
process.getBuiltinModule = (id) => {
|
|
12
|
+
try {
|
|
13
|
+
return require(id);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export class PDFToImageConverter {
|
|
21
|
+
// Claude's maximum image size is 5MB
|
|
22
|
+
static MAX_IMAGE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
23
|
+
static async convertFirstPageToBase64(pdfBuffer, options = {}) {
|
|
24
|
+
const { scale = 2.0, // Higher scale for better quality (1-3 recommended)
|
|
25
|
+
format = 'png' } = options;
|
|
26
|
+
try {
|
|
27
|
+
// Convert PDF to PNG using pdf-to-png-converter
|
|
28
|
+
// This package handles all the canvas/image compatibility issues
|
|
29
|
+
const pngPages = await pdfToPng(pdfBuffer, {
|
|
30
|
+
disableFontFace: false,
|
|
31
|
+
useSystemFonts: false,
|
|
32
|
+
pagesToProcess: [1], // Only convert first page
|
|
33
|
+
verbosityLevel: 0,
|
|
34
|
+
viewportScale: scale
|
|
35
|
+
});
|
|
36
|
+
if (!pngPages || pngPages.length === 0) {
|
|
37
|
+
throw new Error('No pages could be converted from PDF');
|
|
38
|
+
}
|
|
39
|
+
// Get the first page
|
|
40
|
+
const firstPage = pngPages[0];
|
|
41
|
+
if (!firstPage || !firstPage.content) {
|
|
42
|
+
throw new Error('First page conversion failed');
|
|
43
|
+
}
|
|
44
|
+
// Load the PNG image for optimization
|
|
45
|
+
const img = await loadImage(firstPage.content);
|
|
46
|
+
// Always use JPEG for better compression and size control
|
|
47
|
+
// Try different quality levels to fit under the size limit
|
|
48
|
+
const qualities = [0.85, 0.7, 0.6, 0.5, 0.4, 0.3];
|
|
49
|
+
for (const quality of qualities) {
|
|
50
|
+
const canvas = createCanvas(img.width, img.height);
|
|
51
|
+
const ctx = canvas.getContext('2d');
|
|
52
|
+
ctx.drawImage(img, 0, 0);
|
|
53
|
+
const dataUrl = canvas.toDataURL('image/jpeg', quality);
|
|
54
|
+
const sizeInBytes = Math.ceil((dataUrl.length - 'data:image/jpeg;base64,'.length) * 0.75);
|
|
55
|
+
if (sizeInBytes <= this.MAX_IMAGE_SIZE_BYTES) {
|
|
56
|
+
return dataUrl;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// If still too large, reduce dimensions
|
|
60
|
+
const scaleFactor = 0.7;
|
|
61
|
+
const newWidth = Math.floor(img.width * scaleFactor);
|
|
62
|
+
const newHeight = Math.floor(img.height * scaleFactor);
|
|
63
|
+
const canvas = createCanvas(newWidth, newHeight);
|
|
64
|
+
const ctx = canvas.getContext('2d');
|
|
65
|
+
ctx.drawImage(img, 0, 0, newWidth, newHeight);
|
|
66
|
+
// Try with reduced dimensions
|
|
67
|
+
for (const quality of qualities) {
|
|
68
|
+
const dataUrl = canvas.toDataURL('image/jpeg', quality);
|
|
69
|
+
const sizeInBytes = Math.ceil((dataUrl.length - 'data:image/jpeg;base64,'.length) * 0.75);
|
|
70
|
+
if (sizeInBytes <= this.MAX_IMAGE_SIZE_BYTES) {
|
|
71
|
+
return dataUrl;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Last resort: heavily compressed small image
|
|
75
|
+
const smallCanvas = createCanvas(Math.floor(newWidth * 0.5), Math.floor(newHeight * 0.5));
|
|
76
|
+
const smallCtx = smallCanvas.getContext('2d');
|
|
77
|
+
smallCtx.drawImage(img, 0, 0, smallCanvas.width, smallCanvas.height);
|
|
78
|
+
return smallCanvas.toDataURL('image/jpeg', 0.3);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Enhanced error logging for debugging
|
|
82
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
83
|
+
const errorStack = error instanceof Error ? error.stack : '';
|
|
84
|
+
console.error('PDF to image conversion detailed error:', {
|
|
85
|
+
message: errorMessage,
|
|
86
|
+
stack: errorStack,
|
|
87
|
+
errorType: error?.constructor?.name
|
|
88
|
+
});
|
|
89
|
+
throw new Error(`PDF to image conversion failed: ${errorMessage}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
static isScannedPDF(extractedText) {
|
|
93
|
+
// Heuristics to detect scanned/image-only PDFs
|
|
94
|
+
const textLength = extractedText.trim().length;
|
|
95
|
+
const wordCount = extractedText.trim().split(/\s+/).filter(w => w.length > 0).length;
|
|
96
|
+
// Consider it scanned if:
|
|
97
|
+
// - Very little text (< 50 characters)
|
|
98
|
+
// - Very few words (< 10 words)
|
|
99
|
+
// - High ratio of non-alphabetic characters
|
|
100
|
+
const nonAlphaRatio = (extractedText.length - extractedText.replace(/[^a-zA-Z]/g, '').length) / Math.max(extractedText.length, 1);
|
|
101
|
+
return textLength < 50 || wordCount < 10 || nonAlphaRatio > 0.9;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=pdf-to-image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf-to-image.js","sourceRoot":"","sources":["../../src/utils/pdf-to-image.ts"],"names":[],"mappings":"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"}
|
package/eng.traineddata
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amirdaraee/namewise",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -64,12 +64,15 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@anthropic-ai/sdk": "^0.61.0",
|
|
67
|
+
"canvas": "3.2.0",
|
|
67
68
|
"commander": "^14.0.0",
|
|
69
|
+
"exceljs": "^4.4.0",
|
|
68
70
|
"fs-extra": "^11.3.1",
|
|
69
71
|
"inquirer": "^12.9.4",
|
|
70
72
|
"mammoth": "^1.10.0",
|
|
71
73
|
"openai": "^5.19.1",
|
|
72
74
|
"pdf-extraction": "^1.0.2",
|
|
73
|
-
"
|
|
75
|
+
"pdf-to-png-converter": "3.11.0",
|
|
76
|
+
"pdfjs-dist": "5.4.149"
|
|
74
77
|
}
|
|
75
78
|
}
|
package/src/cli/commands.ts
CHANGED
|
@@ -5,7 +5,7 @@ export function setupCommands(program: Command): void {
|
|
|
5
5
|
program
|
|
6
6
|
.command('rename')
|
|
7
7
|
.description('🚀 Rename files in a directory based on their content using AI analysis')
|
|
8
|
-
.argument('
|
|
8
|
+
.argument('[directory]', 'Directory containing files to rename (default: current directory)', '.')
|
|
9
9
|
.option('-p, --provider <provider>', 'AI provider (claude|openai|ollama|lmstudio)', 'claude')
|
|
10
10
|
.option('-k, --api-key <key>', 'API key for cloud providers (or set CLAUDE_API_KEY/OPENAI_API_KEY)')
|
|
11
11
|
.option('-c, --case <convention>', 'Naming convention (kebab-case|snake_case|camelCase|PascalCase|lowercase|UPPERCASE)', 'kebab-case')
|
|
@@ -21,14 +21,16 @@ export function setupCommands(program: Command): void {
|
|
|
21
21
|
🔍 How it works:
|
|
22
22
|
1. Scans directory for supported files (PDF, DOCX, XLSX, TXT, MD, RTF)
|
|
23
23
|
2. Extracts content and metadata from each file
|
|
24
|
-
3.
|
|
25
|
-
4.
|
|
26
|
-
5.
|
|
24
|
+
3. For scanned PDFs with no text, converts to image for AI vision analysis
|
|
25
|
+
4. Uses AI to analyze content and generate descriptive names
|
|
26
|
+
5. Applies your chosen template and naming convention
|
|
27
|
+
6. Renames files (or shows preview with --dry-run)
|
|
27
28
|
|
|
28
29
|
💡 Pro Tips:
|
|
29
30
|
• Always use --dry-run first to preview changes
|
|
30
31
|
• Use 'auto' template for smart file type detection
|
|
31
32
|
• Personal templates work great for documents and photos
|
|
33
|
+
• Scanned PDFs are automatically handled via AI vision (Claude/OpenAI/Ollama)
|
|
32
34
|
• Set API keys as environment variables for cloud providers
|
|
33
35
|
• Local LLMs (Ollama/LMStudio) require running servers first
|
|
34
36
|
|
|
@@ -37,7 +39,11 @@ export function setupCommands(program: Command): void {
|
|
|
37
39
|
• LMStudio: Enable local server mode (default: http://localhost:1234)
|
|
38
40
|
|
|
39
41
|
📝 Examples:
|
|
40
|
-
#
|
|
42
|
+
# Current directory (no directory argument needed)
|
|
43
|
+
namewise rename --dry-run
|
|
44
|
+
namewise rename --provider claude --template document --name "alice"
|
|
45
|
+
|
|
46
|
+
# Specific directory
|
|
41
47
|
namewise rename ./documents --dry-run
|
|
42
48
|
|
|
43
49
|
# Cloud providers (require API keys)
|
|
@@ -45,9 +51,10 @@ export function setupCommands(program: Command): void {
|
|
|
45
51
|
namewise rename ./media --provider openai --template auto
|
|
46
52
|
|
|
47
53
|
# Local LLMs (no API key needed)
|
|
48
|
-
namewise rename
|
|
54
|
+
namewise rename --provider ollama --model llama3.1 --dry-run
|
|
55
|
+
namewise rename ./documents --provider ollama --model llama3.1
|
|
49
56
|
namewise rename ./contracts --provider lmstudio --base-url http://localhost:1234
|
|
50
|
-
|
|
57
|
+
|
|
51
58
|
# Custom Ollama setup
|
|
52
59
|
namewise rename ./files --provider ollama --base-url http://192.168.1.100:11434 --model codellama
|
|
53
60
|
`)
|
package/src/cli/rename.ts
CHANGED
|
@@ -58,7 +58,7 @@ export async function renameFiles(directory: string, options: any): Promise<void
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
// Initialize services
|
|
61
|
-
const parserFactory = new DocumentParserFactory();
|
|
61
|
+
const parserFactory = new DocumentParserFactory(config);
|
|
62
62
|
const aiService = AIServiceFactory.create(config.aiProvider, apiKey, config.localLLMConfig);
|
|
63
63
|
const fileRenamer = new FileRenamer(parserFactory, aiService, config);
|
|
64
64
|
|
package/src/parsers/factory.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import { DocumentParser } from '../types/index.js';
|
|
1
|
+
import { DocumentParser, Config } from '../types/index.js';
|
|
2
2
|
import { PDFParser } from './pdf-parser.js';
|
|
3
3
|
import { WordParser } from './word-parser.js';
|
|
4
4
|
import { ExcelParser } from './excel-parser.js';
|
|
5
5
|
import { TextParser } from './text-parser.js';
|
|
6
6
|
|
|
7
7
|
export class DocumentParserFactory {
|
|
8
|
-
private parsers: DocumentParser[]
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
private parsers: DocumentParser[];
|
|
9
|
+
|
|
10
|
+
constructor(config?: Config) {
|
|
11
|
+
this.parsers = [
|
|
12
|
+
new PDFParser(),
|
|
13
|
+
new WordParser(),
|
|
14
|
+
new ExcelParser(),
|
|
15
|
+
new TextParser()
|
|
16
|
+
];
|
|
17
|
+
}
|
|
14
18
|
|
|
15
19
|
getParser(filePath: string): DocumentParser | null {
|
|
16
20
|
return this.parsers.find(parser => parser.supports(filePath)) || null;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { DocumentParser, ParseResult, DocumentMetadata } from '../types/index.js';
|
|
4
|
+
import { PDFToImageConverter } from '../utils/pdf-to-image.js';
|
|
4
5
|
|
|
5
6
|
export class PDFParser implements DocumentParser {
|
|
7
|
+
constructor() {
|
|
8
|
+
// No constructor parameters needed anymore
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
supports(filePath: string): boolean {
|
|
7
12
|
return path.extname(filePath).toLowerCase() === '.pdf';
|
|
8
13
|
}
|
|
@@ -16,7 +21,23 @@ export class PDFParser implements DocumentParser {
|
|
|
16
21
|
const dataBuffer = fs.readFileSync(filePath);
|
|
17
22
|
const data = await extract(dataBuffer, {});
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
let content = data.text?.trim() || '';
|
|
25
|
+
|
|
26
|
+
// Check if this is a scanned PDF and convert to image for AI analysis
|
|
27
|
+
if (PDFToImageConverter.isScannedPDF(content)) {
|
|
28
|
+
try {
|
|
29
|
+
console.log('🔍 Detected scanned PDF, converting to image for AI analysis...');
|
|
30
|
+
const imageBase64 = await PDFToImageConverter.convertFirstPageToBase64(dataBuffer);
|
|
31
|
+
|
|
32
|
+
// Store the image data as a special marker for the AI service to detect
|
|
33
|
+
content = `[SCANNED_PDF_IMAGE]:${imageBase64}`;
|
|
34
|
+
console.log('✅ PDF converted to image successfully');
|
|
35
|
+
} catch (conversionError) {
|
|
36
|
+
console.warn('⚠️ PDF to image conversion failed:', conversionError instanceof Error ? conversionError.message : 'Unknown error');
|
|
37
|
+
console.log('💡 PDF-poppler requires system dependencies. Falling back to empty content.');
|
|
38
|
+
// Continue with empty content - AI services will handle this gracefully
|
|
39
|
+
}
|
|
40
|
+
}
|
|
20
41
|
|
|
21
42
|
// Extract PDF metadata if available
|
|
22
43
|
const metadata: DocumentMetadata = {};
|
|
@@ -18,25 +18,68 @@ export class ClaudeService implements AIProvider {
|
|
|
18
18
|
const convention = namingConvention as NamingConvention;
|
|
19
19
|
const fileCategory = category as FileCategory;
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
namingConvention: convention,
|
|
25
|
-
category: fileCategory,
|
|
26
|
-
fileInfo
|
|
27
|
-
});
|
|
28
|
-
|
|
21
|
+
// Check if this is a scanned PDF image
|
|
22
|
+
const isScannedPDF = content.startsWith('[SCANNED_PDF_IMAGE]:');
|
|
23
|
+
|
|
29
24
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
let response;
|
|
26
|
+
|
|
27
|
+
if (isScannedPDF) {
|
|
28
|
+
// Extract base64 image data
|
|
29
|
+
const imageBase64 = content.replace('[SCANNED_PDF_IMAGE]:', '');
|
|
30
|
+
|
|
31
|
+
const prompt = buildFileNamePrompt({
|
|
32
|
+
content: 'This is a scanned PDF document converted to an image. Please analyze the image and extract the main content to generate an appropriate filename.',
|
|
33
|
+
originalName,
|
|
34
|
+
namingConvention: convention,
|
|
35
|
+
category: fileCategory,
|
|
36
|
+
fileInfo
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
response = await this.client.messages.create({
|
|
40
|
+
model: 'claude-sonnet-4-5-20250929', // Use Claude Sonnet 4.5 for vision capabilities
|
|
41
|
+
max_tokens: 100,
|
|
42
|
+
messages: [
|
|
43
|
+
{
|
|
44
|
+
role: 'user',
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: 'text',
|
|
48
|
+
text: prompt
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'image',
|
|
52
|
+
source: {
|
|
53
|
+
type: 'base64',
|
|
54
|
+
media_type: imageBase64.startsWith('data:image/png') ? 'image/png' : 'image/jpeg',
|
|
55
|
+
data: imageBase64.split(',')[1] // Remove data:image/format;base64, prefix
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
// Standard text processing
|
|
64
|
+
const prompt = buildFileNamePrompt({
|
|
65
|
+
content,
|
|
66
|
+
originalName,
|
|
67
|
+
namingConvention: convention,
|
|
68
|
+
category: fileCategory,
|
|
69
|
+
fileInfo
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
response = await this.client.messages.create({
|
|
73
|
+
model: 'claude-3-haiku-20240307',
|
|
74
|
+
max_tokens: 100,
|
|
75
|
+
messages: [
|
|
76
|
+
{
|
|
77
|
+
role: 'user',
|
|
78
|
+
content: prompt
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
40
83
|
|
|
41
84
|
const suggestedName = response.content[0].type === 'text'
|
|
42
85
|
? response.content[0].text.trim()
|
|
@@ -51,6 +51,15 @@ export class LMStudioService implements AIProvider {
|
|
|
51
51
|
fileInfo?: FileInfo
|
|
52
52
|
): Promise<string> {
|
|
53
53
|
try {
|
|
54
|
+
// Check if this is a scanned PDF image
|
|
55
|
+
const isScannedPDF = content.startsWith('[SCANNED_PDF_IMAGE]:');
|
|
56
|
+
|
|
57
|
+
if (isScannedPDF) {
|
|
58
|
+
// LM Studio has limited vision support, so we'll fall back to using the original filename
|
|
59
|
+
console.log('⚠️ Scanned PDF detected but LMStudio has limited vision support. Using original filename.');
|
|
60
|
+
return this.sanitizeFilename(originalName);
|
|
61
|
+
}
|
|
62
|
+
|
|
54
63
|
const prompt = this.buildPrompt(content, originalName, namingConvention, category, fileInfo);
|
|
55
64
|
|
|
56
65
|
const response = await this.makeRequest('/v1/chat/completions', {
|
|
@@ -16,6 +16,7 @@ interface OllamaResponse {
|
|
|
16
16
|
interface OllamaChatMessage {
|
|
17
17
|
role: 'system' | 'user' | 'assistant';
|
|
18
18
|
content: string;
|
|
19
|
+
images?: string[]; // For vision models
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export class OllamaService implements AIProvider {
|
|
@@ -39,22 +40,61 @@ export class OllamaService implements AIProvider {
|
|
|
39
40
|
fileInfo?: FileInfo
|
|
40
41
|
): Promise<string> {
|
|
41
42
|
try {
|
|
42
|
-
|
|
43
|
+
// Check if this is a scanned PDF image
|
|
44
|
+
const isScannedPDF = content.startsWith('[SCANNED_PDF_IMAGE]:');
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
let response;
|
|
47
|
+
|
|
48
|
+
if (isScannedPDF) {
|
|
49
|
+
// Extract base64 image data and use a vision model
|
|
50
|
+
const imageBase64 = content.replace('[SCANNED_PDF_IMAGE]:', '');
|
|
51
|
+
const imageData = imageBase64.split(',')[1]; // Remove data:image/format;base64, prefix
|
|
52
|
+
|
|
53
|
+
const prompt = this.buildPrompt(
|
|
54
|
+
'This is a scanned PDF document converted to an image. Please analyze the image and extract the main content to generate an appropriate filename.',
|
|
55
|
+
originalName,
|
|
56
|
+
namingConvention,
|
|
57
|
+
category,
|
|
58
|
+
fileInfo
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Use LLaVA model for vision capabilities
|
|
62
|
+
const visionModel = this.getVisionModel();
|
|
63
|
+
|
|
64
|
+
response = await this.makeRequest('/api/chat', {
|
|
65
|
+
model: visionModel,
|
|
66
|
+
messages: [
|
|
67
|
+
{
|
|
68
|
+
role: 'system',
|
|
69
|
+
content: AI_SYSTEM_PROMPT
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
role: 'user',
|
|
73
|
+
content: prompt,
|
|
74
|
+
images: [imageData]
|
|
75
|
+
}
|
|
76
|
+
] as OllamaChatMessage[],
|
|
77
|
+
stream: false
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
// Standard text processing
|
|
81
|
+
const prompt = this.buildPrompt(content, originalName, namingConvention, category, fileInfo);
|
|
82
|
+
|
|
83
|
+
response = await this.makeRequest('/api/chat', {
|
|
84
|
+
model: this.model,
|
|
85
|
+
messages: [
|
|
86
|
+
{
|
|
87
|
+
role: 'system',
|
|
88
|
+
content: AI_SYSTEM_PROMPT
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
role: 'user',
|
|
92
|
+
content: prompt
|
|
93
|
+
}
|
|
94
|
+
] as OllamaChatMessage[],
|
|
95
|
+
stream: false
|
|
96
|
+
});
|
|
97
|
+
}
|
|
58
98
|
|
|
59
99
|
if (response.message?.content) {
|
|
60
100
|
return this.sanitizeFilename(response.message.content);
|
|
@@ -83,6 +123,19 @@ export class OllamaService implements AIProvider {
|
|
|
83
123
|
});
|
|
84
124
|
}
|
|
85
125
|
|
|
126
|
+
private getVisionModel(): string {
|
|
127
|
+
// Try to use a vision-capable model, fallback to default if not specified
|
|
128
|
+
const visionModels = ['llava', 'llava:7b', 'llava:13b', 'llava:34b', 'llama3.2-vision', 'qwen2-vl'];
|
|
129
|
+
|
|
130
|
+
// If the current model is already a vision model, use it
|
|
131
|
+
if (visionModels.some(vm => this.model.toLowerCase().includes(vm.split(':')[0]))) {
|
|
132
|
+
return this.model;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Otherwise, default to llava (most common vision model in Ollama)
|
|
136
|
+
return 'llava';
|
|
137
|
+
}
|
|
138
|
+
|
|
86
139
|
private sanitizeFilename(filename: string): string {
|
|
87
140
|
return filename
|
|
88
141
|
.trim()
|