feat(gui): Implement manual ticket queue management with priority, multi-select, and drag-drop reordering
This commit is contained in:
152
src/gui_2.py
152
src/gui_2.py
@@ -105,6 +105,8 @@ class App:
|
||||
self.node_editor_config = ed.Config()
|
||||
self.node_editor_ctx = ed.create_editor(self.node_editor_config)
|
||||
self.ui_selected_ticket_id: Optional[str] = None
|
||||
self.ui_selected_tickets: set[str] = set()
|
||||
self.ui_new_ticket_priority: str = "medium"
|
||||
self._autofocus_response_tab = False
|
||||
gui_cfg = self.config.get("gui", {})
|
||||
self.ui_separate_message_panel = gui_cfg.get("separate_message_panel", False)
|
||||
@@ -1930,6 +1932,137 @@ class App:
|
||||
self._scroll_tool_calls_to_bottom = False
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tool_calls_panel")
|
||||
|
||||
def bulk_execute(self) -> None:
|
||||
for tid in self.ui_selected_tickets:
|
||||
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
|
||||
if t: t['status'] = 'ready'
|
||||
self._push_mma_state_update()
|
||||
|
||||
def bulk_skip(self) -> None:
|
||||
for tid in self.ui_selected_tickets:
|
||||
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
|
||||
if t: t['status'] = 'completed'
|
||||
self._push_mma_state_update()
|
||||
|
||||
def bulk_block(self) -> None:
|
||||
for tid in self.ui_selected_tickets:
|
||||
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
|
||||
if t: t['status'] = 'blocked'
|
||||
self._push_mma_state_update()
|
||||
|
||||
def _reorder_ticket(self, src_idx: int, dst_idx: int) -> None:
|
||||
if src_idx == dst_idx: return
|
||||
new_tickets = list(self.active_tickets)
|
||||
ticket = new_tickets.pop(src_idx)
|
||||
new_tickets.insert(dst_idx, ticket)
|
||||
# Validate dependencies: a ticket cannot be placed before any of its dependencies
|
||||
id_to_idx = {str(t.get('id', '')): i for i, t in enumerate(new_tickets)}
|
||||
valid = True
|
||||
for i, t in enumerate(new_tickets):
|
||||
deps = t.get('depends_on', [])
|
||||
for d_id in deps:
|
||||
if d_id in id_to_idx and id_to_idx[d_id] >= i:
|
||||
valid = False
|
||||
break
|
||||
if not valid: break
|
||||
if valid:
|
||||
self.active_tickets = new_tickets
|
||||
self._push_mma_state_update()
|
||||
|
||||
def _render_ticket_queue(self) -> None:
|
||||
imgui.text("Ticket Queue Management")
|
||||
if not self.active_track:
|
||||
imgui.text_disabled("No active track.")
|
||||
return
|
||||
|
||||
# Select All / None
|
||||
if imgui.button("Select All"):
|
||||
self.ui_selected_tickets = {str(t.get('id', '')) for t in self.active_tickets}
|
||||
imgui.same_line()
|
||||
if imgui.button("Select None"):
|
||||
self.ui_selected_tickets.clear()
|
||||
|
||||
imgui.same_line()
|
||||
imgui.spacing()
|
||||
imgui.same_line()
|
||||
|
||||
# Bulk Actions
|
||||
if imgui.button("Bulk Execute"):
|
||||
self.bulk_execute()
|
||||
imgui.same_line()
|
||||
if imgui.button("Bulk Skip"):
|
||||
self.bulk_skip()
|
||||
imgui.same_line()
|
||||
if imgui.button("Bulk Block"):
|
||||
self.bulk_block()
|
||||
# Table
|
||||
flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable | imgui.TableFlags_.scroll_y
|
||||
if imgui.begin_table("ticket_queue_table", 5, flags, imgui.ImVec2(0, 300)):
|
||||
imgui.table_setup_column("Select", imgui.TableColumnFlags_.width_fixed, 40)
|
||||
imgui.table_setup_column("ID", imgui.TableColumnFlags_.width_fixed, 80)
|
||||
imgui.table_setup_column("Priority", imgui.TableColumnFlags_.width_fixed, 100)
|
||||
imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 100)
|
||||
imgui.table_setup_column("Description", imgui.TableColumnFlags_.width_stretch)
|
||||
imgui.table_headers_row()
|
||||
|
||||
for i, t in enumerate(self.active_tickets):
|
||||
tid = str(t.get('id', ''))
|
||||
imgui.table_next_row()
|
||||
|
||||
# Select
|
||||
imgui.table_next_column()
|
||||
is_sel = tid in self.ui_selected_tickets
|
||||
changed, is_sel = imgui.checkbox(f"##sel_{tid}", is_sel)
|
||||
if changed:
|
||||
if is_sel: self.ui_selected_tickets.add(tid)
|
||||
else: self.ui_selected_tickets.discard(tid)
|
||||
|
||||
# ID
|
||||
imgui.table_next_column()
|
||||
is_selected = (tid == self.ui_selected_ticket_id)
|
||||
opened, _ = imgui.selectable(f"{tid}##drag_{tid}", is_selected)
|
||||
if opened:
|
||||
self.ui_selected_ticket_id = tid
|
||||
|
||||
if imgui.begin_drag_drop_source():
|
||||
imgui.set_drag_drop_payload("TICKET_REORDER", i)
|
||||
imgui.text(f"Moving {tid}")
|
||||
imgui.end_drag_drop_source()
|
||||
|
||||
if imgui.begin_drag_drop_target():
|
||||
payload = imgui.accept_drag_drop_payload("TICKET_REORDER")
|
||||
if payload:
|
||||
src_idx = int(payload.data)
|
||||
self._reorder_ticket(src_idx, i)
|
||||
imgui.end_drag_drop_target()
|
||||
|
||||
# Priority
|
||||
|
||||
imgui.table_next_column()
|
||||
prio = t.get('priority', 'medium')
|
||||
p_col = vec4(180, 180, 180) # gray
|
||||
if prio == 'high': p_col = vec4(255, 100, 100) # red
|
||||
elif prio == 'medium': p_col = vec4(255, 255, 100) # yellow
|
||||
|
||||
imgui.push_style_color(imgui.Col_.text, p_col)
|
||||
if imgui.begin_combo(f"##prio_{tid}", prio, imgui.ComboFlags_.height_small):
|
||||
for p_opt in ['high', 'medium', 'low']:
|
||||
if imgui.selectable(p_opt, p_opt == prio)[0]:
|
||||
t['priority'] = p_opt
|
||||
self._push_mma_state_update()
|
||||
imgui.end_combo()
|
||||
imgui.pop_style_color()
|
||||
|
||||
# Status
|
||||
imgui.table_next_column()
|
||||
imgui.text(t.get('status', 'todo'))
|
||||
|
||||
# Description
|
||||
imgui.table_next_column()
|
||||
imgui.text(t.get('description', ''))
|
||||
|
||||
imgui.end_table()
|
||||
|
||||
def _render_mma_dashboard(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
|
||||
# Task 5.3: Dense Summary Line
|
||||
@@ -2178,6 +2311,8 @@ class App:
|
||||
|
||||
imgui.pop_id()
|
||||
imgui.separator()
|
||||
self._render_ticket_queue()
|
||||
imgui.separator()
|
||||
# 4. Task DAG Visualizer
|
||||
imgui.text("Task DAG")
|
||||
if self.active_track and self.node_editor_ctx:
|
||||
@@ -2292,11 +2427,19 @@ class App:
|
||||
_, self.ui_new_ticket_desc = imgui.input_text_multiline("Description##new_ticket", self.ui_new_ticket_desc, imgui.ImVec2(-1, 60))
|
||||
_, self.ui_new_ticket_target = imgui.input_text("Target File##new_ticket", self.ui_new_ticket_target)
|
||||
_, self.ui_new_ticket_deps = imgui.input_text("Depends On (IDs, comma-separated)##new_ticket", self.ui_new_ticket_deps)
|
||||
imgui.text("Priority:")
|
||||
imgui.same_line()
|
||||
if imgui.begin_combo("##new_prio", self.ui_new_ticket_priority):
|
||||
for p_opt in ['high', 'medium', 'low']:
|
||||
if imgui.selectable(p_opt, p_opt == self.ui_new_ticket_priority)[0]:
|
||||
self.ui_new_ticket_priority = p_opt
|
||||
imgui.end_combo()
|
||||
if imgui.button("Create"):
|
||||
new_ticket = {
|
||||
"id": self.ui_new_ticket_id,
|
||||
"description": self.ui_new_ticket_desc,
|
||||
"status": "todo",
|
||||
"priority": self.ui_new_ticket_priority,
|
||||
"assigned_to": "tier3-worker",
|
||||
"target_file": self.ui_new_ticket_target,
|
||||
"depends_on": [d.strip() for d in self.ui_new_ticket_deps.split(",") if d.strip()]
|
||||
@@ -2318,6 +2461,15 @@ class App:
|
||||
ticket = next((t for t in self.active_tickets if str(t.get('id', '')) == self.ui_selected_ticket_id), None)
|
||||
if ticket:
|
||||
imgui.text(f"Status: {ticket.get('status', 'todo')}")
|
||||
prio = ticket.get('priority', 'medium')
|
||||
imgui.text("Priority:")
|
||||
imgui.same_line()
|
||||
if imgui.begin_combo(f"##edit_prio_{ticket.get('id')}", prio):
|
||||
for p_opt in ['high', 'medium', 'low']:
|
||||
if imgui.selectable(p_opt, p_opt == prio)[0]:
|
||||
ticket['priority'] = p_opt
|
||||
self._push_mma_state_update()
|
||||
imgui.end_combo()
|
||||
imgui.text(f"Target: {ticket.get('target_file', '')}")
|
||||
deps = ticket.get('depends_on', [])
|
||||
imgui.text(f"Depends on: {', '.join(deps)}")
|
||||
|
||||
Reference in New Issue
Block a user