|
6 | 6 | import tty
|
7 | 7 | from datetime import datetime
|
8 | 8 | from typing import Any
|
| 9 | +import threading |
| 10 | +import time |
9 | 11 |
|
10 | 12 | import requests
|
11 | 13 | import typer
|
@@ -35,14 +37,66 @@ def __init__(self):
|
35 | 37 | self.tabs = ["recents", "new", "web"]
|
36 | 38 | self.current_tab = 0
|
37 | 39 |
|
| 40 | + # Refresh state |
| 41 | + self.is_refreshing = False |
| 42 | + self._auto_refresh_interval_seconds = 10 |
| 43 | + self._refresh_lock = threading.Lock() |
| 44 | + |
38 | 45 | # New tab state
|
39 | 46 | self.prompt_input = ""
|
| 47 | + |
40 | 48 | self.cursor_position = 0
|
41 | 49 | self.input_mode = False # When true, we're typing in the input box
|
42 | 50 |
|
43 | 51 | # Set up signal handler for Ctrl+C
|
44 | 52 | signal.signal(signal.SIGINT, self._signal_handler)
|
45 | 53 |
|
| 54 | + # Start background auto-refresh thread (daemon) |
| 55 | + self._auto_refresh_thread = threading.Thread(target=self._auto_refresh_loop, daemon=True) |
| 56 | + self._auto_refresh_thread.start() |
| 57 | + |
| 58 | + def _auto_refresh_loop(self): |
| 59 | + """Background loop to auto-refresh recents tab every interval.""" |
| 60 | + while True: |
| 61 | + # Sleep first so we don't immediately spam a refresh on start |
| 62 | + time.sleep(self._auto_refresh_interval_seconds) |
| 63 | + |
| 64 | + if not self.running: |
| 65 | + break |
| 66 | + |
| 67 | + # Only refresh when on recents tab and not currently refreshing |
| 68 | + if self.current_tab == 0 and not self.is_refreshing: |
| 69 | + # Try background refresh; if lock is busy, skip this tick |
| 70 | + acquired = self._refresh_lock.acquire(blocking=False) |
| 71 | + if not acquired: |
| 72 | + continue |
| 73 | + try: |
| 74 | + # Double-check state after acquiring lock |
| 75 | + if self.running and self.current_tab == 0 and not self.is_refreshing: |
| 76 | + self._background_refresh() |
| 77 | + finally: |
| 78 | + self._refresh_lock.release() |
| 79 | + |
| 80 | + def _background_refresh(self): |
| 81 | + """Refresh data without disrupting selection/menu state; redraw if still on recents.""" |
| 82 | + self.is_refreshing = True |
| 83 | + # Do not redraw immediately to reduce flicker; header shows indicator on next paint |
| 84 | + |
| 85 | + previous_index = self.selected_index |
| 86 | + try: |
| 87 | + if self._load_agent_runs(): |
| 88 | + # Preserve selection but clamp to new list bounds |
| 89 | + if self.agent_runs: |
| 90 | + self.selected_index = max(0, min(previous_index, len(self.agent_runs) - 1)) |
| 91 | + else: |
| 92 | + self.selected_index = 0 |
| 93 | + finally: |
| 94 | + self.is_refreshing = False |
| 95 | + |
| 96 | + # Redraw only if still on recents and app running |
| 97 | + if self.running and self.current_tab == 0: |
| 98 | + self._clear_and_redraw() |
| 99 | + |
46 | 100 | def _get_webapp_domain(self) -> str:
|
47 | 101 | """Get the webapp domain based on environment."""
|
48 | 102 | return get_domain()
|
@@ -72,6 +126,10 @@ def _format_status_line(self, left_text: str) -> str:
|
72 | 126 | instructions_line = f"\033[90m{left_text}\033[0m"
|
73 | 127 | org_line = f"{purple_color}• {org_name}{reset_color}"
|
74 | 128 |
|
| 129 | + # Append a subtle refresh indicator when a refresh is in progress |
| 130 | + if getattr(self, "is_refreshing", False): |
| 131 | + org_line += " \033[90m■ Refreshing…\033[0m" |
| 132 | + |
75 | 133 | return f"{instructions_line}\n{org_line}"
|
76 | 134 |
|
77 | 135 | def _load_agent_runs(self) -> bool:
|
@@ -685,9 +743,17 @@ def _open_agent_details(self):
|
685 | 743 |
|
686 | 744 | def _refresh(self):
|
687 | 745 | """Refresh the agent runs list."""
|
| 746 | + # Indicate refresh and redraw immediately so the user sees it |
| 747 | + self.is_refreshing = True |
| 748 | + self._clear_and_redraw() |
| 749 | + |
688 | 750 | if self._load_agent_runs():
|
689 | 751 | self.selected_index = 0 # Reset selection
|
690 | 752 |
|
| 753 | + # Clear refresh indicator and redraw with updated data |
| 754 | + self.is_refreshing = False |
| 755 | + self._clear_and_redraw() |
| 756 | + |
691 | 757 | def _clear_and_redraw(self):
|
692 | 758 | """Clear screen and redraw everything."""
|
693 | 759 | # Move cursor to top and clear screen from cursor down
|
|
0 commit comments