Этап 3: голосовой ввод по горячей клавише

- Глобальная горячая клавиша через RegisterHotKey (по умолчанию Win+Alt+Space)
- Запись с микрофона, конец фразы по паузе через Silero VAD, звуковые сигналы
- Распознавание faster-whisper large-v3-turbo на GPU, CUDA-библиотеки из pip-пакетов nvidia-*
- Состояния «слушаю» и «распознаю», кнопка «Говорить» в чате и пункт в трее
- Голосовая команда прерывает текущий ответ и озвучку
- Тесты горячей клавиши, детектора конца фразы и сеанса голосового ввода

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nid
2026-09-17 03:34:09 +03:00
co-authored by Claude Opus 5
parent fec52dd1c2
commit 513d010c19
19 changed files with 1298 additions and 12 deletions
+21 -3
View File
@@ -25,7 +25,7 @@ from PySide6.QtWidgets import (
)
from agr_assistent import APP_NAME
from agr_assistent.core.assistant import Assistant
from agr_assistent.core.assistant import Assistant, AssistantState
from agr_assistent.ui.icons import state_icon
_ROLE_STYLES = {
@@ -34,6 +34,11 @@ _ROLE_STYLES = {
"error": ("Ошибка", "#e53935"),
}
_VOICE_BUTTON_TEXTS = {
AssistantState.LISTENING: "Готово",
AssistantState.RECOGNIZING: "Распознаю…",
}
# Во время стриминга перерисовываем историю не чаще, чем раз в N мс
_RENDER_INTERVAL_MS = 50
@@ -85,13 +90,23 @@ class ChatWindow(QWidget):
self._send_button.setMinimumWidth(100)
self._send_button.clicked.connect(self._on_send_clicked)
self._voice_button = QPushButton()
self._voice_button.setMinimumWidth(100)
self._voice_button.clicked.connect(assistant.toggle_listening)
self._voice_button.setVisible(assistant.voice_available)
header = QHBoxLayout()
header.addWidget(self._provider_label, 1)
header.addWidget(new_chat_button)
buttons = QVBoxLayout()
buttons.addWidget(self._send_button)
buttons.addWidget(self._voice_button)
buttons.addStretch()
input_row = QHBoxLayout()
input_row.addWidget(self._input, 1)
input_row.addWidget(self._send_button)
input_row.addLayout(buttons)
layout = QVBoxLayout(self)
layout.addLayout(header)
@@ -146,8 +161,11 @@ class ChatWindow(QWidget):
self._submit()
def _on_state_changed(self, state: object) -> None:
current = self._assistant.state
self._send_button.setText("Стоп" if self._assistant.is_busy else "Отправить")
self.setWindowIcon(state_icon(self._assistant.state))
self._voice_button.setText(_VOICE_BUTTON_TEXTS.get(current, "Говорить"))
self._voice_button.setEnabled(current is not AssistantState.RECOGNIZING)
self.setWindowIcon(state_icon(current))
def _update_provider_label(self) -> None:
self._provider_label.setText(