from __future__ import annotations import json from typing import Any import httpx from app.config import settings from app.db import execute, fetch_one from app.services import ollama from app.services import packaging_agent AGENTS: dict[str, dict[str, str]] = { "marketing": {"name": "Marketing", "persona": "Social, brand voice, campaigns for Foodlinkk."}, "bizdev": {"name": "BizDev", "persona": "Pipeline, retail partnerships, deal structuring."}, "finance": {"name": "Finance", "persona": "Margins, cashflow, pricing for food brands."}, "sourcing": {"name": "Sourcing", "persona": "Suppliers, MOQ, lead times, procurement."}, "product": {"name": "Product", "persona": "SKU development, launch timelines, shelf readiness."}, "halal": {"name": "Halal", "persona": "Halal compliance, certification, ingredient vetting."}, "design": {"name": "Design", "persona": "Packaging, visual identity, retail presentation."}, "packaging": {"name": "Packaging", "persona": "SVG/PDF verpakkingsontwerp, stanstekeningen, drukwerk."}, "knowledge": {"name": "Knowledge", "persona": "Internal docs, RAG, policy answers."}, } IMAGE_KEYWORDS = ( "maak foto", "maak een foto", "genereer foto", "genereer afbeelding", "maak afbeelding", "productfoto", "genereer image", "generate image", "make image", "maak plaatje", "/genfoto", ) def _wants_image(raw: str) -> bool: t = (raw or "").strip().lower() return any(k in t for k in IMAGE_KEYWORDS) def _extract_image_prompt(raw: str) -> str: t = raw.strip() lower = t.lower() for k in IMAGE_KEYWORDS: if lower.startswith(k): rest = t[len(k):].strip(" :,-") if rest: return rest for k in IMAGE_KEYWORDS: if k in lower: idx = lower.index(k) + len(k) rest = t[idx:].strip(" :,-") if rest: return rest return t async def _log_event(agent_name: str, event_type: str, title: str, body: str, metadata: dict | None = None) -> None: payload = { "agent_name": agent_name, "agent_type": "herman_delegate", "event_type": event_type, "title": title[:255], "body": body, "metadata": metadata or {}, "status": "completed", "channel": "herman", } try: async with httpx.AsyncClient(timeout=15.0) as client: await client.post(f"{settings.TOOLS_API_URL.rstrip('/')}/events", json=payload) except Exception: try: execute( """INSERT INTO agent_events (agent_name, agent_type, event_type, title, body, status, channel, metadata) VALUES (%s, %s, %s, %s, %s, %s, %s, %s::jsonb)""", (agent_name, "herman_delegate", event_type, title[:255], body, "completed", "herman", json.dumps(metadata or {})), ) except Exception: pass def _pick_agent(raw: str) -> str: text = (raw or "").strip().lower() first = text.split()[0].replace(",", "").replace(".", "") if text else "knowledge" if first in AGENTS: return first for k in AGENTS: if k in text: return k return "knowledge" async def chat(message: str) -> dict[str, Any]: if packaging_agent.wants_packaging(message): try: outcome = await packaging_agent.generate_from_message(message) name = outcome.get("design_name") or "Design" pid = outcome.get("packaging_id", "")[:8] reply_lines = [ f"Packaging agent heeft een design klaar voor je: {name}", f"Type: {outcome.get('type')} · {outcome.get('dimensions')}", f"Project #{outcome.get('cockpit_project_id')}", f"Studio: {outcome.get('studio_url')}", f"PDF: {outcome.get('pdf_url')}", ] if outcome.get("nas", {}).get("ok"): reply_lines.append("Bestanden staan op de NAS in de packaging-map van het project.") reply = "\n".join(reply_lines) await _log_event( "packaging", "packaging_created", f"Design klaar: {name}", f"Herman-opdracht: {message[:1500]}\n\nDesign-ID: {outcome.get('packaging_id')}", { "packaging_id": outcome.get("packaging_id"), "project_id": outcome.get("cockpit_project_id"), "studio_url": outcome.get("studio_url"), "pdf_url": outcome.get("pdf_url"), "nas": outcome.get("nas"), "for_herman": True, }, ) await _log_event( "herman", "packaging_delivered", f"Packaging → Herman: {name}", reply[:2000], { "source_agent": "packaging", "packaging_id": outcome.get("packaging_id"), "project_id": outcome.get("cockpit_project_id"), }, ) return { "agent": "packaging", "agent_label": "Packaging → Herman", "reply": reply, "delegated_agents": ["packaging", "herman"], "routing_reason": "Packaging-opdracht gedetecteerd — design gegenereerd en aan Herman gerapporteerd", "packaging_id": outcome.get("packaging_id"), "packaging_studio_url": outcome.get("studio_url"), "packaging_pdf_url": outcome.get("pdf_url"), } except Exception as exc: await _log_event( "packaging", "packaging_error", "Packaging generatie mislukt", str(exc)[:1500], {"message": message[:500]}, ) return { "agent": "packaging", "agent_label": "Packaging", "reply": f"Packaging agent kon geen design maken: {exc}", "delegated_agents": ["packaging"], } if _wants_image(message): prompt = _extract_image_prompt(message) try: async with httpx.AsyncClient(timeout=620.0) as client: r = await client.post( f"{settings.TOOLS_API_URL.rstrip('/')}/images/generate", json={"prompt": prompt, "width": 512, "height": 512, "steps": 15}, ) r.raise_for_status() data = r.json() filename = data.get("filename", "") subfolder = data.get("subfolder", "") img_type = data.get("type", "output") proxy = f"/api/ai/generated-image?filename={filename}&subfolder={subfolder}&type={img_type}" reply = f"Afbeelding gegenereerd voor: {prompt}" await _log_event("design", "image_generated", "ComfyUI via Herman", prompt[:500], {"filename": filename}) return { "agent": "design", "agent_label": "Design", "reply": reply, "image_url": proxy, "prompt": prompt, } except Exception as exc: return { "agent": "design", "agent_label": "Design", "reply": f"Kon geen afbeelding genereren: {exc}", } try: async with httpx.AsyncClient(timeout=620.0) as client: r = await client.post( f"{settings.HERMAN_ORCHESTRATOR_URL.rstrip('/')}/chat", json={"message": message, "agent": "default", "use_crm": True, "channel": "cockpit"}, ) r.raise_for_status() data = r.json() delegated = data.get("delegated_agents") or [data.get("agent", "herman")] await _log_event( "herman", "openswarm_delegation", f"Herman → {', '.join(delegated)}", message[:2000], {"delegated": delegated, "reason": data.get("routing_reason", "")}, ) return { "agent": data.get("agent", "herman"), "agent_label": data.get("agent_label", "Herman"), "reply": data.get("reply", ""), "delegated_agents": delegated, "routing_reason": data.get("routing_reason", ""), } except Exception as exc: return { "agent": "herman", "agent_label": "Herman", "reply": f"Herman orchestrator niet bereikbaar: {exc}", } async def generate_briefing() -> str: from app.services.briefing import generate_daily_briefing content, stats = await generate_daily_briefing() await _log_event("herman", "briefing", "CEO briefing generated", content[:1500], {"stats": stats}) return content