Einstellungen: Vorauswahl für KI-Generierung (Dokumente + Anlagen)

Zwei neue, pro Benutzer gespeicherte Konfigurationswerte:
GEN_DOKUMENTE_DEFAULT (Standard: Anschreiben + Lebenslauf) und
GEN_ANLAGEN_DEFAULT (Standard: keine). Sie belegen die Häkchen auf der
Bewerbungsseite vor; eine bereits getroffene Dokumentenauswahl der
Bewerbung gewinnt weiterhin. Die REST-API nutzt die Vorauswahl, wenn
dokumente/anlagen im Body fehlen.

Die Einstellungsseite kennt dafür jetzt Checkbox-Gruppen, deren Optionen
auch erst zur Renderzeit feststehen dürfen (die eigenen Anlagen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 22:31:19 +02:00
co-authored by Claude Opus 4.8
parent d20a265ebc
commit 1dd4807ca6
7 changed files with 145 additions and 29 deletions
+10 -6
View File
@@ -12,7 +12,7 @@ const path = require('path');
const fs = require('fs');
const blacklist = require('./blacklist');
const { LABEL_OPTIONS, parseLabels, serializeLabels } = require('./labels');
const { normalizeDokumente } = require('./documents');
const { normalizeDokumente, standardDokumente } = require('./documents');
const { userContext, currentUserId } = require('./context');
const config = require('./config');
@@ -452,14 +452,18 @@ function createExternalApi(deps) {
[id, uid()]
);
// Optional: IDs of static attachments (basis_anhaenge) to enclose; none
// by default. Also reflected in the cover letter's "Anlagen" list.
// Optional: IDs of static attachments (basis_anhaenge) to enclose. Omitted
// means the user's preselection from the settings (GEN_ANLAGEN_DEFAULT,
// default: none); an empty array means explicitly none. Also reflected in
// the cover letter's "Anlagen" list.
const anlagenIds = Array.isArray((req.body || {}).anlagen)
? req.body.anlagen.map((v) => parseInt(v, 10)).filter((n) => !Number.isNaN(n))
: [];
: config.anlagenDefaultIds();
// Optional: which documents to produce (["anschreiben"], ["lebenslauf"] or
// both). Omitted / empty means both.
const dokumente = normalizeDokumente((req.body || {}).dokumente);
// both). Omitted / empty falls back to the user's preselection from the
// settings (GEN_DOKUMENTE_DEFAULT, default: both).
const gewuenscht = (req.body || {}).dokumente;
const dokumente = gewuenscht == null ? standardDokumente() : normalizeDokumente(gewuenscht);
await dbRun('UPDATE bewerbungen SET generierung_dokumente = ? WHERE id = ? AND user_id = ?', [dokumente.join(','), id, uid()]);
runGeneration(id, { anlagenIds, dokumente });
res.status(202).json({ success: true, dokumente });
+43 -1
View File
@@ -26,6 +26,12 @@ const DEFAULTS = {
OLLAMA_MODEL: 'glm-5.2:cloud',
OLLAMA_HOST: 'https://ollama.com',
OLLAMA_TIMEOUT_MS: '300000',
// Which documents are preselected when a generation is started (comma separated,
// see DOKUMENT_TYPEN in lib/documents.js). Empty means both.
GEN_DOKUMENTE_DEFAULT: 'anschreiben,lebenslauf',
// Which static attachments (basis_anhaenge IDs, comma separated) are preselected.
// Empty = none, which is the default.
GEN_ANLAGEN_DEFAULT: '',
// E-Mail (SMTP submission + IMAP receive).
MAIL_HOST: '',
MAIL_SMTP_PORT: '587',
@@ -57,6 +63,32 @@ const FIELDS = [
{ key: 'OLLAMA_TIMEOUT_MS', label: 'Timeout (ms)', help: 'Standard: 300000 (5 Min.)' },
],
},
{
titel: 'Bewerbungsunterlagen (Vorauswahl)',
beschreibung: 'Was ist auf der Bewerbungsseite vorab angehakt, wenn die KI-Generierung gestartet wird? Pro Bewerbung lässt sich die Auswahl weiterhin ändern; die zuletzt gewählten Dokumente bleiben an der jeweiligen Bewerbung erhalten.',
items: [
{
key: 'GEN_DOKUMENTE_DEFAULT',
label: 'Standardmäßig generieren',
type: 'checkboxes',
options: [
{ value: 'anschreiben', label: 'Anschreiben' },
{ value: 'lebenslauf', label: 'Lebenslauf' },
],
help: 'Standard: beides. Die E-Mail-Begleitnachricht wird immer erzeugt. Ohne Häkchen gilt wieder beides.',
},
{
key: 'GEN_ANLAGEN_DEFAULT',
label: 'Standardmäßig beigelegte Anlagen',
type: 'checkboxes',
// Auswahlmöglichkeiten sind die eigenen Anlagen aus „Vorlagen“ und stehen
// erst zur Renderzeit fest — die Route reicht sie unter diesem Namen hinein.
optionsFrom: 'anlagen',
leerHinweis: 'Noch keine Anlagen hinterlegt unter „Vorlagen“ kannst du Zeugnisse & Co. hochladen.',
help: 'Standard: keine. Angehakte Anlagen werden bei jeder Generierung vorausgewählt und im Anschreiben unter „Anlagen“ aufgeführt.',
},
],
},
{
titel: 'E-Mail (SMTP-Versand + IMAP-Empfang)',
beschreibung: 'Versand läuft über den eigenen Mailserver (DKIM/SPF/DMARC-Alignment). Ohne Host/Benutzer/Passwort ist der E-Mail-Teil deaktiviert. Pro Benutzer eigenes Postfach.',
@@ -159,6 +191,16 @@ function getAll() {
return out;
}
// The current user's preselected static attachments (GEN_ANLAGEN_DEFAULT) as
// basis_anhaenge IDs. Deleted attachments may linger in the list; callers match
// them against the existing rows, so stale IDs simply never hit.
function anlagenDefaultIds() {
return String(get('GEN_ANLAGEN_DEFAULT') || '')
.split(',')
.map((v) => parseInt(v, 10))
.filter((n) => !Number.isNaN(n));
}
// Ollama bundle (shared by lib/documents.js + lib/chat.js), for the current user.
function ollama() {
return {
@@ -213,5 +255,5 @@ async function setForUser(userId, key, value) {
module.exports = {
DEFAULTS, FIELDS, PREFIX,
get, getAll, ollama, init, saveAll, setForUser, ensureLoaded, invalidate,
get, getAll, ollama, anlagenDefaultIds, init, saveAll, setForUser, ensureLoaded, invalidate,
};
+9 -1
View File
@@ -153,13 +153,20 @@ const OUTPUT_SCHEMA = {
const DOKUMENT_TYPEN = ['anschreiben', 'lebenslauf'];
// Accept anything (string, array, undefined) and return a clean, ordered list.
// Empty / unknown input means "both" — the default everywhere.
// Empty / unknown input means "both" — the fallback everywhere.
function normalizeDokumente(v) {
const gewaehlt = [].concat(v == null ? [] : v).map((x) => String(x).trim().toLowerCase());
const gefiltert = DOKUMENT_TYPEN.filter((d) => gewaehlt.includes(d));
return gefiltert.length ? gefiltert : DOKUMENT_TYPEN.slice();
}
// The current user's preselection (Einstellungen → GEN_DOKUMENTE_DEFAULT): which
// documents are ticked when a generation is started without an explicit choice.
// An empty / unknown setting falls back to both.
function standardDokumente() {
return normalizeDokumente(String(config.get('GEN_DOKUMENTE_DEFAULT') || '').split(','));
}
// Ask the model only for the documents we actually want. Dropping a section from
// the schema means the model never writes it — that saves a chunk of generation
// time and tokens when only one document is needed.
@@ -3403,4 +3410,5 @@ module.exports = {
renderDesignVorschau,
DOKUMENT_TYPEN,
normalizeDokumente,
standardDokumente,
};
+5 -4
View File
@@ -373,15 +373,16 @@ function buildOpenApiSpec(baseUrl = '') {
items: { type: 'integer' },
description:
'IDs der zusätzlich beizulegenden statischen Anlagen (basis_anhaenge). ' +
'Standard: keine. Die Auswahl erscheint auch im Anschreiben unter "Anlagen".',
'Weglassen = Vorauswahl aus den Einstellungen (GEN_ANLAGEN_DEFAULT, Standard: keine), ' +
'leeres Array = ausdrücklich keine. Die Auswahl erscheint auch im Anschreiben unter "Anlagen".',
},
dokumente: {
type: 'array',
items: { type: 'string', enum: ['anschreiben', 'lebenslauf'] },
description:
'Welche Unterlagen erzeugt werden sollen. Weglassen oder leer = beide (Standard). ' +
'Wird der Lebenslauf nicht erzeugt, führt ihn das Anschreiben auch nicht unter ' +
'"Anlagen" auf, und der E-Mail-Begleittext kündigt ihn nicht an.',
'Welche Unterlagen erzeugt werden sollen. Weglassen = Vorauswahl aus den Einstellungen ' +
'(GEN_DOKUMENTE_DEFAULT, Standard: beide). Wird der Lebenslauf nicht erzeugt, führt ihn ' +
'das Anschreiben auch nicht unter "Anlagen" auf, und der E-Mail-Begleittext kündigt ihn nicht an.',
example: ['anschreiben'],
},
},