diff --git a/public/components/assistant.html b/public/components/assistant.html index a1dfa72..071f3b4 100644 --- a/public/components/assistant.html +++ b/public/components/assistant.html @@ -62,10 +62,12 @@ diff --git a/public/css/assistant.css b/public/css/assistant.css index b37404e..c2761c7 100644 --- a/public/css/assistant.css +++ b/public/css/assistant.css @@ -271,9 +271,10 @@ body.assistant-workspace .assistant-learning-view { min-height: 0; height: 100vh .assistant-composer { border:1px solid var(--g200); border-radius:20px; box-shadow:0 8px 30px rgba(0,0,0,.08); padding:10px 14px 6px; display:flex; flex-direction:column; gap:2px; } .assistant-composer > label { display:none; } #assistant-input { flex:1 1 auto; border:none; outline:none; resize:none; background:transparent; font-size:15px; line-height:1.5; min-height:56px; max-height:180px; padding:6px 0; } -.assistant-composer-footer { display:flex; align-items:center; justify-content:space-between; gap:4px; border-top:1px solid var(--g100); padding-top:4px; margin-top:2px; } +.assistant-composer-footer { display:flex; align-items:center; gap:2px; border-top:1px solid var(--g100); padding-top:4px; margin-top:2px; } +.assistant-composer-footer .assistant-tools { display:flex; align-items:center; gap:2px; margin-right:auto; } .assistant-composer-footer .assistant-attach, .assistant-composer-footer .assistant-voice-btn, .assistant-composer-footer .assistant-mic { width:30px; height:30px; font-size:14px; } -.assistant-composer-footer .btn-send { width:32px; height:32px; font-size:14px; } +.assistant-composer-footer .btn-send { width:32px; height:32px; font-size:14px; margin-left:auto; } .assistant-attach, .assistant-voice-btn, .assistant-mic { width:38px; height:38px; border:none; background:none; border-radius:50%; display:flex; align-items:center; justify-content:center; color:var(--g600); cursor:pointer; font-size:16px; } .assistant-attach:hover, .assistant-voice-btn:hover, .assistant-mic:hover { background:var(--g100); } .assistant-mic.recording { background:var(--danger,#dc2626); color:#fff; } diff --git a/public/js/clinicalAssistant.js b/public/js/clinicalAssistant.js index 1592aa0..450fb7d 100644 --- a/public/js/clinicalAssistant.js +++ b/public/js/clinicalAssistant.js @@ -963,6 +963,11 @@ import { } }; if (typeof transcribeAudio === 'function' && blob && blob.size > 100) { + if (blob.size < 4000 && !live) { + if (typeof showToast === 'function') showToast('Recording was too short or silent — speak closer to the microphone', 'error'); + finish(''); + return; + } transcribeAudio(blob).then(function(data) { // Server transcription wins; the live transcript is the fallback, // never lose what the browser already heard. @@ -1068,11 +1073,6 @@ import { resizeAssistantInput(); if (status) status.textContent = 'Asking…'; var send = document.getElementById('btn-assistant-send'); - if (send) { - send.classList.toggle('busy', !!isBusy); - var sendIcon = send.querySelector('i'); - if (sendIcon) sendIcon.className = isBusy ? 'fas fa-stop' : 'fas fa-arrow-up'; - } if (send) send.click(); } @@ -2048,8 +2048,11 @@ import { } if (label) label.textContent = text || (isBusy ? 'Working...' : 'Ready'); if (send) { - send.disabled = !!isBusy; - send.innerHTML = isBusy ? ' Searching' : ' Ask'; + // The send button becomes a clickable STOP while the assistant works. + send.classList.toggle('busy', !!isBusy); + var icon = send.querySelector('i'); + if (icon) icon.className = isBusy ? 'fas fa-stop' : 'fas fa-arrow-up'; + send.setAttribute('aria-label', isBusy ? 'Stop generating' : 'Send'); } if (input) input.disabled = !!isBusy; if (attachInput) attachInput.disabled = !!isBusy; diff --git a/test/assistant-message-actions.test.js b/test/assistant-message-actions.test.js index ff4065e..e58f53c 100644 --- a/test/assistant-message-actions.test.js +++ b/test/assistant-message-actions.test.js @@ -86,6 +86,20 @@ test('regenerate replays the preceding question for the latest answer and never assert.match(all[all.length - 1].textContent, /Regenerated answer/); }); +test('the send button becomes a clickable stop icon while the assistant works', async t => { + const app = ui(t); + const c = app.context; + const send = app.document.getElementById('btn-assistant-send'); + assert.equal(send.querySelector('i').className, 'fas fa-arrow-up'); + c.setBusy(true, 'Working'); + assert.equal(send.disabled, false, 'still clickable so it can cancel'); + assert.ok(send.classList.contains('busy')); + assert.equal(send.querySelector('i').className, 'fas fa-stop'); + c.setBusy(false, 'Ready'); + assert.equal(send.querySelector('i').className, 'fas fa-arrow-up'); + assert.ok(!send.classList.contains('busy')); +}); + test('editing a user message returns the question to the composer', async t => { const app = ui(t); const c = app.context;