Merge branch 'fe/refactor/learn' into 'fe/develop'
Fe/refactor/learn See merge request s11-s-project/S11P21S002!255
This commit is contained in:
commit
1a64e28b69
25
frontend/package-lock.json
generated
25
frontend/package-lock.json
generated
@ -19,6 +19,7 @@
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@tanstack/react-query": "^5.52.1",
|
||||
"axios": "^1.7.5",
|
||||
@ -4654,6 +4655,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.0.tgz",
|
||||
"integrity": "sha512-gwoxaKZ0oJ4vIgzsfESBuSgJNdc0rv12VhHgcqN0TEJmmZixXG/2XpsLK8kzNWYcnaoRIEEQc0bEi3dIvdUpjw==",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@tanstack/react-query": "^5.52.1",
|
||||
"axios": "^1.7.5",
|
||||
|
@ -3,7 +3,7 @@ import { Image, Layer, Stage, Line, Rect, Text, Group } from 'react-konva';
|
||||
import useImage from 'use-image';
|
||||
import { Label, Shape } from '@/types';
|
||||
import useCommentListQuery from '@/queries/comments/useCommentListQuery';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Toggle } from '@/components/ui/toggle';
|
||||
|
||||
interface ImageWithLabelsProps {
|
||||
imagePath: string;
|
||||
@ -63,14 +63,15 @@ export default function ImageWithLabels({ imagePath, labelData, projectId, image
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
<Toggle
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowComments((prev) => !prev)}
|
||||
pressed={showComments}
|
||||
onPressedChange={(pressed) => setShowComments(pressed)}
|
||||
className="mb-4"
|
||||
>
|
||||
{showComments ? '댓글 숨기기' : '댓글 보기'}
|
||||
</Button>
|
||||
</Toggle>
|
||||
<Stage
|
||||
width={stageDimensions.width}
|
||||
height={stageDimensions.height}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import ModelLineChart from './ModelLineChart';
|
||||
import usePollingTrainingModelReport from '@/queries/reports/usePollingModelReportsQuery';
|
||||
import usePollingTrainingModelReport from '@/hooks/usePollingTrainingModelReport';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { ModelResponse } from '@/types';
|
||||
|
||||
@ -13,16 +13,27 @@ interface TrainingGraphProps {
|
||||
export default function TrainingGraph({ projectId, selectedModel, className }: TrainingGraphProps) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: trainingDataList } = usePollingTrainingModelReport(
|
||||
projectId as number,
|
||||
selectedModel?.id as number,
|
||||
selectedModel?.isTrain || false
|
||||
);
|
||||
const isTrainingEnabled = Boolean(selectedModel?.isTrain);
|
||||
|
||||
const handleTrainingEnd = () => {
|
||||
queryClient.resetQueries({
|
||||
queryKey: ['modelReports', projectId, selectedModel?.id],
|
||||
exact: true,
|
||||
});
|
||||
alert('학습이 완료되었습니다.');
|
||||
};
|
||||
|
||||
const { data: trainingDataList } = usePollingTrainingModelReport({
|
||||
projectId: projectId as number,
|
||||
modelId: selectedModel?.id as number,
|
||||
enabled: isTrainingEnabled,
|
||||
onTrainingEnd: handleTrainingEnd,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedModel || !selectedModel.isTrain) {
|
||||
queryClient.resetQueries({
|
||||
queryKey: [{ type: 'modelReports', projectId, modelId: selectedModel?.id }],
|
||||
queryKey: ['modelReports', projectId, selectedModel?.id],
|
||||
exact: true,
|
||||
});
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import TrainingSettings from './TrainingSettings';
|
||||
import TrainingGraph from './TrainingGraph';
|
||||
import useTrainModelQuery from '@/queries/models/useTrainModelQuery';
|
||||
import usePollingTrainingModelReport from '@/hooks/usePollingTrainingModelReport';
|
||||
import { ModelTrainRequest, ModelResponse } from '@/types';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
@ -24,6 +25,11 @@ export default function TrainingTab({ projectId }: TrainingTabProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleTrainingEnd = () => {
|
||||
setIsPolling(false);
|
||||
setSelectedModel((prevModel) => (prevModel ? { ...prevModel, isTrain: false } : null));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedModel || !numericProjectId || !isPolling) return;
|
||||
|
||||
@ -38,10 +44,10 @@ export default function TrainingTab({ projectId }: TrainingTabProps) {
|
||||
setSelectedModel(updatedModel);
|
||||
|
||||
if (updatedModel.isTrain) {
|
||||
setIsPolling(true);
|
||||
} else if (!updatedModel.isTrain && selectedModel.isTrain) {
|
||||
setIsPolling(false);
|
||||
setSelectedModel(null);
|
||||
} else {
|
||||
setIsPolling(false);
|
||||
setSelectedModel({ ...updatedModel, isTrain: false });
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
@ -51,14 +57,22 @@ export default function TrainingTab({ projectId }: TrainingTabProps) {
|
||||
};
|
||||
}, [selectedModel, numericProjectId, queryClient, isPolling]);
|
||||
|
||||
usePollingTrainingModelReport({
|
||||
projectId: numericProjectId as number,
|
||||
modelId: selectedModel?.id as number,
|
||||
enabled: selectedModel?.isTrain || false,
|
||||
onTrainingEnd: handleTrainingEnd,
|
||||
});
|
||||
|
||||
const handleTrainingStop = () => {
|
||||
setIsPolling(false);
|
||||
setSelectedModel((prevModel) => (prevModel ? { ...prevModel, isTrain: false } : null));
|
||||
//todo: 중단 함수 연결
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-rows-[auto_1fr] gap-8 md:grid-cols-2">
|
||||
<TrainingSettings
|
||||
key={`${selectedModel?.isTrain ? 'training' : 'settings'}-${isPolling}`}
|
||||
projectId={numericProjectId}
|
||||
selectedModel={selectedModel}
|
||||
setSelectedModel={setSelectedModel}
|
||||
@ -68,7 +82,6 @@ export default function TrainingTab({ projectId }: TrainingTabProps) {
|
||||
className="h-full"
|
||||
/>
|
||||
<TrainingGraph
|
||||
key={`${selectedModel?.isTrain ? 'training' : 'graph'}-${isPolling}`}
|
||||
projectId={numericProjectId}
|
||||
selectedModel={selectedModel}
|
||||
className="h-full"
|
||||
|
@ -13,8 +13,8 @@ const buttonVariants = cva(
|
||||
// 'bg-gray-900 text-gray-50 hover:bg-gray-900/90 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/90',
|
||||
// destructive:
|
||||
// 'bg-red-500 text-gray-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/90',
|
||||
outline:
|
||||
'border border-gray-200 bg-white hover:bg-gray-100 hover:text-gray-900 dark:border-gray-800 dark:bg-gray-950 dark:hover:bg-gray-800 dark:hover:text-gray-50',
|
||||
// outline:
|
||||
// 'border border-gray-200 bg-white hover:bg-gray-100 hover:text-gray-900 dark:border-gray-800 dark:bg-gray-950 dark:hover:bg-gray-800 dark:hover:text-gray-50',
|
||||
// outlinePrimary:
|
||||
// 'border border-primary text-primary hover:bg-primary hover:text-white dark:border-primary dark:text-primary dark:hover:bg-primary dark:hover:text-white disabled:border-gray-200 disabled:bg-white disabled:text-gray-500 disabled:hover:bg-gray-100 disabled:hover:text-gray-300',
|
||||
// secondary:
|
||||
|
44
frontend/src/components/ui/toggle.tsx
Normal file
44
frontend/src/components/ui/toggle.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const toggleVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-transparent',
|
||||
outline: 'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-3',
|
||||
sm: 'h-9 px-2.5',
|
||||
lg: 'h-11 px-5',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const Toggle = React.forwardRef<
|
||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, ...props }, ref) => (
|
||||
<TogglePrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
Toggle.displayName = TogglePrimitive.Root.displayName;
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export { Toggle, toggleVariants };
|
43
frontend/src/hooks/usePollingTrainingModelReport.ts
Normal file
43
frontend/src/hooks/usePollingTrainingModelReport.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getTrainingModelReport } from '@/api/reportApi';
|
||||
import { ReportResponse } from '@/types';
|
||||
|
||||
interface UsePollingTrainingModelReportProps {
|
||||
projectId: number;
|
||||
modelId: number;
|
||||
enabled: boolean;
|
||||
onTrainingEnd: () => void;
|
||||
}
|
||||
|
||||
export default function usePollingTrainingModelReport({
|
||||
projectId,
|
||||
modelId,
|
||||
enabled,
|
||||
onTrainingEnd,
|
||||
}: UsePollingTrainingModelReportProps) {
|
||||
const query = useQuery<ReportResponse[]>({
|
||||
queryKey: ['modelReports', projectId, modelId],
|
||||
queryFn: () => getTrainingModelReport(projectId, modelId),
|
||||
enabled,
|
||||
refetchInterval: (data) => {
|
||||
if (!enabled) return false;
|
||||
if (Array.isArray(data)) {
|
||||
const lastReport = data[data.length - 1];
|
||||
return lastReport?.epochTime ? Math.max(2000, lastReport.epochTime / 2) : 2000;
|
||||
}
|
||||
return 2000;
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (query.data && query.data.length > 0) {
|
||||
const lastReport = query.data[query.data.length - 1];
|
||||
if (lastReport.epoch >= lastReport.totalEpochs) {
|
||||
onTrainingEnd();
|
||||
}
|
||||
}
|
||||
}, [query.data, onTrainingEnd]);
|
||||
|
||||
return query;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getTrainingModelReport } from '@/api/reportApi';
|
||||
import { ReportResponse } from '@/types';
|
||||
|
||||
export default function usePollingTrainingModelReport(projectId: number, modelId: number, enabled: boolean) {
|
||||
return useQuery<ReportResponse[]>({
|
||||
queryKey: ['modelReports', projectId, modelId],
|
||||
queryFn: () => getTrainingModelReport(projectId, modelId),
|
||||
refetchInterval: enabled ? 5000 : false,
|
||||
enabled,
|
||||
});
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||
import { getTrainingModelReport } from '@/api/reportApi';
|
||||
import { ReportResponse } from '@/types';
|
||||
|
||||
export default function useTrainingModelReport(projectId: number, modelId: number) {
|
||||
return useSuspenseQuery<ReportResponse[]>({
|
||||
queryKey: ['modelReports', projectId, modelId],
|
||||
queryFn: () => getTrainingModelReport(projectId, modelId),
|
||||
});
|
||||
}
|
24
frontend/src/queries/reports/useTrainingModelReportQuery.ts
Normal file
24
frontend/src/queries/reports/useTrainingModelReportQuery.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getTrainingModelReport } from '@/api/reportApi';
|
||||
import { ReportResponse } from '@/types';
|
||||
|
||||
interface UseTrainingModelReportQueryProps {
|
||||
projectId: number;
|
||||
modelId: number;
|
||||
enabled?: boolean;
|
||||
refetchInterval?: number | false;
|
||||
}
|
||||
|
||||
export default function useTrainingModelReportQuery({
|
||||
projectId,
|
||||
modelId,
|
||||
enabled = true,
|
||||
refetchInterval = false,
|
||||
}: UseTrainingModelReportQueryProps) {
|
||||
return useQuery<ReportResponse[]>({
|
||||
queryKey: ['modelReports', projectId, modelId],
|
||||
queryFn: () => getTrainingModelReport(projectId, modelId),
|
||||
enabled,
|
||||
refetchInterval,
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user