Add ability to delete samples from the ui

This commit is contained in:
Jaret Burkett
2025-09-29 04:49:32 -06:00
parent ebadb321e3
commit 2e9de5eb50
5 changed files with 81 additions and 10 deletions

View File

@@ -1,17 +1,24 @@
import { NextResponse } from 'next/server';
import fs from 'fs';
import { getDatasetsRoot } from '@/server/settings';
import { getDatasetsRoot, getTrainingFolder } from '@/server/settings';
export async function POST(request: Request) {
try {
const body = await request.json();
const { imgPath } = body;
let datasetsPath = await getDatasetsRoot();
const trainingPath = await getTrainingFolder();
// make sure the dataset path is in the image path
if (!imgPath.startsWith(datasetsPath)) {
if (!imgPath.startsWith(datasetsPath) && !imgPath.startsWith(trainingPath)) {
return NextResponse.json({ error: 'Invalid image path' }, { status: 400 });
}
// make sure it is an image
if (!/\.(jpg|jpeg|png|bmp|gif|tiff|webp)$/i.test(imgPath.toLowerCase())) {
return NextResponse.json({ error: 'Not an image' }, { status: 400 });
}
// if img doesnt exist, ignore
if (!fs.existsSync(imgPath)) {
return NextResponse.json({ success: true });

View File

@@ -1,5 +1,5 @@
import Link from 'next/link';
import { Eye, Trash2, Pen, Play, Pause, Cog, NotebookPen } from 'lucide-react';
import { Eye, Trash2, Pen, Play, Pause, Cog } from 'lucide-react';
import { Button } from '@headlessui/react';
import { openConfirm } from '@/components/ConfirmModal';
import { Job } from '@prisma/client';

View File

@@ -68,10 +68,6 @@ const SampleImageCard: React.FC<SampleImageCardProps> = ({
}
}, [isVisible, imageUrl]);
useEffect(() => {
console.log('SampleImageCard isVisible', isVisible, imageUrl);
}, [isVisible, imageUrl]);
const handleLoad = () => setLoaded(true);
return (

View File

@@ -3,6 +3,10 @@ import { useState, useEffect, useMemo, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react';
import { SampleConfig, SampleItem } from '@/types';
import { Cog } from 'lucide-react';
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react';
import { openConfirm } from './ConfirmModal';
import { apiClient } from '@/utils/api';
interface Props {
imgPath: string | null; // current image path
@@ -10,9 +14,17 @@ interface Props {
sampleImages: string[]; // all sample images
sampleConfig: SampleConfig | null;
onChange: (nextPath: string | null) => void; // parent setter
refreshSampleImages?: () => void;
}
export default function SampleImageViewer({ imgPath, numSamples, sampleImages, sampleConfig, onChange }: Props) {
export default function SampleImageViewer({
imgPath,
numSamples,
sampleImages,
sampleConfig,
onChange,
refreshSampleImages,
}: Props) {
const [mounted, setMounted] = useState(false);
const [isOpen, setIsOpen] = useState(Boolean(imgPath));
@@ -220,6 +232,48 @@ export default function SampleImageViewer({ imgPath, numSamples, sampleImages, s
</div>
</div>
</div>
<div className="absolute top-2 right-2 bg-gray-900 rounded-full p-1 leading-[0px] opacity-50 hover:opacity-100">
<Menu>
<MenuButton>
<Cog />
</MenuButton>
<MenuItems
anchor="bottom end"
className="bg-gray-900 border border-gray-700 rounded shadow-lg w-48 px-4 py-2 mt-1 z-50"
>
<MenuItem>
<div
className="cursor-pointer"
onClick={() => {
let message = `Are you sure you want to delete this sample? This action cannot be undone.`;
openConfirm({
title: 'Delete Sample',
message: message,
type: 'warning',
confirmText: 'Delete',
onConfirm: () => {
apiClient
.post('/api/img/delete', { imgPath: imgPath })
.then(() => {
console.log('Image deleted:', imgPath);
onChange(null);
if (refreshSampleImages) {
refreshSampleImages();
}
})
.catch(error => {
console.error('Error deleting image:', error);
});
},
});
}}
>
Delete Sample
</div>
</MenuItem>
</MenuItems>
</Menu>
</div>
</DialogPanel>
</div>
</div>

View File

@@ -8,7 +8,7 @@ import { Button } from '@headlessui/react';
import { FaDownload } from 'react-icons/fa';
import { apiClient } from '@/utils/api';
import classNames from 'classnames';
import { FaCaretDown } from 'react-icons/fa';
import { FaCaretDown, FaCaretUp } from 'react-icons/fa';
import SampleImageViewer from './SampleImageViewer';
interface SampleImagesMenuProps {
@@ -88,6 +88,12 @@ export default function SampleImages({ job }: SampleImagesProps) {
}
};
const scrollToTop = () => {
if (containerRef.current) {
containerRef.current.scrollTo({ top: 0, behavior: 'instant' });
}
};
const PageInfoContent = useMemo(() => {
let icon = null;
let text = '';
@@ -276,9 +282,17 @@ export default function SampleImages({ job }: SampleImagesProps) {
sampleImages={sampleImages}
onChange={setPath => setSelectedSamplePath(setPath)}
sampleConfig={sampleConfig}
refreshSampleImages={refreshSampleImages}
/>
<div
className="fixed bottom-5 right-5 w-10 h-10 rounded-full bg-gray-900 shadow-lg flex items-center justify-center text-white opacity-80 hover:opacity-100 cursor-pointer"
className="fixed top-20 mt-4 right-6 w-10 h-10 rounded-full bg-gray-900 shadow-lg flex items-center justify-center text-white opacity-80 hover:opacity-100 cursor-pointer"
onClick={scrollToTop}
title="Scroll to Top"
>
<FaCaretUp className="text-gray-500 dark:text-gray-400" />
</div>
<div
className="fixed bottom-5 right-6 w-10 h-10 rounded-full bg-gray-900 shadow-lg flex items-center justify-center text-white opacity-80 hover:opacity-100 cursor-pointer"
onClick={scrollToBottom}
title="Scroll to Bottom"
>