mirror of
https://github.com/ostris/ai-toolkit.git
synced 2026-01-26 16:39:47 +00:00
Add ability to delete samples from the ui
This commit is contained in:
@@ -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 });
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user