diff --git a/src/renderer/components/Experiment/Rag/Documents.tsx b/src/renderer/components/Experiment/Rag/Documents.tsx index 6a84c3251dee594eeff307a704657e282574dd33..beb29f85143f4ba79cec72640d3faae9cc19790e 100644 --- a/src/renderer/components/Experiment/Rag/Documents.tsx +++ b/src/renderer/components/Experiment/Rag/Documents.tsx @@ -1,32 +1,35 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ import * as React from 'react'; import { ColorPaletteProp } from '@mui/joy/styles'; -import Avatar from '@mui/joy/Avatar'; -import Box from '@mui/joy/Box'; -import Button from '@mui/joy/Button'; -import Chip from '@mui/joy/Chip'; -import Divider from '@mui/joy/Divider'; -import FormControl from '@mui/joy/FormControl'; -import FormLabel from '@mui/joy/FormLabel'; -import Link from '@mui/joy/Link'; -import Input from '@mui/joy/Input'; -import Modal from '@mui/joy/Modal'; -import ModalDialog from '@mui/joy/ModalDialog'; -import ModalClose from '@mui/joy/ModalClose'; -import Select from '@mui/joy/Select'; -import Option from '@mui/joy/Option'; -import Table from '@mui/joy/Table'; -import Sheet from '@mui/joy/Sheet'; -import Checkbox from '@mui/joy/Checkbox'; -import IconButton, { iconButtonClasses } from '@mui/joy/IconButton'; -import Typography from '@mui/joy/Typography'; -import Menu from '@mui/joy/Menu'; -import MenuButton from '@mui/joy/MenuButton'; -import MenuItem from '@mui/joy/MenuItem'; -import Dropdown from '@mui/joy/Dropdown'; +import { + Box, + Button, + Chip, + Divider, + FormControl, + FormLabel, + Link, + Input, + Modal, + ModalDialog, + ModalClose, + Select, + Option, + Table, + Sheet, + IconButton, + Typography, + Menu, + MenuButton, + MenuItem, + Dropdown, + CircularProgress, + ListItemDecorator, + Stack, +} from '@mui/joy'; import { - CornerLeftUpIcon, + ChevronUpIcon, EyeIcon, FileTextIcon, FileUpIcon, @@ -37,9 +40,7 @@ import { } from 'lucide-react'; import { FilterIcon as FilterAltIcon, - ChevronDownIcon as ArrowDropDownIcon, - BlocksIcon as BlockIcon, - RefreshCcw as AutorenewRoundedIcon, + ChevronDownIcon, MoreVerticalIcon as MoreHorizRoundedIcon, } from 'lucide-react'; import useSWR from 'swr'; @@ -49,10 +50,8 @@ import { formatBytes } from 'renderer/lib/utils'; import * as chatAPI from '../../../lib/transformerlab-api-sdk'; import Dropzone from 'react-dropzone'; import { FaRegFileAlt } from 'react-icons/fa'; - import { FaRegFilePdf } from 'react-icons/fa6'; import { LuFileJson } from 'react-icons/lu'; -import { Alert, CircularProgress, ListItemDecorator, Stack } from '@mui/joy'; import TinyButton from 'renderer/components/Shared/TinyButton'; function descendingComparator<T>(a: T, b: T, orderBy: keyof T) { @@ -69,10 +68,10 @@ type Doc = 'asc' | 'desc'; function getComparator<Key extends keyof any>( order: Doc, - orderBy: Key + orderBy: Key, ): ( a: { [key in Key]: number | string }, - b: { [key in Key]: number | string } + b: { [key in Key]: number | string }, ) => number { return order === 'desc' ? (a, b) => descendingComparator(a, b, orderBy) @@ -85,7 +84,7 @@ function getComparator<Key extends keyof any>( // with exampleArray.slice().sort(exampleComparator) function stableSort<T>( array: readonly T[], - comparator: (a: T, b: T) => number + comparator: (a: T, b: T) => number, ) { if (!Array.isArray(array)) return []; const stabilizedThis = array?.map((el, index) => [el, index] as [T, number]); @@ -116,7 +115,7 @@ function RowMenu({ experimentInfo, filename, mutate, row }) { color="danger" onClick={() => { fetch( - chatAPI.Endpoints.Documents.Delete(experimentInfo?.id, filename) + chatAPI.Endpoints.Documents.Delete(experimentInfo?.id, filename), ).then((response) => { if (response.ok) { console.log(response); @@ -135,6 +134,7 @@ function RowMenu({ experimentInfo, filename, mutate, row }) { } const fetcher = (url) => fetch(url).then((res) => res.json()); +type Order = 'asc' | 'desc'; export default function Documents({ experimentInfo, @@ -143,19 +143,14 @@ export default function Documents({ fixedFolder = '', }) { const [doc, setDoc] = React.useState<Doc>('desc'); - const [selected, setSelected] = React.useState<readonly string[]>([]); const [open, setOpen] = React.useState(false); - const [dropzoneActive, setDropzoneActive] = React.useState(false); - const [previewFile, setPreviewFile] = React.useState<string | null>(null); - const [showFolderModal, setShowFolderModal] = React.useState(false); const [newFolderName, setNewFolderName] = React.useState(''); - const [loading, setLoading] = React.useState(false); - const [currentFolder, setCurrentFolder] = React.useState(fixedFolder); + const [order, setOrder] = React.useState<Order>('asc'); const { data: rows, @@ -163,7 +158,7 @@ export default function Documents({ mutate, } = useSWR( chatAPI.Endpoints.Documents.List(experimentInfo?.id, currentFolder), - fetcher + fetcher, ); const uploadFiles = async (currentFolder, formData) => { @@ -172,7 +167,7 @@ export default function Documents({ { method: 'POST', body: formData, - } + }, ) .then((response) => { if (response.ok) { @@ -197,7 +192,7 @@ export default function Documents({ chatAPI.Endpoints.Documents.CreateFolder(experimentInfo?.id, name), { method: 'POST', - } + }, ); if (!response.ok) { throw new Error('Folder creation failed'); @@ -211,32 +206,12 @@ export default function Documents({ } }; - function drawFile(row) { + function File({ row }) { return ( <tr key={row?.name}> - {/* <td style={{ textAlign: 'center', width: 120 }}> - <Checkbox - size="sm" - checked={selected.includes(row?.name)} - color={ - selected.includes(row?.name) ? 'primary' : undefined - } - onChange={(event) => { - setSelected((ids) => - event.target.checked - ? ids.concat(row?.name) - : ids.filter((itemId) => itemId !== row?.name) - ); - }} - slotProps={{ - checkbox: { sx: { textAlign: 'left' } }, - }} - sx={{ verticalAlign: 'text-bottom' }} - /> - </td> */} <td style={{ paddingLeft: '1rem' }}> <Typography - level="body-xs" + level="body-sm" sx={{ display: 'flex', alignItems: 'center' }} > <FileTextIcon size="16px" style={{ marginRight: '0.5rem' }} /> @@ -310,12 +285,12 @@ export default function Documents({ ); } - function drawFolder(row) { + function Folder({ row }) { return ( <tr key={row?.name} onDoubleClick={() => setCurrentFolder(row?.name)}> <td style={{ paddingLeft: '1rem' }}> <Typography - level="body-xs" + level="body-sm" sx={{ display: 'flex', alignItems: 'center' }} > <FolderIcon size="16px" style={{ marginRight: '0.5rem' }} /> @@ -420,7 +395,7 @@ export default function Documents({ src={chatAPI.Endpoints.Documents.Open( experimentInfo?.id, previewFile, - currentFolder + currentFolder, )} style={{ width: '100%', height: '100%' }} ></iframe> @@ -649,48 +624,21 @@ export default function Documents({ > <thead> <tr> - {/* <th - style={{ - textAlign: 'center', - padding: '12px 6px', - }} - > - <Checkbox - size="sm" - indeterminate={ - selected.length > 0 && - selected.length !== rows?.length - } - checked={selected.length === rows?.length} - onChange={(event) => { - setSelected( - event.target.checked - ? rows?.map((row) => row?.name) - : [] - ); - }} - color={ - selected.length > 0 || - selected.length === rows?.length - ? 'primary' - : undefined - } - sx={{ verticalAlign: 'text-bottom' }} - /> - </th> */} <th style={{ paddingLeft: '1rem' }}> <Link underline="none" color="primary" component="button" - onClick={() => setDoc(doc === 'asc' ? 'desc' : 'asc')} + onClick={() => + setOrder(order === 'asc' ? 'desc' : 'asc') + } fontWeight="lg" - endDecorator={<ArrowDropDownIcon />} + endDecorator={<ChevronUpIcon />} sx={{ '& svg': { transition: '0.2s', transform: - doc === 'desc' + order === 'desc' ? 'rotate(0deg)' : 'rotate(180deg)', }, @@ -723,8 +671,12 @@ export default function Documents({ </td> </tr> )} - {stableSort(rows, getComparator(doc, 'id'))?.map((row) => - row?.type === 'folder' ? drawFolder(row) : drawFile(row) + {stableSort(rows, getComparator(order, 'name'))?.map((row) => + row?.type === 'folder' ? ( + <Folder row={row} /> + ) : ( + <File row={row} /> + ), )} </tbody> </Table> diff --git a/src/renderer/components/Experiment/Settings.tsx b/src/renderer/components/Experiment/Settings.tsx index 5c5fccb943fbb2e5380a2e96ad7038d1b8658ce0..bbd661a7b0f379f390da1e0b320dc53f2af605db 100644 --- a/src/renderer/components/Experiment/Settings.tsx +++ b/src/renderer/components/Experiment/Settings.tsx @@ -15,8 +15,6 @@ export default function ExperimentSettings({ }) { const [showJSON, setShowJSON] = useState(false); - let plugins = experimentInfo?.config?.plugins; - if (!experimentInfo) { return null; } @@ -41,26 +39,13 @@ export default function ExperimentSettings({ {JSON.stringify(experimentInfo, null, 2)} </pre> <Divider sx={{ mt: 2, mb: 2 }} /> - <Typography level="title-lg" mb={2}> - Scripts - </Typography> - {plugins && - plugins.map((plugin) => ( - <> - <Chip color="success" size="lg"> - {plugin} - </Chip> - - </> - ))} - <Divider sx={{ mt: 2, mb: 2 }} /> <Button color="danger" variant="outlined" onClick={() => { if ( confirm( - 'Are you sure you want to delete this project? If you click on "OK" There is no way to recover it.' + 'Are you sure you want to delete this project? If you click on "OK" There is no way to recover it.', ) ) { fetch(chatAPI.DELETE_EXPERIMENT_URL(experimentInfo?.id)); diff --git a/src/renderer/components/ModelZoo/ModelStore.tsx b/src/renderer/components/ModelZoo/ModelStore.tsx index f66ed7b271b81c41ce933be7e5a3762423d96595..5a2906e17c5d94ab87a15d10ad4d09f504c5354c 100644 --- a/src/renderer/components/ModelZoo/ModelStore.tsx +++ b/src/renderer/components/ModelZoo/ModelStore.tsx @@ -20,6 +20,7 @@ import { import { ArrowDownIcon, CheckIcon, + ChevronUpIcon, CreativeCommonsIcon, DownloadIcon, ExternalLinkIcon, @@ -59,10 +60,10 @@ type Order = 'asc' | 'desc'; function getComparator<Key extends keyof any>( order: Order, - orderBy: Key + orderBy: Key, ): ( a: { [key in Key]: number | string }, - b: { [key in Key]: number | string } + b: { [key in Key]: number | string }, ) => number { return order === 'desc' ? (a, b) => descendingComparator(a, b, orderBy) @@ -75,7 +76,7 @@ function getComparator<Key extends keyof any>( // with exampleArray.slice().sort(exampleComparator) function stableSort<T>( array: readonly T[], - comparator: (a: T, b: T) => number + comparator: (a: T, b: T) => number, ) { const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); stabilizedThis.sort((a, b) => { @@ -97,7 +98,8 @@ function getModelHuggingFaceURL(model) { const fetcher = (url) => fetch(url).then((res) => res.json()); export default function ModelStore() { - const [order, setOrder] = useState<Order>('desc'); + const [order, setOrder] = useState<Order>('asc'); + const [orderBy, setOrderBy] = useState('name'); // jobId is null if there is no current download in progress, // and it is -1 if a download has been initiated but it hasn't started yet const [jobId, setJobId] = useState(null); @@ -118,7 +120,7 @@ export default function ModelStore() { const { data: modelDownloadProgress } = useSWR( jobId && jobId != '-1' ? chatAPI.Endpoints.Jobs.Get(jobId) : null, fetcher, - { refreshInterval: 2000 } + { refreshInterval: 2000 }, ); // Creating a separate object to get useEffect for download jobs to work @@ -128,12 +130,12 @@ export default function ModelStore() { // check if we have a Hugging Face access token const { data: hftoken } = useSWR( chatAPI.Endpoints.Config.Get('HuggingfaceUserAccessToken'), - fetcher + fetcher, ); const { data: canLogInToHuggingFace } = useSWR( chatAPI.Endpoints.Models.HuggingFaceLogin(), - fetcher + fetcher, ); // Set isHFAccessTokenSet to true if message in canLogInToHuggingFace is 'OK' @@ -224,7 +226,7 @@ export default function ModelStore() { flexDirection: 'column', overflow: 'hidden', }} - > + > <Box sx={{ position: 'relative', @@ -334,9 +336,20 @@ export default function ModelStore() { underline="none" color="primary" component="button" - onClick={() => setOrder(order === 'asc' ? 'desc' : 'asc')} + onClick={() => { + setOrder(order === 'asc' ? 'desc' : 'asc'); + setOrderBy('name'); + }} fontWeight="lg" - endDecorator={<ArrowDownIcon />} + endDecorator={ + <ChevronUpIcon + color={ + orderBy == 'name' + ? 'var(--joy-palette-primary-plainColor)' + : 'var(--joy-palette-primary-plainDisabledColor)' + } + /> + } sx={{ '& svg': { transition: '0.2s', @@ -351,7 +364,37 @@ export default function ModelStore() { <th style={{ width: 100, padding: 12 }}>License</th> <th style={{ width: 100, padding: 12 }}>Engine</th> - <th style={{ width: 60, padding: 12 }}>Size</th> + <th style={{ width: 60, padding: 12 }}> + {' '} + <Link + underline="none" + color="primary" + component="button" + onClick={() => { + setOrder(order === 'asc' ? 'desc' : 'asc'); + setOrderBy('size_of_model_in_mb'); + }} + fontWeight="lg" + endDecorator={ + <ChevronUpIcon + color={ + orderBy == 'size_of_model_in_mb' + ? 'var(--joy-palette-primary-plainColor)' + : 'var(--joy-palette-primary-plainDisabledColor)' + } + /> + } + sx={{ + '& svg': { + transition: '0.2s', + transform: + order === 'desc' ? 'rotate(0deg)' : 'rotate(180deg)', + }, + }} + > + Size + </Link> + </th> <th style={{ width: 20, padding: 12 }}> </th> <th style={{ width: 80, padding: 12 }}> </th> </tr> @@ -360,7 +403,7 @@ export default function ModelStore() { {modelGalleryData && stableSort( filterByFilters(modelGalleryData, searchText, filters), - getComparator(order, 'name') + getComparator(order, orderBy), ).map((row) => ( <tr key={row.uniqueID}> <td> @@ -473,7 +516,7 @@ export default function ModelStore() { 'To access gated Hugging Face models you must first:\r\r' + '1. Create a READ access token in your Hugging Face account.\r\r' + '2. Enter the token on the Transformer Lab Settings page.\r\r' + - 'Click OK to go to Settings.' + 'Click OK to go to Settings.', ); if (confirm_result) { navigate('/settings'); @@ -492,19 +535,19 @@ export default function ModelStore() { setCurrentlyDownloading(row.name); try { let response = await fetch( - chatAPI.Endpoints.Jobs.Create() + chatAPI.Endpoints.Jobs.Create(), ); const newJobId = await response.json(); setJobId(newJobId); response = await downloadModelFromGallery( row?.uniqueID, - newJobId + newJobId, ); if (response?.status == 'error') { setCurrentlyDownloading(null); setJobId(null); return alert( - `Failed to download:\n${response.message}` + `Failed to download:\n${response.message}`, ); } else if (response?.status == 'unauthorized') { setCurrentlyDownloading(null); @@ -514,7 +557,7 @@ export default function ModelStore() { window .open( getModelHuggingFaceURL(row), - '_blank' + '_blank', ) ?.focus(); } @@ -539,7 +582,7 @@ export default function ModelStore() { value={clamp( modelDownloadProgress?.progress, 0, - 100 + 100, )} sx={{ width: '100px' }} variant="solid" @@ -550,10 +593,10 @@ export default function ModelStore() { <> {clamp( Number.parseFloat( - modelDownloadProgress?.progress + modelDownloadProgress?.progress, ), 0, - 100 + 100, ).toFixed(0)} % </> @@ -570,7 +613,7 @@ export default function ModelStore() { modelDownloadProgress?.job_data ?.downloaded * 1024 * - 1024 + 1024, )} {/* {modelDownloadProgress?.job_data} */} <ArrowDownIcon size="18px" /> diff --git a/src/renderer/components/Settings/TransformerLabSettings.tsx b/src/renderer/components/Settings/TransformerLabSettings.tsx index e845ec3c9ade830810a748cf767f4a426f56c2b4..7d281b3c074fccad5ef77882ba585c83fe842e04 100644 --- a/src/renderer/components/Settings/TransformerLabSettings.tsx +++ b/src/renderer/components/Settings/TransformerLabSettings.tsx @@ -15,6 +15,10 @@ import { Table, Typography, Alert, + Tabs, + TabList, + Tab, + TabPanel, } from '@mui/joy'; import * as chatAPI from 'renderer/lib/transformerlab-api-sdk'; @@ -23,6 +27,8 @@ import { EyeIcon, EyeOffIcon, RotateCcwIcon } from 'lucide-react'; // Import the AIProvidersSettings component. import AIProvidersSettings from './AIProvidersSettings'; +import ViewJobsTab from './ViewJobsTab'; +import { alignBox } from '@nivo/core'; const fetcher = (url) => fetch(url).then((res) => res.json()); @@ -35,7 +41,7 @@ export default function TransformerLabSettings() { mutate: hftokenmutate, } = useSWR( chatAPI.Endpoints.Config.Get('HuggingfaceUserAccessToken'), - fetcher + fetcher, ); const [showJobsOfType, setShowJobsOfType] = React.useState('NONE'); const [showProvidersPage, setShowProvidersPage] = React.useState(false); @@ -71,171 +77,174 @@ export default function TransformerLabSettings() { ); } - - return ( - <> - <Typography level="h1" marginBottom={3}> + <Sheet + sx={{ + width: '100%', + height: '100%', + overflowY: 'hidden', + display: 'flex', + flexDirection: 'column', + }} + > + <Typography level="h1" marginBottom={1}> Transformer Lab Settings </Typography> - <Sheet sx={{ width: '100%', overflowY: 'auto' }}> - {canLogInToHuggingFaceIsLoading && <CircularProgress />} - <Typography level="title-lg" marginBottom={2}> - Huggingface Credentials: - </Typography> - {canLogInToHuggingFace?.message === 'OK' ? ( - <Alert color="success">Login to Huggingface Successful</Alert> - ) : ( - <> - <Alert color="danger" sx={{ mb: 1 }}> - Login to Huggingface Failed. Please set credentials below. - </Alert> - <FormControl sx={{ maxWidth: '500px' }}> - <FormLabel>User Access Token</FormLabel> - {hftokenisloading ? ( - <CircularProgress /> - ) : ( - <Input - name="hftoken" - defaultValue={hftoken} - type="password" - endDecorator={ - <IconButton - onClick={() => { - const x = document.getElementsByName('hftoken')[0]; - x.type = x.type === 'text' ? 'password' : 'text'; - setShowPassword(!showPassword); - }} + <Sheet + sx={{ + height: '100%', + overflowY: 'hidden', + display: 'flex', + flexDirection: 'column', + }} + > + <Tabs + defaultValue={0} + sx={{ + height: '100%', + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + }} + > + <TabList> + <Tab>Settings</Tab> + <Tab>View Jobs</Tab> + </TabList> + <TabPanel value={0} style={{ overflow: 'auto' }}> + {canLogInToHuggingFaceIsLoading && <CircularProgress />} + <Typography level="title-lg" marginBottom={2}> + Huggingface Credentials: + </Typography> + {canLogInToHuggingFace?.message === 'OK' ? ( + <Alert color="success">Login to Huggingface Successful</Alert> + ) : ( + <> + <Alert color="danger" sx={{ mb: 1 }}> + Login to Huggingface Failed. Please set credentials below. + </Alert> + <FormControl sx={{ maxWidth: '500px' }}> + <FormLabel>User Access Token</FormLabel> + {hftokenisloading ? ( + <CircularProgress /> + ) : ( + <Input + name="hftoken" + defaultValue={hftoken} + type="password" + endDecorator={ + <IconButton + onClick={() => { + const x = document.getElementsByName('hftoken')[0]; + x.type = x.type === 'text' ? 'password' : 'text'; + setShowPassword(!showPassword); + }} + > + {showPassword ? <EyeOffIcon /> : <EyeIcon />} + </IconButton> + } + /> + )} + <Button + onClick={async () => { + const token = + document.getElementsByName('hftoken')[0].value; + await fetch( + chatAPI.Endpoints.Config.Set( + 'HuggingfaceUserAccessToken', + token, + ), + ); + // Now manually log in to Huggingface + await fetch(chatAPI.Endpoints.Models.HuggingFaceLogin()); + hftokenmutate(token); + canLogInToHuggingFaceMutate(); + }} + sx={{ marginTop: 1, width: '100px', alignSelf: 'flex-end' }} + > + Save + </Button> + <FormHelperText> + A Huggingface access token is required in order to access + certain models and datasets (those marked as "Gated"). + </FormHelperText> + <FormHelperText> + Documentation here:{' '} + <a + href="https://huggingface.co/docs/hub/security-tokens" + target="_blank" + rel="noreferrer" > - {showPassword ? <EyeOffIcon /> : <EyeIcon />} - </IconButton> - } - /> - )} - <Button - onClick={async () => { - const token = document.getElementsByName('hftoken')[0].value; - await fetch(chatAPI.Endpoints.Config.Set('HuggingfaceUserAccessToken', token)); - // Now manually log in to Huggingface - await fetch(chatAPI.Endpoints.Models.HuggingFaceLogin()); - hftokenmutate(token); - canLogInToHuggingFaceMutate(); - }} - sx={{ marginTop: 1, width: '100px', alignSelf: 'flex-end' }} - > - Save - </Button> - <FormHelperText> - A Huggingface access token is required in order to access certain - models and datasets (those marked as "Gated"). - </FormHelperText> - <FormHelperText> - Documentation here:{' '} - <a - href="https://huggingface.co/docs/hub/security-tokens" - target="_blank" - rel="noreferrer" + https://huggingface.co/docs/hub/security-tokens + </a> + </FormHelperText> + </FormControl> + </> + )} + {wandbLoginStatus?.message === 'OK' ? ( + <Alert color="success"> + Login to Weights & Biases Successful + </Alert> + ) : ( + <FormControl sx={{ maxWidth: '500px', mt: 2 }}> + <FormLabel>Weights & Biases API Key</FormLabel> + <Input name="wandbToken" type="password" /> + <Button + onClick={async () => { + const token = + document.getElementsByName('wandbToken')[0].value; + await fetch( + chatAPI.Endpoints.Config.Set('WANDB_API_KEY', token), + ); + await fetch(chatAPI.Endpoints.Models.wandbLogin()); + wandbLoginMutate(); + }} + sx={{ marginTop: 1, width: '100px', alignSelf: 'flex-end' }} > - https://huggingface.co/docs/hub/security-tokens - </a> - </FormHelperText> - </FormControl> - </> - )} - {wandbLoginStatus?.message === 'OK' ? ( - <Alert color="success">Login to Weights & Biases Successful</Alert> - ) : ( - <FormControl sx={{ maxWidth: '500px', mt: 2 }}> - <FormLabel>Weights & Biases API Key</FormLabel> - <Input name="wandbToken" type="password" /> + Save + </Button> + </FormControl> + )} + <Divider sx={{ mt: 2, mb: 2 }} /> + <Typography level="title-lg" marginBottom={2}> + AI Providers & Models: + </Typography> + {/* Clickable list option */} + <Button variant="soft" onClick={() => setShowProvidersPage(true)}> + Set API Keys for AI Providers + </Button> + + <Divider sx={{ mt: 2, mb: 2 }} /> + <Typography level="title-lg" marginBottom={2}> + Application: + </Typography> <Button - onClick={async () => { - const token = document.getElementsByName('wandbToken')[0].value; - await fetch(chatAPI.Endpoints.Config.Set('WANDB_API_KEY', token)); - await fetch(chatAPI.Endpoints.Models.wandbLogin()); - wandbLoginMutate(); + variant="soft" + onClick={() => { + // find and delete all items in local storage that begin with oneTimePopup: + for (const key in localStorage) { + if (key.startsWith('oneTimePopup')) { + localStorage.removeItem(key); + } + } }} - sx={{ marginTop: 1, width: '100px', alignSelf: 'flex-end' }} > - Save + Reset all Tutorial Popup Screens </Button> - </FormControl> - )} - <Divider sx={{ mt: 2, mb: 2 }} /> - <Typography level="title-lg" marginBottom={2}> - AI Providers & Models: - </Typography> - {/* Clickable list option */} - <Button variant="soft" onClick={() => setShowProvidersPage(true)}> - Set API Keys for AI Providers - </Button> - - <Divider sx={{ mt: 2, mb: 2 }} /> - <Typography level="title-lg" marginBottom={2}> - Application: - </Typography> - <Button - variant="soft" - onClick={() => { - // find and delete all items in local storage that begin with oneTimePopup: - for (const key in localStorage) { - if (key.startsWith('oneTimePopup')) { - localStorage.removeItem(key); - } - } - }} - > - Reset all Tutorial Popup Screens - </Button> - <Divider sx={{ mt: 2, mb: 2 }} /> - <Typography level="title-lg" marginBottom={2}> - View Jobs (debug):{' '} - <IconButton onClick={() => jobsMutate()}> - <RotateCcwIcon size="14px" /> - </IconButton> - </Typography> - <Select - sx={{ width: '400px' }} - value={showJobsOfType} - onChange={(e, newValue) => { - setShowJobsOfType(newValue); - }} - > - <Option value="NONE">None</Option> - <Option value="">All</Option> - <Option value="DOWNLOAD_MODEL">Download Model</Option> - <Option value="LOAD_MODEL">Load Model</Option> - <Option value="TRAIN">Train</Option> - <Option value="GENERATE">Generate</Option> - <Option value="EVAL">Evaluate</Option> - </Select> - {showJobsOfType !== 'NONE' && ( - <Table sx={{ tableLayout: 'auto', overflow: 'scroll' }}> - <thead> - <tr> - <td>Job ID</td> - <td>Job Type</td> - <td>Job Status</td> - <td>Job Progress</td> - <td>Job Data</td> - </tr> - </thead> - <tbody> - {jobs?.map((job) => ( - <tr key={job.id}> - <td>{job.id}</td> - <td>{job.type}</td> - <td>{job.status}</td> - <td>{job.progress}</td> - <td> - <pre>{JSON.stringify(job.job_data, null, 2)}</pre> - </td> - </tr> - ))} - </tbody> - </Table> - )} + </TabPanel> + <TabPanel + value={1} + sx={{ + overflowY: 'hidden', + overflowX: 'hidden', + display: 'flex', + flexDirection: 'column', + }} + > + <ViewJobsTab /> + </TabPanel> + </Tabs> </Sheet> - </> + </Sheet> ); } diff --git a/src/renderer/components/Settings/ViewJobsTab.tsx b/src/renderer/components/Settings/ViewJobsTab.tsx new file mode 100644 index 0000000000000000000000000000000000000000..056cc99ad191c0f1efaa10d7de6e0f98d6f88b42 --- /dev/null +++ b/src/renderer/components/Settings/ViewJobsTab.tsx @@ -0,0 +1,110 @@ +import * as React from 'react'; +import { + Typography, + IconButton, + Select, + Option, + Table, + Box, + Stack, +} from '@mui/joy'; +import { RotateCcwIcon } from 'lucide-react'; +import useSWR from 'swr'; +import * as chatAPI from 'renderer/lib/transformerlab-api-sdk'; + +const fetcher = (url) => fetch(url).then((res) => res.json()); + +const jobTypes = [ + { value: 'NONE', label: 'None' }, + { value: '', label: 'All' }, + { value: 'DOWNLOAD_MODEL', label: 'Download Model' }, + { value: 'LOAD_MODEL', label: 'Load Model' }, + { value: 'TRAIN', label: 'Train' }, + { value: 'GENERATE', label: 'Generate' }, + { value: 'EVAL', label: 'Evaluate' }, +]; + +export default function ViewJobsTab() { + const [type, setType] = React.useState(''); + + const { + data: jobs, + error: jobsError, + isLoading: jobsIsLoading, + mutate: jobsMutate, + } = useSWR(chatAPI.Endpoints.Jobs.GetJobsOfType(type, ''), fetcher); + + return ( + <> + <Stack flexDirection="row" alignItems="center" marginBottom={2}> + <Typography level="title-lg">View Jobs: </Typography> + <Select + sx={{ width: '200px' }} + value={type} + onChange={(e, newValue) => { + setType(newValue); + }} + > + {jobTypes.map((jobType) => ( + <Option key={jobType.value} value={jobType.value}> + {jobType.label} + </Option> + ))} + </Select> + <IconButton onClick={() => jobsMutate()}> + <RotateCcwIcon size="14px" /> + </IconButton> + </Stack> + <Box + style={{ display: 'flex', flexDirection: 'column', overflow: 'auto' }} + > + {type !== 'NONE' && ( + <Table + stickyHeader + sx={{ width: '100%', tableLayout: 'auto', overflow: 'scroll' }} + > + <thead> + <tr> + <th>Job ID</th> + <th>Job Type</th> + <th>Job Status</th> + <th>Job Progress</th> + <th style={{ overflow: 'hidden' }}>Job Data</th> + </tr> + </thead> + <tbody> + {jobs?.map((job) => ( + <tr key={job.id}> + <td>{job.id}</td> + <td>{job.type}</td> + <td>{job.status}</td> + <td>{job.progress}</td> + <td> + <pre + style={{ + maxHeight: '100px', + width: '500px', + overflowY: 'auto', + overflowX: 'hidden', + scrollbarWidth: 'thin', // For Firefox + msOverflowStyle: 'none', // For Internet Explorer and Edge + }} + > + {JSON.stringify(job.job_data, null, 2)} + </pre> + <style> + {` + pre::-webkit-scrollbar { + width: 4px; /* For Chrome, Safari, and Opera */ + }`} + </style> + </td> + </tr> + ))} + </tbody> + </Table> + )} + </Box> + </> + ); +}