Compare commits
13 Commits
67a269b05d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b6d16b4e0 | |||
| 847096d192 | |||
| 7ee50f979a | |||
| 3870bf086c | |||
| 747b810fe1 | |||
| 3ba05b8a6a | |||
| 94598b605a | |||
| 26e03d2c9f | |||
| 6da3d95c0e | |||
| 6ae8737c1a | |||
| 92e7352d37 | |||
| ca8e33837b | |||
| fa5ead2c69 |
@@ -10,7 +10,7 @@ A high-density GUI orchestrator for local LLM-driven coding sessions. Manual Slo
|
||||
**Providers**: Gemini API, Anthropic API, DeepSeek, Gemini CLI (headless), MiniMax
|
||||
**Platform**: Windows (PowerShell) — single developer, local use
|
||||
|
||||

|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -73,6 +73,10 @@ For deep implementation details when planning or implementing tracks, consult `d
|
||||
- **Scoped Inheritance:** Supports **Global** (application-wide) and **Project-Specific** presets. Project presets with the same name automatically override global counterparts, allowing for fine-tuned context tailoring.
|
||||
- **Full AI Profiles:** Presets capture not only the system prompt text but also critical model parameters like **Temperature**, **Top-P**, and **Max Output Tokens**.
|
||||
- **Preset Manager Modal:** A dedicated high-density GUI for creating, editing, and deleting presets with real-time validation and instant application to the active session.
|
||||
- **Agent Personas & Unified Profiles:** Consolidates model settings, provider routing, system prompts, tool presets, and bias profiles into named "Persona" entities.
|
||||
- **Single Configuration Entity:** Switch models, tool weights, and system prompts simultaneously using a single Persona selection.
|
||||
- **Persona Editor Modal:** A dedicated high-density GUI for creating, editing, and deleting Personas.
|
||||
- **MMA Granular Assignment:** Allows assigning specific Personas to individual agents within the 4-Tier Hierarchical MMA.
|
||||
- **Agent Tool Weighting & Bias:** Influences agent tool selection via a weighting system.
|
||||
- **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.
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
- **src/presets.py:** Implements `PresetManager` for high-performance CRUD operations on system prompt presets stored in TOML format (`presets.toml`, `project_presets.toml`). Supports dynamic path resolution and scope-based inheritance.
|
||||
|
||||
- **src/personas.py:** Implements `PersonaManager` for high-performance CRUD operations on unified agent personas stored in TOML format (`personas.toml`, `project_personas.toml`). Handles consolidation of model settings, prompts, and tool biases.
|
||||
|
||||
- **src/tool_bias.py:** Implements the `ToolBiasEngine` for semantic tool description nudging and dynamic tooling strategy generation.
|
||||
|
||||
- **src/tool_presets.py:** Extends `ToolPresetManager` to handle nested `Tool` models, weights, and global `BiasProfile` persistence within `tool_presets.toml`.
|
||||
|
||||
30
config.toml
30
config.toml
@@ -1,11 +1,11 @@
|
||||
[ai]
|
||||
provider = "gemini_cli"
|
||||
model = "gemini-2.5-flash-lite"
|
||||
temperature = 0.30000001192092896
|
||||
provider = "minimax"
|
||||
model = "MiniMax-M2.5"
|
||||
temperature = 0.0
|
||||
max_tokens = 32000
|
||||
history_trunc_limit = 900000
|
||||
active_preset = "TestGlobal"
|
||||
system_prompt = "Overridden Prompt"
|
||||
active_preset = "Default"
|
||||
system_prompt = ""
|
||||
|
||||
[projects]
|
||||
paths = [
|
||||
@@ -16,7 +16,7 @@ paths = [
|
||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_liveexecutionsim.toml",
|
||||
"C:\\projects\\manual_slop\\tests\\artifacts\\temp_project.toml",
|
||||
]
|
||||
active = "C:\\projects\\manual_slop\\tests\\artifacts\\live_gui_workspace\\manual_slop.toml"
|
||||
active = "C:/projects/gencpp/gencpp_sloppy.toml"
|
||||
|
||||
[gui]
|
||||
separate_message_panel = false
|
||||
@@ -24,11 +24,11 @@ separate_response_panel = false
|
||||
separate_tool_calls_panel = false
|
||||
bg_shader_enabled = true
|
||||
crt_filter_enabled = false
|
||||
separate_task_dag = true
|
||||
separate_usage_analytics = true
|
||||
separate_task_dag = false
|
||||
separate_usage_analytics = false
|
||||
separate_tier1 = false
|
||||
separate_tier2 = false
|
||||
separate_tier3 = true
|
||||
separate_tier3 = false
|
||||
separate_tier4 = false
|
||||
|
||||
[gui.show_windows]
|
||||
@@ -36,7 +36,7 @@ separate_tier4 = false
|
||||
"Files & Media" = true
|
||||
"AI Settings" = true
|
||||
"MMA Dashboard" = true
|
||||
"Task DAG" = true
|
||||
"Task DAG" = false
|
||||
"Usage Analytics" = false
|
||||
"Tier 1" = false
|
||||
"Tier 2" = false
|
||||
@@ -44,7 +44,7 @@ separate_tier4 = false
|
||||
"Tier 4" = false
|
||||
"Tier 1: Strategy" = false
|
||||
"Tier 2: Tech Lead" = false
|
||||
"Tier 3: Workers" = true
|
||||
"Tier 3: Workers" = false
|
||||
"Tier 4: QA" = false
|
||||
"Discussion Hub" = true
|
||||
"Operations Hub" = true
|
||||
@@ -53,15 +53,15 @@ Response = false
|
||||
"Tool Calls" = false
|
||||
Theme = true
|
||||
"Log Management" = true
|
||||
Diagnostics = true
|
||||
Diagnostics = false
|
||||
|
||||
[theme]
|
||||
palette = "Nord Dark"
|
||||
font_path = "C:/projects/manual_slop/assets/fonts/Inter-Regular.ttf"
|
||||
font_size = 14.0
|
||||
scale = 1.0
|
||||
transparency = 0.5099999904632568
|
||||
child_transparency = 0.699999988079071
|
||||
scale = 1.0099999904632568
|
||||
transparency = 1.0
|
||||
child_transparency = 1.0
|
||||
|
||||
[mma]
|
||||
max_workers = 4
|
||||
|
||||
BIN
gallery/python_2026-03-11_00-37-21.png
Normal file
BIN
gallery/python_2026-03-11_00-37-21.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 607 KiB |
@@ -73,8 +73,8 @@ Collapsed=0
|
||||
DockId=0xAFC85805,2
|
||||
|
||||
[Window][Theme]
|
||||
Pos=0,1016
|
||||
Size=623,401
|
||||
Pos=0,1670
|
||||
Size=840,467
|
||||
Collapsed=0
|
||||
DockId=0x00000002,2
|
||||
|
||||
@@ -84,14 +84,14 @@ Size=900,700
|
||||
Collapsed=0
|
||||
|
||||
[Window][Diagnostics]
|
||||
Pos=2833,28
|
||||
Size=1007,2109
|
||||
Pos=2641,22
|
||||
Size=1199,2115
|
||||
Collapsed=0
|
||||
DockId=0x0000000C,2
|
||||
|
||||
[Window][Context Hub]
|
||||
Pos=0,1016
|
||||
Size=623,401
|
||||
Pos=0,1670
|
||||
Size=840,467
|
||||
Collapsed=0
|
||||
DockId=0x00000002,1
|
||||
|
||||
@@ -102,26 +102,26 @@ Collapsed=0
|
||||
DockId=0x0000000D,0
|
||||
|
||||
[Window][Discussion Hub]
|
||||
Pos=1296,22
|
||||
Size=659,1395
|
||||
Pos=1668,22
|
||||
Size=971,2115
|
||||
Collapsed=0
|
||||
DockId=0x00000013,0
|
||||
|
||||
[Window][Operations Hub]
|
||||
Pos=625,22
|
||||
Size=669,1395
|
||||
Pos=842,22
|
||||
Size=824,2115
|
||||
Collapsed=0
|
||||
DockId=0x00000012,0
|
||||
|
||||
[Window][Files & Media]
|
||||
Pos=0,1016
|
||||
Size=623,401
|
||||
Pos=0,1670
|
||||
Size=840,467
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][AI Settings]
|
||||
Pos=0,22
|
||||
Size=623,992
|
||||
Size=840,1646
|
||||
Collapsed=0
|
||||
DockId=0x00000001,0
|
||||
|
||||
@@ -131,14 +131,14 @@ Size=416,325
|
||||
Collapsed=0
|
||||
|
||||
[Window][MMA Dashboard]
|
||||
Pos=1957,22
|
||||
Size=603,1395
|
||||
Pos=2641,22
|
||||
Size=1199,2115
|
||||
Collapsed=0
|
||||
DockId=0x0000000C,0
|
||||
|
||||
[Window][Log Management]
|
||||
Pos=1957,22
|
||||
Size=603,1395
|
||||
Pos=2641,22
|
||||
Size=1199,2115
|
||||
Collapsed=0
|
||||
DockId=0x0000000C,1
|
||||
|
||||
@@ -166,8 +166,8 @@ Collapsed=0
|
||||
DockId=0x0000000F,0
|
||||
|
||||
[Window][Tier 3: Workers]
|
||||
Pos=2905,1238
|
||||
Size=935,899
|
||||
Pos=2641,1235
|
||||
Size=1199,902
|
||||
Collapsed=0
|
||||
DockId=0x0000000F,0
|
||||
|
||||
@@ -322,12 +322,12 @@ Size=420,966
|
||||
Collapsed=0
|
||||
|
||||
[Window][Preset Manager]
|
||||
Pos=403,396
|
||||
Size=956,958
|
||||
Pos=1693,826
|
||||
Size=933,839
|
||||
Collapsed=0
|
||||
|
||||
[Window][Task DAG]
|
||||
Pos=1700,1199
|
||||
Pos=1661,1181
|
||||
Size=1079,662
|
||||
Collapsed=0
|
||||
|
||||
@@ -337,13 +337,13 @@ Size=275,375
|
||||
Collapsed=0
|
||||
|
||||
[Window][Tool Preset Manager]
|
||||
Pos=192,90
|
||||
Size=1066,1324
|
||||
Pos=1014,522
|
||||
Size=1155,1011
|
||||
Collapsed=0
|
||||
|
||||
[Window][Persona Editor]
|
||||
Pos=956,447
|
||||
Size=549,447
|
||||
Pos=995,597
|
||||
Size=1868,1434
|
||||
Collapsed=0
|
||||
|
||||
[Table][0xFB6E3870,4]
|
||||
@@ -377,11 +377,11 @@ Column 3 Width=20
|
||||
Column 4 Weight=1.0000
|
||||
|
||||
[Table][0x2A6000B6,4]
|
||||
RefScale=17
|
||||
Column 0 Width=51
|
||||
Column 1 Width=76
|
||||
RefScale=14
|
||||
Column 0 Width=42
|
||||
Column 1 Width=62
|
||||
Column 2 Weight=1.0000
|
||||
Column 3 Width=128
|
||||
Column 3 Width=105
|
||||
|
||||
[Table][0x8BCC69C7,6]
|
||||
RefScale=13
|
||||
@@ -393,18 +393,18 @@ Column 4 Weight=1.0000
|
||||
Column 5 Width=50
|
||||
|
||||
[Table][0x3751446B,4]
|
||||
RefScale=20
|
||||
Column 0 Width=60
|
||||
Column 1 Width=91
|
||||
RefScale=17
|
||||
Column 0 Width=51
|
||||
Column 1 Width=77
|
||||
Column 2 Weight=1.0000
|
||||
Column 3 Width=151
|
||||
Column 3 Width=128
|
||||
|
||||
[Table][0x2C515046,4]
|
||||
RefScale=20
|
||||
Column 0 Width=63
|
||||
RefScale=14
|
||||
Column 0 Width=43
|
||||
Column 1 Weight=1.0000
|
||||
Column 2 Width=152
|
||||
Column 3 Width=60
|
||||
Column 2 Width=106
|
||||
Column 3 Width=42
|
||||
|
||||
[Table][0xD99F45C5,4]
|
||||
Column 0 Sort=0v
|
||||
@@ -425,28 +425,28 @@ Column 1 Width=100
|
||||
Column 2 Weight=1.0000
|
||||
|
||||
[Table][0xA02D8C87,3]
|
||||
RefScale=20
|
||||
Column 0 Width=227
|
||||
Column 1 Width=150
|
||||
RefScale=14
|
||||
Column 0 Width=158
|
||||
Column 1 Width=105
|
||||
Column 2 Weight=1.0000
|
||||
|
||||
[Docking][Data]
|
||||
DockNode ID=0x00000008 Pos=3125,170 Size=593,1157 Split=Y
|
||||
DockNode ID=0x00000009 Parent=0x00000008 SizeRef=1029,147 Selected=0x0469CA7A
|
||||
DockNode ID=0x0000000A Parent=0x00000008 SizeRef=1029,145 Selected=0xDF822E02
|
||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,22 Size=2560,1395 Split=X
|
||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=1955,1183 Split=X
|
||||
DockSpace ID=0xAFC85805 Window=0x079D3A04 Pos=0,22 Size=3840,2115 Split=X
|
||||
DockNode ID=0x00000003 Parent=0xAFC85805 SizeRef=2639,1183 Split=X
|
||||
DockNode ID=0x0000000B Parent=0x00000003 SizeRef=404,1186 Split=X Selected=0xF4139CA2
|
||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=623,858 Split=Y Selected=0x8CA2375C
|
||||
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,989 CentralNode=1 Selected=0x7BD57D6A
|
||||
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,401 Selected=0x1DCB2623
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1330,858 Split=X Selected=0x418C7449
|
||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=669,402 Selected=0x418C7449
|
||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=659,402 Selected=0x6F2B5B04
|
||||
DockNode ID=0x00000007 Parent=0x0000000B SizeRef=840,858 Split=Y Selected=0x8CA2375C
|
||||
DockNode ID=0x00000001 Parent=0x00000007 SizeRef=824,1646 CentralNode=1 Selected=0x7BD57D6A
|
||||
DockNode ID=0x00000002 Parent=0x00000007 SizeRef=824,467 Selected=0x1DCB2623
|
||||
DockNode ID=0x0000000E Parent=0x0000000B SizeRef=1797,858 Split=X Selected=0x418C7449
|
||||
DockNode ID=0x00000012 Parent=0x0000000E SizeRef=824,402 Selected=0x418C7449
|
||||
DockNode ID=0x00000013 Parent=0x0000000E SizeRef=971,402 Selected=0x6F2B5B04
|
||||
DockNode ID=0x0000000D Parent=0x00000003 SizeRef=435,1186 Selected=0x363E93D6
|
||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=603,1183 Split=Y Selected=0x3AEC3498
|
||||
DockNode ID=0x0000000C Parent=0x00000004 SizeRef=1074,1208 Selected=0x2C0206CE
|
||||
DockNode ID=0x0000000F Parent=0x00000004 SizeRef=1074,899 Selected=0x5CDB7A4B
|
||||
DockNode ID=0x00000004 Parent=0xAFC85805 SizeRef=1199,1183 Split=Y Selected=0x3AEC3498
|
||||
DockNode ID=0x0000000C Parent=0x00000004 SizeRef=1074,1208 Selected=0x3AEC3498
|
||||
DockNode ID=0x0000000F Parent=0x00000004 SizeRef=1074,899 Selected=0x655BC6E9
|
||||
|
||||
;;;<<<Layout_655921752_Default>>>;;;
|
||||
;;;<<<HelloImGui_Misc>>>;;;
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
[personas.Default]
|
||||
system_prompt = ""
|
||||
provider = "minimax"
|
||||
tool_preset = "Default"
|
||||
bias_profile = "Balanced"
|
||||
|
||||
[[personas.Default.preferred_models]]
|
||||
model = "MiniMax-M2.5"
|
||||
preferred_models = [
|
||||
"MiniMax-M2.5",
|
||||
]
|
||||
provider = "minimax"
|
||||
temperature = 0.0
|
||||
top_p = 1.0
|
||||
max_output_tokens = 32000
|
||||
history_trunc_limit = 900000
|
||||
|
||||
[[personas.Default.preferred_models]]
|
||||
provider = "gemini_cli"
|
||||
model = "gemini-3-flash-preview"
|
||||
temperature = -1.4901161193847656e-08
|
||||
max_output_tokens = 32000
|
||||
history_trunc_limit = 900000
|
||||
|
||||
@@ -34,13 +34,8 @@ def migrate():
|
||||
preset = models.Preset.from_dict(name, data)
|
||||
persona = models.Persona(
|
||||
name=name,
|
||||
provider=provider,
|
||||
model=model,
|
||||
preferred_models=[model] if model else [],
|
||||
system_prompt=preset.system_prompt,
|
||||
temperature=preset.temperature,
|
||||
top_p=preset.top_p,
|
||||
max_output_tokens=preset.max_output_tokens
|
||||
preferred_models=[{"provider": provider, "model": model}],
|
||||
system_prompt=preset.system_prompt
|
||||
)
|
||||
persona_manager.save_persona(persona, scope="global")
|
||||
print(f"Migrated global preset to persona: {name}")
|
||||
@@ -50,12 +45,13 @@ def migrate():
|
||||
if active_preset and active_preset not in persona_manager.load_all():
|
||||
persona = models.Persona(
|
||||
name=active_preset,
|
||||
provider=provider,
|
||||
model=model,
|
||||
preferred_models=[model] if model else [],
|
||||
system_prompt=ai_cfg.get("system_prompt", ""),
|
||||
temperature=ai_cfg.get("temperature"),
|
||||
max_output_tokens=ai_cfg.get("max_tokens")
|
||||
preferred_models=[{
|
||||
"provider": provider,
|
||||
"model": model,
|
||||
"temperature": ai_cfg.get("temperature"),
|
||||
"max_output_tokens": ai_cfg.get("max_tokens")
|
||||
}],
|
||||
system_prompt=ai_cfg.get("system_prompt", "")
|
||||
)
|
||||
persona_manager.save_persona(persona, scope="global")
|
||||
print(f"Created Initial Legacy persona from active_preset: {active_preset}")
|
||||
|
||||
252
scripts/refactor_ai_settings_2.py
Normal file
252
scripts/refactor_ai_settings_2.py
Normal file
@@ -0,0 +1,252 @@
|
||||
import sys
|
||||
|
||||
with open("src/gui_2.py", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# 1. In _render_provider_panel, remove Fetch Models
|
||||
old_fetch = """ imgui.text("Model")
|
||||
imgui.same_line()
|
||||
if imgui.button("Fetch Models"):
|
||||
self._fetch_models(self.current_provider)
|
||||
if imgui.begin_list_box("##models", imgui.ImVec2(-1, 120)):"""
|
||||
new_fetch = """ imgui.text("Model")
|
||||
if imgui.begin_list_box("##models", imgui.ImVec2(-1, 120)):"""
|
||||
content = content.replace(old_fetch, new_fetch)
|
||||
|
||||
# 2. Extract Persona block
|
||||
# We need to find the start of 'imgui.text("Persona")' and end of 'self._editing_persona_is_new = True'
|
||||
# Let's be very careful.
|
||||
old_persona_block = """ imgui.text("Persona")
|
||||
if not hasattr(self, 'ui_active_persona'):
|
||||
self.ui_active_persona = ""
|
||||
personas = getattr(self.controller, 'personas', {})
|
||||
if imgui.begin_combo("##persona", self.ui_active_persona or "None"):
|
||||
if imgui.selectable("None", not self.ui_active_persona)[0]:
|
||||
self.ui_active_persona = ""
|
||||
for pname in sorted(personas.keys()):
|
||||
if imgui.selectable(pname, pname == self.ui_active_persona)[0]:
|
||||
self.ui_active_persona = pname
|
||||
if pname in personas:
|
||||
persona = personas[pname]
|
||||
self._editing_persona_name = persona.name
|
||||
self._editing_persona_provider = persona.provider or ""
|
||||
self._editing_persona_model = persona.model or ""
|
||||
self._editing_persona_system_prompt = persona.system_prompt or ""
|
||||
self._editing_persona_temperature = persona.temperature or 0.7
|
||||
self._editing_persona_max_tokens = persona.max_output_tokens or 4096
|
||||
self._editing_persona_tool_preset_id = persona.tool_preset or ""
|
||||
self._editing_persona_bias_profile_id = persona.bias_profile or ""
|
||||
import json
|
||||
self._editing_persona_preferred_models = json.dumps(persona.preferred_models) if persona.preferred_models else "[]"
|
||||
self._editing_persona_is_new = False
|
||||
if persona.provider and persona.provider in self.controller.PROVIDERS:
|
||||
self.current_provider = persona.provider
|
||||
if persona.model:
|
||||
self.current_model = persona.model
|
||||
if persona.temperature is not None:
|
||||
ai_client.temperature = persona.temperature
|
||||
if persona.max_output_tokens:
|
||||
ai_client.max_output_tokens = persona.max_output_tokens
|
||||
if persona.system_prompt:
|
||||
ai_client.system_instruction = persona.system_prompt
|
||||
if persona.tool_preset:
|
||||
self.ui_active_tool_preset = persona.tool_preset
|
||||
ai_client.set_tool_preset(persona.tool_preset)
|
||||
if persona.bias_profile:
|
||||
self.ui_active_bias_profile = persona.bias_profile
|
||||
ai_client.set_bias_profile(persona.bias_profile)
|
||||
imgui.end_combo()
|
||||
imgui.same_line()
|
||||
if imgui.button("Manage Personas"):
|
||||
self.show_persona_editor_window = True
|
||||
if self.ui_active_persona and self.ui_active_persona in personas:
|
||||
persona = personas[self.ui_active_persona]
|
||||
self._editing_persona_name = persona.name
|
||||
self._editing_persona_provider = persona.provider or ""
|
||||
self._editing_persona_model = persona.model or ""
|
||||
self._editing_persona_system_prompt = persona.system_prompt or ""
|
||||
self._editing_persona_temperature = persona.temperature if persona.temperature is not None else 0.7
|
||||
self._editing_persona_max_tokens = persona.max_output_tokens if persona.max_output_tokens is not None else 4096
|
||||
self._editing_persona_tool_preset_id = persona.tool_preset or ""
|
||||
self._editing_persona_bias_profile_id = persona.bias_profile or ""
|
||||
self._editing_persona_preferred_models_list = list(persona.preferred_models) if persona.preferred_models else []
|
||||
self._editing_persona_scope = self.controller.persona_manager.get_persona_scope(persona.name)
|
||||
self._editing_persona_is_new = False
|
||||
else:
|
||||
self._editing_persona_name = ""
|
||||
self._editing_persona_provider = self.current_provider
|
||||
self._editing_persona_model = self.current_model
|
||||
self._editing_persona_system_prompt = ""
|
||||
self._editing_persona_temperature = 0.7
|
||||
self._editing_persona_max_tokens = 4096
|
||||
self._editing_persona_tool_preset_id = ""
|
||||
self._editing_persona_bias_profile_id = ""
|
||||
self._editing_persona_preferred_models_list = []
|
||||
self._editing_persona_scope = "project"
|
||||
self._editing_persona_is_new = True"""
|
||||
|
||||
# We need to extract the bias profile block as well
|
||||
old_bias_block = """ 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()"""
|
||||
|
||||
# Remove them from their original spots
|
||||
content = content.replace(old_bias_block, "")
|
||||
content = content.replace(old_persona_block, "")
|
||||
|
||||
# Insert Persona block at the top of _render_provider_panel
|
||||
old_provider_start = """ def _render_provider_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel")
|
||||
imgui.text("Provider")"""
|
||||
new_provider_start = f""" def _render_provider_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel")
|
||||
{old_persona_block}
|
||||
imgui.separator()
|
||||
imgui.text("Provider")"""
|
||||
content = content.replace(old_provider_start, new_provider_start)
|
||||
|
||||
# Update _render_agent_tools_panel
|
||||
old_agent_tools_start = """ def _render_agent_tools_panel(self) -> None:
|
||||
imgui.text_colored(C_LBL, 'Active Tool Preset')"""
|
||||
new_agent_tools_start = f""" def _render_agent_tools_panel(self) -> None:
|
||||
if imgui.collapsing_header("Active Tool Presets & Biases", imgui.TreeNodeFlags_.default_open):
|
||||
imgui.text("Tool Preset")"""
|
||||
content = content.replace(old_agent_tools_start, new_agent_tools_start)
|
||||
|
||||
# Wait, if I do collapsing header, I need to indent the rest of the function.
|
||||
# Instead of indenting the whole function, I can just use the header.
|
||||
# But wait, ImGui collapsing_header doesn't require indenting, it just returns true if open.
|
||||
# So I should write it properly:
|
||||
old_agent_tools_func = """ def _render_agent_tools_panel(self) -> None:
|
||||
imgui.text_colored(C_LBL, 'Active Tool Preset')
|
||||
presets = self.controller.tool_presets
|
||||
preset_names = [""] + sorted(list(presets.keys()))
|
||||
|
||||
# Gracefully handle None or missing preset
|
||||
active = getattr(self, "ui_active_tool_preset", "")
|
||||
if active is None: active = ""
|
||||
try:
|
||||
idx = preset_names.index(active)
|
||||
except ValueError:
|
||||
idx = 0
|
||||
|
||||
ch, new_idx = imgui.combo("##tool_preset_select", idx, preset_names)
|
||||
if ch:
|
||||
self.ui_active_tool_preset = preset_names[new_idx]
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Manage Presets##tools"):
|
||||
self.show_tool_preset_manager_window = True
|
||||
if imgui.is_item_hovered():
|
||||
imgui.set_tooltip("Configure tool availability and default modes.")
|
||||
|
||||
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 imgui.tree_node(cat_name):
|
||||
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()
|
||||
|
||||
imgui.text(tool.name)
|
||||
imgui.same_line(180)
|
||||
|
||||
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}_{tool.name}", mode == "ask"):
|
||||
tool.approval = "ask"
|
||||
imgui.tree_pop()"""
|
||||
|
||||
new_agent_tools_func = """ def _render_agent_tools_panel(self) -> None:
|
||||
if imgui.collapsing_header("Active Tool Presets & Biases", imgui.TreeNodeFlags_.default_open):
|
||||
imgui.text("Tool Preset")
|
||||
presets = self.controller.tool_presets
|
||||
preset_names = [""] + sorted(list(presets.keys()))
|
||||
|
||||
# Gracefully handle None or missing preset
|
||||
active = getattr(self, "ui_active_tool_preset", "")
|
||||
if active is None: active = ""
|
||||
try:
|
||||
idx = preset_names.index(active)
|
||||
except ValueError:
|
||||
idx = 0
|
||||
|
||||
ch, new_idx = imgui.combo("##tool_preset_select", idx, preset_names)
|
||||
if ch:
|
||||
self.ui_active_tool_preset = preset_names[new_idx]
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Manage Tools##tools"):
|
||||
self.show_tool_preset_manager_window = True
|
||||
if imgui.is_item_hovered():
|
||||
imgui.set_tooltip("Configure tool availability and default modes.")
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 4))
|
||||
""" + "\n ".join(old_bias_block.split("\n")) + """
|
||||
|
||||
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 imgui.tree_node(cat_name):
|
||||
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()
|
||||
|
||||
imgui.text(tool.name)
|
||||
imgui.same_line(180)
|
||||
|
||||
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}_{tool.name}", mode == "ask"):
|
||||
tool.approval = "ask"
|
||||
imgui.tree_pop()"""
|
||||
content = content.replace(old_agent_tools_func, new_agent_tools_func)
|
||||
|
||||
# Fix cache text display in Usage Analytics
|
||||
content = content.replace('self._gemini_cache_text = f"Gemini Caches: {count} ({size_bytes / 1024:.1f} KB)"', 'self._gemini_cache_text = f"Cache Usage: {count} ({size_bytes / 1024:.1f} KB)"')
|
||||
content = content.replace('imgui.text_colored(C_LBL, f"Gemini Cache: ACTIVE | Age: {age:.0f}s / {ttl}s | Renews at: {ttl * 0.9:.0f}s")', 'imgui.text_colored(C_LBL, f"Cache Usage: ACTIVE | Age: {age:.0f}s / {ttl}s | Renews at: {ttl * 0.9:.0f}s")')
|
||||
content = content.replace('imgui.text_disabled("Gemini Cache: INACTIVE")', 'imgui.text_disabled("Cache Usage: INACTIVE")')
|
||||
|
||||
# Also, user requested: "The persona should problably just mess with the project system prompt for now."
|
||||
# Currently in persona selection: `ai_client.system_instruction = persona.system_prompt`
|
||||
# Let's change that to `self.ui_project_system_prompt = persona.system_prompt` and remove ai_client direct injection
|
||||
content = content.replace('ai_client.system_instruction = persona.system_prompt', 'self.ui_project_system_prompt = persona.system_prompt')
|
||||
|
||||
with open("src/gui_2.py", "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
print("done")
|
||||
228
scripts/refactor_ai_settings_3.py
Normal file
228
scripts/refactor_ai_settings_3.py
Normal file
@@ -0,0 +1,228 @@
|
||||
import sys
|
||||
|
||||
with open("src/gui_2.py", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# 1. Update _gui_func:
|
||||
# Extract Persona out of Provider panel. I will create a new method _render_persona_selector_panel
|
||||
old_gui_settings = """ if self.show_windows.get("AI Settings", False):
|
||||
exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"])
|
||||
self.show_windows["AI Settings"] = bool(opened)
|
||||
if exp:
|
||||
if imgui.collapsing_header("Provider & Model"):
|
||||
self._render_provider_panel()
|
||||
if imgui.collapsing_header("System Prompts"):
|
||||
self._render_system_prompts_panel()
|
||||
self._render_agent_tools_panel()
|
||||
self._render_cache_panel()
|
||||
|
||||
imgui.end()
|
||||
if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False):
|
||||
exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"])
|
||||
self.show_windows["Usage Analytics"] = bool(opened)
|
||||
if exp:
|
||||
self._render_usage_analytics_panel()
|
||||
imgui.end()"""
|
||||
|
||||
new_gui_settings = """ if self.show_windows.get("AI Settings", False):
|
||||
exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"])
|
||||
self.show_windows["AI Settings"] = bool(opened)
|
||||
if exp:
|
||||
self._render_persona_selector_panel()
|
||||
if imgui.collapsing_header("Provider & Model"):
|
||||
self._render_provider_panel()
|
||||
if imgui.collapsing_header("System Prompts"):
|
||||
self._render_system_prompts_panel()
|
||||
self._render_agent_tools_panel()
|
||||
|
||||
imgui.end()
|
||||
if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False):
|
||||
exp, opened = imgui.begin("Usage Analytics", self.show_windows["Usage Analytics"])
|
||||
self.show_windows["Usage Analytics"] = bool(opened)
|
||||
if exp:
|
||||
self._render_usage_analytics_panel()
|
||||
imgui.end()"""
|
||||
|
||||
content = content.replace(old_gui_settings, new_gui_settings)
|
||||
|
||||
# Update _render_usage_analytics_panel
|
||||
old_usage = """ def _render_usage_analytics_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_usage_analytics_panel")
|
||||
self._render_token_budget_panel()
|
||||
imgui.separator()
|
||||
self._render_tool_analytics_panel()
|
||||
imgui.separator()
|
||||
self._render_session_insights_panel()
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_usage_analytics_panel")"""
|
||||
|
||||
new_usage = """ def _render_usage_analytics_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_usage_analytics_panel")
|
||||
self._render_token_budget_panel()
|
||||
imgui.separator()
|
||||
self._render_cache_panel()
|
||||
imgui.separator()
|
||||
self._render_tool_analytics_panel()
|
||||
imgui.separator()
|
||||
self._render_session_insights_panel()
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_usage_analytics_panel")"""
|
||||
content = content.replace(old_usage, new_usage)
|
||||
|
||||
# Remove the persona block from _render_provider_panel and put it in _render_persona_selector_panel
|
||||
old_persona_block = """ def _render_provider_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel")
|
||||
imgui.text("Persona")
|
||||
if not hasattr(self, 'ui_active_persona'):
|
||||
self.ui_active_persona = ""
|
||||
personas = getattr(self.controller, 'personas', {})
|
||||
if imgui.begin_combo("##persona", self.ui_active_persona or "None"):
|
||||
if imgui.selectable("None", not self.ui_active_persona)[0]:
|
||||
self.ui_active_persona = ""
|
||||
for pname in sorted(personas.keys()):
|
||||
if imgui.selectable(pname, pname == self.ui_active_persona)[0]:
|
||||
self.ui_active_persona = pname
|
||||
if pname in personas:
|
||||
persona = personas[pname]
|
||||
self._editing_persona_name = persona.name
|
||||
self._editing_persona_provider = persona.provider or ""
|
||||
self._editing_persona_model = persona.model or ""
|
||||
self._editing_persona_system_prompt = persona.system_prompt or ""
|
||||
self._editing_persona_temperature = persona.temperature or 0.7
|
||||
self._editing_persona_max_tokens = persona.max_output_tokens or 4096
|
||||
self._editing_persona_tool_preset_id = persona.tool_preset or ""
|
||||
self._editing_persona_bias_profile_id = persona.bias_profile or ""
|
||||
import json
|
||||
self._editing_persona_preferred_models = json.dumps(persona.preferred_models) if persona.preferred_models else "[]"
|
||||
self._editing_persona_is_new = False
|
||||
if persona.provider and persona.provider in self.controller.PROVIDERS:
|
||||
self.current_provider = persona.provider
|
||||
if persona.model:
|
||||
self.current_model = persona.model
|
||||
if persona.temperature is not None:
|
||||
ai_client.temperature = persona.temperature
|
||||
if persona.max_output_tokens:
|
||||
ai_client.max_output_tokens = persona.max_output_tokens
|
||||
if persona.system_prompt:
|
||||
self.ui_project_system_prompt = persona.system_prompt
|
||||
if persona.tool_preset:
|
||||
self.ui_active_tool_preset = persona.tool_preset
|
||||
ai_client.set_tool_preset(persona.tool_preset)
|
||||
if persona.bias_profile:
|
||||
self.ui_active_bias_profile = persona.bias_profile
|
||||
ai_client.set_bias_profile(persona.bias_profile)
|
||||
imgui.end_combo()
|
||||
imgui.same_line()
|
||||
if imgui.button("Manage Personas"):
|
||||
self.show_persona_editor_window = True
|
||||
if self.ui_active_persona and self.ui_active_persona in personas:
|
||||
persona = personas[self.ui_active_persona]
|
||||
self._editing_persona_name = persona.name
|
||||
self._editing_persona_provider = persona.provider or ""
|
||||
self._editing_persona_model = persona.model or ""
|
||||
self._editing_persona_system_prompt = persona.system_prompt or ""
|
||||
self._editing_persona_temperature = persona.temperature if persona.temperature is not None else 0.7
|
||||
self._editing_persona_max_tokens = persona.max_output_tokens if persona.max_output_tokens is not None else 4096
|
||||
self._editing_persona_tool_preset_id = persona.tool_preset or ""
|
||||
self._editing_persona_bias_profile_id = persona.bias_profile or ""
|
||||
self._editing_persona_preferred_models_list = list(persona.preferred_models) if persona.preferred_models else []
|
||||
self._editing_persona_scope = self.controller.persona_manager.get_persona_scope(persona.name)
|
||||
self._editing_persona_is_new = False
|
||||
else:
|
||||
self._editing_persona_name = ""
|
||||
self._editing_persona_provider = self.current_provider
|
||||
self._editing_persona_model = self.current_model
|
||||
self._editing_persona_system_prompt = ""
|
||||
self._editing_persona_temperature = 0.7
|
||||
self._editing_persona_max_tokens = 4096
|
||||
self._editing_persona_tool_preset_id = ""
|
||||
self._editing_persona_bias_profile_id = ""
|
||||
self._editing_persona_preferred_models_list = []
|
||||
self._editing_persona_scope = "project"
|
||||
self._editing_persona_is_new = True
|
||||
imgui.separator()
|
||||
imgui.text("Provider")"""
|
||||
|
||||
new_persona_block = """ def _render_persona_selector_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_persona_selector_panel")
|
||||
imgui.text("Persona")
|
||||
if not hasattr(self, 'ui_active_persona'):
|
||||
self.ui_active_persona = ""
|
||||
personas = getattr(self.controller, 'personas', {})
|
||||
if imgui.begin_combo("##persona", self.ui_active_persona or "None"):
|
||||
if imgui.selectable("None", not self.ui_active_persona)[0]:
|
||||
self.ui_active_persona = ""
|
||||
for pname in sorted(personas.keys()):
|
||||
if imgui.selectable(pname, pname == self.ui_active_persona)[0]:
|
||||
self.ui_active_persona = pname
|
||||
if pname in personas:
|
||||
persona = personas[pname]
|
||||
self._editing_persona_name = persona.name
|
||||
self._editing_persona_system_prompt = persona.system_prompt or ""
|
||||
self._editing_persona_tool_preset_id = persona.tool_preset or ""
|
||||
self._editing_persona_bias_profile_id = persona.bias_profile or ""
|
||||
import copy
|
||||
self._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
|
||||
self._editing_persona_is_new = False
|
||||
|
||||
# Apply persona to current state immediately
|
||||
if persona.preferred_models and len(persona.preferred_models) > 0:
|
||||
first_model = persona.preferred_models[0]
|
||||
if first_model.get("provider"):
|
||||
self.current_provider = first_model.get("provider")
|
||||
if first_model.get("model"):
|
||||
self.current_model = first_model.get("model")
|
||||
if first_model.get("temperature") is not None:
|
||||
ai_client.temperature = first_model.get("temperature")
|
||||
self.temperature = first_model.get("temperature")
|
||||
if first_model.get("max_output_tokens"):
|
||||
ai_client.max_output_tokens = first_model.get("max_output_tokens")
|
||||
self.max_tokens = first_model.get("max_output_tokens")
|
||||
if first_model.get("history_trunc_limit"):
|
||||
self.history_trunc_limit = first_model.get("history_trunc_limit")
|
||||
|
||||
if persona.system_prompt:
|
||||
self.ui_project_system_prompt = persona.system_prompt
|
||||
if persona.tool_preset:
|
||||
self.ui_active_tool_preset = persona.tool_preset
|
||||
ai_client.set_tool_preset(persona.tool_preset)
|
||||
if persona.bias_profile:
|
||||
self.ui_active_bias_profile = persona.bias_profile
|
||||
ai_client.set_bias_profile(persona.bias_profile)
|
||||
imgui.end_combo()
|
||||
imgui.same_line()
|
||||
if imgui.button("Manage Personas"):
|
||||
self.show_persona_editor_window = True
|
||||
if self.ui_active_persona and self.ui_active_persona in personas:
|
||||
persona = personas[self.ui_active_persona]
|
||||
self._editing_persona_name = persona.name
|
||||
self._editing_persona_system_prompt = persona.system_prompt or ""
|
||||
self._editing_persona_tool_preset_id = persona.tool_preset or ""
|
||||
self._editing_persona_bias_profile_id = persona.bias_profile or ""
|
||||
import copy
|
||||
self._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
|
||||
self._editing_persona_scope = self.controller.persona_manager.get_persona_scope(persona.name)
|
||||
self._editing_persona_is_new = False
|
||||
else:
|
||||
self._editing_persona_name = ""
|
||||
self._editing_persona_system_prompt = ""
|
||||
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": getattr(self, "temperature", 0.7),
|
||||
"max_output_tokens": getattr(self, "max_tokens", 4096),
|
||||
"history_trunc_limit": getattr(self, "history_trunc_limit", 900000)
|
||||
}]
|
||||
self._editing_persona_scope = "project"
|
||||
self._editing_persona_is_new = True
|
||||
imgui.separator()
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_persona_selector_panel")
|
||||
|
||||
def _render_provider_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel")
|
||||
imgui.text("Provider")"""
|
||||
content = content.replace(old_persona_block, new_persona_block)
|
||||
|
||||
with open("src/gui_2.py", "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
print("done gui updates")
|
||||
@@ -363,3 +363,4 @@ def main() -> None:
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@@ -2400,3 +2400,4 @@ def get_history_bleed_stats(md_content: Optional[str] = None) -> dict[str, Any]:
|
||||
"current": 0,
|
||||
"percentage": 0,
|
||||
})
|
||||
|
||||
|
||||
@@ -223,3 +223,4 @@ class ApiHookClient:
|
||||
def get_patch_status(self) -> dict[str, Any]:
|
||||
"""Gets the current patch modal status."""
|
||||
return self._make_request('GET', '/api/patch/status') or {}
|
||||
|
||||
|
||||
@@ -523,3 +523,4 @@ class HookServer:
|
||||
if self.thread:
|
||||
self.thread.join()
|
||||
logging.info("Hook server stopped")
|
||||
|
||||
|
||||
@@ -300,7 +300,9 @@ class AppController:
|
||||
self._inject_mode: str = "skeleton"
|
||||
self._inject_preview: str = ""
|
||||
self._show_inject_modal: bool = False
|
||||
self.show_preset_manager_modal: bool = False
|
||||
self.show_preset_manager_window: bool = False
|
||||
self.show_tool_preset_manager_window: bool = False
|
||||
self.show_persona_editor_window: bool = False
|
||||
self._editing_preset_name: str = ""
|
||||
self._editing_preset_content: str = ""
|
||||
self._editing_preset_temperature: float = 0.0
|
||||
@@ -342,7 +344,9 @@ class AppController:
|
||||
'ui_active_tool_preset': 'ui_active_tool_preset',
|
||||
'temperature': 'temperature',
|
||||
'max_tokens': 'max_tokens',
|
||||
'show_preset_manager_modal': 'show_preset_manager_modal',
|
||||
'show_preset_manager_window': 'show_preset_manager_window',
|
||||
'show_tool_preset_manager_window': 'show_tool_preset_manager_window',
|
||||
'show_persona_editor_window': 'show_persona_editor_window',
|
||||
'_editing_preset_name': '_editing_preset_name',
|
||||
'_editing_preset_content': '_editing_preset_content',
|
||||
'_editing_preset_temperature': '_editing_preset_temperature',
|
||||
@@ -390,7 +394,9 @@ class AppController:
|
||||
'ui_active_tool_preset': 'ui_active_tool_preset',
|
||||
'temperature': 'temperature',
|
||||
'max_tokens': 'max_tokens',
|
||||
'show_preset_manager_modal': 'show_preset_manager_modal',
|
||||
'show_preset_manager_window': 'show_preset_manager_window',
|
||||
'show_tool_preset_manager_window': 'show_tool_preset_manager_window',
|
||||
'show_persona_editor_window': 'show_persona_editor_window',
|
||||
'_editing_preset_name': '_editing_preset_name',
|
||||
'_editing_preset_content': '_editing_preset_content',
|
||||
'_editing_preset_temperature': '_editing_preset_temperature',
|
||||
@@ -877,6 +883,8 @@ class AppController:
|
||||
self.persona_manager = PersonaManager(Path(self.active_project_path).parent if self.active_project_path else None)
|
||||
self.personas = self.persona_manager.load_all()
|
||||
|
||||
self._fetch_models(self.current_provider)
|
||||
|
||||
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)
|
||||
@@ -1490,6 +1498,9 @@ class AppController:
|
||||
self._current_provider = value
|
||||
ai_client.reset_session()
|
||||
ai_client.set_provider(value, self.current_model)
|
||||
self.available_models = self.all_available_models.get(value, [])
|
||||
if not self.available_models:
|
||||
self._fetch_models(value)
|
||||
self._token_stats = {}
|
||||
self._token_stats_dirty = True
|
||||
|
||||
@@ -1859,20 +1870,13 @@ class AppController:
|
||||
else:
|
||||
self.ui_project_system_prompt = preset.system_prompt
|
||||
self.ui_project_preset_name = name
|
||||
if preset.temperature is not None:
|
||||
self.temperature = preset.temperature
|
||||
if preset.max_output_tokens is not None:
|
||||
self.max_tokens = preset.max_output_tokens
|
||||
|
||||
def _cb_save_preset(self, name, content, temp, top_p, max_tok, scope):
|
||||
def _cb_save_preset(self, name, content, scope):
|
||||
if not name or not name.strip():
|
||||
raise ValueError("Preset name cannot be empty or whitespace.")
|
||||
preset = models.Preset(
|
||||
name=name,
|
||||
system_prompt=content,
|
||||
temperature=temp,
|
||||
top_p=top_p,
|
||||
max_output_tokens=max_tok
|
||||
system_prompt=content
|
||||
)
|
||||
self.preset_manager.save_preset(preset, scope)
|
||||
self.presets = self.preset_manager.load_all()
|
||||
@@ -2574,3 +2578,4 @@ class AppController:
|
||||
tasks=self.active_track.tickets
|
||||
)
|
||||
project_manager.save_track_state(self.active_track.id, state, self.ui_files_base_dir)
|
||||
|
||||
|
||||
@@ -63,3 +63,4 @@ def get_bg():
|
||||
if _bg is None:
|
||||
_bg = BackgroundShader()
|
||||
return _bg
|
||||
|
||||
|
||||
@@ -118,3 +118,4 @@ if __name__ == "__main__":
|
||||
test_skeletons = "class NewFeature: pass"
|
||||
tickets = generate_tickets(test_brief, test_skeletons)
|
||||
print(json.dumps(tickets, indent=2))
|
||||
|
||||
|
||||
@@ -59,3 +59,4 @@ def estimate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
|
||||
return input_cost + output_cost
|
||||
|
||||
return 0.0
|
||||
|
||||
|
||||
@@ -193,3 +193,4 @@ class ExecutionEngine:
|
||||
ticket = self.dag.ticket_map.get(task_id)
|
||||
if ticket:
|
||||
ticket.status = status
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
from dataclasses import dataclass
|
||||
import shutil
|
||||
import os
|
||||
|
||||
@@ -126,3 +126,4 @@ class UserRequestEvent:
|
||||
"disc_text": self.disc_text,
|
||||
"base_dir": self.base_dir
|
||||
}
|
||||
|
||||
|
||||
@@ -376,3 +376,4 @@ def evict(path: Path) -> None:
|
||||
def list_cached() -> List[Dict[str, Any]]:
|
||||
return []
|
||||
|
||||
|
||||
|
||||
@@ -189,3 +189,4 @@ class GeminiCliAdapter:
|
||||
"""
|
||||
total_chars = len("\n".join(contents))
|
||||
return total_chars // 4
|
||||
|
||||
|
||||
609
src/gui_2.py
609
src/gui_2.py
@@ -96,9 +96,9 @@ class App:
|
||||
self.controller.init_state()
|
||||
self.show_windows.setdefault("Diagnostics", False)
|
||||
self.controller.start_services(self)
|
||||
self.show_preset_manager_modal = False
|
||||
self.show_tool_preset_manager_modal = False
|
||||
self.show_persona_editor_modal = False
|
||||
self.show_preset_manager_window = False
|
||||
self.show_tool_preset_manager_window = False
|
||||
self.show_persona_editor_window = False
|
||||
self.ui_active_tool_preset = ""
|
||||
self.ui_active_bias_profile = ""
|
||||
self.ui_active_persona = ""
|
||||
@@ -111,23 +111,22 @@ class App:
|
||||
self._editing_persona_max_tokens = 4096
|
||||
self._editing_persona_tool_preset_id = ""
|
||||
self._editing_persona_bias_profile_id = ""
|
||||
self._editing_persona_preferred_models = "[]"
|
||||
self._editing_persona_tier_assignments = "{}"
|
||||
self._editing_persona_preferred_models_list: list[dict] = []
|
||||
self._editing_persona_scope = "project"
|
||||
self._editing_persona_is_new = True
|
||||
self._persona_editor_opened = False
|
||||
self._personas_list: dict[str, dict] = {}
|
||||
self._editing_bias_profile_name = ""
|
||||
self._editing_bias_profile_tool_weights = "" # JSON
|
||||
self._editing_bias_profile_cat_mults = "" # JSON
|
||||
self._editing_bias_profile_tool_weights: dict[str, int] = {}
|
||||
self._editing_bias_profile_category_multipliers: dict[str, float] = {}
|
||||
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._new_bias_tool_name = "run_powershell"
|
||||
self._new_bias_category_name = "General"
|
||||
self._editing_preset_name = ""
|
||||
self._editing_preset_content = ""
|
||||
self._editing_preset_temperature = 0.0
|
||||
@@ -385,9 +384,9 @@ class App:
|
||||
self._render_track_proposal_modal()
|
||||
self._render_patch_modal()
|
||||
self._render_save_preset_modal()
|
||||
self._render_preset_manager_modal()
|
||||
self._render_tool_preset_manager_modal()
|
||||
self._render_persona_editor_modal()
|
||||
self._render_preset_manager_window()
|
||||
self._render_tool_preset_manager_window()
|
||||
self._render_persona_editor_window()
|
||||
# Auto-save (every 60s)
|
||||
now = time.time()
|
||||
if now - self._last_autosave >= self._autosave_interval:
|
||||
@@ -452,12 +451,12 @@ class App:
|
||||
exp, opened = imgui.begin("AI Settings", self.show_windows["AI Settings"])
|
||||
self.show_windows["AI Settings"] = bool(opened)
|
||||
if exp:
|
||||
self._render_persona_selector_panel()
|
||||
if imgui.collapsing_header("Provider & Model"):
|
||||
self._render_provider_panel()
|
||||
if imgui.collapsing_header("System Prompts"):
|
||||
self._render_system_prompts_panel()
|
||||
self._render_agent_tools_panel()
|
||||
self._render_cache_panel()
|
||||
|
||||
imgui.end()
|
||||
if self.ui_separate_usage_analytics and self.show_windows.get("Usage Analytics", False):
|
||||
@@ -924,12 +923,7 @@ class App:
|
||||
imgui.close_current_popup()
|
||||
imgui.end_popup()
|
||||
|
||||
def _render_preset_manager_modal(self) -> None:
|
||||
if not self.show_preset_manager_modal: return
|
||||
imgui.open_popup("Preset Manager")
|
||||
opened, self.show_preset_manager_modal = imgui.begin_popup_modal("Preset Manager", self.show_preset_manager_modal)
|
||||
if opened:
|
||||
try:
|
||||
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)
|
||||
try:
|
||||
@@ -937,9 +931,6 @@ class App:
|
||||
if imgui.button("New Preset", imgui.ImVec2(-1, 0)):
|
||||
self._editing_preset_name = ""
|
||||
self._editing_preset_content = ""
|
||||
self._editing_preset_temperature = 0.0
|
||||
self._editing_preset_top_p = 1.0
|
||||
self._editing_preset_max_output_tokens = 4096
|
||||
self._editing_preset_scope = "project"
|
||||
self._editing_preset_is_new = True
|
||||
imgui.separator()
|
||||
@@ -949,9 +940,6 @@ class App:
|
||||
if imgui.selectable(name, is_sel)[0]:
|
||||
self._editing_preset_name = name
|
||||
self._editing_preset_content = p.system_prompt
|
||||
self._editing_preset_temperature = p.temperature if p.temperature is not None else 0.0
|
||||
self._editing_preset_top_p = p.top_p if p.top_p is not None else 1.0
|
||||
self._editing_preset_max_output_tokens = p.max_output_tokens if p.max_output_tokens is not None else 4096
|
||||
self._editing_preset_is_new = False
|
||||
finally:
|
||||
imgui.end_child()
|
||||
@@ -970,23 +958,13 @@ class App:
|
||||
if imgui.radio_button("Project", self._editing_preset_scope == "project"):
|
||||
self._editing_preset_scope = "project"
|
||||
imgui.text("Content:")
|
||||
_, self._editing_preset_content = imgui.input_text_multiline("##edit_content", self._editing_preset_content, imgui.ImVec2(-1, 280))
|
||||
|
||||
imgui.text("Temperature:")
|
||||
_, self._editing_preset_temperature = imgui.input_float("##edit_temp", self._editing_preset_temperature, 0.1, 1.0, "%.2f")
|
||||
imgui.text("Top P:")
|
||||
_, self._editing_preset_top_p = imgui.input_float("##edit_top_p", self._editing_preset_top_p, 0.1, 1.0, "%.2f")
|
||||
imgui.text("Max Output Tokens:")
|
||||
_, self._editing_preset_max_output_tokens = imgui.input_int("##edit_max_tokens", self._editing_preset_max_output_tokens)
|
||||
_, self._editing_preset_content = imgui.input_text_multiline("##edit_content", self._editing_preset_content, imgui.ImVec2(-1, -40))
|
||||
|
||||
if imgui.button("Save", imgui.ImVec2(120, 0)):
|
||||
if self._editing_preset_name.strip():
|
||||
self.controller._cb_save_preset(
|
||||
self._editing_preset_name.strip(),
|
||||
self._editing_preset_content,
|
||||
self._editing_preset_temperature,
|
||||
self._editing_preset_top_p,
|
||||
self._editing_preset_max_output_tokens,
|
||||
self._editing_preset_scope
|
||||
)
|
||||
self.ai_status = f"Preset '{self._editing_preset_name.strip()}' saved to {self._editing_preset_scope}"
|
||||
@@ -1002,21 +980,30 @@ class App:
|
||||
except Exception as e:
|
||||
self.ai_status = f"Error deleting: {e}"
|
||||
imgui.set_item_tooltip("Delete the selected preset")
|
||||
if not is_embedded:
|
||||
imgui.same_line()
|
||||
if imgui.button("Close", imgui.ImVec2(120, 0)):
|
||||
self.show_preset_manager_modal = False
|
||||
imgui.close_current_popup()
|
||||
self.show_preset_manager_window = False
|
||||
finally:
|
||||
imgui.end_child()
|
||||
finally:
|
||||
imgui.end_popup()
|
||||
|
||||
def _render_tool_preset_manager_modal(self) -> None:
|
||||
if not self.show_tool_preset_manager_modal: return
|
||||
imgui.open_popup("Tool Preset Manager")
|
||||
opened, self.show_tool_preset_manager_modal = imgui.begin_popup_modal("Tool Preset Manager", self.show_tool_preset_manager_modal)
|
||||
if opened:
|
||||
def _render_preset_manager_window(self, is_embedded: bool = False) -> None:
|
||||
if not self.show_preset_manager_window and not is_embedded: return
|
||||
|
||||
if not is_embedded:
|
||||
imgui.set_next_window_size(imgui.ImVec2(800, 600), imgui.Cond_.first_use_ever)
|
||||
opened, self.show_preset_manager_window = imgui.begin("Preset Manager", self.show_preset_manager_window)
|
||||
if not opened:
|
||||
imgui.end()
|
||||
return
|
||||
|
||||
try:
|
||||
self._render_preset_manager_content(is_embedded=is_embedded)
|
||||
finally:
|
||||
if not is_embedded:
|
||||
imgui.end()
|
||||
|
||||
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)
|
||||
@@ -1115,14 +1102,14 @@ class App:
|
||||
|
||||
imgui.separator()
|
||||
imgui.text_colored(C_SUB, "Bias Profiles")
|
||||
imgui.begin_child("bias_profiles_area", imgui.ImVec2(0, 200), True)
|
||||
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)
|
||||
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._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())
|
||||
@@ -1132,23 +1119,74 @@ class App:
|
||||
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)
|
||||
self._editing_bias_profile_tool_weights = copy.deepcopy(profile.tool_weights)
|
||||
self._editing_bias_profile_category_multipliers = copy.deepcopy(profile.category_multipliers)
|
||||
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))
|
||||
|
||||
imgui.text_colored(C_KEY, "Tool Weights:")
|
||||
to_remove_tw = []
|
||||
for tw_name, tw_val in list(self._editing_bias_profile_tool_weights.items()):
|
||||
imgui.text(f" {tw_name}:")
|
||||
imgui.same_line(150)
|
||||
imgui.set_next_item_width(100)
|
||||
changed, new_val = imgui.slider_int(f"##tw_{tw_name}", tw_val, 1, 10)
|
||||
if changed: self._editing_bias_profile_tool_weights[tw_name] = new_val
|
||||
imgui.same_line()
|
||||
if imgui.button(f"x##rem_tw_{tw_name}"):
|
||||
to_remove_tw.append(tw_name)
|
||||
for r in to_remove_tw: del self._editing_bias_profile_tool_weights[r]
|
||||
|
||||
# Add Tool Override
|
||||
imgui.set_next_item_width(150)
|
||||
if imgui.begin_combo("##add_tw_combo", self._new_bias_tool_name):
|
||||
for tn in models.AGENT_TOOL_NAMES:
|
||||
if tn not in self._editing_bias_profile_tool_weights:
|
||||
if imgui.selectable(tn, tn == self._new_bias_tool_name)[0]:
|
||||
self._new_bias_tool_name = tn
|
||||
imgui.end_combo()
|
||||
imgui.same_line()
|
||||
if imgui.button("Add Tool Override"):
|
||||
self._editing_bias_profile_tool_weights[self._new_bias_tool_name] = 5
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 4))
|
||||
imgui.text_colored(C_KEY, "Category Multipliers:")
|
||||
to_remove_cm = []
|
||||
for cm_name, cm_val in list(self._editing_bias_profile_category_multipliers.items()):
|
||||
imgui.text(f" {cm_name}:")
|
||||
imgui.same_line(150)
|
||||
imgui.set_next_item_width(100)
|
||||
changed, new_val = imgui.slider_float(f"##cm_{cm_name}", cm_val, 0.1, 5.0, "%.1fx")
|
||||
if changed: self._editing_bias_profile_category_multipliers[cm_name] = new_val
|
||||
imgui.same_line()
|
||||
if imgui.button(f"x##rem_cm_{cm_name}"):
|
||||
to_remove_cm.append(cm_name)
|
||||
for r in to_remove_cm: del self._editing_bias_profile_category_multipliers[r]
|
||||
|
||||
# Add Category Override
|
||||
imgui.set_next_item_width(150)
|
||||
cat_names = sorted(list(models.DEFAULT_TOOL_CATEGORIES.keys()))
|
||||
if imgui.begin_combo("##add_cm_combo", self._new_bias_category_name):
|
||||
for cn in cat_names:
|
||||
if cn not in self._editing_bias_profile_category_multipliers:
|
||||
if imgui.selectable(cn, cn == self._new_bias_category_name)[0]:
|
||||
self._new_bias_category_name = cn
|
||||
imgui.end_combo()
|
||||
imgui.same_line()
|
||||
if imgui.button("Add Category Override"):
|
||||
self._editing_bias_profile_category_multipliers[self._new_bias_category_name] = 1.0
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
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)
|
||||
prof = 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(prof, self._editing_tool_preset_scope)
|
||||
self.ai_status = f"Bias profile '{prof.name}' saved"
|
||||
except Exception as e:
|
||||
@@ -1187,111 +1225,265 @@ class App:
|
||||
if imgui.is_item_hovered():
|
||||
imgui.set_tooltip("Delete this tool preset permanently.")
|
||||
|
||||
if not is_embedded:
|
||||
imgui.same_line()
|
||||
if imgui.button("Close", imgui.ImVec2(100, 0)):
|
||||
self.show_tool_preset_manager_modal = False
|
||||
imgui.close_current_popup()
|
||||
self.show_tool_preset_manager_window = False
|
||||
finally:
|
||||
imgui.end_child()
|
||||
finally:
|
||||
imgui.end_popup()
|
||||
|
||||
def _render_persona_editor_modal(self) -> None:
|
||||
if not self.show_persona_editor_modal: return
|
||||
imgui.open_popup("Persona Editor")
|
||||
opened, self.show_persona_editor_modal = imgui.begin_popup_modal("Persona Editor", self.show_persona_editor_modal)
|
||||
if opened:
|
||||
def _render_tool_preset_manager_window(self, is_embedded: bool = False) -> None:
|
||||
if not self.show_tool_preset_manager_window and not is_embedded: return
|
||||
|
||||
if not is_embedded:
|
||||
imgui.set_next_window_size(imgui.ImVec2(1000, 800), imgui.Cond_.first_use_ever)
|
||||
opened, self.show_tool_preset_manager_window = imgui.begin("Tool Preset Manager", self.show_tool_preset_manager_window)
|
||||
if not opened:
|
||||
imgui.end()
|
||||
return
|
||||
|
||||
try:
|
||||
self._render_tool_preset_manager_content(is_embedded=is_embedded)
|
||||
finally:
|
||||
if not is_embedded:
|
||||
imgui.end()
|
||||
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 is_embedded:
|
||||
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)
|
||||
if not opened:
|
||||
imgui.end()
|
||||
return
|
||||
|
||||
try:
|
||||
avail = imgui.get_content_region_avail()
|
||||
# Left Pane: List of Personas
|
||||
imgui.begin_child("persona_list_area", imgui.ImVec2(250, avail.y), True)
|
||||
try:
|
||||
if imgui.button("New Persona", imgui.ImVec2(-1, 0)):
|
||||
self._editing_persona_name = ""
|
||||
self._editing_persona_system_prompt = ""
|
||||
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_scope = "project"
|
||||
self._editing_persona_is_new = True
|
||||
imgui.separator()
|
||||
personas = getattr(self.controller, 'personas', {})
|
||||
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, is_sel)[0]:
|
||||
p = personas[name]
|
||||
self._editing_persona_name = p.name
|
||||
self._editing_persona_system_prompt = p.system_prompt or ""
|
||||
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
|
||||
finally:
|
||||
imgui.end_child()
|
||||
|
||||
imgui.same_line()
|
||||
|
||||
# Right Pane: Editor
|
||||
imgui.begin_child("persona_edit_area", imgui.ImVec2(0, avail.y), False)
|
||||
try:
|
||||
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.text("Name:")
|
||||
imgui.same_line()
|
||||
imgui.push_item_width(200)
|
||||
_, self._editing_persona_name = imgui.input_text("##pname", self._editing_persona_name, 128)
|
||||
imgui.pop_item_width()
|
||||
|
||||
imgui.text("Scope:")
|
||||
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.separator()
|
||||
|
||||
imgui.text("Preferred Models:")
|
||||
providers = self.controller.PROVIDERS
|
||||
if not hasattr(self, '_persona_pref_models_expanded'):
|
||||
self._persona_pref_models_expanded = {}
|
||||
imgui.begin_child("pref_models_list", imgui.ImVec2(0, 200), True)
|
||||
to_remove = []
|
||||
for i, entry in enumerate(self._editing_persona_preferred_models_list):
|
||||
imgui.push_id(f"pref_model_{i}")
|
||||
|
||||
prov = entry.get("provider", "Unknown")
|
||||
mod = entry.get("model", "Unknown")
|
||||
is_expanded = self._persona_pref_models_expanded.get(i, False)
|
||||
|
||||
if imgui.button("-" if is_expanded else "+"):
|
||||
self._persona_pref_models_expanded[i] = not is_expanded
|
||||
imgui.same_line()
|
||||
|
||||
imgui.text(f"{i+1}. {prov} - {mod}")
|
||||
imgui.same_line(imgui.get_content_region_avail().x - 30)
|
||||
if imgui.button("x"):
|
||||
to_remove.append(i)
|
||||
|
||||
if is_expanded:
|
||||
imgui.indent(20)
|
||||
|
||||
imgui.text("Provider:")
|
||||
imgui.same_line()
|
||||
providers = ["gemini", "anthropic", "deepseek"]
|
||||
p_idx = providers.index(self._editing_persona_provider) + 1 if self._editing_persona_provider in providers else 0
|
||||
imgui.push_item_width(120)
|
||||
_, p_idx = imgui.combo("##pprov", p_idx, ["None"] + providers)
|
||||
self._editing_persona_provider = providers[p_idx - 1] if p_idx > 0 else ""
|
||||
imgui.pop_item_width()
|
||||
imgui.text("Model:")
|
||||
all_models = ["gemini-2.5-flash", "gemini-3.1-pro-preview", "claude-3-5-sonnet", "deepseek-v3"]
|
||||
m_idx = all_models.index(self._editing_persona_model) + 1 if self._editing_persona_model in all_models else 0
|
||||
imgui.push_item_width(150)
|
||||
_, m_idx = imgui.combo("##pmodel", m_idx, ["None"] + all_models)
|
||||
self._editing_persona_model = all_models[m_idx - 1] if m_idx > 0 else ""
|
||||
imgui.pop_item_width()
|
||||
imgui.text("Temp:")
|
||||
imgui.same_line()
|
||||
_, self._editing_persona_temperature = imgui.slider_float("##ptemp", self._editing_persona_temperature, 0.0, 2.0)
|
||||
imgui.text("MaxTok:")
|
||||
imgui.same_line()
|
||||
_, self._editing_persona_max_tokens = imgui.input_int("##pmaxt", self._editing_persona_max_tokens)
|
||||
imgui.set_next_item_width(150)
|
||||
p_idx = providers.index(prov) + 1 if prov in providers else 0
|
||||
changed_p, p_idx = imgui.combo("##prov", p_idx, ["None"] + providers)
|
||||
if changed_p:
|
||||
entry["provider"] = providers[p_idx-1] if p_idx > 0 else ""
|
||||
|
||||
# Tool Preset
|
||||
imgui.same_line()
|
||||
imgui.text("Model:")
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(250)
|
||||
curr_prov = entry.get("provider", "")
|
||||
m_list = self.controller.all_available_models.get(curr_prov, [])
|
||||
m_idx = m_list.index(mod) + 1 if mod in m_list else 0
|
||||
changed_m, m_idx = imgui.combo("##model", m_idx, ["None"] + m_list)
|
||||
if changed_m:
|
||||
entry["model"] = m_list[m_idx-1] if m_idx > 0 else ""
|
||||
|
||||
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.same_line()
|
||||
imgui.text("Max Output Tokens:")
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(100)
|
||||
_, entry["max_output_tokens"] = imgui.input_int("##maxt", entry.get("max_output_tokens", 4096))
|
||||
|
||||
imgui.text("History Truncation Limit:")
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(100)
|
||||
_, entry["history_trunc_limit"] = imgui.input_int("##hist", entry.get("history_trunc_limit", 900000))
|
||||
|
||||
imgui.unindent(20)
|
||||
imgui.dummy(imgui.ImVec2(0, 4))
|
||||
imgui.pop_id()
|
||||
for i in reversed(to_remove):
|
||||
self._editing_persona_preferred_models_list.pop(i)
|
||||
if i in self._persona_pref_models_expanded:
|
||||
del self._persona_pref_models_expanded[i]
|
||||
imgui.end_child()
|
||||
|
||||
if imgui.button("Add Preferred Model"):
|
||||
idx = len(self._editing_persona_preferred_models_list)
|
||||
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
|
||||
})
|
||||
self._persona_pref_models_expanded[idx] = True
|
||||
|
||||
imgui.separator()
|
||||
imgui.text("Tool Preset:")
|
||||
imgui.same_line()
|
||||
preset_names = ["None"] + sorted(self.controller.tool_presets.keys())
|
||||
t_idx = preset_names.index(self._editing_persona_tool_preset_id) if hasattr(self, '_editing_persona_tool_preset_id') and self._editing_persona_tool_preset_id in preset_names else 0
|
||||
imgui.push_item_width(150)
|
||||
_, t_idx = imgui.combo("##ptoolpreset", t_idx, preset_names)
|
||||
self._editing_persona_tool_preset_id = preset_names[t_idx] if t_idx > 0 else None
|
||||
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)
|
||||
_, 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()
|
||||
|
||||
# Bias Profile
|
||||
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 hasattr(self, '_editing_persona_bias_profile_id') and self._editing_persona_bias_profile_id in bias_names else 0
|
||||
imgui.push_item_width(150)
|
||||
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)
|
||||
_, b_idx = imgui.combo("##pbiasprofile", b_idx, bias_names)
|
||||
self._editing_persona_bias_profile_id = bias_names[b_idx] if b_idx > 0 else None
|
||||
self._editing_persona_bias_profile_id = bias_names[b_idx] if b_idx > 0 else ""
|
||||
imgui.pop_item_width()
|
||||
|
||||
imgui.text("Pref Models (JSON):")
|
||||
_, self._editing_persona_preferred_models = imgui.input_text("##pprefmodels", self._editing_persona_preferred_models, 256)
|
||||
imgui.same_line()
|
||||
if imgui.button("Manage Tools##p_tools"):
|
||||
self.show_tool_preset_manager_window = True
|
||||
|
||||
imgui.text("Prompt:")
|
||||
_, self._editing_persona_system_prompt = imgui.input_text_multiline("##pprompt", self._editing_persona_system_prompt, imgui.ImVec2(350, 50))
|
||||
if imgui.button("Save##p", imgui.ImVec2(80, 0)):
|
||||
imgui.separator()
|
||||
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)
|
||||
_, 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:
|
||||
pname = prompt_presets[self._load_preset_idx]
|
||||
if pname in self.controller.presets:
|
||||
p = self.controller.presets[pname]
|
||||
self._editing_persona_system_prompt = p.system_prompt
|
||||
self._load_preset_idx = 0
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Manage Prompts##p_prompts"):
|
||||
self.show_preset_manager_window = True
|
||||
|
||||
_, self._editing_persona_system_prompt = imgui.input_text_multiline("##pprompt", self._editing_persona_system_prompt, imgui.ImVec2(-1, 150))
|
||||
|
||||
imgui.separator()
|
||||
if imgui.button("Save Persona", imgui.ImVec2(120, 0)):
|
||||
if self._editing_persona_name.strip():
|
||||
try:
|
||||
import json
|
||||
pref_models = []
|
||||
try:
|
||||
pref_models = json.loads(self._editing_persona_preferred_models)
|
||||
if not isinstance(pref_models, list):
|
||||
pref_models = []
|
||||
except:
|
||||
pass
|
||||
import copy
|
||||
save_models = copy.deepcopy(self._editing_persona_preferred_models_list)
|
||||
|
||||
persona = models.Persona(
|
||||
name=self._editing_persona_name.strip(),
|
||||
provider=self._editing_persona_provider or None,
|
||||
model=self._editing_persona_model or None,
|
||||
system_prompt=self._editing_persona_system_prompt,
|
||||
temperature=self._editing_persona_temperature,
|
||||
max_output_tokens=self._editing_persona_max_tokens,
|
||||
tool_preset=getattr(self, '_editing_persona_tool_preset_id', None),
|
||||
bias_profile=getattr(self, '_editing_persona_bias_profile_id', None),
|
||||
preferred_models=pref_models,
|
||||
tool_preset=self._editing_persona_tool_preset_id or None,
|
||||
bias_profile=self._editing_persona_bias_profile_id or None,
|
||||
preferred_models=save_models,
|
||||
)
|
||||
self.controller._cb_save_persona(persona, "project")
|
||||
self.ai_status = f"Saved: {persona.name}"
|
||||
self.show_persona_editor_modal = False
|
||||
imgui.close_current_popup()
|
||||
self.controller._cb_save_persona(persona, getattr(self, '_editing_persona_scope', 'project'))
|
||||
self.ai_status = f"Saved Persona: {persona.name}"
|
||||
except Exception as e:
|
||||
self.ai_status = f"Error: {e}"
|
||||
self.ai_status = f"Error saving persona: {e}"
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
else:
|
||||
self.ai_status = "Name required"
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Cancel##p", imgui.ImVec2(80, 0)):
|
||||
self.show_persona_editor_modal = False
|
||||
imgui.close_current_popup()
|
||||
if imgui.button("Delete", imgui.ImVec2(100, 0)):
|
||||
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.ai_status = f"Deleted Persona: {self._editing_persona_name}"
|
||||
self._editing_persona_name = ""
|
||||
self._editing_persona_is_new = True
|
||||
|
||||
if not is_embedded:
|
||||
imgui.same_line()
|
||||
if imgui.button("Close", imgui.ImVec2(100, 0)):
|
||||
self.show_persona_editor_window = False
|
||||
finally:
|
||||
imgui.end_popup()
|
||||
|
||||
|
||||
imgui.end_child()
|
||||
finally:
|
||||
if not is_embedded:
|
||||
imgui.end()
|
||||
def _render_projects_panel(self) -> None:
|
||||
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)
|
||||
@@ -2058,40 +2250,8 @@ def hello():
|
||||
self._scroll_disc_to_bottom = False
|
||||
imgui.end_child()
|
||||
|
||||
def _render_provider_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel")
|
||||
imgui.text("Provider")
|
||||
if imgui.begin_combo("##prov", self.current_provider):
|
||||
for p in PROVIDERS:
|
||||
if imgui.selectable(p, p == self.current_provider)[0]:
|
||||
self.current_provider = p
|
||||
imgui.end_combo()
|
||||
imgui.separator()
|
||||
imgui.text("Model")
|
||||
imgui.same_line()
|
||||
if imgui.button("Fetch Models"):
|
||||
self._fetch_models(self.current_provider)
|
||||
if imgui.begin_list_box("##models", imgui.ImVec2(-1, 120)):
|
||||
for m in self.available_models:
|
||||
if imgui.selectable(m, m == self.current_model)[0]:
|
||||
self.current_model = m
|
||||
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)
|
||||
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()
|
||||
|
||||
def _render_persona_selector_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_persona_selector_panel")
|
||||
imgui.text("Persona")
|
||||
if not hasattr(self, 'ui_active_persona'):
|
||||
self.ui_active_persona = ""
|
||||
@@ -2105,26 +2265,31 @@ def hello():
|
||||
if pname in personas:
|
||||
persona = personas[pname]
|
||||
self._editing_persona_name = persona.name
|
||||
self._editing_persona_provider = persona.provider or ""
|
||||
self._editing_persona_model = persona.model or ""
|
||||
self._editing_persona_system_prompt = persona.system_prompt or ""
|
||||
self._editing_persona_temperature = persona.temperature or 0.7
|
||||
self._editing_persona_max_tokens = persona.max_output_tokens or 4096
|
||||
self._editing_persona_tool_preset_id = persona.tool_preset or ""
|
||||
self._editing_persona_bias_profile_id = persona.bias_profile or ""
|
||||
import json
|
||||
self._editing_persona_preferred_models = json.dumps(persona.preferred_models) if persona.preferred_models else "[]"
|
||||
import copy
|
||||
self._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
|
||||
self._editing_persona_is_new = False
|
||||
if persona.provider and persona.provider in self.controller.PROVIDERS:
|
||||
self.current_provider = persona.provider
|
||||
if persona.model:
|
||||
self.current_model = persona.model
|
||||
if persona.temperature is not None:
|
||||
ai_client.temperature = persona.temperature
|
||||
if persona.max_output_tokens:
|
||||
ai_client.max_output_tokens = persona.max_output_tokens
|
||||
|
||||
# Apply persona to current state immediately
|
||||
if persona.preferred_models and len(persona.preferred_models) > 0:
|
||||
first_model = persona.preferred_models[0]
|
||||
if first_model.get("provider"):
|
||||
self.current_provider = first_model.get("provider")
|
||||
if first_model.get("model"):
|
||||
self.current_model = first_model.get("model")
|
||||
if first_model.get("temperature") is not None:
|
||||
ai_client.temperature = first_model.get("temperature")
|
||||
self.temperature = first_model.get("temperature")
|
||||
if first_model.get("max_output_tokens"):
|
||||
ai_client.max_output_tokens = first_model.get("max_output_tokens")
|
||||
self.max_tokens = first_model.get("max_output_tokens")
|
||||
if first_model.get("history_trunc_limit"):
|
||||
self.history_trunc_limit = first_model.get("history_trunc_limit")
|
||||
|
||||
if persona.system_prompt:
|
||||
ai_client.system_instruction = persona.system_prompt
|
||||
self.ui_project_system_prompt = persona.system_prompt
|
||||
if persona.tool_preset:
|
||||
self.ui_active_tool_preset = persona.tool_preset
|
||||
ai_client.set_tool_preset(persona.tool_preset)
|
||||
@@ -2133,33 +2298,57 @@ def hello():
|
||||
ai_client.set_bias_profile(persona.bias_profile)
|
||||
imgui.end_combo()
|
||||
imgui.same_line()
|
||||
if imgui.button("Edit##persona"):
|
||||
if imgui.button("Manage Personas"):
|
||||
self.show_persona_editor_window = True
|
||||
if self.ui_active_persona and self.ui_active_persona in personas:
|
||||
persona = personas[self.ui_active_persona]
|
||||
self._editing_persona_name = persona.name
|
||||
self._editing_persona_provider = persona.provider or ""
|
||||
self._editing_persona_model = persona.model or ""
|
||||
self._editing_persona_system_prompt = persona.system_prompt or ""
|
||||
self._editing_persona_temperature = persona.temperature or 0.7
|
||||
self._editing_persona_max_tokens = persona.max_output_tokens or 4096
|
||||
self._editing_persona_tool_preset_id = persona.tool_preset or ""
|
||||
self._editing_persona_bias_profile_id = persona.bias_profile or ""
|
||||
import json
|
||||
self._editing_persona_preferred_models = json.dumps(persona.preferred_models) if persona.preferred_models else "[]"
|
||||
import copy
|
||||
self._editing_persona_preferred_models_list = copy.deepcopy(persona.preferred_models) if persona.preferred_models else []
|
||||
self._editing_persona_scope = self.controller.persona_manager.get_persona_scope(persona.name)
|
||||
self._editing_persona_is_new = False
|
||||
else:
|
||||
self._editing_persona_name = ""
|
||||
self._editing_persona_provider = self.current_provider
|
||||
self._editing_persona_model = self.current_model
|
||||
self._editing_persona_system_prompt = ""
|
||||
self._editing_persona_temperature = 0.7
|
||||
self._editing_persona_max_tokens = 4096
|
||||
self._editing_persona_tool_preset_id = ""
|
||||
self._editing_persona_bias_profile_id = ""
|
||||
self._editing_persona_preferred_models = "[]"
|
||||
self._editing_persona_tier_assignments = "{}"
|
||||
self._editing_persona_preferred_models_list = [{
|
||||
"provider": self.current_provider,
|
||||
"model": self.current_model,
|
||||
"temperature": getattr(self, "temperature", 0.7),
|
||||
"max_output_tokens": getattr(self, "max_tokens", 4096),
|
||||
"history_trunc_limit": getattr(self, "history_trunc_limit", 900000)
|
||||
}]
|
||||
self._editing_persona_scope = "project"
|
||||
self._editing_persona_is_new = True
|
||||
self.show_persona_editor_modal = True
|
||||
imgui.separator()
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_persona_selector_panel")
|
||||
|
||||
def _render_provider_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_provider_panel")
|
||||
imgui.text("Provider")
|
||||
if imgui.begin_combo("##prov", self.current_provider):
|
||||
for p in PROVIDERS:
|
||||
if imgui.selectable(p, p == self.current_provider)[0]:
|
||||
self.current_provider = p
|
||||
imgui.end_combo()
|
||||
imgui.separator()
|
||||
imgui.text("Model")
|
||||
if imgui.begin_list_box("##models", imgui.ImVec2(-1, 120)):
|
||||
for m in self.available_models:
|
||||
if imgui.selectable(m, m == self.current_model)[0]:
|
||||
self.current_model = m
|
||||
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)
|
||||
ch, self.history_trunc_limit = imgui.input_int("History Truncation Limit", self.history_trunc_limit, 1024)
|
||||
|
||||
|
||||
|
||||
if self.current_provider == "gemini_cli":
|
||||
imgui.separator()
|
||||
@@ -2284,9 +2473,9 @@ def hello():
|
||||
if cache_stats.get("cache_exists"):
|
||||
age = cache_stats.get("cache_age_seconds", 0)
|
||||
ttl = cache_stats.get("ttl_seconds", 3600)
|
||||
imgui.text_colored(C_LBL, f"Gemini Cache: ACTIVE | Age: {age:.0f}s / {ttl}s | Renews at: {ttl * 0.9:.0f}s")
|
||||
imgui.text_colored(C_LBL, f"Cache Usage: ACTIVE | Age: {age:.0f}s / {ttl}s | Renews at: {ttl * 0.9:.0f}s")
|
||||
else:
|
||||
imgui.text_disabled("Gemini Cache: INACTIVE")
|
||||
imgui.text_disabled("Cache Usage: INACTIVE")
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_token_budget_panel")
|
||||
|
||||
def _render_cache_panel(self) -> None:
|
||||
@@ -2294,9 +2483,7 @@ def hello():
|
||||
if self.current_provider != "gemini":
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_cache_panel")
|
||||
return
|
||||
if not imgui.collapsing_header("Cache Analytics"):
|
||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_cache_panel")
|
||||
return
|
||||
imgui.text_colored(C_LBL, 'Cache Analytics')
|
||||
stats = getattr(self.controller, '_cached_cache_stats', {})
|
||||
if not stats.get("cache_exists"):
|
||||
imgui.text_disabled("No active cache")
|
||||
@@ -2384,6 +2571,8 @@ def hello():
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_usage_analytics_panel")
|
||||
self._render_token_budget_panel()
|
||||
imgui.separator()
|
||||
self._render_cache_panel()
|
||||
imgui.separator()
|
||||
self._render_tool_analytics_panel()
|
||||
imgui.separator()
|
||||
self._render_session_insights_panel()
|
||||
@@ -3450,7 +3639,7 @@ def hello():
|
||||
imgui.end_combo()
|
||||
imgui.same_line(0, 8)
|
||||
if imgui.button("Manage Presets##global"):
|
||||
self.show_preset_manager_modal = True
|
||||
self.show_preset_manager_window = True
|
||||
imgui.set_item_tooltip("Open preset management modal")
|
||||
ch, self.ui_global_system_prompt = imgui.input_text_multiline("##gsp", self.ui_global_system_prompt, imgui.ImVec2(-1, 100))
|
||||
imgui.separator()
|
||||
@@ -3467,15 +3656,15 @@ def hello():
|
||||
imgui.end_combo()
|
||||
imgui.same_line(0, 8)
|
||||
if imgui.button("Manage Presets##project"):
|
||||
self.show_preset_manager_modal = True
|
||||
self.show_preset_manager_window = True
|
||||
imgui.set_item_tooltip("Open preset management modal")
|
||||
ch, self.ui_project_system_prompt = imgui.input_text_multiline("##psp", self.ui_project_system_prompt, imgui.ImVec2(-1, 100))
|
||||
def _render_agent_tools_panel(self) -> None:
|
||||
imgui.text_colored(C_LBL, 'Active Tool Preset')
|
||||
if imgui.collapsing_header("Active Tool Presets & Biases", imgui.TreeNodeFlags_.default_open):
|
||||
imgui.text("Tool Preset")
|
||||
presets = self.controller.tool_presets
|
||||
preset_names = [""] + sorted(list(presets.keys()))
|
||||
|
||||
# Gracefully handle None or missing preset
|
||||
active = getattr(self, "ui_active_tool_preset", "")
|
||||
if active is None: active = ""
|
||||
try:
|
||||
@@ -3489,10 +3678,24 @@ def hello():
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Manage Presets##tools"):
|
||||
self.show_tool_preset_manager_modal = True
|
||||
self.show_tool_preset_manager_window = True
|
||||
if imgui.is_item_hovered():
|
||||
imgui.set_tooltip("Configure tool availability and default modes.")
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 4))
|
||||
imgui.text("Bias Profile")
|
||||
if imgui.begin_combo("##bias", getattr(self, 'ui_active_bias_profile', "") or "None"):
|
||||
if imgui.selectable("None", not getattr(self, 'ui_active_bias_profile', ""))[0]:
|
||||
self.ui_active_bias_profile = ""
|
||||
from src import ai_client
|
||||
ai_client.set_bias_profile(None)
|
||||
for bname in sorted(self.controller.bias_profiles.keys()):
|
||||
if imgui.selectable(bname, bname == getattr(self, 'ui_active_bias_profile', ""))[0]:
|
||||
self.ui_active_bias_profile = bname
|
||||
from src import ai_client
|
||||
ai_client.set_bias_profile(bname)
|
||||
imgui.end_combo()
|
||||
|
||||
imgui.dummy(imgui.ImVec2(0, 8))
|
||||
active_name = self.ui_active_tool_preset
|
||||
if active_name and active_name in presets:
|
||||
@@ -3523,7 +3726,6 @@ def hello():
|
||||
if imgui.radio_button(f"Ask##{cat_name}_{tool.name}", mode == "ask"):
|
||||
tool.approval = "ask"
|
||||
imgui.tree_pop()
|
||||
|
||||
def _render_theme_panel(self) -> None:
|
||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_theme_panel")
|
||||
exp, opened = imgui.begin("Theme", self.show_windows["Theme"])
|
||||
@@ -3708,3 +3910,4 @@ def main() -> None:
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@@ -115,3 +115,4 @@ class LogPruner:
|
||||
sys.stderr.write(f"[LogPruner] Error removing {resolved_path}: {e}\n")
|
||||
|
||||
self.log_registry.save_registry()
|
||||
|
||||
|
||||
@@ -301,3 +301,4 @@ class LogRegistry:
|
||||
})
|
||||
return old_sessions
|
||||
|
||||
|
||||
|
||||
@@ -166,3 +166,4 @@ def render_unindented(text: str) -> None:
|
||||
|
||||
def render_code(code: str, lang: str = "", context_id: str = "default", block_idx: int = 0) -> None:
|
||||
get_renderer().render_code(code, lang, context_id, block_idx)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# mcp_client.py
|
||||
# mcp_client.py
|
||||
"""
|
||||
MCP Client - Multi-tool filesystem and network operations with sandboxing.
|
||||
|
||||
@@ -782,10 +782,10 @@ def get_tree(path: str, max_depth: int = 2) -> str:
|
||||
entries = [e for e in entries if not e.name.startswith('.') and e.name not in ('__pycache__', 'venv', 'env') and e.name != "history.toml" and not e.name.endswith("_history.toml")]
|
||||
for i, entry in enumerate(entries):
|
||||
is_last = (i == len(entries) - 1)
|
||||
connector = "└── " if is_last else "├── "
|
||||
connector = "└── " if is_last else "├── "
|
||||
lines.append(f"{prefix}{connector}{entry.name}")
|
||||
if entry.is_dir():
|
||||
extension = " " if is_last else "│ "
|
||||
extension = " " if is_last else "│ "
|
||||
lines.extend(_build_tree(entry, current_depth + 1, prefix + extension))
|
||||
return lines
|
||||
tree_lines = [f"{p.name}/"] + _build_tree(p, 1)
|
||||
@@ -1466,3 +1466,4 @@ MCP_TOOL_SPECS: list[dict[str, Any]] = [
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -178,3 +178,4 @@ RULES:
|
||||
|
||||
Analyze this error and generate the patch:
|
||||
"""
|
||||
|
||||
|
||||
@@ -349,30 +349,17 @@ class FileItem:
|
||||
class Preset:
|
||||
name: str
|
||||
system_prompt: str
|
||||
temperature: Optional[float] = None
|
||||
top_p: Optional[float] = None
|
||||
max_output_tokens: Optional[int] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
res = {
|
||||
return {
|
||||
"system_prompt": self.system_prompt,
|
||||
}
|
||||
if self.temperature is not None:
|
||||
res["temperature"] = self.temperature
|
||||
if self.top_p is not None:
|
||||
res["top_p"] = self.top_p
|
||||
if self.max_output_tokens is not None:
|
||||
res["max_output_tokens"] = self.max_output_tokens
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, name: str, data: Dict[str, Any]) -> "Preset":
|
||||
return cls(
|
||||
name=name,
|
||||
system_prompt=data.get("system_prompt", ""),
|
||||
temperature=data.get("temperature"),
|
||||
top_p=data.get("top_p"),
|
||||
max_output_tokens=data.get("max_output_tokens"),
|
||||
)
|
||||
|
||||
@dataclass
|
||||
@@ -447,32 +434,48 @@ class BiasProfile:
|
||||
@dataclass
|
||||
class Persona:
|
||||
name: str
|
||||
provider: Optional[str] = None
|
||||
model: Optional[str] = None
|
||||
preferred_models: List[str] = field(default_factory=list)
|
||||
preferred_models: List[Dict[str, Any]] = field(default_factory=list)
|
||||
system_prompt: str = ''
|
||||
temperature: Optional[float] = None
|
||||
top_p: Optional[float] = None
|
||||
max_output_tokens: Optional[int] = None
|
||||
tool_preset: Optional[str] = None
|
||||
bias_profile: Optional[str] = None
|
||||
|
||||
@property
|
||||
def provider(self) -> Optional[str]:
|
||||
if not self.preferred_models: return None
|
||||
return self.preferred_models[0].get("provider")
|
||||
|
||||
@property
|
||||
def model(self) -> Optional[str]:
|
||||
if not self.preferred_models: return None
|
||||
return self.preferred_models[0].get("model")
|
||||
|
||||
@property
|
||||
def temperature(self) -> Optional[float]:
|
||||
if not self.preferred_models: return None
|
||||
return self.preferred_models[0].get("temperature")
|
||||
|
||||
@property
|
||||
def top_p(self) -> Optional[float]:
|
||||
if not self.preferred_models: return None
|
||||
return self.preferred_models[0].get("top_p")
|
||||
|
||||
@property
|
||||
def max_output_tokens(self) -> Optional[int]:
|
||||
if not self.preferred_models: return None
|
||||
return self.preferred_models[0].get("max_output_tokens")
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
res = {
|
||||
"system_prompt": self.system_prompt,
|
||||
}
|
||||
if self.provider is not None:
|
||||
res["provider"] = self.provider
|
||||
if self.model is not None:
|
||||
res["model"] = self.model
|
||||
if self.preferred_models:
|
||||
res["preferred_models"] = self.preferred_models
|
||||
if self.temperature is not None:
|
||||
res["temperature"] = self.temperature
|
||||
if self.top_p is not None:
|
||||
res["top_p"] = self.top_p
|
||||
if self.max_output_tokens is not None:
|
||||
res["max_output_tokens"] = self.max_output_tokens
|
||||
processed = []
|
||||
for m in self.preferred_models:
|
||||
if isinstance(m, str):
|
||||
processed.append({"model": m})
|
||||
else:
|
||||
processed.append(m)
|
||||
res["preferred_models"] = processed
|
||||
if self.tool_preset is not None:
|
||||
res["tool_preset"] = self.tool_preset
|
||||
if self.bias_profile is not None:
|
||||
@@ -481,15 +484,34 @@ class Persona:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, name: str, data: Dict[str, Any]) -> "Persona":
|
||||
raw_models = data.get("preferred_models", [])
|
||||
parsed_models = []
|
||||
for m in raw_models:
|
||||
if isinstance(m, str):
|
||||
parsed_models.append({"model": m})
|
||||
else:
|
||||
parsed_models.append(m)
|
||||
|
||||
# Migration logic: merge legacy fields if they exist
|
||||
legacy = {}
|
||||
for k in ["provider", "model", "temperature", "top_p", "max_output_tokens"]:
|
||||
if data.get(k) is not None:
|
||||
legacy[k] = data[k]
|
||||
|
||||
if legacy:
|
||||
if not parsed_models:
|
||||
parsed_models.append(legacy)
|
||||
else:
|
||||
# Merge into first item if it's missing these specific legacy fields
|
||||
for k, v in legacy.items():
|
||||
if k not in parsed_models[0] or parsed_models[0][k] is None:
|
||||
parsed_models[0][k] = v
|
||||
|
||||
return cls(
|
||||
name=name,
|
||||
provider=data.get("provider"),
|
||||
model=data.get("model"),
|
||||
preferred_models=data.get("preferred_models", []),
|
||||
preferred_models=parsed_models,
|
||||
system_prompt=data.get("system_prompt", ""),
|
||||
temperature=data.get("temperature"),
|
||||
top_p=data.get("top_p"),
|
||||
max_output_tokens=data.get("max_output_tokens"),
|
||||
tool_preset=data.get("tool_preset"),
|
||||
bias_profile=data.get("bias_profile"),
|
||||
)
|
||||
|
||||
|
||||
@@ -614,3 +614,4 @@ def run_worker_lifecycle(ticket: Ticket, context: WorkerContext, context_files:
|
||||
if event_queue:
|
||||
_queue_put(event_queue, "ticket_completed", {"ticket_id": ticket.id, "timestamp": time.time()})
|
||||
return response
|
||||
|
||||
|
||||
@@ -86,3 +86,4 @@ class NativeOrchestrator:
|
||||
"""Tier 4: Generate patch for error"""
|
||||
from src import ai_client
|
||||
return ai_client.run_tier4_patch_generation(error, file_context)
|
||||
|
||||
|
||||
@@ -125,3 +125,4 @@ if __name__ == "__main__":
|
||||
history = get_track_history_summary()
|
||||
tracks = generate_tracks("Implement a basic unit test for the ai_client.py module.", flat, file_items, history_summary=history)
|
||||
print(json.dumps(tracks, indent=2))
|
||||
|
||||
|
||||
@@ -87,3 +87,4 @@ def get_outline(path: Path, code: str) -> str:
|
||||
return outliner.outline(code)
|
||||
else:
|
||||
return f"Outlining not supported for {suffix} files yet."
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional, Callable, List
|
||||
from typing import Optional, Callable, List
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -110,3 +110,4 @@ def get_archive_dir() -> Path:
|
||||
def reset_resolved() -> None:
|
||||
"""For testing only - clear cached resolutions."""
|
||||
_RESOLVED.clear()
|
||||
|
||||
|
||||
@@ -232,3 +232,4 @@ class PerformanceMonitor:
|
||||
self._stop_event.set()
|
||||
if self._cpu_thread.is_alive():
|
||||
self._cpu_thread.join(timeout=2.0)
|
||||
|
||||
|
||||
@@ -47,6 +47,21 @@ class PersonaManager:
|
||||
data["personas"][persona.name] = persona.to_dict()
|
||||
self._save_file(path, data)
|
||||
|
||||
def get_persona_scope(self, name: str) -> str:
|
||||
"""Returns the scope ('global' or 'project') of a persona by name."""
|
||||
if self.project_root:
|
||||
project_path = paths.get_project_personas_path(self.project_root)
|
||||
project_data = self._load_file(project_path)
|
||||
if name in project_data.get("personas", {}):
|
||||
return "project"
|
||||
|
||||
global_path = paths.get_global_personas_path()
|
||||
global_data = self._load_file(global_path)
|
||||
if name in global_data.get("personas", {}):
|
||||
return "global"
|
||||
|
||||
return "project"
|
||||
|
||||
def delete_persona(self, name: str, scope: str = "project") -> None:
|
||||
path = self._get_path(scope)
|
||||
data = self._load_file(path)
|
||||
@@ -67,3 +82,4 @@ class PersonaManager:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(path, "wb") as f:
|
||||
tomli_w.dump(data, f)
|
||||
|
||||
|
||||
@@ -89,3 +89,4 @@ class PresetManager:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(path, "wb") as f:
|
||||
f.write(tomli_w.dumps(data).encode("utf-8"))
|
||||
|
||||
|
||||
@@ -391,3 +391,4 @@ def calculate_track_progress(tickets: list) -> dict:
|
||||
"blocked": blocked,
|
||||
"todo": todo
|
||||
}
|
||||
|
||||
|
||||
@@ -217,3 +217,4 @@ def log_cli_call(command: str, stdin_content: Optional[str], stdout_content: Opt
|
||||
_cli_fh.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@@ -70,3 +70,4 @@ def apply_faux_acrylic_glass(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p
|
||||
flags=imgui.ImDrawFlags_.round_corners_all if rounding > 0 else imgui.ImDrawFlags_.none,
|
||||
thickness=1.0
|
||||
)
|
||||
|
||||
|
||||
@@ -90,3 +90,4 @@ def run_powershell(script: str, base_dir: str, qa_callback: Optional[Callable[[s
|
||||
if 'process' in locals() and process:
|
||||
subprocess.run(["taskkill", "/F", "/T", "/PID", str(process.pid)], capture_output=True)
|
||||
return f"ERROR: {e}"
|
||||
|
||||
|
||||
@@ -190,3 +190,4 @@ def build_summary_markdown(file_items: list[dict[str, Any]]) -> str:
|
||||
summary = item.get("summary", "")
|
||||
parts.append(f"### `{path}`\n\n{summary}")
|
||||
return "\n\n---\n\n".join(parts)
|
||||
|
||||
|
||||
@@ -388,3 +388,4 @@ def load_from_config(config: dict[str, Any]) -> None:
|
||||
if font_path:
|
||||
apply_font(font_path, font_size)
|
||||
set_scale(scale)
|
||||
|
||||
|
||||
@@ -392,3 +392,4 @@ def get_tweaked_theme() -> hello_imgui.ImGuiTweakedTheme:
|
||||
# Sync tweaks
|
||||
tt.tweaks.rounding = 6.0
|
||||
return tt
|
||||
|
||||
|
||||
@@ -82,3 +82,4 @@ def apply_nerv() -> None:
|
||||
style.popup_border_size = 1.0
|
||||
style.child_border_size = 1.0
|
||||
style.tab_border_size = 1.0
|
||||
|
||||
|
||||
@@ -83,3 +83,4 @@ class AlertPulsing:
|
||||
alpha = 0.05 + 0.15 * ((math.sin(time.time() * 4.0) + 1.0) / 2.0)
|
||||
color = imgui.get_color_u32((1.0, 0.0, 0.0, alpha))
|
||||
draw_list.add_rect((0.0, 0.0), (width, height), color, 0.0, 0, 10.0)
|
||||
|
||||
|
||||
@@ -63,3 +63,4 @@ class ToolBiasEngine:
|
||||
lines.append(f"- {cat}: {mult}x")
|
||||
|
||||
return "\n\n".join(lines)
|
||||
|
||||
|
||||
@@ -108,3 +108,4 @@ class ToolPresetManager:
|
||||
if "bias_profiles" in data and name in data["bias_profiles"]:
|
||||
del data["bias_profiles"][name]
|
||||
self._write_raw(path, data)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ def test_load_all_merged(temp_paths):
|
||||
|
||||
def test_save_persona(temp_paths):
|
||||
manager = PersonaManager(project_root=temp_paths["project_root"])
|
||||
persona = Persona(name="New", provider="gemini", system_prompt="Test")
|
||||
persona = Persona(name="New", preferred_models=[{"provider": "gemini"}], system_prompt="Test")
|
||||
|
||||
manager.save_persona(persona, scope="project")
|
||||
loaded = manager.load_all()
|
||||
|
||||
@@ -4,30 +4,38 @@ from src.models import Persona
|
||||
def test_persona_serialization():
|
||||
persona = Persona(
|
||||
name="SecuritySpecialist",
|
||||
provider="anthropic",
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
preferred_models=["claude-3-7-sonnet-20250219", "claude-3-5-sonnet-20241022"],
|
||||
preferred_models=[
|
||||
{"provider": "anthropic", "model": "claude-3-7-sonnet-20250219", "temperature": 0.2, "top_p": 0.9, "max_output_tokens": 4000},
|
||||
"claude-3-5-sonnet-20241022"
|
||||
],
|
||||
system_prompt="You are a security expert.",
|
||||
temperature=0.2,
|
||||
top_p=0.9,
|
||||
max_output_tokens=4000,
|
||||
tool_preset="SecurityTools",
|
||||
bias_profile="Execution-Focused"
|
||||
)
|
||||
|
||||
assert persona.provider == "anthropic"
|
||||
assert persona.model == "claude-3-7-sonnet-20250219"
|
||||
assert persona.temperature == 0.2
|
||||
assert persona.top_p == 0.9
|
||||
assert persona.max_output_tokens == 4000
|
||||
|
||||
data = persona.to_dict()
|
||||
|
||||
assert data["provider"] == "anthropic"
|
||||
assert data["model"] == "claude-3-7-sonnet-20250219"
|
||||
assert "claude-3-5-sonnet-20241022" in data["preferred_models"]
|
||||
# data should NOT have top-level provider/model anymore, it's in preferred_models
|
||||
assert "provider" not in data
|
||||
assert "model" not in data
|
||||
assert data["preferred_models"][0]["provider"] == "anthropic"
|
||||
assert data["preferred_models"][0]["model"] == "claude-3-7-sonnet-20250219"
|
||||
assert data["preferred_models"][1] == {"model": "claude-3-5-sonnet-20241022"}
|
||||
assert data["system_prompt"] == "You are a security expert."
|
||||
assert data["temperature"] == 0.2
|
||||
assert data["top_p"] == 0.9
|
||||
assert data["max_output_tokens"] == 4000
|
||||
assert data["preferred_models"][0]["temperature"] == 0.2
|
||||
assert data["preferred_models"][0]["top_p"] == 0.9
|
||||
assert data["preferred_models"][0]["max_output_tokens"] == 4000
|
||||
assert data["tool_preset"] == "SecurityTools"
|
||||
assert data["bias_profile"] == "Execution-Focused"
|
||||
|
||||
def test_persona_deserialization():
|
||||
# Old config format (legacy)
|
||||
data = {
|
||||
"provider": "gemini",
|
||||
"model": "gemini-2.5-flash",
|
||||
@@ -45,7 +53,9 @@ def test_persona_deserialization():
|
||||
assert persona.name == "Assistant"
|
||||
assert persona.provider == "gemini"
|
||||
assert persona.model == "gemini-2.5-flash"
|
||||
assert persona.preferred_models == ["gemini-2.5-flash"]
|
||||
# Migration logic should have put legacy fields into preferred_models since it only had a string
|
||||
assert persona.preferred_models[0]["provider"] == "gemini"
|
||||
assert persona.preferred_models[0]["model"] == "gemini-2.5-flash"
|
||||
assert persona.system_prompt == "You are a helpful assistant."
|
||||
assert persona.temperature == 0.5
|
||||
assert persona.top_p == 1.0
|
||||
|
||||
Reference in New Issue
Block a user