diff --git a/frontend/src/api/categoryApi.ts b/frontend/src/api/categoryApi.ts new file mode 100644 index 0000000..02a1f42 --- /dev/null +++ b/frontend/src/api/categoryApi.ts @@ -0,0 +1,31 @@ +import api from '@/api/axiosConfig'; +import { LabelCategoryRequest, LabelCategoryResponse } from '@/types'; + +// 레이블 카테고리 리스트 조회 +export async function getProjectCategories(projectId: number) { + return api.get(`/projects/${projectId}/categories`).then(({ data }) => data); +} + +// 레이블 카테고리 추가 +export async function addProjectCategories(projectId: number, categoryData: LabelCategoryRequest) { + return api.post(`/projects/${projectId}/categories`, categoryData).then(({ data }) => data); +} + +// 레이블 카테고리 단일 조회 +export async function getCategoryById(projectId: number, categoryId: number) { + return api.get(`/projects/${projectId}/categories/${categoryId}`).then(({ data }) => data); +} + +// 레이블 카테고리 삭제 +export async function deleteCategory(projectId: number, categoryId: number) { + return api.delete(`/projects/${projectId}/categories/${categoryId}`).then(({ data }) => data); +} + +// 레이블 카테고리 존재 여부 조회 +export async function checkCategoryExists(projectId: number, categoryName: string) { + return api + .get(`/projects/${projectId}/categories/exist`, { + params: { categoryName }, + }) + .then(({ data }) => data); +} diff --git a/frontend/src/api/modelApi.ts b/frontend/src/api/modelApi.ts new file mode 100644 index 0000000..106b735 --- /dev/null +++ b/frontend/src/api/modelApi.ts @@ -0,0 +1,22 @@ +import api from '@/api/axiosConfig'; +import { ModelRequest, ModelResponse, ProjectModelsResponse, ModelCategoryResponse } from '@/types'; + +export async function updateModelName(projectId: number, modelId: number, modelData: ModelRequest) { + return api.put(`/projects/${projectId}/models/${modelId}`, modelData).then(({ data }) => data); +} + +export async function trainModel(projectId: number) { + return api.post(`/projects/${projectId}/train`).then(({ data }) => data); +} + +export async function getProjectModels(projectId: number) { + return api.get(`/projects/${projectId}/models`).then(({ data }) => data); +} + +export async function addProjectModel(projectId: number, modelData: ModelRequest) { + return api.post(`/projects/${projectId}/models`, modelData).then(({ data }) => data); +} + +export async function getModelCategories(modelId: number) { + return api.get(`/models/${modelId}/categories`).then(({ data }) => data); +} diff --git a/frontend/src/pages/ModelManage.tsx b/frontend/src/pages/ModelManage.tsx index eb3e41c..eb814d4 100644 --- a/frontend/src/pages/ModelManage.tsx +++ b/frontend/src/pages/ModelManage.tsx @@ -1,12 +1,11 @@ -import { Rabbit, Bird, Turtle, Settings, Share } from 'lucide-react'; +import { Rabbit, Bird, Turtle } from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import ModelLineChart from '@/components/ModelLineChart'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import ModelBarChart from '@/components/ModelBarChart'; export default function ModelManage() { @@ -27,17 +26,7 @@ export default function ModelManage() { const [lossData] = useState(dummyLossData); const [training, setTraining] = useState(false); - - useEffect(() => { - if (training) { - const socket = new WebSocket('ws://localhost:8080/ws'); - socket.onmessage = (event) => { - const data = JSON.parse(event.data); - console.log('Received data:', data); - }; - return () => socket.close(); - } - }, [training]); + const [selectedModel, setSelectedModel] = useState(null); const handleTrainingToggle = () => { setTraining((prev) => !prev); @@ -48,32 +37,6 @@ export default function ModelManage() {

모델 관리

- - - - - - - 설정 - - - - -
@@ -86,42 +49,61 @@ export default function ModelManage() { 모델 평가 + {/* 모델 학습 탭 */}
-
+ {/* 모델 평가 탭 */} -
-
- -
-
- -
+ {/* 모델 선택 */} +
+ +
+ + {/* 선택된 모델에 따른 BarChart 및 Labeling Preview */} + {selectedModel && ( +
+
+ +
+
+ +
+
+ )}
@@ -190,11 +172,22 @@ function SettingsForm() { placeholder="-1" id="batch" /> + +
@@ -244,3 +237,32 @@ function InputWithLabel({ label, id, placeholder }: InputWithLabelProps) { ); } +interface SelectWithLabelProps { + label: string; + id: string; + options: string[]; + placeholder: string; +} + +function SelectWithLabel({ label, id, options, placeholder }: SelectWithLabelProps) { + return ( +
+ + +
+ ); +} diff --git a/frontend/src/queries/category/useAddCategoryQuery.ts b/frontend/src/queries/category/useAddCategoryQuery.ts new file mode 100644 index 0000000..631b5a9 --- /dev/null +++ b/frontend/src/queries/category/useAddCategoryQuery.ts @@ -0,0 +1,14 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { addProjectCategories } from '@/api/categoryApi'; +import { LabelCategoryRequest } from '@/types'; + +export default function useAddCategoryQuery(projectId: number) { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (categoryData: LabelCategoryRequest) => addProjectCategories(projectId, categoryData), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['projectCategories', projectId] }); + }, + }); +} diff --git a/frontend/src/queries/category/useCategoryByIdQuery.ts b/frontend/src/queries/category/useCategoryByIdQuery.ts new file mode 100644 index 0000000..ef9c501 --- /dev/null +++ b/frontend/src/queries/category/useCategoryByIdQuery.ts @@ -0,0 +1,9 @@ +import { getCategoryById } from '@/api/categoryApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useCategoryByIdQuery(projectId: number, categoryId: number) { + return useSuspenseQuery({ + queryKey: ['category', projectId, categoryId], + queryFn: () => getCategoryById(projectId, categoryId), + }); +} diff --git a/frontend/src/queries/category/useCheckCategoryExistsQuery.ts b/frontend/src/queries/category/useCheckCategoryExistsQuery.ts new file mode 100644 index 0000000..0174c17 --- /dev/null +++ b/frontend/src/queries/category/useCheckCategoryExistsQuery.ts @@ -0,0 +1,9 @@ +import { checkCategoryExists } from '@/api/categoryApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useCheckCategoryExistsQuery(projectId: number, categoryName: string) { + return useSuspenseQuery({ + queryKey: ['categoryExists', projectId, categoryName], + queryFn: () => checkCategoryExists(projectId, categoryName), + }); +} diff --git a/frontend/src/queries/category/useDeleteCategoryQuery.ts b/frontend/src/queries/category/useDeleteCategoryQuery.ts new file mode 100644 index 0000000..94eca1d --- /dev/null +++ b/frontend/src/queries/category/useDeleteCategoryQuery.ts @@ -0,0 +1,13 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { deleteCategory } from '@/api/categoryApi'; + +export default function useDeleteCategoryQuery(projectId: number, categoryId: number) { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: () => deleteCategory(projectId, categoryId), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['projectCategories', projectId] }); + }, + }); +} diff --git a/frontend/src/queries/category/useProjectCategoriesQuery.ts b/frontend/src/queries/category/useProjectCategoriesQuery.ts new file mode 100644 index 0000000..f069959 --- /dev/null +++ b/frontend/src/queries/category/useProjectCategoriesQuery.ts @@ -0,0 +1,9 @@ +import { getProjectCategories } from '@/api/categoryApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useProjectCategoriesQuery(projectId: number) { + return useSuspenseQuery({ + queryKey: ['projectCategories', projectId], + queryFn: () => getProjectCategories(projectId), + }); +} diff --git a/frontend/src/queries/models/useCreateModelQuery.ts b/frontend/src/queries/models/useCreateModelQuery.ts new file mode 100644 index 0000000..595d304 --- /dev/null +++ b/frontend/src/queries/models/useCreateModelQuery.ts @@ -0,0 +1,14 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { addProjectModel } from '@/api/modelApi'; +import { ModelRequest } from '@/types'; + +export default function useCreateModelQuery(projectId: number) { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (modelData: ModelRequest) => addProjectModel(projectId, modelData), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['projectModels', projectId] }); + }, + }); +} diff --git a/frontend/src/queries/models/useModelCategoriesQuery.ts b/frontend/src/queries/models/useModelCategoriesQuery.ts new file mode 100644 index 0000000..cf9fc47 --- /dev/null +++ b/frontend/src/queries/models/useModelCategoriesQuery.ts @@ -0,0 +1,9 @@ +import { getModelCategories } from '@/api/modelApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useModelCategoriesQuery(modelId: number) { + return useSuspenseQuery({ + queryKey: ['modelCategories', modelId], + queryFn: () => getModelCategories(modelId), + }); +} diff --git a/frontend/src/queries/models/useProjectModelsQuery.ts b/frontend/src/queries/models/useProjectModelsQuery.ts new file mode 100644 index 0000000..ba13a2c --- /dev/null +++ b/frontend/src/queries/models/useProjectModelsQuery.ts @@ -0,0 +1,9 @@ +import { getProjectModels } from '@/api/modelApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useProjectModelsQuery(projectId: number) { + return useSuspenseQuery({ + queryKey: ['projectModels', projectId], + queryFn: () => getProjectModels(projectId), + }); +} diff --git a/frontend/src/queries/models/useTrainModelQuery.ts b/frontend/src/queries/models/useTrainModelQuery.ts new file mode 100644 index 0000000..822c7e3 --- /dev/null +++ b/frontend/src/queries/models/useTrainModelQuery.ts @@ -0,0 +1,8 @@ +import { useMutation } from '@tanstack/react-query'; +import { trainModel } from '@/api/modelApi'; + +export default function useTrainModelQuery(projectId: number) { + return useMutation({ + mutationFn: () => trainModel(projectId), + }); +} diff --git a/frontend/src/queries/models/useUpdateModelNameQuery.ts b/frontend/src/queries/models/useUpdateModelNameQuery.ts new file mode 100644 index 0000000..5ba6486 --- /dev/null +++ b/frontend/src/queries/models/useUpdateModelNameQuery.ts @@ -0,0 +1,14 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { updateModelName } from '@/api/modelApi'; +import { ModelRequest } from '@/types'; + +export default function useUpdateModelNameQuery(projectId: number, modelId: number) { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (modelData: ModelRequest) => updateModelName(projectId, modelId, modelData), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['projectModels', projectId] }); + }, + }); +} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 2a556b9..c0bc387 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -280,3 +280,34 @@ export interface ImageFolderRequest { parentId: number; files: File[]; } + +// 모델 요청 DTO (API로 전달할 데이터 타입) +export interface ModelRequest { + name: string; +} + +// 모델 응답 DTO (API로부터 받는 데이터 타입) +export interface ModelResponse { + id: number; + name: string; +} + +// 모델 카테고리 응답 DTO +export interface ModelCategoryResponse { + id: number; + name: string; +} + +// 프로젝트 모델 리스트 응답 DTO +export interface ProjectModelsResponse extends Array {} + +// 카테고리 요청 DTO +export interface LabelCategoryRequest { + labelCategoryList: number[]; +} + +// 카테고리 응답 DTO +export interface LabelCategoryResponse { + id: number; + name: string; +}