From 482d8d86fad753ed9350e21c52aea125352582d4 Mon Sep 17 00:00:00 2001 From: Thomas Hackner Date: Fri, 3 Jul 2026 19:28:34 +0200 Subject: [PATCH] Shorten cover email + strip non-Latin script from AI text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated Begleit-E-Mail was too long for an HR reader and sometimes contained stray non-Latin characters (e.g. CJK "包括"). Make the email a minimal accompanying note (no repetition of the cover letter, max ~3 sentences), add a German-only/Latin-script instruction to both the document and reply prompts, and add a backstop sanitizer in str() that strips any non-Latin script characters the model still emits. Co-Authored-By: Claude --- lib/documents.js | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/lib/documents.js b/lib/documents.js index 6ad96fc..dde771a 100644 --- a/lib/documents.js +++ b/lib/documents.js @@ -171,7 +171,10 @@ async function generateTailoredTexts({ job, basisDokumente, settings, llmNotizen 'Du darfst umformulieren, gewichten, relevante Punkte hervorheben und auf die Stelle ' + 'zuschneiden — aber nichts hinzudichten. Schreibe natürlich, konkret und ohne Floskeln, so wie ein ' + 'deutscher Muttersprachler schreibt: keine Buzzword-Paare, keine leeren Eigenschaftswörter und keine ' + - 'Selbstetiketten wie "Als Muttersprachler ..."; jede Aussage aus einer konkreten Tätigkeit heraus.'; + 'Selbstetiketten wie "Als Muttersprachler ..."; jede Aussage aus einer konkreten Tätigkeit heraus. ' + + 'Verwende AUSSCHLIESSLICH deutsche Sprache und das lateinische Alphabet. Keine englischen, ' + + 'chinesischen, japanischen, koreanischen oder anderen fremdsprachigen Wörter oder Schriftzeichen - ' + + 'auch nicht einzelne Zeichen oder Füllwörter.'; const skeleton = `{\n` + @@ -186,7 +189,7 @@ async function generateTailoredTexts({ job, basisDokumente, settings, llmNotizen ` },\n` + ` "email": {\n` + ` "betreff": "Bewerbung als … (ggf. mit Referenz-/Kennnummer)",\n` + - ` "text": "Kurzer, sofort versendbarer E-Mail-Text: Anrede, 2-3 knappe Absätze, Grußformel, Name - Absätze durch Leerzeile getrennt"\n` + + ` "text": "Sehr kurzer Begleittext nur für die E-Mail: Anrede, ein Satz (Stelle + Unterlagen im Anhang), optional ein kurzer Schlusssatz, Grußformel, Name - jeweils durch Leerzeile getrennt"\n` + ` },\n` + ` "lebenslauf": {\n` + ` "profil": "2-3 Sätze Kurzprofil, zugeschnitten auf die Zielstelle",\n` + @@ -251,17 +254,18 @@ async function generateTailoredTexts({ job, basisDokumente, settings, llmNotizen `Nur der ERSTE Absatz beginnt mit einem KLEINbuchstaben (die Anrede endet mit Komma, der Satz wird ` + `fortgesetzt), z. B. "mit …" oder "als …" — außer das erste Wort ist ein Substantiv, ein Eigenname oder die ` + `Höflichkeitsform "Ihre/Ihr/Ihnen". Alle WEITEREN Absätze sind neue Sätze und beginnen normal mit GROSSbuchstaben.\n` + - `- email: ein KURZES, normgerechtes Begleit-E-Mail-Anschreiben, mit dem die Bewerbung samt Anhängen ` + - `(Lebenslauf, Anschreiben, ggf. Zeugnisse) per E-Mail versendet wird. Es ist NICHT das vollständige ` + - `Anschreiben - wiederhole dessen Inhalt nicht, fasse dich bewusst knapp (Faustregel: höchstens 6-8 Sätze). ` + - `email.betreff: prägnante Betreffzeile, i. d. R. "Bewerbung als " (eine in Anzeige/Notizen genannte ` + - `Referenz-/Kennnummer anhängen). email.text: vollständiger, sofort versendbarer E-Mail-Text mit exakt dieser ` + - `Struktur - (1) dieselbe Anrede wie im Anschreiben; (2) ein Satz, auf welche Stelle man sich bewirbt (falls ` + - `bekannt: wo die Stelle gefunden wurde); (3) ein bis zwei Sätze zur Kernmotivation/Kernqualifikation; ` + - `(4) ein Satz mit dem Hinweis, dass die vollständigen Bewerbungsunterlagen im Anhang beiliegen; (5) ein ` + - `freundlicher Ausblick auf ein persönliches Gespräch; (6) Grußformel "Mit freundlichen Grüßen"; (7) in der ` + - `letzten Zeile der vollständige Name des Bewerbers. Trenne Anrede, Absätze, Grußformel und Name jeweils durch ` + - `eine Leerzeile (\\n\\n). Höflich, sachlich, in Aktiv-Sätzen, ohne Floskeln und Weichspüler, keine Platzhalter.\n` + + `- email: ein SEHR KURZER Begleittext für die E-Mail, mit der die Bewerbung samt Anhängen ` + + `(Lebenslauf, Anschreiben, ggf. Zeugnisse) versendet wird. Es ist NUR die E-Mail-Begleitnotiz, NICHT das ` + + `vollständige Anschreiben - dessen Inhalt NICHT wiederholen. Ein HR-Experte überfliegt diese E-Mail im ` + + `Vorbeigehen: max. 3 Sätze gesamt, auf das absolute Minimum reduziert. KEINE Motivationssätze, KEINE ` + + `Qualifikationsauflistungen, KEINE Wiederholung des Anschreibens. ` + + `email.betreff: "Bewerbung als " (ggf. Referenz-/Kennnummer anhängen). ` + + `email.text: sofort versendbar, exakt diese Struktur - (1) dieselbe Anrede wie im Anschreiben; ` + + `(2) ein Satz: auf welche Stelle man sich bewirbt UND dass die vollständigen Bewerbungsunterlagen im Anhang ` + + `beiliegen; (3) ein kurzer, freundlicher Schlusssatz mit Ausblick auf ein persönliches Gespräch; ` + + `(4) Grußformel "Mit freundlichen Grüßen"; (5) vollständiger Name in der letzten Zeile. ` + + `Trenne Anrede, Text, Grußformel und Name jeweils durch eine Leerzeile (\\n\\n). ` + + `Höflich, sachlich, in Aktiv-Sätzen, ohne Floskeln und Weichspüler, keine Platzhalter.\n` + `- profil: 2–3 Sätze Kurzprofil, konkret auf die Zielstelle zugeschnitten (der individuelle Text zur Stelle).\n` + `- Stil/Sprache (gilt für Anschreiben, Profil UND E-Mail): Schreibe so natürlich, wie ein deutscher ` + `Muttersprachler tatsächlich schreibt - konkrete Verben, kurze klare Sätze. KEINE leeren Floskeln oder ` + @@ -373,7 +377,15 @@ async function generateTailoredTexts({ job, basisDokumente, settings, llmNotizen // Normalize one content string: trim, and force a plain hyphen "-" instead of // en/em dash ("–"/"—") everywhere — applies to every field of both documents // so the requirement holds even if the model still emits dashes. -const str = (v) => (typeof v === 'string' ? v.trim() : (v == null ? '' : String(v).trim())).replace(/[–—]/g, '-'); +// Backstop: strip non-Latin script characters (e.g. stray CJK like "包括") +// the model may emit despite the German-only instruction. Safe for German +// application text — no legitimate field contains these scripts. Preserves +// newlines (paragraph breaks) and common German umlauts/punctuation. +const NON_LATIN_SCRIPT = /[㐀-鿿豈-﫿぀-ヿ가-힯 -〿]/g; +const str = (v) => (typeof v === 'string' ? v.trim() : (v == null ? '' : String(v).trim())) + .replace(/[–—]/g, '-') + .replace(NON_LATIN_SCRIPT, '') + .replace(/[ \t]{2,}/g, ' '); function pick(obj, keys) { if (!obj) return ''; @@ -1127,7 +1139,8 @@ async function generateEmailReply({ incoming, job = {}, settings = {}, hinweise 'die du nicht kennst (z. B. ein genauer Terminvorschlag), setze einen klar erkennbaren ' + 'Platzhalter in eckigen Klammern, z. B. "[Terminvorschlag einfügen]". Schreibe natürlich, ' + 'knapp und verbindlich, wie ein deutscher Muttersprachler - ohne leere Floskeln, ohne ' + - 'Buzzword-Paare und ohne Selbstetiketten wie "Als Muttersprachler ...".'; + 'Buzzword-Paare und ohne Selbstetiketten wie "Als Muttersprachler ...". Verwende ausschließlich ' + + 'deutsche Sprache und das lateinische Alphabet - keine fremdsprachigen Wörter oder Schriftzeichen.'; const user = `# Kontext der Bewerbung\n` + `Unternehmen: ${job.firma || '-'}\n` +