Spaces:
Runtime error
Runtime error
| """Обработчики команд Telegram""" | |
| import json | |
| import threading | |
| from datetime import datetime | |
| from .config import ALLOWED_USER | |
| from .state import STATE | |
| from .process_manager import PROCESS_MANAGER | |
| from . import INTERNET_AGENT | |
| from .file_manager import FILE_MANAGER | |
| from .notification_system import NOTIFICATIONS | |
| from .conductor_engine import ConductorEngine | |
| from .skill_orchestrator import SkillOrchestrator | |
| from .build_mode_editor import BuildModeEditor | |
| from .mode_handlers import run_chat_mode, run_skill_mode, run_build_mode | |
| from .telegram_utils import send_tg, download_tg_file, send_tg_file | |
| from .helpers import parse_skill_agents, parse_build_args, get_current_mode_info | |
| class BotHandler: | |
| HELP_TEXT = """PinkSky v7.0 — МОДУЛЬНАЯ АРХИТЕКТУРА | |
| 🎛️ Режимы: | |
| Chat — отправь сообщение | |
| /skill [задача] — Open Interpreter | |
| /build [спек] — сборка через кондуктор | |
| 🎭 Роли: | |
| /role [guru|hacker|architect|principal|evangelist|techlead|qa|sdet|qe|researcher|critic|universal] | |
| /role list — список ролей | |
| 🧠 Кондукторы: | |
| /conductor [default|strict|creative|economy|review|build] | |
| /conductor list — список | |
| 🤖 Модели: | |
| /model [имя] — переключить модель | |
| /model list — список моделей | |
| 📁 Файлы: | |
| /get [путь] — скачать файл | |
| /stop — остановить все процессы | |
| 🌐 Интернет: | |
| /search [запрос] — поиск в интернете | |
| /cache — статистика кэша | |
| /cache clear — очистить кэш | |
| 📊 Система: | |
| /status — текущий статус | |
| /history — история | |
| /export [json|md] — экспорт истории | |
| /mode — текущий режим""" | |
| def __init__(self, *args, **kwargs): | |
| # Принимаем args/kwargs для совместимости с BaseHTTPRequestHandler.__init__ | |
| # BotHandler не использует их напрямую | |
| self.mode_editor = BuildModeEditor(STATE) | |
| self.skill_orchestrator = SkillOrchestrator(STATE) | |
| def handle_message(self, data: dict): | |
| if "message" not in data: | |
| return | |
| message = data["message"] | |
| chat_id = str(message.get("chat", {}).get("id", "")) | |
| if ALLOWED_USER and chat_id != ALLOWED_USER: | |
| return | |
| NOTIFICATIONS.set_chat_id(chat_id) | |
| text = message.get("text", message.get("caption", "")).strip() | |
| file_id, file_name = None, None | |
| target_msg = message | |
| if "reply_to_message" in message: | |
| if "document" in message["reply_to_message"] or "photo" in message["reply_to_message"]: | |
| target_msg = message["reply_to_message"] | |
| if "document" in target_msg: | |
| file_id = target_msg["document"]["file_id"] | |
| file_name = target_msg["document"].get("file_name", "document.file") | |
| elif "photo" in target_msg: | |
| file_id = target_msg["photo"][-1]["file_id"] | |
| file_name = "photo.jpg" | |
| file_context = "" | |
| if file_id: | |
| dl_path = download_tg_file(file_id, file_name) | |
| if dl_path: | |
| file_context = f"\n\n[SYSTEM: User attached file: ./{dl_path}]" | |
| if not text: | |
| send_tg(chat_id, f"📁 Файл `{file_name}` сохранён. Ответь командой, например: `/build Что в этом файле?`") | |
| return | |
| if not text: | |
| return | |
| parts = text.split(maxsplit=1) | |
| command = parts[0].lower() | |
| args = parts[1].strip() if len(parts) > 1 else "" | |
| # === HELP === | |
| if command in ["/help", "/start"]: | |
| send_tg(chat_id, self.HELP_TEXT) | |
| return | |
| # === STOP === | |
| if command == "/stop": | |
| result = PROCESS_MANAGER.cancel_all() | |
| send_tg(chat_id, f"{result}\n\n{self.HELP_TEXT}") | |
| return | |
| # === STATUS === | |
| if command == "/status": | |
| info = get_current_mode_info() | |
| stats = f"""{info} | |
| 📊 Моделей: {len(STATE.models)} | |
| 📊 Ролей: {len(STATE.roles)} | |
| 📊 Кондукторов: {len(STATE.conductors)} | |
| 🌐 Интернет: {'✅' if STATE.build_context.get('internet_access', True) else '❌'} | |
| 🗑️ Кэш: {INTERNET_AGENT.get_cache_stats()} | |
| 🧵 Активных потоков: {PROCESS_MANAGER.get_active_count()}""" | |
| send_tg(chat_id, stats) | |
| return | |
| # === MODE === | |
| if command == "/mode": | |
| send_tg(chat_id, get_current_mode_info()) | |
| return | |
| # === CONDUCTOR === | |
| if command == "/conductor": | |
| if args == "list": | |
| lines = ["🧠 Доступные кондукторы:"] | |
| for name, cond in STATE.conductors.items(): | |
| marker = " [ACTIVE]" if name == STATE.current_conductor else "" | |
| lines.append(f"- {name}{marker}: {cond.description} [rank_by={cond.auto_rank_by}]") | |
| send_tg(chat_id, "\n".join(lines)) | |
| return | |
| if args in STATE.conductors: | |
| STATE.current_conductor = args | |
| send_tg(chat_id, f"🧠 Кондуктор: {args.upper()}\n{STATE.conductors[args].description}") | |
| else: | |
| available = ", ".join(STATE.conductors.keys()) | |
| send_tg(chat_id, f"Текущий: {STATE.current_conductor}\nДоступные: {available}") | |
| return | |
| # === ROLE === | |
| if command == "/role": | |
| role_parts = args.split(maxsplit=1) | |
| subcmd = role_parts[0].lower() if role_parts else "" | |
| subargs = role_parts[1] if len(role_parts) > 1 else "" | |
| if subcmd == "list": | |
| lines = ["🎭 Доступные роли:"] | |
| for name, role in STATE.roles.items(): | |
| marker = " [ACTIVE]" if name == STATE.current_role else "" | |
| lines.append(f"- {name}{marker}: {role.description} (complexity: {role.complexity})") | |
| send_tg(chat_id, "\n".join(lines)) | |
| return | |
| if subcmd == "info" and subargs: | |
| role = STATE.roles.get(subargs) | |
| if role: | |
| send_tg(chat_id, f"🎭 Роль {subargs}:\n\n{role.description}\n\nComplexity: {role.complexity}\nPreferred: {', '.join(role.preferred_models)}\n\nPrompt:\n```\n{role.prompt[:500]}...\n```") | |
| else: | |
| send_tg(chat_id, f"❌ Роль {subargs} не найдена") | |
| return | |
| if subcmd == "add" and subargs: | |
| add_parts = subargs.split(maxsplit=1) | |
| if len(add_parts) < 2: | |
| send_tg(chat_id, "Формат: /role add <name> <prompt>") | |
| return | |
| new_name, new_prompt = add_parts[0], add_parts[1] | |
| from .models import Role | |
| STATE.roles[new_name] = Role( | |
| name=new_name, prompt=new_prompt, | |
| description=f"Custom role (added {datetime.now().strftime('%Y-%m-%d')})", | |
| complexity="medium" | |
| ) | |
| STATE.save_roles() | |
| send_tg(chat_id, f"✅ Роль {new_name} добавлена!") | |
| return | |
| if subcmd in STATE.roles: | |
| STATE.current_role = subcmd | |
| send_tg(chat_id, f"🎭 Роль: {subcmd.upper()}\n{STATE.roles[subcmd].description}") | |
| else: | |
| available = ", ".join(STATE.roles.keys()) | |
| send_tg(chat_id, f"Текущая: {STATE.current_role}\nДоступные: {available}") | |
| return | |
| # === MODEL === | |
| if command == "/model": | |
| model_parts = args.split(maxsplit=1) | |
| subcmd = model_parts[0].lower() if model_parts else "" | |
| subargs = model_parts[1] if len(model_parts) > 1 else "" | |
| if subcmd == "list": | |
| lines = ["🤖 Модели (по coding rank):"] | |
| for name, model in sorted(STATE.models.items(), key=lambda x: x[1].coding_rank): | |
| if name == "hf_fallback": | |
| continue | |
| marker = " [ACTIVE]" if name == STATE.current_model else "" | |
| tier = 1 | |
| if model.coding_rank > 5: tier = 2 | |
| if model.coding_rank > 12: tier = 3 | |
| if model.coding_rank > 18: tier = 4 | |
| if model.coding_rank > 24: tier = 5 | |
| cost = f"${model.cost_per_1k_output}/1k" if model.cost_per_1k_output > 0 else "free" | |
| lines.append(f"- {name}{marker} -- TIER {tier} | coding={model.coding_rank} speed={model.speed_rank} reasoning={model.reasoning_rank} | {cost}") | |
| send_tg(chat_id, "\n".join(lines)) | |
| return | |
| if subcmd == "add" and subargs: | |
| add_parts = subargs.split() | |
| if len(add_parts) < 2: | |
| send_tg(chat_id, "Формат: /model add <name> <endpoint>") | |
| return | |
| new_name, new_endpoint = add_parts[0], add_parts[1] | |
| from .models import ModelConfig | |
| STATE.models[new_name] = ModelConfig( | |
| name=new_name, provider="openai", endpoint=new_endpoint, | |
| api_key_env="NVIDIA_API_KEY" | |
| ) | |
| STATE.save_models() | |
| send_tg(chat_id, f"✅ Модель {new_name} добавлена!") | |
| return | |
| if subcmd in STATE.models: | |
| STATE.current_model = subcmd | |
| send_tg(chat_id, f"🤖 Модель: {subcmd.upper()}\n{STATE.models[subcmd].endpoint}") | |
| else: | |
| available = ", ".join([k for k in STATE.models.keys() if k != "hf_fallback"]) | |
| send_tg(chat_id, f"Текущая: {STATE.current_model}\nДоступные: {available}") | |
| return | |
| # === GET FILE === | |
| if command == "/get": | |
| if not args: | |
| send_tg(chat_id, "Использование: /get ./projects/test.py") | |
| else: | |
| send_tg_file(chat_id, args) | |
| return | |
| # === SEARCH === | |
| if command == "/search": | |
| if not args: | |
| send_tg(chat_id, "Использование: /search запрос") | |
| return | |
| results = INTERNET_AGENT.search_web(args) | |
| if results: | |
| lines = [f"🔍 Результаты поиска: {args}\n"] | |
| for i, r in enumerate(results[:5], 1): | |
| lines.append(f"{i}. [{r['title']}]({r['url']})\n{r['snippet'][:150]}...\n") | |
| send_tg(chat_id, "\n".join(lines)) | |
| else: | |
| send_tg(chat_id, "❌ Ничего не найдено") | |
| return | |
| # === CACHE === | |
| if command == "/cache": | |
| if args == "clear": | |
| result = INTERNET_AGENT.clear_cache() | |
| send_tg(chat_id, result) | |
| else: | |
| send_tg(chat_id, INTERNET_AGENT.get_cache_stats()) | |
| return | |
| # === HISTORY === | |
| if command == "/history": | |
| mode = args if args in ("chat", "skill", "build") else "chat" | |
| history = getattr(STATE, f"{mode}_history", []) | |
| if not history: | |
| send_tg(chat_id, f"📋 История {mode} пуста") | |
| return | |
| lines = [f"📋 История {mode} (последние 5):"] | |
| for entry in history[-5:]: | |
| ts = entry.get("timestamp", "unknown") | |
| role = entry.get("role", "unknown") | |
| content = entry.get("content", "")[:200] | |
| lines.append(f"\n[{ts}] {role}:\n{content}...") | |
| send_tg(chat_id, "\n".join(lines)) | |
| return | |
| # === EXPORT === | |
| if command == "/export": | |
| fmt = args if args in ("json", "md") else "json" | |
| if fmt == "json": | |
| data = STATE.export_history_json() | |
| FILE_MANAGER.save_file("exports/history.json", data) | |
| send_tg_file(chat_id, "exports/history.json") | |
| else: | |
| data = STATE.export_history_md() | |
| FILE_MANAGER.save_file("exports/history.md", data) | |
| send_tg_file(chat_id, "exports/history.md") | |
| return | |
| # === BUILD MODE === | |
| if command == "/build": | |
| if not args: | |
| send_tg(chat_id, "Укажите спецификацию проекта.") | |
| return | |
| params, clean_args = parse_build_args(args) | |
| run_build_mode(chat_id, clean_args, file_context, params) | |
| return | |
| # === SKILL MODE === | |
| if command == "/skill": | |
| if not args: | |
| send_tg(chat_id, "Укажите задачу.") | |
| return | |
| run_skill_mode(chat_id, args, file_context) | |
| return | |
| # === DEFAULT: CHAT MODE === | |
| run_chat_mode(chat_id, text, file_context) | |