KI-Feinschliff, Volltextsuche über E-Mails, Prompt für die Antwort-Mail

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>
This commit is contained in:
2026-07-14 10:56:41 +02:00
co-authored by Claude Opus 4.8
parent 3c7f5d7c74
commit d86e40a6ff
7 changed files with 984 additions and 77 deletions
+73 -3
View File
@@ -105,9 +105,27 @@
</section>
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h3 class="text-base font-semibold text-gray-800 dark:text-white">Feinschliff</h3>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1 mb-5">
Optional. Rollen und Technologien kommen aus deinem Lebenslauf hier ergänzt du nur Leitplanken.
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<h3 class="text-base font-semibold text-gray-800 dark:text-white">Feinschliff</h3>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
Optional. Rollen und Technologien kommen aus deinem Lebenslauf hier ergänzt du nur Leitplanken.
</p>
</div>
<button type="button" id="feinschliffKi"
<%= (!hatUnterlagen || !hatOllamaKey) ? 'disabled' : '' %>
title="<%= !hatUnterlagen ? 'Dafür fehlen Basis-Unterlagen unter Vorlagen.' : (!hatOllamaKey ? 'Dafür fehlt der Ollama-API-Schlüssel in den Einstellungen.' : 'Leitet Vorschläge aus deinen Basis-Unterlagen ab.') %>"
class="inline-flex items-center gap-2 px-3 py-1.5 shrink-0 text-sm border border-blue-600 text-blue-600 dark:border-blue-400 dark:text-blue-400 rounded-md transition-colors hover:bg-blue-50 dark:hover:bg-blue-900/30 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"></path>
</svg>
<span id="feinschliffKiText">Per KI vorschlagen</span>
</button>
</div>
<p id="feinschliffHinweis" class="hidden text-sm mt-3"></p>
<p class="text-xs text-gray-400 dark:text-gray-500 mt-3 mb-5">
Der KI-Vorschlag wertet deine <a href="/vorlagen" class="underline">Basis-Unterlagen</a> aus und
überschreibt beide Felder. Gespeichert wird er erst, wenn du unten auf „Speichern“ klickst.
</p>
<div class="grid gap-4 sm:grid-cols-2">
@@ -223,6 +241,58 @@
const initial = <%- JSON.stringify(laeufe.map(function (l) { return { status: l.status }; })) %>;
if (offen(initial)) timer = setInterval(poll, 5000);
})();
// "Per KI vorschlagen": lets the model derive the two guard-rail lists from the
// user's Basis-Unterlagen. Only fills the fields — saving stays a deliberate act.
(function () {
const btn = document.getElementById('feinschliffKi');
if (!btn) return;
const label = document.getElementById('feinschliffKiText');
const hinweis = document.getElementById('feinschliffHinweis');
const zusatz = document.getElementById('zusatz_begriffe');
const ausschluesse = document.getElementById('ausschluesse');
const zielUser = <%- fremd ? zielUser.id : 'null' %>;
const FARBE = {
info: 'text-gray-500 dark:text-gray-400',
ok: 'text-green-700 dark:text-green-400',
fehler: 'text-red-600 dark:text-red-400',
};
function melde(text, art) {
hinweis.textContent = text;
hinweis.className = 'text-sm mt-3 ' + FARBE[art];
}
btn.addEventListener('click', async function () {
btn.disabled = true;
label.textContent = 'KI denkt nach …';
melde('Die KI liest deine Basis-Unterlagen das dauert einen Moment.', 'info');
const modus = document.querySelector('input[name="modus"]:checked');
try {
const res = await fetch('/jobsuche/feinschliff', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
ziel_user: zielUser,
modus: modus ? modus.value : '',
staedte: document.getElementById('staedte').value,
}),
});
const data = await res.json().catch(function () { return {}; });
if (!res.ok) throw new Error(data.error || ('Fehler ' + res.status));
zusatz.value = data.zusatz_begriffe || '';
ausschluesse.value = data.ausschluesse || '';
melde('Vorschlag übernommen. Prüfe ihn und klicke auf „Speichern“.', 'ok');
} catch (e) {
melde(e.message || 'Der KI-Vorschlag ist fehlgeschlagen.', 'fehler');
} finally {
btn.disabled = false;
label.textContent = 'Per KI vorschlagen';
}
});
})();
</script>
</body>
</html>