diff --git a/src/main/preload.ts b/src/main/preload.ts index 7ab343a82b3f6a7d704d35181657fdfdf5f139ed..47ff3204b5e10e2c161f8f3f25e3974e064e1d94 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -81,11 +81,13 @@ contextBridge.exposeInMainWorld('storage', { }); contextBridge.exposeInMainWorld('sshClient', { - connect: (data) => ipcRenderer.invoke('ssh:connect', data), - data: (data) => ipcRenderer.send('ssh:data', data), + connect: (data: string) => ipcRenderer.invoke('ssh:connect', data), + data: (data: string) => ipcRenderer.send('ssh:data', data), - onData: (data) => ipcRenderer.on('ssh:data', data), - onSSHConnected: (callback) => ipcRenderer.on('ssh:connected', callback), + onData: (data: any) => ipcRenderer.on('ssh:data', data), + onSSHConnected: (callback: { + (event: IpcRendererEvent, data: string): void; + }) => ipcRenderer.on('ssh:connected', callback), removeAllListeners: () => { ipcRenderer.removeAllListeners('ssh:data'); @@ -94,5 +96,5 @@ contextBridge.exposeInMainWorld('sshClient', { }); contextBridge.exposeInMainWorld('autoUpdater', { - onMessage: (data) => ipcRenderer.on('autoUpdater', data), + onMessage: (data: any) => ipcRenderer.on('autoUpdater', data), }); diff --git a/src/main/ssh-client.js b/src/main/ssh-client.js index 70a9b2d3f8ad613be0f6fcac61f9e1f55cbbc0da..c752860b9529489596dc61ae87354622c49a28f4 100644 --- a/src/main/ssh-client.js +++ b/src/main/ssh-client.js @@ -8,7 +8,7 @@ const HOME_DIR = app.getPath('home'); const default_private_key = ''; const default_private_key_location = HOME_DIR + '/.ssh/id_rsa'; -var mainWindow = null; +let mainWindow = null; function sendToRenderer(channel, data) { if (mainWindow === null) { @@ -119,6 +119,6 @@ ipcMain.handle('ssh:connect', (event, key) => { export default function setupSSHClient(browserWindow) { console.log('setting up ssh client'); - console.log(browserWindow); + // console.log(browserWindow); mainWindow = browserWindow; } diff --git a/src/main/util.ts b/src/main/util.ts index 1abafac94260e4f68fbdd075f39c597bffe96067..18bcd55a73af533d0aa3786c84a6cb05f82a8a53 100644 --- a/src/main/util.ts +++ b/src/main/util.ts @@ -1,6 +1,7 @@ /* eslint import/prefer-default-export: off */ import { URL } from 'url'; import path from 'path'; +import { ExecException } from 'child_process'; const fs = require('fs'); const os = require('os'); const { spawn, exec, ChildProcess } = require('child_process'); @@ -132,21 +133,21 @@ export async function startLocalServer() { console.log('Local server started with pid', localServer.pid); return new Promise((resolve) => { - let err_msg; + let err_msg: string; // if there was an error spawning then stderr will be null if (localServer.stderr) { - localServer.stderr.on('data', (data) => { + localServer.stderr.on('data', (data: string) => { console.error(`stderr: ${data}`); }); } - localServer.on('error', (error_msg) => { - console.log(`child process failed: ${error_msg}`); - err_msg = error_msg; + localServer.on('error', (err: string) => { + console.log(`child process failed: ${err}`); + err_msg = err; }); - localServer.on('close', (code) => { + localServer.on('close', (code: number) => { console.log(`child process exited with code ${code}`); if (code === 0) { @@ -170,7 +171,7 @@ export function killLocalServer() { `Killing local server with pid ${localServer.pid} and all it children` ); var kill = require('tree-kill'); - kill(localServer.pid, 'SIGTERM', function (err) { + kill(localServer.pid, 'SIGTERM', function (err: string) { console.log('Finished killing local server'); console.log(err); resolve(err); @@ -201,10 +202,10 @@ export async function installLocalServer() { ? {} : { shell: '/bin/bash', cwd: root_dir }; try { - const child = exec( + exec( installScriptCommand, options, - (error, stdout, stderr) => { + (error: ExecException | null, stdout: string, stderr: string) => { if (error) { console.error(`exec error: ${error}`); return; @@ -235,7 +236,7 @@ export async function checkDependencies() { let response = { status: '', message: '', - data: [], + data: {}, }; // check if we've done an install/update of dependencies with this build @@ -276,7 +277,7 @@ export async function checkDependencies() { return response; } - const pipListNames = pipList.map((x) => x.name); + const pipListNames = pipList.map((x: { name: string }) => x.name); const keyDependencies = [ 'fastapi', 'pydantic', @@ -299,9 +300,9 @@ export async function checkDependencies() { response.data = missingDependencies; console.log('missingDependencies', missingDependencies); - if (missingDependencies.legnth > 0) { + if (missingDependencies.length > 0) { response.status = 'error'; - const missingList = missingDependencies.data?.join(', '); + const missingList = missingDependencies.join(', '); response.message = `Missing dependencies including: ${missingList}...`; } else { response.status = 'success'; @@ -319,7 +320,7 @@ export async function checkIfCondaEnvironmentExists() { let response = { status: '', message: '', - data: [], + data: {}, }; console.log(JSON.stringify({ error, stdout, stderr })); @@ -352,18 +353,29 @@ export async function checkIfCondaEnvironmentExists() { } } +interface ExecError extends Error { + code?: number; // Exit code of the shell command if the process exited on its own. + stdout?: string; // Standard output from the command. + stderr?: string; // Standard error output from the command. +} + /** * * @param argument parameter to pass to install.sh * @returns the stdout of the process or false on failure. */ -export async function executeInstallStep(argument: string) { +export async function executeInstallStep(argument: string): Promise<{ + error: ExecException | null; + stdout: string; + stderr: string; +}> { const server_dir = await getTransformerLabCodeDir(); if (!fs.existsSync(server_dir)) { - console.log( - 'Install step failed. TransformerLab directory has not been setup.' - ); - return false; + return { + error: new Error('TransformerLab directory has not been setup.'), + stdout: 'TransformerLab directory has not been setup.', + stderr: 'TransformerLab directory has not been setup.', + }; } const installScriptFilename = 'install.sh'; @@ -383,14 +395,19 @@ export async function executeInstallStep(argument: string) { try { ({ error, stdout, stderr } = await awaitExec(exec_cmd, options)); } catch (err) { + console.log('Failed to execute install step', err); console.log(JSON.stringify(err)); - return { - error: err?.code, - stdout: err?.stdout?.toString(), - stderr: err?.stderr?.toString(), - }; + if (err instanceof Error) { + const execError = err as ExecError; + return { + error: new Error(String(execError.code)), + stdout: execError.stdout?.toString() ?? '', + stderr: execError.stderr?.toString() ?? '', + }; + } } + if (stdout) console.log(`${installScriptFilename} stdout:`, stdout); if (stderr) console.error(`${installScriptFilename} stderr:`, stderr); return { error, stdout, stderr }; diff --git a/src/renderer/components/AutoUpdateModal.tsx b/src/renderer/components/AutoUpdateModal.tsx index 4843b175e45135b20caf46a1cd487e5a23e8f0c1..bf21d5a307f5bd1f7ccc1d7d3ccf9d6f0318ddb2 100644 --- a/src/renderer/components/AutoUpdateModal.tsx +++ b/src/renderer/components/AutoUpdateModal.tsx @@ -1,13 +1,14 @@ import React from 'react'; - import { Modal, ModalClose, Sheet, Typography } from '@mui/joy'; -window.autoUpdater.onMessage((message) => { - console.log('autoupdate message', message); - var container = document.getElementById('messages'); - var m = document.createElement('div'); - m.innerHTML = text; + +window.autoUpdater.onMessage((message: Node) => { + // console.log('autoupdate message', message); + const container = document.getElementById('messages') as HTMLDivElement; + // const m = document.createElement('div'); + // m.innerHTML = text; container?.appendChild(message); }); + export default function AutoUpdateModal({}) { const [open, setOpen] = React.useState<boolean>(true); diff --git a/src/renderer/components/Computer.tsx b/src/renderer/components/Computer.tsx index 90274d0f46923197d7f29075b2d4fa02ad2faff1..81b66bc5de66f893be1e687b1bb66b4141c5dcef 100644 --- a/src/renderer/components/Computer.tsx +++ b/src/renderer/components/Computer.tsx @@ -24,11 +24,25 @@ import { formatBytes } from 'renderer/lib/utils'; import { useServerStats } from 'renderer/lib/transformerlab-api-sdk'; function getSystemProperties() { - const information = document.getElementById('info'); + const information = document.getElementById('info') as HTMLPreElement; information.innerText = `This app is using Chrome (v${window.platform.chrome()}), Node.js (v${window.platform.node()}), and Electron (v${window.platform.electron()})`; } -function ComputerCard({ children, title, description = '', chip = '', icon }) { +interface ComputerCardProps { + title: string; + description?: string; + chip?: string; + icon: React.ReactNode; + children: React.ReactNode; +} + +function ComputerCard({ + children, + title, + description = '', + chip = '', + icon, +}: ComputerCardProps) { return ( <Card variant="outlined"> <CardContent> @@ -99,7 +113,7 @@ export default function Computer() { title="GPU Specs" image={undefined} > - {server.gpu?.map((g) => { + {server.gpu?.map((g: Record<string, any>) => { return ( <> 🔥 {g.name} diff --git a/src/renderer/components/Connect/LocalConnection.tsx b/src/renderer/components/Connect/LocalConnection.tsx index 8f4cbeb559ccc0d0792ceb0ab00c5784da5c202f..7412a0b5d0f82bf52bfa0fb597a97c4268710d5e 100644 --- a/src/renderer/components/Connect/LocalConnection.tsx +++ b/src/renderer/components/Connect/LocalConnection.tsx @@ -13,43 +13,24 @@ import { import { CheckCircle2, PlayIcon, RotateCcwIcon } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useCheckLocalConnection } from 'renderer/lib/transformerlab-api-sdk'; - import { FaApple } from 'react-icons/fa6'; +import { + LocalConnectionProvider, + useLocalConnectionContext, +} from './context/localConnectionContext'; +import { setIntervalXTimes, isStep, Steps } from './utils'; +import { Message } from './types/Message'; -// Runs a callback every delay milliseconds, up to repetitions times. -// If the callback returns true, the interval is cleared. -// If the callback returns false, and the interval has run repetitions times, the notSuccessful callback is run. -function setIntervalXTimes(callback, notSuccessful, delay, repetitions) { - var x = 0; - var intervalID = window.setInterval(async function () { - console.log(`trying ${x} times`); - const response = await callback(); - - if (response) { - window.clearInterval(intervalID); - } else if (++x === repetitions) { - notSuccessful(); - window.clearInterval(intervalID); - } - }, delay); -} +function CheckIfInstalled() { + const { activeStep, setActiveStep } = useLocalConnectionContext(); -const Steps = [ - 'CHECK_IF_INSTALLED', - 'CHECK_VERSION', - 'CHECK_IF_CONDA_INSTALLED', - 'CHECK_IF_CONDA_ENVIRONMENT_EXISTS', - 'CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED', - 'CHECK_IF_SERVER_RUNNING_ON_PORT_8000', - 'CHECK_FOR_IMPORTANT_PLUGINS', -]; - -function CheckIfInstalled({ activeStep, setActiveStep }) { const [installStatus, setInstallStatus] = useState('notstarted'); // notstarted, pending, success, error - const [installErrorMessage, setInstallErrorMessage] = useState(null); + const [installErrorMessage, setInstallErrorMessage] = useState<string | null>( + null + ); useEffect(() => { - if (activeStep !== Steps.indexOf('CHECK_IF_INSTALLED')) return; + if (!isStep(activeStep)) return; (async () => { // First check if there are any system requirement issues // If not, then check if installed locally @@ -142,7 +123,9 @@ function CheckIfInstalled({ activeStep, setActiveStep }) { ); } -function CheckCurrentVersion({ activeStep, setActiveStep }) { +function CheckCurrentVersion() { + const { activeStep = 0, setActiveStep } = useLocalConnectionContext(); + const [version, setVersion] = useState('pending'); // pending, or #.#.# const [release, setRelease] = useState(''); const [installStatus, setInstallStatus] = useState('notstarted'); // notstarted, pending, success, error @@ -238,7 +221,9 @@ function CheckCurrentVersion({ activeStep, setActiveStep }) { ); } -function RunServer({ activeStep, setActiveStep }) { +function RunServer() { + const { activeStep, setActiveStep } = useLocalConnectionContext(); + const [thinking, setThinking] = useState(false); const { server, @@ -271,72 +256,70 @@ function RunServer({ activeStep, setActiveStep }) { }, [activeStep, server]); return ( - <> - <Stack spacing={1}> - {activeStep >= Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000') && - server && - !serverError && <Chip color="success">Success!</Chip>} - {activeStep >= Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000') && - (!server || serverError) && <Chip color="danger">Not Running</Chip>} - <ButtonGroup variant="plain" spacing={1}> - {activeStep == - Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000') && - (!server || serverError ? ( - thinking ? ( - <CircularProgress color="primary" /> - ) : ( - <> - <Button - variant="solid" - onClick={async () => { - setThinking(true); - const start_process = - await window.electron.ipcRenderer.invoke( - 'server:startLocalServer' - ); + <Stack spacing={1}> + {activeStep >= Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000') && + server && + !serverError && <Chip color="success">Success!</Chip>} + {activeStep >= Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000') && + (!server || serverError) && <Chip color="danger">Not Running</Chip>} + <ButtonGroup variant="plain" spacing={1}> + {activeStep == Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000') && + (!server || serverError ? ( + thinking ? ( + <CircularProgress color="primary" /> + ) : ( + <> + <Button + variant="solid" + onClick={async () => { + setThinking(true); + const start_process = + await window.electron.ipcRenderer.invoke( + 'server:startLocalServer' + ); - if (start_process?.status == 'error') { - const response_text = - 'Failed to start server: \n' + start_process?.message; - alert(response_text); + if (start_process?.status == 'error') { + const response_text = + 'Failed to start server: \n' + start_process?.message; + alert(response_text); + setThinking(false); + return; + } + //set interval to check if server is running every 2 seconds, 15 times: + setIntervalXTimes( + async () => { + if (!server || serverError) return false; setThinking(false); - return; - } - //set interval to check if server is running every 2 seconds, 15 times: - setIntervalXTimes( - async () => { - if (!server || serverError) return false; - setThinking(false); - setActiveStep( - Steps.indexOf( - 'CHECK_IF_SERVER_RUNNING_ON_PORT_8000' - ) + 1 - ); - return true; - }, - () => { - setThinking(false); - }, - 2000, - 15 - ); - }} - > - Start - </Button> - </> - ) - ) : ( - '' - ))} - </ButtonGroup> - </Stack> - </> + setActiveStep( + Steps.indexOf( + 'CHECK_IF_SERVER_RUNNING_ON_PORT_8000' + ) + 1 + ); + return true; + }, + () => { + setThinking(false); + }, + 2000, + 15 + ); + }} + > + Start + </Button> + </> + ) + ) : ( + '' + ))} + </ButtonGroup> + </Stack> ); } -function CheckForPlugins({ activeStep, setActiveStep }) { - const [missingPlugins, setMissingPlugins] = useState(null); +function CheckForPlugins() { + const { activeStep, setActiveStep } = useLocalConnectionContext(); + const [missingPlugins, setMissingPlugins] = useState([]); const [installing, setInstalling] = useState(false); useEffect(() => { @@ -365,7 +348,7 @@ function CheckForPlugins({ activeStep, setActiveStep }) { )} <Typography level="body-sm"> - {platform.isMac() && platform.arch() == 'arm64' && ( + {window.platform.isMac() && window.platform.arch() == 'arm64' && ( <> You are running on a <FaApple /> Mac with <b>Apple Silicon</b> . @@ -437,7 +420,8 @@ function CheckForPlugins({ activeStep, setActiveStep }) { ); } -function CheckIfCondaInstalled({ activeStep, setActiveStep }) { +function CheckIfCondaInstalled() { + const { activeStep, setActiveStep } = useLocalConnectionContext(); const [installStatus, setInstallStatus] = useState(''); // notstarted, pending, success, error useEffect(() => { @@ -519,9 +503,15 @@ function CheckIfCondaInstalled({ activeStep, setActiveStep }) { ); } -function CheckIfCondaEnvironmentExists({ activeStep, setActiveStep }) { - const [installStatus, setInstallStatus] = useState(''); // notstarted, pending, success, error - const [errorMessage, setErrorMessage] = useState(null); +function CheckIfCondaEnvironmentExists() { + const { activeStep, setActiveStep } = useLocalConnectionContext(); + const [installStatus, setInstallStatus] = useState< + 'success' | 'notstarted' | 'pending' | 'error' | '' + >(''); // notstarted, pending, success, error + const [errorMessage, setErrorMessage] = useState<{ + message: string; + data: any; + } | null>(null); useEffect(() => { if (activeStep !== Steps.indexOf('CHECK_IF_CONDA_ENVIRONMENT_EXISTS')) @@ -628,9 +618,10 @@ function CheckIfCondaEnvironmentExists({ activeStep, setActiveStep }) { ); } -function CheckDependencies({ activeStep, setActiveStep }) { +function CheckDependencies() { + const { activeStep, setActiveStep } = useLocalConnectionContext(); const [installStatus, setInstallStatus] = useState(''); // notstarted, pending, success, error - const [errorMessage, setErrorMessage] = useState(null); + const [errorMessage, setErrorMessage] = useState<Message>(null); useEffect(() => { if (activeStep !== Steps.indexOf('CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED')) @@ -641,10 +632,7 @@ function CheckDependencies({ activeStep, setActiveStep }) { 'server:checkDependencies' ); - if ( - ipcResponse?.status == 'success' && - ipcResponse?.data?.length == 0 - ) { + if (ipcResponse?.status == 'success' && ipcResponse?.data?.length == 0) { setInstallStatus('success'); setActiveStep( Steps.indexOf('CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED') + 1 @@ -665,76 +653,78 @@ function CheckDependencies({ activeStep, setActiveStep }) { }, [activeStep]); return ( - <> - <Stack spacing={1}> - {installStatus == 'success' && <Chip color="success">Success!</Chip>} - {installStatus == 'pending' && ( - <> - <CircularProgress color="primary" /> - <Typography level="body-sm" color="warning"> - Installing. This can take a long while. - </Typography> - </> - )} - {activeStep == - Steps.indexOf('CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED') && - installStatus == 'notstarted' && ( - <ButtonGroup variant="plain" spacing={1}> - <Button - variant="solid" - size="sm" - startDecorator={<RotateCcwIcon size="16px" />} - onClick={async () => { - setInstallStatus('pending'); - setErrorMessage(null); - await window.electron.ipcRenderer.invoke( - 'server:install_install-dependencies' - ); - - const ipcResponse = - await window.electron.ipcRenderer.invoke( - 'server:checkDependencies' - ); + <Stack spacing={1}> + {installStatus == 'success' && <Chip color="success">Success!</Chip>} + {installStatus == 'pending' && ( + <> + <CircularProgress color="primary" /> + <Typography level="body-sm" color="warning"> + Installing. This can take a long while. + </Typography> + </> + )} + {activeStep == Steps.indexOf('CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED') && + installStatus == 'notstarted' && ( + <ButtonGroup variant="plain" spacing={1}> + <Button + variant="solid" + size="sm" + startDecorator={<RotateCcwIcon size="16px" />} + onClick={async () => { + setInstallStatus('pending'); + setErrorMessage(null); + await window.electron.ipcRenderer.invoke( + 'server:install_install-dependencies' + ); - if ( - ipcResponse?.status == 'success' && - ipcResponse?.data?.length == 0 - ) { - setInstallStatus('success'); - setActiveStep( - Steps.indexOf('CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED') + - 1 - ); - return; - } + const ipcResponse = await window.electron.ipcRenderer.invoke( + 'server:checkDependencies' + ); - if (ipcResponse?.status == 'error') { - setErrorMessage({ - message: ipcResponse?.message, - data: ipcResponse?.data, - }); - } else { - setErrorMessage(null); - } - }} - > - Install Dependencies - </Button> - </ButtonGroup> - )} + if ( + ipcResponse?.status == 'success' && + ipcResponse?.data?.length == 0 + ) { + setInstallStatus('success'); + setActiveStep( + Steps.indexOf('CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED') + 1 + ); + return; + } + + if (ipcResponse?.status == 'error') { + setErrorMessage({ + message: ipcResponse?.message, + data: ipcResponse?.data, + }); + } else { + setErrorMessage(null); + } + }} + > + Install Dependencies + </Button> + </ButtonGroup> + )} - <Typography level="body-sm" color="warning"> - {errorMessage?.message} - </Typography> - <Typography level="body-sm" color="neutral"> - {errorMessage?.data?.stdout} {errorMessage?.data?.stderr} - </Typography> - </Stack> - </> + <Typography level="body-sm" color="warning"> + {errorMessage?.message} + </Typography> + <Typography level="body-sm" color="neutral"> + {errorMessage?.data?.stdout} {errorMessage?.data?.stderr} + </Typography> + </Stack> ); } -function InstallStep({ children, thisStep, title, activeStep, setActiveStep }) { +interface InstallStepProps { + children: React.ReactNode; + title: string; + thisStep: number; +} + +function InstallStep({ children, thisStep, title }: InstallStepProps) { + const { activeStep, setActiveStep } = useLocalConnectionContext(); return ( <Step indicator={ @@ -754,108 +744,84 @@ function InstallStep({ children, thisStep, title, activeStep, setActiveStep }) { ); } -function InstallStepper({ setServer }) { +interface InstallStepperProps { + setServer: (server: string) => void; +} + +function InstallStepper({ setServer }: InstallStepperProps) { const [activeStep, setActiveStep] = useState( Steps.indexOf('CHECK_IF_INSTALLED') - ); // 0, 1, 2 + ); function tryToConnect() { const fullServer = 'http://' + 'localhost' + ':' + '8000' + '/'; - window.TransformerLab = {}; - window.TransformerLab.API_URL = fullServer; + window.TransformerLab = { + API_URL: fullServer, + }; + setActiveStep(Steps.indexOf('CHECK_IF_INSTALLED')); setServer(fullServer); } + return ( - <Sheet - sx={{ - display: 'flex', - flexDirection: 'column', - overflow: 'hidden', - height: '100%', - }} - > - <Stepper - orientation="vertical" - sx={{ display: 'flex', overflow: 'auto' }} + <LocalConnectionProvider> + <Sheet + sx={{ + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + height: '100%', + }} > - {/* Active Step: {activeStep} */} - <InstallStep - thisStep={Steps.indexOf('CHECK_IF_INSTALLED')} - title="Check if Server is Installed at ~/.transformerlab/" - activeStep={activeStep} - setActiveStep={setActiveStep} - > - <CheckIfInstalled - activeStep={activeStep} - setActiveStep={setActiveStep} - /> - </InstallStep> - <InstallStep - thisStep={Steps.indexOf('CHECK_VERSION')} - title="Check Current Version" - activeStep={activeStep} - setActiveStep={setActiveStep} - > - <CheckCurrentVersion - activeStep={activeStep} - setActiveStep={setActiveStep} - /> - </InstallStep> - <InstallStep - thisStep={Steps.indexOf('CHECK_IF_CONDA_INSTALLED')} - title="Check if Conda is Installed at ~/.transformerlab/miniconda3/" - activeStep={activeStep} - setActiveStep={setActiveStep} - > - <CheckIfCondaInstalled - activeStep={activeStep} - setActiveStep={setActiveStep} - /> - </InstallStep> - <InstallStep - thisStep={Steps.indexOf('CHECK_IF_CONDA_ENVIRONMENT_EXISTS')} - title="Check if Conda Environment 'transformerlab' Exists" - activeStep={activeStep} - setActiveStep={setActiveStep} - > - <CheckIfCondaEnvironmentExists - activeStep={activeStep} - setActiveStep={setActiveStep} - /> - </InstallStep> - <InstallStep - thisStep={Steps.indexOf('CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED')} - title="Check if Python Dependencies are Installed" - activeStep={activeStep} - setActiveStep={setActiveStep} - > - <CheckDependencies - activeStep={activeStep} - setActiveStep={setActiveStep} - /> - </InstallStep> - <InstallStep - thisStep={Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000')} - title="Check if Server is Running Locally on Port 8000" - activeStep={activeStep} - setActiveStep={setActiveStep} + <Stepper + orientation="vertical" + sx={{ display: 'flex', overflow: 'auto' }} > - <RunServer activeStep={activeStep} setActiveStep={setActiveStep} /> - </InstallStep> - <InstallStep - thisStep={Steps.indexOf('CHECK_FOR_IMPORTANT_PLUGINS')} - title="Check for Important Plugins" - activeStep={activeStep} - setActiveStep={setActiveStep} - > - <CheckForPlugins - activeStep={activeStep} - setActiveStep={setActiveStep} - /> - </InstallStep> - </Stepper> - { + {/* Active Step: {activeStep} */} + <InstallStep + thisStep={Steps.indexOf('CHECK_IF_INSTALLED')} + title="Check if Server is Installed at ~/.transformerlab/" + > + <CheckIfInstalled /> + </InstallStep> + <InstallStep + thisStep={Steps.indexOf('CHECK_VERSION')} + title="Check Current Version" + > + <CheckCurrentVersion /> + </InstallStep> + <InstallStep + thisStep={Steps.indexOf('CHECK_IF_CONDA_INSTALLED')} + title="Check if Conda is Installed at ~/.transformerlab/miniconda3/" + > + <CheckIfCondaInstalled /> + </InstallStep> + <InstallStep + thisStep={Steps.indexOf('CHECK_IF_CONDA_ENVIRONMENT_EXISTS')} + title="Check if Conda Environment 'transformerlab' Exists" + > + <CheckIfCondaEnvironmentExists /> + </InstallStep> + <InstallStep + thisStep={Steps.indexOf('CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED')} + title="Check if Python Dependencies are Installed" + > + <CheckDependencies /> + </InstallStep> + <InstallStep + thisStep={Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000')} + title="Check if Server is Running Locally on Port 8000" + > + <RunServer /> + </InstallStep> + <InstallStep + thisStep={Steps.indexOf('CHECK_FOR_IMPORTANT_PLUGINS')} + title="Check for Important Plugins" + > + <CheckForPlugins /> + </InstallStep> + </Stepper> + <Button size="lg" variant="solid" @@ -867,13 +833,9 @@ function InstallStepper({ setServer }) { > Connect </Button> - } - </Sheet> + </Sheet> + </LocalConnectionProvider> ); } -function LocalConnection({ setServer }) { - return <InstallStepper setServer={setServer} />; -} - -export default LocalConnection; +export default InstallStepper; diff --git a/src/renderer/components/Connect/context/index.ts b/src/renderer/components/Connect/context/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..65db0994f24a7eb181da75e296925f441d695987 --- /dev/null +++ b/src/renderer/components/Connect/context/index.ts @@ -0,0 +1 @@ +export * from './localConnectionContext' diff --git a/src/renderer/components/Connect/context/localConnectionContext.tsx b/src/renderer/components/Connect/context/localConnectionContext.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f8eea5ea1c3f35d4363bc5cd528cef6800b7fccf --- /dev/null +++ b/src/renderer/components/Connect/context/localConnectionContext.tsx @@ -0,0 +1,35 @@ +import { createContext, useContext, useState } from 'react'; + +interface LocalConnectionContext { + children?: React.ReactNode; + activeStep: number; + setActiveStep: React.Dispatch<React.SetStateAction<number>>; +} + +const LocalConnectionContext = createContext<LocalConnectionContext>({ + activeStep: 0, + setActiveStep: (_val) => {}, +}); + +const LocalConnectionProvider: React.FC<{ + children: React.ReactNode; +}> = ({ children }) => { + const [activeStep, setActiveStep] = useState<number>(0); + return ( + <LocalConnectionContext.Provider value={{ activeStep, setActiveStep }}> + {children} + </LocalConnectionContext.Provider> + ); +}; + +const useLocalConnectionContext = () => { + const context = useContext(LocalConnectionContext); + if (!context) { + throw new Error( + 'useLocalConnectionContext must be used within a LocalConnectionProvider' + ); + } + return context; +}; + +export { LocalConnectionProvider, useLocalConnectionContext }; diff --git a/src/renderer/components/Connect/types/Message.ts b/src/renderer/components/Connect/types/Message.ts new file mode 100644 index 0000000000000000000000000000000000000000..cd385c70c9fbc7a49af236686570e79bed23fdba --- /dev/null +++ b/src/renderer/components/Connect/types/Message.ts @@ -0,0 +1,4 @@ +export type Message = { + message: string; + data: any +} | null; diff --git a/src/renderer/components/Connect/utils/index.ts b/src/renderer/components/Connect/utils/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..45cb61e4f0e26a53b4091d0a5119577430006343 --- /dev/null +++ b/src/renderer/components/Connect/utils/index.ts @@ -0,0 +1,2 @@ +export * from './isStep' +export * from './setIntervalXTimes' diff --git a/src/renderer/components/Connect/utils/isStep.ts b/src/renderer/components/Connect/utils/isStep.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae4662ef9dc50c22f99a3fee65f28db25ce4dfe3 --- /dev/null +++ b/src/renderer/components/Connect/utils/isStep.ts @@ -0,0 +1,14 @@ +export const Steps = [ + 'CHECK_IF_INSTALLED', + 'CHECK_VERSION', + 'CHECK_IF_CONDA_INSTALLED', + 'CHECK_IF_CONDA_ENVIRONMENT_EXISTS', + 'CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED', + 'CHECK_IF_SERVER_RUNNING_ON_PORT_8000', + 'CHECK_FOR_IMPORTANT_PLUGINS', +] as const; +export type Step = (typeof Steps)[number]; + +export function isStep(value: any): value is Step { + return Steps.includes(value as Step); +} diff --git a/src/renderer/components/Connect/utils/setIntervalXTimes.ts b/src/renderer/components/Connect/utils/setIntervalXTimes.ts new file mode 100644 index 0000000000000000000000000000000000000000..0cbabf2b21d0db3c00ace2d69d90169a8a281e78 --- /dev/null +++ b/src/renderer/components/Connect/utils/setIntervalXTimes.ts @@ -0,0 +1,24 @@ +// Runs a callback every delay milliseconds, up to repetitions times. +// If the callback returns true, the interval is cleared. +// If the callback returns false, and the interval has run repetitions times, the notSuccessful callback is run. +export function setIntervalXTimes( + callback: () => any, + notSuccessful: () => void, + delay: number, + repetitions: number +) { + let x = 0; + const intervalID = window.setInterval(async function () { + console.log(`trying ${x} times`); + const response = await callback(); + + if (response) { + window.clearInterval(intervalID); + } else if (++x === repetitions) { + notSuccessful(); + window.clearInterval(intervalID); + } + }, delay); + + return intervalID; +} diff --git a/src/renderer/components/Logs.tsx b/src/renderer/components/Logs.tsx index 748e15be5561c754b8a54f4c1eca94a45da295a3..92d8924c01a932f1848994a6dfd738b4a633aa65 100644 --- a/src/renderer/components/Logs.tsx +++ b/src/renderer/components/Logs.tsx @@ -14,14 +14,16 @@ import * as chatAPI from 'renderer/lib/transformerlab-api-sdk'; import useSWR from 'swr'; -const fetcher = (url) => fetch(url).then((res) => res.text()); +const fetcher = (url: string) => fetch(url).then((res) => res.text()); -function objectMinusPrompt(obj) { - const { prompt, ...rest } = obj; +function objectMinusPrompt({ + prompt, + ...rest +}: Record<string, any>): Record<string, any> { return rest; } -function renderJSONLinesLog(logs) { +function renderJSONLinesLog(logs: string) { return logs?.split('\n').map((line, i) => { try { const line_object = JSON.parse(line); @@ -67,7 +69,7 @@ export default function Logs({}) { flexDirection: 'column', }} > - <AccordionGroup>{renderJSONLinesLog(data)}</AccordionGroup> + <AccordionGroup>{renderJSONLinesLog(data ?? '')}</AccordionGroup> </Box> </Sheet> ); diff --git a/src/renderer/components/MainAppPanel.tsx b/src/renderer/components/MainAppPanel.tsx index 019f1c3595bffed026f270523f73baa47d8f4cdb..3f8c93ef2adfbcd2e7157e0df18993b2683dd403 100644 --- a/src/renderer/components/MainAppPanel.tsx +++ b/src/renderer/components/MainAppPanel.tsx @@ -86,7 +86,7 @@ export default function MainAppPanel({ updateConfigs(); } - function setAdaptor(name) { + function setAdaptor(name: string) { fetch( chatAPI.GET_EXPERIMENT_UPDATE_CONFIG_URL( experimentInfo?.id, diff --git a/src/renderer/components/ModelCurrentlyPlayingBar.tsx b/src/renderer/components/ModelCurrentlyPlayingBar.tsx index 9ffb032abd71e1242c705c74ab23acb335939379..0b27f3a2218680d11d347b970eb7c87e30e2bd9e 100644 --- a/src/renderer/components/ModelCurrentlyPlayingBar.tsx +++ b/src/renderer/components/ModelCurrentlyPlayingBar.tsx @@ -6,7 +6,13 @@ import { import { Box, Button, CircularProgress, Typography } from '@mui/joy'; import TinyCircle from './Shared/TinyCircle'; -export default function ModelCurrentlyPlaying({ experimentInfo }) { +interface ModelCurrentlyPlayingProps { + experimentInfo: Record<string, any>; +} + +export default function ModelCurrentlyPlaying({ + experimentInfo, +}: ModelCurrentlyPlayingProps) { const { models, isError, isLoading } = useModelStatus(); const inferenceParams = experimentInfo?.config?.inferenceParams @@ -50,7 +56,7 @@ export default function ModelCurrentlyPlaying({ experimentInfo }) { variant="plain" sx={{ display: models?.length > 0 ? 'flex' : 'none' }} > - {models?.length == 0 ? ( + {models?.length === 0 ? ( <CircularProgress color="warning" /> ) : ( <StopCircleIcon /> diff --git a/src/renderer/components/TransformerLabSettings.tsx b/src/renderer/components/TransformerLabSettings.tsx index 4134fa3f1d02d04ccca0935b51773204f680f854..aa72337b900ede7e1b2ebb41fcdb6c0b3fbb26a5 100644 --- a/src/renderer/components/TransformerLabSettings.tsx +++ b/src/renderer/components/TransformerLabSettings.tsx @@ -17,7 +17,7 @@ import * as chatAPI from 'renderer/lib/transformerlab-api-sdk'; import useSWR from 'swr'; import { EyeIcon, EyeOffIcon } from 'lucide-react'; -const fetcher = (url) => fetch(url).then((res) => res.json()); +const fetcher = (url: string) => fetch(url).then((res) => res.json()); export default function TransformerLabSettings({}) { const [showPassword, setShowPassword] = React.useState(false); @@ -52,12 +52,16 @@ export default function TransformerLabSettings({}) { endDecorator={ <IconButton onClick={() => { - var x = document.getElementsByName('hftoken')[0]; - if (x.type === 'text') { - x.type = 'password'; + const hfToken = document.getElementsByName( + 'hftoken' + )[0] as HTMLInputElement; + + if (hfToken.type === 'text') { + hfToken.type = 'password'; } else { - x.type = 'text'; + hfToken.type = 'text'; } + setShowPassword(!showPassword); }} > @@ -68,7 +72,10 @@ export default function TransformerLabSettings({}) { )} <Button onClick={async () => { - const token = document.getElementsByName('hftoken')[0].value; + const tokenElement = document.getElementsByName( + 'hftoken' + )[0] as HTMLInputElement; + const token = tokenElement.value; await fetch( chatAPI.Endpoints.Config.Set( 'HuggingfaceUserAccessToken', diff --git a/src/renderer/components/Welcome.tsx b/src/renderer/components/Welcome.tsx index fded8eceb3c7b127b7cb147a129c3235f7e90967..a16faec4497a5d08ed1e50c0fb878aa7e4d9ee7d 100644 --- a/src/renderer/components/Welcome.tsx +++ b/src/renderer/components/Welcome.tsx @@ -1,12 +1,8 @@ /* eslint-disable jsx-a11y/anchor-is-valid */ -import { Button, Sheet, Stack, Typography } from '@mui/joy'; -import { ArrowRightCircleIcon, FlaskConicalIcon } from 'lucide-react'; - -async function testStore() {} +import { Sheet, Stack, Typography } from '@mui/joy'; import labImage from '../img/lab.jpg'; - import flaskLogo from '../img/flask.png'; function LogoComponent() { @@ -14,13 +10,15 @@ function LogoComponent() { <img src={flaskLogo} width="38" - style={{ verticalAlign: 'middle', marginBottom: '10px' }} + style={{ + verticalAlign: 'middle', + marginBottom: '10px', + display: 'inline-block', + }} /> ); } -testStore(); - export default function Welcome() { return ( <Sheet diff --git a/src/renderer/preload.d.ts b/src/renderer/preload.d.ts index 3ff48d7b91ae443f317367c9f4ef75e7d41615f9..08ccd4489b3f9218f9d1f6246a0390e6c7a33891 100644 --- a/src/renderer/preload.d.ts +++ b/src/renderer/preload.d.ts @@ -1,10 +1,26 @@ import { ElectronHandler } from 'main/preload'; +interface Platform { + node: () => string, + chrome: () => string, + electron: () => string, + isMac: () => string, + isWindows: () => string, + isLinux: () => string, + platform: () => string, + arch: () => string, +} + declare global { // eslint-disable-next-line no-unused-vars interface Window { electron: ElectronHandler; + TransformerLab: { + API_URL: string; + } + platform: Platform } + } export {};