169 lines
6.2 KiB
Python
169 lines
6.2 KiB
Python
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)
|