more refinement

This commit is contained in:
2026-03-11 21:11:13 -04:00
parent b5007ce96f
commit 44fd370167

View File

@@ -1081,226 +1081,155 @@ class App:
def _render_tool_preset_manager_content(self, is_embedded: bool = False) -> None: def _render_tool_preset_manager_content(self, is_embedded: bool = False) -> None:
avail = imgui.get_content_region_avail() avail = imgui.get_content_region_avail()
if not hasattr(self, "_tool_split_v"): self._tool_split_v = 0.4
try: try:
if imgui.begin_table("tp_main_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v): if imgui.begin_table("tp_main_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v):
imgui.table_setup_column("List", imgui.TableColumnFlags_.width_fixed, 200) imgui.table_setup_column("List", imgui.TableColumnFlags_.width_fixed, 200)
imgui.table_setup_column("Editor", imgui.TableColumnFlags_.width_stretch) imgui.table_setup_column("Editor", imgui.TableColumnFlags_.width_stretch)
imgui.table_next_row() imgui.table_next_row()
# Left Column: Presets # Left Sidebar
imgui.table_next_column() imgui.table_next_column()
imgui.begin_child("tp_list_pane", imgui.ImVec2(0, 0), False) imgui.begin_child("tp_list_pane", imgui.ImVec2(0, 0), False)
if imgui.button("New Tool Preset", imgui.ImVec2(-1, 0)): if imgui.button("New Tool Preset", imgui.ImVec2(-1, 0)):
self._editing_tool_preset_name = "" self._editing_tool_preset_name = ""; self._editing_tool_preset_categories = {cat: {} for cat in models.DEFAULT_TOOL_CATEGORIES}
self._editing_tool_preset_categories = {cat: {} for cat in models.DEFAULT_TOOL_CATEGORIES} self._editing_tool_preset_scope = "project"; self._selected_tool_preset_idx = -1
self._editing_tool_preset_scope = "project"
self._selected_tool_preset_idx = -1
imgui.separator() imgui.separator()
preset_names = sorted(self.controller.tool_presets.keys()) preset_names = sorted(self.controller.tool_presets.keys())
for i, name in enumerate(preset_names): for i, name in enumerate(preset_names):
if imgui.selectable(name, self._selected_tool_preset_idx == i)[0]: if imgui.selectable(name, self._selected_tool_preset_idx == i)[0]:
self._selected_tool_preset_idx = i self._selected_tool_preset_idx = i; self._editing_tool_preset_name = name
self._editing_tool_preset_name = name
preset = self.controller.tool_presets[name] preset = self.controller.tool_presets[name]
self._editing_tool_preset_categories = {cat: copy.deepcopy(tools) for cat, tools in preset.categories.items()} self._editing_tool_preset_categories = {cat: copy.deepcopy(tools) for cat, tools in preset.categories.items()}
imgui.end_child() imgui.end_child()
# Right Column: Editor # Right Editor
imgui.table_next_column() imgui.table_next_column()
avail_r = imgui.get_content_region_avail() avail_r = imgui.get_content_region_avail()
# Subtract space for bottom buttons (approx 45px) # Leave space for bottom buttons
imgui.begin_child("tp_edit_pane", imgui.ImVec2(0, avail_r.y - 45), False) imgui.begin_child("tp_editor_content", imgui.ImVec2(0, avail_r.y - 45), False)
p_name = self._editing_tool_preset_name or "(New Tool Preset)" p_name = self._editing_tool_preset_name or "(New Tool Preset)"
imgui.text_colored(C_IN, f"Editing Tool Preset: {p_name}") imgui.text_colored(C_IN, f"Editing Tool Preset: {p_name}"); imgui.separator()
imgui.separator()
if imgui.begin_table("tp_meta", 2): if imgui.begin_table("tp_meta", 2):
imgui.table_setup_column("L", imgui.TableColumnFlags_.width_fixed, 80) imgui.table_setup_column("L", imgui.TableColumnFlags_.width_fixed, 80); imgui.table_setup_column("F", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("F", imgui.TableColumnFlags_.width_stretch) imgui.table_next_row(); imgui.table_next_column(); imgui.text("Name:"); imgui.table_next_column(); imgui.set_next_item_width(-1); _, self._editing_tool_preset_name = imgui.input_text("##etpn", self._editing_tool_preset_name)
imgui.table_next_row() imgui.table_next_row(); imgui.table_next_column(); imgui.text("Scope:"); imgui.table_next_column()
imgui.table_next_column() if imgui.radio_button("Global", self._editing_tool_preset_scope == "global"): self._editing_tool_preset_scope = "global"
imgui.text("Name:") imgui.same_line();
imgui.table_next_column() if imgui.radio_button("Project", self._editing_tool_preset_scope == "project"): self._editing_tool_preset_scope = "project"
imgui.set_next_item_width(-1)
_, self._editing_tool_preset_name = imgui.input_text("##etpn", self._editing_tool_preset_name)
imgui.table_next_row()
imgui.table_next_column()
imgui.text("Scope:")
imgui.table_next_column()
if imgui.radio_button("Global", self._editing_tool_preset_scope == "global"):
self._editing_tool_preset_scope = "global"
imgui.same_line()
if imgui.radio_button("Project", self._editing_tool_preset_scope == "project"):
self._editing_tool_preset_scope = "project"
imgui.end_table() imgui.end_table()
# Vertical split calculation
total_h = imgui.get_content_region_avail().y - 100
h1 = total_h * self._tool_split_v
h2 = total_h - h1 - 8
imgui.dummy(imgui.ImVec2(0, 4)) imgui.dummy(imgui.ImVec2(0, 4))
imgui.text("Categories & Tools:") if imgui.collapsing_header("Categories & Tools", imgui.TreeNodeFlags_.default_open):
imgui.same_line() imgui.text("Filter:"); imgui.same_line()
cat_opts = ["All"] + sorted(list(models.DEFAULT_TOOL_CATEGORIES.keys())) cat_opts = ["All"] + sorted(list(models.DEFAULT_TOOL_CATEGORIES.keys()))
f_idx = cat_opts.index(self.ui_tool_filter_category) if self.ui_tool_filter_category in cat_opts else 0 f_idx = cat_opts.index(self.ui_tool_filter_category) if self.ui_tool_filter_category in cat_opts else 0
imgui.set_next_item_width(200) imgui.set_next_item_width(200); ch_cat, next_f_idx = imgui.combo("##tp_filter", f_idx, cat_opts)
ch_cat, next_f_idx = imgui.combo("##tp_filter", f_idx, cat_opts) if ch_cat: self.ui_tool_filter_category = cat_opts[next_f_idx]
if ch_cat:
self.ui_tool_filter_category = cat_opts[next_f_idx]
# Use ~35% of height for tool list imgui.begin_child("tp_scroll", imgui.ImVec2(0, h1), True)
tool_list_h = (avail_r.y - 45) * 0.35
imgui.begin_child("tp_scroll", imgui.ImVec2(0, tool_list_h), True)
for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items(): for cat_name, default_tools in models.DEFAULT_TOOL_CATEGORIES.items():
if self.ui_tool_filter_category != "All" and self.ui_tool_filter_category != cat_name: if self.ui_tool_filter_category != "All" and self.ui_tool_filter_category != cat_name: continue
continue
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] = []
curr_cat_tools = self._editing_tool_preset_categories[cat_name] curr_cat_tools = self._editing_tool_preset_categories[cat_name]
if imgui.begin_table(f"tt_{cat_name}", 2, imgui.TableFlags_.borders_inner_v): if imgui.begin_table(f"tt_{cat_name}", 2, imgui.TableFlags_.borders_inner_v):
imgui.table_setup_column("Tool", imgui.TableColumnFlags_.width_fixed, 180) imgui.table_setup_column("Tool", imgui.TableColumnFlags_.width_fixed, 180); imgui.table_setup_column("Ctrls", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Ctrls", imgui.TableColumnFlags_.width_stretch)
for tool_name in default_tools: for tool_name in default_tools:
tool = next((t for t in curr_cat_tools if t.name == tool_name), None) tool = next((t for t in curr_cat_tools if t.name == tool_name), None)
mode = "disabled" if tool is None else tool.approval mode = "disabled" if tool is None else tool.approval
imgui.table_next_row() imgui.table_next_row(); imgui.table_next_column(); imgui.text(tool_name); imgui.table_next_column()
imgui.table_next_column()
imgui.text(tool_name)
imgui.table_next_column()
if imgui.radio_button(f"Off##{cat_name}_{tool_name}", mode == "disabled"): if imgui.radio_button(f"Off##{cat_name}_{tool_name}", mode == "disabled"):
if tool: curr_cat_tools.remove(tool) if tool: curr_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"):
if not tool: if not tool: tool = models.Tool(name=tool_name, approval="auto"); current_cat_tools.append(tool)
tool = models.Tool(name=tool_name, approval="auto") else: tool.approval = "auto"
current_cat_tools.append(tool) imgui.same_line();
else:
tool.approval = "auto"
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"):
if not tool: if not tool: tool = models.Tool(name=tool_name, approval="ask"); current_cat_tools.append(tool)
tool = models.Tool(name=tool_name, approval="ask") else: tool.approval = "ask"
current_cat_tools.append(tool)
else:
tool.approval = "ask"
if tool: if tool:
imgui.same_line() imgui.same_line(); imgui.text(" W:"); imgui.same_line(); imgui.set_next_item_width(60); _, tool.weight = imgui.slider_int(f"##w_{cat_name}_{tool_name}", tool.weight, 1, 5)
imgui.text(" W:") imgui.same_line(); imgui.text(" P:"); imgui.same_line(); imgui.set_next_item_width(-1); pb_str = json.dumps(tool.parameter_bias)
imgui.same_line()
imgui.set_next_item_width(60)
_, tool.weight = imgui.slider_int(f"##w_{cat_name}_{tool_name}", tool.weight, 1, 5)
imgui.same_line()
imgui.text(" P:")
imgui.same_line()
imgui.set_next_item_width(-1)
pb_str = json.dumps(tool.parameter_bias)
ch_pb, pb_new = imgui.input_text(f"##pb_{cat_name}_{tool_name}", pb_str) ch_pb, pb_new = imgui.input_text(f"##pb_{cat_name}_{tool_name}", pb_str)
if ch_pb: if ch_pb:
try: tool.parameter_bias = json.loads(pb_new) try: tool.parameter_bias = json.loads(pb_new)
except: pass except: pass
imgui.end_table() imgui.end_table(); imgui.tree_pop()
imgui.tree_pop()
imgui.end_child() imgui.end_child()
# Vertical Splitter
imgui.button("###tool_splitter", imgui.ImVec2(-1, 4))
if imgui.is_item_active():
self._tool_split_v = max(0.1, min(0.9, self._tool_split_v + imgui.get_io().mouse_delta.y / total_h))
imgui.dummy(imgui.ImVec2(0, 4)) imgui.dummy(imgui.ImVec2(0, 4))
imgui.separator() if imgui.collapsing_header("Bias Profiles", imgui.TreeNodeFlags_.default_open):
imgui.text_colored(C_SUB, "Bias Profiles") imgui.begin_child("bias_area", imgui.ImVec2(0, h2), True)
# Bias fills remaining space
imgui.begin_child("bias_area", imgui.ImVec2(0, imgui.get_content_region_avail().y), True)
if imgui.begin_table("bias_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v): if imgui.begin_table("bias_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v):
imgui.table_setup_column("BList", imgui.TableColumnFlags_.width_fixed, 150) imgui.table_setup_column("BList", imgui.TableColumnFlags_.width_fixed, 150); imgui.table_setup_column("BEdit", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("BEdit", imgui.TableColumnFlags_.width_stretch) imgui.table_next_row(); imgui.table_next_column()
imgui.table_next_row()
imgui.table_next_column()
imgui.begin_child("blist_pane", imgui.ImVec2(0, 0), False) imgui.begin_child("blist_pane", imgui.ImVec2(0, 0), False)
if imgui.button("New Profile", imgui.ImVec2(-1, 0)): if imgui.button("New Profile", imgui.ImVec2(-1, 0)):
self._editing_bias_profile_name = "" self._editing_bias_profile_name = ""; self._editing_bias_profile_tool_weights = {}
self._editing_bias_profile_tool_weights = {} self._editing_bias_profile_category_multipliers = {}; self._selected_bias_profile_idx = -1
self._editing_bias_profile_category_multipliers = {} imgui.separator(); bnames = sorted(self.bias_profiles.keys())
self._selected_bias_profile_idx = -1
imgui.separator()
bnames = sorted(self.bias_profiles.keys())
for i, bname in enumerate(bnames): for i, bname in enumerate(bnames):
if imgui.selectable(bname, self._selected_bias_profile_idx == i)[0]: if imgui.selectable(bname, self._selected_bias_profile_idx == i)[0]:
self._selected_bias_profile_idx = i self._selected_bias_profile_idx = i; self._editing_bias_profile_name = bname; prof = self.bias_profiles[bname]
self._editing_bias_profile_name = bname self._editing_bias_profile_tool_weights = copy.deepcopy(prof.tool_weights); self._editing_bias_profile_category_multipliers = copy.deepcopy(prof.category_multipliers)
prof = self.bias_profiles[bname]
self._editing_bias_profile_tool_weights = copy.deepcopy(prof.tool_weights)
self._editing_bias_profile_category_multipliers = copy.deepcopy(prof.category_multipliers)
imgui.end_child() imgui.end_child()
imgui.table_next_column() imgui.table_next_column()
imgui.begin_child("bedit_pane", imgui.ImVec2(0, 0), False) imgui.begin_child("bedit_pane", imgui.ImVec2(0, 0), False)
imgui.text("Name:") imgui.text("Name:"); imgui.same_line(); imgui.set_next_item_width(-1); _, self._editing_bias_profile_name = imgui.input_text("##bname", self._editing_bias_profile_name)
imgui.same_line() imgui.text_colored(C_KEY, "Tool Weighting:"); imgui.begin_child("btool_scroll", imgui.ImVec2(0, -120), True)
imgui.set_next_item_width(-1)
_, self._editing_bias_profile_name = imgui.input_text("##bname", self._editing_bias_profile_name)
imgui.text_colored(C_KEY, "Tool Weighting:")
imgui.begin_child("btool_scroll", imgui.ImVec2(0, 150), True)
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(f"{cat_name}##b"): if imgui.tree_node(f"{cat_name}##b"):
if imgui.begin_table(f"bt_{cat_name}", 2): if imgui.begin_table(f"bt_{cat_name}", 2):
imgui.table_setup_column("T", imgui.TableColumnFlags_.width_fixed, 150) imgui.table_setup_column("T", imgui.TableColumnFlags_.width_fixed, 220); imgui.table_setup_column("W", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("W", imgui.TableColumnFlags_.width_stretch)
for tn in default_tools: for tn in default_tools:
imgui.table_next_row() imgui.table_next_row(); imgui.table_next_column(); imgui.text(tn); imgui.table_next_column()
imgui.table_next_column() curr_w = self._editing_bias_profile_tool_weights.get(tn, 3); imgui.set_next_item_width(-1)
imgui.text(tn) ch_w, n_w = imgui.slider_int(f"##bw_{tn}", curr_w, 1, 10);
imgui.table_next_column() if ch_w: self._editing_bias_profile_tool_weights[tn] = n_w
curr_w = self._editing_bias_profile_tool_weights.get(tn, 3) imgui.end_table(); imgui.tree_pop()
imgui.set_next_item_width(-1)
ch_w, n_w = imgui.slider_int(f"##bw_{tn}", curr_w, 1, 10)
if ch_w:
self._editing_bias_profile_tool_weights[tn] = n_w
imgui.end_table()
imgui.tree_pop()
imgui.end_child() imgui.end_child()
imgui.text_colored(C_KEY, "Category Multipliers:"); imgui.begin_child("bcat_scroll", imgui.ImVec2(0, 80), True)
imgui.text_colored(C_KEY, "Category Multipliers:")
imgui.begin_child("bcat_scroll", imgui.ImVec2(0, 100), True)
if imgui.begin_table("bcats", 2): if imgui.begin_table("bcats", 2):
imgui.table_setup_column("C", imgui.TableColumnFlags_.width_fixed, 150) imgui.table_setup_column("C", imgui.TableColumnFlags_.width_fixed, 220); imgui.table_setup_column("M", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("M", imgui.TableColumnFlags_.width_stretch)
for cn in sorted(models.DEFAULT_TOOL_CATEGORIES.keys()): for cn in sorted(models.DEFAULT_TOOL_CATEGORIES.keys()):
imgui.table_next_row() imgui.table_next_row(); imgui.table_next_column(); imgui.text(cn); imgui.table_next_column()
imgui.table_next_column() curr_m = self._editing_bias_profile_category_multipliers.get(cn, 1.0); imgui.set_next_item_width(-1)
imgui.text(cn) ch_m, n_m = imgui.slider_float(f"##cm_{cn}", curr_m, 0.1, 5.0, "%.1fx");
imgui.table_next_column() if ch_m: self._editing_bias_profile_category_multipliers[cn] = n_m
curr_m = self._editing_bias_profile_category_multipliers.get(cn, 1.0) imgui.end_table(); imgui.end_child()
imgui.set_next_item_width(-1)
ch_m, n_m = imgui.slider_float(f"##cm_{cn}", curr_m, 0.1, 5.0, "%.1fx")
if ch_m:
self._editing_bias_profile_category_multipliers[cn] = n_m
imgui.end_table()
imgui.end_child()
if imgui.button("Save Profile", imgui.ImVec2(-1, 0)): if imgui.button("Save Profile", imgui.ImVec2(-1, 0)):
try: try:
p = models.BiasProfile(name=self._editing_bias_profile_name, tool_weights=self._editing_bias_profile_tool_weights, category_multipliers=self._editing_bias_profile_category_multipliers) p = models.BiasProfile(name=self._editing_bias_profile_name, tool_weights=self._editing_bias_profile_tool_weights, category_multipliers=self._editing_bias_profile_category_multipliers)
self.controller._cb_save_bias_profile(p, self._editing_tool_preset_scope) self.controller._cb_save_bias_profile(p, self._editing_tool_preset_scope); self.ai_status = f"Saved: {p.name}"
self.ai_status = f"Saved: {p.name}" except Exception as e: self.ai_status = f"Error: {e}"
except Exception as e: imgui.end_child(); imgui.end_table()
self.ai_status = f"Error: {e}"
imgui.end_child()
imgui.end_table()
imgui.end_child() imgui.end_child()
imgui.end_child() imgui.end_child()
# Bottom Buttons # Fixed Bottom Buttons
imgui.separator() imgui.separator()
imgui.dummy(imgui.ImVec2(0, 4))
if imgui.button("Save Tool Preset", imgui.ImVec2(150, 0)): if imgui.button("Save Tool Preset", imgui.ImVec2(150, 0)):
if self._editing_tool_preset_name.strip(): if self._editing_tool_preset_name.strip(): self.controller._cb_save_tool_preset(self._editing_tool_preset_name.strip(), self._editing_tool_preset_categories, self._editing_tool_preset_scope); self.ai_status = f"Saved Preset: {self._editing_tool_preset_name}"
self.controller._cb_save_tool_preset(self._editing_tool_preset_name.strip(), self._editing_tool_preset_categories, self._editing_tool_preset_scope) imgui.same_line();
self.ai_status = f"Saved Preset: {self._editing_tool_preset_name}"
imgui.same_line()
if imgui.button("Delete Preset", imgui.ImVec2(120, 0)): if imgui.button("Delete Preset", imgui.ImVec2(120, 0)):
if self._editing_tool_preset_name: if self._editing_tool_preset_name: self.controller._cb_delete_tool_preset(self._editing_tool_preset_name, self._editing_tool_preset_scope); self._editing_tool_preset_name = ""; self._selected_tool_preset_idx = -1
self.controller._cb_delete_tool_preset(self._editing_tool_preset_name, self._editing_tool_preset_scope) imgui.same_line();
self._editing_tool_preset_name = ""
self._selected_tool_preset_idx = -1
imgui.same_line()
if not is_embedded: if not is_embedded:
if imgui.button("Close", imgui.ImVec2(100, 0)): if imgui.button("Close", imgui.ImVec2(100, 0)): self.show_tool_preset_manager_window = False
self.show_tool_preset_manager_window = False
imgui.end_table() imgui.end_table()
finally: finally:
pass pass
@@ -1322,247 +1251,151 @@ class App:
imgui.end() imgui.end()
def _render_persona_editor_window(self, is_embedded: bool = False) -> None: def _render_persona_editor_window(self, is_embedded: bool = False) -> None:
if not self.show_persona_editor_window and not is_embedded: return if not self.show_persona_editor_window and not is_embedded: return
if not is_embedded: if not is_embedded:
imgui.set_next_window_size(imgui.ImVec2(1000, 800), imgui.Cond_.first_use_ever) imgui.set_next_window_size(imgui.ImVec2(1000, 800), imgui.Cond_.first_use_ever)
opened, self.show_persona_editor_window = imgui.begin("Persona Editor", self.show_persona_editor_window) opened, self.show_persona_editor_window = imgui.begin("Persona Editor", self.show_persona_editor_window)
if not opened: if not opened:
imgui.end() imgui.end(); return
return
if not hasattr(self, "_persona_split_v"): self._persona_split_v = 0.4
try: try:
if imgui.begin_table("persona_main_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v): if imgui.begin_table("persona_main_split", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders_inner_v):
imgui.table_setup_column("List", imgui.TableColumnFlags_.width_fixed, 200) imgui.table_setup_column("List", imgui.TableColumnFlags_.width_fixed, 200)
imgui.table_setup_column("Editor", imgui.TableColumnFlags_.width_stretch) imgui.table_setup_column("Editor", imgui.TableColumnFlags_.width_stretch)
imgui.table_next_row() imgui.table_next_row()
# --- Left Pane: List --- # Left Sidebar
imgui.table_next_column() imgui.table_next_column()
imgui.begin_child("persona_list_pane", imgui.ImVec2(0, 0), False) imgui.begin_child("persona_list_pane", imgui.ImVec2(0, 0), False)
if imgui.button("New Persona", imgui.ImVec2(-1, 0)): if imgui.button("New Persona", imgui.ImVec2(-1, 0)):
self._editing_persona_name = "" self._editing_persona_name = ""; self._editing_persona_system_prompt = ""
self._editing_persona_system_prompt = "" self._editing_persona_tool_preset_id = ""; self._editing_persona_bias_profile_id = ""
self._editing_persona_tool_preset_id = ""
self._editing_persona_bias_profile_id = ""
self._editing_persona_preferred_models_list = [{"provider": self.current_provider, "model": self.current_model, "temperature": 0.7, "max_output_tokens": 4096, "history_trunc_limit": 900000}] self._editing_persona_preferred_models_list = [{"provider": self.current_provider, "model": self.current_model, "temperature": 0.7, "max_output_tokens": 4096, "history_trunc_limit": 900000}]
self._editing_persona_scope = "project" self._editing_persona_scope = "project"; self._editing_persona_is_new = True
self._editing_persona_is_new = True
imgui.separator() imgui.separator()
personas = getattr(self.controller, 'personas', {}) personas = getattr(self.controller, 'personas', {})
for name in sorted(personas.keys()): for name in sorted(personas.keys()):
is_sel = (name == self._editing_persona_name and not getattr(self, '_editing_persona_is_new', False)) if imgui.selectable(name, name == self._editing_persona_name and not getattr(self, '_editing_persona_is_new', False))[0]:
if imgui.selectable(name, is_sel)[0]: p = personas[name]; self._editing_persona_name = p.name; self._editing_persona_system_prompt = p.system_prompt or ""
p = personas[name] self._editing_persona_tool_preset_id = p.tool_preset or ""; self._editing_persona_bias_profile_id = p.bias_profile or ""
self._editing_persona_name = p.name import copy; self._editing_persona_preferred_models_list = copy.deepcopy(p.preferred_models) if p.preferred_models else []
self._editing_persona_system_prompt = p.system_prompt or "" self._editing_persona_scope = self.controller.persona_manager.get_persona_scope(p.name); self._editing_persona_is_new = False
self._editing_persona_tool_preset_id = p.tool_preset or ""
self._editing_persona_bias_profile_id = p.bias_profile or ""
import copy
self._editing_persona_preferred_models_list = copy.deepcopy(p.preferred_models) if p.preferred_models else []
self._editing_persona_scope = self.controller.persona_manager.get_persona_scope(p.name)
self._editing_persona_is_new = False
imgui.end_child() imgui.end_child()
# --- Right Pane: Editor --- # Right Editor
imgui.table_next_column() imgui.table_next_column()
avail = imgui.get_content_region_avail() avail = imgui.get_content_region_avail()
# Subtract space for bottom buttons (approx 45px) # Ensure buttons stay at the absolute bottom
imgui.begin_child("persona_editor_pane", imgui.ImVec2(0, avail.y - 45), False) imgui.begin_child("persona_editor_content", imgui.ImVec2(0, avail.y - 40), False)
header = "New Persona" if getattr(self, '_editing_persona_is_new', True) else f"Editing Persona: {self._editing_persona_name}" header = "New Persona" if getattr(self, '_editing_persona_is_new', True) else f"Editing Persona: {self._editing_persona_name}"
imgui.text_colored(C_IN, header) imgui.text_colored(C_IN, header); imgui.separator()
imgui.separator()
# Metadata
if imgui.begin_table("p_meta", 2): if imgui.begin_table("p_meta", 2):
imgui.table_setup_column("L", imgui.TableColumnFlags_.width_fixed, 60) imgui.table_setup_column("L", imgui.TableColumnFlags_.width_fixed, 60); imgui.table_setup_column("F", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("F", imgui.TableColumnFlags_.width_stretch) imgui.table_next_row(); imgui.table_next_column(); imgui.text("Name:"); imgui.table_next_column(); imgui.set_next_item_width(-1); _, self._editing_persona_name = imgui.input_text("##pname", self._editing_persona_name, 128)
imgui.table_next_row() imgui.table_next_row(); imgui.table_next_column(); imgui.text("Scope:"); imgui.table_next_column()
imgui.table_next_column() if imgui.radio_button("Global##pscope", getattr(self, '_editing_persona_scope', 'project') == "global"): self._editing_persona_scope = "global"
imgui.text("Name:") imgui.same_line();
imgui.table_next_column() if imgui.radio_button("Project##pscope", getattr(self, '_editing_persona_scope', 'project') == "project"): self._editing_persona_scope = "project"
imgui.set_next_item_width(-1)
_, self._editing_persona_name = imgui.input_text("##pname", self._editing_persona_name, 128)
imgui.table_next_row()
imgui.table_next_column()
imgui.text("Scope:")
imgui.table_next_column()
if imgui.radio_button("Global##pscope", getattr(self, '_editing_persona_scope', 'project') == "global"):
self._editing_persona_scope = "global"
imgui.same_line()
if imgui.radio_button("Project##pscope", getattr(self, '_editing_persona_scope', 'project') == "project"):
self._editing_persona_scope = "project"
imgui.end_table() imgui.end_table()
imgui.dummy(imgui.ImVec2(0, 4)) # Resizable Vertical Split for Models vs Prompt
imgui.separator() total_h = imgui.get_content_region_avail().y - 120 # Account for headers and middle tools
imgui.text("Preferred Models:") h1 = total_h * self._persona_split_v
# Use ~35% of remaining height for models h2 = total_h - h1 - 8 # Splitter height
models_h = (avail.y - 45) * 0.35
imgui.begin_child("pref_models_scroll", imgui.ImVec2(0, models_h), True) imgui.dummy(imgui.ImVec2(0, 4)); imgui.separator(); imgui.text("Preferred Models:")
imgui.begin_child("pref_models_scroll", imgui.ImVec2(0, h1), True)
to_remove = [] to_remove = []
providers = self.controller.PROVIDERS providers = self.controller.PROVIDERS
if not hasattr(self, '_persona_pref_models_expanded'): self._persona_pref_models_expanded = {} if not hasattr(self, '_persona_pref_models_expanded'): self._persona_pref_models_expanded = {}
for i, entry in enumerate(self._editing_persona_preferred_models_list): for i, entry in enumerate(self._editing_persona_preferred_models_list):
imgui.push_id(f"pref_model_{i}") imgui.push_id(f"pref_model_{i}")
prov = entry.get("provider", "Unknown") prov, mod, is_expanded = entry.get("provider", "Unknown"), entry.get("model", "Unknown"), self._persona_pref_models_expanded.get(i, False)
mod = entry.get("model", "Unknown") if imgui.button("-" if is_expanded else "+"): self._persona_pref_models_expanded[i] = not is_expanded
is_expanded = self._persona_pref_models_expanded.get(i, False) imgui.same_line(); imgui.text(f"{i+1}."); imgui.same_line(); imgui.text_colored(C_LBL, f"{prov}"); imgui.same_line(); imgui.text("-"); imgui.same_line(); imgui.text_colored(C_IN, f"{mod}")
if imgui.button("-" if is_expanded else "+"):
self._persona_pref_models_expanded[i] = not is_expanded
imgui.same_line()
imgui.text(f"{i+1}.")
imgui.same_line()
imgui.text_colored(C_LBL, f"{prov}")
imgui.same_line()
imgui.text("-")
imgui.same_line()
imgui.text_colored(C_IN, f"{mod}")
if not is_expanded: if not is_expanded:
imgui.same_line() imgui.same_line(); summary = f" (T:{entry.get('temperature', 0.7):.1f}, M:{entry.get('max_output_tokens', 0)}, H:{entry.get('history_trunc_limit', 0)})"
summary = f" (T:{entry.get('temperature', 0.7):.1f}, M:{entry.get('max_output_tokens', 0)}, H:{entry.get('history_trunc_limit', 0)})"
imgui.text_colored(C_SUB, summary) imgui.text_colored(C_SUB, summary)
imgui.same_line(imgui.get_content_region_avail().x - 30);
imgui.same_line(imgui.get_content_region_avail().x - 30) if imgui.button("x"): to_remove.append(i)
if imgui.button("x"):
to_remove.append(i)
if is_expanded: if is_expanded:
imgui.indent(20) imgui.indent(20)
if imgui.begin_table("model_settings", 2, imgui.TableFlags_.borders_inner_v): if imgui.begin_table("model_settings", 2, imgui.TableFlags_.borders_inner_v):
imgui.table_setup_column("Label", imgui.TableColumnFlags_.width_fixed, 120) imgui.table_setup_column("Label", imgui.TableColumnFlags_.width_fixed, 120); imgui.table_setup_column("Control", imgui.TableColumnFlags_.width_stretch)
imgui.table_setup_column("Control", imgui.TableColumnFlags_.width_stretch) imgui.table_next_row(); imgui.table_next_column(); imgui.text("Provider:"); imgui.table_next_column(); imgui.set_next_item_width(-1)
p_idx = providers.index(prov) + 1 if prov in providers else 0; ch_p, p_idx = imgui.combo("##prov", p_idx, ["None"] + providers)
imgui.table_next_row()
imgui.table_next_column()
imgui.text("Provider:")
imgui.table_next_column()
imgui.set_next_item_width(-1)
p_idx = providers.index(prov) + 1 if prov in providers else 0
ch_p, p_idx = imgui.combo("##prov", p_idx, ["None"] + providers)
if ch_p: entry["provider"] = providers[p_idx-1] if p_idx > 0 else "" if ch_p: entry["provider"] = providers[p_idx-1] if p_idx > 0 else ""
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Model:"); imgui.table_next_column(); imgui.set_next_item_width(-1)
imgui.table_next_row() m_list = self.controller.all_available_models.get(entry.get("provider", ""), []); m_idx = m_list.index(mod) + 1 if mod in m_list else 0
imgui.table_next_column()
imgui.text("Model:")
imgui.table_next_column()
imgui.set_next_item_width(-1)
m_list = self.controller.all_available_models.get(entry.get("provider", ""), [])
m_idx = m_list.index(mod) + 1 if mod in m_list else 0
ch_m, m_idx = imgui.combo("##model", m_idx, ["None"] + m_list) ch_m, m_idx = imgui.combo("##model", m_idx, ["None"] + m_list)
if ch_m: entry["model"] = m_list[m_idx-1] if m_idx > 0 else "" if ch_m: entry["model"] = m_list[m_idx-1] if m_idx > 0 else ""
imgui.table_next_row(); imgui.table_next_column(); imgui.text("Temperature:"); imgui.table_next_column(); cw = imgui.get_content_region_avail().x
imgui.table_next_row() imgui.set_next_item_width(cw * 0.7); _, entry["temperature"] = imgui.slider_float("##ts", entry.get("temperature", 0.7), 0.0, 2.0, "%.1f")
imgui.table_next_column() imgui.same_line(); imgui.set_next_item_width(-1); _, entry["temperature"] = imgui.input_float("##ti", entry.get("temperature", 0.7), 0.1, 0.1, "%.1f")
imgui.text("Temperature:") imgui.table_next_row(); imgui.table_next_column(); imgui.text("Max Tokens:"); imgui.table_next_column(); imgui.set_next_item_width(-1); _, entry["max_output_tokens"] = imgui.input_int("##maxt", entry.get("max_output_tokens", 4096))
imgui.table_next_column() imgui.table_next_row(); imgui.table_next_column(); imgui.text("History Limit:"); imgui.table_next_column(); imgui.set_next_item_width(-1); _, entry["history_trunc_limit"] = imgui.input_int("##hist", entry.get("history_trunc_limit", 900000))
cw = imgui.get_content_region_avail().x
imgui.set_next_item_width(cw * 0.7)
_, entry["temperature"] = imgui.slider_float("##ts", entry.get("temperature", 0.7), 0.0, 2.0, "%.1f")
imgui.same_line()
imgui.set_next_item_width(-1)
_, entry["temperature"] = imgui.input_float("##ti", entry.get("temperature", 0.7), 0.1, 0.1, "%.1f")
imgui.table_next_row()
imgui.table_next_column()
imgui.text("Max Tokens:")
imgui.table_next_column()
imgui.set_next_item_width(-1)
_, entry["max_output_tokens"] = imgui.input_int("##maxt", entry.get("max_output_tokens", 4096))
imgui.table_next_row()
imgui.table_next_column()
imgui.text("History Limit:")
imgui.table_next_column()
imgui.set_next_item_width(-1)
_, entry["history_trunc_limit"] = imgui.input_int("##hist", entry.get("history_trunc_limit", 900000))
imgui.end_table() imgui.end_table()
imgui.unindent(20) imgui.unindent(20)
imgui.pop_id() imgui.pop_id()
for i in reversed(to_remove): for i in reversed(to_remove): self._editing_persona_preferred_models_list.pop(i)
self._editing_persona_preferred_models_list.pop(i)
imgui.end_child() imgui.end_child()
if imgui.button("Add Preferred Model", imgui.ImVec2(-1, 0)): # Custom Splitter
self._editing_persona_preferred_models_list.append({"provider": self.current_provider, "model": self.current_model, "temperature": 0.7, "max_output_tokens": 4096, "history_trunc_limit": 900000}) imgui.button("###persona_splitter", imgui.ImVec2(-1, 4))
if imgui.is_item_active():
self._persona_split_v = max(0.1, min(0.9, self._persona_split_v + imgui.get_io().mouse_delta.y / total_h))
imgui.dummy(imgui.ImVec2(0, 4)) imgui.dummy(imgui.ImVec2(0, 2))
imgui.separator() if imgui.button("Add Preferred Model", imgui.ImVec2(-1, 0)): self._editing_persona_preferred_models_list.append({"provider": self.current_provider, "model": self.current_model, "temperature": 0.7, "max_output_tokens": 4096, "history_trunc_limit": 900000})
imgui.dummy(imgui.ImVec2(0, 2))
if imgui.begin_table("p_assign", 2): if imgui.begin_table("p_assign", 2):
imgui.table_setup_column("C1") imgui.table_setup_column("C1"); imgui.table_setup_column("C2"); imgui.table_next_row()
imgui.table_setup_column("C2") imgui.table_next_column(); imgui.text("Tool Preset:"); tn = ["None"] + sorted(self.controller.tool_presets.keys())
imgui.table_next_row()
imgui.table_next_column()
imgui.text("Tool Preset:")
tn = ["None"] + sorted(self.controller.tool_presets.keys())
t_idx = tn.index(self._editing_persona_tool_preset_id) if getattr(self, '_editing_persona_tool_preset_id', '') in tn else 0 t_idx = tn.index(self._editing_persona_tool_preset_id) if getattr(self, '_editing_persona_tool_preset_id', '') in tn else 0
imgui.set_next_item_width(-1) imgui.set_next_item_width(-1); _, t_idx = imgui.combo("##ptp", t_idx, tn); self._editing_persona_tool_preset_id = tn[t_idx] if t_idx > 0 else ""
_, t_idx = imgui.combo("##ptp", t_idx, tn) imgui.table_next_column(); imgui.text("Bias Profile:"); bn = ["None"] + sorted(self.controller.bias_profiles.keys())
self._editing_persona_tool_preset_id = tn[t_idx] if t_idx > 0 else ""
imgui.table_next_column()
imgui.text("Bias Profile:")
bn = ["None"] + sorted(self.controller.bias_profiles.keys())
b_idx = bn.index(self._editing_persona_bias_profile_id) if getattr(self, '_editing_persona_bias_profile_id', '') in bn else 0 b_idx = bn.index(self._editing_persona_bias_profile_id) if getattr(self, '_editing_persona_bias_profile_id', '') in bn else 0
imgui.set_next_item_width(-1) imgui.set_next_item_width(-1); _, b_idx = imgui.combo("##pbp", b_idx, bn); self._editing_persona_bias_profile_id = bn[b_idx] if b_idx > 0 else ""
_, b_idx = imgui.combo("##pbp", b_idx, bn)
self._editing_persona_bias_profile_id = bn[b_idx] if b_idx > 0 else ""
imgui.end_table() imgui.end_table()
if imgui.button("Manage Tools & Biases", imgui.ImVec2(-1, 0)): if imgui.button("Manage Tools & Biases", imgui.ImVec2(-1, 0)): self.show_tool_preset_manager_window = True
self.show_tool_preset_manager_window = True imgui.dummy(imgui.ImVec2(0, 4)); imgui.separator(); imgui.text("System Prompt:")
imgui.dummy(imgui.ImVec2(0, 4))
imgui.separator()
imgui.text("System Prompt:")
imgui.begin_child("p_prompt_header", imgui.ImVec2(0, 30), False) imgui.begin_child("p_prompt_header", imgui.ImVec2(0, 30), False)
imgui.text("Template:") imgui.text("Template:"); imgui.same_line(); p_pre = ["Select..."] + sorted(self.controller.presets.keys())
imgui.same_line()
p_pre = ["Select..."] + sorted(self.controller.presets.keys())
if not hasattr(self, "_load_preset_idx"): self._load_preset_idx = 0 if not hasattr(self, "_load_preset_idx"): self._load_preset_idx = 0
imgui.set_next_item_width(200) imgui.set_next_item_width(200); _, self._load_preset_idx = imgui.combo("##load_p", self._load_preset_idx, p_pre)
_, self._load_preset_idx = imgui.combo("##load_p", self._load_preset_idx, p_pre) imgui.same_line();
imgui.same_line()
if imgui.button("Apply"): if imgui.button("Apply"):
if self._load_preset_idx > 0: if self._load_preset_idx > 0: self._editing_persona_system_prompt = self.controller.presets[p_pre[self._load_preset_idx]].system_prompt
self._editing_persona_system_prompt = self.controller.presets[p_pre[self._load_preset_idx]].system_prompt imgui.same_line();
imgui.same_line() if imgui.button("Manage"): self.show_preset_manager_window = True
if imgui.button("Manage"): imgui.end_child()
self.show_preset_manager_window = True _, self._editing_persona_system_prompt = imgui.input_text_multiline("##pprompt", self._editing_persona_system_prompt, imgui.ImVec2(-1, h2))
imgui.end_child() imgui.end_child()
# Prompt fills remaining space # Fixed Bottom Buttons
_, self._editing_persona_system_prompt = imgui.input_text_multiline("##pprompt", self._editing_persona_system_prompt, imgui.ImVec2(-1, imgui.get_content_region_avail().y))
imgui.end_child()
# --- Bottom Buttons ---
imgui.separator() imgui.separator()
imgui.dummy(imgui.ImVec2(0, 4))
if imgui.button("Save Persona", imgui.ImVec2(150, 0)): if imgui.button("Save Persona", imgui.ImVec2(150, 0)):
if self._editing_persona_name.strip(): if self._editing_persona_name.strip():
try: try:
import copy import copy; persona = models.Persona(name=self._editing_persona_name.strip(), system_prompt=self._editing_persona_system_prompt, tool_preset=self._editing_persona_tool_preset_id or None, bias_profile=self._editing_persona_bias_profile_id or None, preferred_models=copy.deepcopy(self._editing_persona_preferred_models_list))
persona = models.Persona(name=self._editing_persona_name.strip(), system_prompt=self._editing_persona_system_prompt, tool_preset=self._editing_persona_tool_preset_id or None, bias_profile=self._editing_persona_bias_profile_id or None, preferred_models=copy.deepcopy(self._editing_persona_preferred_models_list)) self.controller._cb_save_persona(persona, getattr(self, '_editing_persona_scope', 'project')); self.ai_status = f"Saved: {persona.name}"
self.controller._cb_save_persona(persona, getattr(self, '_editing_persona_scope', 'project')) except Exception as e: self.ai_status = f"Error: {e}"
self.ai_status = f"Saved: {persona.name}" else: self.ai_status = "Name required"
except Exception as e: imgui.same_line();
self.ai_status = f"Error: {e}"
else:
self.ai_status = "Name required"
imgui.same_line()
if imgui.button("Delete", imgui.ImVec2(100, 0)): if imgui.button("Delete", imgui.ImVec2(100, 0)):
if not getattr(self, '_editing_persona_is_new', True) and self._editing_persona_name: if not getattr(self, '_editing_persona_is_new', True) and self._editing_persona_name:
self.controller._cb_delete_persona(self._editing_persona_name, getattr(self, '_editing_persona_scope', 'project')) self.controller._cb_delete_persona(self._editing_persona_name, getattr(self, '_editing_persona_scope', 'project'))
self._editing_persona_name = "" self._editing_persona_name = ""; self._editing_persona_is_new = True
self._editing_persona_is_new = True
if not is_embedded: if not is_embedded:
imgui.same_line() imgui.same_line();
if imgui.button("Close", imgui.ImVec2(100, 0)): if imgui.button("Close", imgui.ImVec2(100, 0)): self.show_persona_editor_window = False
self.show_persona_editor_window = False
imgui.end_table() imgui.end_table()
finally: finally:
if not is_embedded: if not is_embedded: imgui.end()
imgui.end()
def _render_projects_panel(self) -> None: def _render_projects_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_projects_panel") if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_projects_panel")
proj_name = self.project.get("project", {}).get("name", Path(self.active_project_path).stem) proj_name = self.project.get("project", {}).get("name", Path(self.active_project_path).stem)