diff options
| author | Adam Malczewski <[email protected]> | 2026-06-22 03:13:56 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-22 03:13:56 +0900 |
| commit | aa56ae04ea895aac81e98ee02b07ac8f3d27d1dd (patch) | |
| tree | c46cd5f1231716b8707b3d7928ddc27035946d0d | |
| parent | a4f0a0a7424fefcc19e52b871c531ce54cb06964 (diff) | |
| download | dispatch-web-aa56ae04ea895aac81e98ee02b07ac8f3d27d1dd.tar.gz dispatch-web-aa56ae04ea895aac81e98ee02b07ac8f3d27d1dd.zip | |
fix(composer): single context-aware button — Send/Queue/Stop
One button to the right of the text input:
- idle → Send (starts a turn)
- generating + text → Queue (steers via chat.queue)
- generating + empty → Stop (aborts via POST /stop)
| -rw-r--r-- | src/features/chat/ui/Composer.svelte | 42 |
1 files changed, 20 insertions, 22 deletions
diff --git a/src/features/chat/ui/Composer.svelte b/src/features/chat/ui/Composer.svelte index 5952eca..7030153 100644 --- a/src/features/chat/ui/Composer.svelte +++ b/src/features/chat/ui/Composer.svelte @@ -37,12 +37,16 @@ const usage = $derived(computeContextUsage(contextSize, MAX_CONTEXT)); const hasUsage = $derived(contextSize !== undefined); - // While a turn is generating, the send button becomes a "Queue" button that - // enqueues a steering message (`chat.queue`) instead of starting a new turn - // (`chat.send`). Falls back to `onSend` when no `onQueue` is wired. - const steering = $derived(status === "running" && onQueue !== undefined); - const submitLabel = $derived(steering ? "Queue" : "Send"); - const placeholder = $derived(steering ? "Steer the conversation..." : "Type a message..."); + // One button, three modes: + // - idle → "Send" (starts a turn via chat.send) + // - running + text → "Queue" (steers via chat.queue) + // - running + empty → "Stop" (aborts via POST /stop) + const buttonMode = $derived.by<"send" | "queue" | "stop">(() => { + if (status === "running" && !hasText && onStop !== undefined) return "stop"; + if (status === "running" && hasText && onQueue !== undefined) return "queue"; + return "send"; + }); + const placeholder = $derived(status === "running" ? "Steer the conversation..." : "Type a message..."); // As the window fills, escalate color: calm → warning → danger. function fillClass(pct: number): string { @@ -76,7 +80,7 @@ function handleSubmit(): void { const trimmed = text.trim(); if (trimmed.length === 0) return; - if (steering) { + if (buttonMode === "queue") { onQueue?.(trimmed); } else { onSend(trimmed); @@ -99,7 +103,7 @@ handleSubmit(); }} > - <!-- Top bar: expanding textarea + send button --> + <!-- Top bar: expanding textarea + single context-aware button --> <div class="flex items-end gap-2 px-4 pt-3 pb-2"> <textarea bind:this={inputEl} @@ -110,24 +114,18 @@ rows="1" aria-label="Message input" ></textarea> - <button class="btn btn-primary w-20 shrink-0" type="submit" disabled={!hasText}> - {submitLabel} - </button> - {#if status === "running" && onStop} + {#if buttonMode === "stop"} <button - class="btn btn-error btn-square shrink-0" + class="btn btn-error w-20 shrink-0" type="button" aria-label="Stop generation" - onclick={onStop} + onclick={() => onStop?.()} > - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="currentColor" - class="h-4 w-4" - > - <rect x="6" y="6" width="12" height="12" rx="2"></rect> - </svg> + Stop + </button> + {:else} + <button class="btn btn-primary w-20 shrink-0" type="submit" disabled={!hasText}> + {buttonMode === "queue" ? "Queue" : "Send"} </button> {/if} </div> |
