Макеты — один sketches/index.html; approved/ удалён

- sketches/index.html: все экраны одним документом в порядке 1·… (14–19 — бывшие approved)
- approved/ (4 файла групп + 3 макета + README) удалён как дубль
- стили вынесены в sketches/style.css
- README, THEMES, BORROW-FROM-ASSISTENT, MARKDOWN-SOURCE, MIC-ASR-SEARCH
  приведены к фактическому состоянию
This commit is contained in:
2026-09-28 10:07:57 +03:00
parent 960cd60120
commit 27f349550c
25 changed files with 2416 additions and 4448 deletions
-25
View File
@@ -1,25 +0,0 @@
# Вариант 2 — Rail / Voice-first
## Суть
Голос — главное действие, а не дополнение к клавиатуре. Огромная кнопка микрофона
слева в панели ввода, живая расшифровка на экране, пока говоришь.
## Ключевые решения
- **Раскладка:** рейка иконок (76) + диалоги (296) + чат. Рейка — вход в память агента
и инструменты, это не просто мессенджер.
- **Голос:** кнопка 58 px, при записи краснеет и пульсирует. Над полем раскрывается
панель: волна, таймер и **текст, который появляется по мере речи**.
- **Три выхода из записи:** «Отправить» / «В поле ввода» (поправить руками) / «Отменить».
Голосовое сообщение в ленте рисуется с волной и кнопкой проигрывания.
- **Горячие клавиши:** Ctrl+R — запись, удержание пробела — говорить, Esc — отмена.
## Что хорошо
- Голосом пользоваться реально удобно: видно, что тебя услышали, до отправки.
- Ошибку в тексте видно до отправки — можно поправить.
## Что слабо
- Больше элементов на экране, надо разбираться.
- «Пробел на удержание» конфликтует с обычным набором текста — нужна аккуратность.
## Кому
Тому, кто в основном диктует, а не печатает.
-342
View File
@@ -1,342 +0,0 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Вариант 2 — Rail / Voice-first</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0E1418; --panel: #141E24; --panel2: #1B2830; --line: #22323C;
--fg: #E8EEF2; --dim: #7A93A3; --accent: #4FD1C5; --hot: #FF6B6B;
--bot-bubble: #1B2830; --user-bubble: #1F4B54;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
background: #070B0D; color: var(--fg); line-height: 1.45;
display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 24px;
}
.window {
width: 1240px; height: 750px; background: var(--bg); border-radius: 14px; display: flex;
overflow: hidden; border: 1px solid var(--line); box-shadow: 0 24px 80px rgba(0,0,0,.65);
}
/* ---- узкая колонка-рейка: чаты иконками ---- */
.rail {
width: 76px; flex: 0 0 76px; background: var(--panel); border-right: 1px solid var(--line);
display: flex; flex-direction: column; align-items: center; padding: 12px 0; gap: 10px;
}
.rail-btn {
width: 46px; height: 46px; border-radius: 13px; border: none; background: var(--panel2);
color: var(--dim); cursor: pointer; display: grid; place-items: center; font-size: 17px;
position: relative; transition: .15s;
}
.rail-btn:hover { color: var(--fg); }
.rail-btn.active { background: var(--accent); color: #07201D; }
.rail-btn .dot-badge {
position: absolute; top: -3px; right: -3px; background: var(--accent); color: #07201D;
font-size: 10px; font-weight: 800; border-radius: 8px; padding: 1px 5px; min-width: 16px;
}
.rail-btn .label {
position: absolute; bottom: -14px; font-size: 9px; color: var(--dim); white-space: nowrap;
}
.rail-sep { width: 28px; height: 1px; background: var(--line); margin: 8px 0; }
.rail-spacer { flex: 1; }
/* ---- средний столбец: список диалогов ---- */
.list { width: 296px; flex: 0 0 296px; background: var(--panel); border-right: 1px solid var(--line);
display: flex; flex-direction: column; }
.l-head { padding: 16px 18px 12px; }
.l-head h1 { font-size: 16.5px; font-weight: 650; letter-spacing: .2px; }
.l-head p { font-size: 11.5px; color: var(--dim); margin-top: 3px; }
.search {
margin: 0 18px 10px; padding: 9px 12px; border-radius: 10px; border: 1px solid var(--line);
background: var(--bg); color: var(--fg); font-size: 13px; outline: none;
}
.search:focus { border-color: var(--accent); }
.search::placeholder { color: var(--dim); }
.rows { flex: 1; overflow-y: auto; padding: 0 10px 12px; }
.row {
display: flex; gap: 11px; padding: 10px 11px; border-radius: 11px; cursor: pointer;
border-left: 2px solid transparent;
}
.row:hover { background: var(--panel2); }
.row.active { background: var(--panel2); border-left-color: var(--accent); }
.row .av {
width: 38px; height: 38px; flex: 0 0 38px; border-radius: 11px; display: grid; place-items: center;
font-size: 13px; font-weight: 700; background: rgba(79,209,197,.16); color: var(--accent);
}
.row .mid { flex: 1; min-width: 0; }
.row .nm { font-size: 13.5px; font-weight: 550; display: flex; justify-content: space-between; }
.row .tm { font-size: 10.5px; color: var(--dim); font-weight: 400; }
.row .pv { font-size: 12px; color: var(--dim); white-space: nowrap; overflow: hidden;
text-overflow: ellipsis; margin-top: 2px; }
/* ---- основная зона чата ---- */
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.m-head { height: 60px; flex: 0 0 60px; display: flex; align-items: center; gap: 12px;
padding: 0 22px; border-bottom: 1px solid var(--line); }
.m-head .t { font-size: 14.5px; font-weight: 600; }
.m-head .s { font-size: 11.5px; color: var(--dim); }
.ghost { margin-left: auto; display: flex; gap: 6px; }
.ghost button { background: transparent; border: 1px solid var(--line); color: var(--dim);
border-radius: 8px; padding: 5px 11px; font-size: 11.5px; cursor: pointer; }
.ghost button:hover { color: var(--fg); border-color: var(--dim); }
.thread { flex: 1; overflow-y: auto; padding: 22px 26px; display: flex; flex-direction: column; gap: 13px; }
.b { max-width: 76%; padding: 10px 14px; border-radius: 14px; font-size: 13.5px; }
.b.u { align-self: flex-end; background: var(--user-bubble); border-bottom-right-radius: 4px; }
.b.a { align-self: flex-start; background: var(--bot-bubble); border-bottom-left-radius: 4px; }
.b .tm { font-size: 10.5px; color: var(--dim); margin-top: 5px; }
/* голосовое сообщение — с волной прямо в пузыре */
.voice-b {
align-self: flex-end; background: var(--user-bubble); border-radius: 14px 14px 4px 14px;
padding: 10px 14px; display: flex; align-items: center; gap: 11px; min-width: 250px;
}
.play { width: 30px; height: 30px; border-radius: 50%; background: var(--accent); color: #07201D;
border: none; display: grid; place-items: center; cursor: pointer; font-size: 12px; }
.vwave { flex: 1; display: flex; align-items: center; gap: 2px; height: 24px; }
.vwave i { flex: 1; background: rgba(232,238,242,.5); border-radius: 2px; }
.vdur { font-size: 11px; color: var(--dim); font-variant-numeric: tabular-nums; }
/* ---- КОМПОЗЕР: голос — главный ---- */
.composer { border-top: 1px solid var(--line); background: var(--panel); padding: 16px 22px 14px; }
.mic-zone { display: flex; align-items: center; gap: 16px; }
.mic-big {
width: 58px; height: 58px; flex: 0 0 58px; border-radius: 50%; border: none; cursor: pointer;
background: var(--accent); color: #07201D; font-size: 23px; display: grid; place-items: center;
transition: .2s;
}
.mic-big:hover { transform: scale(1.05); }
.mic-big.on { background: var(--hot); color: #fff; animation: ring 1.5s infinite; }
@keyframes ring { 0%,100% { box-shadow: 0 0 0 0 rgba(255,107,107,.6); } 50% { box-shadow: 0 0 0 13px rgba(255,107,107,0); } }
.mic-side { flex: 1; min-width: 0; }
.mic-hint { font-size: 12px; color: var(--dim); margin-bottom: 7px; }
.mic-hint b { color: var(--fg); font-weight: 550; }
.type-row { display: flex; align-items: center; gap: 9px; }
.type-row textarea {
flex: 1; background: var(--bg); border: 1px solid var(--line); border-radius: 10px; padding: 10px 13px;
color: var(--fg); font-family: inherit; font-size: 13.5px; outline: none; resize: none;
height: 40px; max-height: 110px; line-height: 1.4;
}
.type-row textarea:focus { border-color: var(--accent); }
.type-row textarea::placeholder { color: var(--dim); }
.go { height: 40px; padding: 0 17px; border-radius: 10px; border: 1px solid var(--line);
background: transparent; color: var(--dim); font-size: 12.5px; cursor: pointer; }
.go.active { background: var(--accent); border-color: var(--accent); color: #07201D; font-weight: 650; }
/* расшифровка в реальном времени */
.live-asr { margin-top: 11px; display: none; background: var(--bg); border: 1px solid var(--hot);
border-radius: 11px; padding: 11px 14px; }
.live-asr.on { display: block; }
.live-asr .top { display: flex; align-items: center; gap: 9px; margin-bottom: 8px; font-size: 11.5px; color: var(--hot); font-weight: 600; }
.blink { width: 7px; height: 7px; border-radius: 50%; background: var(--hot); animation: bl .9s infinite; }
@keyframes bl { 50% { opacity: .15; } }
.big-wave { display: flex; align-items: center; gap: 3px; height: 30px; margin-bottom: 8px; }
.big-wave i { width: 3px; background: var(--accent); border-radius: 2px; height: 25%; animation: bw .8s infinite ease-in-out; }
.big-wave i:nth-child(2n) { animation-delay: .1s; } .big-wave i:nth-child(3n) { animation-delay: .2s; }
@keyframes bw { 0%,100% { height: 18%; } 50% { height: 100%; } }
.asr-line { font-size: 14px; color: var(--fg); min-height: 20px; }
.asr-line .cursor { display: inline-block; width: 2px; height: 15px; background: var(--accent);
vertical-align: middle; animation: bl .8s infinite; }
.live-actions { display: flex; gap: 8px; margin-top: 11px; }
.live-actions button { border-radius: 9px; padding: 7px 14px; font-size: 12.5px; cursor: pointer; border: 1px solid var(--line); background: transparent; color: var(--dim); }
.live-actions .prim { background: var(--accent); border-color: var(--accent); color: #07201D; font-weight: 620; }
.live-actions .dang { color: var(--hot); }
.foot-note { font-size: 10.5px; color: var(--dim); margin-top: 9px; }
kbd { background: var(--panel2); border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px; font-size: 10px; }
/* окно целиком влезает на любой экран — не режем картинку */
@media (max-height: 820px) { body { padding: 8px; } .window, .win { transform: scale(.78); } }
@media (max-height: 700px) { .window, .win { transform: scale(.66); } }
@media (max-height: 600px) { .window, .win { transform: scale(.56); } }
</style>
</head>
<body>
<div class="window">
<!-- ============ РЕЙКА ============ -->
<nav class="rail">
<button class="rail-btn active" title="Диалоги">💬<span class="dot-badge">3</span></button>
<button class="rail-btn" title="Память агента">🧠</button>
<button class="rail-btn" title="Инструменты">⚙</button>
<div class="rail-sep"></div>
<button class="rail-btn" title="Новый диалог">+</button>
<div class="rail-spacer"></div>
<button class="rail-btn" title="Настройки">⚙︎</button>
</nav>
<!-- ============ СПИСОК ДИАЛОГОВ ============ -->
<section class="list">
<div class="l-head">
<h1>Диалоги</h1>
<p>локальный агент · 3 непрочитанных</p>
</div>
<input class="search" placeholder="Поиск…">
<div class="rows">
<div class="row active">
<div class="av">АГ</div>
<div class="mid">
<div class="nm"><span>agentik</span><span class="tm">14:22</span></div>
<div class="pv">Собрал отчёт, жду правок</div>
</div>
</div>
<div class="row">
<div class="av">ОЧ</div>
<div class="mid">
<div class="nm"><span>Отчёт за квартал</span><span class="tm">12:05</span></div>
<div class="pv">Файл лежит на диске</div>
</div>
</div>
<div class="row">
<div class="av">РЗ</div>
<div class="mid">
<div class="nm"><span>Разбор логов</span><span class="tm">вчера</span></div>
<div class="pv">Нашёл 3 ошибки 502</div>
</div>
</div>
<div class="row">
<div class="av">ПО</div>
<div class="mid">
<div class="nm"><span>Solana-бэкенд</span><span class="tm">вчера</span></div>
<div class="pv">Девайс-нет стартовал</div>
</div>
</div>
<div class="row">
<div class="av">КН</div>
<div class="mid">
<div class="nm"><span>Книги</span><span class="tm">пн</span></div>
<div class="pv">Разложил по полкам</div>
</div>
</div>
</div>
</section>
<!-- ============ ЧАТ ============ -->
<main class="main">
<header class="m-head">
<div>
<div class="t">agentik</div>
<div class="s">на связи · codding-big</div>
</div>
<div class="ghost">
<button>Стоп</button>
<button>Очистить</button>
</div>
</header>
<div class="thread" id="thread">
<div class="b u">Посмотри логи за вчера<span class="tm">14:10</span></div>
<div class="b a">Проверил: 3 ошибки 502 около 04:20, все — обрыв соединения с upstream. Виновник — таймаут прокси. Поправить?<span class="tm">14:12</span></div>
<div class="voice-b">
<button class="play" onclick="togglePlay(this)">▶</button>
<div class="vwave" id="pastWave"></div>
<span class="vdur">0:07</span>
</div>
<div class="b a">Понял: увеличить таймаут до 60 секунд и перезапустить прокси. Сделал — прокси поднят заново, ошибок за последний час нет.<span class="tm">14:20</span></div>
</div>
<div class="composer">
<div class="live-asr" id="liveAsr">
<div class="top"><span class="blink"></span> ЗАПИСЬ · <span id="liveTime">0:00</span></div>
<div class="big-wave" id="bigWave"></div>
<div class="asr-line"><span id="asrLine"></span><span class="cursor"></span></div>
<div class="live-actions">
<button class="prim" onclick="stopRec(false)">Отправить</button>
<button onclick="insertAsText()">В поле ввода</button>
<button class="dang" onclick="stopRec(true)">Отменить</button>
</div>
</div>
<div class="mic-zone">
<button class="mic-big" id="micBtn" title="Запись (Ctrl+R, удержание пробела)">🎙</button>
<div class="mic-side">
<div class="mic-hint"><b>Говорите</b> — текст появится в поле ниже. Можно править перед отправкой.</div>
<div class="type-row">
<textarea id="ta" placeholder="Или напишите руками…"></textarea>
<button class="go" id="goBtn" disabled>Отправить</button>
</div>
</div>
</div>
<div class="foot-note">
<kbd>Ctrl</kbd>+<kbd>R</kbd> — начать/стоп · <kbd>Пробел</kbd> (удержание) — говорить · <kbd>Enter</kbd> — отправить · <kbd>Esc</kbd> — отменить запись
</div>
</div>
</main>
</div>
<script>
const ta = document.getElementById('ta'), goBtn = document.getElementById('goBtn');
const micBtn = document.getElementById('micBtn'), liveAsr = document.getElementById('liveAsr');
const thread = document.getElementById('thread');
document.getElementById('pastWave').innerHTML =
Array.from({length: 34}, () => '<i style="height:' + (18 + Math.random()*80) + '%"></i>').join('');
document.getElementById('bigWave').innerHTML = Array.from({length: 40}, () => '<i></i>').join('');
ta.addEventListener('input', () => {
goBtn.disabled = ta.value.trim() === '';
goBtn.classList.toggle('active', ta.value.trim() !== '');
ta.style.height = '40px'; ta.style.height = Math.min(ta.scrollHeight, 110) + 'px';
});
function send() {
const t = ta.value.trim(); if (!t) return;
add(t, 'u'); ta.value = ''; ta.style.height = '40px';
goBtn.disabled = true; goBtn.classList.remove('active');
setTimeout(() => add('Выполняю: «' + t + '».', 'a'), 800);
}
function add(text, who) {
const d = document.createElement('div'); d.className = 'b ' + who;
const n = new Date();
d.innerHTML = text + '<span class="tm">' + n.getHours() + ':' + String(n.getMinutes()).padStart(2,'0') + '</span>';
thread.appendChild(d); thread.scrollTop = thread.scrollHeight;
}
goBtn.onclick = send;
ta.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } });
// ---- запись с живой расшифровкой ----
const PHRASE = 'посмотри логи за вчерашний день и скажи что там с ошибками';
let tmr = null, sec = 0, wordIdx = 0;
function startRec() {
sec = 0; wordIdx = 0;
liveAsr.classList.add('on'); micBtn.classList.add('on');
document.getElementById('asrLine').textContent = '';
tmr = setInterval(() => {
sec++;
document.getElementById('liveTime').textContent = '0:' + String(sec).padStart(2,'0');
const words = PHRASE.split(' ');
if (wordIdx < words.length) {
document.getElementById('asrLine').textContent = words.slice(0, ++wordIdx).join(' ');
}
}, 450);
}
function stopRec(cancel) {
clearInterval(tmr);
liveAsr.classList.remove('on'); micBtn.classList.remove('on');
if (cancel) return;
const text = document.getElementById('asrLine').textContent;
if (!text) return;
if (sec >= 2) { // короткие — сразу в чат, длинные — в поле
add(text, 'u');
setTimeout(() => add('Записал голосом, разбираю…', 'a'), 700);
}
}
function insertAsText() {
ta.value = (ta.value ? ta.value + ' ' : '') + document.getElementById('asrLine').textContent;
ta.dispatchEvent(new Event('input')); ta.focus(); stopRec(true);
}
function togglePlay(btn) { btn.textContent = btn.textContent === '▶' ? '❚❚' : '▶'; }
micBtn.onclick = () => liveAsr.classList.contains('on') ? stopRec(false) : startRec();
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.key.toLowerCase() === 'r') { e.preventDefault(); micBtn.click(); }
if (e.key === 'Escape' && liveAsr.classList.contains('on')) stopRec(true);
});
</script>
</body>
</html>
-25
View File
@@ -1,25 +0,0 @@
# Вариант 3 — Three-pane / Command center
## Суть
Три панели: диалоги, чат и **состояние агента**. Работа агента видна не как текст
в переписке, а как отдельные раскрывающиеся шаги.
## Ключевые решения
- **Раскладка:** 268 + центр + 306. Третья панель — модель, контекст, инструменты,
память агента, файлы сессии.
- **Шаг агента** — сворачиваемый блок: что делал, сколько заняло, детали по клику.
Это то, чем агент отличается от собеседника, и здесь оно вынесено в структуру.
- **Кнопка записи:** компактная рядом с «Отправить»; при записи снизу появляется
строка: квадрат «стоп», индикатор уровня, текущая расшифровка, таймер.
- **Короткая диктовка** (от 2 с) уходит сразу, текст остаётся в поле.
## Что хорошо
- Видно, что агент делает прямо сейчас и сколько это стоит.
- Удобно на широком экране, ничего не надо прятать.
## Что слабо
- На узком экране (ноутбук 1366) третью панель придётся убирать.
- Настройки сессии — не то, на что хочется смотреть постоянно.
## Кому
Тому, кто держит агента включённым весь день и следит за расходом.
-345
View File
@@ -1,345 +0,0 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Вариант 3 — Three-pane / Command center</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0F1115; --panel: #161A21; --panel2: #1E242D; --panel3: #252C37; --line: #2A323D;
--fg: #ECEFF3; --dim: #808FA0; --accent: #7C8CF8; --ok: #4ADE80; --warn: #FBBF24; --hot: #F87171;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
background: #06080A; color: var(--fg); line-height: 1.45;
display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 22px;
}
.win { width: 1360px; height: 780px; background: var(--bg); border-radius: 13px; overflow: hidden;
border: 1px solid var(--line); display: flex; flex-direction: column; box-shadow: 0 26px 90px rgba(0,0,0,.7); }
/* ---- строка меню-заголовка ---- */
.titlebar { height: 42px; flex: 0 0 42px; display: flex; align-items: center; gap: 12px;
padding: 0 16px; background: var(--panel); border-bottom: 1px solid var(--line); }
.lights { display: flex; gap: 7px; }
.lights i { width: 11px; height: 11px; border-radius: 50%; }
.lights i:nth-child(1) { background: #FF5F57; } .lights i:nth-child(2) { background: #FEBC2E; }
.lights i:nth-child(3) { background: #28C840; }
.tb-title { font-size: 12.5px; color: var(--dim); }
.tb-title b { color: var(--fg); font-weight: 550; }
.tb-right { margin-left: auto; display: flex; align-items: center; gap: 14px; font-size: 11.5px; color: var(--dim); }
.pill { background: var(--panel2); border: 1px solid var(--line); border-radius: 20px; padding: 3px 11px;
display: flex; align-items: center; gap: 6px; }
.pill .d { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.body { flex: 1; display: flex; min-height: 0; }
/* ========== ПАНЕЛЬ 1: чаты ========== */
.p1 { width: 268px; flex: 0 0 268px; background: var(--panel); border-right: 1px solid var(--line);
display: flex; flex-direction: column; }
.p-head { padding: 14px 15px 9px; display: flex; align-items: center; justify-content: space-between; }
.p-head h2 { font-size: 11px; letter-spacing: 1.3px; text-transform: uppercase; color: var(--dim); font-weight: 650; }
.p-head button { background: var(--accent); border: none; color: #fff; border-radius: 7px;
width: 24px; height: 24px; cursor: pointer; font-size: 15px; line-height: 1; }
.f-tabs { display: flex; gap: 4px; padding: 0 12px 10px; flex-wrap: wrap; }
.f-tab { font-size: 11px; padding: 4px 9px; border-radius: 7px; color: var(--dim); background: var(--panel2);
cursor: pointer; border: 1px solid transparent; }
.f-tab.on { color: var(--fg); border-color: var(--accent); background: rgba(124,140,248,.14); }
.q { margin: 0 12px 9px; padding: 7px 11px; border-radius: 8px; border: 1px solid var(--line);
background: var(--bg); color: var(--fg); font-size: 12.5px; outline: none; }
.q:focus { border-color: var(--accent); } .q::placeholder { color: var(--dim); }
.cf { flex: 1; overflow-y: auto; padding: 0 8px 12px; }
.c { display: flex; gap: 10px; padding: 9px 10px; border-radius: 9px; cursor: pointer; }
.c:hover { background: var(--panel2); }
.c.on { background: var(--panel2); box-shadow: inset 2px 0 0 var(--accent); }
.c .a { width: 32px; height: 32px; flex: 0 0 32px; border-radius: 9px; display: grid; place-items: center;
font-size: 11.5px; font-weight: 700; background: var(--panel3); color: var(--accent); }
.c .m { flex: 1; min-width: 0; }
.c .n { font-size: 13px; font-weight: 550; display: flex; justify-content: space-between; gap: 6px; }
.c .n span.t { font-size: 10px; color: var(--dim); font-weight: 400; }
.c .p { font-size: 11.5px; color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
.c.new .n { color: var(--accent); }
/* ========== ПАНЕЛЬ 2: диалог ========== */
.p2 { flex: 1; display: flex; flex-direction: column; min-width: 0; border-right: 1px solid var(--line); }
.p2h { height: 54px; flex: 0 0 54px; display: flex; align-items: center; gap: 11px; padding: 0 18px;
border-bottom: 1px solid var(--line); }
.p2h .nm { font-size: 14px; font-weight: 600; }
.p2h .sub { font-size: 11px; color: var(--dim); }
.p2h .acts { margin-left: auto; display: flex; gap: 6px; }
.p2h .acts button { background: var(--panel2); border: 1px solid var(--line); color: var(--dim);
border-radius: 7px; padding: 5px 11px; font-size: 11.5px; cursor: pointer; }
.p2h .acts button:hover { color: var(--fg); }
.flow { flex: 1; overflow-y: auto; padding: 18px 22px; display: flex; flex-direction: column; gap: 11px; }
.u { align-self: flex-end; max-width: 78%; background: #2A3350; padding: 9px 13px; border-radius: 12px 12px 3px 12px;
font-size: 13.5px; }
.a { align-self: flex-start; max-width: 78%; background: var(--panel2); padding: 9px 13px;
border-radius: 12px 12px 12px 3px; font-size: 13.5px; }
.tm { font-size: 10px; color: var(--dim); margin-top: 4px; }
/* свёрнутый шаг работы агента — то, что отличает агента от чата */
.step { align-self: flex-start; width: 100%; max-width: 92%; background: var(--panel);
border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.step-h { display: flex; align-items: center; gap: 9px; padding: 8px 12px; cursor: pointer; font-size: 12.5px; }
.step-h:hover { background: var(--panel2); }
.step-h .ico { color: var(--accent); }
.step-h .nm { color: var(--fg); font-weight: 550; }
.step-h .stat { margin-left: auto; font-size: 10.5px; color: var(--ok); }
.step-h .chev { color: var(--dim); font-size: 10px; }
.step-b { display: none; padding: 0 12px 11px 34px; font-size: 12px; color: var(--dim); }
.step.open .step-b { display: block; }
.step-b code { color: #A8B6C8; background: var(--bg); border: 1px solid var(--line); border-radius: 5px;
padding: 1px 6px; font-size: 11px; }
/* ========== ПАНЕЛЬ 3: состояние агента ========== */
.p3 { width: 306px; flex: 0 0 306px; background: var(--panel); display: flex; flex-direction: column; }
.sec { border-bottom: 1px solid var(--line); padding: 13px 15px; }
.sec h3 { font-size: 10.5px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--dim);
font-weight: 650; margin-bottom: 10px; display: flex; align-items: center; gap: 7px; }
.kv { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 7px; }
.kv span:first-child { color: var(--dim); }
.kv span:last-child { font-weight: 550; }
.bar { height: 5px; border-radius: 3px; background: var(--panel3); overflow: hidden; margin-top: 4px; }
.bar i { display: block; height: 100%; background: var(--accent); }
.tool-chip { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 6px 0; }
.tool-chip .s { width: 7px; height: 7px; border-radius: 50%; }
.tool-chip .s.run { background: var(--warn); animation: p 1s infinite; }
.tool-chip .s.ok { background: var(--ok); }
@keyframes p { 50% { opacity: .3; } }
.mem { font-size: 11.5px; color: var(--dim); padding: 7px 9px; background: var(--panel2);
border-radius: 8px; margin-bottom: 6px; line-height: 1.4; }
.mem b { color: var(--fg); font-weight: 550; display: block; font-size: 11.5px; margin-bottom: 2px; }
/* ========== КОМПОЗЕР (низ панели 2) ========== */
.comp { border-top: 1px solid var(--line); background: var(--panel); padding: 13px 18px 14px; }
.rec-state { display: none; align-items: center; gap: 12px; background: var(--bg); border: 1px solid var(--hot);
border-radius: 10px; padding: 10px 13px; margin-bottom: 10px; }
.rec-state.on { display: flex; }
.stop-rec { width: 34px; height: 34px; flex: 0 0 34px; border-radius: 50%; background: var(--hot);
border: none; color: #fff; cursor: pointer; display: grid; place-items: center; font-size: 12px; }
.sq { width: 11px; height: 11px; background: #fff; border-radius: 2px; }
.meter { flex: 1; display: flex; align-items: center; gap: 3px; height: 26px; }
.meter i { width: 3px; background: var(--hot); border-radius: 2px; height: 25%; animation: mn .9s infinite ease-in-out; }
.meter i:nth-child(2n) { animation-delay: .12s; } .meter i:nth-child(3n) { animation-delay: .24s; }
@keyframes mn { 0%,100% { height: 20%; } 50% { height: 100%; } }
.rt { font-variant-numeric: tabular-nums; font-size: 12.5px; color: var(--hot); font-weight: 600; }
.rec-state .live-txt { font-size: 11.5px; color: var(--dim); max-width: 260px; overflow: hidden;
text-overflow: ellipsis; white-space: nowrap; }
.crow { display: flex; align-items: flex-end; gap: 8px; }
.cbox { flex: 1; display: flex; align-items: flex-end; background: var(--bg); border: 1px solid var(--line);
border-radius: 10px; padding: 9px 12px; }
.cbox:focus-within { border-color: var(--accent); }
.cbox textarea { flex: 1; background: transparent; border: none; outline: none; color: var(--fg);
font-family: inherit; font-size: 13.5px; resize: none; height: 21px; max-height: 110px; line-height: 1.45; }
.cbox textarea::placeholder { color: var(--dim); }
.ic { width: 36px; height: 36px; border-radius: 9px; border: 1px solid var(--line); background: var(--bg);
color: var(--dim); cursor: pointer; display: grid; place-items: center; font-size: 15px; flex: 0 0 36px; }
.ic:hover { color: var(--fg); }
.ic.rec { border-color: var(--accent); color: var(--accent); }
.ic.rec.live { background: var(--hot); border-color: var(--hot); color: #fff; animation: rp 1.4s infinite; }
@keyframes rp { 0%,100% { box-shadow: 0 0 0 0 rgba(248,113,113,.5); } 50% { box-shadow: 0 0 0 9px rgba(248,113,113,0); } }
.send { height: 36px; padding: 0 16px; border-radius: 9px; border: none; background: var(--accent);
color: #fff; font-size: 12.5px; font-weight: 600; cursor: pointer; }
.send:disabled { background: var(--panel2); color: var(--dim); cursor: default; }
.keys { font-size: 10.5px; color: var(--dim); margin-top: 9px; display: flex; gap: 14px; }
kbd { background: var(--panel2); border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px; font-size: 10px; }
/* окно целиком влезает на любой экран — не режем картинку */
@media (max-height: 820px) { body { padding: 8px; } .window, .win { transform: scale(.78); } }
@media (max-height: 700px) { .window, .win { transform: scale(.66); } }
@media (max-height: 600px) { .window, .win { transform: scale(.56); } }
</style>
</head>
<body>
<div class="win">
<div class="titlebar">
<div class="lights"><i></i><i></i><i></i></div>
<div class="tb-title">agentik-desktop — <b>agentik · рабочий</b></div>
<div class="tb-right">
<span class="pill"><span class="d"></span> агент на связи</span>
<span>Память: 128 заметок</span>
</div>
</div>
<div class="body">
<!-- ======== ПАНЕЛЬ 1 ======== -->
<aside class="p1">
<div class="p-head"><h2>Диалоги</h2><button title="Новый">+</button></div>
<div class="f-tabs">
<div class="f-tab on">Все</div><div class="f-tab">Работа</div>
<div class="f-tab">Инфра</div><div class="f-tab">Дом</div><div class="f-tab">+</div>
</div>
<input class="q" placeholder="Поиск диалога…">
<div class="cf">
<div class="c on"><div class="a">АГ</div><div class="m">
<div class="n"><span>agentik · рабочий</span><span class="t">14:22</span></div>
<div class="p">Собрал отчёт, жду правок</div></div></div>
<div class="c new"><div class="a">ОЧ</div><div class="m">
<div class="n"><span>Отчёт за квартал</span><span class="t">12:05</span></div>
<div class="p">Файл лежит на диске</div></div></div>
<div class="c"><div class="a">РЗ</div><div class="m">
<div class="n"><span>Разбор логов nginx</span><span class="t">вчера</span></div>
<div class="p">Нашёл 3 ошибки 502</div></div></div>
<div class="c"><div class="a">ПО</div><div class="m">
<div class="n"><span>Solana-бэкенд</span><span class="t">вчера</span></div>
<div class="p">Девайс-нет стартовал</div></div></div>
<div class="c"><div class="a">КН</div><div class="m">
<div class="n"><span>Книги</span><span class="t">пн</span></div>
<div class="p">Разложил по полкам</div></div></div>
</div>
</aside>
<!-- ======== ПАНЕЛЬ 2 ======== -->
<section class="p2">
<div class="p2h">
<div>
<div class="nm">agentik · рабочий</div>
<div class="sub">codding-big · контекст 34%</div>
</div>
<div class="acts"><button>Стоп</button><button>Очистить</button></div>
</div>
<div class="flow" id="flow">
<div class="u">Разберись, почему CI красный, и выпусти релиз 3<span class="tm">14:05</span></div>
<div class="step open" onclick="this.classList.toggle('open')">
<div class="step-h"><span class="ico">🔍</span><span class="nm">Поиск причины</span>
<span class="stat">готово · 4 с</span><span class="chev">▼</span></div>
<div class="step-b">Нашёл: в именах тестов длинное тире, сборка идёт в ASCII.<br>
<code>InvalidPathException: unmappable characters</code></div>
</div>
<div class="step" onclick="this.classList.toggle('open')">
<div class="step-h"><span class="ico">✎</span><span class="nm">Правка 16 имён тестов</span>
<span class="stat">готово · 12 с</span><span class="chev">▼</span></div>
<div class="step-b">Заменено на обычный дефис в 4 файлах. Ничего другого не тронуто.</div>
</div>
<div class="step" onclick="this.classList.toggle('open')">
<div class="step-h"><span class="ico">⚙</span><span class="nm">Сборка и проверка</span>
<span class="stat">готово · 1 м 10 с</span><span class="chev">▼</span></div>
<div class="step-b">14 модулей, тесты зелёные, jar-ы собраны.</div>
</div>
<div class="a">Готово. CI зелёный, релиз «3» опубликован: 101 артефакт в Nexus.<span class="tm">14:22</span></div>
</div>
<div class="comp">
<div class="rec-state" id="recState">
<button class="stop-rec" onclick="stopRec(false)"><span class="sq"></span></button>
<div class="meter" id="meter"></div>
<span class="live-txt" id="liveTxt">слушаю…</span>
<span class="rt" id="rt">0:00</span>
<button class="ic" onclick="stopRec(true)" title="Отменить">✕</button>
</div>
<div class="crow">
<div class="cbox"><textarea id="ta" placeholder="Сообщение агенту…"></textarea></div>
<button class="ic rec" id="recBtn" title="Голосовое (Ctrl+R)">🎙</button>
<button class="send" id="sendBtn" disabled>Отправить</button>
</div>
<div class="keys">
<span><kbd>Ctrl</kbd>+<kbd>R</kbd> запись</span>
<span><kbd>Enter</kbd> отправить</span>
<span><kbd>Shift</kbd>+<kbd>Enter</kbd> перенос</span>
</div>
</div>
</section>
<!-- ======== ПАНЕЛЬ 3 ======== -->
<aside class="p3">
<div class="sec">
<h3>Сессия</h3>
<div class="kv"><span>Модель</span><span>codding-big</span></div>
<div class="kv"><span>Контекст</span><span>34 %</span></div>
<div class="bar"><i style="width:34%"></i></div>
<div class="kv" style="margin-top:9px"><span>Ходов</span><span>18</span></div>
<div class="kv"><span>Стоимость</span><span>0.42 ₽</span></div>
</div>
<div class="sec">
<h3>Инструменты</h3>
<div class="tool-chip"><span class="s ok"></span> shell · <span style="color:var(--dim)">готов</span></div>
<div class="tool-chip"><span class="s ok"></span> read_file · <span style="color:var(--dim)">готов</span></div>
<div class="tool-chip"><span class="s run"></span> write_file · <span style="color:var(--dim)">работает</span></div>
<div class="tool-chip"><span class="s ok"></span> memory · <span style="color:var(--dim)">готов</span></div>
</div>
<div class="sec">
<h3>Память агента</h3>
<div class="mem"><b>Про CI agentik</b>длинное тире в именах тестов ломает сборку в ASCII</div>
<div class="mem"><b>Про Nexus</b>публикация идёт через общий action devops/publish</div>
</div>
<div class="sec" style="border-bottom:none">
<h3>Файлы сессии</h3>
<div class="kv"><span>standalone-3.jar</span><span style="color:var(--dim)">240 МБ</span></div>
<div class="kv"><span>agentik-cli-3.jar</span><span style="color:var(--dim)">7.2 МБ</span></div>
</div>
</aside>
</div>
</div>
<script>
const ta = document.getElementById('ta'), sendBtn = document.getElementById('sendBtn');
const flow = document.getElementById('flow');
const recBtn = document.getElementById('recBtn'), recState = document.getElementById('recState');
const liveTxt = document.getElementById('liveTxt');
document.getElementById('meter').innerHTML = Array.from({length: 34}, () => '<i></i>').join('');
ta.addEventListener('input', () => {
sendBtn.disabled = ta.value.trim() === '';
ta.style.height = '21px'; ta.style.height = Math.min(ta.scrollHeight, 110) + 'px';
});
function send() {
const t = ta.value.trim(); if (!t) return;
add(t, 'u'); ta.value = ''; ta.style.height = '21px'; sendBtn.disabled = true;
setTimeout(() => {
const s = document.createElement('div'); s.className = 'step';
s.onclick = () => s.classList.toggle('open');
s.innerHTML = '<div class="step-h"><span class="ico">⚙</span><span class="nm">Выполняю</span>' +
'<span class="stat">идёт…</span><span class="chev">▼</span></div>' +
'<div class="step-b">Задача: ' + t + '</div>';
flow.appendChild(s);
setTimeout(() => {
s.querySelector('.stat').textContent = 'готово · 3 с';
s.querySelector('.stat').style.color = 'var(--ok)';
s.classList.add('open');
add('Сделано: ' + t, 'a');
}, 1400);
}, 300);
}
function add(text, who) {
const d = document.createElement('div'); d.className = who;
const n = new Date();
d.innerHTML = text + '<span class="tm">' + n.getHours() + ':' + String(n.getMinutes()).padStart(2,'0') + '</span>';
flow.appendChild(d); flow.scrollTop = flow.scrollHeight;
}
sendBtn.onclick = send;
ta.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } });
const P = 'разберись с красным си ай и выпусти новый релиз'.split(' ');
let tm = null, sec = 0, wi = 0;
function startRec() {
sec = 0; wi = 0; recState.classList.add('on'); recBtn.classList.add('live');
tm = setInterval(() => {
sec++; document.getElementById('rt').textContent = '0:' + String(sec).padStart(2,'0');
if (wi < P.length) liveTxt.textContent = P.slice(0, ++wi).join(' ');
}, 500);
}
function stopRec(cancel) {
clearInterval(tm); recState.classList.remove('on'); recBtn.classList.remove('live');
if (cancel) return;
const t = liveTxt.textContent;
if (t && sec >= 2) { add(t, 'u'); setTimeout(() => add('Принято голосом, начинаю.', 'a'), 800); }
}
recBtn.onclick = () => recState.classList.contains('on') ? stopRec(false) : startRec();
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.key.toLowerCase() === 'r') { e.preventDefault(); recBtn.click(); }
if (e.key === 'Escape' && recState.classList.contains('on')) stopRec(true);
});
</script>
</body>
</html>
-73
View File
@@ -1,73 +0,0 @@
# Вариант 4 — Настройки: агенты
> **МАКЕТ ОДОБРЕН 2026-09-19.** Одобренная версия — `approved/004-settings-agents.html`
> (без панели-переключателя и без фона основного окна). Этот файл — черновик
> со всеми состояниями и заметками.
## Суть
Два окна друг над другом. Первое — настройки: список агентов и форма нового.
Второе, поверх — **проверка связи**: нажал «Проверить связь» и она идёт
в отдельном окне, а не строкой внутри формы.
## Окно 1: список агентов
- У каждого агента: имя, адрес, состояние — «на связи» зелёным, «не отвечает» серым,
ошибка красным. Видно сразу, а не спрятано в глубине.
- Кнопки в строке: **проверить связь** (⇄), **изменить** (✎), **удалить** (🗑).
Проверка доступна и для уже добавленного агента — из этого же списка.
- Внизу пунктирная «+ Добавить агента».
## Окно 1, вид 2: форма нового агента
- **Название** — с пояснением, что имя придумывает пользователь: сервер своего
имени не сообщает (это из исходников, не догадка).
- **Адрес сервера** — целиком, вместе с путём `/agentik`. Прямо в строке адреса
кнопка **«Проверить связь»**.
- **Чем отличать в списке: цвет или картинка.** Переключатель, выбирается что-то одно.
- **Цвет** — простой выбор из пяти.
- **Картинка** — файл с диска: круглый превью, «Выбрать файл…» и «Убрать».
Картинка показывается там же, где был бы цвет: в списке агентов, в диалогах,
в шапке чата.
- ⚠️ **Картинку копировать себе, а не брать по исходному адресу.** Выбранный файл
сохраняется внутрь клиента; дальше используется только эта копия. Если держать
ссылку на исходный путь, значок пропадёт, когда файл удалят, переименуют или
вынут флешку. То же для картинки по ссылке: скачать и положить себе.
## Окно 2: проверка связи (отдельное, поверх настроек)
Три состояния, сменяются в одном окне:
1. **Идёт** — крутящееся кольцо, «Спрашиваю агента…», под ним адрес.
2. **Отвечает** — зелёная галочка, «Агент отвечает». Подробности: ответ на запрос,
адрес, число диалогов, отклик в миллисекундах. Подсказка «Адрес рабочий».
Кнопка внизу — «Добавить агента» (доступна).
3. **Не отвечает** — красный крестик. Подробности: нет ответа, отклик истёк.
Подсказка: проверить адрес, что агент запущен, что порт открыт и путь `/agentik`
указан целиком. Кнопка внизу заблокирована — добавить нельзя.
Внизу окна всегда есть **«Проверить снова»**. Закрытие окна возвращает к настройкам,
а не выбрасывает из них.
## Почему проверка отдельным окном — лучше
- Места больше: видно и результат, и подробности, и объяснение, что делать дальше.
- Основную форму не дёргает: пока проверяешь — поля на месте, ничего не сбилось.
- Одно и то же окно годится и для формы, и для уже добавленного агента.
## На чём основана проверка (не выдумка)
У сервера агента есть ответ на `GET /agentik/health` (отдаёт «ok») и запрос
списка диалогов. Оба безопасные — ничего не меняют. Адреса взяты из исходников.
⚠️ **Живого агента сейчас не поднято** — на порту 8080 отвечает чужой сервер (404).
Поэтому на настоящем агенте проверка ещё не гонялась: в макете показаны все три
состояния, но замеры времени и число диалогов — условные.
## Фон: папки вернулись
В первом варианте над списком диалогов есть **папки** — «Все чаты», «Работа», «Дом»,
«+». В прошлой версии этого макета я их потерял. Теперь на фоне видны и папки,
и поиск, и значок непрочитанных — как в одобренном варианте 1.
## Плюс: список диалогов с двумя агентами
Диалоги сгруппированы по агентам, у группы цветная точка, у каждой строки цветная
полоска, цвет аватара совпадает с цветом агента из настроек.
## Что слабо / открытые вопросы
- Одного цвета для различения может оказаться мало.
- Где хранится список агентов — не решено.
- Пароля/ключа доступа у библиотеки нет — поля для него нет.
-625
View File
@@ -1,625 +0,0 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Настройки: агенты</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #121218; --panel: #17212B; --panel2: #1C2733; --line: #232B36;
--fg: #EBEBEB; --dim: #768C9E; --accent: #6AB2F2; --badge: #5EB5F7;
--err: #EC3942; --ok: #63C88A; --user-bubble: #2B5278; --bot-bubble: #1C2733;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
background: #0A0A0F; color: var(--fg); line-height: 1.45;
display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 24px;
}
.window {
width: 1180px; height: 740px; background: var(--bg); border-radius: 12px;
display: flex; overflow: hidden; border: 1px solid var(--line); position: relative;
box-shadow: 0 24px 80px rgba(0,0,0,.6);
}
/* ============ фон: обычный клиент ============ */
.sidebar { width: 320px; background: var(--panel); border-right: 1px solid var(--line); display: flex; flex-direction: column; }
.sb-head { padding: 14px 16px 10px; }
.sb-title { font-size: 17px; font-weight: 600; display: flex; align-items: center; justify-content: space-between; }
.search {
margin: 0 16px 10px; padding: 8px 11px; border-radius: 9px; border: 1px solid transparent;
background: var(--panel2); color: var(--fg); font-size: 13px; outline: none; font-family: inherit;
}
.search::placeholder { color: var(--dim); }
/* ── папки: как в выбранном варианте 1 ── */
.folders { display: flex; gap: 6px; padding: 0 16px 10px; overflow-x: auto; }
.folder {
padding: 5px 11px; border-radius: 14px; font-size: 12px; color: var(--dim);
background: var(--panel2); cursor: pointer; white-space: nowrap; border: 1px solid transparent;
}
.folder.active { color: var(--fg); border-color: var(--accent); background: rgba(106,178,242,.12); }
.folder .cnt { color: var(--badge); font-weight: 600; margin-left: 4px; }
.chat-list { flex: 1; overflow-y: auto; padding: 2px 8px 12px; }
.chat-row { display: flex; gap: 11px; padding: 9px 10px; border-radius: 10px; align-items: flex-start; cursor: pointer; }
.chat-row:hover { background: var(--panel2); }
.chat-row.active { background: #233040; }
.avatar {
width: 42px; height: 42px; border-radius: 50%; flex: 0 0 42px; display: grid; place-items: center;
font-size: 15px; font-weight: 600; color: #0F1922; background: var(--accent);
}
.avatar.g2 { background: #86C99A; }
.avatar.g3 { background: #C9A886; }
.chat-mid { flex: 1; min-width: 0; }
.chat-name { font-size: 14px; font-weight: 500; display: flex; justify-content: space-between; gap: 8px; }
.chat-time { font-size: 11px; color: var(--dim); font-weight: 400; }
.chat-prev { font-size: 12.5px; color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.row-tail { flex: 0 0 auto; }
.badge { background: var(--badge); color: #0F1922; font-size: 11px; font-weight: 700;
border-radius: 10px; padding: 1px 7px; }
.main { flex: 1; display: flex; flex-direction: column; }
.chat-head { height: 58px; flex: 0 0 58px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 11px; padding: 0 16px; }
.chat-head .avatar { width: 36px; height: 36px; flex: 0 0 36px; font-size: 13px; }
.head-name { font-size: 14.5px; font-weight: 600; }
.head-sub { font-size: 11.5px; color: var(--dim); }
.head-sub .dot { color: var(--ok); }
.ic-btn { width: 32px; height: 32px; border-radius: 8px; border: none; background: transparent;
color: var(--dim); cursor: pointer; font-size: 16px; margin-left: auto; }
.ic-btn:hover { background: var(--panel2); color: var(--fg); }
.messages { flex: 1; overflow-y: auto; padding: 18px 24px; display: flex; flex-direction: column; gap: 12px; }
.msg { max-width: 74%; padding: 9px 13px; border-radius: 13px; font-size: 13.5px; }
.msg.user { align-self: flex-end; background: var(--user-bubble); border-bottom-right-radius: 4px; }
.msg.bot { align-self: flex-start; background: var(--bot-bubble); border-bottom-left-radius: 4px; }
/* ============ ЗАТЕМНЕНИЕ (одно на оба окна) ============ */
.scrim { position: absolute; inset: 0; background: rgba(0,0,0,.62); z-index: 100; display: none; }
.scrim.on { display: block; }
.scrim.top { background: rgba(0,0,0,.42); z-index: 200; }
/* ============ ОКНО НАСТРОЕК ============ */
.layer { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; pointer-events: none; }
.layer.on { display: flex; }
.layer > * { pointer-events: auto; }
/* порядок слоёв: настройки 150, проверка поверх 250 */
#layerSettings { z-index: 150; }
#layerCheck { z-index: 250; }
.dialog {
width: 620px; min-width: 620px; flex: 0 0 auto; max-height: 620px; background: #17212B;
border: 1px solid var(--line); border-radius: 14px; display: flex; flex-direction: column;
overflow: hidden; box-shadow: 0 26px 80px rgba(0,0,0,.7);
}
.dialog.small { width: 480px; min-width: 480px; max-height: 420px; }
.d-head { display: flex; align-items: center; gap: 11px; padding: 16px 18px 14px; border-bottom: 1px solid var(--line); }
.d-head h2 { font-size: 16px; font-weight: 600; flex: 1; }
.x-btn { width: 30px; height: 30px; border-radius: 8px; border: none; background: transparent;
color: var(--dim); cursor: pointer; font-size: 17px; }
.x-btn:hover { background: var(--panel2); color: var(--fg); }
.d-body { flex: 1; overflow-y: auto; padding: 14px 18px 6px; }
.d-foot { padding: 13px 18px 16px; border-top: 1px solid var(--line); display: flex; gap: 9px; }
/* список агентов */
.agent-card {
display: flex; align-items: center; gap: 12px; padding: 11px 12px; border-radius: 11px;
background: var(--panel2); margin-bottom: 9px; border: 1px solid transparent;
}
.agent-card:hover { border-color: var(--line); }
.agent-card .avatar { width: 40px; height: 40px; flex: 0 0 40px; font-size: 13px; }
.ag-mid { flex: 1; min-width: 0; }
.ag-name { font-size: 14px; font-weight: 550; }
.ag-addr { font-size: 11.5px; color: var(--dim); font-family: ui-monospace, "SF Mono", Menlo, monospace;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 3px; }
.state { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--ok); flex: 0 0 auto; }
.state .d { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.state.off { color: var(--dim); } .state.off .d { background: var(--dim); }
.state.err { color: var(--err); } .state.err .d { background: var(--err); }
.card-acts { display: flex; gap: 4px; flex: 0 0 auto; }
.mini-ic { width: 30px; height: 30px; border-radius: 8px; border: none; background: transparent;
color: var(--dim); cursor: pointer; font-size: 14px; }
.mini-ic:hover { background: var(--panel); color: var(--fg); }
.mini-ic.del:hover { color: var(--err); }
/* форма */
.field { margin-bottom: 15px; }
.field label { display: block; font-size: 12px; color: var(--dim); margin-bottom: 6px; }
.field input {
width: 100%; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line);
background: var(--bg); color: var(--fg); font-size: 13.5px; outline: none; font-family: inherit;
}
.field input:focus { border-color: var(--accent); }
.field input.mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; }
.field .note { font-size: 11px; color: var(--dim); margin-top: 6px; }
.note code { background: var(--panel2); padding: 1px 4px; border-radius: 4px; font-size: 10.5px; }
/* адрес + кнопка проверки в одной строке */
.addr-row { display: flex; gap: 8px; }
.addr-row input { flex: 1; }
.colors { display: flex; gap: 9px; }
.swatch { width: 30px; height: 30px; border-radius: 50%; cursor: pointer; border: 2px solid transparent; }
.swatch.on { border-color: var(--fg); }
/* цвет ИЛИ картинка */
.appearance-tabs { display: flex; gap: 6px; margin-bottom: 11px; }
.appearance-tabs .tab {
padding: 6px 13px; border-radius: 9px; font-size: 12.5px; color: var(--dim);
background: var(--panel2); cursor: pointer; border: 1px solid transparent;
}
.appearance-tabs .tab.on { color: var(--fg); border-color: var(--accent); background: rgba(106,178,242,.12); }
.pane { display: none; }
.pane.on { display: block; }
.pic-row { display: flex; align-items: center; gap: 13px; }
.pic-preview {
width: 54px; height: 54px; border-radius: 50%; flex: 0 0 54px; background: var(--bg);
border: 1px dashed var(--line); display: grid; place-items: center; color: var(--dim);
font-size: 20px; overflow: hidden;
}
.pic-preview.filled { border-style: solid; border-color: var(--line); }
.pic-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pic-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.avatar.pic { background: transparent; padding: 0; overflow: hidden; }
.avatar.pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* кнопки */
.btn { border-radius: 10px; padding: 10px 17px; font-size: 13px; cursor: pointer; border: 1px solid var(--line);
background: transparent; color: var(--fg); font-family: inherit; white-space: nowrap; }
.btn:hover { border-color: var(--dim); }
.btn.prim { background: var(--accent); border-color: var(--accent); color: #0F1922; font-weight: 650; }
.btn.prim:disabled { background: var(--panel2); border-color: var(--line); color: var(--dim); cursor: default; }
.btn.dang { color: var(--err); border-color: rgba(236,57,66,.4); }
.btn.dang:hover { border-color: var(--err); background: rgba(236,57,66,.09); }
.btn.sm { padding: 8px 13px; font-size: 12.5px; }
.btn.wide { width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px;
border-style: dashed; color: var(--dim); }
.btn.wide:hover { color: var(--fg); border-color: var(--dim); }
/* ============ ОКНО ПРОВЕРКИ СВЯЗИ ============ */
.spin { width: 17px; height: 17px; border-radius: 50%; border: 2px solid var(--line);
border-top-color: var(--accent); animation: sp .7s linear infinite; flex: 0 0 17px; }
@keyframes sp { to { transform: rotate(360deg); } }
.probe-head { display: flex; align-items: center; gap: 13px; padding: 4px 0 14px; }
.probe-head .big { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center;
font-size: 19px; flex: 0 0 40px; }
.probe-head.ok .big { background: rgba(99,200,138,.15); color: var(--ok); }
.probe-head.err .big { background: rgba(236,57,66,.15); color: var(--err); }
.probe-head.busy .big { background: transparent; padding: 0; }
.probe-head .txt { flex: 1; }
.probe-head .ttl { font-size: 14px; font-weight: 600; }
.probe-head .sub { font-size: 12px; color: var(--dim); margin-top: 2px; }
.probe-list { font-size: 12px; }
.probe-list div { display: flex; justify-content: space-between; gap: 14px; padding: 7px 0;
border-bottom: 1px solid var(--line); }
.probe-list div:last-child { border-bottom: none; }
.probe-list span { color: var(--dim); }
.probe-list b { font-weight: 550; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; }
.hint-line { font-size: 11.5px; color: var(--dim); margin-top: 12px; padding: 9px 11px;
background: var(--bg); border-radius: 9px; border: 1px solid var(--line); }
/* переключатель состояний — только для показа */
.switcher {
position: fixed; top: 14px; left: 50%; transform: translateX(-50%); display: flex; gap: 7px;
background: rgba(23,33,43,.96); border: 1px solid var(--line); border-radius: 11px; padding: 7px 9px; z-index: 300;
}
.switcher button { background: transparent; border: none; color: var(--dim); font-size: 12px;
padding: 5px 11px; border-radius: 7px; cursor: pointer; font-family: inherit; }
.switcher button.on { background: var(--accent); color: #0F1922; font-weight: 600; }
/* ============ список диалогов с несколькими агентами ============ */
.group-head { display: flex; align-items: center; gap: 8px; padding: 11px 10px 6px; }
.group-head .dot { width: 9px; height: 9px; border-radius: 50%; }
.group-head .nm { font-size: 10.5px; letter-spacing: 1.1px; text-transform: uppercase; color: var(--dim); font-weight: 700; }
.group-head .cnt { font-size: 10.5px; color: var(--dim); margin-left: auto; }
.accent-bar { width: 3px; border-radius: 2px; flex: 0 0 3px; align-self: stretch; }
@media (max-height: 820px) { body { padding: 8px; } .window { transform: scale(.8); } }
@media (max-height: 700px) { .window { transform: scale(.68); } }
</style>
</head>
<body>
<div class="switcher">
<button data-view="list" class="on">Список агентов</button>
<button data-view="add">Добавить агента</button>
<button data-view="check" data-mode="ok">Проверка: отвечает</button>
<button data-view="check" data-mode="err">Проверка: не отвечает</button>
<button data-view="multi">Список диалогов</button>
</div>
<div class="window">
<!-- ===== фон: клиент с папками ===== -->
<aside class="sidebar">
<div class="sb-head">
<div class="sb-title">
<span>Диалоги</span>
<button class="ic-btn" style="margin:0" title="Новый диалог">+</button>
</div>
</div>
<input class="search" placeholder="Поиск диалога…">
<div class="folders">
<div class="folder active">Все чаты <span class="cnt">3</span></div>
<div class="folder">Работа</div>
<div class="folder">Дом</div>
<div class="folder">+</div>
</div>
<div class="chat-list" id="chatArea"></div>
</aside>
<main class="main">
<header class="chat-head">
<div class="avatar">АГ</div>
<div>
<div class="head-name">agentik · рабочий</div>
<div class="head-sub"><span class="dot">●</span> на связи</div>
</div>
<button class="ic-btn" title="Настройки">⚙</button>
</header>
<div class="messages">
<div class="msg user">Проверь, почему падает CI</div>
<div class="msg bot">Нашёл: длинное тире в именах тестов ломает сборку. Починил, CI зелёный.</div>
</div>
</main>
<!-- ===== затемнение под окном настроек ===== -->
<div class="scrim on" id="scrim1"></div>
<!-- ===== ОКНО 1: настройки ===== -->
<div class="layer on" id="layerSettings">
<div class="dialog">
<div class="d-head">
<h2 id="dTitle">Агенты</h2>
<button class="x-btn" onclick="closeAll()">✕</button>
</div>
<!-- ── вид 1: список агентов ── -->
<div class="d-body" id="viewList">
<div class="agent-card">
<div class="avatar">АГ</div>
<div class="ag-mid">
<div class="ag-name">agentik · рабочий</div>
<div class="ag-addr">http://192.168.88.135:8080/agentik</div>
</div>
<div class="state"><span class="d"></span>на связи</div>
<div class="card-acts">
<button class="mini-ic" title="Изменить">✎</button>
<button class="mini-ic del" title="Удалить">🗑</button>
</div>
</div>
<div class="agent-card">
<div class="avatar g2">ДМ</div>
<div class="ag-mid">
<div class="ag-name">домашний</div>
<div class="ag-addr">http://192.168.76.199:8080/agentik</div>
</div>
<div class="state err"><span class="d"></span>не отвечает</div>
<div class="card-acts">
<button class="mini-ic" title="Проверить связь">⇄</button>
<button class="mini-ic" title="Изменить">✎</button>
<button class="mini-ic del" title="Удалить">🗑</button>
</div>
</div>
<button class="btn wide" onclick="show('add')">+ Добавить агента</button>
</div>
<!-- ── вид 2: форма ── -->
<div class="d-body" id="viewAdd" style="display:none">
<div class="field">
<label>Название</label>
<input type="text" value="рабочий" placeholder="как вы его будете звать">
<div class="note">Как называть этого агента в списке. Придумываете сами — сервер своего имени не сообщает.</div>
</div>
<div class="field">
<label>Адрес сервера агента</label>
<div class="addr-row">
<input type="text" class="mono" value="http://192.168.88.135:8080/agentik"
placeholder="http://хост:порт/agentik">
<button class="btn sm" onclick="openCheck('form')">Проверить связь</button>
</div>
<div class="note">Адрес целиком, вместе с путём <code>/agentik</code>. Если агент
на этой же машине — <code>http://localhost:8080/agentik</code>.</div>
</div>
<div class="field">
<label>Как отличать в списке</label>
<div class="appearance-tabs">
<div class="tab on" data-tab="color">Цвет</div>
<div class="tab" data-tab="pic">Картинка</div>
</div>
<!-- цвет -->
<div class="pane on" id="paneColor">
<div class="colors">
<div class="swatch on" style="background:#6AB2F2"></div>
<div class="swatch" style="background:#86C99A"></div>
<div class="swatch" style="background:#C9A886"></div>
<div class="swatch" style="background:#B79AE0"></div>
<div class="swatch" style="background:#E28C8C"></div>
</div>
<div class="note">Цвет остаётся простым выбором: им помечаются диалоги агента
в списке — полоска у строки и точка у группы.</div>
</div>
<!-- картинка -->
<div class="pane" id="panePic">
<div class="pic-row">
<div class="pic-preview" id="picPreview">🖼</div>
<div class="pic-actions">
<button class="btn sm" onclick="pickPic()">Выбрать файл…</button>
<button class="btn sm" id="picClear" onclick="clearPic()" style="display:none">Убрать</button>
</div>
</div>
<div class="note">Картинка копируется <b>внутрь клиента</b> — по исходному
адресу не подтягивается. Иначе значок пропадёт, если файл удалят,
флешку вынут или папку переименуют.</div>
</div>
</div>
</div>
<div class="d-foot" id="footList">
<button class="btn prim" style="flex:1" onclick="closeAll()">Готово</button>
</div>
<div class="d-foot" id="footAdd" style="display:none">
<button class="btn" style="flex:1" onclick="show('list')">Отмена</button>
<button class="btn prim" style="flex:1">Добавить агента</button>
</div>
</div>
</div>
<!-- ===== затемнение под окном проверки ===== -->
<div class="scrim top" id="scrim2"></div>
<!-- ===== ОКНО 2: проверка связи (поверх настроек) ===== -->
<div class="layer" id="layerCheck">
<div class="dialog small">
<div class="d-head">
<h2>Проверка связи</h2>
<button class="x-btn" onclick="closeCheck()">✕</button>
</div>
<div class="d-body">
<div class="probe-head" id="probeHead">
<div class="big" id="probeIcon"></div>
<div class="txt">
<div class="ttl" id="probeTitle">Спрашиваю агента…</div>
<div class="sub" id="probeSub">http://192.168.88.135:8080/agentik</div>
</div>
</div>
<div class="probe-list" id="probeList" style="display:none">
<div><span>Ответ на запрос</span><b id="p1">—</b></div>
<div><span>Отвечает по адресу</span><b id="p2">—</b></div>
<div><span>Диалогов у агента</span><b id="p3">—</b></div>
<div><span>Отклик</span><b id="p4">—</b></div>
</div>
<div class="hint-line" id="probeHint" style="display:none"></div>
</div>
<div class="d-foot">
<button class="btn" style="flex:1" id="probeAgain" onclick="runCheck()">Проверить снова</button>
<button class="btn prim" style="flex:1" id="probeOk" onclick="closeCheck()" disabled>Добавить агента</button>
</div>
</div>
</div>
</div>
<script>
// ---------------- данные списка диалогов ----------------
const CHATS_SINGLE = `
<div class="chat-row active">
<div class="avatar">АГ</div>
<div class="chat-mid">
<div class="chat-name"><span>agentik · рабочий</span><span class="chat-time">14:22</span></div>
<div class="chat-prev">Собрал отчёт, жду правок</div>
</div>
<div class="row-tail"><span class="badge">2</span></div>
</div>
<div class="chat-row">
<div class="avatar g2">ОЧ</div>
<div class="chat-mid">
<div class="chat-name"><span>Отчёт за квартал</span><span class="chat-time">12:05</span></div>
<div class="chat-prev">Файл лежит на диске</div>
</div>
</div>
<div class="chat-row">
<div class="avatar g3">РЗ</div>
<div class="chat-mid">
<div class="chat-name"><span>Разбор логов nginx</span><span class="chat-time">вчера</span></div>
<div class="chat-prev">Нашёл 3 ошибки 502</div>
</div>
</div>`;
const CHATS_MULTI = `
<div class="group-head"><span class="dot" style="background:#6AB2F2"></span>
<span class="nm">рабочий</span><span class="cnt">2 диалога</span></div>
<div class="chat-row active">
<div class="accent-bar" style="background:#6AB2F2"></div>
<div class="avatar">АГ</div>
<div class="chat-mid">
<div class="chat-name"><span>CI и релизы</span><span class="chat-time">14:22</span></div>
<div class="chat-prev">Собрал отчёт, жду правок</div>
</div>
</div>
<div class="chat-row">
<div class="accent-bar" style="background:#6AB2F2"></div>
<div class="avatar">ОЧ</div>
<div class="chat-mid">
<div class="chat-name"><span>Отчёт за квартал</span><span class="chat-time">12:05</span></div>
<div class="chat-prev">Файл лежит на диске</div>
</div>
</div>
<div class="group-head"><span class="dot" style="background:#86C99A"></span>
<span class="nm">домашний</span><span class="cnt">1 диалог</span></div>
<div class="chat-row">
<div class="accent-bar" style="background:#86C99A"></div>
<div class="avatar g2">КН</div>
<div class="chat-mid">
<div class="chat-name"><span>Книги и полки</span><span class="chat-time">вчера</span></div>
<div class="chat-prev">Разложил по авторам</div>
</div>
</div>`;
const chatArea = document.getElementById('chatArea');
chatArea.innerHTML = CHATS_SINGLE;
// ---------------- окна ----------------
function el(id){ return document.getElementById(id); }
function show(v) {
const isList = (v === 'list');
el('viewList').style.display = isList ? 'block' : 'none';
el('viewAdd').style.display = isList ? 'none' : 'block';
el('footList').style.display = isList ? 'flex' : 'none';
el('footAdd').style.display = isList ? 'none' : 'flex';
el('dTitle').textContent = isList ? 'Агенты' : 'Новый агент';
openSettings();
}
function openSettings(){
el('scrim1').classList.add('on');
el('layerSettings').classList.add('on');
}
// закрыть окно настроек (вместе с окном проверки, если оно открыто)
function closeAll(){
el('layerCheck').classList.remove('on');
el('scrim2').classList.remove('on');
el('layerSettings').classList.remove('on');
el('scrim1').classList.remove('on');
}
function closeCheck(){
el('layerCheck').classList.remove('on');
el('scrim2').classList.remove('on');
}
// ---------------- ОКНО ПРОВЕРКИ СВЯЗИ ----------------
// отдельное окно поверх настроек: из формы нового агента или из списка
function openCheck(from, addr) {
el('scrim2').classList.add('on');
el('layerCheck').classList.add('on');
el('probeOk').textContent = (from === 'form') ? 'Добавить агента' : 'Готово';
window.__checkAddr = addr || 'http://192.168.88.135:8080/agentik';
runCheck();
}
function runCheck() {
const head = el('probeHead');
const list = el('probeList');
const hint = el('probeHint');
const icon = el('probeIcon');
const addr = window.__checkAddr || 'http://192.168.88.135:8080/agentik';
head.className = 'probe-head busy';
icon.outerHTML = '<span class="spin" id="probeIcon"></span>';
el('probeTitle').textContent = 'Спрашиваю агента…';
el('probeSub').textContent = addr;
list.style.display = 'none';
hint.style.display = 'none';
el('probeOk').disabled = true;
setTimeout(() => {
const sp = document.getElementById('probeIcon');
sp.outerHTML = '<div class="big" id="probeIcon"></div>';
const fail = (window.__checkMode === 'err');
if (fail) {
el('probeHead').className = 'probe-head err';
el('probeIcon').textContent = '✕';
el('probeTitle').textContent = 'Агент не отвечает';
el('probeSub').textContent = addr;
el('p1').textContent = 'нет ответа'; el('p2').textContent = '—';
el('p3').textContent = '—'; el('p4').textContent = '5000 мс (истекло)';
list.style.display = 'block';
hint.style.display = 'block';
hint.textContent = 'Проверьте адрес и что агент запущен. Убедитесь, что порт открыт и путь /agentik указан целиком.';
el('probeOk').disabled = true;
} else {
el('probeHead').className = 'probe-head ok';
el('probeIcon').textContent = '✓';
el('probeTitle').textContent = 'Агент отвечает';
el('probeSub').textContent = addr;
el('p1').textContent = 'ok'; el('p2').textContent = addr;
el('p3').textContent = '4'; el('p4').textContent = '38 мс';
list.style.display = 'block';
hint.style.display = 'block';
hint.textContent = 'Адрес рабочий. Можно добавлять этого агента.';
el('probeOk').disabled = false;
}
}, 1400);
}
// ---------------- переключатель для показа ----------------
document.querySelectorAll('.switcher button').forEach(b => b.onclick = () => {
document.querySelectorAll('.switcher button').forEach(x => x.classList.remove('on'));
b.classList.add('on');
const v = b.dataset.view;
if (v === 'multi') {
chatArea.innerHTML = CHATS_MULTI;
closeAll();
return;
}
chatArea.innerHTML = CHATS_SINGLE;
if (v === 'list') { show('list'); closeCheck(); }
if (v === 'add') { show('add'); closeCheck(); }
if (v === 'check') {
window.__checkMode = b.dataset.mode || 'ok';
const okAddr = 'http://192.168.88.135:8080/agentik';
const badAddr = 'http://192.168.76.199:8080/agentik';
show(window.__checkMode === 'ok' ? 'add' : 'list');
openCheck('form', window.__checkMode === 'ok' ? okAddr : badAddr);
}
});
document.querySelectorAll('.swatch').forEach(s => s.onclick = () => {
document.querySelectorAll('.swatch').forEach(x => x.classList.remove('on'));
s.classList.add('on');
});
// ---------------- как отличать: цвет ИЛИ картинка ----------------
document.querySelectorAll('.appearance-tabs .tab').forEach(tab => tab.onclick = () => {
document.querySelectorAll('.appearance-tabs .tab').forEach(x => x.classList.remove('on'));
tab.classList.add('on');
const pic = (tab.dataset.tab === 'pic');
el('paneColor').classList.toggle('on', !pic);
el('panePic').classList.toggle('on', pic);
});
// выбор картинки: в настоящем клиенте — системный диалог выбора файла,
// а выбранный файл КОПИРУЕТСЯ в папку клиента (не ссылка на исходный путь)
function pickPic() {
// для макета показываем готовый пример вместо файлового диалога
setPic('data:image/svg+xml;utf8,' + encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" width="54" height="54">
<rect width="54" height="54" fill="#86C99A"/>
<circle cx="27" cy="21" r="8" fill="#121218"/>
<path d="M10 50 Q27 30 44 50 Z" fill="#121218"/>
</svg>`));
}
function setPic(src) {
const p = el('picPreview');
p.innerHTML = `<img src="${src}" alt="">`;
p.classList.add('filled');
el('picClear').style.display = '';
}
function clearPic() {
const p = el('picPreview');
p.innerHTML = '🖼';
p.classList.remove('filled');
el('picClear').style.display = 'none';
}
</script>
</body>
</html>
-72
View File
@@ -1,72 +0,0 @@
# Вариант 5 — Новый диалог: выбор ассистента
> **МАКЕТ ОДОБРЕН 2026-09-19.** Одобренная версия — `approved/005-new-chat-picker.html`.
> Этот файл — черновик со всеми состояниями.
## Суть
Окно, которое появляется при нажатии «+» (новый диалог), **когда ассистентов
больше одного**. Спрашиваем, в каком ассистенте создать диалог.
**Когда окно показывается и когда нет:**
| Сколько ассистентов | Что происходит по «+» |
|---|---|
| **0** | Кнопки «+» **нет вообще** — создавать диалог не в ком |
| **1** | Окно **не показывается**: диалог создаётся сразу в этом ассистенте |
| **2 и больше** | Окно с выбором — этот макет |
## Состояния (переключатель сверху)
1. **Выбор: 2 ассистента** — основной вид. Сверху подсказка «В каком ассистенте
создать диалог?», ниже список. Первый отмечен **по умолчанию** — чтобы создать
диалог, достаточно одного нажатия.
2. **Третий не отвечает** — выбран ассистент без связи. Окно **предупреждает**,
но выбрать даёт: ассистент мог просто перезапускаться. Из списка не выкидываем.
3. **Ассистент один** — окна нет, затемнения нет.
4. **Ассистентов нет** — кнопки «+» нет, пустой список объясняет, что делать,
и ведёт в настройки.
5. **Диалог создан** — новый диалог первым в списке, шапка чата показывает
выбранного ассистента (его цвет и имя).
## Что видно в списке выбора
- **Значок ассистента** — цвет или картинка, как настроено в `004-settings-agents`.
- **Имя** и **адрес сервера** — чтобы различить похожие имена.
- **Состояние связи**: «на связи» зелёным, «не отвечает» красным.
- **Кружок отметки** справа.
- У ассистента без связи предупреждение появляется **при выборе**, а не сразу:
пока не выбран — шума нет.
## Решения
- **Выбран первый по умолчанию.** Открыл окно → сразу «Создать диалог» работает.
Лишнее движение нужно только если нужен не первый.
- **Ассистента без связи выбирать можно.** Запрещать нечестно: связь может
пропасть на секунду, а диалог человек создаёт осознанно. Окно предупреждает
словами, но не мешает.
- **Кнопку «+» при нуле ассистентов не рисуем.** Нажимать нечего — диалог
создавать не в ком.
- **Окно маленькое (480 px) и без вкладок.** Одно действие — один выбор.
Окно настроек (`004`) — 620 px, оно про управление; здесь просто выбор.
## Что НЕ делаем
- Не даём создавать диалог «без ассистента» — по библиотеке `client` диалог
живёт **внутри** агента, отдельно его не создать.
- Не показываем окно ради одного варианта — это лишнее нажатие.
- Не прячем ассистентов без связи (см. решения выше).
## Открытые вопросы
- Что делать, если **все** ассистенты не отвечают: показывать окно со всеми
красными или сразу писать «никто не отвечает»? Сейчас — показываем список.
- Нужен ли в этом окне шорткат «создать и сразу написать», то есть открывать
диалог с фокусом в поле ввода. Пока просто создаём.
- Нужна ли в окне ссылка «добавить нового ассистента» — сейчас её нет, за
добавлением идём в настройки.
## Из чего собрано
Стиль и классы взяты из `004-settings-agents` — тема та же (`#121218`, `#17212B`,
`#6AB2F2`), окно и затемнение устроены так же. Ничего нового не изобреталось.
-436
View File
@@ -1,436 +0,0 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Новый диалог — выбор ассистента</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #121218; --panel: #17212B; --panel2: #1C2733; --line: #232B36;
--fg: #EBEBEB; --dim: #768C9E; --accent: #6AB2F2; --badge: #5EB5F7;
--err: #EC3942; --ok: #63C88A; --user-bubble: #2B5278; --bot-bubble: #1C2733;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
background: #0A0A0F; color: var(--fg); line-height: 1.45;
display: flex; flex-direction: column; align-items: center; justify-content: center;
min-height: 100vh; padding: 24px; gap: 16px;
}
/* ============ переключатель состояний ============ */
.switcher { display: flex; gap: 7px; flex-wrap: wrap; justify-content: center; max-width: 1180px; }
.switcher button {
padding: 7px 13px; border-radius: 9px; border: 1px solid var(--line); background: var(--panel);
color: var(--dim); font-size: 12.5px; cursor: pointer; font-family: inherit;
}
.switcher button:hover { color: var(--fg); }
.switcher button.on { background: var(--accent); border-color: var(--accent); color: #0F1922; font-weight: 600; }
/* ============ окно клиента ============ */
.window {
width: 1180px; height: 740px; background: var(--bg); border-radius: 12px;
display: flex; overflow: hidden; border: 1px solid var(--line); position: relative;
box-shadow: 0 24px 80px rgba(0,0,0,.6);
}
.sidebar { width: 320px; background: var(--panel); border-right: 1px solid var(--line); display: flex; flex-direction: column; }
.sb-head { padding: 14px 16px 10px; }
.sb-title { font-size: 17px; font-weight: 600; display: flex; align-items: center; justify-content: space-between; }
.search {
margin: 0 16px 10px; padding: 8px 11px; border-radius: 9px; border: 1px solid transparent;
background: var(--panel2); color: var(--fg); font-size: 13px; outline: none; font-family: inherit;
}
.search::placeholder { color: var(--dim); }
.folders { display: flex; gap: 6px; padding: 0 16px 10px; overflow-x: auto; }
.folder {
padding: 5px 11px; border-radius: 14px; font-size: 12px; color: var(--dim);
background: var(--panel2); cursor: pointer; white-space: nowrap; border: 1px solid transparent;
}
.folder.active { color: var(--fg); border-color: var(--accent); background: rgba(106,178,242,.12); }
.folder .cnt { color: var(--badge); font-weight: 600; margin-left: 4px; }
.chat-list { flex: 1; overflow-y: auto; padding: 2px 8px 12px; }
.chat-row { display: flex; gap: 11px; padding: 9px 10px; border-radius: 10px; align-items: flex-start; cursor: pointer; }
.chat-row:hover { background: var(--panel2); }
.chat-row.active { background: #233040; }
.avatar {
width: 42px; height: 42px; border-radius: 50%; flex: 0 0 42px; display: grid; place-items: center;
font-size: 15px; font-weight: 600; color: #0F1922; background: var(--accent);
}
.avatar.g2 { background: #86C99A; }
.avatar.g3 { background: #C9A886; }
/* аватар-картинка: клиент показывает СВОЮ копию файла */
.avatar.pic {
background: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40'%3E%3Crect width='40' height='40' fill='%232F4A63'/%3E%3Ccircle cx='20' cy='15' r='6.5' fill='%23DCE6F0'/%3E%3Cpath d='M7 40c0-7.5 5.8-12 13-12s13 4.5 13 12z' fill='%23DCE6F0'/%3E%3C/svg%3E") center/cover;
}
.chat-mid { flex: 1; min-width: 0; }
.chat-name { font-size: 14px; font-weight: 500; display: flex; justify-content: space-between; gap: 8px; }
.chat-time { font-size: 11px; color: var(--dim); font-weight: 400; }
.chat-prev { font-size: 12.5px; color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.row-tail { flex: 0 0 auto; }
.badge { background: var(--badge); color: #0F1922; font-size: 11px; font-weight: 700; border-radius: 10px; padding: 1px 7px; }
/* кнопка «+» рядом с заголовком. Состояние .hidden — при нуле ассистентов */
.ic-btn { width: 32px; height: 32px; border-radius: 8px; border: none; background: transparent;
color: var(--dim); cursor: pointer; font-size: 16px; }
.ic-btn:hover { background: var(--panel2); color: var(--fg); }
.add-btn { font-size: 19px; color: var(--accent); }
.add-btn.hidden { display: none; }
/* пустой список */
.empty { padding: 44px 26px; text-align: center; }
.empty .big { font-size: 34px; opacity: .28; margin-bottom: 12px; }
.empty .ttl { font-size: 14px; font-weight: 600; margin-bottom: 7px; }
.empty .sub { font-size: 12.5px; color: var(--dim); line-height: 1.55; }
.empty .link { color: var(--accent); cursor: pointer; text-decoration: none; border-bottom: 1px dashed var(--accent); }
.main { flex: 1; display: flex; flex-direction: column; }
.chat-head { height: 58px; flex: 0 0 58px; border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 11px; padding: 0 16px; }
.chat-head .avatar { width: 36px; height: 36px; flex: 0 0 36px; font-size: 13px; }
.head-name { font-size: 14.5px; font-weight: 600; }
.head-sub { font-size: 11.5px; color: var(--dim); }
.head-sub .dot { color: var(--ok); }
.chat-head .ic-btn { margin-left: auto; }
.messages { flex: 1; overflow-y: auto; padding: 18px 24px; display: flex; flex-direction: column; gap: 12px; }
.msg { max-width: 74%; padding: 9px 13px; border-radius: 13px; font-size: 13.5px; }
.msg.user { align-self: flex-end; background: var(--user-bubble); border-bottom-right-radius: 4px; }
.msg.bot { align-self: flex-start; background: var(--bot-bubble); border-bottom-left-radius: 4px; }
/* ============ затемнение и окно ============ */
.scrim { position: absolute; inset: 0; background: rgba(0,0,0,.62); z-index: 100; display: none; }
.scrim.on { display: block; }
.layer { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; z-index: 150; pointer-events: none; }
.layer.on { display: flex; }
.layer > * { pointer-events: auto; }
.dialog {
width: 480px; min-width: 480px; flex: 0 0 auto; max-height: 600px; background: var(--panel);
border: 1px solid var(--line); border-radius: 14px; display: flex; flex-direction: column;
overflow: hidden; box-shadow: 0 26px 80px rgba(0,0,0,.7);
}
.d-head { display: flex; align-items: center; gap: 11px; padding: 16px 18px 14px; border-bottom: 1px solid var(--line); }
.d-head h2 { font-size: 16px; font-weight: 600; flex: 1; }
.x-btn { width: 30px; height: 30px; border-radius: 8px; border: none; background: transparent;
color: var(--dim); cursor: pointer; font-size: 17px; }
.x-btn:hover { background: var(--panel2); color: var(--fg); }
.d-body { flex: 1; overflow-y: auto; padding: 14px 18px 8px; }
.d-foot { padding: 13px 18px 16px; border-top: 1px solid var(--line); display: flex; gap: 9px; }
.d-lead { font-size: 12.5px; color: var(--dim); margin-bottom: 12px; }
/* ── вариант выбора ассистента ── */
.agent-opt {
display: flex; align-items: center; gap: 12px; padding: 11px 12px; border-radius: 11px;
background: var(--panel2); margin-bottom: 9px; cursor: pointer;
border: 1.5px solid transparent;
}
.agent-opt:hover { border-color: #33414F; }
.agent-opt.on { border-color: var(--accent); background: rgba(106,178,242,.10); }
.agent-opt .avatar { width: 40px; height: 40px; flex: 0 0 40px; font-size: 13px; }
.ag-mid { flex: 1; min-width: 0; }
.ag-name { font-size: 14px; font-weight: 550; }
.ag-addr { font-size: 11.5px; color: var(--dim); font-family: ui-monospace, "SF Mono", Menlo, monospace;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 3px; }
.ag-state { font-size: 11px; display: flex; align-items: center; gap: 5px; margin-top: 4px; }
.ag-state .d { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.ag-state.ok { color: var(--ok); }
.ag-state.off { color: var(--dim); } .ag-state.off .d { background: var(--dim); }
.ag-state.err { color: var(--err); } .ag-state.err .d { background: var(--err); }
/* кружок-галочка выбора */
.pick { width: 20px; height: 20px; flex: 0 0 20px; border-radius: 50%; border: 2px solid #3D4A58; }
.agent-opt.on .pick { border-color: var(--accent); background: var(--accent); position: relative; }
.agent-opt.on .pick::after {
content: ""; position: absolute; left: 5px; top: 1.5px; width: 5px; height: 9px;
border: solid #0F1922; border-width: 0 2px 2px 0; transform: rotate(42deg);
}
/* предупреждение под списком */
.warn {
display: none; gap: 8px; align-items: flex-start; margin-top: 4px; padding: 10px 12px;
border-radius: 10px; background: rgba(236,57,66,.09); border: 1px solid rgba(236,57,66,.32);
font-size: 12px; color: #F0A6AA;
}
.warn.on { display: flex; }
.warn .i { flex: 0 0 auto; }
/* кнопки */
.btn {
padding: 10px 16px; border-radius: 10px; border: 1px solid var(--line); background: var(--panel2);
color: var(--fg); font-size: 13.5px; cursor: pointer; font-family: inherit;
}
.btn:hover { border-color: #3D4A58; }
.btn.prim { background: var(--accent); border-color: var(--accent); color: #0F1922; font-weight: 600; }
.btn.prim:disabled { background: #253544; border-color: #253544; color: #5C6B7A; cursor: not-allowed; }
</style>
</head>
<body>
<div class="switcher">
<button data-view="pick" class="on">Выбор: 2 ассистента</button>
<button data-view="offline">Третий не отвечает</button>
<button data-view="one">Ассистент один</button>
<button data-view="zero">Ассистентов нет</button>
<button data-view="made">Диалог создан</button>
</div>
<div class="window">
<!-- ================= СПИСОК ДИАЛОГОВ ================= -->
<aside class="sidebar">
<div class="sb-head">
<div class="sb-title">
<span>Диалоги</span>
<button class="ic-btn add-btn" id="newChatBtn" title="Новый диалог">+</button>
</div>
</div>
<input class="search" placeholder="Поиск диалога…" id="searchBox">
<div class="folders" id="folders">
<div class="folder active">Все чаты <span class="cnt" id="cnt">2</span></div>
<div class="folder">Работа</div>
<div class="folder">Дом</div>
<div class="folder">+</div>
</div>
<div class="chat-list" id="chatList"></div>
</aside>
<!-- ================= ЧАТ ================= -->
<main class="main" id="mainPane">
<header class="chat-head">
<div class="avatar" id="headAv">АГ</div>
<div>
<div class="head-name" id="headName">agentik · рабочий</div>
<div class="head-sub"><span class="dot">●</span> на связи</div>
</div>
<button class="ic-btn" title="Настройки">⚙</button>
</header>
<div class="messages" id="msgs">
<div class="msg user">Проверь, почему падает CI</div>
<div class="msg bot">Нашёл: длинное тире в именах тестов ломает сборку. Починил, CI зелёный.</div>
</div>
</main>
<div class="scrim" id="scrim"></div>
<!-- ================= ОКНО: НОВЫЙ ДИАЛОГ ================= -->
<div class="layer" id="layerNew">
<div class="dialog">
<div class="d-head">
<h2>Новый диалог</h2>
<button class="x-btn" onclick="closeDialog()">✕</button>
</div>
<div class="d-body">
<div class="d-lead" id="lead">В каком ассистенте создать диалог?</div>
<div id="agentOpts">
<div class="agent-opt on" data-id="a1" data-ok="1" data-tag="АГ">
<div class="avatar">АГ</div>
<div class="ag-mid">
<div class="ag-name">agentik · рабочий</div>
<div class="ag-addr">http://192.168.88.135:8080/agentik</div>
<div class="ag-state ok"><span class="d"></span>на связи</div>
</div>
<div class="pick"></div>
</div>
<div class="agent-opt" data-id="a2" data-ok="1" data-tag="ДМ">
<div class="avatar g2">ДМ</div>
<div class="ag-mid">
<div class="ag-name">домашний</div>
<div class="ag-addr">http://192.168.76.199:8080/agentik</div>
<div class="ag-state ok"><span class="d"></span>на связи</div>
</div>
<div class="pick"></div>
</div>
<div class="agent-opt" data-id="a3" data-ok="0" data-tag="КА" data-pic="1">
<div class="avatar pic"></div>
<div class="ag-mid">
<div class="ag-name">Кот-ассистент</div>
<div class="ag-addr">http://192.168.88.24:8080/agentik</div>
<div class="ag-state err"><span class="d"></span>не отвечает</div>
</div>
<div class="pick"></div>
</div>
</div>
<div class="warn" id="warn">
<span class="i">⚠</span>
<span id="warnText">Ассистент сейчас не отвечает. Диалог создать можно, но
сообщение не уйдёт, пока он не поднимется.</span>
</div>
</div>
<div class="d-foot">
<button class="btn" style="flex:1" onclick="closeDialog()">Отмена</button>
<button class="btn prim" style="flex:1" id="createBtn" disabled>Создать диалог</button>
</div>
</div>
</div>
</div>
<script>
const el = id => document.getElementById(id);
// ───────── данные состояний ─────────
const AG_ALL = `
<div class="agent-opt on" data-id="a1" data-ok="1">
<div class="avatar">АГ</div>
<div class="ag-mid">
<div class="ag-name">agentik · рабочий</div>
<div class="ag-addr">http://192.168.88.135:8080/agentik</div>
<div class="ag-state ok"><span class="d"></span>на связи</div>
</div>
<div class="pick"></div>
</div>
<div class="agent-opt" data-id="a2" data-ok="1">
<div class="avatar g2">ДМ</div>
<div class="ag-mid">
<div class="ag-name">домашний</div>
<div class="ag-addr">http://192.168.76.199:8080/agentik</div>
<div class="ag-state ok"><span class="d"></span>на связи</div>
</div>
<div class="pick"></div>
</div>
<div class="agent-opt" data-id="a3" data-ok="0">
<div class="avatar pic"></div>
<div class="ag-mid">
<div class="ag-name">Кот-ассистент</div>
<div class="ag-addr">http://192.168.88.24:8080/agentik</div>
<div class="ag-state err"><span class="d"></span>не отвечает</div>
</div>
<div class="pick"></div>
</div>`;
const CHATS_TWO = `
<div class="chat-row active">
<div class="avatar">АГ</div>
<div class="chat-mid">
<div class="chat-name"><span>agentik · рабочий</span><span class="chat-time">14:22</span></div>
<div class="chat-prev">Собрал отчёт, жду правок</div>
</div>
<div class="row-tail"><span class="badge">2</span></div>
</div>
<div class="chat-row">
<div class="avatar g2">ДМ</div>
<div class="chat-mid">
<div class="chat-name"><span>домашний</span><span class="chat-time">12:05</span></div>
<div class="chat-prev">Файл лежит на диске</div>
</div>
</div>`;
const CHATS_NEW = `
<div class="chat-row active">
<div class="avatar g2">ДМ</div>
<div class="chat-mid">
<div class="chat-name"><span>домашний</span><span class="chat-time">сейчас</span></div>
<div class="chat-prev">Новый диалог</div>
</div>
</div>
<div class="chat-row">
<div class="avatar">АГ</div>
<div class="chat-mid">
<div class="chat-name"><span>agentik · рабочий</span><span class="chat-time">14:22</span></div>
<div class="chat-prev">Собрал отчёт, жду правок</div>
</div>
<div class="row-tail"><span class="badge">2</span></div>
</div>`;
const CHATS_EMPTY = `
<div class="empty">
<div class="big">💬</div>
<div class="ttl">Диалогов пока нет</div>
<div class="sub">Ассистенты ещё не добавлены.<br>
Добавьте первого в <a class="link" onclick="alert('Откроется окно настроек — макет 004-settings-agents')">настройках</a>.</div>
</div>`;
// ───────── управление окном ─────────
function setAgents(html) { el('agentOpts').innerHTML = html; bindOpts(); }
function bindOpts() {
document.querySelectorAll('.agent-opt').forEach(o => o.onclick = () => {
document.querySelectorAll('.agent-opt').forEach(x => x.classList.remove('on'));
o.classList.add('on');
el('createBtn').disabled = false;
el('warn').classList.toggle('on', o.dataset.ok === '0');
});
}
function openDialog() {
el('scrim').classList.add('on');
el('layerNew').classList.add('on');
el('createBtn').disabled = false;
}
function closeDialog() {
el('scrim').classList.remove('on');
el('layerNew').classList.remove('on');
}
el('newChatBtn').onclick = openDialog;
document.querySelectorAll('.switcher button').forEach(b => b.onclick = () => {
document.querySelectorAll('.switcher button').forEach(x => x.classList.remove('on'));
b.classList.add('on');
render(b.dataset.view);
});
function render(v) {
closeDialog();
el('warn').classList.remove('on');
if (v === 'pick') {
el('chatList').innerHTML = CHATS_TWO;
el('folders').style.display = ''; el('searchBox').style.display = '';
el('cnt').textContent = '2';
el('newChatBtn').classList.remove('hidden');
setAgents(AG_ALL);
el('createBtn').disabled = false;
openDialog();
}
if (v === 'offline') {
el('chatList').innerHTML = CHATS_TWO;
el('folders').style.display = ''; el('searchBox').style.display = '';
el('cnt').textContent = '2';
el('newChatBtn').classList.remove('hidden');
setAgents(AG_ALL);
const off = document.querySelector('.agent-opt[data-ok="0"]');
document.querySelectorAll('.agent-opt').forEach(x => x.classList.remove('on'));
off.classList.add('on');
el('warn').classList.add('on');
el('createBtn').disabled = false;
openDialog();
}
if (v === 'one') {
el('chatList').innerHTML = CHATS_TWO;
el('folders').style.display = ''; el('searchBox').style.display = '';
el('cnt').textContent = '2';
el('newChatBtn').classList.remove('hidden');
}
if (v === 'zero') {
el('chatList').innerHTML = CHATS_EMPTY;
el('folders').style.display = 'none'; el('searchBox').style.display = 'none';
el('newChatBtn').classList.add('hidden');
}
if (v === 'made') {
el('chatList').innerHTML = CHATS_NEW;
el('folders').style.display = ''; el('searchBox').style.display = '';
el('cnt').textContent = '2';
el('newChatBtn').classList.remove('hidden');
el('headAv').textContent = 'ДМ'; el('headAv').className = 'avatar g2';
el('headName').textContent = 'домашний';
}
}
bindOpts();
render('pick');
</script>
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
# sketches — макеты основного окна
**Здесь рабочий черновик основного окна десктопного клиента.** Это не
«утверждено» — `approved/` живёт отдельно. Правило: одобренное живёт только
там, а в `sketches/` — то, что ещё сравниваем и допиливаем.
## Раскладка: rail / voice-first
Узкая рейка иконок слева (диалоги / память / инструменты / новый),
посередине список диалогов, справа чат. Голосовое сообщение — главное
действие: крупная акцентная кнопка микрофона в композере.
**Цветовая схема:** тёмная тема, общий набор токенов из `../THEMES.md`.
Палитра согласована с Android-клиентом — расширения (`--panel2`, `--line`,
`--badge`, `--ok`, `--user-bubble`, `--bot-bubble`, `--a1`..`--a5`) тут
уже есть.
## Файлы
| Файл | Состояние |
|---|---|
| `style.css` | Общий CSS для всех макетов |
| `1-chats.html` | Слева — список диалогов с цветными полосками агентов, справа — пусто («выберите диалог») |
| `2-chat.html` | Открытый диалог: сообщения, ход работы агента, композер с полем + микрофон + отправка |
| `3-recording.html` | Идёт запись — в композере волна + таймер + ⏸ + ✕ + ➤, одна строка 46 px |
| `4-empty.html` | Список пуст, ни одного ассистента не добавлено |
| `5-new-chat.html` | Модалка по центру — выбор ассистента из добавленных |
| `6-groups.html` | Модалка управления группами: список с действиями «изменить» / «удалить» |
| `7-groups-add.html` | Создание группы: имя + пустой список диалогов + кнопка «+ Добавить диалог» |
| `8-groups-edit.html` | Переименование + теги уже добавленных диалогов с `✕` + блок «ОПАСНОЕ» с удалением группы |
| `9-groups-pick-chat.html` | Выбор диалога для добавления в группу: поиск + чекбоксы по диалогам |
## Как смотреть
Открыть файл в браузере — самодостаточный, без сборки и без зависимостей.
+1426
View File
File diff suppressed because it is too large Load Diff
+610
View File
@@ -0,0 +1,610 @@
/* === палитра тёмной темы (согласована с Android, см. ../THEMES.md) === */
:root {
--bg: #121218; --panel: #17212B; --panel2: #1C2733; --line: #232B36;
--fg: #EBEBEB; --dim: #768C9E; --accent: #6AB2F2; --badge: #5EB5F7;
--err: #EC3942; --ok: #63C88A;
--user-bubble: #2B5278; --bot-bubble: #1C2733;
--a1: #6AB2F2; --a2: #86C99A; --a3: #C9A886; --a4: #B79AE0; --a5: #E28C8C;
--on-accent: #0F1922;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
background: #0A0A0F; color: var(--fg); line-height: 1.45;
display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 18px;
}
.win {
width: 1240px; height: 760px; background: var(--bg);
border-radius: 14px; overflow: hidden; border: 1px solid #2A3340;
display: flex; box-shadow: 0 28px 90px rgba(0,0,0,.7);
}
.cap {
position: absolute; margin-top: -22px; font-size: 12px; color: var(--dim);
}
@media (max-height: 820px) { body { padding: 8px; } .win { transform: scale(.78); } }
@media (max-height: 700px) { .win { transform: scale(.66); } }
@media (max-height: 600px) { .win { transform: scale(.56); } }
/* === средняя колонка: список диалогов === */
.list {
width: 296px; flex: 0 0 296px; background: var(--panel); border-right: 1px solid var(--line);
display: flex; flex-direction: column;
}
.l-head { padding: 14px 16px 10px; display: flex; align-items: center; gap: 8px; }
.l-head h1 { font-size: 15.5px; font-weight: 650; letter-spacing: .2px; }
.chips { display: flex; gap: 6px; padding: 0 14px 10px; overflow-x: auto; }
.chip {
padding: 6px 11px; border-radius: 16px; font-size: 12px; color: var(--dim);
background: transparent; white-space: nowrap; border: 1px solid transparent;
}
.chip:hover { background: var(--panel2); }
.chip.on { color: var(--fg); border-color: var(--accent); background: rgba(106,178,242,.13); }
.chip b { color: var(--badge); font-weight: 700; margin-left: 3px; }
.chip-act {
margin-left: auto; flex: 0 0 auto; padding: 6px 9px; border-radius: 11px;
background: transparent; color: var(--dim); font-size: 13px; cursor: pointer;
border: 1px solid transparent; display: inline-flex; align-items: center;
justify-content: center;
}
.chip-act:hover { color: var(--fg); background: var(--panel2); }
.chip-act svg { width: 14px; height: 14px; stroke: currentColor; fill: none;
stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.q {
margin: 0 14px 10px; height: 36px; border-radius: 11px; border: 1px solid var(--line);
background: var(--bg); color: var(--dim); font-size: 12.5px;
display: flex; align-items: center; gap: 8px; padding: 0 12px;
}
.rows { flex: 1; overflow-y: auto; padding: 0 8px 12px; }
.row {
display: flex; gap: 11px; padding: 9px 10px; border-radius: 12px; cursor: pointer;
position: relative;
}
.row:hover { background: var(--panel2); }
.row.on { background: #233040; }
.row .strip {
position: absolute; left: 0; top: 10px; bottom: 10px; width: 3px;
background: var(--a1); border-radius: 3px;
}
.row .ava {
width: 40px; height: 40px; flex: 0 0 40px; border-radius: 50%;
display: grid; place-items: center; font-size: 14px; font-weight: 700; color: var(--on-accent);
}
.row .mid { flex: 1; min-width: 0; }
.row .l1 { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.row .l1 .n { font-size: 13.5px; font-weight: 550; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row .l1 .t { font-size: 10.5px; color: var(--dim); flex: 0 0 auto; }
.row .l2 { display: flex; justify-content: space-between; gap: 8px; align-items: center; margin-top: 2px; }
.row .l2 .p { font-size: 12px; color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bd { background: var(--badge); color: var(--on-accent); border-radius: 11px; font-size: 10.5px; font-weight: 700; padding: 1px 6px; flex: 0 0 auto; }
/* === основная зона чата === */
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.m-head {
height: 60px; flex: 0 0 60px; display: flex; align-items: center; gap: 11px;
padding: 0 22px; border-bottom: 1px solid var(--line);
}
.m-head .ava { width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center; font-size: 14px; font-weight: 700; color: var(--on-accent); }
.m-head .hd .t1 { font-size: 14.5px; font-weight: 600; }
.m-head .hd .t2 { font-size: 11.5px; color: var(--dim); }
.m-head .hd .t2.ok { color: var(--ok); }
.m-head .hd .t2.bad { color: var(--err); }
.m-head .ghost { margin-left: auto; display: flex; gap: 6px; }
.m-head .ghost button {
background: transparent; border: 1px solid var(--line); color: var(--dim);
border-radius: 8px; padding: 5px 11px; font-size: 11.5px; cursor: pointer;
}
.m-head .ghost button:hover { color: var(--fg); border-color: var(--dim); }
.thread { flex: 1; overflow-y: auto; padding: 20px 26px; display: flex; flex-direction: column; gap: 11px; }
.b {
max-width: 76%; padding: 9px 13px; border-radius: 14px; font-size: 13.5px;
}
.b.u { align-self: flex-end; background: var(--user-bubble); border-bottom-right-radius: 5px; }
.b.a { align-self: flex-start; background: var(--bot-bubble); border-bottom-left-radius: 5px; }
.b b { font-weight: 650; }
.b code { background: rgba(255,255,255,.08); border-radius: 4px; padding: 1px 5px; font-size: 12px; font-family: "DejaVu Sans Mono", monospace; }
.b .tm { font-size: 10.5px; color: var(--dim); margin-top: 5px; }
.cur { display: inline-block; width: 7px; height: 13px; background: var(--accent); vertical-align: -2px; margin-left: 2px; }
/* ход работы агента — компактная раскрывающаяся строка */
.act-line {
align-self: flex-start; display: flex; align-items: center; gap: 9px;
background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
padding: 7px 12px; font-size: 12.5px; color: var(--dim); max-width: 78%;
}
.act-line .g { color: var(--accent); }
.act-line .nm { color: var(--fg); }
.act-line .ch { margin-left: auto; font-size: 11px; }
/* === композер: voice-first — микрофон главный === */
.composer { border-top: 1px solid var(--line); background: var(--panel); padding: 14px 22px 14px; }
.rec-panel {
display: flex; align-items: center; gap: 10px; background: var(--bg);
border: 1px solid var(--err); border-radius: 13px; padding: 4px 6px 4px 13px; height: 46px;
}
.wave { display: flex; align-items: center; gap: 3px; height: 28px; flex: 1; min-width: 0; }
.wave i { width: 3px; background: var(--err); border-radius: 2px; }
.wave i:nth-child(1) { height: 30%; } .wave i:nth-child(2) { height: 70%; }
.wave i:nth-child(3) { height: 45%; } .wave i:nth-child(4) { height: 100%; }
.wave i:nth-child(5) { height: 55%; } .wave i:nth-child(6) { height: 80%; }
.wave i:nth-child(7) { height: 35%; } .wave i:nth-child(8) { height: 65%; }
.wave i:nth-child(9) { height: 25%; }
.rec-time {
font-variant-numeric: tabular-nums; font-size: 13px; color: var(--err);
font-weight: 700; flex: 0 0 auto;
}
.rec-btn {
width: 34px; height: 34px; flex: 0 0 34px; border-radius: 10px;
background: transparent; border: none; color: var(--dim); font-size: 14px;
display: grid; place-items: center; cursor: pointer;
}
.rec-btn:hover { color: var(--fg); background: var(--panel2); }
.rec-btn.rec-send { background: var(--accent); color: var(--on-accent); font-size: 15px; }
.rec-btn.rec-send:hover { background: #82BEF5; }
.composer-row { display: flex; align-items: center; gap: 10px; }
.field {
flex: 1; min-width: 0; height: 46px; border-radius: 13px; background: var(--bg);
border: 1px solid var(--line); display: flex; align-items: center; padding: 0 14px;
font-size: 13.5px; color: var(--dim);
}
.field.focus { border-color: var(--accent); }
.mic {
width: 52px; height: 52px; flex: 0 0 52px; border-radius: 50%;
border: 2px solid var(--accent); background: var(--bg); color: var(--accent);
font-size: 19px; display: grid; place-items: center; cursor: pointer;
}
.mic:hover { background: rgba(106,178,242,.13); }
.mic.rec { background: var(--err); border-color: var(--err); color: #fff; }
.send {
width: 46px; height: 46px; flex: 0 0 46px; border-radius: 13px; border: none;
background: var(--accent); color: var(--on-accent); font-size: 17px;
display: grid; place-items: center; cursor: pointer;
}
.send.off { background: var(--panel2); color: var(--dim); cursor: default; }
.rec-note { font-size: 11px; color: var(--dim); margin-top: 7px; text-align: center; }
/* стрим: вместо поля — широкая красная кнопка «Стоп» */
.stop-wide {
height: 50px; border-radius: 13px; border: 1px solid rgba(236,57,66,.45);
background: rgba(236,57,66,.10); color: #F0A6AA; font-size: 13.5px; font-weight: 600;
display: flex; align-items: center; justify-content: center; gap: 9px; cursor: pointer;
}
/* === пустое состояние === */
.empty {
flex: 1; display: flex; flex-direction: column; align-items: center;
justify-content: center; text-align: center; padding: 30px 34px; gap: 14px;
}
.empty .big {
width: 78px; height: 78px; border-radius: 22px; background: var(--panel);
border: 1px solid var(--line); display: grid; place-items: center; font-size: 32px;
color: var(--dim);
}
.empty h3 { font-size: 17px; font-weight: 600; }
.empty p { font-size: 13px; color: var(--dim); line-height: 1.55; max-width: 340px; }
.btn {
border-radius: 12px; padding: 11px 18px; font-size: 13.5px; cursor: pointer;
border: 1px solid var(--line); background: transparent; color: var(--fg); font-family: inherit;
}
.btn.prim { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 650; }
/* === модалка нового диалога === */
.scrim {
position: absolute; inset: 0; background: rgba(0,0,0,.6);
display: flex; align-items: center; justify-content: center; padding: 22px;
}
.modal {
width: 480px; max-height: 84%; background: var(--panel);
border: 1px solid var(--line); border-radius: 16px; display: flex;
flex-direction: column; overflow: hidden;
}
.modal h2 { font-size: 16px; font-weight: 650; padding: 16px 20px 4px; }
.modal .sub { font-size: 12.5px; color: var(--dim); padding: 0 20px 12px; }
.modal .body { flex: 1; overflow-y: auto; padding: 4px 12px 8px; }
.agent {
display: flex; align-items: center; gap: 12px; padding: 13px 14px;
border-radius: 12px; cursor: pointer; border: 1px solid transparent;
}
.agent:hover { background: var(--panel2); }
.agent.on { border-color: var(--accent); background: rgba(106,178,242,.07); }
.agent .sign { width: 38px; height: 38px; flex: 0 0 38px; border-radius: 50%; display: grid; place-items: center; font-size: 14px; font-weight: 700; color: var(--on-accent); }
.agent .mid { flex: 1; min-width: 0; }
.agent .nm { font-size: 14px; font-weight: 550; }
.agent .ad { font-size: 11.5px; color: var(--dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.agent .ok { color: var(--ok); font-size: 11.5px; }
.agent .bad { color: var(--err); font-size: 11.5px; }
.agent .radio { width: 22px; height: 22px; flex: 0 0 22px; border-radius: 50%; border: 2px solid var(--line); }
.agent.on .radio { border-color: var(--accent); background: radial-gradient(circle, var(--accent) 0 6px, transparent 7px); }
.modal .foot { padding: 12px 16px 16px; display: flex; gap: 10px; border-top: 1px solid var(--line); }
.modal .foot .btn:first-child { flex: 1; }
.modal .warn {
margin: 0 14px 10px; padding: 10px 12px; border-radius: 11px;
background: rgba(236,57,66,.09); border: 1px solid rgba(236,57,66,.32);
font-size: 12px; color: #F0A6AA;
}
/* === модалка управления группами === */
.modal.groups { width: 540px; }
.modal.groups h2 { display: flex; align-items: center; gap: 10px; padding: 14px 20px 6px; }
.modal.groups h2 .x { margin-left: auto; width: 28px; height: 28px; border-radius: 8px; border: none; background: transparent; color: var(--dim); cursor: pointer; font-size: 16px; }
.modal.groups h2 .x:hover { background: var(--panel2); color: var(--fg); }
.modal.groups .body { padding: 6px 14px 10px; }
.grp {
display: flex; align-items: center; gap: 12px; padding: 11px 13px;
border-radius: 11px; background: var(--panel2); margin-bottom: 8px;
border: 1px solid transparent;
}
.grp:hover { border-color: var(--line); }
.grp .mid { flex: 1; min-width: 0; }
.grp .nm { font-size: 14px; font-weight: 550; }
.grp .sub { font-size: 11.5px; color: var(--dim); margin-top: 2px; }
.grp .acts { display: flex; gap: 4px; }
.grp.sys { background: transparent; border: 1px dashed var(--line); }
.grp .lock { font-size: 11px; color: var(--dim); }
.mini-ic { width: 30px; height: 30px; border-radius: 8px; border: none; background: transparent;
color: var(--dim); cursor: pointer; font-size: 14px; display: inline-flex;
align-items: center; justify-content: center; line-height: 1; }
.mini-ic svg, .card-acts svg { width: 16px; height: 16px; stroke: currentColor; fill: none;
stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.mini-ic.del svg, .card-acts.del svg { fill: currentColor; stroke: none; }
.mini-ic:hover { background: var(--panel); color: var(--fg); }
.mini-ic.del:hover { color: var(--err); }
.modal .info {
display: flex; gap: 9px; align-items: flex-start; margin: 4px 14px 12px;
padding: 10px 13px; border-radius: 10px; background: var(--bg); border: 1px solid var(--line);
font-size: 11.5px; color: var(--dim); line-height: 1.5;
}
.modal .danger-zone { margin: 14px 14px 0; padding-top: 14px; border-top: 1px solid var(--line); }
.modal .danger-zone .lbl { font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
color: var(--dim); font-weight: 700; margin-bottom: 8px; }
.modal .form-field { margin: 0 14px 14px; }
.modal .form-field label { display: block; font-size: 12px; color: var(--dim); margin-bottom: 6px; }
.modal .form-field input {
width: 100%; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line);
background: var(--bg); color: var(--fg); font-size: 13.5px; outline: none; font-family: inherit;
}
.modal .form-field input:focus { border-color: var(--accent); }
.modal .form-field .note { font-size: 11px; color: var(--dim); margin-top: 6px; line-height: 1.5; }
.btn.dang { color: var(--err); border-color: rgba(236,57,66,.4); }
.btn.dang:hover { border-color: var(--err); background: rgba(236,57,66,.09); }
.btn.dang.solid { background: var(--err); border-color: var(--err); color: #FFF; font-weight: 650; }
/* === теги диалогов в группе + кнопка «добавить диалог» === */
.modal .tags { margin: 0 14px 12px; }
.modal .tags .lbl { font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
color: var(--dim); font-weight: 700; margin-bottom: 9px; }
.modal .tags .empty { font-size: 12px; color: var(--dim); padding: 9px 12px;
background: var(--bg); border: 1px dashed var(--line); border-radius: 10px; }
.modal .tags-list { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 9px; }
.chat-tag {
display: inline-flex; align-items: center; gap: 7px;
padding: 4px 8px 4px 4px; background: var(--bg);
border: 1px solid var(--line); border-radius: 9px;
}
.chat-tag .ava-sm { width: 22px; height: 22px; flex: 0 0 22px; border-radius: 50%;
display: grid; place-items: center; font-size: 11px; font-weight: 700; color: var(--on-accent); }
.chat-tag .nm { font-size: 12.5px; }
.chat-tag .x { width: 18px; height: 18px; border-radius: 50%; border: none;
background: transparent; color: var(--dim); cursor: pointer;
font-size: 10px; display: grid; place-items: center; }
.chat-tag .x:hover { color: var(--err); background: rgba(236,57,66,.12); }
.add-chat-btn {
display: flex; align-items: center; justify-content: center; gap: 7px;
padding: 9px; width: 100%; cursor: pointer; font-size: 13px; font-family: inherit;
background: transparent; color: var(--dim);
border: 1px dashed var(--line); border-radius: 10px;
}
.add-chat-btn:hover { color: var(--fg); border-color: var(--dim); }
/* === модалка выбора диалога для группы === */
.modal.pick { width: 460px; }
.modal.pick .q {
margin: 4px 14px 8px; padding: 9px 12px; border-radius: 10px;
border: 1px solid var(--line); background: var(--bg);
color: var(--fg); font-size: 13px; outline: none; font-family: inherit;
}
.modal.pick .q:focus { border-color: var(--accent); }
.modal.pick .q::placeholder { color: var(--dim); }
.modal.pick .body { padding: 0 8px 8px; max-height: 360px; }
.pick-row {
display: flex; align-items: center; gap: 11px; padding: 9px 10px;
border-radius: 10px; cursor: pointer; border: 1px solid transparent;
}
.pick-row:hover { background: var(--panel2); }
.pick-row.in { opacity: .42; cursor: default; }
.pick-row .ava-sm { width: 32px; height: 32px; flex: 0 0 32px; border-radius: 50%;
display: grid; place-items: center; font-size: 12px; font-weight: 700; color: var(--on-accent); }
.pick-row .mid { flex: 1; min-width: 0; }
.pick-row .nm { font-size: 13.5px; }
.pick-row .ad { font-size: 11.5px; color: var(--dim); margin-top: 1px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pick-row .check { width: 20px; height: 20px; flex: 0 0 20px; border-radius: 6px;
border: 1.5px solid var(--line); display: grid; place-items: center; color: transparent; font-size: 11px; }
.pick-row .check.on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
/* === 004 · окно агентов (список + форма + поповер проверки) === */
.dialog {
width: 620px; min-width: 620px; flex: 0 0 auto; max-height: 620px;
background: var(--panel); border: 1px solid var(--line); border-radius: 14px;
display: flex; flex-direction: column; overflow: hidden;
box-shadow: 0 26px 80px rgba(0,0,0,.7);
}
.dialog.small { width: 480px; min-width: 480px; max-height: 420px; }
.d-head { display: flex; align-items: center; gap: 11px; padding: 16px 18px 14px;
border-bottom: 1px solid var(--line); }
.d-head h2 { font-size: 16px; font-weight: 600; flex: 1; }
.x-btn { width: 30px; height: 30px; border-radius: 8px; border: none; background: transparent;
color: var(--dim); cursor: pointer; font-size: 17px; }
.x-btn:hover { background: var(--panel2); color: var(--fg); }
.d-body { flex: 1; overflow-y: auto; padding: 14px 18px 6px; }
.d-foot { padding: 13px 18px 16px; border-top: 1px solid var(--line); display: flex; gap: 9px; }
.agent-card {
display: flex; align-items: center; gap: 12px; padding: 11px 12px; border-radius: 11px;
background: var(--panel2); margin-bottom: 9px; border: 1px solid transparent;
}
.agent-card:hover { border-color: var(--line); }
.ag-mid { flex: 1; min-width: 0; }
.ag-name { font-size: 14px; font-weight: 550; }
.ag-desc { font-size: 11.5px; color: var(--fg); opacity: .7;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 3px; }
.ag-addr { font-size: 11.5px; color: var(--dim); font-family: ui-monospace, "SF Mono", Menlo, monospace;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.state { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--ok); flex: 0 0 auto; }
.state .d { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.state.off { color: var(--dim); } .state.off .d { background: var(--dim); }
.state.err { color: var(--err); } .state.err .d { background: var(--err); }
.card-acts { display: flex; gap: 4px; flex: 0 0 auto; }
.field { margin-bottom: 15px; }
.field label { display: block; font-size: 12px; color: var(--dim); margin-bottom: 6px; }
.field input {
width: 100%; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line);
background: var(--bg); color: var(--fg); font-size: 13.5px; outline: none; font-family: inherit;
}
.field input:focus { border-color: var(--accent); }
.field input.mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12.5px; }
.field .note { font-size: 11px; color: var(--dim); margin-top: 6px; line-height: 1.5; }
.note code, .hint-line code { background: var(--panel2); padding: 1px 4px; border-radius: 4px;
font-size: 10.5px; }
.addr-row { display: flex; gap: 8px; }
.addr-row input { flex: 1; }
.appearance-tabs { display: flex; gap: 6px; margin-bottom: 11px; }
.appearance-tabs .tab {
padding: 6px 13px; border-radius: 9px; font-size: 12.5px; color: var(--dim);
background: var(--panel2); cursor: pointer; border: 1px solid transparent;
}
.appearance-tabs .tab.on { color: var(--fg); border-color: var(--accent);
background: rgba(106,178,242,.12); }
.pane { display: none; } .pane.on { display: block; }
.colors { display: flex; gap: 9px; }
.swatch { width: 30px; height: 30px; border-radius: 50%; cursor: pointer;
border: 2px solid transparent; }
.swatch.on { border-color: var(--fg); }
.pic-row { display: flex; align-items: center; gap: 13px; }
.pic-preview {
width: 54px; height: 54px; border-radius: 50%; flex: 0 0 54px; background: var(--bg);
border: 1px dashed var(--line); display: grid; place-items: center; color: var(--dim);
font-size: 20px; overflow: hidden;
}
.pic-preview.filled { border-style: solid; }
.pic-preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pic-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.btn.sm { padding: 8px 13px; font-size: 12.5px; }
.btn.wide { width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px;
border-style: dashed; color: var(--dim); }
.btn.wide:hover { color: var(--fg); border-color: var(--dim); }
.hint-line { font-size: 11.5px; color: var(--dim); margin: 2px 0 16px; padding: 9px 11px;
background: var(--bg); border-radius: 9px; border: 1px solid var(--line); line-height: 1.5; }
/* поповер проверки связи */
.layer { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center;
z-index: 250; }
.spin { width: 17px; height: 17px; border-radius: 50%; border: 2px solid var(--line);
border-top-color: var(--accent); animation: sp .7s linear infinite; flex: 0 0 17px; }
@keyframes sp { to { transform: rotate(360deg); } }
.probe-head { display: flex; align-items: center; gap: 13px; padding: 4px 0 14px; }
.probe-head .big { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center;
font-size: 19px; flex: 0 0 40px; }
.probe-head.ok .big { background: rgba(99,200,138,.15); color: var(--ok); }
.probe-head.err .big { background: rgba(236,57,66,.15); color: var(--err); }
.probe-head.busy .big { background: transparent; padding: 0; }
.probe-head .txt { flex: 1; min-width: 0; }
.probe-head .ttl { font-size: 14px; font-weight: 600; }
.probe-head .sub { font-size: 12px; color: var(--dim); margin-top: 2px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.probe-list { font-size: 12px; }
.probe-list div { display: flex; justify-content: space-between; gap: 14px; padding: 7px 0;
border-bottom: 1px solid var(--line); }
.probe-list div:last-child { border-bottom: none; }
.probe-list span { color: var(--dim); }
.probe-list b { font-weight: 550; font-family: ui-monospace, "SF Mono", Menlo, monospace;
font-size: 11.5px; }
/* === окно клиента .window + сайдбар .sidebar (вариант sidebar-utility) === */
.window {
width: 1180px; height: 740px; background: var(--bg); border-radius: 12px;
display: flex; overflow: hidden; border: 1px solid var(--line); position: relative;
box-shadow: 0 24px 80px rgba(0,0,0,.6);
}
.sidebar { width: 320px; background: var(--panel); border-right: 1px solid var(--line);
display: flex; flex-direction: column; flex: 0 0 320px; }
.sb-head { padding: 14px 16px 10px; }
.sb-title { font-size: 17px; font-weight: 600; display: flex; align-items: center;
justify-content: space-between; }
.icon-btn { width: 30px; height: 30px; border-radius: 8px; border: none;
background: transparent; color: var(--dim); cursor: pointer; display: grid; place-items: center;
font-size: 17px; }
.icon-btn:hover { background: var(--panel2); color: var(--fg); }
.search { padding: 8px 12px; border-radius: 9px; border: 1px solid var(--line);
background: var(--bg); color: var(--fg); font-size: 13px; outline: none; font-family: inherit; }
.search:focus { border-color: var(--accent); }
.search::placeholder { color: var(--dim); }
/* лента папок (sidebar-utility) */
.folders { display: flex; gap: 6px; padding: 6px 16px 10px; overflow-x: auto; }
.folder { padding: 5px 11px; border-radius: 14px; font-size: 12px; color: var(--dim);
background: var(--panel2); cursor: pointer; white-space: nowrap; border: 1px solid transparent; }
.folder.active { color: var(--fg); border-color: var(--accent); background: rgba(106,178,242,.12); }
.folder .cnt { color: var(--badge); font-weight: 600; margin-left: 4px; }
.folder.act { margin-left: auto; background: transparent; color: var(--dim);
display: grid; place-items: center; padding: 5px 9px; font-size: 13px; }
.folder.act svg { width: 14px; height: 14px; stroke: currentColor; fill: none;
stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.folder.act:hover { color: var(--fg); background: var(--panel2); }
.chat-list { flex: 1; overflow-y: auto; padding: 4px 8px 12px; }
.chat-row { display: flex; gap: 11px; padding: 9px 10px; border-radius: 10px; cursor: pointer;
align-items: flex-start; }
.chat-row:hover { background: var(--panel2); }
.chat-row.active { background: #233040; }
.chat-mid { flex: 1; min-width: 0; }
.chat-name { font-size: 14px; font-weight: 500; display: flex; justify-content: space-between; gap: 8px; }
.chat-time { font-size: 11px; color: var(--dim); font-weight: 400; flex: 0 0 auto; }
.chat-prev { font-size: 12.5px; color: var(--dim); white-space: nowrap; overflow: hidden;
text-overflow: ellipsis; margin-top: 2px; }
.badge { background: var(--badge); color: #0F1922; border-radius: 10px; font-size: 11px;
font-weight: 700; padding: 1px 6px; min-width: 19px; text-align: center; }
.row-tail { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; flex: 0 0 auto; }
/* правая часть .main — заголовок чата */
.chat-head { height: 58px; flex: 0 0 58px; border-bottom: 1px solid var(--line);
display: flex; align-items: center; gap: 11px; padding: 0 16px; }
.chat-head .avatar { width: 36px; height: 36px; flex: 0 0 36px; font-size: 13px; }
.head-meta { flex: 1; }
.head-name { font-size: 14.5px; font-weight: 600; }
.head-sub { font-size: 11.5px; color: var(--dim); }
.head-sub .dot { color: var(--ok); }
.back-btn { display: none; width: 32px; height: 32px; flex: 0 0 32px; border-radius: 8px;
border: none; background: transparent; color: var(--fg); cursor: pointer; font-size: 17px;
place-items: center; }
.back-btn:hover { background: var(--panel); }
/* сообщения */
.messages { flex: 1; overflow-y: auto; padding: 18px 24px; display: flex; flex-direction: column;
gap: 12px; }
.msg { max-width: 74%; padding: 9px 13px; border-radius: 13px; font-size: 13.5px; }
.msg.user { align-self: flex-end; background: var(--user-bubble); border-bottom-right-radius: 4px; }
.msg.bot { align-self: flex-start; background: var(--bot-bubble); border-bottom-left-radius: 4px; }
.msg .meta { font-size: 10.5px; color: var(--dim); margin-top: 5px; }
.think { align-self: flex-start; max-width: 80%; border-left: 2px solid var(--accent);
background: rgba(106,178,242,.07); padding: 8px 12px; border-radius: 0 10px 10px 0;
font-size: 12.5px; color: var(--dim); }
.think b { color: var(--accent); font-weight: 600; }
.think .rest { display: none; }
.tool { align-self: flex-start; max-width: 80%; background: var(--panel); border: 1px solid var(--line);
border-radius: 10px; padding: 8px 12px; font-size: 12.5px;
display: flex; align-items: center; gap: 9px; }
.tool .gear { color: var(--accent); }
.tool code { color: var(--dim); font-size: 11.5px; }
/* композер */
.composer { border-top: 1px solid var(--line); padding: 12px 16px 14px; background: var(--panel); }
.attach-strip { display: flex; gap: 8px; margin-bottom: 9px; }
.attach-strip:empty { display: none; }
.composer .chip { background: var(--panel2); border: 1px solid var(--line); border-radius: 8px;
padding: 5px 9px; font-size: 11.5px; color: var(--dim); display: flex; align-items: center; gap: 7px; }
.composer .chip .x { cursor: pointer; color: var(--err); font-weight: 700; }
.input-row { display: flex; align-items: flex-end; gap: 9px; }
.input-wrap { flex: 1; display: flex; align-items: flex-end; background: var(--bg);
border: 1px solid var(--line); border-radius: 11px; padding: 9px 12px; gap: 9px; }
.input-wrap:focus-within { border-color: var(--accent); }
.input-wrap textarea { flex: 1; background: transparent; border: none; outline: none; resize: none;
color: var(--fg); font-family: inherit; font-size: 13.5px; line-height: 1.45; max-height: 120px;
height: 22px; }
.input-wrap textarea::placeholder { color: var(--dim); }
.rec-btn { width: 40px; height: 40px; border-radius: 50%; border: 1px solid var(--line);
background: var(--bg); color: var(--dim); cursor: pointer; display: grid; place-items: center;
font-size: 17px; flex: 0 0 40px; transition: all .18s; }
.rec-btn:hover { color: var(--fg); border-color: var(--dim); }
.rec-btn.recording { background: var(--err); border-color: var(--err); color: #fff;
animation: pulse 1.4s infinite; }
@keyframes pulse { 0%,100% { box-shadow: 0 0 0 0 rgba(236,57,66,.55); }
50% { box-shadow: 0 0 0 9px rgba(236,57,66,0); } }
.send-btn { height: 40px; padding: 0 18px; border-radius: 11px; border: none;
background: var(--accent); color: #0F1922; font-size: 13px; font-weight: 600; cursor: pointer; }
.send-btn:disabled { background: var(--panel2); color: var(--dim); cursor: default; }
.hint { font-size: 11px; color: var(--dim); margin-top: 8px; display: flex;
justify-content: space-between; }
kbd { background: var(--panel2); border: 1px solid var(--line); border-radius: 4px;
padding: 1px 5px; font-size: 10.5px; }
/* запись — полоса над полем */
.rec-panel { display: flex; align-items: center; gap: 12px; margin-bottom: 10px;
background: var(--bg); border: 1px solid var(--err); border-radius: 11px; padding: 9px 13px; }
.wave { display: flex; align-items: center; gap: 3px; height: 22px; flex: 1; }
.wave i { width: 3px; background: var(--err); border-radius: 2px; height: 30%;
animation: wv .9s infinite ease-in-out; }
.wave i:nth-child(2n) { animation-delay: .12s; }
.wave i:nth-child(3n) { animation-delay: .24s; }
.wave i:nth-child(4n) { animation-delay: .36s; }
@keyframes wv { 0%,100% { height: 22%; } 50% { height: 100%; } }
.rec-time { font-variant-numeric: tabular-nums; font-size: 13px; color: var(--err); font-weight: 600; }
.rec-actions { display: flex; gap: 8px; }
.mini { border: 1px solid var(--line); background: var(--panel2); color: var(--fg);
border-radius: 8px; padding: 5px 11px; font-size: 12px; cursor: pointer; }
.mini.ok { background: var(--accent); border-color: var(--accent); color: #0F1922; font-weight: 600; }
.mini.cancel { color: var(--err); }
.asr-note { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--dim);
margin-bottom: 8px; padding-left: 2px; }
.asr-note .live { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
/* === 005 · выбор ассистента (agent-opt) === */
.d-lead { font-size: 12.5px; color: var(--dim); margin-bottom: 12px; }
.agent-opt { display: flex; align-items: center; gap: 12px; padding: 11px 12px;
border-radius: 11px; background: var(--panel2); margin-bottom: 9px; cursor: pointer;
border: 1.5px solid transparent; }
.agent-opt:hover { border-color: #33414F; }
.agent-opt.on { border-color: var(--accent); background: rgba(106,178,242,.10); }
.agent-opt .pick { width: 20px; height: 20px; flex: 0 0 20px; border-radius: 50%;
border: 2px solid #3D4A58; }
.agent-opt.on .pick { border-color: var(--accent); background: var(--accent); position: relative; }
.agent-opt.on .pick::after { content: ""; position: absolute; left: 5px; top: 1.5px; width: 5px;
height: 9px; border: solid #0F1922; border-width: 0 2px 2px 0; transform: rotate(42deg); }
.agent-opt .ag-state { font-size: 11px; display: flex; align-items: center; gap: 5px; margin-top: 4px; }
.agent-opt .ag-state .d { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.agent-opt .ag-state.ok { color: var(--ok); }
.agent-opt .ag-state.off { color: var(--dim); } .agent-opt .ag-state.off .d { background: var(--dim); }
.agent-opt .ag-state.err { color: var(--err); } .agent-opt .ag-state.err .d { background: var(--err); }
/* === 006 · формы групп (approved-вариант) === */
.d-lead { font-size: 12.5px; color: var(--dim); margin-bottom: 12px; }
.warn { display: flex; gap: 9px; align-items: flex-start; margin-bottom: 15px; padding: 11px 13px;
border-radius: 10px; background: rgba(236,57,66,.09); border: 1px solid rgba(236,57,66,.32);
font-size: 12px; color: #F0A6AA; line-height: 1.5; }
.warn .i { flex: 0 0 auto; font-size: 13px; }
.info { display: flex; gap: 9px; align-items: flex-start; margin-top: 4px; padding: 11px 13px;
border-radius: 10px; background: var(--bg); border: 1px solid var(--line);
font-size: 11.5px; color: var(--dim); line-height: 1.5; }
.danger-zone { margin-top: 20px; padding-top: 15px; border-top: 1px solid var(--line); }
.danger-zone .lbl { font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
color: var(--dim); font-weight: 700; margin-bottom: 9px; }
.btn.dang { color: var(--err); border-color: rgba(236,57,66,.4); }
.btn.dang:hover { border-color: var(--err); background: rgba(236,57,66,.09); }
.btn.dang.solid { background: var(--err); border-color: var(--err); color: #FFF; font-weight: 650; }
.btn.wide { width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px;
border-style: dashed; color: var(--dim); }
.btn.wide:hover { color: var(--fg); border-color: var(--dim); }
.grp { display: flex; align-items: center; gap: 12px; padding: 11px 12px; border-radius: 11px;
background: var(--panel2); margin-bottom: 9px; border: 1px solid transparent; }
.grp:hover { border-color: var(--line); }
.grp .mid { flex: 1; min-width: 0; }
.grp .nm { font-size: 14px; font-weight: 550; }
.grp .sub { font-size: 11.5px; color: var(--dim); margin-top: 3px; }
.grp .acts { display: flex; gap: 4px; flex: 0 0 auto; }
.grp.sys { background: transparent; border: 1px dashed var(--line); }
.grp.sys .nm { color: var(--fg); }
.grp .lock { font-size: 11px; color: var(--dim); flex: 0 0 auto; }
.field { margin-bottom: 15px; }
.field label { display: block; font-size: 12px; color: var(--dim); margin-bottom: 6px; }
.field input { width: 100%; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line);
background: var(--bg); color: var(--fg); font-size: 13.5px; outline: none; font-family: inherit; }
.field input:focus { border-color: var(--accent); }
.field .note { font-size: 11px; color: var(--dim); margin-top: 6px; }