diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 5fd81cadf..eb01cdbae 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -10,6 +10,7 @@ import List from "@mui/material/List"; import Divider from "@mui/material/Divider"; import IconButton from "@mui/material/IconButton"; import MenuIcon from "@mui/icons-material/Menu"; +import BuildIcon from '@mui/icons-material/Build'; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import SearchDatasetIcon from "@mui/icons-material/Search"; @@ -301,6 +302,17 @@ export default function PersistentDrawerLeft(props) { + + + + + + + + + + + { + // const dispatch = useDispatch(); + // @ts-ignore + // const getMetadatDefinitions = ( + // name: string | null, + // skip: number, + // limit: number + // ) => dispatch(fetchMetadataDefinitions(name, skip, limit)); + // const createDatasetMetadata = ( + // datasetId: string | undefined, + // metadata: MetadataIn + // ) => dispatch(postDatasetMetadata(datasetId, metadata)); + // const = (formData: FormData) => + // dispatch(datasetCreated(formData)); + // const newDataset = useSelector( + // (state: RootState) => state.dataset.newDataset + // ); + + // useEffect(() => { + // getMetadatDefinitions(null, 0, 100); + // }, []); + + // const metadataDefinitionList = useSelector( + // (state: RootState) => state.metadata.metadataDefinitionList + // ); + const [errorOpen, setErrorOpen] = useState(false); + + // const [datasetRequestForm, setdatasetRequestForm] = useState({}); + // const [metadataRequestForms, setMetadataRequestForms] = useState({}); + // const [allowSubmit, setAllowSubmit] = React.useState(false); + + // const history = useNavigate(); + + // const checkIfFieldsAreRequired = () => { + // let required = false; + + // metadataDefinitionList.forEach((val, idx) => { + // if (val.fields[0].required) { + // required = true; + // } + // }); + + // return required; + // }; + + // step 1 + // const onDatasetSave = (formData: any) => { + // setdatasetRequestForm(formData); + + // // If no metadata fields are marked as required, allow user to skip directly to submit + // if (checkIfFieldsAreRequired()) { + // setAllowSubmit(false); + // } else { + // setAllowSubmit(true); + // } + + // handleNext(); + // }; + // step 2 + // const setMetadata = (metadata: any) => { + // // TODO wrap this in to a function + // setMetadataRequestForms((prevState) => { + // // merge the contents field; e.g. lat lon + // if (metadata.definition in prevState) { + // const prevContent = prevState[metadata.definition].content; + // metadata.content = { ...prevContent, ...metadata.content }; + // } + // return { ...prevState, [metadata.definition]: metadata }; + // }); + + // metadataDefinitionList.map((val, idx) => { + // if (val.fields[0].required) { + // // Condition checks whether the current updated field is a required one + // if ( + // val.name == metadata.definition || + // val.name in metadataRequestForms + // ) { + // setAllowSubmit(true); + // return true; + // } else { + // setAllowSubmit(false); + // return false; + // } + // } + // }); + // }; + + // step + // const [activeStep, setActiveStep] = useState(0); + // const handleNext = () => { + // setActiveStep((prevActiveStep) => prevActiveStep + 1); + // }; + // const handleBack = () => { + // setActiveStep((prevActiveStep) => prevActiveStep - 1); + // }; + + // // finish button post dataset; dataset ID triggers metadata posting + // const handleFinish = () => { + // // create dataset + // createDataset(datasetRequestForm); + // }; + + // useEffect(() => { + // if (newDataset.id) { + // // post new metadata + // Object.keys(metadataRequestForms).map((key) => { + // createDatasetMetadata(newDataset.id, metadataRequestForms[key]); + // }); + + // //reset dataset so next creation can be done + // dispatch(resetDatsetCreated()); + // setMetadataRequestForms({}); + // setdatasetRequestForm({}); + + // // zoom into that newly created dataset + // history(`/datasets/${newDataset.id}`); + // } + // }, [newDataset]); + + useEffect(() => { + fetch('https://huggingface.co/api/models') + .then(response => response.json()) + .then(data => { + // Sort the models by downloads before mapping to modelNames + data.sort((a: any, b: any) => b.downloads - a.downloads); + const modelNames = data.map((model: any) => model.id); + setHuggingFaceModelNames(modelNames); + }) + .catch(error => console.error('Error:', error)); + }, []); + + + const [huggingFaceModelNames, setHuggingFaceModelNames] = useState(["meta/llama2-70B-chat", "google/Flan-t5-large"]); + const [selectedHuggingFaceModelName, setSelectedHuggingFaceModelName] = useState(""); + + const handleHuggingFaceModelSubmit = () => { + const selectedModelName = document.getElementById('huggingface-model-name')?.value; // type: ignore + if (selectedModelName) { + alert(`Selected HuggingFace Model: ${selectedModelName}`); + } else { + alert('No model selected'); + } + }; + + return ( + + + {/*Error Message dialogue*/} + + + + + +

+ Run any HuggingFace model on your data, no code +

+ + + {/*HuggingFace inference*/} + + + Run HuggingFace inference over your files + + You can select any model on the HuggingFace Hub. Then you can run that model over your files, with no code, no infrastructure and all without ever leaving this GUI. + The below models are sorted by number of downloads on HuggingFace Hub. + + + {/* HuggingFace model name input */} + ( + + )} + onInputChange={(event, newInputValue) => { + setSelectedHuggingFaceModelName(newInputValue); + }} + /> + + + {/*access*/} + + + Security Level + + + Choose your endpoint's level of privacy. + + + } label="Protected" /> + + A Protected Endpoint is available from the Internet, secured with TLS/SSL and requires a valid Clowder API Token for authentication. + + } label="Public" /> + + A Public Endpoint is available from the internet, secured with TLS/SSL and requires NO authentication. + + + + + + + + {/* */} + + {/* TODO: possibly implement the Stepper box for iterative form filling... */} + + + {/* + + + + Basic Information + + + A dataset is a container for files, folders and metadata. + + + + + + + + + + Required Metadata + + {metadataDefinitionList.length > 0 ? ( + + This metadata is required when creating a new dataset. + + ) : ( + No metadata required. + )} + + + + + + <> + + + + + + + + */} +
+
+
+ ); +}; + +const IntroMessage = () => { + return ( + <> +

+ Create a new Extractor +

+

+ Why Extractors? +

+

+ At its heart, extractors run a Python function over every file in a dataset. They can run at the click of a button in Clowder web UI or like an event listener every time a new file is uploaded. +

+

+ Extractors are performant, parallel-by-default, web-native Clowder Extractors using CodeFlare & Ray.io. + Check out our 📜 blog post on the incredible speed and developer experience of building on Ray. +

+

+ 🧠 ML Inference +

+

+ Need to process a lot of files? This is great for ML inference and data pre-processing. These examples work out of the box or you can swap in your own model! +

+

+ TODO: These may examples need updating because they're traditional extractors, not this 100% GUI extractor version. + + PyTorch example +
+
+ + TensorFlow Keras example +
+
+ + Huggingface Transformers example +
+
+

+

+ 🔁 Event-driven +

+

+ Have daily data dumps? Extractors are perfect for event-driven actions. They will run code every time a file is uploaded. Uploads themselves can be automated via PyClowder for a totally hands-free data pipeline. +

+ {/*

+ Clowder's rich scientific data ecosystem +

+

+ Benefit from the rich featureset & full extensibility of Clowder: +

+
    +
  • Instead of files on your laptop, use Clowder to add collaborators & share datasets via the browser.
  • +
  • Benefiting scientists, we work with (~)every filetype and have rich extensibility for any job you need to run.
  • +
*/} + + ) +} \ No newline at end of file diff --git a/frontend/src/components/listeners/CreateListenerModal.tsx b/frontend/src/components/listeners/CreateListenerModal.tsx new file mode 100644 index 000000000..5a78fd0f1 --- /dev/null +++ b/frontend/src/components/listeners/CreateListenerModal.tsx @@ -0,0 +1,45 @@ +import React from "react"; + +import { Box, Button } from "@mui/material"; + +import Form from "@rjsf/material-ui"; +import datasetSchema from "../../schema/datasetSchema.json"; +import { FormProps } from "@rjsf/core"; +import { ClowderRjsfTextWidget } from "../styledComponents/ClowderRjsfTextWidget"; +import { ClowderRjsfSelectWidget } from "../styledComponents/ClowderRjsfSelectWidget"; +import { ClowderRjsfErrorList } from "../styledComponents/ClowderRjsfErrorList"; +import { ClowderRjsfTextAreaWidget } from "../styledComponents/ClowderRjsfTextAreaWidget"; + +type CreateDatasetModalProps = { + onSave: any; +}; + +const widgets = { + TextWidget: ClowderRjsfTextWidget, + TextAreaWidget: ClowderRjsfTextAreaWidget, + SelectWidget: ClowderRjsfSelectWidget, +}; + +export const CreateListenerModal: React.FC = ( + props: CreateDatasetModalProps +) => { + const { onSave } = props; + + return ( +
["schema"]} + uiSchema={datasetSchema["uiSchema"] as FormProps["uiSchema"]} + onSubmit={({ formData }) => { + onSave(formData); + }} + ErrorList={ClowderRjsfErrorList} + > + + + + + ); +}; diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index 81895c7a8..71777374b 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -12,6 +12,7 @@ import { CreateMetadataDefinitionPage } from "./components/metadata/CreateMetada import { Dataset as DatasetComponent } from "./components/datasets/Dataset"; import { File as FileComponent } from "./components/files/File"; import { CreateDataset } from "./components/datasets/CreateDataset"; +import { CreateListener } from "./components/listeners/CreateListener"; import { Groups as GroupListComponent } from "./components/groups/Groups"; import { Group as GroupComponent } from "./components/groups/Group"; @@ -190,6 +191,14 @@ export const AppRoutes = (): JSX.Element => { } /> + + + + } + />