Private
Public Access
0
0

fix(gui): Resolve AttributeError in imscope.indent and stabilize render loop

- Add 'indent' context manager to src/imgui_scopes.py.
- Refactor manual imgui.indent/unindent calls in src/gui_2.py to use imscope.indent.
- Fix cascading ImGui assertion failures caused by Open/Close mismatches during exceptions.
- Finalize 'Selectable Thinking Monologs' track.
This commit is contained in:
2026-06-01 19:26:53 -04:00
parent c84777e582
commit 7a434adb7c
4 changed files with 62 additions and 53 deletions
+1 -1
View File
@@ -278,5 +278,5 @@ This file tracks all major tracks for the project. Each track has its own detail
---
- [~] **Track: Selectable Thinking Monologs**
- [x] **Track: Selectable Thinking Monologs**
*Link: [./tracks/selectable_thinking_monologs_20260601/](./tracks/selectable_thinking_monologs_20260601/)*
@@ -11,8 +11,8 @@
- [x] If `thinking_read_mode` is `False` (Pure mode), use `render_selectable_label(app, f"think_text_...", content, multiline=True, height=-1)` to make the text selectable and copyable.
## Phase 2: Verification
- [ ] Task: Verification
- [ ] Verify that thinking traces in the Discussion Hub can be toggled between Pure and Read modes.
- [ ] Verify that text can be selected and copied via Ctrl+C in Pure mode.
- [ ] Verify that the change does not crash other areas rendering thinking traces (like Comms History).
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Verification' (Protocol in workflow.md)
- [x] Task: Verification
- [x] Verify that thinking traces in the Discussion Hub can be toggled between Pure and Read modes.
- [x] Verify that text can be selected and copied via Ctrl+C in Pure mode.
- [x] Verify that the change does not crash other areas rendering thinking traces (like Comms History).
- [x] Task: Conductor - User Manual Verification 'Phase 2: Verification' (Protocol in workflow.md)
+44 -46
View File
@@ -2517,26 +2517,25 @@ def render_persona_editor_window(app: App, is_embedded: bool = False) -> None:
imgui.same_line(imgui.get_content_region_avail().x - 30);
if imgui.button("x"): to_remove.append(i)
if is_expanded:
imgui.indent(20)
if imgui.begin_table("model_settings", 2, imgui.TableFlags_.borders_inner_v):
imgui.table_setup_column("Label", imgui.TableColumnFlags_.width_fixed, 120); imgui.table_setup_column("Control", imgui.TableColumnFlags_.width_stretch)
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Provider:"); imgui.table_next_column(); imgui.set_next_item_width(-1)
p_idx = providers.index(prov) + 1 if prov in providers else 0; ch_p, p_idx = imgui.combo("##prov", p_idx, ["None"] + providers)
if ch_p: entry["provider"] = providers[p_idx-1] if p_idx > 0 else ""
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Model:"); imgui.table_next_column(); imgui.set_next_item_width(-1)
m_list = app.controller.all_available_models.get(entry.get("provider", ""), []); m_idx = m_list.index(mod) + 1 if mod in m_list else 0
ch_m, m_idx = imgui.combo("##model", m_idx, ["None"] + m_list)
if ch_m: entry["model"] = m_list[m_idx-1] if m_idx > 0 else ""
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Temperature:"); imgui.table_next_column(); cw = imgui.get_content_region_avail().x
imgui.set_next_item_width(cw * 0.7); _, entry["temperature"] = imgui.slider_float("##ts", entry.get("temperature", 0.7), 0.0, 2.0, "%.1f")
imgui.same_line(); imgui.set_next_item_width(-1); _, entry["temperature"] = imgui.input_float("##ti", entry.get("temperature", 0.7), 0.1, 0.1, "%.1f")
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Top-P:"); imgui.table_next_column()
imgui.set_next_item_width(cw * 0.7); _, entry["top_p"] = imgui.slider_float("##tp_s", entry.get("top_p", 1.0), 0.0, 1.0, "%.2f")
imgui.same_line(); imgui.set_next_item_width(-1); _, entry["top_p"] = imgui.input_float("##tp_i", entry.get("top_p", 1.0), 0.05, 0.05, "%.2f")
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Max Tokens:"); imgui.table_next_column(); imgui.set_next_item_width(-1); _, entry["max_output_tokens"] = imgui.input_int("##maxt", entry.get("max_output_tokens", 4096))
imgui.table_next_row(); imgui.table_next_column(); imgui.text("History Limit:"); imgui.table_next_column(); imgui.set_next_item_width(-1); _, entry["history_trunc_limit"] = imgui.input_int("##hist", entry.get("history_trunc_limit", 900000))
imgui.end_table()
imgui.unindent(20)
with imscope.indent(20):
if imgui.begin_table("model_settings", 2, imgui.TableFlags_.borders_inner_v):
imgui.table_setup_column("Label", imgui.TableColumnFlags_.width_fixed, 120); imgui.table_setup_column("Control", imgui.TableColumnFlags_.width_stretch)
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Provider:"); imgui.table_next_column(); imgui.set_next_item_width(-1)
p_idx = providers.index(prov) + 1 if prov in providers else 0; ch_p, p_idx = imgui.combo("##prov", p_idx, ["None"] + providers)
if ch_p: entry["provider"] = providers[p_idx-1] if p_idx > 0 else ""
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Model:"); imgui.table_next_column(); imgui.set_next_item_width(-1)
m_list = app.controller.all_available_models.get(entry.get("provider", ""), []); m_idx = m_list.index(mod) + 1 if mod in m_list else 0
ch_m, m_idx = imgui.combo("##model", m_idx, ["None"] + m_list)
if ch_m: entry["model"] = m_list[m_idx-1] if m_idx > 0 else ""
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Temperature:"); imgui.table_next_column(); cw = imgui.get_content_region_avail().x
imgui.set_next_item_width(cw * 0.7); _, entry["temperature"] = imgui.slider_float("##ts", entry.get("temperature", 0.7), 0.0, 2.0, "%.1f")
imgui.same_line(); imgui.set_next_item_width(-1); _, entry["temperature"] = imgui.input_float("##ti", entry.get("temperature", 0.7), 0.1, 0.1, "%.1f")
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Top-P:"); imgui.table_next_column()
imgui.set_next_item_width(cw * 0.7); _, entry["top_p"] = imgui.slider_float("##tp_s", entry.get("top_p", 1.0), 0.0, 1.0, "%.2f")
imgui.same_line(); imgui.set_next_item_width(-1); _, entry["top_p"] = imgui.input_float("##tp_i", entry.get("top_p", 1.0), 0.05, 0.05, "%.2f")
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Max Tokens:"); imgui.table_next_column(); imgui.set_next_item_width(-1); _, entry["max_output_tokens"] = imgui.input_int("##maxt", entry.get("max_output_tokens", 4096))
imgui.table_next_row(); imgui.table_next_column(); imgui.text("History Limit:"); imgui.table_next_column(); imgui.set_next_item_width(-1); _, entry["history_trunc_limit"] = imgui.input_int("##hist", entry.get("history_trunc_limit", 900000))
imgui.end_table()
imgui.pop_id()
for i in reversed(to_remove): app._editing_persona_preferred_models_list.pop(i)
imgui.end_child()
@@ -4654,34 +4653,33 @@ def render_thinking_trace(app: App, entry: dict, segments: list[dict], entry_ind
return
with imscope.style_color(imgui.Col_.child_bg, vec4(40, 35, 25, 180)), \
theme.ai_text_style():
imgui.indent()
show_content = True
if not is_standalone:
header_label = f"Monologue ({len(segments)} traces)###thinking_header_{entry_index}"
show_content = imgui.collapsing_header(header_label)
if show_content:
thinking_read_mode = entry.get("thinking_read_mode", True)
if imgui.button(f"[Pure]##think_pure_{entry_index}" if thinking_read_mode else f"[Read]##think_read_{entry_index}"):
entry["thinking_read_mode"] = not thinking_read_mode
imgui.same_line()
imgui.text_colored(vec4(180, 150, 80), "Selectable toggle")
h = 150 if is_standalone else 100
with imscope.child(f"thinking_content_{entry_index}", 0, h, True):
for idx, seg in enumerate(segments):
content = seg.get("content", "")
marker = seg.get("marker", "thinking")
with imscope.id(f"think_{entry_index}_{idx}"):
imgui.text_colored(vec4(180, 150, 80), f"[{marker}]")
if thinking_read_mode:
if app.ui_word_wrap:
with imscope.text_wrap(imgui.get_content_region_avail().x):
with imscope.indent():
show_content = True
if not is_standalone:
header_label = f"Monologue ({len(segments)} traces)###thinking_header_{entry_index}"
show_content = imgui.collapsing_header(header_label)
if show_content:
thinking_read_mode = entry.get("thinking_read_mode", True)
if imgui.button(f"[Pure]##think_pure_{entry_index}" if thinking_read_mode else f"[Read]##think_read_{entry_index}"):
entry["thinking_read_mode"] = not thinking_read_mode
imgui.same_line()
imgui.text_colored(vec4(180, 150, 80), "Selectable toggle")
h = 150 if is_standalone else 100
with imscope.child(f"thinking_content_{entry_index}", 0, h, True):
for idx, seg in enumerate(segments):
content = seg.get("content", "")
marker = seg.get("marker", "thinking")
with imscope.id(f"think_{entry_index}_{idx}"):
imgui.text_colored(vec4(180, 150, 80), f"[{marker}]")
if thinking_read_mode:
if app.ui_word_wrap:
with imscope.text_wrap(imgui.get_content_region_avail().x):
imgui.text(content)
else:
imgui.text(content)
else:
imgui.text(content)
else:
render_selectable_label(app, f"think_text_{entry_index}_{idx}", content, multiline=True, height=-1)
imgui.separator()
imgui.unindent()
render_selectable_label(app, f"think_text_{entry_index}_{idx}", content, multiline=True, height=-1)
imgui.separator()
def render_selectable_label(app: App, label: str, value: str, width: float = 0.0, multiline: bool = False, height: float = 0.0, color: Optional[imgui.ImVec4] = None) -> None:
with imscope.id(label + str(hash(value))):
+12 -1
View File
@@ -1,4 +1,5 @@
from __future__ import annotations
from typing import Any
from imgui_bundle import imgui
from imgui_bundle import imgui_node_editor
@@ -43,6 +44,16 @@ class _ScopeId:
imgui.pop_id()
return False
def indent(width: float = 0.0): return _ScopeIndent(width)
class _ScopeIndent:
def __init__(self, width: float):
self._width = width
def __enter__(self):
imgui.indent(self._width)
def __exit__(self, *args):
imgui.unindent(self._width)
return False
def menu(label: str): return _ScopeMenu(label)
class _ScopeMenu:
def __init__(self, label: str):
@@ -256,4 +267,4 @@ class _ScopeWindow:
return self._result
def __exit__(self, *args):
imgui.end()
return False
return False