346 lines
20 KiB
HTML
346 lines
20 KiB
HTML
<!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>
|