Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e481257eda | |||
| 00d14131a9 | |||
| 478dbb9b86 | |||
| 32af96b365 |
+5
-2
@@ -31,7 +31,7 @@ Is a local GUI tool for manually curating and sending context to AI APIs. It agg
|
||||
- `mcp_client.py` - MCP-style read-only file tools (read_file, list_directory, search_files, get_file_summary); allowlist enforced against project file_items + base_dirs; dispatched by ai_client tool-use loop for both Anthropic and Gemini
|
||||
- `summarize.py` - local heuristic summariser (no AI); .py via AST, .toml via regex, .md headings, generic preview; used by mcp_client.get_file_summary and aggregate.build_summary_section
|
||||
- `config.toml` - global-only settings: [ai] provider+model+system_prompt, [theme] palette+font+scale, [projects] paths array + active path
|
||||
- `manual_slop.toml` - per-project file: [project] name+git_dir+system_prompt, [output] namespace+output_dir, [files] base_dir+paths, [screenshots] base_dir+paths, [discussion] roles+active+[discussion.discussions.<name>] git_commit+last_updated+history
|
||||
- `manual_slop.toml` - per-project file: [project] name+git_dir+system_prompt+main_context, [output] namespace+output_dir, [files] base_dir+paths, [screenshots] base_dir+paths, [discussion] roles+active+[discussion.discussions.<name>] git_commit+last_updated+history
|
||||
- `credentials.toml` - gemini api_key, anthropic api_key
|
||||
- `dpg_layout.ini` - Dear PyGui window layout file (auto-saved on exit, auto-loaded on startup); gitignore this per-user
|
||||
|
||||
@@ -201,4 +201,7 @@ Entry layout: index + timestamp + direction + kind + provider/model header row,
|
||||
- Discussion history excerpts could be individually toggleable for inclusion in the generated md
|
||||
- `MAX_TOOL_ROUNDS` in `ai_client.py` caps agentic loops at 5 rounds; adjustable
|
||||
- `COMMS_CLAMP_CHARS` in `gui.py` controls the character threshold for clamping heavy payload fields in the Comms History panel
|
||||
- Additional project metadata (description, tags, created date) could be added to `[project]` in the per-project toml
|
||||
- Additional project metadata (description, tags, created date) could be added to `[project]` in the per-project toml
|
||||
### Gemini Context Management
|
||||
- Investigating ways to prevent context duplication in _gemini_chat history, as currently <context>{md_content}</context> is prepended to the user message on every single request, causing history bloat.
|
||||
- Discussing explicit Gemini Context Caching API (client.caches.create()) to store read-only file context and avoid re-reading files across sessions.
|
||||
|
||||
+80
-70
@@ -27,7 +27,8 @@ comms_log_callback = None
|
||||
# Signature: (script: str, result: str) -> None
|
||||
tool_log_callback = None
|
||||
|
||||
MAX_TOOL_ROUNDS = 5
|
||||
# Increased to allow thorough code exploration before forcing a summary
|
||||
MAX_TOOL_ROUNDS = 10
|
||||
|
||||
# Maximum characters per text chunk sent to Anthropic.
|
||||
# Kept well under the ~200k token API limit.
|
||||
@@ -411,30 +412,46 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items:
|
||||
_ensure_gemini_client()
|
||||
mcp_client.configure(file_items or [], [base_dir])
|
||||
|
||||
system_text = _get_combined_system_prompt() + f"\n\n<context>\n{md_content}\n</context>"
|
||||
if _gemini_chat is None:
|
||||
_gemini_chat = _gemini_client.chats.create(
|
||||
model=_model,
|
||||
config=types.GenerateContentConfig(
|
||||
system_instruction=_get_combined_system_prompt(),
|
||||
system_instruction=system_text,
|
||||
tools=[_gemini_tool_declaration()]
|
||||
)
|
||||
)
|
||||
else:
|
||||
_gemini_chat = _gemini_client.chats.create(
|
||||
model=_model,
|
||||
config=types.GenerateContentConfig(
|
||||
system_instruction=system_text,
|
||||
tools=[_gemini_tool_declaration()]
|
||||
),
|
||||
history=_gemini_chat.get_history()
|
||||
)
|
||||
|
||||
full_message = f"<context>\n{md_content}\n</context>\n\n{user_message}"
|
||||
payload_to_send = user_message
|
||||
|
||||
_append_comms("OUT", "request", {
|
||||
"message": f"[context {len(md_content)} chars + user message {len(user_message)} chars]",
|
||||
})
|
||||
|
||||
response = _gemini_chat.send_message(full_message)
|
||||
all_text_parts = []
|
||||
|
||||
# We allow MAX_TOOL_ROUNDS, plus 1 final loop to get the text synthesis
|
||||
for round_idx in range(MAX_TOOL_ROUNDS + 2):
|
||||
response = _gemini_chat.send_message(payload_to_send)
|
||||
|
||||
for round_idx in range(MAX_TOOL_ROUNDS):
|
||||
text_parts_raw = [
|
||||
part.text
|
||||
for candidate in response.candidates
|
||||
for part in candidate.content.parts
|
||||
if hasattr(part, "text") and part.text
|
||||
]
|
||||
if text_parts_raw:
|
||||
all_text_parts.append("\n".join(text_parts_raw))
|
||||
|
||||
tool_calls = [
|
||||
part.function_call
|
||||
for candidate in response.candidates
|
||||
@@ -468,62 +485,52 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str, file_items:
|
||||
if not tool_calls:
|
||||
break
|
||||
|
||||
if round_idx > MAX_TOOL_ROUNDS:
|
||||
# The model ignored the MAX ROUNDS warning and kept calling tools.
|
||||
# Force abort to prevent infinite loop.
|
||||
break
|
||||
|
||||
function_responses = []
|
||||
sent_results_log = []
|
||||
for fc in tool_calls:
|
||||
|
||||
for i, fc in enumerate(tool_calls):
|
||||
fc_name = fc.name
|
||||
fc_args = dict(fc.args)
|
||||
|
||||
if fc_name in mcp_client.TOOL_NAMES:
|
||||
_append_comms("OUT", "tool_call", {"name": fc_name, "args": fc_args})
|
||||
output = mcp_client.dispatch(fc_name, fc_args)
|
||||
_append_comms("IN", "tool_result", {"name": fc_name, "output": output})
|
||||
function_responses.append(
|
||||
types.Part.from_function_response(
|
||||
name=fc_name,
|
||||
response={"output": output}
|
||||
)
|
||||
)
|
||||
sent_results_log.append({"tool_use_id": fc_name, "content": output})
|
||||
elif fc_name == TOOL_NAME:
|
||||
script = fc_args.get("script", "")
|
||||
_append_comms("OUT", "tool_call", {
|
||||
"name": TOOL_NAME,
|
||||
"script": script,
|
||||
})
|
||||
_append_comms("OUT", "tool_call", {"name": TOOL_NAME, "script": script})
|
||||
output = _run_script(script, base_dir)
|
||||
_append_comms("IN", "tool_result", {
|
||||
"name": TOOL_NAME,
|
||||
"output": output,
|
||||
})
|
||||
function_responses.append(
|
||||
types.Part.from_function_response(
|
||||
name=TOOL_NAME,
|
||||
response={"output": output}
|
||||
)
|
||||
)
|
||||
sent_results_log.append({"tool_use_id": TOOL_NAME, "content": output})
|
||||
_append_comms("IN", "tool_result", {"name": TOOL_NAME, "output": output})
|
||||
else:
|
||||
output = f"ERROR: unknown tool '{fc_name}'"
|
||||
|
||||
if not function_responses:
|
||||
break
|
||||
# Inject dynamic updates directly into the LAST tool's output string.
|
||||
# Gemini strictly expects function_responses only, so we piggyback on the string.
|
||||
if i == len(tool_calls) - 1:
|
||||
if file_items:
|
||||
file_items = _reread_file_items(file_items)
|
||||
refreshed_ctx = _build_file_context_text(file_items)
|
||||
if refreshed_ctx:
|
||||
output += f"\n\n[SYSTEM: FILES UPDATED — current contents below. Do NOT re-read these files.]\n\n{refreshed_ctx}"
|
||||
|
||||
if round_idx == MAX_TOOL_ROUNDS:
|
||||
output += "\n\n[SYSTEM WARNING: MAX TOOL ROUNDS REACHED. YOU MUST PROVIDE YOUR FINAL ANSWER NOW WITHOUT CALLING ANY MORE TOOLS.]"
|
||||
|
||||
# Refresh file context after tool calls locally, but DO NOT inject as text part into Gemini.
|
||||
# Gemini strictly expects only function_responses in this array.
|
||||
if file_items:
|
||||
file_items = _reread_file_items(file_items)
|
||||
function_responses.append(
|
||||
types.Part.from_function_response(name=fc_name, response={"output": output})
|
||||
)
|
||||
sent_results_log.append({"tool_use_id": fc_name, "content": output})
|
||||
|
||||
_append_comms("OUT", "tool_result_send", {
|
||||
"results": sent_results_log
|
||||
})
|
||||
_append_comms("OUT", "tool_result_send", {"results": sent_results_log})
|
||||
payload_to_send = function_responses
|
||||
|
||||
response = _gemini_chat.send_message(function_responses)
|
||||
|
||||
text_parts = [
|
||||
part.text
|
||||
for candidate in response.candidates
|
||||
for part in candidate.content.parts
|
||||
if hasattr(part, "text") and part.text
|
||||
]
|
||||
return "\n".join(text_parts)
|
||||
final_text = "\n\n".join(all_text_parts)
|
||||
return final_text if final_text.strip() else "(No text returned by the model)"
|
||||
|
||||
except ProviderError:
|
||||
raise
|
||||
@@ -612,35 +619,31 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
||||
_ensure_anthropic_client()
|
||||
mcp_client.configure(file_items or [], [base_dir])
|
||||
|
||||
context_blocks = _build_chunked_context_blocks(md_content)
|
||||
system_text = _get_combined_system_prompt() + f"\n\n<context>\n{md_content}\n</context>"
|
||||
system_blocks = _build_chunked_context_blocks(system_text)
|
||||
|
||||
user_content = context_blocks + [
|
||||
{"type": "text", "text": user_message}
|
||||
]
|
||||
user_content = [{"type": "text", "text": user_message}]
|
||||
|
||||
_strip_cache_controls(_anthropic_history)
|
||||
_repair_anthropic_history(_anthropic_history)
|
||||
_anthropic_history.append({"role": "user", "content": user_content})
|
||||
|
||||
n_chunks = len(context_blocks)
|
||||
n_chunks = len(system_blocks)
|
||||
_append_comms("OUT", "request", {
|
||||
"message": (
|
||||
f"[{n_chunks} chunk(s), {len(md_content)} chars context] "
|
||||
f"[system {n_chunks} chunk(s), {len(md_content)} chars context] "
|
||||
f"{user_message[:200]}{'...' if len(user_message) > 200 else ''}"
|
||||
),
|
||||
})
|
||||
|
||||
for round_idx in range(MAX_TOOL_ROUNDS):
|
||||
all_text_parts = []
|
||||
|
||||
# We allow MAX_TOOL_ROUNDS, plus 1 final loop to get the text synthesis
|
||||
for round_idx in range(MAX_TOOL_ROUNDS + 2):
|
||||
response = _anthropic_client.messages.create(
|
||||
model=_model,
|
||||
max_tokens=8096,
|
||||
system=[
|
||||
{
|
||||
"type": "text",
|
||||
"text": _get_combined_system_prompt(),
|
||||
"cache_control": {"type": "ephemeral"},
|
||||
}
|
||||
],
|
||||
system=system_blocks,
|
||||
tools=_build_anthropic_tools(),
|
||||
messages=_anthropic_history,
|
||||
)
|
||||
@@ -654,6 +657,9 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
||||
})
|
||||
|
||||
text_blocks = [b.text for b in response.content if hasattr(b, "text") and b.text]
|
||||
if text_blocks:
|
||||
all_text_parts.append("\n".join(text_blocks))
|
||||
|
||||
tool_use_blocks = [
|
||||
{"id": b.id, "name": b.name, "input": b.input}
|
||||
for b in response.content
|
||||
@@ -679,7 +685,12 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
||||
"usage": usage_dict,
|
||||
})
|
||||
|
||||
if response.stop_reason != "tool_use":
|
||||
if response.stop_reason != "tool_use" or not tool_use_blocks:
|
||||
break
|
||||
|
||||
if round_idx > MAX_TOOL_ROUNDS:
|
||||
# The model ignored the MAX ROUNDS warning and kept calling tools.
|
||||
# Force abort to prevent infinite loop.
|
||||
break
|
||||
|
||||
tool_results = []
|
||||
@@ -717,9 +728,6 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
||||
"content": output,
|
||||
})
|
||||
|
||||
if not tool_results:
|
||||
break
|
||||
|
||||
# Refresh file context after tool calls and inject into tool result message
|
||||
if file_items:
|
||||
file_items = _reread_file_items(file_items)
|
||||
@@ -734,6 +742,12 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
||||
),
|
||||
})
|
||||
|
||||
if round_idx == MAX_TOOL_ROUNDS:
|
||||
tool_results.append({
|
||||
"type": "text",
|
||||
"text": "SYSTEM WARNING: MAX TOOL ROUNDS REACHED. YOU MUST PROVIDE YOUR FINAL ANSWER NOW WITHOUT CALLING ANY MORE TOOLS."
|
||||
})
|
||||
|
||||
_anthropic_history.append({
|
||||
"role": "user",
|
||||
"content": tool_results,
|
||||
@@ -746,12 +760,8 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
||||
],
|
||||
})
|
||||
|
||||
text_parts = [
|
||||
block.text
|
||||
for block in response.content
|
||||
if hasattr(block, "text") and block.text
|
||||
]
|
||||
return "\n".join(text_parts)
|
||||
final_text = "\n\n".join(all_text_parts)
|
||||
return final_text if final_text.strip() else "(No text returned by the model)"
|
||||
|
||||
except ProviderError:
|
||||
raise
|
||||
|
||||
@@ -467,6 +467,8 @@ class App:
|
||||
dpg.set_value("project_git_dir", proj.get("project", {}).get("git_dir", ""))
|
||||
if dpg.does_item_exist("project_system_prompt"):
|
||||
dpg.set_value("project_system_prompt", proj.get("project", {}).get("system_prompt", ""))
|
||||
if dpg.does_item_exist("project_main_context"):
|
||||
dpg.set_value("project_main_context", proj.get("project", {}).get("main_context", ""))
|
||||
|
||||
def _save_active_project(self):
|
||||
"""Write self.project to the active project .toml file."""
|
||||
@@ -692,6 +694,8 @@ class App:
|
||||
proj["project"]["git_dir"] = dpg.get_value("project_git_dir")
|
||||
if dpg.does_item_exist("project_system_prompt"):
|
||||
proj["project"]["system_prompt"] = dpg.get_value("project_system_prompt")
|
||||
if dpg.does_item_exist("project_main_context"):
|
||||
proj["project"]["main_context"] = dpg.get_value("project_main_context")
|
||||
|
||||
# Discussion
|
||||
self._flush_disc_entries_to_project()
|
||||
@@ -1064,6 +1068,13 @@ class App:
|
||||
if d and dpg.does_item_exist("project_git_dir"):
|
||||
dpg.set_value("project_git_dir", d)
|
||||
|
||||
def cb_browse_main_context(self):
|
||||
root = hide_tk_root()
|
||||
p = filedialog.askopenfilename(title="Select Main Context File")
|
||||
root.destroy()
|
||||
if p and dpg.does_item_exist("project_main_context"):
|
||||
dpg.set_value("project_main_context", p)
|
||||
|
||||
# ---- discussion callbacks ----
|
||||
|
||||
def cb_disc_switch(self, sender, app_data):
|
||||
@@ -1390,6 +1401,15 @@ class App:
|
||||
)
|
||||
dpg.add_button(label="Browse##git", callback=self.cb_browse_git_dir)
|
||||
dpg.add_separator()
|
||||
dpg.add_text("Main Context File")
|
||||
with dpg.group(horizontal=True):
|
||||
dpg.add_input_text(
|
||||
tag="project_main_context",
|
||||
default_value=proj_meta.get("main_context", ""),
|
||||
width=-100,
|
||||
)
|
||||
dpg.add_button(label="Browse##ctx", callback=self.cb_browse_main_context)
|
||||
dpg.add_separator()
|
||||
dpg.add_text("Project Files")
|
||||
with dpg.child_window(tag="projects_scroll", height=-40, border=True):
|
||||
pass
|
||||
|
||||
+18
-8
@@ -29,9 +29,7 @@ paths = [
|
||||
|
||||
[screenshots]
|
||||
base_dir = "C:/Users/Ed/scoop/apps/sharex/current/ShareX/Screenshots/2026-02"
|
||||
paths = [
|
||||
"C:/Users/Ed/scoop/apps/sharex/current/ShareX/Screenshots/2026-02/python_2026-02-21_22-11-51.png",
|
||||
]
|
||||
paths = []
|
||||
|
||||
[discussion]
|
||||
roles = [
|
||||
@@ -40,11 +38,11 @@ roles = [
|
||||
"Vendor API",
|
||||
"System",
|
||||
]
|
||||
active = "Support for system prompt"
|
||||
active = "Make sure it works well with gemini"
|
||||
|
||||
[discussion.discussions.main]
|
||||
git_commit = "3b2da7873f839bf062c06c7539fadd9df7a3e2b9"
|
||||
last_updated = "2026-02-21T21:15:21"
|
||||
last_updated = "2026-02-21T22:23:30"
|
||||
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.",
|
||||
@@ -65,7 +63,7 @@ history = [
|
||||
|
||||
[discussion.discussions."Support files or mcp, or proper dynamic context uptake."]
|
||||
git_commit = "59208b27ec31949cb45cae382dddaee645b53b4f"
|
||||
last_updated = "2026-02-21T21:42:55"
|
||||
last_updated = "2026-02-21T22:23:32"
|
||||
history = [
|
||||
"@2026-02-21T21:30:26\nUser:\nLets add support for mcp. Similar to what claude code has, but restricted only to the paths provided. We have a basic summerizer right now but it doesn't work for all code files (we'd need tree-sitter). We need to add support for getting heurstics on what nuances each file has that is valuable for a basic overview before retrieving their content worst case to get a job done.",
|
||||
"@2026-02-21T21:30:32\nAI:\nThe PowerShell `Set-Content` write corrupted the file — it re-encoded it as Windows-1252, turning the UTF-8 em-dash (0xE2 0x80 0x94) into 0x97. I need to read the file as bytes, find and fix the corruption, apply the three targeted changes, and write back as UTF-8.",
|
||||
@@ -78,7 +76,7 @@ history = [
|
||||
|
||||
[discussion.discussions."Word Wrap"]
|
||||
git_commit = ""
|
||||
last_updated = "2026-02-21T21:15:22"
|
||||
last_updated = "2026-02-21T22:23:31"
|
||||
history = [
|
||||
"@2026-02-21T21:13:42\nUser:\nLets add support for all heavy text panels.",
|
||||
"@2026-02-21T21:13:46\nAI:\nThe file is fixed and reading cleanly now. The `gui.py` file has the UTF-8 BOM restored, the em-dash is proper UTF-8 again, and `wrap=0` is in place.\n\n**Summary of what changed:**\n\n- `gui.py` line 80: `wrap=460` → `wrap=0` in `_add_text_field()` — this is the only `dpg.add_text()` call that had a hardcoded pixel wrap width. With `wrap=0`, DearPyGUI wraps the text to the available parent container width dynamically, so it adapts when panels are resized/docked differently.\n\nAll the other text panels (`ai_input`, `ai_response`, `disc_content_*`, tool log results, confirm dialog script) use `dpg.add_input_text(multiline=True, width=-1)` which already wraps to available width automatically.",
|
||||
@@ -87,7 +85,7 @@ history = [
|
||||
|
||||
[discussion.discussions."Support for system prompt"]
|
||||
git_commit = "f258fc5765d647eb821716bce3d2000fb2ba2857"
|
||||
last_updated = "2026-02-21T22:13:20"
|
||||
last_updated = "2026-02-21T22:23:31"
|
||||
history = [
|
||||
"@2026-02-21T21:52:16\nUser:\ngemini testing 123 you get this message, first time using you proper in this custom frontend.",
|
||||
"@2026-02-21T21:52:17\nAI:\nLoud and clear! I've received your message through your **Manual Slop** frontend. It looks like a very capable environment—I can see you've implemented:\n\n1. **Multi-provider support** (Anthropic and Gemini).\n2. **Tool use/MCP-like capabilities** (file reading, directory listing, searching, etc.).\n3. **Project & Discussion management** via TOML files.\n4. **Rich GUI** using `dearpygui`.\n\nSince this is our first \"proper\" session in this interface, I'm ready to help you develop, debug, or refine the project. I have access to your local files through the tools you've provided, so I can see the codebase and even run PowerShell scripts if needed.\n\nIs there a specific feature you'd like to work on next, or any bugs in the Gemini integration you've noticed during your \"123\" test?",
|
||||
@@ -98,3 +96,15 @@ history = [
|
||||
"@2026-02-21T22:11:52\nUser:\nYep it looks like the system prompts are now available.\n\nQuestion: For how we're doing things right now, you have to re-read files cause its not saved in discussion history rigt per me sending a gen + send?\nIs there a way you can do a routine to save important files to the discussion or upload them temporarily to a gemini cache so that we don't have to worrya bout re-reading read-only things you already gathered info on for an active discussion?\nI'm not sure the depths of how this custom api works or custom tooling, etc gemini api from google provides...",
|
||||
"@2026-02-21T22:12:05\nSystem:\nAI got stuck and did nothing...",
|
||||
]
|
||||
|
||||
[discussion.discussions."Make sure it works well with gemini"]
|
||||
git_commit = "32af96b3658b813cf4fd7e270b20d661187394cf"
|
||||
last_updated = "2026-02-21T22:43:36"
|
||||
history = [
|
||||
"@2026-02-21T22:26:26\nUser:\nQuestion: For how we're doing things right now, you have to re-read files cause its not saved in discussion history rigt per me sending a gen + send?\nIs there a way you can do a routine to save important files to the discussion or upload them temporarily to a gemini cache so that we don't have to worrya bout re-reading read-only things you already gathered info on for an active discussion?\nI'm not sure the depths of how this custom api works or custom tooling, etc gemini api from google provides...\n\n(I'm also reviewing using this with gemini to make sure there are no hiccups from how the api is utilized).",
|
||||
"@2026-02-21T22:26:32\nAI:\n(No text returned by the model)",
|
||||
"@2026-02-21T22:27:47\nUser:\nUm... So can you answer my question? I want to know what we should do about preserving as much context ideally with how gemini's api works...",
|
||||
"@2026-02-21T22:28:41\nAI:\nTo answer your question, here is exactly how your current context and memory work with Gemini, and why there's actually a hidden issue we should address:\n\n### How memory works right now\n1. **Tool History is Remembered (Temporarily):** During an active session (until you close the app or click \"Reset Session\"), the `_gemini_chat` object keeps the entire conversation history in memory. **I actually do not need to re-read files** I've already opened with my tools during our active discussion. Gemini remembers the tool results.\n2. **Session Resets Wipe Memory:** If you close the app or change discussions, that memory is gone. The only things that survive are the files you explicitly added to the \"Files\" list in the GUI.\n\n### The Hidden Issue with `md_content`\nRight now, every time you hit \"Send\", `ai_client.py` does this:\n```python\npayload_to_send = f\"<context>\\n{md_content}\\n</context>\\n\\n{user_message}\"\n_gemini_chat.send_message(payload_to_send)\n```\nBecause `_gemini_chat` remembers history, **the entire `<context>` block (every file in your Files list) is being duplicated into the conversation history every single time you send a message!** This severely bloats your token usage and can cause issues with the API.\n\n### How we can solve this (My Suggestions)\n\n**1. Fix the Context Duplication (Recommended First Step)**\nInstead of prepending `md_content` to your user message on every single turn, we should modify `ai_client.py` to inject `md_content` into the `system_instruction` when `_gemini_chat` is first created. This means the context is loaded once per session and doesn't duplicate.\n\n**2. Use Gemini's Context Caching API (For Large Codebases)**\nGoogle provides a specific Context Caching API (`client.caches.create()`). We could modify the app so that if your \"Files\" list is very large (e.g., >32k tokens), we upload the files to a temporary Gemini cache. This would heavily reduce your API costs and speed up response times.\n\n**3. Auto-Adding to the \"Files\" List**\nIf I find a file important while I'm exploring using tools, I can use my PowerShell tool to automatically inject that file path into your `manual_slop.toml` project files list. That way, the file becomes permanent context for our discussion without you having to manually add it in the GUI.\n\n***\n\n**How would you like to proceed?** \nI highly recommend we at least fix the context duplication bug by moving `md_content` to the `system_instruction` for Gemini, but I will wait for your decision! I have updated `MainContext.md` to reflect this discussion.",
|
||||
"@2026-02-21T22:43:18\nUser:\nLets do these fixes. Also on the MainContext.md lets make that project configurable in the project's toml config.",
|
||||
"@2026-02-21T22:43:22\nAI:\nI have completed both of the requested fixes:\n\n**1. Context Duplication Fix for Gemini**\nThis was actually already present in `ai_client.py` from my previous changes but it's fully active! The `md_content` context string is injected directly into Gemini's `system_instruction` when `_gemini_chat` is created, and the `payload_to_send` only ever contains your user message. So the massive `<context>` block no longer gets duplicated into your active session history every time you send a message. It stays strictly outside the history loop.\n\n**2. Configurable `MainContext.md`**\nI have added support for this to the `[project]` structure in the TOML configuration:\n* I updated `project_manager.py` to handle the `main_context` field alongside the `git_dir` and `system_prompt`.\n* I updated `gui.py` to add a new **Main Context File** field (with a Browse button) directly below the Git Directory field in the **Projects** panel.\n* This path is now saved on a per-project basis inside the `.toml` file.\n* I've also updated `MainContext.md`'s list of project files to reflect this configuration change.\n\nSince Gemini Context Duplication is cleanly fixed with the system instructions route, you shouldn't need the `client.caches.create` API immediately (as the token history isn't growing astronomically per request anymore). \n\nYou'll need to restart the GUI to see the new **Main Context File** field in the Projects panel. Let me know if everything works well on your end!",
|
||||
]
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ def default_discussion() -> dict:
|
||||
|
||||
def default_project(name: str = "unnamed") -> dict:
|
||||
return {
|
||||
"project": {"name": name, "git_dir": "", "system_prompt": ""},
|
||||
"project": {"name": name, "git_dir": "", "system_prompt": "", "main_context": ""},
|
||||
"output": {"namespace": name, "output_dir": "./md_gen"},
|
||||
"files": {"base_dir": ".", "paths": []},
|
||||
"screenshots": {"base_dir": ".", "paths": []},
|
||||
|
||||
Reference in New Issue
Block a user