From 0eaf712cb51180cbf150fae2945317cce7dd5fe5 Mon Sep 17 00:00:00 2001 From: deep1401 <gandhi0869@gmail.com> Date: Tue, 4 Feb 2025 14:33:48 -0800 Subject: [PATCH] Created new Modal for creating a new evaluation task --- .../Experiment/Eval/EditEvalModal.tsx | 20 +- .../components/Experiment/Eval/Eval.tsx | 24 +- .../Experiment/Eval/EvalTasksTable.tsx | 1 + .../Experiment/Eval/NewEvalModal.tsx | 319 ++++++++++++++++++ 4 files changed, 336 insertions(+), 28 deletions(-) create mode 100644 src/renderer/components/Experiment/Eval/NewEvalModal.tsx diff --git a/src/renderer/components/Experiment/Eval/EditEvalModal.tsx b/src/renderer/components/Experiment/Eval/EditEvalModal.tsx index 83c8d2e8..8cfe1e01 100644 --- a/src/renderer/components/Experiment/Eval/EditEvalModal.tsx +++ b/src/renderer/components/Experiment/Eval/EditEvalModal.tsx @@ -1,6 +1,7 @@ import { useState, FormEvent, useEffect } from 'react'; import useSWR from 'swr'; + import * as chatAPI from 'renderer/lib/transformerlab-api-sdk'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; @@ -109,9 +110,6 @@ export default function TrainingModalLoRA({ } }, [experimentInfo, currentEvalName, pluginId]); - console.log('Experiment Info:', experimentInfo); - console.log("Current Eval Name:", currentEvalName); - // Function to check if any key in the config contains the word "dataset" const hasDatasetKey = (config: any) => { return Object.keys(config).some(key => key.toLowerCase().includes('dataset')); @@ -174,8 +172,7 @@ export default function TrainingModalLoRA({ const formJson = Object.fromEntries((formData as any).entries()); try { const result = await chatAPI.EXPERIMENT_EDIT_EVALUATION(experimentInfo?.id, currentEvalName, formJson) - alert(JSON.stringify(formJson, null, 2)); - console.log('Edit Evaluation Result:', result); + // alert(JSON.stringify(formJson, null, 2)); onClose(); } catch (error) { console.error('Failed to edit evaluation:', error); @@ -203,19 +200,6 @@ export default function TrainingModalLoRA({ height: '100%', justifyContent: 'space-between', }} - // onSubmit={(event: FormEvent<HTMLFormElement>) => { - // event.preventDefault(); - // const formData = new FormData(event.currentTarget); - // const formJson = Object.fromEntries((formData as any).entries()); - - // setNameInput(generateFriendlyName()); - // // Set the run name in formJson as template name - // // formJson.run_name = formJson.template_name; - // // - // // alert(JSON.stringify(formJson, null, 2)); - // const result = await chatAPI.EXPERIMENT_EDIT_EVALUATION(experimentInfo?.id, currentEvalName, formJson) - // onClose(); - // }} onSubmit={handleSubmit} > <Tabs diff --git a/src/renderer/components/Experiment/Eval/Eval.tsx b/src/renderer/components/Experiment/Eval/Eval.tsx index bc4b55bd..841ae8a7 100644 --- a/src/renderer/components/Experiment/Eval/Eval.tsx +++ b/src/renderer/components/Experiment/Eval/Eval.tsx @@ -29,6 +29,9 @@ import { generateFriendlyName } from 'renderer/lib/utils'; import DynamicPluginForm from '../DynamicPluginForm'; import EvalJobsTable from './EvalJobsTable.tsx'; import EvalTasksTable from './EvalTasksTable'; +import NewEvalModal from './NewEvalModal'; + + function getTemplateParametersForPlugin(pluginName, plugins) { if (!pluginName || !plugins) { @@ -109,13 +112,9 @@ export default function Eval({ {/* Plugins: {JSON.stringify(plugins)} */} - <Modal open={open} onClose={() => setOpen(false)}> + {/* <Modal open={open} onClose={() => setOpen(false)}> <ModalDialog> <ModalClose onClick={() => setOpen(false)} /> - {/* <DialogTitle>Add Evalation</DialogTitle> */} - {/* <DialogContent> - Select an evaluation to add to this experiment. - </DialogContent> */} <form onSubmit={(event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); @@ -123,10 +122,6 @@ export default function Eval({ const formJson = Object.fromEntries( (formData as any).entries() ); - // alert(JSON.stringify(formJson)); - - /* The way evals are defined right now, they need a unique name. This is a hack - until we have a better solution */ let nameOfThisEvaluation; if (formJson.run_name) { nameOfThisEvaluation = formJson.run_name; @@ -151,7 +146,16 @@ export default function Eval({ </Stack> </form> </ModalDialog> - </Modal> + </Modal> */} + <NewEvalModal + open={open} + onClose={() => { + setOpen(false); + }} + experimentInfo={experimentInfo} + pluginId={selectedPlugin} + currentEvalName={''} + /> {/* <Typography level="h1" mb={1}> Evaluate </Typography> */} diff --git a/src/renderer/components/Experiment/Eval/EvalTasksTable.tsx b/src/renderer/components/Experiment/Eval/EvalTasksTable.tsx index e51a5842..da2486a9 100644 --- a/src/renderer/components/Experiment/Eval/EvalTasksTable.tsx +++ b/src/renderer/components/Experiment/Eval/EvalTasksTable.tsx @@ -34,6 +34,7 @@ async function evaluationRun( ); } + export default function EvalTasksTable({ experimentInfo, experimentInfoMutate, diff --git a/src/renderer/components/Experiment/Eval/NewEvalModal.tsx b/src/renderer/components/Experiment/Eval/NewEvalModal.tsx new file mode 100644 index 00000000..8dc06125 --- /dev/null +++ b/src/renderer/components/Experiment/Eval/NewEvalModal.tsx @@ -0,0 +1,319 @@ +import { useState, FormEvent, useEffect } from 'react'; +import useSWR from 'swr'; + + +import * as chatAPI from 'renderer/lib/transformerlab-api-sdk'; +import Markdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { + Button, + FormControl, + FormHelperText, + FormLabel, + Input, + Modal, + ModalDialog, + Stack, + Tab, + TabList, + TabPanel, + Tabs, + Sheet, +} from '@mui/joy'; +import DynamicPluginForm from '../DynamicPluginForm'; +import TrainingModalDataTab from '../Train/TraningModalDataTab'; + +import { generateFriendlyName } from 'renderer/lib/utils'; +import exp from 'node:constants'; +const fetcher = (url) => fetch(url).then((res) => res.json()); + +function PluginIntroduction({ experimentInfo, pluginId }) { + const { data, error, isLoading } = useSWR( + chatAPI.Endpoints.Experiment.ScriptGetFile( + experimentInfo?.id, + pluginId, + 'info.md' + ), + fetcher + ); + + return ( + <> + <Markdown remarkPlugins={[remarkGfm]} className="editableSheetContent"> + {data && data != 'FILE NOT FOUND' + ? data + : 'No description for this plugin is availabe.'} + </Markdown> + </> + ); +} + +export default function TrainingModalLoRA({ + open, + onClose, + experimentInfo, + pluginId, + currentEvalName, +}: { + open: boolean; + onClose: () => void; + experimentInfo: any; + template_id?: string; + pluginId: string; + currentEvalName: string; +}) { + // Store the current selected Dataset in this modal + const [selectedDataset, setSelectedDataset] = useState(null); + const [config, setConfig] = useState({}); + const [hasDatasetKey, setHasDatasetKey] = useState(false); + const [nameInput, setNameInput] = useState(''); + const [currentTab, setCurrentTab] = useState(0); + + useEffect(() => { + setNameInput(generateFriendlyName()); + }, []); + + const { data, error, isLoading, mutate } = useSWR( + experimentInfo?.id && + pluginId && + chatAPI.Endpoints.Experiment.ScriptGetFile( + experimentInfo?.id, + pluginId, + 'index.json' + ), + fetcher + ); + +useEffect(() => { + if (data) { + let parsedData; + try { + parsedData = JSON.parse(data); //Parsing data for easy access to parameters} + // Set config as a JSON object with keys of the parameters and values of the default values + let tempconfig = {}; + if (parsedData && parsedData.parameters) { + tempconfig = Object.fromEntries( + Object.entries(parsedData.parameters).map(([key, value]) => [ + key, + value.default, + ]) + ); + } + setConfig(tempconfig); + // Set hasDataset to true in the parsed data, the dataset key is `true` + if (parsedData && parsedData.dataset) { + setHasDatasetKey(true); + } + + + } catch (e) { + console.error('Error parsing data', e); + parsedData = ''; + } + } + }, [pluginId, experimentInfo, config, data]); + + + // Fetch available datasets from the API + const { + data: datasets, + error: datasetsError, + isLoading: datasetsIsLoading, + } = useSWR(chatAPI.Endpoints.Dataset.LocalList(), fetcher); + + const { data: currentDatasetInfo, isLoading: currentDatasetInfoIsLoading } = + useSWR(() => { + if (selectedDataset === null) { + return null; + } + return chatAPI.Endpoints.Dataset.Info(selectedDataset); + }, fetcher); + + // Set config to the plugin config if it is available based on currentEvalName within experiment info + useEffect(() => { + if (experimentInfo && currentEvalName && pluginId) { + const evaluationsStr = experimentInfo.config?.evaluations; + if (typeof evaluationsStr === 'string') { + try { + const evaluations = JSON.parse(evaluationsStr); + if (Array.isArray(evaluations)) { + const evalConfig = evaluations.find( + (evalItem: any) => + evalItem.name === currentEvalName && + evalItem.plugin === pluginId + ); + if (evalConfig) { + setConfig(evalConfig.script_parameters); + } + } + } catch (error) { + console.error('Failed to parse evaluations JSON string:', error); + } + } + } + }, [experimentInfo, currentEvalName, pluginId]); + +// console.log('Experiment Info:', experimentInfo); +// console.log("Current Eval Name:", currentEvalName); +// console.log("Plugin ID:", pluginId); +// console.log("Config:", config); +// console.log("EXP CONFIG", experimentInfo?.config); + +// // Function to check if any key in the config contains the word "dataset" +// const hasDatasetKey = (config: any) => { +// return Object.keys(config).some(key => key.toLowerCase().includes('dataset')); +// }; + + if (!experimentInfo?.id) { + return 'Select an Experiment'; + } + + const currentModel = experimentInfo?.config?.foundation_filename + ? experimentInfo?.config?.foundation_filename + : experimentInfo?.config?.foundation; + + // Set config to the plugin config if it is available based on currentEvalName within experiment info + + function TrainingModalFirstTab() { + return ( + <Stack spacing={2}> + <FormControl> + <FormLabel>Evaluation Task Name</FormLabel> + <Input + required + autoFocus + value={nameInput} //Value needs to be stored in a state variable otherwise it will not update on change/update + onChange={(e) => setNameInput(e.target.value)} + name="template_name" + size="lg" + /> + <FormHelperText> + Give this specific evaluation recipe a unique name + </FormHelperText> + </FormControl> + <FormLabel>Info</FormLabel> + <Sheet color="neutral" variant="soft"> + <Stack direction="column" justifyContent="space-evenly" gap={2} p={2}> + <FormControl sx={{ flex: 1 }}> + <FormLabel>Plugin:</FormLabel> + <Input + readOnly + value={pluginId} + variant="soft" + name="plugin_name" + /> + </FormControl> + <input hidden value={currentModel} name="model_name" readOnly /> + <input + hidden + value={experimentInfo?.config?.foundation_model_architecture} + name="model_architecture" + readOnly + /> + </Stack> + </Sheet> + </Stack> + ); + } + const handleSubmit = async (event: FormEvent<HTMLFormElement>) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const formJson = Object.fromEntries((formData as any).entries()); + try { + if (!formJson.run_name) { + formJson.run_name = formJson.template_name; + } + // Remove the template_name key from a formJson object + const template_name = formJson.template_name; + delete formJson.template_name; + + const result = await chatAPI.EXPERIMENT_ADD_EVALUATION(experimentInfo?.id, template_name, pluginId, formJson); + // alert(JSON.stringify(formJson, null, 2)); + onClose(); + } catch (error) { + console.error('Failed to edit evaluation:', error); + } + }; + + return ( + <Modal open={open}> + <ModalDialog + sx={{ + width: '70dvw', + transform: 'translateX(-50%)', // This undoes the default translateY that centers vertically + top: '5dvh', + overflow: 'auto', + maxHeight: '70dvh', + minHeight: '60dvh', + height: '100%', + }} + > + <form + id="evaluation-form" + style={{ + display: 'flex', + flexDirection: 'column', + height: '100%', + justifyContent: 'space-between', + }} + onSubmit={handleSubmit} + > + <Tabs + aria-label="evaluation Template Tabs" + value={currentTab} + onChange={(event, newValue) => setCurrentTab(newValue)} + sx={{ borderRadius: 'lg', display: 'flex', overflow: 'hidden' }} + > + <TabList> + <Tab>Introduction</Tab> + <Tab>Name</Tab> + <Tab>Plugin Config</Tab> + {hasDatasetKey && <Tab>Dataset</Tab>} + </TabList> + <TabPanel value={0} sx={{ p: 2, overflow: 'auto' }}> + <PluginIntroduction + experimentInfo={experimentInfo} + pluginId={pluginId} + /> + </TabPanel> + <TabPanel value={1} sx={{ p: 2, overflow: 'auto' }} keepMounted> + <TrainingModalFirstTab /> + </TabPanel> + <TabPanel value={2} sx={{ p: 2, overflow: 'auto' }} keepMounted> + <DynamicPluginForm + experimentInfo={experimentInfo} + plugin={pluginId} + /> + </TabPanel> + {hasDatasetKey && (<TabPanel value={3} sx={{ p: 2, overflow: 'auto' }} keepMounted> + <> + <TrainingModalDataTab + datasetsIsLoading={datasetsIsLoading} + datasets={datasets} + selectedDataset={selectedDataset} + setSelectedDataset={setSelectedDataset} + currentDatasetInfoIsLoading={currentDatasetInfoIsLoading} + currentDatasetInfo={currentDatasetInfo} + templateData={null} + injectIntoTemplate={null} + experimentInfo={experimentInfo} + pluginId={pluginId} + /> + </> + </TabPanel> + )} + </Tabs> + <Stack spacing={2} direction="row" justifyContent="flex-end"> + <Button color="danger" variant="soft" onClick={() => onClose()}> + Cancel + </Button> + <Button variant="soft" type="submit" color="success"> + Save Evaluation Task + </Button> + </Stack> + </form> + {/* {JSON.stringify(config, null, 2)} */} + </ModalDialog> + </Modal> + ); +} -- GitLab