Compare commits
11 Commits
1a01e3f112
...
9e51071418
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e51071418 | |||
| 0944aa1c2d | |||
| 34c9919444 | |||
| c1ebdc0c6f | |||
| e0d441ceae | |||
| 9133358c40 | |||
| f21f22e48f | |||
| 97ecd709a9 | |||
| 09902701b4 | |||
| 55475b80e7 | |||
| 84ec24e866 |
@@ -81,6 +81,6 @@ For deep implementation details when planning or implementing tracks, consult `d
|
||||
- **Semantic Nudging:** Automatically prefixes tool and parameter descriptions with priority tags (e.g., [HIGH PRIORITY], [PREFERRED]) to bias model selection.
|
||||
- **Dynamic Tooling Strategy:** Automatically appends a Markdown "Tooling Strategy" section to system instructions based on the active preset and global bias profile.
|
||||
- **Global Bias Profiles:** Application of category-level multipliers (e.g., Execution-Focused, Discovery-Heavy) to influence agent behavior across broad toolsets.
|
||||
- **Priority Badges:** High-density, color-coded visual indicators in tool lists showing the assigned priority level of each capability.
|
||||
- **Priority Badges & Refined Layout:** High-density, color-coded visual indicators in tool lists showing the assigned priority level of each capability. Displays tool names before radio buttons with consistent spacing for improved readability.
|
||||
- **Category-Based Filtering:** Integrated category filtering in both the Active Tools panel and the Tool Preset Manager, allowing users to quickly manage large toolsets.
|
||||
- **Fine-Grained Weight Control:** Integrated sliders in the Preset Manager for adjusting individual tool weights (1-5) and parameter-level biases.
|
||||
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
6. [ ] **Track: Custom Shader and Window Frame Support**
|
||||
*Link: [./tracks/custom_shaders_20260309/](./tracks/custom_shaders_20260309/)*
|
||||
|
||||
7. [ ] **Track: UI/UX Improvements - Presets and AI Settings**
|
||||
7. [x] **Track: UI/UX Improvements - Presets and AI Settings**
|
||||
*Link: [./tracks/presets_ai_settings_ux_20260311/](./tracks/presets_ai_settings_ux_20260311/)*
|
||||
*Goal: Improve the layout, scaling, and control ergonomics of the Preset windows (Personas, Prompts, Tools) and AI Settings panel. Includes dual-control sliders and categorized tool management.*
|
||||
|
||||
|
||||
@@ -7,27 +7,27 @@ This plan focuses on enhancing the layout, scaling, and control ergonomics of th
|
||||
- [x] Task: Identify specific UI sections in `Personas`, `Prompts`, `Tools`, and `AI Settings` windows that require padding and width adjustments. db1f749
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 1: Research and Layout Audit' (Protocol in workflow.md) db1f749
|
||||
|
||||
## Phase 2: Preset Windows Layout & Scaling
|
||||
- [ ] Task: Write tests to verify window layout stability and element visibility during simulated resizes.
|
||||
- [ ] Task: Implement improved resize/scale policies for `Personas`, `Prompts`, and `Tools` windows.
|
||||
- [ ] Task: Apply standardized padding and adjust input box widths across these windows.
|
||||
- [ ] Task: Implement dual-control (Slider + Input Box) for any applicable parameters in these windows.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Preset Windows Layout & Scaling' (Protocol in workflow.md)
|
||||
## Phase 2: Preset Windows Layout & Scaling [checkpoint: 84ec24e]
|
||||
- [x] Task: Write tests to verify window layout stability and element visibility during simulated resizes. 84ec24e
|
||||
- [x] Task: Implement improved resize/scale policies for `Personas`, `Prompts`, and `Tools` windows. 84ec24e
|
||||
- [x] Task: Apply standardized padding and adjust input box widths across these windows. 84ec24e
|
||||
- [x] Task: Implement dual-control (Slider + Input Box) for any applicable parameters in these windows. 84ec24e
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 2: Preset Windows Layout & Scaling' (Protocol in workflow.md) 84ec24e
|
||||
|
||||
## Phase 3: AI Settings Overhaul
|
||||
- [ ] Task: Write tests for AI Settings panel interactions and visual state consistency.
|
||||
- [ ] Task: Refactor AI Settings panel to use visual sliders/knobs for Temperature, Top-P, and Max Tokens.
|
||||
- [ ] Task: Integrate corresponding numeric input boxes for all AI setting sliders.
|
||||
- [ ] Task: Improve visual clarity of preferred model entries when collapsed.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 3: AI Settings Overhaul' (Protocol in workflow.md)
|
||||
## Phase 3: AI Settings Overhaul [checkpoint: 0990270]
|
||||
- [x] Task: Write tests for AI Settings panel interactions and visual state consistency. 0990270
|
||||
- [x] Task: Refactor AI Settings panel to use visual sliders/knobs for Temperature, Top-P, and Max Tokens. 0990270
|
||||
- [x] Task: Integrate corresponding numeric input boxes for all AI setting sliders. 0990270
|
||||
- [x] Task: Improve visual clarity of preferred model entries when collapsed. 0990270
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 3: AI Settings Overhaul' (Protocol in workflow.md) 0990270
|
||||
|
||||
## Phase 4: Tool Management (MCP) Refinement
|
||||
- [ ] Task: Write tests for tool list rendering and category filtering.
|
||||
- [ ] Task: Update the tools section to display tool names before radio buttons with consistent spacing.
|
||||
- [ ] Task: Implement a category-based grouping/filtering system for tools (File I/O, Web, System, etc.).
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 4: Tool Management (MCP) Refinement' (Protocol in workflow.md)
|
||||
## Phase 4: Tool Management (MCP) Refinement [checkpoint: f21f22e]
|
||||
- [x] Task: Write tests for tool list rendering and category filtering. f21f22e
|
||||
- [x] Task: Update the tools section to display tool names before radio buttons with consistent spacing. f21f22e
|
||||
- [x] Task: Implement a category-based grouping/filtering system for tools (File I/O, Web, System, etc.). f21f22e
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 4: Tool Management (MCP) Refinement' (Protocol in workflow.md) f21f22e
|
||||
|
||||
## Phase 5: Final Integration and Verification
|
||||
- [ ] Task: Perform a comprehensive UI audit across all modified windows to ensure visual consistency.
|
||||
- [ ] Task: Run all automated tests and verify no regressions in GUI performance or functionality.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 5: Final Integration and Verification' (Protocol in workflow.md)
|
||||
## Phase 5: Final Integration and Verification [checkpoint: e0d441c]
|
||||
- [x] Task: Perform a comprehensive UI audit across all modified windows to ensure visual consistency. e0d441c
|
||||
- [x] Task: Run all automated tests and verify no regressions in GUI performance or functionality. e0d441c
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 5: Final Integration and Verification' (Protocol in workflow.md) e0d441c
|
||||
|
||||
+10
-2
@@ -42,6 +42,7 @@ from src.events import EventEmitter
|
||||
_provider: str = "gemini"
|
||||
_model: str = "gemini-2.5-flash-lite"
|
||||
_temperature: float = 0.0
|
||||
_top_p: float = 1.0
|
||||
_max_tokens: int = 8192
|
||||
|
||||
_history_trunc_limit: int = 8000
|
||||
@@ -49,11 +50,12 @@ _history_trunc_limit: int = 8000
|
||||
# Global event emitter for API lifecycle events
|
||||
events: EventEmitter = EventEmitter()
|
||||
|
||||
def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000) -> None:
|
||||
global _temperature, _max_tokens, _history_trunc_limit
|
||||
def set_model_params(temp: float, max_tok: int, trunc_limit: int = 8000, top_p: float = 1.0) -> None:
|
||||
global _temperature, _max_tokens, _history_trunc_limit, _top_p
|
||||
_temperature = temp
|
||||
_max_tokens = max_tok
|
||||
_history_trunc_limit = trunc_limit
|
||||
_top_p = top_p
|
||||
|
||||
def get_history_trunc_limit() -> int:
|
||||
return _history_trunc_limit
|
||||
@@ -939,6 +941,7 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str,
|
||||
system_instruction=sys_instr,
|
||||
tools=cast(Any, tools_decl),
|
||||
temperature=_temperature,
|
||||
top_p=_top_p,
|
||||
max_output_tokens=_max_tokens,
|
||||
safety_settings=[types.SafetySetting(category=types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT, threshold=types.HarmBlockThreshold.BLOCK_ONLY_HIGH)]
|
||||
)
|
||||
@@ -1010,6 +1013,7 @@ def _send_gemini(md_content: str, user_message: str, base_dir: str,
|
||||
config = types.GenerateContentConfig(
|
||||
tools=[td] if td else [],
|
||||
temperature=_temperature,
|
||||
top_p=_top_p,
|
||||
max_output_tokens=_max_tokens,
|
||||
)
|
||||
|
||||
@@ -1455,6 +1459,7 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
||||
model=_model,
|
||||
max_tokens=_max_tokens,
|
||||
temperature=_temperature,
|
||||
top_p=_top_p,
|
||||
system=cast(Iterable[anthropic.types.TextBlockParam], system_blocks),
|
||||
tools=cast(Iterable[anthropic.types.ToolParam], _get_anthropic_tools()),
|
||||
messages=cast(Iterable[anthropic.types.MessageParam], _strip_private_keys(_anthropic_history)),
|
||||
@@ -1468,6 +1473,7 @@ def _send_anthropic(md_content: str, user_message: str, base_dir: str, file_item
|
||||
model=_model,
|
||||
max_tokens=_max_tokens,
|
||||
temperature=_temperature,
|
||||
top_p=_top_p,
|
||||
system=cast(Iterable[anthropic.types.TextBlockParam], system_blocks),
|
||||
tools=cast(Iterable[anthropic.types.ToolParam], _get_anthropic_tools()),
|
||||
messages=cast(Iterable[anthropic.types.MessageParam], _strip_private_keys(_anthropic_history)),
|
||||
@@ -1696,6 +1702,7 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
|
||||
if not is_reasoner:
|
||||
request_payload["temperature"] = _temperature
|
||||
request_payload["top_p"] = _top_p
|
||||
# DeepSeek max_tokens is for the output, clamp to 8192 which is their hard limit for V3/Chat
|
||||
request_payload["max_tokens"] = min(_max_tokens, 8192)
|
||||
tools = _get_deepseek_tools()
|
||||
@@ -1927,6 +1934,7 @@ def _send_minimax(md_content: str, user_message: str, base_dir: str,
|
||||
request_payload["stream_options"] = {"include_usage": True}
|
||||
|
||||
request_payload["temperature"] = 1.0
|
||||
request_payload["top_p"] = _top_p
|
||||
request_payload["max_tokens"] = min(_max_tokens, 8192)
|
||||
|
||||
tools = _get_deepseek_tools()
|
||||
|
||||
@@ -61,8 +61,8 @@ class GenerateRequest(BaseModel):
|
||||
prompt: str
|
||||
auto_add_history: bool = True
|
||||
temperature: float | None = None
|
||||
top_p: float | None = None
|
||||
max_tokens: int | None = None
|
||||
|
||||
class ConfirmRequest(BaseModel):
|
||||
approved: bool
|
||||
script: Optional[str] = None
|
||||
@@ -199,6 +199,7 @@ class AppController:
|
||||
self._current_provider: str = "gemini"
|
||||
self._current_model: str = "gemini-2.5-flash-lite"
|
||||
self.temperature: float = 0.0
|
||||
self.top_p: float = 1.0
|
||||
self.max_tokens: int = 8192
|
||||
self.history_trunc_limit: int = 8000
|
||||
# UI-related state moved to controller
|
||||
@@ -484,6 +485,7 @@ class AppController:
|
||||
self._predefined_callbacks: dict[str, Callable[..., Any]] = {
|
||||
'_test_callback_func_write_to_file': self._test_callback_func_write_to_file,
|
||||
'_set_env_var': lambda k, v: os.environ.update({k: v}),
|
||||
'_set_attr': lambda k, v: setattr(self, k, v),
|
||||
'_apply_preset': self._apply_preset,
|
||||
'_cb_save_preset': self._cb_save_preset,
|
||||
'_cb_delete_preset': self._cb_delete_preset,
|
||||
@@ -835,6 +837,7 @@ class AppController:
|
||||
self._current_provider = ai_cfg.get("provider", "gemini")
|
||||
self._current_model = ai_cfg.get("model", "gemini-2.5-flash-lite")
|
||||
self.temperature = ai_cfg.get("temperature", 0.0)
|
||||
self.top_p = ai_cfg.get("top_p", 1.0)
|
||||
self.max_tokens = ai_cfg.get("max_tokens", 8192)
|
||||
self.history_trunc_limit = ai_cfg.get("history_trunc_limit", 8000)
|
||||
projects_cfg = self.config.get("projects", {})
|
||||
@@ -1246,7 +1249,7 @@ class AppController:
|
||||
self.ai_response = ""
|
||||
csp = filter(bool, [self.ui_global_system_prompt.strip(), self.ui_project_system_prompt.strip()])
|
||||
ai_client.set_custom_system_prompt("\n\n".join(csp))
|
||||
ai_client.set_model_params(self.temperature, self.max_tokens, self.history_trunc_limit)
|
||||
ai_client.set_model_params(self.temperature, self.max_tokens, self.history_trunc_limit, self.top_p)
|
||||
ai_client.set_agent_tools(self.ui_agent_tools)
|
||||
# Force update adapter path right before send to bypass potential duplication issues
|
||||
self._update_gcli_adapter(self.ui_gemini_cli_path)
|
||||
@@ -1633,8 +1636,9 @@ class AppController:
|
||||
csp = filter(bool, [self.ui_global_system_prompt.strip(), self.ui_project_system_prompt.strip()])
|
||||
ai_client.set_custom_system_prompt("\n\n".join(csp))
|
||||
temp = req.temperature if req.temperature is not None else self.temperature
|
||||
top_p = req.top_p if req.top_p is not None else self.top_p
|
||||
tokens = req.max_tokens if req.max_tokens is not None else self.max_tokens
|
||||
ai_client.set_model_params(temp, tokens, self.history_trunc_limit)
|
||||
ai_client.set_model_params(temp, tokens, self.history_trunc_limit, top_p)
|
||||
ai_client.set_agent_tools(self.ui_agent_tools)
|
||||
if req.auto_add_history:
|
||||
with self._pending_history_adds_lock:
|
||||
@@ -2265,6 +2269,7 @@ class AppController:
|
||||
"provider": self.current_provider,
|
||||
"model": self.current_model,
|
||||
"temperature": self.temperature,
|
||||
"top_p": self.top_p,
|
||||
"max_tokens": self.max_tokens,
|
||||
"history_trunc_limit": self.history_trunc_limit,
|
||||
"active_preset": self.ui_global_preset_name,
|
||||
|
||||
+102
-19
@@ -78,6 +78,7 @@ class GenerateRequest(BaseModel):
|
||||
prompt: str
|
||||
auto_add_history: bool = True
|
||||
temperature: float | None = None
|
||||
top_p: float | None = None
|
||||
max_tokens: int | None = None
|
||||
|
||||
class ConfirmRequest(BaseModel):
|
||||
@@ -108,6 +109,7 @@ class App:
|
||||
self._editing_persona_model = ""
|
||||
self._editing_persona_system_prompt = ""
|
||||
self._editing_persona_temperature = 0.7
|
||||
self._editing_persona_top_p = 1.0
|
||||
self._editing_persona_max_tokens = 4096
|
||||
self._editing_persona_tool_preset_id = ""
|
||||
self._editing_persona_bias_profile_id = ""
|
||||
@@ -187,6 +189,7 @@ class App:
|
||||
self.ui_crt_filter = True
|
||||
self._nerv_alert = theme_fx.AlertPulsing()
|
||||
self._nerv_flicker = theme_fx.StatusFlicker()
|
||||
self.ui_tool_filter_category = "All"
|
||||
|
||||
def _handle_approve_tool(self, user_data=None) -> None:
|
||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||
@@ -925,7 +928,7 @@ class App:
|
||||
|
||||
def _render_preset_manager_content(self, is_embedded: bool = False) -> None:
|
||||
avail = imgui.get_content_region_avail()
|
||||
imgui.begin_child("preset_list_area", imgui.ImVec2(250, avail.y), True)
|
||||
imgui.begin_child("preset_list_area", imgui.ImVec2(avail.x * 0.25, avail.y), True)
|
||||
try:
|
||||
preset_names = sorted(self.controller.presets.keys())
|
||||
if imgui.button("New Preset", imgui.ImVec2(-1, 0)):
|
||||
@@ -949,17 +952,22 @@ class App:
|
||||
p_name = self._editing_preset_name or "(New Preset)"
|
||||
imgui.text_colored(C_IN, f"Editing Preset: {p_name}")
|
||||
imgui.separator()
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
imgui.text("Name:")
|
||||
imgui.set_next_item_width(-1)
|
||||
_, self._editing_preset_name = imgui.input_text("##edit_name", self._editing_preset_name)
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
imgui.text("Scope:")
|
||||
if imgui.radio_button("Global", self._editing_preset_scope == "global"):
|
||||
self._editing_preset_scope = "global"
|
||||
imgui.same_line()
|
||||
if imgui.radio_button("Project", self._editing_preset_scope == "project"):
|
||||
self._editing_preset_scope = "project"
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
imgui.text("Content:")
|
||||
_, self._editing_preset_content = imgui.input_text_multiline("##edit_content", self._editing_preset_content, imgui.ImVec2(-1, -40))
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
if imgui.button("Save", imgui.ImVec2(120, 0)):
|
||||
if self._editing_preset_name.strip():
|
||||
self.controller._cb_save_preset(
|
||||
@@ -1006,7 +1014,7 @@ class App:
|
||||
def _render_tool_preset_manager_content(self, is_embedded: bool = False) -> None:
|
||||
avail = imgui.get_content_region_avail()
|
||||
# Left Column: Listbox
|
||||
imgui.begin_child("tool_preset_list_area", imgui.ImVec2(250, avail.y), True)
|
||||
imgui.begin_child("tool_preset_list_area", imgui.ImVec2(avail.x * 0.25, avail.y), True)
|
||||
try:
|
||||
if imgui.button("New Tool Preset", imgui.ImVec2(-1, 0)):
|
||||
self._editing_tool_preset_name = ""
|
||||
@@ -1041,6 +1049,7 @@ class App:
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
imgui.text("Name:")
|
||||
imgui.set_next_item_width(-1)
|
||||
_, self._editing_tool_preset_name = imgui.input_text("##edit_tp_name", self._editing_tool_preset_name)
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
@@ -1053,9 +1062,21 @@ class App:
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
imgui.text("Categories & Tools:")
|
||||
cat_options = ["All"] + sorted(list(models.DEFAULT_TOOL_CATEGORIES.keys()))
|
||||
try:
|
||||
f_idx = cat_options.index(self.ui_tool_filter_category)
|
||||
except ValueError:
|
||||
f_idx = 0
|
||||
imgui.set_next_item_width(200)
|
||||
ch_cat, next_f_idx = imgui.combo("Filter Category##tp", f_idx, cat_options)
|
||||
if ch_cat:
|
||||
self.ui_tool_filter_category = cat_options[next_f_idx]
|
||||
|
||||
imgui.begin_child("tp_categories_scroll", imgui.ImVec2(0, 300), True)
|
||||
try:
|
||||
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:
|
||||
continue
|
||||
if imgui.tree_node(cat_name):
|
||||
if cat_name not in self._editing_tool_preset_categories:
|
||||
self._editing_tool_preset_categories[cat_name] = []
|
||||
@@ -1066,6 +1087,9 @@ class App:
|
||||
tool = next((t for t in current_cat_tools if t.name == tool_name), None)
|
||||
mode = "disabled" if tool is None else tool.approval
|
||||
|
||||
imgui.text(tool_name)
|
||||
imgui.same_line(180)
|
||||
|
||||
if imgui.radio_button(f"Off##{cat_name}_{tool_name}", mode == "disabled"):
|
||||
if tool: current_cat_tools.remove(tool)
|
||||
imgui.same_line()
|
||||
@@ -1082,11 +1106,9 @@ class App:
|
||||
current_cat_tools.append(tool)
|
||||
else:
|
||||
tool.approval = "ask"
|
||||
imgui.same_line()
|
||||
imgui.text(tool_name)
|
||||
|
||||
if tool:
|
||||
imgui.same_line(250)
|
||||
imgui.same_line(350)
|
||||
imgui.set_next_item_width(100)
|
||||
_, tool.weight = imgui.slider_int(f"Weight##{cat_name}_{tool_name}", tool.weight, 1, 5)
|
||||
imgui.same_line()
|
||||
@@ -1100,12 +1122,13 @@ class App:
|
||||
finally:
|
||||
imgui.end_child()
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
imgui.separator()
|
||||
imgui.text_colored(C_SUB, "Bias Profiles")
|
||||
imgui.begin_child("bias_profiles_area", imgui.ImVec2(0, 300), True)
|
||||
try:
|
||||
avail_bias = imgui.get_content_region_avail()
|
||||
imgui.begin_child("bias_list", imgui.ImVec2(200, avail_bias.y), False)
|
||||
imgui.begin_child("bias_list", imgui.ImVec2(avail_bias.x * 0.3, avail_bias.y), False)
|
||||
if imgui.button("New Profile", imgui.ImVec2(-1, 0)):
|
||||
self._editing_bias_profile_name = ""
|
||||
self._editing_bias_profile_tool_weights = {}
|
||||
@@ -1125,6 +1148,7 @@ class App:
|
||||
imgui.same_line()
|
||||
imgui.begin_child("bias_edit", imgui.ImVec2(0, avail_bias.y), False)
|
||||
imgui.text("Name:")
|
||||
imgui.set_next_item_width(-1)
|
||||
_, self._editing_bias_profile_name = imgui.input_text("##b_name", self._editing_bias_profile_name)
|
||||
|
||||
imgui.text_colored(C_KEY, "Tool Weights:")
|
||||
@@ -1260,7 +1284,7 @@ class App:
|
||||
try:
|
||||
avail = imgui.get_content_region_avail()
|
||||
# Left Pane: List of Personas
|
||||
imgui.begin_child("persona_list_area", imgui.ImVec2(250, avail.y), True)
|
||||
imgui.begin_child("persona_list_area", imgui.ImVec2(avail.x * 0.25, avail.y), True)
|
||||
try:
|
||||
if imgui.button("New Persona", imgui.ImVec2(-1, 0)):
|
||||
self._editing_persona_name = ""
|
||||
@@ -1301,10 +1325,12 @@ class App:
|
||||
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.separator()
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
imgui.text("Name:")
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(-1)
|
||||
_, self._editing_persona_name = imgui.input_text("##pname", self._editing_persona_name, 128)
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
imgui.text("Scope:")
|
||||
if imgui.radio_button("Global##pscope", getattr(self, '_editing_persona_scope', 'project') == "global"):
|
||||
@@ -1312,8 +1338,10 @@ class App:
|
||||
imgui.same_line()
|
||||
if imgui.radio_button("Project##pscope", getattr(self, '_editing_persona_scope', 'project') == "project"):
|
||||
self._editing_persona_scope = "project"
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
imgui.separator()
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
imgui.text("Preferred Models:")
|
||||
providers = self.controller.PROVIDERS
|
||||
@@ -1321,6 +1349,7 @@ class App:
|
||||
self._persona_pref_models_expanded = {}
|
||||
imgui.begin_child("pref_models_list", imgui.ImVec2(0, 200), True)
|
||||
to_remove = []
|
||||
avail_edit = imgui.get_content_region_avail()
|
||||
for i, entry in enumerate(self._editing_persona_preferred_models_list):
|
||||
imgui.push_id(f"pref_model_{i}")
|
||||
|
||||
@@ -1332,7 +1361,14 @@ class App:
|
||||
self._persona_pref_models_expanded[i] = not is_expanded
|
||||
imgui.same_line()
|
||||
|
||||
imgui.text(f"{i+1}. {prov} - {mod}")
|
||||
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}")
|
||||
|
||||
imgui.same_line(imgui.get_content_region_avail().x - 30)
|
||||
if imgui.button("x"):
|
||||
to_remove.append(i)
|
||||
@@ -1361,8 +1397,11 @@ class App:
|
||||
|
||||
imgui.text("Temperature:")
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(100)
|
||||
_, entry["temperature"] = imgui.input_float("##temp", entry.get("temperature", 0.7), 0.1, 0.1, "%.1f")
|
||||
imgui.set_next_item_width(avail_edit.x * 0.3)
|
||||
_, entry["temperature"] = imgui.slider_float("##temp_slider", entry.get("temperature", 0.7), 0.0, 2.0, "%.1f")
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(80)
|
||||
_, entry["temperature"] = imgui.input_float("##temp_input", entry.get("temperature", 0.7), 0.1, 0.1, "%.1f")
|
||||
|
||||
imgui.same_line()
|
||||
imgui.text("Max Output Tokens:")
|
||||
@@ -1395,40 +1434,43 @@ class App:
|
||||
})
|
||||
self._persona_pref_models_expanded[idx] = True
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
imgui.separator()
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
imgui.text("Tool Preset:")
|
||||
imgui.same_line()
|
||||
t_preset_names = ["None"] + sorted(self.controller.tool_presets.keys())
|
||||
t_idx = t_preset_names.index(self._editing_persona_tool_preset_id) if getattr(self, '_editing_persona_tool_preset_id', '') in t_preset_names else 0
|
||||
imgui.push_item_width(200)
|
||||
imgui.set_next_item_width(200)
|
||||
_, t_idx = imgui.combo("##ptoolpreset", t_idx, t_preset_names)
|
||||
self._editing_persona_tool_preset_id = t_preset_names[t_idx] if t_idx > 0 else ""
|
||||
imgui.pop_item_width()
|
||||
|
||||
imgui.same_line()
|
||||
imgui.text("Bias Profile:")
|
||||
imgui.same_line()
|
||||
bias_names = ["None"] + sorted(self.controller.bias_profiles.keys())
|
||||
b_idx = bias_names.index(self._editing_persona_bias_profile_id) if getattr(self, '_editing_persona_bias_profile_id', '') in bias_names else 0
|
||||
imgui.push_item_width(200)
|
||||
imgui.set_next_item_width(200)
|
||||
_, b_idx = imgui.combo("##pbiasprofile", b_idx, bias_names)
|
||||
self._editing_persona_bias_profile_id = bias_names[b_idx] if b_idx > 0 else ""
|
||||
imgui.pop_item_width()
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Manage Tools##p_tools"):
|
||||
self.show_tool_preset_manager_window = True
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
imgui.separator()
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
imgui.text("System Prompt:")
|
||||
|
||||
imgui.text("Load from Preset:")
|
||||
imgui.same_line()
|
||||
prompt_presets = ["Select..."] + sorted(self.controller.presets.keys())
|
||||
if not hasattr(self, "_load_preset_idx"): self._load_preset_idx = 0
|
||||
imgui.push_item_width(150)
|
||||
imgui.set_next_item_width(150)
|
||||
_, self._load_preset_idx = imgui.combo("##load_preset", self._load_preset_idx, prompt_presets)
|
||||
imgui.pop_item_width()
|
||||
imgui.same_line()
|
||||
if imgui.button("Apply##apply_p"):
|
||||
if self._load_preset_idx > 0:
|
||||
@@ -1444,7 +1486,10 @@ class App:
|
||||
|
||||
_, self._editing_persona_system_prompt = imgui.input_text_multiline("##pprompt", self._editing_persona_system_prompt, imgui.ImVec2(-1, 150))
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
imgui.separator()
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
|
||||
if imgui.button("Save Persona", imgui.ImVec2(120, 0)):
|
||||
if self._editing_persona_name.strip():
|
||||
try:
|
||||
@@ -2344,8 +2389,33 @@ def hello():
|
||||
imgui.end_list_box()
|
||||
imgui.separator()
|
||||
imgui.text("Parameters")
|
||||
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)
|
||||
# Temperature
|
||||
imgui.push_id("temp")
|
||||
imgui.set_next_item_width(imgui.get_content_region_avail().x * 0.6)
|
||||
_, self.temperature = imgui.slider_float("##slider", self.temperature, 0.0, 2.0, "%.2f")
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(-1)
|
||||
_, self.temperature = imgui.input_float("Temp", self.temperature, 0.0, 0.0, "%.2f")
|
||||
imgui.pop_id()
|
||||
|
||||
# Top-P
|
||||
imgui.push_id("top_p")
|
||||
imgui.set_next_item_width(imgui.get_content_region_avail().x * 0.6)
|
||||
_, self.top_p = imgui.slider_float("##slider", self.top_p, 0.0, 1.0, "%.2f")
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(-1)
|
||||
_, self.top_p = imgui.input_float("Top-P", self.top_p, 0.0, 0.0, "%.2f")
|
||||
imgui.pop_id()
|
||||
|
||||
# Max Tokens
|
||||
imgui.push_id("max_tokens")
|
||||
imgui.set_next_item_width(imgui.get_content_region_avail().x * 0.6)
|
||||
_, self.max_tokens = imgui.slider_int("##slider", self.max_tokens, 1, 32768)
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(-1)
|
||||
_, self.max_tokens = imgui.input_int("MaxTok", self.max_tokens)
|
||||
imgui.pop_id()
|
||||
|
||||
ch, self.history_trunc_limit = imgui.input_int("History Truncation Limit", self.history_trunc_limit, 1024)
|
||||
|
||||
|
||||
@@ -3696,11 +3766,24 @@ def hello():
|
||||
ai_client.set_bias_profile(bname)
|
||||
imgui.end_combo()
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
cat_options = ["All"] + sorted(list(models.DEFAULT_TOOL_CATEGORIES.keys()))
|
||||
try:
|
||||
f_idx = cat_options.index(self.ui_tool_filter_category)
|
||||
except ValueError:
|
||||
f_idx = 0
|
||||
imgui.set_next_item_width(200)
|
||||
ch_cat, next_f_idx = imgui.combo("Filter Category##agent", f_idx, cat_options)
|
||||
if ch_cat:
|
||||
self.ui_tool_filter_category = cat_options[next_f_idx]
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
active_name = self.ui_active_tool_preset
|
||||
if active_name and active_name in presets:
|
||||
preset = presets[active_name]
|
||||
for cat_name, tools in preset.categories.items():
|
||||
if self.ui_tool_filter_category != "All" and self.ui_tool_filter_category != cat_name:
|
||||
continue
|
||||
if imgui.tree_node(cat_name):
|
||||
for tool in tools:
|
||||
if tool.weight >= 5:
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import pytest
|
||||
import time
|
||||
from src.api_hook_client import ApiHookClient
|
||||
|
||||
@pytest.mark.timeout(30)
|
||||
def test_change_provider_via_hook(live_gui) -> None:
|
||||
"""Verify that we can change the current provider via the API hook."""
|
||||
client = ApiHookClient()
|
||||
if not client.wait_for_server():
|
||||
pytest.fail("Server did not become ready")
|
||||
|
||||
# Change provider to 'anthropic'
|
||||
client.set_value('current_provider', 'anthropic')
|
||||
|
||||
# Wait for state to reflect change
|
||||
success = False
|
||||
state = {}
|
||||
for _ in range(20):
|
||||
state = client.get_gui_state()
|
||||
if state.get('current_provider') == 'anthropic':
|
||||
success = True
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
assert success, f"Provider did not update. Current state: {state}"
|
||||
|
||||
@pytest.mark.timeout(30)
|
||||
def test_set_params_via_custom_callback(live_gui) -> None:
|
||||
"""Verify we can use custom_callback to set temperature and max_tokens."""
|
||||
client = ApiHookClient()
|
||||
if not client.wait_for_server():
|
||||
pytest.fail("Server did not become ready")
|
||||
|
||||
# Set temperature via custom_callback using _set_attr
|
||||
client.post_gui({
|
||||
"action": "custom_callback",
|
||||
"callback": "_set_attr",
|
||||
"args": ["temperature", 0.85]
|
||||
})
|
||||
|
||||
# Set max_tokens via custom_callback using _set_attr
|
||||
client.post_gui({
|
||||
"action": "custom_callback",
|
||||
"callback": "_set_attr",
|
||||
"args": ["max_tokens", 1024]
|
||||
})
|
||||
|
||||
# Verify via get_gui_state
|
||||
success = False
|
||||
state = {}
|
||||
for _ in range(20):
|
||||
state = client.get_gui_state()
|
||||
if state.get('temperature') == 0.85 and state.get('max_tokens') == 1024:
|
||||
success = True
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
assert success, f"Params did not update via custom_callback. Got: {state}"
|
||||
@@ -0,0 +1,46 @@
|
||||
import pytest
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from src.api_hook_client import ApiHookClient
|
||||
|
||||
def test_preset_windows_opening(live_gui):
|
||||
"""Test opening Preset Manager, Tool Preset Manager, and Persona Editor via custom_callback."""
|
||||
client = ApiHookClient()
|
||||
assert client.wait_for_server(timeout=15)
|
||||
|
||||
# Push custom_callback events to set window visibility flags
|
||||
# These rely on the _set_attr predefined callback in AppController
|
||||
windows = [
|
||||
"show_preset_manager_window",
|
||||
"show_tool_preset_manager_window",
|
||||
"show_persona_editor_window"
|
||||
]
|
||||
|
||||
for window in windows:
|
||||
client.push_event("custom_callback", {
|
||||
"callback": "_set_attr",
|
||||
"args": [window, True]
|
||||
})
|
||||
|
||||
# Wait 1 second as requested
|
||||
time.sleep(1.0)
|
||||
|
||||
# Verify the app is still responsive
|
||||
status = client.get_status()
|
||||
assert status.get("status") == "ok"
|
||||
|
||||
def test_api_hook_under_load(live_gui):
|
||||
"""Verify the API Hook can still respond under load."""
|
||||
client = ApiHookClient()
|
||||
assert client.wait_for_server(timeout=15)
|
||||
|
||||
def make_request(_):
|
||||
return client.get_status()
|
||||
|
||||
# Send 20 parallel requests using a thread pool
|
||||
with ThreadPoolExecutor(max_workers=10) as executor:
|
||||
results = list(executor.map(make_request, range(20)))
|
||||
|
||||
for res in results:
|
||||
assert res is not None
|
||||
assert res.get("status") == "ok"
|
||||
@@ -0,0 +1,47 @@
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
# Ensure project root is in path for imports
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
from src.api_hook_client import ApiHookClient
|
||||
|
||||
def test_tool_management_state_updates(live_gui: Any) -> None:
|
||||
client = ApiHookClient()
|
||||
# Wait for hook server to be ready
|
||||
assert client.wait_for_server(timeout=30), "Hook server did not respond within 30s"
|
||||
|
||||
# Test setting ui_active_tool_preset via custom_callback
|
||||
# callback '_set_attr' is defined in AppController._predefined_callbacks
|
||||
preset_name = "TestToolPreset"
|
||||
client.push_event("custom_callback", {
|
||||
"callback": "_set_attr",
|
||||
"args": ["ui_active_tool_preset", preset_name]
|
||||
})
|
||||
|
||||
# Test setting ui_active_bias_profile via custom_callback
|
||||
bias_profile = "TestBiasProfile"
|
||||
client.push_event("custom_callback", {
|
||||
"callback": "_set_attr",
|
||||
"args": ["ui_active_bias_profile", bias_profile]
|
||||
})
|
||||
|
||||
# Give some time for the GUI task to process (it's async via _pending_gui_tasks)
|
||||
time.sleep(2.0)
|
||||
|
||||
# Verify via get_gui_state
|
||||
state = client.get_gui_state()
|
||||
|
||||
assert state.get("ui_active_tool_preset") == preset_name, f"Expected {preset_name}, got {state.get('ui_active_tool_preset')}"
|
||||
assert state.get("ui_active_bias_profile") == bias_profile, f"Expected {bias_profile}, got {state.get('ui_active_bias_profile')}"
|
||||
|
||||
def test_tool_management_gettable_fields(live_gui: Any) -> None:
|
||||
client = ApiHookClient()
|
||||
assert client.wait_for_server(timeout=30)
|
||||
|
||||
# Ensure they are at least present in the state (even if None/empty)
|
||||
state = client.get_gui_state()
|
||||
assert "ui_active_tool_preset" in state
|
||||
assert "ui_active_bias_profile" in state
|
||||
Reference in New Issue
Block a user