Choose enclosed attachments before generating; drop instant-generate
The generate form now lets the user pick which extra attachments (basis_anhaenge) to enclose — none selected by default. Only the chosen ones are attached, and their names are passed to the LLM so the cover letter's "Anlagen" list and wording reflect exactly what is enclosed. Job offers keep only "Als Bewerbung übernehmen" (draft first); the "Übernehmen & KI-Unterlagen" button and its route are removed. REST API: POST /applications/:id/generate accepts an optional `anlagen` array of attachment IDs (Swagger updated). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -435,7 +435,7 @@ async function attachVerlauf(applications) {
|
||||
// Run the AI document generation for one application (async, fire-and-forget).
|
||||
// Loads the base documents + user settings, asks the LLM to tailor them to the
|
||||
// job, writes the resulting PDFs to disk and links them as attachments.
|
||||
async function runGeneration(bewerbungId) {
|
||||
async function runGeneration(bewerbungId, options = {}) {
|
||||
try {
|
||||
const bewerbung = await dbGet('SELECT * FROM bewerbungen WHERE id = ?', [bewerbungId]);
|
||||
if (!bewerbung) return;
|
||||
@@ -443,6 +443,10 @@ async function runGeneration(bewerbungId) {
|
||||
const basisAnhaenge = await dbAll('SELECT * FROM basis_anhaenge ORDER BY id ASC');
|
||||
const settings = await dbGet('SELECT * FROM settings WHERE id = 1');
|
||||
|
||||
// Only the explicitly selected extra attachments are enclosed (default: none).
|
||||
const anlagenIds = Array.isArray(options.anlagenIds) ? options.anlagenIds.map(Number) : [];
|
||||
const selectedAnhaenge = basisAnhaenge.filter((a) => anlagenIds.includes(a.id));
|
||||
|
||||
const { documents, email } = await generateApplicationDocuments({
|
||||
job: {
|
||||
firma: bewerbung.firma,
|
||||
@@ -453,8 +457,9 @@ async function runGeneration(bewerbungId) {
|
||||
},
|
||||
basisDokumente,
|
||||
settings,
|
||||
// Names of the static attachments so the cover letter can list them under "Anlagen".
|
||||
zusatzAnlagen: basisAnhaenge.map((a) => a.name || a.dateiname),
|
||||
// Names of the selected attachments so the cover letter (and the LLM) lists
|
||||
// exactly these under "Anlagen".
|
||||
zusatzAnlagen: selectedAnhaenge.map((a) => a.name || a.dateiname),
|
||||
// Free-text notes (company address, contact person, extra context) for the LLM.
|
||||
llmNotizen: bewerbung.llm_notizen || '',
|
||||
// Signature image placed under the closing salutation (instead of the typed name).
|
||||
@@ -478,8 +483,8 @@ async function runGeneration(bewerbungId) {
|
||||
await storeAnhang(doc.name, doc.filename, doc.mime, doc.buffer);
|
||||
}
|
||||
|
||||
// Static extra attachments (e.g. Zeugnisse) — copied as-is
|
||||
for (const ba of basisAnhaenge) {
|
||||
// Selected extra attachments (e.g. Zeugnisse) — copied as-is
|
||||
for (const ba of selectedAnhaenge) {
|
||||
const src = path.join(basisAnhaengeDir, ba.pfad);
|
||||
if (!fs.existsSync(src)) continue;
|
||||
await storeAnhang(ba.name || ba.dateiname, ba.dateiname, ba.mime || 'application/octet-stream', fs.readFileSync(src));
|
||||
@@ -1078,6 +1083,8 @@ initializeDatabase().then(() => {
|
||||
[id]
|
||||
);
|
||||
const basisCountRow = await dbGet('SELECT COUNT(*) as count FROM basis_dokumente');
|
||||
// Available static attachments (Zeugnisse etc.) to optionally enclose.
|
||||
const basisAnhaenge = await dbAll('SELECT id, name, dateiname FROM basis_anhaenge ORDER BY id ASC');
|
||||
|
||||
// E-Mail correspondence (sent + received), oldest first, with attachments.
|
||||
const emails = await dbAll(
|
||||
@@ -1112,6 +1119,7 @@ initializeDatabase().then(() => {
|
||||
mailError: req.query.mailerror ? String(req.query.mailerror) : '',
|
||||
mailOk: req.query.mailok ? String(req.query.mailok) : '',
|
||||
basisCount: basisCountRow ? basisCountRow.count : 0,
|
||||
basisAnhaenge,
|
||||
artOptions: ART_OPTIONS,
|
||||
statusOptions: STATUS_OPTIONS,
|
||||
hideSettings: true
|
||||
@@ -1642,7 +1650,9 @@ initializeDatabase().then(() => {
|
||||
await dbRun('DELETE FROM anhaenge WHERE bewerbung_id = ?', [id]);
|
||||
await dbRun("UPDATE bewerbungen SET generierung_status = 'ausstehend', generierung_fehler = NULL WHERE id = ?", [id]);
|
||||
|
||||
runGeneration(id);
|
||||
// Selected extra attachments (checkbox values); none by default.
|
||||
const anlagenIds = [].concat(req.body.anlage || []).map((v) => parseInt(v, 10)).filter((n) => !Number.isNaN(n));
|
||||
runGeneration(id, { anlagenIds });
|
||||
res.redirect('/bewerbung/' + id);
|
||||
} catch (error) {
|
||||
console.error('Error generating documents:', error);
|
||||
@@ -1784,28 +1794,6 @@ initializeDatabase().then(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// Übernehmen AND immediately kick off the AI generation, then jump to the
|
||||
// application page where the progress is shown.
|
||||
app.post('/jobangebote/:id/uebernehmen-generieren', async (req, res) => {
|
||||
try {
|
||||
const angebot = await dbGet('SELECT * FROM jobangebote WHERE id = ?', [req.params.id]);
|
||||
if (!angebot) return res.status(404).send('Jobangebot nicht gefunden');
|
||||
const bewerbungId = await uebernehmeAngebot(angebot);
|
||||
|
||||
// Clear any previously generated files, then (re)start generation.
|
||||
const alte = await dbAll('SELECT * FROM anhaenge WHERE bewerbung_id = ?', [bewerbungId]);
|
||||
for (const a of alte) fs.promises.unlink(path.join(anhaengeDir, a.pfad)).catch(() => {});
|
||||
await dbRun('DELETE FROM anhaenge WHERE bewerbung_id = ?', [bewerbungId]);
|
||||
await dbRun("UPDATE bewerbungen SET generierung_status = 'ausstehend', generierung_fehler = NULL WHERE id = ?", [bewerbungId]);
|
||||
runGeneration(bewerbungId);
|
||||
|
||||
res.redirect('/bewerbung/' + bewerbungId);
|
||||
} catch (error) {
|
||||
console.error('Error converting + generating from job offer:', error);
|
||||
res.status(500).send('Serverfehler');
|
||||
}
|
||||
});
|
||||
|
||||
// Edit an offer's fields — mainly to paste/adjust the full job description
|
||||
// (any length) before turning it into an application.
|
||||
app.post('/jobangebote/:id/bearbeiten', async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user