Files
agentik-desktop/sketches/004-settings-agents/index.html
T

425 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<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; }
.chat-list { flex: 1; overflow-y: auto; padding: 4px 8px 12px; }
.chat-row { display: flex; gap: 11px; padding: 9px 10px; border-radius: 10px; align-items: flex-start; }
.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; }
.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; }
.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); }
.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);
display: flex; align-items: center; justify-content: center; z-index: 100;
}
.scrim.hide { display: none; }
.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);
}
.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; display: flex; align-items: center; gap: 8px; }
.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; }
.err-note { font-size: 11.5px; color: var(--err); margin-top: 6px; display: none; }
.err-note.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); }
/* проверка подключения */
.check-row { display: flex; align-items: center; gap: 11px; padding: 11px 13px;
background: var(--bg); border: 1px solid var(--line); border-radius: 10px; }
.check-row .txt { flex: 1; font-size: 12.5px; }
.check-row .txt b { font-weight: 600; }
.check-row.ok { border-color: rgba(99,200,138,.5); }
.check-row.err { border-color: rgba(236,57,66,.5); }
.spin { width: 15px; height: 15px; border-radius: 50%; border: 2px solid var(--line);
border-top-color: var(--accent); animation: sp .7s linear infinite; flex: 0 0 15px; }
@keyframes sp { to { transform: rotate(360deg); } }
/* результаты проверки */
.probe { font-size: 11.5px; color: var(--dim); margin-top: 11px; }
.probe div { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid var(--line); }
.probe div:last-child { border-bottom: none; }
.probe b { color: var(--fg); font-weight: 550; }
/* кнопки */
.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; }
.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); }
.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); }
/* переключатель состояний — только для показа */
.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: 200;
}
.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; }
.chat-row { cursor: pointer; }
.chat-row:hover { background: var(--panel2); }
.chat-row.active { background: #233040; }
.agent-tag { font-size: 10px; font-weight: 700; padding: 1px 5px; border-radius: 5px;
background: rgba(106,178,242,.16); color: var(--accent); }
.agent-tag.g2 { background: rgba(134,201,154,.16); color: #86C99A; }
.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-ok">Проверка: успех</button>
<button data-view="check-err">Проверка: ошибка</button>
<button data-view="multi">Список диалогов</button>
</div>
<div class="window">
<!-- ===== фон: клиент ===== -->
<aside class="sidebar">
<div class="sb-head">
<div class="sb-title">
<span id="sbTitle">Диалоги</span>
<button class="x-btn" title="Новый диалог">+</button>
</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">на связи</div>
</div>
</header>
<div class="messages">
<div class="msg user">Проверь, почему падает CI</div>
<div class="msg bot">Нашёл: длинное тире в именах тестов ломает сборку. Починил, CI зелёный.</div>
</div>
</main>
<!-- ===== модалка настроек ===== -->
<div class="scrim" id="scrim">
<div class="dialog">
<div class="d-head">
<h2 id="dTitle">Агенты</h2>
<button class="x-btn" onclick="document.getElementById('scrim').classList.add('hide')">✕</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 off"><span class="d"></span>не отвечает</div>
<div class="card-acts">
<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>
<input type="text" class="mono" value="http://192.168.88.135:8080/agentik"
placeholder="http://хост:порт/agentik">
<div class="note">Адрес целиком, вместе с путём <code>/agentik</code>. Если агент поднимается
на этой же машине — <code>http://localhost:8080/agentik</code>.</div>
<div class="err-note" id="addrErr">Адрес должен начинаться с http:// или https://</div>
</div>
<div class="field">
<label>Цвет — чтобы отличать в списке</label>
<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>
<div class="check-row" id="checkRow">
<button class="btn" onclick="runCheck()" style="padding:7px 13px;font-size:12.5px">Проверить связь</button>
<span class="txt" id="checkTxt">Нажмите, чтобы проверить, отвечает ли агент</span>
</div>
<div class="probe" id="probe" 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>
</div>
<div class="d-foot" id="footList">
<button class="btn prim" style="flex:1" onclick="document.getElementById('scrim').classList.add('hide')">Готово</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>
<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>
<div class="chat-row">
<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>`;
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;
// ---------------- переключение видов ----------------
const views = { list:'viewList', add:'viewAdd' };
function show(v) {
document.getElementById('viewList').style.display = (v === 'list') ? 'block' : 'none';
document.getElementById('viewAdd').style.display = (v === 'add') ? 'block' : 'none';
document.getElementById('footList').style.display = (v === 'list') ? 'flex' : 'none';
document.getElementById('footAdd').style.display = (v === 'add') ? 'flex' : 'none';
document.getElementById('dTitle').textContent = (v === 'list') ? 'Агенты' : 'Новый агент';
document.getElementById('scrim').classList.remove('hide');
if (v === 'add') resetCheck();
}
function resetCheck() {
const row = document.getElementById('checkRow');
row.className = 'check-row';
document.getElementById('checkTxt').textContent = 'Нажмите, чтобы проверить, отвечает ли агент';
document.getElementById('probe').style.display = 'none';
}
// проверка связи: три состояния — идёт / успех / ошибка
function runCheck(mode) {
const row = document.getElementById('checkRow');
const txt = document.getElementById('checkTxt');
const probe = document.getElementById('probe');
row.className = 'check-row';
row.firstElementChild.outerHTML =
'<span class="spin"></span>';
txt.textContent = 'Спрашиваю агента…';
probe.style.display = 'none';
setTimeout(() => {
const fail = (mode === 'err');
// вернуть кнопку на место
row.firstElementChild.outerHTML =
'<button class="btn" onclick="runCheck()" style="padding:7px 13px;font-size:12.5px">Проверить связь</button>';
if (fail) {
row.className = 'check-row err';
txt.innerHTML = '<b>Не отвечает.</b> Проверьте адрес и что агент запущен.';
probe.style.display = 'block';
document.getElementById('p1').textContent = 'нет ответа';
document.getElementById('p2').textContent = '—';
document.getElementById('p3').textContent = '—';
} else {
row.className = 'check-row ok';
txt.innerHTML = '<b>Отвечает.</b> Можно добавлять.';
probe.style.display = 'block';
document.getElementById('p1').textContent = 'ok';
document.getElementById('p2').textContent = 'http://192.168.88.135:8080/agentik';
document.getElementById('p3').textContent = '4';
}
}, 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 === 'list') { chatArea.innerHTML = CHATS_SINGLE; document.getElementById('sbTitle').textContent = 'Диалоги'; show('list'); }
if (v === 'add') { chatArea.innerHTML = CHATS_SINGLE; show('add'); }
if (v === 'check-ok') { chatArea.innerHTML = CHATS_SINGLE; show('add'); setTimeout(() => runCheck('ok'), 60); }
if (v === 'check-err') { chatArea.innerHTML = CHATS_SINGLE; show('add'); setTimeout(() => runCheck('err'), 60); }
if (v === 'multi') { chatArea.innerHTML = CHATS_MULTI; document.getElementById('sbTitle').textContent = 'Диалоги';
document.getElementById('scrim').classList.add('hide'); }
});
// на «Список диалогов» модалку закрываем
document.querySelector('[data-view="multi"]').addEventListener('click', () => {
document.getElementById('scrim').classList.add('hide');
});
// выбор цвета
document.querySelectorAll('.swatch').forEach(s => s.onclick = () => {
document.querySelectorAll('.swatch').forEach(x => x.classList.remove('on'));
s.classList.add('on');
});
</script>
</body>
</html>