Story 13: Implement Stop button with backend cancellation
- Add tokio watch channel for cancellation signaling - Implement cancel_chat command - Add cancellation checks in streaming loop and before tool execution - Stop button (■) replaces Send button (↑) during generation - Preserve partial streaming content when cancelled - Clean UX: no error messages on cancellation - Backend properly stops streaming and prevents tool execution Closes Story 13
This commit is contained in:
@@ -1,94 +0,0 @@
|
||||
# Story: Stop Button to Cancel Model Response
|
||||
|
||||
## User Story
|
||||
**As a** User
|
||||
**I want** a Stop button to appear while the model is generating a response
|
||||
**So that** I can explicitly cancel long-running or unwanted responses without waiting for completion.
|
||||
|
||||
## Acceptance Criteria
|
||||
* [ ] A "Stop" button should appear in place of the Send button while the model is generating
|
||||
* [ ] Clicking the Stop button should immediately cancel the ongoing generation
|
||||
* [ ] The backend request to Ollama should be cancelled (not just ignored)
|
||||
* [ ] Any partial response generated before stopping should remain visible in the chat
|
||||
* [ ] The UI should return to normal state (Send button visible, input enabled) after stopping
|
||||
* [ ] The input field should remain enabled during generation (user can type while waiting)
|
||||
* [ ] Optional: Escape key should also trigger stop (keyboard shortcut)
|
||||
* [ ] The stopped message should remain in history (not be removed)
|
||||
|
||||
## Out of Scope
|
||||
* Automatic interruption by typing (too aggressive)
|
||||
* Confirmation dialog before stopping (immediate action is preferred)
|
||||
* Undo/redo functionality after stopping
|
||||
* Streaming partial responses (that's Story 18)
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Frontend (TypeScript)
|
||||
* Replace Send button (↑) with Stop button (⬛ or "Stop") when `loading` is true
|
||||
* On Stop click, call `invoke("cancel_chat")` and set `loading = false`
|
||||
* Keep input field enabled during generation (no `disabled` attribute)
|
||||
* Optional: Add Escape key handler to trigger stop when input is focused
|
||||
* Visual design: Make Stop button clearly distinct from Send button
|
||||
|
||||
### Backend (Rust)
|
||||
* ✅ Already implemented: `cancel_chat` command with tokio watch channel
|
||||
* ✅ Already implemented: `tokio::select!` racing Ollama request vs cancellation
|
||||
* When cancelled, backend returns early with "Chat cancelled by user" error
|
||||
* Partial messages from completed tool calls remain in history
|
||||
|
||||
### UX Flow
|
||||
1. User sends message → Send button changes to Stop button
|
||||
2. Model starts generating → User sees "Thinking..." and Stop button
|
||||
3. User clicks Stop → Backend cancels Ollama request
|
||||
4. Partial response (if any) stays visible in chat
|
||||
5. Stop button changes back to Send button
|
||||
6. User can now send a new message
|
||||
|
||||
### Standard Pattern (ChatGPT/Claude style)
|
||||
* Stop button is the standard pattern used by ChatGPT, Claude, and other chat UIs
|
||||
* No auto-interrupt on typing (too confusing - messages would disappear)
|
||||
* Explicit user action required (button click or Escape key)
|
||||
* Partial responses remain visible (not removed from history)
|
||||
|
||||
## Related Functional Specs
|
||||
* Functional Spec: UI/UX
|
||||
* Related to Story 18 (Streaming) - Stop button should work with streaming too
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Backend Cancellation (Already Implemented)
|
||||
```rust
|
||||
// In SessionState
|
||||
pub cancel_tx: watch::Sender<bool>,
|
||||
pub cancel_rx: watch::Receiver<bool>,
|
||||
|
||||
// In chat command
|
||||
select! {
|
||||
result = chat_future => { /* normal completion */ }
|
||||
_ = cancel_rx.changed() => {
|
||||
return Err("Chat cancelled by user".to_string());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend Integration
|
||||
```tsx
|
||||
<button
|
||||
onClick={loading ? cancelGeneration : sendMessage}
|
||||
disabled={!input.trim() && !loading}
|
||||
>
|
||||
{loading ? "⬛ Stop" : "↑"}
|
||||
</button>
|
||||
|
||||
const cancelGeneration = () => {
|
||||
invoke("cancel_chat").catch(console.error);
|
||||
setLoading(false);
|
||||
};
|
||||
```
|
||||
|
||||
## Testing Considerations
|
||||
* Test with long multi-turn generations (tool use)
|
||||
* Test that partial responses remain visible
|
||||
* Test that new messages can be sent after stopping
|
||||
* Test Escape key shortcut (if implemented)
|
||||
* Test that backend actually cancels (check Ollama logs/CPU)
|
||||
99
.living_spec/stories/15_new_session_cancellation.md
Normal file
99
.living_spec/stories/15_new_session_cancellation.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Story 14: New Session Cancellation
|
||||
|
||||
## User Story
|
||||
**As a** User
|
||||
**I want** the backend to stop processing when I start a new session
|
||||
**So that** tools don't silently execute in the background and streaming doesn't leak into my new session
|
||||
|
||||
## The Problem
|
||||
|
||||
**Current Behavior (THE BUG):**
|
||||
1. User sends message → Backend starts streaming → About to execute a tool (e.g., `write_file`)
|
||||
2. User clicks "New Session" and confirms
|
||||
3. Frontend clears messages and UI state
|
||||
4. **Backend keeps running** → Tool executes → File gets written → Streaming continues
|
||||
5. **Streaming tokens appear in the new session**
|
||||
6. User has no idea these side effects occurred in the background
|
||||
|
||||
**Why This Is Critical:**
|
||||
- Tool calls have real side effects (file writes, shell commands, searches)
|
||||
- These happen silently after user thinks they've started fresh
|
||||
- Streaming from old session leaks into new session
|
||||
- Can cause confusion, data corruption, or unexpected system state
|
||||
- User expects "New Session" to mean a clean slate
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Clicking "New Session" and confirming cancels any in-flight backend request
|
||||
- [ ] Tool calls that haven't started yet are NOT executed
|
||||
- [ ] Streaming from old request does NOT appear in new session
|
||||
- [ ] Backend stops processing immediately when cancellation is triggered
|
||||
- [ ] New session starts with completely clean state
|
||||
- [ ] No silent side effects in background after new session starts
|
||||
|
||||
## Out of Scope
|
||||
- Stop button during generation (that's Story 13)
|
||||
- Improving the confirmation dialog (already done in Story 20)
|
||||
- Rolling back already-executed tools (partial work stays)
|
||||
|
||||
## Implementation Approach
|
||||
|
||||
### Backend
|
||||
- Uses same `cancel_chat` command as Story 13
|
||||
- Same cancellation mechanism (tokio::select!, watch channel)
|
||||
|
||||
### Frontend
|
||||
- Call `invoke("cancel_chat")` BEFORE clearing UI state in `clearSession()`
|
||||
- Wait for cancellation to complete before clearing messages
|
||||
- Ensure old streaming events don't arrive after clear
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Test Tool Call Prevention:**
|
||||
- Send message that will use tools (e.g., "search all TypeScript files")
|
||||
- Click "New Session" while it's thinking
|
||||
- Confirm in dialog
|
||||
- Verify tool does NOT execute (check logs/filesystem)
|
||||
- Verify new session is clean
|
||||
|
||||
2. **Test Streaming Leak Prevention:**
|
||||
- Send message requesting long response
|
||||
- While streaming, click "New Session" and confirm
|
||||
- Verify old streaming stops immediately
|
||||
- Verify NO tokens from old request appear in new session
|
||||
- Type new message and verify only new response appears
|
||||
|
||||
3. **Test File Write Prevention:**
|
||||
- Ask to write a file: "Create test.txt with current timestamp"
|
||||
- Click "New Session" before tool executes
|
||||
- Check filesystem: test.txt should NOT exist
|
||||
- Verify no background file creation happens
|
||||
|
||||
## Success Criteria
|
||||
|
||||
**Before (BROKEN):**
|
||||
```
|
||||
User: "Search files and write results.txt"
|
||||
Backend: Starts streaming...
|
||||
User: *clicks New Session, confirms*
|
||||
Frontend: Clears UI ✓
|
||||
Backend: Still running... executes search... writes file... ✗
|
||||
Result: File written silently in background ✗
|
||||
Old streaming tokens appear in new session ✗
|
||||
```
|
||||
|
||||
**After (FIXED):**
|
||||
```
|
||||
User: "Search files and write results.txt"
|
||||
Backend: Starts streaming...
|
||||
User: *clicks New Session, confirms*
|
||||
Frontend: Calls cancel_chat, waits, then clears UI ✓
|
||||
Backend: Receives cancellation, stops immediately ✓
|
||||
Backend: Tools NOT executed ✓
|
||||
Result: Clean new session, no background activity ✓
|
||||
```
|
||||
|
||||
## Related Stories
|
||||
- Story 13: Stop Button (shares same backend cancellation mechanism)
|
||||
- Story 20: New Session confirmation dialog (UX for triggering this)
|
||||
- Story 18: Streaming Responses (must not leak between sessions)
|
||||
82
.living_spec/stories/archive/13_stop_button.md
Normal file
82
.living_spec/stories/archive/13_stop_button.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Story 13: Stop Button
|
||||
|
||||
## User Story
|
||||
**As a** User
|
||||
**I want** a Stop button to cancel the model's response while it's generating
|
||||
**So that** I can immediately stop long-running or unwanted responses without waiting for completion
|
||||
|
||||
## The Problem
|
||||
|
||||
**Current Behavior:**
|
||||
- User sends message → Model starts generating
|
||||
- User realizes they don't want the response (wrong question, too long, etc.)
|
||||
- **No way to stop it** - must wait for completion
|
||||
- Tool calls will execute even if user wants to cancel
|
||||
|
||||
**Why This Matters:**
|
||||
- Long responses waste time
|
||||
- Tool calls have side effects (file writes, searches, shell commands)
|
||||
- User has no control once generation starts
|
||||
- Standard UX pattern in ChatGPT, Claude, etc.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Stop button (⬛) appears in place of Send button (↑) while model is generating
|
||||
- [ ] Clicking Stop immediately cancels the backend request
|
||||
- [ ] Tool calls that haven't started yet are NOT executed after cancellation
|
||||
- [ ] Streaming stops immediately
|
||||
- [ ] Partial response generated before stopping remains visible in chat
|
||||
- [ ] Stop button becomes Send button again after cancellation
|
||||
- [ ] User can immediately send a new message after stopping
|
||||
- [ ] Input field remains enabled during generation
|
||||
|
||||
## Out of Scope
|
||||
- Escape key shortcut (can add later)
|
||||
- Confirmation dialog (immediate action is better UX)
|
||||
- Undo/redo functionality
|
||||
- New Session flow (that's Story 14)
|
||||
|
||||
## Implementation Approach
|
||||
|
||||
### Backend
|
||||
- Add `cancel_chat` command callable from frontend
|
||||
- Use `tokio::select!` to race chat execution vs cancellation signal
|
||||
- Check cancellation before executing each tool
|
||||
- Return early when cancelled (not an error - expected behavior)
|
||||
|
||||
### Frontend
|
||||
- Replace Send button with Stop button when `loading` is true
|
||||
- On Stop click: call `invoke("cancel_chat")` and set `loading = false`
|
||||
- Keep input enabled during generation
|
||||
- Visual: Make Stop button clearly distinct (⬛ or "Stop" text)
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Test Stop During Streaming:**
|
||||
- Send message requesting long response
|
||||
- Click Stop while streaming
|
||||
- Verify streaming stops immediately
|
||||
- Verify partial response remains visible
|
||||
- Verify can send new message
|
||||
|
||||
2. **Test Stop Before Tool Execution:**
|
||||
- Send message that will use tools
|
||||
- Click Stop while "thinking" (before tool executes)
|
||||
- Verify tool does NOT execute (check logs/filesystem)
|
||||
|
||||
3. **Test Stop During Tool Execution:**
|
||||
- Send message with multiple tool calls
|
||||
- Click Stop after first tool executes
|
||||
- Verify remaining tools do NOT execute
|
||||
|
||||
## Success Criteria
|
||||
|
||||
**Before:**
|
||||
- User sends message → No way to stop → Must wait for completion → Frustrating UX
|
||||
|
||||
**After:**
|
||||
- User sends message → Stop button appears → User clicks Stop → Generation cancels immediately → Partial response stays → Can send new message
|
||||
|
||||
## Related Stories
|
||||
- Story 14: New Session Cancellation (same backend mechanism, different trigger)
|
||||
- Story 18: Streaming Responses (Stop must work with streaming)
|
||||
Reference in New Issue
Block a user