diff --git a/handlers/voice.py b/handlers/voice.py
index 7e1c0f2..d04a9da 100644
--- a/handlers/voice.py
+++ b/handlers/voice.py
@@ -1,18 +1,41 @@
import os
import logging
-import aiohttp
-import json
+import asyncio
import sqlite3
+import subprocess
from datetime import datetime
from aiogram import Router, F, Bot
-from aiogram.types import Message
+from aiogram.types import Message, BufferedInputFile
from config import ADMIN_USER_ID, DATABASE_PATH
-from pathlib import Path
+import google.generativeai as genai
+import edge_tts
logger = logging.getLogger(__name__)
router = Router()
-WHISPER_API_URL = "http://192.168.10.168:9001/transcribe"
+# Валидный проверенный ключ Gemini API
+WORKING_GEMINI_KEY = "AIzaSyAJMfQ_mRZ1picRXVFNaeFJ7fpAkZxUKyk"
+genai.configure(api_key=WORKING_GEMINI_KEY)
+
+def execute_bash_command(command: str) -> str:
+ """Выполняет bash-команду на локальном Linux хосте (DockerServer) или по SSH на узлах сети (MacMini 192.168.10.167, Synology 192.168.10.105, MikroTik 192.168.10.215, NanoPi 192.168.10.1) и возвращает результат вывода."""
+ logger.info(f"⚡️ [Voice Agent Command] Executing bash: {command}")
+ try:
+ res = subprocess.run(
+ command,
+ shell=True,
+ capture_output=True,
+ text=True,
+ timeout=45
+ )
+ out = (res.stdout or "") + ("\n" + res.stderr if res.stderr else "")
+ output_str = out.strip()
+ if len(output_str) > 3000:
+ output_str = output_str[:3000] + "\n... [вывод обрезан]"
+ return output_str if output_str else "Команда выполнена успешно (вывод пуст)."
+ except Exception as e:
+ logger.error(f"❌ Bash Execution Error: {e}")
+ return f"Ошибка выполнения bash-команды: {e}"
def log_voice_to_db(user_id, username, msg_type, duration, text):
"""Фиксация статистики в основную базу Домового"""
@@ -39,68 +62,141 @@ def log_voice_to_db(user_id, username, msg_type, duration, text):
except Exception as e:
logger.error(f"❌ DB Log error: {e}")
+async def text_to_speech_bytes(text: str) -> bytes:
+ """Синтез качественного голосового ответа на русском через Edge TTS"""
+ # Обрезаем длинный текст для аудио-резюме
+ short_text = text[:400] if len(text) > 400 else text
+ communicate = edge_tts.Communicate(short_text, "ru-RU-DmitryNeural")
+ out_path = f"/tmp/tts_{os.getpid()}_{int(datetime.now().timestamp())}.mp3"
+ await communicate.save(out_path)
+ audio_bytes = b""
+ if os.path.exists(out_path):
+ with open(out_path, "rb") as f:
+ audio_bytes = f.read()
+ os.remove(out_path)
+ return audio_bytes
+
+def process_audio_with_gemini(audio_path: str) -> str:
+ """Прямое транскрибирование аудио через Gemini Multimodal API"""
+ try:
+ logger.info(f"Выгрузка {audio_path} в Gemini Multimodal Audio API...")
+ audio_file = genai.upload_file(path=audio_path)
+
+ prompt = (
+ "Точно транскрибируй эту голосовую заметку на русском языке в текст команды или вопроса. "
+ "Не добавляй никаких лишних комментариев, отдай только чистый текст произнесенных слов."
+ )
+
+ model = genai.GenerativeModel('gemini-2.5-flash')
+ response = model.generate_content([audio_file, prompt])
+
+ try:
+ genai.delete_file(name=audio_file.name)
+ except Exception:
+ pass
+
+ return response.text.strip() if response.text else ""
+ except Exception as e:
+ logger.error(f"Ошибка Gemini Audio API: {e}")
+ return ""
+
+async def execute_antigravity_agent(user_text: str) -> str:
+ """Обработка команды агентом Antigravity Lead Agent с правом выполнения bash-команд на хосте"""
+ system_instruction = (
+ "Ты — Antigravity Lead Agent, суперинтеллектуальный помощник Александра с абсолютными правами управления инфраструктурой. "
+ "Пользователь Александр обращается к тебе голосом в Телеграмме. Ты обладаешь ТЕМИ ЖЕ ПОЛНОМОЧИЯМИ И ВОЗМОЖНОСТЯМИ, что и в консоли Antigravity CLI! "
+ "Если пользователь просит проверить системные ресурсы, диски, процессы, файлы, контейнеры, сеть, провести чистку или выполнить любые действия на хосте или соседних серверах (MacMini, Synology, MikroTik, NanoPi) — "
+ "ОБЯЗАТЕЛЬНО используй функцию execute_bash_command для выполнения консольных команд! "
+ "Пример команд: 'df -h', 'du -d 1 -h /home/matrixhasyou', 'docker ps', 'docker system df', 'ssh matrixhasyou@192.168.10.167 ...'. "
+ "Всегда последовательно выполняй необходимые команды, анализируй выхлоп и давай развернутый, четкий, табличный и структурированный ответ."
+ )
+
+ models_to_try = ['gemini-3.5-flash-lite', 'gemini-3.5-flash', 'gemini-2.5-flash']
+ last_error = None
+
+ for model_name in models_to_try:
+ try:
+ logger.info(f"🚀 [Antigravity Agent] Попытка запуска с моделью: {model_name}")
+ model = genai.GenerativeModel(
+ model_name=model_name,
+ system_instruction=system_instruction,
+ tools=[execute_bash_command]
+ )
+ chat = model.start_chat(enable_automatic_function_calling=True)
+ response = chat.send_message(user_text)
+ if response and response.text:
+ return response.text.strip()
+ except Exception as e:
+ logger.warning(f"⚠️ [Antigravity Agent] Сбой на модели {model_name}: {e}")
+ last_error = e
+ await asyncio.sleep(2)
+
+ return f"❌ Ошибка исполнения Antigravity Agent: {last_error}"
+
@router.message(F.voice | F.video_note)
async def handle_voice_message(message: Message, bot: Bot):
- # ПЕРЕВОДИМ ТОЛЬКО ДЛЯ ТЕБЯ (СУПЕРАДМИНА)
+ # ПЕРЕВОДИМ И ИСПОЛНЯЕМ ТОЛЬКО ДЛЯ ТЕБЯ (СУПЕРАДМИНА)
if int(message.from_user.id) != int(ADMIN_USER_ID):
return
- status_msg = await message.reply("⏳ Распознаю голос (Whisper AI)...", parse_mode="HTML")
+ status_msg = await message.reply("🎙 Слушаю и расшифровываю через Gemini Multimodal API...", parse_mode="HTML")
await bot.send_chat_action(message.chat.id, "typing")
try:
if message.voice:
file_id = message.voice.file_id
- filename = f"{file_id}.oga"
+ filename = f"voice_{file_id}.ogg"
+ duration = message.voice.duration
msg_type = "VOICE"
else:
file_id = message.video_note.file_id
- filename = f"{file_id}.mp4"
+ filename = f"note_{file_id}.mp4"
+ duration = message.video_note.duration
msg_type = "VIDEO_NOTE"
- file = await bot.get_file(file_id)
-
- # Используем aiohttp для отправки файла в микросервис
- async with aiohttp.ClientSession() as session:
- # Скачиваем файл во временный буфер
- file_url = f"https://api.telegram.org/file/bot{bot.token}/{file.file_path}"
-
- # Прокси если нужно (подтянется из конфига если настроено, но тут идем напрямую)
- async with session.get(file_url) as file_resp:
- if file_resp.status != 200:
- raise Exception(f"Не удалось скачать файл из TG: {file_resp.status}")
-
- file_content = await file_resp.read()
-
- # Отправляем в наш Whisper-Service
- data = aiohttp.FormData()
- data.add_field('file', file_content, filename=filename)
-
- async with session.post(WHISPER_API_URL, data=data) as whisper_resp:
- if whisper_resp.status != 200:
- err_text = await whisper_resp.text()
- raise Exception(f"Ошибка Whisper API: {err_text}")
-
- result = await whisper_resp.json()
- text = result.get("text", "")
- duration = result.get("duration", 0)
+ local_audio_path = f"/tmp/{filename}"
+ file_info = await bot.get_file(file_id)
+ await bot.download_file(file_info.file_path, local_audio_path)
- if text:
- # Логируем в базу
- log_voice_to_db(message.from_user.id, message.from_user.username, msg_type, int(duration), text)
+ # 1. Прямая расшифровка через Gemini Audio
+ transcribed_text = process_audio_with_gemini(local_audio_path)
+
+ if os.path.exists(local_audio_path):
+ os.remove(local_audio_path)
+
+ if transcribed_text:
+ log_voice_to_db(message.from_user.id, message.from_user.username, msg_type, int(duration), transcribed_text)
- # Формируем красивый ответ
- response = f"📝 РАСШИФРОВКА ({msg_type})\n"
- response += f"⏱ Длительность: {int(duration)}с\n"
- response += f"━━━━━━━━━━━━━━\n"
- response += f"{text}"
+ await status_msg.edit_text(f"🗣 Команда принята: «{transcribed_text}»\n\n⚙️ Агент выполняет системную команду...", parse_mode="HTML")
+ await bot.send_chat_action(message.chat.id, "record_voice")
+
+ # 2. Исполнение Antigravity Agent с вызовом функций хоста
+ agent_response = await execute_antigravity_agent(transcribed_text)
+
+ # 3. Отправка текстового ответа
+ formatted_text = f"🎯 Ответ Antigravity Agent:\n\n{agent_response}"
- await status_msg.edit_text(response, parse_mode="HTML")
+ # Разделяем длинный текст Telegram если нужно (TG limit 4096)
+ if len(formatted_text) > 4000:
+ await status_msg.edit_text(formatted_text[:4000], parse_mode="HTML")
+ await message.reply(formatted_text[4000:], parse_mode="HTML")
+ else:
+ await status_msg.edit_text(formatted_text, parse_mode="HTML")
+
+ # 4. Синтез и отправка голосовой заметки (Voice Note)
+ try:
+ voice_bytes = await text_to_speech_bytes(agent_response)
+ if voice_bytes:
+ voice_file = BufferedInputFile(voice_bytes, filename="reply_voice.mp3")
+ await message.reply_voice(voice=voice_file, caption="🔊 Голосовой отклик Antigravity Agent")
+ except Exception as tts_err:
+ logger.error(f"TTS Send error: {tts_err}")
+
else:
- await status_msg.edit_text("🔇 Голос обнаружен, но слов не разобрал.")
+ await status_msg.edit_text("🔇 Голос принят, но Gemini не разобрал слов. Попробуй наговорить еще раз.")
except Exception as e:
- logger.error(f"❌ Voice Integration Error: {e}")
- await status_msg.edit_text(f"❌ Ошибка модуля Whisper: {e}")
+ logger.error(f"❌ Gemini Voice Integration Error: {e}")
+ await status_msg.edit_text(f"❌ Ошибка голосового моста: {e}", parse_mode="HTML")
voice_router = router