feat(ui): добавить веб-интерфейс и dashboard
Добавлен веб-интерфейс для Browser Use API с главной страницей и dashboard. Реализованы: HTML-шаблоны для UI, новый эндпоинт GET /api/tasks для списка задач, поддержка remote debugging для браузера через Chromium args. Обновлена конфигурация docker-compose с новыми переменными окружения.
This commit is contained in:
1 parent
1a22f692ff
commit
0fab25c80f
3 files changed
+208
-6
No files matched your search
Binary file not shown.
@@ -9,10 +9,13 @@ from fastapi import FastAPI, HTTPException
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from browser_use import Agent, Browser, ChatOpenAI
|
from browser_use import Agent, Browser, ChatOpenAI
|
||||||
from browser_use.agent.views import AgentHistoryList
|
from browser_use.agent.views import AgentHistoryList
|
||||||
|
from browser_use.browser.context import BrowserContextConfig, BrowserConfig
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
|
||||||
# Обход проверки API ключа browser-use
|
# Настройки Remote Debugging
|
||||||
os.environ["OPENAI_API_KEY"] = os.environ.get("OPENAI_API_KEY", "dummy")
|
REMOTE_DEBUGGING_PORT = int(os.getenv("REMOTE_DEBUGGING_PORT", "9222"))
|
||||||
|
REMOTE_DEBUGGING_ENABLED = os.getenv("REMOTE_DEBUGGING_ENABLED", "false").lower() == "true"
|
||||||
|
|
||||||
app = FastAPI(title="Browser Use API", version="1.0.0")
|
app = FastAPI(title="Browser Use API", version="1.0.0")
|
||||||
|
|
||||||
@@ -20,7 +23,7 @@ app = FastAPI(title="Browser Use API", version="1.0.0")
|
|||||||
class TaskRequest(BaseModel):
|
class TaskRequest(BaseModel):
|
||||||
task: str
|
task: str
|
||||||
max_steps: Optional[int] = 20
|
max_steps: Optional[int] = 20
|
||||||
model: Optional[str] = "openai/gpt-4o-mini"
|
model: Optional[str] = DEFAULT_MODEL
|
||||||
|
|
||||||
class TaskResponse(BaseModel):
|
class TaskResponse(BaseModel):
|
||||||
task_id: str
|
task_id: str
|
||||||
@@ -69,12 +72,22 @@ async def run_browser_task(task_id: str, task: str, max_steps: int, model: str):
|
|||||||
api_key=api_key
|
api_key=api_key
|
||||||
)
|
)
|
||||||
|
|
||||||
# Инициализация браузера с постоянной директорией для cookies
|
# Настройки браузера
|
||||||
browser = Browser(
|
browser_config = BrowserConfig(
|
||||||
user_data_dir="/app/browser-data",
|
user_data_dir="/app/browser-data",
|
||||||
profile_directory="Default"
|
profile_directory="Default",
|
||||||
|
headless=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Добавляем remote debugging если включено
|
||||||
|
if REMOTE_DEBUGGING_ENABLED:
|
||||||
|
browser_config.extra_chromium_args = [
|
||||||
|
f"--remote-debugging-port={REMOTE_DEBUGGING_PORT}",
|
||||||
|
"--remote-debugging-address=0.0.0.0"
|
||||||
|
]
|
||||||
|
|
||||||
|
browser = Browser(config=browser_config)
|
||||||
|
|
||||||
# Создание агента
|
# Создание агента
|
||||||
agent = Agent(
|
agent = Agent(
|
||||||
task=task,
|
task=task,
|
||||||
@@ -117,6 +130,22 @@ async def get_task(task_id: str):
|
|||||||
error=task.get("error")
|
error=task.get("error")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@app.get("/api/tasks")
|
||||||
|
async def list_tasks():
|
||||||
|
"""Получить список всех задач"""
|
||||||
|
return {
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"task_id": task_id,
|
||||||
|
"status": task["status"],
|
||||||
|
"task": task.get("task", ""),
|
||||||
|
"result": task.get("result"),
|
||||||
|
"error": task.get("error")
|
||||||
|
}
|
||||||
|
for task_id, task in tasks.items()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
@app.get("/api/health")
|
@app.get("/api/health")
|
||||||
async def health_check():
|
async def health_check():
|
||||||
"""Проверка здоровья сервера"""
|
"""Проверка здоровья сервера"""
|
||||||
@@ -126,5 +155,174 @@ async def health_check():
|
|||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Web UI - Главная страница
|
||||||
|
WEB_UI_HTML = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Browser Use API</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
|
||||||
|
h1 { color: #333; }
|
||||||
|
.card { border: 1px solid #ddd; border-radius: 8px; padding: 20px; margin: 20px 0; }
|
||||||
|
.btn { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
|
||||||
|
.btn:hover { background: #0056b3; }
|
||||||
|
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }
|
||||||
|
pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Browser Use API</h1>
|
||||||
|
<p>REST API для автоматизации браузера с помощью AI</p>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>Быстрые ссылки</h2>
|
||||||
|
<p><a href="/dashboard" class="btn">Открыть Dashboard</a></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>API Endpoints</h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>POST /api/tasks</code> - Создать задачу</li>
|
||||||
|
<li><code>GET /api/tasks/{task_id}</code> - Получить результат</li>
|
||||||
|
<li><code>GET /api/health</code> - Проверка здоровья</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>Пример использования</h2>
|
||||||
|
<pre>curl -X POST http://localhost:8383/api/tasks \\
|
||||||
|
-H "Content-Type: application/json" \\
|
||||||
|
-d '{"task": "Перейди на example.com", "max_steps": 20}'</pre>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
DASHBOARD_HTML = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Browser Use Dashboard</title>
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
|
||||||
|
.container { max-width: 1200px; margin: 0 auto; }
|
||||||
|
h1 { color: #333; }
|
||||||
|
.card { background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||||||
|
.task-form { display: flex; gap: 10px; flex-wrap: wrap; }
|
||||||
|
input, select { padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
|
||||||
|
input[type="text"] { flex: 1; min-width: 200px; }
|
||||||
|
input[type="number"] { width: 100px; }
|
||||||
|
button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; }
|
||||||
|
button:hover { background: #0056b3; }
|
||||||
|
.task-list { list-style: none; padding: 0; }
|
||||||
|
.task-item { border: 1px solid #eee; padding: 15px; margin: 10px 0; border-radius: 4px; }
|
||||||
|
.status-pending { border-left: 4px solid #ffc107; }
|
||||||
|
.status-running { border-left: 4px solid #17a2b8; }
|
||||||
|
.status-completed { border-left: 4px solid #28a745; }
|
||||||
|
.status-failed { border-left: 4px solid #dc3545; }
|
||||||
|
.badge { display: inline-block; padding: 4px 8px; border-radius: 3px; font-size: 12px; background: #eee; }
|
||||||
|
.result { background: #f8f9fa; padding: 10px; border-radius: 4px; margin-top: 10px; white-space: pre-wrap; }
|
||||||
|
.nav { margin-bottom: 20px; }
|
||||||
|
.nav a { color: #007bff; text-decoration: none; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="nav"><a href="/">← На главную</a></div>
|
||||||
|
<h1>Browser Use Dashboard</h1>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>Создать задачу</h2>
|
||||||
|
<form class="task-form" id="taskForm">
|
||||||
|
<input type="text" id="taskInput" placeholder="Описание задачи (например: Перейди на example.com)" required>
|
||||||
|
<input type="number" id="maxSteps" value="20" min="1" max="100" placeholder="Шаги">
|
||||||
|
<select id="modelSelect">
|
||||||
|
<option value="arcee-ai/trinity-large-preview:free">arcee-ai/trinity-large-preview:free</option>
|
||||||
|
<option value="openai/gpt-4o-mini">openai/gpt-4o-mini</option>
|
||||||
|
<option value="google/gemini-2.0-flash-001">google/gemini-2.0-flash-001</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit">Запустить</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h2>Задачи</h2>
|
||||||
|
<button onclick="refreshTasks()">Обновить</button>
|
||||||
|
<ul class="task-list" id="taskList"></ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let pollInterval;
|
||||||
|
|
||||||
|
document.getElementById('taskForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const task = document.getElementById('taskInput').value;
|
||||||
|
const max_steps = parseInt(document.getElementById('maxSteps').value);
|
||||||
|
const model = document.getElementById('modelSelect').value;
|
||||||
|
|
||||||
|
const response = await fetch('/api/tasks', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({task, max_steps, model})
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
alert('Задача создана! ID: ' + data.task_id);
|
||||||
|
refreshTasks();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function refreshTasks() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/tasks');
|
||||||
|
const data = await res.json();
|
||||||
|
const list = document.getElementById('taskList');
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
|
const taskList = data.tasks || [];
|
||||||
|
if (taskList.length === 0) {
|
||||||
|
list.innerHTML = '<li>Нет задач. Создайте первую задачу!</li>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const task of taskList) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'task-item status-' + task.status;
|
||||||
|
li.innerHTML = `
|
||||||
|
<strong>Task ID:</strong> ${task.task_id}<br>
|
||||||
|
<strong>Задача:</strong> ${task.task || 'N/A'}<br>
|
||||||
|
<span class="badge">${task.status}</span><br>
|
||||||
|
<strong>Результат:</strong>
|
||||||
|
<div class="result">${task.result || task.error || 'Ожидание...'}</div>
|
||||||
|
`;
|
||||||
|
list.appendChild(li);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshTasks();
|
||||||
|
setInterval(refreshTasks, 5000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
async def root():
|
||||||
|
"""Главная страница"""
|
||||||
|
return HTMLResponse(content=WEB_UI_HTML)
|
||||||
|
|
||||||
|
@app.get("/dashboard", response_class=HTMLResponse)
|
||||||
|
async def dashboard():
|
||||||
|
"""Dashboard"""
|
||||||
|
return HTMLResponse(content=DASHBOARD_HTML)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8383)
|
uvicorn.run(app, host="0.0.0.0", port=8383)
|
||||||
@@ -9,8 +9,12 @@ services:
|
|||||||
container_name: browser-use
|
container_name: browser-use
|
||||||
ports:
|
ports:
|
||||||
- "8383:8383"
|
- "8383:8383"
|
||||||
|
- "9222:9222"
|
||||||
environment:
|
environment:
|
||||||
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
||||||
|
- LLM_MODEL_NAME=${LLM_MODEL_NAME:-arcee-ai/trinity-large-preview:free}
|
||||||
|
- REMOTE_DEBUGGING_ENABLED=${REMOTE_DEBUGGING_ENABLED:-false}
|
||||||
|
- REMOTE_DEBUGGING_PORT=${REMOTE_DEBUGGING_PORT:-9222}
|
||||||
volumes:
|
volumes:
|
||||||
- /home/nafer/dokploy_data/browser-use:/app/browser-data
|
- /home/nafer/dokploy_data/browser-use:/app/browser-data
|
||||||
shm_size: '2gb'
|
shm_size: '2gb'
|
||||||
|
|||||||
Reference in new issue
Block a user