fixes
This commit is contained in:
@@ -24,11 +24,11 @@
|
|||||||
- **Config** - namespace, output dir, save
|
- **Config** - namespace, output dir, save
|
||||||
- **Files** - base_dir, scrollable path list with remove, add file(s), add wildcard
|
- **Files** - base_dir, scrollable path list with remove, add file(s), add wildcard
|
||||||
- **Screenshots** - base_dir, scrollable path list with remove, add screenshot(s)
|
- **Screenshots** - base_dir, scrollable path list with remove, add screenshot(s)
|
||||||
- **Discussion History** - multiline text box, `---` as separator between excerpts, save splits on `---` back into toml array
|
- **Discussion History** - structured block editor; each entry has a role combo (User/AI/Vendor API/System) and a multiline content field; buttons: Insert Before, Remove per entry; global buttons: + Entry, Clear All, Save; `-> History` buttons on Message and Response panels append the current message/response as a new entry
|
||||||
- **Provider** - provider combo (gemini/anthropic), model listbox populated from API, fetch models button
|
- **Provider** - provider combo (gemini/anthropic), model listbox populated from API, fetch models button
|
||||||
- **Message** - multiline input, Gen+Send button, MD Only button, Reset session button
|
- **Message** - multiline input, Gen+Send button, MD Only button, Reset session button
|
||||||
- **Response** - readonly multiline displaying last AI response
|
- **Response** - readonly multiline displaying last AI response
|
||||||
- **Tool Calls** - scrollable log of every PowerShell tool call the AI made, showing script and result; Clear button
|
- **Tool Calls** - scrollable log of every PowerShell tool call the AI made; shows first line of script + result (script body omitted from display, full script saved to `.ps1` file via session_logger); Clear button
|
||||||
- **Comms History** - rich structured live log of every API interaction; status line at top; colour legend; Clear button; each entry rendered with kind-specific layout rather than raw JSON
|
- **Comms History** - rich structured live log of every API interaction; status line at top; colour legend; Clear button; each entry rendered with kind-specific layout rather than raw JSON
|
||||||
|
|
||||||
**Layout persistence:**
|
**Layout persistence:**
|
||||||
@@ -88,7 +88,7 @@ Status line and colour legend live at the top of the Comms History window (above
|
|||||||
**Session Logger (session_logger.py):**
|
**Session Logger (session_logger.py):**
|
||||||
- `open_session()` called once at GUI startup; creates `logs/` and `scripts/generated/` directories; opens `logs/comms_<ts>.log` and `logs/toolcalls_<ts>.log` (line-buffered)
|
- `open_session()` called once at GUI startup; creates `logs/` and `scripts/generated/` directories; opens `logs/comms_<ts>.log` and `logs/toolcalls_<ts>.log` (line-buffered)
|
||||||
- `log_comms(entry)` appends each comms entry as a JSON-L line to the comms log; called from `App._on_comms_entry` (background thread); thread-safe via GIL + line buffering
|
- `log_comms(entry)` appends each comms entry as a JSON-L line to the comms log; called from `App._on_comms_entry` (background thread); thread-safe via GIL + line buffering
|
||||||
- `log_tool_call(script, result, script_path)` appends a markdown-formatted tool-call record to the toolcalls log and writes the script to `scripts/generated/<ts>_<seq:04d>.ps1`; uses a `threading.Lock` for the sequence counter
|
- `log_tool_call(script, result, script_path)` writes the script to `scripts/generated/<ts>_<seq:04d>.ps1` and appends a markdown record to the toolcalls log **without** the script body (just the file path + result), keeping the log readable; uses a `threading.Lock` for the sequence counter
|
||||||
- `close_session()` flushes and closes both file handles; called just before `dpg.destroy_context()`
|
- `close_session()` flushes and closes both file handles; called just before `dpg.destroy_context()`
|
||||||
- `_on_tool_log` in `App` is wired to `ai_client.tool_log_callback` and calls `session_logger.log_tool_call`
|
- `_on_tool_log` in `App` is wired to `ai_client.tool_log_callback` and calls `session_logger.log_tool_call`
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -286,7 +286,7 @@ Collapsed=0
|
|||||||
|
|
||||||
[Window][###104]
|
[Window][###104]
|
||||||
Pos=1525,1246
|
Pos=1525,1246
|
||||||
Size=2315,607
|
Size=2315,621
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000026,0
|
DockId=0x00000026,0
|
||||||
|
|
||||||
@@ -309,8 +309,8 @@ Collapsed=0
|
|||||||
DockId=0x00000012,1
|
DockId=0x00000012,1
|
||||||
|
|
||||||
[Window][###97]
|
[Window][###97]
|
||||||
Pos=1525,1855
|
Pos=1525,1869
|
||||||
Size=2315,282
|
Size=2315,268
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
DockId=0x00000020,0
|
DockId=0x00000020,0
|
||||||
|
|
||||||
@@ -369,6 +369,31 @@ Pos=1578,868
|
|||||||
Size=700,440
|
Size=700,440
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][###717]
|
||||||
|
Pos=1578,868
|
||||||
|
Size=700,440
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][###849]
|
||||||
|
Pos=1578,868
|
||||||
|
Size=700,440
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][###987]
|
||||||
|
Pos=1578,868
|
||||||
|
Size=700,440
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][###1131]
|
||||||
|
Pos=1578,868
|
||||||
|
Size=700,440
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
|
[Window][###1281]
|
||||||
|
Pos=1578,868
|
||||||
|
Size=700,440
|
||||||
|
Collapsed=0
|
||||||
|
|
||||||
[Docking][Data]
|
[Docking][Data]
|
||||||
DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,0 Size=3840,2137 Split=X Selected=0x40484D8F
|
DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,0 Size=3840,2137 Split=X Selected=0x40484D8F
|
||||||
DockNode ID=0x00000003 Parent=0x7C6B3D9B SizeRef=599,1161 Split=Y Selected=0xEE087978
|
DockNode ID=0x00000003 Parent=0x7C6B3D9B SizeRef=599,1161 Split=Y Selected=0xEE087978
|
||||||
@@ -395,10 +420,10 @@ DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,0 Size=3840,
|
|||||||
DockNode ID=0x00000018 Parent=0x00000013 SizeRef=1309,1749 Split=Y Selected=0x88A8C2FF
|
DockNode ID=0x00000018 Parent=0x00000013 SizeRef=1309,1749 Split=Y Selected=0x88A8C2FF
|
||||||
DockNode ID=0x00000019 Parent=0x00000018 SizeRef=2440,1412 Split=X Selected=0x88A8C2FF
|
DockNode ID=0x00000019 Parent=0x00000018 SizeRef=2440,1412 Split=X Selected=0x88A8C2FF
|
||||||
DockNode ID=0x00000023 Parent=0x00000019 SizeRef=1160,737 Split=Y Selected=0x4F935A1E
|
DockNode ID=0x00000023 Parent=0x00000019 SizeRef=1160,737 Split=Y Selected=0x4F935A1E
|
||||||
DockNode ID=0x0000001F Parent=0x00000023 SizeRef=2315,1853 Split=Y Selected=0x4F935A1E
|
DockNode ID=0x0000001F Parent=0x00000023 SizeRef=2315,1867 Split=Y Selected=0x4F935A1E
|
||||||
DockNode ID=0x00000025 Parent=0x0000001F SizeRef=2315,1244 CentralNode=1 Selected=0x4F935A1E
|
DockNode ID=0x00000025 Parent=0x0000001F SizeRef=2315,1244 CentralNode=1 Selected=0x4F935A1E
|
||||||
DockNode ID=0x00000026 Parent=0x0000001F SizeRef=2315,607 Selected=0x7D28643F
|
DockNode ID=0x00000026 Parent=0x0000001F SizeRef=2315,621 Selected=0x7D28643F
|
||||||
DockNode ID=0x00000020 Parent=0x00000023 SizeRef=2315,282 Selected=0x4C2F06CB
|
DockNode ID=0x00000020 Parent=0x00000023 SizeRef=2315,268 Selected=0x4C2F06CB
|
||||||
DockNode ID=0x00000024 Parent=0x00000019 SizeRef=1153,737 Selected=0x88A8C2FF
|
DockNode ID=0x00000024 Parent=0x00000019 SizeRef=1153,737 Selected=0x88A8C2FF
|
||||||
DockNode ID=0x0000001A Parent=0x00000018 SizeRef=2440,723 Selected=0x3A881EEF
|
DockNode ID=0x0000001A Parent=0x00000018 SizeRef=2440,723 Selected=0x3A881EEF
|
||||||
DockNode ID=0x00000014 Parent=0x00000010 SizeRef=1967,445 Selected=0xC36FF36B
|
DockNode ID=0x00000014 Parent=0x00000010 SizeRef=1967,445 Selected=0xC36FF36B
|
||||||
|
|||||||
143
gui.py
143
gui.py
@@ -426,15 +426,8 @@ class App:
|
|||||||
dpg.delete_item("tool_log_scroll", children_only=True)
|
dpg.delete_item("tool_log_scroll", children_only=True)
|
||||||
for i, (script, result) in enumerate(self._tool_log, 1):
|
for i, (script, result) in enumerate(self._tool_log, 1):
|
||||||
with dpg.group(parent="tool_log_scroll"):
|
with dpg.group(parent="tool_log_scroll"):
|
||||||
dpg.add_text(f"Call #{i}", color=(140, 200, 255))
|
first_line = script.strip().splitlines()[0][:80] if script.strip() else "(empty)"
|
||||||
dpg.add_input_text(
|
dpg.add_text(f"Call #{i}: {first_line}", color=(140, 200, 255))
|
||||||
default_value=script,
|
|
||||||
multiline=True,
|
|
||||||
readonly=True,
|
|
||||||
width=-1,
|
|
||||||
height=72,
|
|
||||||
)
|
|
||||||
dpg.add_text("Result:", color=(180, 255, 180))
|
|
||||||
dpg.add_input_text(
|
dpg.add_input_text(
|
||||||
default_value=result,
|
default_value=result,
|
||||||
multiline=True,
|
multiline=True,
|
||||||
@@ -455,8 +448,12 @@ class App:
|
|||||||
self.config["screenshots"]["base_dir"] = dpg.get_value("shots_base_dir")
|
self.config["screenshots"]["base_dir"] = dpg.get_value("shots_base_dir")
|
||||||
self.config["files"]["paths"] = self.files
|
self.config["files"]["paths"] = self.files
|
||||||
self.config["screenshots"]["paths"] = self.screenshots
|
self.config["screenshots"]["paths"] = self.screenshots
|
||||||
raw = dpg.get_value("discussion_box")
|
# Pull latest content edits from disc widgets
|
||||||
self.history = [s.strip() for s in raw.split("---") if s.strip()]
|
for i, entry in enumerate(self.disc_entries):
|
||||||
|
tag = f"disc_content_{i}"
|
||||||
|
if dpg.does_item_exist(tag):
|
||||||
|
entry["content"] = dpg.get_value(tag)
|
||||||
|
self.history = self._disc_serialize()
|
||||||
self.config["discussion"] = {"history": self.history}
|
self.config["discussion"] = {"history": self.history}
|
||||||
self.config["ai"] = {
|
self.config["ai"] = {
|
||||||
"provider": self.current_provider,
|
"provider": self.current_provider,
|
||||||
@@ -672,6 +669,7 @@ class App:
|
|||||||
self.available_models = []
|
self.available_models = []
|
||||||
self._rebuild_models_list()
|
self._rebuild_models_list()
|
||||||
self._fetch_models(self.current_provider)
|
self._fetch_models(self.current_provider)
|
||||||
|
self._rebuild_disc_list()
|
||||||
|
|
||||||
def cb_model_changed(self, sender, app_data):
|
def cb_model_changed(self, sender, app_data):
|
||||||
if app_data:
|
if app_data:
|
||||||
@@ -697,6 +695,103 @@ class App:
|
|||||||
|
|
||||||
# ---------------------------------------------------------------- build ui
|
# ---------------------------------------------------------------- build ui
|
||||||
|
|
||||||
|
# ------------------------------------------------------------ disc history
|
||||||
|
|
||||||
|
def _disc_serialize(self) -> list[str]:
|
||||||
|
"""Flatten disc_entries back to a single TOML history string per logical block."""
|
||||||
|
lines = []
|
||||||
|
for e in self.disc_entries:
|
||||||
|
lines.append(f"{e['role']}:\n{e['content']}")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def _rebuild_disc_list(self):
|
||||||
|
if not dpg.does_item_exist("disc_scroll"):
|
||||||
|
return
|
||||||
|
dpg.delete_item("disc_scroll", children_only=True)
|
||||||
|
for i, entry in enumerate(self.disc_entries):
|
||||||
|
with dpg.group(parent="disc_scroll"):
|
||||||
|
with dpg.group(horizontal=True):
|
||||||
|
dpg.add_combo(
|
||||||
|
tag=f"disc_role_{i}",
|
||||||
|
items=DISC_ROLES,
|
||||||
|
default_value=entry["role"],
|
||||||
|
width=120,
|
||||||
|
callback=self._make_disc_role_cb(i),
|
||||||
|
)
|
||||||
|
dpg.add_button(
|
||||||
|
label="Insert Before",
|
||||||
|
callback=self._make_disc_insert_cb(i),
|
||||||
|
)
|
||||||
|
dpg.add_button(
|
||||||
|
label="Remove",
|
||||||
|
callback=self._make_disc_remove_cb(i),
|
||||||
|
)
|
||||||
|
dpg.add_input_text(
|
||||||
|
tag=f"disc_content_{i}",
|
||||||
|
default_value=entry["content"],
|
||||||
|
multiline=True,
|
||||||
|
width=-1,
|
||||||
|
height=100,
|
||||||
|
callback=self._make_disc_content_cb(i),
|
||||||
|
on_enter=False,
|
||||||
|
)
|
||||||
|
dpg.add_separator()
|
||||||
|
|
||||||
|
def _make_disc_role_cb(self, idx: int):
|
||||||
|
def cb(sender, app_data):
|
||||||
|
if idx < len(self.disc_entries):
|
||||||
|
self.disc_entries[idx]["role"] = app_data
|
||||||
|
return cb
|
||||||
|
|
||||||
|
def _make_disc_content_cb(self, idx: int):
|
||||||
|
def cb(sender, app_data):
|
||||||
|
if idx < len(self.disc_entries):
|
||||||
|
self.disc_entries[idx]["content"] = app_data
|
||||||
|
return cb
|
||||||
|
|
||||||
|
def _make_disc_insert_cb(self, idx: int):
|
||||||
|
def cb():
|
||||||
|
self.disc_entries.insert(idx, {"role": "User", "content": ""})
|
||||||
|
self._rebuild_disc_list()
|
||||||
|
return cb
|
||||||
|
|
||||||
|
def _make_disc_remove_cb(self, idx: int):
|
||||||
|
def cb():
|
||||||
|
if idx < len(self.disc_entries):
|
||||||
|
self.disc_entries.pop(idx)
|
||||||
|
self._rebuild_disc_list()
|
||||||
|
return cb
|
||||||
|
|
||||||
|
def cb_disc_append_entry(self):
|
||||||
|
self.disc_entries.append({"role": "User", "content": ""})
|
||||||
|
self._rebuild_disc_list()
|
||||||
|
|
||||||
|
def cb_disc_clear(self):
|
||||||
|
self.disc_entries.clear()
|
||||||
|
self._rebuild_disc_list()
|
||||||
|
|
||||||
|
def cb_disc_save(self):
|
||||||
|
# Pull any in-progress edits from widgets into disc_entries
|
||||||
|
for i, entry in enumerate(self.disc_entries):
|
||||||
|
tag = f"disc_content_{i}"
|
||||||
|
if dpg.does_item_exist(tag):
|
||||||
|
entry["content"] = dpg.get_value(tag)
|
||||||
|
self._flush_to_config()
|
||||||
|
save_config(self.config)
|
||||||
|
self._update_status("discussion saved")
|
||||||
|
|
||||||
|
def cb_append_message_to_history(self):
|
||||||
|
msg = dpg.get_value("ai_input")
|
||||||
|
if msg:
|
||||||
|
self.disc_entries.append({"role": "User", "content": msg})
|
||||||
|
self._rebuild_disc_list()
|
||||||
|
|
||||||
|
def cb_append_response_to_history(self):
|
||||||
|
resp = self.ai_response
|
||||||
|
if resp:
|
||||||
|
self.disc_entries.append({"role": "AI", "content": resp})
|
||||||
|
self._rebuild_disc_list()
|
||||||
|
|
||||||
def _build_ui(self):
|
def _build_ui(self):
|
||||||
|
|
||||||
with dpg.window(
|
with dpg.window(
|
||||||
@@ -781,22 +876,17 @@ class App:
|
|||||||
label="Discussion History",
|
label="Discussion History",
|
||||||
tag="win_discussion",
|
tag="win_discussion",
|
||||||
pos=(824, 8),
|
pos=(824, 8),
|
||||||
width=400,
|
width=420,
|
||||||
height=500,
|
height=600,
|
||||||
no_close=True,
|
no_close=True,
|
||||||
):
|
):
|
||||||
dpg.add_input_text(
|
|
||||||
tag="discussion_box",
|
|
||||||
default_value="\n---\n".join(self.history),
|
|
||||||
multiline=True,
|
|
||||||
width=-1,
|
|
||||||
height=-64,
|
|
||||||
)
|
|
||||||
dpg.add_separator()
|
|
||||||
with dpg.group(horizontal=True):
|
with dpg.group(horizontal=True):
|
||||||
dpg.add_button(label="Add Separator", callback=self.cb_add_excerpt)
|
dpg.add_button(label="+ Entry", callback=self.cb_disc_append_entry)
|
||||||
dpg.add_button(label="Clear", callback=self.cb_clear_discussion)
|
dpg.add_button(label="Clear All", callback=self.cb_disc_clear)
|
||||||
dpg.add_button(label="Save", callback=self.cb_save_discussion)
|
dpg.add_button(label="Save", callback=self.cb_disc_save)
|
||||||
|
dpg.add_separator()
|
||||||
|
with dpg.child_window(tag="disc_scroll", height=-1, border=False):
|
||||||
|
pass
|
||||||
|
|
||||||
with dpg.window(
|
with dpg.window(
|
||||||
label="Provider",
|
label="Provider",
|
||||||
@@ -846,6 +936,7 @@ class App:
|
|||||||
dpg.add_button(label="Gen + Send", callback=self.cb_generate_send)
|
dpg.add_button(label="Gen + Send", callback=self.cb_generate_send)
|
||||||
dpg.add_button(label="MD Only", callback=self.cb_md_only)
|
dpg.add_button(label="MD Only", callback=self.cb_md_only)
|
||||||
dpg.add_button(label="Reset", callback=self.cb_reset_session)
|
dpg.add_button(label="Reset", callback=self.cb_reset_session)
|
||||||
|
dpg.add_button(label="-> History", callback=self.cb_append_message_to_history)
|
||||||
|
|
||||||
with dpg.window(
|
with dpg.window(
|
||||||
label="Response",
|
label="Response",
|
||||||
@@ -860,8 +951,10 @@ class App:
|
|||||||
multiline=True,
|
multiline=True,
|
||||||
readonly=True,
|
readonly=True,
|
||||||
width=-1,
|
width=-1,
|
||||||
height=-1,
|
height=-48,
|
||||||
)
|
)
|
||||||
|
dpg.add_separator()
|
||||||
|
dpg.add_button(label="-> History", callback=self.cb_append_response_to_history)
|
||||||
|
|
||||||
with dpg.window(
|
with dpg.window(
|
||||||
label="Tool Calls",
|
label="Tool Calls",
|
||||||
|
|||||||
@@ -108,12 +108,11 @@ def log_tool_call(script: str, result: str, script_path: str | None):
|
|||||||
ps1_path = None
|
ps1_path = None
|
||||||
ps1_name = f"(write error: {exc})"
|
ps1_name = f"(write error: {exc})"
|
||||||
|
|
||||||
# Append to the tool-call sequence log
|
# Append to the tool-call sequence log (script body omitted - see .ps1 file)
|
||||||
try:
|
try:
|
||||||
_tool_fh.write(
|
_tool_fh.write(
|
||||||
f"## Call #{seq} [{ts_entry}]\n"
|
f"## Call #{seq} [{ts_entry}]\n"
|
||||||
f"Script file: {ps1_path}\n\n"
|
f"Script file: {ps1_path}\n\n"
|
||||||
f"```powershell\n{script}\n```\n\n"
|
|
||||||
f"### Result\n\n"
|
f"### Result\n\n"
|
||||||
f"```\n{result}\n```\n\n"
|
f"```\n{result}\n```\n\n"
|
||||||
f"---\n\n"
|
f"---\n\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user