More compaction/cleanup to gui

This commit is contained in:
2026-05-13 08:03:13 -04:00
parent 48c32becaf
commit 16428efc6f
+122 -242
View File
@@ -1,6 +1,7 @@
# gui_2.py
# defer: parse
from __future__ import annotations
import copy
import datetime
import difflib
import json
@@ -23,6 +24,7 @@ from pathlib import Path
from pydantic import BaseModel
from tkinter import filedialog, Tk
from typing import Optional, Any
from src.diff_viewer import apply_patch_to_file
from src import ai_client
from src import aggregate
from src import api_hooks
@@ -40,6 +42,8 @@ from src import log_pruner
from src import models
from src import mcp_client
from src import markdown_helper
from src import shaders
from src import synthesis_formatter
from src import theme_2 as theme
from src import theme_nerv_fx as theme_fx
from src import thinking_parser
@@ -1249,10 +1253,8 @@ class App:
imgui.text(f"Age: {age_str}")
imgui.text(f"TTL: {remaining_str} ({ttl_pct:.0f}%)")
color = imgui.ImVec4(0.2, 0.8, 0.2, 1.0)
if ttl_pct < 20:
color = imgui.ImVec4(1.0, 0.2, 0.2, 1.0)
elif ttl_pct < 50:
color = imgui.ImVec4(1.0, 0.8, 0.0, 1.0)
if ttl_pct < 20: color = imgui.ImVec4(1.0, 0.2, 0.2, 1.0)
elif ttl_pct < 50: color = imgui.ImVec4(1.0, 0.8, 0.0, 1.0)
imgui.push_style_color(imgui.Col_.plot_histogram, color)
imgui.progress_bar(ttl_pct / 100.0, imgui.ImVec2(-1, 0), f"{ttl_pct:.0f}%")
imgui.pop_style_color()
@@ -1401,10 +1403,8 @@ class App:
imgui.table_set_column_index(2)
imgui.text(f"{avg_time:.0f}")
imgui.table_set_column_index(3)
if fail_pct > 0:
imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{fail_pct:.0f}%")
else:
imgui.text("0%")
if fail_pct > 0: imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{fail_pct:.0f}%")
else: imgui.text("0%")
imgui.end_table()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tool_analytics_panel")
@@ -1413,15 +1413,11 @@ class App:
imgui.text_colored(C_LBL, 'Prompt Utilization')
usage = self.session_usage
total = usage["input_tokens"] + usage["output_tokens"]
if total == 0 and usage.get("total_tokens", 0) > 0:
total = usage["total_tokens"]
if total == 0 and usage.get("total_tokens", 0) > 0: total = usage["total_tokens"]
self._render_selectable_label("session_telemetry_tokens", f"Tokens: {total:,} (In: {usage['input_tokens']:,} Out: {usage['output_tokens']:,})", width=-1, color=C_RES)
if usage.get("last_latency", 0.0) > 0:
imgui.text_colored(C_LBL, f" Last Latency: {usage['last_latency']:.2f}s")
if usage["cache_read_input_tokens"]:
imgui.text_colored(C_LBL, f" Cache Read: {usage['cache_read_input_tokens']:,} Creation: {usage['cache_creation_input_tokens']:,}")
if self._gemini_cache_text:
imgui.text_colored(C_SUB, self._gemini_cache_text)
if usage.get("last_latency", 0.0) > 0: imgui.text_colored(C_LBL, f" Last Latency: {usage['last_latency']:.2f}s")
if usage["cache_read_input_tokens"]: imgui.text_colored(C_LBL, f" Cache Read: {usage['cache_read_input_tokens']:,} Creation: {usage['cache_creation_input_tokens']:,}")
if self._gemini_cache_text: imgui.text_colored(C_SUB, self._gemini_cache_text)
imgui.separator()
if self._token_stats_dirty:
@@ -1437,12 +1433,9 @@ class App:
current = stats.get("estimated_prompt_tokens", stats.get("total_tokens", 0))
limit = stats.get("max_prompt_tokens", 0)
headroom = stats.get("headroom_tokens", max(0, limit - current))
if pct < 50.0:
color = imgui.ImVec4(0.2, 0.8, 0.2, 1.0)
elif pct < 80.0:
color = imgui.ImVec4(1.0, 0.8, 0.0, 1.0)
else:
color = imgui.ImVec4(1.0, 0.2, 0.2, 1.0)
if pct < 50.0: color = imgui.ImVec4(0.2, 0.8, 0.2, 1.0)
elif pct < 80.0: color = imgui.ImVec4(1.0, 0.8, 0.0, 1.0)
else: color = imgui.ImVec4(1.0, 0.2, 0.2, 1.0)
imgui.push_style_color(imgui.Col_.plot_histogram, color)
imgui.progress_bar(pct / 100.0, imgui.ImVec2(-1, 0), f"{pct:.1f}%")
imgui.pop_style_color()
@@ -1490,14 +1483,12 @@ class App:
if stats.get("would_trim"):
imgui.text_colored(imgui.ImVec4(1.0, 0.3, 0.0, 1.0), "WARNING: Next call will trim history")
trimmable = stats.get("trimmable_turns", 0)
if trimmable:
imgui.text_disabled(f"Trimmable turns: {trimmable}")
if trimmable: imgui.text_disabled(f"Trimmable turns: {trimmable}")
msgs = stats.get("messages")
if msgs:
shown = 0
for msg in msgs:
if shown >= 3:
break
if shown >= 3: break
if msg.get("trimmable"):
role = msg.get("role", "?")
toks = msg.get("tokens", 0)
@@ -1634,8 +1625,7 @@ class App:
modes = ["native", "beads"]
current_idx = modes.index(self.ui_project_execution_mode) if self.ui_project_execution_mode in modes else 0
ch, new_idx = imgui.combo("##exec_mode", current_idx, modes)
if ch:
self.ui_project_execution_mode = modes[new_idx]
if ch: self.ui_project_execution_mode = modes[new_idx]
imgui.separator()
imgui.text("Git Directory")
ch, self.ui_project_git_dir = imgui.input_text("##git_dir", self.ui_project_git_dir)
@@ -1670,14 +1660,12 @@ class App:
is_active = (pp == self.active_project_path)
if imgui.button(f"x##p{i}"):
removed = self.project_paths.pop(i)
if removed == self.active_project_path and self.project_paths:
self._switch_project(self.project_paths[0])
if removed == self.active_project_path and self.project_paths: self._switch_project(self.project_paths[0])
break
imgui.same_line()
marker = " *" if is_active else ""
if is_active: imgui.push_style_color(imgui.Col_.text, C_IN)
if imgui.button(f"{Path(pp).stem}{marker}##ps{i}"):
self._switch_project(pp)
if imgui.button(f"{Path(pp).stem}{marker}##ps{i}"): self._switch_project(pp)
if is_active: imgui.pop_style_color()
imgui.same_line()
imgui.text_colored(C_LBL, pp)
@@ -1700,8 +1688,7 @@ class App:
name = Path(p).stem
proj = project_manager.default_project(name)
project_manager.save_project(proj, p)
if p not in self.project_paths:
self.project_paths.append(p)
if p not in self.project_paths: self.project_paths.append(p)
self._switch_project(p)
imgui.same_line()
if imgui.button("Save All"):
@@ -1721,7 +1708,7 @@ class App:
imgui.text_colored(C_IN, "System Path Configuration")
imgui.separator()
def render_path_field(label: str, attr: str, key: str, tooltip: str):
def _render_path_field(label: str, attr: str, key: str, tooltip: str):
info = path_info.get(key, {'source': 'unknown'})
imgui.text(label)
if imgui.is_item_hovered(): imgui.set_tooltip(tooltip)
@@ -1741,8 +1728,7 @@ class App:
def _render_external_tools_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_external_tools_panel")
if imgui.button("Refresh External MCPs"):
self.event_queue.put("refresh_external_mcps", None)
if imgui.button("Refresh External MCPs"): self.event_queue.put("refresh_external_mcps", None)
imgui.separator()
@@ -1755,12 +1741,9 @@ class App:
imgui.same_line()
# Green for running, Yellow for starting, Red for error, Gray for idle
col = (0.5, 0.5, 0.5, 1.0)
if status == 'running':
col = (0.0, 1.0, 0.0, 1.0)
elif status == 'starting':
col = (1.0, 1.0, 0.0, 1.0)
elif status == 'error':
col = (1.0, 0.0, 0.0, 1.0)
if status == 'running': col = (0.0, 1.0, 0.0, 1.0)
elif status == 'starting': col = (1.0, 1.0, 0.0, 1.0)
elif status == 'error': col = (1.0, 0.0, 0.0, 1.0)
imgui.color_button(f"##status_{sname}", col)
imgui.same_line()
imgui.text(sname)
@@ -1795,13 +1778,11 @@ class App:
models.save_config(self.config)
self.ai_status = f"Default editor set to: {editor_name}"
render_path_field("Logs Directory", "ui_logs_dir", "logs_dir", "Directory where session JSON-L logs and artifacts are stored.")
render_path_field("Scripts Directory", "ui_scripts_dir", "scripts_dir", "Directory for AI-generated PowerShell scripts.")
_render_path_field("Logs Directory", "ui_logs_dir", "logs_dir", "Directory where session JSON-L logs and artifacts are stored.")
_render_path_field("Scripts Directory", "ui_scripts_dir", "scripts_dir", "Directory for AI-generated PowerShell scripts.")
imgui.separator()
if imgui.button("Apply", imgui.ImVec2(120, 0)):
self._save_paths()
if imgui.button("Apply", imgui.ImVec2(120, 0)): self._save_paths()
imgui.same_line()
if imgui.button("Reset", imgui.ImVec2(120, 0)):
self.init_state()
@@ -1818,8 +1799,7 @@ class App:
"scripts_dir": self.ui_scripts_dir
}
cfg_path = paths.get_config_path()
if cfg_path.exists():
shutil.copy(cfg_path, str(cfg_path) + ".bak")
if cfg_path.exists(): shutil.copy(cfg_path, str(cfg_path) + ".bak")
models.save_config(self.config)
paths.reset_resolved()
self.init_state()
@@ -1871,8 +1851,7 @@ class App:
imgui.separator()
imgui.text(f"Status: {self.controller.rag_status}")
if imgui.button("Rebuild Index"):
self.controller.event_queue.put('click', 'btn_rebuild_rag_index')
if imgui.button("Rebuild Index"): self.controller.event_queue.put('click', 'btn_rebuild_rag_index')
def _render_system_prompts_panel(self) -> None:
imgui.text("Global System Prompt (all projects)")
@@ -1882,24 +1861,19 @@ class App:
if imgui.begin_combo("##global_preset", current_global):
for name in preset_names:
is_sel = (name == current_global)
if imgui.selectable(name, is_sel)[0]:
self.controller._apply_preset(name, "global")
if is_sel:
imgui.set_item_default_focus()
if imgui.selectable(name, is_sel)[0]: self.controller._apply_preset(name, "global")
if is_sel: imgui.set_item_default_focus()
imgui.end_combo()
imgui.same_line(0, 8)
if imgui.button("Manage Presets##global"):
self.show_preset_manager_window = True
if imgui.button("Manage Presets##global"): self.show_preset_manager_window = True
imgui.set_item_tooltip("Open preset management modal")
ch, self.ui_global_system_prompt = imgui.input_text_multiline("##gsp", self.ui_global_system_prompt, imgui.ImVec2(-1, 100))
imgui.separator()
_, self.ui_use_default_base_prompt = imgui.checkbox("Use Default Base System Prompt", self.ui_use_default_base_prompt)
imgui.same_line()
if imgui.button("Reset to Default##btn_reset_base_prompt"):
self.controller._cb_reset_base_prompt()
if imgui.button("Reset to Default##btn_reset_base_prompt"): self.controller._cb_reset_base_prompt()
imgui.same_line()
if imgui.button("Show Diff##btn_show_base_prompt_diff"):
self.controller._cb_show_base_prompt_diff()
if imgui.button("Show Diff##btn_show_base_prompt_diff"): self.controller._cb_show_base_prompt_diff()
imgui.set_item_tooltip("Compare current base prompt with the default.")
imgui.same_line()
@@ -1923,14 +1897,11 @@ class App:
if imgui.begin_combo("##project_preset", current_project):
for name in preset_names:
is_sel = (name == current_project)
if imgui.selectable(name, is_sel)[0]:
self.controller._apply_preset(name, "project")
if is_sel:
imgui.set_item_default_focus()
if imgui.selectable(name, is_sel)[0]: self.controller._apply_preset(name, "project")
if is_sel: imgui.set_item_default_focus()
imgui.end_combo()
imgui.same_line(0, 8)
if imgui.button("Manage Presets##project"):
self.show_preset_manager_window = True
if imgui.button("Manage Presets##project"): self.show_preset_manager_window = True
imgui.set_item_tooltip("Open preset management modal")
ch, self.ui_project_system_prompt = imgui.input_text_multiline("##psp", self.ui_project_system_prompt, imgui.ImVec2(-1, 100))
@@ -1952,10 +1923,8 @@ class App:
self.ui_active_tool_preset = preset_names[new_idx]
imgui.same_line()
if imgui.button("Manage Presets##tools"):
self.show_tool_preset_manager_window = True
if imgui.is_item_hovered():
imgui.set_tooltip("Configure tool availability and default modes.")
if imgui.button("Manage Presets##tools"): self.show_tool_preset_manager_window = True
if imgui.is_item_hovered(): imgui.set_tooltip("Configure tool availability and default modes.")
imgui.dummy(imgui.ImVec2(0, 4))
imgui.text("Bias Profile")
@@ -1964,8 +1933,7 @@ class App:
self.ui_active_bias_profile = ""
ai_client.set_bias_profile(None)
for bname in sorted(self.controller.bias_profiles.keys()):
if not bname:
continue
if not bname: continue
if imgui.selectable(bname, bname == getattr(self, 'ui_active_bias_profile', ""))[0]:
self.ui_active_bias_profile = bname
ai_client.set_bias_profile(bname)
@@ -1989,14 +1957,10 @@ class App:
if self.ui_tool_filter_category != "All" and self.ui_tool_filter_category != cat_name: continue
if imgui.tree_node(cat_name):
for tool in tools:
if tool.weight >= 5:
imgui.text_colored(vec4(255, 100, 100), "[HIGH]"); imgui.same_line()
elif tool.weight == 4:
imgui.text_colored(vec4(255, 255, 100), "[PREF]"); imgui.same_line()
elif tool.weight == 2:
imgui.text_colored(vec4(255, 150, 50), "[REJECT]"); imgui.same_line()
elif tool.weight <= 1:
imgui.text_colored(vec4(180, 180, 180), "[LOW]"); imgui.same_line()
if tool.weight >= 5: imgui.text_colored(vec4(255, 100, 100), "[HIGH]"); imgui.same_line()
elif tool.weight == 4: imgui.text_colored(vec4(255, 255, 100), "[PREF]"); imgui.same_line()
elif tool.weight == 2: imgui.text_colored(vec4(255, 150, 50), "[REJECT]"); imgui.same_line()
elif tool.weight <= 1: imgui.text_colored(vec4(180, 180, 180), "[LOW]"); imgui.same_line()
imgui.text(tool.name); imgui.same_line(180)
@@ -2090,8 +2054,7 @@ class App:
self._selected_preset_idx = -1
if not is_embedded:
imgui.same_line()
if imgui.button("Close##p", imgui.ImVec2(100, 0)):
self.show_preset_manager_window = False
if imgui.button("Close##p", imgui.ImVec2(100, 0)): self.show_preset_manager_window = False
imgui.end_table()
def _render_preset_manager_window(self, is_embedded: bool = False) -> None:
@@ -2100,8 +2063,7 @@ class App:
imgui.set_next_window_size(imgui.ImVec2(1000, 800), imgui.Cond_.first_use_ever)
with imscope.window("Prompt Presets Manager", self.show_preset_manager_window) as (opened, visible):
self.show_preset_manager_window = visible
if opened:
self._render_preset_manager_content(is_embedded=is_embedded)
if opened: self._render_preset_manager_content(is_embedded=is_embedded)
else:
self._render_preset_manager_content(is_embedded=is_embedded)
@@ -2284,8 +2246,7 @@ class App:
imgui.set_next_window_size(imgui.ImVec2(1000, 800), imgui.Cond_.first_use_ever)
with imscope.window("Tool Preset Manager", self.show_tool_preset_manager_window) as (opened, visible):
self.show_tool_preset_manager_window = visible
if opened:
self._render_tool_preset_manager_content(is_embedded=is_embedded)
if opened: self._render_tool_preset_manager_content(is_embedded=is_embedded)
else:
self._render_preset_manager_content(is_embedded=is_embedded)
@@ -2523,15 +2484,12 @@ class App:
def _render_persona_selector_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_persona_selector_panel")
imgui.text("Persona")
if not hasattr(self, 'ui_active_persona'):
self.ui_active_persona = ""
if not hasattr(self, 'ui_active_persona'): self.ui_active_persona = ""
personas = getattr(self.controller, 'personas', {})
if imgui.begin_combo("##persona", self.ui_active_persona or "None"):
if imgui.selectable("None", not self.ui_active_persona)[0]:
self.ui_active_persona = ""
if imgui.selectable("None", not self.ui_active_persona)[0]: self.ui_active_persona = ""
for pname in sorted(personas.keys()):
if not pname:
continue
if not pname: continue
if imgui.selectable(pname, pname == self.ui_active_persona)[0]:
self.ui_active_persona = pname
if pname in personas:
@@ -2542,7 +2500,6 @@ class App:
self._editing_persona_bias_profile_id = persona.bias_profile or ""
self._editing_persona_context_preset_id = getattr(persona, 'context_preset', '') or ""
self._editing_persona_aggregation_strategy = getattr(persona, 'aggregation_strategy', '') or ""
import copy
self._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
self._editing_persona_is_new = False
@@ -2562,8 +2519,7 @@ class App:
if first_model.get("history_trunc_limit"):
self.history_trunc_limit = first_model.get("history_trunc_limit")
if persona.system_prompt:
self.ui_project_system_prompt = persona.system_prompt
if persona.system_prompt: self.ui_project_system_prompt = persona.system_prompt
if persona.tool_preset:
self.ui_active_tool_preset = persona.tool_preset
ai_client.set_tool_preset(persona.tool_preset)
@@ -2585,7 +2541,6 @@ class App:
self._editing_persona_bias_profile_id = persona.bias_profile or ""
self._editing_persona_context_preset_id = getattr(persona, 'context_preset', '') or ""
self._editing_persona_aggregation_strategy = getattr(persona, 'aggregation_strategy', '') or ""
import copy
self._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
self._editing_persona_scope = self.controller.persona_manager.get_persona_scope(persona.name)
self._editing_persona_is_new = False
@@ -2863,12 +2818,9 @@ class App:
if f_path != self._cached_ast_file_path:
outline = ""
try:
if f_path.lower().endswith('.py'):
outline = mcp_client.py_get_code_outline(f_path)
elif f_path.lower().endswith(('.c', '.h')):
outline = mcp_client.ts_c_get_code_outline(f_path)
else:
outline = mcp_client.ts_cpp_get_code_outline(f_path)
if f_path.lower().endswith('.py'): outline = mcp_client.py_get_code_outline(f_path)
elif f_path.lower().endswith(('.c', '.h')): outline = mcp_client.ts_c_get_code_outline(f_path)
else: outline = mcp_client.ts_cpp_get_code_outline(f_path)
except Exception as e:
outline = f"Error fetching outline: {e}"
@@ -2881,8 +2833,7 @@ class App:
if m:
indent_str, kind, name, start_ln, end_ln = m.groups()
indent = len(indent_str)
while stack and stack[-1][0] >= indent:
stack.pop()
while stack and stack[-1][0] >= indent: stack.pop()
stack.append((indent, name))
full_path = '::'.join([s[1] for s in stack])
self._cached_ast_nodes.append({
@@ -2909,8 +2860,7 @@ class App:
#region: LEFT COLUMN (Tree) ---
if imgui.begin_child("ast_tree_scroll", imgui.ImVec2(0, 600), True):
if not self._cached_ast_nodes:
imgui.text("No AST nodes found or error fetching outline.")
if not self._cached_ast_nodes: imgui.text("No AST nodes found or error fetching outline.")
else:
for node in self._cached_ast_nodes:
indent = node['indent']
@@ -2926,14 +2876,11 @@ class App:
current_mode = f_item.ast_mask.get(full_path, 'hide')
imgui.push_id(full_path)
if imgui.radio_button("Def", current_mode == 'def'):
f_item.ast_mask[full_path] = 'def'
if imgui.radio_button("Def", current_mode == 'def'): f_item.ast_mask[full_path] = 'def'
imgui.same_line()
if imgui.radio_button("Sig", current_mode == 'sig'):
f_item.ast_mask[full_path] = 'sig'
if imgui.radio_button("Sig", current_mode == 'sig'): f_item.ast_mask[full_path] = 'sig'
imgui.same_line()
if imgui.radio_button("Hide", current_mode == 'hide'):
f_item.ast_mask[full_path] = 'hide'
if imgui.radio_button("Hide", current_mode == 'hide'): f_item.ast_mask[full_path] = 'hide'
imgui.pop_id()
imgui.end_child()
#endregion: LEFT COLUMN (Tree)
@@ -2953,12 +2900,10 @@ class App:
deepest_node = None
for node in self._cached_ast_nodes:
if node['start_line'] <= line_num <= node['end_line']:
if deepest_node is None or node['indent'] > deepest_node['indent']:
deepest_node = node
if deepest_node is None or node['indent'] > deepest_node['indent']: deepest_node = node
mode = 'hide'
if deepest_node:
mode = f_item.ast_mask.get(deepest_node['full_path'], 'hide')
if deepest_node: mode = f_item.ast_mask.get(deepest_node['full_path'], 'hide')
pos = imgui.get_cursor_screen_pos()
line_height = imgui.get_text_line_height()
@@ -2986,8 +2931,7 @@ class App:
#endregion: AST Inspector
if not opened:
self.ui_inspecting_ast_file = None
if not opened: self.ui_inspecting_ast_file = None
def _render_add_context_files_modal(self) -> None:
if imgui.begin_popup_modal("Select Context Files", None, imgui.WindowFlags_.always_auto_resize)[0]:
@@ -3034,11 +2978,9 @@ class App:
_, self._new_workspace_profile_name = imgui.input_text("##profile_name", self._new_workspace_profile_name)
imgui.text("Scope:")
if imgui.radio_button("Project", self._new_workspace_profile_scope == "project"):
self._new_workspace_profile_scope = "project"
if imgui.radio_button("Project", self._new_workspace_profile_scope == "project"): self._new_workspace_profile_scope = "project"
imgui.same_line()
if imgui.radio_button("Global", self._new_workspace_profile_scope == "global"):
self._new_workspace_profile_scope = "global"
if imgui.radio_button("Global", self._new_workspace_profile_scope == "global"): self._new_workspace_profile_scope = "global"
imgui.separator()
if imgui.button("Save", (120, 0)):
@@ -3108,8 +3050,7 @@ class App:
f_item.custom_slices.append(slice_data)
def _render_context_screenshots(self) -> None:
for i, s in enumerate(self.screenshots):
imgui.text(s)
for i, s in enumerate(self.screenshots): imgui.text(s)
def _render_context_batch_actions(self, total_lines: int, total_ast: int) -> None:
imgui.text("Batch:")
@@ -3117,22 +3058,18 @@ class App:
if imgui.button(f"{mode.capitalize()}##batch"):
for f in self.context_files:
f_path = f.path if hasattr(f, "path") else str(f)
if f_path in self.ui_selected_context_files:
f.view_mode = mode
if f_path in self.ui_selected_context_files: f.view_mode = mode
imgui.same_line()
if imgui.button("Sel All##selall"):
for f in self.context_files:
f_path = f.path if hasattr(f, "path") else str(f)
self.ui_selected_context_files.add(f_path)
imgui.same_line()
if imgui.button("Unsel All##unselall"):
self.ui_selected_context_files.clear()
if imgui.button("Unsel All##unselall"): self.ui_selected_context_files.clear()
imgui.same_line()
if imgui.button("Add Files"):
imgui.open_popup("Select Context Files")
if imgui.button("Add Files"): imgui.open_popup("Select Context Files")
imgui.same_line()
if imgui.button("Add All##addall"):
import copy
context_paths = {f.path if hasattr(f, "path") else str(f) for f in self.context_files}
for f in self.files:
f_path = f.path if hasattr(f, "path") else str(f)
@@ -3145,8 +3082,7 @@ class App:
new_files = []
for f in self.context_files:
f_path = f.path if hasattr(f, "path") else str(f)
if f_path not in self.ui_selected_context_files:
new_files.append(f)
if f_path not in self.ui_selected_context_files: new_files.append(f)
self.context_files = new_files
self.ui_selected_context_files.clear()
imgui.same_line()
@@ -3185,15 +3121,11 @@ class App:
for idx in range(start, end + 1):
item = self.context_files[idx]
item_path = item.path if hasattr(item, "path") else str(item)
if is_sel:
self.ui_selected_context_files.add(item_path)
if is_sel: self.ui_selected_context_files.add(item_path)
else: self.ui_selected_context_files.discard(item_path)
else:
self.ui_selected_context_files.discard(item_path)
else:
if is_sel:
self.ui_selected_context_files.add(f_path)
else:
self.ui_selected_context_files.discard(f_path)
if is_sel: self.ui_selected_context_files.add(f_path)
else: self.ui_selected_context_files.discard(f_path)
self._last_selected_context_index = i
imgui.same_line()
@@ -3222,8 +3154,7 @@ class App:
self.show_text_viewer = True
imgui.table_set_column_index(1)
if not hasattr(f_item, "view_mode"):
f_item.view_mode = "summary"
if not hasattr(f_item, "view_mode"): f_item.view_mode = "summary"
view_modes = ["full", "summary", "skeleton", "outline", "masked", "none"]
try:
current_idx = view_modes.index(f_item.view_mode)
@@ -3232,12 +3163,10 @@ class App:
f_item.view_mode = "summary"
imgui.set_next_item_width(120)
changed_vm, new_idx = imgui.combo(f"##vm{i}", current_idx, view_modes)
if changed_vm:
f_item.view_mode = view_modes[new_idx]
if changed_vm: f_item.view_mode = view_modes[new_idx]
imgui.same_line()
if imgui.button(f"[Save]##vpsave{i}"):
imgui.open_popup(f"save_vp_popup{i}")
if imgui.button(f"[Save]##vpsave{i}"): imgui.open_popup(f"save_vp_popup{i}")
if imgui.begin_popup(f"save_vp_popup{i}"):
imgui.text("Preset Name:")
@@ -3250,13 +3179,11 @@ class App:
imgui.end_popup()
imgui.same_line()
if imgui.button(f"[Load]##vpload{i}"):
imgui.open_popup(f"load_vp_popup{i}")
if imgui.button(f"[Load]##vpload{i}"): imgui.open_popup(f"load_vp_popup{i}")
if imgui.begin_popup(f"load_vp_popup{i}"):
vp_names = sorted([vp.name for vp in self.controller.view_presets])
if not vp_names:
imgui.text("No presets saved.")
if not vp_names: imgui.text("No presets saved.")
for vp_name in vp_names:
if imgui.selectable(vp_name):
self.controller._cb_apply_view_preset(vp_name, f_item)
@@ -3271,8 +3198,7 @@ class App:
presets = self.controller.project.get('context_presets', {})
preset_names = [""] + sorted(presets.keys())
active = getattr(self, "ui_active_context_preset", "")
if active not in preset_names:
active = ""
if active not in preset_names: active = ""
try:
idx = preset_names.index(active)
except ValueError:
@@ -3280,12 +3206,10 @@ class App:
ch, new_idx = imgui.combo("##ctx_preset", idx, preset_names)
if ch:
self.ui_active_context_preset = preset_names[new_idx]
if preset_names[new_idx]:
self.load_context_preset(preset_names[new_idx])
if preset_names[new_idx]: self.load_context_preset(preset_names[new_idx])
imgui.same_line()
changed, new_name = imgui.input_text("##new_preset", getattr(self, "ui_new_context_preset_name", ""))
if changed:
self.ui_new_context_preset_name = new_name
if changed: self.ui_new_context_preset_name = new_name
imgui.same_line()
if imgui.button("Save##ctx"):
if getattr(self, "ui_new_context_preset_name", "").strip():
@@ -3298,13 +3222,9 @@ class App:
self.ui_active_context_preset = ""
def _update_context_file_stats(self) -> tuple[int, int]:
if not hasattr(self, '_file_stats_cache'):
self._file_stats_cache = {}
if not hasattr(self, '_file_stats_queue'):
self._file_stats_queue = []
if not hasattr(self, '_file_stats_worker_active'):
self._file_stats_worker_active = False
if not hasattr(self, '_file_stats_cache'): self._file_stats_cache = {}
if not hasattr(self, '_file_stats_queue'): self._file_stats_queue = []
if not hasattr(self, '_file_stats_worker_active'): self._file_stats_worker_active = False
total_lines = 0
total_ast = 0
@@ -3313,8 +3233,7 @@ class App:
f_path = f.path if hasattr(f, "path") else str(f)
mtime = os.path.getmtime(f_path) if os.path.exists(f_path) else 0
cache_key = f"{f_path}_{mtime}"
if cache_key not in self._file_stats_cache:
missing_keys.append((f_path, cache_key))
if cache_key not in self._file_stats_cache: missing_keys.append((f_path, cache_key))
else:
stats = self._file_stats_cache[cache_key]
total_lines += stats.get("lines", 0)
@@ -3330,7 +3249,6 @@ class App:
self._file_stats_worker_active = False
threading.Thread(target=_stats_worker, daemon=True).start()
return total_lines, total_ast
#endregion: Context Management
@@ -3704,43 +3622,35 @@ class App:
val = math.sin(time.time() * 10 * math.pi)
alpha = 1.0 if val > 0 else 0.0
c = imgui.ImVec4(0.39, 1.0, 0.39, alpha)
if theme.is_nerv_active():
c = vec4(80, 255, 80, alpha) # DATA_GREEN for LIVE in NERV
if theme.is_nerv_active(): c = vec4(80, 255, 80, alpha) # DATA_GREEN for LIVE in NERV
imgui.text_colored(c, "LIVE")
imgui.separator()
ch, self.ui_ai_input = imgui.input_text_multiline("##ai_in", self.ui_ai_input, imgui.ImVec2(-1, -40))
# Keyboard shortcuts
io = imgui.get_io()
ctrl_l = io.key_ctrl and imgui.is_key_pressed(imgui.Key.l)
if ctrl_l:
self.ui_ai_input = ""
if ctrl_l: self.ui_ai_input = ""
imgui.separator()
is_busy = self.ai_status in ['sending...', 'streaming...']
send_busy = False
with self._send_thread_lock:
if self.send_thread and self.send_thread.is_alive():
send_busy = True
if self.send_thread and self.send_thread.is_alive(): send_busy = True
if is_busy: send_busy = True
imgui.begin_disabled(send_busy)
ctrl_enter = io.key_ctrl and imgui.is_key_pressed(imgui.Key.enter)
label = "Gen + Send (Busy)" if send_busy else "Gen + Send"
if (imgui.button(label) or ctrl_enter) and not send_busy:
self._handle_generate_send()
if (imgui.button(label) or ctrl_enter) and not send_busy: self._handle_generate_send()
imgui.end_disabled()
imgui.same_line()
if imgui.button("MD Only"):
self._handle_md_only()
if imgui.button("MD Only"): self._handle_md_only()
imgui.same_line()
if imgui.button("Inject File"):
self.show_inject_modal = True
if imgui.button("Inject File"): self.show_inject_modal = True
imgui.same_line()
if imgui.button("-> History"):
if self.ui_ai_input:
self.disc_entries.append({"role": "User", "content": self.ui_ai_input, "collapsed": False, "ts": project_manager.now_ts()})
if self.ui_ai_input: self.disc_entries.append({"role": "User", "content": self.ui_ai_input, "collapsed": False, "ts": project_manager.now_ts()})
imgui.same_line()
if imgui.button("Reset"):
self._handle_reset_session()
if imgui.button("Reset"): self._handle_reset_session()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_message_panel")
def _render_synthesis_panel(self) -> None:
@@ -3750,19 +3660,15 @@ class App:
"""
imgui.text("Select takes to synthesize:")
discussions = self.project.get('discussion', {}).get('discussions', {})
if not hasattr(self, 'ui_synthesis_selected_takes'):
self.ui_synthesis_selected_takes = {name: False for name in discussions}
if not hasattr(self, 'ui_synthesis_prompt'):
self.ui_synthesis_prompt = ""
for name in discussions:
_, self.ui_synthesis_selected_takes[name] = imgui.checkbox(name, self.ui_synthesis_selected_takes.get(name, False))
if not hasattr(self, 'ui_synthesis_selected_takes'): self.ui_synthesis_selected_takes = {name: False for name in discussions}
if not hasattr(self, 'ui_synthesis_prompt'): self.ui_synthesis_prompt = ""
for name in discussions: _, self.ui_synthesis_selected_takes[name] = imgui.checkbox(name, self.ui_synthesis_selected_takes.get(name, False))
imgui.spacing()
imgui.text("Synthesis Prompt:")
_, self.ui_synthesis_prompt = imgui.input_text_multiline("##synthesis_prompt", self.ui_synthesis_prompt, imgui.ImVec2(-1, 100))
if imgui.button("Generate Synthesis"):
selected = [name for name, sel in self.ui_synthesis_selected_takes.items() if sel]
if len(selected) > 1:
from src import synthesis_formatter
discussions_dict = self.project.get('discussion', {}).get('discussions', {})
takes_dict = {name: discussions_dict.get(name, {}).get('history', []) for name in selected}
diff_text = synthesis_formatter.format_takes_diff(takes_dict)
@@ -3801,15 +3707,13 @@ class App:
full_md, _, _ = src.aggregate.run(flat)
self._focus_md_cache[cp_name] = full_md
display_md = full_md
if imgui.button("Copy"):
imgui.set_clipboard_text(display_md)
if imgui.button("Copy"): imgui.set_clipboard_text(display_md)
imgui.begin_child("last_agg_md", imgui.ImVec2(0, 0), True)
markdown_helper.render(display_md, context_id="snapshot_agg")
imgui.end_child()
imgui.end_tab_item()
if imgui.begin_tab_item("System Prompt")[0]:
if imgui.button("Copy"):
imgui.set_clipboard_text(self.last_resolved_system_prompt)
if imgui.button("Copy"): imgui.set_clipboard_text(self.last_resolved_system_prompt)
imgui.begin_child("last_sys_prompt", imgui.ImVec2(0, 0), True)
markdown_helper.render(self.last_resolved_system_prompt, context_id="snapshot_sys")
imgui.end_child()
@@ -3852,8 +3756,7 @@ class App:
if self.ai_response:
segments, response = thinking_parser.parse_thinking_trace(self.ai_response)
entry = {"role": "AI", "content": response, "collapsed": True, "ts": project_manager.now_ts()}
if segments:
entry["thinking_segments"] = [{"content": s.content, "marker": s.marker} for s in segments]
if segments: entry["thinking_segments"] = [{"content": s.content, "marker": s.marker} for s in segments]
self.disc_entries.append(entry)
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_response_panel")
@@ -3969,8 +3872,7 @@ class App:
def _render_comms_history_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_comms_history_panel")
st_col = vec4(200, 220, 160)
if theme.is_nerv_active():
st_col = vec4(80, 255, 80) # DATA_GREEN for status in NERV
if theme.is_nerv_active(): st_col = vec4(80, 255, 80) # DATA_GREEN for status in NERV
imgui.text_colored(st_col, f"Status: {self.ai_status}")
imgui.same_line()
if imgui.button("Clear##comms"):
@@ -4082,8 +3984,7 @@ class App:
self._scroll_comms_to_bottom = False
imgui.end_child()
if self.is_viewing_prior_session:
imgui.pop_style_color()
if self.is_viewing_prior_session: imgui.pop_style_color()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_comms_history_panel")
#endregion: Operations Monitor
@@ -4254,7 +4155,6 @@ class App:
imgui.open_popup("Apply Patch?")
with imscope.popup_modal("Apply Patch?", True, imgui.WindowFlags_.always_auto_resize) as (opened, _):
if opened:
from src import shaders
p_min = imgui.get_window_pos()
p_max = imgui.ImVec2(p_min.x + imgui.get_window_size().x, p_min.y + imgui.get_window_size().y)
shaders.draw_soft_shadow(imgui.get_background_draw_list(), p_min, p_max, imgui.ImVec4(0, 0, 0, 0.6), 25.0, 6.0)
@@ -4263,8 +4163,7 @@ class App:
imgui.separator()
if self._pending_patch_files:
imgui.text("Files to modify:")
for f in self._pending_patch_files:
imgui.text(f" - {f}")
for f in self._pending_patch_files: imgui.text(f" - {f}")
imgui.separator()
if self._patch_error_message:
imgui.text_colored(vec4(255, 77, 77), f"Error: {self._patch_error_message}")
@@ -4274,18 +4173,13 @@ class App:
if self._pending_patch_text:
diff_lines = self._pending_patch_text.split("\n")
for line in diff_lines:
if line.startswith("+++") or line.startswith("---") or line.startswith("@@"):
imgui.text_colored(vec4(77, 178, 255), line)
elif line.startswith("+"):
imgui.text_colored(vec4(51, 230, 51), line)
elif line.startswith("-"):
imgui.text_colored(vec4(230, 51, 51), line)
else:
imgui.text(line)
if line.startswith("+++") or line.startswith("---") or line.startswith("@@"): imgui.text_colored(vec4(77, 178, 255), line)
elif line.startswith("+"): imgui.text_colored(vec4(51, 230, 51), line)
elif line.startswith("-"): imgui.text_colored(vec4(230, 51, 51), line)
else: imgui.text(line)
imgui.end_child()
imgui.separator()
if imgui.button("Open in External Editor"):
self._open_patch_in_external_editor()
if imgui.button("Open in External Editor"): self._open_patch_in_external_editor()
imgui.same_line()
if imgui.button("Apply Patch"):
self._apply_pending_patch()
@@ -4304,7 +4198,6 @@ class App:
self._patch_error_message = "No patch to apply"
return
try:
from src.diff_viewer import apply_patch_to_file
base_dir = str(self.controller.current_project_dir) if hasattr(self.controller, 'current_project_dir') else "."
success, msg = apply_patch_to_file(self._pending_patch_text, base_dir)
if success:
@@ -4337,8 +4230,7 @@ class App:
return
temp_path = create_temp_modified_file(self._pending_patch_text)
result = launcher.launch_diff(None, original_path, temp_path)
if result is None:
self._patch_error_message = "Failed to launch external editor"
if result is None: self._patch_error_message = "Failed to launch external editor"
else:
self._patch_error_message = None
self._vscode_diff_process = result
@@ -4369,29 +4261,22 @@ class App:
else:
imgui.text("Default Editor:")
editor_names = sorted(list(editors.keys()))
if default_name and default_name in editor_names:
current_idx = editor_names.index(default_name)
else:
current_idx = 0
if default_name and default_name in editor_names: current_idx = editor_names.index(default_name)
else: current_idx = 0
changed, new_idx = imgui.combo("##editor_combo", current_idx, editor_names)
if changed:
self._set_external_editor_default(editor_names[new_idx])
if changed: self._set_external_editor_default(editor_names[new_idx])
imgui.text("")
imgui.text("Configured Editors:")
imgui.separator()
for name in editor_names:
editor = editors.get(name)
if not editor:
continue
if not editor: continue
is_default = name == default_name
marker = " (default)" if is_default else ""
if is_default:
imgui.text_colored(C_IN, f" {name}{marker}")
else:
imgui.text(f" {name}{marker}")
if is_default: imgui.text_colored(C_IN, f" {name}{marker}")
else: imgui.text(f" {name}{marker}")
imgui.text(f" {editor.path}")
if editor.diff_args:
imgui.textDisabled(f" diff: {editor.diff_args}")
if editor.diff_args: imgui.textDisabled(f" diff: {editor.diff_args}")
imgui.text("")
imgui.text("Config: config.toml [tools.text_editors]")
imgui.text("Override: manual_slop.toml default_editor")
@@ -4410,8 +4295,7 @@ class App:
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()
if not dlg: imgui.close_current_popup()
else:
imgui.text("The AI wants to run the following PowerShell script:")
imgui.text_colored(vec4(200, 200, 100), f"base_dir: {dlg._base_dir}")
@@ -4430,8 +4314,7 @@ class App:
dlg._approved = True
dlg._done = True
dlg._condition.notify_all()
with self._pending_dialog_lock:
self._pending_dialog = None
with self._pending_dialog_lock: self._pending_dialog = None
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Reject", imgui.ImVec2(120, 0)):
@@ -4439,8 +4322,7 @@ class App:
dlg._approved = False
dlg._done = True
dlg._condition.notify_all()
with self._pending_dialog_lock:
self._pending_dialog = None
with self._pending_dialog_lock: self._pending_dialog = None
imgui.close_current_popup()
imgui.end_popup()
@@ -4750,8 +4632,7 @@ def hello():
self._show_add_ticket_form = False
self._push_mma_state_update()
imgui.same_line()
if imgui.button("Cancel"):
self._show_add_ticket_form = False
if imgui.button("Cancel"): self._show_add_ticket_form = False
imgui.end_child()
else:
imgui.text_disabled("No active MMA track or tickets.")
@@ -4911,8 +4792,7 @@ def hello():
if imgui.begin_popup_modal("Cycle Detected!", None, imgui.WindowFlags_.always_auto_resize)[0]:
imgui.text_colored(imgui.ImVec4(1, 0.3, 0.3, 1), "The dependency graph contains a cycle!")
imgui.text("Please remove the circular dependency.")
if imgui.button("OK"):
imgui.close_current_popup()
if imgui.button("OK"): imgui.close_current_popup()
imgui.end_popup()
def _render_mma_track_summary(self) -> None: