298 lines
14 KiB
HTML
298 lines
14 KiB
HTML
<!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> |