Files
manual_slop/src/gui_2.py

3515 lines
140 KiB
Python

# gui_2.py
from __future__ import annotations
import tomli_w
import time
import math
import json
import sys
import os
import copy
from pathlib import Path
from tkinter import filedialog, Tk
from typing import Optional, Any
from src import ai_client
from src import cost_tracker
from src import session_logger
from src import project_manager
from src import paths
from src import presets
from src import theme_2 as theme
from src import theme_nerv_fx as theme_fx
from src import api_hooks
import numpy as np
from src import log_registry
from src import log_pruner
from src import models
from src import app_controller
from src import mcp_client
from src import markdown_helper
from src import bg_shader
import re
from pydantic import BaseModel
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed
PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"]
COMMS_CLAMP_CHARS: int = 300
def hide_tk_root() -> Tk:
root = Tk()
root.withdraw()
root.wm_attributes("-topmost", True)
return root
# Color Helpers
def vec4(r: float, g: float, b: float, a: float = 1.0) -> imgui.ImVec4: return imgui.ImVec4(r/255, g/255, b/255, a)
C_OUT: imgui.ImVec4 = vec4(100, 200, 255)
C_IN: imgui.ImVec4 = vec4(140, 255, 160)
C_REQ: imgui.ImVec4 = vec4(255, 220, 100)
C_RES: imgui.ImVec4 = vec4(180, 255, 180)
C_TC: imgui.ImVec4 = vec4(255, 180, 80)
C_TR: imgui.ImVec4 = vec4(180, 220, 255)
C_TRS: imgui.ImVec4 = vec4(200, 180, 255)
C_LBL: imgui.ImVec4 = vec4(180, 180, 180)
C_VAL: imgui.ImVec4 = vec4(220, 220, 220)
C_KEY: imgui.ImVec4 = vec4(140, 200, 255)
C_NUM: imgui.ImVec4 = vec4(180, 255, 180)
C_SUB: imgui.ImVec4 = vec4(220, 200, 120)
DIR_COLORS: dict[str, imgui.ImVec4] = {"OUT": C_OUT, "IN": C_IN}
KIND_COLORS: dict[str, imgui.ImVec4] = {"request": C_REQ, "response": C_RES, "tool_call": C_TC, "tool_result": C_TR, "tool_result_send": C_TRS}
HEAVY_KEYS: set[str] = {"message", "text", "script", "output", "content"}
def truncate_entries(entries: list[dict[str, Any]], max_pairs: int) -> list[dict[str, Any]]:
if max_pairs <= 0:
return []
count = 0
target = max_pairs * 2
for i in range(len(entries) - 1, -1, -1):
role = entries[i].get("role", "")
if role in ("User", "AI"):
count += 1
if count == target:
return entries[i:]
return entries
class GenerateRequest(BaseModel):
prompt: str
auto_add_history: bool = True
temperature: float | None = None
max_tokens: int | None = None
class ConfirmRequest(BaseModel):
approved: bool
script: Optional[str] = None
class App:
"""The main ImGui interface orchestrator for Manual Slop."""
def __init__(self) -> None:
# Initialize controller and delegate state
self.controller = app_controller.AppController()
# Restore legacy PROVIDERS to controller if needed (it already has it via delegation if set on class level, but let's be explicit)
if not hasattr(self.controller, 'PROVIDERS'):
self.controller.PROVIDERS = PROVIDERS
self.controller.init_state()
self.show_windows.setdefault("Diagnostics", False)
self.controller.start_services(self)
self.show_preset_manager_modal = False
self.show_tool_preset_manager_modal = False
self.ui_active_tool_preset = ""
self.ui_active_bias_profile = ""
self._editing_bias_profile_name = ""
self._editing_bias_profile_tool_weights = "" # JSON
self._editing_bias_profile_cat_mults = "" # JSON
self._editing_bias_profile_scope = "project"
self._editing_tool_preset_name = ''
self._editing_tool_preset_categories = {}
self._editing_tool_preset_scope = 'project'
self._selected_tool_preset_idx = -1
self._editing_bias_profile_name = ""
self._editing_bias_profile_tool_weights = "{}"
self._editing_bias_profile_category_multipliers = "{}"
self._selected_bias_profile_idx = -1
self._editing_preset_name = ""
self._editing_preset_content = ""
self._editing_preset_temperature = 0.0
self._editing_preset_top_p = 1.0
self._editing_preset_max_output_tokens = 4096
self._editing_preset_scope = "project"
self._editing_preset_is_new = False
self._presets_list: dict[str, dict] = {}
# Aliases for controller-owned locks
self._send_thread_lock = self.controller._send_thread_lock
self._disc_entries_lock = self.controller._disc_entries_lock
self._pending_comms_lock = self.controller._pending_comms_lock
self._pending_tool_calls_lock = self.controller._pending_tool_calls_lock
self._pending_history_adds_lock = self.controller._pending_history_adds_lock
self._pending_gui_tasks_lock = self.controller._pending_gui_tasks_lock
self._pending_dialog_lock = self.controller._pending_dialog_lock
self._api_event_queue_lock = self.controller._api_event_queue_lock
self._discussion_names_cache: list[str] = []
self._discussion_names_dirty: bool = True
# Initialize node editor context
self.node_editor_config = ed.Config()
self.node_editor_ctx = ed.create_editor(self.node_editor_config)
self.ui_selected_ticket_id: Optional[str] = None
self.ui_selected_tickets: set[str] = set()
self.ui_new_ticket_priority: str = 'medium'
self._autofocus_response_tab = False
gui_cfg = self.config.get("gui", {})
self.ui_separate_message_panel = gui_cfg.get("separate_message_panel", False)
self.ui_separate_response_panel = gui_cfg.get("separate_response_panel", False)
self.ui_separate_tool_calls_panel = gui_cfg.get("separate_tool_calls_panel", False)
self.ui_separate_task_dag = gui_cfg.get("separate_task_dag", False)
self.ui_separate_usage_analytics = gui_cfg.get("separate_usage_analytics", False)
self.ui_separate_tier1 = gui_cfg.get("separate_tier1", False)
self.ui_separate_tier2 = gui_cfg.get("separate_tier2", False)
self.ui_separate_tier3 = gui_cfg.get("separate_tier3", False)
self.ui_separate_tier4 = gui_cfg.get("separate_tier4", False)
self.show_windows.setdefault("Usage Analytics", False)
self.show_windows.setdefault("Tier 1: Strategy", False)
self.show_windows.setdefault("Tier 2: Tech Lead", False)
self.show_windows.setdefault("Tier 3: Workers", False)
self.show_windows.setdefault("Tier 4: QA", False)
self.ui_multi_viewport = gui_cfg.get("multi_viewport", False)
self.layout_presets = self.config.get("layout_presets", {})
self._new_preset_name = ""
self._show_save_preset_modal = False
self._comms_log_cache: list[dict[str, Any]] = []
self._comms_log_dirty: bool = True
self._tool_log_cache: list[dict[str, Any]] = []
self._tool_log_dirty: bool = True
self._last_ui_focus_agent: Optional[str] = None
self._log_registry: Optional[log_registry.LogRegistry] = None
self.perf_profiling_enabled = False
self.perf_show_graphs: dict[str, bool] = {}
self._token_stats: dict[str, Any] = {}
self._token_stats_dirty: bool = True
self.perf_history: dict[str, list] = {"frame_time": [0.0] * 100, "fps": [0.0] * 100}
self._nerv_crt = theme_fx.CRTFilter()
self.ui_crt_filter = True
self._nerv_alert = theme_fx.AlertPulsing()
self._nerv_flicker = theme_fx.StatusFlicker()
def _handle_approve_tool(self, user_data=None) -> None:
"""UI-level wrapper for approving a pending tool execution ask."""
self._handle_approve_ask()
def _handle_approve_mma_step(self, user_data=None) -> None:
"""UI-level wrapper for approving a pending MMA step."""
self._handle_mma_respond(approved=True)
def _handle_approve_spawn(self, user_data=None) -> None:
"""UI-level wrapper for approving a pending MMA sub-agent spawn."""
self._handle_mma_respond(approved=True)
def __getattr__(self, name: str) -> Any:
if name != 'controller' and hasattr(self, 'controller') and hasattr(self.controller, name):
return getattr(self.controller, name)
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
def __setattr__(self, name: str, value: Any) -> None:
if name == 'controller':
super().__setattr__(name, value)
elif hasattr(self, 'controller') and hasattr(self.controller, name):
setattr(self.controller, name, value)
else:
super().__setattr__(name, value)
@property
def current_provider(self) -> str:
return self.controller.current_provider
@current_provider.setter
def current_provider(self, value: str) -> None:
self.controller.current_provider = value
@property
def current_model(self) -> str:
return self.controller.current_model
@current_model.setter
def current_model(self, value: str) -> None:
self.controller.current_model = value
@property
def perf_profiling_enabled(self) -> bool:
return self.controller.perf_profiling_enabled
@perf_profiling_enabled.setter
def perf_profiling_enabled(self, value: bool) -> None:
self.controller.perf_profiling_enabled = value
def shutdown(self) -> None:
"""Cleanly shuts down the app's background tasks and saves state."""
try:
if hasattr(self, 'runner_params') and self.runner_params.ini_filename:
imgui.save_ini_settings_to_disk(self.runner_params.ini_filename)
except:
pass
self.controller.shutdown()
def _test_callback_func_write_to_file(self, data: str) -> None:
"""A dummy function that a custom_callback would execute for testing."""
# Ensure the directory exists if running from a different cwd
os.makedirs("tests/artifacts", exist_ok=True)
with open("tests/artifacts/temp_callback_output.txt", "w") as f:
f.write(data)
# ---------------------------------------------------------------- helpers
def _render_text_viewer(self, label: str, content: str) -> None:
if imgui.button("[+]##" + str(id(content))):
self.show_text_viewer = True
self.text_viewer_title = label
self.text_viewer_content = content
def _render_heavy_text(self, label: str, content: str, id_suffix: str = "") -> None:
imgui.text_colored(C_LBL, f"{label}:")
imgui.same_line()
if imgui.button("[+]##" + label + id_suffix):
self.show_text_viewer = True
self.text_viewer_title = label
self.text_viewer_content = content
if not content:
imgui.text_disabled("(empty)")
return
is_md = label in ("message", "text", "content")
ctx_id = f"heavy_{label}_{id_suffix}"
is_nerv = theme.is_nerv_active()
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
if len(content) > COMMS_CLAMP_CHARS:
imgui.begin_child(f"heavy_text_child_{label}_{id_suffix}", imgui.ImVec2(0, 80), True)
if is_md:
markdown_helper.render(content, context_id=ctx_id)
else:
markdown_helper.render_code(content, context_id=ctx_id)
imgui.end_child()
else:
if is_md:
markdown_helper.render(content, context_id=ctx_id)
else:
markdown_helper.render_code(content, context_id=ctx_id)
if is_nerv: imgui.pop_style_color()
# ---------------------------------------------------------------- gui
def _render_selectable_label(self, label: str, value: str, width: float = 0.0, multiline: bool = False, height: float = 0.0, color: Optional[imgui.ImVec4] = None) -> None:
imgui.push_id(label + str(hash(value)))
pops = 4
imgui.push_style_color(imgui.Col_.frame_bg, vec4(0, 0, 0, 0))
imgui.push_style_color(imgui.Col_.frame_bg_hovered, vec4(0, 0, 0, 0))
imgui.push_style_color(imgui.Col_.frame_bg_active, vec4(0, 0, 0, 0))
imgui.push_style_color(imgui.Col_.border, vec4(0, 0, 0, 0))
if color:
imgui.push_style_color(imgui.Col_.text, color)
pops += 1
imgui.push_style_var(imgui.StyleVar_.frame_border_size, 0.0)
imgui.push_style_var(imgui.StyleVar_.frame_padding, imgui.ImVec2(0, 0))
if multiline:
imgui.input_text_multiline("##" + label, value, imgui.ImVec2(width, height), imgui.InputTextFlags_.read_only)
else:
if width > 0: imgui.set_next_item_width(width)
imgui.input_text("##" + label, value, imgui.InputTextFlags_.read_only)
imgui.pop_style_color(pops)
imgui.pop_style_var(2)
imgui.pop_id()
def _show_menus(self) -> None:
if imgui.begin_menu("manual slop"):
if imgui.menu_item("Quit", "Ctrl+Q", False)[0]:
self.runner_params.app_shall_exit = True
imgui.end_menu()
if imgui.begin_menu("Windows"):
for w in self.show_windows.keys():
_, self.show_windows[w] = imgui.menu_item(w, "", self.show_windows[w])
imgui.end_menu()
if imgui.begin_menu("Project"):
if imgui.menu_item("Save All", "", False)[0]:
self._flush_to_project()
self._save_active_project()
self._flush_to_config()
models.save_config(self.config)
self.ai_status = "config saved"
if imgui.menu_item("Reset Session", "", False)[0]:
ai_client.reset_session()
ai_client.clear_comms_log()
self._tool_log.clear()
self._comms_log.clear()
self.ai_status = "session reset"
self.ai_response = ""
if imgui.menu_item("Generate MD Only", "", False)[0]:
try:
md, path, *_ = self._do_generate()
self.last_md = md
self.last_md_path = path
self.ai_status = f"md written: {path.name}"
except Exception as e:
self.ai_status = f"error: {e}"
imgui.end_menu()
def _gui_func(self) -> None:
pushed_prior_tint = False
# Render background shader
bg = bg_shader.get_bg()
if bg.enabled:
ws = imgui.get_io().display_size
bg.render(ws.x, ws.y)
if theme.is_nerv_active():
ws = imgui.get_io().display_size
self._nerv_alert.update(self.ai_status)
self._nerv_alert.render(ws.x, ws.y)
self._nerv_crt.enabled = self.ui_crt_filter
self._nerv_crt.render(ws.x, ws.y)
if self.perf_profiling_enabled: self.perf_monitor.start_component("_gui_func")
if self.is_viewing_prior_session:
imgui.push_style_color(imgui.Col_.window_bg, vec4(50, 40, 20))
pushed_prior_tint = True
try:
self.perf_monitor.start_frame()
self._autofocus_response_tab = self.controller._autofocus_response_tab
# Process GUI task queue
# DEBUG: Check if tasks exist before processing
if hasattr(self, 'controller') and hasattr(self.controller, '_pending_gui_tasks'):
pending_count = len(self.controller._pending_gui_tasks)
if pending_count > 0:
sys.stderr.write(f"[DEBUG gui_2] _gui_func: found {pending_count} pending tasks\n")
sys.stderr.flush()
self._process_pending_gui_tasks()
self._process_pending_history_adds()
if self.controller._process_pending_tool_calls():
self._tool_log_dirty = True
self._render_track_proposal_modal()
self._render_patch_modal()
self._render_save_preset_modal()
self._render_preset_manager_modal()
self._render_tool_preset_manager_modal()
# Auto-save (every 60s)
now = time.time()
if now - self._last_autosave >= self._autosave_interval:
self._last_autosave = now
try:
self._flush_to_project()
self._save_active_project()
self._flush_to_config()
models.save_config(self.config)
except Exception:
pass # silent — don't disrupt the GUI loop
# Sync pending comms
with self._pending_comms_lock:
if self._pending_comms:
if self.ui_auto_scroll_comms:
self._scroll_comms_to_bottom = True
self._comms_log_dirty = True
for c in self._pending_comms:
self._comms_log.append(c)
self._pending_comms.clear()
if self.ui_focus_agent != self._last_ui_focus_agent:
self._comms_log_dirty = True
self._tool_log_dirty = True
self._last_ui_focus_agent = self.ui_focus_agent
if self._comms_log_dirty:
if self.is_viewing_prior_session:
self._comms_log_cache = self.prior_session_entries
else:
log_raw = list(self._comms_log)
if self.ui_focus_agent:
self._comms_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(self.ui_focus_agent)]
else:
self._comms_log_cache = log_raw
self._comms_log_dirty = False
if self._tool_log_dirty:
log_raw = list(self._tool_log)
if self.ui_focus_agent:
self._tool_log_cache = [e for e in log_raw if e.get("source_tier", "").startswith(self.ui_focus_agent)]
else:
self._tool_log_cache = log_raw
self._tool_log_dirty = False
if self.show_windows.get("Context Hub", False):
exp, opened = imgui.begin("Context Hub", self.show_windows["Context Hub"])
self.show_windows["Context Hub"] = bool(opened)
if exp:
self._render_projects_panel()
imgui.end()
if self.show_windows.get("Files & Media", False):
exp, opened = imgui.begin("Files & Media", self.show_windows["Files & Media"])
self.show_windows["Files & Media"] = bool(opened)
if exp:
if imgui.collapsing_header("Files"):
self._render_files_panel()
if imgui.collapsing_header("Screenshots"):
self._render_screenshots_panel()
imgui.end()
if self.show_windows.get("AI Settings", False):
exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"])
self.show_windows["AI Settings"] = bool(opened)
if exp:
if imgui.collapsing_header("Provider & Model"):
self._render_provider_panel()
if imgui.collapsing_header("System Prompts"):
self._render_system_prompts_panel()
self._render_agent_tools_panel()
self._render_cache_panel()
imgui.end()
if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False):
exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"])
self.show_windows["Usage Analytics"] = bool(opened)
if exp:
self._render_usage_analytics_panel()
imgui.end()
if self.show_windows.get("MMA Dashboard", False):
exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
self.show_windows["MMA Dashboard"] = bool(opened)
if exp:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
self._render_mma_dashboard()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
imgui.end()
if self.ui_separate_task_dag and self.show_windows.get("Task DAG", False):
exp, opened = imgui.begin("Task DAG", self.show_windows["Task DAG"])
self.show_windows["Task DAG"] = bool(opened)
if exp:
self._render_task_dag_panel()
imgui.end()
if self.ui_separate_tier1 and self.show_windows.get("Tier 1: Strategy", False):
exp, opened = imgui.begin("Tier 1: Strategy", self.show_windows["Tier 1: Strategy"])
self.show_windows["Tier 1: Strategy"] = bool(opened)
if exp:
self._render_tier_stream_panel("Tier 1", "Tier 1")
imgui.end()
if self.ui_separate_tier2 and self.show_windows.get("Tier 2: Tech Lead", False):
exp, opened = imgui.begin("Tier 2: Tech Lead", self.show_windows["Tier 2: Tech Lead"])
self.show_windows["Tier 2: Tech Lead"] = bool(opened)
if exp:
self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
imgui.end()
if self.ui_separate_tier3 and self.show_windows.get("Tier 3: Workers", False):
exp, opened = imgui.begin("Tier 3: Workers", self.show_windows["Tier 3: Workers"])
self.show_windows["Tier 3: Workers"] = bool(opened)
if exp:
self._render_tier_stream_panel("Tier 3", None)
imgui.end()
if self.ui_separate_tier4 and self.show_windows.get("Tier 4: QA", False):
exp, opened = imgui.begin("Tier 4: QA", self.show_windows["Tier 4: QA"])
self.show_windows["Tier 4: QA"] = bool(opened)
if exp:
self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
imgui.end()
if self.show_windows.get("Theme", False):
self._render_theme_panel()
if self.show_windows.get("Discussion Hub", False):
exp, opened = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"])
self.show_windows["Discussion Hub"] = bool(opened)
if exp:
# Top part for the history
imgui.begin_child("HistoryChild", size=(0, -200))
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
self._render_discussion_panel()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_discussion_panel")
imgui.end_child()
# Bottom part with tabs for message and response
# Detach controls
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
ch1, self.ui_separate_message_panel = imgui.checkbox("Pop Out Message", self.ui_separate_message_panel)
imgui.same_line()
ch2, self.ui_separate_response_panel = imgui.checkbox("Pop Out Response", self.ui_separate_response_panel)
if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
imgui.pop_style_var()
show_message_tab = not self.ui_separate_message_panel
show_response_tab = not self.ui_separate_response_panel
if show_message_tab or show_response_tab:
if imgui.begin_tab_bar("discussion_tabs"):
# Task: Auto-focus Response tab when response received
tab_flags = imgui.TabItemFlags_.none
if self._autofocus_response_tab:
tab_flags = imgui.TabItemFlags_.set_selected
self._autofocus_response_tab = False
self.controller._autofocus_response_tab = False
if show_message_tab:
if imgui.begin_tab_item("Message", None)[0]:
self._render_message_panel()
imgui.end_tab_item()
if show_response_tab:
if imgui.begin_tab_item("Response", None, tab_flags)[0]:
self._render_response_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
else:
imgui.text_disabled("Message & Response panels are detached.")
imgui.end()
if self.show_windows.get("Operations Hub", False):
exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
self.show_windows["Operations Hub"] = bool(opened)
if exp:
imgui.text("Focus Agent:")
imgui.same_line()
focus_label = self.ui_focus_agent or "All"
if imgui.begin_combo("##focus_agent", focus_label, imgui.ComboFlags_.width_fit_preview):
if imgui.selectable("All", self.ui_focus_agent is None)[0]:
self.ui_focus_agent = None
for tier in ["Tier 2", "Tier 3", "Tier 4"]:
if imgui.selectable(tier, self.ui_focus_agent == tier)[0]:
self.ui_focus_agent = tier
imgui.end_combo()
imgui.same_line()
if self.ui_focus_agent:
if imgui.button("x##clear_focus"):
self.ui_focus_agent = None
if exp:
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
ch1, self.ui_separate_tool_calls_panel = imgui.checkbox("Pop Out Tool Calls", self.ui_separate_tool_calls_panel)
if ch1: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel
imgui.same_line()
ch2, self.ui_separate_usage_analytics = imgui.checkbox("Pop Out Usage Analytics", self.ui_separate_usage_analytics)
if ch2: self.show_windows["Usage Analytics"] = self.ui_separate_usage_analytics
imgui.pop_style_var()
show_tc_tab = not self.ui_separate_tool_calls_panel
show_usage_tab = not self.ui_separate_usage_analytics
if imgui.begin_tab_bar("ops_tabs"):
if imgui.begin_tab_item("Comms History")[0]:
self._render_comms_history_panel()
imgui.end_tab_item()
if show_tc_tab:
if imgui.begin_tab_item("Tool Calls")[0]:
self._render_tool_calls_panel()
imgui.end_tab_item()
if show_usage_tab:
if imgui.begin_tab_item("Usage Analytics")[0]:
self._render_usage_analytics_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
imgui.end()
if self.ui_separate_message_panel and self.show_windows.get("Message", False):
exp, opened = imgui.begin("Message", self.show_windows["Message"])
self.show_windows["Message"] = bool(opened)
if exp:
self._render_message_panel()
imgui.end()
if self.ui_separate_response_panel and self.show_windows.get("Response", False):
exp, opened = imgui.begin("Response", self.show_windows["Response"])
self.show_windows["Response"] = bool(opened)
if exp:
self._render_response_panel()
imgui.end()
if self.ui_separate_tool_calls_panel and self.show_windows.get("Tool Calls", False):
exp, opened = imgui.begin("Tool Calls", self.show_windows["Tool Calls"])
self.show_windows["Tool Calls"] = bool(opened)
if exp:
self._render_tool_calls_panel()
imgui.end()
if self.show_windows.get("Log Management", False):
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
self._render_log_management()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_log_management")
if self.show_windows.get("Diagnostics", False):
self._render_diagnostics_panel()
self.perf_monitor.end_frame()
# ---- Modals / Popups
with self._pending_dialog_lock:
dlg = self._pending_dialog
if dlg:
if not self._pending_dialog_open:
imgui.open_popup("Approve PowerShell Command")
self._pending_dialog_open = True
else:
self._pending_dialog_open = False
if imgui.begin_popup_modal("Approve PowerShell Command", None, imgui.WindowFlags_.always_auto_resize)[0]:
if not dlg:
imgui.close_current_popup()
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}")
imgui.separator()
# Checkbox to toggle full preview inside modal
_, self.show_text_viewer = imgui.checkbox("Show Full Preview", self.show_text_viewer)
if self.show_text_viewer:
imgui.begin_child("preview_child", imgui.ImVec2(600, 300), True)
imgui.text_unformatted(dlg._script)
imgui.end_child()
else:
ch, dlg._script = imgui.input_text_multiline("##confirm_script", dlg._script, imgui.ImVec2(-1, 200))
imgui.separator()
if imgui.button("Approve & Run", imgui.ImVec2(120, 0)):
with dlg._condition:
dlg._approved = True
dlg._done = True
dlg._condition.notify_all()
with self._pending_dialog_lock:
self._pending_dialog = None
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Reject", imgui.ImVec2(120, 0)):
with dlg._condition:
dlg._approved = False
dlg._done = True
dlg._condition.notify_all()
with self._pending_dialog_lock:
self._pending_dialog = None
imgui.close_current_popup()
imgui.end_popup()
if self._pending_ask_dialog:
if not self._ask_dialog_open:
imgui.open_popup("Approve Tool Execution")
self._ask_dialog_open = True
else:
self._ask_dialog_open = False
if imgui.begin_popup_modal("Approve Tool Execution", None, imgui.WindowFlags_.always_auto_resize)[0]:
if not self._pending_ask_dialog or self._ask_tool_data is None:
imgui.close_current_popup()
else:
tool_name = self._ask_tool_data.get("tool", "unknown")
tool_args = self._ask_tool_data.get("args", {})
imgui.text("The AI wants to execute a tool:")
imgui.text_colored(vec4(200, 200, 100), f"Tool: {tool_name}")
imgui.separator()
imgui.text("Arguments:")
imgui.begin_child("ask_args_child", imgui.ImVec2(400, 200), True)
imgui.text_unformatted(json.dumps(tool_args, indent=2))
imgui.end_child()
imgui.separator()
if imgui.button("Approve", imgui.ImVec2(120, 0)):
self._handle_approve_ask()
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Deny", imgui.ImVec2(120, 0)):
self._handle_reject_ask()
imgui.close_current_popup()
imgui.end_popup()
# MMA Step Approval Modal
if self._pending_mma_approval:
if not self._mma_approval_open:
imgui.open_popup("MMA Step Approval")
self._mma_approval_open = True
self._mma_approval_edit_mode = False
self._mma_approval_payload = self._pending_mma_approval.get("payload", "")
else:
self._mma_approval_open = False
if imgui.begin_popup_modal("MMA Step Approval", None, imgui.WindowFlags_.always_auto_resize)[0]:
if not self._pending_mma_approval:
imgui.close_current_popup()
else:
ticket_id = self._pending_mma_approval.get("ticket_id", "??")
imgui.text(f"Ticket {ticket_id} is waiting for tool execution approval.")
imgui.separator()
if self._mma_approval_edit_mode:
imgui.text("Edit Raw Payload (Manual Memory Mutation):")
_, self._mma_approval_payload = imgui.input_text_multiline("##mma_payload", self._mma_approval_payload, imgui.ImVec2(600, 400))
else:
imgui.text("Proposed Tool Call:")
imgui.begin_child("mma_preview", imgui.ImVec2(600, 300), True)
imgui.text_unformatted(str(self._pending_mma_approval.get("payload", "")))
imgui.end_child()
imgui.separator()
if imgui.button("Approve", imgui.ImVec2(120, 0)):
self._handle_mma_respond(approved=True, payload=self._mma_approval_payload)
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Edit Payload" if not self._mma_approval_edit_mode else "Show Original", imgui.ImVec2(120, 0)):
self._mma_approval_edit_mode = not self._mma_approval_edit_mode
imgui.same_line()
if imgui.button("Abort Ticket", imgui.ImVec2(120, 0)):
self._handle_mma_respond(approved=False)
imgui.close_current_popup()
imgui.end_popup()
# MMA Spawn Approval Modal
if self._pending_mma_spawn:
if not self._mma_spawn_open:
imgui.open_popup("MMA Spawn Approval")
self._mma_spawn_open = True
self._mma_spawn_edit_mode = False
self._mma_spawn_prompt = self._pending_mma_spawn.get("prompt", "")
self._mma_spawn_context = self._pending_mma_spawn.get("context_md", "")
else:
self._mma_spawn_open = False
if imgui.begin_popup_modal("MMA Spawn Approval", None, imgui.WindowFlags_.always_auto_resize)[0]:
if not self._pending_mma_spawn:
imgui.close_current_popup()
else:
role = self._pending_mma_spawn.get("role", "??")
ticket_id = self._pending_mma_spawn.get("ticket_id", "??")
imgui.text(f"Spawning {role} for Ticket {ticket_id}")
imgui.separator()
if self._mma_spawn_edit_mode:
imgui.text("Edit Prompt:")
_, self._mma_spawn_prompt = imgui.input_text_multiline("##spawn_prompt", self._mma_spawn_prompt, imgui.ImVec2(800, 200))
imgui.text("Edit Context MD:")
_, self._mma_spawn_context = imgui.input_text_multiline("##spawn_context", self._mma_spawn_context, imgui.ImVec2(800, 300))
else:
imgui.text("Proposed Prompt:")
imgui.begin_child("spawn_prompt_preview", imgui.ImVec2(800, 150), True)
imgui.text_unformatted(self._mma_spawn_prompt)
imgui.end_child()
imgui.text("Proposed Context MD:")
imgui.begin_child("spawn_context_preview", imgui.ImVec2(800, 250), True)
imgui.text_unformatted(self._mma_spawn_context)
imgui.end_child()
imgui.separator()
if imgui.button("Approve", imgui.ImVec2(120, 0)):
self._handle_mma_respond(approved=True, prompt=self._mma_spawn_prompt, context_md=self._mma_spawn_context)
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Edit Mode" if not self._mma_spawn_edit_mode else "Preview Mode", imgui.ImVec2(120, 0)):
self._mma_spawn_edit_mode = not self._mma_spawn_edit_mode
imgui.same_line()
if imgui.button("Abort", imgui.ImVec2(120, 0)):
self._handle_mma_respond(approved=False, abort=True)
imgui.close_current_popup()
imgui.end_popup()
# Cycle Detected Popup
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()
imgui.end_popup()
if self.show_script_output:
if self._trigger_script_blink:
self._trigger_script_blink = False
self._is_script_blinking = True
self._script_blink_start_time = time.time()
try:
imgui.set_window_focus("Last Script Output") # type: ignore[call-arg]
except Exception:
pass
if self._is_script_blinking:
elapsed = time.time() - self._script_blink_start_time
if elapsed > 1.5:
self._is_script_blinking = False
else:
val = math.sin(elapsed * 8 * math.pi)
alpha = 60/255 if val > 0 else 0
imgui.push_style_color(imgui.Col_.frame_bg, vec4(0, 100, 255, alpha))
imgui.push_style_color(imgui.Col_.child_bg, vec4(0, 100, 255, alpha))
imgui.set_next_window_size(imgui.ImVec2(800, 600), imgui.Cond_.first_use_ever)
expanded, opened = imgui.begin("Last Script Output", self.show_script_output)
self.show_script_output = bool(opened)
if expanded:
imgui.text("Script:")
imgui.same_line()
self._render_text_viewer("Last Script", self.ui_last_script_text)
if self.ui_word_wrap:
imgui.begin_child("lso_s_wrap", imgui.ImVec2(-1, 200), True)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(self.ui_last_script_text)
imgui.pop_text_wrap_pos()
imgui.end_child()
else:
imgui.input_text_multiline("##lso_s", self.ui_last_script_text, imgui.ImVec2(-1, 200), imgui.InputTextFlags_.read_only)
imgui.separator()
imgui.text("Output:")
imgui.same_line()
self._render_text_viewer("Last Output", self.ui_last_script_output)
if self.ui_word_wrap:
imgui.begin_child("lso_o_wrap", imgui.ImVec2(-1, -1), True)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(self.ui_last_script_output)
imgui.pop_text_wrap_pos()
imgui.end_child()
else:
imgui.input_text_multiline("##lso_o", self.ui_last_script_output, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
if self._is_script_blinking:
imgui.pop_style_color(2)
imgui.end()
if self.show_text_viewer:
imgui.set_next_window_size(imgui.ImVec2(900, 700), imgui.Cond_.first_use_ever)
expanded, opened = imgui.begin(f"Text Viewer - {self.text_viewer_title}", self.show_text_viewer)
self.show_text_viewer = bool(opened)
if expanded:
if self.ui_word_wrap:
imgui.begin_child("tv_wrap", imgui.ImVec2(-1, -1), False)
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(self.text_viewer_content)
imgui.pop_text_wrap_pos()
imgui.end_child()
else:
imgui.input_text_multiline("##tv_c", self.text_viewer_content, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
imgui.end()
# Inject File Modal
if getattr(self, "show_inject_modal", False):
imgui.open_popup("Inject File")
self.show_inject_modal = False
if imgui.begin_popup_modal("Inject File", None, imgui.WindowFlags_.always_auto_resize)[0]:
files = self.project.get('files', {}).get('paths', [])
imgui.text("Select File to Inject:")
imgui.begin_child("inject_file_list", imgui.ImVec2(0, 200), True)
for f_path in files:
is_selected = (self._inject_file_path == f_path)
if imgui.selectable(f_path, is_selected)[0]:
self._inject_file_path = f_path
self.controller._update_inject_preview()
imgui.end_child()
imgui.separator()
if imgui.radio_button("Skeleton", self._inject_mode == "skeleton"):
self._inject_mode = "skeleton"
self.controller._update_inject_preview()
imgui.same_line()
if imgui.radio_button("Full", self._inject_mode == "full"):
self._inject_mode = "full"
self.controller._update_inject_preview()
imgui.separator()
imgui.text("Preview:")
imgui.begin_child("inject_preview_area", imgui.ImVec2(600, 300), True)
imgui.text_unformatted(self._inject_preview)
imgui.end_child()
imgui.separator()
if imgui.button("Inject", imgui.ImVec2(120, 0)):
formatted = f"## File: {self._inject_file_path}\n```python\n{self._inject_preview}\n```\n"
with self._disc_entries_lock:
self.disc_entries.append({
"role": "Context",
"content": formatted,
"collapsed": True,
"ts": project_manager.now_ts()
})
self._scroll_disc_to_bottom = True
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Cancel", imgui.ImVec2(120, 0)):
imgui.close_current_popup()
imgui.end_popup()
except Exception as e:
print(f"ERROR in _gui_func: {e}")
import traceback
traceback.print_exc()
if pushed_prior_tint:
imgui.pop_style_color()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_gui_func")
def _render_save_preset_modal(self) -> None:
if not self._show_save_preset_modal: return
imgui.open_popup("Save Layout Preset")
if imgui.begin_popup_modal("Save Layout Preset", True, imgui.WindowFlags_.always_auto_resize)[0]:
imgui.text("Preset Name:")
_, self._new_preset_name = imgui.input_text("##preset_name", self._new_preset_name)
if imgui.button("Save", imgui.ImVec2(120, 0)):
if self._new_preset_name.strip():
ini_data = imgui.save_ini_settings_to_memory()
self.layout_presets[self._new_preset_name.strip()] = {
"ini": ini_data,
"multi_viewport": self.ui_multi_viewport
}
self.config["layout_presets"] = self.layout_presets
models.save_config(self.config)
self._show_save_preset_modal = False
self._new_preset_name = ""
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Cancel", imgui.ImVec2(120, 0)):
self._show_save_preset_modal = False
imgui.close_current_popup()
imgui.end_popup()
def _render_preset_manager_modal(self) -> None:
if not self.show_preset_manager_modal: return
imgui.open_popup("Preset Manager")
opened, self.show_preset_manager_modal = imgui.begin_popup_modal("Preset Manager", self.show_preset_manager_modal)
if opened:
try:
avail = imgui.get_content_region_avail()
imgui.begin_child("preset_list_area", imgui.ImVec2(250, avail.y), True)
try:
preset_names = sorted(self.controller.presets.keys())
if imgui.button("New Preset", imgui.ImVec2(-1, 0)):
self._editing_preset_name = ""
self._editing_preset_content = ""
self._editing_preset_temperature = 0.0
self._editing_preset_top_p = 1.0
self._editing_preset_max_output_tokens = 4096
self._editing_preset_scope = "project"
self._editing_preset_is_new = True
imgui.separator()
for name in preset_names:
p = self.controller.presets[name]
is_sel = (name == self._editing_preset_name)
if imgui.selectable(name, is_sel)[0]:
self._editing_preset_name = name
self._editing_preset_content = p.system_prompt
self._editing_preset_temperature = p.temperature if p.temperature is not None else 0.0
self._editing_preset_top_p = p.top_p if p.top_p is not None else 1.0
self._editing_preset_max_output_tokens = p.max_output_tokens if p.max_output_tokens is not None else 4096
self._editing_preset_is_new = False
finally:
imgui.end_child()
imgui.same_line()
imgui.begin_child("preset_edit_area", imgui.ImVec2(0, avail.y), False)
try:
p_name = self._editing_preset_name or "(New Preset)"
imgui.text_colored(C_IN, f"Editing Preset: {p_name}")
imgui.separator()
imgui.text("Name:")
_, self._editing_preset_name = imgui.input_text("##edit_name", self._editing_preset_name)
imgui.text("Scope:")
if imgui.radio_button("Global", self._editing_preset_scope == "global"):
self._editing_preset_scope = "global"
imgui.same_line()
if imgui.radio_button("Project", self._editing_preset_scope == "project"):
self._editing_preset_scope = "project"
imgui.text("Content:")
_, self._editing_preset_content = imgui.input_text_multiline("##edit_content", self._editing_preset_content, imgui.ImVec2(-1, 280))
imgui.text("Temperature:")
_, self._editing_preset_temperature = imgui.input_float("##edit_temp", self._editing_preset_temperature, 0.1, 1.0, "%.2f")
imgui.text("Top P:")
_, self._editing_preset_top_p = imgui.input_float("##edit_top_p", self._editing_preset_top_p, 0.1, 1.0, "%.2f")
imgui.text("Max Output Tokens:")
_, self._editing_preset_max_output_tokens = imgui.input_int("##edit_max_tokens", self._editing_preset_max_output_tokens)
if imgui.button("Save", imgui.ImVec2(120, 0)):
if self._editing_preset_name.strip():
self.controller._cb_save_preset(
self._editing_preset_name.strip(),
self._editing_preset_content,
self._editing_preset_temperature,
self._editing_preset_top_p,
self._editing_preset_max_output_tokens,
self._editing_preset_scope
)
self.ai_status = f"Preset '{self._editing_preset_name.strip()}' saved to {self._editing_preset_scope}"
imgui.set_item_tooltip("Save the current preset settings")
imgui.same_line()
if imgui.button("Delete", imgui.ImVec2(120, 0)):
if self._editing_preset_name.strip():
try:
self.controller._cb_delete_preset(self._editing_preset_name.strip(), self._editing_preset_scope)
self.ai_status = f"Preset '{self._editing_preset_name}' deleted from {self._editing_preset_scope}"
self._editing_preset_name = ""
self._editing_preset_content = ""
except Exception as e:
self.ai_status = f"Error deleting: {e}"
imgui.set_item_tooltip("Delete the selected preset")
imgui.same_line()
if imgui.button("Close", imgui.ImVec2(120, 0)):
self.show_preset_manager_modal = False
imgui.close_current_popup()
finally:
imgui.end_child()
finally:
imgui.end_popup()
def _render_tool_preset_manager_modal(self) -> None:
if not self.show_tool_preset_manager_modal: return
imgui.open_popup("Tool Preset Manager")
opened, self.show_tool_preset_manager_modal = imgui.begin_popup_modal("Tool Preset Manager", self.show_tool_preset_manager_modal)
if opened:
try:
avail = imgui.get_content_region_avail()
# Left Column: Listbox
imgui.begin_child("tool_preset_list_area", imgui.ImVec2(250, avail.y), True)
try:
if imgui.button("New Tool Preset", imgui.ImVec2(-1, 0)):
self._editing_tool_preset_name = ""
self._editing_tool_preset_categories = {cat: {} for cat in models.DEFAULT_TOOL_CATEGORIES}
self._editing_tool_preset_scope = "project"
self._selected_tool_preset_idx = -1
if imgui.is_item_hovered():
imgui.set_tooltip("Create a new tool preset configuration.")
imgui.separator()
preset_names = sorted(self.controller.tool_presets.keys())
for i, name in enumerate(preset_names):
is_selected = (self._selected_tool_preset_idx == i)
if imgui.selectable(name, is_selected)[0]:
self._selected_tool_preset_idx = i
self._editing_tool_preset_name = name
preset = self.controller.tool_presets[name]
self._editing_tool_preset_categories = {cat: {} for cat in models.DEFAULT_TOOL_CATEGORIES}
for cat, tools in preset.categories.items():
self._editing_tool_preset_categories[cat] = copy.deepcopy(tools)
finally:
imgui.end_child()
imgui.same_line()
# Right Column: Edit Area
imgui.begin_child("tool_preset_edit_area", imgui.ImVec2(0, avail.y), False)
try:
p_name = self._editing_tool_preset_name or "(New Tool Preset)"
imgui.text_colored(C_IN, f"Editing Tool Preset: {p_name}")
imgui.separator()
imgui.dummy(imgui.ImVec2(0, 8))
imgui.text("Name:")
_, self._editing_tool_preset_name = imgui.input_text("##edit_tp_name", self._editing_tool_preset_name)
imgui.dummy(imgui.ImVec2(0, 8))
imgui.text("Scope:")
if imgui.radio_button("Global", self._editing_tool_preset_scope == "global"):
self._editing_tool_preset_scope = "global"
imgui.same_line()
if imgui.radio_button("Project", self._editing_tool_preset_scope == "project"):
self._editing_tool_preset_scope = "project"
imgui.dummy(imgui.ImVec2(0, 8))
imgui.text("Categories & Tools:")
imgui.begin_child("tp_categories_scroll", imgui.ImVec2(0, 300), True)
try:
for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items():
if imgui.tree_node(cat_name):
if cat_name not in self._editing_tool_preset_categories:
self._editing_tool_preset_categories[cat_name] = []
current_cat_tools = self._editing_tool_preset_categories[cat_name] # List of Tool
for tool_name in default_tools:
# Find existing Tool object in list
tool = next((t for t in current_cat_tools if t.name == tool_name), None)
mode = "disabled" if tool is None else tool.approval
if imgui.radio_button(f"Off##{cat_name}_{tool_name}", mode == "disabled"):
if tool: current_cat_tools.remove(tool)
imgui.same_line()
if imgui.radio_button(f"Auto##{cat_name}_{tool_name}", mode == "auto"):
if not tool:
tool = models.Tool(name=tool_name, approval="auto")
current_cat_tools.append(tool)
else:
tool.approval = "auto"
imgui.same_line()
if imgui.radio_button(f"Ask##{cat_name}_{tool_name}", mode == "ask"):
if not tool:
tool = models.Tool(name=tool_name, approval="ask")
current_cat_tools.append(tool)
else:
tool.approval = "ask"
imgui.same_line()
imgui.text(tool_name)
if tool:
imgui.same_line(250)
imgui.set_next_item_width(100)
_, tool.weight = imgui.slider_int(f"Weight##{cat_name}_{tool_name}", tool.weight, 1, 5)
imgui.same_line()
pb_str = json.dumps(tool.parameter_bias)
imgui.set_next_item_width(150)
ch_pb, pb_new = imgui.input_text(f"Params##{cat_name}_{tool_name}", pb_str)
if ch_pb:
try: tool.parameter_bias = json.loads(pb_new)
except: pass
imgui.tree_pop()
finally:
imgui.end_child()
imgui.separator()
imgui.text_colored(C_SUB, "Bias Profiles")
imgui.begin_child("bias_profiles_area", imgui.ImVec2(0, 200), True)
try:
avail_bias = imgui.get_content_region_avail()
imgui.begin_child("bias_list", imgui.ImVec2(200, avail_bias.y), False)
if imgui.button("New Profile", imgui.ImVec2(-1, 0)):
self._editing_bias_profile_name = ""
self._editing_bias_profile_tool_weights = "{}"
self._editing_bias_profile_category_multipliers = "{}"
self._selected_bias_profile_idx = -1
imgui.separator()
bnames = sorted(self.bias_profiles.keys())
for i, bname in enumerate(bnames):
is_sel = (self._selected_bias_profile_idx == i)
if imgui.selectable(bname, is_sel)[0]:
self._selected_bias_profile_idx = i
self._editing_bias_profile_name = bname
profile = self.bias_profiles[bname]
self._editing_bias_profile_tool_weights = json.dumps(profile.tool_weights, indent=1)
self._editing_bias_profile_category_multipliers = json.dumps(profile.category_multipliers, indent=1)
imgui.end_child()
imgui.same_line()
imgui.begin_child("bias_edit", imgui.ImVec2(0, avail_bias.y), False)
imgui.text("Name:")
_, self._editing_bias_profile_name = imgui.input_text("##b_name", self._editing_bias_profile_name)
imgui.text("Tool Weights (JSON):")
_, self._editing_bias_profile_tool_weights = imgui.input_text_multiline("##b_tw", self._editing_bias_profile_tool_weights, imgui.ImVec2(-1, 60))
imgui.text("Category Multipliers (JSON):")
_, self._editing_bias_profile_category_multipliers = imgui.input_text_multiline("##b_cm", self._editing_bias_profile_category_multipliers, imgui.ImVec2(-1, 60))
if imgui.button("Save Profile"):
try:
tw = json.loads(self._editing_bias_profile_tool_weights)
cm = json.loads(self._editing_bias_profile_category_multipliers)
prof = models.BiasProfile(name=self._editing_bias_profile_name, tool_weights=tw, category_multipliers=cm)
self.controller._cb_save_bias_profile(prof, self._editing_tool_preset_scope)
self.ai_status = f"Bias profile '{prof.name}' saved"
except Exception as e:
self.ai_status = f"Error: {e}"
imgui.same_line()
if imgui.button("Delete Profile"):
self.controller._cb_delete_bias_profile(self._editing_bias_profile_name, self._editing_tool_preset_scope)
self.ai_status = f"Bias profile deleted"
imgui.end_child()
finally:
imgui.end_child()
imgui.dummy(imgui.ImVec2(0, 8))
if imgui.button("Save", imgui.ImVec2(100, 0)):
if self._editing_tool_preset_name.strip():
self.controller._cb_save_tool_preset(
self._editing_tool_preset_name.strip(),
self._editing_tool_preset_categories,
self._editing_tool_preset_scope
)
self.ai_status = f"Tool preset '{self._editing_tool_preset_name}' saved"
if imgui.is_item_hovered():
imgui.set_tooltip("Save the current tool preset configuration.")
imgui.same_line()
if imgui.button("Delete", imgui.ImVec2(100, 0)):
if self._editing_tool_preset_name.strip():
self.controller._cb_delete_tool_preset(
self._editing_tool_preset_name.strip(),
self._editing_tool_preset_scope
)
self.ai_status = f"Tool preset '{self._editing_tool_preset_name}' deleted"
self._editing_tool_preset_name = ""
self._editing_tool_preset_categories = {}
self._selected_tool_preset_idx = -1
if imgui.is_item_hovered():
imgui.set_tooltip("Delete this tool preset permanently.")
imgui.same_line()
if imgui.button("Close", imgui.ImVec2(100, 0)):
self.show_tool_preset_manager_modal = False
imgui.close_current_popup()
finally:
imgui.end_child()
finally:
imgui.end_popup()
def _render_projects_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_projects_panel")
proj_name = self.project.get("project", {}).get("name", Path(self.active_project_path).stem)
imgui.text_colored(C_IN, f"Active: {proj_name}")
imgui.separator()
imgui.text("Git Directory")
ch, self.ui_project_git_dir = imgui.input_text("##git_dir", self.ui_project_git_dir)
imgui.same_line()
if imgui.button("Browse##git"):
r = hide_tk_root()
d = filedialog.askdirectory(title="Select Git Directory")
r.destroy()
if d: self.ui_project_git_dir = d
imgui.separator()
imgui.text("Main Context File")
ch, self.ui_project_main_context = imgui.input_text("##main_ctx", self.ui_project_main_context)
imgui.same_line()
if imgui.button("Browse##ctx"):
r = hide_tk_root()
p = filedialog.askopenfilename(title="Select Main Context File")
r.destroy()
if p: self.ui_project_main_context = p
imgui.separator()
imgui.text("Output Dir")
ch, self.ui_output_dir = imgui.input_text("##out_dir", self.ui_output_dir)
imgui.same_line()
if imgui.button("Browse##out"):
r = hide_tk_root()
d = filedialog.askdirectory(title="Select Output Dir")
r.destroy()
if d: self.ui_output_dir = d
imgui.separator()
imgui.text("Project Files")
imgui.begin_child("proj_files", imgui.ImVec2(0, 150), True)
for i, pp in enumerate(self.project_paths):
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])
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 is_active: imgui.pop_style_color()
imgui.same_line()
imgui.text_colored(C_LBL, pp)
imgui.end_child()
if imgui.button("Add Project"):
r = hide_tk_root()
p = filedialog.askopenfilename(
title="Select Project .toml",
filetypes=[("TOML", "*.toml"), ("All", "*.*")],
)
r.destroy()
if p and p not in self.project_paths:
self.project_paths.append(p)
imgui.same_line()
if imgui.button("New Project"):
r = hide_tk_root()
p = filedialog.asksaveasfilename(title="Create New Project .toml", defaultextension=".toml", filetypes=[("TOML", "*.toml"), ("All", "*.*")])
r.destroy()
if p:
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)
self._switch_project(p)
imgui.same_line()
if imgui.button("Save All"):
self._flush_to_project()
self._save_active_project()
self._flush_to_config()
models.save_config(self.config)
self.ai_status = "config saved"
ch, self.ui_word_wrap = imgui.checkbox("Word-Wrap (Read-only panels)", self.ui_word_wrap)
ch, self.ui_summary_only = imgui.checkbox("Summary Only (send file structure, not full content)", self.ui_summary_only)
ch, self.ui_auto_scroll_comms = imgui.checkbox("Auto-scroll Comms History", self.ui_auto_scroll_comms)
ch, self.ui_auto_scroll_tool_calls = imgui.checkbox("Auto-scroll Tool History", self.ui_auto_scroll_tool_calls)
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_projects_panel")
def _render_track_proposal_modal(self) -> None:
if self._show_track_proposal_modal:
imgui.open_popup("Track Proposal")
if imgui.begin_popup_modal("Track Proposal", True, imgui.WindowFlags_.always_auto_resize)[0]:
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)
# Render soft shadow behind the modal
shaders.draw_soft_shadow(imgui.get_background_draw_list(), p_min, p_max, imgui.ImVec4(0, 0, 0, 0.6), 25.0, 6.0)
if not self._show_track_proposal_modal:
imgui.close_current_popup()
imgui.end_popup()
return
imgui.text_colored(C_IN, "Proposed Implementation Tracks")
imgui.separator()
if not self.proposed_tracks:
imgui.text("No tracks generated.")
else:
for idx, track in enumerate(self.proposed_tracks):
# Title Edit
changed_t, new_t = imgui.input_text(f"Title##{idx}", track.get('title', ''))
if changed_t:
track['title'] = new_t
# Goal Edit
changed_g, new_g = imgui.input_text_multiline(f"Goal##{idx}", track.get('goal', ''), imgui.ImVec2(-1, 60))
if changed_g:
track['goal'] = new_g
# Buttons
if imgui.button(f"Remove##{idx}"):
self.proposed_tracks.pop(idx)
break
imgui.same_line()
if imgui.button(f"Start This Track##{idx}"):
self._cb_start_track(idx)
imgui.separator()
if imgui.button("Accept", imgui.ImVec2(120, 0)):
self._cb_accept_tracks()
self._show_track_proposal_modal = False
imgui.close_current_popup()
imgui.same_line()
if imgui.button("Cancel", imgui.ImVec2(120, 0)):
self._show_track_proposal_modal = False
imgui.close_current_popup()
imgui.end_popup()
def _render_patch_modal(self) -> None:
if not self._show_patch_modal:
return
imgui.open_popup("Apply Patch?")
if imgui.begin_popup_modal("Apply Patch?", True, imgui.WindowFlags_.always_auto_resize)[0]:
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)
imgui.text_colored(vec4(255, 230, 77), "Tier 4 QA Generated a Patch")
imgui.separator()
if self._pending_patch_files:
imgui.text("Files to modify:")
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}")
imgui.separator()
imgui.text("Diff Preview:")
imgui.begin_child("patch_diff_scroll", imgui.ImVec2(-1, 280), True)
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)
imgui.end_child()
imgui.separator()
if imgui.button("Apply Patch"):
self._apply_pending_patch()
imgui.same_line()
if imgui.button("Reject"):
self._show_patch_modal = False
self._pending_patch_text = None
self._pending_patch_files = []
self._patch_error_message = None
imgui.close_current_popup()
imgui.end_popup()
def _apply_pending_patch(self) -> None:
if not self._pending_patch_text:
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:
self._show_patch_modal = False
self._pending_patch_text = None
self._pending_patch_files = []
self._patch_error_message = None
imgui.close_current_popup()
else:
self._patch_error_message = msg
except Exception as e:
self._patch_error_message = str(e)
def request_patch_from_tier4(self, error: str, file_context: str) -> None:
try:
from src import ai_client
from src.diff_viewer import parse_diff
patch_text = ai_client.run_tier4_patch_generation(error, file_context)
if patch_text and "---" in patch_text and "+++" in patch_text:
diff_files = parse_diff(patch_text)
file_paths = [df.old_path for df in diff_files]
self._pending_patch_text = patch_text
self._pending_patch_files = file_paths
self._show_patch_modal = True
else:
self._patch_error_message = patch_text or "No patch generated"
except Exception as e:
self._patch_error_message = str(e)
def _render_log_management(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_log_management")
exp, opened = imgui.begin("Log Management", self.show_windows["Log Management"])
self.show_windows["Log Management"] = bool(opened)
if not exp:
imgui.end()
return
if self._log_registry is None:
self._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml"))
else:
if imgui.button("Refresh Registry"):
self._log_registry = log_registry.LogRegistry(str(paths.get_logs_dir() / "log_registry.toml"))
imgui.same_line()
if imgui.button("Load Log"):
self.cb_load_prior_log()
imgui.same_line()
if imgui.button("Force Prune Logs"):
self.controller.event_queue.put("gui_task", {"action": "click", "item": "btn_prune_logs"})
registry = self._log_registry
sessions = registry.data
if imgui.begin_table("sessions_table", 7, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
imgui.table_setup_column("Session ID")
imgui.table_setup_column("Start Time")
imgui.table_setup_column("Star")
imgui.table_setup_column("Reason")
imgui.table_setup_column("Size (KB)")
imgui.table_setup_column("Msgs")
imgui.table_setup_column("Actions")
imgui.table_headers_row()
for session_id, s_data in sessions.items():
imgui.table_next_row()
imgui.table_next_column()
imgui.text(session_id)
imgui.table_next_column()
imgui.text(s_data.get("start_time", ""))
imgui.table_next_column()
whitelisted = s_data.get("whitelisted", False)
if whitelisted:
imgui.text_colored(vec4(255, 215, 0), "YES")
else:
imgui.text("NO")
metadata = s_data.get("metadata") or {}
imgui.table_next_column()
imgui.text(metadata.get("reason", ""))
imgui.table_next_column()
imgui.text(str(metadata.get("size_kb", "")))
imgui.table_next_column()
imgui.text(str(metadata.get("message_count", "")))
imgui.table_next_column()
if imgui.button(f"Load##{session_id}"):
self.cb_load_prior_log(s_data.get("path"))
imgui.same_line()
if whitelisted:
if imgui.button(f"Unstar##{session_id}"):
registry.update_session_metadata(
session_id,
message_count=int(metadata.get("message_count") or 0),
errors=int(metadata.get("errors") or 0),
size_kb=int(metadata.get("size_kb") or 0),
whitelisted=False,
reason=str(metadata.get("reason") or "")
)
else:
if imgui.button(f"Star##{session_id}"):
registry.update_session_metadata(
session_id,
message_count=int(metadata.get("message_count") or 0),
errors=int(metadata.get("errors") or 0),
size_kb=int(metadata.get("size_kb") or 0),
whitelisted=True,
reason="Manually whitelisted"
)
imgui.end_table()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_log_management")
imgui.end()
def _render_diagnostics_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_diagnostics_panel")
exp, opened = imgui.begin("Diagnostics", self.show_windows.get("Diagnostics", False))
self.show_windows["Diagnostics"] = bool(opened)
if not exp:
imgui.end()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_diagnostics_panel")
return
metrics = self.perf_monitor.get_metrics()
imgui.text("Performance Telemetry")
imgui.same_line()
_, self.perf_profiling_enabled = imgui.checkbox("Enable Profiling", self.perf_profiling_enabled)
imgui.separator()
if imgui.begin_table("perf_table", 3, imgui.TableFlags_.borders_inner_h):
imgui.table_setup_column("Metric")
imgui.table_setup_column("Value")
imgui.table_setup_column("Graph")
imgui.table_headers_row()
for label, key, format_str in [
("FPS", "fps", "%.1f"),
("Frame Time (ms)", "frame_time_ms", "%.2f"),
("CPU %", "cpu_percent", "%.1f"),
("Input Lag (ms)", "input_lag_ms", "%.1f")
]:
imgui.table_next_row()
imgui.table_next_column()
imgui.text(label)
imgui.table_next_column()
if key == "fps":
avg_val = imgui.get_io().framerate
else:
avg_val = metrics.get(f"{key}_avg", metrics.get(key, 0.0))
imgui.text(format_str % avg_val)
imgui.table_next_column()
self.perf_show_graphs.setdefault(key, False)
_, self.perf_show_graphs[key] = imgui.checkbox(f"##g_{key}", self.perf_show_graphs[key])
imgui.end_table()
if self.perf_profiling_enabled:
imgui.separator()
imgui.text("Detailed Component Timings (Moving Average)")
if imgui.begin_table("comp_timings", 3, imgui.TableFlags_.borders):
imgui.table_setup_column("Component")
imgui.table_setup_column("Avg (ms)")
imgui.table_setup_column("Graph")
imgui.table_headers_row()
for key, val in metrics.items():
if key.startswith("time_") and key.endswith("_ms") and not key.endswith("_avg"):
comp_name = key[5:-3]
avg_val = metrics.get(f"{key}_avg", val)
imgui.table_next_row()
imgui.table_next_column()
imgui.text(comp_name)
imgui.table_next_column()
if avg_val > 10.0:
imgui.text_colored(imgui.ImVec4(1.0, 0.2, 0.2, 1.0), f"{avg_val:.2f}")
else:
imgui.text(f"{avg_val:.2f}")
imgui.table_next_column()
self.perf_show_graphs.setdefault(comp_name, False)
_, self.perf_show_graphs[comp_name] = imgui.checkbox(f"##g_{comp_name}", self.perf_show_graphs[comp_name])
imgui.end_table()
imgui.separator()
imgui.text("Performance Graphs")
for key, show in self.perf_show_graphs.items():
if show:
imgui.text(f"History: {key}")
hist_data = self.perf_monitor.get_history(key)
if hist_data:
import numpy as np
imgui.plot_lines(f"##plot_{key}", np.array(hist_data, dtype=np.float32), graph_size=imgui.ImVec2(-1, 60))
else:
imgui.text_disabled(f"(no history data for {key})")
imgui.separator()
imgui.text("Diagnostic Log")
if imgui.begin_table("diag_log_table", 3, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
imgui.table_setup_column("Timestamp", imgui.TableColumnFlags_.width_fixed, 150)
imgui.table_setup_column("Type", imgui.TableColumnFlags_.width_fixed, 100)
imgui.table_setup_column("Message")
imgui.table_headers_row()
for entry in reversed(self.controller.diagnostic_log):
imgui.table_next_row()
imgui.table_next_column()
imgui.text(entry.get("ts", ""))
imgui.table_next_column()
imgui.text(entry.get("type", ""))
imgui.table_next_column()
imgui.text_wrapped(entry.get("message", ""))
imgui.end_table()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_diagnostics_panel")
imgui.end()
def _render_markdown_test(self) -> None:
imgui.text("Markdown Test Panel")
imgui.separator()
md = """
# Header 1
## Header 2
### Header 3
This is **bold** text and *italic* text.
And ***bold italic*** text.
* List item 1
* List item 2
* Sub-item
[Link to Google](https://google.com)
```python
def hello():
print("Markdown works!")
```
"""
markdown_helper.render(md)
def _render_files_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_files_panel")
imgui.text("Paths")
imgui.same_line()
imgui.text("| Base Dir:")
imgui.same_line()
imgui.set_next_item_width(-100)
ch, self.ui_files_base_dir = imgui.input_text("##f_base", self.ui_files_base_dir)
imgui.same_line()
if imgui.button("Browse##fb"):
r = hide_tk_root()
d = filedialog.askdirectory()
r.destroy()
if d: self.ui_files_base_dir = d
imgui.separator()
imgui.begin_child("f_paths", imgui.ImVec2(0, -40), True)
if imgui.begin_table("files_table", 4, imgui.TableFlags_.resizable | imgui.TableFlags_.borders):
imgui.table_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 40)
imgui.table_setup_column("File Path", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 150)
imgui.table_setup_column("Cache", imgui.TableColumnFlags_.width_fixed, 40)
imgui.table_headers_row()
for i, f_item in enumerate(self.files):
imgui.table_next_row()
# Actions
imgui.table_set_column_index(0)
if imgui.button(f"x##f{i}"):
self.files.pop(i)
break
# File Path
imgui.table_set_column_index(1)
imgui.text(f_item.path if hasattr(f_item, "path") else str(f_item))
# Flags
imgui.table_set_column_index(2)
if hasattr(f_item, "auto_aggregate"):
changed_agg, f_item.auto_aggregate = imgui.checkbox(f"Agg##a{i}", f_item.auto_aggregate)
imgui.same_line()
changed_full, f_item.force_full = imgui.checkbox(f"Full##f{i}", f_item.force_full)
# Cache
imgui.table_set_column_index(3)
path = f_item.path if hasattr(f_item, "path") else str(f_item)
is_cached = any(path in c for c in getattr(self, "_cached_files", []))
if is_cached:
imgui.text_colored("", imgui.ImVec4(0, 1, 0, 1)) # Green dot
else:
imgui.text_disabled("")
imgui.end_table()
imgui.end_child()
if imgui.button("Add File(s)"):
r = hide_tk_root()
paths = filedialog.askopenfilenames()
r.destroy()
for p in paths:
if p not in [f.path if hasattr(f, "path") else f for f in self.files]:
self.files.append(models.FileItem(path=p))
imgui.same_line()
if imgui.button("Add Wildcard"):
r = hide_tk_root()
d = filedialog.askdirectory()
r.destroy()
if d: self.files.append(models.FileItem(path=str(Path(d) / "**" / "*")))
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_files_panel")
def _render_screenshots_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_screenshots_panel")
imgui.text("Paths")
imgui.same_line()
imgui.text("| Base Dir:")
imgui.same_line()
imgui.set_next_item_width(-100)
ch, self.ui_shots_base_dir = imgui.input_text("##s_base", self.ui_shots_base_dir)
imgui.same_line()
if imgui.button("Browse##sb"):
r = hide_tk_root()
d = filedialog.askdirectory()
r.destroy()
if d: self.ui_shots_base_dir = d
imgui.separator()
imgui.begin_child("s_paths", imgui.ImVec2(0, -40), True)
for i, s in enumerate(self.screenshots):
if imgui.button(f"x##s{i}"):
self.screenshots.pop(i)
break
imgui.same_line()
imgui.text(s)
imgui.end_child()
if imgui.button("Add Screenshot(s)"):
r = hide_tk_root()
paths = filedialog.askopenfilenames(
title="Select Screenshots",
filetypes=[("Images", "*.png *.jpg *.jpeg *.gif *.bmp *.webp"), ("All", "*.*")],
)
r.destroy()
for p in paths:
if p not in self.screenshots: self.screenshots.append(p)
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_screenshots_panel")
def _render_discussion_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_discussion_panel")
# THINKING indicator
is_thinking = self.ai_status in ["sending..."]
if is_thinking:
val = math.sin(time.time() * 10 * math.pi)
alpha = 1.0 if val > 0 else 0.0
c = vec4(255, 100, 100, alpha)
if theme.is_nerv_active():
c = vec4(255, 50, 50, alpha) # More vibrant for NERV
imgui.text_colored(c, "THINKING...")
imgui.separator()
# Prior session viewing mode
if self.is_viewing_prior_session:
imgui.push_style_color(imgui.Col_.child_bg, vec4(50, 40, 20))
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
imgui.same_line()
if imgui.button("Exit Prior Session"):
self.is_viewing_prior_session = False
self.prior_session_entries.clear()
self.prior_disc_entries.clear()
self._comms_log_dirty = True
imgui.separator()
imgui.begin_child("prior_scroll", imgui.ImVec2(0, 0), False)
clipper = imgui.ListClipper()
clipper.begin(len(self.prior_disc_entries))
while clipper.step():
for idx in range(clipper.display_start, clipper.display_end):
entry = self.prior_disc_entries[idx]
imgui.push_id(f"prior_disc_{idx}")
collapsed = entry.get("collapsed", False)
if imgui.button("+" if collapsed else "-"):
entry["collapsed"] = not collapsed
imgui.same_line()
role = entry.get("role", "??")
ts = entry.get("ts", "")
imgui.text_colored(C_LBL, f"[{role}]")
if ts:
imgui.same_line()
imgui.text_colored(vec4(160, 160, 160), str(ts))
content = entry.get("content", "")
if collapsed:
imgui.same_line()
preview = content.replace("\n", " ")[:80]
if len(content) > 80: preview += "..."
imgui.text_colored(vec4(180, 180, 180), preview)
else:
is_nerv = theme.is_nerv_active()
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
markdown_helper.render(content, context_id=f'prior_disc_{idx}')
if is_nerv: imgui.pop_style_color()
imgui.separator()
imgui.pop_id()
imgui.end_child()
imgui.pop_style_color()
return
if not self.is_viewing_prior_session and imgui.collapsing_header("Discussions", imgui.TreeNodeFlags_.default_open):
names = self._get_discussion_names()
if imgui.begin_combo("##disc_sel", self.active_discussion):
for name in names:
is_selected = (name == self.active_discussion)
if imgui.selectable(name, is_selected)[0]:
self._switch_discussion(name)
if is_selected:
imgui.set_item_default_focus()
imgui.end_combo()
if self.active_track:
imgui.same_line()
changed, self._track_discussion_active = imgui.checkbox("Track Discussion", self._track_discussion_active)
if changed:
if self._track_discussion_active:
self._flush_disc_entries_to_project()
history_strings = project_manager.load_track_history(self.active_track.id, self.ui_files_base_dir)
with self._disc_entries_lock:
self.disc_entries = models.parse_history_entries(history_strings, self.disc_roles)
self.ai_status = f"track discussion: {self.active_track.id}"
else:
self._flush_disc_entries_to_project()
# Restore project discussion
self._switch_discussion(self.active_discussion)
disc_sec = self.project.get("discussion", {})
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
git_commit = disc_data.get("git_commit", "")
last_updated = disc_data.get("last_updated", "")
imgui.text_colored(C_LBL, "commit:")
imgui.same_line()
self._render_selectable_label('git_commit_val', git_commit[:12] if git_commit else '(none)', width=100, color=(C_IN if git_commit else C_LBL))
imgui.same_line()
if imgui.button("Update Commit"):
git_dir = self.ui_project_git_dir
if git_dir:
cmt = project_manager.get_git_commit(git_dir)
if cmt:
disc_data["git_commit"] = cmt
disc_data["last_updated"] = project_manager.now_ts()
self.ai_status = f"commit: {cmt[:12]}"
imgui.text_colored(C_LBL, "updated:")
imgui.same_line()
imgui.text_colored(C_SUB, last_updated if last_updated else "(never)")
ch, self.ui_disc_new_name_input = imgui.input_text("##new_disc", self.ui_disc_new_name_input)
imgui.same_line()
if imgui.button("Create"):
nm = self.ui_disc_new_name_input.strip()
if nm: self._create_discussion(nm); self.ui_disc_new_name_input = ""
imgui.same_line()
if imgui.button("Rename"):
nm = self.ui_disc_new_name_input.strip()
if nm: self._rename_discussion(self.active_discussion, nm); self.ui_disc_new_name_input = ""
imgui.same_line()
if imgui.button("Delete"):
self._delete_discussion(self.active_discussion)
if not self.is_viewing_prior_session:
imgui.separator()
if imgui.button("+ Entry"):
self.disc_entries.append({"role": self.disc_roles[0] if self.disc_roles else "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
imgui.same_line()
if imgui.button("-All"):
for e in self.disc_entries: e["collapsed"] = True
imgui.same_line()
if imgui.button("+All"):
for e in self.disc_entries: e["collapsed"] = False
imgui.same_line()
if imgui.button("Clear All"):
self.disc_entries.clear()
imgui.same_line()
if imgui.button("Save"):
self._flush_to_project()
self._save_active_project()
self._flush_to_config()
models.save_config(self.config)
self.ai_status = "discussion saved"
ch, self.ui_auto_add_history = imgui.checkbox("Auto-add message & response to history", self.ui_auto_add_history)
# Truncation controls
imgui.text("Keep Pairs:")
imgui.same_line()
imgui.set_next_item_width(80)
ch, self.ui_disc_truncate_pairs = imgui.input_int("##trunc_pairs", self.ui_disc_truncate_pairs, 1)
if self.ui_disc_truncate_pairs < 1: self.ui_disc_truncate_pairs = 1
imgui.same_line()
if imgui.button("Truncate"):
with self._disc_entries_lock:
self.disc_entries = truncate_entries(self.disc_entries, self.ui_disc_truncate_pairs)
self.ai_status = f"history truncated to {self.ui_disc_truncate_pairs} pairs"
imgui.separator()
if imgui.collapsing_header("Roles"):
imgui.begin_child("roles_scroll", imgui.ImVec2(0, 100), True)
for i, r in enumerate(self.disc_roles):
if imgui.button(f"x##r{i}"):
self.disc_roles.pop(i)
break
imgui.same_line()
imgui.text(r)
imgui.end_child()
ch, self.ui_disc_new_role_input = imgui.input_text("##new_role", self.ui_disc_new_role_input)
imgui.same_line()
if imgui.button("Add"):
r = self.ui_disc_new_role_input.strip()
if r and r not in self.disc_roles:
self.disc_roles.append(r)
self.ui_disc_new_role_input = ""
imgui.separator()
imgui.begin_child("disc_scroll", imgui.ImVec2(0, 0), False)
clipper = imgui.ListClipper()
clipper.begin(len(self.disc_entries))
while clipper.step():
for i in range(clipper.display_start, clipper.display_end):
entry = self.disc_entries[i]
imgui.push_id(str(i))
collapsed = entry.get("collapsed", False)
read_mode = entry.get("read_mode", False)
if imgui.button("+" if collapsed else "-"):
entry["collapsed"] = not collapsed
imgui.same_line()
self._render_text_viewer(f"Entry #{i+1}", entry["content"])
imgui.same_line()
imgui.set_next_item_width(120)
if imgui.begin_combo("##role", entry["role"]):
for r in self.disc_roles:
if imgui.selectable(r, r == entry["role"])[0]:
entry["role"] = r
imgui.end_combo()
if not collapsed:
imgui.same_line()
if imgui.button("[Edit]" if read_mode else "[Read]"):
entry["read_mode"] = not read_mode
ts_str = entry.get("ts", "")
if ts_str:
imgui.same_line()
imgui.text_colored(vec4(120, 120, 100), str(ts_str))
if collapsed:
imgui.same_line()
if imgui.button("Ins"):
self.disc_entries.insert(i, {"role": "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
imgui.same_line()
if imgui.button("Del"):
self.disc_entries.pop(i)
imgui.pop_id()
break # Break from inner loop, clipper will re-step
imgui.same_line()
preview = entry["content"].replace("\\n", " ")[:60]
if len(entry["content"]) > 60: preview += "..."
imgui.text_colored(vec4(160, 160, 150), preview)
if not collapsed:
if read_mode:
content = entry["content"]
pattern = re.compile(r"\[Definition: (.*?) from (.*?) \(line (\d+)\)\](\s+```[\s\S]*?```)?")
matches = list(pattern.finditer(content))
is_nerv = theme.is_nerv_active()
if not matches:
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
markdown_helper.render(content, context_id=f'disc_{i}')
if is_nerv: imgui.pop_style_color()
else:
imgui.begin_child(f"read_content_{i}", imgui.ImVec2(0, 150), True)
if self.ui_word_wrap: imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
last_idx = 0
for m_idx, match in enumerate(matches):
before = content[last_idx:match.start()]
if before:
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
markdown_helper.render(before, context_id=f'disc_{i}_b_{m_idx}')
if is_nerv: imgui.pop_style_color()
header_text = match.group(0).split("\n")[0].strip()
path = match.group(2)
code_block = match.group(4)
if imgui.collapsing_header(header_text):
if imgui.button(f"[Source]##{i}_{match.start()}"):
res = mcp_client.read_file(path)
if res:
self.text_viewer_title = path
self.text_viewer_content = res
self.show_text_viewer = True
if code_block:
# Render code block with highlighting
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
markdown_helper.render(code_block, context_id=f'disc_{i}_c_{m_idx}')
if is_nerv: imgui.pop_style_color()
last_idx = match.end()
after = content[last_idx:]
if after:
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
markdown_helper.render(after, context_id=f'disc_{i}_a')
if is_nerv: imgui.pop_style_color()
if self.ui_word_wrap: imgui.pop_text_wrap_pos()
imgui.end_child()
else:
ch, entry["content"] = imgui.input_text_multiline("##content", entry["content"], imgui.ImVec2(-1, 150))
imgui.separator()
imgui.pop_id()
if self._scroll_disc_to_bottom:
imgui.set_scroll_here_y(1.0)
self._scroll_disc_to_bottom = False
imgui.end_child()
def _render_provider_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel")
imgui.text("Provider")
if imgui.begin_combo("##prov", self.current_provider):
for p in PROVIDERS:
if imgui.selectable(p, p == self.current_provider)[0]:
self.current_provider = p
imgui.end_combo()
imgui.separator()
imgui.text("Model")
imgui.same_line()
if imgui.button("Fetch Models"):
self._fetch_models(self.current_provider)
if imgui.begin_list_box("##models", imgui.ImVec2(-1, 120)):
for m in self.available_models:
if imgui.selectable(m, m == self.current_model)[0]:
self.current_model = m
imgui.end_list_box()
imgui.separator()
imgui.text("Parameters")
ch, self.temperature = imgui.slider_float("Temperature", self.temperature, 0.0, 2.0, "%.2f")
ch, self.max_tokens = imgui.input_int("Max Tokens (Output)", self.max_tokens, 1024)
ch, self.history_trunc_limit = imgui.input_int("History Truncation Limit", self.history_trunc_limit, 1024)
imgui.text("Bias Profile")
if imgui.begin_combo("##bias", self.ui_active_bias_profile or "None"):
if imgui.selectable("None", not self.ui_active_bias_profile)[0]:
self.ui_active_bias_profile = ""
ai_client.set_bias_profile(None)
for bname in sorted(self.bias_profiles.keys()):
if imgui.selectable(bname, bname == self.ui_active_bias_profile)[0]:
self.ui_active_bias_profile = bname
ai_client.set_bias_profile(bname)
imgui.end_combo()
imgui.text("Persona")
if not hasattr(self, 'ui_active_persona'):
self.ui_active_persona = ""
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 = ""
for pname in sorted(getattr(self.controller, 'personas', {}).keys()):
if imgui.selectable(pname, pname == self.ui_active_persona)[0]:
self.ui_active_persona = pname
imgui.end_combo()
if self.current_provider == "gemini_cli":
imgui.separator()
imgui.text("Gemini CLI")
sid = "None"
if hasattr(ai_client, "_gemini_cli_adapter") and ai_client._gemini_cli_adapter:
sid = ai_client._gemini_cli_adapter.session_id or "None"
imgui.text("Session ID:"); imgui.same_line(); self._render_selectable_label("gemini_cli_sid", sid, width=200)
if imgui.button("Reset CLI Session"):
ai_client.reset_session()
imgui.text("Binary Path")
ch, self.ui_gemini_cli_path = imgui.input_text("##gcli_path", self.ui_gemini_cli_path)
imgui.same_line()
if imgui.button("Browse##gcli"):
r = hide_tk_root()
p = filedialog.askopenfilename(title="Select gemini CLI binary")
r.destroy()
if p:
self.ui_gemini_cli_path = p
if ch:
if hasattr(ai_client, "_gemini_cli_adapter") and ai_client._gemini_cli_adapter:
ai_client._gemini_cli_adapter.binary_path = self.ui_gemini_cli_path
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_provider_panel")
def _render_token_budget_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_token_budget_panel")
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"]
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)
imgui.separator()
if self._token_stats_dirty:
self._token_stats_dirty = False
# Offload to background thread via event queue
self.controller.event_queue.put("refresh_api_metrics", {"md_content": self._last_stable_md or ""})
stats = self._token_stats
if not stats:
imgui.text_disabled("Token stats unavailable")
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_token_budget_panel")
return
pct = stats.get("utilization_pct", 0.0)
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)
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()
imgui.text_disabled(f"{current:,} / {limit:,} tokens ({headroom:,} remaining)")
sys_tok = stats.get("system_tokens", 0)
tool_tok = stats.get("tools_tokens", 0)
hist_tok = stats.get("history_tokens", 0)
total_tok = sys_tok + tool_tok + hist_tok or 1
if imgui.begin_table("token_breakdown", 3, imgui.TableFlags_.borders_inner_h | imgui.TableFlags_.sizing_fixed_fit):
imgui.table_setup_column("Component")
imgui.table_setup_column("Tokens")
imgui.table_setup_column("Pct")
imgui.table_headers_row()
for lbl, tok in [("System", sys_tok), ("Tools", tool_tok), ("History", hist_tok)]:
imgui.table_next_row()
imgui.table_set_column_index(0); imgui.text(lbl)
imgui.table_set_column_index(1); imgui.text(f"{tok:,}")
imgui.table_set_column_index(2); imgui.text(f"{tok / total_tok * 100:.0f}%")
imgui.end_table()
imgui.separator()
imgui.text("MMA Tier Costs")
if hasattr(self, 'mma_tier_usage') and self.mma_tier_usage:
if imgui.begin_table("tier_cost_breakdown", 4, imgui.TableFlags_.borders_inner_h | imgui.TableFlags_.sizing_fixed_fit):
imgui.table_setup_column("Tier")
imgui.table_setup_column("Model")
imgui.table_setup_column("Tokens")
imgui.table_setup_column("Est. Cost")
imgui.table_headers_row()
for tier, stats in self.mma_tier_usage.items():
model = stats.get('model', 'unknown')
in_t = stats.get('input', 0)
out_t = stats.get('output', 0)
tokens = in_t + out_t
cost = cost_tracker.estimate_cost(model, in_t, out_t)
imgui.table_next_row()
imgui.table_set_column_index(0); self._render_selectable_label(f"tier_{tier}", tier, width=-1)
imgui.table_set_column_index(1); self._render_selectable_label(f"model_{tier}", model.split("-")[0], width=-1)
imgui.table_set_column_index(2); self._render_selectable_label(f"tokens_{tier}", f"{tokens:,}", width=-1)
imgui.table_set_column_index(3); self._render_selectable_label(f"cost_{tier}", f"${cost:.4f}", width=-1, color=imgui.ImVec4(0.2, 0.9, 0.2, 1))
imgui.end_table()
tier_total = sum(cost_tracker.estimate_cost(stats.get('model', ''), stats.get('input', 0), stats.get('output', 0)) for stats in self.mma_tier_usage.values())
self._render_selectable_label("session_total_cost", f"Session Total: ${tier_total:.4f}", width=-1, color=imgui.ImVec4(0, 1, 0, 1))
else:
imgui.text_disabled("No MMA tier usage data")
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}")
msgs = stats.get("messages")
if msgs:
shown = 0
for msg in msgs:
if shown >= 3:
break
if msg.get("trimmable"):
role = msg.get("role", "?")
toks = msg.get("tokens", 0)
imgui.text_disabled(f" [{role}] ~{toks:,} tokens")
shown += 1
imgui.separator()
cache_stats = getattr(self.controller, '_cached_cache_stats', {})
if cache_stats.get("cache_exists"):
age = cache_stats.get("cache_age_seconds", 0)
ttl = cache_stats.get("ttl_seconds", 3600)
imgui.text_colored(C_LBL, f"Gemini Cache: ACTIVE | Age: {age:.0f}s / {ttl}s | Renews at: {ttl * 0.9:.0f}s")
else:
imgui.text_disabled("Gemini Cache: INACTIVE")
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_token_budget_panel")
def _render_cache_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_cache_panel")
if self.current_provider != "gemini":
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_cache_panel")
return
if not imgui.collapsing_header("Cache Analytics"):
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_cache_panel")
return
stats = getattr(self.controller, '_cached_cache_stats', {})
if not stats.get("cache_exists"):
imgui.text_disabled("No active cache")
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_cache_panel")
return
age_sec = stats.get("cache_age_seconds", 0)
ttl_remaining = stats.get("ttl_remaining", 0)
ttl_total = stats.get("ttl_seconds", 3600)
age_str = f"{age_sec/60:.0f}m {age_sec%60:.0f}s"
remaining_str = f"{ttl_remaining/60:.0f}m {ttl_remaining%60:.0f}s"
ttl_pct = (ttl_remaining / ttl_total * 100) if ttl_total > 0 else 0
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)
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()
if imgui.button("Clear Cache"):
self.controller.clear_cache()
self._cache_cleared_timestamp = time.time()
if hasattr(self, '_cache_cleared_timestamp') and time.time() - self._cache_cleared_timestamp < 5:
imgui.text_colored(imgui.ImVec4(0.2, 1.0, 0.2, 1.0), "Cache cleared - will rebuild on next request")
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_cache_panel")
def _render_tool_analytics_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tool_analytics_panel")
imgui.text_colored(C_LBL, 'Tool Usage')
imgui.separator()
now = time.time()
if not hasattr(self, '_tool_stats_cache_time') or now - self._tool_stats_cache_time > 1.0:
self._cached_tool_stats = getattr(self.controller, '_tool_stats', {})
tool_stats = getattr(self.controller, '_cached_tool_stats', {})
if not tool_stats:
imgui.text_disabled("No tool usage data")
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tool_analytics_panel")
return
if imgui.begin_table("tool_stats", 4, imgui.TableFlags_.borders | imgui.TableFlags_.sortable):
imgui.table_setup_column("Tool")
imgui.table_setup_column("Count")
imgui.table_setup_column("Avg (ms)")
imgui.table_setup_column("Fail %")
imgui.table_headers_row()
sorted_tools = sorted(tool_stats.items(), key=lambda x: -x[1].get("count", 0))
for tool_name, stats in sorted_tools:
count = stats.get("count", 0)
total_time = stats.get("total_time_ms", 0)
failures = stats.get("failures", 0)
avg_time = total_time / count if count > 0 else 0
fail_pct = (failures / count * 100) if count > 0 else 0
imgui.table_next_row()
imgui.table_set_column_index(0)
imgui.text(tool_name)
imgui.table_set_column_index(1)
imgui.text(str(count))
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%")
imgui.end_table()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tool_analytics_panel")
def _render_session_insights_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_session_insights_panel")
imgui.text_colored(C_LBL, 'Session Insights')
imgui.separator()
insights = self.controller.get_session_insights()
imgui.text(f"Total Tokens: {insights.get('total_tokens', 0):,}")
imgui.text(f"API Calls: {insights.get('call_count', 0)}")
imgui.text(f"Burn Rate: {insights.get('burn_rate', 0):.0f} tokens/min")
imgui.text(f"Session Cost: ${insights.get('session_cost', 0):.4f}")
completed = insights.get('completed_tickets', 0)
efficiency = insights.get('efficiency', 0)
imgui.text(f"Completed: {completed}")
imgui.text(f"Tokens/Ticket: {efficiency:.0f}" if efficiency > 0 else "Tokens/Ticket: N/A")
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_session_insights_panel")
def _render_usage_analytics_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_usage_analytics_panel")
self._render_token_budget_panel()
imgui.separator()
self._render_tool_analytics_panel()
imgui.separator()
self._render_session_insights_panel()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_usage_analytics_panel")
def _render_message_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_message_panel")
# LIVE indicator
is_live = self.ai_status in ["running powershell...", "fetching url...", "searching web...", "powershell done, awaiting AI..."]
if is_live:
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
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_enter = io.key_ctrl and imgui.is_key_pressed(imgui.Key.enter)
ctrl_l = io.key_ctrl and imgui.is_key_pressed(imgui.Key.l)
if ctrl_l:
self.ui_ai_input = ""
imgui.separator()
send_busy = False
with self._send_thread_lock:
if self.send_thread and self.send_thread.is_alive():
send_busy = True
if (imgui.button("Gen + Send") or ctrl_enter) and not send_busy:
self._handle_generate_send()
imgui.same_line()
if imgui.button("MD Only"):
self._handle_md_only()
imgui.same_line()
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()})
imgui.same_line()
if imgui.button("Reset"):
self._handle_reset_session()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_message_panel")
def _render_response_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_response_panel")
if self._trigger_blink:
self._trigger_blink = False
self._is_blinking = True
self._blink_start_time = time.time()
try:
imgui.set_window_focus("Response") # type: ignore[call-arg]
except:
pass
is_blinking = False
if self._is_blinking:
elapsed = time.time() - self._blink_start_time
if elapsed > 1.5:
self._is_blinking = False
else:
is_blinking = True
val = math.sin(elapsed * 8 * math.pi)
alpha = 50/255 if val > 0 else 0
imgui.push_style_color(imgui.Col_.frame_bg, vec4(0, 255, 0, alpha))
imgui.push_style_color(imgui.Col_.child_bg, vec4(0, 255, 0, alpha))
# --- Always Render Content ---
imgui.begin_child("response_scroll_area", imgui.ImVec2(0, -40), True)
is_nerv = theme.is_nerv_active()
if is_nerv: imgui.push_style_color(imgui.Col_.text, vec4(80, 255, 80))
markdown_helper.render(self.ai_response, context_id="response")
if is_nerv: imgui.pop_style_color()
imgui.end_child()
imgui.separator()
if imgui.button("-> History"):
if self.ai_response:
self.disc_entries.append({"role": "AI", "content": self.ai_response, "collapsed": True, "ts": project_manager.now_ts()})
if is_blinking:
imgui.pop_style_color(2)
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_response_panel")
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
imgui.text_colored(st_col, f"Status: {self.ai_status}")
imgui.same_line()
if imgui.button("Clear##comms"):
ai_client.clear_comms_log()
self._comms_log.clear()
self._comms_log_dirty = True
if self.is_viewing_prior_session:
imgui.same_line()
if imgui.button("Exit Prior Session"):
self.is_viewing_prior_session = False
self.prior_session_entries.clear()
self._comms_log_dirty = True
self.ai_status = "idle"
imgui.separator()
imgui.text_colored(C_OUT, "OUT")
imgui.same_line()
imgui.text_colored(C_REQ, "request")
imgui.same_line()
imgui.text_colored(C_TC, "tool_call")
imgui.same_line()
imgui.text(" ")
imgui.same_line()
imgui.text_colored(C_IN, "IN")
imgui.same_line()
imgui.text_colored(C_RES, "response")
imgui.same_line()
imgui.text_colored(C_TR, "tool_result")
imgui.separator()
# Use tinted background for prior session
if self.is_viewing_prior_session:
imgui.push_style_color(imgui.Col_.child_bg, vec4(40, 30, 20))
imgui.begin_child("comms_scroll", imgui.ImVec2(0, 0), False, imgui.WindowFlags_.horizontal_scrollbar)
log_to_render = self._comms_log_cache
clipper = imgui.ListClipper()
clipper.begin(len(log_to_render))
while clipper.step():
for i in range(clipper.display_start, clipper.display_end):
entry = log_to_render[i]
imgui.push_id(f"comms_entry_{i}")
i_display = i + 1
ts = entry.get("ts", "00:00:00")
direction = entry.get("direction", "??")
kind = entry.get("kind", entry.get("type", "??"))
provider = entry.get("provider", "?")
model = entry.get("model", "?")
tier = entry.get("source_tier", "main")
payload = entry.get("payload", {})
if not payload and kind not in ("request", "response", "tool_call", "tool_result"):
payload = entry # legacy
# Row 1: #Idx TS DIR KIND Provider/Model [Tier]
imgui.text_colored(C_LBL, f"#{i_display}")
imgui.same_line()
imgui.text_colored(vec4(160, 160, 160), ts)
ticket_id = entry.get("mma_ticket_id")
if ticket_id:
imgui.same_line()
imgui.text_colored(vec4(255, 120, 120), f"[{ticket_id}]")
imgui.same_line()
d_col = DIR_COLORS.get(direction, C_VAL)
imgui.text_colored(d_col, direction)
imgui.same_line()
k_col = KIND_COLORS.get(kind, C_VAL)
imgui.text_colored(k_col, kind)
imgui.same_line()
imgui.text_colored(C_LBL, f"{provider}/{model}")
imgui.same_line()
imgui.text_colored(C_SUB, f"[{tier}]")
# Optimized content rendering using _render_heavy_text logic
idx_str = str(i)
if kind == "request":
self._render_heavy_text("message", payload.get("message", ""), idx_str)
if payload.get("system"):
self._render_heavy_text("system", payload.get("system", ""), idx_str)
elif kind == "response":
r = payload.get("round", 0)
sr = payload.get("stop_reason", "STOP")
imgui.text_colored(C_LBL, f"round: {r} stop_reason: {sr}")
self._render_heavy_text("text", payload.get("text", ""), idx_str)
tcs = payload.get("tool_calls", [])
if tcs:
self._render_heavy_text("tool_calls", json.dumps(tcs, indent=1), idx_str)
elif kind == "tool_call":
self._render_heavy_text(payload.get("name", "call"), payload.get("script") or json.dumps(payload.get("args", {}), indent=1), idx_str)
elif kind == "tool_result":
self._render_heavy_text(payload.get("name", "result"), payload.get("output", ""), idx_str)
else:
self._render_heavy_text("data", str(payload), idx_str)
imgui.separator()
imgui.pop_id()
if self._scroll_comms_to_bottom:
imgui.set_scroll_here_y(1.0)
self._scroll_comms_to_bottom = False
imgui.end_child()
if self.is_viewing_prior_session:
imgui.pop_style_color()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_comms_history_panel")
def _render_tool_calls_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tool_calls_panel")
imgui.text("Tool call history")
imgui.same_line()
if imgui.button("Clear##tc"):
self._tool_log.clear()
self._tool_log_dirty = True
imgui.separator()
log_to_render = self._tool_log_cache
flags = imgui.TableFlags_.resizable | imgui.TableFlags_.hideable | imgui.TableFlags_.borders_inner_v | imgui.TableFlags_.row_bg | imgui.TableFlags_.scroll_y
if imgui.begin_table("tool_calls_table", 4, flags, imgui.ImVec2(0, 0)):
imgui.table_setup_column("#", imgui.TableColumnFlags_.width_fixed, 40)
imgui.table_setup_column("Tier", imgui.TableColumnFlags_.width_fixed, 60)
imgui.table_setup_column("Script", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Result", imgui.TableColumnFlags_.width_fixed, 100)
imgui.table_headers_row()
clipper = imgui.ListClipper()
clipper.begin(len(log_to_render))
while clipper.step():
for i in range(clipper.display_start, clipper.display_end):
entry = log_to_render[i]
imgui.table_next_row()
imgui.table_next_column()
imgui.text_colored(C_LBL, f"#{i+1}")
imgui.table_next_column()
imgui.text_colored(C_SUB, f"[{entry.get('source_tier', 'main')}]")
imgui.table_next_column()
script = entry.get("script", "")
res = entry.get("result", "")
# Use a clear, formatted combined view for the detail window
combined = f"COMMAND:\n{script}\n\n{'='*40}\nOUTPUT:\n{res}"
script_preview = script.replace("\n", " ")[:150]
if len(script) > 150: script_preview += "..."
self._render_selectable_label(f'tc_script_{i}', script_preview, width=-1)
if imgui.is_item_clicked():
self.text_viewer_title = f"Tool Call #{i+1} Details"
self.text_viewer_content = combined
self.show_text_viewer = True
imgui.table_next_column()
res_preview = res.replace("\n", " ")[:30]
if len(res) > 30: res_preview += "..."
self._render_selectable_label(f'tc_res_{i}', res_preview, width=-1)
if imgui.is_item_clicked():
self.text_viewer_title = f"Tool Call #{i+1} Details"
self.text_viewer_content = combined
self.show_text_viewer = True
imgui.end_table()
if self._scroll_tool_calls_to_bottom:
imgui.set_scroll_here_y(1.0)
self._scroll_tool_calls_to_bottom = False
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tool_calls_panel")
def bulk_execute(self) -> None:
for tid in self.ui_selected_tickets:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
if t: t['status'] = 'ready'
self._push_mma_state_update()
def bulk_skip(self) -> None:
for tid in self.ui_selected_tickets:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
if t: t['status'] = 'completed'
self._push_mma_state_update()
def bulk_block(self) -> None:
for tid in self.ui_selected_tickets:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == tid), None)
if t: t['status'] = 'blocked'
self._push_mma_state_update()
def _cb_kill_ticket(self, ticket_id: str) -> None:
if self.controller and hasattr(self.controller, 'engine') and self.controller.engine:
self.controller.engine.kill_worker(ticket_id)
def _cb_block_ticket(self, ticket_id: str) -> None:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == ticket_id), None)
if t:
t['status'] = 'blocked'
t['manual_block'] = True
t['blocked_reason'] = '[MANUAL] User blocked'
changed = True
while changed:
changed = False
for t in self.active_tickets:
if t.get('status') == 'todo':
for dep_id in t.get('depends_on', []):
dep = next((x for x in self.active_tickets if str(x.get('id', '')) == dep_id), None)
if dep and dep.get('status') == 'blocked':
t['status'] = 'blocked'
changed = True
break
self._push_mma_state_update()
def _cb_unblock_ticket(self, ticket_id: str) -> None:
t = next((t for t in self.active_tickets if str(t.get('id', '')) == ticket_id), None)
if t and t.get('manual_block', False):
t['status'] = 'todo'
t['manual_block'] = False
t['blocked_reason'] = None
changed = True
while changed:
changed = False
for t in self.active_tickets:
if t.get('status') == 'blocked' and not t.get('manual_block', False):
can_run = True
for dep_id in t.get('depends_on', []):
dep = next((x for x in self.active_tickets if str(x.get('id', '')) == dep_id), None)
if dep and dep.get('status') != 'completed':
can_run = False
break
if can_run:
t['status'] = 'todo'
changed = True
self._push_mma_state_update()
def _reorder_ticket(self, src_idx: int, dst_idx: int) -> None:
if src_idx == dst_idx: return
new_tickets = list(self.active_tickets)
ticket = new_tickets.pop(src_idx)
new_tickets.insert(dst_idx, ticket)
# Validate dependencies: a ticket cannot be placed before any of its dependencies
id_to_idx = {str(t.get('id', '')): i for i, t in enumerate(new_tickets)}
valid = True
for i, t in enumerate(new_tickets):
deps = t.get('depends_on', [])
for d_id in deps:
if d_id in id_to_idx and id_to_idx[d_id] >= i:
valid = False
break
if not valid: break
if valid:
self.active_tickets = new_tickets
self._push_mma_state_update()
def _render_ticket_queue(self) -> None:
imgui.text("Ticket Queue Management")
if not self.active_track:
imgui.text_disabled("No active track.")
return
# Select All / None
if imgui.button("Select All"):
self.ui_selected_tickets = {str(t.get('id', '')) for t in self.active_tickets}
imgui.same_line()
if imgui.button("Select None"):
self.ui_selected_tickets.clear()
imgui.same_line()
imgui.spacing()
imgui.same_line()
# Bulk Actions
if imgui.button("Bulk Execute"):
self.bulk_execute()
imgui.same_line()
if imgui.button("Bulk Skip"):
self.bulk_skip()
imgui.same_line()
if imgui.button("Bulk Block"):
self.bulk_block()
# Table
flags = imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable | imgui.TableFlags_.scroll_y
if imgui.begin_table("ticket_queue_table", 7, flags, imgui.ImVec2(0, 300)):
imgui.table_setup_column("Select", imgui.TableColumnFlags_.width_fixed, 40)
imgui.table_setup_column("ID", imgui.TableColumnFlags_.width_fixed, 80)
imgui.table_setup_column("Priority", imgui.TableColumnFlags_.width_fixed, 100)
imgui.table_setup_column("Model", imgui.TableColumnFlags_.width_fixed, 150)
imgui.table_setup_column("Status", imgui.TableColumnFlags_.width_fixed, 100)
imgui.table_setup_column("Description", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Actions", imgui.TableColumnFlags_.width_fixed, 80)
imgui.table_headers_row()
for i, t in enumerate(self.active_tickets):
tid = str(t.get('id', ''))
imgui.table_next_row()
# Select
imgui.table_next_column()
is_sel = tid in self.ui_selected_tickets
changed, is_sel = imgui.checkbox(f"##sel_{tid}", is_sel)
if changed:
if is_sel: self.ui_selected_tickets.add(tid)
else: self.ui_selected_tickets.discard(tid)
# ID
imgui.table_next_column()
is_selected = (tid == self.ui_selected_ticket_id)
opened, _ = imgui.selectable(f"{tid}##drag_{tid}", is_selected)
if opened:
self.ui_selected_ticket_id = tid
if imgui.begin_drag_drop_source():
imgui.set_drag_drop_payload("TICKET_REORDER", i)
imgui.text(f"Moving {tid}")
imgui.end_drag_drop_source()
if imgui.begin_drag_drop_target():
payload = imgui.accept_drag_drop_payload("TICKET_REORDER")
if payload:
src_idx = int(payload.data)
self._reorder_ticket(src_idx, i)
imgui.end_drag_drop_target()
# Priority
imgui.table_next_column()
prio = t.get('priority', 'medium')
p_col = vec4(180, 180, 180) # gray
if prio == 'high': p_col = vec4(255, 100, 100) # red
elif prio == 'medium': p_col = vec4(255, 255, 100) # yellow
imgui.push_style_color(imgui.Col_.text, p_col)
if imgui.begin_combo(f"##prio_{tid}", prio, imgui.ComboFlags_.height_small):
for p_opt in ['high', 'medium', 'low']:
if imgui.selectable(p_opt, p_opt == prio)[0]:
t['priority'] = p_opt
self._push_mma_state_update()
imgui.end_combo()
imgui.pop_style_color()
# Model
imgui.table_next_column()
model_override = t.get('model_override')
current_model = model_override if model_override else "Default"
if imgui.begin_combo(f"##model_{tid}", current_model, imgui.ComboFlags_.height_small):
if imgui.selectable("Default", model_override is None)[0]:
t['model_override'] = None
self._push_mma_state_update()
for model in ["gemini-2.5-flash-lite", "gemini-2.5-flash", "gemini-3-flash-preview", "gemini-3.1-pro-preview", "deepseek-v3"]:
if imgui.selectable(model, model_override == model)[0]:
t['model_override'] = model
self._push_mma_state_update()
imgui.end_combo()
# Status
imgui.table_next_column()
status = t.get('status', 'todo')
if t.get('model_override'):
imgui.text_colored(imgui.ImVec4(1, 0.5, 0, 1), f"{status} [{t.get('model_override')}]")
else:
imgui.text(t.get('status', 'todo'))
# Description
imgui.table_next_column()
imgui.text(t.get('description', ''))
# Actions - Kill button for in_progress tickets
imgui.table_next_column()
status = t.get('status', 'todo')
if status == 'in_progress':
if imgui.button(f"Kill##{tid}"):
self._cb_kill_ticket(tid)
elif status == 'todo':
if imgui.button(f"Block##{tid}"):
self._cb_block_ticket(tid)
elif status == 'blocked' and t.get('manual_block', False):
if imgui.button(f"Unblock##{tid}"):
self._cb_unblock_ticket(tid)
imgui.end_table()
def _render_mma_dashboard(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_mma_dashboard")
is_nerv = theme.is_nerv_active()
if self.is_viewing_prior_session:
c = vec4(255, 200, 100)
if is_nerv: c = vec4(255, 152, 48) # NERV_ORANGE
imgui.text_colored(c, "HISTORICAL VIEW - READ ONLY")
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_mma_dashboard")
return
# Task 5.3: Dense Summary Line
track_name = self.active_track.description if self.active_track else "None"
track_stats = {"percentage": 0.0, "completed": 0, "total": 0, "in_progress": 0, "blocked": 0, "todo": 0}
if self.active_track:
track_stats = project_manager.calculate_track_progress(self.active_track.tickets)
total_cost = 0.0
for usage in self.mma_tier_usage.values():
model = usage.get('model', 'unknown')
in_t = usage.get('input', 0)
out_t = usage.get('output', 0)
total_cost += cost_tracker.estimate_cost(model, in_t, out_t)
imgui.text("Track:")
imgui.same_line()
imgui.text_colored(C_VAL, track_name)
imgui.same_line()
imgui.text(" | Status:")
imgui.same_line()
if self.mma_status == "paused":
c = imgui.ImVec4(1, 0.5, 0, 1)
if is_nerv: c = vec4(255, 152, 48)
imgui.text_colored(c, "PIPELINE PAUSED")
imgui.same_line()
status_col = imgui.ImVec4(1, 1, 1, 1)
if self.mma_status == "idle": status_col = imgui.ImVec4(0.7, 0.7, 0.7, 1)
elif self.mma_status == "running": status_col = imgui.ImVec4(1, 1, 0, 1)
elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
elif self.mma_status == "paused": status_col = imgui.ImVec4(1, 0.5, 0, 1)
if is_nerv:
if self.mma_status == "running": status_col = vec4(80, 255, 80) # DATA_GREEN
elif self.mma_status == "error": status_col = vec4(255, 72, 64) # ALERT_RED
imgui.text_colored(status_col, self.mma_status.upper())
imgui.same_line()
imgui.text(" | Cost:")
imgui.same_line()
imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"${total_cost:,.4f}")
# Progress Bar
perc = track_stats["percentage"] / 100.0
p_color = imgui.ImVec4(0.0, 1.0, 0.0, 1.0)
if track_stats["percentage"] < 33:
p_color = imgui.ImVec4(1.0, 0.0, 0.0, 1.0)
elif track_stats["percentage"] < 66:
p_color = imgui.ImVec4(1.0, 1.0, 0.0, 1.0)
imgui.push_style_color(imgui.Col_.plot_histogram, p_color)
imgui.progress_bar(perc, imgui.ImVec2(-1, 0), f"{track_stats['percentage']:.1f}%")
imgui.pop_style_color()
# Detailed breakdown
if imgui.begin_table("ticket_stats_breakdown", 4):
imgui.table_next_column()
imgui.text_colored(C_LBL, "Completed:")
imgui.same_line()
imgui.text_colored(C_VAL, str(track_stats["completed"]))
imgui.table_next_column()
imgui.text_colored(C_LBL, "In Progress:")
imgui.same_line()
imgui.text_colored(C_VAL, str(track_stats["in_progress"]))
imgui.table_next_column()
imgui.text_colored(C_LBL, "Blocked:")
imgui.same_line()
imgui.text_colored(C_VAL, str(track_stats["blocked"]))
imgui.table_next_column()
imgui.text_colored(C_LBL, "Todo:")
imgui.same_line()
imgui.text_colored(C_VAL, str(track_stats["todo"]))
imgui.end_table()
if self.active_track:
remaining = track_stats["total"] - track_stats["completed"]
eta_mins = (self._avg_ticket_time * remaining) / 60.0
imgui.text_colored(C_LBL, "ETA:")
imgui.same_line()
imgui.text_colored(C_VAL, f"~{int(eta_mins)}m ({remaining} tickets remaining)")
imgui.separator()
imgui.text_colored(C_LBL, 'Epic Planning (Tier 1)')
_, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80))
if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)):
self._cb_plan_epic()
imgui.separator()
# 0. Conductor Setup
if imgui.collapsing_header("Conductor Setup"):
if imgui.button("Run Setup Scan"):
self._cb_run_conductor_setup()
if self.ui_conductor_setup_summary:
imgui.input_text_multiline("##setup_summary", self.ui_conductor_setup_summary, imgui.ImVec2(-1, 120), imgui.InputTextFlags_.read_only)
imgui.separator()
# 1. Track Browser
imgui.text("Track Browser")
if imgui.begin_table("mma_tracks_table", 4, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
imgui.table_setup_column("Title")
imgui.table_setup_column("Status")
imgui.table_setup_column("Progress")
imgui.table_setup_column("Actions")
imgui.table_headers_row()
for track in self.tracks:
imgui.table_next_row()
imgui.table_next_column()
imgui.text(track.get("title", "Untitled"))
imgui.table_next_column()
status = track.get("status", "unknown").lower()
if status == "new":
imgui.text_colored(imgui.ImVec4(0.7, 0.7, 0.7, 1.0), "NEW")
elif status == "active":
c = imgui.ImVec4(1.0, 1.0, 0.0, 1.0)
if is_nerv: c = vec4(80, 255, 80)
imgui.text_colored(c, "ACTIVE")
elif status == "done":
imgui.text_colored(imgui.ImVec4(0.0, 1.0, 0.0, 1.0), "DONE")
elif status == "blocked":
imgui.text_colored(imgui.ImVec4(1.0, 0.0, 0.0, 1.0), "BLOCKED")
else:
imgui.text(status)
imgui.table_next_column()
progress = track.get("progress", 0.0)
if progress < 0.33:
p_color = imgui.ImVec4(1.0, 0.0, 0.0, 1.0)
elif progress < 0.66:
p_color = imgui.ImVec4(1.0, 1.0, 0.0, 1.0)
else:
p_color = imgui.ImVec4(0.0, 1.0, 0.0, 1.0)
imgui.push_style_color(imgui.Col_.plot_histogram, p_color)
imgui.progress_bar(progress, imgui.ImVec2(-1, 0), f"{int(progress*100)}%")
imgui.pop_style_color()
imgui.table_next_column()
if imgui.button(f"Load##{track.get('id')}"):
self._cb_load_track(str(track.get("id") or ""))
imgui.end_table()
# 1b. New Track Form
imgui.text("Create New Track")
changed_n, self.ui_new_track_name = imgui.input_text("Name##new_track", self.ui_new_track_name)
changed_d, self.ui_new_track_desc = imgui.input_text_multiline("Description##new_track", self.ui_new_track_desc, imgui.ImVec2(-1, 60))
imgui.text("Type:")
imgui.same_line()
if imgui.begin_combo("##track_type", self.ui_new_track_type):
for ttype in ["feature", "chore", "fix"]:
if imgui.selectable(ttype, self.ui_new_track_type == ttype)[0]:
self.ui_new_track_type = ttype
imgui.end_combo()
if imgui.button("Create Track"):
self._cb_create_track(self.ui_new_track_name, self.ui_new_track_desc, self.ui_new_track_type)
self.ui_new_track_name = ""
self.ui_new_track_desc = ""
imgui.separator()
# 2. Global Controls
changed, self.mma_step_mode = imgui.checkbox("Step Mode (HITL)", self.mma_step_mode)
if changed:
# We could push an event here if the engine needs to know immediately
pass
imgui.same_line()
imgui.text(f"Status: {self.mma_status.upper()}")
if self.controller and hasattr(self.controller, 'engine') and self.controller.engine and hasattr(self.controller.engine, '_pause_event'):
imgui.same_line()
is_paused = self.controller.engine._pause_event.is_set()
label = "Resume" if is_paused else "Pause"
if imgui.button(label):
if is_paused:
self.controller.engine.resume()
else:
self.controller.engine.pause()
if self.active_tier:
imgui.same_line()
imgui.text_colored(C_VAL, f"| Active: {self.active_tier}")
# Approval pending indicator
any_pending = (
self._pending_mma_spawn is not None or
self._pending_mma_approval is not None or
self._pending_ask_dialog
)
if any_pending:
alpha = abs(math.sin(time.time() * 5))
imgui.same_line()
c = imgui.ImVec4(1.0, 0.3, 0.3, alpha)
if is_nerv: c = vec4(255, 72, 64, alpha) # ALERT_RED
imgui.text_colored(c, " APPROVAL PENDING")
imgui.same_line()
if imgui.button("Go to Approval"):
pass # scroll/focus handled by existing dialog rendering
imgui.separator()
# 3. Token Usage Table
imgui.text("Tier Usage (Tokens & Cost)")
if imgui.begin_table("mma_usage", 5, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg):
imgui.table_setup_column("Tier")
imgui.table_setup_column("Model")
imgui.table_setup_column("Input")
imgui.table_setup_column("Output")
imgui.table_setup_column("Est. Cost")
imgui.table_headers_row()
usage = self.mma_tier_usage
total_cost = 0.0
for tier, stats in usage.items():
imgui.table_next_row()
imgui.table_next_column()
imgui.text(tier)
imgui.table_next_column()
model = stats.get('model', 'unknown')
imgui.text(model)
imgui.table_next_column()
in_t = stats.get('input', 0)
imgui.text(f"{in_t:,}")
imgui.table_next_column()
out_t = stats.get('output', 0)
imgui.text(f"{out_t:,}")
imgui.table_next_column()
cost = cost_tracker.estimate_cost(model, in_t, out_t)
total_cost += cost
imgui.text(f"${cost:,.4f}")
# Total Row
imgui.table_next_row()
imgui.table_set_bg_color(imgui.TableBgTarget_.row_bg0, imgui.get_color_u32(imgui.Col_.plot_lines_hovered))
imgui.table_next_column()
imgui.text("TOTAL")
imgui.table_next_column()
imgui.text("")
imgui.table_next_column()
imgui.text("")
imgui.table_next_column()
imgui.text("")
imgui.table_next_column()
imgui.text(f"${total_cost:,.4f}")
imgui.end_table()
imgui.separator()
# 3b. Tier Model Config
if imgui.collapsing_header("Tier Model Config"):
for tier in self.mma_tier_usage.keys():
imgui.text(f"{tier}:")
imgui.same_line()
current_model = self.mma_tier_usage[tier].get("model", "unknown")
current_provider = self.mma_tier_usage[tier].get("provider", "gemini")
imgui.push_id(f"tier_cfg_{tier}")
# Provider selection
imgui.push_item_width(80)
if imgui.begin_combo("##prov", current_provider):
for p in PROVIDERS:
if imgui.selectable(p, p == current_provider)[0]:
self.mma_tier_usage[tier]["provider"] = p
# Reset model to default for provider
models_list = self.controller.all_available_models.get(p, [])
if models_list:
self.mma_tier_usage[tier]["model"] = models_list[0]
imgui.end_combo()
imgui.pop_item_width()
imgui.same_line()
# Model selection
imgui.push_item_width(150)
models_list = self.controller.all_available_models.get(current_provider, [])
if imgui.begin_combo("##model", current_model):
for model in models_list:
if imgui.selectable(model, current_model == model)[0]:
self.mma_tier_usage[tier]["model"] = model
imgui.end_combo()
imgui.pop_item_width()
imgui.same_line()
# Tool Preset selection
imgui.push_item_width(-1)
current_preset = self.mma_tier_usage[tier].get("tool_preset") or "None"
preset_names = ["None"] + sorted(self.controller.tool_presets.keys())
if imgui.begin_combo("##preset", current_preset):
for preset_name in preset_names:
if imgui.selectable(preset_name, current_preset == preset_name)[0]:
self.mma_tier_usage[tier]["tool_preset"] = None if preset_name == "None" else preset_name
imgui.end_combo()
imgui.pop_item_width()
imgui.pop_id()
imgui.separator()
self._render_ticket_queue()
imgui.separator()
ch, self.ui_separate_task_dag = imgui.checkbox("Pop Out Task DAG", self.ui_separate_task_dag)
if ch:
self.show_windows["Task DAG"] = self.ui_separate_task_dag
if not self.ui_separate_task_dag:
self._render_task_dag_panel()
# 6. Edit Selected Ticket
if self.ui_selected_ticket_id:
imgui.separator()
imgui.text_colored(C_VAL, f"Editing: {self.ui_selected_ticket_id}")
ticket = next((t for t in self.active_tickets if str(t.get('id', '')) == self.ui_selected_ticket_id), None)
if ticket:
imgui.text(f"Status: {ticket.get('status', 'todo')}")
prio = ticket.get('priority', 'medium')
imgui.text("Priority:")
imgui.same_line()
if imgui.begin_combo(f"##edit_prio_{ticket.get('id')}", prio):
for p_opt in ['high', 'medium', 'low']:
if imgui.selectable(p_opt, p_opt == prio)[0]:
ticket['priority'] = p_opt
self._push_mma_state_update()
imgui.end_combo()
imgui.text(f"Target: {ticket.get('target_file', '')}")
deps = ticket.get('depends_on', [])
imgui.text(f"Depends on: {', '.join(deps)}")
if imgui.button(f"Mark Complete##{self.ui_selected_ticket_id}"):
ticket['status'] = 'done'
self._push_mma_state_update()
imgui.same_line()
if imgui.button(f"Delete##{self.ui_selected_ticket_id}"):
self.active_tickets = [t for t in self.active_tickets if str(t.get('id', '')) != self.ui_selected_ticket_id]
self.ui_selected_ticket_id = None
self._push_mma_state_update()
imgui.separator()
imgui.text("Agent Streams")
if imgui.begin_tab_bar("mma_streams_tabs"):
# Tier 1
if imgui.begin_tab_item("Tier 1")[0]:
ch, self.ui_separate_tier1 = imgui.checkbox("Pop Out Tier 1", self.ui_separate_tier1)
if ch: self.show_windows["Tier 1: Strategy"] = self.ui_separate_tier1
if not self.ui_separate_tier1:
self._render_tier_stream_panel("Tier 1", "Tier 1")
else:
imgui.text_disabled("Tier 1 stream is detached.")
imgui.end_tab_item()
# Tier 2
if imgui.begin_tab_item("Tier 2")[0]:
ch, self.ui_separate_tier2 = imgui.checkbox("Pop Out Tier 2", self.ui_separate_tier2)
if ch: self.show_windows["Tier 2: Tech Lead"] = self.ui_separate_tier2
if not self.ui_separate_tier2:
self._render_tier_stream_panel("Tier 2", "Tier 2 (Tech Lead)")
else:
imgui.text_disabled("Tier 2 stream is detached.")
imgui.end_tab_item()
# Tier 3
if imgui.begin_tab_item("Tier 3")[0]:
ch, self.ui_separate_tier3 = imgui.checkbox("Pop Out Tier 3", self.ui_separate_tier3)
if ch: self.show_windows["Tier 3: Workers"] = self.ui_separate_tier3
if not self.ui_separate_tier3:
self._render_tier_stream_panel("Tier 3", None)
else:
imgui.text_disabled("Tier 3 stream is detached.")
imgui.end_tab_item()
# Tier 4
if imgui.begin_tab_item("Tier 4")[0]:
ch, self.ui_separate_tier4 = imgui.checkbox("Pop Out Tier 4", self.ui_separate_tier4)
if ch: self.show_windows["Tier 4: QA"] = self.ui_separate_tier4
if not self.ui_separate_tier4:
self._render_tier_stream_panel("Tier 4", "Tier 4 (QA)")
else:
imgui.text_disabled("Tier 4 stream is detached.")
imgui.end_tab_item()
imgui.end_tab_bar()
def _render_task_dag_panel(self) -> None:
# 4. Task DAG Visualizer
imgui.text("Task DAG")
if self.active_track and self.node_editor_ctx:
ed.set_current_editor(self.node_editor_ctx)
ed.begin('Visual DAG')
# Selection detection
selected = ed.get_selected_nodes()
if selected:
for node_id in selected:
node_val = node_id.id()
for t in self.active_tickets:
if abs(hash(str(t.get('id', '')))) == node_val:
self.ui_selected_ticket_id = str(t.get('id', ''))
break
break
for t in self.active_tickets:
tid = str(t.get('id', '??'))
int_id = abs(hash(tid))
ed.begin_node(ed.NodeId(int_id))
imgui.text_colored(C_KEY, f"Ticket: {tid}")
status = t.get('status', 'todo')
s_col = C_VAL
if status == 'done' or status == 'complete': s_col = C_IN
elif status == 'in_progress' or status == 'running': s_col = C_OUT
elif status == 'error': s_col = imgui.ImVec4(1, 0.4, 0.4, 1)
imgui.text("Status: ")
imgui.same_line()
imgui.text_colored(s_col, status)
imgui.text(f"Target: {t.get('target_file','')}")
ed.begin_pin(ed.PinId(abs(hash(tid + "_in"))), ed.PinKind.input)
imgui.text("->")
ed.end_pin()
imgui.same_line()
ed.begin_pin(ed.PinId(abs(hash(tid + "_out"))), ed.PinKind.output)
imgui.text("->")
ed.end_pin()
ed.end_node()
for t in self.active_tickets:
tid = str(t.get('id', '??'))
for dep in t.get('depends_on', []):
ed.link(ed.LinkId(abs(hash(dep + "_" + tid))), ed.PinId(abs(hash(dep + "_out"))), ed.PinId(abs(hash(tid + "_in"))))
# Handle link creation
if ed.begin_create():
start_pin = ed.PinId()
end_pin = ed.PinId()
if ed.query_new_link(start_pin, end_pin):
if ed.accept_new_item():
s_id = start_pin.id()
e_id = end_pin.id()
source_tid = None
target_tid = None
for t in self.active_tickets:
tid = str(t.get('id', ''))
if abs(hash(tid + "_out")) == s_id: source_tid = tid
if abs(hash(tid + "_out")) == e_id: source_tid = tid
if abs(hash(tid + "_in")) == s_id: target_tid = tid
if abs(hash(tid + "_in")) == e_id: target_tid = tid
if source_tid and target_tid and source_tid != target_tid:
for t in self.active_tickets:
if str(t.get('id', '')) == target_tid:
if source_tid not in t.get('depends_on', []):
t.setdefault('depends_on', []).append(source_tid)
self._push_mma_state_update()
break
ed.end_create()
# Handle link deletion
if ed.begin_delete():
link_id = ed.LinkId()
while ed.query_deleted_link(link_id):
if ed.accept_deleted_item():
lid_val = link_id.id()
for t in self.active_tickets:
tid = str(t.get('id', ''))
deps = t.get('depends_on', [])
if any(abs(hash(d + "_" + tid)) == lid_val for d in deps):
t['depends_on'] = [dep for dep in deps if abs(hash(dep + "_" + tid)) != lid_val]
self._push_mma_state_update()
break
ed.end_delete()
# Validate DAG after any changes
try:
from src.dag_engine import TrackDAG
ticket_dicts = [{'id': str(t.get('id', '')), 'depends_on': t.get('depends_on', [])} for t in self.active_tickets]
temp_dag = TrackDAG(ticket_dicts)
if temp_dag.has_cycle():
imgui.open_popup("Cycle Detected!")
except Exception:
pass
ed.end()
# 5. Add Ticket Form
imgui.separator()
if imgui.button("Add Ticket"):
self._show_add_ticket_form = not self._show_add_ticket_form
if self._show_add_ticket_form:
# Default Ticket ID
max_id = 0
for t in self.active_tickets:
tid = t.get('id', '')
if tid.startswith('T-'):
try: max_id = max(max_id, int(tid[2:]))
except: pass
self.ui_new_ticket_id = f"T-{max_id + 1:03d}"
self.ui_new_ticket_desc = ""
self.ui_new_ticket_target = ""
self.ui_new_ticket_deps = ""
if self._show_add_ticket_form:
imgui.begin_child("add_ticket_form", imgui.ImVec2(-1, 220), True)
imgui.text_colored(C_VAL, "New Ticket Details")
_, self.ui_new_ticket_id = imgui.input_text("ID##new_ticket", self.ui_new_ticket_id)
_, self.ui_new_ticket_desc = imgui.input_text_multiline("Description##new_ticket", self.ui_new_ticket_desc, imgui.ImVec2(-1, 60))
_, self.ui_new_ticket_target = imgui.input_text("Target File##new_ticket", self.ui_new_ticket_target)
_, self.ui_new_ticket_deps = imgui.input_text("Depends On (IDs, comma-separated)##new_ticket", self.ui_new_ticket_deps)
imgui.text("Priority:")
imgui.same_line()
if imgui.begin_combo("##new_prio", self.ui_new_ticket_priority):
for p_opt in ['high', 'medium', 'low']:
if imgui.selectable(p_opt, p_opt == self.ui_new_ticket_priority)[0]:
self.ui_new_ticket_priority = p_opt
imgui.end_combo()
if imgui.button("Create"):
new_ticket = {
"id": self.ui_new_ticket_id,
"description": self.ui_new_ticket_desc,
"status": "todo",
"priority": self.ui_new_ticket_priority,
"assigned_to": "tier3-worker",
"target_file": self.ui_new_ticket_target,
"depends_on": [d.strip() for d in self.ui_new_ticket_deps.split(",") if d.strip()]
}
self.active_tickets.append(new_ticket)
self._show_add_ticket_form = False
self._push_mma_state_update()
imgui.same_line()
if imgui.button("Cancel"):
self._show_add_ticket_form = False
imgui.end_child()
else:
imgui.text_disabled("No active MMA track.")
def _render_tier_stream_panel(self, tier_key: str, stream_key: str | None) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_tier_stream_panel")
if self.is_viewing_prior_session:
imgui.text_colored(vec4(255, 200, 100), "HISTORICAL VIEW - READ ONLY")
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tier_stream_panel")
return
if stream_key is not None:
content = self.mma_streams.get(stream_key, "")
imgui.begin_child(f"##stream_content_{tier_key}", imgui.ImVec2(-1, -1))
self._render_selectable_label(f'stream_{tier_key}', content, width=-1, multiline=True, height=0)
try:
if len(content) != self._tier_stream_last_len.get(stream_key, -1):
imgui.set_scroll_here_y(1.0)
self._tier_stream_last_len[stream_key] = len(content)
except (TypeError, AttributeError):
pass
imgui.end_child()
else:
tier3_keys = [k for k in self.mma_streams if "Tier 3" in k]
if not tier3_keys:
imgui.text_disabled("No worker output yet.")
else:
worker_status = getattr(self, '_worker_status', {})
for key in tier3_keys:
ticket_id = key.split(": ", 1)[-1] if ": " in key else key
status = worker_status.get(key, "unknown")
if status == "running":
imgui.text_colored(imgui.ImVec4(1, 1, 0, 1), f"{ticket_id} [{status}]")
elif status == "completed":
imgui.text_colored(imgui.ImVec4(0, 1, 0, 1), f"{ticket_id} [{status}]")
elif status == "failed":
imgui.text_colored(imgui.ImVec4(1, 0, 0, 1), f"{ticket_id} [{status}]")
else:
imgui.text(f"{ticket_id} [{status}]")
imgui.begin_child(f"##tier3_{ticket_id}_scroll", imgui.ImVec2(-1, 150), True)
self._render_selectable_label(f'stream_t3_{ticket_id}', self.mma_streams[key], width=-1, multiline=True, height=0)
try:
if len(self.mma_streams[key]) != self._tier_stream_last_len.get(key, -1):
imgui.set_scroll_here_y(1.0)
self._tier_stream_last_len[key] = len(self.mma_streams[key])
except (TypeError, AttributeError):
pass
imgui.end_child()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_tier_stream_panel")
def _render_system_prompts_panel(self) -> None:
imgui.text("Global System Prompt (all projects)")
preset_names = sorted(self.controller.presets.keys())
current_global = self.controller.ui_global_preset_name or "Select Preset..."
imgui.set_next_item_width(200)
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()
imgui.end_combo()
imgui.same_line(0, 8)
if imgui.button("Manage Presets##global"):
self.show_preset_manager_modal = 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()
imgui.text("Project System Prompt")
current_project = self.controller.ui_project_preset_name or "Select Preset..."
imgui.set_next_item_width(200)
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()
imgui.end_combo()
imgui.same_line(0, 8)
if imgui.button("Manage Presets##project"):
self.show_preset_manager_modal = 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))
def _render_agent_tools_panel(self) -> None:
imgui.text_colored(C_LBL, 'Active Tool Preset')
presets = self.controller.tool_presets
preset_names = [""] + sorted(list(presets.keys()))
# Gracefully handle None or missing preset
active = getattr(self, "ui_active_tool_preset", "")
if active is None: active = ""
try:
idx = preset_names.index(active)
except ValueError:
idx = 0
ch, new_idx = imgui.combo("##tool_preset_select", idx, preset_names)
if ch:
self.ui_active_tool_preset = preset_names[new_idx]
imgui.same_line()
if imgui.button("Manage Presets##tools"):
self.show_tool_preset_manager_modal = True
if imgui.is_item_hovered():
imgui.set_tooltip("Configure tool availability and default modes.")
imgui.dummy(imgui.ImVec2(0, 8))
active_name = self.ui_active_tool_preset
if active_name and active_name in presets:
preset = presets[active_name]
for cat_name, tools in preset.categories.items():
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()
imgui.text(tool.name)
imgui.same_line(180)
mode = tool.approval
if imgui.radio_button(f"Auto##{cat_name}_{tool.name}", mode == "auto"):
tool.approval = "auto"
imgui.same_line()
if imgui.radio_button(f"Ask##{cat_name}_{tool.name}", mode == "ask"):
tool.approval = "ask"
imgui.tree_pop()
def _render_theme_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_theme_panel")
exp, opened = imgui.begin("Theme", self.show_windows["Theme"])
self.show_windows["Theme"] = bool(opened)
if exp:
imgui.text("Palette")
cp = theme.get_current_palette()
if imgui.begin_combo("##pal", cp):
for p in theme.get_palette_names():
if imgui.selectable(p, p == cp)[0]:
theme.apply(p)
self._flush_to_config()
models.save_config(self.config)
imgui.end_combo()
imgui.separator()
ch1, self.ui_separate_message_panel = imgui.checkbox("Separate Message Panel", self.ui_separate_message_panel)
ch2, self.ui_separate_response_panel = imgui.checkbox("Separate Response Panel", self.ui_separate_response_panel)
ch3, self.ui_separate_tool_calls_panel = imgui.checkbox("Separate Tool Calls Panel", self.ui_separate_tool_calls_panel)
if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
if ch3: self.show_windows["Tool Calls"] = self.ui_separate_tool_calls_panel
imgui.separator()
imgui.text("Font")
imgui.push_item_width(-150)
ch, path = imgui.input_text("##fontp", theme.get_current_font_path())
imgui.pop_item_width()
if ch: theme._current_font_path = path
imgui.same_line()
if imgui.button("Browse##font"):
r = hide_tk_root()
p = filedialog.askopenfilename(filetypes=[("Fonts", "*.ttf *.otf"), ("All", "*.*")])
r.destroy()
if p: theme._current_font_path = p
imgui.text("Size (px)")
imgui.same_line()
imgui.push_item_width(100)
ch, size = imgui.input_float("##fonts", theme.get_current_font_size(), 1.0, 1.0, "%.0f")
if ch: theme._current_font_size = size
imgui.pop_item_width()
imgui.same_line()
if imgui.button("Apply Font (Requires Restart)"):
self._flush_to_config()
models.save_config(self.config)
self.ai_status = "Font settings saved. Restart required."
imgui.separator()
imgui.text("UI Scale (DPI)")
ch, scale = imgui.slider_float("##scale", theme.get_current_scale(), 0.5, 3.0, "%.2f")
if ch:
theme.set_scale(scale)
self._flush_to_config()
models.save_config(self.config)
imgui.text("Panel Transparency")
ch_t, trans = imgui.slider_float("##trans", theme.get_transparency(), 0.1, 1.0, "%.2f")
if ch_t:
theme.set_transparency(trans)
self._flush_to_config()
models.save_config(self.config)
imgui.text("Panel Item Transparency")
ch_ct, ctrans = imgui.slider_float("##ctrans", theme.get_child_transparency(), 0.1, 1.0, "%.2f")
if ch_ct:
theme.set_child_transparency(ctrans)
bg = bg_shader.get_bg()
ch_bg, bg.enabled = imgui.checkbox("Animated Background Shader", bg.enabled)
if ch_bg:
gui_cfg = self.config.setdefault("gui", {})
gui_cfg["bg_shader_enabled"] = bg.enabled
self._flush_to_config()
models.save_config(self.config)
ch_crt, self.ui_crt_filter = imgui.checkbox("CRT Filter", self.ui_crt_filter)
if ch_crt:
gui_cfg = self.config.setdefault("gui", {})
gui_cfg["crt_filter_enabled"] = self.ui_crt_filter
self._flush_to_config()
models.save_config(self.config)
self._flush_to_config()
models.save_config(self.config)
imgui.end()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_theme_panel")
def _load_fonts(self) -> None:
# Set hello_imgui assets folder to the actual absolute path
assets_dir = Path(__file__).parent.parent / "assets"
if assets_dir.exists():
hello_imgui.set_assets_folder(str(assets_dir.absolute()))
# Improved font rendering with oversampling
config = imgui.ImFontConfig()
config.oversample_h = 3
config.oversample_v = 3
font_path, font_size = theme.get_font_loading_params()
if font_path:
p = Path(font_path)
if p.is_absolute():
try:
if p.is_relative_to(assets_dir):
font_path = str(p.relative_to(assets_dir)).replace("\\", "/")
except (ValueError, AttributeError):
pass # Fallback to original font_path if relative_to fails or on old Python
# Just try loading it directly; hello_imgui will look in the assets folder
try:
self.main_font = hello_imgui.load_font_ttf_with_font_awesome_icons(font_path, font_size, config)
except Exception as e:
print(f"Failed to load main font {font_path}: {e}")
self.main_font = None
else:
self.main_font = None
try:
params = hello_imgui.FontLoadingParams(font_config=config)
self.mono_font = hello_imgui.load_font("fonts/MapleMono-Regular.ttf", font_size, params)
except Exception as e:
print(f"Failed to load mono font: {e}")
self.mono_font = None
def _post_init(self) -> None:
theme.apply_current()
def run(self) -> None:
"""Initializes the ImGui runner and starts the main application loop."""
if "--headless" in sys.argv:
print("Headless mode active")
self._fetch_models(self.current_provider)
import uvicorn
headless_cfg = self.config.get("headless", {})
port = headless_cfg.get("port", 8000)
api = self.create_api()
uvicorn.run(api, host="0.0.0.0", port=port)
else:
theme.load_from_config(self.config)
self.runner_params = hello_imgui.RunnerParams()
self.runner_params.app_window_params.window_title = "manual slop"
self.runner_params.app_window_params.window_geometry.size = (1680, 1200)
self.runner_params.imgui_window_params.enable_viewports = getattr(self, "ui_multi_viewport", False)
self.runner_params.imgui_window_params.remember_theme = True
self.runner_params.imgui_window_params.tweaked_theme = theme.get_tweaked_theme()
self.runner_params.imgui_window_params.default_imgui_window_type = hello_imgui.DefaultImGuiWindowType.provide_full_screen_dock_space
# Enforce DPI Awareness and User Scale
user_scale = theme.get_current_scale()
self.runner_params.dpi_aware_params.dpi_window_size_factor = user_scale
# Detect Monitor Refresh Rate for capping
fps_cap = 60.0
try:
# Use PowerShell to get max refresh rate across all controllers
cmd = "powershell -NoProfile -Command \"Get-CimInstance -ClassName Win32_VideoController | Select-Object -ExpandProperty CurrentRefreshRate\""
out = subprocess.check_output(cmd, shell=True).decode().splitlines()
rates = [float(r.strip()) for r in out if r.strip().isdigit()]
if rates: fps_cap = max(rates)
except Exception: pass
# Enable idling with monitor refresh rate to effectively cap FPS
self.runner_params.fps_idling.enable_idling = True
self.runner_params.fps_idling.fps_idle = fps_cap
self.runner_params.imgui_window_params.show_menu_bar = True
self.runner_params.imgui_window_params.show_menu_view_themes = True
self.runner_params.ini_folder_type = hello_imgui.IniFolderType.current_folder
self.runner_params.ini_filename = "manualslop_layout.ini"
self.runner_params.callbacks.show_gui = self._gui_func
self.runner_params.callbacks.show_menus = self._show_menus
self.runner_params.callbacks.load_additional_fonts = self._load_fonts
self.runner_params.callbacks.setup_imgui_style = theme.apply_current
self.runner_params.callbacks.post_init = self._post_init
self._fetch_models(self.current_provider)
md_options = markdown_helper.get_renderer().options
immapp.run(self.runner_params, add_ons_params=immapp.AddOnsParams(with_markdown_options=md_options))
# On exit
self.shutdown()
session_logger.close_session()
def main() -> None:
app = App()
app.run()
if __name__ == "__main__":
main()