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

User annotation libraries #110

Merged
merged 5 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion apps/server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def create_app():
# only retrieves feature library that already exists after setup
# might create a libarary in the future
def create_feature_library(part_library_file_name):
if part_library_file_name.startswith('https://synbiohub.org'): #if url, return the obj if indexed with url(sbh downloads only)
if ('synbiohub.org' in part_library_file_name): #if url, return the obj if indexed with url(sbh downloads only)
if part_library_file_name in FEATURE_LIBRARIES:
return FEATURE_LIBRARIES[part_library_file_name]
else: #for locally stored sbh collections(indexed with file name for annotation)
Expand Down Expand Up @@ -200,6 +200,7 @@ def run_synbict(sbol_content: str, part_library_file_names: list[str]) -> tuple[
target_library = FeatureLibrary([target_doc])
# feature_library = FEATURE_LIBRARIES[0]
feature_library = create_feature_library(part_lib_f_name)
print(f"feature library for {part_lib_f_name}: {feature_library}")
min_feature_length = 10
annotater = FeatureAnnotater(feature_library, min_feature_length)
min_target_length = 10
Expand Down Expand Up @@ -423,6 +424,43 @@ def annotate_text():
biobert_result = run_biobert(free_text)
return {"text": free_text, "annotations": biobert_result}

@app.post("/api/importUserLibrary")
def import_library():
request_data = request.get_json()
SBHSessionToken = request_data['sessionToken']
collectionURL = request_data['url']

headers = {
"Accept": "text/plain",
"X-authorization": SBHSessionToken
}

response = requests.get(collectionURL, headers=headers)

# Check if the request was successful
if response.status_code == 200:
feature_doc = sbol2.Document()
feature_doc.readString(response.text)
FEATURE_LIBRARIES[collectionURL] = FeatureLibrary([feature_doc])
print(f"all libraries: {FEATURE_LIBRARIES.keys()}")

return {"response": response.text}
else:
print(f"Request failed with status code: {response.status_code}")
return {"response": response.text}

@app.post("/api/deleteUserLibrary")
def remove_library():
request_data = request.get_json()
collectionURL = request_data['url']

if FEATURE_LIBRARIES[collectionURL]: del FEATURE_LIBRARIES[collectionURL]
else: return {"response": "Library does not exist"}

print(f"\nupdated libraries: {FEATURE_LIBRARIES.keys()}")

return {"response": "Library successfully deleted"}

# if __name__ == '__main__':
# app.run(debug=True,host='0.0.0.0',port=5000)
if __name__ == "__main__":
Expand Down
19 changes: 14 additions & 5 deletions apps/web/src/components/CurationForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function SynBioHubClient({opened, onClose, setIsInteractingWithSynBioHub, synBio
);
}

function SynBioHubClientLogin({ synBioHubs }) {
export function SynBioHubClientLogin({ synBioHubs }) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [inputError, setInputError] = useState(false);
Expand All @@ -62,7 +62,15 @@ function SynBioHubClientLogin({ synBioHubs }) {
data={synBioHubs}
onChange={(v) => {
setWorkingSynBioHubUrlPrefix(v);
}}
}}
searchable
creatable
getCreateLabel={(query) => `Custom SBH: ${query}`}
onCreate={(query) => {
const item = { value: query, label: query };
synBioHubs.push(query)
return item;
}}
/>
<TextInput
label="Email"
Expand All @@ -87,7 +95,7 @@ function SynBioHubClientLogin({ synBioHubs }) {
const params = new URLSearchParams();
params.append('email', email);
params.append('password', password);
const synbiohub_url_login = "https:/" + "/synbiohub.org/login";
const synbiohub_url_login = workingSynBioHubUrlPrefix + "/login";
setIsLoading(true);
const response = await fetch(synbiohub_url_login, {
method: "POST",
Expand All @@ -99,7 +107,7 @@ function SynBioHubClientLogin({ synBioHubs }) {
if (response.ok) {
setInputError(false);
const session_token = await response.text();
// setSynBioHubSessionToken(session_token);
localStorage.setItem("synBioHubs", JSON.stringify(synBioHubs))
loginToSynBioHubFn(session_token, workingSynBioHubUrlPrefix);

} else if (response.status == 401) {
Expand Down Expand Up @@ -440,7 +448,8 @@ export default function CurationForm({ }) {
const loadSynBioHubs = async () => {
const response = await fetch("https://wor.synbiohub.org/instances");
const registries = await response.json();
setSynBioHubs(registries.map(r => r.uriPrefix));
if (localStorage.getItem("synBioHubs")) setSynBioHubs(JSON.parse(localStorage.getItem("synBioHubs")))
else setSynBioHubs(registries.map(r => r.uriPrefix));
};

function isValid(sequence) {
Expand Down
207 changes: 188 additions & 19 deletions apps/web/src/components/SequenceSection.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { useState, useEffect, forwardRef, createElement } from "react"
import { useForceUpdate } from "@mantine/hooks"
import { Checkbox } from '@mantine/core';
import { Button, Center, Group, Loader, NavLink, Space, CopyButton, ActionIcon, Tooltip, Textarea, MultiSelect, Text, Highlight} from "@mantine/core"
import { Checkbox, CloseButton, Flex, Grid, SegmentedControl, Select, Title } from '@mantine/core';
import { Button, Center, Group, Stack, Loader, Modal, NavLink, Space, CopyButton, ActionIcon, Tooltip, Textarea, MultiSelect, Text, Highlight} from "@mantine/core"
import { FiDownloadCloud } from "react-icons/fi"
import { FaCheck, FaPencilAlt, FaPlus, FaTimes, FaArrowRight } from "react-icons/fa"
import { mutateDocument, mutateSequencePartLibrariesSelected, useAsyncLoader, useStore } from "../modules/store"
import AnnotationCheckbox from "./AnnotationCheckbox"
import FormSection from "./FormSection"
import SequenceHighlighter from "./SequenceHighlighter"
import { Copy, Check } from "tabler-icons-react"
import { showErrorNotification } from '../modules/util'
import { showErrorNotification, showNotificationSuccess } from "../modules/util"
import "../../src/sequence-edit.css"
import { HighlightWithinTextarea } from 'react-highlight-within-textarea'
import { openConfirmModal, openContextModal } from "@mantine/modals"
import { SynBioHubClientLogin } from "./CurationForm";
import { importLibrary } from "../modules/api";

const WORDSIZE = 8;

Expand Down Expand Up @@ -143,21 +146,7 @@ function Sequence({ colors }) {
}
style={{ maxWidth: "800px" }}
>
{workingSequence !== false ? //TextArea is the editor // TODO: add highlight in TextArea
// <Textarea
// size="md"
// minRows={20}
// value={workingSequence}
// onChange={event => {
// const textArea = event.currentTarget;
// const start = textArea.selectionStart;
// const end = textArea.selectionEnd;
// // isValid?nothing : update the state
// console.log("isInValid: ", start);
// setWorkingSequence(textArea.value); //value is the value of ACTG, reformat is adding spaces, call validate function
// }}
// styles={{ input: { font: "14px monospace", lineHeight: "1.5em", error: true } }}
// />
{workingSequence !== false ?
<HighlightWithinTextarea
value={workingSequence}
highlight={{
Expand Down Expand Up @@ -235,6 +224,21 @@ function Annotations({ colors }) {

const { isActive, setActive } = useStore(s => s.sequenceAnnotationActions)
const sequence = useStore(s => s.document?.root.sequence)?.toLowerCase()

const libraryImported = useStore(s => s.libraryImported)
const isLoggedInToSynBioHub = useStore(s => s.isLoggedInToSomeSynBioHub);
const [ isInteractingWithSynBioHub, setIsInteractingWithSynBioHub ] = useState(false);
const [ isImportingLibrary, setIsImportingLibrary ] = useState(false);
const [ synBioHubs, setSynBioHubs ] = useState([]);

useStore(s => s.libraryImported);

const loadSynBioHubs = async () => {
const response = await fetch("https://wor.synbiohub.org/instances");
const registries = await response.json();
if (localStorage.getItem("synBioHubs")) setSynBioHubs(JSON.parse(localStorage.getItem("synBioHubs")))
else setSynBioHubs(registries.map(r => r.uriPrefix));
};

const sequencePartLibraries = [
{ value: 'local_libraries', label: 'SeqImprove Local Libraries'},
Expand Down Expand Up @@ -263,6 +267,9 @@ function Annotations({ colors }) {
];

const [sequencePartLibrariesSelected, setSequencePartLibrariesSelected] = useState([]);
const importedLibraries = useStore(s => s.importedLibraries)
const toggleLibrary = useStore(s => s.toggleImportedLibraries)
const removeLibrary = useStore(s => s.removeImportedLibrary)


const AnnotationCheckboxContainer = forwardRef((props, ref) => (
Expand All @@ -272,10 +279,16 @@ function Annotations({ colors }) {
));

const handleAnalyzeSequenceClick = () => {
if (sequencePartLibrariesSelected.length > 0) loadSequenceAnnotations()
const libs = importedLibraries.filter((lib) =>
lib.enabled == true)
if (sequencePartLibrariesSelected.length > 0 || libs.length > 0) {
loadSequenceAnnotations(libs)
}
else showErrorNotification('No libraries selected ', 'Select one or more libraries to continue')
}

const handleClose = (library) => {removeLibrary(library)};

return (
<FormSection title="Sequence Annotations" key="Sequence Annotations">
{annotations.map((anno, i) =>
Expand Down Expand Up @@ -323,6 +336,52 @@ function Annotations({ colors }) {
})}
/>


{libraryImported && <Stack mt="sm" gap="xs">
{importedLibraries.map((library, index) => (
<Grid key={index}>
<Grid.Col span={10}>
<Checkbox
label={library.label}
checked={library.enabled}
onChange={() => toggleLibrary(index)}
key={index}
/>
</Grid.Col>
<Grid.Col span={2}>
<Tooltip label="Delete from server memory">
<CloseButton
onClick={() => handleClose(library)}
/>
</Tooltip>
</Grid.Col>
</Grid>
))}
</Stack>
}

<NavLink
label="Import Library"
icon={<FaPlus />}
variant="subtle"
active={true}
color="blue"
onClick={() => {
loadSynBioHubs();
setIsInteractingWithSynBioHub(true);
}}
sx={{ borderRadius: 6 }}
/>

<SynBioHubClient
opened={isInteractingWithSynBioHub}
setIsInteractingWithSynBioHub={setIsInteractingWithSynBioHub}
onClose={() => setIsInteractingWithSynBioHub(false)}
setOpened={setIsInteractingWithSynBioHub}
synBioHubs={synBioHubs}
setIsImportingLibrary={setIsImportingLibrary}
/>

{loading ?
<Center>
<Loader my={30} size="sm" variant="dots" /> :
Expand All @@ -333,6 +392,7 @@ function Annotations({ colors }) {
variant="subtle"
active={true}
color="blue"
disabled={isImportingLibrary}
onClick={handleAnalyzeSequenceClick}
sx={{ borderRadius: 6 }}
/>
Expand All @@ -341,6 +401,115 @@ function Annotations({ colors }) {
)
}

function SynBioHubClient({opened, onClose, setIsInteractingWithSynBioHub, synBioHubs, setIsImportingLibrary}) {
const isLoggedInToSynBioHub = useStore(s => s.isLoggedInToSomeSynBioHub);

return (
<Modal
title="SynBioHub"
opened={opened}
onClose={onClose}
size={"auto"}
>
{isLoggedInToSynBioHub ?
<SynBioHubClientSelect setIsInteractingWithSynBioHub={setIsInteractingWithSynBioHub} setIsImportingLibrary={setIsImportingLibrary}/> :
<SynBioHubClientLogin synBioHubs={synBioHubs} />
}
</Modal>
);
}

function SynBioHubClientSelect({ setIsInteractingWithSynBioHub, setIsImportingLibrary }) {
const synBioHubUrlPrefix = useStore(s => s.synBioHubUrlPrefix);
const [ synBioHubSessionToken, _ ] = useState(sessionStorage.getItem('SynBioHubSessionToken'));
const [inputError, setInputError] = useState(false);
const [ isLoading, setIsLoading ] = useState(false);
const libraryImported = useStore(s => s.libraryImported);
const [ id, setID ] = useState("collection_id");
const [ rootCollectionsLoaded, setRootCollectionsLoaded ] = useState(false);
const [ rootCollectionsIDs, setRootCollectionsIDs ] = useState([]);
const [ rootCollections, setRootCollections ] = useState([]);
const [ rootCollectionURI, setRootCollectionURI ] = useState('');
const [ selectedCollectionID, selectCollectionID ] = useState('');

const xml = useStore(s => s.serializeXML());

(async () => {
if (!rootCollectionsLoaded) { // curl -X GET -H "Accept: text/plain" -H "X-authorization: 5ab3af6e-2ddd-4ac2-af76-d4285d2ffe03" https://synbiohub.org/rootCollections
console.log(synBioHubSessionToken);
const response2 = await fetch(synBioHubUrlPrefix + "/rootCollections", {
method: "GET",
headers: {
"Accept": "text/plain",
"X-authorization": synBioHubSessionToken,
},
});

const _rootCollections = await response2.json();

let regex = RegExp(synBioHubUrlPrefix.replace(/^https?/, 'https?') + "/user/.*");
const userRootCollections = _rootCollections.filter(collection => collection.uri.match(regex));
setRootCollections(userRootCollections);
setRootCollectionsIDs(userRootCollections.map(collection => collection.displayId));
setRootCollectionsLoaded(true);
}
})();

const [ inputErrorID, setInputErrorID ] = useState(false);
const importedLibraries = useStore(s => s.importedLibraries)
const addLibrary = useStore(s => s.addImportedLibrary)

return (
<Group>
<Title order={3}>Download from SynBioHub</Title>
<Group>
<Group>
{!rootCollectionsLoaded ?
<Center>
<Loader my={30} size="sm" variant="dots" />
</Center> :
<Select
label="Root Collection"
placeholder="Pick one"
data={rootCollectionsIDs}
onChange={(v) => {
setRootCollectionURI(rootCollections.find(collection => collection.displayId == v).uri)
selectCollectionID(v)
}}
searchable
/>
}

{isLoading ?
<Center>
<Loader my={30} size="sm" variant="dots" />
</Center> :
<Button onClick={async () => {
const params = new FormData();
//send to backend
params.append('rootCollections', rootCollectionURI);
// Create a Blob from the text
setIsLoading(true);
const response = importLibrary(synBioHubSessionToken, rootCollectionURI, setIsImportingLibrary)

if (response) {
setInputError(false);
setIsInteractingWithSynBioHub(false);
showNotificationSuccess("Success!", "Imported Library: " + selectedCollectionID + ".");
mutateDocument(useStore.setState, state => {state.libraryImported = true});
addLibrary({ value: rootCollectionURI, label: selectedCollectionID, enabled: false})
} else {
showErrorNotification("Failure.", "SynBioHub did not accept the request");
}
}}>
Submit
</Button>
}
</Group>
</Group>
</Group>
)
}

export default {
Sequence, Annotations
Expand Down
Loading