Add interactive timeout popup for slow participants
When a participant takes longer than 60 seconds during a discussion turn, the GUI now shows a modal popup asking the user whether to: - Wait 60 more seconds (extends the timeout) - Abort (kills the participant process) This provides better user control over slow AI providers instead of just timing out silently. Implementation: - Track start times and elapsed time per participant in _run_turn_thread - Signal main thread via _slow_participant when timeout exceeded - _poll_background_tasks checks for signal and shows popup - _show_timeout_popup creates modal with Wait/Abort buttons - Callbacks set _timeout_response to communicate back to worker thread 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c8ca9ea91d
commit
76f22cd601
|
|
@ -674,6 +674,11 @@ class DiscussionGUI:
|
|||
self._output_lines = []
|
||||
self._diagram_textures = {} # Cache for loaded textures
|
||||
|
||||
# Slow participant timeout tracking
|
||||
self._slow_participant = None # Alias of slow participant
|
||||
self._slow_participant_elapsed = 0 # How long they've been running
|
||||
self._timeout_response = None # User's response: "wait" or "abort"
|
||||
|
||||
# Read-aloud state
|
||||
self._reading_session_id: Optional[str] = None
|
||||
self._reading_button_tag: Optional[str] = None
|
||||
|
|
@ -1889,10 +1894,42 @@ class DiscussionGUI:
|
|||
self._add_output("Waiting for responses...")
|
||||
self._add_output("")
|
||||
|
||||
# Track start times and timeout state
|
||||
import time
|
||||
start_times = {alias: time.time() for alias in processes}
|
||||
timeout_threshold = 60 # Initial timeout in seconds
|
||||
warned_participants = set() # Track who we've warned about
|
||||
|
||||
# Tail log file while waiting
|
||||
last_pos = 0
|
||||
import time
|
||||
while any(p.poll() is None for p in processes.values()):
|
||||
current_time = time.time()
|
||||
|
||||
# Check for slow participants
|
||||
for alias, process in processes.items():
|
||||
if process.poll() is None and alias not in warned_participants:
|
||||
elapsed = current_time - start_times[alias]
|
||||
if elapsed > timeout_threshold:
|
||||
# Signal main thread to show timeout dialog
|
||||
self._slow_participant = alias
|
||||
self._slow_participant_elapsed = int(elapsed)
|
||||
self._timeout_response = None
|
||||
|
||||
# Wait for user response (main thread will set this)
|
||||
while self._timeout_response is None and process.poll() is None:
|
||||
time.sleep(0.1)
|
||||
|
||||
if self._timeout_response == "abort":
|
||||
self._add_output(f" Aborting {alias} (user requested)")
|
||||
process.kill()
|
||||
warned_participants.add(alias)
|
||||
elif self._timeout_response == "wait":
|
||||
# Give another 60 seconds
|
||||
start_times[alias] = current_time
|
||||
self._add_output(f" Extending timeout for {alias}")
|
||||
|
||||
self._slow_participant = None
|
||||
|
||||
try:
|
||||
if log_file.exists():
|
||||
with open(log_file, 'r') as f:
|
||||
|
|
@ -2424,6 +2461,38 @@ class DiscussionGUI:
|
|||
dpg.add_spacer(width=20)
|
||||
dpg.add_button(label="OK", callback=lambda: dpg.delete_item(window_tag))
|
||||
|
||||
def _show_timeout_popup(self):
|
||||
"""Show popup when a participant is taking too long."""
|
||||
window_tag = "timeout_popup"
|
||||
if dpg.does_item_exist(window_tag):
|
||||
return # Already showing
|
||||
|
||||
alias = self._slow_participant
|
||||
elapsed = self._slow_participant_elapsed
|
||||
|
||||
def on_wait():
|
||||
"""User chose to wait longer."""
|
||||
self._timeout_response = "wait"
|
||||
dpg.delete_item(window_tag)
|
||||
|
||||
def on_abort():
|
||||
"""User chose to abort this participant."""
|
||||
self._timeout_response = "abort"
|
||||
dpg.delete_item(window_tag)
|
||||
|
||||
with dpg.window(label="Slow Participant", tag=window_tag, modal=True,
|
||||
width=450, height=160, pos=[475, 350], no_collapse=True):
|
||||
dpg.add_text(f"Participant '{alias}' is taking longer than expected.",
|
||||
color=(255, 200, 100))
|
||||
dpg.add_text(f"Running for {elapsed} seconds...")
|
||||
dpg.add_spacer(height=15)
|
||||
dpg.add_text("Would you like to wait longer or abort this participant?")
|
||||
dpg.add_spacer(height=15)
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_button(label="Wait 60 more seconds", callback=on_wait, width=180)
|
||||
dpg.add_spacer(width=20)
|
||||
dpg.add_button(label="Abort", callback=on_abort, width=100)
|
||||
|
||||
def _get_templates(self) -> list[str]:
|
||||
"""Get list of available template names."""
|
||||
templates = []
|
||||
|
|
@ -4137,6 +4206,10 @@ final = json.dumps(parsed)''',
|
|||
dpg.set_value("output_text", current + new_text + "\n")
|
||||
self._last_output_index = len(self._output_lines)
|
||||
|
||||
# Check for slow participant timeout popup
|
||||
if self._slow_participant and not dpg.does_item_exist("timeout_popup"):
|
||||
self._show_timeout_popup()
|
||||
|
||||
# Check if turn completed and needs refresh
|
||||
if getattr(self, '_turn_complete', False):
|
||||
self._turn_complete = False
|
||||
|
|
|
|||
Loading…
Reference in New Issue