Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PP: File workspace #26

Closed
wants to merge 11 commits into from
3 changes: 3 additions & 0 deletions GUI/back-end/data_collection/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,6 @@
"function": "download_lovd_database_for_eys_gene"
}
}

PATH_TO_WORKSPACE = "workspaces"
FILENAME_REGEX = r'^[^<>:"/\\|?*\n]+\.txt$'
11 changes: 9 additions & 2 deletions GUI/back-end/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -72,4 +78,5 @@ def process():

# Run the app
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=8080)
# app.run(debug=True, host='0.0.0.0', port=8080)
app.run(debug=True, port=8080)
3 changes: 2 additions & 1 deletion GUI/back-end/router.py
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
1 change: 1 addition & 0 deletions GUI/back-end/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -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
#################################################
94 changes: 94 additions & 0 deletions GUI/back-end/routes/workspace.py
Original file line number Diff line number Diff line change
@@ -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/<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/<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)
34 changes: 8 additions & 26 deletions GUI/front-end/src/components/displays/chat/ChatDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

Expand All @@ -14,47 +15,28 @@ export const ChatDisplay: React.FC<Props> = () => {
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) => [
...prevInstances,
<ChatInstance icon={<AccountCircleIcon />} 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,
<ChatInstance icon={<KathLogo />} 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,
// <ChatInstance icon={<KathLogo />} author={applicationContext.name} content={randomMockUpText} />,
// ]);
// setIsInputDisabled(false);
// }, 1000);
///////////////////////////////////////
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {}

Expand Down Expand Up @@ -50,15 +50,15 @@ export const ExtensionDisplay: React.FC<Props> = () => {
</Typography>
</Box>
</Box>
{/* <Box
<Box
display={'flex'}
flexDirection={'column'}
height={'calc(100% - 80px)'}
width={'100%'}
sx={{ backgroundColor: Theme.palette.background.paper }}
>
{selectedTab === 'Functionality' ? <FunctionalityInstance /> : <HistoryInstance />}
</Box> */}
</Box>
</Box>
);
};
147 changes: 147 additions & 0 deletions GUI/front-end/src/components/displays/extension/FileInstance.tsx
Original file line number Diff line number Diff line change
@@ -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<string[]>([]);
const [file, setFile] = useState<string | ArrayBuffer | null>(null);
const [fileName, setFileName] = useState<string>('');

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 && (
<>
<Typography>{languageContext.language === 'en' ? 'Files' : 'Failai'}</Typography>
<Box
sx={{
color: Theme.palette.text.secondary,
backgroundColor: Theme.palette.background.default,
borderRadius: '10px',
padding: '10px',
px: '10px',
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
}}>
<FileInput onFileSelect={onFileSelect} />
{file && fileName && (fileName)}
</Box>
<Button
variant="contained"
sx={{
color: Theme.palette.text.secondary,
backgroundColor: Theme.palette.background.default,
borderRadius: '10px',
height: '40px',
px: '10px',
marginBottom: '10px',
marginTop: '10px',
}}
fullWidth
disabled={!file || !fileName}
onClick={() => createNewFile()}
>
{languageContext.language === 'en' ? 'Upload file' : 'Įkelti failą'}
</Button>
{
files?.length > 0 ? (
files.map((file) => (
<Typography key={file}>
{file}
<IconButton
edge="end"
aria-label="delete"
size="small"
onClick={ async () => {
const data = {
workspace: workspaceContext.workspace,
file_name: file,
}

await deleteFile.mutateAsync(data);
}}
>
<DeleteForeverIcon sx={{ height: '30px' }} />
</IconButton>
</Typography>
))
) : (
<Typography>{languageContext.language === 'en' ? 'No files' : 'Nėra failų'}</Typography>
)}
</Box>
</>
)}
</>
);
}

export default FileInstance;
Loading
Loading