diff --git a/src/gui_2.py b/src/gui_2.py index 72f15aa..059d69a 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -992,7 +992,7 @@ class App: if p not in self.screenshots: self.screenshots.append(p) return - def _gui_func__abusrd_try_scope(self) -> None: + def _render_main_interface(self) -> None: self.perf_monitor.start_frame() self._autofocus_response_tab = self.controller._autofocus_response_tab @@ -1095,6 +1095,496 @@ class App: else: self._pending_dialog_open = False + if imgui.begin_popup_modal("Approve PowerShell Command", None, imgui.WindowFlags_.always_auto_resize)[0]: + if not dlg: + imgui.close_current_popup() + + def _render_window_if_open(self, name: str, render_func: Callable[[], None], flag_condition: bool = True) -> None: + """Helper to render a window only if its toggle is active.""" + if not flag_condition or not self.show_windows.get(name, False): return + with imscope.window(name, self.show_windows[name]) as (exp, opened): + self.show_windows[name] = bool(opened) + if exp: render_func() + + def _render_project_settings_hub(self) -> None: + with imscope.tab_bar('context_hub_tabs'): + with imscope.tab_item('Projects') as (exp, _): + if exp: self._render_projects_panel() + with imscope.tab_item('Paths') as (exp, _): + if exp: self._render_paths_panel() + + def _render_ai_settings_hub(self) -> None: + self._render_persona_selector_panel() + if imgui.collapsing_header("Provider & Model"): self._render_provider_panel() + if imgui.collapsing_header("System Prompts"): self._render_system_prompts_panel() + if imgui.collapsing_header("RAG Settings"): self._render_rag_panel() + self._render_agent_tools_panel() + + def _render_discussion_hub(self) -> None: + with imscope.tab_bar("discussion_hub_tabs"): + with imscope.tab_item("Discussion") as (exp, _): + if exp: self._render_discussion_tab() + with imscope.tab_item("Context Composition") as (exp, _): + if exp: self._render_context_composition_panel() + with imscope.tab_item("Snapshot") as (exp, _): + if exp: self._render_snapshot_tab() + with imscope.tab_item("Takes") as (exp, _): + if exp: self._render_takes_panel() + + def _render_operations_hub(self) -> None: + imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4)) + ch1, self.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", self.ui_separate_tool_calls_panel) + if ch1: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel + imgui.same_line() + ch2, self.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", self.ui_separate_usage_analytics) + if ch2: self.show_windows["Usage Analytics"] = self.ui_separate_usage_analytics + imgui.same_line() + ch3, self.ui_separate_external_tools = imgui.checkbox('Pop Out External Tools', self.ui_separate_external_tools) + if ch3: self.show_windows['External Tools'] = self.ui_separate_external_tools + imgui.pop_style_var() + show_tc_tab, show_usage_tab = not self.ui_separate_tool_calls_panel, not self.ui_separate_usage_analytics + with imscope.tab_bar("ops_tabs"): + with imscope.tab_item("Comms History") as (exp, _): + if exp: self._render_comms_history_panel() + if show_tc_tab: + with imscope.tab_item("Tool Calls") as (exp, _): + if exp: self._render_tool_calls_panel() + if show_usage_tab: + with imscope.tab_item("Usage Analytics") as (exp, _): + if exp: self._render_usage_analytics_panel() + if not self.ui_separate_external_tools: + with imscope.tab_item("External Tools") as (exp, _): + if exp: + self._render_external_tools_panel() + imgui.separator(); imgui.text("") + try: self._render_external_editor_panel() + except Exception as e: imgui.text_colored(vec4(1, 0.3, 0.3, 1), f"Error: {str(e)}") + with imscope.tab_item("Workspace Layouts") as (exp, _): + if exp: + imgui.text("Experimental: Auto-switch layout by Tier") + ch, self.controller.ui_auto_switch_layout = imgui.checkbox("Enable Auto-Switch", self.controller.ui_auto_switch_layout) + if self.controller.ui_auto_switch_layout: + imgui.separator(); imgui.text("Tier Bindings (select profile for each tier)") + profiles = [""] + [p.name for p in self.controller.workspace_profiles.values()] + for t in ["Tier 1", "Tier 2", "Tier 3", "Tier 4"]: + curr = self.controller.ui_tier_layout_bindings.get(t, ""); idx = profiles.index(curr) if curr in profiles else 0 + ch_combo, new_idx = imgui.combo(t, idx, profiles) + if ch_combo: self.controller.ui_tier_layout_bindings[t] = profiles[new_idx] + + def _render_thinking_indicator(self) -> None: + is_thinking = self.ai_status in ['sending...', 'streaming...', 'running powershell...'] + if is_thinking: + val = math.sin(time.time() * 10 * math.pi) + alpha = 1.0 if val > 0 else 0.0 + c = vec4(255, 50, 50, alpha) if theme.is_nerv_active() else vec4(255, 100, 100, alpha) + imgui.text_colored(c, "THINKING..."); imgui.same_line() + + def _render_prior_session_view(self) -> None: + imgui.push_style_color(imgui.Col_.child_bg, vec4(50, 40, 20)) + if imgui.button("Exit Prior Session"): self.controller.cb_exit_prior_session(); self._comms_log_dirty = True + imgui.separator() + with imscope.child("prior_scroll"): + clipper = imgui.ListClipper(); clipper.begin(len(self.prior_disc_entries)) + while clipper.step(): + for idx in range(clipper.display_start, clipper.display_end): + entry = self.prior_disc_entries[idx]; with imscope.id(f"prior_disc_{idx}"): + collapsed = entry.get("collapsed", False) + if imgui.button("+" if collapsed else "-"): entry["collapsed"] = not collapsed + imgui.same_line(); role, ts = entry.get("role", "??"), entry.get("ts", "") + imgui.text_colored(C_LBL, f"[{role}]") + if ts: imgui.same_line(); imgui.text_colored(vec4(160, 160, 160), str(ts)) + content = entry.get("content", "") + if collapsed: + imgui.same_line(); preview = content.replace("\n", " ")[:80] + if len(content) > 80: preview += "..." + imgui.text_colored(vec4(180, 180, 180), preview) + else: + is_nerv = theme.is_nerv_active() + if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80)) + markdown_helper.render(content, context_id=f'prior_disc_{idx}') + if is_nerv: imgui.pop_style_color() + imgui.separator() + imgui.pop_style_color() + + def _render_discussion_selector(self) -> None: + if not imgui.collapsing_header("Discussions", imgui.TreeNodeFlags_.default_open): return + names = self._get_discussion_names(); grouped = {} + for name in names: + base = name.split("_take_")[0]; grouped.setdefault(base, []).append(name) + active_base = self.active_discussion.split("_take_")[0] + if active_base not in grouped: active_base = names[0] if names else "" + base_names = sorted(grouped.keys()) + if imgui.begin_combo("##disc_sel", active_base): + for bname in base_names: + is_selected = (bname == active_base) + if imgui.selectable(bname, is_selected)[0]: + target = bname if bname in names else grouped[bname][0] + if target != self.active_discussion: self._switch_discussion(target) + if is_selected: imgui.set_item_default_focus() + imgui.end_combo() + active_base = self.active_discussion.split("_take_")[0]; current_takes = grouped.get(active_base, []) + if imgui.begin_tab_bar("discussion_takes_tabs"): + for take_name in current_takes: + label = "Original" if take_name == active_base else take_name.replace(f"{active_base}_", "").replace("_", " ").title() + flags = imgui.TabItemFlags_.set_selected if take_name == self.active_discussion else 0 + with imscope.tab_item(f"{label}###{take_name}", flags) as (exp, _): + if exp and take_name != self.active_discussion: self._switch_discussion(take_name) + with imscope.tab_item("Synthesis###Synthesis") as (exp, _): + if exp: self._render_synthesis_panel() + imgui.end_tab_bar() + if "_take_" in self.active_discussion: + if imgui.button("Promote Take"): + base_name = self.active_discussion.split("_take_")[0]; new_name = f"{base_name}_promoted"; counter = 1 + while new_name in names: new_name = f"{base_name}_promoted_{counter}"; counter += 1 + project_manager.promote_take(self.project, self.active_discussion, new_name); self._switch_discussion(new_name) + imgui.same_line() + if self.active_track: + imgui.same_line(); ch, self._track_discussion_active = imgui.checkbox("Track Discussion", self._track_discussion_active) + if ch: + if self._track_discussion_active: + self._flush_disc_entries_to_project() + history_strings = project_manager.load_track_history(self.active_track.id, self.active_project_root) + with self._disc_entries_lock: self.disc_entries = models.parse_history_entries(history_strings, self.disc_roles) + self.ai_status = f"track discussion: {self.active_track.id}" + else: self._flush_disc_entries_to_project(); self._switch_discussion(self.active_discussion); self.ai_status = "track discussion disabled" + self._render_discussion_metadata() + + def _render_discussion_metadata(self) -> None: + disc_data = self.project.get("discussion", {}).get("discussions", {}).get(self.active_discussion, {}) + git_commit, last_updated = disc_data.get("git_commit", ""), disc_data.get("last_updated", "") + imgui.text_colored(C_LBL, "commit:"); imgui.same_line() + self._render_selectable_label('git_commit_val', git_commit[:12] if git_commit else '(none)', width=100, color=(C_IN if git_commit else C_LBL)) + imgui.same_line() + if imgui.button("Update Commit"): + if self.ui_project_git_dir: + cmt = project_manager.get_git_commit(self.ui_project_git_dir) + if cmt: disc_data["git_commit"], disc_data["last_updated"], self.ai_status = cmt, project_manager.now_ts(), f"commit: {cmt[:12]}" + imgui.text_colored(C_LBL, "updated:"); imgui.same_line(); imgui.text_colored(C_SUB, last_updated if last_updated else "(never)") + ch, self.ui_disc_new_name_input = imgui.input_text("##new_disc", self.ui_disc_new_name_input); imgui.same_line() + if imgui.button("Create"): + nm = self.ui_disc_new_name_input.strip() + if nm: self._create_discussion(nm); self.ui_disc_new_name_input = "" + imgui.same_line() + if imgui.button("Rename"): + nm = self.ui_disc_new_name_input.strip() + if nm: self._rename_discussion(self.active_discussion, nm); self.ui_disc_new_name_input = "" + imgui.same_line() + if imgui.button("Delete"): self._delete_discussion(self.active_discussion) + + def _render_discussion_entry_controls(self) -> None: + if imgui.button("+ Entry"): self.disc_entries.append({"role": self.disc_roles[0] if self.disc_roles else "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()}) + imgui.same_line() + if imgui.button("-All"): + for e in self.disc_entries: e["collapsed"] = True + imgui.same_line() + if imgui.button("+All"): + for e in self.disc_entries: e["collapsed"] = False + imgui.same_line() + if imgui.button("Clear All"): self.disc_entries.clear() + imgui.same_line() + if imgui.button("Save"): self._flush_to_project(); self._flush_to_config(); models.save_config(self.config); self.ai_status = "discussion saved" + _, self.ui_auto_add_history = imgui.checkbox("Auto-add message & response to history", self.ui_auto_add_history) + imgui.text("Keep Pairs:"); imgui.same_line(); imgui.set_next_item_width(80) + ch, self.ui_disc_truncate_pairs = imgui.input_int("##trunc_pairs", self.ui_disc_truncate_pairs, 1) + if self.ui_disc_truncate_pairs < 1: self.ui_disc_truncate_pairs = 1 + imgui.same_line() + if imgui.button("Truncate"): + with self._disc_entries_lock: self.disc_entries = truncate_entries(self.disc_entries, self.ui_disc_truncate_pairs) + self.ai_status = f"history truncated to {self.ui_disc_truncate_pairs} pairs" + + def _render_discussion_roles(self) -> None: + if imgui.collapsing_header("Roles"): + with imscope.child("roles_scroll", size_y=100, flags=True): + for i, r in enumerate(list(self.disc_roles)): + with imscope.id(f"role_{i}"): + if imgui.button("X"): self.disc_roles.pop(i); break + imgui.same_line(); imgui.text(r) + ch, self.ui_disc_new_role_input = imgui.input_text("##new_role", self.ui_disc_new_role_input); imgui.same_line() + if imgui.button("Add"): + r = self.ui_disc_new_role_input.strip() + if r and r not in self.disc_roles: self.disc_roles.append(r); self.ui_disc_new_role_input = "" + + def _render_discussion_entries(self) -> None: + with imscope.child("disc_scroll"): + display_entries = self.disc_entries + if self.ui_focus_agent: + tier_usage = self.mma_tier_usage.get(self.ui_focus_agent) + if tier_usage: + persona_name = tier_usage.get("persona") + if persona_name: display_entries = [e for e in self.disc_entries if e.get("role") == persona_name or e.get("role") == "User"] + clipper = imgui.ListClipper(); clipper.begin(len(display_entries)) + while clipper.step(): + for i in range(clipper.display_start, clipper.display_end): + self._render_discussion_entry(display_entries[i], i) + if self._scroll_disc_to_bottom: imgui.set_scroll_here_y(1.0); self._scroll_disc_to_bottom = False + + def _render_discussion_entry(self, entry: dict, index: int) -> None: + with imscope.id(f"disc_{index}"): + collapsed, read_mode = entry.get("collapsed", False), entry.get("read_mode", False) + if imgui.button("+" if collapsed else "-"): entry["collapsed"] = not collapsed + imgui.same_line(); self._render_text_viewer(f"Entry #{index+1}", entry["content"]); imgui.same_line(); imgui.set_next_item_width(120) + if imgui.begin_combo("##role", entry["role"]): + for r in self.disc_roles: + if imgui.selectable(r, r == entry["role"])[0]: entry["role"] = r + imgui.end_combo() + if not collapsed: + imgui.same_line() + if imgui.button("[Edit]" if read_mode else "[Read]"): entry["read_mode"] = not read_mode + ts_str = entry.get("ts", "") + if ts_str: + imgui.same_line(); imgui.text_colored(vec4(120, 120, 100), str(ts_str)); e_dt = project_manager.parse_ts(ts_str) + if e_dt: + e_unix, next_unix = e_dt.timestamp(), float('inf') + if index + 1 < len(self.disc_entries): + n_ts = self.disc_entries[index+1].get("ts", ""); n_dt = project_manager.parse_ts(n_ts) + if n_dt: next_unix = n_dt.timestamp() + injected = [f for f in self.files if hasattr(f, 'injected_at') and f.injected_at and e_unix <= f.injected_at < next_unix] + if injected: + imgui.same_line(); imgui.text_colored(vec4(100, 255, 100), f"[{len(injected)}+]") + if imgui.is_item_hovered(): imgui.set_tooltip("Files injected at this point:\n" + "\n".join([f.path for f in injected])) + if collapsed: + imgui.same_line() + if imgui.button("Ins"): self.disc_entries.insert(index, {"role": "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()}) + imgui.same_line() + if imgui.button("Del"): self.disc_entries.pop(index); return + imgui.same_line() + if imgui.button("Branch"): self._branch_discussion(index) + imgui.same_line(); preview = entry["content"].replace("\n", " ")[:60] + if len(entry["content"]) > 60: preview += "..." + if not preview.strip() and entry.get("thinking_segments"): + preview = entry["thinking_segments"][0]["content"].replace("\n", " ")[:60] + if len(entry["thinking_segments"][0]["content"]) > 60: preview += "..." + imgui.text_colored(vec4(160, 160, 150), preview) + if not collapsed: + thinking_segments, has_content = entry.get("thinking_segments", []), bool(entry.get("content", "").strip()) + if thinking_segments: self._render_thinking_trace(thinking_segments, index, is_standalone=not has_content) + if read_mode: self._render_discussion_entry_read_mode(entry, index) + else: + if not (bool(thinking_segments) and not has_content): ch, entry["content"] = imgui.input_text_multiline("##content", entry["content"], imgui.ImVec2(-1, 150)) + imgui.separator() + + def _render_discussion_entry_read_mode(self, entry: dict, index: int) -> None: + content = entry["content"] + if not content.strip(): return + if '## Retrieved Context' in content: + rag_match = re.search(r'## Retrieved Context\n\n([\s\S]*?)(?=\n\n#|\Z)', content) + if rag_match: + rag_section = rag_match.group(1) + if imgui.collapsing_header('Retrieved Context'): + chunks = re.finditer(r'### Chunk (\d+) \(Source: (.*?)\)\n([\s\S]*?)(?=\n### Chunk|\Z)', rag_section) + for chunk_match in chunks: + idx, path, chunk_content = chunk_match.group(1), chunk_match.group(2), chunk_match.group(3) + if imgui.collapsing_header(f'Chunk {idx}: {path}'): + if imgui.button(f'[Source]##rag_{index}_{idx}'): + res = mcp_client.read_file(path) + if res: self.text_viewer_title, self.text_viewer_content, self.text_viewer_type, self.show_text_viewer = path, res, (Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'), True + imgui.text_unformatted(chunk_content) + content = content[:rag_match.start()] + content[rag_match.end():] + pattern = re.compile(r"\[Definition: (.*?) from (.*?) \(line (\d+)\)\](\s+```[\s\S]*?```)?") + matches, is_nerv = list(pattern.finditer(content)), theme.is_nerv_active() + if not matches: + if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80)) + markdown_helper.render(content, context_id=f'disc_{index}') + if is_nerv: imgui.pop_style_color() + else: + with imscope.child(f"read_content_{index}", size_y=150, flags=True): + if self.ui_word_wrap: imgui.push_text_wrap_pos(imgui.get_content_region_avail().x) + last_idx = 0 + for m_idx, match in enumerate(matches): + before = content[last_idx:match.start()] + if before: + if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80)) + markdown_helper.render(before, context_id=f'disc_{index}_b_{m_idx}') + if is_nerv: imgui.pop_style_color() + header_text, path, code_block = match.group(0).split("\n")[0].strip(), match.group(2), match.group(4) + if imgui.collapsing_header(header_text): + if imgui.button(f"[Source]##{index}_{match.start()}"): + res = mcp_client.read_file(path) + if res: self.text_viewer_title, self.text_viewer_content, self.text_viewer_type, self.show_text_viewer = path, res, (Path(path).suffix.lstrip('.') if Path(path).suffix else 'text'), True + if code_block: + if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80)) + markdown_helper.render(code_block, context_id=f'disc_{index}_c_{m_idx}') + if is_nerv: imgui.pop_style_color() + last_idx = match.end() + after = content[last_idx:] + if after: + if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80)) + markdown_helper.render(after, context_id=f'disc_{index}_a') + if is_nerv: imgui.pop_style_color() + if self.ui_word_wrap: imgui.pop_text_wrap_pos() + + def _render_mma_focus_selector(self) -> None: + imgui.text("Focus Agent:"); imgui.same_line() + focus_label = self.ui_focus_agent or "All" + if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview): + if imgui.selectable("All", self.ui_focus_agent is None)[0]: self.ui_focus_agent = None + for tier in ["Tier 2", "Tier 3", "Tier 4"]: + if imgui.selectable(tier, self.ui_focus_agent == tier)[0]: self.ui_focus_agent = tier + imgui.end_combo() + imgui.same_line() + if self.ui_focus_agent and imgui.button("x##clear_focus"): self.ui_focus_agent = None + + def _render_mma_track_summary(self) -> None: + is_nerv = theme.is_nerv_active() + track_name = self.active_track.description if self.active_track else "None" + if getattr(self, "ui_project_execution_mode", "native") == "beads": track_name = "Beads Graph" + track_stats = project_manager.calculate_track_progress(self.active_track.tickets if self.active_track else self.active_tickets) + total_cost = sum(cost_tracker.estimate_cost(u.get('model','unknown'), u.get('input',0), u.get('output',0)) for u in self.mma_tier_usage.values()) + imgui.text("Track:"); imgui.same_line(); imgui.text_colored(C_VAL, track_name); imgui.same_line(); imgui.text(" | Status:"); imgui.same_line() + if self.mma_status == "paused": + imgui.text_colored(vec4(255, 152, 48) if is_nerv else imgui.ImVec4(1, 0.5, 0, 1), "PIPELINE PAUSED"); imgui.same_line() + status_col = imgui.ImVec4(1, 1, 1, 1) + if self.mma_status == "idle": status_col = imgui.ImVec4(0.7, 0.7, 0.7, 1) + elif self.mma_status == "running": status_col = vec4(80, 255, 80) if is_nerv else imgui.ImVec4(1, 1, 0, 1) + elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1) + elif self.mma_status == "error": status_col = vec4(255, 72, 64) if is_nerv else imgui.ImVec4(1, 0, 0, 1) + elif self.mma_status == "paused": status_col = imgui.ImVec4(1, 0.5, 0, 1) + imgui.text_colored(status_col, self.mma_status.upper()); imgui.same_line(); imgui.text(" | Cost:"); imgui.same_line(); imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"${total_cost:,.4f}") + perc = track_stats["percentage"] / 100.0 + p_color = imgui.ImVec4(1, 0, 0, 1) if track_stats["percentage"] < 33 else (imgui.ImVec4(1, 1, 0, 1) if track_stats["percentage"] < 66 else imgui.ImVec4(0, 1, 0, 1)) + imgui.push_style_color(imgui.Col_.plot_histogram, p_color); imgui.progress_bar(perc, imgui.ImVec2(-1, 0), f"{track_stats['percentage']:.1f}%"); imgui.pop_style_color() + if imgui.begin_table("ticket_stats_breakdown", 4): + for lbl, val in [("Completed:", track_stats["completed"]), ("In Progress:", track_stats["in_progress"]), ("Blocked:", track_stats["blocked"]), ("Todo:", track_stats["todo"])]: + imgui.table_next_column(); imgui.text_colored(C_LBL, lbl); imgui.same_line(); imgui.text_colored(C_VAL, str(val)) + imgui.end_table() + if self.active_track: + remaining = track_stats["total"] - track_stats["completed"] + eta_mins = (self._avg_ticket_time * remaining) / 60.0 + imgui.text_colored(C_LBL, "ETA:"); imgui.same_line(); imgui.text_colored(C_VAL, f"~{int(eta_mins)}m ({remaining} tickets remaining)") + + def _render_mma_epic_planner(self) -> None: + imgui.text_colored(C_LBL, 'Epic Planning (Tier 1)') + _, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80)) + if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)): self._cb_plan_epic() + + def _render_mma_conductor_setup(self) -> None: + if imgui.button("Run Setup Scan"): self._cb_run_conductor_setup() + if self.ui_conductor_setup_summary: imgui.input_text_multiline("##setup_summary", self.ui_conductor_setup_summary, imgui.ImVec2(-1, 120), imgui.InputTextFlags_.read_only) + + def _render_mma_track_browser(self) -> None: + imgui.text("Track Browser") + if imgui.begin_table("mma_tracks_table", 4, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable): + imgui.table_setup_column("Title"); imgui.table_setup_column("Status"); imgui.table_setup_column("Progress"); imgui.table_setup_column("Actions"); imgui.table_headers_row() + for track in self.tracks: + imgui.table_next_row(); imgui.table_next_column(); imgui.text(track.get("title", "Untitled")); imgui.table_next_column() + status = track.get("status", "unknown").lower() + c = imgui.ImVec4(0.7, 0.7, 0.7, 1) if status == "new" else (vec4(80, 255, 80) if status == "active" and theme.is_nerv_active() else (imgui.ImVec4(1, 1, 0, 1) if status == "active" else (imgui.ImVec4(0, 1, 0, 1) if status == "done" else (imgui.ImVec4(1, 0, 0, 1) if status == "blocked" else imgui.ImVec4(1, 1, 1, 1))))) + imgui.text_colored(c, status.upper()); imgui.table_next_column() + prog = track.get("progress", 0.0) + p_c = imgui.ImVec4(1, 0, 0, 1) if prog < 0.33 else (imgui.ImVec4(1, 1, 0, 1) if prog < 0.66 else imgui.ImVec4(0, 1, 0, 1)) + imgui.push_style_color(imgui.Col_.plot_histogram, p_c); imgui.progress_bar(prog, imgui.ImVec2(-1, 0), f"{int(prog*100)}%"); imgui.pop_style_color(); imgui.table_next_column() + if imgui.button(f"Load##{track.get('id')}"): self._cb_load_track(str(track.get("id") or "")) + imgui.end_table() + imgui.text("Create New Track") + _, self.ui_new_track_name = imgui.input_text("Name##new_track", self.ui_new_track_name) + _, self.ui_new_track_desc = imgui.input_text_multiline("Description##new_track", self.ui_new_track_desc, imgui.ImVec2(-1, 60)) + imgui.text("Type:"); imgui.same_line() + if imgui.begin_combo("##track_type", self.ui_new_track_type): + for ttype in ["feature", "chore", "fix"]: + if imgui.selectable(ttype, self.ui_new_track_type == ttype)[0]: self.ui_new_track_type = ttype + imgui.end_combo() + if imgui.button("Create Track"): + self._cb_create_track(self.ui_new_track_name, self.ui_new_track_desc, self.ui_new_track_type) + self.ui_new_track_name = ""; self.ui_new_track_desc = "" + + def _render_mma_global_controls(self) -> None: + changed, self.mma_step_mode = imgui.checkbox("Step Mode (HITL)", self.mma_step_mode) + imgui.same_line(); imgui.text(f"Status: {self.mma_status.upper()}") + if self.controller and hasattr(self.controller, 'engine') and self.controller.engine and hasattr(self.controller.engine, '_pause_event'): + imgui.same_line() + is_paused = self.controller.engine._pause_event.is_set() + if imgui.button("Resume" if is_paused else "Pause"): + if is_paused: self.controller.engine.resume() + else: self.controller.engine.pause() + if self.active_tier: + imgui.same_line(); imgui.text_colored(C_VAL, f"| Active: {self.active_tier}") + any_pending = len(self._pending_mma_spawns) > 0 or len(self._pending_mma_approvals) > 0 or self._pending_ask_dialog + if any_pending: + alpha = abs(math.sin(time.time() * 5)) + c = vec4(255, 72, 64, alpha) if theme.is_nerv_active() else imgui.ImVec4(1, 0.3, 0.3, alpha) + imgui.same_line(); imgui.text_colored(c, " APPROVAL PENDING"); imgui.same_line() + if imgui.button("Go to Approval"): pass + + def _render_mma_usage_section(self) -> None: + imgui.text("Tier Usage (Tokens & Cost)") + if imgui.begin_table("mma_usage", 5, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg): + imgui.table_setup_column("Tier"); imgui.table_setup_column("Model"); imgui.table_setup_column("Input"); imgui.table_setup_column("Output"); imgui.table_setup_column("Est. Cost"); imgui.table_headers_row() + total_cost = 0.0 + for tier, stats in self.mma_tier_usage.items(): + imgui.table_next_row(); imgui.table_next_column(); imgui.text(tier); imgui.table_next_column(); model = stats.get('model', 'unknown'); imgui.text(model); imgui.table_next_column(); in_t = stats.get('input', 0); imgui.text(f"{in_t:,}"); imgui.table_next_column(); out_t = stats.get('output', 0); imgui.text(f"{out_t:,}"); imgui.table_next_column(); cost = cost_tracker.estimate_cost(model, in_t, out_t); total_cost += cost; imgui.text(f"${cost:,.4f}") + imgui.table_next_row(); imgui.table_set_bg_color(imgui.TableBgTarget_.row_bg0, imgui.get_color_u32(imgui.Col_.plot_lines_hovered)); imgui.table_next_column(); imgui.text("TOTAL"); imgui.table_next_column(); imgui.text(""); imgui.table_next_column(); imgui.text(""); imgui.table_next_column(); imgui.text(""); imgui.table_next_column(); imgui.text(f"${total_cost:,.4f}"); imgui.end_table() + if imgui.collapsing_header("Tier Model Config"): + for tier in self.mma_tier_usage.keys(): + imgui.text(f"{tier}:"); imgui.same_line(); curr_model, curr_prov = self.mma_tier_usage[tier].get("model", "unknown"), self.mma_tier_usage[tier].get("provider", "gemini") + with imscope.id(f"tier_cfg_{tier}"): + imgui.push_item_width(80) + if imgui.begin_combo("##prov", curr_prov): + for p in models.PROVIDERS: + if imgui.selectable(p, p == curr_prov)[0]: + self.mma_tier_usage[tier]["provider"] = p + models_list = self.controller.all_available_models.get(p, []) + if models_list: self.mma_tier_usage[tier]["model"] = models_list[0] + imgui.end_combo() + imgui.pop_item_width(); imgui.same_line(); imgui.push_item_width(150) + models_list = self.controller.all_available_models.get(curr_prov, []) + if imgui.begin_combo("##model", curr_model): + for m in models_list: + if imgui.selectable(m, curr_model == m)[0]: self.mma_tier_usage[tier]["model"] = m + imgui.end_combo() + imgui.pop_item_width(); imgui.same_line(); imgui.push_item_width(-1) + curr_preset = self.mma_tier_usage[tier].get("tool_preset") or "None" + p_names = ["None"] + sorted(self.controller.tool_presets.keys()) + if imgui.begin_combo("##preset", curr_preset): + for pn in p_names: + if imgui.selectable(pn, curr_preset == pn)[0]: self.mma_tier_usage[tier]["tool_preset"] = None if pn == "None" else pn + imgui.end_combo() + imgui.pop_item_width(); imgui.same_line(); imgui.push_item_width(150) + curr_pers = self.mma_tier_usage[tier].get("persona") or "None" + personas = getattr(self.controller, 'personas', {}) + pers_opts = ["None"] + sorted(personas.keys()) + if imgui.begin_combo("##persona", curr_pers): + for pern in pers_opts: + if imgui.selectable(pern, curr_pers == pern)[0]: self.mma_tier_usage[tier]["persona"] = None if pern == "None" else pern + imgui.end_combo() + imgui.pop_item_width() + + def _render_mma_ticket_editor(self) -> None: + 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', '')}"); imgui.text(f"Depends on: {', '.join(ticket.get('depends_on', []))}") + personas = getattr(self.controller, 'personas', {}); curr_pers = ticket.get('persona_id', '') + imgui.text("Persona Override:"); imgui.same_line() + pers_opts = ["None"] + sorted(personas.keys()); curr_idx = pers_opts.index(curr_pers) + 1 if curr_pers in pers_opts else 0 + _, curr_idx = imgui.combo(f"##ticket_persona_{ticket.get('id')}", curr_idx, pers_opts) + ticket['persona_id'] = None if curr_idx == 0 or pers_opts[curr_idx] == "None" else pers_opts[curr_idx] + 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_mma_agent_streams(self) -> None: + imgui.text("Agent Streams") + if imgui.begin_tab_bar("mma_streams_tabs"): + for tier, label, sep_flag_attr in [("Tier 1", "Tier 1", "ui_separate_tier1"), ("Tier 2", "Tier 2 (Tech Lead)", "ui_separate_tier2"), ("Tier 3", None, "ui_separate_tier3"), ("Tier 4", "Tier 4 (QA)", "ui_separate_tier4")]: + with imscope.tab_item(tier) as (exp, _): + if exp: + sep_val = getattr(self, sep_flag_attr); ch, new_val = imgui.checkbox(f"Pop Out {tier}", sep_val) + if ch: + setattr(self, sep_flag_attr, new_val) + self.show_windows[f"{tier}: Strategy" if tier == "Tier 1" else (f"{tier}: Tech Lead" if tier == "Tier 2" else (f"{tier}: Workers" if tier == "Tier 3" else f"{tier}: QA"))] = new_val + if not new_val: self._render_tier_stream_panel(tier, label) + else: imgui.text_disabled(f"{tier} stream is detached.") + if getattr(self, "ui_project_execution_mode", "native") == "beads": + with imscope.tab_item("Beads") as (exp, _): + if exp: self._render_beads_tab() + imgui.end_tab_bar() + if imgui.begin_popup_modal("Approve PowerShell Command", None, imgui.WindowFlags_.always_auto_resize)[0]: if not dlg: imgui.close_current_popup() @@ -4693,429 +5183,35 @@ def hello(): imgui.end_table() def _render_mma_dashboard(self) -> None: - """ - [C: tests/test_gui_progress.py:test_render_mma_dashboard_progress, tests/test_mma_approval_indicators.py:TestMMAApprovalIndicators.test_approval_badge_shown_when_ask_dialog_pending, tests/test_mma_approval_indicators.py:TestMMAApprovalIndicators.test_approval_badge_shown_when_mma_approval_pending, tests/test_mma_approval_indicators.py:TestMMAApprovalIndicators.test_approval_badge_shown_when_spawn_pending, tests/test_mma_approval_indicators.py:TestMMAApprovalIndicators.test_no_approval_badge_when_idle] - """ + """Main MMA dashboard interface.""" if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard") - - # Focus Agent dropdown - imgui.text("Focus Agent:") - imgui.same_line() - focus_label = self.ui_focus_agent or "All" - if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview): - if imgui.selectable("All", self.ui_focus_agent is None)[0]: - self.ui_focus_agent = None - for tier in ["Tier 2", "Tier 3", "Tier 4"]: - if imgui.selectable(tier, self.ui_focus_agent == tier)[0]: - self.ui_focus_agent = tier - imgui.end_combo() - imgui.same_line() - if self.ui_focus_agent: - if imgui.button("x##clear_focus"): - self.ui_focus_agent = None + self._render_mma_focus_selector() imgui.separator() - - is_nerv = theme.is_nerv_active() if self.is_viewing_prior_session: - c = vec4(255, 200, 100) - if is_nerv: c = vec4(255, 152, 48) # NERV_ORANGE + c = vec4(255, 152, 48) if theme.is_nerv_active() else vec4(255, 200, 100) imgui.text_colored(c, "HISTORICAL VIEW - READ ONLY") if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard") return - # Task 5.3: Dense Summary Line - track_name = self.active_track.description if self.active_track else "None" - if getattr(self, "ui_project_execution_mode", "native") == "beads": - track_name = "Beads Graph" - track_stats = {"percentage": 0.0, "completed": 0, "total": 0, "in_progress": 0, "blocked": 0, "todo": 0} - if self.active_track: - track_stats = project_manager.calculate_track_progress(self.active_track.tickets) - elif self.active_tickets: - track_stats = project_manager.calculate_track_progress(self.active_tickets) - total_cost = 0.0 - for usage in self.mma_tier_usage.values(): - model = usage.get('model', 'unknown') - in_t = usage.get('input', 0) - out_t = usage.get('output', 0) - total_cost += cost_tracker.estimate_cost(model, in_t, out_t) - - imgui.text("Track:") - imgui.same_line() - imgui.text_colored(C_VAL, track_name) - imgui.same_line() - imgui.text(" | Status:") - imgui.same_line() - if self.mma_status == "paused": - c = imgui.ImVec4(1, 0.5, 0, 1) - if is_nerv: c = vec4(255, 152, 48) - imgui.text_colored(c, "PIPELINE PAUSED") - imgui.same_line() - status_col = imgui.ImVec4(1, 1, 1, 1) - if self.mma_status == "idle": status_col = imgui.ImVec4(0.7, 0.7, 0.7, 1) - elif self.mma_status == "running": status_col = imgui.ImVec4(1, 1, 0, 1) - elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1) - elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1) - elif self.mma_status == "paused": status_col = imgui.ImVec4(1, 0.5, 0, 1) - - if is_nerv: - if self.mma_status == "running": status_col = vec4(80, 255, 80) # DATA_GREEN - elif self.mma_status == "error": status_col = vec4(255, 72, 64) # ALERT_RED - - imgui.text_colored(status_col, self.mma_status.upper()) - imgui.same_line() - imgui.text(" | Cost:") - imgui.same_line() - imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"${total_cost:,.4f}") - - # Progress Bar - perc = track_stats["percentage"] / 100.0 - p_color = imgui.ImVec2(0.0, 1.0) # WAIT WRONG TYPE - - p_color = imgui.ImVec4(0.0, 1.0, 0.0, 1.0) - if track_stats["percentage"] < 33: - p_color = imgui.ImVec4(1.0, 0.0, 0.0, 1.0) - elif track_stats["percentage"] < 66: - p_color = imgui.ImVec4(1.0, 1.0, 0.0, 1.0) - - imgui.push_style_color(imgui.Col_.plot_histogram, p_color) - imgui.progress_bar(perc, imgui.ImVec2(-1, 0), f"{track_stats['percentage']:.1f}%") - imgui.pop_style_color() - - # Detailed breakdown - if imgui.begin_table("ticket_stats_breakdown", 4): - imgui.table_next_column() - imgui.text_colored(C_LBL, "Completed:") - imgui.same_line() - imgui.text_colored(C_VAL, str(track_stats["completed"])) - - imgui.table_next_column() - imgui.text_colored(C_LBL, "In Progress:") - imgui.same_line() - imgui.text_colored(C_VAL, str(track_stats["in_progress"])) - - imgui.table_next_column() - imgui.text_colored(C_LBL, "Blocked:") - imgui.same_line() - imgui.text_colored(C_VAL, str(track_stats["blocked"])) - - imgui.table_next_column() - imgui.text_colored(C_LBL, "Todo:") - imgui.same_line() - imgui.text_colored(C_VAL, str(track_stats["todo"])) - - imgui.end_table() - - if self.active_track: - remaining = track_stats["total"] - track_stats["completed"] - eta_mins = (self._avg_ticket_time * remaining) / 60.0 - imgui.text_colored(C_LBL, "ETA:") - imgui.same_line() - imgui.text_colored(C_VAL, f"~{int(eta_mins)}m ({remaining} tickets remaining)") - + self._render_mma_track_summary() imgui.separator() - imgui.text_colored(C_LBL, 'Epic Planning (Tier 1)') - _, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80)) - if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)): - self._cb_plan_epic() - + self._render_mma_epic_planner() imgui.separator() - # 0. Conductor Setup - if imgui.collapsing_header("Conductor Setup"): - if imgui.button("Run Setup Scan"): - self._cb_run_conductor_setup() - if self.ui_conductor_setup_summary: - imgui.input_text_multiline("##setup_summary", self.ui_conductor_setup_summary, imgui.ImVec2(-1, 120), imgui.InputTextFlags_.read_only) + if imgui.collapsing_header("Conductor Setup"): self._render_mma_conductor_setup() imgui.separator() - # 1. Track Browser - imgui.text("Track Browser") - if imgui.begin_table("mma_tracks_table", 4, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable): - imgui.table_setup_column("Title") - imgui.table_setup_column("Status") - imgui.table_setup_column("Progress") - imgui.table_setup_column("Actions") - imgui.table_headers_row() - for track in self.tracks: - imgui.table_next_row() - imgui.table_next_column() - imgui.text(track.get("title", "Untitled")) - imgui.table_next_column() - status = track.get("status", "unknown").lower() - if status == "new": - imgui.text_colored(imgui.ImVec4(0.7, 0.7, 0.7, 1.0), "NEW") - elif status == "active": - c = imgui.ImVec4(1.0, 1.0, 0.0, 1.0) - if is_nerv: c = vec4(80, 255, 80) - imgui.text_colored(c, "ACTIVE") - elif status == "done": - imgui.text_colored(imgui.ImVec4(0.0, 1.0, 0.0, 1.0), "DONE") - elif status == "blocked": - imgui.text_colored(imgui.ImVec4(1.0, 0.0, 0.0, 1.0), "BLOCKED") - else: - imgui.text(status) - imgui.table_next_column() - progress = track.get("progress", 0.0) - if progress < 0.33: - p_color = imgui.ImVec4(1.0, 0.0, 0.0, 1.0) - elif progress < 0.66: - p_color = imgui.ImVec4(1.0, 1.0, 0.0, 1.0) - else: - p_color = imgui.ImVec4(0.0, 1.0, 0.0, 1.0) - imgui.push_style_color(imgui.Col_.plot_histogram, p_color) - imgui.progress_bar(progress, imgui.ImVec2(-1, 0), f"{int(progress*100)}%") - imgui.pop_style_color() - imgui.table_next_column() - if imgui.button(f"Load##{track.get('id')}"): - self._cb_load_track(str(track.get("id") or "")) - imgui.end_table() - # 1b. New Track Form - imgui.text("Create New Track") - changed_n, self.ui_new_track_name = imgui.input_text("Name##new_track", self.ui_new_track_name) - changed_d, self.ui_new_track_desc = imgui.input_text_multiline("Description##new_track", self.ui_new_track_desc, imgui.ImVec2(-1, 60)) - imgui.text("Type:") - imgui.same_line() - if imgui.begin_combo("##track_type", self.ui_new_track_type): - for ttype in ["feature", "chore", "fix"]: - if imgui.selectable(ttype, self.ui_new_track_type == ttype)[0]: - self.ui_new_track_type = ttype - imgui.end_combo() - if imgui.button("Create Track"): - self._cb_create_track(self.ui_new_track_name, self.ui_new_track_desc, self.ui_new_track_type) - self.ui_new_track_name = "" - self.ui_new_track_desc = "" + self._render_mma_track_browser() imgui.separator() - # 2. Global Controls - changed, self.mma_step_mode = imgui.checkbox("Step Mode (HITL)", self.mma_step_mode) - if changed: - # We could push an event here if the engine needs to know immediately - pass - imgui.same_line() - imgui.text(f"Status: {self.mma_status.upper()}") - if self.controller and hasattr(self.controller, 'engine') and self.controller.engine and hasattr(self.controller.engine, '_pause_event'): - imgui.same_line() - is_paused = self.controller.engine._pause_event.is_set() - label = "Resume" if is_paused else "Pause" - if imgui.button(label): - if is_paused: - self.controller.engine.resume() - else: - self.controller.engine.pause() - if self.active_tier: - imgui.same_line() - imgui.text_colored(C_VAL, f"| Active: {self.active_tier}") - # Approval pending indicator - any_pending = ( - len(self._pending_mma_spawns) > 0 or - len(self._pending_mma_approvals) > 0 or - self._pending_ask_dialog - ) - if any_pending: - alpha = abs(math.sin(time.time() * 5)) - imgui.same_line() - c = imgui.ImVec4(1.0, 0.3, 0.3, alpha) - if is_nerv: c = vec4(255, 72, 64, alpha) # ALERT_RED - imgui.text_colored(c, " APPROVAL PENDING") - imgui.same_line() - if imgui.button("Go to Approval"): - pass # scroll/focus handled by existing dialog rendering + self._render_mma_global_controls() imgui.separator() - # 3. Token Usage Table - imgui.text("Tier Usage (Tokens & Cost)") - if imgui.begin_table("mma_usage", 5, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg): - imgui.table_setup_column("Tier") - imgui.table_setup_column("Model") - imgui.table_setup_column("Input") - imgui.table_setup_column("Output") - imgui.table_setup_column("Est. Cost") - imgui.table_headers_row() - usage = self.mma_tier_usage - total_cost = 0.0 - for tier, stats in usage.items(): - imgui.table_next_row() - imgui.table_next_column() - imgui.text(tier) - imgui.table_next_column() - model = stats.get('model', 'unknown') - imgui.text(model) - imgui.table_next_column() - in_t = stats.get('input', 0) - imgui.text(f"{in_t:,}") - imgui.table_next_column() - out_t = stats.get('output', 0) - imgui.text(f"{out_t:,}") - imgui.table_next_column() - cost = cost_tracker.estimate_cost(model, in_t, out_t) - total_cost += cost - imgui.text(f"${cost:,.4f}") - # Total Row - imgui.table_next_row() - imgui.table_set_bg_color(imgui.TableBgTarget_.row_bg0, imgui.get_color_u32(imgui.Col_.plot_lines_hovered)) - imgui.table_next_column() - imgui.text("TOTAL") - imgui.table_next_column() - imgui.text("") - imgui.table_next_column() - imgui.text("") - imgui.table_next_column() - imgui.text("") - imgui.table_next_column() - imgui.text(f"${total_cost:,.4f}") - imgui.end_table() - imgui.separator() - # 3b. Tier Model Config - if imgui.collapsing_header("Tier Model Config"): - for tier in self.mma_tier_usage.keys(): - imgui.text(f"{tier}:") - imgui.same_line() - current_model = self.mma_tier_usage[tier].get("model", "unknown") - current_provider = self.mma_tier_usage[tier].get("provider", "gemini") - - imgui.push_id(f"tier_cfg_{tier}") - - # Provider selection - imgui.push_item_width(80) - if imgui.begin_combo("##prov", current_provider): - for p in models.PROVIDERS: - if imgui.selectable(p, p == current_provider)[0]: - self.mma_tier_usage[tier]["provider"] = p - # Reset model to default for provider - models_list = self.controller.all_available_models.get(p, []) - if models_list: - self.mma_tier_usage[tier]["model"] = models_list[0] - imgui.end_combo() - imgui.pop_item_width() - - imgui.same_line() - - # Model selection - imgui.push_item_width(150) - models_list = self.controller.all_available_models.get(current_provider, []) - if imgui.begin_combo("##model", current_model): - for model in models_list: - if imgui.selectable(model, current_model == model)[0]: - self.mma_tier_usage[tier]["model"] = model - imgui.end_combo() - imgui.pop_item_width() - - imgui.same_line() - - # Tool Preset selection - imgui.push_item_width(-1) - current_preset = self.mma_tier_usage[tier].get("tool_preset") or "None" - preset_names = ["None"] + sorted(self.controller.tool_presets.keys()) - if imgui.begin_combo("##preset", current_preset): - for preset_name in preset_names: - if imgui.selectable(preset_name, current_preset == preset_name)[0]: - self.mma_tier_usage[tier]["tool_preset"] = None if preset_name == "None" else preset_name - imgui.end_combo() - imgui.pop_item_width() - - imgui.same_line() - - # Persona selection - imgui.push_item_width(150) - current_persona = self.mma_tier_usage[tier].get("persona") or "None" - personas = getattr(self.controller, 'personas', {}) - persona_options = ["None"] + sorted(personas.keys()) - if imgui.begin_combo("##persona", current_persona): - for persona_name in persona_options: - if imgui.selectable(persona_name, current_persona == persona_name)[0]: - self.mma_tier_usage[tier]["persona"] = None if persona_name == "None" else persona_name - imgui.end_combo() - imgui.pop_item_width() - - imgui.pop_id() + self._render_mma_usage_section() 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)}") - personas = getattr(self.controller, 'personas', {}) - current_persona = ticket.get('persona_id', '') - imgui.text("Persona Override:") - imgui.same_line() - persona_options = ["None"] + sorted(personas.keys()) - current_idx = persona_options.index(current_persona) + 1 if current_persona in persona_options else 0 - _, current_idx = imgui.combo(f"##ticket_persona_{ticket.get('id')}", current_idx, persona_options) - if current_idx > 0: - ticket['persona_id'] = None if persona_options[current_idx] == "None" else persona_options[current_idx] - else: - ticket['persona_id'] = "" - 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() - + self._render_window_if_open("Task DAG", self._render_task_dag_panel, not self.ui_separate_task_dag) + if self.ui_selected_ticket_id: self._render_mma_ticket_editor() imgui.separator() - imgui.text("Agent Streams") - if imgui.begin_tab_bar("mma_streams_tabs"): - # Tier 1 - if imgui.begin_tab_item("Tier 1")[0]: - ch, self.ui_separate_tier1 = imgui.checkbox("Pop Out Tier 1", self.ui_separate_tier1) - if ch: self.show_windows["Tier 1: Strategy"] = self.ui_separate_tier1 - if not self.ui_separate_tier1: - self._render_tier_stream_panel("Tier 1", "Tier 1") - else: - imgui.text_disabled("Tier 1 stream is detached.") - imgui.end_tab_item() - # Tier 2 - if imgui.begin_tab_item("Tier 2")[0]: - ch, self.ui_separate_tier2 = imgui.checkbox("Pop Out Tier 2", self.ui_separate_tier2) - if ch: self.show_windows["Tier 2: Tech Lead"] = self.ui_separate_tier2 - if not self.ui_separate_tier2: - self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)") - else: - imgui.text_disabled("Tier 2 stream is detached.") - imgui.end_tab_item() - # Tier 3 - if imgui.begin_tab_item("Tier 3")[0]: - ch, self.ui_separate_tier3 = imgui.checkbox("Pop Out Tier 3", self.ui_separate_tier3) - if ch: self.show_windows["Tier 3: Workers"] = self.ui_separate_tier3 - if not self.ui_separate_tier3: - self._render_tier_stream_panel("Tier 3", None) - else: - imgui.text_disabled("Tier 3 stream is detached.") - imgui.end_tab_item() - # Tier 4 - if imgui.begin_tab_item("Tier 4")[0]: - ch, self.ui_separate_tier4 = imgui.checkbox("Pop Out Tier 4", self.ui_separate_tier4) - if ch: self.show_windows["Tier 4: QA"] = self.ui_separate_tier4 - if not self.ui_separate_tier4: - self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)") - else: - imgui.text_disabled("Tier 4 stream is detached.") - imgui.end_tab_item() - if getattr(self, "ui_project_execution_mode", "native") == "beads": - if imgui.begin_tab_item("Beads")[0]: - self._render_beads_tab() - imgui.end_tab_item() - imgui.end_tab_bar() + self._render_mma_agent_streams() + if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard") + def _render_task_dag_panel(self) -> None: # 4. Task DAG Visualizer