ускоренная работа

This commit is contained in:
2026-01-09 19:59:31 +03:00
parent ce28fede74
commit fd373d83f3
10 changed files with 322 additions and 46 deletions

View File

@@ -16,8 +16,15 @@ Flow:
import signal
import sys
import threading
import queue
import re
import os
from collections import deque
# Для воспроизведения звуков (mp3)
from pygame import mixer
# Импорт наших модулей
from .audio.wakeword import (
wait_for_wakeword,
@@ -26,7 +33,7 @@ from .audio.wakeword import (
stop_monitoring as stop_wakeword_monitoring,
)
from .audio.stt import listen, cleanup as cleanup_stt, get_recognizer
from .core.ai import ask_ai, translate_text
from .core.ai import ask_ai, ask_ai_stream, translate_text
from .core.cleaner import clean_response
from .audio.tts import speak, initialize as init_tts
from .audio.sound_level import set_volume, parse_volume_text
@@ -124,8 +131,19 @@ def main():
# Устанавливаем перехватчик Ctrl+C
signal.signal(signal.SIGINT, signal_handler)
# Предварительная инициализация моделей (занимает пару секунд при старте)
# Предварительная инициализация моделей
print("⏳ Инициализация моделей...")
# Инициализация звуковой системы для эффектов
mixer.init()
ding_sound_path = "assets/sounds/ding.wav"
ding_sound = None
if os.path.exists(ding_sound_path):
ding_sound = mixer.Sound(ding_sound_path)
ding_sound.set_volume(0.3)
else:
print(f"⚠️ Звук {ding_sound_path} не найден")
get_recognizer().initialize() # Подключение к Deepgram
init_tts() # Загрузка нейросети для синтеза речи (Silero)
alarm_clock = get_alarm_clock() # Загрузка будильников
@@ -163,6 +181,10 @@ def main():
if not detected:
continue
# Воспроизводим звук активации
if ding_sound:
ding_sound.play()
# Фраза услышана! Слушаем команду пользователя (7 секунд тишины макс)
user_text = listen(timeout_seconds=7.0)
else:
@@ -205,7 +227,8 @@ def main():
]
# Проверяем точное совпадение или если фраза начинается с "повтори" (но не "повтори за мной")
if user_text_lower in repeat_phrases or (
user_text_lower.startswith("повтори") and "за мной" not in user_text_lower
user_text_lower.startswith("повтори")
and "за мной" not in user_text_lower
):
if last_response:
print(f"🔁 Повторяю: {last_response}")
@@ -296,39 +319,103 @@ def main():
print("⏹️ Перевод прерван - слушаю следующий вопрос")
continue
# --- Шаг 3: Запрос к AI (обычный чат) ---
# --- Шаг 3: Запрос к AI (Streaming) ---
# Добавляем сообщение пользователя в историю
chat_history.append({"role": "user", "content": user_text})
# Отправляем историю диалога в Perplexity
ai_response = ask_ai(list(chat_history))
# Очередь для предложений, которые нужно озвучить
tts_q = queue.Queue()
# Флаг прерывания для worker-а
interrupt_event = threading.Event()
# Добавляем ответ AI в историю
chat_history.append({"role": "assistant", "content": ai_response})
def tts_worker():
"""Фоновый поток, читающий предложения из очереди и озвучивающий их."""
while True:
item = tts_q.get()
if item is None: # Poison pill (сигнал остановки)
tts_q.task_done()
break
# --- Шаг 4: Очистка ответа ---
# Убираем Markdown (**жирный**, *курсив*) и готовим числа для озвучки
clean_text = clean_response(ai_response, language="ru")
text, lang = item
# Сохраняем последний ответ для функции "еще раз"
last_response = clean_text
# Если уже было прерывание, просто пропускаем (чистим очередь)
if interrupt_event.is_set():
tts_q.task_done()
continue
# --- Шаг 5: Озвучка ответа ---
# check_interrupt=check_wakeword_once позволяет прервать речь, сказав "Alexandr"
completed = speak(
clean_text, check_interrupt=check_wakeword_once, language="ru"
)
# Озвучиваем
completed = speak(
text, check_interrupt=check_wakeword_once, language=lang
)
# После озвучки обязательно закрываем поток микрофона, который открывался для проверки прерывания
if not completed:
interrupt_event.set() # Сообщаем всем, что нас перебили
tts_q.task_done()
# Запускаем поток озвучки
worker_thread = threading.Thread(target=tts_worker, daemon=True)
worker_thread.start()
full_response = ""
buffer = ""
try:
# Получаем генератор потока от AI
stream_generator = ask_ai_stream(list(chat_history))
print("🤖 AI говорит: ", end="", flush=True)
for chunk in stream_generator:
# Если в процессе генерации нас перебили (на ранних фразах), прерываем получение
if interrupt_event.is_set():
break
buffer += chunk
full_response += chunk
print(chunk, end="", flush=True)
# Проверяем на конец предложения (. ! ? + пробел или конец строки)
# Эвристика: ищем знаки препинания, после которых идет пробел или перевод строки
if re.search(r"[.!?\n]+(?:\s|$)", buffer):
# Очищаем и отправляем в очередь
clean_chunk = clean_response(buffer, language="ru")
if clean_chunk.strip():
tts_q.put((clean_chunk, "ru"))
buffer = ""
# Отправляем остаток (если есть)
if buffer.strip() and not interrupt_event.is_set():
clean_chunk = clean_response(buffer, language="ru")
if clean_chunk.strip():
tts_q.put((clean_chunk, "ru"))
except Exception as e:
print(f"\n❌ Ошибка стриминга: {e}")
speak("Произошла ошибка при получении ответа.")
# Ждем, пока все договорится
# Добавляем poison pill, чтобы поток завершился, когда очередь пуста
tts_q.put(None)
worker_thread.join()
print() # Перенос строки после вывода AI
# Добавляем полный ответ AI в историю
chat_history.append({"role": "assistant", "content": full_response})
# Сохраняем для "повтори"
last_response = clean_response(full_response, language="ru")
# После озвучки обязательно закрываем поток микрофона
stop_wakeword_monitoring()
# Включаем режим диалога (следующий запрос можно говорить без имени)
skip_wakeword = True
if not completed:
if interrupt_event.is_set():
print("⏹️ Ответ прерван - слушаю следующий вопрос")
# Если перебили, значит есть новый вопрос, сразу слушаем его (цикл перезапустится)
pass
# Если перебили, цикл перезапустится и skip_wakeword уже True
print()
print("-" * 30)