feat(gui): Refactor text viewer to use rich rendering and toolbar

This commit is contained in:
2026-03-17 23:10:33 -04:00
parent 74c9d4b992
commit a91b8dcc99
2 changed files with 67 additions and 9 deletions

View File

@@ -40,7 +40,7 @@ else:
win32con = None
from pydantic import BaseModel
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced
PROVIDERS: list[str] = ["gemini", "anthropic", "gemini_cli", "deepseek", "minimax"]
COMMS_CLAMP_CHARS: int = 300
@@ -107,6 +107,7 @@ class App:
self.controller.init_state()
self.show_windows.setdefault("Diagnostics", False)
self.controller.start_services(self)
self.controller._predefined_callbacks['_render_text_viewer'] = self._render_text_viewer
self.show_preset_manager_window = False
self.show_tool_preset_manager_window = False
self.show_persona_editor_window = False
@@ -115,6 +116,7 @@ class App:
self.text_viewer_content = ''
self.text_viewer_type = 'text'
self.text_viewer_wrap = True
self._text_viewer_editor: Optional[ced.TextEditor] = None
self.ui_active_tool_preset = ""
self.ui_active_bias_profile = ""
self.ui_active_persona = ""
@@ -286,9 +288,9 @@ class App:
f.write(data)
# ---------------------------------------------------------------- helpers
def _render_text_viewer(self, label: str, content: str, text_type: str = 'text') -> None:
def _render_text_viewer(self, label: str, content: str, text_type: str = 'text', force_open: bool = False) -> None:
self.text_viewer_type = text_type
if imgui.button("[+]##" + str(id(content))):
if imgui.button("[+]##" + str(id(content))) or force_open:
self.show_text_viewer = True
self.text_viewer_title = label
self.text_viewer_content = content
@@ -1003,14 +1005,42 @@ class App:
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()
# Toolbar
if imgui.button("Copy"):
imgui.set_clipboard_text(self.text_viewer_content)
imgui.same_line()
_, self.text_viewer_wrap = imgui.checkbox("Word Wrap", self.text_viewer_wrap)
imgui.separator()
renderer = markdown_helper.get_renderer()
tv_type = getattr(self, "text_viewer_type", "text")
if tv_type == 'markdown':
imgui.begin_child("tv_md_scroll", imgui.ImVec2(-1, -1), True)
markdown_helper.render(self.text_viewer_content, context_id='text_viewer')
imgui.end_child()
elif tv_type in renderer._lang_map:
if self._text_viewer_editor is None:
self._text_viewer_editor = ced.TextEditor()
self._text_viewer_editor.set_read_only_enabled(True)
self._text_viewer_editor.set_show_line_numbers_enabled(True)
# Sync text and language
lang_id = renderer._lang_map[tv_type]
if self._text_viewer_editor.get_text().strip() != self.text_viewer_content.strip():
self._text_viewer_editor.set_text(self.text_viewer_content)
self._text_viewer_editor.set_language_definition(lang_id)
self._text_viewer_editor.render('##tv_editor', a_size=imgui.ImVec2(-1, -1))
else:
imgui.input_text_multiline("##tv_c", self.text_viewer_content, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
if self.text_viewer_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):

View File

@@ -0,0 +1,28 @@
import pytest
import time
from src.api_hook_client import ApiHookClient
def test_text_viewer_state_update(live_gui) -> None:
"""
Verifies that we can set text viewer state and it is reflected in GUI state.
"""
client = ApiHookClient()
label = "Test Viewer Label"
content = "This is test content for the viewer."
text_type = "markdown"
# Add a task to push a custom callback that mutates the app state
def set_viewer_state(app):
app.show_text_viewer = True
app.text_viewer_title = label
app.text_viewer_content = content
app.text_viewer_type = text_type
client.push_event("custom_callback", {"callback": set_viewer_state})
time.sleep(0.5)
state = client.get_gui_state()
assert state is not None
assert state.get('show_text_viewer') == True
assert state.get('text_viewer_title') == label
assert state.get('text_viewer_type') == text_type