From eab1945035ac81ba028a51a3b9e95e520df40b0a Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 5 May 2026 21:09:51 -0400 Subject: [PATCH] feat(workspace): implement layout capture/restore and controller integration --- src/app_controller.py | 20 +++++++++++++++++++- src/gui_2.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/app_controller.py b/src/app_controller.py index 484d006..b987874 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -6,6 +6,7 @@ import os import re from typing import Any, List, Dict, Optional, Callable from pathlib import Path +from src import workspace_manager import json import uuid import tomli_w @@ -635,7 +636,9 @@ class AppController: '_cb_save_tool_preset': self._cb_save_tool_preset, '_cb_delete_tool_preset': self._cb_delete_tool_preset, '_switch_project': self._switch_project, - '_refresh_from_project': self._refresh_from_project + '_refresh_from_project': self._refresh_from_project, + 'save_workspace_profile': self._cb_save_workspace_profile, + 'load_workspace_profile': self._cb_load_workspace_profile, } def _update_gcli_adapter(self, path: str) -> None: @@ -1038,6 +1041,8 @@ class AppController: self.project_paths = list(projects_cfg.get("paths", [])) self.active_project_path = projects_cfg.get("active", "") self._load_active_project() + self.workspace_manager = workspace_manager.WorkspaceManager(project_root=Path(self.active_project_path).parent if self.active_project_path else None) + self.workspace_profiles = self.workspace_manager.load_all_profiles() # Deserialize FileItems in files.paths raw_paths = self.project.get("files", {}).get("paths", []) self.files = [] @@ -2250,6 +2255,19 @@ class AppController: if self.rag_config and self.rag_config.enabled: self._rebuild_rag_index() + def _cb_save_workspace_profile(self, name: str, scope: str = 'project') -> None: + if not hasattr(self, '_app') or not self._app: + return + profile = self._app._capture_workspace_profile(name) + self.workspace_manager.save_profile(profile, scope=scope) + self.workspace_profiles = self.workspace_manager.load_all_profiles() + + def _cb_load_workspace_profile(self, name: str) -> None: + if name in self.workspace_profiles: + profile = self.workspace_profiles[name] + if hasattr(self, '_app') and self._app: + self._app._apply_workspace_profile(profile) + def _apply_preset(self, name: str, scope: str) -> None: print(f"[DEBUG] _apply_preset: name={name}, scope={scope}") if name == "None": diff --git a/src/gui_2.py b/src/gui_2.py index d42d6ed..2a2674a 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -25,6 +25,7 @@ from src import log_registry from src import log_pruner from src import models from src import app_controller +from src import workspace_manager from src import mcp_client from src import aggregate from src import markdown_helper @@ -103,6 +104,7 @@ class App: def __init__(self) -> None: # Initialize controller and delegate state self.controller = app_controller.AppController() + self.controller._app = self from src import history self.history = history.HistoryManager(max_capacity=100) self._last_ui_snapshot: Optional[history.UISnapshot] = None @@ -115,6 +117,8 @@ class App: if not hasattr(self.controller, 'PROVIDERS'): self.controller.PROVIDERS = PROVIDERS self.controller.init_state() + self.workspace_manager = workspace_manager.WorkspaceManager(project_root=self.active_project_root) + self.workspace_profiles = self.workspace_manager.load_all_profiles() self.show_windows.setdefault("Diagnostics", False) self.controller.start_services(self) self.controller._predefined_callbacks['_render_text_viewer'] = self._render_text_viewer @@ -346,6 +350,35 @@ class App: finally: self._is_applying_snapshot = False + def _capture_workspace_profile(self, name: str) -> models.WorkspaceProfile: + ini = imgui.save_ini_settings_to_memory() + panel_states = { + "ui_separate_message_panel": getattr(self, "ui_separate_message_panel", False), + "ui_separate_response_panel": getattr(self, "ui_separate_response_panel", False), + "ui_separate_tool_calls_panel": getattr(self, "ui_separate_tool_calls_panel", False), + "ui_separate_task_dag": getattr(self, "ui_separate_task_dag", False), + "ui_separate_usage_analytics": getattr(self, "ui_separate_usage_analytics", False), + "ui_separate_tier1": getattr(self, "ui_separate_tier1", False), + "ui_separate_tier2": getattr(self, "ui_separate_tier2", False), + "ui_separate_tier3": getattr(self, "ui_separate_tier3", False), + "ui_separate_tier4": getattr(self, "ui_separate_tier4", False), + "ui_separate_external_tools": getattr(self, "ui_separate_external_tools", False), + "ui_discussion_split_h": getattr(self, "ui_discussion_split_h", 300.0), + } + return models.WorkspaceProfile( + name=name, + ini_content=ini, + show_windows=copy.deepcopy(self.show_windows), + panel_states=panel_states + ) + + def _apply_workspace_profile(self, profile: models.WorkspaceProfile): + imgui.load_ini_settings_from_memory(profile.ini_content) + self.show_windows.update(profile.show_windows) + for k, v in profile.panel_states.items(): + if hasattr(self, k): + setattr(self, k, v) + def _handle_undo(self) -> None: sys.stderr.write(f"[DEBUG History] _handle_undo called. can_undo={self.history.can_undo}\n") sys.stderr.flush()