3v324v23 commited on
Commit
eeea7be
·
1 Parent(s): 74e8d5b

Реализация полноценного прокси для работы Playground UI в HuggingFace Space

Browse files
Files changed (1) hide show
  1. app.py +188 -72
app.py CHANGED
@@ -6,6 +6,10 @@ import time
6
  from pathlib import Path
7
  import signal
8
  import shutil
 
 
 
 
9
 
10
  def main():
11
  processes = []
@@ -23,29 +27,33 @@ def main():
23
  print(f"ERROR: Playground directory not found at {playground_dir}", file=sys.stderr)
24
  return 1
25
 
26
- # Создаем директорию для логов
27
- os.makedirs("/tmp/ten_agent", exist_ok=True)
 
 
 
28
 
29
  # Запускаем API сервер
30
  print("Starting TEN-Agent API server on port 8080...")
31
  api_server_env = os.environ.copy()
32
- api_server_env["LOG_PATH"] = "/tmp/ten_agent"
33
  api_server_env["LOG_STDOUT"] = "true"
34
  api_server_process = subprocess.Popen([str(api_binary)], env=api_server_env)
35
  processes.append(api_server_process)
36
 
37
  # Даем время API серверу запуститься
38
- time.sleep(5)
39
 
40
- # Запускаем Playground UI
41
  print("Starting Playground UI on port 3000...")
42
  playground_env = os.environ.copy()
43
  playground_env["AGENT_SERVER_URL"] = "http://localhost:8080" # Подключаемся к локальному API серверу
 
44
 
45
- # Исправляем параметры командной строки для Next.js
46
  playground_process = subprocess.Popen(
47
- ["pnpm", "start", "--", "--port", "3000"],
48
- cwd=str(playground_dir),
49
  env=playground_env
50
  )
51
  processes.append(playground_process)
@@ -53,71 +61,173 @@ def main():
53
  # Даем время Playground UI запуститься
54
  time.sleep(5)
55
 
56
- # Запускаем простую HTML страницу для Hugging Face
57
- from http.server import HTTPServer, SimpleHTTPRequestHandler
58
-
59
- class SimpleHandler(SimpleHTTPRequestHandler):
 
 
 
 
 
 
 
60
  def do_GET(self):
61
- self.send_response(200)
62
- self.send_header('Content-type', 'text/html; charset=utf-8')
63
- self.end_headers()
 
64
 
65
- html_content = """
66
- <!DOCTYPE html>
67
- <html>
68
- <head>
69
- <title>TEN Agent - Hugging Face Space</title>
70
- <meta charset="utf-8">
71
- <style>
72
- body { font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }
73
- h1 { color: #333; }
74
- .info { background: #f8f9fa; border-left: 4px solid #28a745; padding: 15px; margin-bottom: 20px; }
75
- .warning { background: #fff3cd; border-left: 4px solid #ffc107; padding: 15px; margin-bottom: 20px; }
76
- .endpoint { background: #e9ecef; padding: 10px; border-radius: 5px; font-family: monospace; }
77
- .api { margin-top: 30px; }
78
- </style>
79
- </head>
80
- <body>
81
- <h1>TEN Agent запущен успешно!</h1>
82
- <div class="info">
83
- <p>TEN Agent API сервер работает на порту 8080.</p>
84
- <p>Playground UI запущен на порту 3000.</p>
85
- </div>
86
-
87
- <div class="warning">
88
- <p><strong>Внимание:</strong> Из-за ограничений Hugging Face Space, доступ к интерфейсу через прокси может быть нестабильным.</p>
89
- </div>
90
-
91
- <div class="api">
92
- <h2>API эндпоинты:</h2>
93
- <ul>
94
- <li>API сервер: <span class="endpoint">http://localhost:8080</span></li>
95
- <li>Playground UI: <span class="endpoint">http://localhost:3000</span></li>
96
- </ul>
97
- </div>
98
-
99
- <h2>Инструкция по локальному использованию</h2>
100
- <p>Для наилучшего опыта, подключите локальный Playground к этому API:</p>
101
- <ol>
102
- <li>Клонируйте репозиторий: <code>git clone https://github.com/TEN-framework/TEN-Agent.git</code></li>
103
- <li>Перейдите в директорию playground: <code>cd TEN-Agent/playground</code></li>
104
- <li>Установите зависимости: <code>pnpm install</code></li>
105
- <li>Запустите Playground с подключением к API: <code>AGENT_SERVER_URL=https://nitrox-ten.hf.space pnpm dev</code></li>
106
- <li>Откройте в браузере: <code>http://localhost:3000</code></li>
107
- </ol>
108
-
109
- <p>См. <a href="https://github.com/TEN-framework/TEN-Agent" target="_blank">документацию TEN Agent</a> для получения дополнительной информации.</p>
110
- </body>
111
- </html>
112
- """
113
 
114
- self.wfile.write(html_content.encode('utf-8'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
- # Запускаем HTTP сервер
117
- port = 7860 # Hugging Face Space обычно ожидает сервер на порту 7860
118
- print(f"Starting HTTP server on port {port}...")
119
- httpd = HTTPServer(('0.0.0.0', port), SimpleHandler)
120
- httpd.serve_forever()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  except KeyboardInterrupt:
123
  print("Shutting down...")
@@ -125,10 +235,16 @@ def main():
125
  # Завершаем все процессы при выходе
126
  for proc in processes:
127
  try:
128
- proc.terminate()
129
- proc.wait(timeout=5)
 
130
  except:
131
- proc.kill()
 
 
 
 
 
132
 
133
  return 0
134
 
 
6
  from pathlib import Path
7
  import signal
8
  import shutil
9
+ import http.client
10
+ import socketserver
11
+ from http.server import HTTPServer, BaseHTTPRequestHandler
12
+ import threading
13
 
14
  def main():
15
  processes = []
 
27
  print(f"ERROR: Playground directory not found at {playground_dir}", file=sys.stderr)
28
  return 1
29
 
30
+ # Создаем директорию для логов с правами доступа
31
+ log_dir = "/tmp/ten_agent"
32
+ os.makedirs(log_dir, exist_ok=True)
33
+ # Даем всем права на запись в директорию логов
34
+ os.chmod(log_dir, 0o777)
35
 
36
  # Запускаем API сервер
37
  print("Starting TEN-Agent API server on port 8080...")
38
  api_server_env = os.environ.copy()
39
+ api_server_env["LOG_PATH"] = log_dir
40
  api_server_env["LOG_STDOUT"] = "true"
41
  api_server_process = subprocess.Popen([str(api_binary)], env=api_server_env)
42
  processes.append(api_server_process)
43
 
44
  # Даем время API серверу запуститься
45
+ time.sleep(3)
46
 
47
+ # Запускаем Playground UI на порту 3000
48
  print("Starting Playground UI on port 3000...")
49
  playground_env = os.environ.copy()
50
  playground_env["AGENT_SERVER_URL"] = "http://localhost:8080" # Подключаемся к локальному API серверу
51
+ playground_env["NODE_ENV"] = "production" # Убедимся, что запускаем в production режиме
52
 
53
+ # Запускаем Next.js с корректными параметрами
54
  playground_process = subprocess.Popen(
55
+ "cd /app/playground && pnpm start -- --port 3000",
56
+ shell=True,
57
  env=playground_env
58
  )
59
  processes.append(playground_process)
 
61
  # Даем время Playground UI запуститься
62
  time.sleep(5)
63
 
64
+ # Создаем эффективный прокси-сервер
65
+ class ProxyHandler(BaseHTTPRequestHandler):
66
+ # Отключаем логирование каждого запроса
67
+ def log_message(self, format, *args):
68
+ if args and args[0].startswith('GET /?logs=container'):
69
+ return # Игнорируем логи для запросов логов контейнера
70
+ sys.stderr.write("%s - - [%s] %s\n" %
71
+ (self.client_address[0],
72
+ self.log_date_time_string(),
73
+ format % args))
74
+
75
  def do_GET(self):
76
+ self._handle_request('GET')
77
+
78
+ def do_POST(self):
79
+ self._handle_request('POST')
80
 
81
+ def do_PUT(self):
82
+ self._handle_request('PUT')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
+ def do_DELETE(self):
85
+ self._handle_request('DELETE')
86
+
87
+ def do_OPTIONS(self):
88
+ self._handle_request('OPTIONS')
89
+
90
+ def _handle_request(self, method):
91
+ try:
92
+ # Определяем, какой сервер должен обработать запрос
93
+ target_host = 'localhost'
94
+
95
+ # API endpoints идут на порт 8080 (API сервер)
96
+ if self.path.startswith('/health') or \
97
+ self.path.startswith('/list') or \
98
+ self.path.startswith('/graphs') or \
99
+ self.path.startswith('/start') or \
100
+ self.path.startswith('/stop') or \
101
+ self.path.startswith('/ping') or \
102
+ self.path.startswith('/token') or \
103
+ self.path.startswith('/dev-tmp') or \
104
+ self.path.startswith('/vector'):
105
+ target_port = 8080
106
+ else:
107
+ # Все остальные запросы (включая / и UI assets) идут на порт 3000 (Playground)
108
+ target_port = 3000
109
+
110
+ # Пробуем подключиться к целевому серверу
111
+ conn = http.client.HTTPConnection(target_host, target_port, timeout=30)
112
+
113
+ # Получаем данные запроса для POST/PUT
114
+ body = None
115
+ if method in ['POST', 'PUT']:
116
+ content_length = int(self.headers.get('Content-Length', 0))
117
+ body = self.rfile.read(content_length) if content_length > 0 else None
118
+
119
+ # Копируем все заголовки запроса
120
+ headers = {k: v for k, v in self.headers.items()}
121
+
122
+ # Исправляем Host заголовок
123
+ headers['Host'] = f"{target_host}:{target_port}"
124
+
125
+ # Отправляем запрос на правильный сервер
126
+ conn.request(method, self.path, body=body, headers=headers)
127
+
128
+ # Получаем ответ
129
+ response = conn.getresponse()
130
+
131
+ # Отправляем статус ответа
132
+ self.send_response(response.status)
133
+
134
+ # Копируем все заголовки ответа
135
+ for header, value in response.getheaders():
136
+ if header.lower() != 'transfer-encoding': # Исключаем заголо��ок transfer-encoding
137
+ self.send_header(header, value)
138
+
139
+ # Завершаем заголовки
140
+ self.end_headers()
141
+
142
+ # Отправляем тело ответа
143
+ chunk_size = 8192
144
+ while True:
145
+ chunk = response.read(chunk_size)
146
+ if not chunk:
147
+ break
148
+ self.wfile.write(chunk)
149
+
150
+ # Закрываем соединение
151
+ conn.close()
152
+
153
+ except Exception as e:
154
+ print(f"Proxy error for {method} {self.path}: {str(e)}", file=sys.stderr)
155
+
156
+ # Для запросов мониторинга не показываем ошибку
157
+ if self.path == '/?logs=container':
158
+ self.send_response(200)
159
+ self.send_header('Content-type', 'text/plain')
160
+ self.end_headers()
161
+ self.wfile.write(b"OK")
162
+ return
163
+
164
+ # Отправляем страницу с ошибкой и разъяснением
165
+ self.send_response(500)
166
+ self.send_header('Content-type', 'text/html; charset=utf-8')
167
+ self.end_headers()
168
+
169
+ error_message = f"""
170
+ <!DOCTYPE html>
171
+ <html>
172
+ <head>
173
+ <title>TEN Agent - Error</title>
174
+ <meta charset="utf-8">
175
+ <style>
176
+ body {{ font-family: Arial, sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }}
177
+ h1 {{ color: #dc3545; }}
178
+ .error {{ background: #f8d7da; border-left: 4px solid #dc3545; padding: 15px; margin-bottom: 20px; }}
179
+ pre {{ background: #f8f9fa; padding: 10px; border-radius: 5px; overflow-x: auto; }}
180
+ </style>
181
+ </head>
182
+ <body>
183
+ <h1>Произошла ошибка при обработке запроса</h1>
184
+ <div class="error">
185
+ <p><strong>Детали ошибки:</strong> {str(e)}</p>
186
+ <p>Целевой порт: {target_port}</p>
187
+ </div>
188
+ <p>Система пытается запустить все компоненты. Попробуйте обновить страницу через минуту.</p>
189
+ </body>
190
+ </html>
191
+ """
192
+
193
+ self.wfile.write(error_message.encode('utf-8'))
194
 
195
+ # Запускаем HTTP прокси-сервер
196
+ port = 7860 # Hugging Face Space ожидает сервер на порту 7860
197
+ print(f"Starting proxy server on port {port}...")
198
+
199
+ # Разрешаем повторное использование адреса и порта
200
+ class ReuseAddressServer(socketserver.ThreadingTCPServer):
201
+ allow_reuse_address = True
202
+ daemon_threads = True
203
+
204
+ server = ReuseAddressServer(('0.0.0.0', port), ProxyHandler)
205
+
206
+ # Запускаем сервер
207
+ server_thread = threading.Thread(target=server.serve_forever)
208
+ server_thread.daemon = True
209
+ server_thread.start()
210
+
211
+ # Продолжаем выполнение, чтобы можно было обработать сигналы остановки
212
+ while True:
213
+ # Проверяем, что все процессы еще живы
214
+ if not api_server_process.poll() is None:
215
+ print("API server has stopped, restarting...")
216
+ api_server_process = subprocess.Popen([str(api_binary)], env=api_server_env)
217
+ processes = [p for p in processes if p != api_server_process]
218
+ processes.append(api_server_process)
219
+
220
+ if not playground_process.poll() is None:
221
+ print("Playground UI has stopped, restarting...")
222
+ playground_process = subprocess.Popen(
223
+ "cd /app/playground && pnpm start -- --port 3000",
224
+ shell=True,
225
+ env=playground_env
226
+ )
227
+ processes = [p for p in processes if p != playground_process]
228
+ processes.append(playground_process)
229
+
230
+ time.sleep(10)
231
 
232
  except KeyboardInterrupt:
233
  print("Shutting down...")
 
235
  # Завершаем все процессы при выходе
236
  for proc in processes:
237
  try:
238
+ if proc and proc.poll() is None:
239
+ proc.terminate()
240
+ proc.wait(timeout=5)
241
  except:
242
+ if proc and proc.poll() is None:
243
+ proc.kill()
244
+
245
+ # Останавливаем сервер
246
+ if 'server' in locals():
247
+ server.shutdown()
248
 
249
  return 0
250