feat: Add Stop/Cancel buttons for long-running AI operations
Summary
Add the ability for users to cancel/stop long-running operations in the chat interface. This improves UX when operations get stuck, take too long, or when the user changes their mind.
Problem
Currently, when a user initiates an operation (document upload, LLM query, entity extraction), there's no way to cancel it:
- Stuck operations: If processing fails silently (e.g., database error), the UI shows a spinner indefinitely
- Wrong input: User uploads wrong document but must wait for full processing
- Slow LLM responses: No way to abort a slow or stuck LLM call
- Changed mind: User wants to try a different approach but can't cancel current operation
Real-world example: Document intake analysis failed due to missing database column, but the frontend kept polling indefinitely with no way for the user to cancel or retry.
Proposed Solution
1. Cancel Button for Document Processing
┌─────────────────────────────────────────────────┐
│ 📄 Analyzing document... │
│ │
│ ✓ Uploading document │
│ ✓ Extracting text content │
│ ◐ Analyzing document type │
│ ○ Extracting basic case info │
│ │
│ [Cancel] │
└─────────────────────────────────────────────────┘
Behavior:
- Stops frontend polling
- Marks document as
processing_cancelledin database - Shows "Processing cancelled" message with retry option
- Document remains uploaded (text extraction may be complete)
2. Stop Button for LLM Streaming
┌─────────────────────────────────────────────────┐
│ 🤖 Lexinox is typing... │
│ │
│ Based on the document you provided, I can see │
│ that this appears to be a criminal case │
│ involving...█ │
│ │
│ [■ Stop generating] │
└─────────────────────────────────────────────────┘
Behavior:
- Aborts the streaming response
- Keeps partial response visible
- Shows "[Response stopped]" indicator
- User can send new message
3. Timeout with Auto-Cancel
┌─────────────────────────────────────────────────┐
│ ⚠️ This is taking longer than expected │
│ │
│ Document analysis has been running for 60s. │
│ This might indicate a problem. │
│ │
│ [Keep waiting] [Cancel and retry] │
└─────────────────────────────────────────────────┘
Behavior:
- Shows after configurable timeout (e.g., 60s)
- User chooses to wait or cancel
- Provides context about what might be wrong
Technical Implementation
Frontend Changes
- New state management:
``javascript``
const [processingState, setProcessingState] = useState({
isProcessing: false,
canCancel: true,
operationType: null, // 'document_upload', 'llm_stream', 'entity_extraction'
startTime: null,
})
- Cancel handler:
```javascript
const handleCancel = async () => {
// Stop polling/streaming
abortControllerRef.current?.abort()
// Notify backend (optional)
if (processingState.taskId) {
await api.post(/tasks/${processingState.taskId}/cancel)
}
// Reset UI
setProcessingState({ isProcessing: false, ... })
showToast('Operation cancelled')
}
```
- AbortController integration:
```javascript
const abortControllerRef = useRef(null)
const startOperation = async () => {
abortControllerRef.current = new AbortController()
try {
await fetch(url, { signal: abortControllerRef.current.signal })
} catch (e) {
if (e.name === 'AbortError') {
// User cancelled - handle gracefully
}
}
}
```
Backend Changes (Optional, for full cancellation)
- Task tracking endpoint:
``python``
@router.post("/tasks/{task_id}/cancel")
async def cancel_task(task_id: str):
# Mark task as cancelled in database
# Stop any background processing if possible
- Document status update:
``python``
class DocumentProcessingStatus:
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled" # New status
Affected Components
Frontend
frontend/src/components/ChatInterface.jsx- Main chat with stop buttonfrontend/src/components/DocumentUploadProgress.jsx- Cancel during uploadfrontend/src/components/IntakeProgressTracker.jsx- Cancel intake analysisfrontend/src/hooks/useLLMStream.js- Abort streaming (new hook)
Backend
app/api/routes/documents.py- Cancel endpointapp/services/document_service.py- Handle cancellationapp/models/database.py- Add CANCELLED status
UI/UX Considerations
- Button placement: Stop button should be prominent but not accidentally clickable
- Confirmation: No confirmation needed for stop (reversible action)
- Feedback: Clear indication that operation was cancelled
- Recovery: Easy path to retry or try something else
- Partial results: Show any partial results (e.g., extracted text even if analysis cancelled)
Acceptance Criteria
- [ ] Cancel button visible during document processing
- [ ] Stop button visible during LLM streaming responses
- [ ] Clicking cancel/stop immediately stops the operation
- [ ] UI shows clear feedback that operation was cancelled
- [ ] User can retry or proceed with different action
- [ ] Timeout warning appears for operations exceeding threshold
- [ ] Partial results preserved when possible
- [ ] No orphaned background processes
Priority
Medium - This is a quality-of-life improvement that significantly improves UX for edge cases and error scenarios.
Related
- Document intake processing flow
- LLM streaming responses
- Error handling and recovery
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗