| | |
| | import os |
| | import sys |
| | import json |
| | import time |
| | import shutil |
| | import signal |
| | import logging |
| | import subprocess |
| | from pathlib import Path |
| | import tempfile |
| | import threading |
| |
|
| | |
| | logging.basicConfig( |
| | level=logging.INFO, |
| | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
| | handlers=[logging.StreamHandler()] |
| | ) |
| | logger = logging.getLogger("TEN-Agent") |
| |
|
| | |
| | TMP_DIR = Path("/tmp/ten_user") |
| | LOG_DIR = TMP_DIR / "logs" |
| | AGENTS_DIR = TMP_DIR / "agents" |
| | RAG_DIR = TMP_DIR / "rag_data" |
| |
|
| | def setup_environment(): |
| | """Настройка базового окружения""" |
| | logger.info("Current directory: %s", os.getcwd()) |
| | logger.info("Current user: %s", os.environ.get('USER', 'unknown')) |
| | |
| | |
| | logger.info("HOME: %s", os.environ.get('HOME', 'Not set')) |
| | logger.info("PATH: %s", os.environ.get('PATH', 'Not set')) |
| | |
| | |
| | logger.info("Checking permissions for %s", "/tmp") |
| | try: |
| | result = subprocess.run(["ls", "-la", "/tmp"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| | logger.info(" - %s", result.stdout.decode('utf-8').strip()) |
| | |
| | |
| | test_file = "/tmp/ten_test_write.txt" |
| | can_write = False |
| | try: |
| | with open(test_file, 'w') as f: |
| | f.write("Test write") |
| | can_write = True |
| | logger.info(" - Can write: Yes") |
| | os.remove(test_file) |
| | except Exception as e: |
| | logger.info(" - Can write: No (%s)", str(e)) |
| | |
| | if not can_write: |
| | logger.error("Cannot write to /tmp. This is required for operation.") |
| | sys.exit(1) |
| | except Exception as e: |
| | logger.error("Error checking permissions: %s", str(e)) |
| |
|
| | def create_directory_structure(): |
| | """Создание необходимой структуры директорий""" |
| | logger.info("Creating user directory...") |
| | |
| | |
| | TMP_DIR.mkdir(exist_ok=True) |
| | logger.info(f"Created directory structure at {TMP_DIR}") |
| | |
| | |
| | AGENTS_DIR.mkdir(exist_ok=True) |
| | RAG_DIR.mkdir(exist_ok=True) |
| | LOG_DIR.mkdir(exist_ok=True) |
| | |
| | |
| | server_log_dir = Path("/tmp/ten_agent/logs") |
| | server_log_dir.parent.mkdir(exist_ok=True) |
| | server_log_dir.mkdir(exist_ok=True) |
| | |
| | logger.info(f"Created log directory at {LOG_DIR}") |
| | logger.info(f"Created server log directory at {server_log_dir}") |
| |
|
| | def create_env_file(): |
| | """Создание файла .env для API сервера""" |
| | logger.info("Creating .env file...") |
| | |
| | |
| | env_path = Path("/app/.env") |
| | tmp_env_path = Path("/tmp/ten_agent/.env") |
| | |
| | |
| | env_content = """ |
| | LOG_LEVEL=debug |
| | LOG_DIR=/tmp/ten_agent/logs |
| | AGENT_SERVER_DIRECTORY=/tmp/ten_user/agents |
| | AGENT_SERVER_HOST=0.0.0.0 |
| | AGENT_SERVER_PORT=8080 |
| | PUBLIC_URL=http://localhost:7860 |
| | DISABLE_CAMERA=true |
| | """ |
| | |
| | |
| | with open(tmp_env_path, 'w') as f: |
| | f.write(env_content) |
| | |
| | |
| | try: |
| | with open(env_path, 'w') as f: |
| | f.write(env_content) |
| | logger.info(f"Created .env file at {env_path}") |
| | except Exception as e: |
| | logger.warning(f"Could not create .env in /app, using temporary one: {e}") |
| | |
| | logger.info(f"Created .env file at {tmp_env_path}") |
| | |
| | |
| | os.environ["LOG_LEVEL"] = "debug" |
| | os.environ["LOG_DIR"] = "/tmp/ten_agent/logs" |
| | os.environ["AGENT_SERVER_DIRECTORY"] = str(AGENTS_DIR) |
| | os.environ["AGENT_SERVER_HOST"] = "0.0.0.0" |
| | os.environ["AGENT_SERVER_PORT"] = "8080" |
| | os.environ["PUBLIC_URL"] = "http://localhost:7860" |
| | os.environ["DISABLE_CAMERA"] = "true" |
| |
|
| | def create_basic_config(): |
| | """Создание базовых конфигурационных файлов""" |
| | logger.info("Creating basic configuration files...") |
| | |
| | |
| | property_path = AGENTS_DIR / "property.json" |
| | if not property_path.exists(): |
| | property_data = { |
| | "_ten": { |
| | "version": "0.0.1" |
| | }, |
| | "name": "TEN Agent Example", |
| | "version": "0.0.1", |
| | "extensions": ["openai_chatgpt"], |
| | "description": "A basic voice agent with OpenAI", |
| | "graphs": [ |
| | { |
| | "id": "voice_agent", |
| | "name": "Voice Agent", |
| | "description": "Basic voice agent with OpenAI", |
| | "file": "examples/voice_agent.json" |
| | }, |
| | { |
| | "id": "chat_agent", |
| | "name": "Chat Agent", |
| | "description": "Simple chat agent", |
| | "file": "examples/chat_agent.json" |
| | } |
| | ] |
| | } |
| | |
| | with open(property_path, 'w') as f: |
| | json.dump(property_data, f, indent=2) |
| | |
| | |
| | examples_dir = AGENTS_DIR / "examples" |
| | examples_dir.mkdir(exist_ok=True) |
| | |
| | |
| | voice_agent_path = examples_dir / "voice_agent.json" |
| | if not voice_agent_path.exists(): |
| | voice_data = { |
| | "_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": "Вы полезный голосовой помощник." |
| | } |
| | } |
| | }, |
| | { |
| | "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" |
| | } |
| | |
| | with open(voice_agent_path, 'w') as f: |
| | json.dump(voice_data, f, indent=2) |
| | |
| | |
| | chat_agent_path = examples_dir / "chat_agent.json" |
| | if not chat_agent_path.exists(): |
| | chat_data = { |
| | "_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": "Вы полезный чат-бот." |
| | } |
| | } |
| | }, |
| | { |
| | "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" |
| | } |
| | |
| | with open(chat_agent_path, 'w') as f: |
| | json.dump(chat_data, f, indent=2) |
| | |
| | |
| | manifest_path = AGENTS_DIR / "manifest.json" |
| | if not manifest_path.exists(): |
| | manifest_data = { |
| | "_ten": { |
| | "version": "0.0.1" |
| | }, |
| | "name": "default", |
| | "agents": [ |
| | { |
| | "name": "voice_agent", |
| | "description": "A simple voice agent", |
| | "type": "voice" |
| | }, |
| | { |
| | "name": "chat_agent", |
| | "description": "A text chat agent", |
| | "type": "chat" |
| | } |
| | ] |
| | } |
| | |
| | with open(manifest_path, 'w') as f: |
| | json.dump(manifest_data, f, indent=2) |
| | |
| | logger.info("Basic configuration files created successfully.") |
| |
|
| | def run_api_server(): |
| | """Запуск API сервера""" |
| | logger.info(f"Starting API server with agents directory: {AGENTS_DIR}") |
| | |
| | |
| | api_binary = Path("/app/server/bin/api") |
| | if not api_binary.exists(): |
| | logger.error(f"API binary not found at {api_binary}") |
| | raise FileNotFoundError(f"API binary not found at {api_binary}") |
| | |
| | |
| | work_dir = Path("/app/server") |
| | env = os.environ.copy() |
| | env["AGENT_SERVER_DIRECTORY"] = str(AGENTS_DIR) |
| | env["LOG_DIR"] = "/tmp/ten_agent/logs" |
| | |
| | cmd = [str(api_binary)] |
| | logger.info(f"Running command: {' '.join(cmd)}") |
| | logger.info(f"With AGENT_SERVER_DIRECTORY={env['AGENT_SERVER_DIRECTORY']}") |
| | logger.info(f"With LOG_DIR={env['LOG_DIR']}") |
| | |
| | |
| | api_process = subprocess.Popen( |
| | cmd, |
| | cwd=str(work_dir), |
| | env=env, |
| | stdout=subprocess.PIPE, |
| | stderr=subprocess.PIPE, |
| | text=True, |
| | bufsize=1, |
| | ) |
| | |
| | |
| | def log_output(stream, prefix): |
| | for line in stream: |
| | logger.info(f"{prefix}: {line.strip()}") |
| | |
| | threading.Thread(target=log_output, args=(api_process.stdout, "API"), daemon=True).start() |
| | threading.Thread(target=log_output, args=(api_process.stderr, "API ERROR"), daemon=True).start() |
| | |
| | |
| | time.sleep(2) |
| | if api_process.poll() is not None: |
| | logger.error(f"API server failed to start with exit code {api_process.returncode}") |
| | |
| | stdout, stderr = api_process.communicate() |
| | logger.error(f"API stdout: {stdout}") |
| | logger.error(f"API stderr: {stderr}") |
| | |
| | |
| | logger.info("Trying alternative API server launch method...") |
| | try: |
| | |
| | wrapper_script = Path("/app/api_wrapper.py") |
| | if wrapper_script.exists(): |
| | logger.info("Using api_wrapper.py as fallback") |
| | env["AGENT_DIR"] = str(AGENTS_DIR) |
| | wrapper_process = subprocess.Popen( |
| | ["python3", str(wrapper_script)], |
| | env=env, |
| | stdout=subprocess.PIPE, |
| | stderr=subprocess.PIPE, |
| | text=True, |
| | bufsize=1, |
| | ) |
| | |
| | threading.Thread(target=log_output, args=(wrapper_process.stdout, "WRAPPER"), daemon=True).start() |
| | threading.Thread(target=log_output, args=(wrapper_process.stderr, "WRAPPER ERROR"), daemon=True).start() |
| | |
| | time.sleep(2) |
| | if wrapper_process.poll() is not None: |
| | logger.error(f"Wrapper script failed with code {wrapper_process.returncode}") |
| | stdout, stderr = wrapper_process.communicate() |
| | logger.error(f"Wrapper stdout: {stdout}") |
| | logger.error(f"Wrapper stderr: {stderr}") |
| | raise RuntimeError("All API server launch methods failed") |
| | |
| | logger.info("API server started through wrapper script") |
| | return wrapper_process |
| | except Exception as e: |
| | logger.error(f"Alternative launch method also failed: {e}") |
| | raise RuntimeError(f"API server failed to start with exit code {api_process.returncode}") |
| | |
| | logger.info("API server started successfully") |
| | return api_process |
| |
|
| | def run_proxy_server(): |
| | """Запуск прокси-сервера для обработки запросов TEN-Agent Designer API""" |
| | |
| | logger.info("Starting proxy server for Designer API requests") |
| | |
| | import http.server |
| | import socketserver |
| | import urllib.request |
| | import urllib.error |
| | import json |
| | |
| | class ProxyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): |
| | def do_POST(self): |
| | logger.info(f"Received POST request: {self.path}") |
| | |
| | |
| | if self.path.startswith('/api/dev/v1/packages/reload') or self.path.startswith('/api/designer/v1/packages/reload'): |
| | try: |
| | logger.info("Redirecting to /graphs") |
| | |
| | try: |
| | with urllib.request.urlopen("http://localhost:8080/graphs") as response: |
| | data = response.read().decode('utf-8') |
| | |
| | |
| | if not data or "Invalid format" in data: |
| | logger.info("Server returned error or empty response, creating custom response") |
| | |
| | property_path = AGENTS_DIR / "property.json" |
| | if property_path.exists(): |
| | try: |
| | with open(property_path, 'r') as f: |
| | property_data = json.load(f) |
| | graphs = property_data.get("graphs", []) |
| | except Exception as e: |
| | logger.error(f"Error reading property.json: {e}") |
| | graphs = [] |
| | else: |
| | graphs = [] |
| | |
| | |
| | for graph in graphs: |
| | if "id" not in graph: |
| | graph["id"] = graph.get("name", "").lower().replace(" ", "_") |
| | else: |
| | try: |
| | |
| | graphs = json.loads(data) |
| | except json.JSONDecodeError: |
| | logger.error(f"Error parsing JSON from server response: {data}") |
| | graphs = [] |
| | |
| | |
| | formatted_response = { |
| | "data": graphs, |
| | "status": 200, |
| | "message": "Success" |
| | } |
| | |
| | response_data = json.dumps(formatted_response).encode('utf-8') |
| | |
| | |
| | self.send_response(200) |
| | self.send_header('Content-Type', 'application/json') |
| | self.send_header('Content-Length', len(response_data)) |
| | self.send_header('Access-Control-Allow-Origin', '*') |
| | self.end_headers() |
| | self.wfile.write(response_data) |
| | logger.info(f"Sent response: {response_data.decode('utf-8')}") |
| | except urllib.error.URLError as e: |
| | logger.error(f"Error when redirecting: {e}") |
| | |
| | property_path = AGENTS_DIR / "property.json" |
| | if property_path.exists(): |
| | try: |
| | with open(property_path, 'r') as f: |
| | property_data = json.load(f) |
| | graphs = property_data.get("graphs", []) |
| | except Exception as e: |
| | logger.error(f"Error reading property.json: {e}") |
| | graphs = [] |
| | else: |
| | graphs = [] |
| | |
| | |
| | for graph in graphs: |
| | if "id" not in graph: |
| | graph["id"] = graph.get("name", "").lower().replace(" ", "_") |
| | |
| | formatted_response = { |
| | "data": graphs, |
| | "status": 200, |
| | "message": "Success" |
| | } |
| | |
| | response_data = json.dumps(formatted_response).encode('utf-8') |
| | |
| | self.send_response(200) |
| | self.send_header('Content-Type', 'application/json') |
| | self.send_header('Content-Length', len(response_data)) |
| | self.send_header('Access-Control-Allow-Origin', '*') |
| | self.end_headers() |
| | self.wfile.write(response_data) |
| | logger.info(f"Sent hardcoded response: {response_data.decode('utf-8')}") |
| | except Exception as e: |
| | logger.error(f"Unexpected error: {e}") |
| | self.send_error(500, f"Internal Server Error: {str(e)}") |
| | else: |
| | self.send_error(404, "Not Found") |
| | |
| | def do_OPTIONS(self): |
| | self.send_response(200) |
| | self.send_header('Access-Control-Allow-Origin', '*') |
| | self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') |
| | self.send_header('Access-Control-Allow-Headers', 'Content-Type') |
| | self.end_headers() |
| | |
| | def do_GET(self): |
| | logger.info(f"Received GET request: {self.path}") |
| | |
| | |
| | if self.path.startswith('/api/designer/v1/addons/extensions'): |
| | |
| | extensions = [ |
| | { |
| | "id": "openai_chatgpt", |
| | "name": "OpenAI ChatGPT", |
| | "version": "0.0.1", |
| | "description": "Integration with OpenAI ChatGPT API", |
| | "nodes": [ |
| | { |
| | "id": "openai_chatgpt", |
| | "name": "OpenAI ChatGPT", |
| | "category": "AI", |
| | "description": "Sends message to OpenAI ChatGPT API" |
| | } |
| | ] |
| | } |
| | ] |
| | |
| | formatted_response = { |
| | "data": extensions, |
| | "status": 200, |
| | "message": "Success" |
| | } |
| | |
| | response_data = json.dumps(formatted_response).encode('utf-8') |
| | |
| | self.send_response(200) |
| | self.send_header('Content-Type', 'application/json') |
| | self.send_header('Content-Length', len(response_data)) |
| | self.send_header('Access-Control-Allow-Origin', '*') |
| | self.end_headers() |
| | self.wfile.write(response_data) |
| | logger.info(f"Sent response for extensions: {response_data.decode('utf-8')}") |
| | else: |
| | self.send_error(404, "Not Found") |
| | |
| | |
| | port = 49483 |
| | httpd = socketserver.ThreadingTCPServer(("", port), ProxyHTTPRequestHandler) |
| | logger.info(f"Proxy server started at port {port}") |
| | |
| | |
| | proxy_thread = threading.Thread(target=httpd.serve_forever) |
| | proxy_thread.daemon = True |
| | proxy_thread.start() |
| | |
| | return httpd |
| |
|
| | def run_playground_proxy(): |
| | """Запускает прокси-сервер для обслуживания Playground UI и проксирования запросов к API""" |
| | import http.server |
| | import socketserver |
| | from urllib.parse import urljoin, urlparse |
| | import urllib.request |
| | import urllib.error |
| | |
| | class PlaygroundProxyHandler(http.server.SimpleHTTPRequestHandler): |
| | def log_message(self, format, *args): |
| | logger.info(f"PLAYGROUND-PROXY: {format % args}") |
| | |
| | def do_GET(self): |
| | |
| | if self.path.startswith('/api/'): |
| | api_path = self.path[5:] |
| | api_url = f"http://localhost:8080/{api_path}" |
| | |
| | try: |
| | logger.info(f"Proxying GET request to API: {api_url}") |
| | with urllib.request.urlopen(api_url) as response: |
| | data = response.read() |
| | self.send_response(response.status) |
| | |
| | |
| | for header, value in response.getheaders(): |
| | if header.lower() not in ('transfer-encoding', 'connection'): |
| | self.send_header(header, value) |
| | |
| | |
| | self.send_header('Access-Control-Allow-Origin', '*') |
| | self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') |
| | self.send_header('Access-Control-Allow-Headers', 'Content-Type') |
| | |
| | self.end_headers() |
| | self.wfile.write(data) |
| | except urllib.error.URLError as e: |
| | logger.error(f"Error proxying GET request to API: {e}") |
| | self.send_error(502, f"Error proxying to API: {e}") |
| | return |
| | |
| | |
| | elif self.path.startswith('/designer/'): |
| | designer_path = self.path[10:] |
| | designer_url = f"http://localhost:49483/{designer_path}" |
| | |
| | try: |
| | logger.info(f"Proxying GET request to designer: {designer_url}") |
| | with urllib.request.urlopen(designer_url) as response: |
| | data = response.read() |
| | self.send_response(response.status) |
| | |
| | |
| | for header, value in response.getheaders(): |
| | if header.lower() not in ('transfer-encoding', 'connection'): |
| | self.send_header(header, value) |
| | |
| | |
| | self.send_header('Access-Control-Allow-Origin', '*') |
| | self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') |
| | self.send_header('Access-Control-Allow-Headers', 'Content-Type') |
| | |
| | self.end_headers() |
| | self.wfile.write(data) |
| | except urllib.error.URLError as e: |
| | logger.error(f"Error proxying GET request to designer: {e}") |
| | self.send_error(502, f"Error proxying to designer: {e}") |
| | return |
| | |
| | |
| | elif self.path == '/' or self.path == '': |
| | self.send_response(302) |
| | self.send_header('Location', 'https://ten-framework.github.io/TEN-Playground-Static/') |
| | self.end_headers() |
| | return |
| | |
| | |
| | else: |
| | self.send_error(404, "File Not Found") |
| | |
| | def do_POST(self): |
| | |
| | if self.path.startswith('/api/'): |
| | api_path = self.path[5:] |
| | api_url = f"http://localhost:8080/{api_path}" |
| | |
| | content_length = int(self.headers.get('Content-Length', 0)) |
| | post_data = self.rfile.read(content_length) if content_length > 0 else b'' |
| | |
| | try: |
| | logger.info(f"Proxying POST request to API: {api_url}") |
| | request = urllib.request.Request( |
| | api_url, |
| | data=post_data, |
| | headers={k: v for k, v in self.headers.items() if k.lower() not in ('host', 'content-length')}, |
| | method='POST' |
| | ) |
| | |
| | with urllib.request.urlopen(request) as response: |
| | data = response.read() |
| | self.send_response(response.status) |
| | |
| | |
| | for header, value in response.getheaders(): |
| | if header.lower() not in ('transfer-encoding', 'connection'): |
| | self.send_header(header, value) |
| | |
| | |
| | self.send_header('Access-Control-Allow-Origin', '*') |
| | self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') |
| | self.send_header('Access-Control-Allow-Headers', 'Content-Type') |
| | |
| | self.end_headers() |
| | self.wfile.write(data) |
| | except urllib.error.URLError as e: |
| | logger.error(f"Error proxying POST request to API: {e}") |
| | self.send_error(502, f"Error proxying to API: {e}") |
| | |
| | |
| | elif self.path.startswith('/designer/'): |
| | designer_path = self.path[10:] |
| | designer_url = f"http://localhost:49483/{designer_path}" |
| | |
| | content_length = int(self.headers.get('Content-Length', 0)) |
| | post_data = self.rfile.read(content_length) if content_length > 0 else b'' |
| | |
| | try: |
| | logger.info(f"Proxying POST request to designer: {designer_url}") |
| | request = urllib.request.Request( |
| | designer_url, |
| | data=post_data, |
| | headers={k: v for k, v in self.headers.items() if k.lower() not in ('host', 'content-length')}, |
| | method='POST' |
| | ) |
| | |
| | with urllib.request.urlopen(request) as response: |
| | data = response.read() |
| | self.send_response(response.status) |
| | |
| | |
| | for header, value in response.getheaders(): |
| | if header.lower() not in ('transfer-encoding', 'connection'): |
| | self.send_header(header, value) |
| | |
| | |
| | self.send_header('Access-Control-Allow-Origin', '*') |
| | self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') |
| | self.send_header('Access-Control-Allow-Headers', 'Content-Type') |
| | |
| | self.end_headers() |
| | self.wfile.write(data) |
| | except urllib.error.URLError as e: |
| | logger.error(f"Error proxying POST request to designer: {e}") |
| | self.send_error(502, f"Error proxying to designer: {e}") |
| | else: |
| | self.send_error(404, "Not Found") |
| | |
| | def do_OPTIONS(self): |
| | self.send_response(200) |
| | self.send_header('Access-Control-Allow-Origin', '*') |
| | self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') |
| | self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization') |
| | self.end_headers() |
| | |
| | |
| | port = 7860 |
| | httpd = socketserver.ThreadingTCPServer(("", port), PlaygroundProxyHandler) |
| | logger.info(f"Playground proxy server started on port {port}") |
| | logger.info(f"Access Playground at: https://nitrox-ten.hf.space/") |
| | |
| | |
| | def check_api_availability(): |
| | while True: |
| | try: |
| | |
| | with urllib.request.urlopen("http://localhost:8080/graphs") as response: |
| | if response.status == 200: |
| | data = response.read().decode('utf-8') |
| | logger.info(f"API /graphs endpoint is available: {data}") |
| | except Exception as e: |
| | logger.warning(f"API check failed: {e}") |
| | |
| | time.sleep(30) |
| | |
| | api_check_thread = threading.Thread(target=check_api_availability, daemon=True) |
| | api_check_thread.start() |
| | |
| | |
| | httpd.serve_forever() |
| |
|
| | def run_playground(): |
| | """Запуск Playground UI""" |
| | logger.info("Starting Playground UI in development mode") |
| | |
| | |
| | env = os.environ.copy() |
| | env["PORT"] = "7860" |
| | env["AGENT_SERVER_URL"] = "http://localhost:8080" |
| | env["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true" |
| | env["NEXT_PUBLIC_DISABLE_CAMERA"] = "true" |
| | |
| | |
| | pnpm_home = "/tmp/pnpm-store" |
| | os.makedirs(pnpm_home, exist_ok=True) |
| | env["PNPM_HOME"] = pnpm_home |
| | env["HOME"] = "/tmp" |
| | env["XDG_CONFIG_HOME"] = "/tmp/.config" |
| | env["XDG_CACHE_HOME"] = "/tmp/.cache" |
| | env["XDG_DATA_HOME"] = "/tmp/.local/share" |
| | |
| | |
| | os.makedirs("/tmp/.config", exist_ok=True) |
| | os.makedirs("/tmp/.cache", exist_ok=True) |
| | os.makedirs("/tmp/.local/share", exist_ok=True) |
| | |
| | logger.info(f"Set PNPM_HOME to {pnpm_home}") |
| | logger.info(f"Set HOME to {env['HOME']}") |
| | |
| | playground_dir = Path("/app/playground") |
| | if not playground_dir.exists(): |
| | logger.error(f"Playground directory not found at {playground_dir}") |
| | raise FileNotFoundError(f"Playground directory not found at {playground_dir}") |
| | |
| | |
| | npmrc_path = playground_dir / ".npmrc" |
| | try: |
| | with open(npmrc_path, 'w') as f: |
| | f.write(f"cache=/tmp/.npm-cache\n") |
| | f.write(f"tmp=/tmp/.npm-tmp\n") |
| | logger.info(f"Created .npmrc file at {npmrc_path}") |
| | except Exception as e: |
| | logger.warning(f"Could not create .npmrc: {e}") |
| | |
| | |
| | os.makedirs("/tmp/.npm-cache", exist_ok=True) |
| | os.makedirs("/tmp/.npm-tmp", exist_ok=True) |
| | |
| | |
| | logger.info("Trying to start Playground with npx next dev...") |
| | cmd = ["npx", "next", "dev", "--port", "7860"] |
| | logger.info(f"Running command in {playground_dir}: {' '.join(cmd)}") |
| | |
| | |
| | playground_process = subprocess.Popen( |
| | cmd, |
| | cwd=str(playground_dir), |
| | env=env, |
| | stdout=subprocess.PIPE, |
| | stderr=subprocess.PIPE, |
| | text=True, |
| | bufsize=1, |
| | ) |
| | |
| | |
| | def log_output(stream, prefix): |
| | for line in stream: |
| | logger.info(f"{prefix}: {line.strip()}") |
| | |
| | threading.Thread(target=log_output, args=(playground_process.stdout, "PLAYGROUND"), daemon=True).start() |
| | threading.Thread(target=log_output, args=(playground_process.stderr, "PLAYGROUND ERROR"), daemon=True).start() |
| | |
| | |
| | time.sleep(5) |
| | if playground_process.poll() is not None: |
| | logger.warning(f"npx next dev failed with code {playground_process.returncode}, trying alternative method...") |
| | |
| | |
| | logger.info("Trying to start Playground with node directly...") |
| | |
| | |
| | next_page_js = playground_dir / ".next/server/app/page.js" |
| | if next_page_js.exists(): |
| | cmd = ["node", str(next_page_js)] |
| | logger.info(f"Running command: {' '.join(cmd)}") |
| | |
| | playground_process = subprocess.Popen( |
| | cmd, |
| | cwd=str(playground_dir), |
| | env=env, |
| | stdout=subprocess.PIPE, |
| | stderr=subprocess.PIPE, |
| | text=True, |
| | bufsize=1, |
| | ) |
| | |
| | threading.Thread(target=log_output, args=(playground_process.stdout, "PLAYGROUND (NODE)"), daemon=True).start() |
| | threading.Thread(target=log_output, args=(playground_process.stderr, "PLAYGROUND ERROR (NODE)"), daemon=True).start() |
| | |
| | time.sleep(3) |
| | if playground_process.poll() is not None: |
| | logger.error(f"All Playground UI launch methods failed") |
| | logger.info("Starting playground proxy as fallback") |
| | |
| | |
| | playground_thread = threading.Thread(target=run_playground_proxy, daemon=True) |
| | playground_thread.start() |
| | |
| | |
| | playground_process = subprocess.Popen( |
| | ["tail", "-f", "/dev/null"], |
| | stdout=subprocess.PIPE, |
| | stderr=subprocess.PIPE, |
| | ) |
| | else: |
| | logger.info("Starting playground proxy as fallback") |
| | |
| | |
| | playground_thread = threading.Thread(target=run_playground_proxy, daemon=True) |
| | playground_thread.start() |
| | |
| | |
| | playground_process = subprocess.Popen( |
| | ["tail", "-f", "/dev/null"], |
| | stdout=subprocess.PIPE, |
| | stderr=subprocess.PIPE, |
| | ) |
| | |
| | logger.info("Playground UI started successfully or proxy is running") |
| | return playground_process |
| |
|
| | def main(): |
| | """Основная функция запуска""" |
| | try: |
| | |
| | setup_environment() |
| | |
| | |
| | create_directory_structure() |
| | |
| | |
| | create_env_file() |
| | |
| | |
| | create_basic_config() |
| | |
| | |
| | api_process = run_api_server() |
| | proxy_server = run_proxy_server() |
| | playground_process = run_playground() |
| | |
| | |
| | logger.info("All services started. Monitoring status...") |
| | |
| | try: |
| | while True: |
| | |
| | if api_process.poll() is not None: |
| | logger.error(f"API server exited with code {api_process.returncode}") |
| | break |
| | |
| | |
| | if playground_process.poll() is not None: |
| | logger.error(f"Playground UI exited with code {playground_process.returncode}") |
| | break |
| | |
| | time.sleep(5) |
| | except KeyboardInterrupt: |
| | logger.info("Received interrupt signal") |
| | finally: |
| | |
| | logger.info("Stopping services...") |
| | |
| | try: |
| | if api_process and api_process.poll() is None: |
| | api_process.terminate() |
| | api_process.wait(timeout=5) |
| | except Exception as e: |
| | logger.warning(f"Error stopping API server: {e}") |
| | |
| | try: |
| | if playground_process and playground_process.poll() is None: |
| | playground_process.terminate() |
| | playground_process.wait(timeout=5) |
| | except Exception as e: |
| | logger.warning(f"Error stopping Playground UI: {e}") |
| | |
| | try: |
| | proxy_server.shutdown() |
| | except Exception as e: |
| | logger.warning(f"Error stopping proxy server: {e}") |
| | |
| | except Exception as e: |
| | logger.error(f"Error: {e}", exc_info=True) |
| | return 1 |
| | |
| | return 0 |
| |
|
| | if __name__ == "__main__": |
| | |
| | signal.signal(signal.SIGINT, lambda sig, frame: sys.exit(0)) |
| | signal.signal(signal.SIGTERM, lambda sig, frame: sys.exit(0)) |
| | |
| | |
| | sys.exit(main()) |