feat(mma): Implement Pop Out Task DAG option in MMA Dashboard
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
- [x] Task: Bugfix: Correct `PresetManager` initialization to use project parent directory.
|
||||
- [x] Task: Hardening: Wrap modal rendering in `try...finally` to prevent ImGui state corruption.
|
||||
- [x] Task: Hardening: Ensure `PresetManager._save_file` validates that parent is a directory.
|
||||
- [x] Task: Feature: Implement "Pop Out Task DAG" option in MMA Dashboard.
|
||||
- [x] Task: Final UI polish (spacing, icons, tooltips).
|
||||
- [x] Task: Run full suite of relevant tests.
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 4: Final Integration & Polish' (Protocol in workflow.md)
|
||||
|
||||
10
config.toml
10
config.toml
@@ -2,10 +2,10 @@
|
||||
provider = "minimax"
|
||||
model = "MiniMax-M2.5"
|
||||
temperature = 0.0
|
||||
max_tokens = 4096
|
||||
max_tokens = 32000
|
||||
history_trunc_limit = 900000
|
||||
active_preset = "Default"
|
||||
system_prompt = "Not sure yet."
|
||||
system_prompt = ""
|
||||
|
||||
[projects]
|
||||
paths = [
|
||||
@@ -41,13 +41,13 @@ Response = false
|
||||
"Tool Calls" = false
|
||||
Theme = true
|
||||
"Log Management" = true
|
||||
Diagnostics = false
|
||||
Diagnostics = true
|
||||
|
||||
[theme]
|
||||
palette = "Nord Dark"
|
||||
font_path = "C:/projects/manual_slop/assets/fonts/Inter-Regular.ttf"
|
||||
font_size = 12.0
|
||||
scale = 1.2999999523162842
|
||||
font_size = 14.0
|
||||
scale = 1.2000000476837158
|
||||
transparency = 0.550000011920929
|
||||
child_transparency = 0.6399999856948853
|
||||
|
||||
|
||||
@@ -73,8 +73,8 @@ Collapsed=0
|
||||
DockId=0xAFC85805,2
|
||||
|
||||
[Window][Theme]
|
||||
Pos=0,30
|
||||
Size=762,943
|
||||
Pos=0,29
|
||||
Size=762,736
|
||||
Collapsed=0
|
||||
DockId=0x00000005,1
|
||||
|
||||
@@ -90,8 +90,8 @@ Collapsed=0
|
||||
DockId=0x0000000C,2
|
||||
|
||||
[Window][Context Hub]
|
||||
Pos=0,30
|
||||
Size=762,943
|
||||
Pos=0,29
|
||||
Size=762,736
|
||||
Collapsed=0
|
||||
DockId=0x00000005,0
|
||||
|
||||
@@ -102,26 +102,26 @@ Collapsed=0
|
||||
DockId=0x0000000D,0
|
||||
|
||||
[Window][Discussion Hub]
|
||||
Pos=1668,30
|
||||
Size=1004,2107
|
||||
Pos=1668,29
|
||||
Size=1004,2108
|
||||
Collapsed=0
|
||||
DockId=0x00000013,0
|
||||
|
||||
[Window][Operations Hub]
|
||||
Pos=764,30
|
||||
Size=902,2107
|
||||
Pos=764,29
|
||||
Size=902,2108
|
||||
Collapsed=0
|
||||
DockId=0x00000012,0
|
||||
|
||||
[Window][Files & Media]
|
||||
Pos=0,2013
|
||||
Size=762,124
|
||||
Pos=0,1520
|
||||
Size=762,617
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][AI Settings]
|
||||
Pos=0,975
|
||||
Size=762,1036
|
||||
Pos=0,767
|
||||
Size=762,751
|
||||
Collapsed=0
|
||||
DockId=0x00000001,0
|
||||
|
||||
@@ -131,14 +131,14 @@ Size=416,325
|
||||
Collapsed=0
|
||||
|
||||
[Window][MMA Dashboard]
|
||||
Pos=2674,30
|
||||
Size=1166,1675
|
||||
Pos=2674,29
|
||||
Size=1166,1676
|
||||
Collapsed=0
|
||||
DockId=0x0000000C,0
|
||||
|
||||
[Window][Log Management]
|
||||
Pos=2674,30
|
||||
Size=1166,1675
|
||||
Pos=2674,29
|
||||
Size=1166,1676
|
||||
Collapsed=0
|
||||
DockId=0x0000000C,1
|
||||
|
||||
@@ -166,8 +166,8 @@ Collapsed=0
|
||||
DockId=0x0000000F,2
|
||||
|
||||
[Window][Tier 3: Workers]
|
||||
Pos=764,30
|
||||
Size=902,2107
|
||||
Pos=764,29
|
||||
Size=902,2108
|
||||
Collapsed=0
|
||||
DockId=0x00000012,1
|
||||
|
||||
@@ -323,7 +323,7 @@ Collapsed=0
|
||||
|
||||
[Window][Preset Manager]
|
||||
Pos=786,858
|
||||
Size=780,650
|
||||
Size=956,942
|
||||
Collapsed=0
|
||||
|
||||
[Table][0xFB6E3870,4]
|
||||
@@ -380,11 +380,11 @@ Column 2 Weight=1.0000
|
||||
Column 3 Width=106
|
||||
|
||||
[Table][0x2C515046,4]
|
||||
RefScale=18
|
||||
Column 0 Width=58
|
||||
RefScale=20
|
||||
Column 0 Width=64
|
||||
Column 1 Weight=1.0000
|
||||
Column 2 Width=138
|
||||
Column 3 Width=54
|
||||
Column 2 Width=153
|
||||
Column 3 Width=60
|
||||
|
||||
[Table][0xD99F45C5,4]
|
||||
Column 0 Sort=0v
|
||||
@@ -414,14 +414,14 @@ Column 2 Weight=1.0000
|
||||
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,30 Size=3840,2107 Split=X
|
||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,29 Size=3840,2108 Split=X
|
||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2672,1183 Split=X
|
||||
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=762,858 Split=Y Selected=0x8CA2375C
|
||||
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=295,943 Selected=0xF4139CA2
|
||||
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=295,1168 Split=Y Selected=0x7BD57D6A
|
||||
DockNode ID=0x00000001 Parent=0x00000006 SizeRef=824,1036 CentralNode=1 Selected=0x7BD57D6A
|
||||
DockNode ID=0x00000002 Parent=0x00000006 SizeRef=824,124 Selected=0x1DCB2623
|
||||
DockNode ID=0x00000005 Parent=0x00000007 SizeRef=295,736 Selected=0xF4139CA2
|
||||
DockNode ID=0x00000006 Parent=0x00000007 SizeRef=295,1369 Split=Y Selected=0x7BD57D6A
|
||||
DockNode ID=0x00000001 Parent=0x00000006 SizeRef=824,750 CentralNode=1 Selected=0x7BD57D6A
|
||||
DockNode ID=0x00000002 Parent=0x00000006 SizeRef=824,617 Selected=0x1DCB2623
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1908,858 Split=X Selected=0x418C7449
|
||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=902,402 Selected=0x418C7449
|
||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=1004,402 Selected=0x6F2B5B04
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[presets.Default]
|
||||
system_prompt = "Not sure yet."
|
||||
system_prompt = ""
|
||||
temperature = 0.0
|
||||
top_p = 1.0
|
||||
max_output_tokens = 4096
|
||||
max_output_tokens = 32000
|
||||
|
||||
15
scripts/tasks/popout_dag.toml
Normal file
15
scripts/tasks/popout_dag.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
prompt = """
|
||||
In src/gui_2.py:
|
||||
1. In '__init__', initialize 'self.ui_separate_task_dag = gui_cfg.get("separate_task_dag", False)'.
|
||||
2. In '_gui_func', add logic to render the 'Task DAG' window if 'self.ui_separate_task_dag' and 'self.show_windows.get("Task DAG", False)' are true. Use:
|
||||
if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
|
||||
exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"])
|
||||
self.show_windows["Task DAG"] = bool(opened)
|
||||
if exp:
|
||||
self._render_task_dag_panel()
|
||||
imgui.end()
|
||||
3. In '_render_mma_dashboard', add 'ch, self.ui_separate_task_dag = imgui.checkbox("Pop Out Task DAG", self.ui_separate_task_dag)' before the '4. Task DAG Visualizer' section. If 'ch' is true, update 'self.show_windows["Task DAG"] = self.ui_separate_task_dag'.
|
||||
4. Move the entire '4. Task DAG Visualizer' and '5. Add Ticket Form' sections into a new method '_render_task_dag_panel(self)'.
|
||||
5. In '_render_mma_dashboard', call 'self._render_task_dag_panel()' only if 'not self.ui_separate_task_dag'.
|
||||
Use 1-space indentation.
|
||||
"""
|
||||
13
scripts/tasks/popout_dag.txt
Normal file
13
scripts/tasks/popout_dag.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
In src/gui_2.py:
|
||||
1. In '__init__', initialize 'self.ui_separate_task_dag = gui_cfg.get("separate_task_dag", False)'.
|
||||
2. In '_gui_func', add logic to render the 'Task DAG' window if 'self.ui_separate_task_dag' and 'self.show_windows.get("Task DAG", False)' are true. Use:
|
||||
if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
|
||||
exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"])
|
||||
self.show_windows["Task DAG"] = bool(opened)
|
||||
if exp:
|
||||
self._render_task_dag_panel()
|
||||
imgui.end()
|
||||
3. In '_render_mma_dashboard', add 'ch, self.ui_separate_task_dag = imgui.checkbox("Pop Out Task DAG", self.ui_separate_task_dag)' before the '4. Task DAG Visualizer' section. If 'ch' is true, update 'self.show_windows["Task DAG"] = self.ui_separate_task_dag'.
|
||||
4. Move the entire '4. Task DAG Visualizer' and '5. Add Ticket Form' sections into a new method '_render_task_dag_panel(self)'.
|
||||
5. In '_render_mma_dashboard', call 'self._render_task_dag_panel()' only if 'not self.ui_separate_task_dag'.
|
||||
Use 1-space indentation.
|
||||
@@ -342,10 +342,13 @@ class AppController:
|
||||
'_editing_preset_temperature': '_editing_preset_temperature',
|
||||
'_editing_preset_top_p': '_editing_preset_top_p',
|
||||
'_editing_preset_max_output_tokens': '_editing_preset_max_output_tokens',
|
||||
'_editing_preset_scope': '_editing_preset_scope'
|
||||
'_editing_preset_scope': '_editing_preset_scope',
|
||||
'show_windows': 'show_windows',
|
||||
'ui_separate_task_dag': 'ui_separate_task_dag'
|
||||
}
|
||||
self._gettable_fields = dict(self._settable_fields)
|
||||
self._gettable_fields.update({
|
||||
'show_windows': 'show_windows',
|
||||
'ui_focus_agent': 'ui_focus_agent',
|
||||
'active_discussion': 'active_discussion',
|
||||
'_track_discussion_active': '_track_discussion_active',
|
||||
@@ -378,7 +381,8 @@ class AppController:
|
||||
'_editing_preset_temperature': '_editing_preset_temperature',
|
||||
'_editing_preset_top_p': '_editing_preset_top_p',
|
||||
'_editing_preset_max_output_tokens': '_editing_preset_max_output_tokens',
|
||||
'_editing_preset_scope': '_editing_preset_scope'
|
||||
'_editing_preset_scope': '_editing_preset_scope',
|
||||
'ui_separate_task_dag': 'ui_separate_task_dag'
|
||||
})
|
||||
self.perf_monitor = performance_monitor.get_monitor()
|
||||
self._perf_profiling_enabled = False
|
||||
@@ -778,6 +782,7 @@ class AppController:
|
||||
|
||||
def init_state(self):
|
||||
"""Initializes the application state from configurations."""
|
||||
self.ui_separate_task_dag = False
|
||||
self.config = models.load_config()
|
||||
theme.load_from_config(self.config)
|
||||
ai_cfg = self.config.get("ai", {})
|
||||
@@ -836,6 +841,7 @@ class AppController:
|
||||
"Files & Media": True,
|
||||
"AI Settings": True,
|
||||
"MMA Dashboard": True,
|
||||
"Task DAG": False,
|
||||
"Tier 1: Strategy": True,
|
||||
"Tier 2: Tech Lead": True,
|
||||
"Tier 3: Workers": True,
|
||||
@@ -2177,6 +2183,7 @@ class AppController:
|
||||
"separate_message_panel": getattr(self, "ui_separate_message_panel", False),
|
||||
"separate_response_panel": getattr(self, "ui_separate_response_panel", False),
|
||||
"separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False),
|
||||
"separate_task_dag": self.ui_separate_task_dag,
|
||||
"bg_shader_enabled": bg_shader.get_bg().enabled
|
||||
})
|
||||
self.config["gui"] = gui_cfg
|
||||
|
||||
72
src/gui_2.py
72
src/gui_2.py
@@ -126,6 +126,7 @@ class App:
|
||||
self.ui_separate_message_panel = gui_cfg.get("separate_message_panel", False)
|
||||
self.ui_separate_response_panel = gui_cfg.get("separate_response_panel", False)
|
||||
self.ui_separate_tool_calls_panel = gui_cfg.get("separate_tool_calls_panel", False)
|
||||
self.ui_separate_task_dag = gui_cfg.get("separate_task_dag", False)
|
||||
self.ui_multi_viewport = gui_cfg.get("multi_viewport", False)
|
||||
self.layout_presets = self.config.get("layout_presets", {})
|
||||
self._new_preset_name = ""
|
||||
@@ -426,6 +427,13 @@ class App:
|
||||
self._render_mma_dashboard()
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
|
||||
imgui.end()
|
||||
|
||||
if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
|
||||
exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"])
|
||||
self.show_windows["Task DAG"] = bool(opened)
|
||||
if exp:
|
||||
self._render_task_dag_panel()
|
||||
imgui.end()
|
||||
if self.show_windows.get("Tier 1: Strategy", False):
|
||||
exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
|
||||
self.show_windows["Tier 1: Strategy"] = bool(opened)
|
||||
@@ -2709,6 +2717,42 @@ def hello():
|
||||
imgui.separator()
|
||||
self._render_ticket_queue()
|
||||
imgui.separator()
|
||||
ch, self.ui_separate_task_dag = imgui.checkbox("Pop Out Task DAG", self.ui_separate_task_dag)
|
||||
if ch:
|
||||
self.show_windows["Task DAG"] = self.ui_separate_task_dag
|
||||
|
||||
if not self.ui_separate_task_dag:
|
||||
self._render_task_dag_panel()
|
||||
|
||||
# 6. Edit Selected Ticket
|
||||
if self.ui_selected_ticket_id:
|
||||
imgui.separator()
|
||||
imgui.text_colored(C_VAL, f"Editing: {self.ui_selected_ticket_id}")
|
||||
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)}")
|
||||
if imgui.button(f"Mark Complete##{self.ui_selected_ticket_id}"):
|
||||
ticket['status'] = 'done'
|
||||
self._push_mma_state_update()
|
||||
imgui.same_line()
|
||||
if imgui.button(f"Delete##{self.ui_selected_ticket_id}"):
|
||||
self.active_tickets = [t for t in self.active_tickets if str(t.get('id', '')) != self.ui_selected_ticket_id]
|
||||
self.ui_selected_ticket_id = None
|
||||
self._push_mma_state_update()
|
||||
|
||||
def _render_task_dag_panel(self) -> None:
|
||||
# 4. Task DAG Visualizer
|
||||
imgui.text("Task DAG")
|
||||
if self.active_track and self.node_editor_ctx:
|
||||
@@ -2850,34 +2894,6 @@ def hello():
|
||||
else:
|
||||
imgui.text_disabled("No active MMA track.")
|
||||
|
||||
# 6. Edit Selected Ticket
|
||||
if self.ui_selected_ticket_id:
|
||||
imgui.separator()
|
||||
imgui.text_colored(C_VAL, f"Editing: {self.ui_selected_ticket_id}")
|
||||
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)}")
|
||||
if imgui.button(f"Mark Complete##{self.ui_selected_ticket_id}"):
|
||||
ticket['status'] = 'done'
|
||||
self._push_mma_state_update()
|
||||
imgui.same_line()
|
||||
if imgui.button(f"Delete##{self.ui_selected_ticket_id}"):
|
||||
self.active_tickets = [t for t in self.active_tickets if str(t.get('id', '')) != self.ui_selected_ticket_id]
|
||||
self.ui_selected_ticket_id = None
|
||||
self._push_mma_state_update()
|
||||
|
||||
def _render_tier_stream_panel(self, tier_key: str, stream_key: str | None) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tier_stream_panel")
|
||||
if self.is_viewing_prior_session:
|
||||
|
||||
30
tests/test_task_dag_popout_sim.py
Normal file
30
tests/test_task_dag_popout_sim.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import pytest
|
||||
import time
|
||||
from src.api_hook_client import ApiHookClient
|
||||
|
||||
def test_task_dag_popout(live_gui):
|
||||
client = ApiHookClient()
|
||||
|
||||
# 1. Check initial state
|
||||
state = client.get_gui_state()
|
||||
assert state.get("ui_separate_task_dag") is False
|
||||
|
||||
# 2. Enable popout and ensure window is shown
|
||||
client.set_value("ui_separate_task_dag", True)
|
||||
# We need to manually set show_windows["Task DAG"] as well since we are bypassing the checkbox logic
|
||||
show_windows = state.get("show_windows", {})
|
||||
show_windows["Task DAG"] = True
|
||||
client.set_value("show_windows", show_windows)
|
||||
time.sleep(1)
|
||||
|
||||
# 3. Verify state
|
||||
state = client.get_gui_state()
|
||||
assert state.get("ui_separate_task_dag") is True
|
||||
assert state.get("show_windows", {}).get("Task DAG") is True
|
||||
|
||||
# 4. Disable popout
|
||||
client.set_value("ui_separate_task_dag", False)
|
||||
time.sleep(1)
|
||||
|
||||
state = client.get_gui_state()
|
||||
assert state.get("ui_separate_task_dag") is False
|
||||
Reference in New Issue
Block a user