fix(gui): move lock init before use, protect disc_entries with threading lock
This commit is contained in:
125
gui_2.py
125
gui_2.py
@@ -161,6 +161,16 @@ class App:
|
|||||||
"""The main ImGui interface orchestrator for Manual Slop."""
|
"""The main ImGui interface orchestrator for Manual Slop."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
# Initialize locks first to avoid initialization order issues
|
||||||
|
self._send_thread_lock = threading.Lock()
|
||||||
|
self._disc_entries_lock = threading.Lock()
|
||||||
|
self._pending_comms_lock = threading.Lock()
|
||||||
|
self._pending_tool_calls_lock = threading.Lock()
|
||||||
|
self._pending_history_adds_lock = threading.Lock()
|
||||||
|
self._pending_gui_tasks_lock = threading.Lock()
|
||||||
|
self._pending_dialog_lock = threading.Lock()
|
||||||
|
self._api_event_queue_lock = threading.Lock()
|
||||||
|
|
||||||
self.config = load_config()
|
self.config = load_config()
|
||||||
self.event_queue = events.AsyncEventQueue()
|
self.event_queue = events.AsyncEventQueue()
|
||||||
self._loop = asyncio.new_event_loop()
|
self._loop = asyncio.new_event_loop()
|
||||||
@@ -185,6 +195,7 @@ class App:
|
|||||||
self.disc_roles: list[str] = list(disc_sec.get("roles", list(DISC_ROLES)))
|
self.disc_roles: list[str] = list(disc_sec.get("roles", list(DISC_ROLES)))
|
||||||
self.active_discussion = disc_sec.get("active", "main")
|
self.active_discussion = disc_sec.get("active", "main")
|
||||||
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
|
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
|
||||||
|
with self._disc_entries_lock:
|
||||||
self.disc_entries: list[dict[str, Any]] = _parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
self.disc_entries: list[dict[str, Any]] = _parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||||
self.ui_output_dir = self.project.get("output", {}).get("output_dir", "./md_gen")
|
self.ui_output_dir = self.project.get("output", {}).get("output_dir", "./md_gen")
|
||||||
self.ui_files_base_dir = self.project.get("files", {}).get("base_dir", ".")
|
self.ui_files_base_dir = self.project.get("files", {}).get("base_dir", ".")
|
||||||
@@ -216,7 +227,6 @@ class App:
|
|||||||
self.last_md_path: Path | None = None
|
self.last_md_path: Path | None = None
|
||||||
self.last_file_items: list[Any] = []
|
self.last_file_items: list[Any] = []
|
||||||
self.send_thread: threading.Thread | None = None
|
self.send_thread: threading.Thread | None = None
|
||||||
self._send_thread_lock = threading.Lock()
|
|
||||||
self.models_thread: threading.Thread | None = None
|
self.models_thread: threading.Thread | None = None
|
||||||
_default_windows = {
|
_default_windows = {
|
||||||
"Context Hub": True,
|
"Context Hub": True,
|
||||||
@@ -241,7 +251,6 @@ class App:
|
|||||||
self.text_viewer_content = ""
|
self.text_viewer_content = ""
|
||||||
self._pending_dialog: ConfirmDialog | None = None
|
self._pending_dialog: ConfirmDialog | None = None
|
||||||
self._pending_dialog_open = False
|
self._pending_dialog_open = False
|
||||||
self._pending_dialog_lock = threading.Lock()
|
|
||||||
self._pending_actions: dict[str, ConfirmDialog] = {}
|
self._pending_actions: dict[str, ConfirmDialog] = {}
|
||||||
self._pending_ask_dialog = False
|
self._pending_ask_dialog = False
|
||||||
self._ask_dialog_open = False
|
self._ask_dialog_open = False
|
||||||
@@ -270,11 +279,8 @@ class App:
|
|||||||
self._tool_log: list[tuple[str, str, float]] = []
|
self._tool_log: list[tuple[str, str, float]] = []
|
||||||
self._comms_log: list[dict[str, Any]] = []
|
self._comms_log: list[dict[str, Any]] = []
|
||||||
self._pending_comms: list[dict[str, Any]] = []
|
self._pending_comms: list[dict[str, Any]] = []
|
||||||
self._pending_comms_lock = threading.Lock()
|
|
||||||
self._pending_tool_calls: list[tuple[str, str, float]] = []
|
self._pending_tool_calls: list[tuple[str, str, float]] = []
|
||||||
self._pending_tool_calls_lock = threading.Lock()
|
|
||||||
self._pending_history_adds: list[dict[str, Any]] = []
|
self._pending_history_adds: list[dict[str, Any]] = []
|
||||||
self._pending_history_adds_lock = threading.Lock()
|
|
||||||
self._trigger_blink = False
|
self._trigger_blink = False
|
||||||
self._is_blinking = False
|
self._is_blinking = False
|
||||||
self._blink_start_time = 0.0
|
self._blink_start_time = 0.0
|
||||||
@@ -285,7 +291,6 @@ class App:
|
|||||||
self._scroll_comms_to_bottom = False
|
self._scroll_comms_to_bottom = False
|
||||||
self._scroll_tool_calls_to_bottom = False
|
self._scroll_tool_calls_to_bottom = False
|
||||||
self._pending_gui_tasks: list[dict[str, Any]] = []
|
self._pending_gui_tasks: list[dict[str, Any]] = []
|
||||||
self._pending_gui_tasks_lock = threading.Lock()
|
|
||||||
self.session_usage = {"input_tokens": 0, "output_tokens": 0, "cache_read_input_tokens": 0, "cache_creation_input_tokens": 0}
|
self.session_usage = {"input_tokens": 0, "output_tokens": 0, "cache_read_input_tokens": 0, "cache_creation_input_tokens": 0}
|
||||||
self._token_budget_pct = 0.0
|
self._token_budget_pct = 0.0
|
||||||
self._token_budget_current = 0
|
self._token_budget_current = 0
|
||||||
@@ -427,6 +432,8 @@ class App:
|
|||||||
}
|
}
|
||||||
self._discussion_names_cache: list[str] = []
|
self._discussion_names_cache: list[str] = []
|
||||||
self._discussion_names_dirty: bool = True
|
self._discussion_names_dirty: bool = True
|
||||||
|
self.hook_server = api_hooks.HookServer(self)
|
||||||
|
self.hook_server.start()
|
||||||
|
|
||||||
def create_api(self) -> FastAPI:
|
def create_api(self) -> FastAPI:
|
||||||
"""Creates and configures the FastAPI application for headless mode."""
|
"""Creates and configures the FastAPI application for headless mode."""
|
||||||
@@ -669,6 +676,7 @@ class App:
|
|||||||
self.disc_roles = list(disc_sec.get("roles", list(DISC_ROLES)))
|
self.disc_roles = list(disc_sec.get("roles", list(DISC_ROLES)))
|
||||||
self.active_discussion = disc_sec.get("active", "main")
|
self.active_discussion = disc_sec.get("active", "main")
|
||||||
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
|
disc_data = disc_sec.get("discussions", {}).get(self.active_discussion, {})
|
||||||
|
with self._disc_entries_lock:
|
||||||
self.disc_entries = _parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
self.disc_entries = _parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||||
proj = self.project
|
proj = self.project
|
||||||
self.ui_output_dir = proj.get("output", {}).get("output_dir", "./md_gen")
|
self.ui_output_dir = proj.get("output", {}).get("output_dir", "./md_gen")
|
||||||
@@ -712,6 +720,7 @@ class App:
|
|||||||
if self.active_track:
|
if self.active_track:
|
||||||
track_history = project_manager.load_track_history(self.active_track.id, self.ui_files_base_dir)
|
track_history = project_manager.load_track_history(self.active_track.id, self.ui_files_base_dir)
|
||||||
if track_history:
|
if track_history:
|
||||||
|
with self._disc_entries_lock:
|
||||||
self.disc_entries = _parse_history_entries(track_history, self.disc_roles)
|
self.disc_entries = _parse_history_entries(track_history, self.disc_roles)
|
||||||
|
|
||||||
def _cb_load_track(self, track_id: str) -> None:
|
def _cb_load_track(self, track_id: str) -> None:
|
||||||
@@ -735,6 +744,7 @@ class App:
|
|||||||
self.active_tickets = [asdict(t) if not isinstance(t, dict) else t for t in tickets]
|
self.active_tickets = [asdict(t) if not isinstance(t, dict) else t for t in tickets]
|
||||||
# Load track-scoped history
|
# Load track-scoped history
|
||||||
history = project_manager.load_track_history(track_id, self.ui_files_base_dir)
|
history = project_manager.load_track_history(track_id, self.ui_files_base_dir)
|
||||||
|
with self._disc_entries_lock:
|
||||||
if history:
|
if history:
|
||||||
self.disc_entries = _parse_history_entries(history, self.disc_roles)
|
self.disc_entries = _parse_history_entries(history, self.disc_roles)
|
||||||
else:
|
else:
|
||||||
@@ -769,12 +779,20 @@ class App:
|
|||||||
self.ai_status = f"discussion not found: {name}"
|
self.ai_status = f"discussion not found: {name}"
|
||||||
return
|
return
|
||||||
self.active_discussion = name
|
self.active_discussion = name
|
||||||
|
self.active_discussion_idx = -1
|
||||||
|
discussions_root = self.project.get("discussions", [])
|
||||||
|
for i, d in enumerate(discussions_root):
|
||||||
|
if isinstance(d, dict) and d.get("title") == name:
|
||||||
|
self.active_discussion_idx = i
|
||||||
|
break
|
||||||
self._track_discussion_active = False
|
self._track_discussion_active = False
|
||||||
disc_sec["active"] = name
|
disc_sec["active"] = name
|
||||||
self._discussion_names_dirty = True
|
self._discussion_names_dirty = True
|
||||||
disc_data = discussions[name]
|
disc_data = discussions[name]
|
||||||
|
with self._disc_entries_lock:
|
||||||
self.disc_entries = _parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
self.disc_entries = _parse_history_entries(disc_data.get("history", []), self.disc_roles)
|
||||||
self.ai_status = f"discussion: {name}"
|
self.ai_status = f"discussion: {name}"
|
||||||
|
sys.stderr.write(f'[DEBUG] Switched to {name}. disc_entries len: {len(self.disc_entries)}\n')
|
||||||
|
|
||||||
def _flush_disc_entries_to_project(self) -> None:
|
def _flush_disc_entries_to_project(self) -> None:
|
||||||
history_strings = [project_manager.entry_to_str(e) for e in self.disc_entries]
|
history_strings = [project_manager.entry_to_str(e) for e in self.disc_entries]
|
||||||
@@ -827,10 +845,29 @@ class App:
|
|||||||
# ---------------------------------------------------------------- logic
|
# ---------------------------------------------------------------- logic
|
||||||
|
|
||||||
def _on_comms_entry(self, entry: dict) -> None:
|
def _on_comms_entry(self, entry: dict) -> None:
|
||||||
|
# sys.stderr.write(f"[DEBUG] _on_comms_entry: {entry.get('kind')} {entry.get('direction')}\n")
|
||||||
session_logger.log_comms(entry)
|
session_logger.log_comms(entry)
|
||||||
entry["local_ts"] = time.time()
|
entry["local_ts"] = time.time()
|
||||||
|
kind = entry.get("kind")
|
||||||
|
payload = entry.get("payload", {})
|
||||||
|
if kind in ("tool_result", "tool_call"):
|
||||||
|
role = "Tool" if kind == "tool_result" else "Vendor API"
|
||||||
|
content = ""
|
||||||
|
if kind == "tool_result":
|
||||||
|
content = payload.get("output", "")
|
||||||
|
else:
|
||||||
|
content = payload.get("script") or payload.get("args") or payload.get("message", "")
|
||||||
|
if isinstance(content, dict):
|
||||||
|
content = json.dumps(content, indent=1)
|
||||||
|
with self._pending_history_adds_lock:
|
||||||
|
self._pending_history_adds.append({
|
||||||
|
"role": role,
|
||||||
|
"content": f"[{kind.upper().replace('_', ' ')}]\n{content}",
|
||||||
|
"collapsed": True,
|
||||||
|
"ts": entry.get("ts", project_manager.now_ts())
|
||||||
|
})
|
||||||
# If this is a history_add kind, route it to history queue instead
|
# If this is a history_add kind, route it to history queue instead
|
||||||
if entry.get("kind") == "history_add":
|
if kind == "history_add":
|
||||||
payload = entry.get("payload", {})
|
payload = entry.get("payload", {})
|
||||||
with self._pending_history_adds_lock:
|
with self._pending_history_adds_lock:
|
||||||
self._pending_history_adds.append({
|
self._pending_history_adds.append({
|
||||||
@@ -1008,6 +1045,32 @@ class App:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error executing GUI task: {e}")
|
print(f"Error executing GUI task: {e}")
|
||||||
|
|
||||||
|
def _process_pending_history_adds(self) -> None:
|
||||||
|
"""Synchronizes pending history entries to the active discussion and project state."""
|
||||||
|
with self._pending_history_adds_lock:
|
||||||
|
items = self._pending_history_adds[:]
|
||||||
|
self._pending_history_adds.clear()
|
||||||
|
if not items:
|
||||||
|
return
|
||||||
|
self._scroll_disc_to_bottom = True
|
||||||
|
for item in items:
|
||||||
|
role = item.get("role", "unknown")
|
||||||
|
if item.get("role") and item["role"] not in self.disc_roles:
|
||||||
|
self.disc_roles.append(item["role"])
|
||||||
|
disc_sec = self.project.get("discussion", {})
|
||||||
|
discussions = disc_sec.get("discussions", {})
|
||||||
|
disc_data = discussions.get(self.active_discussion)
|
||||||
|
if disc_data is not None:
|
||||||
|
if item.get("disc_title", self.active_discussion) == self.active_discussion:
|
||||||
|
if self.disc_entries is not disc_data.get("history"):
|
||||||
|
if "history" not in disc_data:
|
||||||
|
disc_data["history"] = []
|
||||||
|
disc_data["history"].append(project_manager.entry_to_str(item))
|
||||||
|
disc_data["last_updated"] = project_manager.now_ts()
|
||||||
|
with self._disc_entries_lock:
|
||||||
|
self.disc_entries.append(item)
|
||||||
|
print(f'[DEBUG] Added to disc_entries. Current len: {len(self.disc_entries)}')
|
||||||
|
|
||||||
def _handle_approve_script(self) -> None:
|
def _handle_approve_script(self) -> None:
|
||||||
"""Logic for approving a pending script via API hooks."""
|
"""Logic for approving a pending script via API hooks."""
|
||||||
print("[DEBUG] _handle_approve_script called")
|
print("[DEBUG] _handle_approve_script called")
|
||||||
@@ -1142,6 +1205,8 @@ class App:
|
|||||||
self.ai_status = "session reset"
|
self.ai_status = "session reset"
|
||||||
self.ai_response = ""
|
self.ai_response = ""
|
||||||
self.ui_ai_input = ""
|
self.ui_ai_input = ""
|
||||||
|
with self._pending_history_adds_lock:
|
||||||
|
self._pending_history_adds.clear()
|
||||||
|
|
||||||
def _handle_md_only(self) -> None:
|
def _handle_md_only(self) -> None:
|
||||||
"""Logic for the 'MD Only' action."""
|
"""Logic for the 'MD Only' action."""
|
||||||
@@ -1186,6 +1251,17 @@ class App:
|
|||||||
"""Runs the internal asyncio event loop."""
|
"""Runs the internal asyncio event loop."""
|
||||||
asyncio.set_event_loop(self._loop)
|
asyncio.set_event_loop(self._loop)
|
||||||
self._loop.create_task(self._process_event_queue())
|
self._loop.create_task(self._process_event_queue())
|
||||||
|
|
||||||
|
# Fallback: process queues even if GUI thread is idling/stuck
|
||||||
|
async def queue_fallback():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self._process_pending_gui_tasks()
|
||||||
|
self._process_pending_history_adds()
|
||||||
|
except: pass
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
self._loop.create_task(queue_fallback())
|
||||||
self._loop.run_forever()
|
self._loop.run_forever()
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
@@ -1205,8 +1281,8 @@ class App:
|
|||||||
while True:
|
while True:
|
||||||
event_name, payload = await self.event_queue.get()
|
event_name, payload = await self.event_queue.get()
|
||||||
if event_name == "user_request":
|
if event_name == "user_request":
|
||||||
# Handle the request (simulating what was previously in do_send thread)
|
# Handle the request in a separate thread to avoid blocking the loop
|
||||||
self._handle_request_event(payload)
|
self._loop.run_in_executor(None, self._handle_request_event, payload)
|
||||||
elif event_name == "response":
|
elif event_name == "response":
|
||||||
# Handle AI response event
|
# Handle AI response event
|
||||||
with self._pending_gui_tasks_lock:
|
with self._pending_gui_tasks_lock:
|
||||||
@@ -1512,7 +1588,7 @@ class App:
|
|||||||
imgui.text(content)
|
imgui.text(content)
|
||||||
imgui.pop_text_wrap_pos()
|
imgui.pop_text_wrap_pos()
|
||||||
else:
|
else:
|
||||||
if imgui.begin_child(f"heavy_text_child_{label}", imgui.ImVec2(0, 80), True):
|
imgui.begin_child(f"heavy_text_child_{label}", imgui.ImVec2(0, 80), True)
|
||||||
imgui.input_text_multiline(f"##{label}_input", content, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
|
imgui.input_text_multiline(f"##{label}_input", content, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
else:
|
else:
|
||||||
@@ -1583,15 +1659,6 @@ class App:
|
|||||||
for tc in self._pending_tool_calls:
|
for tc in self._pending_tool_calls:
|
||||||
self._tool_log.append(tc)
|
self._tool_log.append(tc)
|
||||||
self._pending_tool_calls.clear()
|
self._pending_tool_calls.clear()
|
||||||
# Sync pending history adds
|
|
||||||
with self._pending_history_adds_lock:
|
|
||||||
if self._pending_history_adds:
|
|
||||||
self._scroll_disc_to_bottom = True
|
|
||||||
for item in self._pending_history_adds:
|
|
||||||
if item["role"] not in self.disc_roles:
|
|
||||||
self.disc_roles.append(item["role"])
|
|
||||||
self.disc_entries.append(item)
|
|
||||||
self._pending_history_adds.clear()
|
|
||||||
# ---- Menubar
|
# ---- Menubar
|
||||||
if imgui.begin_main_menu_bar():
|
if imgui.begin_main_menu_bar():
|
||||||
if imgui.begin_menu("manual slop"):
|
if imgui.begin_menu("manual slop"):
|
||||||
@@ -1668,7 +1735,7 @@ class App:
|
|||||||
exp, self.show_windows["Discussion Hub"] = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"])
|
exp, self.show_windows["Discussion Hub"] = imgui.begin("Discussion Hub", self.show_windows["Discussion Hub"])
|
||||||
if exp:
|
if exp:
|
||||||
# Top part for the history
|
# Top part for the history
|
||||||
if imgui.begin_child("HistoryChild", size=(0, -200)):
|
imgui.begin_child("HistoryChild", size=(0, -200))
|
||||||
self._render_discussion_panel()
|
self._render_discussion_panel()
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
# Bottom part with tabs for message and response
|
# Bottom part with tabs for message and response
|
||||||
@@ -2455,6 +2522,7 @@ class App:
|
|||||||
if self._track_discussion_active:
|
if self._track_discussion_active:
|
||||||
self._flush_disc_entries_to_project()
|
self._flush_disc_entries_to_project()
|
||||||
history_strings = project_manager.load_track_history(self.active_track.id, self.ui_files_base_dir)
|
history_strings = project_manager.load_track_history(self.active_track.id, self.ui_files_base_dir)
|
||||||
|
with self._disc_entries_lock:
|
||||||
self.disc_entries = _parse_history_entries(history_strings, self.disc_roles)
|
self.disc_entries = _parse_history_entries(history_strings, self.disc_roles)
|
||||||
self.ai_status = f"track discussion: {self.active_track.id}"
|
self.ai_status = f"track discussion: {self.active_track.id}"
|
||||||
else:
|
else:
|
||||||
@@ -2521,6 +2589,7 @@ class App:
|
|||||||
if self.ui_disc_truncate_pairs < 1: self.ui_disc_truncate_pairs = 1
|
if self.ui_disc_truncate_pairs < 1: self.ui_disc_truncate_pairs = 1
|
||||||
imgui.same_line()
|
imgui.same_line()
|
||||||
if imgui.button("Truncate"):
|
if imgui.button("Truncate"):
|
||||||
|
with self._disc_entries_lock:
|
||||||
self.disc_entries = truncate_entries(self.disc_entries, self.ui_disc_truncate_pairs)
|
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"
|
self.ai_status = f"history truncated to {self.ui_disc_truncate_pairs} pairs"
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
@@ -2831,7 +2900,7 @@ class App:
|
|||||||
if imgui.button("Clear##tc"):
|
if imgui.button("Clear##tc"):
|
||||||
self._tool_log.clear()
|
self._tool_log.clear()
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
if imgui.begin_child("tc_scroll"):
|
imgui.begin_child("scroll_area")
|
||||||
clipper = imgui.ListClipper()
|
clipper = imgui.ListClipper()
|
||||||
clipper.begin(len(self._tool_log))
|
clipper.begin(len(self._tool_log))
|
||||||
while clipper.step():
|
while clipper.step():
|
||||||
@@ -2848,13 +2917,13 @@ class App:
|
|||||||
self.text_viewer_title = f"Call Script #{i}"
|
self.text_viewer_title = f"Call Script #{i}"
|
||||||
self.text_viewer_content = script
|
self.text_viewer_content = script
|
||||||
if self.ui_word_wrap:
|
if self.ui_word_wrap:
|
||||||
if imgui.begin_child(f"tc_script_wrap_{i}", imgui.ImVec2(-1, 72), True):
|
imgui.begin_child(f"tc_script_wrap_{i}", imgui.ImVec2(-1, 72), True)
|
||||||
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
||||||
imgui.text(script)
|
imgui.text(script)
|
||||||
imgui.pop_text_wrap_pos()
|
imgui.pop_text_wrap_pos()
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
else:
|
else:
|
||||||
if imgui.begin_child(f"tc_script_fixed_width_{i}", imgui.ImVec2(0, 72), True, imgui.WindowFlags_.horizontal_scrollbar):
|
imgui.begin_child(f"tc_script_fixed_width_{i}", imgui.ImVec2(0, 72), True, imgui.WindowFlags_.horizontal_scrollbar)
|
||||||
imgui.input_text_multiline(f"##tc_script_res_{i}", script, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
|
imgui.input_text_multiline(f"##tc_script_res_{i}", script, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
# Result Display
|
# Result Display
|
||||||
@@ -2865,13 +2934,13 @@ class App:
|
|||||||
self.text_viewer_title = f"Call Output #{i}"
|
self.text_viewer_title = f"Call Output #{i}"
|
||||||
self.text_viewer_content = result
|
self.text_viewer_content = result
|
||||||
if self.ui_word_wrap:
|
if self.ui_word_wrap:
|
||||||
if imgui.begin_child(f"tc_res_wrap_{i}", imgui.ImVec2(-1, 72), True):
|
imgui.begin_child(f"tc_res_wrap_{i}", imgui.ImVec2(-1, 72), True)
|
||||||
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
|
||||||
imgui.text(result)
|
imgui.text(result)
|
||||||
imgui.pop_text_wrap_pos()
|
imgui.pop_text_wrap_pos()
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
else:
|
else:
|
||||||
if imgui.begin_child(f"tc_res_fixed_width_{i}", imgui.ImVec2(0, 72), True, imgui.WindowFlags_.horizontal_scrollbar):
|
imgui.begin_child(f"tc_res_fixed_width_{i}", imgui.ImVec2(0, 72), True, imgui.WindowFlags_.horizontal_scrollbar)
|
||||||
imgui.input_text_multiline(f"##tc_res_val_{i}", result, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
|
imgui.input_text_multiline(f"##tc_res_val_{i}", result, imgui.ImVec2(-1, -1), imgui.InputTextFlags_.read_only)
|
||||||
imgui.end_child()
|
imgui.end_child()
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
@@ -2895,7 +2964,7 @@ class App:
|
|||||||
imgui.separator()
|
imgui.separator()
|
||||||
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
|
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
|
||||||
imgui.separator()
|
imgui.separator()
|
||||||
if imgui.begin_child("comms_scroll"):
|
imgui.begin_child("scroll_area")
|
||||||
clipper = imgui.ListClipper()
|
clipper = imgui.ListClipper()
|
||||||
clipper.begin(len(self._comms_log))
|
clipper.begin(len(self._comms_log))
|
||||||
while clipper.step():
|
while clipper.step():
|
||||||
@@ -3172,7 +3241,7 @@ class App:
|
|||||||
def _render_tier_stream_panel(self, tier_key: str, stream_key: str | None) -> None:
|
def _render_tier_stream_panel(self, tier_key: str, stream_key: str | None) -> None:
|
||||||
if stream_key is not None:
|
if stream_key is not None:
|
||||||
content = self.mma_streams.get(stream_key, "")
|
content = self.mma_streams.get(stream_key, "")
|
||||||
if imgui.begin_child("##stream_content", imgui.ImVec2(-1, -1)):
|
imgui.begin_child(f"##stream_content_{tier_key}", imgui.ImVec2(-1, -1))
|
||||||
imgui.text_wrapped(content)
|
imgui.text_wrapped(content)
|
||||||
try:
|
try:
|
||||||
if len(content) != self._tier_stream_last_len.get(stream_key, -1):
|
if len(content) != self._tier_stream_last_len.get(stream_key, -1):
|
||||||
@@ -3189,7 +3258,7 @@ class App:
|
|||||||
for key in tier3_keys:
|
for key in tier3_keys:
|
||||||
ticket_id = key.split(": ", 1)[-1] if ": " in key else key
|
ticket_id = key.split(": ", 1)[-1] if ": " in key else key
|
||||||
imgui.text(ticket_id)
|
imgui.text(ticket_id)
|
||||||
if imgui.begin_child(f"##tier3_{ticket_id}_scroll", imgui.ImVec2(-1, 150), True):
|
imgui.begin_child(f"##tier3_{ticket_id}_scroll", imgui.ImVec2(-1, 150), True)
|
||||||
imgui.text_wrapped(self.mma_streams[key])
|
imgui.text_wrapped(self.mma_streams[key])
|
||||||
try:
|
try:
|
||||||
if len(self.mma_streams[key]) != self._tier_stream_last_len.get(key, -1):
|
if len(self.mma_streams[key]) != self._tier_stream_last_len.get(key, -1):
|
||||||
|
|||||||
Reference in New Issue
Block a user