KI: Modellauswahl als Dropdown + Abgleich mit Produktionsstand
Einstellungen -> KI: statt freiem Modellnamen ein Dropdown mit den Chat-Modellen, die der hinterlegte OpenAI-Schluessel nutzen darf. - Backend: listAiModels() fragt OpenAI GET /v1/models, filtert auf Chat-Modelle (gpt-*, o*, chatgpt-*; ohne Embedding/Audio/Bild/Instruct usw.), neueste zuerst, 10 min im Speicher (Schluesselwechsel laedt neu) - Neuer Endpunkt GET /api/ai/models (admin) - Frontend: aktives Modell bleibt waehlbar, auch wenn nicht mehr gelistet; Rueckfall auf Textfeld, wenn die Liste nicht abrufbar ist - Doku: 05-ki (Modellauswahl mit Ablaufdiagramm), 10-api, 08-anleitung Zieht ausserdem den bereits live laufenden, bisher nicht eingecheckten Stand nach (perinco-Kontaktabgleich/MergeDialog, Projektmargen, Whisper-Anpassungen u. a.). Repo entspricht damit icrm.icon-experts.de. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3f25355a6d
commit
cbac02a96a
@@ -152,6 +152,24 @@ Projekt).
|
||||
(Aktiv / Nicht aktiv / Abgeschlossen), `rate`, `notes` (HTML). Im Menü heißt die
|
||||
Ansicht **Laufende Projekte**.
|
||||
|
||||
`rate` ist ein **Tagessatz** in Euro – das, was ICON dem Kunden berechnet. Beim
|
||||
Berater stehen dagegen **Stundensätze** (`rate_onsite`, `rate_remote` in
|
||||
`icon_contacts`). Die Marge rechnet mit **1 Tag = 8 Stunden**:
|
||||
|
||||
```
|
||||
Marge = rate − Stundensatz des Beraters × 8
|
||||
```
|
||||
|
||||
Die Marge wird **nicht gespeichert**. Sie ist eine Rechnung aus zwei Feldern,
|
||||
die sich beide ändern können – ein gespeicherter Wert wäre nach der ersten
|
||||
Satzänderung falsch, ohne dass es jemand merkt. `GET /api/projects` liefert die
|
||||
beiden Sätze des Beraters deshalb als `contact_rate_onsite` /
|
||||
`contact_rate_remote` mit; gerechnet wird in `src/components/projects/margin.ts`.
|
||||
|
||||
Es gibt am Projekt kein Feld für die Einsatzart. **Vorrang hat der
|
||||
Remote-Satz**; nur wenn er fehlt, wird der Vor-Ort-Satz genommen. Welcher es
|
||||
war, steht in der Oberfläche dabei.
|
||||
|
||||
Im übernommenen Bestand steht im Kopf von `notes` ein Textblock aus dem alten
|
||||
CRM (`KUNDE:`, `KONTAKT:`, `EXPERTE:`, `START:`, `ENDE:`), darunter der
|
||||
übernommene Ausschreibungstext als HTML. `src/lib/richText.ts` macht daraus im
|
||||
|
||||
+7
-1
@@ -38,7 +38,7 @@ flowchart TD
|
||||
V --> KON["contacts/<br/>Liste, Kacheln, Netzwerk, CV-Import"]
|
||||
V --> UNT["companies/<br/>Liste, Kacheln, Group, Netzwerk"]
|
||||
V --> AUS["tenders/<br/>Tabelle, Live!, Archiv"]
|
||||
V --> PRJ["projects/"]
|
||||
V --> PRJ["projects/<br/>Kacheln, Karte, Marge"]
|
||||
V --> HIS["history/"]
|
||||
V --> SET["settings/<br/>Nutzer, Stammdaten, Konto, Diktat"]
|
||||
KON --> NW["network/Network.tsx<br/>d3-force + SVG"]
|
||||
@@ -46,6 +46,12 @@ flowchart TD
|
||||
NW --> CT["network/CompanyTree.tsx"]
|
||||
```
|
||||
|
||||
`projects/margin.ts` rechnet die Marge: Tagessatz des Projekts minus
|
||||
Stundensatz des Beraters mal acht. Die Rechnung liegt neben der Ansicht und
|
||||
nicht im Backend, weil sie in der Projektkarte **live** mitlaufen muss – dort
|
||||
lassen sich Tagessatz und Berater ändern, bevor gespeichert wird. Siehe
|
||||
`docs/02-datenmodell.md`, Abschnitt `icon_projects`.
|
||||
|
||||
Zwei Dinge liegen bewusst in `App.tsx`:
|
||||
|
||||
* Die Listen der Unternehmen und Benutzer werden **einmal** geladen und
|
||||
|
||||
+34
-1
@@ -22,12 +22,45 @@ Dienst neu starten müssen.
|
||||
| Anzeige | nur verkürzt (`sk-proj…a1b2`) – vollständig verlässt der Schlüssel den Server nie |
|
||||
| Eingabe | Passwortfeld, schreibt nur; ein bestehender Wert wird ersetzt |
|
||||
| Entfernen | löscht den Wert in der Datenbank; steht in der `.env` einer, gilt wieder dieser |
|
||||
| Modell | frei setzbar, Vorgabe `gpt-5.6-luna` |
|
||||
| Modell | Auswahlliste der Chat-Modelle, die der Schlüssel nutzen darf (siehe unten); Vorgabe `gpt-5.6-luna` |
|
||||
| Test | ein Miniaufruf gegen das Modell – sagt, ob Schlüssel, Modellname und Kontingent zusammenpassen |
|
||||
|
||||
In der Historie landet, **dass** jemand den Schlüssel gewechselt hat, nie der
|
||||
Wert. Ein stiller Wechsel wäre später nicht mehr nachvollziehbar.
|
||||
|
||||
### Modellauswahl
|
||||
|
||||
Statt eines freien Textfelds zeigt der Reiter ein Dropdown. Die Liste kommt von
|
||||
`GET /api/ai/models`, das Backend fragt dafür OpenAI `GET /v1/models` mit dem
|
||||
hinterlegten Schlüssel (`listAiModels()` in `ai.js`) – angezeigt wird also genau
|
||||
das, was dieser Schlüssel darf.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant B as Browser (AiSettings)
|
||||
participant S as Backend /api/ai/models
|
||||
participant O as OpenAI /v1/models
|
||||
B->>S: GET (admin)
|
||||
alt Liste im Speicher und < 10 min alt, gleicher Schlüssel
|
||||
S-->>B: { models }
|
||||
else
|
||||
S->>O: GET mit Bearer-Schlüssel
|
||||
O-->>S: alle Modelle
|
||||
S->>S: nur Chat-Modelle, neueste zuerst
|
||||
S-->>B: { models }
|
||||
end
|
||||
```
|
||||
|
||||
| Regel | Umsetzung |
|
||||
| --- | --- |
|
||||
| Nur Chat-Modelle | ID beginnt mit `gpt-`, `o<Ziffer>` oder `chatgpt-`; raus fallen Embedding, Whisper, TTS, Bild, Audio, Realtime, Transcribe, Moderation, Search, Instruct, Codex, Computer-Use, Sora |
|
||||
| Reihenfolge | nach `created` absteigend – neueste oben |
|
||||
| Zwischenspeicher | 10 Minuten im Speicher; ein anderer Schlüssel lädt sofort neu |
|
||||
| Aktives Modell nicht mehr gelistet | bleibt im Dropdown, markiert „– nicht verfügbar" |
|
||||
| Vorgabemodell | markiert „(Vorgabe)" |
|
||||
| Liste nicht abrufbar (kein Schlüssel, OpenAI down) | Rückfall auf das Textfeld mit Hinweis und *Erneut versuchen* |
|
||||
| Schlüssel gespeichert/entfernt | Liste wird neu geladen bzw. verworfen |
|
||||
|
||||
> **Fallstrick:** `gpt-5.6-luna` akzeptiert **kein eigenes `temperature`** und
|
||||
> weist jeden Wert außer dem Standard mit HTTP 400 ab. `ai.js` schickt deshalb
|
||||
> keines. `response_format: {type:'json_object'}` funktioniert dagegen.
|
||||
|
||||
@@ -159,9 +159,33 @@ Zwei Untermenüs:
|
||||
|
||||
## Laufende Projekte
|
||||
|
||||
Titel, Kunde, Experte, Zeitraum, Status und Notizen. Die Notizen sind ein
|
||||
WYSIWYG-Feld – im übernommenen Bestand steht dort ein Kopfblock aus dem alten
|
||||
CRM und darunter der Ausschreibungstext.
|
||||
Titel, Kunde, Experte, Zeitraum, Status, Tagessatz und Notizen. Die Notizen
|
||||
sind ein WYSIWYG-Feld – im übernommenen Bestand steht dort ein Kopfblock aus dem
|
||||
alten CRM und darunter der Ausschreibungstext.
|
||||
|
||||
### Marge
|
||||
|
||||
Jede Projektkachel weist die Marge aus, und in der Projektkarte steht die
|
||||
Rechnung aufgeschlüsselt:
|
||||
|
||||
| Zeile | Woher |
|
||||
| --- | --- |
|
||||
| Tagessatz Kunde | Feld **Tagessatz** am Projekt |
|
||||
| Stundensatz Berater | Kontaktkarte des Experten |
|
||||
| Kosten (8 h) | Stundensatz × 8 |
|
||||
| Marge | Tagessatz − Kosten, dazu der Prozentsatz vom Tagessatz |
|
||||
|
||||
**Ein Tag sind 8 Stunden.** Hat der Berater einen Remote- *und* einen
|
||||
Vor-Ort-Satz, wird mit dem **Remote-Satz** gerechnet; fehlt der, mit dem
|
||||
Vor-Ort-Satz. Welcher benutzt wurde, steht daneben.
|
||||
|
||||
Die Farbe ist eine schnelle Ampel: grün ab 25 %, gelb darüber bis 0, rot bei
|
||||
negativer Marge.
|
||||
|
||||
Fehlt der Tagessatz oder hat der Berater keinen Stundensatz, steht statt einer
|
||||
Zahl der Grund dort – eine leere Zeile ließe offen, ob die Marge null oder
|
||||
unbekannt ist. Der Tagessatz wird am Projekt gepflegt, die Stundensätze beim
|
||||
Kontakt (*Kontakt → Stundensatz vor Ort / remote*).
|
||||
|
||||
## Historie
|
||||
|
||||
@@ -191,7 +215,7 @@ nichts gespeichert. Details: [04 Diktat](04-diktat.md).
|
||||
| --- | --- | --- |
|
||||
| Nutzerverwaltung | Administratoren | Benutzer anlegen, Rolle, aktiv/inaktiv |
|
||||
| Stammdaten | Administratoren | Auswahllisten: Branchen, Arten der Kontakte |
|
||||
| KI | Administratoren | OpenAI-Schlüssel, Modell, Verbindungstest |
|
||||
| KI | Administratoren | OpenAI-Schlüssel, Modell (Auswahlliste der für den Schlüssel verfügbaren Modelle), Verbindungstest |
|
||||
| Mein Konto | alle | Foto, Farbe, Passwort, Design |
|
||||
| Diktat | alle | Spracherkennung einrichten |
|
||||
|
||||
|
||||
@@ -71,6 +71,11 @@ auch Phasenwechsel, der Schalter `online` und die Zuweisung von Kontakten.
|
||||
|
||||
`GET /`, `POST /`, `PATCH /:id`, `DELETE /:id`.
|
||||
|
||||
`GET /` liefert zu jedem Projekt zusätzlich `contact_rate_onsite` und
|
||||
`contact_rate_remote` – die Stundensätze des zugeordneten Beraters. Sie sind
|
||||
nur für die Margen-Anzeige da und **nicht** über `PATCH` änderbar; gepflegt
|
||||
werden sie am Kontakt.
|
||||
|
||||
## Leads – `/api/leads`
|
||||
|
||||
| Endpunkt | Zweck |
|
||||
@@ -102,6 +107,7 @@ auch Phasenwechsel, der Schalter `online` und die Zuweisung von Kontakten.
|
||||
| `GET /status` | `{ configured, model }` – steuert, ob die Knöpfe erscheinen |
|
||||
| `POST /text` | Schreibhilfe: `{ task, text, prompt }` |
|
||||
| `GET /config` | *admin* – Zustand der Einrichtung; der Schlüssel nur verkürzt |
|
||||
| `GET /models` | *admin* – `{ models: [{ id, created }] }`, Chat-Modelle des hinterlegten Schlüssels, neueste zuerst (10 min zwischengespeichert) |
|
||||
| `PATCH /config` | *admin* – `{ key?, model? }`; `key: ''` entfernt ihn wieder |
|
||||
| `POST /test` | *admin* – Miniaufruf gegen das Modell |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user