This commit is contained in:
2026-03-06 19:54:52 -05:00
parent 36a1bd4257
commit 0e9f84f026
7 changed files with 565 additions and 307 deletions

View File

@@ -24,6 +24,7 @@ import threading
import requests # type: ignore[import-untyped]
from typing import Optional, Callable, Any, List, Union, cast, Iterable
import os
from pathlib import Path
from src import project_manager
from src import file_cache
from src import mcp_client
@@ -157,8 +158,11 @@ def get_comms_log() -> list[dict[str, Any]]:
def clear_comms_log() -> None:
_comms_log.clear()
def get_credentials_path() -> Path:
return Path(os.environ.get("SLOP_CREDENTIALS", "credentials.toml"))
def _load_credentials() -> dict[str, Any]:
cred_path = os.environ.get("SLOP_CREDENTIALS", "credentials.toml")
cred_path = get_credentials_path()
try:
with open(cred_path, "rb") as f:
return tomllib.load(f)

View File

@@ -148,10 +148,10 @@ class AppController:
"last_latency": 0.0
}
self.mma_tier_usage: Dict[str, Dict[str, Any]] = {
"Tier 1": {"input": 0, "output": 0, "model": "gemini-3.1-pro-preview"},
"Tier 2": {"input": 0, "output": 0, "model": "gemini-3-flash-preview"},
"Tier 3": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
"Tier 4": {"input": 0, "output": 0, "model": "gemini-2.5-flash-lite"},
"Tier 1": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-3.1-pro-preview"},
"Tier 2": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-3-flash-preview"},
"Tier 3": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-2.5-flash-lite"},
"Tier 4": {"input": 0, "output": 0, "provider": "gemini", "model": "gemini-2.5-flash-lite"},
}
self.perf_monitor: performance_monitor.PerformanceMonitor = performance_monitor.PerformanceMonitor()
self._pending_gui_tasks: List[Dict[str, Any]] = []
@@ -195,6 +195,8 @@ class AppController:
self.ui_global_system_prompt: str = ""
self.ui_agent_tools: Dict[str, bool] = {}
self.available_models: List[str] = []
self.all_available_models: Dict[str, List[str]] = {} # provider -> list of models
self._autofocus_response_tab = False
self.proposed_tracks: List[Dict[str, Any]] = []
self._show_track_proposal_modal: bool = False
self.ai_status: str = 'idle'
@@ -401,6 +403,8 @@ class AppController:
self._trigger_blink = True
if not stream_id:
self._token_stats_dirty = True
if not is_streaming:
self._autofocus_response_tab = True
# ONLY add to history when turn is complete
if self.ui_auto_add_history and not stream_id and not is_streaming:
role = payload.get("role", "AI")
@@ -408,7 +412,7 @@ class AppController:
self._pending_history_adds.append({
"role": role,
"content": self.ai_response,
"collapsed": False,
"collapsed": True,
"ts": project_manager.now_ts()
})
elif action in ("mma_stream", "mma_stream_append"):
@@ -429,7 +433,19 @@ class AppController:
payload = task # Fallback to task if payload missing or wrong type
self.mma_status = payload.get("status", "idle")
self.active_tier = payload.get("active_tier")
self.mma_tier_usage = payload.get("tier_usage", self.mma_tier_usage)
# Preserve existing model/provider config if not explicitly in payload
new_usage = payload.get("tier_usage", {})
for tier, data in new_usage.items():
if tier in self.mma_tier_usage:
# Update usage counts but keep selected model/provider if not in update
self.mma_tier_usage[tier]["input"] = data.get("input", self.mma_tier_usage[tier]["input"])
self.mma_tier_usage[tier]["output"] = data.get("output", self.mma_tier_usage[tier]["output"])
if "model" in data: self.mma_tier_usage[tier]["model"] = data["model"]
if "provider" in data: self.mma_tier_usage[tier]["provider"] = data["provider"]
else:
self.mma_tier_usage[tier] = data
self.active_tickets = payload.get("tickets", [])
track_data = payload.get("track")
if track_data:
@@ -622,6 +638,8 @@ class AppController:
"Tier 4: QA": True,
"Discussion Hub": True,
"Operations Hub": True,
"Message": False,
"Response": False,
"Theme": True,
"Log Management": False,
"Diagnostics": False,
@@ -725,7 +743,14 @@ class AppController:
def do_fetch() -> None:
try:
models_list = ai_client.list_models(provider)
for p in self.PROVIDERS:
try:
self.all_available_models[p] = ai_client.list_models(p)
except Exception as e:
sys.stderr.write(f"[DEBUG] Error fetching models for {p}: {e}\n")
self.all_available_models[p] = []
models_list = self.all_available_models.get(provider, [])
self.available_models = models_list
if self.current_model not in models_list and models_list:
self.current_model = models_list[0]
@@ -851,7 +876,7 @@ class AppController:
self._pending_history_adds.append({
"role": "User",
"content": event.prompt,
"collapsed": False,
"collapsed": True,
"ts": project_manager.now_ts()
})
# Clear response area for new turn
@@ -896,6 +921,13 @@ class AppController:
entry["local_ts"] = time.time()
kind = entry.get("kind")
payload = entry.get("payload", {})
if kind == "response" and "usage" in payload:
u = payload["usage"]
for k in ["input_tokens", "output_tokens", "cache_read_input_tokens", "cache_creation_input_tokens", "total_tokens"]:
if k in u:
self.session_usage[k] += u.get(k, 0) or 0
if kind in ("tool_result", "tool_call"):
role = "Tool" if kind == "tool_result" else "Vendor API"
content = ""
@@ -1173,7 +1205,7 @@ class AppController:
self._pending_history_adds.append({
"role": "User",
"content": user_msg,
"collapsed": False,
"collapsed": True,
"ts": project_manager.now_ts()
})
try:
@@ -1183,7 +1215,7 @@ class AppController:
self._pending_history_adds.append({
"role": "AI",
"content": resp,
"collapsed": False,
"collapsed": True,
"ts": project_manager.now_ts()
})
self._recalculate_session_usage()
@@ -1669,6 +1701,7 @@ class AppController:
# Save MMA State
mma_sec = proj.setdefault("mma", {})
mma_sec["epic"] = self.ui_epic_input
mma_sec["tier_models"] = {t: {"model": d["model"], "provider": d.get("provider", "gemini")} for t, d in self.mma_tier_usage.items()}
if self.active_track:
mma_sec["active_track"] = asdict(self.active_track)
else:
@@ -1684,7 +1717,11 @@ class AppController:
}
self.config["ai"]["system_prompt"] = self.ui_global_system_prompt
self.config["projects"] = {"paths": self.project_paths, "active": self.active_project_path}
self.config["gui"] = {"show_windows": self.show_windows}
self.config["gui"] = {
"show_windows": self.show_windows,
"separate_message_panel": getattr(self, "ui_separate_message_panel", False),
"separate_response_panel": getattr(self, "ui_separate_response_panel", False),
}
theme.save_to_config(self.config)
def _do_generate(self) -> tuple[str, Path, list[dict[str, Any]], str, str]:

View File

@@ -103,6 +103,13 @@ class App:
self.node_editor_config = ed.Config()
self.node_editor_ctx = ed.create_editor(self.node_editor_config)
self.ui_selected_ticket_id: Optional[str] = None
self._autofocus_response_tab = False
gui_cfg = self.config.get("gui", {})
self.ui_separate_message_panel = gui_cfg.get("separate_message_panel", False)
self.ui_separate_response_panel = gui_cfg.get("separate_response_panel", False)
self._comms_log_cache: list[dict[str, Any]] = []
self._comms_log_dirty: bool = True
self._last_ui_focus_agent: Optional[str] = None
def _handle_approve_tool(self, user_data=None) -> None:
"""UI-level wrapper for approving a pending tool execution ask."""
@@ -149,6 +156,11 @@ class App:
def shutdown(self) -> None:
"""Cleanly shuts down the app's background tasks and saves state."""
try:
if hasattr(self, 'runner_params') and self.runner_params.ini_filename:
imgui.save_ini_settings_to_disk(self.runner_params.ini_filename)
except:
pass
self.controller.shutdown()
def _test_callback_func_write_to_file(self, data: str) -> None:
@@ -226,6 +238,7 @@ class App:
def _gui_func(self) -> None:
try:
self.perf_monitor.start_frame()
self._autofocus_response_tab = self.controller._autofocus_response_tab
# Process GUI task queue
# DEBUG: Check if tasks exist before processing
if hasattr(self, 'controller') and hasattr(self.controller, '_pending_gui_tasks'):
@@ -249,11 +262,28 @@ class App:
pass # silent — don't disrupt the GUI loop
# Sync pending comms
with self._pending_comms_lock:
if self._pending_comms and self.ui_auto_scroll_comms:
self._scroll_comms_to_bottom = True
for c in self._pending_comms:
self._comms_log.append(c)
self._pending_comms.clear()
if self._pending_comms:
if self.ui_auto_scroll_comms:
self._scroll_comms_to_bottom = True
self._comms_log_dirty = True
for c in self._pending_comms:
self._comms_log.append(c)
self._pending_comms.clear()
if self.ui_focus_agent != self._last_ui_focus_agent:
self._comms_log_dirty = True
self._last_ui_focus_agent = self.ui_focus_agent
if self._comms_log_dirty:
if self.is_viewing_prior_session:
self._comms_log_cache = self.prior_session_entries
else:
log_raw = list(self._comms_log)
if self.ui_focus_agent:
self._comms_log_cache = [e for e in log_raw if e.get("source_tier") == self.ui_focus_agent]
else:
self._comms_log_cache = log_raw
self._comms_log_dirty = False
with self._pending_tool_calls_lock:
if self._pending_tool_calls and self.ui_auto_scroll_tool_calls:
self._scroll_tool_calls_to_bottom = True
@@ -281,10 +311,11 @@ class App:
if exp:
if imgui.collapsing_header("Provider & Model"):
self._render_provider_panel()
if imgui.collapsing_header("System Prompts"):
self._render_system_prompts_panel()
if imgui.collapsing_header("Token Budget"):
self._render_token_budget_panel()
if imgui.collapsing_header("System Prompts"):
self._render_system_prompts_panel()
imgui.end()
if self.show_windows.get("MMA Dashboard", False):
exp, opened = imgui.begin("MMA Dashboard", self.show_windows["MMA Dashboard"])
@@ -327,14 +358,39 @@ class App:
self._render_discussion_panel()
imgui.end_child()
# Bottom part with tabs for message and response
if imgui.begin_tab_bar("MessageResponseTabs"):
if imgui.begin_tab_item("Message")[0]:
self._render_message_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("Response")[0]:
self._render_response_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
# Detach controls
imgui.push_style_var(imgui.StyleVar_.item_spacing, imgui.ImVec2(10, 4))
ch1, self.ui_separate_message_panel = imgui.checkbox("Pop Out Message", self.ui_separate_message_panel)
imgui.same_line()
ch2, self.ui_separate_response_panel = imgui.checkbox("Pop Out Response", self.ui_separate_response_panel)
if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
imgui.pop_style_var()
show_message_tab = not self.ui_separate_message_panel
show_response_tab = not self.ui_separate_response_panel
if show_message_tab or show_response_tab:
if imgui.begin_tab_bar("discussion_tabs"):
# Task: Auto-focus Response tab when response received
tab_flags = imgui.TabItemFlags_.none
if self._autofocus_response_tab:
tab_flags = imgui.TabItemFlags_.set_selected
self._autofocus_response_tab = False
self.controller._autofocus_response_tab = False
if show_message_tab:
if imgui.begin_tab_item("Message", None)[0]:
self._render_message_panel()
imgui.end_tab_item()
if show_response_tab:
if imgui.begin_tab_item("Response", None, tab_flags)[0]:
self._render_response_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
else:
imgui.text_disabled("Message & Response panels are detached.")
imgui.end()
if self.show_windows.get("Operations Hub", False):
exp, opened = imgui.begin("Operations Hub", self.show_windows["Operations Hub"])
@@ -354,16 +410,32 @@ class App:
if self.ui_focus_agent:
if imgui.button("x##clear_focus"):
self.ui_focus_agent = None
imgui.separator()
if imgui.begin_tab_bar("OperationsTabs"):
if imgui.begin_tab_item("Tool Calls")[0]:
self._render_tool_calls_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("Comms History")[0]:
self._render_comms_history_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
if exp:
if imgui.begin_tab_bar("ops_tabs"):
if imgui.begin_tab_item("Comms History")[0]:
self._render_comms_history_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("Tool Calls")[0]:
self._render_tool_calls_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
imgui.end()
if self.ui_separate_message_panel and self.show_windows.get("Message", False):
exp, opened = imgui.begin("Message", self.show_windows["Message"])
self.show_windows["Message"] = bool(opened)
if exp:
self._render_message_panel()
imgui.end()
if self.ui_separate_response_panel and self.show_windows.get("Response", False):
exp, opened = imgui.begin("Response", self.show_windows["Response"])
self.show_windows["Response"] = bool(opened)
if exp:
self._render_response_panel()
imgui.end()
if self.show_windows.get("Log Management", False):
self._render_log_management()
if self.show_windows["Diagnostics"]:
@@ -729,11 +801,6 @@ class App:
ch, val = imgui.checkbox(f"Enable {t_name}", val)
if ch:
self.ui_agent_tools[t_name] = val
imgui.separator()
imgui.text_colored(C_LBL, 'MMA Orchestration')
_, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80))
if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)):
self._cb_plan_epic()
def _render_track_proposal_modal(self) -> None:
if self._show_track_proposal_modal:
@@ -917,28 +984,32 @@ class App:
self.prior_session_entries.clear()
imgui.separator()
imgui.begin_child("prior_scroll", imgui.ImVec2(0, 0), False)
for idx, entry in enumerate(self.prior_session_entries):
imgui.push_id(f"prior_{idx}")
kind = entry.get("kind", entry.get("type", ""))
imgui.text_colored(C_LBL, f"#{idx+1}")
imgui.same_line()
ts = entry.get("ts", entry.get("timestamp", ""))
if ts:
imgui.text_colored(vec4(160, 160, 160), str(ts))
clipper = imgui.ListClipper()
clipper.begin(len(self.prior_session_entries))
while clipper.step():
for idx in range(clipper.display_start, clipper.display_end):
entry = self.prior_session_entries[idx]
imgui.push_id(f"prior_{idx}")
kind = entry.get("kind", entry.get("type", ""))
imgui.text_colored(C_LBL, f"#{idx+1}")
imgui.same_line()
imgui.text_colored(C_KEY, str(kind))
payload = entry.get("payload", entry)
text = payload.get("text", payload.get("message", payload.get("content", "")))
if text:
preview = str(text).replace("\\n", " ")[:200]
if self.ui_word_wrap:
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(preview)
imgui.pop_text_wrap_pos()
else:
imgui.text(preview)
imgui.separator()
imgui.pop_id()
ts = entry.get("ts", entry.get("timestamp", ""))
if ts:
imgui.text_colored(vec4(160, 160, 160), str(ts))
imgui.same_line()
imgui.text_colored(C_KEY, str(kind))
payload = entry.get("payload", entry)
text = payload.get("text", payload.get("message", payload.get("content", "")))
if text:
preview = str(text).replace("\n", " ")[:200]
if self.ui_word_wrap:
imgui.push_text_wrap_pos(imgui.get_content_region_avail().x)
imgui.text(preview)
imgui.pop_text_wrap_pos()
else:
imgui.text(preview)
imgui.separator()
imgui.pop_id()
imgui.end_child()
imgui.pop_style_color()
return
@@ -1000,7 +1071,7 @@ class App:
if not self.is_viewing_prior_session:
imgui.separator()
if imgui.button("+ Entry"):
self.disc_entries.append({"role": self.disc_roles[0] if self.disc_roles else "User", "content": "", "collapsed": False, "ts": project_manager.now_ts()})
self.disc_entries.append({"role": self.disc_roles[0] if self.disc_roles else "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
imgui.same_line()
if imgui.button("-All"):
for e in self.disc_entries: e["collapsed"] = True
@@ -1076,7 +1147,7 @@ class App:
if collapsed:
imgui.same_line()
if imgui.button("Ins"):
self.disc_entries.insert(i, {"role": "User", "content": "", "collapsed": False, "ts": project_manager.now_ts()})
self.disc_entries.insert(i, {"role": "User", "content": "", "collapsed": True, "ts": project_manager.now_ts()})
imgui.same_line()
self._render_text_viewer(f"Entry #{i+1}", entry["content"])
imgui.same_line()
@@ -1147,8 +1218,9 @@ class App:
if ch:
if hasattr(ai_client, "_gemini_cli_adapter") and ai_client._gemini_cli_adapter:
ai_client._gemini_cli_adapter.binary_path = self.ui_gemini_cli_path
imgui.separator()
imgui.text("Telemetry")
def _render_token_budget_panel(self) -> None:
imgui.text("Session Telemetry")
usage = self.session_usage
total = usage["input_tokens"] + usage["output_tokens"]
if total == 0 and usage.get("total_tokens", 0) > 0:
@@ -1160,8 +1232,8 @@ class App:
imgui.text_colored(C_LBL, f" Cache Read: {usage['cache_read_input_tokens']:,} Creation: {usage['cache_creation_input_tokens']:,}")
if self._gemini_cache_text:
imgui.text_colored(C_SUB, self._gemini_cache_text)
imgui.separator()
def _render_token_budget_panel(self) -> None:
if self._token_stats_dirty:
self._token_stats_dirty = False
self._refresh_api_metrics({}, md_content=self._last_stable_md or None)
@@ -1316,6 +1388,7 @@ class App:
if imgui.button("Exit Prior Session"):
self.is_viewing_prior_session = False
self.prior_session_entries.clear()
self._comms_log_dirty = True
self.ai_status = "idle"
imgui.separator()
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
@@ -1334,62 +1407,77 @@ class App:
imgui.same_line()
imgui.text_colored(C_TR, "tool_result")
imgui.separator()
# Use tinted background for prior session
if self.is_viewing_prior_session:
imgui.push_style_color(imgui.Col_.child_bg, vec4(40, 30, 20))
imgui.begin_child("comms_scroll", imgui.ImVec2(0, 0), False, imgui.WindowFlags_.horizontal_scrollbar)
log_to_render = self.prior_session_entries if self.is_viewing_prior_session else list(self._comms_log)
if self.ui_focus_agent and not self.is_viewing_prior_session:
log_to_render = [e for e in log_to_render if e.get("source_tier") == self.ui_focus_agent]
clipper = imgui.ListClipper()
clipper.begin(len(log_to_render))
while clipper.step():
for i_minus_one in range(clipper.display_start, clipper.display_end):
i = i_minus_one + 1
entry = log_to_render[i_minus_one]
local_ts = entry.get("local_ts", 0)
blink_alpha = 0.0
if entry.get("_blinking", False):
elapsed = time.time() - entry.get("_blink_start", 0)
if elapsed < 1.5:
blink_alpha = 0.3 + 0.7 * abs(math.sin(elapsed * 8 * math.pi))
log_to_render = self._comms_log_cache
flags = imgui.TableFlags_.resizable | imgui.TableFlags_.hideable | imgui.TableFlags_.borders_inner_v | imgui.TableFlags_.row_bg | imgui.TableFlags_.scroll_y
if imgui.begin_table("comms_table", 5, flags):
imgui.table_setup_column("#", imgui.TableColumnFlags_.width_fixed, 40)
imgui.table_setup_column("Tier", imgui.TableColumnFlags_.width_fixed, 60)
imgui.table_setup_column("Type", imgui.TableColumnFlags_.width_fixed, 80)
imgui.table_setup_column("!", imgui.TableColumnFlags_.width_fixed, 20)
imgui.table_setup_column("Content", imgui.TableColumnFlags_.width_stretch)
clipper = imgui.ListClipper()
clipper.begin(len(log_to_render))
while clipper.step():
for i in range(clipper.display_start, clipper.display_end):
entry = log_to_render[i]
imgui.table_next_row()
i_display = i + 1
source = entry.get("source_tier", "main")
msg_type = entry.get("type", "?")
content_text = entry.get("content", "")
if len(content_text) > COMMS_CLAMP_CHARS:
content_text = content_text[:COMMS_CLAMP_CHARS] + "..."
# FG Color based on type
fg = C_VAL
if msg_type == "request": fg = C_REQ
elif msg_type == "response": fg = C_RES
elif msg_type == "tool_call": fg = C_TC
elif msg_type == "tool_result": fg = C_TR
elif msg_type == "error": fg = vec4(255, 80, 80)
imgui.table_next_column()
imgui.text_colored(C_LBL, f"#{i_display}")
imgui.table_next_column()
imgui.text_colored(C_SUB, f"[{source}]")
imgui.table_next_column()
imgui.text_colored(fg, msg_type)
imgui.table_next_column()
elapsed = time.time() - entry.get("local_ts", 0)
if elapsed < 3.0:
blink = (math.sin(elapsed * 15) * 0.5 + 0.5)
imgui.text_colored(vec4(255, 255, 0, blink), "*")
else:
entry["_blinking"] = False
source = entry.get("source_tier", "?")
msg_type = entry.get("type", "?")
content_text = entry.get("content", "")
if len(content_text) > COMMS_CLAMP_CHARS:
content_text = content_text[:COMMS_CLAMP_CHARS] + "..."
if msg_type == "request":
bg = vec4(60, 40, 20, 180)
fg = C_REQ
elif msg_type == "response":
bg = vec4(20, 60, 40, 180)
fg = C_RES
elif msg_type == "tool_call":
bg = vec4(40, 40, 80, 180)
fg = C_TC
elif msg_type == "tool_result":
bg = vec4(80, 40, 40, 180)
fg = C_TR
elif msg_type == "error":
bg = vec4(80, 20, 20, 180)
fg = vec4(1, 0.3, 0.3, 1)
else:
bg = vec4(50, 50, 50, 180)
fg = C_OUT if source == "AI" else C_IN
imgui.push_style_color(imgui.Col_.child_bg, bg)
imgui.push_style_color(imgui.Col_.text, fg)
imgui.text_colored(C_KEY, f"#{i}")
imgui.same_line()
imgui.text_colored(C_LBL, f"[{source}]")
imgui.same_line()
imgui.text_colored(C_LBL, msg_type)
if blink_alpha > 0:
imgui.same_line()
imgui.text_colored(vec4(1, 1, 0, blink_alpha), "")
imgui.same_line()
imgui.text_wrapped(content_text)
imgui.pop_style_color(2)
imgui.text("")
imgui.table_next_column()
if self.ui_word_wrap:
imgui.push_text_wrap_pos(0.0)
imgui.text_unformatted(content_text)
imgui.pop_text_wrap_pos()
else:
imgui.text_unformatted(content_text)
imgui.end_table()
if self._scroll_comms_to_bottom:
imgui.set_scroll_here_y(1.0)
self._scroll_comms_to_bottom = False
imgui.end_child()
if self.is_viewing_prior_session:
imgui.pop_style_color()
@@ -1482,6 +1570,13 @@ class App:
elif self.mma_status == "done": status_col = imgui.ImVec4(0, 1, 0, 1)
elif self.mma_status == "error": status_col = imgui.ImVec4(1, 0, 0, 1)
imgui.text_colored(status_col, self.mma_status.upper())
imgui.separator()
imgui.text_colored(C_LBL, 'Epic Planning (Tier 1)')
_, self.ui_epic_input = imgui.input_text_multiline('##epic_input', self.ui_epic_input, imgui.ImVec2(-1, 80))
if imgui.button('Plan Epic (Tier 1)', imgui.ImVec2(-1, 0)):
self._cb_plan_epic()
imgui.separator()
# 0. Conductor Setup
if imgui.collapsing_header("Conductor Setup"):
@@ -1631,12 +1726,36 @@ class App:
imgui.text(f"{tier}:")
imgui.same_line()
current_model = self.mma_tier_usage[tier].get("model", "unknown")
if imgui.begin_combo(f"##combo_{tier}", current_model):
for model in self.available_models:
current_provider = self.mma_tier_usage[tier].get("provider", "gemini")
imgui.push_id(f"tier_cfg_{tier}")
# Provider selection
imgui.push_item_width(100)
if imgui.begin_combo("##prov", current_provider):
for p in PROVIDERS:
if imgui.selectable(p, p == current_provider)[0]:
self.mma_tier_usage[tier]["provider"] = p
# Reset model to default for provider
models_list = self.controller.all_available_models.get(p, [])
if models_list:
self.mma_tier_usage[tier]["model"] = models_list[0]
imgui.end_combo()
imgui.pop_item_width()
imgui.same_line()
# Model selection
imgui.push_item_width(-1)
models_list = self.controller.all_available_models.get(current_provider, [])
if imgui.begin_combo("##model", current_model):
for model in models_list:
if imgui.selectable(model, current_model == model)[0]:
self.mma_tier_usage[tier]["model"] = model
self.project.setdefault("mma", {}).setdefault("tier_models", {})[tier] = model
imgui.end_combo()
imgui.pop_item_width()
imgui.pop_id()
imgui.separator()
# 4. Task DAG Visualizer
imgui.text("Task DAG")
@@ -1820,137 +1939,6 @@ class App:
pass
imgui.end_child()
imgui.text_colored(vec4(200, 220, 160), f"Status: {self.ai_status}")
imgui.same_line()
if imgui.button("Clear##comms"):
ai_client.clear_comms_log()
self._comms_log.clear()
imgui.same_line()
if imgui.button("Load Log"):
self.cb_load_prior_log()
if self.is_viewing_prior_session:
imgui.same_line()
if imgui.button("Exit Prior Session"):
self.is_viewing_prior_session = False
self.prior_session_entries.clear()
self.ai_status = "idle"
imgui.separator()
imgui.text_colored(vec4(255, 200, 100), "VIEWING PRIOR SESSION")
imgui.separator()
imgui.text_colored(C_OUT, "OUT")
imgui.same_line()
imgui.text_colored(C_REQ, "request")
imgui.same_line()
imgui.text_colored(C_TC, "tool_call")
imgui.same_line()
imgui.text(" ")
imgui.same_line()
imgui.text_colored(C_IN, "IN")
imgui.same_line()
imgui.text_colored(C_RES, "response")
imgui.same_line()
imgui.text_colored(C_TR, "tool_result")
imgui.separator()
# Use tinted background for prior session
if self.is_viewing_prior_session:
imgui.push_style_color(imgui.Col_.child_bg, vec4(40, 30, 20))
imgui.begin_child("comms_scroll", imgui.ImVec2(0, 0), False, imgui.WindowFlags_.horizontal_scrollbar)
log_to_render = self.prior_session_entries if self.is_viewing_prior_session else list(self._comms_log)
if self.ui_focus_agent and not self.is_viewing_prior_session:
log_to_render = [e for e in log_to_render if e.get("source_tier") == self.ui_focus_agent]
for idx_minus_one, entry in enumerate(log_to_render):
idx = idx_minus_one + 1
local_ts = entry.get("local_ts", 0)
# Blink effect
blink_alpha = 0.0
if local_ts > 0 and not self.is_viewing_prior_session:
elapsed = time.time() - local_ts
if elapsed < 3.0:
blink_alpha = (1.0 - (elapsed / 3.0)) * 0.3 * (math.sin(elapsed * 10) * 0.5 + 0.5)
imgui.push_id(f"comms_{idx}")
if blink_alpha > 0:
# Draw a background highlight for the entry
imgui.get_window_draw_list()
imgui.get_cursor_screen_pos()
# Estimate height or just use a fixed height for the background
# It's better to wrap the entry in a group or just use separators
# For now, let's just use the style color push if we are sure we pop it
imgui.push_style_color(imgui.Col_.child_bg, vec4(0, 255, 0, blink_alpha))
# We still need a child or a group to apply the background to
imgui.begin_group()
d = entry.get("direction", "IN")
k = entry.get("kind", "response")
imgui.text_colored(vec4(160, 160, 160), f"#{idx}")
imgui.same_line()
imgui.text_colored(vec4(160, 160, 160), entry.get("ts", "00:00:00"))
imgui.same_line()
imgui.text_colored(DIR_COLORS.get(d, C_VAL), d)
imgui.same_line()
imgui.text_colored(KIND_COLORS.get(k, C_VAL), k)
imgui.same_line()
imgui.text_colored(C_LBL, f"{entry.get('provider', '?')}/{entry.get('model', '?')}")
imgui.same_line()
tier_label = entry.get("source_tier") or "main"
imgui.text_colored(C_SUB, f"[{tier_label}]")
payload = entry.get("payload", {})
if k == "request":
self._render_heavy_text("message", payload.get("message", ""))
elif k == "response":
imgui.text_colored(C_LBL, "round:")
imgui.same_line()
imgui.text_colored(C_VAL, str(payload.get("round", "")))
imgui.text_colored(C_LBL, "stop_reason:")
imgui.same_line()
imgui.text_colored(vec4(255, 200, 120), str(payload.get("stop_reason", "")))
text = payload.get("text", "")
if text: self._render_heavy_text("text", text)
imgui.text_colored(C_LBL, "tool_calls:")
tcs = payload.get("tool_calls", [])
if not tcs: imgui.text_colored(C_VAL, " (none)")
for tc_i, tc in enumerate(tcs):
imgui.text_colored(C_KEY, f" call[{tc_i}] {tc.get('name', '?')}")
if tc.get("id"):
imgui.text_colored(C_LBL, " id:")
imgui.same_line()
imgui.text_colored(C_VAL, str(tc["id"]))
if "args" in tc or "input" in tc:
self._render_heavy_text(f"call_{tc_i}_args", str(tc.get("args") or tc.get("input")))
elif k == "tool_call":
imgui.text_colored(C_KEY, payload.get("name", "?"))
if payload.get("id"):
imgui.text_colored(C_LBL, " id:")
imgui.same_line()
imgui.text_colored(C_VAL, str(payload["id"]))
if "script" in payload: self._render_heavy_text("script", payload["script"])
if "args" in payload: self._render_heavy_text("args", str(payload["args"]))
elif k == "tool_result":
imgui.text_colored(C_KEY, payload.get("name", "?"))
if payload.get("id"):
imgui.text_colored(C_LBL, " id:")
imgui.same_line()
imgui.text_colored(C_VAL, str(payload["id"]))
if "output" in payload: self._render_heavy_text("output", payload["output"])
if "results" in payload:
for r_i, r in enumerate(payload["results"]):
imgui.text_colored(C_LBL, f" Result[{r_i}]:")
self._render_heavy_text(f"res_{r_i}", str(r))
if "usage" in payload:
u = payload["usage"]
u_str = f"In: {u.get('input_tokens', 0)} Out: {u.get('output_tokens', 0)}"
if u.get("cache_read_input_tokens"): u_str += f" (Cache: {u['cache_read_input_tokens']})"
imgui.text_colored(C_SUB, f" Usage: {u_str}")
imgui.separator()
if blink_alpha > 0:
imgui.end_group()
imgui.pop_style_color()
imgui.pop_id()
if self._scroll_comms_to_bottom:
imgui.set_scroll_here_y(1.0)
self._scroll_comms_to_bottom = False
imgui.end_child()
if self.is_viewing_prior_session:
imgui.pop_style_color()
def _render_system_prompts_panel(self) -> None:
imgui.text("Global System Prompt (all projects)")
ch, self.ui_global_system_prompt = imgui.input_text_multiline("##gsp", self.ui_global_system_prompt, imgui.ImVec2(-1, 100))
@@ -1969,6 +1957,13 @@ class App:
if imgui.selectable(p, p == cp)[0]:
theme.apply(p)
imgui.end_combo()
imgui.separator()
ch1, self.ui_separate_message_panel = imgui.checkbox("Separate Message Panel", self.ui_separate_message_panel)
ch2, self.ui_separate_response_panel = imgui.checkbox("Separate Response Panel", self.ui_separate_response_panel)
if ch1: self.show_windows["Message"] = self.ui_separate_message_panel
if ch2: self.show_windows["Response"] = self.ui_separate_response_panel
imgui.separator()
imgui.text("Font")
imgui.push_item_width(-150)

View File

@@ -57,7 +57,7 @@ def parse_history_entries(history_strings: list[str], roles: list[str]) -> list[
content = rest[match.end():].strip()
else:
content = rest
entries.append({"role": role, "content": content, "collapsed": False, "ts": ts})
entries.append({"role": role, "content": content, "collapsed": True, "ts": ts})
return entries
@dataclass