feat(ui): Implement Task DAG Visualizer using ImGui tree nodes
This commit is contained in:
129
gui_2.py
129
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")
|
||||
|
||||
Reference in New Issue
Block a user