/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { useEffect, useMemo, useState } from 'react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { Layers3, Loader2, Pencil, Plus, RefreshCcw, Trash2, X, } from 'lucide-react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { cn } from '@/lib/utils' import { useIsMobile } from '@/hooks/use-mobile' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { Dialog, DialogClose, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from '@/components/ui/empty' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { ConfirmDialog } from '@/components/confirm-dialog' import { StatusBadge } from '@/components/status-badge' import { deletePrefillGroup, getPrefillGroups } from '../../api' import { prefillGroupsQueryKeys } from '../../lib' import type { PrefillGroup } from '../../types' import { PREFILL_GROUP_TYPE_META, parseEndpointKeys, parseStringItems, } from '../prefill-group-shared' type PrefillGroupManagementDialogProps = { open: boolean onOpenChange: (open: boolean) => void onCreateGroup: () => void onEditGroup: (group: PrefillGroup) => void } export function PrefillGroupManagementDialog({ open, onOpenChange, onCreateGroup, onEditGroup, }: PrefillGroupManagementDialogProps) { const { t } = useTranslation() const queryClient = useQueryClient() const isMobile = useIsMobile() const [deleteState, setDeleteState] = useState<{ open: boolean group: PrefillGroup | null }>({ open: false, group: null }) const [isDeleting, setIsDeleting] = useState(false) const { data, isLoading, isFetching, error, refetch: refetchGroups, } = useQuery({ queryKey: prefillGroupsQueryKeys.list(), queryFn: () => getPrefillGroups(), enabled: open, }) const groups = useMemo(() => data?.data ?? [], [data?.data]) const sortedGroups = useMemo( () => [...groups].sort((a, b) => { if (a.type === b.type) { return a.name.localeCompare(b.name) } return a.type.localeCompare(b.type) }), [groups] ) const normalizedGroups = useMemo( () => sortedGroups.map((group) => { const meta = PREFILL_GROUP_TYPE_META[group.type] || { label: group.type, badge: 'neutral' as const, } const parsedItems = group.type === 'endpoint' ? parseEndpointKeys(group.items) : parseStringItems(group.items) return { group, meta, parsedItems } }), [sortedGroups] ) useEffect(() => { if (!open) { setDeleteState({ open: false, group: null }) setIsDeleting(false) } }, [open]) const handleDeleteClick = (group: PrefillGroup) => { setDeleteState({ open: true, group }) } const handleDeleteConfirm = async () => { if (!deleteState.group) return setIsDeleting(true) try { const response = await deletePrefillGroup(deleteState.group.id) if (response.success) { toast.success(`Deleted "${deleteState.group.name}"`) queryClient.invalidateQueries({ queryKey: prefillGroupsQueryKeys.lists(), }) setDeleteState({ open: false, group: null }) } else { toast.error(response.message || 'Failed to delete group') } } catch (err: unknown) { toast.error((err as Error)?.message || 'Failed to delete group') } finally { setIsDeleting(false) } } return ( <> {t('Prefill Group Management')} {t( 'Create reusable bundles of models, tags, endpoints, and user groups to speed up configuration elsewhere in the console.' )} } > {t('Close dialog')} {t('New Group')} refetchGroups()} disabled={isFetching} > {isFetching ? ( ) : ( )} {t('Refresh')} {error && ( {t('Unable to load groups')} {(error as Error).message || 'Please retry or refresh the page.'} )} {isLoading ? ( {t('Fetching prefill groups...')} ) : normalizedGroups.length === 0 ? ( {t('No prefill groups yet')} {t( 'Create your first group to reuse model, tag, or endpoint selections anywhere in the dashboard.' )} {t( 'Prefill groups help you keep complex configurations in sync.' )} ) : isMobile ? ( {normalizedGroups.map(({ group, meta, parsedItems }) => ( {group.name} {meta.label} ยท #{group.id} {group.description ? ( {group.description} ) : ( No description provided )} onEditGroup(group)} > Edit group handleDeleteClick(group)} > Delete group Items {parsedItems.length > 0 ? ( {parsedItems.slice(0, 6).map((item) => ( ))} {parsedItems.length > 6 && ( )} ) : ( {group.type === 'endpoint' ? 'No endpoint mappings configured.' : 'No items configured yet.'} )} ))} ) : ( {t('Group')} {t('Type')} {t('Items')} {t('Actions')} {normalizedGroups.map( ({ group, meta, parsedItems }) => ( {group.name} {group.description ? ( {group.description} ) : ( No description provided )} {parsedItems.length > 0 ? ( <> {parsedItems .slice(0, 6) .map((item) => ( ))} {parsedItems.length > 6 && ( )} > ) : ( {group.type === 'endpoint' ? 'No endpoint mappings configured.' : 'No items configured yet.'} )} {parsedItems.length} item {parsedItems.length === 1 ? '' : 's'} onEditGroup(group)} > Edit group handleDeleteClick(group)} > Delete group ) )} )} setDeleteState({ open: next, group: null })} title={t('Delete group')} desc={ {t('Are you sure you want to delete')}{' '} {deleteState.group?.name} {t('? This action cannot be undone.')} } destructive confirmText={isDeleting ? 'Deleting...' : 'Delete'} isLoading={isDeleting} handleConfirm={handleDeleteConfirm} /> > ) }
{t('Fetching prefill groups...')}
{group.type === 'endpoint' ? 'No endpoint mappings configured.' : 'No items configured yet.'}
{group.description}
No description provided
{t('Are you sure you want to delete')}{' '} {deleteState.group?.name} {t('? This action cannot be undone.')}