From 8f5c0e9e5d016762a5a1a0652274da602653d608 Mon Sep 17 00:00:00 2001 From: claude-dev Date: Thu, 30 Jul 2026 21:25:59 +0000 Subject: [PATCH] feat(eu-umbau): Umschalter fuer den KI-Weg in der Oberflaeche Im Anlege- und Bearbeiten-Dialog einer Lage laesst sich jetzt waehlen, ob sie ueber den heutigen Weg (Anthropic) oder den EU-Weg (Bedrock Frankfurt + staan) laeuft; Standard bleibt die Vorgabe der Organisation. Dazu ai_backend in den Schemas und den Lagen-Endpunkten (Create, Update, Ausgabe), Leerstring setzt bewusst auf Standard zurueck. In der Lagen-Kopfzeile zeigt ein Kennzeichen 'EU' an, dass die Verarbeitung europaeisch laeuft. UI-Sync: bewusst NUR online. Der Lokal-Fork hat kein Bedrock-Backend, dort waere das Feld funktionslos (wie X-Zugaenge und Studio-Gating). Co-Authored-By: Claude Fable 5 --- src/models.py | 9 +++++++++ src/routers/incidents.py | 11 ++++++++--- src/static/css/style.css | 15 +++++++++++++++ src/static/dashboard.html | 9 +++++++++ src/static/js/app.js | 7 +++++++ 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/models.py b/src/models.py index 769e78b..06ba553 100644 --- a/src/models.py +++ b/src/models.py @@ -62,6 +62,9 @@ class IncidentCreate(BaseModel): include_telegram: bool = False include_x: bool = False visibility: str = Field(default="public", pattern="^(public|private)$") + # KI-Weg dieser Lage (EU-Umbau). Leer/None = Vorgabe der Organisation + # bzw. globaler Default. 'cli' = Anthropic, 'bedrock' = EU (Frankfurt + staan). + ai_backend: Optional[str] = Field(default=None, pattern="^(cli|bedrock)$") class IncidentUpdate(BaseModel): @@ -77,6 +80,10 @@ class IncidentUpdate(BaseModel): include_telegram: Optional[bool] = None include_x: Optional[bool] = None visibility: Optional[str] = Field(default=None, pattern="^(public|private)$") + # KI-Weg. Leerstring "" setzt bewusst auf "Vorgabe der Organisation" zurueck, + # deshalb hier auch der Leerstring erlaubt (exclude_none im Router laesst + # None durchfallen, "" kommt an und wird zu NULL). + ai_backend: Optional[str] = Field(default=None, pattern="^(cli|bedrock|)$") class DescriptionEnhanceRequest(BaseModel): @@ -108,6 +115,7 @@ class IncidentResponse(BaseModel): international_sources: bool = True include_telegram: bool = False include_x: bool = False + ai_backend: Optional[str] = None created_by: int created_by_username: str = "" created_at: str @@ -137,6 +145,7 @@ class IncidentListItem(BaseModel): international_sources: bool = True include_telegram: bool = False include_x: bool = False + ai_backend: Optional[str] = None created_by: int created_by_username: str = "" created_at: str diff --git a/src/routers/incidents.py b/src/routers/incidents.py index dd32e98..1231480 100644 --- a/src/routers/incidents.py +++ b/src/routers/incidents.py @@ -22,6 +22,7 @@ router = APIRouter(prefix="/api/incidents", tags=["incidents"]) INCIDENT_UPDATE_COLUMNS = { "title", "description", "type", "status", "refresh_mode", "refresh_interval", "refresh_start_time", "retention_days", "international_sources", "include_telegram", "include_x", "visibility", + "ai_backend", } @@ -89,7 +90,7 @@ async def list_incidents( query = ( "SELECT id, title, description, type, status, refresh_mode, refresh_interval, " "refresh_start_time, retention_days, visibility, " - "international_sources, include_telegram, include_x, created_by, created_at, updated_at, " + "international_sources, include_telegram, include_x, ai_backend, created_by, created_at, updated_at, " "CASE WHEN summary IS NOT NULL AND summary != '' THEN 1 ELSE 0 END AS has_summary " "FROM incidents WHERE tenant_id = ? AND (visibility = 'public' OR created_by = ?)" ) @@ -121,8 +122,8 @@ async def create_incident( cursor = await db.execute( """INSERT INTO incidents (title, description, type, refresh_mode, refresh_interval, refresh_start_time, retention_days, international_sources, include_telegram, include_x, visibility, - tenant_id, created_by, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", + ai_backend, tenant_id, created_by, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", ( data.title, data.description, @@ -135,6 +136,7 @@ async def create_incident( 1 if data.include_telegram else 0, 1 if data.include_x else 0, data.visibility, + data.ai_backend or None, tenant_id, current_user["id"], now, @@ -385,6 +387,9 @@ async def update_incident( continue if field in ("international_sources", "include_telegram", "include_x"): updates[field] = 1 if value else 0 + elif field == "ai_backend": + # Leerstring = "Vorgabe der Organisation", wird als NULL gespeichert. + updates[field] = value or None else: updates[field] = value diff --git a/src/static/css/style.css b/src/static/css/style.css index 45eaf7e..e5af820 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -6180,3 +6180,18 @@ body.tutorial-active .tutorial-cursor { .pipeline-block.status-active { box-shadow: var(--glow-accent); } .pipeline-stage.is-looping .pipeline-loop { animation: none !important; opacity: 1; } } + +/* EU-Kennzeichen in der Lagen-Kopfzeile (EU-Umbau) */ +.incident-eu-badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.5px; + background: rgba(16, 185, 129, 0.16); + color: #10B981; + flex-shrink: 0; +} +.incident-eu-badge[hidden] { display: none; } diff --git a/src/static/dashboard.html b/src/static/dashboard.html index c5135dd..1904b01 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -164,6 +164,7 @@

+
@@ -411,6 +412,14 @@
+
+ + +