diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index e713cafa4c7..044aeab34dc 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -105,6 +105,7 @@ export const globalSettingsSchema = z.object({ historyPreviewCollapsed: z.boolean().optional(), profileThresholds: z.record(z.string(), z.number()).optional(), hasOpenedModeSelector: z.boolean().optional(), + systemPromptWarningDismissed: z.boolean().optional(), }) export type GlobalSettings = z.infer diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 73ebf59d4c8..2211735ef85 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -217,6 +217,7 @@ export type ExtensionState = Pick< | "codebaseIndexConfig" | "codebaseIndexModels" | "profileThresholds" + | "systemPromptWarningDismissed" > & { version: string clineMessages: ClineMessage[] diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index a4f18c870c1..affc5073908 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -95,6 +95,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction - {hasSystemPromptOverride && ( + {hasSystemPromptOverride && !systemPromptWarningDismissed && (
diff --git a/webview-ui/src/components/chat/SystemPromptWarning.tsx b/webview-ui/src/components/chat/SystemPromptWarning.tsx index 0ed7a727337..7d7398737a4 100644 --- a/webview-ui/src/components/chat/SystemPromptWarning.tsx +++ b/webview-ui/src/components/chat/SystemPromptWarning.tsx @@ -1,15 +1,27 @@ import React from "react" import { useAppTranslation } from "@/i18n/TranslationContext" +import { useExtensionState } from "@/context/ExtensionStateContext" export const SystemPromptWarning: React.FC = () => { const { t } = useAppTranslation() + const { setSystemPromptWarningDismissed } = useExtensionState() + + const handleDismiss = () => { + setSystemPromptWarningDismissed(true) + } return (
- {t("chat:systemPromptWarning")} + {t("chat:systemPromptWarning")} +
) } diff --git a/webview-ui/src/components/chat/__tests__/SystemPromptWarning.spec.tsx b/webview-ui/src/components/chat/__tests__/SystemPromptWarning.spec.tsx new file mode 100644 index 00000000000..624412ee9b1 --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/SystemPromptWarning.spec.tsx @@ -0,0 +1,83 @@ +// npx vitest run src/components/chat/__tests__/SystemPromptWarning.spec.tsx + +import { describe, it, expect, vi } from "vitest" +import { render, screen, fireEvent } from "@testing-library/react" +import { SystemPromptWarning } from "../SystemPromptWarning" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { useAppTranslation } from "@/i18n/TranslationContext" + +// Mock the hooks +vi.mock("@/context/ExtensionStateContext") +vi.mock("@/i18n/TranslationContext") + +const mockSetSystemPromptWarningDismissed = vi.fn() +const mockT = vi.fn((key: string) => { + if (key === "chat:systemPromptWarning") { + return "WARNING: Custom system prompt override active. This can severely break functionality and cause unpredictable behavior." + } + return key +}) + +describe("SystemPromptWarning", () => { + beforeEach(() => { + vi.clearAllMocks() + ;(useExtensionState as any).mockReturnValue({ + setSystemPromptWarningDismissed: mockSetSystemPromptWarningDismissed, + }) + ;(useAppTranslation as any).mockReturnValue({ + t: mockT, + }) + }) + + it("renders the warning message", () => { + render() + + expect(screen.getByText(/WARNING: Custom system prompt override active/)).toBeInTheDocument() + }) + + it("renders the warning icon", () => { + render() + + const warningIcon = document.querySelector(".codicon-warning") + expect(warningIcon).toBeInTheDocument() + }) + + it("renders the dismiss button", () => { + render() + + const dismissButton = screen.getByRole("button") + expect(dismissButton).toBeInTheDocument() + expect(dismissButton).toHaveAttribute("title", "Dismiss warning") + }) + + it("renders the close icon in dismiss button", () => { + render() + + const closeIcon = document.querySelector(".codicon-close") + expect(closeIcon).toBeInTheDocument() + }) + + it("calls setSystemPromptWarningDismissed when dismiss button is clicked", () => { + render() + + const dismissButton = screen.getByRole("button") + fireEvent.click(dismissButton) + + expect(mockSetSystemPromptWarningDismissed).toHaveBeenCalledWith(true) + expect(mockSetSystemPromptWarningDismissed).toHaveBeenCalledTimes(1) + }) + + it("has correct CSS classes for styling", () => { + render() + + const container = document.querySelector(".flex.items-center.px-4.py-2.mb-2") + expect(container).toBeInTheDocument() + expect(container).toHaveClass("bg-vscode-editorWarning-foreground", "text-vscode-editor-background") + }) + + it("uses translation for the warning text", () => { + render() + + expect(mockT).toHaveBeenCalledWith("chat:systemPromptWarning") + }) +}) diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index c87ccdb6e92..cb501f8ff29 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -40,6 +40,8 @@ export interface ExtensionStateContextType extends ExtensionState { mdmCompliant?: boolean hasOpenedModeSelector: boolean // New property to track if user has opened mode selector setHasOpenedModeSelector: (value: boolean) => void // Setter for the new property + systemPromptWarningDismissed?: boolean // New property to track if system prompt warning is dismissed + setSystemPromptWarningDismissed: (value: boolean) => void // Setter for the new property condensingApiConfigId?: string setCondensingApiConfigId: (value: string) => void customCondensingPrompt?: string @@ -183,6 +185,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode condensingApiConfigId: "", // Default empty string for condensing API config ID customCondensingPrompt: "", // Default empty string for custom condensing prompt hasOpenedModeSelector: false, // Default to false (not opened yet) + systemPromptWarningDismissed: false, // Default to false (not dismissed yet) autoApprovalEnabled: false, customModes: [], maxOpenTabsContext: 20, @@ -431,6 +434,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setHistoryPreviewCollapsed: (value) => setState((prevState) => ({ ...prevState, historyPreviewCollapsed: value })), setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })), + setSystemPromptWarningDismissed: (value) => + setState((prevState) => ({ ...prevState, systemPromptWarningDismissed: value })), setAutoCondenseContext: (value) => setState((prevState) => ({ ...prevState, autoCondenseContext: value })), setAutoCondenseContextPercent: (value) => setState((prevState) => ({ ...prevState, autoCondenseContextPercent: value })),