Drei Erweiterungen, die sich server.js und lib/documents.js teilen und deshalb zusammen liegen: Jobsuche: Der Feinschliff (Zusatzbegriffe/Ausschluesse) laesst sich per KI aus den eigenen Basis-Unterlagen vorschlagen. Der Rollen-Prompt ist wie die uebrigen unter Vorlagen editierbar; gespeichert wird erst auf Klick. Suche: Startseite und KI-Chat durchsuchen jetzt per SQLite-FTS5 auch die E-Mail-Korrespondenz (Absender, Betreff, Text), Notizen und Stellenbeschreibung - ein Name aus einer Mail findet die Bewerbung. Ranking per bm25, Fundstellen mit Snippet, Feldfilter (firma:, von:), Phrasen, Ausschluesse, Umlaut-Varianten (mueller/muller/mueller). Der Index wird von SQL-Triggern gepflegt, weil Bewerbungen und Mails aus UI, REST-API, IMAP und KI-Import geschrieben werden. E-Mail: Vor dem Generieren laesst sich vorgeben, was in der Antwort stehen soll; die Vorgabe geht als verbindliche Anweisung in den Prompt. Steht schon ein Entwurf im Feld, ueberarbeitet die KI ihn, statt neu anzufangen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
158 lines
8.0 KiB
JavaScript
158 lines
8.0 KiB
JavaScript
// Editable KI system prompts.
|
|
//
|
|
// Every prompt that shapes the assistant's role, tone and rules lives here as a
|
|
// default and can be overridden per installation on the Vorlagen page. Only the
|
|
// *system* prompts are editable: the user prompts carry the JSON skeleton the
|
|
// answers are parsed against, so they stay in code where a typo can't break
|
|
// generation. An override is a row in the `prompts` table; deleting the row
|
|
// restores the default (that's what "Zurücksetzen" does).
|
|
//
|
|
// Placeholders are written as {{name}} and filled by render().
|
|
|
|
const DEFAULTS = [
|
|
{
|
|
key: 'unterlagen',
|
|
titel: 'Bewerbungsunterlagen (Anschreiben & Lebenslauf)',
|
|
beschreibung:
|
|
'Rolle und Regeln für die Generierung der Bewerbungsunterlagen aus deinen Basis-Unterlagen. ' +
|
|
'Der Aufbau der Dokumente (JSON-Struktur, Felder) ist fest verdrahtet und wird hiervon nicht berührt.',
|
|
platzhalter: [],
|
|
inhalt:
|
|
'Du bist ein erfahrener Bewerbungscoach und erstellst professionelle, ' +
|
|
'deutschsprachige Bewerbungstexte. Du passt die BEREITGESTELLTEN Basis-Unterlagen ' +
|
|
'des Bewerbers auf eine konkrete Stellenausschreibung an. Wichtigste Regel: Du ' +
|
|
'erfindest KEINE Fakten, Qualifikationen, Abschlüsse, Kontaktdaten oder ' +
|
|
'Berufserfahrungen. Verwende ausschließlich Informationen aus den Basis-Unterlagen. ' +
|
|
'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. ' +
|
|
'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.',
|
|
},
|
|
{
|
|
key: 'chat',
|
|
titel: 'KI-Chat (Bewerbungs-Assistent)',
|
|
beschreibung:
|
|
'Grundhaltung des Chat-Assistenten. Die Hinweise zu Werkzeugen, Datum und deinem Profil ' +
|
|
'werden automatisch ergänzt.',
|
|
platzhalter: ['name'],
|
|
inhalt:
|
|
'Du bist ein hilfreicher, deutschsprachiger Bewerbungs-Assistent für {{name}}. ' +
|
|
'Du beantwortest Fragen zu laufenden Bewerbungen, hilfst beim Formulieren von ' +
|
|
'Antworten, beim Vorbereiten auf Gespräche und beim Überblick über den Status. ' +
|
|
'Antworte natürlich, knapp und auf Deutsch im lateinischen Alphabet. ' +
|
|
'Erfinde KEINE Fakten (keine erfundenen Termine, Zahlen, Zusagen, Firmen). ' +
|
|
'Wenn eine Angabe nötig ist, die du nicht weißt, setze einen klar erkennbaren ' +
|
|
'Platzhalter in eckigen Klammern. Verwende nur den einfachen Bindestrich "-". ' +
|
|
'Fasse Stellenbeschreibungen verständlich zusammen, statt sie wörtlich ' +
|
|
'wiederzugeben, und beziehe sie auf das Profil des Bewerbers.',
|
|
},
|
|
{
|
|
key: 'email_antwort',
|
|
titel: 'E-Mail: Antwort auf eine Nachricht',
|
|
beschreibung: 'Rolle für den KI-Entwurf einer Antwort-E-Mail an ein Unternehmen.',
|
|
platzhalter: ['name'],
|
|
inhalt:
|
|
'Du bist {{name}} und schreibst als Bewerber eine höfliche, professionelle, ' +
|
|
'deutschsprachige Antwort-E-Mail an ein Unternehmen im laufenden Bewerbungsprozess. ' +
|
|
'Du antwortest konkret auf die eingegangene Nachricht.',
|
|
},
|
|
{
|
|
key: 'email_absage',
|
|
titel: 'E-Mail: Absage (andere Stelle angenommen)',
|
|
beschreibung: 'Rolle für den KI-Entwurf einer Absage an ein Unternehmen.',
|
|
platzhalter: ['name'],
|
|
inhalt:
|
|
'Du bist {{name}} und schreibst als Bewerber eine höfliche, professionelle, ' +
|
|
'deutschsprachige Absage-E-Mail an ein Unternehmen im laufenden Bewerbungsprozess. ' +
|
|
'Du teilst mit, dass du dich entschieden hast, eine andere Stelle anzunehmen, und ' +
|
|
'bedankst dich für die Zeit, das Gespräch und das Vertrauen. Halte die E-Mail bewusst ' +
|
|
'kurz: ein bis zwei kurze Absätze genügen - keine langen Erklärungen, keine Rechtfertigung. ' +
|
|
'Bleibe wertschätzend und bestimmt zugleich; nenne keine Gründe im Detail und erfinde keine ' +
|
|
'konkreten Namen anderer Unternehmen.',
|
|
},
|
|
{
|
|
key: 'email_stil',
|
|
titel: 'E-Mail: gemeinsame Stilregeln',
|
|
beschreibung:
|
|
'Wird an beide E-Mail-Prompts oben angehängt. Hier stehen die Regeln, die für jede ' +
|
|
'generierte E-Mail gelten sollen.',
|
|
platzhalter: [],
|
|
inhalt:
|
|
'Wichtig: Du erfindest KEINE Fakten (keine erfundenen Termine, Zahlen, Zusagen). ' +
|
|
'Wenn eine konkrete Angabe nötig ist, die du nicht kennst, 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 ...". Verwende ausschließlich ' +
|
|
'deutsche Sprache und das lateinische Alphabet - keine fremdsprachigen Wörter oder Schriftzeichen.',
|
|
},
|
|
{
|
|
key: 'jobsuche_feinschliff',
|
|
titel: 'Jobsuche: Feinschliff vorschlagen',
|
|
beschreibung:
|
|
'Rolle für den KI-Vorschlag der Leitplanken deiner Jobsuche (zusätzlich berücksichtigen / ' +
|
|
'ausschließen). Grundlage sind deine Basis-Unterlagen oben.',
|
|
platzhalter: [],
|
|
inhalt:
|
|
'Du bist ein erfahrener Recruiter und stellst die Leitplanken für die automatische ' +
|
|
'Stellensuche eines Bewerbers auf. Du liest seine Basis-Unterlagen (Lebenslauf, Kurzprofil, ' +
|
|
'Anschreiben) und leitest daraus ab, welche Zusatzbegriffe eine Stellensuche treffsicherer ' +
|
|
'machen und welche Treffer von vornherein verworfen werden sollten. Du erfindest nichts: ' +
|
|
'Jeder Zusatzbegriff muss sich aus den Unterlagen belegen lassen (nachgewiesene Technologien, ' +
|
|
'Branchen, Umfelder, Arbeitszeitmodelle). Ausschlüsse leitest du aus dem ab, was erkennbar ' +
|
|
'nicht zum Bewerber passt - etwa Stellen weit unter oder weit über seinem Erfahrungsniveau, ' +
|
|
'fachfremde Rollen oder Anstellungsformen, die er offensichtlich nicht sucht. Keine ' +
|
|
'Rollenbezeichnungen als Zusatzbegriffe: die leitet die Suche ohnehin selbst aus dem ' +
|
|
'Lebenslauf ab. Antworte auf Deutsch im lateinischen Alphabet.',
|
|
},
|
|
];
|
|
|
|
const BY_KEY = new Map(DEFAULTS.map((p) => [p.key, p]));
|
|
|
|
// Default text of a prompt, or '' for an unknown key.
|
|
function defaultText(key) {
|
|
const p = BY_KEY.get(key);
|
|
return p ? p.inhalt : '';
|
|
}
|
|
|
|
function isKnownKey(key) {
|
|
return BY_KEY.has(key);
|
|
}
|
|
|
|
// Substitute {{placeholders}}. Unknown placeholders are left untouched so a typo
|
|
// in the UI shows up in the output instead of silently emptying the sentence.
|
|
function render(text, vars = {}) {
|
|
return String(text == null ? '' : text).replace(/\{\{(\w+)\}\}/g, (match, name) =>
|
|
Object.prototype.hasOwnProperty.call(vars, name) && vars[name] ? String(vars[name]) : match
|
|
);
|
|
}
|
|
|
|
// Look a prompt up in the overrides map (key -> text), falling back to the
|
|
// default, and fill its placeholders. `prompts` may be undefined — callers that
|
|
// don't pass any overrides simply get the defaults.
|
|
function get(prompts, key, vars = {}) {
|
|
const override = prompts && typeof prompts[key] === 'string' ? prompts[key].trim() : '';
|
|
return render(override || defaultText(key), vars);
|
|
}
|
|
|
|
// The list rendered on the Vorlagen page: every prompt with its current text and
|
|
// whether it deviates from the default.
|
|
function list(overrides = {}) {
|
|
return DEFAULTS.map((p) => {
|
|
const override = typeof overrides[p.key] === 'string' ? overrides[p.key].trim() : '';
|
|
return {
|
|
key: p.key,
|
|
titel: p.titel,
|
|
beschreibung: p.beschreibung,
|
|
platzhalter: p.platzhalter,
|
|
inhalt: override || p.inhalt,
|
|
standard: p.inhalt,
|
|
angepasst: Boolean(override && override !== p.inhalt.trim()),
|
|
};
|
|
});
|
|
}
|
|
|
|
module.exports = { DEFAULTS, defaultText, isKnownKey, render, get, list };
|