""" Configuration Models for Flare Platform """ from pydantic import BaseModel, Field, field_serializer from datetime import datetime from typing import Optional, List, Dict, Any class BaseModelWithDatetime(BaseModel): """Base model with consistent datetime serialization""" class Config: # Datetime'ları her zaman ISO 8601 formatında serialize et json_encoders = { datetime: lambda v: v.isoformat() if v else None } # ===================== User & Auth ===================== class UserConfig(BaseModelWithDatetime): username: str password_hash: str salt: Optional[str] = None # ===================== Provider Models ===================== class ProviderDefinition(BaseModelWithDatetime): type: str # llm, tts, stt name: str display_name: str requires_endpoint: bool requires_api_key: bool requires_repo_info: Optional[bool] = False description: str features: Optional[Dict[str, Any]] = Field(default_factory=dict) def has_feature(self, feature_name: str) -> bool: """Check if provider has a specific feature""" return feature_name in self.features if self.features else False def get_feature(self, feature_name: str, default: Any = None) -> Any: """Get feature value with default""" if not self.features: return default return self.features.get(feature_name, default) def get_feature_bool(self, feature_name: str, default: bool = False) -> bool: """Get boolean feature value""" if not self.features: return default value = self.features.get(feature_name, default) if isinstance(value, bool): return value if isinstance(value, str): return value.lower() in ('true', '1', 'yes', 'on') return bool(value) def get_feature_int(self, feature_name: str, default: int = 0) -> int: """Get integer feature value""" if not self.features: return default value = self.features.get(feature_name, default) try: return int(value) except (ValueError, TypeError): return default def get_feature_str(self, feature_name: str, default: str = "") -> str: """Get string feature value""" if not self.features: return default value = self.features.get(feature_name, default) return str(value) if value is not None else default class ProviderSettings(BaseModelWithDatetime): name: str api_key: Optional[str] = None endpoint: Optional[str] = None settings: Optional[Dict[str, Any]] = Field(default_factory=dict) # ===================== Global Config ===================== class GlobalConfig(BaseModelWithDatetime): llm_provider: ProviderSettings = Field( default_factory=lambda: ProviderSettings( name="spark_cloud", api_key="", endpoint="http://localhost:8080", settings={} ) ) tts_provider: ProviderSettings = Field( default_factory=lambda: ProviderSettings( name="no_tts", api_key="", endpoint=None, settings={} ) ) stt_provider: ProviderSettings = Field( default_factory=lambda: ProviderSettings( name="no_stt", api_key="", endpoint=None, settings={} ) ) providers: List[ProviderDefinition] = Field(default_factory=list) users: List[UserConfig] = Field(default_factory=list) last_update_date: Optional[str] = None last_update_user: Optional[str] = None def is_cloud_mode(self) -> bool: """Check if running in cloud mode""" import os return bool(os.environ.get("SPACE_ID")) def get_provider_config(self, provider_type: str, provider_name: str) -> Optional[ProviderDefinition]: """Get provider definition by type and name""" return next( (p for p in self.providers if p.type == provider_type and p.name == provider_name), None ) # ===================== Localization Models ===================== class LocalizedExample(BaseModelWithDatetime): locale_code: str example: str class LocalizedCaption(BaseModelWithDatetime): locale_code: str caption: str # ===================== Parameter Models ===================== class ParameterConfig(BaseModelWithDatetime): name: str caption: List[LocalizedCaption] type: str # str, int, float, bool, date required: bool = True variable_name: str extraction_prompt: Optional[str] = None validation_regex: Optional[str] = None invalid_prompt: Optional[str] = None type_error_prompt: Optional[str] = None def canonical_type(self) -> str: """Get canonical type name""" return self.type.lower() def get_caption_for_locale(self, locale: str) -> str: """Get caption for specific locale""" for cap in self.caption: if cap.locale_code == locale: return cap.caption # Fallback to first caption return self.caption[0].caption if self.caption else self.name # ===================== Intent Models ===================== class IntentConfig(BaseModelWithDatetime): name: str caption: str requiresApproval: Optional[bool] = False dependencies: List[str] = Field(default_factory=list) examples: List[LocalizedExample] = Field(default_factory=list) detection_prompt: str parameters: List[ParameterConfig] = Field(default_factory=list) action: str fallback_timeout_prompt: Optional[str] = None fallback_error_prompt: Optional[str] = None def get_examples_for_locale(self, locale: str) -> List[str]: """Get examples for specific locale""" examples = [] for ex in self.examples: if ex.locale_code == locale: examples.append(ex.example) # Fallback to any available examples if locale not found if not examples and self.examples: # Try language part only (tr-TR -> tr) if '-' in locale: lang_code = locale.split('-')[0] for ex in self.examples: if ex.locale_code.startswith(lang_code): examples.append(ex.example) # If still no examples, return all examples if not examples: examples = [ex.example for ex in self.examples] return examples def get_examples_for_locale(self, locale: str) -> List[str]: """Get examples for specific locale""" examples = [] for ex in self.examples: if ex.locale_code == locale: examples.append(ex.example) # Fallback to any available examples if locale not found if not examples and self.examples: # Try language part only (tr-TR -> tr) if '-' in locale: lang_code = locale.split('-')[0] for ex in self.examples: if ex.locale_code.startswith(lang_code): examples.append(ex.example) # If still no examples, return all examples if not examples: examples = [ex.example for ex in self.examples] return examples # ===================== LLM Configuration ===================== class GenerationConfig(BaseModelWithDatetime): max_new_tokens: int = 512 temperature: float = 0.7 top_p: float = 0.9 top_k: Optional[int] = None repetition_penalty: Optional[float] = None do_sample: Optional[bool] = True num_beams: Optional[int] = None length_penalty: Optional[float] = None early_stopping: Optional[bool] = None class LLMConfiguration(BaseModelWithDatetime): repo_id: str generation_config: GenerationConfig = Field(default_factory=GenerationConfig) use_fine_tune: bool = False fine_tune_zip: Optional[str] = "" # ===================== Version Models ===================== class VersionConfig(BaseModelWithDatetime): no: int caption: str description: Optional[str] = None published: bool = False deleted: bool = False general_prompt: str welcome_prompt: Optional[str] = None llm: LLMConfiguration intents: List[IntentConfig] = Field(default_factory=list) created_date: str created_by: str publish_date: Optional[str] = None published_by: Optional[str] = None last_update_date: Optional[str] = None last_update_user: Optional[str] = None # ===================== Project Models ===================== class ProjectConfig(BaseModelWithDatetime): id: int name: str caption: str icon: Optional[str] = "folder" description: Optional[str] = None enabled: bool = True default_locale: str = "tr" supported_locales: List[str] = Field(default_factory=lambda: ["tr"]) timezone: str = "Europe/Istanbul" region: str = "tr-TR" versions: List[VersionConfig] = Field(default_factory=list) version_id_counter: int = 1 deleted: bool = False created_date: str created_by: str last_update_date: Optional[str] = None last_update_user: Optional[str] = None # ===================== API Models ===================== class RetryConfig(BaseModelWithDatetime): retry_count: int = 3 backoff_seconds: int = 2 strategy: str = "static" # static, exponential class AuthConfig(BaseModelWithDatetime): enabled: bool token_endpoint: Optional[str] = None response_token_path: Optional[str] = None token_request_body: Optional[Dict[str, Any]] = None token_refresh_endpoint: Optional[str] = None token_refresh_body: Optional[Dict[str, Any]] = None class ResponseMapping(BaseModelWithDatetime): variable_name: str type: str # str, int, float, bool, date json_path: str caption: List[LocalizedCaption] class APIConfig(BaseModelWithDatetime): name: str url: str method: str = "POST" headers: Dict[str, str] = Field(default_factory=dict) body_template: Dict[str, Any] = Field(default_factory=dict) timeout_seconds: int = 10 retry: RetryConfig = Field(default_factory=RetryConfig) proxy: Optional[str] = None auth: Optional[AuthConfig] = None response_prompt: Optional[str] = None response_mappings: List[ResponseMapping] = Field(default_factory=list) deleted: bool = False created_date: Optional[str] = None created_by: Optional[str] = None last_update_date: Optional[str] = None last_update_user: Optional[str] = None # ===================== Activity Log ===================== class ActivityLogEntry(BaseModelWithDatetime): id: Optional[int] = None timestamp: str username: str action: str entity_type: str entity_name: Optional[str] = None details: Optional[str] = None # ===================== Root Configuration ===================== class ServiceConfig(BaseModelWithDatetime): global_config: GlobalConfig = Field(alias="config") projects: List[ProjectConfig] = Field(default_factory=list) apis: List[APIConfig] = Field(default_factory=list) activity_log: List[ActivityLogEntry] = Field(default_factory=list) project_id_counter: int = 1 last_update_date: Optional[str] = None last_update_user: Optional[str] = None class Config: populate_by_name = True def build_index(self) -> None: """Build indexes for quick lookup""" # This method can be extended to build various indexes pass def get_api(self, api_name: str) -> Optional[APIConfig]: """Get API config by name""" return next((api for api in self.apis if api.name == api_name), None) # For backward compatibility - alias GlobalConfiguration = GlobalConfig