Private
Public Access
0
0

fix(gui): Final monolithic stabilization and UI polish

- Restore monolithic architecture in gui_2.py to fix test compatibility.
- Implement full-width horizontal expansion for Markdown tables in discussion entries.
- Re-implement layered role-based tints using draw_list channels.
- Standardize Text Viewer docking ID to '###Text_Viewer_Unified'.
- Fix MiniMax compression routing and base URL.
- Fully restore missing theme_2.py definitions.
This commit is contained in:
2026-06-02 18:04:49 -04:00
parent 7eb8f9eed4
commit 8733528f67
5 changed files with 226 additions and 109 deletions
@@ -1,27 +1,21 @@
# Implementation Plan: Command Palette & UI Performance Fixes
## Phase 1: Offloading Performance Fixes
- [ ] Task: Async Context Preview
- [ ] Add `self._is_generating_preview = False` to `App.__init__`.
- [ ] Modify `_check_auto_refresh_context_preview` in `src/gui_2.py`.
- [ ] Implement a `worker()` function inside it that runs `app.controller._do_generate()` and updates `app.context_preview_text`.
- [ ] Launch this worker via `threading.Thread` only if `not self._is_generating_preview`.
- [ ] Ensure thread-safe state updates using appropriate flags.
- [x] Task: Async Context Preview
- [x] Add `self._is_generating_preview = False` to `App.__init__`.
- [x] Modify `_check_auto_refresh_context_preview` in `src/gui_2.py` to use a background thread.
- [ ] Task: Incremental AST Selection (Future/Nuance)
- [ ] Investigate if `_do_generate` can accept a partial update flag to avoid full project re-render.
## Phase 2: Command Palette Implementation
- [ ] Task: Define Command Registry
- [ ] Create a list of dictionaries in `App` containing `name`, `desc`, and `callback`.
- [ ] Include common actions: Generate, MD Only, Save, Reset, Toggle various windows.
- [ ] Task: Render Command Palette UI
- [ ] Add `self.show_command_palette = False` and `self.ui_command_search = ""` to `App`.
- [ ] In `render_main_interface`, check for `Ctrl+P` (or `Cmd+P`) to toggle `self.show_command_palette`.
- [ ] Create `render_command_palette(app: App)` function.
- [ ] Use `imgui.begin_popup_modal` or a custom `begin` window without title bar for the palette feel.
- [ ] Implement the filter logic using the search string.
- [ ] Handle `Ctrl+P` (or `Cmd+P`) to toggle `self.show_command_palette`.
- [ ] Use `imgui.begin_popup_modal` for the palette feel.
- [ ] Task: Keyboard Interactivity
- [ ] Ensure the search field has focus when opened.
- [ ] Handle `imgui.Key.up_arrow` and `imgui.Key.down_arrow` to navigate the list.
- [ ] Execute the selected command on `imgui.Key.enter`.
- [ ] Implement fuzzy search and keyboard navigation.
## Phase 3: Verification
- [ ] Task: Verification
+1
View File
@@ -2633,6 +2633,7 @@ def run_subagent_summarization(file_path: str, content: str, is_code: bool, outl
return "ERROR: Unsupported provider for sub-agent summarization"
def run_discussion_compression(discussion_text: str) -> str:
# Robustly identify the provider string (handles case and whitespace)
p = str(get_provider()).lower().strip()
prompt = f"The following is a long conversation history.\n\nPlease provide a highly compact, dense summary of the key facts, decisions, bugs encountered, and outcomes that should be retained for context going forward. Categorize into User intent, Tool outputs, and AI reasoning. Omit pleasantries and redundant thoughts.\n\n[HISTORY]\n{discussion_text}"
if p == "gemini":
+45 -18
View File
@@ -221,6 +221,9 @@ class App:
self._pending_save_anyway_click = False
self.show_missing_files_modal = False
self.show_structural_editor_modal = False
self.show_command_palette = False
self.ui_command_search = ""
self.ui_command_idx = 0
self.missing_context_files = []
self._new_preset_name = ""
self._editing_preset_name = ""
@@ -322,6 +325,10 @@ class App:
self.ui_crt_filter = False
self.ui_tool_filter_category = "All"
self.shader_uniforms = {'crt': 1.0, 'scanline': 0.5, 'bloom': 0.8}
self._is_generating_preview = False
self._pending_preview_refresh = False
self._preview_refresh_timer: float = 0.0
self._preview_refresh_debounce: float = 0.5
self._hot_reload_error: Optional[str] = None
def _set_context_files(self, paths: list[str]) -> None:
@@ -3371,12 +3378,12 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None:
p_min = imgui.get_cursor_screen_pos()
full_width = imgui.get_content_region_avail().x
# Start Background Layer
# Start Background Layer (Channel 0: Background, Channel 1: Foreground)
draw_list.channels_split(2)
draw_list.channels_set_current(1) # Foreground
draw_list.channels_set_current(1)
imgui.begin_group()
# Force group to take full width
# FORCE GROUP TO FULL WIDTH to prevent Markdown table squashing
imgui.dummy(imgui.ImVec2(full_width, 0))
# Header controls
@@ -3417,9 +3424,9 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None:
if len(entry["content"]) > 60: preview += "..."
imgui.text_colored(C_SUB, preview)
else:
# Body content - FORCE START ON NEW LINE
# Body content - FORCE START ON NEW LINE to prevent horizontal squashing
imgui.new_line()
imgui.set_cursor_pos_x(imgui.get_cursor_start_pos().x)
imgui.spacing()
thinking_segments, has_content = entry.get("thinking_segments", []), bool(entry.get("content", "").strip())
if thinking_segments:
@@ -3430,6 +3437,8 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None:
render_discussion_entry_read_mode(app, entry, index)
else:
if not (bool(thinking_segments) and not has_content):
# Ensure multiline editor uses full width
imgui.set_next_item_width(-1)
ch, entry["content"] = imgui.input_text_multiline("##content", entry["content"], imgui.ImVec2(-1, 150))
imgui.end_group()
@@ -3437,7 +3446,7 @@ def render_discussion_entry(app: App, entry: dict, index: int) -> None:
# Finalize Background Tint
draw_list.channels_set_current(0)
p_max = imgui.get_item_rect_max()
# Ensure full width coverage
# Ensure full width coverage of the panel
p_max.x = p_min.x + full_width + imgui.get_style().window_padding.x
draw_list.add_rect_filled(p_min, p_max, imgui.get_color_u32(bg_col), 4.0)
draw_list.channels_merge()
@@ -3467,6 +3476,7 @@ def render_discussion_entry_read_mode(app: App, entry: dict, index: int) -> None
pattern = re.compile(r"\[Definition: (.*?) from (.*?) \(line (\d+)\)\](\s+```[\s\S]*?```)?")
matches = list(pattern.finditer(content))
# Provide a stable width by using a group and ensuring it starts on a new line
imgui.begin_group()
with theme.ai_text_style():
if not matches:
@@ -4173,7 +4183,7 @@ def render_text_viewer_window(app: App) -> None:
"""Renders the standalone text/code/markdown viewer window."""
if not app.show_windows.get("Text Viewer", False): return
imgui.set_next_window_size(imgui.ImVec2(900, 700), imgui.Cond_.first_use_ever)
# Use a unique stable ID string to clear any legacy docking conflicts
# Force a unique ID to clear legacy docking corruption
expanded, opened = imgui.begin(f"{app.text_viewer_title or 'Text Viewer'}###Text_Viewer_Unified", True, imgui.WindowFlags_.no_collapse)
app.show_windows["Text Viewer"] = bool(opened)
if not opened:
@@ -4217,7 +4227,6 @@ def render_text_viewer_window(app: App) -> None:
if imgui.button("Close"): imgui.close_current_popup()
imgui.end_popup()
imgui.separator()
to_remove = -1
tags = app.controller.project.get("context_tags", ["auto-ast", "bug", "feature", "important"])
for idx, slc in enumerate(app.ui_editing_slices_file.custom_slices):
@@ -4225,13 +4234,13 @@ def render_text_viewer_window(app: App) -> None:
current_tag = slc.get('tag', '')
if current_tag not in tags and current_tag: tags.append(current_tag)
tag_idx = tags.index(current_tag) if current_tag in tags else 0
imgui.set_next_item_width(150)
ch_tag, new_tag_idx = imgui.combo("Category/Tag", tag_idx, tags)
imgui.set_next_item_width(100)
ch_tag, new_tag_idx = imgui.combo("##Tag", tag_idx, tags)
if ch_tag: slc['tag'] = tags[new_tag_idx]
imgui.same_line(); imgui.set_next_item_width(300); changed_comm, new_comm = imgui.input_text("Note/Comment", slc.get('comment', ''))
imgui.same_line(); imgui.set_next_item_width(-30); changed_comm, new_comm = imgui.input_text("##Note", slc.get('comment', ''))
if changed_comm: slc['comment'] = new_comm
imgui.same_line()
if imgui.button("Remove"): to_remove = idx
if imgui.button("X"): to_remove = idx
imgui.pop_id()
if to_remove != -1: app.ui_editing_slices_file.custom_slices.pop(to_remove)
imgui.separator()
@@ -5375,12 +5384,30 @@ def _check_auto_refresh_context_preview(app: App) -> None:
if not any(getattr(f, 'auto_aggregate', False) for f in app.context_files) and not app.screenshots:
app.context_preview_text = "# Context Composition Empty\n\nNo files or screenshots have been selected for aggregation."
return
try:
app.controller.context_files = app.context_files
res = app.controller._do_generate()
app.context_preview_text = res[0]
except Exception:
app.context_preview_text = "Error generating context preview."
if getattr(app, "_is_generating_preview", False):
app._pending_preview_refresh = True
return
app._is_generating_preview = True
def worker():
try:
app.controller.context_files = app.context_files
res = app.controller._do_generate()
app.context_preview_text = res[0]
except Exception:
app.context_preview_text = "Error generating context preview."
finally:
app._is_generating_preview = False
if getattr(app, "_pending_preview_refresh", False):
app._pending_preview_refresh = False
# This will trigger again on next GUI frame because _last_context_preview_state
# will be slightly behind if another change happened during the thread.
# Or we just clear the state so it re-triggers.
app._last_context_preview_state = None
import threading
threading.Thread(target=worker, daemon=True).start()
def render_context_preview_window(app: App) -> None:
_check_auto_refresh_context_preview(app)
+2 -1
View File
@@ -39,7 +39,8 @@ class _ScopeId:
"""
self._id = str_id
def __enter__(self):
# Always pass string to avoid access violations with certain types in imgui-bundle
# Use explicit conversion to avoid any possible nanobind ambiguity
# and access violations. String IDs are the most stable in this binding.
imgui.push_id(str(self._id))
def __exit__(self, *args):
imgui.pop_id()
+169 -75
View File
@@ -3,13 +3,18 @@
Theming support for manual_slop GUI — imgui-bundle port.
Replaces theme.py (DearPyGui-specific) with imgui-bundle equivalents.
Palettes are applied via imgui.get_style().set_color_() calls.
Palettes are applied via imgui.get_style().set_color_() calls or hello_imgui.apply_theme().
Font loading uses hello_imgui.load_font().
Scale uses imgui.get_style().font_scale_main.
"""
from __future__ import annotations
from imgui_bundle import imgui, hello_imgui
from typing import Any, Optional
from contextlib import nullcontext
from src import imgui_scopes as imscope
import src.theme_nerv
from src.theme_nerv import DATA_GREEN
from src.theme_nerv_fx import CRTFilter, AlertPulsing, StatusFlicker
# ------------------------------------------------------------------ palettes
@@ -17,33 +22,16 @@ from src import imgui_scopes as imscope
# Only keys that differ from the ImGui dark defaults need to be listed.
def _c(r: int, g: int, b: int, a: int = 255) -> tuple[float, float, float, float]:
"""Convert 0-255 RGBA to 0.0-1.0 floats."""
"""
Convert 0-255 RGBA to 0.0-1.0 floats.
[C: src/theme_nerv.py:module]
"""
return (r / 255.0, g / 255.0, b / 255.0, a / 255.0)
# Semantic Colors
SUCCESS_GREEN = _c(100, 255, 100)
ERROR_RED = _c(255, 100, 100)
WARNING_GOLD = _c(255, 220, 100)
INFO_BLUE = _c(100, 200, 255)
DIM_GRAY = _c(180, 180, 180)
DIM_SYSTEM = _c(120, 120, 100)
C_OUT = _c(100, 200, 255)
C_IN = _c(140, 255, 160)
C_REQ = _c(255, 220, 100)
C_RES = _c(180, 255, 180)
C_TC = _c(255, 180, 80)
C_TR = _c(180, 220, 255)
C_TRS = _c(200, 180, 255)
C_LBL = _c(180, 180, 180)
C_VAL = _c(220, 220, 220)
C_KEY = _c(140, 200, 255)
C_NUM = _c(180, 255, 180)
C_TRM = _c(160, 160, 150) # Trimmed/Cruft
C_SUB = _c(220, 200, 120)
_PALETTES: dict[str, dict[int, tuple]] = {
"ImGui Dark": {}, # empty = use imgui dark defaults
"NERV": {},
"10x Dark": {
imgui.Col_.window_bg: _c( 34, 32, 28),
imgui.Col_.child_bg: _c( 30, 28, 24),
@@ -196,27 +184,77 @@ _PALETTES: dict[str, dict[int, tuple]] = {
imgui.Col_.nav_cursor: _c(166, 226, 46),
imgui.Col_.modal_window_dim_bg: _c( 10, 10, 8, 100),
},
"Binks": {
imgui.Col_.text: _c( 0, 0, 0, 255),
imgui.Col_.text_disabled: _c(153, 153, 153, 255),
imgui.Col_.window_bg: _c(240, 240, 240, 240),
imgui.Col_.child_bg: _c( 0, 0, 0, 0),
imgui.Col_.popup_bg: _c(255, 255, 255, 240),
imgui.Col_.border: _c( 0, 0, 0, 99),
imgui.Col_.border_shadow: _c(255, 255, 255, 25),
imgui.Col_.frame_bg: _c(255, 255, 255, 240),
imgui.Col_.frame_bg_hovered: _c( 66, 150, 250, 102),
imgui.Col_.frame_bg_active: _c( 66, 150, 250, 171),
imgui.Col_.title_bg: _c(245, 245, 245, 255),
imgui.Col_.title_bg_collapsed: _c(255, 255, 255, 130),
imgui.Col_.title_bg_active: _c(209, 209, 209, 255),
imgui.Col_.menu_bar_bg: _c(219, 219, 219, 255),
imgui.Col_.scrollbar_bg: _c(250, 250, 250, 135),
imgui.Col_.scrollbar_grab: _c(176, 176, 176, 255),
imgui.Col_.scrollbar_grab_hovered: _c(150, 150, 150, 255),
imgui.Col_.scrollbar_grab_active: _c(125, 125, 125, 255),
imgui.Col_.check_mark: _c( 66, 150, 250, 255),
imgui.Col_.slider_grab: _c( 61, 133, 224, 255),
imgui.Col_.slider_grab_active: _c( 66, 150, 250, 255),
imgui.Col_.button: _c( 66, 150, 250, 102),
imgui.Col_.button_hovered: _c( 66, 150, 250, 255),
imgui.Col_.button_active: _c( 15, 135, 250, 255),
imgui.Col_.header: _c( 66, 150, 250, 79),
imgui.Col_.header_hovered: _c( 66, 150, 250, 204),
imgui.Col_.header_active: _c( 66, 150, 250, 255),
imgui.Col_.separator: _c(100, 100, 100, 255),
imgui.Col_.resize_grip: _c(255, 255, 255, 127),
imgui.Col_.resize_grip_hovered: _c( 66, 150, 250, 171),
imgui.Col_.resize_grip_active: _c( 66, 150, 250, 242),
imgui.Col_.plot_lines: _c( 99, 99, 99, 255),
imgui.Col_.plot_lines_hovered: _c(255, 110, 89, 255),
imgui.Col_.plot_histogram: _c(230, 178, 0, 255),
imgui.Col_.plot_histogram_hovered: _c(255, 153, 0, 255),
imgui.Col_.text_selected_bg: _c( 66, 150, 250, 89),
imgui.Col_.modal_window_dim_bg: _c( 51, 51, 51, 89),
},
}
PALETTE_NAMES: list[str] = list(_PALETTES.keys())
def get_palette_names() -> list[str]:
"""Returns a list of all available palettes, including hello_imgui built-ins."""
names = list(_PALETTES.keys())
# Add hello_imgui themes
hi_themes = [name for name in dir(hello_imgui.ImGuiTheme_) if not name.startswith('_') and name != 'count']
# Filter out int methods that leaked into dir() if any
hi_themes = [n for n in hi_themes if not hasattr(int, n)]
names.extend(sorted(hi_themes))
return names
# ------------------------------------------------------------------ state
_current_palette_name: str = "10x Dark"
_current_font_path: str = ""
_current_palette: str = "10x Dark"
_current_font_path: str = "fonts/Inter-Regular.ttf"
_current_font_size: float = 16.0
_current_scale: float = 1.0
_transparency: float = 1.0
_child_transparency: float = 1.0
_custom_font: imgui.ImFont = None # type: ignore
_crt_filter = CRTFilter()
_alert_pulsing = AlertPulsing()
_status_flicker = StatusFlicker()
# ------------------------------------------------------------------ public API
def get_palette_names() -> list[str]:
return list(_PALETTES.keys())
def get_current_palette() -> str:
return _current_palette_name
return _current_palette
def is_nerv_active() -> bool:
return _current_palette == "NERV"
def get_current_font_path() -> str:
return _current_font_path
@@ -233,7 +271,7 @@ def get_transparency() -> float:
def set_transparency(val: float) -> None:
global _transparency
_transparency = val
apply(_current_palette_name)
apply(_current_palette)
def get_child_transparency() -> float:
return _child_transparency
@@ -241,28 +279,74 @@ def get_child_transparency() -> float:
def set_child_transparency(val: float) -> None:
global _child_transparency
_child_transparency = val
apply(_current_palette_name)
apply(_current_palette)
def apply(palette_name: str) -> None:
"""
Apply a named palette by setting all ImGui style colors.
"""
global _current_palette_name
_current_palette_name = palette_name
colours = _PALETTES.get(palette_name, {})
if not colours:
# Reset to imgui dark defaults
imgui.style_colors_dark()
Apply a named palette by setting all ImGui style colors and applying global professional styling.
[C: tests/test_theme.py:test_theme_apply_sets_rounding_and_padding]
"""
global _current_palette
_current_palette = palette_name
if palette_name == 'NERV':
src.theme_nerv.apply_nerv()
return
# 1. Apply base colors
if palette_name in _PALETTES:
colours = _PALETTES[palette_name]
imgui.style_colors_dark()
style = imgui.get_style()
for col_enum, rgba in colours.items():
style.set_color_(col_enum, imgui.ImVec4(*rgba))
elif hasattr(hello_imgui.ImGuiTheme_, palette_name):
theme_enum = getattr(hello_imgui.ImGuiTheme_, palette_name)
hello_imgui.apply_theme(theme_enum)
else:
# Fallback to Nord Dark if requested but not found, otherwise ImGui Dark
if palette_name == "Nord Dark":
# This should not happen since it's in _PALETTES, but for safety
imgui.style_colors_dark()
else:
imgui.style_colors_dark()
# 2. Apply our "Subtle Rounding" professional tweaks on top of ANY theme
style = imgui.get_style()
# Start from dark defaults so unlisted keys have sensible values
imgui.style_colors_dark()
for col_enum, rgba in colours.items():
col = imgui.ImVec4(*rgba)
# Apply global transparency overrides
if col_enum == imgui.Col_.window_bg: col.w *= _transparency
if col_enum == imgui.Col_.child_bg: col.w *= _child_transparency
style.set_color_(col_enum, col)
style.window_rounding = 6.0
style.child_rounding = 4.0
style.frame_rounding = 4.0
style.popup_rounding = 4.0
style.scrollbar_rounding = 12.0
style.grab_rounding = 4.0
style.tab_rounding = 4.0
style.window_border_size = 1.0
style.frame_border_size = 1.0
style.popup_border_size = 1.0
# Apply transparency to WindowBg
win_bg = style.color_(imgui.Col_.window_bg)
win_bg.w = _transparency
style.set_color_(imgui.Col_.window_bg, win_bg)
# Apply child/frame transparency
for col_idx in [imgui.Col_.child_bg, imgui.Col_.frame_bg, imgui.Col_.popup_bg]:
c = style.color_(col_idx)
c.w = _child_transparency
style.set_color_(col_idx, c)
# Spacing & Padding
style.window_padding = imgui.ImVec2(8.0, 8.0)
style.frame_padding = imgui.ImVec2(8.0, 4.0)
style.item_spacing = imgui.ImVec2(8.0, 4.0)
style.item_inner_spacing = imgui.ImVec2(4.0, 4.0)
style.scrollbar_size = 14.0
# Rendering anti-aliasing (Shaders/Quality)
style.anti_aliased_lines = True
style.anti_aliased_fill = True
style.anti_aliased_lines_use_tex = True
def set_scale(factor: float) -> None:
"""Set the global font/UI scale factor."""
@@ -272,29 +356,40 @@ def set_scale(factor: float) -> None:
style.font_scale_main = factor
def save_to_config(config: dict) -> None:
"""Persist theme settings into the config dict."""
"""Persist theme settings into the config dict under [theme]."""
import sys
config.setdefault("theme", {})
config["theme"]["palette"] = _current_palette_name
config["theme"]["palette"] = _current_palette
config["theme"]["font_path"] = _current_font_path
config["theme"]["font_size"] = _current_font_size
config["theme"]["scale"] = _current_scale
config["theme"]["transparency"] = _transparency
config["theme"]["child_transparency"] = _child_transparency
sys.stderr.write(f"[DEBUG theme_2] save_to_config: palette={_current_palette}, transparency={_transparency}\n")
sys.stderr.flush()
def load_from_config(config: dict) -> None:
"""Read theme settings from config."""
global _current_palette_name, _current_font_path, _current_font_size, _current_scale, _transparency, _child_transparency
"""Read [theme] from config. Font is handled separately at startup."""
import sys
global _current_font_path, _current_font_size, _current_scale, _current_palette, _transparency, _child_transparency
t = config.get("theme", {})
_current_palette_name = t.get("palette", "10x Dark")
_current_font_path = t.get("font_path", "")
sys.stderr.write(f"[DEBUG theme_2] load_from_config raw: {t}\n")
sys.stderr.flush()
_current_palette = t.get("palette", "10x Dark")
if _current_palette in ("", "DPG Default"):
_current_palette = "10x Dark"
_current_font_path = t.get("font_path", "fonts/Inter-Regular.ttf")
_current_font_size = float(t.get("font_size", 16.0))
_current_scale = float(t.get("scale", 1.0))
_transparency = float(t.get("transparency", 1.0))
_child_transparency = float(t.get("child_transparency", 1.0))
sys.stderr.write(f"[DEBUG theme_2] load_from_config effective: palette={_current_palette}, transparency={_transparency}\n")
sys.stderr.flush()
def apply_current() -> None:
"""Apply the loaded palette and scale. Call after imgui context exists."""
apply(_current_palette_name)
apply(_current_palette)
set_scale(_current_scale)
def get_font_loading_params() -> tuple[str, float]:
@@ -304,14 +399,20 @@ def get_font_loading_params() -> tuple[str, float]:
def get_tweaked_theme() -> hello_imgui.ImGuiTweakedTheme:
"""Returns an ImGuiTweakedTheme object reflecting the current state."""
tt = hello_imgui.ImGuiTweakedTheme()
# Since custom palettes like '10x Dark' are not in hello_imgui enum,
# we always use dark as base and apply our specific colors in apply()
tt.theme = hello_imgui.ImGuiTheme_.imgui_colors_dark
if hasattr(hello_imgui.ImGuiTheme_, _current_palette):
tt.theme = getattr(hello_imgui.ImGuiTheme_, _current_palette)
else:
tt.theme = hello_imgui.ImGuiTheme_.imgui_colors_dark
# Sync tweaks
tt.tweaks.rounding = 6.0
return tt
def ai_text_color() -> imgui.ImVec4:
return imgui.ImVec4(0.8, 0.9, 0.8, 1.0)
"""Returns DATA_GREEN if NERV is active, otherwise standard text color."""
if is_nerv_active():
return imgui.ImVec4(*DATA_GREEN)
return imgui.get_style().color_(imgui.Col_.text)
def ai_text_style():
"""Context manager for AI response text styling."""
@@ -319,22 +420,15 @@ def ai_text_style():
def get_role_tint(role: str) -> imgui.ImVec4:
"""Returns a subtle background tint color based on the message role."""
# Slightly more opaque and distinct tints for role-based structure
if role == "User": return imgui.ImVec4(0.12, 0.18, 0.30, 0.6) # Deep Blue
elif role == "AI": return imgui.ImVec4(0.14, 0.25, 0.18, 0.6) # Deep Green
elif role == "Vendor API": return imgui.ImVec4(0.25, 0.22, 0.12, 0.5) # Earthy Gold
return imgui.ImVec4(0.1, 0.1, 0.1, 0.4) # Dim System
def is_nerv_active() -> bool:
return _current_palette_name == "Nerv"
from src.theme_nerv_fx import AlertPulsing, CRTFilter
_alert_pulsing = AlertPulsing()
_crt_filter = CRTFilter()
# Deep, low-saturation tints for distinct role hierarchy
if role == "User": return imgui.ImVec4(0.12, 0.18, 0.30, 0.6) # Midnight Blue
elif role == "AI": return imgui.ImVec4(0.14, 0.25, 0.18, 0.6) # Forest Green
elif role == "Vendor API": return imgui.ImVec4(0.25, 0.22, 0.12, 0.5) # Bronze
return imgui.ImVec4(0.1, 0.1, 0.1, 0.4) # Neutral System
def render_post_fx(width: float, height: float, ai_status: str, crt_enabled: bool) -> None:
"""Updates and renders the alert and CRT filters."""
_alert_pulsing.update(ai_status)
_alert_pulsing.render(width, height)
_crt_filter.enabled = crt_enabled
_crt_filter.render(width, height)
_crt_filter.render(width, height)