diff --git a/webview-ui/src/components/prompts/PromptsView.tsx b/webview-ui/src/components/prompts/PromptsView.tsx index 4c84416a2c..c494387042 100644 --- a/webview-ui/src/components/prompts/PromptsView.tsx +++ b/webview-ui/src/components/prompts/PromptsView.tsx @@ -1,14 +1,6 @@ -import React, { useState, useEffect, useMemo, useCallback } from "react" +import React, { useState, useEffect, useMemo, useCallback, useRef } from "react" import { Button } from "@/components/ui/button" -import { - VSCodeTextArea, - VSCodeDropdown, - VSCodeOption, - VSCodeTextField, - VSCodeCheckbox, - VSCodeRadioGroup, - VSCodeRadio, -} from "@vscode/webview-ui-toolkit/react" +import { VSCodeCheckbox, VSCodeRadioGroup, VSCodeRadio } from "@vscode/webview-ui-toolkit/react" import { useExtensionState } from "@src/context/ExtensionStateContext" import { @@ -29,6 +21,25 @@ import { Tab, TabContent, TabHeader } from "../common/Tab" import i18next from "i18next" import { useAppTranslation } from "@src/i18n/TranslationContext" import { Trans } from "react-i18next" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Textarea, + Popover, + PopoverContent, + PopoverTrigger, + Command, + CommandInput, + CommandList, + CommandEmpty, + CommandItem, + CommandGroup, + Input, +} from "../ui" +import { ChevronsUpDown, X } from "lucide-react" // Get all available groups that should show in prompts view const availableGroups = (Object.keys(TOOL_GROUPS) as ToolGroup[]).filter((group) => !TOOL_GROUPS[group].alwaysAvailable) @@ -78,9 +89,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { const [isToolsEditMode, setIsToolsEditMode] = useState(false) const [showConfigMenu, setShowConfigMenu] = useState(false) const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false) - const [activeSupportTab, setActiveSupportTab] = useState("ENHANCE") + const [activeSupportOption, setActiveSupportOption] = useState("ENHANCE") const [isSystemPromptDisclosureOpen, setIsSystemPromptDisclosureOpen] = useState(false) + // State for mode selection popover and search + const [open, setOpen] = useState(false) + const [searchValue, setSearchValue] = useState("") + const searchInputRef = useRef(null) + // Direct update functions const updateAgentPrompt = useCallback( (mode: Mode, promptData: PromptComponent) => { @@ -144,9 +160,24 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { // Exit tools edit mode when switching modes setIsToolsEditMode(false) }, - [visualMode, switchMode, setIsToolsEditMode], + [visualMode, switchMode], ) + // Handler for popover open state change + const onOpenChange = useCallback((open: boolean) => { + setOpen(open) + // Reset search when closing the popover + if (!open) { + setTimeout(() => setSearchValue(""), 100) + } + }, []) + + // Handler for clearing search input + const onClearSearch = useCallback(() => { + setSearchValue("") + searchInputRef.current?.focus() + }, []) + // Helper function to get current mode's config const getCurrentMode = useCallback((): ModeConfig | undefined => { const findMode = (m: ModeConfig): boolean => m.slug === visualMode @@ -480,45 +511,95 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { {t("prompts:modes.createModeHelpText")} -
- {modes.map((modeConfig) => { - const isActive = visualMode === modeConfig.slug - return ( - - ) - })} +
+ + + + + + +
+ + {searchValue.length > 0 && ( +
+ +
+ )} +
+ + + {searchValue && ( +
+ {t("prompts:modes.noMatchFound")} +
+ )} +
+ + {modes + .filter((modeConfig) => + searchValue + ? modeConfig.name + .toLowerCase() + .includes(searchValue.toLowerCase()) + : true, + ) + .map((modeConfig) => ( + { + handleModeSwitch(modeConfig) + setOpen(false) + }} + data-testid={`mode-option-${modeConfig.slug}`}> +
+ {modeConfig.name} + {modeConfig.slug} +
+
+ ))} +
+
+
+
+
-
+
{/* Only show name and delete for custom modes */} {visualMode && findModeBySlug(visualMode, customModes) && (
{t("prompts:createModeDialog.name.label")}
- ) => { - const target = - (e as CustomEvent)?.detail?.target || - ((e as any).target as HTMLInputElement) + onChange={(e) => { const customMode = findModeBySlug(visualMode, customModes) if (customMode) { updateCustomMode(visualMode, { ...customMode, - name: target.value, + name: e.target.value, source: customMode.source || "global", }) } @@ -541,7 +622,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
)} -
+
{t("prompts:roleDefinition.title")}
{!findModeBySlug(visualMode, customModes) && ( @@ -563,7 +644,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
{t("prompts:roleDefinition.description")}
- { const customMode = findModeBySlug(visualMode, customModes) const prompt = customModePrompts?.[visualMode] as PromptComponent @@ -575,7 +656,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { })()} onChange={(e) => { const value = - (e as CustomEvent)?.detail?.target?.value || + (e as unknown as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value const customMode = findModeBySlug(visualMode, customModes) if (customMode) { @@ -592,35 +673,35 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { }) } }} + className="resize-y w-full" rows={4} - resize="vertical" - style={{ width: "100%" }} data-testid={`${getCurrentMode()?.slug || "code"}-prompt-textarea`} />
{/* Mode settings */} <> -
-
- {t("prompts:apiConfiguration.title")} -
-
- { - const value = e.detail?.target?.value || e.target?.value +
+
{t("prompts:apiConfiguration.title")}
+
+
{t("prompts:apiConfiguration.select")}
@@ -721,15 +802,9 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { {/* Role definition for both built-in and custom modes */} -
-
-
{t("prompts:customInstructions.title")}
+
+
+
{t("prompts:customInstructions.title")}
{!findModeBySlug(visualMode, customModes) && ( )}
-
+
{t("prompts:customInstructions.description", { modeName: getCurrentMode()?.name || "Code", })}
- { const customMode = findModeBySlug(visualMode, customModes) const prompt = customModePrompts?.[visualMode] as PromptComponent @@ -768,7 +838,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { })()} onChange={(e) => { const value = - (e as CustomEvent)?.detail?.target?.value || + (e as unknown as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value const customMode = findModeBySlug(visualMode, customModes) if (customMode) { @@ -788,16 +858,10 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { } }} rows={4} - resize="vertical" - style={{ width: "100%" }} + className="w-full resize-y" data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`} /> -
+
{ components={{ span: ( { const currentMode = getCurrentMode() if (!currentMode) return @@ -834,13 +894,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
-
-
+
+
{/* Custom System Prompt Disclosure */} -
+
-

- {t("prompts:globalCustomInstructions.title")} -

+

{t("prompts:globalCustomInstructions.title")}

- {t("prompts:globalCustomInstructions.description", { language: i18next.language })} + {t("prompts:globalCustomInstructions.description", { + language: i18next.language, + })}
- { const value = - (e as CustomEvent)?.detail?.target?.value || + (e as unknown as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value setCustomInstructions(value || undefined) vscode.postMessage({ @@ -938,21 +993,16 @@ const PromptsView = ({ onDone }: PromptsViewProps) => { }) }} rows={4} - resize="vertical" - className="w-full" + className="w-full resize-y" data-testid="global-custom-instructions-textarea" /> -
+
vscode.postMessage({ type: "openFile", @@ -970,156 +1020,113 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
-
-

- {t("prompts:supportPrompts.title")} -

-
- {Object.keys(supportPrompt.default).map((type) => ( - - ))} +
+

{t("prompts:supportPrompts.title")}

+
+
{/* Support prompt description */} -
- {t(`prompts:supportPrompts.types.${activeSupportTab}.description`)} +
+ {t(`prompts:supportPrompts.types.${activeSupportOption}.description`)}
- {/* Show active tab content */} -
-
-
{t("prompts:supportPrompts.prompt")}
+
+
+
{t("prompts:supportPrompts.prompt")}
- { const value = - (e as CustomEvent)?.detail?.target?.value || + (e as unknown as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value const trimmedValue = value.trim() - updateSupportPrompt(activeSupportTab, trimmedValue || undefined) + updateSupportPrompt(activeSupportOption, trimmedValue || undefined) }} rows={6} - resize="vertical" - style={{ width: "100%" }} + className="resize-y w-full" /> - {activeSupportTab === "ENHANCE" && ( + {activeSupportOption === "ENHANCE" && ( <>
-
-
-
-
+
+
+
+
{t("prompts:supportPrompts.enhance.apiConfiguration")}
-
+
{t("prompts:supportPrompts.enhance.apiConfigDescription")}
- { - const value = e.detail?.target?.value || e.target?.value - setEnhancementApiConfigId(value) +
-
- +