Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { ChatInput } from "../chat-input";
import { SandboxHostStyleProvider } from "@/contexts/sandbox-host-style-context";
import {
SandboxHostStyleProvider,
SandboxHostThemeProvider,
} from "@/contexts/sandbox-host-style-context";
import type { ModelDefinition } from "@/shared/types";

vi.mock("@/stores/preferences/preferences-provider", () => ({
usePreferencesStore: (selector: (state: { themeMode: "light" }) => unknown) =>
selector({ themeMode: "light" }),
}));
Comment on lines +10 to +13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the shared client store preset here.

Inlining usePreferencesStore in this file bypasses the standard client-test harness and only models themeMode. Please switch this to the shared store preset so the test environment stays aligned with the rest of the suite.

As per coding guidelines, mcpjam-inspector/client/**/__tests__/*.test.{ts,tsx}: Use mock presets from client/src/test/mocks/ including mcpApiPresets and storePresets in client tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/components/chat-v2/__tests__/ChatInput.test.tsx`
around lines 10 - 13, The test currently inlines usePreferencesStore which
bypasses the shared test harness; replace the inline mock with the shared store
preset from the client test mocks so the suite uses the same store shape. Remove
the current vi.mock implementation for usePreferencesStore and instead apply the
shared store preset (storePresets) together with any existing mcpApiPresets used
in tests so the preference state (and other stores) are provided consistently;
locate the mock setup in ChatInput.test.tsx where usePreferencesStore is mocked
and swap it to import/apply storePresets (and mcpApiPresets if needed) from the
test mocks.


// Mock child components
vi.mock("../chat-input/model-selector", () => ({
ModelSelector: ({
Expand Down Expand Up @@ -160,6 +168,21 @@ describe("ChatInput", () => {
"bg-[#1f1f1f]",
);
});

it("keeps the textarea transparent inside a dark host-scoped composer", () => {
render(
<SandboxHostStyleProvider value="chatgpt">
<SandboxHostThemeProvider value="dark">
<ChatInput {...defaultProps} />
</SandboxHostThemeProvider>
</SandboxHostStyleProvider>,
);

expect(screen.getByPlaceholderText("Type your message...")).toHaveClass(
"bg-transparent",
"dark:bg-transparent",
);
});
});

describe("input handling", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe("MessageView", () => {
);
});

it("uses a neutral placeholder for sandbox assistant avatars", () => {
it("renders the provider logo for sandbox assistant avatars", () => {
const message = createMessage({
role: "assistant",
parts: [{ type: "text", text: "Hello" }],
Expand All @@ -157,8 +157,8 @@ describe("MessageView", () => {
</SandboxHostStyleProvider>,
);

expect(screen.getByLabelText("Assistant")).toBeInTheDocument();
expect(screen.queryByRole("img")).not.toBeInTheDocument();
expect(screen.getByLabelText("GPT-4 assistant")).toBeInTheDocument();
expect(screen.getByRole("img")).toHaveAttribute("alt", "gpt-4 logo");
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ describe("ThinkingIndicator", () => {
expect(screen.getByRole("img")).toHaveAttribute("alt", "gpt-4 logo");
});

it("renders a neutral placeholder inside sandboxes", () => {
it("renders the provider logo inside sandboxes", () => {
render(
<SandboxHostStyleProvider value="chatgpt">
<ThinkingIndicator model={defaultModel} />
</SandboxHostStyleProvider>,
);

expect(screen.getByLabelText("Assistant")).toBeInTheDocument();
expect(screen.queryByRole("img")).not.toBeInTheDocument();
expect(screen.getByLabelText("GPT-4 assistant")).toBeInTheDocument();
expect(screen.getByRole("img")).toHaveAttribute("alt", "gpt-4 logo");
});
});
38 changes: 31 additions & 7 deletions mcpjam-inspector/client/src/components/chat-v2/chat-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ import { MCPPromptResultCard } from "@/components/chat-v2/chat-input/prompts/mcp
import type { SkillResult } from "@/components/chat-v2/chat-input/skills/skill-types";
import { SkillResultCard } from "@/components/chat-v2/chat-input/skills/skill-result-card";
import { usePostHog } from "posthog-js/react";
import { useSandboxHostStyle } from "@/contexts/sandbox-host-style-context";
import {
useSandboxHostStyle,
useSandboxHostTheme,
} from "@/contexts/sandbox-host-style-context";
import { usePreferencesStore } from "@/stores/preferences/preferences-provider";

interface ChatInputProps {
value: string;
Expand Down Expand Up @@ -134,6 +138,10 @@ export function ChatInput({
}: ChatInputProps) {
const posthog = usePostHog();
const sandboxHostStyle = useSandboxHostStyle();
const sandboxHostTheme = useSandboxHostTheme();
const globalThemeMode = usePreferencesStore((s) => s.themeMode);
const resolvedThemeMode = sandboxHostTheme ?? globalThemeMode;
const isDarkSandboxTheme = resolvedThemeMode === "dark";
const formRef = useRef<HTMLFormElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
Expand Down Expand Up @@ -328,21 +336,37 @@ export function ChatInput({

const composerClasses =
sandboxHostStyle === "chatgpt"
? "sandbox-host-composer rounded-[1.75rem] border-transparent bg-[#f4f4f4] shadow-none dark:bg-[#2f2f2f]"
? cn(
"sandbox-host-composer rounded-[1.75rem] border-transparent shadow-none",
isDarkSandboxTheme ? "bg-[#2f2f2f]" : "bg-[#f4f4f4]",
)
: sandboxHostStyle === "claude"
? "sandbox-host-composer rounded-[1.35rem] border-[#d7cfbf] bg-[#f5f0e8] shadow-none dark:border-[#4b463d] dark:bg-[#34322e]"
? cn(
"sandbox-host-composer rounded-[1.35rem] shadow-none",
isDarkSandboxTheme
? "border-[#4b463d] bg-[#34322e]"
: "border-[#d7cfbf] bg-[#f5f0e8]",
)
: "rounded-3xl border border-border/40 bg-muted/70";
const activeSubmitButtonClasses =
sandboxHostStyle === "chatgpt"
? "bg-[#1f1f1f] text-white hover:bg-[#303030] dark:bg-[#f4f4f4] dark:text-[#1f1f1f] dark:hover:bg-[#e8e8e8]"
? isDarkSandboxTheme
? "bg-[#f4f4f4] text-[#1f1f1f] hover:bg-[#e8e8e8]"
: "bg-[#1f1f1f] text-white hover:bg-[#303030]"
: sandboxHostStyle === "claude"
? "bg-[#e27d47] text-white hover:bg-[#d16f3d] dark:bg-[#d07b53] dark:text-[#fff7f0] dark:hover:bg-[#c06f49]"
? isDarkSandboxTheme
? "bg-[#d07b53] text-[#fff7f0] hover:bg-[#c06f49]"
: "bg-[#e27d47] text-white hover:bg-[#d16f3d]"
: "bg-primary text-primary-foreground hover:bg-primary/90";
const inactiveSubmitButtonClasses =
sandboxHostStyle === "chatgpt"
? "bg-[#e7e7e7] text-[#9b9b9b] cursor-not-allowed dark:bg-[#3a3a3a] dark:text-[#8a8a8a]"
? isDarkSandboxTheme
? "bg-[#3a3a3a] text-[#8a8a8a] cursor-not-allowed"
: "bg-[#e7e7e7] text-[#9b9b9b] cursor-not-allowed"
: sandboxHostStyle === "claude"
? "bg-[#ebe5dc] text-[#b6ada0] cursor-not-allowed dark:bg-[#45413b] dark:text-[#8d857a]"
? isDarkSandboxTheme
? "bg-[#45413b] text-[#8d857a] cursor-not-allowed"
: "bg-[#ebe5dc] text-[#b6ada0] cursor-not-allowed"
: "bg-muted text-muted-foreground cursor-not-allowed";

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Standard React component for Vite
import { getProviderLogoFromProvider } from "../../shared/chat-helpers";
import { cn } from "@/lib/chat-utils";
import { getProviderColor } from "../../shared/chat-helpers";
import { getProviderColorForTheme } from "../../shared/chat-helpers";
import { usePreferencesStore } from "@/stores/preferences/preferences-provider";
import { useSandboxHostTheme } from "@/contexts/sandbox-host-style-context";

interface ProviderLogoProps {
provider: string;
Expand All @@ -15,7 +16,9 @@ export function ProviderLogo({
customProviderName,
}: ProviderLogoProps) {
const themeMode = usePreferencesStore((s) => s.themeMode);
const logoSrc = getProviderLogoFromProvider(provider, themeMode);
const sandboxHostTheme = useSandboxHostTheme();
const resolvedThemeMode = sandboxHostTheme ?? themeMode;
const logoSrc = getProviderLogoFromProvider(provider, resolvedThemeMode);

if (!logoSrc) {
// Custom providers: first-letter badge matching the Settings tab style
Expand All @@ -28,7 +31,12 @@ export function ProviderLogo({
);
}
return (
<div className={cn("h-3 w-3 rounded-sm", getProviderColor(provider))} />
<div
className={cn(
"h-3 w-3 rounded-sm",
getProviderColorForTheme(provider, resolvedThemeMode),
)}
/>
);
} else {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,13 @@ export function getAssistantAvatarDescriptor({
themeMode,
sandboxHostStyle,
}: AssistantAvatarOptions): AssistantAvatarDescriptor {
if (sandboxHostStyle !== null) {
return {
logoSrc: null,
logoAlt: null,
avatarClasses: DEFAULT_AVATAR_CLASSES,
ariaLabel: "Assistant",
};
}

return {
logoSrc: getProviderLogoFromModel(model, themeMode),
logoAlt: `${model.id} logo`,
avatarClasses: DEFAULT_AVATAR_CLASSES,
avatarClasses:
sandboxHostStyle !== null
? `sandbox-host-assistant-avatar ${DEFAULT_AVATAR_CLASSES}`
: DEFAULT_AVATAR_CLASSES,
ariaLabel: `${model.name} assistant`,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,35 +89,48 @@ export const getProviderLogoFromModel = (
};

export const getProviderColor = (provider: string) => {
return getProviderColorForTheme(provider);
};

export const getProviderColorForTheme = (
provider: string,
themeMode?: "light" | "dark" | "system",
) => {
const resolveThemeClasses = (lightClasses: string, darkClasses: string) => {
if (themeMode === "light") return lightClasses;
if (themeMode === "dark") return darkClasses;
return `${lightClasses} dark:${darkClasses}`;
};

switch (provider) {
case "anthropic":
return "text-orange-600 dark:text-orange-400";
return resolveThemeClasses("text-orange-600", "text-orange-400");
case "openai":
return "text-green-600 dark:text-green-400";
return resolveThemeClasses("text-green-600", "text-green-400");
case "deepseek":
return "text-blue-600 dark:text-blue-400";
return resolveThemeClasses("text-blue-600", "text-blue-400");
case "google":
return "text-red-600 dark:text-red-400";
return resolveThemeClasses("text-red-600", "text-red-400");
case "mistral":
return "text-orange-500 dark:text-orange-400";
return resolveThemeClasses("text-orange-500", "text-orange-400");
case "ollama":
return "text-gray-600 dark:text-gray-400";
return resolveThemeClasses("text-gray-600", "text-gray-400");
case "xai":
return "text-purple-600 dark:text-purple-400";
return resolveThemeClasses("text-purple-600", "text-purple-400");
case "azure":
return "text-purple-600 dark:text-purple-400";
return resolveThemeClasses("text-purple-600", "text-purple-400");
case "custom":
return "bg-gradient-to-br from-teal-500 to-cyan-600";
case "moonshotai":
return "text-cyan-600 dark:text-cyan-400";
return resolveThemeClasses("text-cyan-600", "text-cyan-400");
case "z-ai":
return "text-indigo-600 dark:text-indigo-400";
return resolveThemeClasses("text-indigo-600", "text-indigo-400");
case "minimax":
return "text-pink-600 dark:text-pink-400";
return resolveThemeClasses("text-pink-600", "text-pink-400");
case "meta":
return "text-blue-500 dark:text-blue-400";
return resolveThemeClasses("text-blue-500", "text-blue-400");
default:
return "text-blue-600 dark:text-blue-400";
return resolveThemeClasses("text-blue-600", "text-blue-400");
Comment on lines +95 to +133
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Restore the dropped provider color mappings.

This switch no longer handles openrouter, and it swaps the old moonshotai / z-ai colors from mcpjam-inspector/client/src/lib/provider-logos.ts:92-123. Fallback badges will quietly regress.

Patch sketch
     case "custom":
       return "bg-gradient-to-br from-teal-500 to-cyan-600";
+    case "openrouter":
+      return resolveThemeClasses("text-purple-500", "text-purple-400");
     case "moonshotai":
-      return resolveThemeClasses("text-cyan-600", "text-cyan-400");
+      return resolveThemeClasses("text-indigo-600", "text-indigo-400");
     case "z-ai":
-      return resolveThemeClasses("text-indigo-600", "text-indigo-400");
+      return resolveThemeClasses("text-cyan-600", "text-cyan-400");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getProviderColorForTheme = (
provider: string,
themeMode?: "light" | "dark" | "system",
) => {
const resolveThemeClasses = (lightClasses: string, darkClasses: string) => {
if (themeMode === "light") return lightClasses;
if (themeMode === "dark") return darkClasses;
return `${lightClasses} dark:${darkClasses}`;
};
switch (provider) {
case "anthropic":
return "text-orange-600 dark:text-orange-400";
return resolveThemeClasses("text-orange-600", "text-orange-400");
case "openai":
return "text-green-600 dark:text-green-400";
return resolveThemeClasses("text-green-600", "text-green-400");
case "deepseek":
return "text-blue-600 dark:text-blue-400";
return resolveThemeClasses("text-blue-600", "text-blue-400");
case "google":
return "text-red-600 dark:text-red-400";
return resolveThemeClasses("text-red-600", "text-red-400");
case "mistral":
return "text-orange-500 dark:text-orange-400";
return resolveThemeClasses("text-orange-500", "text-orange-400");
case "ollama":
return "text-gray-600 dark:text-gray-400";
return resolveThemeClasses("text-gray-600", "text-gray-400");
case "xai":
return "text-purple-600 dark:text-purple-400";
return resolveThemeClasses("text-purple-600", "text-purple-400");
case "azure":
return "text-purple-600 dark:text-purple-400";
return resolveThemeClasses("text-purple-600", "text-purple-400");
case "custom":
return "bg-gradient-to-br from-teal-500 to-cyan-600";
case "moonshotai":
return "text-cyan-600 dark:text-cyan-400";
return resolveThemeClasses("text-cyan-600", "text-cyan-400");
case "z-ai":
return "text-indigo-600 dark:text-indigo-400";
return resolveThemeClasses("text-indigo-600", "text-indigo-400");
case "minimax":
return "text-pink-600 dark:text-pink-400";
return resolveThemeClasses("text-pink-600", "text-pink-400");
case "meta":
return "text-blue-500 dark:text-blue-400";
return resolveThemeClasses("text-blue-500", "text-blue-400");
default:
return "text-blue-600 dark:text-blue-400";
return resolveThemeClasses("text-blue-600", "text-blue-400");
export const getProviderColorForTheme = (
provider: string,
themeMode?: "light" | "dark" | "system",
) => {
const resolveThemeClasses = (lightClasses: string, darkClasses: string) => {
if (themeMode === "light") return lightClasses;
if (themeMode === "dark") return darkClasses;
return `${lightClasses} dark:${darkClasses}`;
};
switch (provider) {
case "anthropic":
return resolveThemeClasses("text-orange-600", "text-orange-400");
case "openai":
return resolveThemeClasses("text-green-600", "text-green-400");
case "deepseek":
return resolveThemeClasses("text-blue-600", "text-blue-400");
case "google":
return resolveThemeClasses("text-red-600", "text-red-400");
case "mistral":
return resolveThemeClasses("text-orange-500", "text-orange-400");
case "ollama":
return resolveThemeClasses("text-gray-600", "text-gray-400");
case "xai":
return resolveThemeClasses("text-purple-600", "text-purple-400");
case "azure":
return resolveThemeClasses("text-purple-600", "text-purple-400");
case "custom":
return "bg-gradient-to-br from-teal-500 to-cyan-600";
case "openrouter":
return resolveThemeClasses("text-purple-500", "text-purple-400");
case "moonshotai":
return resolveThemeClasses("text-indigo-600", "text-indigo-400");
case "z-ai":
return resolveThemeClasses("text-cyan-600", "text-cyan-400");
case "minimax":
return resolveThemeClasses("text-pink-600", "text-pink-400");
case "meta":
return resolveThemeClasses("text-blue-500", "text-blue-400");
default:
return resolveThemeClasses("text-blue-600", "text-blue-400");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@mcpjam-inspector/client/src/components/chat-v2/shared/chat-helpers.ts` around
lines 95 - 133, The switch in getProviderColorForTheme accidentally dropped the
"openrouter" case and swapped colors for "moonshotai" and "z-ai" compared to the
canonical mappings, causing fallback/regression in badges; update the
getProviderColorForTheme function (and its inner resolveThemeClasses) to add a
case for "openrouter" with the correct light/dark classes and restore the
original color pairs for "moonshotai" and "z-ai" to match the mappings in
provider-logos.ts (use the same class strings there), ensuring all other
provider cases remain unchanged.

}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ import { MessageCircle } from "lucide-react";

import { ModelDefinition } from "@/shared/types";
import { usePreferencesStore } from "@/stores/preferences/preferences-provider";
import { useSandboxHostStyle } from "@/contexts/sandbox-host-style-context";
import {
useSandboxHostStyle,
useSandboxHostTheme,
} from "@/contexts/sandbox-host-style-context";
import { getAssistantAvatarDescriptor } from "./assistant-avatar";

export function ThinkingIndicator({ model }: { model: ModelDefinition }) {
const themeMode = usePreferencesStore((s) => s.themeMode);
const sandboxHostStyle = useSandboxHostStyle();
const sandboxHostTheme = useSandboxHostTheme();
const assistantAvatar = getAssistantAvatarDescriptor({
model,
themeMode,
themeMode: sandboxHostTheme ?? themeMode,
sandboxHostStyle,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import type { CspMode } from "@/stores/ui-playground-store";
import type { CspViolation } from "@/stores/widget-debug-store";
import { usePreferencesStore } from "@/stores/preferences/preferences-provider";
import { useSandboxHostTheme } from "@/contexts/sandbox-host-style-context";

interface CspDebugPanelProps {
cspInfo?: {
Expand Down Expand Up @@ -186,6 +188,9 @@ function generateCodeSnippet(
}

export function CspDebugPanel({ cspInfo, protocol }: CspDebugPanelProps) {
const themeMode = usePreferencesStore((s) => s.themeMode);
const sandboxHostTheme = useSandboxHostTheme();
const resolvedThemeMode = sandboxHostTheme ?? themeMode;
const currentMode = cspInfo?.mode ?? "permissive";
const violations = cspInfo?.violations ?? [];
const hasViolations = violations.length > 0;
Expand Down Expand Up @@ -235,7 +240,13 @@ export function CspDebugPanel({ cspInfo, protocol }: CspDebugPanelProps) {
{/* Suggested Fix */}
{hasViolations && suggestedFixes.length > 0 && (
<details className="group">
<summary className="flex items-center gap-1.5 text-amber-600 dark:text-amber-400 cursor-pointer list-none">
<summary
className={`flex items-center gap-1.5 cursor-pointer list-none ${
resolvedThemeMode === "dark"
? "text-amber-400"
: "text-amber-600"
}`}
>
<ChevronRight className="h-3.5 w-3.5 transition-transform group-open:rotate-90" />
<Lightbulb className="h-3.5 w-3.5" />
<span className="font-medium">Suggested fix</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import { PartSwitch } from "./part-switch";
import { ModelDefinition } from "@/shared/types";
import { type DisplayMode } from "@/stores/ui-playground-store";
import { usePreferencesStore } from "@/stores/preferences/preferences-provider";
import { useSandboxHostStyle } from "@/contexts/sandbox-host-style-context";
import {
useSandboxHostStyle,
useSandboxHostTheme,
} from "@/contexts/sandbox-host-style-context";
import { groupAssistantPartsIntoSteps } from "./thread-helpers";
import { ToolServerMap } from "@/lib/apis/mcp-tools-api";
import { UIType } from "@/lib/mcp-ui/mcp-apps-utils";
Expand Down Expand Up @@ -70,9 +73,10 @@ export function MessageView({
}) {
const themeMode = usePreferencesStore((s) => s.themeMode);
const sandboxHostStyle = useSandboxHostStyle();
const sandboxHostTheme = useSandboxHostTheme();
const assistantAvatar = getAssistantAvatarDescriptor({
model,
themeMode,
themeMode: sandboxHostTheme ?? themeMode,
sandboxHostStyle,
});
// Hide widget state messages (these are internal and sent to the model)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { cn } from "@/lib/chat-utils";
import { TextPart } from "./text-part";
import { useClientConfigStore } from "@/stores/client-config-store";
import { extractHostDisplayModes } from "@/lib/client-config";
import { useSandboxHostTheme } from "@/contexts/sandbox-host-style-context";

type ApprovalVisualState = "pending" | "approved" | "denied";
type TraceDisplayMode = "markdown" | "json-markdown";
Expand Down Expand Up @@ -119,8 +120,16 @@ export function ToolPart({
const toolState = getToolStateMeta(state);
const StatusIcon = toolState?.Icon;
const themeMode = usePreferencesStore((s) => s.themeMode);
const sandboxHostTheme = useSandboxHostTheme();
const resolvedThemeMode = sandboxHostTheme ?? themeMode;
const mcpIconClassName =
themeMode === "dark" ? "h-3 w-3 filter invert" : "h-3 w-3";
resolvedThemeMode === "dark" ? "h-3 w-3 filter invert" : "h-3 w-3";
const pendingApprovalClasses =
resolvedThemeMode === "dark"
? "text-[11px] font-medium text-pending"
: "text-[11px] font-medium text-pending-foreground";
const approvedToolClasses =
"flex items-center gap-1 text-[11px] font-medium text-success";
const needsApproval = state === "approval-requested" && !!approvalId;
const [approvalVisualState, setApprovalVisualState] =
useState<ApprovalVisualState>("pending");
Expand Down Expand Up @@ -553,12 +562,12 @@ export function ToolPart({
</span>
</span>
{needsApproval && approvalVisualState === "pending" && (
<span className="text-[11px] font-medium text-pending-foreground dark:text-pending">
<span className={pendingApprovalClasses}>
Approve tool call?
</span>
)}
{needsApproval && approvalVisualState === "approved" && (
<span className="flex items-center gap-1 text-[11px] font-medium text-success dark:text-success">
<span className={approvedToolClasses}>
<ShieldCheck className="h-3.5 w-3.5" />
Approved
</span>
Expand Down
Loading
Loading