mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-26 15:40:18 +00:00
Show status/thinking messages in chat window, debug empty responses
- Add ThinkingMessage variant to AppEvent for chat-visible status - Add set_thinking() and clear_thinking() to AppState - Thinking messages show as system messages with spinner indicator - Auto-clear thinking messages when agent response arrives - Tool started/completed now shown in chat window - Add debug logging to NEAR AI provider to diagnose empty responses - Log raw response output when text extraction returns empty Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
3d709eb253
commit
42d6e87186
+29
-1
@@ -20,8 +20,10 @@ pub enum AppEvent {
|
||||
ApprovalRequested(ApprovalRequest),
|
||||
/// Streaming chunk received.
|
||||
StreamChunk(String),
|
||||
/// Log message from the application.
|
||||
/// Log message from the application (shown in status line).
|
||||
LogMessage(String),
|
||||
/// Thinking/status message (shown in chat window).
|
||||
ThinkingMessage(String),
|
||||
/// Force a redraw.
|
||||
Redraw,
|
||||
/// Quit the application.
|
||||
@@ -153,10 +155,36 @@ impl AppState {
|
||||
if self.streaming_buffer.is_some() {
|
||||
self.streaming_buffer = None;
|
||||
}
|
||||
// Remove any pending thinking message before adding the response
|
||||
self.clear_thinking();
|
||||
self.messages.push(ChatMessage::agent(content));
|
||||
self.scroll_to_bottom();
|
||||
}
|
||||
|
||||
/// Add or update a thinking/status message (shown as system message).
|
||||
pub fn set_thinking(&mut self, content: impl Into<String>) {
|
||||
let content = content.into();
|
||||
// Check if last message is a thinking message (system with InProgress status)
|
||||
if let Some(last) = self.messages.last_mut() {
|
||||
if last.role == MessageRole::System && last.status == Some(MessageStatus::InProgress) {
|
||||
last.content = content;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Add new thinking message
|
||||
self.messages
|
||||
.push(ChatMessage::system(content).with_status(MessageStatus::InProgress));
|
||||
self.scroll_to_bottom();
|
||||
}
|
||||
|
||||
/// Clear any thinking/status message.
|
||||
pub fn clear_thinking(&mut self) {
|
||||
// Remove any thinking messages (system with InProgress status)
|
||||
self.messages.retain(|msg| {
|
||||
!(msg.role == MessageRole::System && msg.status == Some(MessageStatus::InProgress))
|
||||
});
|
||||
}
|
||||
|
||||
/// Start streaming a response.
|
||||
pub fn start_streaming(&mut self) {
|
||||
self.streaming_buffer = Some(String::new());
|
||||
|
||||
@@ -258,13 +258,13 @@ fn handle_app_event(app: &mut AppState, event: AppEvent) {
|
||||
app.add_agent_message(content);
|
||||
}
|
||||
AppEvent::ToolStarted { name } => {
|
||||
app.set_status(format!("Running tool: {}...", name));
|
||||
app.set_thinking(format!("⚙️ Running tool: {}...", name));
|
||||
}
|
||||
AppEvent::ToolCompleted { name, success } => {
|
||||
if success {
|
||||
app.set_status(format!("Tool {} completed", name));
|
||||
app.set_thinking(format!("✓ Tool {} completed", name));
|
||||
} else {
|
||||
app.set_status(format!("Tool {} failed", name));
|
||||
app.set_thinking(format!("✗ Tool {} failed", name));
|
||||
}
|
||||
}
|
||||
AppEvent::ApprovalRequested(request) => {
|
||||
@@ -288,5 +288,8 @@ fn handle_app_event(app: &mut AppState, event: AppEvent) {
|
||||
AppEvent::LogMessage(msg) => {
|
||||
app.set_status(msg);
|
||||
}
|
||||
AppEvent::ThinkingMessage(msg) => {
|
||||
app.set_thinking(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,13 +111,13 @@ impl Channel for TuiChannel {
|
||||
|
||||
async fn send_status(&self, status: StatusUpdate) -> Result<(), ChannelError> {
|
||||
let event = match status {
|
||||
StatusUpdate::Thinking(msg) => AppEvent::LogMessage(format!("🤔 {}", msg)),
|
||||
StatusUpdate::Thinking(msg) => AppEvent::ThinkingMessage(format!("🤔 {}", msg)),
|
||||
StatusUpdate::ToolStarted { name } => AppEvent::ToolStarted { name },
|
||||
StatusUpdate::ToolCompleted { name, success } => {
|
||||
AppEvent::ToolCompleted { name, success }
|
||||
}
|
||||
StatusUpdate::StreamChunk(chunk) => AppEvent::StreamChunk(chunk),
|
||||
StatusUpdate::Status(msg) => AppEvent::LogMessage(msg),
|
||||
StatusUpdate::Status(msg) => AppEvent::ThinkingMessage(msg),
|
||||
};
|
||||
self.event_tx
|
||||
.send(event)
|
||||
|
||||
@@ -118,16 +118,24 @@ impl LlmProvider for NearAiProvider {
|
||||
|
||||
let response: NearAiResponse = self.send_request("responses", &request).await?;
|
||||
|
||||
tracing::debug!("NEAR AI response: {:?}", response);
|
||||
|
||||
// Extract text from response output
|
||||
let text = response
|
||||
.output
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
tracing::debug!("Processing output item: type={}", item.item_type);
|
||||
if item.item_type == "message" {
|
||||
item.content.as_ref().and_then(|contents| {
|
||||
contents
|
||||
.iter()
|
||||
.filter_map(|c| {
|
||||
tracing::debug!(
|
||||
"Content item: type={}, text={:?}",
|
||||
c.content_type,
|
||||
c.text
|
||||
);
|
||||
if c.content_type == "output_text" {
|
||||
c.text.clone()
|
||||
} else {
|
||||
@@ -143,6 +151,13 @@ impl LlmProvider for NearAiProvider {
|
||||
.collect::<Vec<_>>()
|
||||
.join("");
|
||||
|
||||
if text.is_empty() {
|
||||
tracing::warn!(
|
||||
"Empty response from NEAR AI. Raw output: {:?}",
|
||||
response.output
|
||||
);
|
||||
}
|
||||
|
||||
Ok(CompletionResponse {
|
||||
content: text,
|
||||
finish_reason: FinishReason::Stop,
|
||||
|
||||
Reference in New Issue
Block a user