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