Globale Suche (Volltext + erweiterte Suche)
This commit is contained in:
@@ -0,0 +1,125 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
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_rate` ist ein **Tagessatz**; `null` heißt *nicht hinterlegt*, nicht
|
||||||
|
*null Euro*. Verglichen wird nur, wo ein Wert steht.
|
||||||
|
* Kontakte mit `mail_pool` sind noch nicht gepflegt – sie zählen mit, werden
|
||||||
|
aber als aus dem Postfach stammend benannt.
|
||||||
|
* `icon_contacts` sind Kontakte des ICON CRM. Ihre Kennung ist eine Zeichenkette
|
||||||
|
(`icon-31`) und gehört **nicht** in `refs`.
|
||||||
|
|
||||||
|
## Schlüssel
|
||||||
|
|
||||||
|
Die erweiterte Suche benutzt **denselben OpenAI-Schlüssel wie der Scanner** –
|
||||||
|
`openai_api_key` in der Scanner-Tabelle `settings`, gepflegt unter
|
||||||
|
*Einstellungen → Scanner*, 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: `gpt-4o-mini`, wie im Scanner. 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 des Scanners |
|
||||||
|
| `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.
|
||||||
Reference in New Issue
Block a user