amazing
This commit is contained in:
171
gui.py
171
gui.py
@@ -1,4 +1,5 @@
|
||||
import dearpygui.dearpygui as dpg
|
||||
# gui.py
|
||||
import dearpygui.dearpygui as dpg
|
||||
import tomllib
|
||||
import tomli_w
|
||||
import threading
|
||||
@@ -12,6 +13,9 @@ import shell_runner
|
||||
CONFIG_PATH = Path("config.toml")
|
||||
PROVIDERS = ["gemini", "anthropic"]
|
||||
|
||||
# Max chars shown inline for a heavy comms field before clamping to a scrollable box
|
||||
COMMS_CLAMP_CHARS = 300
|
||||
|
||||
|
||||
def load_config() -> dict:
|
||||
with open(CONFIG_PATH, "rb") as f:
|
||||
@@ -30,6 +34,76 @@ def hide_tk_root() -> Tk:
|
||||
return root
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ comms rendering helpers
|
||||
|
||||
# Direction -> colour
|
||||
_DIR_COLORS = {
|
||||
"OUT": (100, 200, 255), # blue-ish
|
||||
"IN": (140, 255, 160), # green-ish
|
||||
}
|
||||
|
||||
# Kind -> colour
|
||||
_KIND_COLORS = {
|
||||
"request": (255, 220, 100),
|
||||
"response": (180, 255, 180),
|
||||
"tool_call": (255, 180, 80),
|
||||
"tool_result": (180, 220, 255),
|
||||
"tool_result_send": (200, 180, 255),
|
||||
}
|
||||
|
||||
_HEAVY_KEYS = {"message", "text", "script", "output", "content"}
|
||||
|
||||
|
||||
def _add_comms_field(parent: str, label: str, value: str, heavy: bool):
|
||||
"""Add a labelled field inside parent. Heavy fields get a clamped input_text box."""
|
||||
with dpg.group(horizontal=False, parent=parent):
|
||||
dpg.add_text(f"{label}:", color=(200, 200, 200))
|
||||
if heavy and len(value) > COMMS_CLAMP_CHARS:
|
||||
# Show clamped scrollable box
|
||||
dpg.add_input_text(
|
||||
default_value=value,
|
||||
multiline=True,
|
||||
readonly=True,
|
||||
width=-1,
|
||||
height=80,
|
||||
)
|
||||
else:
|
||||
dpg.add_text(value if value else "(empty)", wrap=460)
|
||||
|
||||
|
||||
def _render_comms_entry(parent: str, entry: dict, idx: int):
|
||||
direction = entry["direction"]
|
||||
kind = entry["kind"]
|
||||
ts = entry["ts"]
|
||||
provider = entry["provider"]
|
||||
model = entry["model"]
|
||||
payload = entry["payload"]
|
||||
|
||||
dir_color = _DIR_COLORS.get(direction, (220, 220, 220))
|
||||
kind_color = _KIND_COLORS.get(kind, (220, 220, 220))
|
||||
|
||||
with dpg.group(horizontal=False, parent=parent):
|
||||
# Header row
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_text(f"#{idx}", color=(160, 160, 160))
|
||||
dpg.add_text(ts, color=(160, 160, 160))
|
||||
dpg.add_text(direction, color=dir_color)
|
||||
dpg.add_text(kind, color=kind_color)
|
||||
dpg.add_text(f"{provider}/{model}", color=(180, 180, 180))
|
||||
|
||||
# Payload fields
|
||||
for key, val in payload.items():
|
||||
is_heavy = key in _HEAVY_KEYS
|
||||
if isinstance(val, (dict, list)):
|
||||
import json
|
||||
val_str = json.dumps(val, ensure_ascii=False, indent=2)
|
||||
else:
|
||||
val_str = str(val)
|
||||
_add_comms_field(parent, key, val_str, is_heavy)
|
||||
|
||||
dpg.add_separator()
|
||||
|
||||
|
||||
class ConfirmDialog:
|
||||
"""
|
||||
Modal confirmation window for a proposed PowerShell script.
|
||||
@@ -127,8 +201,45 @@ class App:
|
||||
|
||||
self._tool_log: list[tuple[str, str]] = []
|
||||
|
||||
# Comms log entries queued from background thread for main-thread rendering
|
||||
self._pending_comms: list[dict] = []
|
||||
self._pending_comms_lock = threading.Lock()
|
||||
self._comms_entry_count = 0
|
||||
|
||||
ai_client.set_provider(self.current_provider, self.current_model)
|
||||
ai_client.confirm_and_run_callback = self._confirm_and_run
|
||||
ai_client.comms_log_callback = self._on_comms_entry
|
||||
|
||||
# ---------------------------------------------------------------- comms log
|
||||
|
||||
def _on_comms_entry(self, entry: dict):
|
||||
"""Called from background thread; queue for main thread."""
|
||||
with self._pending_comms_lock:
|
||||
self._pending_comms.append(entry)
|
||||
|
||||
def _flush_pending_comms(self):
|
||||
"""Called every frame from the main render loop."""
|
||||
with self._pending_comms_lock:
|
||||
entries = self._pending_comms[:]
|
||||
self._pending_comms.clear()
|
||||
for entry in entries:
|
||||
self._comms_entry_count += 1
|
||||
self._append_comms_entry(entry, self._comms_entry_count)
|
||||
|
||||
def _append_comms_entry(self, entry: dict, idx: int):
|
||||
if not dpg.does_item_exist("comms_scroll"):
|
||||
return
|
||||
_render_comms_entry("comms_scroll", entry, idx)
|
||||
|
||||
def _rebuild_comms_log(self):
|
||||
"""Full redraw from ai_client.get_comms_log() — used after clear/reset."""
|
||||
if not dpg.does_item_exist("comms_scroll"):
|
||||
return
|
||||
dpg.delete_item("comms_scroll", children_only=True)
|
||||
self._comms_entry_count = 0
|
||||
for entry in ai_client.get_comms_log():
|
||||
self._comms_entry_count += 1
|
||||
_render_comms_entry("comms_scroll", entry, self._comms_entry_count)
|
||||
|
||||
# ---------------------------------------------------------------- tool execution
|
||||
|
||||
@@ -194,7 +305,7 @@ class App:
|
||||
self.config["discussion"] = {"history": self.history}
|
||||
self.config["ai"] = {
|
||||
"provider": self.current_provider,
|
||||
"model": self.current_model,
|
||||
"model": self.current_model,
|
||||
}
|
||||
|
||||
def _do_generate(self) -> tuple[str, Path]:
|
||||
@@ -357,8 +468,15 @@ class App:
|
||||
|
||||
def cb_reset_session(self):
|
||||
ai_client.reset_session()
|
||||
ai_client.clear_comms_log()
|
||||
self._tool_log.clear()
|
||||
self._rebuild_tool_log()
|
||||
# Clear pending queue and counter, then wipe the comms panel
|
||||
with self._pending_comms_lock:
|
||||
self._pending_comms.clear()
|
||||
self._comms_entry_count = 0
|
||||
if dpg.does_item_exist("comms_scroll"):
|
||||
dpg.delete_item("comms_scroll", children_only=True)
|
||||
self._update_status("session reset")
|
||||
self._update_response("")
|
||||
|
||||
@@ -411,6 +529,14 @@ class App:
|
||||
self._tool_log.clear()
|
||||
self._rebuild_tool_log()
|
||||
|
||||
def cb_clear_comms(self):
|
||||
ai_client.clear_comms_log()
|
||||
with self._pending_comms_lock:
|
||||
self._pending_comms.clear()
|
||||
self._comms_entry_count = 0
|
||||
if dpg.does_item_exist("comms_scroll"):
|
||||
dpg.delete_item("comms_scroll", children_only=True)
|
||||
|
||||
# ---------------------------------------------------------------- build ui
|
||||
|
||||
def _build_ui(self):
|
||||
@@ -519,7 +645,7 @@ class App:
|
||||
tag="win_provider",
|
||||
pos=(1232, 8),
|
||||
width=420,
|
||||
height=280,
|
||||
height=260,
|
||||
no_close=True,
|
||||
):
|
||||
dpg.add_text("Provider")
|
||||
@@ -542,13 +668,11 @@ class App:
|
||||
num_items=6,
|
||||
callback=self.cb_model_changed,
|
||||
)
|
||||
dpg.add_separator()
|
||||
dpg.add_text("Status: idle", tag="ai_status")
|
||||
|
||||
with dpg.window(
|
||||
label="Message",
|
||||
tag="win_message",
|
||||
pos=(1232, 296),
|
||||
pos=(1232, 276),
|
||||
width=420,
|
||||
height=280,
|
||||
no_close=True,
|
||||
@@ -568,7 +692,7 @@ class App:
|
||||
with dpg.window(
|
||||
label="Response",
|
||||
tag="win_response",
|
||||
pos=(1232, 584),
|
||||
pos=(1232, 564),
|
||||
width=420,
|
||||
height=300,
|
||||
no_close=True,
|
||||
@@ -584,7 +708,7 @@ class App:
|
||||
with dpg.window(
|
||||
label="Tool Calls",
|
||||
tag="win_tool_log",
|
||||
pos=(1232, 892),
|
||||
pos=(1232, 872),
|
||||
width=420,
|
||||
height=300,
|
||||
no_close=True,
|
||||
@@ -596,6 +720,34 @@ class App:
|
||||
with dpg.child_window(tag="tool_log_scroll", height=-1, border=False):
|
||||
pass
|
||||
|
||||
# ---- Comms History panel (new) ----
|
||||
with dpg.window(
|
||||
label="Comms History",
|
||||
tag="win_comms",
|
||||
pos=(1660, 8),
|
||||
width=520,
|
||||
height=1164,
|
||||
no_close=True,
|
||||
):
|
||||
# Status line lives here now
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_text("Status: idle", tag="ai_status", color=(200, 220, 160))
|
||||
dpg.add_spacer(width=16)
|
||||
dpg.add_button(label="Clear", callback=self.cb_clear_comms)
|
||||
dpg.add_separator()
|
||||
# Colour legend
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_text("OUT", color=_DIR_COLORS["OUT"])
|
||||
dpg.add_text("request", color=_KIND_COLORS["request"])
|
||||
dpg.add_text("tool_call", color=_KIND_COLORS["tool_call"])
|
||||
dpg.add_spacer(width=8)
|
||||
dpg.add_text("IN", color=_DIR_COLORS["IN"])
|
||||
dpg.add_text("response", color=_KIND_COLORS["response"])
|
||||
dpg.add_text("tool_result", color=_KIND_COLORS["tool_result"])
|
||||
dpg.add_separator()
|
||||
with dpg.child_window(tag="comms_scroll", height=-1, border=False, horizontal_scrollbar=True):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
dpg.create_context()
|
||||
dpg.configure_app(docking=True, docking_space=True, init_file="dpg_layout.ini")
|
||||
@@ -614,6 +766,9 @@ class App:
|
||||
if dialog is not None:
|
||||
dialog.show()
|
||||
|
||||
# Flush any comms entries queued from background threads
|
||||
self._flush_pending_comms()
|
||||
|
||||
dpg.render_dearpygui_frame()
|
||||
|
||||
dpg.save_init_file("dpg_layout.ini")
|
||||
|
||||
Reference in New Issue
Block a user