From 7252d759ef15b34e6de7fc041df638cf6ebc00ba Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 27 Feb 2026 22:51:55 -0500 Subject: [PATCH] feat(ui): Implement Task DAG Visualizer using ImGui tree nodes --- gui_2.py | 129 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 88 insertions(+), 41 deletions(-) diff --git a/gui_2.py b/gui_2.py index 2519d16..a14b4dd 100644 --- a/gui_2.py +++ b/gui_2.py @@ -2837,7 +2837,7 @@ class App: # 2. Active Track Info if self.active_track: - imgui.text(f"Track: {self.active_track.get('title', 'Unknown')}") + imgui.text(f"Track: {self.active_track.description}") # Progress bar tickets = self.active_tickets @@ -2871,49 +2871,96 @@ class App: imgui.separator() - # 4. Ticket Queue - imgui.text("Ticket Queue") - if imgui.begin_table("mma_tickets", 4, imgui.TableFlags_.borders_inner_h | imgui.TableFlags_.resizable): - imgui.table_setup_column("ID", imgui.TableColumnFlags_.width_fixed, 80) - imgui.table_setup_column("Target", imgui.TableColumnFlags_.width_stretch) - imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 100) - imgui.table_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 120) - imgui.table_headers_row() - + # 4. Task DAG Visualizer + imgui.text("Task DAG") + if self.active_track: + tickets_by_id = {t.get('id'): t for t in self.active_tickets} + all_ids = set(tickets_by_id.keys()) + + # Build children map + children_map = {} for t in self.active_tickets: - tid = t.get('id', '??') - imgui.table_next_row() - imgui.table_next_column() - imgui.text(str(tid)) - - imgui.table_next_column() - imgui.text(str(t.get('target_file', 'general'))) - - imgui.table_next_column() - status = t.get('status', 'pending').upper() - - if status == 'RUNNING': - imgui.push_style_color(imgui.Col_.text, vec4(255, 255, 0)) # Yellow - elif status == 'COMPLETE': - imgui.push_style_color(imgui.Col_.text, vec4(0, 255, 0)) # Green - elif status == 'BLOCKED' or status == 'ERROR': - imgui.push_style_color(imgui.Col_.text, vec4(255, 0, 0)) # Red - elif status == 'PAUSED': - imgui.push_style_color(imgui.Col_.text, vec4(255, 165, 0)) # Orange - - imgui.text(status) - - if status in ['RUNNING', 'COMPLETE', 'BLOCKED', 'ERROR', 'PAUSED']: - imgui.pop_style_color() + for dep in t.get('depends_on', []): + if dep not in children_map: children_map[dep] = [] + children_map[dep].append(t.get('id')) + + # Roots are those whose depends_on elements are NOT in all_ids + roots = [] + for t in self.active_tickets: + deps = t.get('depends_on', []) + has_local_dep = any(d in all_ids for d in deps) + if not has_local_dep: + roots.append(t) + + rendered = set() + for root in roots: + self._render_ticket_dag_node(root, tickets_by_id, children_map, rendered) + else: + imgui.text_disabled("No active MMA track.") - imgui.table_next_column() - if imgui.button(f"Retry##{tid}"): - self._cb_ticket_retry(tid) - imgui.same_line() - if imgui.button(f"Skip##{tid}"): - self._cb_ticket_skip(tid) + def _render_ticket_dag_node(self, ticket, tickets_by_id, children_map, rendered): + tid = ticket.get('id', '??') + target = ticket.get('target_file', 'general') + status = ticket.get('status', 'pending').upper() + + # Determine color + status_color = vec4(200, 200, 200) # Gray (TODO) + if status == 'RUNNING': + status_color = vec4(255, 255, 0) # Yellow + elif status == 'COMPLETE': + status_color = vec4(0, 255, 0) # Green + elif status in ['BLOCKED', 'ERROR']: + status_color = vec4(255, 0, 0) # Red + elif status == 'PAUSED': + status_color = vec4(255, 165, 0) # Orange + + flags = imgui.TreeNodeFlags_.open_on_arrow | imgui.TreeNodeFlags_.open_on_double_click | imgui.TreeNodeFlags_.default_open + children = children_map.get(tid, []) + if not children: + flags |= imgui.TreeNodeFlags_.leaf + + # Check if already rendered elsewhere to avoid infinite recursion or duplicate subtrees + is_duplicate = tid in rendered + + node_open = imgui.tree_node_ex(f"##{tid}", flags) + + # Detail View / Tooltip + if imgui.is_item_hovered(): + imgui.begin_tooltip() + imgui.text_colored(C_KEY, f"ID: {tid}") + imgui.text_colored(C_LBL, f"Target: {target}") + imgui.text_colored(C_LBL, f"Description:") + imgui.same_line() + imgui.text_wrapped(ticket.get('description', 'N/A')) + deps = ticket.get('depends_on', []) + if deps: + imgui.text_colored(C_LBL, f"Depends on: {', '.join(deps)}") + imgui.end_tooltip() - imgui.end_table() + imgui.same_line() + imgui.text_colored(C_KEY, tid) + imgui.same_line(150) + imgui.text_disabled(str(target)) + imgui.same_line(400) + imgui.text_colored(status_color, status) + + imgui.same_line(500) + if imgui.button(f"Retry##{tid}"): + self._cb_ticket_retry(tid) + imgui.same_line() + if imgui.button(f"Skip##{tid}"): + self._cb_ticket_skip(tid) + + if node_open: + if not is_duplicate: + rendered.add(tid) + for child_id in children: + child = tickets_by_id.get(child_id) + if child: + self._render_ticket_dag_node(child, tickets_by_id, children_map, rendered) + else: + imgui.text_disabled(" (shown above)") + imgui.tree_pop() def _render_tool_calls_panel(self): imgui.text("Tool call history")