3v324v23 commited on
Commit
9006d1c
·
1 Parent(s): 8dd6527

Полное изменение подхода: создание отдельного мок-сервера для API дизайнера

Browse files
Files changed (2) hide show
  1. Dockerfile +6 -28
  2. app.py +143 -47
Dockerfile CHANGED
@@ -62,6 +62,7 @@ RUN mkdir -p /app/server/bin && \
62
  mkdir -p /tmp/ten_agent && \
63
  mkdir -p /app/.pnpm-store && \
64
  mkdir -p /app/backup && \
 
65
  chown -R tenuser:tenuser /app && \
66
  chown -R tenuser:tenuser /tmp/ten_agent
67
 
@@ -69,33 +70,25 @@ RUN mkdir -p /app/server/bin && \
69
  COPY .env /app/.env
70
  RUN chown tenuser:tenuser /app/.env
71
 
72
- # Патчим файлы для поддержки чтения графов
73
- # 1. Патчим метод чтения графов в API сервере для прямого чтения из файла property.json
74
- RUN echo 'package internal\n\nimport (\n\t"encoding/json"\n\t"fmt"\n\t"io/ioutil"\n\t"os"\n\t"path/filepath"\n)\n\n// GraphInfo представляет информацию о графе\ntype GraphInfo struct {\n\tName string `json:"name"`\n\tDescription string `json:"description"`\n\tFile string `json:"file"`\n}\n\n// PropertyJson структура файла property.json\ntype PropertyJson struct {\n\tTen map[string]interface{} `json:"_ten"`\n\tName string `json:"name"`\n\tVersion string `json:"version"`\n\tExtensions []string `json:"extensions"`\n\tDescription string `json:"description"`\n\tGraphs []GraphInfo `json:"graphs"`\n}\n\n// ReadGraphsFromPropertyJson читает список графов из property.json\nfunc ReadGraphsFromPropertyJson() ([]GraphInfo, error) {\n\tpropertyPath := filepath.Join("/app/agents", "property.json")\n\n\tdata, err := ioutil.ReadFile(propertyPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf("ошибка чтения property.json: %v", err)\n\t}\n\n\tvar property PropertyJson\n\terr = json.Unmarshal(data, &property)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf("ошибка разбора property.json: %v", err)\n\t}\n\n\treturn property.Graphs, nil\n}' > /app/server/internal/property_parser.go
75
 
76
- # 2. Патчим обработчик для чтения графов из property.json
77
- RUN sed -i 's/func (s \*HttpServer) handleGraphs/func oldHandleGraphs/g' /app/server/internal/http_server.go && \
78
- echo 'func (s *HttpServer) handleGraphs(c *gin.Context) {\n\tgraphs, err := ReadGraphsFromPropertyJson()\n\tif err != nil {\n\t\ts.logger.Error(fmt.Sprintf("Failed to read graphs: %v", err))\n\t\tc.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, graphs)\n}' >> /app/server/internal/http_server.go
79
-
80
- # Патчим файл конфигурации Next.js для перенаправления запросов
81
  RUN echo 'module.exports = { \
82
  async rewrites() { \
83
  return [ \
84
  { \
85
  source: "/api/designer/:path*", \
86
- destination: "/api/agents/:path*", \
87
  }, \
88
  { \
89
  source: "/api/dev/:path*", \
90
- destination: "/api/agents/:path*", \
91
  }, \
92
  ]; \
93
  }, \
94
  };' > /app/playground/next.config.js
95
 
96
- # Патчим API сервер для обработки запросов дизайнера
97
- RUN echo 'package main\n\nimport (\n\t"net/http"\n\t\n\t"github.com/gin-gonic/gin"\n)\n\nfunc init() {\n\t// Добавляем обработчик для дизайнера\n\tregisterDesignerHandlers()\n}\n\nfunc registerDesignerHandlers() {\n\t// Регистрация дополнительных обработчиков в gin\n\thttp.DefaultServeMux.HandleFunc("/api/designer/v1/packages/reload", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set("Content-Type", "application/json")\n\t\tw.WriteHeader(http.StatusOK)\n\t\tw.Write([]byte(`{"success":true}`))\n\t})\n\n\thttp.DefaultServeMux.HandleFunc("/api/dev/v1/packages/reload", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set("Content-Type", "application/json")\n\t\tw.WriteHeader(http.StatusOK)\n\t\tw.Write([]byte(`{"success":true}`))\n\t})\n}' > /app/server/designer_handlers.go
98
-
99
  # Компилируем сервер
100
  RUN cd /app/server && \
101
  go mod tidy && \
@@ -109,19 +102,6 @@ ENV PATH="$PNPM_HOME:$PATH"
109
  RUN cd /app/playground && \
110
  pnpm install
111
 
112
- # Подготавливаем пример агента для работы Select Graph
113
- RUN cd /app && \
114
- mkdir -p agents/examples && \
115
- mkdir -p agents/examples/default && \
116
- mkdir -p agents/examples/demo && \
117
- mkdir -p agents/examples/experimental
118
-
119
- # Копируем примеры из репозитория
120
- RUN cd /app && \
121
- cp -r agents/examples/default agents/ && \
122
- cp -r agents/examples/demo agents/ && \
123
- cp -r agents/examples/experimental agents/
124
-
125
  # Создаем базовый manifest.json
126
  RUN echo '{\n\
127
  "name": "default",\n\
@@ -177,8 +157,6 @@ RUN echo '{\n\
177
 
178
  # Устанавливаем правильные права доступа для всех файлов
179
  RUN chmod -R 777 /app && \
180
- chown -R tenuser:tenuser /app/agents && \
181
- chown -R tenuser:tenuser /app/server && \
182
  find /app -type d -exec chmod 777 {} \; && \
183
  find /app -type f -exec chmod 666 {} \; && \
184
  chmod +x /app/server/bin/api
 
62
  mkdir -p /tmp/ten_agent && \
63
  mkdir -p /app/.pnpm-store && \
64
  mkdir -p /app/backup && \
65
+ mkdir -p /app/mock-api && \
66
  chown -R tenuser:tenuser /app && \
67
  chown -R tenuser:tenuser /tmp/ten_agent
68
 
 
70
  COPY .env /app/.env
71
  RUN chown tenuser:tenuser /app/.env
72
 
73
+ # Создаем файл для мок-API дизайнера
74
+ RUN echo '{\n "success": true,\n "packages": [\n {\n "name": "default",\n "description": "Default package",\n "graphs": [\n {\n "name": "Voice Agent",\n "description": "Voice Agent with OpenAI",\n "file": "voice_agent.json",\n "id": "voice_agent",\n "package": "default"\n },\n {\n "name": "Chat Agent",\n "description": "Chat Agent",\n "file": "chat_agent.json",\n "id": "chat_agent",\n "package": "default"\n }\n ]\n }\n ]\n}' > /app/mock-api/designer-packages.json
 
75
 
76
+ # Настраиваем прокси-правила в Next.js для перенаправления запросов к дизайнеру на обычный API
 
 
 
 
77
  RUN echo 'module.exports = { \
78
  async rewrites() { \
79
  return [ \
80
  { \
81
  source: "/api/designer/:path*", \
82
+ destination: "http://localhost:8080/api/agents/:path*", \
83
  }, \
84
  { \
85
  source: "/api/dev/:path*", \
86
+ destination: "http://localhost:8080/api/agents/:path*", \
87
  }, \
88
  ]; \
89
  }, \
90
  };' > /app/playground/next.config.js
91
 
 
 
 
92
  # Компилируем сервер
93
  RUN cd /app/server && \
94
  go mod tidy && \
 
102
  RUN cd /app/playground && \
103
  pnpm install
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  # Создаем базовый manifest.json
106
  RUN echo '{\n\
107
  "name": "default",\n\
 
157
 
158
  # Устанавливаем правильные права доступа для всех файлов
159
  RUN chmod -R 777 /app && \
 
 
160
  find /app -type d -exec chmod 777 {} \; && \
161
  find /app -type f -exec chmod 666 {} \; && \
162
  chmod +x /app/server/bin/api
app.py CHANGED
@@ -17,6 +17,7 @@ import socketserver
17
  import requests
18
  from threading import Thread
19
  from http import HTTPStatus
 
20
 
21
  # Настройка логирования
22
  logging.basicConfig(level=logging.INFO,
@@ -34,6 +35,51 @@ CHAT_AGENT_JSON = AGENTS_DIR / "chat_agent.json"
34
  API_BINARY = Path("/app/server/bin/api")
35
  PLAYGROUND_DIR = Path("/app/playground")
36
  BACKUP_DIR = Path("/app/backup")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  class ProxyHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
39
  """HTTP-прокси для отладки и исправления запросов между UI и API"""
@@ -198,6 +244,46 @@ class ProxyHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
198
  """Переопределение логирования"""
199
  logger.debug(f"PROXY: {self.address_string()} - {format % args}")
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  def run_proxy_server(port=9090):
202
  """Запуск прокси-сервера"""
203
  try:
@@ -234,48 +320,40 @@ def backup_file(filepath):
234
  except Exception as e:
235
  logger.error(f"Ошибка при создании резервной копии {filepath}: {e}")
236
 
237
- def check_and_create_property_json():
238
- """Проверяет наличие property.json и создает его при необходимости"""
239
- if not PROPERTY_JSON.exists():
240
- logger.warning(f"{PROPERTY_JSON} не найден, создаем файл...")
241
-
242
- property_data = {
243
- "_ten": {}, # Важное поле для TEN формата
244
- "name": "TEN Agent Example",
245
- "version": "0.0.1",
246
- "extensions": ["openai_chatgpt"],
247
- "description": "A basic voice agent with OpenAI",
248
- "graphs": [
249
- {
250
- "name": "Voice Agent",
251
- "description": "Basic voice agent with OpenAI",
252
- "file": "voice_agent.json"
253
- },
254
- {
255
- "name": "Chat Agent",
256
- "description": "Simple chat agent",
257
- "file": "chat_agent.json"
258
- }
259
- ]
260
- }
261
-
262
- # Проверяем и создаем директории
263
- PROPERTY_JSON.parent.mkdir(parents=True, exist_ok=True)
264
-
265
- # Создаем временный файл и затем перемещаем его
266
- with tempfile.NamedTemporaryFile(mode='w', delete=False) as temp_file:
267
- json.dump(property_data, temp_file, indent=2)
268
- temp_path = temp_file.name
269
-
270
- # Копируем временный файл в целевой
271
- try:
272
- shutil.copy2(temp_path, PROPERTY_JSON)
273
- os.chmod(PROPERTY_JSON, 0o666) # Устанавливаем права доступа rw-rw-rw-
274
  logger.info(f"Файл {PROPERTY_JSON} создан успешно")
275
- except Exception as e:
276
- logger.error(f"Ошибка при создании {PROPERTY_JSON}: {e}")
277
- finally:
278
- os.unlink(temp_path) # Удаляем временный файл
279
 
280
  def check_and_create_agent_files():
281
  """Проверяет наличие всех необходимых файлов агентов и создает их при необходимости"""
@@ -389,7 +467,7 @@ def test_api():
389
  if len(json_data) == 0:
390
  logger.warning("API вернул пустой список графов, исправляем property.json")
391
  backup_file(PROPERTY_JSON)
392
- check_and_create_property_json()
393
  check_and_create_agent_files()
394
  ensure_directory_permissions(AGENTS_DIR)
395
  # Перезапускаем API сервер
@@ -406,9 +484,21 @@ def test_api():
406
  except Exception as e:
407
  logger.error(f"Неизвестная ошибка при запросе к API: {e}")
408
 
 
 
 
 
 
 
 
 
 
409
  def main():
410
  processes = []
411
  try:
 
 
 
412
  # Проверяем существование файлов
413
  if not API_BINARY.exists():
414
  logger.error(f"API binary не найден: {API_BINARY}")
@@ -423,7 +513,7 @@ def main():
423
  ensure_directory_permissions(BACKUP_DIR)
424
 
425
  # Проверяем и создаем property.json
426
- check_and_create_property_json()
427
 
428
  # Проверяем и создаем файлы агентов
429
  check_and_create_agent_files()
@@ -431,6 +521,12 @@ def main():
431
  # Проверка файлов перед запуском
432
  check_files()
433
 
 
 
 
 
 
 
434
  # Запускаем API сервер
435
  logger.info("Запуск TEN-Agent API сервера на порту 8080...")
436
  api_process = subprocess.Popen([str(API_BINARY)])
@@ -454,10 +550,10 @@ def main():
454
  os.environ["NEXT_PUBLIC_EDIT_GRAPH_MODE"] = "true" # Включаем расширенный режим редактирования
455
  os.environ["NEXT_PUBLIC_DISABLE_CAMERA"] = "true" # Отключаем запрос на использование камеры
456
 
457
- # Важные переменные для отключения запросов к дизайнеру
458
- os.environ["NEXT_PUBLIC_DEV_MODE"] = "false"
459
- os.environ["NEXT_PUBLIC_API_BASE_URL"] = f"/api/agents"
460
- os.environ["NEXT_PUBLIC_DESIGNER_API_URL"] = f"http://localhost:{proxy_port}"
461
 
462
  # Запускаем Playground UI
463
  playground_process = subprocess.Popen(
 
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
  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"""
 
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:
 
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
  """Проверяет наличие всех необходимых файлов агентов и создает их при необходимости"""
 
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 сервер
 
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
  ensure_directory_permissions(BACKUP_DIR)
514
 
515
  # Проверяем и создаем property.json
516
+ ensure_property_json()
517
 
518
  # Проверяем и создаем файлы агентов
519
  check_and_create_agent_files()
 
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)])
 
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(