feat: enhance project-specific slash commands management
- Add scopeFilter prop to SlashCommandsManager for filtering by scope - Replace browser confirm() with proper delete confirmation dialog - Fix slash_command_delete to handle project commands with project_path param - Add Slash Commands tab to ProjectSettings as the default tab - Add Commands button to ClaudeCodeSession for quick access - Improve error handling and user feedback for delete operations - Better UI text when showing project-specific commands only
This commit is contained in:
@@ -416,11 +416,25 @@ pub async fn slash_command_save(
|
|||||||
|
|
||||||
/// Delete a slash command
|
/// Delete a slash command
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn slash_command_delete(command_id: String) -> Result<String, String> {
|
pub async fn slash_command_delete(command_id: String, project_path: Option<String>) -> Result<String, String> {
|
||||||
info!("Deleting slash command: {}", command_id);
|
info!("Deleting slash command: {}", command_id);
|
||||||
|
|
||||||
// Get the command to find its file path
|
// First, we need to determine if this is a project command by parsing the ID
|
||||||
let command = slash_command_get(command_id.clone()).await?;
|
let is_project_command = command_id.starts_with("project-");
|
||||||
|
|
||||||
|
// If it's a project command and we don't have a project path, error out
|
||||||
|
if is_project_command && project_path.is_none() {
|
||||||
|
return Err("Project path required to delete project commands".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all commands (including project commands if applicable)
|
||||||
|
let commands = slash_commands_list(project_path).await?;
|
||||||
|
|
||||||
|
// Find the command by ID
|
||||||
|
let command = commands
|
||||||
|
.into_iter()
|
||||||
|
.find(|cmd| cmd.id == command_id)
|
||||||
|
.ok_or_else(|| format!("Command not found: {}", command_id))?;
|
||||||
|
|
||||||
// Delete the file
|
// Delete the file
|
||||||
fs::remove_file(&command.file_path)
|
fs::remove_file(&command.file_path)
|
||||||
|
@@ -10,7 +10,8 @@ import {
|
|||||||
Settings,
|
Settings,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
X,
|
X,
|
||||||
Hash
|
Hash,
|
||||||
|
Command
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -25,6 +26,7 @@ import { FloatingPromptInput, type FloatingPromptInputRef } from "./FloatingProm
|
|||||||
import { ErrorBoundary } from "./ErrorBoundary";
|
import { ErrorBoundary } from "./ErrorBoundary";
|
||||||
import { TimelineNavigator } from "./TimelineNavigator";
|
import { TimelineNavigator } from "./TimelineNavigator";
|
||||||
import { CheckpointSettings } from "./CheckpointSettings";
|
import { CheckpointSettings } from "./CheckpointSettings";
|
||||||
|
import { SlashCommandsManager } from "./SlashCommandsManager";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
|
||||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { SplitPane } from "@/components/ui/split-pane";
|
import { SplitPane } from "@/components/ui/split-pane";
|
||||||
@@ -87,6 +89,7 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
const [timelineVersion, setTimelineVersion] = useState(0);
|
const [timelineVersion, setTimelineVersion] = useState(0);
|
||||||
const [showSettings, setShowSettings] = useState(false);
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
const [showForkDialog, setShowForkDialog] = useState(false);
|
const [showForkDialog, setShowForkDialog] = useState(false);
|
||||||
|
const [showSlashCommandsSettings, setShowSlashCommandsSettings] = useState(false);
|
||||||
const [forkCheckpointId, setForkCheckpointId] = useState<string | null>(null);
|
const [forkCheckpointId, setForkCheckpointId] = useState<string | null>(null);
|
||||||
const [forkSessionName, setForkSessionName] = useState("");
|
const [forkSessionName, setForkSessionName] = useState("");
|
||||||
|
|
||||||
@@ -995,6 +998,17 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
Hooks
|
Hooks
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{projectPath && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setShowSlashCommandsSettings(true)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<Command className="h-4 w-4 mr-2" />
|
||||||
|
Commands
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{showSettings && (
|
{showSettings && (
|
||||||
<CheckpointSettings
|
<CheckpointSettings
|
||||||
@@ -1386,6 +1400,23 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Slash Commands Settings Dialog */}
|
||||||
|
{showSlashCommandsSettings && (
|
||||||
|
<Dialog open={showSlashCommandsSettings} onOpenChange={setShowSlashCommandsSettings}>
|
||||||
|
<DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Slash Commands</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Manage project-specific slash commands for {projectPath}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex-1 overflow-y-auto">
|
||||||
|
<SlashCommandsManager projectPath={projectPath} />
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { HooksEditor } from '@/components/HooksEditor';
|
import { HooksEditor } from '@/components/HooksEditor';
|
||||||
|
import { SlashCommandsManager } from '@/components/SlashCommandsManager';
|
||||||
import { api } from '@/lib/api';
|
import { api } from '@/lib/api';
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
@@ -11,7 +12,8 @@ import {
|
|||||||
Settings,
|
Settings,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Shield
|
Shield,
|
||||||
|
Command
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
@@ -31,7 +33,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
onBack,
|
onBack,
|
||||||
className
|
className
|
||||||
}) => {
|
}) => {
|
||||||
const [activeTab, setActiveTab] = useState('project');
|
const [activeTab, setActiveTab] = useState('commands');
|
||||||
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null);
|
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null);
|
||||||
|
|
||||||
// Other hooks settings
|
// Other hooks settings
|
||||||
@@ -88,7 +90,7 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Settings className="h-5 w-5 text-muted-foreground" />
|
<Settings className="h-5 w-5 text-muted-foreground" />
|
||||||
<h2 className="text-xl font-semibold">Hooks</h2>
|
<h2 className="text-xl font-semibold">Project Settings</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -106,6 +108,10 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
<TabsList className="mb-6">
|
<TabsList className="mb-6">
|
||||||
|
<TabsTrigger value="commands" className="gap-2">
|
||||||
|
<Command className="h-4 w-4" />
|
||||||
|
Slash Commands
|
||||||
|
</TabsTrigger>
|
||||||
<TabsTrigger value="project" className="gap-2">
|
<TabsTrigger value="project" className="gap-2">
|
||||||
<GitBranch className="h-4 w-4" />
|
<GitBranch className="h-4 w-4" />
|
||||||
Project Hooks
|
Project Hooks
|
||||||
@@ -116,6 +122,26 @@ export const ProjectSettings: React.FC<ProjectSettingsProps> = ({
|
|||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="commands" className="space-y-6">
|
||||||
|
<Card className="p-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-2">Project Slash Commands</h3>
|
||||||
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
|
Custom commands that are specific to this project. These commands are stored in
|
||||||
|
<code className="mx-1 px-2 py-1 bg-muted rounded text-xs">.claude/slash-commands/</code>
|
||||||
|
and can be committed to version control.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SlashCommandsManager
|
||||||
|
projectPath={project.path}
|
||||||
|
scopeFilter="project"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="project" className="space-y-6">
|
<TabsContent value="project" className="space-y-6">
|
||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
@@ -33,6 +33,7 @@ import { COMMON_TOOL_MATCHERS } from "@/types/hooks";
|
|||||||
interface SlashCommandsManagerProps {
|
interface SlashCommandsManagerProps {
|
||||||
projectPath?: string;
|
projectPath?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
scopeFilter?: 'project' | 'user' | 'all';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CommandForm {
|
interface CommandForm {
|
||||||
@@ -88,13 +89,14 @@ const getCommandIcon = (command: SlashCommand) => {
|
|||||||
export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
||||||
projectPath,
|
projectPath,
|
||||||
className,
|
className,
|
||||||
|
scopeFilter = 'all',
|
||||||
}) => {
|
}) => {
|
||||||
const [commands, setCommands] = useState<SlashCommand[]>([]);
|
const [commands, setCommands] = useState<SlashCommand[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [selectedScope, setSelectedScope] = useState<'all' | 'project' | 'user'>('all');
|
const [selectedScope, setSelectedScope] = useState<'all' | 'project' | 'user'>(scopeFilter === 'all' ? 'all' : scopeFilter as 'project' | 'user');
|
||||||
const [expandedCommands, setExpandedCommands] = useState<Set<string>>(new Set());
|
const [expandedCommands, setExpandedCommands] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
// Edit dialog state
|
// Edit dialog state
|
||||||
@@ -109,6 +111,11 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
scope: 'user'
|
scope: 'user'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Delete confirmation dialog state
|
||||||
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
|
const [commandToDelete, setCommandToDelete] = useState<SlashCommand | null>(null);
|
||||||
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
|
||||||
// Load commands on mount
|
// Load commands on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadCommands();
|
loadCommands();
|
||||||
@@ -136,7 +143,7 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
content: "",
|
content: "",
|
||||||
description: "",
|
description: "",
|
||||||
allowedTools: [],
|
allowedTools: [],
|
||||||
scope: projectPath ? 'project' : 'user'
|
scope: scopeFilter !== 'all' ? scopeFilter : (projectPath ? 'project' : 'user')
|
||||||
});
|
});
|
||||||
setEditDialogOpen(true);
|
setEditDialogOpen(true);
|
||||||
};
|
};
|
||||||
@@ -179,20 +186,35 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (command: SlashCommand) => {
|
const handleDeleteClick = (command: SlashCommand) => {
|
||||||
if (!confirm(`Delete command "${command.full_command}"?`)) {
|
setCommandToDelete(command);
|
||||||
return;
|
setDeleteDialogOpen(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (!commandToDelete) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.slashCommandDelete(command.id);
|
setDeleting(true);
|
||||||
|
setError(null);
|
||||||
|
await api.slashCommandDelete(commandToDelete.id, projectPath);
|
||||||
|
setDeleteDialogOpen(false);
|
||||||
|
setCommandToDelete(null);
|
||||||
await loadCommands();
|
await loadCommands();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to delete command:", err);
|
console.error("Failed to delete command:", err);
|
||||||
setError("Failed to delete command");
|
const errorMessage = err instanceof Error ? err.message : "Failed to delete command";
|
||||||
|
setError(errorMessage);
|
||||||
|
} finally {
|
||||||
|
setDeleting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cancelDelete = () => {
|
||||||
|
setDeleteDialogOpen(false);
|
||||||
|
setCommandToDelete(null);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleExpanded = (commandId: string) => {
|
const toggleExpanded = (commandId: string) => {
|
||||||
setExpandedCommands(prev => {
|
setExpandedCommands(prev => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
@@ -226,6 +248,16 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
|
|
||||||
// Filter commands
|
// Filter commands
|
||||||
const filteredCommands = commands.filter(cmd => {
|
const filteredCommands = commands.filter(cmd => {
|
||||||
|
// Hide default commands
|
||||||
|
if (cmd.scope === 'default') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply scopeFilter if set to specific scope
|
||||||
|
if (scopeFilter !== 'all' && cmd.scope !== scopeFilter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Scope filter
|
// Scope filter
|
||||||
if (selectedScope !== 'all' && cmd.scope !== selectedScope) {
|
if (selectedScope !== 'all' && cmd.scope !== selectedScope) {
|
||||||
return false;
|
return false;
|
||||||
@@ -262,9 +294,13 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold">Slash Commands</h3>
|
<h3 className="text-lg font-semibold">
|
||||||
|
{scopeFilter === 'project' ? 'Project Slash Commands' : 'Slash Commands'}
|
||||||
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
Create custom commands to streamline your workflow
|
{scopeFilter === 'project'
|
||||||
|
? 'Create custom commands for this project'
|
||||||
|
: 'Create custom commands to streamline your workflow'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={handleCreateNew} size="sm" className="gap-2">
|
<Button onClick={handleCreateNew} size="sm" className="gap-2">
|
||||||
@@ -286,6 +322,7 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{scopeFilter === 'all' && (
|
||||||
<Select value={selectedScope} onValueChange={(value: any) => setSelectedScope(value)}>
|
<Select value={selectedScope} onValueChange={(value: any) => setSelectedScope(value)}>
|
||||||
<SelectTrigger className="w-[150px]">
|
<SelectTrigger className="w-[150px]">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
@@ -296,6 +333,7 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
<SelectItem value="user">User</SelectItem>
|
<SelectItem value="user">User</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Error Message */}
|
{/* Error Message */}
|
||||||
@@ -316,11 +354,17 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Command className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
|
<Command className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{searchQuery ? "No commands found" : "No commands created yet"}
|
{searchQuery
|
||||||
|
? "No commands found"
|
||||||
|
: scopeFilter === 'project'
|
||||||
|
? "No project commands created yet"
|
||||||
|
: "No commands created yet"}
|
||||||
</p>
|
</p>
|
||||||
{!searchQuery && (
|
{!searchQuery && (
|
||||||
<Button onClick={handleCreateNew} variant="outline" size="sm" className="mt-4">
|
<Button onClick={handleCreateNew} variant="outline" size="sm" className="mt-4">
|
||||||
Create your first command
|
{scopeFilter === 'project'
|
||||||
|
? "Create your first project command"
|
||||||
|
: "Create your first command"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -414,7 +458,7 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => handleDelete(command)}
|
onClick={() => handleDeleteClick(command)}
|
||||||
className="h-8 w-8 text-destructive hover:text-destructive"
|
className="h-8 w-8 text-destructive hover:text-destructive"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
@@ -465,24 +509,28 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
<Select
|
<Select
|
||||||
value={commandForm.scope}
|
value={commandForm.scope}
|
||||||
onValueChange={(value: 'project' | 'user') => setCommandForm(prev => ({ ...prev, scope: value }))}
|
onValueChange={(value: 'project' | 'user') => setCommandForm(prev => ({ ...prev, scope: value }))}
|
||||||
disabled={!projectPath && commandForm.scope === 'project'}
|
disabled={scopeFilter !== 'all' || (!projectPath && commandForm.scope === 'project')}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
{(scopeFilter === 'all' || scopeFilter === 'user') && (
|
||||||
<SelectItem value="user">
|
<SelectItem value="user">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Globe className="h-4 w-4" />
|
<Globe className="h-4 w-4" />
|
||||||
User (Global)
|
User (Global)
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
)}
|
||||||
|
{(scopeFilter === 'all' || scopeFilter === 'project') && (
|
||||||
<SelectItem value="project" disabled={!projectPath}>
|
<SelectItem value="project" disabled={!projectPath}>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FolderOpen className="h-4 w-4" />
|
<FolderOpen className="h-4 w-4" />
|
||||||
Project
|
Project
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
@@ -619,6 +667,53 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Delete Confirmation Dialog */}
|
||||||
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
|
<DialogContent className="max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Delete Command</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4 py-4">
|
||||||
|
<p>Are you sure you want to delete this command?</p>
|
||||||
|
{commandToDelete && (
|
||||||
|
<div className="p-3 bg-muted rounded-md">
|
||||||
|
<code className="text-sm font-mono">{commandToDelete.full_command}</code>
|
||||||
|
{commandToDelete.description && (
|
||||||
|
<p className="text-sm text-muted-foreground mt-1">{commandToDelete.description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
This action cannot be undone. The command file will be permanently deleted.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={cancelDelete} disabled={deleting}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={confirmDelete}
|
||||||
|
disabled={deleting}
|
||||||
|
>
|
||||||
|
{deleting ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||||
|
Deleting...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Trash2 className="h-4 w-4 mr-2" />
|
||||||
|
Delete
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
@@ -1825,11 +1825,12 @@ export const api = {
|
|||||||
/**
|
/**
|
||||||
* Deletes a slash command
|
* Deletes a slash command
|
||||||
* @param commandId - Unique identifier of the command to delete
|
* @param commandId - Unique identifier of the command to delete
|
||||||
|
* @param projectPath - Optional project path for deleting project commands
|
||||||
* @returns Promise resolving to deletion message
|
* @returns Promise resolving to deletion message
|
||||||
*/
|
*/
|
||||||
async slashCommandDelete(commandId: string): Promise<string> {
|
async slashCommandDelete(commandId: string, projectPath?: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
return await invoke<string>("slash_command_delete", { commandId });
|
return await invoke<string>("slash_command_delete", { commandId, projectPath });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete slash command:", error);
|
console.error("Failed to delete slash command:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
Reference in New Issue
Block a user