mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
refactor: general UI improvements (#2987)
This commit is contained in:
parent
2e2c0a8f02
commit
883cb927a3
24 changed files with 403 additions and 490 deletions
|
|
@ -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<SupportPromptType>("ENHANCE")
|
||||
const [activeSupportOption, setActiveSupportOption] = useState<SupportPromptType>("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<HTMLInputElement>(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")}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 items-center mb-3 flex-wrap py-1">
|
||||
{modes.map((modeConfig) => {
|
||||
const isActive = visualMode === modeConfig.slug
|
||||
return (
|
||||
<button
|
||||
key={modeConfig.slug}
|
||||
data-testid={`${modeConfig.slug}-tab`}
|
||||
data-active={isActive ? "true" : "false"}
|
||||
onClick={() => handleModeSwitch(modeConfig)}
|
||||
className={`px-2 py-1 border-none rounded cursor-pointer font-bold ${
|
||||
isActive
|
||||
? "bg-vscode-button-background text-vscode-button-foreground opacity-100"
|
||||
: "bg-transparent text-vscode-foreground opacity-80"
|
||||
}`}>
|
||||
{modeConfig.name}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
<div className="flex items-center gap-1 mb-3">
|
||||
<Popover open={open} onOpenChange={onOpenChange}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="combobox"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="grow justify-between"
|
||||
data-testid="mode-select-trigger">
|
||||
<div>{getCurrentMode()?.name || t("prompts:modes.selectMode")}</div>
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0 w-[var(--radix-popover-trigger-width)]">
|
||||
<Command>
|
||||
<div className="relative">
|
||||
<CommandInput
|
||||
ref={searchInputRef}
|
||||
value={searchValue}
|
||||
onValueChange={setSearchValue}
|
||||
placeholder={t("prompts:modes.selectMode")}
|
||||
className="h-9 mr-4"
|
||||
data-testid="mode-search-input"
|
||||
/>
|
||||
{searchValue.length > 0 && (
|
||||
<div className="absolute right-2 top-0 bottom-0 flex items-center justify-center">
|
||||
<X
|
||||
className="text-vscode-input-foreground opacity-50 hover:opacity-100 size-4 p-0.5 cursor-pointer"
|
||||
onClick={onClearSearch}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
{searchValue && (
|
||||
<div className="py-2 px-1 text-sm">
|
||||
{t("prompts:modes.noMatchFound")}
|
||||
</div>
|
||||
)}
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{modes
|
||||
.filter((modeConfig) =>
|
||||
searchValue
|
||||
? modeConfig.name
|
||||
.toLowerCase()
|
||||
.includes(searchValue.toLowerCase())
|
||||
: true,
|
||||
)
|
||||
.map((modeConfig) => (
|
||||
<CommandItem
|
||||
key={modeConfig.slug}
|
||||
value={modeConfig.slug}
|
||||
onSelect={() => {
|
||||
handleModeSwitch(modeConfig)
|
||||
setOpen(false)
|
||||
}}
|
||||
data-testid={`mode-option-${modeConfig.slug}`}>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span>{modeConfig.name}</span>
|
||||
<span className="text-foreground">{modeConfig.slug}</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "20px" }}>
|
||||
<div className="mb-5">
|
||||
{/* Only show name and delete for custom modes */}
|
||||
{visualMode && findModeBySlug(visualMode, customModes) && (
|
||||
<div className="flex gap-3 mb-4">
|
||||
<div className="flex-1">
|
||||
<div className="font-bold mb-1">{t("prompts:createModeDialog.name.label")}</div>
|
||||
<div className="flex gap-2">
|
||||
<VSCodeTextField
|
||||
<Input
|
||||
type="text"
|
||||
value={getModeProperty(findModeBySlug(visualMode, customModes), "name") ?? ""}
|
||||
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
|
||||
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) => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div className="mb-4">
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<div className="font-bold">{t("prompts:roleDefinition.title")}</div>
|
||||
{!findModeBySlug(visualMode, customModes) && (
|
||||
|
|
@ -563,7 +644,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
<div className="text-sm text-vscode-descriptionForeground mb-2">
|
||||
{t("prompts:roleDefinition.description")}
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
<Textarea
|
||||
value={(() => {
|
||||
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`}
|
||||
/>
|
||||
</div>
|
||||
{/* Mode settings */}
|
||||
<>
|
||||
<div style={{ marginBottom: "12px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
|
||||
{t("prompts:apiConfiguration.title")}
|
||||
</div>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<VSCodeDropdown
|
||||
value={currentApiConfigName || ""}
|
||||
onChange={(e: any) => {
|
||||
const value = e.detail?.target?.value || e.target?.value
|
||||
<div className="mb-3">
|
||||
<div className="font-bold mb-1">{t("prompts:apiConfiguration.title")}</div>
|
||||
<div className="mb-2">
|
||||
<Select
|
||||
value={currentApiConfigName}
|
||||
onValueChange={(value) => {
|
||||
vscode.postMessage({
|
||||
type: "loadApiConfiguration",
|
||||
text: value,
|
||||
})
|
||||
}}
|
||||
className="w-full">
|
||||
{(listApiConfigMeta || []).map((config) => (
|
||||
<VSCodeOption key={config.id} value={config.name}>
|
||||
{config.name}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
}}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(listApiConfigMeta || []).map((config) => (
|
||||
<SelectItem key={config.id} value={config.name}>
|
||||
{config.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="text-xs mt-1.5 text-vscode-descriptionForeground">
|
||||
{t("prompts:apiConfiguration.select")}
|
||||
</div>
|
||||
|
|
@ -721,15 +802,9 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</>
|
||||
|
||||
{/* Role definition for both built-in and custom modes */}
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "4px",
|
||||
}}>
|
||||
<div style={{ fontWeight: "bold" }}>{t("prompts:customInstructions.title")}</div>
|
||||
<div className="mb-2">
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<div className="font-bold">{t("prompts:customInstructions.title")}</div>
|
||||
{!findModeBySlug(visualMode, customModes) && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
|
@ -746,17 +821,12 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
<div className="text-[13px] text-vscode-descriptionForeground mb-2">
|
||||
{t("prompts:customInstructions.description", {
|
||||
modeName: getCurrentMode()?.name || "Code",
|
||||
})}
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
<Textarea
|
||||
value={(() => {
|
||||
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`}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginTop: "5px",
|
||||
}}>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1.5">
|
||||
<Trans
|
||||
i18nKey="prompts:customInstructions.loadFromFile"
|
||||
values={{
|
||||
|
|
@ -807,11 +871,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
components={{
|
||||
span: (
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-textLink-foreground)",
|
||||
cursor: "pointer",
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
className="text-vscode-textLink-foreground cursor-pointer underline"
|
||||
onClick={() => {
|
||||
const currentMode = getCurrentMode()
|
||||
if (!currentMode) return
|
||||
|
|
@ -834,13 +894,8 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: "40px",
|
||||
marginBottom: "20px",
|
||||
borderBottom: "1px solid var(--vscode-input-border)",
|
||||
}}>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<div className="pb-4 border-b border-vscode-input-border">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={() => {
|
||||
|
|
@ -874,7 +929,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
|
||||
{/* Custom System Prompt Disclosure */}
|
||||
<div className="mt-12">
|
||||
<div className="mt-4">
|
||||
<button
|
||||
onClick={() => setIsSystemPromptDisclosureOpen(!isSystemPromptDisclosureOpen)}
|
||||
className="flex items-center text-xs text-vscode-foreground hover:text-vscode-textLink-foreground focus:outline-none"
|
||||
|
|
@ -918,18 +973,18 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
|
||||
<div className="pb-5 border-b border-vscode-input-border">
|
||||
<h3 style={{ color: "var(--vscode-foreground)", marginBottom: "12px" }}>
|
||||
{t("prompts:globalCustomInstructions.title")}
|
||||
</h3>
|
||||
<h3 className="text-vscode-foreground mb-3">{t("prompts:globalCustomInstructions.title")}</h3>
|
||||
|
||||
<div className="text-sm text-vscode-descriptionForeground mb-2">
|
||||
{t("prompts:globalCustomInstructions.description", { language: i18next.language })}
|
||||
{t("prompts:globalCustomInstructions.description", {
|
||||
language: i18next.language,
|
||||
})}
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
value={customInstructions ?? ""}
|
||||
<Textarea
|
||||
value={customInstructions}
|
||||
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
|
||||
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"
|
||||
/>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1.5 mb-10">
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1.5">
|
||||
<Trans
|
||||
i18nKey="prompts:globalCustomInstructions.loadFromFile"
|
||||
components={{
|
||||
span: (
|
||||
<span
|
||||
style={{
|
||||
color: "var(--vscode-textLink-foreground)",
|
||||
cursor: "pointer",
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
className="text-vscode-textLink-foreground cursor-pointer underline"
|
||||
onClick={() =>
|
||||
vscode.postMessage({
|
||||
type: "openFile",
|
||||
|
|
@ -970,156 +1020,113 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: "20px",
|
||||
paddingBottom: "60px",
|
||||
borderBottom: "1px solid var(--vscode-input-border)",
|
||||
}}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", marginBottom: "12px" }}>
|
||||
{t("prompts:supportPrompts.title")}
|
||||
</h3>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
alignItems: "center",
|
||||
marginBottom: "12px",
|
||||
flexWrap: "wrap",
|
||||
padding: "4px 0",
|
||||
}}>
|
||||
{Object.keys(supportPrompt.default).map((type) => (
|
||||
<button
|
||||
key={type}
|
||||
data-testid={`${type}-tab`}
|
||||
data-active={activeSupportTab === type ? "true" : "false"}
|
||||
onClick={() => setActiveSupportTab(type as SupportPromptType)}
|
||||
style={{
|
||||
padding: "4px 8px",
|
||||
border: "none",
|
||||
background: activeSupportTab === type ? "var(--vscode-button-background)" : "none",
|
||||
color:
|
||||
activeSupportTab === type
|
||||
? "var(--vscode-button-foreground)"
|
||||
: "var(--vscode-foreground)",
|
||||
cursor: "pointer",
|
||||
opacity: activeSupportTab === type ? 1 : 0.8,
|
||||
borderRadius: "3px",
|
||||
fontWeight: "bold",
|
||||
}}>
|
||||
{t(`prompts:supportPrompts.types.${type}.label`)}
|
||||
</button>
|
||||
))}
|
||||
<div className="mt-5 pb-15 border-b border-vscode-input-border">
|
||||
<h3 className="text-vscode-foreground mb-3">{t("prompts:supportPrompts.title")}</h3>
|
||||
<div className="flex gap-4 items-center flex-wrap py-1">
|
||||
<Select
|
||||
value={activeSupportOption}
|
||||
onValueChange={(type) => setActiveSupportOption(type as SupportPromptType)}>
|
||||
<SelectTrigger className="w-full" data-testid="support-prompt-select-trigger">
|
||||
<SelectValue placeholder={t("settings:common.select")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.keys(supportPrompt.default).map((type) => (
|
||||
<SelectItem key={type} value={type} data-testid={`${type}-option`}>
|
||||
{t(`prompts:supportPrompts.types.${type}.label`)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Support prompt description */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
margin: "8px 0 16px",
|
||||
}}>
|
||||
{t(`prompts:supportPrompts.types.${activeSupportTab}.description`)}
|
||||
<div className="text-[13px] text-vscode-descriptionForeground my-2 mb-4">
|
||||
{t(`prompts:supportPrompts.types.${activeSupportOption}.description`)}
|
||||
</div>
|
||||
|
||||
{/* Show active tab content */}
|
||||
<div key={activeSupportTab}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "4px",
|
||||
}}>
|
||||
<div style={{ fontWeight: "bold" }}>{t("prompts:supportPrompts.prompt")}</div>
|
||||
<div key={activeSupportOption}>
|
||||
<div className="flex justify-between items-center mb-1">
|
||||
<div className="font-bold">{t("prompts:supportPrompts.prompt")}</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleSupportReset(activeSupportTab)}
|
||||
title={t("prompts:supportPrompts.resetPrompt", { promptType: activeSupportTab })}>
|
||||
onClick={() => handleSupportReset(activeSupportOption)}
|
||||
title={t("prompts:supportPrompts.resetPrompt", {
|
||||
promptType: activeSupportOption,
|
||||
})}>
|
||||
<span className="codicon codicon-discard"></span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<VSCodeTextArea
|
||||
value={getSupportPromptValue(activeSupportTab)}
|
||||
<Textarea
|
||||
value={getSupportPromptValue(activeSupportOption)}
|
||||
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 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" && (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
color: "var(--vscode-foreground)",
|
||||
fontSize: "13px",
|
||||
marginBottom: "20px",
|
||||
marginTop: "5px",
|
||||
}}></div>
|
||||
<div style={{ marginBottom: "12px" }}>
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
|
||||
<div className="text-vscode-foreground text-[13px] mb-5 mt-1.5"></div>
|
||||
<div className="mb-3">
|
||||
<div className="mb-2">
|
||||
<div className="font-bold mb-1">
|
||||
{t("prompts:supportPrompts.enhance.apiConfiguration")}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
<div className="text-[13px] text-vscode-descriptionForeground">
|
||||
{t("prompts:supportPrompts.enhance.apiConfigDescription")}
|
||||
</div>
|
||||
</div>
|
||||
<VSCodeDropdown
|
||||
value={enhancementApiConfigId || ""}
|
||||
data-testid="api-config-dropdown"
|
||||
onChange={(e: any) => {
|
||||
const value = e.detail?.target?.value || e.target?.value
|
||||
setEnhancementApiConfigId(value)
|
||||
<Select
|
||||
value={enhancementApiConfigId || "-"}
|
||||
onValueChange={(value) => {
|
||||
// normalise to empty string for empty value
|
||||
// because we can't use it directly for the select element
|
||||
setEnhancementApiConfigId(value === "-" ? "" : value)
|
||||
vscode.postMessage({
|
||||
type: "enhancementApiConfigId",
|
||||
text: value,
|
||||
})
|
||||
}}
|
||||
style={{ width: "300px" }}>
|
||||
<VSCodeOption value="">
|
||||
{t("prompts:supportPrompts.enhance.useCurrentConfig")}
|
||||
</VSCodeOption>
|
||||
{(listApiConfigMeta || []).map((config) => (
|
||||
<VSCodeOption key={config.id} value={config.id}>
|
||||
{config.name}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
}}>
|
||||
<SelectTrigger data-testid="api-config-select" className="w-full">
|
||||
<SelectValue
|
||||
placeholder={t("prompts:supportPrompts.enhance.useCurrentConfig")}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="-">
|
||||
{t("prompts:supportPrompts.enhance.useCurrentConfig")}
|
||||
</SelectItem>
|
||||
{(listApiConfigMeta || []).map((config) => (
|
||||
<SelectItem
|
||||
key={config.id}
|
||||
value={config.id}
|
||||
data-testid={`${config.id}-option`}>
|
||||
{config.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: "12px" }}>
|
||||
<VSCodeTextArea
|
||||
<div className="mt-4">
|
||||
<Textarea
|
||||
value={testPrompt}
|
||||
onChange={(e) => setTestPrompt((e.target as HTMLTextAreaElement).value)}
|
||||
placeholder={t("prompts:supportPrompts.enhance.testPromptPlaceholder")}
|
||||
rows={3}
|
||||
resize="vertical"
|
||||
style={{ width: "100%" }}
|
||||
className="w-full resize-y"
|
||||
data-testid="test-prompt-textarea"
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
marginTop: "8px",
|
||||
display: "flex",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
}}>
|
||||
<div className="mt-2 flex justify-start items-center gap-2">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={handleTestEnhancement}
|
||||
|
|
@ -1135,92 +1142,50 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</TabContent>
|
||||
|
||||
{isCreateModeDialogOpen && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
zIndex: 1000,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: "calc(100vw - 100px)",
|
||||
height: "100%",
|
||||
backgroundColor: "var(--vscode-editor-background)",
|
||||
boxShadow: "-2px 0 5px rgba(0, 0, 0, 0.2)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
position: "relative",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "20px",
|
||||
overflowY: "auto",
|
||||
minHeight: 0,
|
||||
}}>
|
||||
<div className="fixed inset-0 flex justify-end bg-black/50 z-[1000]">
|
||||
<div className="w-[calc(100vw-100px)] h-full bg-vscode-editor-background shadow-md flex flex-col relative">
|
||||
<div className="flex-1 p-5 overflow-y-auto min-h-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsCreateModeDialogOpen(false)}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "20px",
|
||||
right: "20px",
|
||||
}}>
|
||||
className="absolute top-5 right-5">
|
||||
<span className="codicon codicon-close"></span>
|
||||
</Button>
|
||||
<h2 style={{ margin: "0 0 16px" }}>{t("prompts:createModeDialog.title")}</h2>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
|
||||
{t("prompts:createModeDialog.name.label")}
|
||||
</div>
|
||||
<VSCodeTextField
|
||||
<h2 className="mb-4">{t("prompts:createModeDialog.title")}</h2>
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">{t("prompts:createModeDialog.name.label")}</div>
|
||||
<Input
|
||||
type="text"
|
||||
value={newModeName}
|
||||
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
|
||||
const target =
|
||||
(e as CustomEvent)?.detail?.target ||
|
||||
((e as any).target as HTMLInputElement)
|
||||
handleNameChange(target.value)
|
||||
onChange={(e) => {
|
||||
handleNameChange(e.target.value)
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
className="w-full"
|
||||
/>
|
||||
{nameError && (
|
||||
<div className="text-xs text-vscode-errorForeground mt-1">{nameError}</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
|
||||
{t("prompts:createModeDialog.slug.label")}
|
||||
</div>
|
||||
<VSCodeTextField
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">{t("prompts:createModeDialog.slug.label")}</div>
|
||||
<Input
|
||||
type="text"
|
||||
value={newModeSlug}
|
||||
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
|
||||
const target =
|
||||
(e as CustomEvent)?.detail?.target ||
|
||||
((e as any).target as HTMLInputElement)
|
||||
setNewModeSlug(target.value)
|
||||
onChange={(e) => {
|
||||
setNewModeSlug(e.target.value)
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
className="w-full"
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginTop: "4px",
|
||||
}}>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-1">
|
||||
{t("prompts:createModeDialog.slug.description")}
|
||||
</div>
|
||||
{slugError && (
|
||||
<div className="text-xs text-vscode-errorForeground mt-1">{slugError}</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
|
||||
{t("prompts:createModeDialog.saveLocation.label")}
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">{t("prompts:createModeDialog.saveLocation.label")}</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground mb-2">
|
||||
{t("prompts:createModeDialog.saveLocation.description")}
|
||||
</div>
|
||||
|
|
@ -1233,12 +1198,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
}}>
|
||||
<VSCodeRadio value="global">
|
||||
{t("prompts:createModeDialog.saveLocation.global.label")}
|
||||
<div
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginTop: "2px",
|
||||
}}>
|
||||
<div className="text-xs text-vscode-descriptionForeground mt-0.5">
|
||||
{t("prompts:createModeDialog.saveLocation.global.description")}
|
||||
</div>
|
||||
</VSCodeRadio>
|
||||
|
|
@ -1263,17 +1223,13 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
}}>
|
||||
{t("prompts:createModeDialog.roleDefinition.description")}
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
<Textarea
|
||||
value={newModeRoleDefinition}
|
||||
onChange={(e) => {
|
||||
const value =
|
||||
(e as CustomEvent)?.detail?.target?.value ||
|
||||
((e as any).target as HTMLTextAreaElement).value
|
||||
setNewModeRoleDefinition(value)
|
||||
setNewModeRoleDefinition(e.target.value)
|
||||
}}
|
||||
rows={4}
|
||||
resize="vertical"
|
||||
style={{ width: "100%" }}
|
||||
className="w-full resize-y"
|
||||
/>
|
||||
{roleDefinitionError && (
|
||||
<div className="text-xs text-vscode-errorForeground mt-1">
|
||||
|
|
@ -1281,24 +1237,12 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
|
||||
{t("prompts:createModeDialog.tools.label")}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">{t("prompts:createModeDialog.tools.label")}</div>
|
||||
<div className="text-[13px] text-vscode-descriptionForeground mb-2">
|
||||
{t("prompts:createModeDialog.tools.description")}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
|
||||
gap: "8px",
|
||||
}}>
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2">
|
||||
{availableGroups.map((group) => (
|
||||
<VSCodeCheckbox
|
||||
key={group}
|
||||
|
|
@ -1323,41 +1267,24 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
<div className="text-xs text-vscode-errorForeground mt-1">{groupsError}</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ marginBottom: "16px" }}>
|
||||
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>
|
||||
<div className="mb-4">
|
||||
<div className="font-bold mb-1">
|
||||
{t("prompts:createModeDialog.customInstructions.label")}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: "13px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
marginBottom: "8px",
|
||||
}}>
|
||||
<div className="text-[13px] text-vscode-descriptionForeground mb-2">
|
||||
{t("prompts:createModeDialog.customInstructions.description")}
|
||||
</div>
|
||||
<VSCodeTextArea
|
||||
<Textarea
|
||||
value={newModeCustomInstructions}
|
||||
onChange={(e) => {
|
||||
const value =
|
||||
(e as CustomEvent)?.detail?.target?.value ||
|
||||
((e as any).target as HTMLTextAreaElement).value
|
||||
setNewModeCustomInstructions(value)
|
||||
setNewModeCustomInstructions(e.target.value)
|
||||
}}
|
||||
rows={4}
|
||||
resize="vertical"
|
||||
style={{ width: "100%" }}
|
||||
className="w-full resize-y"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
padding: "12px 20px",
|
||||
gap: "8px",
|
||||
borderTop: "1px solid var(--vscode-editor-lineHighlightBorder)",
|
||||
backgroundColor: "var(--vscode-editor-background)",
|
||||
}}>
|
||||
<div className="flex justify-end p-3 px-5 gap-2 border-t border-vscode-editor-lineHighlightBorder bg-vscode-editor-background">
|
||||
<Button variant="secondary" onClick={() => setIsCreateModeDialogOpen(false)}>
|
||||
{t("prompts:createModeDialog.buttons.cancel")}
|
||||
</Button>
|
||||
|
|
@ -1370,71 +1297,27 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|||
)}
|
||||
|
||||
{isDialogOpen && (
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
zIndex: 1000,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: "calc(100vw - 100px)",
|
||||
height: "100%",
|
||||
backgroundColor: "var(--vscode-editor-background)",
|
||||
boxShadow: "-2px 0 5px rgba(0, 0, 0, 0.2)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
position: "relative",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "20px",
|
||||
overflowY: "auto",
|
||||
minHeight: 0,
|
||||
}}>
|
||||
<div className="fixed inset-0 flex justify-end bg-black/50 z-[1000]">
|
||||
<div className="w-[calc(100vw-100px)] h-full bg-vscode-editor-background shadow-md flex flex-col relative">
|
||||
<div className="flex-1 p-5 overflow-y-auto min-h-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setIsDialogOpen(false)}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "20px",
|
||||
right: "20px",
|
||||
}}>
|
||||
className="absolute top-5 right-5">
|
||||
<span className="codicon codicon-close"></span>
|
||||
</Button>
|
||||
<h2 style={{ margin: "0 0 16px" }}>
|
||||
<h2 className="mb-4">
|
||||
{selectedPromptTitle ||
|
||||
t("prompts:systemPrompt.title", { modeName: getCurrentMode()?.name || "Code" })}
|
||||
t("prompts:systemPrompt.title", {
|
||||
modeName: getCurrentMode()?.name || "Code",
|
||||
})}
|
||||
</h2>
|
||||
<pre
|
||||
style={{
|
||||
padding: "8px",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
fontFamily: "var(--vscode-editor-font-family)",
|
||||
fontSize: "var(--vscode-editor-font-size)",
|
||||
color: "var(--vscode-editor-foreground)",
|
||||
backgroundColor: "var(--vscode-editor-background)",
|
||||
border: "1px solid var(--vscode-editor-lineHighlightBorder)",
|
||||
borderRadius: "4px",
|
||||
overflowY: "auto",
|
||||
}}>
|
||||
<pre className="p-2 whitespace-pre-wrap break-words font-mono text-vscode-editor-font-size text-vscode-editor-foreground bg-vscode-editor-background border border-vscode-editor-lineHighlightBorder rounded overflow-y-auto">
|
||||
{selectedPromptContent}
|
||||
</pre>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
padding: "12px 20px",
|
||||
borderTop: "1px solid var(--vscode-editor-lineHighlightBorder)",
|
||||
backgroundColor: "var(--vscode-editor-background)",
|
||||
}}>
|
||||
<div className="flex justify-end p-3 px-5 border-t border-vscode-editor-lineHighlightBorder bg-vscode-editor-background">
|
||||
<Button variant="secondary" onClick={() => setIsDialogOpen(false)}>
|
||||
{t("prompts:createModeDialog.close")}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,22 @@ jest.mock("@src/utils/vscode", () => ({
|
|||
},
|
||||
}))
|
||||
|
||||
// Mock all lucide-react icons with a proxy to handle any icon requested
|
||||
jest.mock("lucide-react", () => {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get: function (obj, prop) {
|
||||
// Return a component factory for any icon that's requested
|
||||
if (prop === "__esModule") {
|
||||
return true
|
||||
}
|
||||
return () => <div data-testid={`${String(prop)}-icon`}>{String(prop)}</div>
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
const mockExtensionState = {
|
||||
customModePrompts: {},
|
||||
listApiConfigMeta: [
|
||||
|
|
@ -19,6 +35,9 @@ const mockExtensionState = {
|
|||
enhancementApiConfigId: "",
|
||||
setEnhancementApiConfigId: jest.fn(),
|
||||
mode: "code",
|
||||
customModes: [],
|
||||
customSupportPrompts: [],
|
||||
currentApiConfigName: "",
|
||||
customInstructions: "Initial instructions",
|
||||
setCustomInstructions: jest.fn(),
|
||||
}
|
||||
|
|
@ -32,69 +51,67 @@ const renderPromptsView = (props = {}) => {
|
|||
)
|
||||
}
|
||||
|
||||
class MockResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
global.ResizeObserver = MockResizeObserver
|
||||
|
||||
Element.prototype.scrollIntoView = jest.fn()
|
||||
|
||||
describe("PromptsView", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("renders all mode tabs", () => {
|
||||
it("displays the current mode name in the select trigger", () => {
|
||||
renderPromptsView({ mode: "code" })
|
||||
const selectTrigger = screen.getByTestId("mode-select-trigger")
|
||||
expect(selectTrigger).toHaveTextContent("Code")
|
||||
})
|
||||
|
||||
it("opens the mode selection popover when the trigger is clicked", async () => {
|
||||
renderPromptsView()
|
||||
expect(screen.getByTestId("code-tab")).toBeInTheDocument()
|
||||
expect(screen.getByTestId("ask-tab")).toBeInTheDocument()
|
||||
expect(screen.getByTestId("architect-tab")).toBeInTheDocument()
|
||||
const selectTrigger = screen.getByTestId("mode-select-trigger")
|
||||
fireEvent.click(selectTrigger)
|
||||
await waitFor(() => {
|
||||
expect(selectTrigger).toHaveAttribute("aria-expanded", "true")
|
||||
})
|
||||
})
|
||||
|
||||
it("defaults to current mode as active tab", () => {
|
||||
renderPromptsView({ mode: "ask" })
|
||||
it("filters mode options based on search input", async () => {
|
||||
renderPromptsView()
|
||||
const selectTrigger = screen.getByTestId("mode-select-trigger")
|
||||
fireEvent.click(selectTrigger)
|
||||
|
||||
const codeTab = screen.getByTestId("code-tab")
|
||||
const askTab = screen.getByTestId("ask-tab")
|
||||
const architectTab = screen.getByTestId("architect-tab")
|
||||
const searchInput = screen.getByTestId("mode-search-input")
|
||||
fireEvent.change(searchInput, { target: { value: "ask" } })
|
||||
|
||||
expect(askTab).toHaveAttribute("data-active", "true")
|
||||
expect(codeTab).toHaveAttribute("data-active", "false")
|
||||
expect(architectTab).toHaveAttribute("data-active", "false")
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("mode-option-ask")).toBeInTheDocument()
|
||||
expect(screen.queryByTestId("mode-option-code")).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId("mode-option-architect")).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it("switches between tabs correctly", async () => {
|
||||
const { rerender } = render(
|
||||
<ExtensionStateContext.Provider value={{ ...mockExtensionState, mode: "code" } as any}>
|
||||
<PromptsView onDone={jest.fn()} />
|
||||
</ExtensionStateContext.Provider>,
|
||||
)
|
||||
it("selects a mode from the dropdown and sends update message", async () => {
|
||||
renderPromptsView()
|
||||
const selectTrigger = screen.getByTestId("mode-select-trigger")
|
||||
fireEvent.click(selectTrigger)
|
||||
|
||||
const codeTab = screen.getByTestId("code-tab")
|
||||
const askTab = screen.getByTestId("ask-tab")
|
||||
const architectTab = screen.getByTestId("architect-tab")
|
||||
const askOption = await waitFor(() => screen.getByTestId("mode-option-ask"))
|
||||
fireEvent.click(askOption)
|
||||
|
||||
// Initial state matches current mode (code)
|
||||
expect(codeTab).toHaveAttribute("data-active", "true")
|
||||
expect(askTab).toHaveAttribute("data-active", "false")
|
||||
expect(architectTab).toHaveAttribute("data-active", "false")
|
||||
|
||||
// Click Ask tab and update context
|
||||
fireEvent.click(askTab)
|
||||
rerender(
|
||||
<ExtensionStateContext.Provider value={{ ...mockExtensionState, mode: "ask" } as any}>
|
||||
<PromptsView onDone={jest.fn()} />
|
||||
</ExtensionStateContext.Provider>,
|
||||
)
|
||||
|
||||
expect(askTab).toHaveAttribute("data-active", "true")
|
||||
expect(codeTab).toHaveAttribute("data-active", "false")
|
||||
expect(architectTab).toHaveAttribute("data-active", "false")
|
||||
|
||||
// Click Architect tab and update context
|
||||
fireEvent.click(architectTab)
|
||||
rerender(
|
||||
<ExtensionStateContext.Provider value={{ ...mockExtensionState, mode: "architect" } as any}>
|
||||
<PromptsView onDone={jest.fn()} />
|
||||
</ExtensionStateContext.Provider>,
|
||||
)
|
||||
|
||||
expect(architectTab).toHaveAttribute("data-active", "true")
|
||||
expect(askTab).toHaveAttribute("data-active", "false")
|
||||
expect(codeTab).toHaveAttribute("data-active", "false")
|
||||
expect(mockExtensionState.setEnhancementApiConfigId).not.toHaveBeenCalled() // Ensure this is not called by mode switch
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "mode",
|
||||
text: "ask",
|
||||
})
|
||||
await waitFor(() => {
|
||||
expect(selectTrigger).toHaveAttribute("aria-expanded", "false")
|
||||
})
|
||||
})
|
||||
|
||||
it("handles prompt changes correctly", async () => {
|
||||
|
|
@ -159,21 +176,19 @@ describe("PromptsView", () => {
|
|||
it("handles API configuration selection", async () => {
|
||||
renderPromptsView()
|
||||
|
||||
// Click the ENHANCE tab first to show the API config dropdown
|
||||
const enhanceTab = screen.getByTestId("ENHANCE-tab")
|
||||
fireEvent.click(enhanceTab)
|
||||
const trigger = screen.getByTestId("support-prompt-select-trigger")
|
||||
fireEvent.click(trigger)
|
||||
|
||||
// Wait for the ENHANCE tab click to take effect
|
||||
const dropdown = await waitFor(() => screen.getByTestId("api-config-dropdown"))
|
||||
fireEvent.change(dropdown, {
|
||||
target: { value: "config1" },
|
||||
})
|
||||
const enhanceOption = await waitFor(() => screen.getByTestId("ENHANCE-option"))
|
||||
fireEvent.click(enhanceOption)
|
||||
|
||||
expect(mockExtensionState.setEnhancementApiConfigId).toHaveBeenCalledWith("config1")
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "enhancementApiConfigId",
|
||||
text: "config1",
|
||||
})
|
||||
const apiConfig = await waitFor(() => screen.getByTestId("api-config-select"))
|
||||
fireEvent.click(apiConfig)
|
||||
|
||||
const config1 = await waitFor(() => screen.getByTestId("config1-option"))
|
||||
fireEvent.click(config1)
|
||||
|
||||
expect(mockExtensionState.setEnhancementApiConfigId).toHaveBeenCalledWith("config1") // Ensure this is not called by mode switch
|
||||
})
|
||||
|
||||
it("handles clearing custom instructions correctly", async () => {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import { HTMLAttributes, useState } from "react"
|
|||
import { X } from "lucide-react"
|
||||
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { VSCodeTextField, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { Button, Slider } from "@/components/ui"
|
||||
import { Button, Input, Slider } from "@/components/ui"
|
||||
|
||||
import { SetCachedStateField } from "./types"
|
||||
import { SectionHeader } from "./SectionHeader"
|
||||
|
|
@ -203,9 +203,9 @@ export const AutoApproveSettings = ({
|
|||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<VSCodeTextField
|
||||
<Input
|
||||
value={commandInput}
|
||||
onInput={(e: any) => setCommandInput(e.target.value)}
|
||||
onChange={(e: any) => setCommandInput(e.target.value)}
|
||||
onKeyDown={(e: any) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
|
|
@ -216,7 +216,7 @@ export const AutoApproveSettings = ({
|
|||
className="grow"
|
||||
data-testid="command-input"
|
||||
/>
|
||||
<Button onClick={handleAddCommand} data-testid="add-command-button">
|
||||
<Button className="h-8" onClick={handleAddCommand} data-testid="add-command-button">
|
||||
{t("settings:autoApprove.execute.addButton")}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -234,7 +234,7 @@ export const AutoApproveSettings = ({
|
|||
}}>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<div>{cmd}</div>
|
||||
<X className="text-primary-foreground scale-75" />
|
||||
<X className="text-foreground scale-75" />
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
|||
import { Database } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Slider } from "@/components/ui"
|
||||
import { Input, Slider } from "@/components/ui"
|
||||
|
||||
import { SetCachedStateField } from "./types"
|
||||
import { SectionHeader } from "./SectionHeader"
|
||||
|
|
@ -96,7 +96,7 @@ export const ContextManagementSettings = ({
|
|||
<div className="flex flex-col gap-2">
|
||||
<span className="font-medium">{t("settings:contextManagement.maxReadFile.label")}</span>
|
||||
<div className="flex items-center gap-4">
|
||||
<input
|
||||
<Input
|
||||
type="number"
|
||||
pattern="-?[0-9]*"
|
||||
className="w-24 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border px-2 py-1 rounded text-right [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none disabled:opacity-50"
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof
|
|||
<AlertDialogPrimitive.Action
|
||||
className={cn(
|
||||
buttonVariants(),
|
||||
"bg-vscode-button-background text-vscode-button-foreground hover:bg-vscode-button-hoverBackground border border-transparent h-6 px-3 py-1",
|
||||
"bg-vscode-button-background text-vscode-button-foreground hover:bg-vscode-button-hoverBackground h-6 px-3 py-1 border",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
@ -103,7 +103,7 @@ function AlertDialogCancel({ className, ...props }: React.ComponentProps<typeof
|
|||
<AlertDialogPrimitive.Cancel
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground hover:bg-vscode-button-secondaryHoverBackground border border-vscode-button-border h-6 px-3 py-1",
|
||||
"bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground hover:bg-vscode-button-secondaryHoverBackground h-6 px-3 py-1 border",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,12 @@ const buttonVariants = cva(
|
|||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border border-vscode-input-border bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
default: "border border-vscode-input-border bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-vscode-input-border bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
"border border-vscode-input-border bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"border border-vscode-input-border bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
"border border-vscode-input-border bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
combobox:
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ const Slider = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn("relative flex w-full touch-none select-none items-center", className)}
|
||||
{...props}>
|
||||
<SliderPrimitive.Track className="relative w-full h-[8px] grow overflow-hidden bg-accent border border-[#767676] dark:border-[#858585] rounded-sm">
|
||||
<SliderPrimitive.Track className="relative w-full h-[8px] grow overflow-hidden bg-accent rounded-sm border">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-vscode-button-background" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="block h-3 w-3 rounded-full border border-primary/50 bg-primary shadow transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
<SliderPrimitive.Thumb className="block h-3 w-3 rounded-full border border-primary/50 bg-vscode-button-background transition-colors cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
))
|
||||
Slider.displayName = SliderPrimitive.Root.displayName
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"tex
|
|||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[60px] w-full rounded-xs px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus:outline-0 focus-visible:outline-none focus-visible:border-vscode-focusBorder disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"border-[var(--vscode-input-border,var(--vscode-input-background))] focus-visible:border-vscode-focusBorder",
|
||||
"flex min-h-[60px] w-full rounded-xs px-3 py-2 text-base placeholder:text-muted-foreground focus:outline-0 focus-visible:outline-none focus-visible:border-vscode-focusBorder disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"border border-[var(--vscode-input-border,var(--vscode-input-background))] focus-visible:border-vscode-focusBorder",
|
||||
"bg-vscode-input-background",
|
||||
"text-vscode-input-foreground",
|
||||
className,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Editar configuració de modes",
|
||||
"editGlobalModes": "Editar modes globals",
|
||||
"editProjectModes": "Editar modes de projecte (.roomodes)",
|
||||
"createModeHelpText": "Feu clic a + per crear un nou mode personalitzat, o simplement demaneu a Roo al xat que en creï un per a vostè!"
|
||||
"createModeHelpText": "Feu clic a + per crear un nou mode personalitzat, o simplement demaneu a Roo al xat que en creï un per a vostè!",
|
||||
"selectMode": "Cerqueu modes"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "Configuració d'API",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Moduskonfiguration bearbeiten",
|
||||
"editGlobalModes": "Globale Modi bearbeiten",
|
||||
"editProjectModes": "Projektmodi bearbeiten (.roomodes)",
|
||||
"createModeHelpText": "Klicke auf +, um einen neuen benutzerdefinierten Modus zu erstellen, oder bitte Roo einfach im Chat, einen für dich zu erstellen!"
|
||||
"createModeHelpText": "Klicke auf +, um einen neuen benutzerdefinierten Modus zu erstellen, oder bitte Roo einfach im Chat, einen für dich zu erstellen!",
|
||||
"selectMode": "Modi suchen"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "API-Konfiguration",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Edit modes configuration",
|
||||
"editGlobalModes": "Edit Global Modes",
|
||||
"editProjectModes": "Edit Project Modes (.roomodes)",
|
||||
"createModeHelpText": "Hit the + to create a new custom mode, or just ask Roo in chat to create one for you!"
|
||||
"createModeHelpText": "Hit the + to create a new custom mode, or just ask Roo in chat to create one for you!",
|
||||
"selectMode": "Search modes"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "API Configuration",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Editar configuración de modos",
|
||||
"editGlobalModes": "Editar modos globales",
|
||||
"editProjectModes": "Editar modos del proyecto (.roomodes)",
|
||||
"createModeHelpText": "¡Haz clic en + para crear un nuevo modo personalizado, o simplemente pídele a Roo en el chat que te cree uno!"
|
||||
"createModeHelpText": "¡Haz clic en + para crear un nuevo modo personalizado, o simplemente pídele a Roo en el chat que te cree uno!",
|
||||
"selectMode": "Buscar modos"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "Configuración de API",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Modifier la configuration des modes",
|
||||
"editGlobalModes": "Modifier les modes globaux",
|
||||
"editProjectModes": "Modifier les modes du projet (.roomodes)",
|
||||
"createModeHelpText": "Cliquez sur + pour créer un nouveau mode personnalisé, ou demandez simplement à Roo dans le chat de vous en créer un !"
|
||||
"createModeHelpText": "Cliquez sur + pour créer un nouveau mode personnalisé, ou demandez simplement à Roo dans le chat de vous en créer un !",
|
||||
"selectMode": "Rechercher les modes"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "Configuration API",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "मोड कॉन्फ़िगरेशन संपादित करें",
|
||||
"editGlobalModes": "ग्लोबल मोड्स संपादित करें",
|
||||
"editProjectModes": "प्रोजेक्ट मोड्स संपादित करें (.roomodes)",
|
||||
"createModeHelpText": "नया कस्टम मोड बनाने के लिए + पर क्लिक करें, या बस चैट में Roo से आपके लिए एक बनाने को कहें!"
|
||||
"createModeHelpText": "नया कस्टम मोड बनाने के लिए + पर क्लिक करें, या बस चैट में Roo से आपके लिए एक बनाने को कहें!",
|
||||
"selectMode": "मोड खोजें"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "API कॉन्फ़िगरेशन",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Modifica configurazione modalità",
|
||||
"editGlobalModes": "Modifica modalità globali",
|
||||
"editProjectModes": "Modifica modalità di progetto (.roomodes)",
|
||||
"createModeHelpText": "Clicca sul + per creare una nuova modalità personalizzata, o chiedi semplicemente a Roo nella chat di crearne una per te!"
|
||||
"createModeHelpText": "Clicca sul + per creare una nuova modalità personalizzata, o chiedi semplicemente a Roo nella chat di crearne una per te!",
|
||||
"selectMode": "Cerca modalità"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "Configurazione API",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "モード設定を編集",
|
||||
"editGlobalModes": "グローバルモードを編集",
|
||||
"editProjectModes": "プロジェクトモードを編集 (.roomodes)",
|
||||
"createModeHelpText": "+ をクリックして新しいカスタムモードを作成するか、チャットで Roo に作成を依頼してください!"
|
||||
"createModeHelpText": "+ をクリックして新しいカスタムモードを作成するか、チャットで Roo に作成を依頼してください!",
|
||||
"selectMode": "モードを検索"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "API設定",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "모드 구성 편집",
|
||||
"editGlobalModes": "전역 모드 편집",
|
||||
"editProjectModes": "프로젝트 모드 편집 (.roomodes)",
|
||||
"createModeHelpText": "새 커스텀 모드를 만들려면 + 버튼을 클릭하거나, 채팅에서 Roo에게 만들어달라고 요청하세요!"
|
||||
"createModeHelpText": "새 커스텀 모드를 만들려면 + 버튼을 클릭하거나, 채팅에서 Roo에게 만들어달라고 요청하세요!",
|
||||
"selectMode": "모드 검색"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "API 구성",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Edytuj konfigurację trybów",
|
||||
"editGlobalModes": "Edytuj tryby globalne",
|
||||
"editProjectModes": "Edytuj tryby projektu (.roomodes)",
|
||||
"createModeHelpText": "Kliknij +, aby utworzyć nowy niestandardowy tryb, lub po prostu poproś Roo w czacie, aby utworzył go dla Ciebie!"
|
||||
"createModeHelpText": "Kliknij +, aby utworzyć nowy niestandardowy tryb, lub po prostu poproś Roo w czacie, aby utworzył go dla Ciebie!",
|
||||
"selectMode": "Szukaj trybów"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "Konfiguracja API",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Editar configuração de modos",
|
||||
"editGlobalModes": "Editar modos globais",
|
||||
"editProjectModes": "Editar modos do projeto (.roomodes)",
|
||||
"createModeHelpText": "Clique em + para criar um novo modo personalizado, ou simplesmente peça ao Roo no chat para criar um para você!"
|
||||
"createModeHelpText": "Clique em + para criar um novo modo personalizado, ou simplesmente peça ao Roo no chat para criar um para você!",
|
||||
"selectMode": "Buscar modos"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "Configuração de API",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Редактировать конфигурацию режимов",
|
||||
"editGlobalModes": "Редактировать глобальные режимы",
|
||||
"editProjectModes": "Редактировать режимы проекта (.roomodes)",
|
||||
"createModeHelpText": "Нажмите +, чтобы создать новый пользовательский режим, или просто попросите Roo в чате создать его для вас!"
|
||||
"createModeHelpText": "Нажмите +, чтобы создать новый пользовательский режим, или просто попросите Roo в чате создать его для вас!",
|
||||
"selectMode": "Поиск режимов"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "Конфигурация API",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Mod yapılandırmasını düzenle",
|
||||
"editGlobalModes": "Global modları düzenle",
|
||||
"editProjectModes": "Proje modlarını düzenle (.roomodes)",
|
||||
"createModeHelpText": "Yeni bir özel mod oluşturmak için + düğmesine tıklayın veya sohbette Roo'dan sizin için bir tane oluşturmasını isteyin!"
|
||||
"createModeHelpText": "Yeni bir özel mod oluşturmak için + düğmesine tıklayın veya sohbette Roo'dan sizin için bir tane oluşturmasını isteyin!",
|
||||
"selectMode": "Modları Ara"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "API Yapılandırması",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "Chỉnh sửa cấu hình chế độ",
|
||||
"editGlobalModes": "Chỉnh sửa chế độ toàn cục",
|
||||
"editProjectModes": "Chỉnh sửa chế độ dự án (.roomodes)",
|
||||
"createModeHelpText": "Nhấn + để tạo chế độ tùy chỉnh mới, hoặc chỉ cần yêu cầu Roo trong chat tạo một chế độ cho bạn!"
|
||||
"createModeHelpText": "Nhấn + để tạo chế độ tùy chỉnh mới, hoặc chỉ cần yêu cầu Roo trong chat tạo một chế độ cho bạn!",
|
||||
"selectMode": "Tìm kiếm chế độ"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "Cấu hình API",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "模式设置",
|
||||
"editGlobalModes": "修改全局模式",
|
||||
"editProjectModes": "编辑项目模式 (.roomodes)",
|
||||
"createModeHelpText": "点击 + 创建模式,或在对话时让Roo创建一个新模式。"
|
||||
"createModeHelpText": "点击 + 创建模式,或在对话时让Roo创建一个新模式。",
|
||||
"selectMode": "搜索模式"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "API配置",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
"editModesConfig": "編輯模式設定",
|
||||
"editGlobalModes": "編輯全域模式",
|
||||
"editProjectModes": "編輯專案模式 (.roomodes)",
|
||||
"createModeHelpText": "點選 + 建立新的自訂模式,或者在聊天中直接請 Roo 為您建立!"
|
||||
"createModeHelpText": "點選 + 建立新的自訂模式,或者在聊天中直接請 Roo 為您建立!",
|
||||
"selectMode": "搜尋模式"
|
||||
},
|
||||
"apiConfiguration": {
|
||||
"title": "API 設定",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue