diff --git a/src/app_controller.py b/src/app_controller.py index e7aded7..1e46bd2 100644 --- a/src/app_controller.py +++ b/src/app_controller.py @@ -857,9 +857,12 @@ class AppController: self.preset_manager = presets.PresetManager(Path(self.active_project_path).parent if self.active_project_path else None) self.presets = self.preset_manager.load_all() self.tool_preset_manager = tool_presets.ToolPresetManager(Path(self.active_project_path).parent if self.active_project_path else None) - self.tool_presets = self.tool_preset_manager.load_all() - self.ui_active_tool_preset = os.environ.get('SLOP_TOOL_PRESET') + self.tool_presets = self.tool_preset_manager.load_all_presets() + self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles() + self.ui_active_tool_preset = os.environ.get('SLOP_TOOL_PRESET') or ai_cfg.get("active_tool_preset") + self.ui_active_bias_profile = ai_cfg.get("active_bias_profile") ai_client.set_tool_preset(self.ui_active_tool_preset) + ai_client.set_bias_profile(self.ui_active_bias_profile) self.ui_global_preset_name = ai_cfg.get("active_preset") self.ui_project_preset_name = proj_meta.get("active_preset") @@ -1819,7 +1822,8 @@ class AppController: self.preset_manager.project_root = Path(self.ui_files_base_dir) self.presets = self.preset_manager.load_all() self.tool_preset_manager.project_root = Path(self.ui_files_base_dir) - self.tool_presets = self.tool_preset_manager.load_all() + self.tool_presets = self.tool_preset_manager.load_all_presets() + self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles() def _apply_preset(self, name: str, scope: str) -> None: if name == "None": @@ -1862,11 +1866,19 @@ class AppController: def _cb_save_tool_preset(self, name, categories, scope): preset = models.ToolPreset(name=name, categories=categories) self.tool_preset_manager.save_preset(preset, scope) - self.tool_presets = self.tool_preset_manager.load_all() + self.tool_presets = self.tool_preset_manager.load_all_presets() def _cb_delete_tool_preset(self, name, scope): self.tool_preset_manager.delete_preset(name, scope) - self.tool_presets = self.tool_preset_manager.load_all() + self.tool_presets = self.tool_preset_manager.load_all_presets() + + def _cb_save_bias_profile(self, profile: models.BiasProfile, scope: str = "project"): + self.tool_preset_manager.save_bias_profile(profile, scope) + self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles() + + def _cb_delete_bias_profile(self, name: str, scope: str = "project"): + self.tool_preset_manager.delete_bias_profile(name, scope) + self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles() def _cb_load_track(self, track_id: str) -> None: state = project_manager.load_track_state(track_id, self.ui_files_base_dir) diff --git a/src/gui_2.py b/src/gui_2.py index d023be8..79a62f9 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -99,10 +99,19 @@ class App: self.show_preset_manager_modal = False self.show_tool_preset_manager_modal = False self.ui_active_tool_preset = "" + self.ui_active_bias_profile = "" + self._editing_bias_profile_name = "" + self._editing_bias_profile_tool_weights = "" # JSON + self._editing_bias_profile_cat_mults = "" # JSON + self._editing_bias_profile_scope = "project" self._editing_tool_preset_name = '' self._editing_tool_preset_categories = {} self._editing_tool_preset_scope = 'project' self._selected_tool_preset_idx = -1 + self._editing_bias_profile_name = "" + self._editing_bias_profile_tool_weights = "{}" + self._editing_bias_profile_category_multipliers = "{}" + self._selected_bias_profile_idx = -1 self._editing_preset_name = "" self._editing_preset_content = "" self._editing_preset_temperature = 0.0 @@ -1040,39 +1049,101 @@ class App: imgui.dummy(imgui.ImVec2(0, 8)) imgui.text("Categories & Tools:") - imgui.begin_child("tp_categories_scroll", imgui.ImVec2(0, -40), True) + imgui.begin_child("tp_categories_scroll", imgui.ImVec2(0, 300), True) try: for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items(): if imgui.tree_node(cat_name): if cat_name not in self._editing_tool_preset_categories: - self._editing_tool_preset_categories[cat_name] = {} - current_cat_tools = self._editing_tool_preset_categories[cat_name] + self._editing_tool_preset_categories[cat_name] = [] + current_cat_tools = self._editing_tool_preset_categories[cat_name] # List of Tool + for tool_name in default_tools: - # Determine current mode: disabled (not present), auto, or ask - if tool_name not in current_cat_tools: - mode = "disabled" - else: - config = current_cat_tools[tool_name] - if isinstance(config, dict): - mode = config.get("mode", "auto") - else: - mode = str(config) + # Find existing Tool object in list + tool = next((t for t in current_cat_tools if t.name == tool_name), None) + mode = "disabled" if tool is None else tool.approval - if imgui.radio_button(f"Disabled##{cat_name}_{tool_name}", mode == "disabled"): - if tool_name in current_cat_tools: - del current_cat_tools[tool_name] + if imgui.radio_button(f"Off##{cat_name}_{tool_name}", mode == "disabled"): + if tool: current_cat_tools.remove(tool) imgui.same_line() if imgui.radio_button(f"Auto##{cat_name}_{tool_name}", mode == "auto"): - current_cat_tools[tool_name] = "auto" + if not tool: + tool = models.Tool(name=tool_name, approval="auto") + current_cat_tools.append(tool) + else: + tool.approval = "auto" imgui.same_line() if imgui.radio_button(f"Ask##{cat_name}_{tool_name}", mode == "ask"): - current_cat_tools[tool_name] = "ask" + if not tool: + tool = models.Tool(name=tool_name, approval="ask") + current_cat_tools.append(tool) + else: + tool.approval = "ask" imgui.same_line() imgui.text(tool_name) + + if tool: + imgui.same_line(250) + imgui.set_next_item_width(100) + _, tool.weight = imgui.slider_int(f"Weight##{cat_name}_{tool_name}", tool.weight, 1, 5) + imgui.same_line() + pb_str = json.dumps(tool.parameter_bias) + imgui.set_next_item_width(150) + ch_pb, pb_new = imgui.input_text(f"Params##{cat_name}_{tool_name}", pb_str) + if ch_pb: + try: tool.parameter_bias = json.loads(pb_new) + except: pass imgui.tree_pop() finally: imgui.end_child() + imgui.separator() + imgui.text_colored(C_SUB, "Bias Profiles") + imgui.begin_child("bias_profiles_area", imgui.ImVec2(0, 200), True) + try: + avail_bias = imgui.get_content_region_avail() + imgui.begin_child("bias_list", imgui.ImVec2(200, avail_bias.y), False) + if imgui.button("New Profile", imgui.ImVec2(-1, 0)): + self._editing_bias_profile_name = "" + self._editing_bias_profile_tool_weights = "{}" + self._editing_bias_profile_category_multipliers = "{}" + self._selected_bias_profile_idx = -1 + imgui.separator() + bnames = sorted(self.bias_profiles.keys()) + for i, bname in enumerate(bnames): + is_sel = (self._selected_bias_profile_idx == i) + if imgui.selectable(bname, is_sel)[0]: + self._selected_bias_profile_idx = i + self._editing_bias_profile_name = bname + profile = self.bias_profiles[bname] + self._editing_bias_profile_tool_weights = json.dumps(profile.tool_weights, indent=1) + self._editing_bias_profile_category_multipliers = json.dumps(profile.category_multipliers, indent=1) + imgui.end_child() + imgui.same_line() + imgui.begin_child("bias_edit", imgui.ImVec2(0, avail_bias.y), False) + imgui.text("Name:") + _, self._editing_bias_profile_name = imgui.input_text("##b_name", self._editing_bias_profile_name) + imgui.text("Tool Weights (JSON):") + _, self._editing_bias_profile_tool_weights = imgui.input_text_multiline("##b_tw", self._editing_bias_profile_tool_weights, imgui.ImVec2(-1, 60)) + imgui.text("Category Multipliers (JSON):") + _, self._editing_bias_profile_category_multipliers = imgui.input_text_multiline("##b_cm", self._editing_bias_profile_category_multipliers, imgui.ImVec2(-1, 60)) + + if imgui.button("Save Profile"): + try: + tw = json.loads(self._editing_bias_profile_tool_weights) + cm = json.loads(self._editing_bias_profile_category_multipliers) + prof = models.BiasProfile(name=self._editing_bias_profile_name, tool_weights=tw, category_multipliers=cm) + self.controller._cb_save_bias_profile(prof, self._editing_tool_preset_scope) + self.ai_status = f"Bias profile '{prof.name}' saved" + except Exception as e: + self.ai_status = f"Error: {e}" + imgui.same_line() + if imgui.button("Delete Profile"): + self.controller._cb_delete_bias_profile(self._editing_bias_profile_name, self._editing_tool_preset_scope) + self.ai_status = f"Bias profile deleted" + imgui.end_child() + finally: + imgui.end_child() + imgui.dummy(imgui.ImVec2(0, 8)) if imgui.button("Save", imgui.ImVec2(100, 0)): if self._editing_tool_preset_name.strip(): @@ -1897,6 +1968,16 @@ def hello(): ch, self.temperature = imgui.slider_float("Temperature", self.temperature, 0.0, 2.0, "%.2f") ch, self.max_tokens = imgui.input_int("Max Tokens (Output)", self.max_tokens, 1024) ch, self.history_trunc_limit = imgui.input_int("History Truncation Limit", self.history_trunc_limit, 1024) + imgui.text("Bias Profile") + if imgui.begin_combo("##bias", self.ui_active_bias_profile or "None"): + if imgui.selectable("None", not self.ui_active_bias_profile)[0]: + self.ui_active_bias_profile = "" + ai_client.set_bias_profile(None) + for bname in sorted(self.bias_profiles.keys()): + if imgui.selectable(bname, bname == self.ui_active_bias_profile)[0]: + self.ui_active_bias_profile = bname + ai_client.set_bias_profile(bname) + imgui.end_combo() if self.current_provider == "gemini_cli": imgui.separator() imgui.text("Gemini CLI") @@ -3210,27 +3291,29 @@ def hello(): preset = presets[active_name] for cat_name, tools in preset.categories.items(): if imgui.tree_node(cat_name): - for t_name, t_cfg in tools.items(): - imgui.text(t_name) - imgui.same_line(150) + for tool in tools: + if tool.weight >= 5: + imgui.text_colored(vec4(255, 100, 100), "[HIGH]") + imgui.same_line() + elif tool.weight == 4: + imgui.text_colored(vec4(255, 255, 100), "[PREF]") + imgui.same_line() + elif tool.weight == 2: + imgui.text_colored(vec4(255, 150, 50), "[REJECT]") + imgui.same_line() + elif tool.weight <= 1: + imgui.text_colored(vec4(180, 180, 180), "[LOW]") + imgui.same_line() - # Determine current mode - if isinstance(t_cfg, dict): - mode = t_cfg.get("mode", "auto") - else: - mode = str(t_cfg) + imgui.text(tool.name) + imgui.same_line(180) - if imgui.radio_button(f"Auto##{cat_name}_{t_name}", mode == "auto"): - if isinstance(t_cfg, dict): - t_cfg["mode"] = "auto" - else: - preset.categories[cat_name][t_name] = "auto" + mode = tool.approval + if imgui.radio_button(f"Auto##{cat_name}_{tool.name}", mode == "auto"): + tool.approval = "auto" imgui.same_line() - if imgui.radio_button(f"Ask##{cat_name}_{t_name}", mode == "ask"): - if isinstance(t_cfg, dict): - t_cfg["mode"] = "ask" - else: - preset.categories[cat_name][t_name] = "ask" + if imgui.radio_button(f"Ask##{cat_name}_{tool.name}", mode == "ask"): + tool.approval = "ask" imgui.tree_pop() def _render_theme_panel(self) -> None: