feat(ui): Implement Task DAG Visualizer using ImGui tree nodes
This commit is contained in:
117
gui_2.py
117
gui_2.py
@@ -2837,7 +2837,7 @@ class App:
|
|||||||
|
|
||||||
# 2. Active Track Info
|
# 2. Active Track Info
|
||||||
if self.active_track:
|
if self.active_track:
|
||||||
imgui.text(f"Track: {self.active_track.get('title', 'Unknown')}")
|
imgui.text(f"Track: {self.active_track.description}")
|
||||||
|
|
||||||
# Progress bar
|
# Progress bar
|
||||||
tickets = self.active_tickets
|
tickets = self.active_tickets
|
||||||
@@ -2871,49 +2871,96 @@ class App:
|
|||||||
|
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
|
|
||||||
# 4. Ticket Queue
|
# 4. Task DAG Visualizer
|
||||||
imgui.text("Ticket Queue")
|
imgui.text("Task DAG")
|
||||||
if imgui.begin_table("mma_tickets", 4, imgui.TableFlags_.borders_inner_h | imgui.TableFlags_.resizable):
|
if self.active_track:
|
||||||
imgui.table_setup_column("ID", imgui.TableColumnFlags_.width_fixed, 80)
|
tickets_by_id = {t.get('id'): t for t in self.active_tickets}
|
||||||
imgui.table_setup_column("Target", imgui.TableColumnFlags_.width_stretch)
|
all_ids = set(tickets_by_id.keys())
|
||||||
imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 100)
|
|
||||||
imgui.table_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 120)
|
|
||||||
imgui.table_headers_row()
|
|
||||||
|
|
||||||
|
# Build children map
|
||||||
|
children_map = {}
|
||||||
for t in self.active_tickets:
|
for t in self.active_tickets:
|
||||||
tid = t.get('id', '??')
|
for dep in t.get('depends_on', []):
|
||||||
imgui.table_next_row()
|
if dep not in children_map: children_map[dep] = []
|
||||||
imgui.table_next_column()
|
children_map[dep].append(t.get('id'))
|
||||||
imgui.text(str(tid))
|
|
||||||
|
|
||||||
imgui.table_next_column()
|
# Roots are those whose depends_on elements are NOT in all_ids
|
||||||
imgui.text(str(t.get('target_file', 'general')))
|
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)
|
||||||
|
|
||||||
imgui.table_next_column()
|
rendered = set()
|
||||||
status = t.get('status', 'pending').upper()
|
for root in roots:
|
||||||
|
self._render_ticket_dag_node(root, tickets_by_id, children_map, rendered)
|
||||||
|
else:
|
||||||
|
imgui.text_disabled("No active MMA track.")
|
||||||
|
|
||||||
if status == 'RUNNING':
|
def _render_ticket_dag_node(self, ticket, tickets_by_id, children_map, rendered):
|
||||||
imgui.push_style_color(imgui.Col_.text, vec4(255, 255, 0)) # Yellow
|
tid = ticket.get('id', '??')
|
||||||
elif status == 'COMPLETE':
|
target = ticket.get('target_file', 'general')
|
||||||
imgui.push_style_color(imgui.Col_.text, vec4(0, 255, 0)) # Green
|
status = ticket.get('status', 'pending').upper()
|
||||||
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)
|
# 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
|
||||||
|
|
||||||
if status in ['RUNNING', 'COMPLETE', 'BLOCKED', 'ERROR', 'PAUSED']:
|
flags = imgui.TreeNodeFlags_.open_on_arrow | imgui.TreeNodeFlags_.open_on_double_click | imgui.TreeNodeFlags_.default_open
|
||||||
imgui.pop_style_color()
|
children = children_map.get(tid, [])
|
||||||
|
if not children:
|
||||||
|
flags |= imgui.TreeNodeFlags_.leaf
|
||||||
|
|
||||||
imgui.table_next_column()
|
# Check if already rendered elsewhere to avoid infinite recursion or duplicate subtrees
|
||||||
if imgui.button(f"Retry##{tid}"):
|
is_duplicate = tid in rendered
|
||||||
self._cb_ticket_retry(tid)
|
|
||||||
imgui.same_line()
|
|
||||||
if imgui.button(f"Skip##{tid}"):
|
|
||||||
self._cb_ticket_skip(tid)
|
|
||||||
|
|
||||||
imgui.end_table()
|
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.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):
|
def _render_tool_calls_panel(self):
|
||||||
imgui.text("Tool call history")
|
imgui.text("Tool call history")
|
||||||
|
|||||||
Reference in New Issue
Block a user