#!/usr/bin/env python3 import os import subprocess import sys import time import json from pathlib import Path import signal import threading import shutil import http.server import socketserver import urllib.request import urllib.error import gradio as gr # Проверяем, запущены ли мы в HuggingFace Space IS_HF_SPACE = os.environ.get("SPACE_ID") is not None print(f"Running in HuggingFace Space: {IS_HF_SPACE}") # Конфигурация путей TMP_DIR = Path("/tmp/ten_user") AGENTS_DIR = TMP_DIR / "agents" LOGS_DIR = TMP_DIR / "logs" NEXTJS_PORT = int(os.environ.get("UI_PORT", 3000)) API_PORT = int(os.environ.get("API_PORT", 8080)) GRADIO_PORT = int(os.environ.get("GRADIO_PORT", 7860)) # Адрес для подключения к UI в iframe if IS_HF_SPACE: # В HuggingFace Space нужно использовать внутренний URL UI_URL = f"https://{os.environ.get('SPACE_ID', 'unknown-space')}.hf.space/_next/iframe/3000" INTERNAL_HOST = "0.0.0.0" else: # В локальном окружении используем localhost UI_URL = f"http://localhost:{NEXTJS_PORT}" INTERNAL_HOST = "localhost" def create_directories(): """Создает необходимые директории""" print("Создание директорий...") # Создаем только директории, без попытки создания файлов TMP_DIR.mkdir(exist_ok=True, parents=True) AGENTS_DIR.mkdir(exist_ok=True, parents=True) LOGS_DIR.mkdir(exist_ok=True, parents=True) # Не создаем файл server.log, т.к. он вызывает ошибку прав доступа # (LOGS_DIR / "server.log").touch() print(f"Директории созданы в {TMP_DIR}") # Выводим информацию о правах доступа для отладки print(f"Права доступа для {TMP_DIR}: {os.stat(TMP_DIR).st_mode}") if LOGS_DIR.exists(): print(f"Директория логов создана: {LOGS_DIR}") print(f"Права доступа для {LOGS_DIR}: {os.stat(LOGS_DIR).st_mode}") else: print(f"Директория логов не создана: {LOGS_DIR}") # Пробуем записать лог в /tmp напрямую, где должны быть права на запись try: tmp_log_file = Path("/tmp/ten_server.log") tmp_log_file.touch() print(f"Создан файл лога в /tmp: {tmp_log_file}") except Exception as e: print(f"Не удалось создать файл лога в /tmp: {e}") # Это не критическая ошибка, продолжаем работу def create_config_files(): """Создает базовые файлы конфигурации""" print("Создание конфигурационных файлов...") # Используем директорию в /tmp напрямую, без вложенных директорий config_dir = Path("/tmp") property_file_path = config_dir / "property.json" voice_agent_path = config_dir / "voice_agent.json" chat_agent_path = config_dir / "chat_agent.json" # Создаем property.json с графами property_data = { "name": "TEN Agent Demo", "version": "0.0.1", "extensions": ["openai_chatgpt", "elevenlabs_tts", "deepgram_asr"], "description": "TEN Agent on Hugging Face Space", "graphs": [ { "name": "Voice Agent", "description": "Basic voice agent with OpenAI and ElevenLabs", "file": "voice_agent.json" }, { "name": "Chat Agent", "description": "Simple chat agent with OpenAI", "file": "chat_agent.json" } ] } try: with open(property_file_path, "w") as f: json.dump(property_data, f, indent=2) print(f"Файл {property_file_path} создан успешно") except Exception as e: print(f"Ошибка при создании {property_file_path}: {e}") return False # Создаем voice_agent.json voice_agent = { "_ten": {"version": "0.0.1"}, "nodes": [ { "id": "start", "type": "start", "data": {"x": 100, "y": 100} }, { "id": "openai_chatgpt", "type": "openai_chatgpt", "data": { "x": 300, "y": 200, "properties": { "model": "gpt-3.5-turbo", "temperature": 0.7, "system_prompt": "You are a helpful assistant." } } }, { "id": "elevenlabs_tts", "type": "elevenlabs_tts", "data": { "x": 500, "y": 200, "properties": { "voice_id": "21m00Tcm4TlvDq8ikWAM" } } }, { "id": "deepgram_asr", "type": "deepgram_asr", "data": { "x": 300, "y": 300, "properties": { "language": "ru" } } }, { "id": "end", "type": "end", "data": {"x": 700, "y": 100} } ], "edges": [ {"id": "start_to_chatgpt", "source": "start", "target": "openai_chatgpt"}, {"id": "chatgpt_to_tts", "source": "openai_chatgpt", "target": "elevenlabs_tts"}, {"id": "tts_to_end", "source": "elevenlabs_tts", "target": "end"}, {"id": "asr_to_chatgpt", "source": "deepgram_asr", "target": "openai_chatgpt"} ], "groups": [], "templates": [], "root": "start" } try: with open(voice_agent_path, "w") as f: json.dump(voice_agent, f, indent=2) print(f"Файл {voice_agent_path} создан успешно") except Exception as e: print(f"Ошибка при создании {voice_agent_path}: {e}") return False # Создаем chat_agent.json (упрощенная версия) chat_agent = { "_ten": {"version": "0.0.1"}, "nodes": [ { "id": "start", "type": "start", "data": {"x": 100, "y": 100} }, { "id": "openai_chatgpt", "type": "openai_chatgpt", "data": { "x": 300, "y": 200, "properties": { "model": "gpt-3.5-turbo", "temperature": 0.7, "system_prompt": "You are a helpful chat assistant." } } }, { "id": "end", "type": "end", "data": {"x": 500, "y": 100} } ], "edges": [ {"id": "start_to_chatgpt", "source": "start", "target": "openai_chatgpt"}, {"id": "chatgpt_to_end", "source": "openai_chatgpt", "target": "end"} ], "groups": [], "templates": [], "root": "start" } try: with open(chat_agent_path, "w") as f: json.dump(chat_agent, f, indent=2) print(f"Файл {chat_agent_path} создан успешно") except Exception as e: print(f"Ошибка при создании {chat_agent_path}: {e}") return False # Обновляем глобальную переменную AGENTS_DIR для использования нового пути global AGENTS_DIR AGENTS_DIR = config_dir print(f"Конфигурационные файлы созданы успешно в директории {config_dir}") return True def start_api_server(): """Запускает API сервер""" print("Запуск API сервера...") # Устанавливаем переменные окружения api_env = os.environ.copy() api_env["TEN_AGENT_DIR"] = str(AGENTS_DIR) api_env["API_PORT"] = str(API_PORT) # Выводим информацию о директории с агентами для отладки print(f"Директория с агентами: {AGENTS_DIR}") print(f"Файлы в директории агентов:") if AGENTS_DIR.exists(): for file in AGENTS_DIR.iterdir(): if file.name.endswith('.json'): print(f" - {file.name} ({os.path.getsize(file)}b)") # В HuggingFace Space нужны дополнительные настройки if IS_HF_SPACE: print("Configuring API server for HuggingFace Space environment...") # Указываем серверу специально использовать API wrapper api_env["USE_WRAPPER"] = "true" # Отключаем логирование в файл api_env["TEN_LOG_DISABLE_FILE"] = "true" # Указываем путь для временных файлов api_env["TMP_DIR"] = "/tmp" # Запускаем Python API wrapper api_cmd = ["python", "api_wrapper.py"] print(f"Running API command: {' '.join(api_cmd)}") api_process = subprocess.Popen( api_cmd, env=api_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) # Ждем запуска сервера time.sleep(2) # Проверяем, что процесс не упал if api_process.poll() is not None: stdout, stderr = api_process.communicate() print(f"API сервер не запустился!") print(f"STDOUT: {stdout.decode()}") print(f"STDERR: {stderr.decode()}") return None print(f"API server started and listening on port {API_PORT}") # Запускаем поток для вывода логов def log_output(process, prefix): for line in iter(process.stdout.readline, b''): print(f"[{prefix}] {line.decode().strip()}") for line in iter(process.stderr.readline, b''): print(f"[{prefix} ERROR] {line.decode().strip()}") log_thread = threading.Thread(target=log_output, args=(api_process, "API")) log_thread.daemon = True log_thread.start() return api_process def start_playground(): """Запускает Playground UI через Next.js""" print("Запуск Playground UI...") # Создаем директорию для Next.js в /tmp, где у нас есть права на запись tmp_playground_dir = Path("/tmp/ten_playground") if not tmp_playground_dir.exists(): print(f"Создаем временную директорию для Next.js: {tmp_playground_dir}") tmp_playground_dir.mkdir(exist_ok=True, parents=True) # Копируем необходимые файлы из /app/playground в /tmp/ten_playground print("Копируем файлы Next.js приложения во временную директорию...") try: os.system(f"cp -r /app/playground/app /tmp/ten_playground/") os.system(f"cp -r /app/playground/public /tmp/ten_playground/") os.system(f"cp /app/playground/package.json /tmp/ten_playground/") os.system(f"cp /app/playground/next.config.mjs /tmp/ten_playground/") os.system(f"cp /app/playground/tailwind.config.js /tmp/ten_playground/") os.system(f"cp /app/playground/postcss.config.js /tmp/ten_playground/") # Проверяем, что файлы скопировались if not (tmp_playground_dir / "app").exists(): print("Не удалось скопировать файлы Next.js. Создаем простое приложение...") create_simple_next_app(tmp_playground_dir) except Exception as e: print(f"Ошибка при копировании файлов: {e}") print("Создаем простое приложение Next.js...") create_simple_next_app(tmp_playground_dir) # Устанавливаем переменные окружения ui_env = os.environ.copy() ui_env["PORT"] = str(NEXTJS_PORT) ui_env["AGENT_SERVER_URL"] = f"http://{INTERNAL_HOST}:{API_PORT}" ui_env["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true" ui_env["NEXT_PUBLIC_DISABLE_CAMERA"] = "false" # В HuggingFace Space нужны дополнительные настройки if IS_HF_SPACE: print("Configuring for HuggingFace Space environment...") ui_env["NEXT_PUBLIC_IS_HF_SPACE"] = "true" # Отключаем строгие проверки CORS для работы в iframe ui_env["NEXT_PUBLIC_DISABLE_CORS"] = "true" # Запускаем UI из временной директории, где у нас есть права на запись ui_cmd = f"cd {tmp_playground_dir} && npx next dev" print(f"Running UI command: {ui_cmd}") ui_process = subprocess.Popen( ui_cmd, env=ui_env, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) # Ждем запуска UI time.sleep(5) # Проверяем, что процесс не упал if ui_process.poll() is not None: stdout, stderr = ui_process.communicate() print(f"Playground UI не запустился!") print(f"STDOUT: {stdout.decode()}") print(f"STDERR: {stderr.decode()}") return None # Запускаем поток для вывода логов def log_output(process, prefix): for line in iter(process.stdout.readline, b''): print(f"[{prefix}] {line.decode().strip()}") for line in iter(process.stderr.readline, b''): print(f"[{prefix} ERROR] {line.decode().strip()}") log_thread = threading.Thread(target=log_output, args=(ui_process, "UI")) log_thread.daemon = True log_thread.start() return ui_process def create_interface(): """Создает Gradio интерфейс для редиректа""" with gr.Blocks() as demo: gr.Markdown("# TEN Agent на Hugging Face Space") gr.Markdown("## Управление и мониторинг") # Статус серверов status_md = gr.Markdown("### Статус: Инициализация...") with gr.Row(): col1, col2 = gr.Column(), gr.Column() with col1: # Информация об API сервере gr.Markdown(f""" ### API сервер API сервер работает по адресу: http://{INTERNAL_HOST}:{API_PORT} Доступные эндпоинты: - `/graphs` - Список доступных графов - `/health` - Статус API сервера """) # Кнопка для проверки API check_api_btn = gr.Button("Проверить API сервер") api_result = gr.JSON(label="Результат запроса к API") def check_api(): try: import requests response = requests.get(f"http://{INTERNAL_HOST}:{API_PORT}/health") return response.json() except Exception as e: return {"status": "error", "message": str(e)} check_api_btn.click(check_api, outputs=api_result) with col2: # Информация о UI сервере gr.Markdown(f""" ### UI сервер UI сервер доступен по адресу: {UI_URL} Открыть UI в новой вкладке """) # Функция для открытия UI в iframe iframe_btn = gr.Button("Показать UI в iframe") def show_iframe(): return f"""
Для запуска сессии можно использовать API вручную:
curl -X POST http://localhost:8080/start -H "Content-Type: application/json" -d '{"graph_file":"voice_agent.json"}'