fix: improve streaming, alarms, and AI TTS
This commit is contained in:
@@ -18,6 +18,121 @@ ALARM_FILE = BASE_DIR / "data" / "alarms.json"
|
||||
ALARM_SOUND = BASE_DIR / "assets" / "sounds" / "Apex-1.mp3"
|
||||
ASK_ALARM_TIME_PROMPT = "На какое время мне поставить будильник?"
|
||||
|
||||
_NUMBER_UNITS = {
|
||||
"ноль": 0,
|
||||
"один": 1,
|
||||
"одна": 1,
|
||||
"два": 2,
|
||||
"две": 2,
|
||||
"три": 3,
|
||||
"четыре": 4,
|
||||
"пять": 5,
|
||||
"шесть": 6,
|
||||
"семь": 7,
|
||||
"восемь": 8,
|
||||
"девять": 9,
|
||||
}
|
||||
_NUMBER_TEENS = {
|
||||
"десять": 10,
|
||||
"одиннадцать": 11,
|
||||
"двенадцать": 12,
|
||||
"тринадцать": 13,
|
||||
"четырнадцать": 14,
|
||||
"пятнадцать": 15,
|
||||
"шестнадцать": 16,
|
||||
"семнадцать": 17,
|
||||
"восемнадцать": 18,
|
||||
"девятнадцать": 19,
|
||||
}
|
||||
_NUMBER_TENS = {
|
||||
"двадцать": 20,
|
||||
"тридцать": 30,
|
||||
"сорок": 40,
|
||||
"пятьдесят": 50,
|
||||
}
|
||||
_PARTS_OF_DAY = {"утра", "дня", "вечера", "ночи"}
|
||||
_FILLER_WORDS = {"мне", "меня", "пожалуйста", "на", "в", "во", "к", "и"}
|
||||
_HOUR_WORDS = {"час", "часа", "часов"}
|
||||
_MINUTE_WORDS = {"минута", "минуту", "минуты", "минут"}
|
||||
|
||||
|
||||
def _parse_number_tokens(tokens, start_index: int):
|
||||
if start_index >= len(tokens):
|
||||
return None, 0
|
||||
|
||||
token = tokens[start_index]
|
||||
if token.isdigit():
|
||||
return int(token), 1
|
||||
|
||||
if token in _NUMBER_TEENS:
|
||||
return _NUMBER_TEENS[token], 1
|
||||
|
||||
if token in _NUMBER_TENS:
|
||||
value = _NUMBER_TENS[token]
|
||||
if start_index + 1 < len(tokens):
|
||||
next_token = tokens[start_index + 1]
|
||||
if next_token in _NUMBER_UNITS:
|
||||
value += _NUMBER_UNITS[next_token]
|
||||
return value, 2
|
||||
return value, 1
|
||||
|
||||
if token in _NUMBER_UNITS:
|
||||
return _NUMBER_UNITS[token], 1
|
||||
|
||||
return None, 0
|
||||
|
||||
|
||||
def _apply_part_of_day(hour: int, part_of_day: str | None) -> int:
|
||||
if not part_of_day:
|
||||
return hour
|
||||
|
||||
if part_of_day == "утра":
|
||||
return 0 if hour == 12 else hour
|
||||
if part_of_day == "ночи":
|
||||
return 0 if hour == 12 else hour
|
||||
if part_of_day in {"дня", "вечера"} and hour < 12:
|
||||
return hour + 12
|
||||
return hour
|
||||
|
||||
|
||||
def _extract_alarm_time_words(text: str):
|
||||
tokens = re.findall(r"[a-zа-я0-9]+", text.lower().replace("ё", "е"))
|
||||
markers = {"будильник", "разбуди", "поставь", "установи", "включи", "на", "в", "к"}
|
||||
|
||||
for index, token in enumerate(tokens):
|
||||
if token not in markers:
|
||||
continue
|
||||
|
||||
current = index + 1
|
||||
while current < len(tokens) and tokens[current] in _FILLER_WORDS:
|
||||
current += 1
|
||||
|
||||
hour, consumed = _parse_number_tokens(tokens, current)
|
||||
if hour is None:
|
||||
continue
|
||||
current += consumed
|
||||
|
||||
if current < len(tokens) and tokens[current] in _HOUR_WORDS:
|
||||
current += 1
|
||||
|
||||
minute = 0
|
||||
if current < len(tokens) and tokens[current] not in _PARTS_OF_DAY:
|
||||
parsed_minute, minute_consumed = _parse_number_tokens(tokens, current)
|
||||
if parsed_minute is not None:
|
||||
minute = parsed_minute
|
||||
current += minute_consumed
|
||||
if current < len(tokens) and tokens[current] in _MINUTE_WORDS:
|
||||
current += 1
|
||||
|
||||
part_of_day = None
|
||||
if current < len(tokens) and tokens[current] in _PARTS_OF_DAY:
|
||||
part_of_day = tokens[current]
|
||||
|
||||
if 0 <= hour <= 23 and 0 <= minute <= 59:
|
||||
return _apply_part_of_day(hour, part_of_day), minute
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class AlarmClock:
|
||||
def __init__(self):
|
||||
@@ -70,10 +185,10 @@ class AlarmClock:
|
||||
if re.search(r"\b(каждый день|ежедневно)\b", text):
|
||||
return [0, 1, 2, 3, 4, 5, 6]
|
||||
|
||||
if re.search(r"\b(по будн|в будн|будние)\b", text):
|
||||
if re.search(r"\b(?:по\s+будн\w*|в\s+будн\w*|будн\w*)\b", text):
|
||||
days.update([0, 1, 2, 3, 4])
|
||||
|
||||
if re.search(r"\b(по выходн|в выходн|выходные)\b", text):
|
||||
if re.search(r"\b(?:по\s+выходн\w*|в\s+выходн\w*|выходн\w*)\b", text):
|
||||
days.update([5, 6])
|
||||
|
||||
day_patterns = {
|
||||
@@ -268,32 +383,32 @@ class AlarmClock:
|
||||
|
||||
days = self._extract_alarm_days(text)
|
||||
|
||||
# Поиск формата "7:30", "7.30"
|
||||
match = re.search(r"\b(\d{1,2})[:.-](\d{2})\b", text)
|
||||
# Поиск формата "7:30", "7.30" и вариантов с "в/на/к".
|
||||
match = re.search(r"(?:\b(?:на|в|во|к)\s+)?(\d{1,2})[:.-](\d{2})\b", text)
|
||||
if match:
|
||||
h, m = int(match.group(1)), int(match.group(2))
|
||||
period_match = re.search(
|
||||
r"\b(?:на|в|во|к)?\s*" + re.escape(match.group(0).strip()) + r"\s+(утра|дня|вечера|ночи)\b",
|
||||
text,
|
||||
)
|
||||
part_of_day = period_match.group(1) if period_match else None
|
||||
h = _apply_part_of_day(h, part_of_day)
|
||||
if 0 <= h <= 23 and 0 <= m <= 59:
|
||||
self.add_alarm_with_days(h, m, days=days)
|
||||
days_phrase = self._format_days_phrase(days)
|
||||
suffix = f" {days_phrase}" if days_phrase else ""
|
||||
return f"Я установил будильник на {h} часов {m} минут{suffix}."
|
||||
|
||||
# Поиск формата словами "на 7 часов 15 минут"
|
||||
# Поиск формата цифрами: "в 7 утра", "на 7", "к 6 30"
|
||||
match_time = re.search(
|
||||
r"на\s+(\d{1,2})(?:\s*(?:часов|часа|час))?(?:\s+(\d{1,2})(?:\s*(?:минут|минуты|минута))?)?",
|
||||
r"(?:\b(?:на|в|во|к)\s+)?(\d{1,2})(?:\s*(?:часов|часа|час))?(?:\s+(\d{1,2})(?:\s*(?:минут|минуты|минута))?)?(?:\s+(утра|дня|вечера|ночи))?\b",
|
||||
text,
|
||||
)
|
||||
|
||||
if match_time:
|
||||
h = int(match_time.group(1))
|
||||
m = int(match_time.group(2)) if match_time.group(2) else 0
|
||||
|
||||
# Умная коррекция времени (если говорят "в 8", а сейчас 9, то это скорее 8 вечера или 8 утра завтра)
|
||||
# Здесь простая логика AM/PM
|
||||
if "вечера" in text and h < 12:
|
||||
h += 12
|
||||
elif "утра" in text and h == 12:
|
||||
h = 0
|
||||
h = _apply_part_of_day(h, match_time.group(3))
|
||||
|
||||
if 0 <= h <= 23 and 0 <= m <= 59:
|
||||
self.add_alarm_with_days(h, m, days=days)
|
||||
@@ -301,6 +416,15 @@ class AlarmClock:
|
||||
suffix = f" {days_phrase}" if days_phrase else ""
|
||||
return f"Хорошо, разбужу вас в {h}:{m:02d}{suffix}."
|
||||
|
||||
# Поиск формата словами: "в семь утра", "будильник семь тридцать"
|
||||
word_time = _extract_alarm_time_words(text)
|
||||
if word_time:
|
||||
h, m = word_time
|
||||
self.add_alarm_with_days(h, m, days=days)
|
||||
days_phrase = self._format_days_phrase(days)
|
||||
suffix = f" {days_phrase}" if days_phrase else ""
|
||||
return f"Хорошо, разбужу вас в {h}:{m:02d}{suffix}."
|
||||
|
||||
if re.search(r"(постав|установ|запусти|включи|разбуди)", text) or text.strip() in {
|
||||
"будильник",
|
||||
"поставь будильник",
|
||||
|
||||
Reference in New Issue
Block a user