diff --git a/src/models.py b/src/models.py index 2766984..d162f11 100644 --- a/src/models.py +++ b/src/models.py @@ -68,6 +68,12 @@ class IncidentUpdate(BaseModel): visibility: Optional[str] = Field(default=None, pattern="^(public|private)$") +class DescriptionEnhanceRequest(BaseModel): + title: str = Field(min_length=3) + description: str | None = None + type: str = Field(default="adhoc", pattern="^(adhoc|research)$") + + class IncidentResponse(BaseModel): id: int title: str diff --git a/src/routers/incidents.py b/src/routers/incidents.py index 8b2d8b6..9e00790 100644 --- a/src/routers/incidents.py +++ b/src/routers/incidents.py @@ -1,7 +1,7 @@ """Incidents-Router: Lagen verwalten (Multi-Tenant).""" from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, status from fastapi.responses import StreamingResponse -from models import IncidentCreate, IncidentUpdate, IncidentResponse, SubscriptionUpdate, SubscriptionResponse +from models import IncidentCreate, IncidentUpdate, IncidentResponse, SubscriptionUpdate, SubscriptionResponse, DescriptionEnhanceRequest from auth import get_current_user from middleware.license_check import require_writable_license from database import db_dependency, get_db @@ -155,6 +155,65 @@ async def get_refreshing_incidents( } +# --- Beschreibung generieren (Prompt Enhancement) --- + +ENHANCE_PROMPT_RESEARCH = """Du generierst ein strukturiertes Recherche-Briefing fuer ein OSINT-Lagemonitoring-System. +WICHTIG: Verwende IMMER echte UTF-8-Umlaute (ae, oe, ue, ss) und KEINE Umschreibungen. + +Titel: {title} +Vorhandener Kontext: {context} +Typ: Hintergrundrecherche + +Erstelle ein praezises Recherche-Briefing mit: +1. Vollstaendiger Name/Bezeichnung des Themas (inkl. Rechtsform bei Unternehmen, voller Name bei Personen) +2. Recherche-Schwerpunkte (5-8 thematische Punkte, z.B. Geschichte, Finanzen, Fuehrung, Kontroversen, Innovation) +3. Relevante Suchbegriffe (deutsch + englisch, inkl. Abkuerzungen und alternative Schreibweisen) + +Schreibe NUR das Briefing als Fliesstext mit Aufzaehlungen. Keine Erklaerungen davor oder danach.""" + +ENHANCE_PROMPT_ADHOC = """Du generierst eine praezise Vorfallsbeschreibung fuer ein OSINT-Lagemonitoring-System. +WICHTIG: Verwende IMMER echte UTF-8-Umlaute (ae, oe, ue, ss) und KEINE Umschreibungen. + +Titel: {title} +Vorhandener Kontext: {context} +Typ: Live-Monitoring (aktuelle Ereignisse) + +Erstelle eine knappe, informative Beschreibung mit: +1. Was ist passiert / worum geht es +2. Wo (geographischer Kontext) +3. Wer ist beteiligt (Akteure, Organisationen, Laender) +4. Wonach soll gesucht werden (aktuelle Entwicklungen, Reaktionen, Hintergruende) + +Schreibe NUR die Beschreibung als Fliesstext (3-5 Zeilen). Keine Erklaerungen davor oder danach.""" + +_enhance_logger = logging.getLogger("osint.enhance") + + +@router.post("/enhance-description") +async def enhance_description( + data: DescriptionEnhanceRequest, + current_user: dict = Depends(get_current_user), +): + """Generiert eine strukturierte Beschreibung per KI aus dem Titel.""" + from agents.claude_client import call_claude + from config import CLAUDE_MODEL_FAST + + template = ENHANCE_PROMPT_RESEARCH if data.type == "research" else ENHANCE_PROMPT_ADHOC + context = data.description.strip() if data.description and data.description.strip() else "Kein Kontext angegeben" + prompt = template.format(title=data.title.strip(), context=context) + + try: + result, usage = await call_claude(prompt, tools=None, model=CLAUDE_MODEL_FAST) + _enhance_logger.info( + f"Beschreibung generiert fuer \"{data.title[:50]}\": " + f"{usage.input_tokens}in/{usage.output_tokens}out" + ) + return {"description": result.strip()} + except Exception as e: + _enhance_logger.error(f"Beschreibung generieren fehlgeschlagen: {e}") + raise HTTPException(status_code=500, detail="Beschreibung konnte nicht generiert werden") + + @router.get("/{incident_id}", response_model=IncidentResponse) async def get_incident( incident_id: int, diff --git a/src/static/css/style.css b/src/static/css/style.css index 86d1c30..034f8da 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -2076,6 +2076,26 @@ a:hover { margin-top: var(--sp-xs); } +.description-enhance-row { + margin-top: var(--sp-md); + display: flex; + align-items: center; +} + +.spinner-inline { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid var(--border); + border-top-color: var(--accent-primary); + border-radius: 50%; + animation: spin-inline 0.8s linear infinite; +} + +@keyframes spin-inline { + to { transform: rotate(360deg); } +} + /* === Inline-Zitate === */ .citation { color: var(--accent); diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 741a033..d7348b5 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -335,6 +335,13 @@