diff --git a/GUI/back-end/data_collection/constants.py b/GUI/back-end/data_collection/constants.py index 0e3c5cc..5ef19a5 100644 --- a/GUI/back-end/data_collection/constants.py +++ b/GUI/back-end/data_collection/constants.py @@ -236,3 +236,6 @@ "function": "download_lovd_database_for_eys_gene" } } + +PATH_TO_WORKSPACE = "workspaces" +FILENAME_REGEX = r'^[^<>:"/\\|?*\n]+\.txt$' \ No newline at end of file diff --git a/GUI/back-end/main.py b/GUI/back-end/main.py index 0a8237c..0260c0c 100644 --- a/GUI/back-end/main.py +++ b/GUI/back-end/main.py @@ -35,10 +35,16 @@ # Configurations cors = CORS(app, resources={r"/*": {"origins": "*"}}) +# Setup base workspace dir +if not os.path.exists(constants.PATH_TO_WORKSPACE): + os.mkdir(constants.PATH_TO_WORKSPACE) @app.route('/api/v1/request', methods=['POST']) def process(): - request_data = request.get_json() + request_data = request.json.get('content') + request_workspace = request.json.get('workspace') + print(request_data) + print(request_workspace) print("Got data") logging.info(request_data) if not request_data: @@ -72,4 +78,5 @@ def process(): # Run the app if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0', port=8080) \ No newline at end of file + # app.run(debug=True, host='0.0.0.0', port=8080) + app.run(debug=True, port=8080) \ No newline at end of file diff --git a/GUI/back-end/router.py b/GUI/back-end/router.py index 4cbba2d..a564fbf 100644 --- a/GUI/back-end/router.py +++ b/GUI/back-end/router.py @@ -1,5 +1,5 @@ from flask import Blueprint -from routes import route_users_bp, route_profiles_bp +from routes import route_workspace_bp, route_users_bp, route_profiles_bp def router_bp(prefix): router_bp = Blueprint('router', __name__, url_prefix=prefix) @@ -8,6 +8,7 @@ def router_bp(prefix): ### THESE LINES ARE ONLY FOR EXAMPLE PURPOSES ### router_bp.register_blueprint(route_users_bp) router_bp.register_blueprint(route_profiles_bp) + router_bp.register_blueprint(route_workspace_bp) ################################################# return router_bp \ No newline at end of file diff --git a/GUI/back-end/routes/__init__.py b/GUI/back-end/routes/__init__.py index 0d83d32..99a92c6 100644 --- a/GUI/back-end/routes/__init__.py +++ b/GUI/back-end/routes/__init__.py @@ -1,4 +1,5 @@ ### THESE LINES ARE ONLY FOR EXAMPLE PURPOSES ### from .profiles import route_profiles_bp from .users import route_users_bp +from .workspace import route_workspace_bp ################################################# \ No newline at end of file diff --git a/GUI/back-end/routes/workspace.py b/GUI/back-end/routes/workspace.py new file mode 100644 index 0000000..5c44418 --- /dev/null +++ b/GUI/back-end/routes/workspace.py @@ -0,0 +1,94 @@ +from flask import Blueprint, jsonify, request, make_response +import os +from data_collection.constants import PATH_TO_WORKSPACE, FILENAME_REGEX +import re + +route_workspace_bp = Blueprint('workspace', __name__) + +@route_workspace_bp.route('/workspace', methods=['POST']) +def create_workspace(): + workspace = request.get_json() + + path = f"{PATH_TO_WORKSPACE}/{workspace}" + + if os.path.exists(path) and os.path.isdir(workspace): + return make_response(jsonify({'message': 'Workspace already exists!'}), 200) + + try: + os.mkdir(path) + return make_response(jsonify({'message': 'Workspace created!'}), 200) + except Exception as e: + return make_response(jsonify({'message': str(e)}), 400) + +@route_workspace_bp.route('/workspace', methods=['GET']) +def get_workspaces(): + workspaces = os.listdir(PATH_TO_WORKSPACE) + return make_response(jsonify({'workspaces': workspaces}), 200) + +@route_workspace_bp.route('/workspace/', methods=['GET']) +def get_workspace(workspace): + path = f"{PATH_TO_WORKSPACE}/{workspace}" + if os.path.exists(path) and os.path.isdir(path): + all_files = os.listdir(path) + return make_response(jsonify({'files': all_files}), 200) + else: + return make_response(jsonify({'message': 'Workspace not found!'}), 404) + +@route_workspace_bp.route('/workspace/', methods=['DELETE']) +def delete_workspace(workspace): + path = f"{PATH_TO_WORKSPACE}/{workspace}" + if os.path.exists(path) and os.path.isdir(path): + delete_all_files_in_directory(path) + return make_response(jsonify({'message': 'Workspace deleted!'}), 200) + else: + return make_response(jsonify({'message': 'Workspace not found!'}), 404) + +@route_workspace_bp.route('/workspace/file/upload', methods=['POST']) +def upload_file(): + workspace = request.form.get('workspace') + filename = request.form.get('file_name') + + if 'file' not in request.files: + return make_response(jsonify({'message': 'No file part in the request'}), 400) + + file = request.files['file'] + + if filename == '': + return make_response(jsonify({'message': 'No file selected for uploading'}), 400) + + if not re.match(FILENAME_REGEX, filename): + return make_response(jsonify({'message': 'Invalid file name!'}), 400) + + if not os.path.exists(f"{PATH_TO_WORKSPACE}/{workspace}"): + return make_response(jsonify({'message': 'Workspace not found!'}), 404) + + file.filename = filename + + if file and file.filename.endswith('.txt'): + path = f"{PATH_TO_WORKSPACE}/{workspace}/{file.filename}" + file.save(path) + return make_response(jsonify({'message': 'File successfully uploaded'}), 200) + + return make_response(jsonify({'message': 'File must be a .txt file'}), 400) + +@route_workspace_bp.route('/workspace/file/delete', methods=['DELETE']) +def delete_file(): + file_name = request.json.get('file_name') + workspace = request.json.get('workspace') + + if not re.match(FILENAME_REGEX, file_name): + return make_response(jsonify({'message': 'Invalid file name!'}), 400) + + path = f"{PATH_TO_WORKSPACE}/{workspace}/{file_name}" + if os.path.exists(path) and os.path.isfile(path): + os.remove(path) + return make_response(jsonify({'message': 'File deleted!'}), 200) + else: + return make_response(jsonify({'message': 'File not found!'}), 404) + +def delete_all_files_in_directory(path): + for file_name in os.listdir(path): + file_path = os.path.join(path, file_name) + if os.path.isfile(file_path): + os.remove(file_path) + os.rmdir(path) \ No newline at end of file diff --git a/GUI/front-end/src/components/displays/chat/ChatDisplay.tsx b/GUI/front-end/src/components/displays/chat/ChatDisplay.tsx index 9ababf5..3428284 100644 --- a/GUI/front-end/src/components/displays/chat/ChatDisplay.tsx +++ b/GUI/front-end/src/components/displays/chat/ChatDisplay.tsx @@ -6,6 +6,7 @@ import { AccountCircle as AccountCircleIcon } from '@mui/icons-material'; import { useApplicationContext } from '../../../contexts'; import { KathLogo } from '../../svgs'; import { useSendRequest } from '../../../hooks'; +import { useWorkspaceContext } from '../../../contexts/tool/UseWorkspaceContext'; interface Props {} @@ -14,20 +15,9 @@ export const ChatDisplay: React.FC = () => { const [isInputDisabled, setIsInputDisabled] = React.useState(false); const applicationContext = useApplicationContext(); + const workspaceContext = useWorkspaceContext(); const sendRequest = useSendRequest(); - // Array of mock-up texts - // const mockUpTexts = [ - // 'Gene frequencies can change over time due to natural selection, mutation, genetic drift, and gene flow.', - // 'The BRCA1 gene, linked to breast cancer, is a subject of extensive research.', - // 'Genome-wide association studies (GWAS) have identified numerous genetic variants associated with complex diseases.', - // 'In the field of genetics, the concept of gene frequency is of paramount importance. It refers to the frequency with which a particular gene variant occurs in a population. This frequency can change over time due to various evolutionary forces such as natural selection, mutation, genetic drift, and gene flow. For instance, if a gene variant confers a survival advantage, its frequency may increase in the population over generations due to natural selection.', - // 'One of the most researched genes in human genetics is the BRCA1 gene. This gene produces a protein that helps suppress the growth of tumors. Mutations in the BRCA1 gene can lead to a significantly increased risk for breast and ovarian cancer. Researchers are continually studying this gene to understand its function better and develop effective therapies for individuals carrying BRCA1 mutations.', - // 'Genome-wide association studies (GWAS) have revolutionized our understanding of the genetic basis of complex diseases. These studies involve scanning the genomes of thousands of individuals to identify genetic variants associated with a particular disease. For example, GWAS have identified numerous genetic variants associated with diseases such as diabetes, heart disease, and various forms of cancer. However, most of these variants only slightly increase the risk of disease, indicating that these diseases are influenced by a combination of genetic, environmental, and lifestyle factors.', - // 'The study of gene frequencies, also known as population genetics, is a fascinating and complex field that seeks to understand how genetic variation occurs within and between populations. This field of study is critical for understanding evolution, genetic diseases, and biodiversity. Gene frequencies can change over time due to several factors, including natural selection, mutation, genetic drift, and gene flow. Natural selection is the process by which certain traits become more or less common in a population based on the survival and reproductive success of individuals with those traits. Mutations, which are changes in the DNA sequence, can introduce new genetic variants into a population. Genetic drift is a random process that can cause gene frequencies to change from one generation to the next, particularly in small populations. Gene flow, which is the transfer of genetic variation from one population to another, can also affect gene frequencies.\n\nOne area of intense research in human genetics is the study of specific genes associated with diseases. For example, the BRCA1 and BRCA2 genes are known to be associated with a higher risk of breast and ovarian cancer. These genes produce proteins that help repair damaged DNA, and when these genes are mutated, the DNA repair process may not function correctly, leading to the development of cancer. Extensive research is being conducted to better understand these genes and develop effective therapies for individuals carrying these mutations.\n\nAnother important area of research is genome-wide association studies (GWAS), which aim to identify genetic variants associated with specific diseases. These studies involve scanning the genomes of thousands of individuals to identify genetic variants that are more common in individuals with a particular disease compared to healthy individuals. GWAS have identified numerous genetic variants associated with a wide range of diseases, including diabetes, heart disease, and various forms of cancer. However, most of these variants only slightly increase the risk of disease, indicating that these diseases are influenced by a combination of genetic, environmental, and lifestyle factors.Despite the significant advances in our understanding of human genetics, there is still much to learn. For example, many of the genetic variants identified by GWAS explain only a small fraction of the heritability of complex diseases, suggesting that there are likely many more genetic variants to be discovered. Furthermore, the role of rare genetic variants, which are not well captured by GWAS, is an area of active research. There is also increasing interest in understanding the role of non-coding regions of the genome, which do not code for proteins but may play important roles in regulating gene expression. As our understanding of human genetics continues to evolve, it is clear that this field of research will continue to provide important insights into human health and disease.\n\nPlease note that this is a simplified explanation and the actual research in genetics is much more complex and nuanced. For accurate and up-to-date information, please refer to scientific literature or consult a genetics professional.', - // ]; - ///////////////////////// - const handleSubmit = async (content: string) => { setIsInputDisabled(true); setChatInstances((prevInstances) => [ @@ -35,26 +25,18 @@ export const ChatDisplay: React.FC = () => { } author='User' content={content} />, ]); - const responseResult = await sendRequest.mutateAsync(content); + const data = { + content: content, + workspace: workspaceContext.workspace, + } + + const responseResult = await sendRequest.mutateAsync(data); setIsInputDisabled(false); setChatInstances((prevInstances) => [ ...prevInstances, } author={applicationContext.name} content={responseResult} />, ]); - - // Temporary mock up response - // setTimeout(() => { - // const randomIndex = Math.floor(Math.random() * mockUpTexts.length); - // const randomMockUpText = mockUpTexts[randomIndex]; - - // setChatInstances((prevInstances) => [ - // ...prevInstances, - // } author={applicationContext.name} content={randomMockUpText} />, - // ]); - // setIsInputDisabled(false); - // }, 1000); - /////////////////////////////////////// }; return ( diff --git a/GUI/front-end/src/components/displays/extension/ExtensionDisplay.tsx b/GUI/front-end/src/components/displays/extension/ExtensionDisplay.tsx index b0520b7..2801598 100644 --- a/GUI/front-end/src/components/displays/extension/ExtensionDisplay.tsx +++ b/GUI/front-end/src/components/displays/extension/ExtensionDisplay.tsx @@ -1,8 +1,8 @@ import { Box, Typography, useTheme } from '@mui/material'; import { useState } from 'react'; import { useLanguageContext } from '../../../contexts'; -// import FunctionalityInstance from './FunctionalityInstance'; -// import HistoryInstance from './HistoryInstance'; +import FunctionalityInstance from './FunctionalityInstance'; +import HistoryInstance from './HistoryInstance'; interface Props {} @@ -50,7 +50,7 @@ export const ExtensionDisplay: React.FC = () => { - {/* = () => { sx={{ backgroundColor: Theme.palette.background.paper }} > {selectedTab === 'Functionality' ? : } - */} + ); }; diff --git a/GUI/front-end/src/components/displays/extension/FileInstance.tsx b/GUI/front-end/src/components/displays/extension/FileInstance.tsx new file mode 100644 index 0000000..c9891e3 --- /dev/null +++ b/GUI/front-end/src/components/displays/extension/FileInstance.tsx @@ -0,0 +1,147 @@ +import { Box, Typography, useTheme, IconButton, Button } from "@mui/material"; +import { useLanguageContext } from "../../../contexts"; +import { useEffect, useState } from "react"; +import { useDeleteFile, useGetWorkspaceFiles, useUploadFile } from "../../../hooks"; +import { useWorkspaceContext } from "../../../contexts/tool/UseWorkspaceContext"; +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import FileInput from "../../fileInput/FileInput"; + +const FileInstance: React.FC = () => { + + const Theme = useTheme(); + + const languageContext = useLanguageContext(); + const workspaceContext = useWorkspaceContext(); + const deleteFile = useDeleteFile(); + const createFile = useUploadFile(); + + + const [files, setFiles] = useState([]); + const [file, setFile] = useState(null); + const [fileName, setFileName] = useState(''); + + const { data, isLoading, isFetching } = useGetWorkspaceFiles(workspaceContext.workspace as string); + + useEffect(() => { + if (data?.files.length > 0) { + setFiles(data.files); + } else { + setFiles([]); + } + }, [data]); + + const onFileSelect = (selectedFile: string | ArrayBuffer | null, name: string) => { + setFile(selectedFile); + setFileName(name); + } + + const createNewFile = async () => { + + const binaryData = dataURLtoBlob(file); + + const data = new FormData(); + data.append('workspace', workspaceContext.workspace as string); + data.append('file', binaryData); + data.append('file_name', fileName); + + await createFile.mutateAsync(data); + + setFile(null); + setFileName(''); + } + + const dataURLtoBlob = (file : string | ArrayBuffer | null) => { + const stringFile = file as string; + const arr = stringFile.split(","); + const mimeMatch = arr[0].match(/:(.*?);/); + + if (!mimeMatch) { + return new Blob(); + } + + const mime = mimeMatch[1]; + const bstr = atob(arr[1]); + let n = bstr.length; + const u8arr = new Uint8Array(n); + + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + + return new Blob([u8arr], { type: mime }); + }; + + return ( + <> + {workspaceContext.workspace && !isLoading && !isFetching && ( + <> + {languageContext.language === 'en' ? 'Files' : 'Failai'} + + + + {file && fileName && (fileName)} + + + { + files?.length > 0 ? ( + files.map((file) => ( + + {file} + { + const data = { + workspace: workspaceContext.workspace, + file_name: file, + } + + await deleteFile.mutateAsync(data); + }} + > + + + + )) + ) : ( + {languageContext.language === 'en' ? 'No files' : 'Nėra failų'} + )} + + + )} + + ); +} + +export default FileInstance; \ No newline at end of file diff --git a/GUI/front-end/src/components/displays/extension/FunctionalityInstance.tsx b/GUI/front-end/src/components/displays/extension/FunctionalityInstance.tsx index 8464c1a..b8f15da 100644 --- a/GUI/front-end/src/components/displays/extension/FunctionalityInstance.tsx +++ b/GUI/front-end/src/components/displays/extension/FunctionalityInstance.tsx @@ -2,6 +2,7 @@ import { ArrowDropDown as ArrowDropDownIcon } from '@mui/icons-material/'; import { Box, MenuItem, Select, Typography, styled, useTheme } from '@mui/material'; import { useLanguageContext, useToolContext } from '../../../contexts'; import React from 'react'; +import WorkspaceInstance from './WorkspaceInstance'; interface Props {} @@ -44,6 +45,8 @@ const FunctionalityInstance: React.FC = () => { ))} + {languageContext.language === 'en' ? 'Workspace' : 'Darbo aplinka'} + ); }; diff --git a/GUI/front-end/src/components/displays/extension/WorkspaceInstance.tsx b/GUI/front-end/src/components/displays/extension/WorkspaceInstance.tsx new file mode 100644 index 0000000..99c8501 --- /dev/null +++ b/GUI/front-end/src/components/displays/extension/WorkspaceInstance.tsx @@ -0,0 +1,148 @@ +import { Select, useTheme, styled, MenuItem, Button, IconButton, Input, FormControl, InputLabel } from "@mui/material"; +import { ArrowDropDown as ArrowDropDownIcon } from '@mui/icons-material/'; +import { useLanguageContext } from "../../../contexts"; +import { useWorkspaceContext } from "../../../contexts/tool/UseWorkspaceContext"; +import { useEffect, useState } from "react"; +import { useCreateWorkspace, useDeleteWorkspace, useGetWorkspaces } from "../../../hooks"; +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import FileInstance from "./FileInstance"; + +const WorkspaceInstance: React.FC = () => { + const Theme = useTheme(); + + const languageContext = useLanguageContext(); + const workspaceContext = useWorkspaceContext(); + + const [awailableWorkspaces, setAwailableWorkspaces] = useState([]); + const [newWorkspace, setNewWorkspace] = useState(''); + const [isOpen, setIsOpen] = useState(false); + + const { data, isLoading, isFetching } = useGetWorkspaces(); + const createWorkspace = useCreateWorkspace(); + const deleteWorkspace = useDeleteWorkspace(); + + const createNewWorkspace = () => { + createWorkspace.mutate(newWorkspace); + workspaceContext.update(newWorkspace); + setNewWorkspace(''); + } + + useEffect(() => { + if (data?.workspaces.length > 0) { + setAwailableWorkspaces(data.workspaces); + } else { + setAwailableWorkspaces([]); + } + }, [data]); + + return ( + <> + + {!newWorkspace && + + {languageContext.language === 'en' ? 'Create new workspace' : 'Pridėti darbo aplinką'} + } + setNewWorkspace(e.target.value)} + /> + + + {awailableWorkspaces.length > 0 && !isFetching && !isLoading && ( + <> + + {!workspaceContext.workspace && + + {languageContext.language === 'en' ? 'Select workspace' : 'Pasirinkti darbo aplinką'} + } + + + + + )} + + ); +} + +export default WorkspaceInstance; \ No newline at end of file diff --git a/GUI/front-end/src/components/fileInput/FileInput.tsx b/GUI/front-end/src/components/fileInput/FileInput.tsx new file mode 100644 index 0000000..2bd40cc --- /dev/null +++ b/GUI/front-end/src/components/fileInput/FileInput.tsx @@ -0,0 +1,57 @@ +import { Button } from "@mui/material"; +import { useRef } from "react"; +import { useLanguageContext } from "../../contexts"; + +function FileInput({ onFileSelect } : { onFileSelect: (file: string | ArrayBuffer | null, name: string) => void}){ + const languageContext = useLanguageContext(); + const inputRef = useRef(null); + + const handleFileSelect = (e: React.ChangeEvent) => { + if(e.target.files && e.target.files.length > 0){ + const file = e.target.files[0]; + + if(!['text/plain'].includes(file.type)){ + alert('Please select a text file'); + return; + } + + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => { + onFileSelect(reader.result, file.name); + }; + + if (inputRef.current) { + inputRef.current.value = ''; + } + } + + + }; + + const onFileInputClick = () => { + if (inputRef.current) { + inputRef.current.click(); + } + }; + + return ( +
+ + +
+ ); +} + +export default FileInput; \ No newline at end of file diff --git a/GUI/front-end/src/contexts/index.ts b/GUI/front-end/src/contexts/index.ts index b32bde7..b87254d 100644 --- a/GUI/front-end/src/contexts/index.ts +++ b/GUI/front-end/src/contexts/index.ts @@ -6,3 +6,4 @@ export { ApplicationContextProvider } from './application/ApplicationContextProv export { useApplicationContext } from './application/UseApplicationContext'; export { ToolContextProvider } from './tool/ToolContextProvider'; export { useToolContext } from './tool/UseToolContext'; +export { WorkspaceContextProvider, WorkspaceContext } from './tool/WorkspaceContextProvider'; diff --git a/GUI/front-end/src/contexts/tool/UseWorkspaceContext.ts b/GUI/front-end/src/contexts/tool/UseWorkspaceContext.ts new file mode 100644 index 0000000..1d7047f --- /dev/null +++ b/GUI/front-end/src/contexts/tool/UseWorkspaceContext.ts @@ -0,0 +1,4 @@ +import { useContext } from "react"; +import { WorkspaceContext } from "./WorkspaceContextProvider"; + +export const useWorkspaceContext = () => useContext(WorkspaceContext); \ No newline at end of file diff --git a/GUI/front-end/src/contexts/tool/WorkspaceContextProvider.tsx b/GUI/front-end/src/contexts/tool/WorkspaceContextProvider.tsx new file mode 100644 index 0000000..1ca958d --- /dev/null +++ b/GUI/front-end/src/contexts/tool/WorkspaceContextProvider.tsx @@ -0,0 +1,29 @@ +import React, { createContext, useState } from "react"; + +interface WorkspaceContextProps { + workspace: string | null; + update: (newWorkspace: string) => void; +} + +export const WorkspaceContext = createContext({ + workspace: null, + update: () => {}, +}) + +interface Props { + children?: React.ReactNode; +} + +export const WorkspaceContextProvider: React.FC = ({ children }) => { + const [workspace, setWorkspace] = useState(null); + + function updateWorkspace(newWorkspace: string) { + setWorkspace(newWorkspace); + } + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/GUI/front-end/src/hooks/index.ts b/GUI/front-end/src/hooks/index.ts index 925e7e5..69b5d48 100644 --- a/GUI/front-end/src/hooks/index.ts +++ b/GUI/front-end/src/hooks/index.ts @@ -1,2 +1,3 @@ export { useGetUsers } from './user/userHook'; export { useSendRequest } from './request/requestHook'; +export { useGetWorkspaces, useCreateWorkspace, useDeleteWorkspace, useGetWorkspaceFiles, useUploadFile, useDeleteFile } from './workspace/workspaceHook'; diff --git a/GUI/front-end/src/hooks/workspace/workspaceHook.ts b/GUI/front-end/src/hooks/workspace/workspaceHook.ts new file mode 100644 index 0000000..f94b474 --- /dev/null +++ b/GUI/front-end/src/hooks/workspace/workspaceHook.ts @@ -0,0 +1,64 @@ +import { useMutation, useQueryClient, useQuery } from "@tanstack/react-query"; +import { createWorkspace, getWorkspaces, getWorkspaceFiles, deleteWorkspace, uploadFile, deleteFile } from "../../services"; + +export const useCreateWorkspace = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: createWorkspace, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['workspace'] }); + }, + }); +}; + +export const useGetWorkspaces = () => { + return useQuery({ + queryKey: ['workspace'], + queryFn: getWorkspaces, + refetchOnWindowFocus: false, + refetchInterval: false, + }); +}; + +export const useGetWorkspaceFiles = (workspace: string) => { + return useQuery({ + queryKey: ['workspaceFiles', workspace], + queryFn: getWorkspaceFiles, + refetchOnWindowFocus: false, + refetchInterval: false, + }); +}; + +export const useDeleteWorkspace = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: deleteWorkspace, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['workspace'] }); + }, + }); +}; + +export const useUploadFile = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: uploadFile, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['workspaceFiles'] }); + }, + }); +} + +export const useDeleteFile = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: deleteFile, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['workspaceFiles'] }); + }, + }); +} \ No newline at end of file diff --git a/GUI/front-end/src/main/app/App.tsx b/GUI/front-end/src/main/app/App.tsx index beeab4f..f35a25c 100644 --- a/GUI/front-end/src/main/app/App.tsx +++ b/GUI/front-end/src/main/app/App.tsx @@ -1,22 +1,13 @@ import React, { useEffect } from 'react'; import { Box, useTheme } from '@mui/material'; -import { useGetUsers } from '../../hooks'; import { ExtensionDisplay, ChatDisplay } from '../../components/displays'; import BaseLayout from '../layout/BaseLayout'; import HeaderLayout from '../layout/HeaderLayout'; import '../../css/app/App.css'; function App() { - /// THESE LINES ARE ONLY FOR EXAMPLE PURPOSES /// - - const { data } = useGetUsers(); - useEffect(() => { - console.log(data); - }, [data]); - - ///////////////////////////////////////////////// - const [isDrawerOpen, setIsDrawerOpen] = React.useState(false); + const [isDrawerOpen, setIsDrawerOpen] = React.useState(true); const Theme = useTheme(); useEffect(() => { @@ -60,8 +51,7 @@ function App() { }} > {/* Up - header */} - {/* TODO: set to isDrawerOpen */} - setIsDrawerOpen(!isDrawerOpen)} /> + setIsDrawerOpen(!isDrawerOpen)} /> {/* Chat display */} - - - - - - - + + + + + + + + + diff --git a/GUI/front-end/src/services/index.ts b/GUI/front-end/src/services/index.ts index e7dc0d8..a6818c5 100644 --- a/GUI/front-end/src/services/index.ts +++ b/GUI/front-end/src/services/index.ts @@ -1,2 +1,3 @@ export { getUsers } from './user/userService'; export { sendRequest } from './request/requestService'; +export { createWorkspace, getWorkspaces, getWorkspaceFiles, deleteWorkspace, uploadFile, deleteFile } from './workspace/workspaceService'; diff --git a/GUI/front-end/src/services/request/requestService.ts b/GUI/front-end/src/services/request/requestService.ts index 4a295b5..c21c537 100644 --- a/GUI/front-end/src/services/request/requestService.ts +++ b/GUI/front-end/src/services/request/requestService.ts @@ -2,9 +2,9 @@ import { ENDPOINTS } from "../../types/constants"; import httpClient from "../httpClient"; -export async function sendRequest(request: string) { +export async function sendRequest(data: { content: string, workspace: string | null}) { return await httpClient - .post(ENDPOINTS.REQUEST, request) + .post(ENDPOINTS.REQUEST, data) .then((res) => res.data) .catch((err) => { console.error(err); diff --git a/GUI/front-end/src/services/workspace/workspaceService.ts b/GUI/front-end/src/services/workspace/workspaceService.ts new file mode 100644 index 0000000..4e23da0 --- /dev/null +++ b/GUI/front-end/src/services/workspace/workspaceService.ts @@ -0,0 +1,65 @@ +import { ENDPOINTS } from "../../types/constants"; +import httpClient from "../httpClient"; + +export async function createWorkspace(workspace: string) { + return await httpClient + .post(ENDPOINTS.WORKSPACE.CREATE_WORKSPACE, workspace) + .then((res) => res.data) + .catch((err) => { + console.error(err); + }); +} + +export async function getWorkspaces() { + return await httpClient + .get(ENDPOINTS.WORKSPACE.GET_WORKSPACES) + .then((res) => res.data) + .catch((err) => { + console.error(err); + }); +} + +export async function getWorkspaceFiles({ queryKey } : { queryKey: [string, string] }) { + const [, workspace] = queryKey; + + if(workspace === null) return Promise.resolve({ files: [] }); + + return await httpClient + .get(ENDPOINTS.WORKSPACE.GET_WORKSPACE_FILES(workspace)) + .then((res) => res.data) + .catch((err) => { + console.error(err); + }); +} + +export async function deleteWorkspace(workspace: string) { + return await httpClient + .delete(ENDPOINTS.WORKSPACE.DELETE_WORKSPACE(workspace)) + .then((res) => res.data) + .catch((err) => { + console.error(err); + }); +} + +export async function uploadFile(data: FormData) { + const originalContentType = httpClient.defaults.headers['Content-Type']; + httpClient.defaults.headers['Content-Type'] = 'multipart/form-data'; + + try { + const response = await httpClient.post(ENDPOINTS.WORKSPACE.UPLOAD_FILE, data); + return response.data; + } catch (error) { + console.error(error); + } finally { + httpClient.defaults.headers['Content-Type'] = originalContentType; + } +} + +export async function deleteFile(data: { workspace: string | null; file_name: string }) { + return await httpClient + .delete(ENDPOINTS.WORKSPACE.DELETE_FILE, { data }) + .then((res) => res.data) + .catch((err) => { + console.error(err); + }); +} \ No newline at end of file diff --git a/GUI/front-end/src/types/constants/endpoints.ts b/GUI/front-end/src/types/constants/endpoints.ts index b188564..18e0d98 100644 --- a/GUI/front-end/src/types/constants/endpoints.ts +++ b/GUI/front-end/src/types/constants/endpoints.ts @@ -3,4 +3,12 @@ export const ENDPOINTS = { USERS: `/users`, PROFILES: `/profiles`, REQUEST: '/request', + WORKSPACE : { + CREATE_WORKSPACE: '/workspace', + GET_WORKSPACES: '/workspace', + GET_WORKSPACE_FILES: (workspace : string) => `/workspace/${workspace}`, + DELETE_WORKSPACE: (workspace : string) => `/workspace/${workspace}`, + UPLOAD_FILE: `/workspace/file/upload`, + DELETE_FILE: `/workspace/file/delete`, + } }; \ No newline at end of file