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:
schakal00783
2026-09-23 10:26:30 +02:00
co-authored by Claude Opus 5.5
parent 3f25355a6d
commit cbac02a96a
40 changed files with 1476 additions and 80 deletions
+8 -2
View File
@@ -56,7 +56,7 @@ export interface DirectoryUser {
* solange die Liste noch nicht geladen ist.
*/
export type ContactKind = string;
export const DEFAULT_CONTACT_KINDS = ['Experte', 'Ansprechpartner', 'Lead'];
export const DEFAULT_CONTACT_KINDS = ['Experte', 'Ansprechpartner (Kunde)', 'Lead'];
/*
* Der frueher vorhandene Verfuegbarkeitsstatus ist entfallen (Featureliste vom
@@ -76,9 +76,11 @@ export const CONTACT_STATE_LABELS: Record<ContactState, string> = {
export interface Contact {
id: number;
/** Akademischer Titel als Freitext ("Dr.", "Prof. Dr."). */
title: string | null;
first_name: string | null;
last_name: string;
/** Vor- und Nachname, vom Backend zusammengesetzt. */
/** Titel, Vor- und Nachname, vom Backend zusammengesetzt. */
name: string;
position: string | null;
email: string | null;
@@ -271,9 +273,13 @@ export interface Project {
contact_id: number | null;
contact_name?: string | null;
contact_avatar?: string | null;
/** Stundensaetze des Beraters - die Liste liefert sie mit, siehe `projectMargin`. */
contact_rate_onsite?: number | null;
contact_rate_remote?: number | null;
started_on: string | null;
ended_on: string | null;
status: 'Aktiv' | 'Nicht aktiv' | 'Abgeschlossen';
/** Tagessatz, den ICON dem Kunden berechnet (in Euro). */
rate: number | null;
notes: string | null;
}