diff --git a/src/gui_2.py b/src/gui_2.py index 5475d2d6..659ef357 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -258,6 +258,47 @@ def _apply_runtime_caps_override(app: "App", caps: "VendorCapabilities") -> "Ven return replace(caps, local=True) return caps + +def _render_v2_capability_badges(caps: "VendorCapabilities") -> None: + """Render small colored badges for the 11 v2 capability flags. + + Only fields where caps. is True are shown. Each badge + has a tooltip with the field name. Per-field colors map to + the existing theme convention: green for supported, grey for + not. Fields with no entry (False) are silently omitted. + + Added 2026-06-11 as part of Phase 5 t5_4 (UI adaptations for + new v2 fields). The 11 fields are the v2 matrix fields beyond + the original 7 v1 fields (vision, tool_calling, caching, + streaming, model_discovery, context_window, cost_tracking) + which are already gated elsewhere in the GUI. + [C: src/gui_2.py:render_provider_panel] +""" + badged_fields: list[tuple[str, str]] = [ + ("reasoning", "Reasoning"), + ("structured_output", "JSON"), + ("code_execution", "Code"), + ("web_search", "Web"), + ("x_search", "X"), + ("file_search", "File"), + ("mcp_support", "MCP"), + ("audio", "Audio"), + ("video", "Video"), + ("grounding", "Ground"), + ("computer_use", "Comp"), + ] + enabled: list[tuple[str, str]] = [] + for field_name, label in badged_fields: + if getattr(caps, field_name, False): + enabled.append((field_name, label)) + if not enabled: + return + imgui.text("Capabilities") + for field_name, label in enabled: + imgui.same_line() + imgui.text_colored(theme.get_color("status_success"), f" [{label}]") + if imgui.is_item_hovered(): + imgui.set_tooltip(f"caps.{field_name}=True") class App: """The main ImGui interface orchestrator for Manual Slop.""" @@ -2334,6 +2375,7 @@ def render_provider_panel(app: App) -> None: if app.current_provider == "llama": base_url = getattr(ai_client, "_llama_base_url", "") imgui.set_tooltip(f"Local backend: {base_url or 'unknown'}" if base_url else "Local backend") + _render_v2_capability_badges(caps) imgui.separator() imgui.text("Model") if imgui.begin_list_box("##models", imgui.ImVec2(-1, 120)): diff --git a/tests/test_vendor_capabilities.py b/tests/test_vendor_capabilities.py index 139e2996..733ade4d 100644 --- a/tests/test_vendor_capabilities.py +++ b/tests/test_vendor_capabilities.py @@ -193,3 +193,30 @@ def test_deepseek_wildcard_falls_back_to_v3_defaults() -> None: caps = get_capabilities('deepseek', 'deepseek-future-unregistered') assert caps.reasoning is True assert caps.structured_output is True + +def test_v2_capability_badge_helper_contains_all_11_v2_fields() -> None: + """The GUI's v2 capability badges should render badges for all + 11 v2 fields. This test ensures the helper stays in sync with + the v2 matrix.""" + import src.gui_2 + import inspect + src_lines: str = inspect.getsource(src.gui_2._render_v2_capability_badges) + known_v2_fields: list[str] = [ + "reasoning", "structured_output", "code_execution", + "web_search", "x_search", "file_search", "mcp_support", + "audio", "video", "grounding", "computer_use", + ] + for field in known_v2_fields: + assert f'"{field}"' in src_lines, f'v2 field {field!r} missing from _render_v2_capability_badges helper' + +def test_v2_capability_badge_helper_skips_disabled_fields() -> None: + """Sanity: a caps with all v2 fields False should produce no + badges. We can verify this by passing a default-constructed + VendorCapabilities and asserting the helper returns without + erroring. (We can't easily verify the ImGui output without + a live context, but we can verify the helper is a no-op on + the no-cap case.)""" + from src.gui_2 import _render_v2_capability_badges + from src.vendor_capabilities import VendorCapabilities + empty_caps = VendorCapabilities(vendor='test', model='empty') + _render_v2_capability_badges(empty_caps)