Fix thinking display to use single line with proper timer updates

parent 0d76e514
...@@ -452,45 +452,56 @@ class CoderClient: ...@@ -452,45 +452,56 @@ class CoderClient:
def _handle_streaming_response(self, response: requests.Response) -> str: def _handle_streaming_response(self, response: requests.Response) -> str:
"""Handle streaming response from API.""" """Handle streaming response from API."""
import time import time
import re
full_content = "" full_content = ""
tool_calls = [] tool_calls = []
current_tool_call = None
in_thinking = False in_thinking = False
thinking_content = "" thinking_content = ""
thinking_start_time = 0 thinking_start_time = 0
last_elapsed = -1 last_elapsed = -1
printed_final_thinking = False thinking_line_printed = False
def update_thinking_display(force=False): def print_thinking_line(elapsed, content, final=False):
"""Update the thinking display line.""" """Print or update the thinking line."""
nonlocal last_elapsed nonlocal thinking_line_printed
if not in_thinking: # Filter out <tool> tags and newlines for display
return display_content = re.sub(r'<tool[^>]*>.*?</tool>', '', content, flags=re.DOTALL)
current_time = time.time() display_content = display_content.replace('\n', ' ').replace('\r', '')
elapsed = int(current_time - thinking_start_time) display_content = display_content.strip()
if elapsed != last_elapsed or force:
last_elapsed = elapsed # Get last 60 chars to fit on screen
# Clear line and redraw if len(display_content) > 60:
display_text = thinking_content[-70:] # Last 70 chars to fit on screen display_content = "..." + display_content[-60:]
if len(thinking_content) > 70:
display_text = "..." + display_text line = f"[{elapsed}s] Thinking: [{display_content}]"
# Use carriage return to overwrite the line
line = f"[{elapsed}s] Thinking: [{display_text}]" if not thinking_line_printed:
# Pad with spaces to clear previous content # First time - just print
print(f"\r{Colors.DIM}{line:<100}{Colors.RESET}", end='', flush=True) print(f"\r{Colors.DIM}{line}{Colors.RESET}", end='', flush=True)
thinking_line_printed = True
else:
# Overwrite existing line
# Clear to end of line and print new content
print(f"\r\033[K{Colors.DIM}{line}{Colors.RESET}", end='', flush=True)
if final:
print() # Move to new line
# Use iter_content with smaller chunk size for better real-time handling # Use iter_content with smaller chunk size for better real-time handling
buffer = "" buffer = ""
last_update = time.time() last_timer_update = time.time()
for chunk in response.iter_content(chunk_size=256, decode_unicode=True): for chunk in response.iter_content(chunk_size=256, decode_unicode=True):
current_time = time.time() current_time = time.time()
# Update thinking timer every second even without new content # Update thinking timer every second
if in_thinking and current_time - last_update >= 1.0: if in_thinking:
update_thinking_display() elapsed = int(current_time - thinking_start_time)
last_update = current_time if elapsed != last_elapsed and current_time - last_timer_update >= 1.0:
last_elapsed = elapsed
last_timer_update = current_time
print_thinking_line(elapsed, thinking_content)
if chunk: if chunk:
buffer += chunk buffer += chunk
...@@ -520,35 +531,35 @@ class CoderClient: ...@@ -520,35 +531,35 @@ class CoderClient:
if '<think>' in content: if '<think>' in content:
in_thinking = True in_thinking = True
thinking_start_time = time.time() thinking_start_time = time.time()
last_update = thinking_start_time last_timer_update = thinking_start_time
last_elapsed = 0
thinking_content = "" thinking_content = ""
thinking_line_printed = False
# Print initial thinking line
print_thinking_line(0, "")
continue continue
if in_thinking: if in_thinking:
if '</think>' in content: if '</think>' in content:
# End of thinking # End of thinking
in_thinking = False in_thinking = False
# Show final thinking line parts = content.split('</think>', 1)
elapsed = int(current_time - thinking_start_time) think_part = parts[0]
think_part = content.split('</think>')[0]
if think_part: if think_part:
thinking_content += think_part thinking_content += think_part
# Clear line and show final thinking
display_text = thinking_content[-70:] # Last 70 chars # Show final thinking line
if len(thinking_content) > 70: elapsed = int(current_time - thinking_start_time)
display_text = "..." + display_text print_thinking_line(elapsed, thinking_content, final=True)
line = f"[{elapsed}s] Thinking: [{display_text}]"
print(f"\r{Colors.DIM}{line:<100}{Colors.RESET}\n", end='', flush=True)
printed_final_thinking = True
# Get content after </think> # Get content after </think>
actual_content = content.split('</think>', 1)[-1] actual_content = parts[1] if len(parts) > 1 else ""
if actual_content: if actual_content:
print(actual_content, end='', flush=True) print(actual_content, end='', flush=True)
full_content += actual_content full_content += actual_content
else: else:
# Still thinking - accumulate and display # Still thinking - accumulate
thinking_content += content thinking_content += content
update_thinking_display(force=True)
else: else:
# Normal content # Normal content
print(content, end='', flush=True) print(content, end='', flush=True)
...@@ -580,8 +591,8 @@ class CoderClient: ...@@ -580,8 +591,8 @@ class CoderClient:
except json.JSONDecodeError: except json.JSONDecodeError:
continue continue
if in_thinking and not printed_final_thinking: if in_thinking:
print("\n", end='', flush=True) print() # End thinking line if still in thinking
print() # Newline after streaming print() # Newline after streaming
# Execute tool calls if any # Execute tool calls if any
...@@ -625,16 +636,30 @@ class CoderClient: ...@@ -625,16 +636,30 @@ class CoderClient:
else: else:
result = self.tool_executor.execute(tool_name, arguments) result = self.tool_executor.execute(tool_name, arguments)
# Show result summary
if "error" in result:
print(f"{Colors.RED}Error: {result['error']}{Colors.RESET}")
elif result.get('declined'):
pass # Already printed "Skipped"
else:
print(f"{Colors.GREEN}Success{Colors.RESET}")
# Show command output for execute_command
if tool_name == 'execute_command' and 'stdout' in result:
stdout = result['stdout'].strip()
if stdout:
# Show first few lines of output
lines = stdout.split('\n')[:20]
print(f"{Colors.CYAN}Output:{Colors.RESET}")
for line in lines:
print(f" {line}")
if len(stdout.split('\n')) > 20:
print(f" {Colors.DIM}... ({len(stdout.split(chr(10))) - 20} more lines){Colors.RESET}")
tool_results.append({ tool_results.append({
"tool_call_id": tc['id'], "tool_call_id": tc['id'],
"role": "tool", "role": "tool",
"content": json.dumps(result) "content": json.dumps(result)
}) })
if "error" in result:
print(f"{Colors.RED}Error: {result['error']}{Colors.RESET}")
elif not result.get('declined'):
print(f"{Colors.GREEN}Success{Colors.RESET}")
# Add assistant message with tool calls to history # Add assistant message with tool calls to history
self.conversation_history.append({ self.conversation_history.append({
...@@ -653,7 +678,7 @@ class CoderClient: ...@@ -653,7 +678,7 @@ class CoderClient:
self.conversation_history.extend(tool_results) self.conversation_history.extend(tool_results)
# Get follow-up response with tool results # Get follow-up response with tool results
print("\n[Getting follow-up response...]") print(f"\n{Colors.DIM}[Getting follow-up response...]{Colors.RESET}")
return self._get_follow_up_response() return self._get_follow_up_response()
# Add assistant response to history # Add assistant response to history
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment