3v324v23 commited on
Commit
9c56f14
·
1 Parent(s): 9006d1c

Исправление проблемы отображения графов: добавление поля predefined_graphs и прокси-сервер для модификации ответов API

Browse files
Files changed (2) hide show
  1. app.py +181 -338
  2. proxy_server.py +229 -0
app.py CHANGED
@@ -14,10 +14,7 @@ import urllib.error
14
  import tempfile
15
  import http.server
16
  import socketserver
17
- import requests
18
- from threading import Thread
19
- from http import HTTPStatus
20
- from typing import Dict, Any, Optional
21
 
22
  # Настройка логирования
23
  logging.basicConfig(level=logging.INFO,
@@ -35,263 +32,6 @@ CHAT_AGENT_JSON = AGENTS_DIR / "chat_agent.json"
35
  API_BINARY = Path("/app/server/bin/api")
36
  PLAYGROUND_DIR = Path("/app/playground")
37
  BACKUP_DIR = Path("/app/backup")
38
- MOCK_API_DIR = Path("/app/mock-api")
39
-
40
- # Загружаем мок-ответы для дизайнера
41
- DESIGNER_PACKAGES_JSON = MOCK_API_DIR / "designer-packages.json"
42
-
43
- # Мок-данные для ответов дизайнера
44
- mock_responses = {}
45
-
46
- def load_mock_responses():
47
- """Загружает предварительно созданные мок-ответы"""
48
- global mock_responses
49
-
50
- try:
51
- if DESIGNER_PACKAGES_JSON.exists():
52
- with open(DESIGNER_PACKAGES_JSON, 'r') as f:
53
- mock_responses['packages'] = json.load(f)
54
- logger.info(f"Загружен мок-ответ для дизайнера: {mock_responses['packages']}")
55
- except Exception as e:
56
- logger.error(f"Ошибка при загрузке мок-ответов: {e}")
57
- # Создаём запасной ответ
58
- mock_responses['packages'] = {
59
- "success": True,
60
- "packages": [
61
- {
62
- "name": "default",
63
- "description": "Default package",
64
- "graphs": [
65
- {
66
- "name": "Voice Agent",
67
- "description": "Voice Agent with OpenAI",
68
- "file": "voice_agent.json",
69
- "id": "voice_agent",
70
- "package": "default"
71
- },
72
- {
73
- "name": "Chat Agent",
74
- "description": "Chat Agent",
75
- "file": "chat_agent.json",
76
- "id": "chat_agent",
77
- "package": "default"
78
- }
79
- ]
80
- }
81
- ]
82
- }
83
-
84
- class ProxyHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
85
- """HTTP-прокси для отладки и исправления запросов между UI и API"""
86
-
87
- def __init__(self, *args, **kwargs):
88
- # Целевой API сервер
89
- self.api_host = "localhost"
90
- self.api_port = 8080
91
- super().__init__(*args, **kwargs)
92
-
93
- def do_GET(self):
94
- """Обработка GET запросов"""
95
- logger.info(f"PROXY: GET запрос: {self.path}")
96
-
97
- # Специальная обработка для запроса графов
98
- if self.path == "/graphs" or self.path == "/api/agents/graphs":
99
- self._handle_graphs_request()
100
- return
101
-
102
- # Проксирование запроса к API
103
- target_url = f"http://{self.api_host}:{self.api_port}{self.path}"
104
- try:
105
- response = requests.get(target_url, headers=self._get_headers())
106
- self._send_response(response)
107
- except Exception as e:
108
- logger.error(f"PROXY: Ошибка при проксировании GET-запроса: {e}")
109
- self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
110
-
111
- def do_POST(self):
112
- """Обработка POST запросов"""
113
- content_length = int(self.headers.get('Content-Length', 0))
114
- post_data = self.rfile.read(content_length) if content_length > 0 else b''
115
-
116
- logger.info(f"PROXY: POST запрос: {self.path}")
117
-
118
- # Специальные запросы для дизайнера
119
- if "/api/designer/v1/packages/reload" in self.path or "/api/dev/v1/packages/reload" in self.path:
120
- self._handle_designer_reload()
121
- return
122
-
123
- # Проксирование запроса к API
124
- target_url = f"http://{self.api_host}:{self.api_port}{self.path}"
125
- try:
126
- response = requests.post(target_url, data=post_data, headers=self._get_headers())
127
- self._send_response(response)
128
- except Exception as e:
129
- logger.error(f"PROXY: Ошибка при проксировании POST-запроса: {e}")
130
- self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
131
-
132
- def _handle_graphs_request(self):
133
- """Специальная обработка для запроса графов"""
134
- try:
135
- # Читаем графы из property.json
136
- with open(PROPERTY_JSON, 'r') as f:
137
- property_data = json.load(f)
138
-
139
- graphs = property_data.get('graphs', [])
140
- logger.info(f"PROXY: Возвращаем графы напрямую: {json.dumps(graphs)}")
141
-
142
- self.send_response(HTTPStatus.OK)
143
- self.send_header('Content-Type', 'application/json')
144
- self.end_headers()
145
- self.wfile.write(json.dumps(graphs).encode('utf-8'))
146
- except Exception as e:
147
- logger.error(f"PROXY: Ошибка при обработке запроса графов: {e}")
148
- self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
149
-
150
- def _handle_designer_reload(self):
151
- """Обработка запросов дизайнера"""
152
- # Новый формат ответа для designer API
153
- response_data = {
154
- "status": 200,
155
- "packages": [
156
- {
157
- "name": "default",
158
- "description": "Default package",
159
- "agents": [
160
- {
161
- "name": "voice_agent",
162
- "description": "A simple voice agent"
163
- },
164
- {
165
- "name": "chat_agent",
166
- "description": "A simple chat agent"
167
- }
168
- ],
169
- "graphs": []
170
- }
171
- ]
172
- }
173
-
174
- # Читаем графы из property.json
175
- try:
176
- with open(PROPERTY_JSON, 'r') as f:
177
- property_data = json.load(f)
178
-
179
- graphs = property_data.get('graphs', [])
180
- # Обеспечиваем форматирование совместимое с designer API
181
- formatted_graphs = []
182
- for graph in graphs:
183
- # Копируем граф и добавляем дополнительные поля, которые могут ожидаться
184
- graph_copy = graph.copy()
185
- if 'id' not in graph_copy:
186
- graph_copy['id'] = graph_copy.get('name', '').lower().replace(' ', '_')
187
- if 'package' not in graph_copy:
188
- graph_copy['package'] = 'default'
189
- formatted_graphs.append(graph_copy)
190
-
191
- response_data["packages"][0]["graphs"] = formatted_graphs
192
-
193
- # Добавим альтернативные форматы в лог
194
- alternative_format = {
195
- "success": True,
196
- "packages": [
197
- {
198
- "name": "default",
199
- "description": "Default package",
200
- "graphs": formatted_graphs
201
- }
202
- ]
203
- }
204
-
205
- alternate_format2 = formatted_graphs
206
-
207
- logger.info(f"PROXY: Ответ для designer API, формат 1: {json.dumps(response_data)}")
208
- logger.info(f"PROXY: Ответ для designer API, формат 2: {json.dumps(alternative_format)}")
209
- logger.info(f"PROXY: Ответ для designer API, формат 3: {json.dumps(alternate_format2)}")
210
-
211
- # Тестируем разные варианты ответов
212
- # 1. Отправляем третий формат (простой массив графов)
213
- self.send_response(HTTPStatus.OK)
214
- self.send_header('Content-Type', 'application/json')
215
- self.end_headers()
216
- self.wfile.write(json.dumps(alternate_format2).encode('utf-8'))
217
-
218
- # В следующих вызовах попробуем другие форматы
219
-
220
- except Exception as e:
221
- logger.error(f"PROXY: Ошибка при чтении графов для дизайнера: {e}")
222
- self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, str(e))
223
-
224
- def _get_headers(self):
225
- """Получение заголовков для проксирования"""
226
- headers = {}
227
- for header in self.headers:
228
- headers[header] = self.headers[header]
229
- return headers
230
-
231
- def _send_response(self, response):
232
- """Отправка ответа клиенту"""
233
- self.send_response(response.status_code)
234
-
235
- # Копируем заголовки
236
- for header, value in response.headers.items():
237
- self.send_header(header, value)
238
- self.end_headers()
239
-
240
- # Отправляем тело ответа
241
- self.wfile.write(response.content)
242
-
243
- def log_message(self, format, *args):
244
- """Переопределение логирования"""
245
- logger.debug(f"PROXY: {self.address_string()} - {format % args}")
246
-
247
- class MockDesignerAPIHandler(http.server.BaseHTTPRequestHandler):
248
- """Обработчик для мок-API дизайнера"""
249
-
250
- def do_GET(self):
251
- """Обработка GET запросов"""
252
- logger.info(f"MOCK API: GET запрос: {self.path}")
253
-
254
- if self.path == "/api/designer/v1/packages" or self.path == "/api/dev/v1/packages":
255
- self._send_designer_packages()
256
- elif self.path.startswith("/api/designer/v1/") or self.path.startswith("/api/dev/v1/"):
257
- self._send_success_response({"success": True})
258
- else:
259
- self.send_error(404, "Endpoint not found")
260
-
261
- def do_POST(self):
262
- """Обработка POST запросов"""
263
- logger.info(f"MOCK API: POST запрос: {self.path}")
264
-
265
- if self.path == "/api/designer/v1/packages/reload" or self.path == "/api/dev/v1/packages/reload":
266
- self._send_designer_packages()
267
- elif self.path.startswith("/api/designer/v1/") or self.path.startswith("/api/dev/v1/"):
268
- self._send_success_response({"success": True})
269
- else:
270
- self.send_error(404, "Endpoint not found")
271
-
272
- def _send_designer_packages(self):
273
- """Отправляет информацию о пакетах и графах"""
274
- self._send_success_response(mock_responses['packages'])
275
-
276
- def _send_success_response(self, data: Dict[str, Any]):
277
- """Отправляет успешный ответ с данными"""
278
- self.send_response(200)
279
- self.send_header('Content-Type', 'application/json')
280
- self.end_headers()
281
- self.wfile.write(json.dumps(data).encode('utf-8'))
282
-
283
- def log_message(self, format, *args):
284
- """Настраиваем логирование для сервера"""
285
- logger.debug(f"MOCK API: {self.address_string()} - {format % args}")
286
-
287
- def run_proxy_server(port=9090):
288
- """Запуск прокси-сервера"""
289
- try:
290
- with socketserver.TCPServer(("", port), ProxyHTTPRequestHandler) as httpd:
291
- logger.info(f"Запуск прокси-сервера на порту {port}")
292
- httpd.serve_forever()
293
- except Exception as e:
294
- logger.error(f"Ошибка при запуске прокси-сервера: {e}")
295
 
296
  def ensure_directory_permissions(directory_path):
297
  """Обеспечиваем правильные разрешения для директории"""
@@ -320,40 +60,107 @@ def backup_file(filepath):
320
  except Exception as e:
321
  logger.error(f"Ошибка при создании резервной копии {filepath}: {e}")
322
 
323
- def ensure_property_json():
324
- """Проверяет и создает property.json при необходимости"""
 
 
 
 
325
  try:
326
- if not PROPERTY_JSON.exists():
327
- logger.warning(f"{PROPERTY_JSON} не найден, создаем файл...")
328
-
329
- property_data = {
330
- "_ten": {},
331
- "name": "TEN Agent Example",
332
- "version": "0.0.1",
333
- "extensions": ["openai_chatgpt"],
334
- "description": "A basic voice agent with OpenAI",
335
- "graphs": [
336
- {
337
- "name": "Voice Agent",
338
- "description": "Basic voice agent with OpenAI",
339
- "file": "voice_agent.json"
340
- },
341
- {
342
- "name": "Chat Agent",
343
- "description": "Simple chat agent",
344
- "file": "chat_agent.json"
345
- }
346
- ]
347
- }
348
-
349
- PROPERTY_JSON.parent.mkdir(parents=True, exist_ok=True)
350
-
351
- with open(PROPERTY_JSON, 'w') as f:
352
- json.dump(property_data, f, indent=2)
353
-
354
- logger.info(f"Файл {PROPERTY_JSON} создан успешно")
 
 
 
 
 
 
 
355
  except Exception as e:
356
- logger.error(f"Ошибка при создании {PROPERTY_JSON}: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
  def check_and_create_agent_files():
359
  """Проверяет наличие всех необходимых файлов агентов и создает их при необходимости"""
@@ -448,6 +255,41 @@ def check_files():
448
  subprocess.run(["stat", str(AGENTS_DIR)])
449
  subprocess.run(["stat", str(PROPERTY_JSON)])
450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  def test_api():
452
  """Делает запрос к API для получения списка графов"""
453
  logger.info("=== Тестирование API ===")
@@ -458,47 +300,46 @@ def test_api():
458
  data = response.read().decode('utf-8')
459
  logger.info(f"Ответ /graphs: {data}")
460
 
461
- # Проверяем структуру ответа
462
- try:
463
- json_data = json.loads(data)
464
- if isinstance(json_data, list) and len(json_data) > 0:
 
 
 
 
465
  logger.info(f"API вернул {len(json_data)} графов")
466
- # Если API вернул пустой список, исправляем это
467
- if len(json_data) == 0:
468
- logger.warning("API вернул пустой список графов, исправляем property.json")
469
- backup_file(PROPERTY_JSON)
470
- ensure_property_json()
471
- check_and_create_agent_files()
472
- ensure_directory_permissions(AGENTS_DIR)
473
- # Перезапускаем API сервер
474
- logger.info("Перезапускаем API сервер...")
475
- subprocess.run(["pkill", "-f", str(API_BINARY)])
476
- time.sleep(1)
477
- subprocess.Popen([str(API_BINARY)])
478
  else:
479
  logger.warning("API вернул пустой список графов")
480
- except json.JSONDecodeError:
481
- logger.error("Ответ API не является валидным JSON")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
482
  except urllib.error.URLError as e:
483
  logger.error(f"Ошибка запроса к API: {e}")
484
  except Exception as e:
485
  logger.error(f"Неизвестная ошибка при запросе к API: {e}")
486
 
487
- def run_mock_api_server(port=8090):
488
- """Запускает мок-сервер для API дизайнера"""
489
- try:
490
- with socketserver.TCPServer(("", port), MockDesignerAPIHandler) as httpd:
491
- logger.info(f"Запуск мок-API сервера на порту {port}")
492
- httpd.serve_forever()
493
- except Exception as e:
494
- logger.error(f"Ошибка при запуске мок-API сервера: {e}")
495
-
496
  def main():
497
  processes = []
498
  try:
499
- # Загружаем мок-ответы
500
- load_mock_responses()
501
-
502
  # Проверяем существование файлов
503
  if not API_BINARY.exists():
504
  logger.error(f"API binary не найден: {API_BINARY}")
@@ -513,7 +354,10 @@ def main():
513
  ensure_directory_permissions(BACKUP_DIR)
514
 
515
  # Проверяем и создаем property.json
516
- ensure_property_json()
 
 
 
517
 
518
  # Проверяем и создаем файлы агентов
519
  check_and_create_agent_files()
@@ -521,12 +365,6 @@ def main():
521
  # Проверка файлов перед запуском
522
  check_files()
523
 
524
- # Запускаем мок-сервер для API дизайнера
525
- mock_api_port = 8090
526
- mock_api_thread = threading.Thread(target=run_mock_api_server, args=(mock_api_port,))
527
- mock_api_thread.daemon = True
528
- mock_api_thread.start()
529
-
530
  # Запускаем API сервер
531
  logger.info("Запуск TEN-Agent API сервера на порту 8080...")
532
  api_process = subprocess.Popen([str(API_BINARY)])
@@ -537,23 +375,28 @@ def main():
537
  test_thread.daemon = True
538
  test_thread.start()
539
 
540
- # Запускаем прокси-сервер для перехвата и модификации запросов
541
  proxy_port = 9090
542
- proxy_thread = Thread(target=run_proxy_server, args=(proxy_port,))
543
- proxy_thread.daemon = True
544
- proxy_thread.start()
 
 
 
 
 
545
 
546
  # Запускаем Playground UI в режиме dev на порту 7860 (порт Hugging Face)
547
  logger.info("Запуск Playground UI в режиме разработки на порту 7860...")
548
  os.environ["PORT"] = "7860"
549
- os.environ["AGENT_SERVER_URL"] = f"http://localhost:{proxy_port}" # Указываем прокси вместо прямого API
550
  os.environ["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true" # Включаем расширенный режим редактирования
551
  os.environ["NEXT_PUBLIC_DISABLE_CAMERA"] = "true" # Отключаем запрос на использование камеры
552
 
553
- # Настраиваем переменные для дизайнера
554
- os.environ["NEXT_PUBLIC_DEV_MODE"] = "true" # Включаем режим разработчика
555
  os.environ["NEXT_PUBLIC_API_BASE_URL"] = "/api/agents"
556
- os.environ["NEXT_PUBLIC_DESIGNER_API_URL"] = f"http://localhost:{mock_api_port}"
557
 
558
  # Запускаем Playground UI
559
  playground_process = subprocess.Popen(
 
14
  import tempfile
15
  import http.server
16
  import socketserver
17
+ from typing import Dict, Any, Optional, Union
 
 
 
18
 
19
  # Настройка логирования
20
  logging.basicConfig(level=logging.INFO,
 
32
  API_BINARY = Path("/app/server/bin/api")
33
  PLAYGROUND_DIR = Path("/app/playground")
34
  BACKUP_DIR = Path("/app/backup")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  def ensure_directory_permissions(directory_path):
37
  """Обеспечиваем правильные разрешения для директории"""
 
60
  except Exception as e:
61
  logger.error(f"Ошибка при создании резервной копии {filepath}: {e}")
62
 
63
+ def update_property_json():
64
+ """Проверяет существующий property.json и добавляет поле predefined_graphs, если его нет"""
65
+ if not PROPERTY_JSON.exists():
66
+ logger.info(f"{PROPERTY_JSON} не существует, нет необходимости в обновлении")
67
+ return False
68
+
69
  try:
70
+ with open(PROPERTY_JSON, 'r') as f:
71
+ data = json.load(f)
72
+
73
+ if "predefined_graphs" in data:
74
+ logger.info(f"{PROPERTY_JSON} уже содержит поле predefined_graphs")
75
+ return False
76
+
77
+ # Создаем резервную копию перед изменением
78
+ backup_file(PROPERTY_JSON)
79
+
80
+ # Добавляем поле predefined_graphs на основе поля graphs или создаем его
81
+ if "graphs" in data:
82
+ data["predefined_graphs"] = data["graphs"]
83
+ logger.info(f"Добавлено поле predefined_graphs на основе поля graphs")
84
+ else:
85
+ # Создаем стандартные графы
86
+ data["predefined_graphs"] = [
87
+ {
88
+ "name": "Voice Agent",
89
+ "description": "Basic voice agent with OpenAI",
90
+ "file": "voice_agent.json"
91
+ },
92
+ {
93
+ "name": "Chat Agent",
94
+ "description": "Simple chat agent",
95
+ "file": "chat_agent.json"
96
+ }
97
+ ]
98
+ logger.info(f"Добавлено стандартное поле predefined_graphs")
99
+
100
+ # Сохраняем обновленный файл
101
+ with open(PROPERTY_JSON, 'w') as f:
102
+ json.dump(data, f, indent=2)
103
+
104
+ logger.info(f"{PROPERTY_JSON} успешно обновлен с добавлением поля predefined_graphs")
105
+ return True
106
  except Exception as e:
107
+ logger.error(f"Ошибка при обновлении {PROPERTY_JSON}: {e}")
108
+ return False
109
+
110
+ def check_and_create_property_json():
111
+ """Проверяет наличие property.json и создает его при необходимости"""
112
+ if not PROPERTY_JSON.exists():
113
+ logger.warning(f"{PROPERTY_JSON} не найден, создаем файл...")
114
+
115
+ property_data = {
116
+ "_ten": {}, # Важное поле для TEN формата
117
+ "name": "TEN Agent Example",
118
+ "version": "0.0.1",
119
+ "extensions": ["openai_chatgpt"],
120
+ "description": "A basic voice agent with OpenAI",
121
+ "predefined_graphs": [ # Важное поле, которое ожидает API
122
+ {
123
+ "name": "Voice Agent",
124
+ "description": "Basic voice agent with OpenAI",
125
+ "file": "voice_agent.json"
126
+ },
127
+ {
128
+ "name": "Chat Agent",
129
+ "description": "Simple chat agent",
130
+ "file": "chat_agent.json"
131
+ }
132
+ ],
133
+ "graphs": [
134
+ {
135
+ "name": "Voice Agent",
136
+ "description": "Basic voice agent with OpenAI",
137
+ "file": "voice_agent.json"
138
+ },
139
+ {
140
+ "name": "Chat Agent",
141
+ "description": "Simple chat agent",
142
+ "file": "chat_agent.json"
143
+ }
144
+ ]
145
+ }
146
+
147
+ # Проверяем и создаем директории
148
+ PROPERTY_JSON.parent.mkdir(parents=True, exist_ok=True)
149
+
150
+ # Создаем временный файл и затем перемещаем его
151
+ with tempfile.NamedTemporaryFile(mode='w', delete=False) as temp_file:
152
+ json.dump(property_data, temp_file, indent=2)
153
+ temp_path = temp_file.name
154
+
155
+ # Копируем временный файл в целевой
156
+ try:
157
+ shutil.copy2(temp_path, PROPERTY_JSON)
158
+ os.chmod(PROPERTY_JSON, 0o666) # Устанавливаем права доступа rw-rw-rw-
159
+ logger.info(f"Файл {PROPERTY_JSON} создан успешно")
160
+ except Exception as e:
161
+ logger.error(f"Ошибка при создании {PROPERTY_JSON}: {e}")
162
+ finally:
163
+ os.unlink(temp_path) # Удаляем временный файл
164
 
165
  def check_and_create_agent_files():
166
  """Проверяет наличие всех необходимых файлов агентов и создает их при необходимости"""
 
255
  subprocess.run(["stat", str(AGENTS_DIR)])
256
  subprocess.run(["stat", str(PROPERTY_JSON)])
257
 
258
+ def analyze_api_response(response_data):
259
+ """Анализирует ответ API и выводит диагностическую информацию"""
260
+ try:
261
+ # Пробуем распарсить JSON
262
+ if not response_data or response_data.strip() == "":
263
+ logger.error("API вернул пустой ответ")
264
+ return None
265
+
266
+ json_data = json.loads(response_data)
267
+
268
+ # Проверяем тип данных
269
+ if isinstance(json_data, list):
270
+ logger.info(f"API вернул список с {len(json_data)} элементами")
271
+ if len(json_data) > 0:
272
+ logger.info(f"Структура первого элемента: {json.dumps(json_data[0], indent=2)}")
273
+ return json_data
274
+ elif isinstance(json_data, dict):
275
+ logger.info(f"API вернул словарь с ключами: {list(json_data.keys())}")
276
+ # Проверяем наличие кода ошибки
277
+ if "code" in json_data:
278
+ logger.error(f"API вернул ошибку с кодом: {json_data['code']}")
279
+ if "msg" in json_data:
280
+ logger.error(f"Сообщение ошибки: {json_data['msg']}")
281
+ return json_data
282
+ else:
283
+ logger.warning(f"API вернул неожиданный тип данных: {type(json_data)}")
284
+ return json_data
285
+ except json.JSONDecodeError as e:
286
+ logger.error(f"Ошибка декодирования JSON: {e}")
287
+ logger.error(f"Сырые данные: {response_data}")
288
+ return None
289
+ except Exception as e:
290
+ logger.error(f"Ошибка при анализе ответа API: {e}")
291
+ return None
292
+
293
  def test_api():
294
  """Делает запрос к API для получения списка графов"""
295
  logger.info("=== Тестирование API ===")
 
300
  data = response.read().decode('utf-8')
301
  logger.info(f"Ответ /graphs: {data}")
302
 
303
+ # Анализируем ответ API
304
+ json_data = analyze_api_response(data)
305
+
306
+ # Проверяем структуру ответа и реагируем на проблемы
307
+ if json_data is None:
308
+ logger.error("Не удалось проанализировать ответ API")
309
+ elif isinstance(json_data, list):
310
+ if len(json_data) > 0:
311
  logger.info(f"API вернул {len(json_data)} графов")
 
 
 
 
 
 
 
 
 
 
 
 
312
  else:
313
  logger.warning("API вернул пустой список графов")
314
+ # Пробуем исправить property.json, если он уже существует
315
+ if PROPERTY_JSON.exists():
316
+ logger.info("Пробуем обновить существующий property.json...")
317
+ if update_property_json():
318
+ # Перезапускаем API сервер после обновления
319
+ logger.info("Перезапускаем API сервер после обновления property.json...")
320
+ subprocess.run(["pkill", "-f", str(API_BINARY)])
321
+ time.sleep(1)
322
+ subprocess.Popen([str(API_BINARY)])
323
+ elif isinstance(json_data, dict) and "code" in json_data:
324
+ logger.warning("API вернул ошибку, исправляем property.json")
325
+ backup_file(PROPERTY_JSON)
326
+ check_and_create_property_json()
327
+ update_property_json() # Добавляем вызов функции обновления
328
+ check_and_create_agent_files()
329
+ ensure_directory_permissions(AGENTS_DIR)
330
+ # Перезапускаем API сервер
331
+ logger.info("Перезапускаем API сервер...")
332
+ subprocess.run(["pkill", "-f", str(API_BINARY)])
333
+ time.sleep(1)
334
+ subprocess.Popen([str(API_BINARY)])
335
  except urllib.error.URLError as e:
336
  logger.error(f"Ошибка запроса к API: {e}")
337
  except Exception as e:
338
  logger.error(f"Неизвестная ошибка при запросе к API: {e}")
339
 
 
 
 
 
 
 
 
 
 
340
  def main():
341
  processes = []
342
  try:
 
 
 
343
  # Проверяем существование файлов
344
  if not API_BINARY.exists():
345
  logger.error(f"API binary не найден: {API_BINARY}")
 
354
  ensure_directory_permissions(BACKUP_DIR)
355
 
356
  # Проверяем и создаем property.json
357
+ check_and_create_property_json()
358
+
359
+ # Проверяем и обновляем существующий property.json, если он не содержит predefined_graphs
360
+ update_property_json()
361
 
362
  # Проверяем и создаем файлы агентов
363
  check_and_create_agent_files()
 
365
  # Проверка файлов перед запуском
366
  check_files()
367
 
 
 
 
 
 
 
368
  # Запускаем API сервер
369
  logger.info("Запуск TEN-Agent API сервера на порту 8080...")
370
  api_process = subprocess.Popen([str(API_BINARY)])
 
375
  test_thread.daemon = True
376
  test_thread.start()
377
 
378
+ # Запускаем прокси-сервер
379
  proxy_port = 9090
380
+ proxy_script = Path("proxy_server.py")
381
+ if proxy_script.exists():
382
+ logger.info(f"Запуск прокси-сервера на порту {proxy_port}")
383
+ proxy_process = subprocess.Popen([sys.executable, str(proxy_script)], env=dict(os.environ, PROXY_PORT=str(proxy_port)))
384
+ processes.append(proxy_process)
385
+ else:
386
+ logger.warning(f"Файл прокси-сервера не найден: {proxy_script}, будет использовано прямое подключение к API")
387
+ proxy_port = 8080 # Fallback на порт API сервера
388
 
389
  # Запускаем Playground UI в режиме dev на порту 7860 (порт Hugging Face)
390
  logger.info("Запуск Playground UI в режиме разработки на порту 7860...")
391
  os.environ["PORT"] = "7860"
392
+ os.environ["AGENT_SERVER_URL"] = f"http://localhost:{proxy_port}" # Используем прокси вместо прямого подключения
393
  os.environ["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true" # Включаем расширенный режим редактирования
394
  os.environ["NEXT_PUBLIC_DISABLE_CAMERA"] = "true" # Отключаем запрос на использование камеры
395
 
396
+ # Важные переменные для отключения запросов к дизайнеру
397
+ os.environ["NEXT_PUBLIC_DEV_MODE"] = "false"
398
  os.environ["NEXT_PUBLIC_API_BASE_URL"] = "/api/agents"
399
+ os.environ["NEXT_PUBLIC_DESIGNER_API_URL"] = f"http://localhost:{proxy_port}" # Используем прокси для запросов дизайнера
400
 
401
  # Запускаем Playground UI
402
  playground_process = subprocess.Popen(
proxy_server.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import http.server
3
+ import socketserver
4
+ import urllib.request
5
+ import urllib.error
6
+ import json
7
+ import logging
8
+ import sys
9
+ import os
10
+ from pathlib import Path
11
+
12
+ # Настройка логирования
13
+ logging.basicConfig(level=logging.INFO,
14
+ format='%(asctime)s [%(levelname)s] %(message)s',
15
+ datefmt='%Y-%m-%d %H:%M:%S')
16
+
17
+ logger = logging.getLogger('ten-proxy')
18
+
19
+ # Настройки прокси
20
+ PROXY_PORT = 9090
21
+ API_SERVER = "http://localhost:8080"
22
+
23
+ # Мок-данные для /graphs
24
+ MOCK_GRAPHS_RESPONSE = [
25
+ {
26
+ "name": "Voice Agent",
27
+ "description": "Voice Agent with OpenAI",
28
+ "file": "voice_agent.json",
29
+ "id": "voice_agent",
30
+ "package": "default"
31
+ },
32
+ {
33
+ "name": "Chat Agent",
34
+ "description": "Chat Agent",
35
+ "file": "chat_agent.json",
36
+ "id": "chat_agent",
37
+ "package": "default"
38
+ }
39
+ ]
40
+
41
+ # Мок-данные для designer API
42
+ MOCK_DESIGNER_RESPONSE = {
43
+ "success": True,
44
+ "packages": [
45
+ {
46
+ "name": "default",
47
+ "description": "Default package",
48
+ "graphs": [
49
+ {
50
+ "name": "Voice Agent",
51
+ "description": "Voice Agent with OpenAI",
52
+ "file": "voice_agent.json",
53
+ "id": "voice_agent",
54
+ "package": "default"
55
+ },
56
+ {
57
+ "name": "Chat Agent",
58
+ "description": "Chat Agent",
59
+ "file": "chat_agent.json",
60
+ "id": "chat_agent",
61
+ "package": "default"
62
+ }
63
+ ]
64
+ }
65
+ ]
66
+ }
67
+
68
+ class ProxyHandler(http.server.BaseHTTPRequestHandler):
69
+ """Обработчик запросов для прокси-сервера"""
70
+
71
+ def do_GET(self):
72
+ """Обработка GET запросов"""
73
+ logger.info(f"PROXY: GET запрос: {self.path}")
74
+
75
+ # Перехватываем запросы к /graphs
76
+ if self.path == "/graphs":
77
+ self._handle_graphs_request()
78
+ # Перехватываем запросы к designer API
79
+ elif self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
80
+ self._handle_designer_request()
81
+ # Все остальные запросы перенаправляем на API сервер
82
+ else:
83
+ self._proxy_request("GET")
84
+
85
+ def do_POST(self):
86
+ """Обработка POST запросов"""
87
+ logger.info(f"PROXY: POST запрос: {self.path}")
88
+
89
+ # Перехватываем запросы к designer API
90
+ if self.path.startswith("/api/designer/") or self.path.startswith("/api/dev/"):
91
+ self._handle_designer_request()
92
+ # Все остальные запросы перенаправляем на API сервер
93
+ else:
94
+ self._proxy_request("POST")
95
+
96
+ def do_OPTIONS(self):
97
+ """Обработка OPTIONS запросов для CORS"""
98
+ self.send_response(200)
99
+ self.send_header('Access-Control-Allow-Origin', '*')
100
+ self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
101
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
102
+ self.end_headers()
103
+
104
+ def _handle_graphs_request(self):
105
+ """Обрабатывает запросы к /graphs с возможностью модификации ответа"""
106
+ try:
107
+ # Сначала пробуем получить данные от реального API
108
+ url = f"{API_SERVER}{self.path}"
109
+ try:
110
+ with urllib.request.urlopen(url) as response:
111
+ data = response.read().decode('utf-8')
112
+
113
+ # Проверяем, является ли ответ валидным JSON
114
+ try:
115
+ json_data = json.loads(data)
116
+
117
+ # Если API вернул пустой список или ошибку, заменяем на мок-данные
118
+ if (isinstance(json_data, list) and len(json_data) == 0) or (isinstance(json_data, dict) and "code" in json_data):
119
+ logger.warning(f"API вернул пустой список или ошибку, используем мок-данные")
120
+ self._send_success_response(MOCK_GRAPHS_RESPONSE)
121
+ return
122
+
123
+ # Если всё нормально, возвращаем оригинальный ответ
124
+ logger.info(f"API вернул непустой список графов, используем его")
125
+ self._send_response_with_data(200, data)
126
+ return
127
+ except json.JSONDecodeError:
128
+ logger.error(f"Ответ API не является валидным JSON: {data}")
129
+ self._send_success_response(MOCK_GRAPHS_RESPONSE)
130
+ return
131
+ except urllib.error.URLError as e:
132
+ logger.error(f"Не удалось подключиться к API: {e}")
133
+ # В случае ошибки используем мок-данные
134
+ self._send_success_response(MOCK_GRAPHS_RESPONSE)
135
+ return
136
+ except Exception as e:
137
+ logger.error(f"Ошибка при обработке запроса /graphs: {e}")
138
+ self._send_success_response(MOCK_GRAPHS_RESPONSE)
139
+
140
+ def _handle_designer_request(self):
141
+ """Обрабатывает запросы к designer API, всегда возвращая мок-данные"""
142
+ logger.info(f"Перехват запроса к Designer API: {self.path}")
143
+ self._send_success_response(MOCK_DESIGNER_RESPONSE)
144
+
145
+ def _proxy_request(self, method):
146
+ """Перенаправляет запрос на API сервер"""
147
+ try:
148
+ url = f"{API_SERVER}{self.path}"
149
+
150
+ # Получение данных запроса для POST
151
+ data = None
152
+ if method == "POST":
153
+ content_length = int(self.headers.get('Content-Length', 0))
154
+ data = self.rfile.read(content_length)
155
+
156
+ # Создание запроса
157
+ req = urllib.request.Request(url, data=data, method=method)
158
+
159
+ # Копирование заголовков
160
+ for header, value in self.headers.items():
161
+ if header.lower() not in ["host", "content-length"]:
162
+ req.add_header(header, value)
163
+
164
+ # Выполнение запроса
165
+ try:
166
+ with urllib.request.urlopen(req) as response:
167
+ # Чтение данных ответа
168
+ response_data = response.read()
169
+
170
+ # Отправка ответа клиенту
171
+ self.send_response(response.status)
172
+
173
+ # Копирование заголовков ответа
174
+ for header, value in response.getheaders():
175
+ if header.lower() != "transfer-encoding":
176
+ self.send_header(header, value)
177
+
178
+ # Добавление CORS заголовков
179
+ self.send_header('Access-Control-Allow-Origin', '*')
180
+ self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
181
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
182
+
183
+ self.end_headers()
184
+ self.wfile.write(response_data)
185
+ except urllib.error.URLError as e:
186
+ logger.error(f"Ошибка при проксировании запроса: {e}")
187
+ self.send_error(502, f"Bad Gateway: {e}")
188
+ except Exception as e:
189
+ logger.error(f"Ошибка при обработке запроса: {e}")
190
+ self.send_error(500, f"Internal Server Error: {e}")
191
+
192
+ def _send_success_response(self, data):
193
+ """Отправляет успешный ответ с данными"""
194
+ response_data = json.dumps(data)
195
+ self._send_response_with_data(200, response_data)
196
+
197
+ def _send_response_with_data(self, status_code, data):
198
+ """Отправляет ответ с указанным статусом и данными"""
199
+ self.send_response(status_code)
200
+ self.send_header('Content-Type', 'application/json')
201
+ self.send_header('Access-Control-Allow-Origin', '*')
202
+ self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
203
+ self.send_header('Access-Control-Allow-Headers', 'Content-Type')
204
+ self.end_headers()
205
+
206
+ if isinstance(data, str):
207
+ self.wfile.write(data.encode('utf-8'))
208
+ else:
209
+ self.wfile.write(data)
210
+
211
+ def log_message(self, format, *args):
212
+ """Настраиваем логирование для сервера"""
213
+ logger.debug(f"PROXY: {self.address_string()} - {format % args}")
214
+
215
+ def run_proxy_server(port=PROXY_PORT):
216
+ """Запускает прокси-сервер"""
217
+ try:
218
+ with socketserver.TCPServer(("", port), ProxyHandler) as httpd:
219
+ logger.info(f"Запуск прокси-сервера на порту {port}")
220
+ httpd.serve_forever()
221
+ except KeyboardInterrupt:
222
+ logger.info("Прокси-сервер остановлен")
223
+ except Exception as e:
224
+ logger.error(f"Ошибка при запуске ��рокси-сервера: {e}")
225
+
226
+ if __name__ == "__main__":
227
+ # Запуск прокси-сервера
228
+ proxy_port = int(os.environ.get("PROXY_PORT", PROXY_PORT))
229
+ run_proxy_server(proxy_port)