# Copyright (c) 2025 Patrick Motsch # All rights reserved. """Utility datamodels: Prompt, TextMultilingual.""" from typing import Any, Dict, Optional from pydantic import BaseModel, Field, field_validator from modules.datamodels.datamodelBase import PowerOnModel from modules.shared.i18nRegistry import i18nModel import uuid @i18nModel("Prompt") class Prompt(PowerOnModel): """Benutzer- oder System-Prompt fuer die KI.""" id: str = Field( default_factory=lambda: str(uuid.uuid4()), description="Primary key", json_schema_extra={"label": "ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False}, ) mandateId: str = Field( default="", description="ID of the mandate this prompt belongs to", json_schema_extra={"label": "Mandanten-ID", "frontend_type": "text", "frontend_readonly": True, "frontend_required": False}, ) isSystem: bool = Field( default=False, description="System prompt visible to all users (read-only for non-SysAdmin)", json_schema_extra={"label": "System", "frontend_type": "boolean", "frontend_readonly": True, "frontend_required": False}, ) content: str = Field( description="Content of the prompt", json_schema_extra={"label": "Inhalt", "frontend_type": "textarea", "frontend_readonly": False, "frontend_required": True}, ) name: str = Field( description="Name of the prompt", json_schema_extra={"label": "Name", "frontend_type": "text", "frontend_readonly": False, "frontend_required": True}, ) @field_validator('isSystem', mode='before') @classmethod def _coerceIsSystem(cls, v): """Existing records may have isSystem=None (field didn't exist). Treat None as False.""" if v is None: return False return v class TextMultilingual(BaseModel): """Multilingual text field. Language codes follow ISO 639-1 (en, de, fr, it, …).""" en: str = Field(description="English text (default language, required)") de: Optional[str] = Field(None, description="German text") fr: Optional[str] = Field(None, description="French text") it: Optional[str] = Field(None, description="Italian text") @field_validator('en') @classmethod def _validateEnRequired(cls, v): if not v or not v.strip(): raise ValueError("English text (en) is required and cannot be empty") return v def model_dump(self, **kwargs) -> Dict[str, str]: result = {} for key in self.model_fields: value = getattr(self, key, None) if value is not None: result[key] = value return result @classmethod def from_dict(cls, data: Dict[str, str]) -> 'TextMultilingual': fields = {k: data[k] for k in cls.model_fields if k in data} fields.setdefault('en', '') return cls(**fields) def get_text(self, lang: str = 'en') -> str: """Get text for *lang*. Falls back to English.""" value = getattr(self, lang, None) if value: return value return self.en @classmethod def fromUniform(cls, text: str) -> "TextMultilingual": """Same string in all languages (bootstrap / i18n key until per-language values exist in DB).""" t = text.strip() if not t: raise ValueError("Text must be non-empty") return cls(en=t, de=t, fr=t, it=t) def coerce_text_multilingual(val: Any) -> TextMultilingual: """Normalize str, dict, or TextMultilingual for Role.description and similar fields.""" if isinstance(val, TextMultilingual): return val if isinstance(val, dict): if not val: return TextMultilingual.fromUniform("—") d = {k: val[k] for k in TextMultilingual.model_fields if k in val and val[k] is not None} if not d.get("en"): d["en"] = (d.get("de") or d.get("fr") or "—").strip() or "—" return TextMultilingual(**{k: d[k] for k in TextMultilingual.model_fields if k in d}) if isinstance(val, str) and val.strip(): return TextMultilingual.fromUniform(val) return TextMultilingual.fromUniform("—")