ускоренная работа
This commit is contained in:
131
app/main.py
131
app/main.py
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user