diff --git a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx index 5e78205c87a15bc44c2c835859c29cb1c983c1f1..00c22f7bdb3c4939a109da9c429bf44a61bd72c1 100644 --- a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx +++ b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx @@ -31,10 +31,7 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { data: recipesData, error: recipesError, isLoading: isLoading, - } = useSWR( - chatAPI.Endpoints.Recipes.Gallery(), - fetcher - ); + } = useSWR(chatAPI.Endpoints.Recipes.Gallery(), fetcher); const recipes = recipesData; @@ -53,7 +50,7 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { .catch((e) => { console.error(e); alert(e); - return ""; + return ''; }); if (!recipe_text) { handleClose(); @@ -64,34 +61,36 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { // We should use the name in the recipe, not the filename! // const recipe_name = generateFriendlyName(); // For now: Remove the last . and extension from the filename - const recipe_name = file.name.replace(/\.[^/.]+$/, ""); + const recipe_name = file.name.replace(/\.[^/.]+$/, ''); uploadRecipe(recipe_name, recipe_text); }; // Given a recipe string, uploads to API. const uploadRecipe = async (recipe_name, recipe_text) => { - setUploading(true); //This is for the loading spinner const response = await fetch( - chatAPI.Endpoints.Recipes.Import(recipe_name), { - method: 'POST', - body: recipe_text, - }).then((response) => { - if (response.ok) { - return response.json(); - } else { - const error_msg = `${response.statusText}`; - throw new Error(error_msg); + chatAPI.Endpoints.Recipes.Import(recipe_name), + { + method: 'POST', + body: recipe_text, } - }) - .then((data) => { - console.log('Server response:', data); - }) - .catch((error) => { - alert(error); - }); - + ) + .then((response) => { + if (response.ok) { + return response.json(); + } else { + const error_msg = `${response.statusText}`; + throw new Error(error_msg); + } + }) + .then((data) => { + console.log('Server response:', data); + }) + .catch((error) => { + alert(error); + }); + setUploading(false); handleClose(); }; @@ -105,19 +104,19 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { <Box sx={{ maxHeight: '450px', overflow: 'auto' }}> <Table - aria-labelledby="tableTitle" - stickyHeader - hoverRow - sx={{ - '--TableCell-headBackground': (theme) => - theme.vars.palette.background.level1, - '--Table-headerUnderlineThickness': '1px', - '--TableRow-hoverBackground': (theme) => - theme.vars.palette.background.level1, - height: '100px', - overflow: 'auto', - }} - > + aria-labelledby="tableTitle" + stickyHeader + hoverRow + sx={{ + '--TableCell-headBackground': (theme) => + theme.vars.palette.background.level1, + '--Table-headerUnderlineThickness': '1px', + '--TableRow-hoverBackground': (theme) => + theme.vars.palette.background.level1, + height: '100px', + overflow: 'auto', + }} + > <thead> <tr> <th style={{ width: 150, padding: 12 }}>Name</th> @@ -126,55 +125,54 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { </tr> </thead> <tbody> - {!isLoading && recipes && recipes.map((row) => ( - <tr key={row.metadata?.name}> - <td> - <Typography fontWeight="lg"> - {row.metadata?.name} - </Typography> - </td> - <td> - <Typography fontWeight="sm"> - {row.metadata?.description} - </Typography> - </td> - <td> - <Button - size="sm" - onClick={() => { - const recipe_text = YAML.stringify(row); - uploadRecipe(row.metadata?.name, recipe_text); - }} - > - Add - </Button> - </td> - </tr> - ))} - {isLoading && ( - <tr> - <td colSpan={5}> - <CircularProgress color="primary" /> - <Typography + {!isLoading && + recipes && + recipes.map((row) => ( + <tr key={row.metadata?.name}> + <td> + <Typography fontWeight="lg"> + {row.metadata?.name} + </Typography> + </td> + <td> + <Typography fontWeight="sm"> + {row.metadata?.description} + </Typography> + </td> + <td> + <Button + size="sm" + onClick={() => { + const recipe_text = YAML.stringify(row); + uploadRecipe(row.metadata?.name, recipe_text); + }} + > + Use + </Button> + </td> + </tr> + ))} + {isLoading && ( + <tr> + <td colSpan={5}> + <CircularProgress color="primary" /> + <Typography level="body-lg" justifyContent="center" margin={5} - > - Loading recipes... - </Typography> - </td> - </tr> - )} + > + Loading recipes... + </Typography> + </td> + </tr> + )} </tbody> </Table> </Box> <Divider sx={{ my: 2 }} /> - <Typography - level="title-lg" - > - </Typography> + <Typography level="title-lg"></Typography> <Box // Making the modal a set size sx={{ display: 'flex', diff --git a/src/renderer/components/Experiment/Train/TrainLoRA.tsx b/src/renderer/components/Experiment/Train/TrainLoRA.tsx index 3aa3a889c854f1c0d627c9cd9b2ec3c3bd1a2681..bb58b76c0f1373640f62e1308401009c348e9158 100644 --- a/src/renderer/components/Experiment/Train/TrainLoRA.tsx +++ b/src/renderer/components/Experiment/Train/TrainLoRA.tsx @@ -30,6 +30,7 @@ import { LineChartIcon, Plug2Icon, PlusIcon, + ScrollIcon, StopCircle, StopCircleIcon, Trash2Icon, @@ -53,12 +54,13 @@ function formatTemplateConfig(config): ReactElement { const c = JSON.parse(config); // Remove the author/full path from the model name for cleanliness - const short_model_name = c.model_name.split("/").pop(); + const short_model_name = c.model_name.split('/').pop(); const r = ( <> <b>Model:</b> {short_model_name} <br /> - <b>Dataset:</b> {c.dataset_name} <FileTextIcon size={14} /><br /> + <b>Dataset:</b> {c.dataset_name} <FileTextIcon size={14} /> + <br /> {/* <b>Adaptor:</b> {c.adaptor_name} <br /> */} {/* {JSON.stringify(c)} */} </> @@ -187,6 +189,21 @@ export default function TrainLoRA({ experimentInfo }) { New </MenuButton> <Menu sx={{ maxWidth: '300px' }}> + <MenuItem disabled variant="soft" color="primary"> + <Typography level="title-sm"> + Start from a pre-existing recipe: + </Typography> + </MenuItem> + <MenuItem + onClick={() => { + setImportRecipeModalOpen(true); + }} + > + <ListItemDecorator> + <ScrollIcon /> + </ListItemDecorator> + <Typography level="title-sm">Recipe Library</Typography> + </MenuItem> <MenuItem disabled variant="soft" color="primary"> <Typography level="title-sm"> Select a training plugin from the following list: @@ -220,23 +237,6 @@ export default function TrainLoRA({ experimentInfo }) { </div> </MenuItem> ))} - <MenuItem disabled variant="soft" color="primary"> - <Typography level="title-sm"> - Or upload an existing recipe file: - </Typography> - </MenuItem> - <MenuItem - onClick={() => { - setImportRecipeModalOpen(true); - }} - > - <ListItemDecorator> - <UploadIcon /> - </ListItemDecorator> - <Typography level="title-sm"> - Import Recipe - </Typography> - </MenuItem> </Menu> </Dropdown> </Stack> @@ -325,19 +325,20 @@ export default function TrainLoRA({ experimentInfo }) { </Button> <IconButton onClick={async () => { - await fetch( - chatAPI.Endpoints.Recipes.Export(row[0]) - ).then((response) => response.blob()) + await fetch( + chatAPI.Endpoints.Recipes.Export(row[0]) + ) + .then((response) => response.blob()) .then((blob) => { // Create blob link to download const url = window.URL.createObjectURL( - new Blob([blob]), + new Blob([blob]) ); const link = document.createElement('a'); link.href = url; link.setAttribute( 'download', - `recipe.yaml`, + `recipe.yaml` ); // Append to html link, click and remove diff --git a/src/renderer/components/Header.tsx b/src/renderer/components/Header.tsx index 9e590485680bf1327decc4b394d0ce5433485eb8..3c8e9873f7f8c042fac345ac55a456abc5b0502f 100644 --- a/src/renderer/components/Header.tsx +++ b/src/renderer/components/Header.tsx @@ -8,6 +8,8 @@ import { Link2Icon } from 'lucide-react'; import { formatBytes } from 'renderer/lib/utils'; import ModelCurrentlyPlayingBar from './ModelCurrentlyPlayingBar'; +import { Link as ReactRouterLink, useNavigate } from 'react-router-dom'; + function StatsBar({ connection, setConnection }) { const [cs, setCS] = useState({ cpu: [0], gpu: [0], mem: [0] }); const { server, isLoading, isError } = useServerStats(); @@ -122,45 +124,49 @@ function StatsBar({ connection, setConnection }) { p: 1, }} > - <Box sx={{ display: 'flex', gap: 1, width: '100%', mt: 1 }}> + <Box sx={{ display: 'flex', width: '100%', mt: 1 }}> <Box> - <Typography - textColor="text.secondary" - fontSize="sm" - sx={{ mb: 1 }} - > - {/* {JSON.stringify(server)} */} - <Stack> - <Typography fontSize="sm">{connection}</Typography> - <Typography> - <b>OS: </b> - {server?.os_alias[0]} - </Typography> - <Typography> - <b>CPU: </b> - {server?.cpu} - </Typography> - <Typography> - <b>GPU: </b> - {server?.gpu[0].name} - </Typography> - <Typography> - <b>GPU Memory: </b> - {formatBytes(server?.gpu[0].total_memory)} - </Typography> - </Stack> - </Typography> - <Button - variant="solid" - color="danger" - size="small" - sx={{ m: 0, p: 1 }} - onClick={() => { - setConnection(''); - }} - > - Disconnect - </Button> + {/* {JSON.stringify(server)} */} + <Stack gap={0}> + <Typography level="title-lg">{connection}</Typography> + <Typography> + <b>OS: </b> + {server?.os_alias[0]} + </Typography> + <Typography> + <b>CPU: </b> + {server?.cpu} + </Typography> + <Typography> + <b>GPU: </b> + {server?.gpu[0].name == 'cpu' + ? 'N/A' + : server?.gpu[0].name} + </Typography> + <Typography> + <b>GPU Memory: </b> + {formatBytes(server?.gpu[0].total_memory) == '0 Bytes' + ? 'N/A' + : formatBytes(server?.gpu[0].total_memory)} + </Typography> + <Typography p={1}> + <ReactRouterLink to="/computer"> + More about this computer + </ReactRouterLink> + </Typography> + + <Button + variant="solid" + color="danger" + size="small" + sx={{ m: 0, p: 1 }} + onClick={() => { + setConnection(''); + }} + > + Disconnect + </Button> + </Stack> </Box> </Box> </Box>