feat(bias): implement GUI integration for tool weights and bias profiles

This commit is contained in:
2026-03-10 10:24:02 -04:00
parent 6021f84b05
commit 1c83b3e519
2 changed files with 135 additions and 40 deletions

View File

@@ -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.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.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_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.tool_presets = self.tool_preset_manager.load_all_presets()
self.ui_active_tool_preset = os.environ.get('SLOP_TOOL_PRESET') 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_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_global_preset_name = ai_cfg.get("active_preset")
self.ui_project_preset_name = proj_meta.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.preset_manager.project_root = Path(self.ui_files_base_dir)
self.presets = self.preset_manager.load_all() self.presets = self.preset_manager.load_all()
self.tool_preset_manager.project_root = Path(self.ui_files_base_dir) 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: def _apply_preset(self, name: str, scope: str) -> None:
if name == "None": if name == "None":
@@ -1862,11 +1866,19 @@ class AppController:
def _cb_save_tool_preset(self, name, categories, scope): def _cb_save_tool_preset(self, name, categories, scope):
preset = models.ToolPreset(name=name, categories=categories) preset = models.ToolPreset(name=name, categories=categories)
self.tool_preset_manager.save_preset(preset, scope) 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): def _cb_delete_tool_preset(self, name, scope):
self.tool_preset_manager.delete_preset(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: def _cb_load_track(self, track_id: str) -> None:
state = project_manager.load_track_state(track_id, self.ui_files_base_dir) state = project_manager.load_track_state(track_id, self.ui_files_base_dir)

View File

@@ -99,10 +99,19 @@ class App:
self.show_preset_manager_modal = False self.show_preset_manager_modal = False
self.show_tool_preset_manager_modal = False self.show_tool_preset_manager_modal = False
self.ui_active_tool_preset = "" 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_name = ''
self._editing_tool_preset_categories = {} self._editing_tool_preset_categories = {}
self._editing_tool_preset_scope = 'project' self._editing_tool_preset_scope = 'project'
self._selected_tool_preset_idx = -1 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_name = ""
self._editing_preset_content = "" self._editing_preset_content = ""
self._editing_preset_temperature = 0.0 self._editing_preset_temperature = 0.0
@@ -1040,39 +1049,101 @@ class App:
imgui.dummy(imgui.ImVec2(0, 8)) imgui.dummy(imgui.ImVec2(0, 8))
imgui.text("Categories & Tools:") 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: try:
for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items(): for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items():
if imgui.tree_node(cat_name): if imgui.tree_node(cat_name):
if cat_name not in self._editing_tool_preset_categories: if cat_name not in self._editing_tool_preset_categories:
self._editing_tool_preset_categories[cat_name] = {} self._editing_tool_preset_categories[cat_name] = []
current_cat_tools = 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)
if imgui.radio_button(f"Disabled##{cat_name}_{tool_name}", mode == "disabled"): for tool_name in default_tools:
if tool_name in current_cat_tools: # Find existing Tool object in list
del current_cat_tools[tool_name] 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"Off##{cat_name}_{tool_name}", mode == "disabled"):
if tool: current_cat_tools.remove(tool)
imgui.same_line() imgui.same_line()
if imgui.radio_button(f"Auto##{cat_name}_{tool_name}", mode == "auto"): 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() imgui.same_line()
if imgui.radio_button(f"Ask##{cat_name}_{tool_name}", mode == "ask"): 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.same_line()
imgui.text(tool_name) 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() imgui.tree_pop()
finally: finally:
imgui.end_child() 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)) imgui.dummy(imgui.ImVec2(0, 8))
if imgui.button("Save", imgui.ImVec2(100, 0)): if imgui.button("Save", imgui.ImVec2(100, 0)):
if self._editing_tool_preset_name.strip(): 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.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.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) 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": if self.current_provider == "gemini_cli":
imgui.separator() imgui.separator()
imgui.text("Gemini CLI") imgui.text("Gemini CLI")
@@ -3210,27 +3291,29 @@ def hello():
preset = presets[active_name] preset = presets[active_name]
for cat_name, tools in preset.categories.items(): for cat_name, tools in preset.categories.items():
if imgui.tree_node(cat_name): if imgui.tree_node(cat_name):
for t_name, t_cfg in tools.items(): for tool in tools:
imgui.text(t_name) if tool.weight >= 5:
imgui.same_line(150) 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 imgui.text(tool.name)
if isinstance(t_cfg, dict): imgui.same_line(180)
mode = t_cfg.get("mode", "auto")
else:
mode = str(t_cfg)
if imgui.radio_button(f"Auto##{cat_name}_{t_name}", mode == "auto"): mode = tool.approval
if isinstance(t_cfg, dict): if imgui.radio_button(f"Auto##{cat_name}_{tool.name}", mode == "auto"):
t_cfg["mode"] = "auto" tool.approval = "auto"
else:
preset.categories[cat_name][t_name] = "auto"
imgui.same_line() imgui.same_line()
if imgui.radio_button(f"Ask##{cat_name}_{t_name}", mode == "ask"): if imgui.radio_button(f"Ask##{cat_name}_{tool.name}", mode == "ask"):
if isinstance(t_cfg, dict): tool.approval = "ask"
t_cfg["mode"] = "ask"
else:
preset.categories[cat_name][t_name] = "ask"
imgui.tree_pop() imgui.tree_pop()
def _render_theme_panel(self) -> None: def _render_theme_panel(self) -> None: