Files
---/app/templates/index.html
2025-12-06 15:12:19 +03:00

298 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SwarmMind</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
background-attachment: fixed;
color: #e0e0e0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.chat-container {
width: 90%;
max-width: 1000px;
height: 92vh;
background: rgba(20, 20, 30, 0.9);
backdrop-filter: blur(14px);
border-radius: 24px;
overflow: hidden;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.7);
border: 1px solid rgba(120, 120, 255, 0.2);
display: flex;
flex-direction: column;
}
.chat-header {
padding: 18px 24px;
background: linear-gradient(to right, #1a1a2e, #16213e);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.chat-header h1 {
margin: 0;
font-size: 1.6em;
background: linear-gradient(to right, #00d4ff, #7b68ee);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
}
/* === КАСТОМНЫЙ ДРОПДАУН === */
.custom-select { position: relative; width: 160px; }
.select-trigger {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 14px; background: rgba(40, 40, 60, 0.9); border: 1px solid #555;
border-radius: 12px; cursor: pointer; transition: all 0.3s ease;
font-size: 1em; color: #e0e0e0;
}
.select-trigger:hover { border-color: #7b68ee; box-shadow: 0 0 0 2px rgba(123,104,238,0.2); }
.select-trigger::after { content: '↓'; font-size: 0.8em; margin-left: 8px; transition: transform 0.3s; }
.select-trigger.open::after { transform: rotate(180deg); }
.select-options {
position: absolute; top: 100%; left: 0; right: 0;
background: rgba(30,30,50,0.98); border: 1px solid #555; border-radius: 12px;
margin-top: 6px; max-height: 240px; overflow-y: auto; opacity: 0; visibility: hidden;
transform: translateY(-10px); transition: all 0.3s ease; box-shadow: 0 10px 30px rgba(0,0,0,0.5); z-index: 100;
}
.select-options.open { opacity: 1; visibility: visible; transform: translateY(0); }
.option { padding: 12px 16px; display: flex; align-items: center; gap: 10px; cursor: pointer; }
.option:hover { background: rgba(123,104,238,0.3); color: white; }
/* === КНОПКИ ФУНКЦИЙ === */
.feature-btn {
padding: 10px 16px; background: rgba(109, 109, 144, 0.8); border: 1px solid #555;
border-radius: 12px; cursor: pointer; font-size: 0.9em; transition: all 0.3s ease;
display: flex; align-items: center; gap: 8px; white-space: nowrap;
}
.feature-btn:hover { background: rgba(123,104,238,0.3); border-color: #7b68ee; }
.chat-messages { flex: 1; padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; }
.message { max-width: 78%; padding: 14px 18px; border-radius: 18px; line-height: 1.5; word-wrap: break-word; animation: fadeIn 0.3s; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.user-message { align-self: flex-end; background: linear-gradient(to right, #273991ff, #1b47afff); color: white; border-bottom-right-radius: 4px; }
.agent-message { align-self: flex-start; background: rgba(50,50,70,0.9); color: #e0e0e0; border: 1px solid rgba(100,100,150,0.2); border-bottom-left-radius: 4px; }
/* === КРАСИВАЯ СКРЕПКА === */
/* === ИНПУТ + КНОПКА СКРЕПКИ === */
.chat-input {
display: flex; padding: 18px; background: rgba(25,25,35,0.95); backdrop-filter: blur(10px);
border-top: 1px solid rgba(100,100,200,0.1); gap: 12px; align-items: center;
}
.chat-input input { flex: 1; padding: 14px 18px; background: rgba(40,40,60,0.8); color: #e0e0e0; border: 1px solid #555; border-radius: 14px 0 0 14px; font-size: 1em; outline: none; }
.chat-input input:focus { border-color: #7b68ee; box-shadow: 0 0 0 2px rgba(123,104,238,0.2); }
.attach-btn {
width: 48px; height: 48px; background: rgba(60,60,80,0.8); border: 1px solid #555;
border-radius: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center;
transition: all 0.3s ease; font-size: 1.3em;
}
.attach-btn:hover { background: rgba(123,104,238,0.3); border-color: #7b68ee; transform: rotate(15deg); }
.chat-input button { padding: 14px 28px; background: linear-gradient(to right, #7b68ee, #2460b5ff); color: white; border: none; border-radius: 0 14px 14px 0; cursor: pointer; font-weight: 600; transition: all 0.3s; box-shadow: 0 4px 15px rgba(123,104,238,0.4); }
.chat-input button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(123,104,238,0.5); }
/* === МОДАЛЬНОЕ ОКНО ЗАГРУЗКИ === */
.upload-modal {
position: absolute; bottom: 80px; left: 50%; transform: translateX(-50%);
width: 90%; max-width: 500px; background: rgba(30,30,50,0.98); border: 1px solid #555;
border-radius: 16px; padding: 24px; text-align: center; box-shadow: 0 15px 40px rgba(0,0,0,0.6);
opacity: 0; visibility: hidden; transition: all 0.3s ease; z-index: 1000;
}
.upload-modal.open { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(-10px); }
.drop-zone {
border: 2px dashed #555; border-radius: 16px; padding: 40px; margin-top: 16px;
background: rgba(40,40,60,0.3); cursor: pointer; transition: all 0.3s;
}
.drop-zone:hover { border-color: #7b68ee; background: rgba(123,104,238,0.1); }
.drop-zone.dragover { border-color: #00d4ff; background: rgba(0,212,255,0.1); }
.close-upload {
position: absolute; top: 12px; right: 16px; background: none; border: none;
font-size: 1.4em; color: #aaa; cursor: pointer; transition: color 0.2s;
}
.close-upload:hover { color: #ff6b6b; }
</style>
</head>
<body>
<div class="chat-container">
<!-- HEADER -->
<div class="chat-header">
<h1>SwarmMind</h1>
<div class="custom-select" id="custom-select">
<div class="select-trigger" id="trigger"><span id="selected-agent">Авто</span></div>
<div class="select-options" id="options">
{% for agent in agents %}
<div class="option" data-value="{{ agent }}"><span>{{ agent }}</span></div>
{% endfor %}
</div>
</div>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<button class="feature-btn" onclick="alert('Цепочка: Lawyer to Accountant to Final')"><span>Chain</span></button>
<button class="feature-btn" onclick="alert('Голосовой ввод в разработке')"><span>Microphone</span></button>
<button class="feature-btn" onclick="alert('Экспорт в PDF')"><span>PDF</span></button>
<button class="feature-btn" onclick="alert('API Key: sk-...')"><span>Key</span> API</button>
<button class="feature-btn" onclick="alert('Совещание: все агенты отвечают')"><span>Team</span>mode</button>
</div>
</div>
<!-- ЧАТ -->
<div class="chat-messages" id="chat-messages"></div>
<!-- ИНПУТ + СКРЕПКА -->
<div class="chat-input">
<button class="attach-btn" id="attach-btn" title="Прикрепить файл">
<span class="icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 4a6 6 0 0 1 0 12h8a6 6 0 0 1 0-12" stroke="#e0e0e0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 4v12" stroke="#e0e0e0" stroke-width="2" stroke-linecap="round"/>
</svg>
</span>
</button>
<input type="text" id="user-input" placeholder="Введите сообщение...">
<button onclick="sendMessage()">Отправить</button>
</div>
<!-- МОДАЛЬНОЕ ОКНО ЗАГРУЗКИ -->
<div class="upload-modal" id="upload-modal">
<button class="close-upload" id="close-upload">x</button>
<p><strong>Перетащите файл сюда</strong><br><small>PDF, DOCX, TXT — до 10 МБ</small></p>
<div class="drop-zone" id="drop-zone"></div>
</div>
</div>
<script>
const chatHistory = {};
let currentAgent = 'Auto';
window.addEventListener('load', () => {
const agents = {{ agents|tojson }};
agents.forEach(a => chatHistory[a] = []);
setupDropdown();
setupAttachButton();
renderChat();
});
// === ДРОПДАУН ===
function setupDropdown() {
const trigger = document.getElementById('trigger');
const options = document.getElementById('options');
const selected = document.getElementById('selected-agent');
trigger.addEventListener('click', () => {
options.classList.toggle('open');
trigger.classList.toggle('open');
});
document.querySelectorAll('.option').forEach(opt => {
opt.addEventListener('click', () => {
currentAgent = opt.dataset.value;
selected.textContent = currentAgent;
options.classList.remove('open');
trigger.classList.remove('open');
renderChat();
});
});
document.addEventListener('click', e => {
if (!trigger.contains(e.target) && !options.contains(e.target)) {
options.classList.remove('open');
trigger.classList.remove('open');
}
});
}
// === КНОПКА СКРЕПКИ + МОДАЛКА ===
function setupAttachButton() {
const btn = document.getElementById('attach-btn');
const modal = document.getElementById('upload-modal');
const close = document.getElementById('close-upload');
const zone = document.getElementById('drop-zone');
btn.addEventListener('click', () => {
modal.classList.add('open');
});
close.addEventListener('click', () => {
modal.classList.remove('open');
});
// Закрытие при клике вне
modal.addEventListener('click', e => {
if (e.target === modal) modal.classList.remove('open');
});
// Drag & Drop
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(ev => {
zone.addEventListener(ev, e => { e.preventDefault(); e.stopPropagation(); });
});
['dragenter', 'dragover'].forEach(ev => {
zone.addEventListener(ev, () => zone.classList.add('dragover'));
});
['dragleave', 'drop'].forEach(ev => {
zone.addEventListener(ev, () => zone.classList.remove('dragover'));
});
zone.addEventListener('drop', e => {
const file = e.dataTransfer.files[0];
if (file) {
alert(`Загружен: ${file.name}`);
modal.classList.remove('open');
}
});
}
// === ЧАТ ===
function renderChat() {
const div = document.getElementById('chat-messages');
div.innerHTML = '';
(chatHistory[currentAgent] || []).forEach(m => {
const el = document.createElement('div');
el.className = `message ${m.type}-message`;
el.textContent = m.text;
if (m.error) el.style.color = '#ff6b6b';
div.appendChild(el);
});
div.scrollTop = div.scrollHeight;
}
async function sendMessage() {
const input = document.getElementById('user-input');
const msg = input.value.trim();
if (!msg) return;
chatHistory[currentAgent].push({ type: 'user', text: msg });
input.value = '';
chatHistory[currentAgent].push({ type: 'agent', text: 'Печатает' });
renderChat();
try {
const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: msg, agent: currentAgent }) });
chatHistory[currentAgent].pop();
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
chatHistory[currentAgent].push({ type: 'agent', text: data.reply });
} catch (err) {
chatHistory[currentAgent].pop();
chatHistory[currentAgent].push({ type: 'agent', text: `Ошибка: ${err.message}`, error: true });
}
renderChat();
}
document.getElementById('user-input').addEventListener('keypress', e => {
if (e.key === 'Enter') { e.preventDefault(); sendMessage(); }
});
</script>
</body>
</html>