diff --git a/VERSION b/VERSION index d788d43..78bae5b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.31 +0.0.32 diff --git a/src/version.h b/src/version.h index cdc4b24..aa18dbb 100644 --- a/src/version.h +++ b/src/version.h @@ -11,9 +11,9 @@ #ifndef SOVEREIGN_BROWSER_VERSION_H #define SOVEREIGN_BROWSER_VERSION_H -#define SB_VERSION "v0.0.31" +#define SB_VERSION "v0.0.32" #define SB_VERSION_MAJOR 0 #define SB_VERSION_MINOR 0 -#define SB_VERSION_PATCH 31 +#define SB_VERSION_PATCH 32 #endif /* SOVEREIGN_BROWSER_VERSION_H */ diff --git a/www/agents/chat.css b/www/agents/chat.css index 9bb3273..86bcfa1 100644 --- a/www/agents/chat.css +++ b/www/agents/chat.css @@ -378,10 +378,6 @@ html.dark .skill-badge-unsaved { #composer-host { flex: 1; min-width: 0; } -#stop-btn { - flex-shrink: 0; min-width: 80px; margin-left: 0; -} - /* The shared post-composer library wraps the host element: it inserts a * .post-composer-wrapper around #composer-host and appends the send * button as a SIBLING of the host (inside the wrapper). So the send @@ -408,6 +404,20 @@ html.dark .skill-badge-unsaved { #input-area .post-composer-send-btn:disabled { opacity: 0.5; cursor: not-allowed; } +/* Stop-mode: the send button transforms into a stop button while the + * agent is running. Use a distinct (red-ish) style to signal that + * clicking it will cancel the agent. */ +#input-area .post-composer-send-btn.is-stop-mode { + border-color: var(--danger, #c33); + color: var(--danger, #c33); + background: transparent; +} +#input-area .post-composer-send-btn.is-stop-mode:hover, +#input-area .post-composer-send-btn.is-stop-mode:focus { + background: var(--danger, #c33); + color: #fff; + border-color: var(--danger, #c33); +} /* ── Responsive: narrow sidebar viewport (< 400px) ────────────── * When the chat page is loaded in the agent sidebar (~350px wide), diff --git a/www/agents/chat.html b/www/agents/chat.html index 1a84b8e..c40ad25 100644 --- a/www/agents/chat.html +++ b/www/agents/chat.html @@ -26,7 +26,6 @@
-
diff --git a/www/agents/chat.js b/www/agents/chat.js index 9f59faa..af0b802 100644 --- a/www/agents/chat.js +++ b/www/agents/chat.js @@ -467,7 +467,6 @@ function clearStatusMessage() { * fallback. Posts status messages as system bubbles in the chat * window instead of updating a status bar. */ function applyStatus(s) { - var stopBtn = document.getElementById('stop-btn'); var map = {idle:'Idle', thinking:'Thinking...', tool_call:'Calling tool', complete:'Complete', error:'Error', cancelled:'Cancelled'}; @@ -492,13 +491,24 @@ function applyStatus(s) { } var running = (s.state === 'thinking' || s.state === 'tool_call'); - if (stopBtn) stopBtn.style.display = running ? '' : 'none'; - if (composerApi && typeof composerApi.setDisabled === 'function') { - /* Disable while running OR when no chat is selected. The composer - * must never be enabled if there is no active conversation. */ + /* Convert the send button into a stop button while the agent is + * running, instead of showing a separate stop button. The user can't + * send again anyway while the agent is working. */ + if (composerApi) { var hasChat = (currentConvId !== null && currentConvId !== undefined && String(currentConvId) !== ''); - composerApi.setDisabled(running || !hasChat); + if (running) { + if (typeof composerApi.setStopMode === 'function') { + composerApi.setStopMode(cancelAgent); + } + } else { + if (typeof composerApi.clearStopMode === 'function') { + composerApi.clearStopMode(); + } + if (typeof composerApi.setDisabled === 'function') { + composerApi.setDisabled(!hasChat); + } + } } /* On transition to complete/error/cancelled, fetch final diff --git a/www/js/post-composer.js b/www/js/post-composer.js index 2a621df..8ca3917 100644 --- a/www/js/post-composer.js +++ b/www/js/post-composer.js @@ -264,6 +264,15 @@ window.mountComposer = function mountComposer(hostEl, options = {}) { sendButton.textContent = 'Send'; sendButton.disabled = true; + /* Stop-mode state: when active, the send button transforms into a + * stop button (different label, click handler, and styling). This is + * used by the agent chat to let the user cancel an in-progress agent + * run without needing a separate stop button. */ + let stopModeActive = false; + let stopModeHandler = null; + const sendLabel = 'Send'; + const stopLabel = 'Stop'; + if (layout === 'inline') { wrapper.classList.add('post-composer-wrapper--inline'); if (showPreview) wrapper.appendChild(previewEl); @@ -733,6 +742,11 @@ window.mountComposer = function mountComposer(hostEl, options = {}) { } async function onSendClick() { + /* In stop mode, the button acts as Stop — route to the stop handler. */ + if (stopModeActive) { + if (stopModeHandler) stopModeHandler(); + return; + } if (sendButton.disabled) return; if (!onSubmit) return; @@ -827,6 +841,27 @@ window.mountComposer = function mountComposer(hostEl, options = {}) { externallyDisabled = !!disabled; updateSendButtonVisibility(); }, + /* Convert the send button into a stop button. The provided handler + * is called when the user clicks the (now stop) button. The text + * area is NOT disabled — the user can type their next message while + * the agent runs, but the button acts as Stop until clearStopMode() + * is called. */ + setStopMode(handler) { + stopModeActive = true; + stopModeHandler = typeof handler === 'function' ? handler : null; + sendButton.textContent = stopLabel; + sendButton.classList.add('is-stop-mode'); + sendButton.disabled = false; + sendButton.classList.add('is-visible'); + }, + /* Revert the stop button back to a send button. */ + clearStopMode() { + stopModeActive = false; + stopModeHandler = null; + sendButton.textContent = sendLabel; + sendButton.classList.remove('is-stop-mode'); + updateSendButtonVisibility(); + }, show() { showComposer(); }, diff --git a/www/js/post-composer.mjs b/www/js/post-composer.mjs index 60048ee..ece2498 100644 --- a/www/js/post-composer.mjs +++ b/www/js/post-composer.mjs @@ -264,6 +264,15 @@ export function mountComposer(hostEl, options = {}) { sendButton.textContent = 'Send'; sendButton.disabled = true; + /* Stop-mode state: when active, the send button transforms into a + * stop button (different label, click handler, and styling). This is + * used by the agent chat to let the user cancel an in-progress agent + * run without needing a separate stop button. */ + let stopModeActive = false; + let stopModeHandler = null; + const sendLabel = 'Send'; + const stopLabel = 'Stop'; + if (layout === 'inline') { wrapper.classList.add('post-composer-wrapper--inline'); if (showPreview) wrapper.appendChild(previewEl); @@ -733,6 +742,11 @@ export function mountComposer(hostEl, options = {}) { } async function onSendClick() { + /* In stop mode, the button acts as Stop — route to the stop handler. */ + if (stopModeActive) { + if (stopModeHandler) stopModeHandler(); + return; + } if (sendButton.disabled) return; if (!onSubmit) return; @@ -827,6 +841,21 @@ export function mountComposer(hostEl, options = {}) { externallyDisabled = !!disabled; updateSendButtonVisibility(); }, + setStopMode(handler) { + stopModeActive = true; + stopModeHandler = typeof handler === 'function' ? handler : null; + sendButton.textContent = stopLabel; + sendButton.classList.add('is-stop-mode'); + sendButton.disabled = false; + sendButton.classList.add('is-visible'); + }, + clearStopMode() { + stopModeActive = false; + stopModeHandler = null; + sendButton.textContent = sendLabel; + sendButton.classList.remove('is-stop-mode'); + updateSendButtonVisibility(); + }, show() { showComposer(); },