2 Commits

Author SHA1 Message Date
ed caa67206fa ux improvements 2026-02-21 23:08:42 -05:00
ed 173e09059d config updates 2026-02-21 22:47:39 -05:00
5 changed files with 188 additions and 82 deletions
+7 -7
View File
@@ -1,12 +1,5 @@
# Manual Slop # Manual Slop
## Baseline
Make sure to update this file every time.
DO NOT EVER make a shell script unless told to. DO NOT EVER make a readme or a file describing your changes unless your are told to. If you have commands I should be entering into the command line or if you have something to explain to me, please just use code blocks or normal text output. DO NOT DO ANYTHING OTHER THAN WHAT YOU WERE TOLD TODO. DO NOT EVER, EVER DO ANYTHING OTHER THAN WHAT YOU WERE TOLD TO DO. IF YOU WANT TO DO OTHER THINGS, SIMPLY SUGGEST THEM, AND THEN I WILL REVIEW YOUR CHANGES, AND MAKE THE DECISION ON HOW TO PROCEED. WHEN WRITING SCRIPTS USE A 120-160 character limit per line. I don't want to see scrunched code.
Make destructive modifications to the project, ITS OK, I HAVE GIT HISTORY TO MANAGE THE PROJECTS.
## Summary ## Summary
Is a local GUI tool for manually curating and sending context to AI APIs. It aggregates files, screenshots, and discussion history into a structured markdown file and sends it to a chosen AI provider with a user-written message. The AI can also execute PowerShell scripts within the project directory, with user confirmation required before each execution. Is a local GUI tool for manually curating and sending context to AI APIs. It aggregates files, screenshots, and discussion history into a structured markdown file and sends it to a chosen AI provider with a user-written message. The AI can also execute PowerShell scripts within the project directory, with user confirmation required before each execution.
@@ -202,6 +195,13 @@ Entry layout: index + timestamp + direction + kind + provider/model header row,
- `MAX_TOOL_ROUNDS` in `ai_client.py` caps agentic loops at 5 rounds; adjustable - `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 - `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 ### 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. - 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. - Discussing explicit Gemini Context Caching API (client.caches.create()) to store read-only file context and avoid re-reading files across sessions.
### Latest Changes
- Removed `Config` panel from the GUI to streamline per-project configuration.
- `output_dir` was moved into the Projects panel.
- `auto_add_history` was moved to the Discussion History panel.
- `namespace` is no longer a configurable field; `aggregate.py` automatically uses the active project's `name` property.
+3 -1
View File
@@ -128,7 +128,9 @@ def build_markdown(base_dir: Path, files: list[str], screenshot_base_dir: Path,
return "\n\n---\n\n".join(parts) return "\n\n---\n\n".join(parts)
def run(config: dict) -> tuple[str, Path]: def run(config: dict) -> tuple[str, Path]:
namespace = config["output"]["namespace"] namespace = config.get("project", {}).get("name")
if not namespace:
namespace = config.get("output", {}).get("namespace", "project")
output_dir = Path(config["output"]["output_dir"]) output_dir = Path(config["output"]["output_dir"])
base_dir = Path(config["files"]["base_dir"]) base_dir = Path(config["files"]["base_dir"])
files = config["files"].get("paths", []) files = config["files"].get("paths", [])
+163 -69
View File
@@ -3,6 +3,8 @@ import dearpygui.dearpygui as dpg
import tomllib import tomllib
import tomli_w import tomli_w
import threading import threading
import time
import math
from pathlib import Path from pathlib import Path
from tkinter import filedialog, Tk from tkinter import filedialog, Tk
import aggregate import aggregate
@@ -239,54 +241,71 @@ class ConfirmDialog:
ConfirmDialog._next_id += 1 ConfirmDialog._next_id += 1
self._uid = ConfirmDialog._next_id self._uid = ConfirmDialog._next_id
self._tag = f"confirm_dlg_{self._uid}" self._tag = f"confirm_dlg_{self._uid}"
self._script = script # Cast to str to ensure DPG doesn't crash on None or weird objects
self._base_dir = base_dir self._script = str(script) if script is not None else ""
self._base_dir = str(base_dir) if base_dir is not None else ""
self._event = threading.Event() self._event = threading.Event()
self._approved = False self._approved = False
def show(self): def show(self):
"""Called from main thread only.""" """Called from main thread only. Wrapped in try/except to prevent thread lockups."""
w, h = 700, 440 try:
vp_w = dpg.get_viewport_width() w, h = 700, 440
vp_h = dpg.get_viewport_height() vp_w = dpg.get_viewport_width()
px = max(0, (vp_w - w) // 2) vp_h = dpg.get_viewport_height()
py = max(0, (vp_h - h) // 2) px = max(0, (vp_w - w) // 2)
py = max(0, (vp_h - h) // 2)
with dpg.window( with dpg.window(
label=f"Approve PowerShell Command #{self._uid}", label=f"Approve PowerShell Command #{self._uid}",
tag=self._tag, tag=self._tag,
modal=True, modal=True,
no_close=True, no_close=True,
pos=(px, py), pos=(px, py),
width=w, width=w,
height=h, height=h,
): ):
dpg.add_text("The AI wants to run the following PowerShell script:") dpg.add_text("The AI wants to run the following PowerShell script:")
dpg.add_text(f"base_dir: {self._base_dir}", color=(200, 200, 100)) dpg.add_text(f"base_dir: {self._base_dir}", color=(200, 200, 100))
dpg.add_separator() dpg.add_separator()
dpg.add_input_text( dpg.add_input_text(
tag=f"{self._tag}_script", tag=f"{self._tag}_script",
default_value=self._script, default_value=self._script,
multiline=True, multiline=True,
width=-1, width=-1,
height=-72, height=-72,
readonly=False, readonly=False,
) )
dpg.add_separator() dpg.add_separator()
with dpg.group(horizontal=True): with dpg.group(horizontal=True):
dpg.add_button(label="Approve & Run", callback=self._cb_approve) dpg.add_button(label="Approve & Run", callback=self._cb_approve)
dpg.add_button(label="Reject", callback=self._cb_reject) dpg.add_button(label="Reject", callback=self._cb_reject)
dpg.focus_item(self._tag)
except Exception as e:
print(f"ERROR rendering ConfirmDialog: {e}")
self._approved = False
self._event.set()
def _cb_approve(self): def _cb_approve(self):
self._script = dpg.get_value(f"{self._tag}_script") try:
self._script = dpg.get_value(f"{self._tag}_script")
except Exception:
pass
self._approved = True self._approved = True
self._event.set() self._event.set()
dpg.delete_item(self._tag) try:
dpg.delete_item(self._tag)
except Exception:
pass
def _cb_reject(self): def _cb_reject(self):
self._approved = False self._approved = False
self._event.set() self._event.set()
dpg.delete_item(self._tag) try:
dpg.delete_item(self._tag)
except Exception:
pass
def wait(self) -> tuple[bool, str]: def wait(self) -> tuple[bool, str]:
"""Called from background thread. Blocks until user acts.""" """Called from background thread. Blocks until user acts."""
@@ -363,6 +382,15 @@ class App:
self._pending_comms_lock = threading.Lock() self._pending_comms_lock = threading.Lock()
self._comms_entry_count = 0 self._comms_entry_count = 0
# Auto-history queues
self._pending_history_adds: list[dict] = []
self._pending_history_adds_lock = threading.Lock()
# Blink state
self._trigger_blink = False
self._is_blinking = False
self._blink_start_time = 0.0
session_logger.open_session() session_logger.open_session()
ai_client.set_provider(self.current_provider, self.current_model) ai_client.set_provider(self.current_provider, self.current_model)
ai_client.confirm_and_run_callback = self._confirm_and_run ai_client.confirm_and_run_callback = self._confirm_and_run
@@ -452,8 +480,6 @@ class App:
def _refresh_project_widgets(self): def _refresh_project_widgets(self):
"""Push project-level values into the GUI widgets.""" """Push project-level values into the GUI widgets."""
proj = self.project proj = self.project
if dpg.does_item_exist("namespace"):
dpg.set_value("namespace", proj.get("output", {}).get("namespace", ""))
if dpg.does_item_exist("output_dir"): if dpg.does_item_exist("output_dir"):
dpg.set_value("output_dir", proj.get("output", {}).get("output_dir", "./md_gen")) dpg.set_value("output_dir", proj.get("output", {}).get("output_dir", "./md_gen"))
if dpg.does_item_exist("files_base_dir"): if dpg.does_item_exist("files_base_dir"):
@@ -469,6 +495,8 @@ class App:
dpg.set_value("project_system_prompt", proj.get("project", {}).get("system_prompt", "")) dpg.set_value("project_system_prompt", proj.get("project", {}).get("system_prompt", ""))
if dpg.does_item_exist("project_main_context"): if dpg.does_item_exist("project_main_context"):
dpg.set_value("project_main_context", proj.get("project", {}).get("main_context", "")) dpg.set_value("project_main_context", proj.get("project", {}).get("main_context", ""))
if dpg.does_item_exist("auto_add_history"):
dpg.set_value("auto_add_history", proj.get("discussion", {}).get("auto_add", False))
def _save_active_project(self): def _save_active_project(self):
"""Write self.project to the active project .toml file.""" """Write self.project to the active project .toml file."""
@@ -586,6 +614,16 @@ class App:
self._rebuild_discussion_selector() self._rebuild_discussion_selector()
self._update_status(f"commit: {commit[:12]}") self._update_status(f"commit: {commit[:12]}")
def _queue_history_add(self, role: str, content: str):
"""Safely queue a new history entry from a background thread."""
with self._pending_history_adds_lock:
self._pending_history_adds.append({
"role": role,
"content": content,
"collapsed": False,
"ts": project_manager.now_ts()
})
# ---------------------------------------------------------------- comms log # ---------------------------------------------------------------- comms log
def _on_comms_entry(self, entry: dict): def _on_comms_entry(self, entry: dict):
@@ -671,8 +709,6 @@ class App:
# Output # Output
proj.setdefault("output", {}) proj.setdefault("output", {})
if dpg.does_item_exist("namespace"):
proj["output"]["namespace"] = dpg.get_value("namespace")
if dpg.does_item_exist("output_dir"): if dpg.does_item_exist("output_dir"):
proj["output"]["output_dir"] = dpg.get_value("output_dir") proj["output"]["output_dir"] = dpg.get_value("output_dir")
@@ -702,6 +738,8 @@ class App:
disc_sec = proj.setdefault("discussion", {}) disc_sec = proj.setdefault("discussion", {})
disc_sec["roles"] = self.disc_roles disc_sec["roles"] = self.disc_roles
disc_sec["active"] = self.active_discussion disc_sec["active"] = self.active_discussion
if dpg.does_item_exist("auto_add_history"):
disc_sec["auto_add"] = dpg.get_value("auto_add_history")
def _flush_to_config(self): def _flush_to_config(self):
"""Pull global settings into self.config (config.toml).""" """Pull global settings into self.config (config.toml)."""
@@ -988,13 +1026,30 @@ class App:
ai_client.set_custom_system_prompt("\n\n".join(combined_sp)) ai_client.set_custom_system_prompt("\n\n".join(combined_sp))
def do_send(): def do_send():
auto_add = dpg.get_value("auto_add_history") if dpg.does_item_exist("auto_add_history") else False
if auto_add:
self._queue_history_add("User", user_msg)
try: try:
response = ai_client.send(self.last_md, user_msg, base_dir, self.last_file_items) response = ai_client.send(self.last_md, user_msg, base_dir, self.last_file_items)
self._update_response(response) self._update_response(response)
self._update_status("done") self._update_status("done")
except Exception as e: self._trigger_blink = True
self._update_response(f"ERROR: {e}") if auto_add:
self._queue_history_add("AI", response)
except ProviderError as e:
resp = e.ui_message()
self._update_response(resp)
self._update_status("error") self._update_status("error")
self._trigger_blink = True
if auto_add:
self._queue_history_add("Vendor API", resp)
except Exception as e:
resp = f"ERROR: {e}"
self._update_response(resp)
self._update_status("error")
self._trigger_blink = True
if auto_add:
self._queue_history_add("System", resp)
self.send_thread = threading.Thread(target=do_send, daemon=True) self.send_thread = threading.Thread(target=do_send, daemon=True)
self.send_thread.start() self.send_thread.start()
@@ -1385,7 +1440,7 @@ class App:
tag="win_projects", tag="win_projects",
pos=(8, 8), pos=(8, 8),
width=400, width=400,
height=260, height=340,
no_close=True, no_close=True,
): ):
proj_meta = self.project.get("project", {}) proj_meta = self.project.get("project", {})
@@ -1410,6 +1465,15 @@ class App:
) )
dpg.add_button(label="Browse##ctx", callback=self.cb_browse_main_context) dpg.add_button(label="Browse##ctx", callback=self.cb_browse_main_context)
dpg.add_separator() dpg.add_separator()
dpg.add_text("Output Dir")
with dpg.group(horizontal=True):
dpg.add_input_text(
tag="output_dir",
default_value=self.project.get("output", {}).get("output_dir", "./md_gen"),
width=-100,
)
dpg.add_button(label="Browse##out", callback=self.cb_browse_output)
dpg.add_separator()
dpg.add_text("Project Files") dpg.add_text("Project Files")
with dpg.child_window(tag="projects_scroll", height=-40, border=True): with dpg.child_window(tag="projects_scroll", height=-40, border=True):
pass pass
@@ -1418,35 +1482,11 @@ class App:
dpg.add_button(label="New Project", callback=self.cb_new_project) dpg.add_button(label="New Project", callback=self.cb_new_project)
dpg.add_button(label="Save All", callback=self.cb_save_config) dpg.add_button(label="Save All", callback=self.cb_save_config)
# ---- Config panel ----
with dpg.window(
label="Config",
tag="win_config",
pos=(8, 276),
width=400,
height=160,
no_close=True,
):
dpg.add_text("Namespace")
dpg.add_input_text(
tag="namespace",
default_value=self.project.get("output", {}).get("namespace", ""),
width=-1,
)
dpg.add_text("Output Dir")
with dpg.group(horizontal=True):
dpg.add_input_text(
tag="output_dir",
default_value=self.project.get("output", {}).get("output_dir", "./md_gen"),
width=-220,
)
dpg.add_button(label="Browse Output Dir", callback=self.cb_browse_output)
# ---- Files panel ---- # ---- Files panel ----
with dpg.window( with dpg.window(
label="Files", label="Files",
tag="win_files", tag="win_files",
pos=(8, 444), pos=(8, 356),
width=400, width=400,
height=400, height=400,
no_close=True, no_close=True,
@@ -1520,6 +1560,11 @@ class App:
dpg.add_button(label="+All", callback=self.cb_disc_expand_all) dpg.add_button(label="+All", callback=self.cb_disc_expand_all)
dpg.add_button(label="Clear All", callback=self.cb_disc_clear) dpg.add_button(label="Clear All", callback=self.cb_disc_clear)
dpg.add_button(label="Save", callback=self.cb_disc_save) dpg.add_button(label="Save", callback=self.cb_disc_save)
dpg.add_checkbox(
tag="auto_add_history",
label="Auto-add message & response to history",
default_value=self.project.get("discussion", {}).get("auto_add", False)
)
dpg.add_separator() dpg.add_separator()
with dpg.collapsing_header(label="Roles", default_open=False): with dpg.collapsing_header(label="Roles", default_open=False):
with dpg.child_window(tag="disc_roles_scroll", height=96, border=True): with dpg.child_window(tag="disc_roles_scroll", height=96, border=True):
@@ -1695,13 +1740,63 @@ class App:
self._fetch_models(self.current_provider) self._fetch_models(self.current_provider)
while dpg.is_dearpygui_running(): while dpg.is_dearpygui_running():
# Show any pending confirmation dialog on the main thread # Show any pending confirmation dialog on the main thread safely
with self._pending_dialog_lock: with self._pending_dialog_lock:
dialog = self._pending_dialog dialog = self._pending_dialog
self._pending_dialog = None self._pending_dialog = None
if dialog is not None: if dialog is not None:
dialog.show() dialog.show()
# Process queued history additions
with self._pending_history_adds_lock:
adds = self._pending_history_adds[:]
self._pending_history_adds.clear()
if adds:
for item in adds:
if item["role"] not in self.disc_roles:
self.disc_roles.append(item["role"])
self._rebuild_disc_roles_list()
self.disc_entries.append(item)
self._rebuild_disc_list()
if dpg.does_item_exist("disc_scroll"):
# Force scroll to bottom using a very large number
dpg.set_y_scroll("disc_scroll", 99999)
# Handle retro arcade blinking effect
if self._trigger_blink:
self._trigger_blink = False
self._is_blinking = True
self._blink_start_time = time.time()
if dpg.does_item_exist("win_response"):
dpg.focus_item("win_response")
if self._is_blinking:
elapsed = time.time() - self._blink_start_time
if elapsed > 1.5:
self._is_blinking = False
if dpg.does_item_exist("response_blink_theme"):
try:
dpg.bind_item_theme("ai_response", 0)
except Exception:
pass
else:
# Square-wave style retro blink (4 times per second)
val = math.sin(elapsed * 8 * math.pi)
alpha = 120 if val > 0 else 0
if not dpg.does_item_exist("response_blink_theme"):
with dpg.theme(tag="response_blink_theme"):
with dpg.theme_component(dpg.mvInputText):
dpg.add_theme_color(dpg.mvThemeCol_FrameBg, (0, 255, 0, alpha), tag="response_blink_color")
else:
dpg.set_value("response_blink_color", [0, 255, 0, alpha])
if dpg.does_item_exist("ai_response"):
try:
dpg.bind_item_theme("ai_response", "response_blink_theme")
except Exception:
pass
# Flush any comms entries queued from background threads # Flush any comms entries queued from background threads
self._flush_pending_comms() self._flush_pending_comms()
@@ -1725,4 +1820,3 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+13 -4
View File
@@ -1,7 +1,8 @@
[project] [project]
name = "manual_slop" name = "manual_slop"
git_dir = "C:/projects/manual_slop" git_dir = "C:/projects/manual_slop"
system_prompt = "Make sure to update MainContext.md every time.\nMake destructive modifications to the project, ITS OK, I HAVE GIT HISTORY TO MANAGE THE PROJECTS.\n\nMainContext.md should be read if the discussion doesn't resolve whats being worked on." system_prompt = "Make sure to update MainContext.md every time.\nMake destructive modifications to the project, ITS OK, I HAVE GIT HISTORY TO MANAGE THE PROJECTS."
main_context = "C:/projects/manual_slop/MainContext.md"
[output] [output]
namespace = "manual_slop" namespace = "manual_slop"
@@ -16,7 +17,6 @@ paths = [
"gemini.py", "gemini.py",
"gui.py", "gui.py",
"pyproject.toml", "pyproject.toml",
"MainContext.md",
"C:/projects/manual_slop/shell_runner.py", "C:/projects/manual_slop/shell_runner.py",
"C:/projects/manual_slop/session_logger.py", "C:/projects/manual_slop/session_logger.py",
"C:/projects/manual_slop/file_cache.py", "C:/projects/manual_slop/file_cache.py",
@@ -38,7 +38,8 @@ roles = [
"Vendor API", "Vendor API",
"System", "System",
] ]
active = "Make sure it works well with gemini" active = "Misc UX improvements"
auto_add = true
[discussion.discussions.main] [discussion.discussions.main]
git_commit = "3b2da7873f839bf062c06c7539fadd9df7a3e2b9" git_commit = "3b2da7873f839bf062c06c7539fadd9df7a3e2b9"
@@ -99,7 +100,7 @@ history = [
[discussion.discussions."Make sure it works well with gemini"] [discussion.discussions."Make sure it works well with gemini"]
git_commit = "32af96b3658b813cf4fd7e270b20d661187394cf" git_commit = "32af96b3658b813cf4fd7e270b20d661187394cf"
last_updated = "2026-02-21T22:43:36" last_updated = "2026-02-21T22:46:10"
history = [ 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: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:26:32\nAI:\n(No text returned by the model)",
@@ -108,3 +109,11 @@ history = [
"@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: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!", "@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!",
] ]
[discussion.discussions."Misc UX improvements"]
git_commit = ""
last_updated = "2026-02-21T23:08:11"
history = [
"@2026-02-21T23:03:51\nUser:\nMake the namespace for the generated markdown file the projet name instead of that specific namespace in the config panel.\nThe ouptut dir can be in the project panel spec (that way we can just remove config panel entirely).",
"@2026-02-21T23:04:55\nAI:\nI have applied the changes to streamline the project settings and remove the old Config panel completely:\n\n1. **GUI Layout Changes (`gui.py`)**:\n - The `Config` panel has been completely removed.\n - The **Output Dir** field has been moved into the **Projects** panel (right above the Project Files list).\n - The **Auto-add history** checkbox has been moved to the top of the **Discussion History** panel (directly under the entry toolbar buttons).\n - The **Files** panel was shifted upward to reclaim the space left behind by the old Config panel.\n\n2. **Namespace Management (`aggregate.py` & `project_manager.py`)**:\n - The explicitly defined `namespace` field is gone.\n - Instead, `aggregate.py` now directly infers the output markdown file namespace from the active project's `name` property. (e.g., saving under the `manual_slop` project will just prefix the markdown with `manual_slop_001.md`).\n - `project_manager.py` was updated to no longer scaffold out `namespace` strings into new `.toml` files, and `flat_config` now injects the core `project` dictionary segment so the generators can access the internal name.\n\n3. **`MainContext.md` Updated**:\n - I have updated the markdown file with the latest layout structures and explained that `namespace` is no longer a configurable setting but dynamically determined by the project profile.\n\nYou'll need to restart the application for the GUI layout and DearPyGUI state changes to take effect! Let me know how it looks.",
]
+2 -1
View File
@@ -91,7 +91,7 @@ def default_discussion() -> dict:
def default_project(name: str = "unnamed") -> dict: def default_project(name: str = "unnamed") -> dict:
return { return {
"project": {"name": name, "git_dir": "", "system_prompt": "", "main_context": ""}, "project": {"name": name, "git_dir": "", "system_prompt": "", "main_context": ""},
"output": {"namespace": name, "output_dir": "./md_gen"}, "output": {"output_dir": "./md_gen"},
"files": {"base_dir": ".", "paths": []}, "files": {"base_dir": ".", "paths": []},
"screenshots": {"base_dir": ".", "paths": []}, "screenshots": {"base_dir": ".", "paths": []},
"discussion": { "discussion": {
@@ -139,6 +139,7 @@ def flat_config(proj: dict, disc_name: str | None = None) -> dict:
name = disc_name or disc_sec.get("active", "main") name = disc_name or disc_sec.get("active", "main")
disc_data = disc_sec.get("discussions", {}).get(name, {}) disc_data = disc_sec.get("discussions", {}).get(name, {})
return { return {
"project": proj.get("project", {}),
"output": proj.get("output", {}), "output": proj.get("output", {}),
"files": proj.get("files", {}), "files": proj.get("files", {}),
"screenshots": proj.get("screenshots", {}), "screenshots": proj.get("screenshots", {}),