Требования (черновик) + вариант 1: убрана подсказка, кольцо токенов, узкое окно с кнопкой назад
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
|||||||
|
# Требования к десктопному клиенту agentik
|
||||||
|
|
||||||
|
**Статус:** накидываем. Это черновик — ни один пункт не является обязательным к
|
||||||
|
исполнению в том виде, как записан. Позже пересматриваем.
|
||||||
|
|
||||||
|
**Основа дизайна:** вариант 1 (`sketches/001-sidebar-utility`). Варианты 2 и 3
|
||||||
|
остаются рядом как источник идей.
|
||||||
|
|
||||||
|
**Сейчас описываем только поведение.** Кода нет и не пишем, пока требования не устоятся.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Общее
|
||||||
|
|
||||||
|
- **R1.** Клиент — только для одного ассистента, не для переписки с людьми.
|
||||||
|
Никаких «пользователей», «контактов», «групп» — их нет и не должно быть.
|
||||||
|
- **R2.** Стиль — тёмная тема, уже принятая в клиентах пользователя
|
||||||
|
(фон `#121218`, панели `#17212B`, текст `#EBEBEB`).
|
||||||
|
- **R3.** Что видно на экране в любой момент: список диалогов, выбранный диалог
|
||||||
|
и поле ввода. Больше ничего обязательного нет.
|
||||||
|
|
||||||
|
## 2. Раскладка и адаптивность
|
||||||
|
|
||||||
|
- **R4.** **Широкое окно** — две части: список диалогов слева, открытый диалог справа.
|
||||||
|
- **R5.** **Узкое окно** — на экране что-то одно: либо список диалогов, либо
|
||||||
|
конкретный диалог. Переключение — как в Телеграме.
|
||||||
|
- **R6.** В узком окне у открытого диалога сверху слева — кнопка «Назад».
|
||||||
|
Нажатие возвращает к списку диалогов на всю ширину.
|
||||||
|
- **R7.** Порог «широкое/узкое» — по ширине окна, одна величина. Какой именно
|
||||||
|
размер считать порогом — решим позже.
|
||||||
|
|
||||||
|
## 3. Список диалогов
|
||||||
|
|
||||||
|
- **R8.** Список диалогов — это список бесед с одним и тем же ассистентом,
|
||||||
|
различающихся темой (задача, контекст, история).
|
||||||
|
- **R9.** Группировка диалогов остаётся: «Работа», «Дом» и подобное. Подпись и
|
||||||
|
состав групп — настраиваемые.
|
||||||
|
- **R10.** В строке диалога: название, последнее сообщение, время, счётчик
|
||||||
|
непрочитанных.
|
||||||
|
- **R11.** Есть поиск по диалогам.
|
||||||
|
|
||||||
|
## 4. Открытый диалог
|
||||||
|
|
||||||
|
- **R12.** Показ сообщений: мои и ассистента, с временем.
|
||||||
|
- **R13.** Ответ ассистента можно прервать — на видном месте кнопка «Стоп».
|
||||||
|
- **R14.** Сообщения приходят вживую, по мере генерации (текст дотекает).
|
||||||
|
|
||||||
|
## 5. Панель ввода
|
||||||
|
|
||||||
|
- **R15.** Поле ввода, кнопка «Отправить» и **отдельная кнопка записи микрофона**.
|
||||||
|
- **R16.** Подсказка о горячих клавишах под полем **убирается** — её не должно быть.
|
||||||
|
(Убрать «Ctrl+R — запись · Enter — отправить · Shift+Enter — новая строка».)
|
||||||
|
- **R17.** Пока идёт запись, ввод текста не должен вводить в заблуждение:
|
||||||
|
должно быть понятно, что сейчас пишется голос.
|
||||||
|
- **R18.** Отмена записи — явная.
|
||||||
|
|
||||||
|
## 6. Голос
|
||||||
|
|
||||||
|
- **R19.** Кнопка записи включает микрофон и останавливает его. Точная механика
|
||||||
|
(по нажатию или на удержание) — см. раздел 10.
|
||||||
|
- **R20.** Распознавание речи — **отдельная тема, здесь не решается.** В интерфейсе
|
||||||
|
важно только: где кнопка, что видно во время записи, что происходит после.
|
||||||
|
|
||||||
|
## 7. Состояние агента
|
||||||
|
|
||||||
|
- **R21.** Показывать, **сколько токенов израсходовано** за диалог целиком.
|
||||||
|
- **R22.** Показывать, **сколько занято сейчас** и **сколько всего доступно**.
|
||||||
|
- **R23.** Показывать это **кольцом заполнения** — как сильно заполнено.
|
||||||
|
- **R24.** По умолчанию состояние **свёрнуто**; по нажатию — разворачивается
|
||||||
|
с подробностями.
|
||||||
|
- **R25.** Место и точный вид — решим позже.
|
||||||
|
|
||||||
|
## 8. Ход работы агента
|
||||||
|
|
||||||
|
- **R26.** Показывать не только ответы ассистента, но и происходящее вокруг них:
|
||||||
|
вызовы инструментов, рассуждения, ошибки.
|
||||||
|
- **R27.** Свёрнутый вид — одна строка: кратко, что происходит
|
||||||
|
(например, «вызов инструмента: такой-то»).
|
||||||
|
- **R28.** Развёрнутый вид — подробности этого шага: аргументы, результат, время.
|
||||||
|
- **R29.** Сворачивание/разворачивание — по нажатию на строку.
|
||||||
|
- **R30.** Что именно показывать в строке — решим позже.
|
||||||
|
|
||||||
|
## 9. Отдельным списком: чего в библиотеке agentik СЕЙЧАС нет
|
||||||
|
|
||||||
|
Проверено по коду `pw.binom.agentik:client` (версия 3):
|
||||||
|
|
||||||
|
- **Токенов нет вообще.** Ни в сообщениях, ни в событиях, ни в диалоге. То есть
|
||||||
|
R21–R23 сейчас не из чего взять — потребуется доработка на стороне агента
|
||||||
|
и протокола. Это не задача дизайна, но без неё кольцо нарисовать нечем.
|
||||||
|
- **Есть события** (их и показываем в R26–R30): начало ответа, рассуждение,
|
||||||
|
текст по мере генерации, картинка, вызов инструмента, результат инструмента,
|
||||||
|
конец хода, прерывание, ошибка.
|
||||||
|
- **Есть события о диалогах целиком:** создан, удалён, переименован.
|
||||||
|
- **Есть:** прерывание ответа, история сообщений, список диалогов.
|
||||||
|
|
||||||
|
## 10. Решения, которые ещё не приняты
|
||||||
|
|
||||||
|
- Запись: по нажатию (нажал — говоришь — нажал) или на удержание.
|
||||||
|
- Нужны ли вложения (картинки) в этом клиенте.
|
||||||
|
- Как именно выглядит кольцо токенов и где живёт.
|
||||||
|
|
||||||
|
## 11. Как проверяем
|
||||||
|
|
||||||
|
Проверка — **живой прогон**, а не «собралось». Правило пользователя: перед показом
|
||||||
|
гонять сценарий руками. Сценарии по этим требованиям будут отдельно.
|
||||||
@@ -71,8 +71,9 @@
|
|||||||
/* ---------- правая часть: чат ---------- */
|
/* ---------- правая часть: чат ---------- */
|
||||||
.main { flex: 1; display: flex; flex-direction: column; }
|
.main { flex: 1; display: flex; flex-direction: column; }
|
||||||
.chat-head {
|
.chat-head {
|
||||||
|
position: relative;
|
||||||
height: 58px; flex: 0 0 58px; border-bottom: 1px solid var(--line); display: flex; align-items: center;
|
height: 58px; flex: 0 0 58px; border-bottom: 1px solid var(--line); display: flex; align-items: center;
|
||||||
gap: 11px; padding: 0 16px;
|
gap: 8px; padding: 0 16px;
|
||||||
}
|
}
|
||||||
.chat-head .avatar { width: 36px; height: 36px; flex: 0 0 36px; font-size: 13px; }
|
.chat-head .avatar { width: 36px; height: 36px; flex: 0 0 36px; font-size: 13px; }
|
||||||
.head-meta { flex: 1; }
|
.head-meta { flex: 1; }
|
||||||
@@ -80,6 +81,46 @@
|
|||||||
.head-sub { font-size: 11.5px; color: var(--dim); }
|
.head-sub { font-size: 11.5px; color: var(--dim); }
|
||||||
.head-sub .dot { color: #63C88A; }
|
.head-sub .dot { color: #63C88A; }
|
||||||
|
|
||||||
|
/* кнопка «назад» — нужна только в узком окне */
|
||||||
|
.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); }
|
||||||
|
|
||||||
|
/* кольцо израсходованного контекста */
|
||||||
|
.tok-btn {
|
||||||
|
display: flex; align-items: center; gap: 8px; background: transparent; border: none;
|
||||||
|
cursor: pointer; color: var(--dim); padding: 4px 6px; border-radius: 9px; font-size: 11.5px;
|
||||||
|
}
|
||||||
|
.tok-btn:hover { background: var(--panel); color: var(--fg); }
|
||||||
|
.tok-ring { position: relative; width: 27px; height: 27px; flex: 0 0 27px; }
|
||||||
|
.tok-ring svg { transform: rotate(-90deg); display: block; }
|
||||||
|
.tok-ring .pct {
|
||||||
|
position: absolute; inset: 0; display: grid; place-items: center;
|
||||||
|
font-size: 8.5px; font-weight: 700; color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* развёрнутая карточка состояния */
|
||||||
|
.tok-panel {
|
||||||
|
display: none; position: absolute; top: 62px; right: 16px; width: 300px; min-width: 300px;
|
||||||
|
flex: 0 0 auto; z-index: 40;
|
||||||
|
background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
|
||||||
|
box-shadow: 0 16px 48px rgba(0,0,0,.55); padding: 14px 16px;
|
||||||
|
}
|
||||||
|
.tok-panel.on { display: block; }
|
||||||
|
.tok-panel h4 { font-size: 10.5px; letter-spacing: 1.1px; text-transform: uppercase;
|
||||||
|
color: var(--dim); margin-bottom: 11px; font-weight: 700; }
|
||||||
|
.tok-row { display: flex; justify-content: space-between; font-size: 12.5px; margin-bottom: 8px; }
|
||||||
|
.tok-row span:first-child { color: var(--dim); }
|
||||||
|
.tok-row span:last-child { font-weight: 600; }
|
||||||
|
.tok-bar { height: 6px; border-radius: 3px; background: var(--panel2); overflow: hidden; margin: 3px 0 12px; }
|
||||||
|
.tok-bar i { display: block; height: 100%; background: var(--accent); }
|
||||||
|
.tok-sep { height: 1px; background: var(--line); margin: 12px 0; }
|
||||||
|
.tok-kv { font-size: 11.5px; color: var(--dim); display: flex; justify-content: space-between; margin-bottom: 6px; }
|
||||||
|
.tok-kv b { color: var(--fg); font-weight: 600; }
|
||||||
|
|
||||||
.messages { flex: 1; overflow-y: auto; padding: 18px 24px; display: flex; flex-direction: column; gap: 12px; }
|
.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 { 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.user { align-self: flex-end; background: var(--user-bubble); border-bottom-right-radius: 4px; }
|
||||||
@@ -164,6 +205,20 @@
|
|||||||
.asr-note.on { display: flex; }
|
.asr-note.on { display: flex; }
|
||||||
.asr-note .live { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
|
.asr-note .live { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
|
||||||
|
|
||||||
|
/* ---------- узкое окно: либо список, либо чат ---------- */
|
||||||
|
.backdrop {
|
||||||
|
display: none; position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: 90;
|
||||||
|
}
|
||||||
|
.backdrop.on { display: block; }
|
||||||
|
@media (max-width: 880px) {
|
||||||
|
body { padding: 0; }
|
||||||
|
.window { width: 100vw; height: 100vh; border-radius: 0; border: none; transform: none !important; }
|
||||||
|
.window.mode-chat .sidebar { display: none; }
|
||||||
|
.window.mode-list .main { display: none; }
|
||||||
|
.sidebar { width: 100%; flex: 1 1 auto; border-right: none; }
|
||||||
|
.back-btn { display: grid; }
|
||||||
|
}
|
||||||
|
|
||||||
/* окно целиком влезает на любой экран — не режем картинку */
|
/* окно целиком влезает на любой экран — не режем картинку */
|
||||||
@media (max-height: 820px) { body { padding: 8px; } .window, .win { transform: scale(.78); } }
|
@media (max-height: 820px) { body { padding: 8px; } .window, .win { transform: scale(.78); } }
|
||||||
@media (max-height: 700px) { .window, .win { transform: scale(.66); } }
|
@media (max-height: 700px) { .window, .win { transform: scale(.66); } }
|
||||||
@@ -225,12 +280,41 @@
|
|||||||
<!-- ================= ЧАТ ================= -->
|
<!-- ================= ЧАТ ================= -->
|
||||||
<main class="main">
|
<main class="main">
|
||||||
<header class="chat-head">
|
<header class="chat-head">
|
||||||
|
<button class="back-btn" id="backBtn" title="Назад к диалогам">←</button>
|
||||||
<div class="avatar">АГ</div>
|
<div class="avatar">АГ</div>
|
||||||
<div class="head-meta">
|
<div class="head-meta">
|
||||||
<div class="head-name">agentik · рабочий</div>
|
<div class="head-name">agentik · рабочий</div>
|
||||||
<div class="head-sub"><span class="dot">●</span> на связи · модель codding-big</div>
|
<div class="head-sub"><span class="dot">●</span> на связи</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button class="tok-btn" id="tokBtn" title="Израсходовано контекста — нажми для подробностей">
|
||||||
|
<span class="tok-ring">
|
||||||
|
<svg width="27" height="27">
|
||||||
|
<circle cx="13.5" cy="13.5" r="11" fill="none" stroke="#232B36" stroke-width="2.5"></circle>
|
||||||
|
<circle cx="13.5" cy="13.5" r="11" fill="none" stroke="#6AB2F2" stroke-width="2.5"
|
||||||
|
stroke-linecap="round" stroke-dasharray="69.1" stroke-dashoffset="45.6"></circle>
|
||||||
|
</svg>
|
||||||
|
<span class="pct">34%</span>
|
||||||
|
</span>
|
||||||
|
<span>34.3k / 100k</span>
|
||||||
|
</button>
|
||||||
<button class="icon-btn" title="Параметры диалога">⋯</button>
|
<button class="icon-btn" title="Параметры диалога">⋯</button>
|
||||||
|
|
||||||
|
<!-- разворачивается по нажатию на кольцо -->
|
||||||
|
<div class="tok-panel" id="tokPanel">
|
||||||
|
<h4>Контекст диалога</h4>
|
||||||
|
<div class="tok-row"><span>Занято сейчас</span><span>34 300</span></div>
|
||||||
|
<div class="tok-bar"><i style="width:34%"></i></div>
|
||||||
|
<div class="tok-row"><span>Всего доступно</span><span>100 000</span></div>
|
||||||
|
<div class="tok-row"><span>Осталось</span><span>65 700</span></div>
|
||||||
|
<div class="tok-sep"></div>
|
||||||
|
<h4>За диалог целиком</h4>
|
||||||
|
<div class="tok-kv"><span>Отправлено в модель</span><b>412 800</b></div>
|
||||||
|
<div class="tok-kv"><span>Получено от модели</span><b>58 240</b></div>
|
||||||
|
<div class="tok-kv"><span>Ходов</span><b>18</b></div>
|
||||||
|
<div class="tok-sep"></div>
|
||||||
|
<div class="tok-kv"><span>Модель</span><b>codding-big</b></div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="messages" id="messages">
|
<div class="messages" id="messages">
|
||||||
@@ -277,8 +361,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
<span><kbd>Ctrl</kbd>+<kbd>R</kbd> — запись голоса · <kbd>Enter</kbd> — отправить · <kbd>Shift</kbd>+<kbd>Enter</kbd> — новая строка</span>
|
<span id="statusHint"></span>
|
||||||
<span id="statusHint">готов</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -357,6 +440,35 @@
|
|||||||
document.addEventListener('keydown', e => {
|
document.addEventListener('keydown', e => {
|
||||||
if (e.ctrlKey && e.key.toLowerCase() === 'r') { e.preventDefault(); recBtn.click(); }
|
if (e.ctrlKey && e.key.toLowerCase() === 'r') { e.preventDefault(); recBtn.click(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------- широкое / узкое окно ----------
|
||||||
|
const win = document.querySelector('.window');
|
||||||
|
const NARROW = 880;
|
||||||
|
function applyMode() {
|
||||||
|
if (innerWidth > NARROW) { // широкое: видно и то, и другое
|
||||||
|
win.classList.remove('mode-chat', 'mode-list');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// узкое: показываем что-то одно, по умолчанию список
|
||||||
|
if (!win.classList.contains('mode-chat') && !win.classList.contains('mode-list')) {
|
||||||
|
win.classList.add('mode-list');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.querySelectorAll('.chat-row').forEach(r => r.addEventListener('click', () => {
|
||||||
|
document.querySelectorAll('.chat-row').forEach(x => x.classList.remove('active'));
|
||||||
|
r.classList.add('active');
|
||||||
|
if (innerWidth <= NARROW) win.classList.remove('mode-list'), win.classList.add('mode-chat');
|
||||||
|
}));
|
||||||
|
document.getElementById('backBtn').onclick = () => win.classList.remove('mode-chat'), win.classList.add('mode-list');
|
||||||
|
addEventListener('resize', applyMode);
|
||||||
|
applyMode();
|
||||||
|
|
||||||
|
// ---------- кольцо токенов: свёрнуто -> развёрнуто ----------
|
||||||
|
const tokPanel = document.getElementById('tokPanel');
|
||||||
|
document.getElementById('tokBtn').onclick = e => { e.stopPropagation(); tokPanel.classList.toggle('on'); };
|
||||||
|
document.addEventListener('click', e => {
|
||||||
|
if (tokPanel.classList.contains('on') && !tokPanel.contains(e.target)) tokPanel.classList.remove('on');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user