from __future__ import annotations import json from typing import Any from sqlalchemy import select from services.shared.core import utc_now_iso from services.shared.models import ( VoiceNameCollectionConfig, VoiceNameCollectionConfigOut, VoiceNameCollectionLanguageTexts, VoiceNameCollectionTextsConfig, ) from services.shared.sql_models import VoiceNameCollectionSettingsRow VOICE_NAME_COLLECTION_SETTINGS_KEY = "global" def voice_name_collection_default_config() -> VoiceNameCollectionConfig: return VoiceNameCollectionConfig( enabled=True, texts=VoiceNameCollectionTextsConfig( ru=VoiceNameCollectionLanguageTexts( start_prompt="Здравствуйте. Назовите, пожалуйста, ваше имя.", personalized_greeting_template=( "Я AI-оператор компании. Здравствуйте, {name}. " "Коротко расскажите, с чем помочь, и я сразу начну разбираться." ), confirmation_greeting_template=( "Я AI-оператор компании. Если я правильно расслышал, вас зовут {name}? " "И чем помочь?" ), inline_followup_prompt="И ещё подскажите, как мне к вам обращаться?", ), kz=VoiceNameCollectionLanguageTexts( start_prompt="Сәлеметсіз бе. Атыңызды атаңызшы.", personalized_greeting_template=( "Men kompaniyanyn AI operatoriymyn. Salemetsiz be, {name}. " "Suragynyzdy aitanyz, men birden komektesuge tyrisamyn." ), confirmation_greeting_template=( "Men kompaniyanyn AI operatoriymyn. Durys estisem, atynyz {name} pa? " "Qalai komektesemin?" ), inline_followup_prompt="Tagy bir naqtylasam, sizge qalai qaratamyn?", ), ), ) def _settings_row(session) -> VoiceNameCollectionSettingsRow | None: return session.execute( select(VoiceNameCollectionSettingsRow).where( VoiceNameCollectionSettingsRow.settings_key == VOICE_NAME_COLLECTION_SETTINGS_KEY ) ).scalar_one_or_none() def _normalize_config_payload(raw: Any) -> VoiceNameCollectionConfig: if isinstance(raw, VoiceNameCollectionConfig): return raw if not isinstance(raw, dict): raise ValueError("Voice name collection config payload must be an object") return VoiceNameCollectionConfig.model_validate(raw) def load_voice_name_collection_config(session) -> VoiceNameCollectionConfigOut: default_config = voice_name_collection_default_config() row = _settings_row(session) if row is None: return VoiceNameCollectionConfigOut(config=default_config, updated_at=None, source="defaults") try: parsed = json.loads(row.config_json or "{}") config = _normalize_config_payload(parsed) return VoiceNameCollectionConfigOut(config=config, updated_at=row.updated_at, source="database") except Exception: return VoiceNameCollectionConfigOut(config=default_config, updated_at=row.updated_at, source="defaults") def load_effective_voice_name_collection_config(session) -> VoiceNameCollectionConfig: return load_voice_name_collection_config(session).config def save_voice_name_collection_config( session, config_payload: VoiceNameCollectionConfig | dict[str, Any], ) -> VoiceNameCollectionConfigOut: config = _normalize_config_payload(config_payload) row = _settings_row(session) now = utc_now_iso() if row is None: row = VoiceNameCollectionSettingsRow( settings_key=VOICE_NAME_COLLECTION_SETTINGS_KEY, config_json="{}", updated_at=now, ) session.add(row) row.config_json = json.dumps(config.model_dump(), ensure_ascii=False) row.updated_at = now session.commit() session.refresh(row) return VoiceNameCollectionConfigOut(config=config, updated_at=row.updated_at, source="database") def voice_name_collection_texts( config: VoiceNameCollectionConfig, language: str, ) -> VoiceNameCollectionLanguageTexts: if str(language or "").strip().lower() == "kz": return config.texts.kz return config.texts.ru def voice_name_collection_start_prompt(config: VoiceNameCollectionConfig, language: str) -> str: return voice_name_collection_texts(config, language).start_prompt def voice_name_collection_personalized_greeting( config: VoiceNameCollectionConfig, language: str, name: str, ) -> str: return voice_name_collection_texts(config, language).personalized_greeting_template.format(name=name) def voice_name_collection_confirmation_greeting( config: VoiceNameCollectionConfig, language: str, name: str, ) -> str: return voice_name_collection_texts(config, language).confirmation_greeting_template.format(name=name) def voice_name_collection_inline_followup(config: VoiceNameCollectionConfig, language: str) -> str: return voice_name_collection_texts(config, language).inline_followup_prompt def voice_name_collection_followup_markers( config: VoiceNameCollectionConfig, language: str, ) -> tuple[str, ...]: texts = voice_name_collection_texts(config, language) markers = [texts.inline_followup_prompt] for template in (texts.confirmation_greeting_template, texts.personalized_greeting_template): for chunk in str(template or "").split("{name}"): normalized = chunk.strip(" ,.!?;:-") if len(normalized) >= 5: markers.append(normalized) markers.extend( [ "как мне к вам обращаться", "как к вам обращаться", "вас зовут", "atyngyz", "qalai qaratamyn", ] ) unique_markers: list[str] = [] seen: set[str] = set() for marker in markers: key = marker.strip() if not key or key in seen: continue seen.add(key) unique_markers.append(key) return tuple(unique_markers)