Neuer Admin-Reiter "KI": zeigt die letzten vier Zeichen des hinterlegten OpenAI-Schluessels samt Quelle, nimmt einen neuen an (vorher gegen /v1/models geprueft) und laesst je Einsatzzweck das Modell per Dropdown waehlen - Scanner-Bewertung, Abgleich "bereits beworben", erweiterte Suche. Die Auswahl kommt live von OpenAI, Vorgaben sind die bisher fest verdrahteten Modelle. Reasoning-Modelle bekommen reasoning_effort statt temperature, damit ein Wechsel keinen 400er ausloest. Schluesselfeld aus dem Scanner-Reiter entfernt; Doku in docs/14-ki.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.2 KiB
12 Globale Suche
Ein Feld oben in der Kopfleiste, zwei Modi. Übernommen aus dem ICON CRM – gleiche Gesten, gleicher Aufbau, damit niemand zweimal lernen muss, wo er hinfassen soll.
Strg+K (bzw. Cmd+K) springt von überall dorthin, Esc leert das Feld.
| Modus | Symbol | Verhalten |
|---|---|---|
| Volltext | Lupe | läuft beim Tippen mit (220 ms Verzögerung), Treffer nach Bereichen gruppiert |
| Erweitert | Funke | eine Frage in Worten, beantwortet vom Modell – erst auf Enter |
Was durchsucht wird
flowchart LR
Q["Suchfeld"] --> API["GET /api/search?q="]
API --> L[("crm_leads")]
API --> O[("crm_opportunities")]
API --> A[("crm_acquisitions")]
API --> E[("crm_events")]
API --> IC["ICON CRM<br/>fetchIconContacts()"]
| Gruppe | Quelle | Felder |
|---|---|---|
| Kontakte | crm_leads |
Name, Firma, E-Mail, Telefon, Mobil, Position, Tags, Notizen, Ort |
| Kontakte (ICON CRM) | Schnittstelle | Name, Position, Firma, E-Mail, Telefon, Tags |
| Opportunities | crm_opportunities |
Titel, Firma, Beschreibung, Notizen, Tags, nächster Schritt |
| Akquise | crm_acquisitions |
Titel, Firma, Beschreibung, Notizen, Tags, Absender, Anbieter, Berater |
| Termine | crm_events |
Titel, Beschreibung, Ort |
Gelöschtes (deleted_at) bleibt außen vor. Kontakte aus dem Vorbereich „Aus
Mails" (mail_pool = 1) erscheinen mit, aber hinter den gepflegten und mit dem
Hinweis „aus dem Postfach" – sie sind oft unvollständig.
Bewusst LIKE statt Volltextindex: die Datenmengen sind klein, und ein
FULLTEXT-Index hülfe bei Teilwortsuche („informat" soll „Finanz Informatik"
finden) nicht.
Die Kontakte des ICON CRM liegen nicht in dieser Datenbank; sie kommen über die
Schnittstelle (fetchIconContacts(), eine Minute Zwischenspeicher) und werden
im Speicher gefiltert. Fällt das andere Haus aus, fehlt nur diese Gruppe – die
übrige Suche bleibt benutzbar. Ihre Treffer sind gold umrandet und öffnen
nichts: gepflegt werden sie drüben.
Was ein Treffer öffnet
| Gruppe | Klick führt zu |
|---|---|
| Kontakte | die Kontaktkarte (goTo('Kontakte', { leadId })) |
| Opportunities | die Karte der Opportunity (goTo('Opportunities', { opportunityId })) |
| Akquise | die Akquise-Ansicht |
| Termine | die Interviews-Ansicht |
| Kontakte (ICON CRM) | nichts – nur Anzeige |
Für Kontakt und Opportunity gibt es einen Direktaufruf: navigation.goTo()
nimmt dafür leadId bzw. opportunityId entgegen. Akquise und Termine kennen
keinen – dort wechselt der Treffer in die Ansicht.
Erweiterte Suche
sequenceDiagram
actor B as Benutzer
participant E as Express
participant M as MariaDB
participant O as OpenAI
B->>E: POST /api/search/ai { question }
E->>M: Bestand lesen (Kontakte, Opportunities, Akquise, Termine)
E->>E: zusammenfassen, ab 400 Zeilen je Liste kuerzen
E->>O: Systemprompt + Bestand + Frage
O-->>E: { answer, refs }
E-->>B: Antwort + anklickbare Chips
Sie läuft nicht beim Tippen mit, sondern erst auf Enter – jede Anfrage kostet. Und sie erzeugt kein SQL: der Bestand wird mitgegeben. Ein Modell, das Abfragen auf die Datenbank schreiben darf, wäre eine Angriffsfläche; der Bestand ist klein genug, um ihn vollständig mitzuliefern. Wird gekürzt, sagt die Antwort das dazu.
Die genannten Datensätze kommen als refs zurück und stehen unter der Antwort
als anklickbare Schaltflächen.
Drei Regeln stehen ausdrücklich im Systemprompt, weil die Antworten sonst falsch werden:
daily_rateist ein Tagessatz;nullheißt nicht hinterlegt, nicht null Euro. Verglichen wird nur, wo ein Wert steht.- Kontakte mit
mail_poolsind noch nicht gepflegt – sie zählen mit, werden aber als aus dem Postfach stammend benannt. icon_contactssind Kontakte des ICON CRM. Ihre Kennung ist eine Zeichenkette (icon-31) und gehört nicht inrefs.
Schlüssel
Die erweiterte Suche benutzt denselben OpenAI-Schlüssel wie der Scanner –
openai_api_key in der Scanner-Tabelle settings, gepflegt unter
Einstellungen → KI (siehe 14 KI), ersatzweise OPENAI_API_KEY aus der Umgebung. Ein
zweiter Schlüssel an zweiter Stelle wäre eine Quelle für den Fall „die eine
Funktion geht, die andere nicht, und keiner weiß warum".
Modell: einstellbar unter Einstellungen → KI, Vorgabe gpt-4o-mini. Fehlt der Schlüssel, antwortet der
Endpunkt mit 503 und dem Hinweis auf die Einstellungen; die Volltextsuche
bleibt davon unberührt.
Der Aufruf liegt in server/src/ai.js – bewusst ohne das openai-Paket:
gebraucht wird ein einziger Aufruf, und fetch ist in Node eingebaut. Der
Scanner behält sein SDK, dort hängt mehr daran.
Dateien
| Datei | Aufgabe |
|---|---|
server/src/routes/search.js |
beide Endpunkte |
server/src/ai.js |
OpenAI-Aufruf, Schlüssel, Modellwahl |
server/src/routes/ai.js |
Einstellungen → KI (siehe 14 KI) |
server/src/routes/iconCrm.js |
fetchIconContacts() samt Zwischenspeicher |
src/components/GlobalSearch.tsx |
das Feld, beide Modi, die Trefferliste |
src/components/Header.tsx |
bindet es mittig in die Kopfleiste ein |
Auf schmalen Fenstern (< 768 px) wird das Feld ausgeblendet – dort ist der Titel der Ansicht wichtiger.