Repro
- Load any reasoning-capable model (tested with
unsloth/Qwen3.5-4B-MTP-GGUF, Think pill default off, so toggle it on).
- Send a prompt that triggers a longer thinking phase, e.g.
Write a long detailed essay (1500 words) on operating systems. Reply with the essay only, no <think>.
- While the message still shows
Generating... (before any visible answer tokens arrive), click the Stop button.
Observed
The assistant bubble is replaced by a floating row of 4 action buttons (Copy / Refresh / Delete / More) and nothing else. No message body, no "Generation stopped" indicator, no thinking-time chip. The composer is correctly re-enabled and the send button reappears, but the visible state suggests the message itself disappeared.
A user who is not looking at the network tab would reasonably ask: "did my stop click do anything?", "where is the partial answer?", or "is the chat broken?". This is the exact failure mode that produces the original "tools work, but I don't see anything happening or it looks stuck" complaint.
Why this matters
Studio's cancel path actually works (verified: /api/inference/load-progress resolves, llama-server stops streaming, the composer re-enables in well under 1s). The bug is purely visual: when there is no rendered content above the action-button row, the row floats with nothing to anchor it.
Suggested fix (frontend, smallest diff)
In studio/frontend/src/components/assistant-ui/thread.tsx (the AssistantMessage component, around the <MessagePrimitive.Root> near line 999 / 1166), gate the action-button row on the presence of message content OR show a placeholder when both content.length === 0 and status?.type === "incomplete" with reason: "cancelled". Two reasonable placeholders:
<span className="text-muted-foreground italic">Generation stopped</span> (short, terminal-state-aware)
- Surface the partial reasoning panel even if its content was empty -
Thought for {seconds}s reads better than blank.
Either is a one-component change. Both have precedent in the codebase: the thinking panel already collapses to "Thought for N seconds" once streaming stops, and the tool-fallback component already renders a "Cancelled tool" label when status.reason is "cancelled".
Screenshots
Pre-cancel: model is in thinking phase, stop button visible bottom right, only "Generating..." in middle of empty assistant area.
Post-cancel (i=05, ~3s after stop click): user prompt at top, 4 action buttons floating mid-screen, composer enabled at bottom. No message body. No "Generation stopped" label.
Environment
Repro
unsloth/Qwen3.5-4B-MTP-GGUF, Think pill default off, so toggle it on).Write a long detailed essay (1500 words) on operating systems. Reply with the essay only, no <think>.Generating...(before any visible answer tokens arrive), click the Stop button.Observed
The assistant bubble is replaced by a floating row of 4 action buttons (Copy / Refresh / Delete / More) and nothing else. No message body, no "Generation stopped" indicator, no thinking-time chip. The composer is correctly re-enabled and the send button reappears, but the visible state suggests the message itself disappeared.
A user who is not looking at the network tab would reasonably ask: "did my stop click do anything?", "where is the partial answer?", or "is the chat broken?". This is the exact failure mode that produces the original "tools work, but I don't see anything happening or it looks stuck" complaint.
Why this matters
Studio's cancel path actually works (verified:
/api/inference/load-progressresolves, llama-server stops streaming, the composer re-enables in well under 1s). The bug is purely visual: when there is no rendered content above the action-button row, the row floats with nothing to anchor it.Suggested fix (frontend, smallest diff)
In
studio/frontend/src/components/assistant-ui/thread.tsx(the AssistantMessage component, around the<MessagePrimitive.Root>near line 999 / 1166), gate the action-button row on the presence of message content OR show a placeholder when bothcontent.length === 0andstatus?.type === "incomplete"withreason: "cancelled". Two reasonable placeholders:<span className="text-muted-foreground italic">Generation stopped</span>(short, terminal-state-aware)Thought for {seconds}sreads better than blank.Either is a one-component change. Both have precedent in the codebase: the thinking panel already collapses to "Thought for N seconds" once streaming stops, and the tool-fallback component already renders a "Cancelled tool" label when status.reason is
"cancelled".Screenshots
Pre-cancel: model is in thinking phase, stop button visible bottom right, only "Generating..." in middle of empty assistant area.
Post-cancel (i=05, ~3s after stop click): user prompt at top, 4 action buttons floating mid-screen, composer enabled at bottom. No message body. No "Generation stopped" label.
Environment
unslothai/unsloth@a2f3793plus PR studio: engage draft-mtp on vision MTP GGUFs (drop incorrect vision gate) #5560 (MTP-on-vision) appliedunsloth/Qwen3.5-4B-MTP-GGUFandunsloth/Qwen3.5-9B-MTP-GGUF, UD-Q4_K_XL, 16384 context, f16 KV