From cc9188fa081d87266e9d1b6932fcc5a30025a82d Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 8 Sep 2026 22:45:13 +0200 Subject: [PATCH] fix: OWUI hover-delete on saved-chat rows (row click opens, trash icon deletes) --- public/css/assistant.css | 4 ++++ public/js/clinicalAssistant.js | 23 +++++++++++++++++++++++ test/assistant-message-actions.test.js | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/public/css/assistant.css b/public/css/assistant.css index ab5b122f..b61c8666 100644 --- a/public/css/assistant.css +++ b/public/css/assistant.css @@ -219,3 +219,7 @@ body.assistant-workspace .assistant-learning-view { min-height: 0; height: 100vh /* Saved chats occupy the full left rail and scroll inside it */ .assistant-history { display:flex; flex-direction:column; height:100%; min-height:0; } .assistant-history .card { flex:1 1 auto; display:flex; flex-direction:column; min-height:0; overflow:hidden; } +.assistant-saved-chat { position:relative; } +.assistant-saved-chat-delete { position:absolute; top:6px; right:8px; display:flex; align-items:center; justify-content:center; width:24px; height:24px; border-radius:6px; color:var(--g500, #6b7280); opacity:0; transition:opacity .12s ease; pointer-events:none; } +.assistant-saved-chat:hover .assistant-saved-chat-delete, .assistant-saved-chat:focus-within .assistant-saved-chat-delete { opacity:1; pointer-events:auto; } +.assistant-saved-chat-delete:hover { color:var(--danger, #dc2626); background:rgba(220,38,38,.08); } diff --git a/public/js/clinicalAssistant.js b/public/js/clinicalAssistant.js index e6ade01c..6548e175 100644 --- a/public/js/clinicalAssistant.js +++ b/public/js/clinicalAssistant.js @@ -861,6 +861,13 @@ import { renderAttachments(); return; } + var deleteBtn = e.target.closest('[data-assistant-delete-chat]'); + if (deleteBtn) { + e.preventDefault(); + e.stopPropagation(); + deleteSavedChat(deleteBtn.getAttribute('data-assistant-delete-chat')); + return; + } var loadBtn = e.target.closest('[data-assistant-load-chat]'); if (loadBtn) { e.preventDefault(); @@ -1495,13 +1502,29 @@ import { } // Open WebUI behavior: the whole row opens the chat — no Load/Delete buttons. wrap.innerHTML = chats.map(function (chat) { + // Open WebUI behavior: the row opens the chat; a hover trash icon deletes it. return ''; }).join(''); } + function deleteSavedChat(id) { + if (assistantBusy) return; + return deleteSavedAssistantChat(id) + .then(function (data) { + if (!data.success) throw new Error(data.error || 'Delete failed'); + if (String(currentChatId) === String(id)) performClearConversation(); + loadSavedChats(); + if (typeof showToast === 'function') showToast('Chat deleted', 'success'); + }) + .catch(function (err) { + if (typeof showToast === 'function') showToast(err.message, 'error'); + }); + } + function loadSavedChat(id) { if (assistantBusy) return; setBusy(true, 'Loading chat...'); diff --git a/test/assistant-message-actions.test.js b/test/assistant-message-actions.test.js index 3f0d566f..1237434a 100644 --- a/test/assistant-message-actions.test.js +++ b/test/assistant-message-actions.test.js @@ -85,6 +85,25 @@ test('regenerate replays the preceding question for the latest answer and never assert.match(all[all.length - 1].textContent, /Regenerated answer/); }); +test('saved-chat rows open on click; the hover trash deletes without opening', async t => { + const app = ui(t); + const c = app.context; + c.fetchSavedAssistantChats = async () => ({ success: true, chats: [{ id: 7, title: 'Stored chat', updated_at: new Date().toISOString() }] }); + c.fetchSavedAssistantChat = async id => ({ success: true, chat: { id: id, title: 'Stored chat', payload: { version: 2, messages: [], sources: [], lastAnswer: '' } } }); + await c.loadSavedChats(); + const row = app.document.querySelector('[data-assistant-load-chat="7"]'); + assert.ok(row, 'row rendered as the click target'); + assert.ok(row.querySelector('[data-assistant-delete-chat="7"]'), 'hover delete icon present'); + row.click(); + await new Promise(r => setImmediate(r)); await new Promise(r => setImmediate(r)); + assert.equal(String(c.currentChatId), '7', 'row click opens the chat'); + const deleted = []; + app.context.deleteSavedAssistantChat = async id => { deleted.push(id); return { success: true }; }; + row.querySelector('[data-assistant-delete-chat="7"]').click(); + await new Promise(r => setImmediate(r)); + assert.deepEqual(deleted, ['7'], 'delete icon deletes'); +}); + test('regenerate on a non-latest answer refuses honestly without touching the chat', t => { const app = ui(t); const c = app.context;