import React, { useState, useEffect, useMemo, useCallback } from "react" import { VSCodeButton, VSCodeTextArea, VSCodeDropdown, VSCodeOption, VSCodeTextField, VSCodeCheckbox, } from "@vscode/webview-ui-toolkit/react" import { useExtensionState } from "../../context/ExtensionStateContext" import { Mode, PromptComponent, getRoleDefinition, getAllModes, ModeConfig, enhancePrompt, } from "../../../../src/shared/modes" import { TOOL_GROUPS, GROUP_DISPLAY_NAMES, ToolGroup } from "../../../../src/shared/tool-groups" import { vscode } from "../../utils/vscode" // Get all available groups from GROUP_DISPLAY_NAMES const availableGroups = Object.keys(TOOL_GROUPS) as ToolGroup[] type PromptsViewProps = { onDone: () => void } const PromptsView = ({ onDone }: PromptsViewProps) => { const { customPrompts, listApiConfigMeta, enhancementApiConfigId, setEnhancementApiConfigId, mode, customInstructions, setCustomInstructions, preferredLanguage, setPreferredLanguage, customModes, } = useExtensionState() // Memoize modes to preserve array order const modes = useMemo(() => getAllModes(customModes), [customModes]) const [testPrompt, setTestPrompt] = useState("") const [isEnhancing, setIsEnhancing] = useState(false) const [isDialogOpen, setIsDialogOpen] = useState(false) const [selectedPromptContent, setSelectedPromptContent] = useState("") const [selectedPromptTitle, setSelectedPromptTitle] = useState("") const [isToolsEditMode, setIsToolsEditMode] = useState(false) const [isCreateModeDialogOpen, setIsCreateModeDialogOpen] = useState(false) // Direct update functions const updateAgentPrompt = useCallback( (mode: Mode, promptData: PromptComponent) => { const existingPrompt = customPrompts?.[mode] const updatedPrompt = { ...existingPrompt, ...promptData } // Only include properties that differ from defaults if (updatedPrompt.roleDefinition === getRoleDefinition(mode)) { delete updatedPrompt.roleDefinition } vscode.postMessage({ type: "updatePrompt", promptMode: mode, customPrompt: updatedPrompt, }) }, [customPrompts], ) const updateCustomMode = useCallback((slug: string, modeConfig: ModeConfig) => { vscode.postMessage({ type: "updateCustomMode", slug, modeConfig, }) }, []) // Helper function to find a mode by slug const findModeBySlug = useCallback( (searchSlug: string, modes: readonly ModeConfig[] | undefined): ModeConfig | undefined => { if (!modes) return undefined const isModeWithSlug = (mode: ModeConfig): mode is ModeConfig => mode.slug === searchSlug return modes.find(isModeWithSlug) }, [], ) const switchMode = useCallback((slug: string) => { vscode.postMessage({ type: "mode", text: slug, }) }, []) // Handle mode switching with explicit state initialization const handleModeSwitch = useCallback( (modeConfig: ModeConfig) => { if (modeConfig.slug === mode) return // Prevent unnecessary updates // First switch the mode switchMode(modeConfig.slug) // Exit tools edit mode when switching modes setIsToolsEditMode(false) }, [mode, switchMode, setIsToolsEditMode], ) // Helper function to get current mode's config const getCurrentMode = useCallback((): ModeConfig | undefined => { const findMode = (m: ModeConfig): boolean => m.slug === mode return customModes?.find(findMode) || modes.find(findMode) }, [mode, customModes, modes]) // Helper function to safely access mode properties const getModeProperty = ( mode: ModeConfig | undefined, property: T, ): ModeConfig[T] | undefined => { return mode?.[property] } // State for create mode dialog const [newModeName, setNewModeName] = useState("") const [newModeSlug, setNewModeSlug] = useState("") const [newModeRoleDefinition, setNewModeRoleDefinition] = useState("") const [newModeCustomInstructions, setNewModeCustomInstructions] = useState("") const [newModeGroups, setNewModeGroups] = useState(availableGroups) // Reset form fields when dialog opens useEffect(() => { if (isCreateModeDialogOpen) { setNewModeGroups(availableGroups) setNewModeRoleDefinition("") setNewModeCustomInstructions("") } }, [isCreateModeDialogOpen]) // Helper function to generate a unique slug from a name const generateSlug = useCallback((name: string, attempt = 0): string => { const baseSlug = name .toLowerCase() .replace(/[^a-z0-9-]+/g, "-") .replace(/^-+|-+$/g, "") return attempt === 0 ? baseSlug : `${baseSlug}-${attempt}` }, []) // Handler for name changes const handleNameChange = useCallback( (name: string) => { setNewModeName(name) setNewModeSlug(generateSlug(name)) }, [generateSlug], ) const handleCreateMode = useCallback(() => { if (!newModeName.trim() || !newModeSlug.trim()) return const newMode: ModeConfig = { slug: newModeSlug, name: newModeName, roleDefinition: newModeRoleDefinition.trim() || "", customInstructions: newModeCustomInstructions.trim() || undefined, groups: newModeGroups, } updateCustomMode(newModeSlug, newMode) switchMode(newModeSlug) setIsCreateModeDialogOpen(false) setNewModeName("") setNewModeSlug("") setNewModeRoleDefinition("") setNewModeCustomInstructions("") setNewModeGroups(availableGroups) }, [ newModeName, newModeSlug, newModeRoleDefinition, newModeCustomInstructions, newModeGroups, updateCustomMode, switchMode, ]) const isNameOrSlugTaken = useCallback( (name: string, slug: string) => { return modes.some((m) => m.slug === slug || m.name === name) }, [modes], ) const openCreateModeDialog = useCallback(() => { const baseNamePrefix = "New Custom Mode" // Find unique name and slug let attempt = 0 let name = baseNamePrefix let slug = generateSlug(name) while (isNameOrSlugTaken(name, slug)) { attempt++ name = `${baseNamePrefix} ${attempt + 1}` slug = generateSlug(name) } setNewModeName(name) setNewModeSlug(slug) setIsCreateModeDialogOpen(true) }, [generateSlug, isNameOrSlugTaken]) // Handler for group checkbox changes const handleGroupChange = useCallback( (group: ToolGroup, isCustomMode: boolean, customMode: ModeConfig | undefined) => (e: Event | React.FormEvent) => { if (!isCustomMode) return // Prevent changes to built-in modes const target = (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement) const checked = target.checked const oldGroups = customMode?.groups || [] let newGroups: readonly ToolGroup[] if (checked) { newGroups = [...oldGroups, group] } else { newGroups = oldGroups.filter((g) => g !== group) } if (customMode) { updateCustomMode(customMode.slug, { ...customMode, groups: newGroups, }) } }, [updateCustomMode], ) useEffect(() => { const handler = (event: MessageEvent) => { const message = event.data if (message.type === "enhancedPrompt") { if (message.text) { setTestPrompt(message.text) } setIsEnhancing(false) } else if (message.type === "systemPrompt") { if (message.text) { setSelectedPromptContent(message.text) setSelectedPromptTitle(`System Prompt (${message.mode} mode)`) setIsDialogOpen(true) } } } window.addEventListener("message", handler) return () => window.removeEventListener("message", handler) }, []) const updateEnhancePrompt = (value: string | undefined) => { vscode.postMessage({ type: "updateEnhancedPrompt", text: value, }) } const handleEnhancePromptChange = (e: Event | React.FormEvent): void => { const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value const trimmedValue = value.trim() if (trimmedValue !== enhancePrompt.default) { updateEnhancePrompt(trimmedValue || enhancePrompt.default) } } const handleAgentReset = (modeSlug: string) => { // Only reset role definition for built-in modes const existingPrompt = customPrompts?.[modeSlug] updateAgentPrompt(modeSlug, { ...existingPrompt, roleDefinition: undefined, }) } const handleEnhanceReset = () => { updateEnhancePrompt(undefined) } const getEnhancePromptValue = (): string => { return enhancePrompt.get(customPrompts) } const handleTestEnhancement = () => { if (!testPrompt.trim()) return setIsEnhancing(true) vscode.postMessage({ type: "enhancePrompt", text: testPrompt, }) } return (

Prompts

Done
Preferred Language

Select the language that Cline should use for communication.

Custom Instructions for All Modes
These instructions apply to all modes. They provide a base set of behaviors that can be enhanced by mode-specific instructions below.
{ const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value setCustomInstructions(value || undefined) vscode.postMessage({ type: "customInstructions", text: value.trim() || undefined, }) }} rows={4} resize="vertical" style={{ width: "100%" }} data-testid="global-custom-instructions-textarea" />
Instructions can also be loaded from{" "} vscode.postMessage({ type: "openFile", text: "./.clinerules", values: { create: true, content: "", }, }) }> .clinerules {" "} in your workspace.

Mode-Specific Prompts

{ vscode.postMessage({ type: "openFile", text: "settings/cline_custom_modes.json", }) }}>
Hit the + to create a new custom mode, or just ask Roo in chat to create one for you!
{modes.map((modeConfig) => { const isActive = mode === modeConfig.slug return ( ) })}
{/* Only show name and delete for custom modes */} {mode && findModeBySlug(mode, customModes) && (
Name
) => { const target = (e as CustomEvent)?.detail?.target || ((e as any).target as HTMLInputElement) const customMode = findModeBySlug(mode, customModes) if (customMode) { updateCustomMode(mode, { ...customMode, name: target.value, }) } }} style={{ width: "100%" }} /> { vscode.postMessage({ type: "deleteCustomMode", slug: mode, }) }}>
)}
Role Definition
{!findModeBySlug(mode, customModes) && ( { const currentMode = getCurrentMode() if (currentMode?.slug) { handleAgentReset(currentMode.slug) } }} title="Reset to default" data-testid="role-definition-reset"> )}
Define Roo's expertise and personality for this mode. This description shapes how Roo presents itself and approaches tasks.
{ const customMode = findModeBySlug(mode, customModes) const prompt = customPrompts?.[mode] return customMode?.roleDefinition ?? prompt?.roleDefinition ?? getRoleDefinition(mode) })()} onChange={(e) => { const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value const customMode = findModeBySlug(mode, customModes) if (customMode) { // For custom modes, update the JSON file updateCustomMode(mode, { ...customMode, roleDefinition: value.trim() || "", }) } else { // For built-in modes, update the prompts updateAgentPrompt(mode, { roleDefinition: value.trim() || undefined, }) } }} rows={4} resize="vertical" style={{ width: "100%" }} data-testid={`${getCurrentMode()?.slug || "code"}-prompt-textarea`} />
{/* Mode settings */} <> {/* Show tools for all modes */}
Available Tools
{findModeBySlug(mode, customModes) && ( setIsToolsEditMode(!isToolsEditMode)} title={isToolsEditMode ? "Done editing" : "Edit tools"}> )}
{!findModeBySlug(mode, customModes) && (
Tools for built-in modes cannot be modified
)} {isToolsEditMode && findModeBySlug(mode, customModes) ? (
{availableGroups.map((group) => { const currentMode = getCurrentMode() const isCustomMode = findModeBySlug(mode, customModes) const customMode = isCustomMode const isGroupEnabled = isCustomMode ? customMode?.groups?.includes(group) : currentMode?.groups?.includes(group) return ( {GROUP_DISPLAY_NAMES[group]} ) })}
) : (
{(() => { const currentMode = getCurrentMode() const enabledGroups = currentMode?.groups || [] return enabledGroups.map((group) => GROUP_DISPLAY_NAMES[group]).join(", ") })()}
)}
{/* Role definition for both built-in and custom modes */}
Mode-specific Custom Instructions
Add behavioral guidelines specific to {getCurrentMode()?.name || "Code"} mode.
{ const customMode = findModeBySlug(mode, customModes) const prompt = customPrompts?.[mode] return customMode?.customInstructions ?? prompt?.customInstructions ?? "" })()} onChange={(e) => { const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value const customMode = findModeBySlug(mode, customModes) if (customMode) { // For custom modes, update the JSON file updateCustomMode(mode, { ...customMode, customInstructions: value.trim() || undefined, }) } else { // For built-in modes, update the prompts const existingPrompt = customPrompts?.[mode] updateAgentPrompt(mode, { ...existingPrompt, customInstructions: value.trim() || undefined, }) } }} rows={4} resize="vertical" style={{ width: "100%" }} data-testid={`${getCurrentMode()?.slug || "code"}-custom-instructions-textarea`} />
Custom instructions specific to {getCurrentMode()?.name || "Code"} mode can also be loaded from{" "} { const currentMode = getCurrentMode() if (!currentMode) return // Open or create an empty file vscode.postMessage({ type: "openFile", text: `./.clinerules-${currentMode.slug}`, values: { create: true, content: "", }, }) }}> .clinerules-{getCurrentMode()?.slug || "code"} {" "} in your workspace.
{ const currentMode = getCurrentMode() if (currentMode) { vscode.postMessage({ type: "getSystemPrompt", mode: currentMode.slug, }) } }} data-testid="preview-prompt-button"> Preview System Prompt

Prompt Enhancement

Use prompt enhancement to get tailored suggestions or improvements for your inputs. This ensures Roo understands your intent and provides the best possible responses.
API Configuration
You can select an API configuration to always use for enhancing prompts, or just use whatever is currently selected
{ const value = e.detail?.target?.value || e.target?.value setEnhancementApiConfigId(value) vscode.postMessage({ type: "enhancementApiConfigId", text: value, }) }} style={{ width: "300px" }}> Use currently selected API configuration {(listApiConfigMeta || []).map((config) => ( {config.name} ))}
Enhancement Prompt
This prompt will be used to refine your input when you hit the sparkle icon in chat.
setTestPrompt((e.target as HTMLTextAreaElement).value)} placeholder="Enter a prompt to test the enhancement" rows={3} resize="vertical" style={{ width: "100%" }} data-testid="test-prompt-textarea" />
Preview Prompt Enhancement
{/* Bottom padding */}
{isCreateModeDialogOpen && (
setIsCreateModeDialogOpen(false)} style={{ position: "absolute", top: "20px", right: "20px", }}>

Create New Mode

Name
) => { const target = (e as CustomEvent)?.detail?.target || ((e as any).target as HTMLInputElement) handleNameChange(target.value) }} style={{ width: "100%" }} />
Slug
) => { const target = (e as CustomEvent)?.detail?.target || ((e as any).target as HTMLInputElement) setNewModeSlug(target.value) }} style={{ width: "100%" }} />
The slug is used in URLs and file names. It should be lowercase and contain only letters, numbers, and hyphens.
Role Definition
Define Roo's expertise and personality for this mode.
{ const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value setNewModeRoleDefinition(value) }} rows={4} resize="vertical" style={{ width: "100%" }} />
Available Tools
Select which tools this mode can use.
{availableGroups.map((group) => ( ) => { const target = (e as CustomEvent)?.detail?.target || (e.target as HTMLInputElement) const checked = target.checked if (checked) { setNewModeGroups([...newModeGroups, group]) } else { setNewModeGroups(newModeGroups.filter((g) => g !== group)) } }}> {GROUP_DISPLAY_NAMES[group]} ))}
Custom Instructions
Add behavioral guidelines specific to this mode.
{ const value = (e as CustomEvent)?.detail?.target?.value || ((e as any).target as HTMLTextAreaElement).value setNewModeCustomInstructions(value) }} rows={4} resize="vertical" style={{ width: "100%" }} />
setIsCreateModeDialogOpen(false)}>Cancel Create Mode
)} {isDialogOpen && (
setIsDialogOpen(false)} style={{ position: "absolute", top: "20px", right: "20px", }}>

{selectedPromptTitle}

								{selectedPromptContent}
							
setIsDialogOpen(false)}>Close
)}
) } export default PromptsView