feat: the panel's own confirm and prompt dialogs everywhere; the assistant's field is one line with a square send button

This commit is contained in:
redtea committed 2026-10-15 22:16:02 +03:00
1 parent 3f113ce8d6
commit 1f930e5aea
13 files changed
+104 -41

No files matched your search

+10 -4
View File
@@ -6,6 +6,8 @@
// Off until the user turns it on in the profile.
(function () {
'use strict';
const PLANE = '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M2.4 10.6 20.6 3.3c.8-.3 1.5.4 1.2 1.2l-7.3 18.2c-.3.8-1.5.8-1.8 0l-2.6-6.9-6.9-2.6c-.8-.3-.8-1.5 0-1.8zm7.3 4.4 2 5.2L18 6 9.7 15z"/></svg>';
const STOP = '<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="5" y="5" width="14" height="14" rx="2"/></svg>';
const esc = v => String(v).replace(/[&<>"]/g, c => ({'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;'}[c]));
const fab = document.createElement('button');
fab.type = 'button';
@@ -22,8 +24,8 @@
'<button type="button" class="btn ghost small ai-clear" title="Новый разговор">Очистить</button>' +
'<button type="button" class="btn ghost small icon close x ai-close" aria-label="Закрыть"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg></button></div>' +
'<div class="ai-list"><p class="muted ai-hello">Спросите о том, что открыто в панели: ассистент видит вкладки и активный терминал или папку. Команды он предложит кнопкой «Выполнить».</p></div>' +
'<form class="ai-form-in"><textarea rows="2" placeholder="Сообщение… (Enter - отправить, Shift+Enter - строка)"></textarea>' +
'<button type="submit" class="ai-send">Отправить</button></form>';
'<form class="ai-form-in"><textarea rows="1" placeholder="Сообщение…"></textarea>' +
'<button type="submit" class="ai-send" title="Отправить" aria-label="Отправить">' + PLANE + '</button></form>';
document.body.append(fab, box);
const q = s => box.querySelector(s);
const list = q('.ai-list'), input = q('textarea'), send = q('.ai-send');
@@ -122,7 +124,7 @@
const el = add('assistant', '');
el.classList.add('wait');
ac = new AbortController();
send.textContent = 'Стоп';
send.innerHTML = STOP; send.title = 'Остановить';
let answer = '';
try {
const r = await fetch('/api/panel/ai/chat', {
@@ -144,7 +146,7 @@
if (e.name !== 'AbortError') answer += (answer ? '\n' : '') + '[ошибка: ' + (e.message || 'нет связи') + ']';
} finally {
ac = null;
send.textContent = 'Отправить';
send.innerHTML = PLANE; send.title = 'Отправить';
el.classList.remove('wait');
render(el, answer || '[нет ответа]', true);
talk.push({role: 'assistant', content: answer});
@@ -156,9 +158,13 @@
const text = input.value.trim();
if (!text) return;
input.value = '';
grow();
where();
ask(text);
};
// The field grows with the text, up to a few lines.
function grow() { input.style.height = '40px'; input.style.height = Math.min(140, input.scrollHeight + 2) + 'px'; }
input.addEventListener('input', grow);
input.addEventListener('keydown', ev => {
if (ev.key === 'Enter' && !ev.shiftKey && !ev.isComposing) { ev.preventDefault(); q('.ai-form-in').requestSubmit(); }
});