fixing...

This commit is contained in:
2026-02-21 20:22:19 -05:00
parent 4a9c23b2da
commit c101d7b7d1
5 changed files with 262 additions and 119 deletions

View File

@@ -277,6 +277,29 @@ def _run_script(script: str, base_dir: str) -> str:
return output
# ------------------------------------------------------------------ content block serialisation
def _content_block_to_dict(block) -> dict:
"""
Convert an Anthropic SDK content block object to a plain dict.
This ensures history entries are always JSON-serialisable dicts,
not opaque SDK objects that may fail on re-serialisation.
"""
if isinstance(block, dict):
return block
if hasattr(block, "model_dump"):
return block.model_dump()
if hasattr(block, "to_dict"):
return block.to_dict()
# Fallback: manually construct based on type
block_type = getattr(block, "type", None)
if block_type == "text":
return {"type": "text", "text": block.text}
if block_type == "tool_use":
return {"type": "tool_use", "id": block.id, "name": block.name, "input": block.input}
return {"type": "text", "text": str(block)}
# ------------------------------------------------------------------ gemini
def _ensure_gemini_client():
@@ -322,7 +345,7 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str) -> str:
part.function_call
for candidate in response.candidates
for part in candidate.content.parts
if part.function_call is not None
if hasattr(part, "function_call") and part.function_call is not None
]
_append_comms("IN", "response", {
@@ -434,9 +457,6 @@ def _repair_anthropic_history(history: list[dict]):
if isinstance(block, dict):
if block.get("type") == "tool_use":
tool_use_ids.append(block["id"])
else:
if getattr(block, "type", None) == "tool_use":
tool_use_ids.append(block.id)
if not tool_use_ids:
return
history.append({
@@ -489,16 +509,19 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str) -> str:
messages=_anthropic_history,
)
# Convert SDK content block objects to plain dicts before storing in history
serialised_content = [_content_block_to_dict(b) for b in response.content]
_anthropic_history.append({
"role": "assistant",
"content": response.content,
"content": serialised_content,
})
text_blocks = [b.text for b in response.content if hasattr(b, "text") and b.text]
tool_use_blocks = [
{"id": b.id, "name": b.name, "input": b.input}
for b in response.content
if b.type == "tool_use"
if getattr(b, "type", None) == "tool_use"
]
usage_dict: dict = {}
@@ -525,7 +548,7 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str) -> str:
tool_results = []
for block in response.content:
if block.type == "tool_use" and block.name == TOOL_NAME:
if getattr(block, "type", None) == "tool_use" and getattr(block, "name", None) == TOOL_NAME:
script = block.input.get("script", "")
_append_comms("OUT", "tool_call", {
"name": TOOL_NAME,

View File

@@ -25,7 +25,13 @@ base_dir = "C:/Users/Ed/scoop/apps/sharex/current/ShareX/Screenshots/2026-02"
paths = []
[discussion]
history = []
history = [
"User:\nLets add multiple project support. currently this project would be the \"manual_slop\" project. \nI need to make a separate \"manual_slop.toml\" for this and offload things that would be within a per project to it.\nI also want to have several discussion histories that I can label. \nThis would allow me to stash and continue separate effort's histories..\nIt would be important if these discussions saved the last git commit they were on. (I'll need a buton to manually update it).\nAnd timestamps on each discussion entry to give an idea of the duration involved and \nhow the project has evolved since the discussion stream's effort was last done.\nBasically preserve context so ai can use that as valuable information in its working memory.\n\nTo load a project I'll need to hoist it into the config by providing the path.\nMaybe allow multiple project tomls to be specified path wise in the array.",
"User:\nOk your started to work on multiple-project support, you added project_manager.py and manaual_slop.toml, its not being used yet. the rest of the codebase hasn't intergrated with the changes.\nThere might be something wrong with powershell tool use. If you suspect so try to fix that but otherwise lets finish the support for multiple projects.",
"AI:\nGood. Now the big one — rewriting `gui.py`. The integration points are:\n\n- Load/save via `project_manager` instead of raw `config.toml` for project data\n- A **Projects** panel: list of loaded project paths, add/remove, switch active project, \"New Project\" button\n- A **Discussions** sub-section inside Discussion History panel: list named discussions, create/rename/delete/switch active, show git commit + timestamp, \"Update Commit\" button\n- `disc_entries` timestamps wired up via `entry_to_str`/`str_to_entry`\n- `_flush_to_project()` saves to the active `.toml`; `_flush_to_config()` saves global config only",
"User:\nSure do it, I can't restart the program till you do those changes cause otherwise the config will not work.",
"AI:\nI have a full picture of the current state. The plan is clear. I'll write the new `gui.py` as a Python script to avoid all PowerShell string-escaping issues — this is a full rewrite of that file since the integration touches nearly every part of it.\n\nKey changes:\n- `App.__init__` loads from `config.toml` for global settings (`[ai]`, `[theme]`, `[projects]`), then loads the active project `.toml` via `project_manager`\n- `_flush_to_project()` / `_flush_to_config()` save to the right places\n- **Projects panel** (`win_projects`): list of project paths from `config.toml [projects]`, add/remove, switch active — reloads all project-derived state\n- **Discussion selector** inside the Discussion History panel: list named discussions, create new, rename, delete, switch — each has git commit + last_updated shown, \"Update Commit\" button\n- `disc_entries` now round-trips through `project_manager.entry_to_str` / `str_to_entry` (preserving `@timestamp` lines)\n- New entries get a `ts` stamp when appended via `-> History`",
]
roles = [
"User",
"AI",

View File

@@ -10,13 +10,13 @@ Collapsed=0
[Window][###22]
Pos=0,0
Size=498,253
Size=498,409
Collapsed=0
DockId=0x00000039,0
DockId=0x0000003D,0
[Window][###30]
Pos=0,654
Size=498,853
Pos=0,781
Size=498,726
Collapsed=0
DockId=0x0000003B,0
@@ -62,10 +62,10 @@ Size=700,440
Collapsed=0
[Window][###126]
Pos=396,0
Size=783,2137
Pos=500,0
Size=768,2137
Collapsed=0
DockId=0x00000031,0
DockId=0x00000013,2
[Window][###147]
Pos=1578,868
@@ -95,8 +95,8 @@ Collapsed=0
DockId=0x00000011,0
[Window][###91]
Pos=1215,1692
Size=2625,445
Pos=1270,0
Size=1049,2137
Collapsed=0
DockId=0x00000014,0
@@ -104,19 +104,19 @@ DockId=0x00000014,0
Pos=2531,0
Size=1309,1690
Collapsed=0
DockId=0x0000001F,0
DockId=0x00000031,0
[Window][###106]
Pos=601,0
Size=922,2137
Pos=500,0
Size=768,2137
Collapsed=0
DockId=0x00000037,0
DockId=0x00000013,0
[Window][###100]
Pos=396,0
Size=783,2137
Collapsed=0
DockId=0x00000031,1
DockId=0x00000029,1
[Window][###133]
Pos=1306,785
@@ -147,7 +147,7 @@ Collapsed=0
Pos=376,0
Size=942,2137
Collapsed=0
DockId=0x00000037,2
DockId=0x00000013,2
[Window][###78]
Pos=0,1422
@@ -165,7 +165,7 @@ DockId=0x00000015,0
Pos=551,0
Size=1060,2137
Collapsed=0
DockId=0x0000001F,1
DockId=0x00000031,1
[Window][###110]
Pos=2438,0
@@ -177,7 +177,7 @@ DockId=0x00000016,0
Pos=500,0
Size=745,2137
Collapsed=0
DockId=0x00000037,0
DockId=0x00000013,0
[Window][###145]
Pos=1578,868
@@ -213,7 +213,7 @@ Collapsed=0
Pos=351,0
Size=645,1548
Collapsed=0
DockId=0x00000037,0
DockId=0x00000013,0
[Window][###75]
Pos=0,1352
@@ -225,13 +225,13 @@ DockId=0x00000022,0
Pos=1181,0
Size=1224,2137
Collapsed=0
DockId=0x0000001F,0
DockId=0x00000031,0
[Window][###92]
Pos=376,0
Size=942,2137
Collapsed=0
DockId=0x00000037,0
DockId=0x00000013,0
[Window][###107]
Pos=1525,1414
@@ -243,7 +243,7 @@ DockId=0x0000001A,0
Pos=500,0
Size=858,2137
Collapsed=0
DockId=0x00000037,0
DockId=0x00000013,0
[Window][###142]
Pos=0,328
@@ -302,19 +302,19 @@ DockId=0x00000028,0
Pos=998,0
Size=853,602
Collapsed=0
DockId=0x0000001F,0
DockId=0x00000031,0
[Window][###89]
Pos=351,0
Size=645,1548
Collapsed=0
DockId=0x00000037,1
DockId=0x00000013,1
[Window][###97]
Pos=1247,0
Size=1055,2137
Collapsed=0
DockId=0x00000031,0
DockId=0x00000029,0
[Window][###139]
Pos=1578,868
@@ -677,19 +677,19 @@ DockId=0x00000035,0
Pos=2321,1160
Size=1519,977
Collapsed=0
DockId=0x0000001F,0
DockId=0x00000031,0
[Window][###129]
Pos=500,0
Size=858,2137
Collapsed=0
DockId=0x00000037,2
DockId=0x00000013,2
[Window][###135]
Pos=500,0
Size=858,2137
Collapsed=0
DockId=0x00000037,1
DockId=0x00000013,1
[Window][###341]
Pos=1578,868
@@ -727,19 +727,19 @@ DockId=0x0000003C,0
Pos=2304,0
Size=1536,1069
Collapsed=0
DockId=0x0000001F,0
DockId=0x00000031,0
[Window][###132]
Pos=500,0
Size=745,2137
Size=768,2137
Collapsed=0
DockId=0x00000037,2
DockId=0x00000013,1
[Window][###138]
Pos=500,0
Size=745,2137
Collapsed=0
DockId=0x00000037,1
DockId=0x00000013,1
[Window][###358]
Pos=1578,868
@@ -756,24 +756,89 @@ Pos=1578,868
Size=700,440
Collapsed=0
[Window][###148]
Pos=0,411
Size=498,368
Collapsed=0
DockId=0x0000003E,0
[Window][###114]
Pos=2321,1071
Size=1519,1066
Collapsed=0
DockId=0x00000032,0
[Window][###122]
Pos=2321,0
Size=1519,1069
Collapsed=0
DockId=0x00000031,0
[Window][###240]
Pos=1578,868
Size=700,440
Collapsed=0
[Window][###315]
Pos=1578,868
Size=700,440
Collapsed=0
[Window][###405]
Pos=1578,868
Size=700,440
Collapsed=0
[Window][###488]
Pos=1578,868
Size=700,440
Collapsed=0
[Window][###586]
Pos=1578,868
Size=700,440
Collapsed=0
[Window][###1986]
Pos=1578,868
Size=700,440
Collapsed=0
[Window][###2086]
Pos=1578,868
Size=700,440
Collapsed=0
[Window][###2187]
Pos=1578,868
Size=700,440
Collapsed=0
[Window][###2295]
Pos=1578,868
Size=700,440
Collapsed=0
[Docking][Data]
DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,0 Size=3840,2137 Split=X Selected=0x40484D8F
DockNode ID=0x00000003 Parent=0x7C6B3D9B SizeRef=498,1161 Split=Y Selected=0xEE087978
DockNode ID=0x00000005 Parent=0x00000003 SizeRef=235,354 Split=Y Selected=0xEE087978
DockNode ID=0x00000005 Parent=0x00000003 SizeRef=235,779 Split=Y Selected=0xEE087978
DockNode ID=0x0000002D Parent=0x00000005 SizeRef=374,239 Split=Y Selected=0xEE087978
DockNode ID=0x0000002F Parent=0x0000002D SizeRef=374,326 Split=Y Selected=0xEE087978
DockNode ID=0x00000033 Parent=0x0000002F SizeRef=394,253 Split=Y Selected=0xEE087978
DockNode ID=0x00000039 Parent=0x00000033 SizeRef=498,410 Selected=0xEE087978
DockNode ID=0x00000039 Parent=0x00000033 SizeRef=498,410 Split=Y Selected=0xEE087978
DockNode ID=0x0000003D Parent=0x00000039 SizeRef=498,409 Selected=0xEE087978
DockNode ID=0x0000003E Parent=0x00000039 SizeRef=498,368 Selected=0x2349CB28
DockNode ID=0x0000003A Parent=0x00000033 SizeRef=498,240 Selected=0x2DE5F58C
DockNode ID=0x00000034 Parent=0x0000002F SizeRef=394,397 Selected=0xE5057AFC
DockNode ID=0x00000030 Parent=0x0000002D SizeRef=374,324 Selected=0x69F9D389
DockNode ID=0x0000002E Parent=0x00000005 SizeRef=374,411 Selected=0xFBBC1691
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=235,805 Split=Y Selected=0x5F94F9BD
DockNode ID=0x00000006 Parent=0x00000003 SizeRef=235,1356 Split=Y Selected=0x5F94F9BD
DockNode ID=0x00000009 Parent=0x00000006 SizeRef=235,453 Split=Y Selected=0x5F94F9BD
DockNode ID=0x0000001D Parent=0x00000009 SizeRef=364,766 Split=Y Selected=0x5F94F9BD
DockNode ID=0x00000021 Parent=0x0000001D SizeRef=549,696 Split=Y Selected=0x5F94F9BD
DockNode ID=0x00000027 Parent=0x00000021 SizeRef=549,793 Split=Y Selected=0x5F94F9BD
DockNode ID=0x0000003B Parent=0x00000027 SizeRef=498,853 Selected=0x5F94F9BD
DockNode ID=0x0000003B Parent=0x00000027 SizeRef=498,726 Selected=0x5F94F9BD
DockNode ID=0x0000003C Parent=0x00000027 SizeRef=498,628 Selected=0x083320CE
DockNode ID=0x00000028 Parent=0x00000021 SizeRef=549,688 Selected=0xBEC5E8CB
DockNode ID=0x00000022 Parent=0x0000001D SizeRef=549,785 Selected=0x0CE534DB
@@ -786,21 +851,22 @@ DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,0 Size
DockNode ID=0x0000000F Parent=0x00000008 SizeRef=1819,2137 Split=Y Selected=0x07E8375F
DockNode ID=0x00000011 Parent=0x0000000F SizeRef=835,425 Selected=0x72F373AE
DockNode ID=0x00000012 Parent=0x0000000F SizeRef=835,1710 Split=X Selected=0xC6DC3F21
DockNode ID=0x00000037 Parent=0x00000012 SizeRef=858,2137 Selected=0xC6DC3F21
DockNode ID=0x00000037 Parent=0x00000012 SizeRef=858,2137 Split=X Selected=0x4A209654
DockNode ID=0x00000013 Parent=0x00000037 SizeRef=768,2137 Selected=0x74FCE331
DockNode ID=0x00000014 Parent=0x00000037 SizeRef=1049,2137 Selected=0xC36FF36B
DockNode ID=0x00000038 Parent=0x00000012 SizeRef=959,2137 Selected=0x0B8F7C1B
DockNode ID=0x00000010 Parent=0x00000008 SizeRef=1519,2137 Split=Y Selected=0xCE7F911A
DockNode ID=0x00000013 Parent=0x00000010 SizeRef=1967,1690 Split=X Selected=0xCE7F911A
DockNode ID=0x00000017 Parent=0x00000013 SizeRef=1314,1749 Selected=0x4B454E0B
DockNode ID=0x00000018 Parent=0x00000013 SizeRef=1309,1749 Split=Y Selected=0x88A8C2FF
DockNode ID=0x00000010 Parent=0x00000008 SizeRef=1519,2137 Split=X Selected=0xCE7F911A
DockNode ID=0x00000017 Parent=0x00000010 SizeRef=1314,1749 Selected=0x4B454E0B
DockNode ID=0x00000018 Parent=0x00000010 SizeRef=1309,1749 Split=Y Selected=0x88A8C2FF
DockNode ID=0x00000019 Parent=0x00000018 SizeRef=2440,1412 Split=X Selected=0x88A8C2FF
DockNode ID=0x00000023 Parent=0x00000019 SizeRef=2009,737 Split=Y Selected=0x4F935A1E
DockNode ID=0x00000025 Parent=0x00000023 SizeRef=2315,1244 Split=X Selected=0x4F935A1E
DockNode ID=0x00000029 Parent=0x00000025 SizeRef=853,1867 Split=X Selected=0xFDB3860E
DockNode ID=0x00000031 Parent=0x00000029 SizeRef=1055,2137 Selected=0x4C2F06CB
DockNode ID=0x00000032 Parent=0x00000029 SizeRef=1536,2137 Split=Y Selected=0x0D80EC84
DockNode ID=0x00000035 Parent=0x00000032 SizeRef=2500,1158 Selected=0xF1D4CD4A
DockNode ID=0x00000036 Parent=0x00000032 SizeRef=2500,977 Split=Y Selected=0xF5102835
DockNode ID=0x0000001F Parent=0x00000036 SizeRef=1325,1069 CentralNode=1 Selected=0x0D80EC84
DockNode ID=0x00000029 Parent=0x00000025 SizeRef=853,1867 Split=Y Selected=0xFDB3860E
DockNode ID=0x00000035 Parent=0x00000029 SizeRef=2500,1158 Selected=0xF1D4CD4A
DockNode ID=0x00000036 Parent=0x00000029 SizeRef=2500,977 Split=Y Selected=0xF5102835
DockNode ID=0x0000001F Parent=0x00000036 SizeRef=1325,1069 Split=Y Selected=0xBFA03094
DockNode ID=0x00000031 Parent=0x0000001F SizeRef=1519,1069 CentralNode=1 Selected=0xBFA03094
DockNode ID=0x00000032 Parent=0x0000001F SizeRef=1519,1066 Selected=0xB674B79A
DockNode ID=0x00000020 Parent=0x00000036 SizeRef=1325,1066 Selected=0xC56063F4
DockNode ID=0x0000002A Parent=0x00000025 SizeRef=890,1867 Selected=0x40484D8F
DockNode ID=0x00000026 Parent=0x00000023 SizeRef=2315,621 Selected=0x7D28643F
@@ -808,7 +874,6 @@ DockSpace ID=0x7C6B3D9B Window=0xA87D555D Pos=0,0 Size
DockNode ID=0x0000002B Parent=0x00000024 SizeRef=1153,968 Selected=0xB8D8893E
DockNode ID=0x0000002C Parent=0x00000024 SizeRef=1153,1167 Selected=0xCCB4E4FA
DockNode ID=0x0000001A Parent=0x00000018 SizeRef=2440,723 Selected=0x3A881EEF
DockNode ID=0x00000014 Parent=0x00000010 SizeRef=1967,445 Selected=0xC36FF36B
DockNode ID=0x00000002 Parent=0x00000004 SizeRef=2227,1161 Split=X Selected=0x714F2F7B
DockNode ID=0x0000000B Parent=0x00000002 SizeRef=968,1161 Selected=0xC915D9DA
DockNode ID=0x0000000C Parent=0x00000002 SizeRef=1661,1161 Split=Y Selected=0x714F2F7B

28
patch_test.py Normal file
View File

@@ -0,0 +1,28 @@
# patch_gui.py — apply multi-project support to gui.py
import re
from pathlib import Path
src = Path("C:/projects/manual_slop/gui.py").read_text(encoding="utf-8")
# ── 1. Add project_manager import after "import theme" ──────────────────────
src = src.replace(
"import theme\n",
"import theme\nimport project_manager\n",
1
)
# ── 2. Add PROJECT_TOML_PATH constant after CONFIG_PATH line ─────────────────
src = src.replace(
'CONFIG_PATH = Path("config.toml")\n',
'CONFIG_PATH = Path("config.toml")\nPROVIDERS = ["gemini", "anthropic"]\n',
1
)
# Remove the duplicate PROVIDERS = [...] that already exists two lines down
src = src.replace(
'PROVIDERS = ["gemini", "anthropic"]\nPROVIDERS = ["gemini", "anthropic"]\n',
'PROVIDERS = ["gemini", "anthropic"]\n',
1
)
print("Pass 1 done - imports & constants")
print(repr(src[:300]))

View File

@@ -1,20 +1,29 @@
# project_manager.py
import subprocess, datetime, tomllib, tomli_w, re
import subprocess
import datetime
import tomllib
import tomli_w
import re
from pathlib import Path
TS_FMT = "%Y-%m-%dT%H:%M:%S"
def now_ts() -> str:
return datetime.datetime.now().strftime(TS_FMT)
def parse_ts(s: str):
try: return datetime.datetime.strptime(s, TS_FMT)
except: return None
# ── entry serialisation ───────────────────────────────────────────────────────
def parse_ts(s: str):
try:
return datetime.datetime.strptime(s, TS_FMT)
except Exception:
return None
# ── entry serialisation ──────────────────────────────────────────────────────
def entry_to_str(entry: dict) -> str:
"""Serialise a disc entry dict -> stored string. Always uses real newlines."""
"""Serialise a disc entry dict -> stored string."""
ts = entry.get("ts", "")
role = entry.get("role", "User")
content = entry.get("content", "")
@@ -22,11 +31,11 @@ def entry_to_str(entry: dict) -> str:
return f"@{ts}\n{role}:\n{content}"
return f"{role}:\n{content}"
def str_to_entry(raw: str, roles: list[str]) -> dict:
"""Parse a stored string back to a disc entry dict."""
ts = ""
rest = raw
# Strip optional leading @timestamp line
if rest.startswith("@"):
nl = rest.find("\n")
if nl != -1:
@@ -48,29 +57,37 @@ def str_to_entry(raw: str, roles: list[str]) -> dict:
content = parts[1].strip() if len(parts) > 1 else ""
return {"role": matched_role, "content": content, "collapsed": False, "ts": ts}
# ── git helpers ───────────────────────────────────────────────────────────────
# ── git helpers ──────────────────────────────────────────────────────────────
def get_git_commit(git_dir: str) -> str:
try:
r = subprocess.run(["git", "rev-parse", "HEAD"],
capture_output=True, text=True, cwd=git_dir, timeout=5)
r = subprocess.run(
["git", "rev-parse", "HEAD"],
capture_output=True, text=True, cwd=git_dir, timeout=5,
)
return r.stdout.strip() if r.returncode == 0 else ""
except Exception:
return ""
def get_git_log(git_dir: str, n: int = 5) -> str:
try:
r = subprocess.run(["git", "log", "--oneline", f"-{n}"],
capture_output=True, text=True, cwd=git_dir, timeout=5)
r = subprocess.run(
["git", "log", "--oneline", f"-{n}"],
capture_output=True, text=True, cwd=git_dir, timeout=5,
)
return r.stdout.strip() if r.returncode == 0 else ""
except Exception:
return ""
# ── default structures ────────────────────────────────────────────────────────
# ── default structures ───────────────────────────────────────────────────────
def default_discussion() -> dict:
return {"git_commit": "", "last_updated": now_ts(), "history": []}
def default_project(name: str = "unnamed") -> dict:
return {
"project": {"name": name, "git_dir": ""},
@@ -84,16 +101,19 @@ def default_project(name: str = "unnamed") -> dict:
},
}
# ── load / save ───────────────────────────────────────────────────────────────
# ── load / save ──────────────────────────────────────────────────────────────
def load_project(path) -> dict:
with open(path, "rb") as f:
return tomllib.load(f)
def save_project(proj: dict, path):
with open(path, "wb") as f:
tomli_w.dump(proj, f)
# ── migration helper ─────────────────────────────────────────────────────────
def migrate_from_legacy_config(cfg: dict) -> dict:
@@ -105,11 +125,12 @@ def migrate_from_legacy_config(cfg: dict) -> dict:
proj[key] = dict(cfg[key])
disc = cfg.get("discussion", {})
proj["discussion"]["roles"] = disc.get("roles", ["User", "AI", "Vendor API", "System"])
main = proj["discussion"]["discussions"]["main"]
main["history"] = disc.get("history", [])
main["last_updated"] = now_ts()
main_disc = proj["discussion"]["discussions"]["main"]
main_disc["history"] = disc.get("history", [])
main_disc["last_updated"] = now_ts()
return proj
# ── flat config for aggregate.run() ─────────────────────────────────────────
def flat_config(proj: dict, disc_name: str | None = None) -> dict: