202 lines
10 KiB
Python
202 lines
10 KiB
Python
import os
|
||
import logging
|
||
import asyncio
|
||
import sqlite3
|
||
import subprocess
|
||
from datetime import datetime
|
||
from aiogram import Router, F, Bot
|
||
from aiogram.types import Message, BufferedInputFile
|
||
from config import ADMIN_USER_ID, DATABASE_PATH
|
||
import google.generativeai as genai
|
||
import edge_tts
|
||
|
||
logger = logging.getLogger(__name__)
|
||
router = Router()
|
||
|
||
# Валидный проверенный ключ 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):
|
||
"""Фиксация статистики в основную базу Домового"""
|
||
try:
|
||
conn = sqlite3.connect(DATABASE_PATH)
|
||
cursor = conn.cursor()
|
||
cursor.execute('''
|
||
CREATE TABLE IF NOT EXISTS voice_recognition_logs (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
user_id INTEGER,
|
||
username TEXT,
|
||
message_type TEXT,
|
||
duration INTEGER,
|
||
text TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
''')
|
||
cursor.execute('''
|
||
INSERT INTO voice_recognition_logs (user_id, username, message_type, duration, text)
|
||
VALUES (?, ?, ?, ?, ?)
|
||
''', (user_id, username, msg_type, duration, text))
|
||
conn.commit()
|
||
conn.close()
|
||
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("🎙 <i>Слушаю и расшифровываю через Gemini Multimodal API...</i>", parse_mode="HTML")
|
||
await bot.send_chat_action(message.chat.id, "typing")
|
||
|
||
try:
|
||
if message.voice:
|
||
file_id = message.voice.file_id
|
||
filename = f"voice_{file_id}.ogg"
|
||
duration = message.voice.duration
|
||
msg_type = "VOICE"
|
||
else:
|
||
file_id = message.video_note.file_id
|
||
filename = f"note_{file_id}.mp4"
|
||
duration = message.video_note.duration
|
||
msg_type = "VIDEO_NOTE"
|
||
|
||
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)
|
||
|
||
# 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)
|
||
|
||
await status_msg.edit_text(f"🗣 <b>Команда принята:</b> «<i>{transcribed_text}</i>»\n\n⚙️ <b>Агент выполняет системную команду...</b>", 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"🎯 <b>Ответ Antigravity Agent:</b>\n\n{agent_response}"
|
||
|
||
# Разделяем длинный текст 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("🔇 Голос принят, но Gemini не разобрал слов. Попробуй наговорить еще раз.")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ Gemini Voice Integration Error: {e}")
|
||
await status_msg.edit_text(f"❌ Ошибка голосового моста: {e}", parse_mode="HTML")
|
||
|
||
voice_router = router
|