diff --git a/src/app_controller.py b/src/app_controller.py index 245ff4e0..a1bf4c46 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -951,6 +951,7 @@ class AppController: self.prior_mma_dashboard_state = {} self.diagnostic_log: List[Dict[str, Any]] = [] self.ui_active_tool_preset: str | None = None + self.ui_active_bias_profile: str | None = None self.available_models: List[str] = [] self.all_available_models: Dict[str, List[str]] = {} self.proposed_tracks: List[Dict[str, Any]] = [] @@ -2782,6 +2783,17 @@ class AppController: self.tool_preset_manager.project_root = Path(self.active_project_root) self.tool_presets = self.tool_preset_manager.load_all_presets() self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles() + self.personas = self.persona_manager.load_all() + if self.ui_active_persona and self.ui_active_persona not in self.personas: + self.ui_active_persona = "" + if self.ui_active_tool_preset and self.ui_active_tool_preset not in self.tool_presets: + self.ui_active_tool_preset = None + if self.ui_active_bias_profile and self.ui_active_bias_profile not in self.bias_profiles: + self.ui_active_bias_profile = None + if self.ui_project_preset_name and self.ui_project_preset_name not in self.presets: + self.ui_project_preset_name = None + ai_client.set_tool_preset(self.ui_active_tool_preset) + ai_client.set_bias_profile(self.ui_active_bias_profile) raw_presets = proj.get("view_presets", []) if isinstance(raw_presets, dict): self.view_presets = [models.NamedViewPreset.from_dict({"name": name, **data}) for name, data in raw_presets.items()] diff --git a/tests/test_project_switch_persona_preset.py b/tests/test_project_switch_persona_preset.py new file mode 100644 index 00000000..6a93e095 --- /dev/null +++ b/tests/test_project_switch_persona_preset.py @@ -0,0 +1,139 @@ +import os +import json +import pytest +import tempfile +import shutil +from pathlib import Path +from src.app_controller import AppController +from src import models +from src.personas import PersonaManager +from src import presets, tool_presets +from src import project_manager + + +def _setup_two_projects(tmp_path): + project_a_dir = tmp_path / "project_a" + project_a_dir.mkdir() + project_a_path = project_a_dir / "project.toml" + project_a_path.write_text('[project]\nname = "project_a"\nactive_preset = "PresetA"\n') + + project_b_dir = tmp_path / "project_b" + project_b_dir.mkdir() + project_b_path = project_b_dir / "project.toml" + project_b_path.write_text('[project]\nname = "project_b"\n') + + (project_a_dir / "project_personas.toml").write_text( + '[personas."PersonaA"]\nsystem_prompt = "Project A persona"\npreferred_models = []\n' + ) + (project_b_dir / "project_personas.toml").write_text( + '[personas."PersonaB"]\nsystem_prompt = "Project B persona"\npreferred_models = []\n' + ) + (project_a_dir / "project_presets.toml").write_text( + '[presets."PresetA"]\nsystem_prompt = "Project A preset"\n' + ) + (project_b_dir / "project_presets.toml").write_text( + '[presets."PresetB"]\nsystem_prompt = "Project B preset"\n' + ) + + return project_a_path, project_b_path + + +def test_switch_project_resets_invalid_persona(tmp_path, monkeypatch): + project_a_path, project_b_path = _setup_two_projects(tmp_path) + + ctrl = AppController() + monkeypatch.setattr(ctrl, "_rebuild_rag_index", lambda: None) + monkeypatch.setattr(ctrl, "_flush_to_project", lambda: None) + + ctrl.active_project_path = str(project_a_path) + ctrl.project = project_manager.load_project(str(project_a_path)) + ctrl.preset_manager = presets.PresetManager(Path(project_a_path).parent) + ctrl.tool_preset_manager = tool_presets.ToolPresetManager(Path(project_a_path).parent) + ctrl.persona_manager = PersonaManager(Path(project_a_path).parent) + ctrl._refresh_from_project() + + assert "PersonaA" in ctrl.personas + assert "PersonaB" not in ctrl.personas + ctrl.ui_active_persona = "PersonaA" + + ctrl._switch_project(str(project_b_path)) + + assert "PersonaA" not in ctrl.personas + assert "PersonaB" in ctrl.personas + assert ctrl.ui_active_persona == "" + + +def test_switch_project_resets_invalid_preset(tmp_path, monkeypatch): + project_a_path, project_b_path = _setup_two_projects(tmp_path) + + ctrl = AppController() + monkeypatch.setattr(ctrl, "_rebuild_rag_index", lambda: None) + monkeypatch.setattr(ctrl, "_flush_to_project", lambda: None) + + ctrl.active_project_path = str(project_a_path) + ctrl.project = project_manager.load_project(str(project_a_path)) + ctrl.preset_manager = presets.PresetManager(Path(project_a_path).parent) + ctrl.tool_preset_manager = tool_presets.ToolPresetManager(Path(project_a_path).parent) + ctrl.persona_manager = PersonaManager(Path(project_a_path).parent) + ctrl._refresh_from_project() + + assert ctrl.ui_project_preset_name == "PresetA" + + ctrl._switch_project(str(project_b_path)) + + assert "PresetA" not in ctrl.presets + assert "PresetB" in ctrl.presets + assert ctrl.ui_project_preset_name is None + + +def test_switch_project_resets_invalid_tool_preset(tmp_path, monkeypatch): + project_a_path, project_b_path = _setup_two_projects(tmp_path) + + (Path(project_a_path).parent / "project_tool_presets.toml").write_text( + '[presets."ToolA"]\ndescription = "A"\n[presets.tools."run_powershell"]\nweight = 1\n' + ) + (Path(project_b_path).parent / "project_tool_presets.toml").write_text( + '[presets."ToolB"]\ndescription = "B"\n[presets.tools."run_powershell"]\nweight = 1\n' + ) + + ctrl = AppController() + monkeypatch.setattr(ctrl, "_rebuild_rag_index", lambda: None) + monkeypatch.setattr(ctrl, "_flush_to_project", lambda: None) + + ctrl.active_project_path = str(project_a_path) + ctrl.project = project_manager.load_project(str(project_a_path)) + ctrl.preset_manager = presets.PresetManager(Path(project_a_path).parent) + ctrl.tool_preset_manager = tool_presets.ToolPresetManager(Path(project_a_path).parent) + ctrl.persona_manager = PersonaManager(Path(project_a_path).parent) + ctrl._refresh_from_project() + + ctrl.ui_active_tool_preset = "ToolA" + assert "ToolA" in ctrl.tool_presets + + ctrl._switch_project(str(project_b_path)) + + assert "ToolA" not in ctrl.tool_presets + assert "ToolB" in ctrl.tool_presets + assert ctrl.ui_active_tool_preset is None + + +def test_switch_project_preserves_global_preset(tmp_path, monkeypatch): + project_a_path, project_b_path = _setup_two_projects(tmp_path) + + ctrl = AppController() + monkeypatch.setattr(ctrl, "_rebuild_rag_index", lambda: None) + monkeypatch.setattr(ctrl, "_flush_to_project", lambda: None) + + ctrl.active_project_path = str(project_a_path) + ctrl.project = project_manager.load_project(str(project_a_path)) + ctrl.preset_manager = presets.PresetManager(Path(project_a_path).parent) + ctrl.tool_preset_manager = tool_presets.ToolPresetManager(Path(project_a_path).parent) + ctrl.persona_manager = PersonaManager(Path(project_a_path).parent) + ctrl._refresh_from_project() + + ctrl.ui_global_preset_name = "GlobalPresetA" + original_global = ctrl.ui_global_preset_name + + ctrl._switch_project(str(project_b_path)) + + assert ctrl.ui_global_preset_name == original_global