Fix permissions issues on HuggingFace Space: Add fallback UI options
Browse files- Dockerfile +4 -8
- app.py +298 -35
- simple_next_app.js +34 -0
- start.sh +8 -2
Dockerfile
CHANGED
|
@@ -18,8 +18,8 @@ COPY proxy.py /app/
|
|
| 18 |
COPY .space/hf-space.sh /app/
|
| 19 |
|
| 20 |
# Создаем директории
|
| 21 |
-
RUN mkdir -p /tmp/ten_user/agents /tmp/ten_user/logs /app/backup
|
| 22 |
-
RUN chmod -R 777 /tmp
|
| 23 |
|
| 24 |
# Создаем и активируем виртуальную среду Python
|
| 25 |
RUN python3 -m venv /app/venv
|
|
@@ -38,12 +38,8 @@ RUN mkdir -p /app/playground
|
|
| 38 |
RUN cp -r /tmp/ten-agent/playground/* /app/playground/
|
| 39 |
RUN rm -rf /tmp/ten-agent
|
| 40 |
|
| 41 |
-
# Устанавливаем pnpm
|
| 42 |
-
RUN npm install -g pnpm@8
|
| 43 |
-
|
| 44 |
-
# Устанавливаем dependencies для playground
|
| 45 |
-
WORKDIR /app/playground
|
| 46 |
-
RUN pnpm install
|
| 47 |
|
| 48 |
# Возвращаемся в основную директорию
|
| 49 |
WORKDIR /app
|
|
|
|
| 18 |
COPY .space/hf-space.sh /app/
|
| 19 |
|
| 20 |
# Создаем директории
|
| 21 |
+
RUN mkdir -p /tmp/ten_user/agents /tmp/ten_user/logs /app/backup /tmp/ten_playground
|
| 22 |
+
RUN chmod -R 777 /tmp/ten_playground
|
| 23 |
|
| 24 |
# Создаем и активируем виртуальную среду Python
|
| 25 |
RUN python3 -m venv /app/venv
|
|
|
|
| 38 |
RUN cp -r /tmp/ten-agent/playground/* /app/playground/
|
| 39 |
RUN rm -rf /tmp/ten-agent
|
| 40 |
|
| 41 |
+
# Устанавливаем pnpm и next глобально для использования с npx
|
| 42 |
+
RUN npm install -g pnpm@8 next@latest
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
# Возвращаемся в основную директорию
|
| 45 |
WORKDIR /app
|
app.py
CHANGED
|
@@ -240,6 +240,31 @@ def start_playground():
|
|
| 240 |
"""Запускает Playground UI через Next.js"""
|
| 241 |
print("Запуск Playground UI...")
|
| 242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
# Устанавливаем переменные окружения
|
| 244 |
ui_env = os.environ.copy()
|
| 245 |
ui_env["PORT"] = str(NEXTJS_PORT)
|
|
@@ -254,8 +279,8 @@ def start_playground():
|
|
| 254 |
# Отключаем строгие проверки CORS для работы в iframe
|
| 255 |
ui_env["NEXT_PUBLIC_DISABLE_CORS"] = "true"
|
| 256 |
|
| 257 |
-
# Запускаем UI
|
| 258 |
-
ui_cmd = "cd
|
| 259 |
print(f"Running UI command: {ui_cmd}")
|
| 260 |
ui_process = subprocess.Popen(
|
| 261 |
ui_cmd,
|
|
@@ -293,7 +318,7 @@ def create_interface():
|
|
| 293 |
"""Создает Gradio интерфейс для редиректа"""
|
| 294 |
with gr.Blocks() as demo:
|
| 295 |
gr.Markdown("# TEN Agent на Hugging Face Space")
|
| 296 |
-
gr.Markdown("##
|
| 297 |
|
| 298 |
# Статус серверов
|
| 299 |
status_md = gr.Markdown("### Статус: Инициализация...")
|
|
@@ -302,40 +327,76 @@ def create_interface():
|
|
| 302 |
col1, col2 = gr.Column(), gr.Column()
|
| 303 |
|
| 304 |
with col1:
|
| 305 |
-
#
|
| 306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
|
| 308 |
# Функция для открытия UI в iframe
|
|
|
|
|
|
|
| 309 |
def show_iframe():
|
| 310 |
return f"""
|
| 311 |
<div style="border: 1px solid #ccc; padding: 10px; border-radius: 5px;">
|
| 312 |
-
<iframe src="{UI_URL}" width="100%" height="
|
| 313 |
</div>
|
| 314 |
"""
|
| 315 |
|
| 316 |
iframe_area = gr.HTML()
|
| 317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
- Deepgram API Key
|
| 332 |
-
- Agora App ID и Certificate
|
| 333 |
-
""")
|
| 334 |
|
| 335 |
# Статус серверов и логи
|
| 336 |
-
with gr.Accordion("
|
| 337 |
-
api_status = gr.Textbox(label="Статус API сервера", value="
|
| 338 |
-
ui_status = gr.Textbox(label="Статус UI сервера", value="
|
| 339 |
|
| 340 |
# Функция обновления статуса
|
| 341 |
def update_status():
|
|
@@ -346,6 +407,9 @@ def create_interface():
|
|
| 346 |
|
| 347 |
status_btn = gr.Button("Обновить статус")
|
| 348 |
status_btn.click(update_status, outputs=[api_status, ui_status, status_md])
|
|
|
|
|
|
|
|
|
|
| 349 |
|
| 350 |
return demo
|
| 351 |
|
|
@@ -359,6 +423,199 @@ def is_port_in_use(port):
|
|
| 359 |
except:
|
| 360 |
return False
|
| 361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
def main():
|
| 363 |
# Создаем директории и файлы конфигурации
|
| 364 |
create_directories()
|
|
@@ -370,12 +627,16 @@ def main():
|
|
| 370 |
print("Не удалось запустить API сервер")
|
| 371 |
return
|
| 372 |
|
| 373 |
-
#
|
| 374 |
ui_process = start_playground()
|
|
|
|
|
|
|
| 375 |
if not ui_process:
|
| 376 |
-
print("Не удалось запустить Playground UI")
|
| 377 |
-
|
| 378 |
-
|
|
|
|
|
|
|
| 379 |
|
| 380 |
# Создаем Gradio интерфейс
|
| 381 |
demo = create_interface()
|
|
@@ -388,19 +649,21 @@ def main():
|
|
| 388 |
while True:
|
| 389 |
if api_process.poll() is not None:
|
| 390 |
print("API сервер остановлен")
|
| 391 |
-
ui_process.
|
|
|
|
| 392 |
break
|
| 393 |
|
| 394 |
-
if ui_process.poll() is not None:
|
| 395 |
-
print("UI
|
| 396 |
-
|
| 397 |
-
|
| 398 |
|
| 399 |
time.sleep(1)
|
| 400 |
except KeyboardInterrupt:
|
| 401 |
print("Принудительная остановка...")
|
| 402 |
api_process.terminate()
|
| 403 |
-
ui_process
|
|
|
|
| 404 |
|
| 405 |
if __name__ == "__main__":
|
| 406 |
# Корректная обработка сигналов
|
|
|
|
| 240 |
"""Запускает Playground UI через Next.js"""
|
| 241 |
print("Запуск Playground UI...")
|
| 242 |
|
| 243 |
+
# Создаем директорию для Next.js в /tmp, где у нас есть права на запись
|
| 244 |
+
tmp_playground_dir = Path("/tmp/ten_playground")
|
| 245 |
+
if not tmp_playground_dir.exists():
|
| 246 |
+
print(f"Создаем временную директорию для Next.js: {tmp_playground_dir}")
|
| 247 |
+
tmp_playground_dir.mkdir(exist_ok=True, parents=True)
|
| 248 |
+
|
| 249 |
+
# Копируем необходимые файлы из /app/playground в /tmp/ten_playground
|
| 250 |
+
print("Копируем файлы Next.js приложения во временную директорию...")
|
| 251 |
+
try:
|
| 252 |
+
os.system(f"cp -r /app/playground/app /tmp/ten_playground/")
|
| 253 |
+
os.system(f"cp -r /app/playground/public /tmp/ten_playground/")
|
| 254 |
+
os.system(f"cp /app/playground/package.json /tmp/ten_playground/")
|
| 255 |
+
os.system(f"cp /app/playground/next.config.mjs /tmp/ten_playground/")
|
| 256 |
+
os.system(f"cp /app/playground/tailwind.config.js /tmp/ten_playground/")
|
| 257 |
+
os.system(f"cp /app/playground/postcss.config.js /tmp/ten_playground/")
|
| 258 |
+
|
| 259 |
+
# Проверяем, что файлы скопировались
|
| 260 |
+
if not (tmp_playground_dir / "app").exists():
|
| 261 |
+
print("Не удалось скопировать файлы Next.js. Создаем простое приложение...")
|
| 262 |
+
create_simple_next_app(tmp_playground_dir)
|
| 263 |
+
except Exception as e:
|
| 264 |
+
print(f"Ошибка при копировании файлов: {e}")
|
| 265 |
+
print("Создаем простое приложение Next.js...")
|
| 266 |
+
create_simple_next_app(tmp_playground_dir)
|
| 267 |
+
|
| 268 |
# Устанавливаем переменные окружения
|
| 269 |
ui_env = os.environ.copy()
|
| 270 |
ui_env["PORT"] = str(NEXTJS_PORT)
|
|
|
|
| 279 |
# Отключаем строгие проверки CORS для работы в iframe
|
| 280 |
ui_env["NEXT_PUBLIC_DISABLE_CORS"] = "true"
|
| 281 |
|
| 282 |
+
# Запускаем UI из временной директории, где у нас есть права на запись
|
| 283 |
+
ui_cmd = f"cd {tmp_playground_dir} && npx next dev"
|
| 284 |
print(f"Running UI command: {ui_cmd}")
|
| 285 |
ui_process = subprocess.Popen(
|
| 286 |
ui_cmd,
|
|
|
|
| 318 |
"""Создает Gradio интерфейс для редиректа"""
|
| 319 |
with gr.Blocks() as demo:
|
| 320 |
gr.Markdown("# TEN Agent на Hugging Face Space")
|
| 321 |
+
gr.Markdown("## Управление и мониторинг")
|
| 322 |
|
| 323 |
# Статус серверов
|
| 324 |
status_md = gr.Markdown("### Статус: Инициализация...")
|
|
|
|
| 327 |
col1, col2 = gr.Column(), gr.Column()
|
| 328 |
|
| 329 |
with col1:
|
| 330 |
+
# Информация об API сервере
|
| 331 |
+
gr.Markdown(f"""
|
| 332 |
+
### API сервер
|
| 333 |
+
|
| 334 |
+
API сервер работает по адресу: http://{INTERNAL_HOST}:{API_PORT}
|
| 335 |
+
|
| 336 |
+
Доступные эндпоинты:
|
| 337 |
+
- `/graphs` - Список доступных графов
|
| 338 |
+
- `/health` - Статус API сервера
|
| 339 |
+
""")
|
| 340 |
+
|
| 341 |
+
# Кнопка для проверки API
|
| 342 |
+
check_api_btn = gr.Button("Проверить API сервер")
|
| 343 |
+
api_result = gr.JSON(label="Результат запроса к API")
|
| 344 |
+
|
| 345 |
+
def check_api():
|
| 346 |
+
try:
|
| 347 |
+
import requests
|
| 348 |
+
response = requests.get(f"http://{INTERNAL_HOST}:{API_PORT}/health")
|
| 349 |
+
return response.json()
|
| 350 |
+
except Exception as e:
|
| 351 |
+
return {"status": "error", "message": str(e)}
|
| 352 |
+
|
| 353 |
+
check_api_btn.click(check_api, outputs=api_result)
|
| 354 |
+
|
| 355 |
+
with col2:
|
| 356 |
+
# Информация о UI сервере
|
| 357 |
+
gr.Markdown(f"""
|
| 358 |
+
### UI сервер
|
| 359 |
+
|
| 360 |
+
UI сервер доступен по адресу: {UI_URL}
|
| 361 |
+
|
| 362 |
+
<a href="{UI_URL}" target="_blank" style="display: inline-block; padding: 10px 15px; background-color: #4CAF50; color: white; text-decoration: none; border-radius: 4px; margin: 10px 0;">Открыть UI в новой вкладке</a>
|
| 363 |
+
""")
|
| 364 |
|
| 365 |
# Функция для открытия UI в iframe
|
| 366 |
+
iframe_btn = gr.Button("Показать UI в iframe")
|
| 367 |
+
|
| 368 |
def show_iframe():
|
| 369 |
return f"""
|
| 370 |
<div style="border: 1px solid #ccc; padding: 10px; border-radius: 5px;">
|
| 371 |
+
<iframe src="{UI_URL}" width="100%" height="500px" frameborder="0"></iframe>
|
| 372 |
</div>
|
| 373 |
"""
|
| 374 |
|
| 375 |
iframe_area = gr.HTML()
|
| 376 |
+
iframe_btn.click(show_iframe, outputs=iframe_area)
|
| 377 |
+
|
| 378 |
+
# Ссылки на документацию и важные настройки
|
| 379 |
+
with gr.Accordion("Инструкции", open=False):
|
| 380 |
+
gr.Markdown("""
|
| 381 |
+
### Важные настройки
|
| 382 |
|
| 383 |
+
Для полноценной работы необходимо настроить следующие API ключи:
|
| 384 |
+
|
| 385 |
+
1. **OpenAI API Key** - для работы с языковыми моделями
|
| 386 |
+
2. **Deepgram API Key** - для распознавания речи
|
| 387 |
+
3. **ElevenLabs API Key** - для синтеза речи
|
| 388 |
+
4. **Agora App ID и Certificate** - для работы с RTC
|
| 389 |
+
|
| 390 |
+
### Доступные графы
|
| 391 |
+
|
| 392 |
+
1. **Voice Agent** - Голосовой агент с OpenAI и ElevenLabs
|
| 393 |
+
2. **Chat Agent** - Текстовый чат с OpenAI
|
| 394 |
+
""")
|
|
|
|
|
|
|
|
|
|
| 395 |
|
| 396 |
# Статус серверов и логи
|
| 397 |
+
with gr.Accordion("Статус системы", open=False):
|
| 398 |
+
api_status = gr.Textbox(label="Статус API сервера", value="Проверка...", interactive=False)
|
| 399 |
+
ui_status = gr.Textbox(label="Статус UI сервера", value="Проверка...", interactive=False)
|
| 400 |
|
| 401 |
# Функция обновления статуса
|
| 402 |
def update_status():
|
|
|
|
| 407 |
|
| 408 |
status_btn = gr.Button("Обновить статус")
|
| 409 |
status_btn.click(update_status, outputs=[api_status, ui_status, status_md])
|
| 410 |
+
|
| 411 |
+
# Обновляем статус при загрузке
|
| 412 |
+
demo.load(update_status, outputs=[api_status, ui_status, status_md])
|
| 413 |
|
| 414 |
return demo
|
| 415 |
|
|
|
|
| 423 |
except:
|
| 424 |
return False
|
| 425 |
|
| 426 |
+
def create_simple_next_app(target_dir):
|
| 427 |
+
"""Создает простое Next.js приложение в указанной директории"""
|
| 428 |
+
print(f"Создание простого Next.js приложения в {target_dir}")
|
| 429 |
+
|
| 430 |
+
# Создаем базовую структуру Next.js приложения
|
| 431 |
+
app_dir = target_dir / "app"
|
| 432 |
+
app_dir.mkdir(exist_ok=True, parents=True)
|
| 433 |
+
|
| 434 |
+
# Создаем простой package.json
|
| 435 |
+
package_json = {
|
| 436 |
+
"name": "ten-agent-simple-ui",
|
| 437 |
+
"version": "0.1.0",
|
| 438 |
+
"private": True,
|
| 439 |
+
"scripts": {
|
| 440 |
+
"dev": "next dev",
|
| 441 |
+
"build": "next build",
|
| 442 |
+
"start": "next start"
|
| 443 |
+
},
|
| 444 |
+
"dependencies": {
|
| 445 |
+
"next": "latest",
|
| 446 |
+
"react": "latest",
|
| 447 |
+
"react-dom": "latest"
|
| 448 |
+
}
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
with open(target_dir / "package.json", "w") as f:
|
| 452 |
+
json.dump(package_json, f, indent=2)
|
| 453 |
+
|
| 454 |
+
# Создаем простой next.config.js
|
| 455 |
+
next_config = """/** @type {import('next').NextConfig} */
|
| 456 |
+
const nextConfig = {
|
| 457 |
+
reactStrictMode: true,
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
module.exports = nextConfig
|
| 461 |
+
"""
|
| 462 |
+
|
| 463 |
+
with open(target_dir / "next.config.js", "w") as f:
|
| 464 |
+
f.write(next_config)
|
| 465 |
+
|
| 466 |
+
# Создаем простую страницу
|
| 467 |
+
page_content = """export default function Home() {
|
| 468 |
+
return (
|
| 469 |
+
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
|
| 470 |
+
<h1>TEN Agent UI</h1>
|
| 471 |
+
<p>API server is running at: <a href="http://localhost:8080">http://localhost:8080</a></p>
|
| 472 |
+
|
| 473 |
+
<div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f0f0f0', borderRadius: '5px' }}>
|
| 474 |
+
<p>API endpoints:</p>
|
| 475 |
+
<ul>
|
| 476 |
+
<li><a href="http://localhost:8080/graphs">/graphs</a> - Available graphs</li>
|
| 477 |
+
<li><a href="http://localhost:8080/health">/health</a> - API server status</li>
|
| 478 |
+
</ul>
|
| 479 |
+
</div>
|
| 480 |
+
|
| 481 |
+
<div style={{ marginTop: '20px' }}>
|
| 482 |
+
<button
|
| 483 |
+
style={{
|
| 484 |
+
padding: '10px 15px',
|
| 485 |
+
backgroundColor: '#4CAF50',
|
| 486 |
+
color: 'white',
|
| 487 |
+
border: 'none',
|
| 488 |
+
borderRadius: '5px',
|
| 489 |
+
cursor: 'pointer'
|
| 490 |
+
}}
|
| 491 |
+
onClick={() => window.location.href = 'http://localhost:8080/graphs'}
|
| 492 |
+
>
|
| 493 |
+
Go to API
|
| 494 |
+
</button>
|
| 495 |
+
</div>
|
| 496 |
+
</div>
|
| 497 |
+
);
|
| 498 |
+
}
|
| 499 |
+
"""
|
| 500 |
+
|
| 501 |
+
with open(app_dir / "page.js", "w") as f:
|
| 502 |
+
f.write(page_content)
|
| 503 |
+
|
| 504 |
+
# Создаем простой layout.js
|
| 505 |
+
layout_content = """export const metadata = {
|
| 506 |
+
title: 'TEN Agent',
|
| 507 |
+
description: 'Simple UI for TEN Agent',
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
export default function RootLayout({ children }) {
|
| 511 |
+
return (
|
| 512 |
+
<html lang="en">
|
| 513 |
+
<body>{children}</body>
|
| 514 |
+
</html>
|
| 515 |
+
)
|
| 516 |
+
}
|
| 517 |
+
"""
|
| 518 |
+
|
| 519 |
+
with open(app_dir / "layout.js", "w") as f:
|
| 520 |
+
f.write(layout_content)
|
| 521 |
+
|
| 522 |
+
print(f"Простое Next.js приложение создано в {target_dir}")
|
| 523 |
+
|
| 524 |
+
def start_simple_ui():
|
| 525 |
+
"""Запускает простой HTTP сервер для UI"""
|
| 526 |
+
print("Запуск простого HTTP сервера...")
|
| 527 |
+
|
| 528 |
+
# Создаем директорию для UI
|
| 529 |
+
simple_ui_dir = Path("/tmp/ten_ui")
|
| 530 |
+
simple_ui_dir.mkdir(exist_ok=True, parents=True)
|
| 531 |
+
|
| 532 |
+
# Создаем простую HTML страницу
|
| 533 |
+
html_content = """<!DOCTYPE html>
|
| 534 |
+
<html lang="en">
|
| 535 |
+
<head>
|
| 536 |
+
<meta charset="UTF-8">
|
| 537 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 538 |
+
<title>TEN Agent Simple UI</title>
|
| 539 |
+
<style>
|
| 540 |
+
body {
|
| 541 |
+
font-family: Arial, sans-serif;
|
| 542 |
+
max-width: 800px;
|
| 543 |
+
margin: 0 auto;
|
| 544 |
+
padding: 20px;
|
| 545 |
+
}
|
| 546 |
+
.card {
|
| 547 |
+
background-color: #f5f5f5;
|
| 548 |
+
border-radius: 8px;
|
| 549 |
+
padding: 16px;
|
| 550 |
+
margin-bottom: 16px;
|
| 551 |
+
}
|
| 552 |
+
button {
|
| 553 |
+
background-color: #4CAF50;
|
| 554 |
+
border: none;
|
| 555 |
+
color: white;
|
| 556 |
+
padding: 10px 15px;
|
| 557 |
+
text-align: center;
|
| 558 |
+
text-decoration: none;
|
| 559 |
+
display: inline-block;
|
| 560 |
+
font-size: 16px;
|
| 561 |
+
margin: 4px 2px;
|
| 562 |
+
cursor: pointer;
|
| 563 |
+
border-radius: 4px;
|
| 564 |
+
}
|
| 565 |
+
a {
|
| 566 |
+
color: #0066cc;
|
| 567 |
+
}
|
| 568 |
+
</style>
|
| 569 |
+
</head>
|
| 570 |
+
<body>
|
| 571 |
+
<h1>TEN Agent UI</h1>
|
| 572 |
+
<div class="card">
|
| 573 |
+
<h2>API Server</h2>
|
| 574 |
+
<p>API сервер работает по адресу: <a href="http://localhost:8080" target="_blank">http://localhost:8080</a></p>
|
| 575 |
+
<p>Доступные эндпоинты:</p>
|
| 576 |
+
<ul>
|
| 577 |
+
<li><a href="http://localhost:8080/graphs" target="_blank">/graphs</a> - Список доступных графов</li>
|
| 578 |
+
<li><a href="http://localhost:8080/health" target="_blank">/health</a> - Статус API сервера</li>
|
| 579 |
+
</ul>
|
| 580 |
+
</div>
|
| 581 |
+
|
| 582 |
+
<div class="card">
|
| 583 |
+
<h2>Запуск сессии</h2>
|
| 584 |
+
<p>Для запуска сессии можно использовать API вручную:</p>
|
| 585 |
+
<pre>curl -X POST http://localhost:8080/start -H "Content-Type: application/json" -d '{"graph_file":"voice_agent.json"}'</pre>
|
| 586 |
+
</div>
|
| 587 |
+
</body>
|
| 588 |
+
</html>
|
| 589 |
+
"""
|
| 590 |
+
|
| 591 |
+
with open(simple_ui_dir / "index.html", "w") as f:
|
| 592 |
+
f.write(html_content)
|
| 593 |
+
|
| 594 |
+
# Запускаем простой HTTP сервер на порту 3000
|
| 595 |
+
server_cmd = f"cd {simple_ui_dir} && python -m http.server {NEXTJS_PORT}"
|
| 596 |
+
print(f"Running simple HTTP server: {server_cmd}")
|
| 597 |
+
|
| 598 |
+
server_process = subprocess.Popen(
|
| 599 |
+
server_cmd,
|
| 600 |
+
shell=True,
|
| 601 |
+
stdout=subprocess.PIPE,
|
| 602 |
+
stderr=subprocess.PIPE
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
# Ждем запуска сервера
|
| 606 |
+
time.sleep(2)
|
| 607 |
+
|
| 608 |
+
# Проверяем, что процесс не упал
|
| 609 |
+
if server_process.poll() is not None:
|
| 610 |
+
stdout, stderr = server_process.communicate()
|
| 611 |
+
print(f"Простой HTTP сервер не запустился!")
|
| 612 |
+
print(f"STDOUT: {stdout.decode()}")
|
| 613 |
+
print(f"STDERR: {stderr.decode()}")
|
| 614 |
+
return None
|
| 615 |
+
|
| 616 |
+
print(f"Простой HTTP сервер запущен и слушает на порту {NEXTJS_PORT}")
|
| 617 |
+
return server_process
|
| 618 |
+
|
| 619 |
def main():
|
| 620 |
# Создаем директории и файлы конфигурации
|
| 621 |
create_directories()
|
|
|
|
| 627 |
print("Не удалось запустить API сервер")
|
| 628 |
return
|
| 629 |
|
| 630 |
+
# Пробуем запустить Playground UI через Next.js
|
| 631 |
ui_process = start_playground()
|
| 632 |
+
|
| 633 |
+
# Если запуск через Next.js не удался, пробуем запустить простой HTTP сервер
|
| 634 |
if not ui_process:
|
| 635 |
+
print("Не удалось запустить Playground UI через Next.js, пробуем простой HTTP сервер...")
|
| 636 |
+
ui_process = start_simple_ui()
|
| 637 |
+
|
| 638 |
+
if not ui_process:
|
| 639 |
+
print("Не удалось запустить ни один UI сервер. Продолжаем только с API сервером.")
|
| 640 |
|
| 641 |
# Создаем Gradio интерфейс
|
| 642 |
demo = create_interface()
|
|
|
|
| 649 |
while True:
|
| 650 |
if api_process.poll() is not None:
|
| 651 |
print("API сервер остановлен")
|
| 652 |
+
if ui_process and ui_process.poll() is None:
|
| 653 |
+
ui_process.terminate()
|
| 654 |
break
|
| 655 |
|
| 656 |
+
if ui_process and ui_process.poll() is not None:
|
| 657 |
+
print("UI сервер остановлен, пробуем перезапустить...")
|
| 658 |
+
# Пробуем запустить простой HTTP сервер, если UI процесс упал
|
| 659 |
+
ui_process = start_simple_ui()
|
| 660 |
|
| 661 |
time.sleep(1)
|
| 662 |
except KeyboardInterrupt:
|
| 663 |
print("Принудительная остановка...")
|
| 664 |
api_process.terminate()
|
| 665 |
+
if ui_process:
|
| 666 |
+
ui_process.terminate()
|
| 667 |
|
| 668 |
if __name__ == "__main__":
|
| 669 |
# Корректная обработка сигналов
|
simple_next_app.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// File: /tmp/ten_playground/app/page.js
|
| 2 |
+
// Simple Next.js app that redirects to API server
|
| 3 |
+
export default function Home() {
|
| 4 |
+
return (
|
| 5 |
+
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
|
| 6 |
+
<h1>TEN Agent UI Loader</h1>
|
| 7 |
+
<p>API server is running at: <a href="http://localhost:8080">http://localhost:8080</a></p>
|
| 8 |
+
|
| 9 |
+
<div style={{ marginTop: '20px', padding: '10px', backgroundColor: '#f0f0f0', borderRadius: '5px' }}>
|
| 10 |
+
<p>Используйте API напрямую:</p>
|
| 11 |
+
<ul>
|
| 12 |
+
<li><a href="http://localhost:8080/graphs">/graphs</a> - Список доступных графов</li>
|
| 13 |
+
<li><a href="http://localhost:8080/health">/health</a> - Статус API сервера</li>
|
| 14 |
+
</ul>
|
| 15 |
+
</div>
|
| 16 |
+
|
| 17 |
+
<div style={{ marginTop: '20px' }}>
|
| 18 |
+
<button
|
| 19 |
+
style={{
|
| 20 |
+
padding: '10px 15px',
|
| 21 |
+
backgroundColor: '#4CAF50',
|
| 22 |
+
color: 'white',
|
| 23 |
+
border: 'none',
|
| 24 |
+
borderRadius: '5px',
|
| 25 |
+
cursor: 'pointer'
|
| 26 |
+
}}
|
| 27 |
+
onClick={() => window.location.href = 'http://localhost:8080/graphs'}
|
| 28 |
+
>
|
| 29 |
+
Перейти к API
|
| 30 |
+
</button>
|
| 31 |
+
</div>
|
| 32 |
+
</div>
|
| 33 |
+
);
|
| 34 |
+
}
|
start.sh
CHANGED
|
@@ -14,11 +14,17 @@ else
|
|
| 14 |
PYTHON_CMD="python3"
|
| 15 |
fi
|
| 16 |
|
| 17 |
-
# Создаем необходимые директории
|
| 18 |
echo "Creating temporary directories in /tmp..."
|
| 19 |
mkdir -p /tmp/ten_user/agents
|
| 20 |
mkdir -p /tmp/ten_user/logs
|
| 21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
# Проверяем наличие файлов
|
| 24 |
echo "Checking necessary files..."
|
|
|
|
| 14 |
PYTHON_CMD="python3"
|
| 15 |
fi
|
| 16 |
|
| 17 |
+
# Создаем необходимые директории без изменения прав
|
| 18 |
echo "Creating temporary directories in /tmp..."
|
| 19 |
mkdir -p /tmp/ten_user/agents
|
| 20 |
mkdir -p /tmp/ten_user/logs
|
| 21 |
+
mkdir -p /tmp/ten_playground
|
| 22 |
+
|
| 23 |
+
# Проверяем, что директории созданы
|
| 24 |
+
echo "Checking directories..."
|
| 25 |
+
if [ -d "/tmp/ten_user" ]; then
|
| 26 |
+
echo "✅ /tmp/ten_user exists"
|
| 27 |
+
fi
|
| 28 |
|
| 29 |
# Проверяем наличие файлов
|
| 30 |
echo "Checking necessary files..."
|