diff --git a/web/packages/studio/src/components/FilesetActionMenu/index.tsx b/web/packages/studio/src/components/FilesetActionMenu/index.tsx new file mode 100644 index 0000000000..b82b60bea9 --- /dev/null +++ b/web/packages/studio/src/components/FilesetActionMenu/index.tsx @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { getEntityReference } from '@nemo/common/src/namedEntity'; +import { useFilesDeleteFileset } from '@nemo/sdk/generated/platform/api'; +import type { FilesetOutput } from '@nemo/sdk/generated/platform/schema'; +import { + Button, + DropdownContent, + DropdownItem, + DropdownRoot, + DropdownTrigger, +} from '@nvidia/foundations-react-core'; +import { invalidateDatasetCaches } from '@studio/api/datasets/invalidateDatasetCaches'; +import { DatasetCreateModal } from '@studio/components/DatasetCreateModal'; +import { DatasetCreateModalMode } from '@studio/components/DatasetCreateModal/constants'; +import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal'; +import { EllipsisVertical } from 'lucide-react'; +import { type FC, useState } from 'react'; + +export interface FilesetActionMenuProps { + fileset: FilesetOutput; + onNavigateToDetails?: (fileset: FilesetOutput) => void; + onFilesetUpdated?: (fileset: FilesetOutput) => void; + onFilesetDeleted?: (fileset: FilesetOutput) => void; +} + +export const FilesetActionMenu: FC = ({ + fileset, + onNavigateToDetails, + onFilesetUpdated, + onFilesetDeleted, +}) => { + const [modalOpen, setModalOpen] = useState<'edit' | 'delete' | undefined>(undefined); + const { mutateAsync: deleteFileset } = useFilesDeleteFileset({ + mutation: { + onSuccess: (_data, variables) => { + invalidateDatasetCaches(variables.workspace, variables.name, ['list']); + }, + }, + }); + + const handleDeleteFileset = async (): Promise => { + try { + if (!fileset?.workspace || !fileset?.name) { + throw new Error('Fileset workspace or name is undefined'); + } + await deleteFileset({ workspace: fileset.workspace, name: fileset.name }); + onFilesetDeleted?.(fileset); + return true; + } catch (error) { + console.error('Failed to delete fileset:', error); + return false; + } + }; + + const handleModalClose = () => { + setModalOpen(undefined); + }; + + const handleFilesetUpdated = (updatedFileset: FilesetOutput) => { + onFilesetUpdated?.(updatedFileset); + handleModalClose(); + }; + + return ( + <> + + + + + + {onNavigateToDetails && ( + onNavigateToDetails(fileset)}>View + )} + setModalOpen('edit')}>Edit + setModalOpen('delete')} danger> + Delete + + + + + {modalOpen === 'edit' && ( + + )} + + {modalOpen === 'delete' && ( + + )} + + ); +}; diff --git a/web/packages/studio/src/routes/FilesetDetailRoute/FilesetCard/index.tsx b/web/packages/studio/src/routes/FilesetDetailRoute/FilesetCard/index.tsx index c147267a8c..e9e689f983 100644 --- a/web/packages/studio/src/routes/FilesetDetailRoute/FilesetCard/index.tsx +++ b/web/packages/studio/src/routes/FilesetDetailRoute/FilesetCard/index.tsx @@ -6,7 +6,7 @@ import { type FilesetFileOutput, type FilesetOutput, } from '@nemo/sdk/generated/platform/schema'; -import { Stack, Text } from '@nvidia/foundations-react-core'; +import { Stack } from '@nvidia/foundations-react-core'; import { useDatasetFileContent } from '@studio/api/datasets/useDatasetFileContent'; import { DatasetSamplePanel } from '@studio/routes/FilesetDetailRoute/FilesetCard/DatasetSamplePanel'; import { ReadmeBody } from '@studio/routes/FilesetDetailRoute/FilesetCard/ReadmeBody'; @@ -62,11 +62,6 @@ export const FilesetCard: FC = ({ >
- {fileset.description && ( - - {fileset.description} - - )} { const workspace = useWorkspaceFromPath(); + const navigate = useNavigate(); const { filesetName } = useRequiredPathParams([ROUTE_PARAMS.filesetName]); const filesetId = getEntityReference({ namespace: workspace, name: filesetName }); @@ -105,17 +108,36 @@ export const FilesetDetailRoute: FC = () => { // The source line (HF/NGC origin) is meaningful for model/generic filesets; // datasets don't surface it. const source = isDataset ? undefined : getModelSource(fileset); - const description = source ? ( - - {creatorToIcon(source.creatorSlug, { className: 'w-4 h-4 flex-shrink-0' })} - {source.path} - + const descriptionText = {fileset.description}; + const description = isDataset ? ( + descriptionText + ) : source ? ( + + + {creatorToIcon(source.creatorSlug, { className: 'w-4 h-4 flex-shrink-0' })} + {source.path} + + {descriptionText} + ) : undefined; return ( - + + + {filesetName} + navigate(getWorkspaceFilesetsRoute(workspace))} + /> + + + } + slotDescription={description} + /> {cardLabel}