gateway/modules/datamodels/datamodelUtils.py
2026-04-10 12:33:27 +02:00

106 lines
4.1 KiB
Python

# 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("")