Files
ICON-CRM/server/src/cvText.js
T
2026-09-14 16:55:34 +02:00

84 lines
2.6 KiB
JavaScript

import fs from 'node:fs/promises';
import path from 'node:path';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
/*
* Text aus einem Lebenslauf holen.
*
* Unterstuetzt PDF, DOCX und reinen Text. Das alte .doc-Format (Word 97) bleibt
* aussen vor: es braeuchte einen eigenen Parser, und wer eine solche Datei hat,
* speichert sie in Sekunden als PDF.
*
* pdf-parse wird ueber den internen Pfad geladen. Der Paket-Einstieg fuehrt
* beim Import eine Selbstprobe aus und sucht dafuer eine Testdatei, die im
* npm-Paket nicht mitgeliefert wird - das laesst den Start scheitern.
*/
const pdfParse = require('pdf-parse/lib/pdf-parse.js');
/** Mehr Text braucht kein Modell, um einen Lebenslauf zu verstehen. */
const MAX_CHARS = 60_000;
export function isSupported(filename, mime = '') {
const ext = path.extname(filename).toLowerCase();
return (
['.pdf', '.docx', '.txt', '.md'].includes(ext) ||
mime === 'application/pdf' ||
mime === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
mime.startsWith('text/')
);
}
/**
* Liest die Datei und gibt ihren Text zurueck.
*
* @param {string} filePath absoluter Pfad
* @param {string} originalName fuer die Endung
*/
export async function extractText(filePath, originalName) {
const ext = path.extname(originalName || filePath).toLowerCase();
if (ext === '.pdf') {
const result = await pdfParse(await fs.readFile(filePath));
return clean(result.text);
}
if (ext === '.docx') {
// mammoth liefert den Fliesstext ohne Formatierung - genau das, was das
// Modell braucht.
const mammoth = require('mammoth');
const result = await mammoth.extractRawText({ path: filePath });
return clean(result.value);
}
if (ext === '.txt' || ext === '.md') {
return clean(await fs.readFile(filePath, 'utf8'));
}
throw Object.assign(new Error('Dateityp wird nicht unterstützt (PDF, DOCX oder Text).'), {
status: 400,
});
}
/*
* Aufraeumen: PDF-Auszuege enthalten viele harte Zeilenumbrueche und
* Mehrfachleerzeichen aus dem Satzspiegel. Das kostet nur Token und erschwert
* dem Modell das Lesen.
*/
function clean(text) {
const result = String(text ?? '')
.replace(/\r/g, '')
.replace(/[ \t]+/g, ' ')
.replace(/\n{3,}/g, '\n\n')
.trim();
if (result.length <= MAX_CHARS) return result;
// Anfang und Ende behalten: vorn stehen Name und Kontaktdaten, hinten oft
// Ausbildung und Zertifikate.
const head = result.slice(0, Math.floor(MAX_CHARS * 0.7));
const tail = result.slice(-Math.floor(MAX_CHARS * 0.3));
return `${head}\n\n[… gekürzt …]\n\n${tail}`;
}