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:
Dave
2025-12-27 18:32:15 +00:00
parent 846967ee99
commit e1fb0e3d19
10 changed files with 920 additions and 763 deletions
+24 -1
View File
@@ -47,6 +47,7 @@ impl OllamaProvider {
model: &str,
messages: &[Message],
tools: &[ToolDefinition],
cancel_rx: &mut tokio::sync::watch::Receiver<bool>,
) -> Result<CompletionResponse, String> {
let client = reqwest::Client::new();
let url = format!("{}/api/chat", self.base_url.trim_end_matches('/'));
@@ -108,7 +109,29 @@ impl OllamaProvider {
let mut accumulated_content = String::new();
let mut final_tool_calls: Option<Vec<ToolCall>> = None;
while let Some(chunk_result) = stream.next().await {
loop {
// Check for cancellation
if *cancel_rx.borrow() {
return Err("Chat cancelled by user".to_string());
}
let chunk_result = tokio::select! {
chunk = stream.next() => {
match chunk {
Some(c) => c,
None => break,
}
}
_ = cancel_rx.changed() => {
// changed() fires on any change, check if it's actually true
if *cancel_rx.borrow() {
return Err("Chat cancelled by user".to_string());
} else {
continue;
}
}
};
let chunk = chunk_result.map_err(|e| format!("Stream error: {}", e))?;
buffer.push_str(&String::from_utf8_lossy(&chunk));