Files
agent-maxim/simple_dashboard.py
T
Anton 052591281d Initial clean version - Agent Maxim
- Complete agent system with planning and execution
- Bridge API with FastAPI server
- WhatsApp-style mobile dashboard
- Enhanced web search with query improvement
- Security guards and policy checks
- Comprehensive documentation
2026-04-09 18:07:43 +03:00

342 lines
9.6 KiB
Python

"""Simple Dashboard - WhatsApp-style mobile UI for Agent Maxim."""
from __future__ import annotations
import sys
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
import uvicorn
sys.path.insert(0, str(Path(__file__).parent))
app = FastAPI(title="Agent Maxim Dashboard")
@app.get("/", response_class=HTMLResponse)
async def dashboard():
"""Serve WhatsApp-style chat dashboard."""
html_content = """
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Maxim</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: #e5ddd5;
height: 100vh;
overflow: hidden;
}
#chat {
height: 100vh;
display: flex;
flex-direction: column;
background: #e5ddd5;
}
#header {
background: #128C7E;
color: white;
padding: 12px 16px;
font-weight: 600;
font-size: 18px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 10;
}
#messages {
flex: 1;
overflow-y: auto;
padding: 10px;
display: flex;
flex-direction: column;
gap: 8px;
}
.message {
max-width: 85%;
padding: 10px 14px;
border-radius: 12px;
font-size: 15px;
line-height: 1.4;
word-wrap: break-word;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.user {
background: #dcf8c6;
margin-left: auto;
border-bottom-right-radius: 4px;
}
.agent {
background: white;
margin-right: auto;
border-bottom-left-radius: 4px;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.loading {
background: white;
margin-right: auto;
border-bottom-left-radius: 4px;
color: #666;
font-style: italic;
}
#inputBox {
display: flex;
padding: 10px;
background: #f0f0f0;
border-top: 1px solid #ddd;
gap: 8px;
}
input {
flex: 1;
padding: 12px 16px;
border-radius: 24px;
border: 1px solid #ccc;
outline: none;
font-size: 15px;
background: white;
transition: all 0.2s;
}
input:focus {
border-color: #128C7E;
box-shadow: 0 0 0 2px rgba(18, 140, 126, 0.1);
}
button {
padding: 12px 20px;
border: none;
border-radius: 24px;
background: #128C7E;
color: white;
font-size: 16px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
width: 50px;
}
button:hover {
background: #0d6e63;
}
button:active {
transform: scale(0.95);
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.result-title {
font-weight: 600;
color: #128C7E;
margin-bottom: 4px;
font-size: 14px;
}
.result-snippet {
color: #555;
font-size: 13px;
}
.result-link {
color: #06c;
text-decoration: none;
font-size: 12px;
display: block;
margin-top: 4px;
}
.result-link:hover {
text-decoration: underline;
}
.divider {
border-top: 1px solid #eee;
margin: 8px 0;
}
</style>
</head>
<body>
<div id="chat">
<div id="header">Agent Maxim</div>
<div id="messages"></div>
<div id="inputBox">
<input id="input" placeholder="Напиши задачу..." autocomplete="off" />
<button id="sendBtn" onclick="send()">➤</button>
</div>
</div>
<script>
const API_URL = '/runs';
const input = document.getElementById('input');
const sendBtn = document.getElementById('sendBtn');
function addMessage(text, cls) {
const msg = document.createElement('div');
msg.className = 'message ' + cls;
msg.innerHTML = text;
document.getElementById('messages').appendChild(msg);
scrollToBottom();
return msg;
}
function scrollToBottom() {
const messages = document.getElementById('messages');
messages.scrollTop = messages.scrollHeight;
}
function formatResponse(text) {
if (!text) return 'Нет ответа';
// Check if it contains web search results
if (text.includes('Title:') && text.includes('Snippet:')) {
return formatSearchResults(text);
}
// Format numbered items
return text.replace(/\n/g, '<br>');
}
function formatSearchResults(text) {
const lines = text.split('\n');
let html = '';
let currentResult = null;
for (const line of lines) {
line = line.trim();
if (line.startsWith('Title:')) {
if (currentResult) {
html += formatSingleResult(currentResult);
}
currentResult = {
title: line.replace('Title:', '').trim(),
snippet: '',
url: ''
};
} else if (line.startsWith('Snippet:')) {
if (currentResult) {
currentResult.snippet = line.replace('Snippet:', '').trim();
}
} else if (line.startsWith('URL:')) {
if (currentResult) {
currentResult.url = line.replace('URL:', '').trim();
}
}
}
if (currentResult) {
html += formatSingleResult(currentResult);
}
return html || text.replace(/\n/g, '<br>');
}
function formatSingleResult(result) {
html = '<div class="result-title">' + result.title + '</div>';
html += '<div class="result-snippet">' + result.snippet + '</div>';
if (result.url) {
html += '<a href="' + result.url + '" target="_blank" class="result-link">' + result.url + '</a>';
}
html += '<div class="divider"></div>';
return html;
}
async function send() {
const text = input.value.trim();
if (!text) return;
input.value = '';
input.disabled = true;
sendBtn.disabled = true;
addMessage(text, 'user');
const loadingMsg = addMessage('⏳ Думаю...', 'loading');
try {
const res = await fetch(API_URL, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
goal: text,
adapter: 'agent'
})
});
const data = await res.json();
loadingMsg.remove();
if (data.response) {
addMessage(formatResponse(data.response), 'agent');
} else {
addMessage('Ошибка: нет ответа от агента', 'agent');
}
} catch (err) {
loadingMsg.remove();
addMessage('Ошибка: ' + err.message, 'agent');
}
input.disabled = false;
sendBtn.disabled = false;
input.focus();
}
input.addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
send();
}
});
// Focus input on load
input.focus();
// Welcome message
setTimeout(() => {
addMessage('Привет! Я Agent Maxim. Чем могу помочь?', 'agent');
}, 500);
</script>
</body>
</html>
"""
return HTMLResponse(content=html_content)
if __name__ == "__main__":
print("Starting Agent Maxim Dashboard on http://0.0.0.0:8610")
print("Mobile-friendly WhatsApp-style UI")
uvicorn.run(app, host="0.0.0.0", port=8610, reload=False)