feat(vscode): Delete Agent Group (#109)
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
|||||||
RiSearchLine,
|
RiSearchLine,
|
||||||
RiGitBranchLine,
|
RiGitBranchLine,
|
||||||
} from '@remixicon/react';
|
} from '@remixicon/react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||||
@@ -15,6 +16,14 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useAgentGroupsStore, type AgentGroup } from '@/stores/useAgentGroupsStore';
|
import { useAgentGroupsStore, type AgentGroup } from '@/stores/useAgentGroupsStore';
|
||||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||||
@@ -41,6 +50,34 @@ interface AgentGroupItemProps {
|
|||||||
|
|
||||||
const AgentGroupItem: React.FC<AgentGroupItemProps> = ({ group, isSelected, onSelect }) => {
|
const AgentGroupItem: React.FC<AgentGroupItemProps> = ({ group, isSelected, onSelect }) => {
|
||||||
const [menuOpen, setMenuOpen] = React.useState(false);
|
const [menuOpen, setMenuOpen] = React.useState(false);
|
||||||
|
const [showDeleteConfirm, setShowDeleteConfirm] = React.useState(false);
|
||||||
|
const [isDeleting, setIsDeleting] = React.useState(false);
|
||||||
|
const deleteGroup = useAgentGroupsStore((state) => state.deleteGroup);
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
setIsDeleting(true);
|
||||||
|
try {
|
||||||
|
const { success, deletedCount, failedCount } = await deleteGroup(group.name);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
toast.success(`Deleted agent group "${group.name}"`, {
|
||||||
|
description: `${deletedCount} session${deletedCount !== 1 ? 's' : ''} removed with worktrees archived.`,
|
||||||
|
});
|
||||||
|
} else if (deletedCount > 0) {
|
||||||
|
toast.warning(`Partially deleted agent group "${group.name}"`, {
|
||||||
|
description: `${deletedCount} deleted, ${failedCount} failed.`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast.error(`Failed to delete agent group "${group.name}"`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(`Failed to delete agent group "${group.name}"`);
|
||||||
|
console.error('Delete group error:', error);
|
||||||
|
} finally {
|
||||||
|
setIsDeleting(false);
|
||||||
|
setShowDeleteConfirm(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -86,13 +123,47 @@ const AgentGroupItem: React.FC<AgentGroupItemProps> = ({ group, isSelected, onSe
|
|||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="min-w-[140px]">
|
<DropdownMenuContent align="end" className="min-w-[140px]">
|
||||||
<DropdownMenuItem className="text-destructive focus:text-destructive">
|
<DropdownMenuItem
|
||||||
|
className="text-destructive focus:text-destructive"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setMenuOpen(false);
|
||||||
|
setShowDeleteConfirm(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Dialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
|
||||||
|
<DialogContent showCloseButton={!isDeleting}>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Delete Agent Group</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Are you sure you want to delete "{group.name}"? This will remove {group.sessionCount} session{group.sessionCount !== 1 ? 's' : ''} and archive their worktrees. This action cannot be undone.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowDeleteConfirm(false)}
|
||||||
|
disabled={isDeleting}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={isDeleting}
|
||||||
|
>
|
||||||
|
{isDeleting ? 'Deleting...' : 'Delete'}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { create } from 'zustand';
|
|||||||
import { devtools } from 'zustand/middleware';
|
import { devtools } from 'zustand/middleware';
|
||||||
import { opencodeClient } from '@/lib/opencode/client';
|
import { opencodeClient } from '@/lib/opencode/client';
|
||||||
import { useDirectoryStore } from './useDirectoryStore';
|
import { useDirectoryStore } from './useDirectoryStore';
|
||||||
|
import { useSessionStore } from './useSessionStore';
|
||||||
import type { WorktreeMetadata } from '@/types/worktree';
|
import type { WorktreeMetadata } from '@/types/worktree';
|
||||||
import { listWorktrees, mapWorktreeToMetadata } from '@/lib/git/worktreeService';
|
import { listWorktrees, mapWorktreeToMetadata } from '@/lib/git/worktreeService';
|
||||||
import type { Session } from '@opencode-ai/sdk/v2';
|
import type { Session } from '@opencode-ai/sdk/v2';
|
||||||
@@ -72,6 +73,8 @@ interface AgentGroupsActions {
|
|||||||
getSelectedGroup: () => AgentGroup | null;
|
getSelectedGroup: () => AgentGroup | null;
|
||||||
/** Get the currently selected session */
|
/** Get the currently selected session */
|
||||||
getSelectedSession: () => AgentGroupSession | null;
|
getSelectedSession: () => AgentGroupSession | null;
|
||||||
|
/** Delete a group and all its sessions, archiving worktrees */
|
||||||
|
deleteGroup: (groupName: string) => Promise<{ success: boolean; deletedCount: number; failedCount: number }>;
|
||||||
/** Clear error */
|
/** Clear error */
|
||||||
clearError: () => void;
|
clearError: () => void;
|
||||||
}
|
}
|
||||||
@@ -318,6 +321,47 @@ export const useAgentGroupsStore = create<AgentGroupsStore>()(
|
|||||||
clearError: () => {
|
clearError: () => {
|
||||||
set({ error: null });
|
set({ error: null });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
deleteGroup: async (groupName: string) => {
|
||||||
|
const { groups, selectedGroupName } = get();
|
||||||
|
const group = groups.find((g) => g.name === groupName);
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
return { success: false, deletedCount: 0, failedCount: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all session IDs from the group
|
||||||
|
const sessionIds = group.sessions.map((s) => s.id);
|
||||||
|
|
||||||
|
if (sessionIds.length === 0) {
|
||||||
|
return { success: true, deletedCount: 0, failedCount: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete sessions using sessionStore.deleteSessions
|
||||||
|
// archiveWorktree: true - removes the git worktree
|
||||||
|
// deleteRemoteBranch: false - does not delete remote branch
|
||||||
|
const { deletedIds, failedIds } = await useSessionStore.getState().deleteSessions(
|
||||||
|
sessionIds,
|
||||||
|
{
|
||||||
|
archiveWorktree: true,
|
||||||
|
deleteRemoteBranch: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the deleted group was selected, clear selection
|
||||||
|
if (selectedGroupName === groupName) {
|
||||||
|
set({ selectedGroupName: null, selectedSessionId: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload groups to reflect changes
|
||||||
|
await get().loadGroups();
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: failedIds.length === 0,
|
||||||
|
deletedCount: deletedIds.length,
|
||||||
|
failedCount: failedIds.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
{ name: 'agent-groups-store' }
|
{ name: 'agent-groups-store' }
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user