Merge branch 'fe/refactor/review-detail' into 'fe/develop'
Refactor: 학습 부분 리팩토링 완료 See merge request s11-s-project/S11P21S002!250
This commit is contained in:
commit
8ecd21a34b
@ -1,17 +1,23 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Image, Layer, Stage, Line, Rect } from 'react-konva';
|
||||
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';
|
||||
|
||||
interface ImageWithLabelsProps {
|
||||
imagePath: string;
|
||||
labelData: string;
|
||||
projectId: number;
|
||||
imageId: number;
|
||||
}
|
||||
|
||||
export default function ImageWithLabels({ imagePath, labelData }: ImageWithLabelsProps) {
|
||||
export default function ImageWithLabels({ imagePath, labelData, projectId, imageId }: ImageWithLabelsProps) {
|
||||
const [image] = useImage(imagePath);
|
||||
const [labels, setLabels] = useState<Label[]>([]);
|
||||
const [stageDimensions, setStageDimensions] = useState({ width: window.innerWidth, height: window.innerHeight });
|
||||
const { data: commentList } = useCommentListQuery(projectId, imageId);
|
||||
const [showComments, setShowComments] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLabelData = async () => {
|
||||
@ -55,41 +61,77 @@ export default function ImageWithLabels({ imagePath, labelData }: ImageWithLabel
|
||||
return { x: scale, y: scale };
|
||||
};
|
||||
|
||||
return image ? (
|
||||
<Stage
|
||||
width={stageDimensions.width}
|
||||
height={stageDimensions.height}
|
||||
className="overflow-hidden bg-gray-200"
|
||||
scale={getScale()}
|
||||
>
|
||||
<Layer>{image && <Image image={image} />}</Layer>
|
||||
<Layer>
|
||||
{labels.map((label) =>
|
||||
label.type === 'rectangle' ? (
|
||||
<Rect
|
||||
key={label.id}
|
||||
x={label.coordinates[0][0]}
|
||||
y={label.coordinates[0][1]}
|
||||
width={label.coordinates[1][0] - label.coordinates[0][0]}
|
||||
height={label.coordinates[1][1] - label.coordinates[0][1]}
|
||||
stroke={label.color}
|
||||
strokeWidth={2}
|
||||
listening={false}
|
||||
/>
|
||||
) : (
|
||||
<Line
|
||||
key={label.id}
|
||||
points={label.coordinates.flat()}
|
||||
stroke={label.color}
|
||||
strokeWidth={2}
|
||||
closed
|
||||
listening={false}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowComments((prev) => !prev)}
|
||||
className="mb-4"
|
||||
>
|
||||
{showComments ? '댓글 숨기기' : '댓글 보기'}
|
||||
</Button>
|
||||
<Stage
|
||||
width={stageDimensions.width}
|
||||
height={stageDimensions.height}
|
||||
className="overflow-hidden bg-gray-200"
|
||||
scale={getScale()}
|
||||
>
|
||||
<Layer>{image && <Image image={image} />}</Layer>
|
||||
<Layer>
|
||||
{labels.map((label) =>
|
||||
label.type === 'rectangle' ? (
|
||||
<Rect
|
||||
key={label.id}
|
||||
x={label.coordinates[0][0]}
|
||||
y={label.coordinates[0][1]}
|
||||
width={label.coordinates[1][0] - label.coordinates[0][0]}
|
||||
height={label.coordinates[1][1] - label.coordinates[0][1]}
|
||||
stroke={label.color}
|
||||
strokeWidth={2}
|
||||
listening={false}
|
||||
/>
|
||||
) : (
|
||||
<Line
|
||||
key={label.id}
|
||||
points={label.coordinates.flat()}
|
||||
stroke={label.color}
|
||||
strokeWidth={2}
|
||||
closed
|
||||
listening={false}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Layer>
|
||||
{showComments && (
|
||||
<Layer>
|
||||
{commentList?.map((comment) => (
|
||||
<Group
|
||||
key={comment.id}
|
||||
x={comment.positionX}
|
||||
y={comment.positionY}
|
||||
>
|
||||
<Rect
|
||||
width={150}
|
||||
height={50}
|
||||
fill="white"
|
||||
cornerRadius={10}
|
||||
shadowBlur={5}
|
||||
/>
|
||||
<Text
|
||||
x={10}
|
||||
y={10}
|
||||
text={comment.content}
|
||||
fontSize={14}
|
||||
fill="black"
|
||||
width={130}
|
||||
listening={false}
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
</Layer>
|
||||
)}
|
||||
</Layer>
|
||||
</Stage>
|
||||
) : (
|
||||
<div></div>
|
||||
</Stage>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -48,9 +48,13 @@ export default function TrainingSettings({
|
||||
}
|
||||
};
|
||||
|
||||
const isTraining = selectedModel?.isTrain;
|
||||
const isWaiting = isPolling && !isTraining;
|
||||
|
||||
return (
|
||||
<fieldset className={cn('grid gap-6 rounded-lg border p-4', className)}>
|
||||
<legend className="-ml-1 px-1 text-sm font-medium">모델 설정</legend>
|
||||
|
||||
<div className="grid gap-3">
|
||||
<SelectWithLabel
|
||||
label="모델 선택"
|
||||
@ -69,7 +73,7 @@ export default function TrainingSettings({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{!selectedModel?.isTrain && (
|
||||
{!isPolling && !isTraining && (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InputWithLabel
|
||||
@ -130,19 +134,30 @@ export default function TrainingSettings({
|
||||
variant="outlinePrimary"
|
||||
size="lg"
|
||||
onClick={handleSubmit}
|
||||
disabled={!selectedModel || isPolling}
|
||||
disabled={!selectedModel}
|
||||
>
|
||||
{isPolling ? '대기 중...' : '학습 시작'}
|
||||
학습 시작
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{selectedModel?.isTrain && (
|
||||
|
||||
{isWaiting && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
onClick={handleTrainingStop}
|
||||
>
|
||||
학습 중단
|
||||
대기 중
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{isTraining && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="lg"
|
||||
onClick={handleTrainingStop}
|
||||
>
|
||||
학습 중
|
||||
</Button>
|
||||
)}
|
||||
</fieldset>
|
||||
|
@ -16,9 +16,6 @@ export default function TrainingTab({ projectId }: TrainingTabProps) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: startTraining } = useTrainModelQuery(numericProjectId as number, {
|
||||
onSuccess: () => {
|
||||
setIsPolling(true);
|
||||
},
|
||||
onError: () => {
|
||||
alert('학습 요청 실패');
|
||||
setIsPolling(false);
|
||||
@ -28,24 +25,34 @@ export default function TrainingTab({ projectId }: TrainingTabProps) {
|
||||
const handleTrainingStart = (trainData: ModelTrainRequest) => {
|
||||
if (numericProjectId !== null) {
|
||||
startTraining(trainData);
|
||||
setIsPolling(true);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedModel || !numericProjectId || !isPolling) return;
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['projectModels', numericProjectId] });
|
||||
}, 2000);
|
||||
const intervalId = setInterval(async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ['projectModels', numericProjectId] });
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
clearInterval(intervalId);
|
||||
setIsPolling(false);
|
||||
}, 30000);
|
||||
const models = await queryClient.getQueryData<ModelResponse[]>(['projectModels', numericProjectId]);
|
||||
|
||||
const updatedModel = models?.find((model) => model.id === selectedModel.id);
|
||||
|
||||
if (updatedModel) {
|
||||
setSelectedModel(updatedModel);
|
||||
|
||||
if (updatedModel.isTrain) {
|
||||
setIsPolling(true);
|
||||
} else if (!updatedModel.isTrain && selectedModel.isTrain) {
|
||||
setIsPolling(false);
|
||||
setSelectedModel(null);
|
||||
}
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, [selectedModel, numericProjectId, queryClient, isPolling]);
|
||||
|
||||
@ -56,7 +63,7 @@ export default function TrainingTab({ projectId }: TrainingTabProps) {
|
||||
return (
|
||||
<div className="grid grid-rows-[auto_1fr] gap-8 md:grid-cols-2">
|
||||
<TrainingSettings
|
||||
key={selectedModel?.isTrain ? 'training' : 'settings'}
|
||||
key={`${selectedModel?.isTrain ? 'training' : 'settings'}-${isPolling}`}
|
||||
projectId={numericProjectId}
|
||||
selectedModel={selectedModel}
|
||||
setSelectedModel={setSelectedModel}
|
||||
@ -66,7 +73,7 @@ export default function TrainingTab({ projectId }: TrainingTabProps) {
|
||||
className="h-full"
|
||||
/>
|
||||
<TrainingGraph
|
||||
key={selectedModel?.isTrain ? 'training' : 'graph'}
|
||||
key={`${selectedModel?.isTrain ? 'training' : 'graph'}-${isPolling}`}
|
||||
projectId={numericProjectId}
|
||||
selectedModel={selectedModel}
|
||||
className="h-full"
|
||||
|
@ -95,6 +95,8 @@ export default function ReviewDetail(): JSX.Element {
|
||||
<ImageWithLabels
|
||||
imagePath={image.imagePath}
|
||||
labelData={image.dataPath}
|
||||
projectId={Number(projectId)}
|
||||
imageId={image.id}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
@ -5,5 +5,6 @@ export default function useProjectModelsQuery(projectId: number) {
|
||||
return useSuspenseQuery({
|
||||
queryKey: ['projectModels', projectId],
|
||||
queryFn: () => getProjectModels(projectId),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user