Implement viewing of images

This commit is contained in:
Nicolas Mowen 2025-06-04 05:47:20 -06:00
parent d014922a99
commit 3420de88be
3 changed files with 74 additions and 54 deletions

View File

@ -435,7 +435,7 @@ def transcribe_audio(request: Request, body: AudioTranscriptionBody):
def get_classification_dataset(name: str): def get_classification_dataset(name: str):
dataset_dict: dict[str, list[str]] = {} dataset_dict: dict[str, list[str]] = {}
dataset_dir = os.path.join(MODEL_CACHE_DIR, f"{sanitize_filename(name)}/dataset") dataset_dir = os.path.join(CLIPS_DIR, sanitize_filename(name), "dataset")
if not os.path.exists(dataset_dir): if not os.path.exists(dataset_dir):
return JSONResponse(status_code=200, content={}) return JSONResponse(status_code=200, content={})
@ -459,7 +459,7 @@ def get_classification_dataset(name: str):
@router.get("/classification/{name}/train") @router.get("/classification/{name}/train")
def get_classification_images(name: str): def get_classification_images(name: str):
train_dir = os.path.join(CLIPS_DIR, sanitize_filename(name)) train_dir = os.path.join(CLIPS_DIR, sanitize_filename(name), "train")
if not os.path.exists(train_dir): if not os.path.exists(train_dir):
return JSONResponse(status_code=200, content=[]) return JSONResponse(status_code=200, content=[])
@ -492,9 +492,7 @@ async def train_configured_model(
status_code=404, status_code=404,
) )
background_tasks.add_task( background_tasks.add_task(train_classification_model, name)
train_classification_model, os.path.join(MODEL_CACHE_DIR, name)
)
return JSONResponse( return JSONResponse(
content={"success": True, "message": "Started classification model training."}, content={"success": True, "message": "Started classification model training."},
status_code=200, status_code=200,

View File

@ -38,12 +38,10 @@ export default function ModelSelectionView({
<div className="flex size-full gap-2 p-2"> <div className="flex size-full gap-2 p-2">
{classificationConfigs.map((config) => ( {classificationConfigs.map((config) => (
<div <div
key={config.name}
className={cn( className={cn(
"flex h-52 cursor-pointer flex-col gap-2 rounded-lg bg-card p-2 outline outline-[3px]", "flex h-52 cursor-pointer flex-col gap-2 rounded-lg bg-card p-2 outline outline-[3px]",
isMobile && "w-full", isMobile && "w-full",
false
? "shadow-selected outline-selected"
: "outline-transparent duration-500",
)} )}
onClick={() => onClick(config)} onClick={() => onClick(config)}
onContextMenu={() => { onContextMenu={() => {

View File

@ -40,8 +40,12 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
// dataset // dataset
const { data: trainImages } = useSWR(`classification/${model.name}/train`); const { data: trainImages } = useSWR<string[]>(
const { data: dataset } = useSWR(`classification/${model.name}/dataset`); `classification/${model.name}/train`,
);
const { data: dataset } = useSWR<{ [id: string]: string[] }>(
`classification/${model.name}/dataset`,
);
// actions // actions
@ -54,8 +58,8 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
<div className="flex flex-row justify-between gap-2 align-middle"> <div className="flex flex-row justify-between gap-2 align-middle">
<LibrarySelector <LibrarySelector
pageToggle={pageToggle} pageToggle={pageToggle}
dataset={dataset} dataset={dataset || {}}
trainImages={trainImages} trainImages={trainImages || []}
setPageToggle={setPageToggle} setPageToggle={setPageToggle}
onDelete={() => {}} onDelete={() => {}}
onRename={() => {}} onRename={() => {}}
@ -65,13 +69,18 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
{pageToggle == "train" ? ( {pageToggle == "train" ? (
<TrainGrid <TrainGrid
model={model} model={model}
trainImages={trainImages} trainImages={trainImages || []}
selected={false} selected={false}
onClickImages={() => {}} onClickImages={() => {}}
onDelete={() => {}} onDelete={() => {}}
/> />
) : ( ) : (
<DatasetGrid /> <DatasetGrid
modelName={model.name}
categoryName={pageToggle}
images={dataset?.[pageToggle] || []}
onDelete={() => {}}
/>
)} )}
</div> </div>
); );
@ -259,51 +268,65 @@ function LibrarySelector({
} }
type DatasetGridProps = { type DatasetGridProps = {
name: string; modelName: string;
categoryName: string;
images: string[]; images: string[];
onDelete: (name: string, ids: string[]) => void; onDelete: (modelName: string, categoryName: string, ids: string[]) => void;
}; };
function DatasetGrid({ name, images, onDelete }: DatasetGridProps) { function DatasetGrid({
modelName,
categoryName,
images,
onDelete,
}: DatasetGridProps) {
const { t } = useTranslation(["views/classificationModel"]); const { t } = useTranslation(["views/classificationModel"]);
return ( return (
<div <div className="grid grid-cols-10 gap-2 overflow-y-auto">
className={cn( {images.map((image) => (
"flex cursor-pointer flex-col gap-2 rounded-lg bg-card outline outline-[3px]", <div
)} className={cn(
> "flex h-60 cursor-pointer flex-col gap-2 rounded-lg bg-card outline outline-[3px]",
<div "outline-transparent duration-500",
className={cn( )}
"w-full overflow-hidden p-2 *:text-card-foreground", onClick={() => {
isMobile && "flex justify-center", //e.stopPropagation();
)} //onClickImages([data.raw], e.ctrlKey || e.metaKey);
> }}
<img >
className="h-40 rounded-lg" <div
src={`${baseUrl}clips/faces/${name}/${images}`} className={cn(
/> "w-full overflow-hidden p-2 *:text-card-foreground",
</div> isMobile && "flex justify-center",
<div className="rounded-b-lg bg-card p-3"> )}
<div className="flex w-full flex-row items-center justify-between gap-2"> >
<div className="flex flex-col items-start text-xs text-primary-variant"> <img
<div className="smart-capitalize">{name}</div> className="rounded-lg"
src={`${baseUrl}clips/${modelName}/dataset/${categoryName}/${image}`}
/>
</div> </div>
<div className="flex flex-row items-start justify-end gap-5 md:gap-4"> <div className="rounded-b-lg bg-card p-3">
<Tooltip> <div className="flex w-full flex-row items-center justify-between gap-2">
<TooltipTrigger> <div className="flex w-full flex-row items-start justify-end gap-5 md:gap-4">
<LuTrash2 <Tooltip>
className="size-5 cursor-pointer text-primary-variant hover:text-primary" <TooltipTrigger>
onClick={(e) => { <LuTrash2
e.stopPropagation(); className="size-5 cursor-pointer text-primary-variant hover:text-primary"
onDelete(name, images); onClick={(e) => {
}} e.stopPropagation();
/> onDelete(modelName, categoryName, [image]);
</TooltipTrigger> }}
<TooltipContent>{t("button.deleteFaceAttempts")}</TooltipContent> />
</Tooltip> </TooltipTrigger>
<TooltipContent>
{t("button.deleteClassificationAttempts")}
</TooltipContent>
</Tooltip>
</div>
</div>
</div> </div>
</div> </div>
</div> ))}
</div> </div>
); );
} }
@ -339,9 +362,10 @@ function TrainGrid({
); );
return ( return (
<div className="grid size-full grid-cols-10 gap-2 overflow-y-auto"> <div className="grid grid-cols-10 gap-2 overflow-y-auto">
{trainData.map((data) => ( {trainData?.map((data) => (
<div <div
key={data.timestamp}
className={cn( className={cn(
"flex cursor-pointer flex-col gap-2 rounded-lg bg-card outline outline-[3px]", "flex cursor-pointer flex-col gap-2 rounded-lg bg-card outline outline-[3px]",
selected selected
@ -361,7 +385,7 @@ function TrainGrid({
> >
<img <img
className="h-48 rounded-lg" className="h-48 rounded-lg"
src={`${baseUrl}clips/${model.name}/${data.raw}`} src={`${baseUrl}clips/${model.name}/train/${data.raw}`}
/> />
</div> </div>
<div className="rounded-b-lg bg-card p-3"> <div className="rounded-b-lg bg-card p-3">