|
|
@@ -19,6 +19,7 @@ import {
|
|
|
ModeConfig,
|
|
|
GroupEntry,
|
|
|
} from "../../../../src/shared/modes"
|
|
|
+import { CustomModeSchema } from "../../../../src/core/config/CustomModesSchema"
|
|
|
import {
|
|
|
supportPrompt,
|
|
|
SupportPromptType,
|
|
|
@@ -157,15 +158,34 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|
|
const [newModeGroups, setNewModeGroups] = useState<GroupEntry[]>(availableGroups)
|
|
|
const [newModeSource, setNewModeSource] = useState<ModeSource>("global")
|
|
|
|
|
|
+ // Field-specific error states
|
|
|
+ const [nameError, setNameError] = useState<string>("")
|
|
|
+ const [slugError, setSlugError] = useState<string>("")
|
|
|
+ const [roleDefinitionError, setRoleDefinitionError] = useState<string>("")
|
|
|
+ const [groupsError, setGroupsError] = useState<string>("")
|
|
|
+
|
|
|
+ // Helper to reset form state
|
|
|
+ const resetFormState = useCallback(() => {
|
|
|
+ // Reset form fields
|
|
|
+ setNewModeName("")
|
|
|
+ setNewModeSlug("")
|
|
|
+ setNewModeGroups(availableGroups)
|
|
|
+ setNewModeRoleDefinition("")
|
|
|
+ setNewModeCustomInstructions("")
|
|
|
+ setNewModeSource("global")
|
|
|
+ // Reset error states
|
|
|
+ setNameError("")
|
|
|
+ setSlugError("")
|
|
|
+ setRoleDefinitionError("")
|
|
|
+ setGroupsError("")
|
|
|
+ }, [])
|
|
|
+
|
|
|
// Reset form fields when dialog opens
|
|
|
useEffect(() => {
|
|
|
if (isCreateModeDialogOpen) {
|
|
|
- setNewModeGroups(availableGroups)
|
|
|
- setNewModeRoleDefinition("")
|
|
|
- setNewModeCustomInstructions("")
|
|
|
- setNewModeSource("global")
|
|
|
+ resetFormState()
|
|
|
}
|
|
|
- }, [isCreateModeDialogOpen])
|
|
|
+ }, [isCreateModeDialogOpen, resetFormState])
|
|
|
|
|
|
// Helper function to generate a unique slug from a name
|
|
|
const generateSlug = useCallback((name: string, attempt = 0): string => {
|
|
|
@@ -186,26 +206,52 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|
|
)
|
|
|
|
|
|
const handleCreateMode = useCallback(() => {
|
|
|
- if (!newModeName.trim() || !newModeSlug.trim()) return
|
|
|
+ // Clear previous errors
|
|
|
+ setNameError("")
|
|
|
+ setSlugError("")
|
|
|
+ setRoleDefinitionError("")
|
|
|
+ setGroupsError("")
|
|
|
|
|
|
const source = newModeSource
|
|
|
const newMode: ModeConfig = {
|
|
|
slug: newModeSlug,
|
|
|
name: newModeName,
|
|
|
- roleDefinition: newModeRoleDefinition.trim() || "",
|
|
|
+ roleDefinition: newModeRoleDefinition.trim(),
|
|
|
customInstructions: newModeCustomInstructions.trim() || undefined,
|
|
|
groups: newModeGroups,
|
|
|
source,
|
|
|
}
|
|
|
+
|
|
|
+ // Validate the mode against the schema
|
|
|
+ const result = CustomModeSchema.safeParse(newMode)
|
|
|
+ if (!result.success) {
|
|
|
+ // Map Zod errors to specific fields
|
|
|
+ result.error.errors.forEach((error) => {
|
|
|
+ const field = error.path[0] as string
|
|
|
+ const message = error.message
|
|
|
+
|
|
|
+ switch (field) {
|
|
|
+ case "name":
|
|
|
+ setNameError(message)
|
|
|
+ break
|
|
|
+ case "slug":
|
|
|
+ setSlugError(message)
|
|
|
+ break
|
|
|
+ case "roleDefinition":
|
|
|
+ setRoleDefinitionError(message)
|
|
|
+ break
|
|
|
+ case "groups":
|
|
|
+ setGroupsError(message)
|
|
|
+ break
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
updateCustomMode(newModeSlug, newMode)
|
|
|
switchMode(newModeSlug)
|
|
|
setIsCreateModeDialogOpen(false)
|
|
|
- setNewModeName("")
|
|
|
- setNewModeSlug("")
|
|
|
- setNewModeRoleDefinition("")
|
|
|
- setNewModeCustomInstructions("")
|
|
|
- setNewModeGroups(availableGroups)
|
|
|
- setNewModeSource("global")
|
|
|
+ resetFormState()
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
}, [
|
|
|
newModeName,
|
|
|
@@ -431,7 +477,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|
|
|
|
|
<div className="mt-5">
|
|
|
<div onClick={(e) => e.stopPropagation()} className="flex justify-between items-center mb-3">
|
|
|
- <h3 className="text-vscode-foreground m-0">Mode-Specific Prompts</h3>
|
|
|
+ <h3 className="text-vscode-foreground m-0">Modes</h3>
|
|
|
<div className="flex gap-2">
|
|
|
<VSCodeButton appearance="icon" onClick={openCreateModeDialog} title="Create new mode">
|
|
|
<span className="codicon codicon-add"></span>
|
|
|
@@ -727,7 +773,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|
|
alignItems: "center",
|
|
|
marginBottom: "4px",
|
|
|
}}>
|
|
|
- <div style={{ fontWeight: "bold" }}>Mode-specific Custom Instructions</div>
|
|
|
+ <div style={{ fontWeight: "bold" }}>Mode-specific Custom Instructions (optional)</div>
|
|
|
{!findModeBySlug(mode, customModes) && (
|
|
|
<VSCodeButton
|
|
|
appearance="icon"
|
|
|
@@ -1069,6 +1115,9 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|
|
}}
|
|
|
style={{ width: "100%" }}
|
|
|
/>
|
|
|
+ {nameError && (
|
|
|
+ <div className="text-xs text-vscode-errorForeground mt-1">{nameError}</div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
<div style={{ marginBottom: "16px" }}>
|
|
|
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Slug</div>
|
|
|
@@ -1091,6 +1140,9 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|
|
The slug is used in URLs and file names. It should be lowercase and contain only
|
|
|
letters, numbers, and hyphens.
|
|
|
</div>
|
|
|
+ {slugError && (
|
|
|
+ <div className="text-xs text-vscode-errorForeground mt-1">{slugError}</div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
<div style={{ marginBottom: "16px" }}>
|
|
|
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Save Location</div>
|
|
|
@@ -1147,6 +1199,11 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|
|
resize="vertical"
|
|
|
style={{ width: "100%" }}
|
|
|
/>
|
|
|
+ {roleDefinitionError && (
|
|
|
+ <div className="text-xs text-vscode-errorForeground mt-1">
|
|
|
+ {roleDefinitionError}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
<div style={{ marginBottom: "16px" }}>
|
|
|
<div style={{ fontWeight: "bold", marginBottom: "4px" }}>Available Tools</div>
|
|
|
@@ -1184,9 +1241,14 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|
|
</VSCodeCheckbox>
|
|
|
))}
|
|
|
</div>
|
|
|
+ {groupsError && (
|
|
|
+ <div className="text-xs text-vscode-errorForeground mt-1">{groupsError}</div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
<div style={{ marginBottom: "16px" }}>
|
|
|
- <div style={{ fontWeight: "bold", marginBottom: "4px" }}>Custom Instructions</div>
|
|
|
+ <div style={{ fontWeight: "bold", marginBottom: "4px" }}>
|
|
|
+ Custom Instructions (optional)
|
|
|
+ </div>
|
|
|
<div
|
|
|
style={{
|
|
|
fontSize: "13px",
|
|
|
@@ -1219,10 +1281,7 @@ const PromptsView = ({ onDone }: PromptsViewProps) => {
|
|
|
backgroundColor: "var(--vscode-editor-background)",
|
|
|
}}>
|
|
|
<VSCodeButton onClick={() => setIsCreateModeDialogOpen(false)}>Cancel</VSCodeButton>
|
|
|
- <VSCodeButton
|
|
|
- appearance="primary"
|
|
|
- onClick={handleCreateMode}
|
|
|
- disabled={!newModeName.trim() || !newModeSlug.trim()}>
|
|
|
+ <VSCodeButton appearance="primary" onClick={handleCreateMode}>
|
|
|
Create Mode
|
|
|
</VSCodeButton>
|
|
|
</div>
|