"""Dona: reuse the existing, data-only dashboard renderer for Hermes replies. This module never executes model-supplied HTML, JavaScript or instructions. Only `dona_dynamic.normalize_dashboard` can build cards; invalid content remains text. """ from __future__ import annotations import re from typing import Any from services import dona_dynamic MAX_JSON = dona_dynamic.MAX_RESPONSE def is_monitor_request(prompt: str) -> bool: """Route clearly operational Proxmox/Wazuh checks to existing dashboard API.""" q = str(prompt or '').casefold() if not re.search(r'\b(proxmox|wazuh|cluster|clusters)\b', q): return False return bool(re.search( r'\b(v[eé]rifi(?:e|es|er|ez)|contr[oô]le(?:r|s|z)?|surveille(?:r|s|z)?|' r'montre|affiche|tableau|[eé]tat|statut|status|ressources?|' r'performances?|noeuds?|nœuds?|serveurs?|alertes?|supervision|' r'fonctionn(?:e|ement)|disponibilit[eé])\b', q)) def _candidate_text(answer: Any, display: Any) -> str | None: if isinstance(display, dict): if isinstance(display.get('cards'), list): # Raw dashboard object should not be converted via repr(). import json return json.dumps(display, ensure_ascii=False) if display.get('type') in (None, 'text'): raw = display.get('text') if isinstance(raw, str) and '"cards"' in raw: return raw if isinstance(answer, str) and '"cards"' in answer: return answer if isinstance(answer, dict): if isinstance(answer.get('cards'), list): import json return json.dumps(answer, ensure_ascii=False) for key in ('text', 'speech'): raw = answer.get(key) if isinstance(raw, str) and '"cards"' in raw: return raw return None def _clean_line_continuations(text: str) -> str: """Repair two common harmless Hermes JSON formatting artefacts only. Trailing backslash + real newline (copied JSON), and invalid ``\\~`` escaping in approximate values. Other malformed content is left untouched. """ text = re.sub(r'\\[ \t]*\r?\n', '\n', text) return text.replace('\\~', '~') def as_board(answer: Any, display: Any, prompt: str) -> dict | None: """Return a server-validated dashboard, or None for an ordinary answer.""" raw = _candidate_text(answer, display) if not raw or len(raw) > MAX_JSON: return None try: extracted = dona_dynamic.extract_dashboard(_clean_line_continuations(raw)) return dona_dynamic.normalize_dashboard(extracted, prompt=prompt, origin='hermes') except (ValueError, TypeError, KeyError): return None def convert(answer: Any, prompt: str, display: Any, speech: str): """Convert ONLY actual card JSON. Preserve ordinary answers and images.""" if isinstance(display, dict) and display.get('type') not in (None, 'text'): return display, speech board = as_board(answer, display, prompt) if board is None: return display, speech return {'type': 'dona_dynamic', 'dashboard': board}, 'Voici le tableau de bord : ' + board['title']