diff --git a/README.md b/README.md index 4d1c623..ebc62f4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ sketches/001-sidebar-utility/index.html # привычный мессенджер — ВЫБРАН sketches/002-rail-voice-first/index.html # голос — главное действие sketches/003-three-pane-command/index.html # три панели + состояние агента -sketches/004-settings-agents/index.html # настройки: добавление агентов +sketches/004-settings-agents/index.html # настройки агентов + проверка связи ``` `REQUIREMENTS.md` — требования. Статус: накидываем, ни один пункт не обязателен @@ -34,6 +34,11 @@ sketches/004-settings-agents/index.html # настройки: добавл точка у группы, цвет аватара), возможно плюс метка. - **Markdown** — показывать разметкой, а не сырыми символами. Готовая библиотека уже используется в клиенте assistent, писать свою не надо. +- **Папки над списком диалогов** («Все чаты», «Работа», «Дом») — часть выбранного + варианта 1, не терять их при доработках. +- **Проверка связи с агентом** — в отдельном окне поверх настроек: идёт → отвечает + → не отвечает, с подробностями и подсказкой. Добавить агента можно только + после успешной проверки. ## Как смотреть diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 82229cf..4ee3284 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -33,6 +33,9 @@ - **R8.** В строке диалога: название, последнее сообщение, время, счётчик непрочитанных. - **R9.** Есть поиск по диалогам. +- **R9.1.** Над списком — **папки** (круглые кнопки): «Все чаты», «Работа», «Дом» + и «+» для новой. Это часть одобренного варианта 1, не забывать при доработках. +- **R9.2.** В строке диалога — круглый значок непрочитанных, если есть. ## 4. Открытый диалог @@ -79,8 +82,13 @@ кнопки изменить и удалить. - **R23.3.** Форма нового агента: название, адрес сервера, цвет для различения. Под каждым полем — пояснение простым языком. -- **R23.4.** **Проверка связи до сохранения.** Кнопка спрашивает агента и показывает - ответ, адрес и число диалогов. Три состояния: спрашиваю → отвечает → не отвечает. +- **R23.4.** **Проверка связи до сохранения** — в **отдельном окне поверх настроек**, + не строкой внутри формы. Нажал «Проверить связь» — открылось второе окно, в нём + идёт проверка. Пока идёт: «спрашиваю агента…» с крутящимся значком. Результат — + зелёный значок «отвечает» или красный «не отвечает», и под ним подробности: + ответ на запрос, адрес, число диалогов, отклик в миллисекундах. + Кнопка «Добавить агента» становится доступной только при успехе. + Есть «Проверить снова». Закрытие окна возвращает к настройкам. Основа: агент отвечает на запрос состояния и отдаёт список диалогов; оба запроса ничего не меняют. - **R23.5.** Цвет агента работает не только в настройках: тем же цветом помечаются diff --git a/sketches/004-settings-agents/README.md b/sketches/004-settings-agents/README.md index 93bd546..9e6258c 100644 --- a/sketches/004-settings-agents/README.md +++ b/sketches/004-settings-agents/README.md @@ -1,40 +1,61 @@ # Вариант 4 — Настройки: агенты ## Суть -Модальное окно, где заводятся агенты: имя, адрес сервера, цвет. Список уже -добавленных и форма нового — два состояния одного окна. +Два окна друг над другом. Первое — настройки: список агентов и форма нового. +Второе, поверх — **проверка связи**: нажал «Проверить связь» и она идёт +в отдельном окне, а не строкой внутри формы. -## Ключевые решения -- **Окно, а не отдельная страница.** Настройки агентов открываются поверх чата — - не теряешь, где был. -- **Состояние агента видно сразу:** «на связи» зелёным, «не отвечает» серым. - В списке, а не спрятано в глубине. -- **Проверка связи до сохранения.** Кнопка «Проверить связь» спрашивает агента - и показывает, что ответил: ответ на запрос, адрес, сколько у него диалогов. - Три состояния: спрашиваю (крутится) → отвечает (зелёное) → не отвечает (красное). -- **Цвет — для различения.** Пять вариантов. Тот же цвет потом работает в списке - диалогов: полоска у строки и точка у группы. -- **Пояснения под полями** — простым языком, зачем это поле и что писать. +## Окно 1: список агентов +- У каждого агента: имя, адрес, состояние — «на связи» зелёным, «не отвечает» серым, + ошибка красным. Видно сразу, а не спрятано в глубине. +- Кнопки в строке: **проверить связь** (⇄), **изменить** (✎), **удалить** (🗑). + Проверка доступна и для уже добавленного агента — из этого же списка. +- Внизу пунктирная «+ Добавить агента». -## Проверка связи — на чём основана -У сервера агента есть ответ на `GET /agentik/health` (отдаёт «ok») и список -диалогов. Оба запроса безопасные: ничего не меняют. Если агент лежит — -«не отвечает», и это видно до добавления, а не после. +## Окно 1, вид 2: форма нового агента +- **Название** — с пояснением, что имя придумывает пользователь: сервер своего + имени не сообщает (это из исходников, не догадка). +- **Адрес сервера** — целиком, вместе с путём `/agentik`. Прямо в строке адреса + кнопка **«Проверить связь»**. +- **Цвет** для различения, пять вариантов. + +## Окно 2: проверка связи (отдельное, поверх настроек) +Три состояния, сменяются в одном окне: + +1. **Идёт** — крутящееся кольцо, «Спрашиваю агента…», под ним адрес. +2. **Отвечает** — зелёная галочка, «Агент отвечает». Подробности: ответ на запрос, + адрес, число диалогов, отклик в миллисекундах. Подсказка «Адрес рабочий». + Кнопка внизу — «Добавить агента» (доступна). +3. **Не отвечает** — красный крестик. Подробности: нет ответа, отклик истёк. + Подсказка: проверить адрес, что агент запущен, что порт открыт и путь `/agentik` + указан целиком. Кнопка внизу заблокирована — добавить нельзя. + +Внизу окна всегда есть **«Проверить снова»**. Закрытие окна возвращает к настройкам, +а не выбрасывает из них. + +## Почему проверка отдельным окном — лучше +- Места больше: видно и результат, и подробности, и объяснение, что делать дальше. +- Основную форму не дёргает: пока проверяешь — поля на месте, ничего не сбилось. +- Одно и то же окно годится и для формы, и для уже добавленного агента. + +## На чём основана проверка (не выдумка) +У сервера агента есть ответ на `GET /agentik/health` (отдаёт «ok») и запрос +списка диалогов. Оба безопасные — ничего не меняют. Адреса взяты из исходников. ⚠️ **Живого агента сейчас не поднято** — на порту 8080 отвечает чужой сервер (404). -Поэтому в макете проверка показана в трёх состояниях, но на живом агенте она -ещё не проверялась. Это не выдумка: адреса запросов взяты из исходников. +Поэтому на настоящем агенте проверка ещё не гонялась: в макете показаны все три +состояния, но замеры времени и число диалогов — условные. -## Второе состояние: список диалогов с двумя агентами -Показано, как различать агентов, когда их несколько: -- **Заголовок группы** с цветной точкой и числом диалогов; -- **Цветная полоска** слева у каждого диалога; -- **Цвет аватара** совпадает с цветом агента. +## Фон: папки вернулись +В первом варианте над списком диалогов есть **папки** — «Все чаты», «Работа», «Дом», +«+». В прошлой версии этого макета я их потерял. Теперь на фоне видны и папки, +и поиск, и значок непрочитанных — как в одобренном варианте 1. -Это позволяет не путать, кто из агентов что сказал, даже в длинном списке. +## Плюс: список диалогов с двумя агентами +Диалоги сгруппированы по агентам, у группы цветная точка, у каждой строки цветная +полоска, цвет аватара совпадает с цветом агента из настроек. ## Что слабо / открытые вопросы -- Одного цвета для различения может оказаться мало — возможно, понадобится - подпись агента прямо в строке диалога. -- Где хранится список агентов (в файле на диске или на сервере) — не решено. -- Нужен ли пароль/ключ доступа к агенту — у библиотеки такого поля нет. +- Одного цвета для различения может оказаться мало. +- Где хранится список агентов — не решено. +- Пароля/ключа доступа у библиотеки нет — поля для него нет. diff --git a/sketches/004-settings-agents/index.html b/sketches/004-settings-agents/index.html index ae61d76..b072b03 100644 --- a/sketches/004-settings-agents/index.html +++ b/sketches/004-settings-agents/index.html @@ -22,42 +22,76 @@ 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; } + .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); - display: flex; align-items: center; justify-content: center; z-index: 100; - } - .scrim.hide { display: none; } + /* ============ ЗАТЕМНЕНИЕ (одно на оба окна) ============ */ + .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; @@ -74,7 +108,7 @@ .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-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; } @@ -97,48 +131,58 @@ .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; } + .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 { 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; } + 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); } + .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: 200; + 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; } @@ -149,12 +193,6 @@ .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); } } @@ -166,20 +204,29 @@
- - + +
- + @@ -188,8 +235,9 @@
АГ
agentik · рабочий
-
на связи
+
● на связи
+
Проверь, почему падает CI
@@ -197,12 +245,15 @@
- -
+ +
+ + +

Агенты

- +
@@ -226,8 +277,9 @@
домашний
http://192.168.76.199:8080/agentik
-
не отвечает
+
не отвечает
+
@@ -246,11 +298,13 @@
- -
Адрес целиком, вместе с путём /agentik. Если агент поднимается +
+ + +
+
Адрес целиком, вместе с путём /agentik. Если агент на этой же машине — http://localhost:8080/agentik.
-
Адрес должен начинаться с http:// или https://
@@ -263,21 +317,10 @@
- -
- - Нажмите, чтобы проверить, отвечает ли агент -
- -
- +
+ + +
+ + +
+
+
+

Проверка связи

+ +
+ +
+
+
+
+
Спрашиваю агента…
+
http://192.168.88.135:8080/agentik
+
+
+ + + + +
+ +
+ + +
+
+