from __future__ import annotations from datetime import datetime from typing import Literal from pydantic import BaseModel, Field, field_validator class SettingsCompanyForm(BaseModel): companyName: str = Field(min_length=1, max_length=120) displayName: str = Field(min_length=1, max_length=120) companyCode: str = Field(default="", max_length=64) recordNumber: str = Field(default="", max_length=120) copyright: str = Field(default="", max_length=255) @field_validator("companyName", "displayName", "companyCode", "recordNumber", "copyright", mode="before") @classmethod def strip_string(cls, value: str | None) -> str | None: if value is None: return None return value.strip() class SettingsAdminForm(BaseModel): adminAccount: str = Field(min_length=1, max_length=120) adminEmail: str = Field(min_length=1, max_length=255) newPassword: str = Field(default="", max_length=128) confirmPassword: str = Field(default="", max_length=128) sessionTimeout: int = Field(default=30, ge=5, le=240) noticeEmail: str = Field(default="", max_length=255) mfaEnabled: bool = True strongPassword: bool = True loginAlertEnabled: bool = True adminPasswordConfigured: bool = False @field_validator("adminAccount", "adminEmail", "newPassword", "confirmPassword", "noticeEmail", mode="before") @classmethod def strip_string(cls, value: str | None) -> str | None: if value is None: return None return value.strip() class SettingsSessionForm(BaseModel): conversationRetentionDays: int = Field(default=3, ge=1, le=10) class SettingsLlmForm(BaseModel): mainProvider: str = Field(min_length=1, max_length=64) mainModel: str = Field(min_length=1, max_length=255) mainEndpoint: str = Field(min_length=1, max_length=512) mainApiKey: str = Field(default="", max_length=1024) mainApiKeyConfigured: bool = False backupProvider: str = Field(min_length=1, max_length=64) backupModel: str = Field(min_length=1, max_length=255) backupEndpoint: str = Field(min_length=1, max_length=512) backupApiKey: str = Field(default="", max_length=1024) backupApiKeyConfigured: bool = False embeddingProvider: str = Field(min_length=1, max_length=64) embeddingModel: str = Field(min_length=1, max_length=255) embeddingEndpoint: str = Field(min_length=1, max_length=512) embeddingApiKey: str = Field(default="", max_length=1024) embeddingApiKeyConfigured: bool = False rerankerProvider: str = Field(min_length=1, max_length=64) rerankerModel: str = Field(min_length=1, max_length=255) rerankerEndpoint: str = Field(min_length=1, max_length=512) rerankerApiKey: str = Field(default="", max_length=1024) rerankerApiKeyConfigured: bool = False @field_validator( "mainProvider", "mainModel", "mainEndpoint", "mainApiKey", "backupProvider", "backupModel", "backupEndpoint", "backupApiKey", "embeddingProvider", "embeddingModel", "embeddingEndpoint", "embeddingApiKey", "rerankerProvider", "rerankerModel", "rerankerEndpoint", "rerankerApiKey", mode="before", ) @classmethod def strip_string(cls, value: str | None) -> str | None: if value is None: return None return value.strip() class SettingsLogForm(BaseModel): level: str = Field(min_length=1, max_length=16) retentionDays: int = Field(default=180, ge=1, le=3650) archiveCycle: str = Field(default="weekly", max_length=32) logPath: str = Field(min_length=1, max_length=255) alertEmail: str = Field(default="", max_length=255) operationAudit: bool = True loginAudit: bool = True maskSensitive: bool = True @field_validator("level", "archiveCycle", "logPath", "alertEmail", mode="before") @classmethod def strip_string(cls, value: str | None) -> str | None: if value is None: return None return value.strip() class SettingsRenderForm(BaseModel): enabled: bool = False publicUrl: str = Field(default="", max_length=512) jwtSecret: str = Field(default="", max_length=1024) jwtSecretConfigured: bool = False @field_validator("publicUrl", "jwtSecret", mode="before") @classmethod def strip_string(cls, value: str | None) -> str | None: if value is None: return None return value.strip() class SettingsMailForm(BaseModel): smtpHost: str = Field(min_length=1, max_length=255) port: int = Field(default=465, ge=1, le=65535) encryption: str = Field(default="SSL/TLS", max_length=32) senderName: str = Field(default="", max_length=120) senderAddress: str = Field(default="", max_length=255) username: str = Field(default="", max_length=255) password: str = Field(default="", max_length=1024) passwordConfigured: bool = False alertEnabled: bool = True digestEnabled: bool = False digestTime: str = Field(default="09:00", max_length=16) defaultReceiver: str = Field(default="", max_length=255) @field_validator( "smtpHost", "encryption", "senderName", "senderAddress", "username", "password", "digestTime", "defaultReceiver", mode="before", ) @classmethod def strip_string(cls, value: str | None) -> str | None: if value is None: return None return value.strip() class SettingsRead(BaseModel): companyForm: SettingsCompanyForm adminForm: SettingsAdminForm sessionForm: SettingsSessionForm hermesForm: dict llmForm: SettingsLlmForm renderForm: SettingsRenderForm logForm: SettingsLogForm mailForm: SettingsMailForm class SettingsWrite(BaseModel): companyForm: SettingsCompanyForm adminForm: SettingsAdminForm sessionForm: SettingsSessionForm hermesForm: dict llmForm: SettingsLlmForm renderForm: SettingsRenderForm logForm: SettingsLogForm mailForm: SettingsMailForm class ModelConnectivityTestRequest(BaseModel): provider: str = Field(min_length=1, max_length=64) endpoint: str = Field(min_length=1, max_length=512) model: str = Field(min_length=1, max_length=255) api_key: str | None = Field(default=None, max_length=1024) capability: Literal["chat", "embedding", "reranker"] = "chat" slot: Literal["main", "backup", "embedding", "reranker"] | None = None @field_validator("provider", "endpoint", "model", "api_key", mode="before") @classmethod def strip_model_string(cls, value: str | None) -> str | None: if value is None: return None return value.strip() class ModelConnectivityTestRead(BaseModel): ok: bool provider: str model: str endpoint: str capability: str detail: str status_code: int | None = None checked_at: datetime class RuntimeModelConfigRead(BaseModel): slot: Literal["main", "backup", "embedding", "reranker"] provider: str model: str endpoint: str apiKey: str capability: Literal["chat", "embedding", "reranker"]