From c60e26e68e2113db99bf7ad49571a15fa053a5da Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Fri, 10 Jan 2025 11:46:42 -0500 Subject: [PATCH 01/45] Automatically download the dataset of a recipe if it's not already installed --- .../components/Experiment/Train/ImportRecipeModal.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx index ff2e8731..b159960f 100644 --- a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx +++ b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx @@ -114,7 +114,15 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { msg += "Download model " + response.model.path } if (!response.dataset.downloaded) { - msg += "Download dataset " + response.dataset.path + msg += "Download dataset " + response.dataset.path; + fetch(chatAPI.Endpoints.Dataset.Download(response.dataset.path)) + .then((response) => { + if (!response.ok) { + console.log(response); + throw new Error(`HTTP Status: ${response.status}`); + } + return response.json(); + }) } if (msg) { const alert_msg = "Warning: To use this recipe you will need to: " + msg From 5f7ab22742390f7995ec63237b0cd90b9b588923 Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Fri, 10 Jan 2025 15:50:39 -0500 Subject: [PATCH 02/45] Updated the error messages to better match what was going on, with the model download code commented out for now --- .../Experiment/Train/ImportRecipeModal.tsx | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx index b159960f..30a483a1 100644 --- a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx +++ b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx @@ -110,11 +110,8 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { alert("Warning: This recipe does not have an associated dataset") } else { let msg = ""; - if (!response.model.downloaded) { - msg += "Download model " + response.model.path - } if (!response.dataset.downloaded) { - msg += "Download dataset " + response.dataset.path; + msg += "\n Download dataset " + response.dataset.path + ", this will be done automatically"; fetch(chatAPI.Endpoints.Dataset.Download(response.dataset.path)) .then((response) => { if (!response.ok) { @@ -123,6 +120,28 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { } return response.json(); }) + .catch((error) => { + alert('Download failed:\n' + error); + }); + } + if (!response.model.downloaded) { + msg += "\n Download model " + response.model.path + ", please go to the model zoo to download it"; + // fetch(chatAPI.downloadModelFromHuggingFace(response.model.path)) + // .then((response) => { + // if (!response.ok) { + // console.log(response); + // throw new Error(`HTTP Status: ${response.status}`); + // } + // return response.json(); + // }) + // .then((response_json) => { + // if (response_json?.status == 'error') { + // throw new Error(response_json.message); + // } + // }) + // .catch((error) => { + // alert('Download failed:\n' + error); + // }); } if (msg) { const alert_msg = "Warning: To use this recipe you will need to: " + msg From 85313d83d2a7e43bca20625e5515c227b860bf34 Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Fri, 10 Jan 2025 15:50:39 -0500 Subject: [PATCH 03/45] Updated the error messages to better match what was going on, with the model download code commented out for now --- .../Experiment/Train/ImportRecipeModal.tsx | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx index ff2e8731..30a483a1 100644 --- a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx +++ b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx @@ -110,11 +110,38 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { alert("Warning: This recipe does not have an associated dataset") } else { let msg = ""; - if (!response.model.downloaded) { - msg += "Download model " + response.model.path - } if (!response.dataset.downloaded) { - msg += "Download dataset " + response.dataset.path + msg += "\n Download dataset " + response.dataset.path + ", this will be done automatically"; + fetch(chatAPI.Endpoints.Dataset.Download(response.dataset.path)) + .then((response) => { + if (!response.ok) { + console.log(response); + throw new Error(`HTTP Status: ${response.status}`); + } + return response.json(); + }) + .catch((error) => { + alert('Download failed:\n' + error); + }); + } + if (!response.model.downloaded) { + msg += "\n Download model " + response.model.path + ", please go to the model zoo to download it"; + // fetch(chatAPI.downloadModelFromHuggingFace(response.model.path)) + // .then((response) => { + // if (!response.ok) { + // console.log(response); + // throw new Error(`HTTP Status: ${response.status}`); + // } + // return response.json(); + // }) + // .then((response_json) => { + // if (response_json?.status == 'error') { + // throw new Error(response_json.message); + // } + // }) + // .catch((error) => { + // alert('Download failed:\n' + error); + // }); } if (msg) { const alert_msg = "Warning: To use this recipe you will need to: " + msg From 1de02162733a38ee1ed97d6a6083958c99385cd4 Mon Sep 17 00:00:00 2001 From: ali asaria Date: Mon, 13 Jan 2025 11:10:37 -0500 Subject: [PATCH 04/45] change default api port to 8338 --- .../Connect/DetailedTooltipsForEachStep.json | 2 +- .../components/Connect/LocalConnection.tsx | 24 +++++++++---------- .../components/Connect/LoginModal.tsx | 12 +++++----- .../components/OutputTerminal/index.tsx | 2 +- src/renderer/lib/transformerlab-api-sdk.ts | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/renderer/components/Connect/DetailedTooltipsForEachStep.json b/src/renderer/components/Connect/DetailedTooltipsForEachStep.json index 2e1fccc7..5c6bc88c 100644 --- a/src/renderer/components/Connect/DetailedTooltipsForEachStep.json +++ b/src/renderer/components/Connect/DetailedTooltipsForEachStep.json @@ -4,6 +4,6 @@ "In this step we install Conda if it is not already installed. Conda is a tool that packages a full version of Python specifically for Transformer Lab's Engine. It is used often in Machine Learning because it is good at packaging Python and dependencies alongside the necessary tools to access your GPU, etc. By installing a separate version of Python, we can avoid conflicts with other Python packages you may have installed.", "Now that Conda is installed, we create a 'Conda Environment' just for Transformer Lab. This is where all the Python dependencies will be installed.", "Here we run install all the Python dependencies for Transformer Lab. This step runs 'pip install -r requirements.txt' and then installs Flash Attention if applicable. Transformer Lab incorporates dozens of important LLM Python Libraries so this step can take a very long while as gigabytes of Python packages are downloaded and installed to your machine.", - "Now all the requirements for Transformer Lab's Engine are installed. This step starts up the engine on Port 8000 so that the app can communicate with the engine.", + "Now all the requirements for Transformer Lab's Engine are installed. This step starts up the engine on Port 8338 so that the app can communicate with the engine.", "Plugins are a way to extend the functionality of Transformer Lab. In this step we install only the plugins that are appropriate for your specific machine. For example if you are using a Mac, we will auto install the inference and training plugins that work specifically for Macs. In this step we query the API to ask what plugins are already installed and then install the ones that are missing." ] diff --git a/src/renderer/components/Connect/LocalConnection.tsx b/src/renderer/components/Connect/LocalConnection.tsx index 34c4236e..cddc944d 100644 --- a/src/renderer/components/Connect/LocalConnection.tsx +++ b/src/renderer/components/Connect/LocalConnection.tsx @@ -48,7 +48,7 @@ const Steps = [ 'CHECK_IF_CONDA_INSTALLED', //2 'CHECK_IF_CONDA_ENVIRONMENT_EXISTS', //3 'CHECK_IF_PYTHON_DEPENDENCIES_INSTALLED', //4 - 'CHECK_IF_SERVER_RUNNING_ON_PORT_8000', //5 + 'CHECK_IF_SERVER_RUNNING_ON_PORT_8338', //5 'CHECK_FOR_IMPORTANT_PLUGINS', //6 ]; @@ -117,22 +117,22 @@ function InstallStepper({ setServer }) { } = useCheckLocalConnection(); // This useEffect will be triggered on every server update -- we use this to check - // if the server is running on port 8000 and if so, display the Connect button + // if the server is running on port 8338 and if so, display the Connect button useEffect(() => { - if (activeStep !== Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000')) + if (activeStep !== Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8338')) return; logStep(activeStep); if (server && !serverError) { console.log('The server is up; I think things are good'); - setActiveStep(Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000') + 1); + setActiveStep(Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8338') + 1); setThinking(false); return; } else { console.log('we are on step 6 and the server is not up'); if (userRequestedInstall) { - stepsFunctions[Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000')](); + stepsFunctions[Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8338')](); } } }, [server, activeStep, userRequestedInstall]); @@ -314,7 +314,7 @@ function InstallStepper({ setServer }) { (async () => { const p = await fetch( - 'http://localhost:8000/plugins/list_missing_plugins_for_current_platform' + 'http://localhost:8338/plugins/list_missing_plugins_for_current_platform' ); const json = await p.json(); setMissingPlugins(json); @@ -331,7 +331,7 @@ function InstallStepper({ setServer }) { }, [activeStep, userRequestedInstall]); function tryToConnect() { - const fullServer = 'http://' + 'localhost' + ':' + '8000' + '/'; + const fullServer = 'http://' + 'localhost' + ':' + '8338' + '/'; window.TransformerLab = {}; window.TransformerLab.API_URL = fullServer; setActiveStep(Steps.indexOf('CHECK_IF_INSTALLED')); @@ -345,7 +345,7 @@ function InstallStepper({ setServer }) { // before starting the process, check one more time if it is running if (server && !serverError) { setThinking(false); - setActiveStep(Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000') + 1); + setActiveStep(Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8338') + 1); return; } @@ -534,7 +534,7 @@ function InstallStepper({ setServer }) { async function checkForPlugins() { setInstallingPlugins(true); await fetch( - 'http://localhost:8000/plugins/install_missing_plugins_for_current_platform' + 'http://localhost:8338/plugins/install_missing_plugins_for_current_platform' ); setInstallingPlugins(false); setMissingPlugins([]); @@ -565,7 +565,7 @@ function InstallStepper({ setServer }) { await installDependencies(); setThinking(false); }; - stepsFunctions[Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8000')] = + stepsFunctions[Steps.indexOf('CHECK_IF_SERVER_RUNNING_ON_PORT_8338')] = async () => { await runServer(); // don't run set thinking -- server needs to be polled @@ -661,8 +661,8 @@ function InstallStepper({ setServer }) { activeStep={activeStep} > Server Port - + */} - {/* */} - {/* */} - {/* */} - - - Watch our{' '} - - Getting Started Video - - , or access our{' '} - - full documentation - {' '} - for more ideas! - + + + Watch our{' '} + + Getting Started Video + + , or access our{' '} + + full documentation + {' '} + for more ideas! + + - - - + ); } diff --git a/src/renderer/img/flask.svg b/src/renderer/img/flask.svg deleted file mode 100644 index 01827105..00000000 --- a/src/renderer/img/flask.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/renderer/img/hex.svg b/src/renderer/img/hex.svg new file mode 100644 index 00000000..647548fa --- /dev/null +++ b/src/renderer/img/hex.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + From 3b0521b3c16aebaee7470d85a9d391427cffb82f Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Tue, 14 Jan 2025 11:05:19 -0500 Subject: [PATCH 20/45] hide the progress box if the info is loading --- src/renderer/components/Experiment/Train/TrainLoRA.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/Experiment/Train/TrainLoRA.tsx b/src/renderer/components/Experiment/Train/TrainLoRA.tsx index 8fd6b3bc..29fa469c 100644 --- a/src/renderer/components/Experiment/Train/TrainLoRA.tsx +++ b/src/renderer/components/Experiment/Train/TrainLoRA.tsx @@ -184,7 +184,9 @@ export default function TrainLoRA({ experimentInfo }) { overflow: 'hidden', }} > - + { !downloadJobsIsLoading && + + } {/* Train */} }> From fe5c067523af91ee7999c9dad27a3136395e66b8 Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Tue, 14 Jan 2025 11:09:19 -0500 Subject: [PATCH 21/45] Fixed the training status text when a failure occurs from success to failure. --- src/renderer/components/Experiment/Train/TrainLoRA.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/Experiment/Train/TrainLoRA.tsx b/src/renderer/components/Experiment/Train/TrainLoRA.tsx index bf402945..715d5acd 100644 --- a/src/renderer/components/Experiment/Train/TrainLoRA.tsx +++ b/src/renderer/components/Experiment/Train/TrainLoRA.tsx @@ -538,7 +538,7 @@ export default function TrainLoRA({ experimentInfo }) { level="body-sm" color="danger" > - Success:{' '} + Failure:{' '} {job?.job_data?.completion_details} )} From 6395f67aa8832406eae9f397ec9a7c118dd93ad9 Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Tue, 14 Jan 2025 11:05:19 -0500 Subject: [PATCH 22/45] hide the progress box if the info is loading --- src/renderer/components/Experiment/Train/TrainLoRA.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/components/Experiment/Train/TrainLoRA.tsx b/src/renderer/components/Experiment/Train/TrainLoRA.tsx index 715d5acd..3bffee04 100644 --- a/src/renderer/components/Experiment/Train/TrainLoRA.tsx +++ b/src/renderer/components/Experiment/Train/TrainLoRA.tsx @@ -173,6 +173,9 @@ export default function TrainLoRA({ experimentInfo }) { overflow: 'hidden', }} > + { !downloadJobsIsLoading && + + } {/* Train */} }> From 38dd9f699a40d95a1919b9e02a7fba0b23b012cd Mon Sep 17 00:00:00 2001 From: ali asaria Date: Tue, 14 Jan 2025 20:10:18 -0500 Subject: [PATCH 23/45] checking in a temporary log viewer -- this is hardcoded to only work on localhost. must improve before next release. --- src/renderer/App.tsx | 25 +++--- src/renderer/components/Nav/Sidebar.tsx | 12 ++- .../components/OutputTerminal/index.tsx | 79 +++++++++++++------ 3 files changed, 81 insertions(+), 35 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index f0d20ee6..fb894c3c 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -15,6 +15,7 @@ import * as chatAPI from 'renderer/lib/transformerlab-api-sdk'; import useSWR from 'swr'; import XtermJSDrawer from './components/Connect/XtermJS'; +import OutputTerminal from './components/OutputTerminal'; // import OutputTerminal from './components/OutputTerminal'; // import AutoUpdateModal from './components/AutoUpdateModal'; @@ -27,7 +28,7 @@ export default function App() { const [sshConnection, setSSHConnection] = useState(null); - const [drawerOpen, setDrawerOpen] = useState(false); + const [logsDrawerOpen, setLogsDrawerOpen] = useState(false); useEffect(() => { async function getSavedExperimentId() { @@ -84,13 +85,18 @@ export default function App() { width: '100dvw', overflow: 'hidden', gridTemplateColumns: '220px 1fr', - gridTemplateRows: '60px 5fr 0fr', - gridTemplateAreas: ` + gridTemplateRows: logsDrawerOpen ? '60px 5fr 1fr' : '60px 5fr 0.01fr', + gridTemplateAreas: logsDrawerOpen + ? ` + "sidebar header" + "sidebar main" + "sidebar footer" + ` + : ` "sidebar header" "sidebar main" "sidebar footer" `, - // backgroundColor: (theme) => theme.vars.palette.background.surface, })} > @@ -105,7 +111,8 @@ export default function App() { - {/* */} + - + /> */} ); } diff --git a/src/renderer/components/Nav/Sidebar.tsx b/src/renderer/components/Nav/Sidebar.tsx index 24014138..d9d3ec7c 100644 --- a/src/renderer/components/Nav/Sidebar.tsx +++ b/src/renderer/components/Nav/Sidebar.tsx @@ -22,6 +22,7 @@ import { FolderSearch2Icon, TextIcon, RectangleEllipsisIcon, + LogsIcon, } from 'lucide-react'; import { ButtonGroup, IconButton, Sheet, Tooltip } from '@mui/joy'; @@ -39,7 +40,8 @@ import ColorSchemeToggle from './ColorSchemeToggle'; export default function Sidebar({ experimentInfo, setExperimentId, - setDrawerOpen, + logsDrawerOpen, + setLogsDrawerOpen, }) { const { models, isError, isLoading } = useModelStatus(); const { outdatedPluginsCount } = usePluginStatus(experimentInfo); @@ -269,6 +271,14 @@ export default function Sidebar({ + + setLogsDrawerOpen(!logsDrawerOpen)} + > + + + diff --git a/src/renderer/components/OutputTerminal/index.tsx b/src/renderer/components/OutputTerminal/index.tsx index 204d97aa..61034bac 100644 --- a/src/renderer/components/OutputTerminal/index.tsx +++ b/src/renderer/components/OutputTerminal/index.tsx @@ -1,40 +1,69 @@ -import { Sheet } from '@mui/joy'; -import { useEffect } from 'react'; +import { Box, Sheet } from '@mui/joy'; +import { FitAddon } from '@xterm/addon-fit'; +import { Terminal } from '@xterm/xterm'; +import { useEffect, useRef } from 'react'; export default function OutputTerminal({}) { + const terminalRef = useRef(null); + let term: Terminal | null = null; + + const fitAddon = new FitAddon(); + + function handleResize() { + fitAddon.fit(); + } + useEffect(() => { - var source = new EventSource('http://pop-os:8338/stream-logs'); + // This is hardcoded to local for now -- just building + var source = new EventSource('http://localhost:8338/server/stream_log'); source.onmessage = function (event) { - var logs = document.getElementById('logs'); - logs.innerHTML += event.data + '
'; - // Scroll to bottom - logs.scrollTop = document.getElementById('logs').scrollHeight; + // console.log(event.data); + // var logs = document.getElementById('logs'); + // logs.innerHTML += event.data + '
'; + // // Scroll to bottom + // logs.scrollTop = document.getElementById('logs').scrollHeight; + + if (term !== null) { + console.log(event.data); + const lines = JSON.parse(event.data); + console.log(lines); + lines.forEach((line: string) => { + term.writeln(line); + if (terminalRef.current) { + terminalRef.current.scrollIntoView({ behavior: 'smooth' }); + } + }); + } + window.addEventListener('resize', handleResize); }; + term = new Terminal(); + term.loadAddon(fitAddon); + + if (terminalRef.current) term.open(terminalRef.current); + fitAddon.fit(); + + window.addEventListener('resize', handleResize); + return () => { + term?.dispose(); source.close(); + window.removeEventListener('resize', handleResize); }; }); return ( - -
+
-
+ ref={terminalRef} + > + ); } From b0c978d71f6ef32e2c2fab449d04b1a8f999e890 Mon Sep 17 00:00:00 2001 From: ali asaria Date: Wed, 15 Jan 2025 11:08:35 -0500 Subject: [PATCH 24/45] improve look of xterm at bottom of screen --- .../components/OutputTerminal/index.tsx | 92 +++++++++++++------ src/renderer/styles.css | 8 ++ 2 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/renderer/components/OutputTerminal/index.tsx b/src/renderer/components/OutputTerminal/index.tsx index 61034bac..3d345e03 100644 --- a/src/renderer/components/OutputTerminal/index.tsx +++ b/src/renderer/components/OutputTerminal/index.tsx @@ -1,11 +1,18 @@ import { Box, Sheet } from '@mui/joy'; import { FitAddon } from '@xterm/addon-fit'; import { Terminal } from '@xterm/xterm'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useState } from 'react'; + +import useSWRSubscription from 'swr/subscription'; +import type { SWRSubscriptionOptions } from 'swr/subscription'; + +const TERMINAL_SPEED = 100; //ms between adding each line (create an animation effect) export default function OutputTerminal({}) { const terminalRef = useRef(null); let term: Terminal | null = null; + let lineQueue: string[] = []; + let isProcessing = false; const fitAddon = new FitAddon(); @@ -13,31 +20,35 @@ export default function OutputTerminal({}) { fitAddon.fit(); } - useEffect(() => { - // This is hardcoded to local for now -- just building - var source = new EventSource('http://localhost:8338/server/stream_log'); - source.onmessage = function (event) { - // console.log(event.data); - // var logs = document.getElementById('logs'); - // logs.innerHTML += event.data + '
'; - // // Scroll to bottom - // logs.scrollTop = document.getElementById('logs').scrollHeight; + function processQueue() { + if (lineQueue.length === 0) { + isProcessing = false; + return; + } - if (term !== null) { - console.log(event.data); - const lines = JSON.parse(event.data); - console.log(lines); - lines.forEach((line: string) => { - term.writeln(line); - if (terminalRef.current) { - terminalRef.current.scrollIntoView({ behavior: 'smooth' }); - } - }); - } - window.addEventListener('resize', handleResize); - }; + isProcessing = true; + const line = lineQueue.shift()!; + term?.writeln(line.replace(/\n$/, '')); + if (terminalRef.current) { + terminalRef.current.scrollIntoView({ behavior: 'smooth' }); + } + + setTimeout(() => { + processQueue(); + }, TERMINAL_SPEED); // 100ms delay between each line + } + + function addLinesOneByOne(lines: string[]) { + lineQueue = lineQueue.concat(lines); + if (!isProcessing) { + processQueue(); + } + } - term = new Terminal(); + useEffect(() => { + term = new Terminal({ + smoothScrollDuration: 200, // Set smooth scroll duration to 200ms + }); term.loadAddon(fitAddon); if (terminalRef.current) term.open(terminalRef.current); @@ -45,22 +56,43 @@ export default function OutputTerminal({}) { window.addEventListener('resize', handleResize); + const eventSource = new EventSource( + 'http://localhost:8338/server/stream_log' + ); + eventSource.onmessage = (event) => { + if (term !== null) { + const lines = JSON.parse(event.data); + addLinesOneByOne(lines); + } + }; + eventSource.onerror = (error) => { + console.error('EventSource failed:', error); + }; + return () => { + eventSource.close(); term?.dispose(); - source.close(); window.removeEventListener('resize', handleResize); }; }); return ( - + diff --git a/src/renderer/styles.css b/src/renderer/styles.css index b16be2ac..ee46913a 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -187,4 +187,12 @@ textarea { font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI"; font-size: 16px; padding: 16px; +} + +.xterm-viewport::-webkit-scrollbar { + background-color: var(--joy-palette-background-level-1); +} + +.xterm-viewport::-webkit-scrollbar-thumb { + background: var(--joy-palette-primary-400); } \ No newline at end of file From c26fe793f7fb8fb1a3e8988fcb4d0991937889df Mon Sep 17 00:00:00 2001 From: ali asaria Date: Wed, 15 Jan 2025 11:10:53 -0500 Subject: [PATCH 25/45] remove hardcoded URL in log viewer --- src/renderer/components/OutputTerminal/index.tsx | 5 ++--- src/renderer/lib/transformerlab-api-sdk.ts | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/OutputTerminal/index.tsx b/src/renderer/components/OutputTerminal/index.tsx index 3d345e03..c825f8e4 100644 --- a/src/renderer/components/OutputTerminal/index.tsx +++ b/src/renderer/components/OutputTerminal/index.tsx @@ -3,8 +3,7 @@ import { FitAddon } from '@xterm/addon-fit'; import { Terminal } from '@xterm/xterm'; import { useEffect, useRef, useState } from 'react'; -import useSWRSubscription from 'swr/subscription'; -import type { SWRSubscriptionOptions } from 'swr/subscription'; +import * as chatAPI from '../../lib/transformerlab-api-sdk'; const TERMINAL_SPEED = 100; //ms between adding each line (create an animation effect) @@ -57,7 +56,7 @@ export default function OutputTerminal({}) { window.addEventListener('resize', handleResize); const eventSource = new EventSource( - 'http://localhost:8338/server/stream_log' + chatAPI.Endpoints.ServerInfo.StreamLog() ); eventSource.onmessage = (event) => { if (term !== null) { diff --git a/src/renderer/lib/transformerlab-api-sdk.ts b/src/renderer/lib/transformerlab-api-sdk.ts index 1040bec5..e9a8638e 100644 --- a/src/renderer/lib/transformerlab-api-sdk.ts +++ b/src/renderer/lib/transformerlab-api-sdk.ts @@ -1133,6 +1133,7 @@ Endpoints.Recipes = { Endpoints.ServerInfo = { Get: () => API_URL() + 'server/info', PythonLibraries: () => API_URL() + 'server/python_libraries', + StreamLog: () => API_URL() + 'server/stream_log', }; export function GET_TRAINING_TEMPLATE_URL() { From dfff775e1ba2de53ba9505406b2d3e5b223198ce Mon Sep 17 00:00:00 2001 From: ali asaria Date: Wed, 15 Jan 2025 12:35:34 -0500 Subject: [PATCH 26/45] improve global log panel on resize --- src/renderer/App.tsx | 15 +------ .../components/OutputTerminal/index.tsx | 43 +++++++++++++------ 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index fb894c3c..b00e0a25 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -85,14 +85,8 @@ export default function App() { width: '100dvw', overflow: 'hidden', gridTemplateColumns: '220px 1fr', - gridTemplateRows: logsDrawerOpen ? '60px 5fr 1fr' : '60px 5fr 0.01fr', - gridTemplateAreas: logsDrawerOpen - ? ` - "sidebar header" - "sidebar main" - "sidebar footer" - ` - : ` + gridTemplateRows: logsDrawerOpen ? '60px 5fr 1fr' : '60px 5fr 0.5fr', + gridTemplateAreas: ` "sidebar header" "sidebar main" "sidebar footer" @@ -145,11 +139,6 @@ export default function App() { setSSHConnection={setSSHConnection} /> - {/* */} ); } diff --git a/src/renderer/components/OutputTerminal/index.tsx b/src/renderer/components/OutputTerminal/index.tsx index c825f8e4..06cc0733 100644 --- a/src/renderer/components/OutputTerminal/index.tsx +++ b/src/renderer/components/OutputTerminal/index.tsx @@ -1,13 +1,12 @@ -import { Box, Sheet } from '@mui/joy'; +import { useEffect, useRef } from 'react'; import { FitAddon } from '@xterm/addon-fit'; import { Terminal } from '@xterm/xterm'; -import { useEffect, useRef, useState } from 'react'; - import * as chatAPI from '../../lib/transformerlab-api-sdk'; +import { Box, Sheet } from '@mui/joy'; const TERMINAL_SPEED = 100; //ms between adding each line (create an animation effect) -export default function OutputTerminal({}) { +const OutputTerminal = ({}) => { const terminalRef = useRef(null); let term: Terminal | null = null; let lineQueue: string[] = []; @@ -15,11 +14,11 @@ export default function OutputTerminal({}) { const fitAddon = new FitAddon(); - function handleResize() { + const handleResize = () => { fitAddon.fit(); - } + }; - function processQueue() { + const processQueue = () => { if (lineQueue.length === 0) { isProcessing = false; return; @@ -35,14 +34,14 @@ export default function OutputTerminal({}) { setTimeout(() => { processQueue(); }, TERMINAL_SPEED); // 100ms delay between each line - } + }; - function addLinesOneByOne(lines: string[]) { + const addLinesOneByOne = (lines: string[]) => { lineQueue = lineQueue.concat(lines); if (!isProcessing) { processQueue(); } - } + }; useEffect(() => { term = new Terminal({ @@ -53,7 +52,13 @@ export default function OutputTerminal({}) { if (terminalRef.current) term.open(terminalRef.current); fitAddon.fit(); - window.addEventListener('resize', handleResize); + const resizeObserver = new ResizeObserver(() => { + handleResize(); + }); + + if (terminalRef.current) { + resizeObserver.observe(terminalRef.current); + } const eventSource = new EventSource( chatAPI.Endpoints.ServerInfo.StreamLog() @@ -71,8 +76,18 @@ export default function OutputTerminal({}) { return () => { eventSource.close(); term?.dispose(); - window.removeEventListener('resize', handleResize); + if (terminalRef.current) { + resizeObserver.unobserve(terminalRef.current); + } + resizeObserver.disconnect(); }; + }, [chatAPI.Endpoints.ServerInfo.StreamLog()]); + + useEffect(() => { + const timeoutId = setTimeout(() => { + handleResize(); + }, 1000); + return () => clearTimeout(timeoutId); }); return ( @@ -97,4 +112,6 @@ export default function OutputTerminal({}) { > ); -} +}; + +export default OutputTerminal; From 2e876c3893321fe2cdf949c230fb47a2a12c7e98 Mon Sep 17 00:00:00 2001 From: ali asaria Date: Wed, 15 Jan 2025 13:02:39 -0500 Subject: [PATCH 27/45] resize log window using chevron --- src/renderer/App.tsx | 31 +++++++++++++++++-- src/renderer/components/Nav/Sidebar.tsx | 4 +-- .../components/OutputTerminal/index.tsx | 22 +++++++------ 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index b00e0a25..182a3d16 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -16,6 +16,13 @@ import * as chatAPI from 'renderer/lib/transformerlab-api-sdk'; import useSWR from 'swr'; import XtermJSDrawer from './components/Connect/XtermJS'; import OutputTerminal from './components/OutputTerminal'; +import { + ChevronDown, + ChevronDownIcon, + ChevronUpIcon, + Icon, +} from 'lucide-react'; +import { IconButton } from '@mui/joy'; // import OutputTerminal from './components/OutputTerminal'; // import AutoUpdateModal from './components/AutoUpdateModal'; @@ -85,7 +92,9 @@ export default function App() { width: '100dvw', overflow: 'hidden', gridTemplateColumns: '220px 1fr', - gridTemplateRows: logsDrawerOpen ? '60px 5fr 1fr' : '60px 5fr 0.5fr', + gridTemplateRows: logsDrawerOpen + ? '60px 5fr 300px' + : '60px 5fr 120px', gridTemplateAreas: ` "sidebar header" "sidebar main" @@ -131,7 +140,25 @@ export default function App() { experimentInfoMutate={experimentInfoMutate} /> - + + setLogsDrawerOpen(!logsDrawerOpen)} + > + {logsDrawerOpen ? : } + + + - + {/* setLogsDrawerOpen(!logsDrawerOpen)} > - + */} diff --git a/src/renderer/components/OutputTerminal/index.tsx b/src/renderer/components/OutputTerminal/index.tsx index 06cc0733..5f31d26e 100644 --- a/src/renderer/components/OutputTerminal/index.tsx +++ b/src/renderer/components/OutputTerminal/index.tsx @@ -6,6 +6,15 @@ import { Box, Sheet } from '@mui/joy'; const TERMINAL_SPEED = 100; //ms between adding each line (create an animation effect) +// Debounce function +const debounce = (func: Function, wait: number) => { + let timeout: NodeJS.Timeout; + return (...args: any[]) => { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), wait); + }; +}; + const OutputTerminal = ({}) => { const terminalRef = useRef(null); let term: Terminal | null = null; @@ -14,9 +23,9 @@ const OutputTerminal = ({}) => { const fitAddon = new FitAddon(); - const handleResize = () => { + const handleResize = debounce(() => { fitAddon.fit(); - }; + }, 300); const processQueue = () => { if (lineQueue.length === 0) { @@ -83,22 +92,15 @@ const OutputTerminal = ({}) => { }; }, [chatAPI.Endpoints.ServerInfo.StreamLog()]); - useEffect(() => { - const timeoutId = setTimeout(() => { - handleResize(); - }, 1000); - return () => clearTimeout(timeoutId); - }); - return ( Date: Wed, 15 Jan 2025 14:36:06 -0500 Subject: [PATCH 28/45] tweaks to size --- src/renderer/App.tsx | 23 +++++++++++++++---- .../components/OutputTerminal/index.tsx | 23 +++++-------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 182a3d16..3d987485 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -92,9 +92,7 @@ export default function App() { width: '100dvw', overflow: 'hidden', gridTemplateColumns: '220px 1fr', - gridTemplateRows: logsDrawerOpen - ? '60px 5fr 300px' - : '60px 5fr 120px', + gridTemplateRows: logsDrawerOpen ? '60px 5fr 300px' : '60px 5fr 80px', gridTemplateAreas: ` "sidebar header" "sidebar main" @@ -155,9 +153,24 @@ export default function App() { sx={{ padding: 0, margin: 0, minHeight: 0 }} onClick={() => setLogsDrawerOpen(!logsDrawerOpen)} > - {logsDrawerOpen ? : } + {logsDrawerOpen ? ( + + ) : ( + + )} - + + {' '} + { }, [chatAPI.Endpoints.ServerInfo.StreamLog()]); return ( - - - + ref={terminalRef} + > ); }; From fa04640cc225a22181181bbe40b8702979aae3bc Mon Sep 17 00:00:00 2001 From: ali asaria Date: Wed, 15 Jan 2025 14:38:33 -0500 Subject: [PATCH 29/45] fit terminal to window better --- src/renderer/App.tsx | 2 +- src/renderer/components/OutputTerminal/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 3d987485..41e7a16d 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -92,7 +92,7 @@ export default function App() { width: '100dvw', overflow: 'hidden', gridTemplateColumns: '220px 1fr', - gridTemplateRows: logsDrawerOpen ? '60px 5fr 300px' : '60px 5fr 80px', + gridTemplateRows: logsDrawerOpen ? '60px 5fr 308px' : '60px 5fr 80px', gridTemplateAreas: ` "sidebar header" "sidebar main" diff --git a/src/renderer/components/OutputTerminal/index.tsx b/src/renderer/components/OutputTerminal/index.tsx index 7992572f..a31385d3 100644 --- a/src/renderer/components/OutputTerminal/index.tsx +++ b/src/renderer/components/OutputTerminal/index.tsx @@ -35,7 +35,7 @@ const OutputTerminal = ({}) => { isProcessing = true; const line = lineQueue.shift()!; - term?.writeln(line.replace(/\n$/, '')); + term?.write(line.replace(/\n$/, '\r\n')); if (terminalRef.current) { terminalRef.current.scrollIntoView({ behavior: 'smooth' }); } From daf30d50952e9db23929a65a3221674b9291052d Mon Sep 17 00:00:00 2001 From: ali asaria Date: Wed, 15 Jan 2025 14:41:01 -0500 Subject: [PATCH 30/45] hide terminal completely on collapse --- src/renderer/App.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 41e7a16d..c5bea45f 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -23,6 +23,7 @@ import { Icon, } from 'lucide-react'; import { IconButton } from '@mui/joy'; +import { log } from 'node:console'; // import OutputTerminal from './components/OutputTerminal'; // import AutoUpdateModal from './components/AutoUpdateModal'; @@ -92,7 +93,7 @@ export default function App() { width: '100dvw', overflow: 'hidden', gridTemplateColumns: '220px 1fr', - gridTemplateRows: logsDrawerOpen ? '60px 5fr 308px' : '60px 5fr 80px', + gridTemplateRows: logsDrawerOpen ? '60px 5fr 308px' : '60px 5fr 18px', gridTemplateAreas: ` "sidebar header" "sidebar main" @@ -143,7 +144,7 @@ export default function App() { gridArea: 'footer', display: 'flex', flexDirection: 'column', - height: '100%', + height: logsDrawerOpen ? '100%' : '18px', width: '100%', overflow: 'hidden', alignItems: 'flex-end', @@ -161,7 +162,7 @@ export default function App() { Date: Wed, 15 Jan 2025 14:44:35 -0500 Subject: [PATCH 31/45] allow any logendpoint --- src/renderer/components/OutputTerminal/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/renderer/components/OutputTerminal/index.tsx b/src/renderer/components/OutputTerminal/index.tsx index a31385d3..35d95194 100644 --- a/src/renderer/components/OutputTerminal/index.tsx +++ b/src/renderer/components/OutputTerminal/index.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'; import { FitAddon } from '@xterm/addon-fit'; import { Terminal } from '@xterm/xterm'; import * as chatAPI from '../../lib/transformerlab-api-sdk'; -import { Box, Sheet } from '@mui/joy'; +import { Sheet } from '@mui/joy'; const TERMINAL_SPEED = 100; //ms between adding each line (create an animation effect) @@ -15,7 +15,9 @@ const debounce = (func: Function, wait: number) => { }; }; -const OutputTerminal = ({}) => { +const OutputTerminal = ({ + logEndpoint = chatAPI.Endpoints.ServerInfo.StreamLog(), +}) => { const terminalRef = useRef(null); let term: Terminal | null = null; let lineQueue: string[] = []; @@ -69,9 +71,7 @@ const OutputTerminal = ({}) => { resizeObserver.observe(terminalRef.current); } - const eventSource = new EventSource( - chatAPI.Endpoints.ServerInfo.StreamLog() - ); + const eventSource = new EventSource(logEndpoint); eventSource.onmessage = (event) => { if (term !== null) { const lines = JSON.parse(event.data); @@ -90,7 +90,7 @@ const OutputTerminal = ({}) => { } resizeObserver.disconnect(); }; - }, [chatAPI.Endpoints.ServerInfo.StreamLog()]); + }, [logEndpoint]); return ( Date: Wed, 15 Jan 2025 18:38:02 -0500 Subject: [PATCH 32/45] Streamthe output of trainers --- .../components/Experiment/Train/TrainLoRA.tsx | 3 +- .../Train/ViewOutputModalStreaming.tsx | 36 +++++++++++++++++++ .../components/OutputTerminal/index.tsx | 5 ++- src/renderer/lib/transformerlab-api-sdk.ts | 2 ++ 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 src/renderer/components/Experiment/Train/ViewOutputModalStreaming.tsx diff --git a/src/renderer/components/Experiment/Train/TrainLoRA.tsx b/src/renderer/components/Experiment/Train/TrainLoRA.tsx index 715d5acd..c86800f8 100644 --- a/src/renderer/components/Experiment/Train/TrainLoRA.tsx +++ b/src/renderer/components/Experiment/Train/TrainLoRA.tsx @@ -46,6 +46,7 @@ import ImportRecipeModal from './ImportRecipeModal'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; +import ViewOutputModalStreaming from './ViewOutputModalStreaming'; dayjs.extend(relativeTime); var duration = require('dayjs/plugin/duration'); dayjs.extend(duration); @@ -156,7 +157,7 @@ export default function TrainLoRA({ experimentInfo }) { currentTensorboard={currentTensorboardForModal} setCurrentTensorboard={setCurrentTensorboardForModal} /> - diff --git a/src/renderer/components/Experiment/Train/ViewOutputModalStreaming.tsx b/src/renderer/components/Experiment/Train/ViewOutputModalStreaming.tsx new file mode 100644 index 00000000..6eed6f0d --- /dev/null +++ b/src/renderer/components/Experiment/Train/ViewOutputModalStreaming.tsx @@ -0,0 +1,36 @@ +import useSWR from 'swr'; + +import { Box, Modal, ModalClose, ModalDialog, Typography } from '@mui/joy'; + +import * as chatAPI from 'renderer/lib/transformerlab-api-sdk'; +import OutputTerminal from 'renderer/components/OutputTerminal'; + +const fetcher = (url) => fetch(url).then((res) => res.json()); + +export default function ViewOutputModalStreaming({ jobId, setJobId }) { + return ( + setJobId(-1)}> + + + Output from job: {jobId} + + + + + + ); +} diff --git a/src/renderer/components/OutputTerminal/index.tsx b/src/renderer/components/OutputTerminal/index.tsx index 35d95194..1f884455 100644 --- a/src/renderer/components/OutputTerminal/index.tsx +++ b/src/renderer/components/OutputTerminal/index.tsx @@ -4,8 +4,6 @@ import { Terminal } from '@xterm/xterm'; import * as chatAPI from '../../lib/transformerlab-api-sdk'; import { Sheet } from '@mui/joy'; -const TERMINAL_SPEED = 100; //ms between adding each line (create an animation effect) - // Debounce function const debounce = (func: Function, wait: number) => { let timeout: NodeJS.Timeout; @@ -17,6 +15,7 @@ const debounce = (func: Function, wait: number) => { const OutputTerminal = ({ logEndpoint = chatAPI.Endpoints.ServerInfo.StreamLog(), + lineAnimationDelay = 100, }) => { const terminalRef = useRef(null); let term: Terminal | null = null; @@ -44,7 +43,7 @@ const OutputTerminal = ({ setTimeout(() => { processQueue(); - }, TERMINAL_SPEED); // 100ms delay between each line + }, lineAnimationDelay); // 100ms delay between each line }; const addLinesOneByOne = (lines: string[]) => { diff --git a/src/renderer/lib/transformerlab-api-sdk.ts b/src/renderer/lib/transformerlab-api-sdk.ts index e9a8638e..8ea977b4 100644 --- a/src/renderer/lib/transformerlab-api-sdk.ts +++ b/src/renderer/lib/transformerlab-api-sdk.ts @@ -1323,6 +1323,8 @@ Endpoints.Experiment = { 'plugins/delete_plugin?pluginId=' + pluginId, GetOutputFromJob: (jobId: string) => API_URL() + `train/job/${jobId}/output`, + StreamOutputFromJob: (jobId: string) => + API_URL() + `train/job/${jobId}/stream_output`, }; Endpoints.Jobs = { From daf3f71e2545a168c576c3d493cb76707285b76d Mon Sep 17 00:00:00 2001 From: ali asaria Date: Wed, 15 Jan 2025 21:32:33 -0500 Subject: [PATCH 33/45] fix color --- src/renderer/App.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index c5bea45f..0c2e0a17 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -148,6 +148,7 @@ export default function App() { width: '100%', overflow: 'hidden', alignItems: 'flex-end', + backgroundColor: 'var(--joy-palette-background-level3)', }} > Date: Wed, 15 Jan 2025 21:33:46 -0500 Subject: [PATCH 34/45] fix --- src/renderer/components/Data/DataStore.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/Data/DataStore.tsx b/src/renderer/components/Data/DataStore.tsx index 1613c143..27eb4a18 100644 --- a/src/renderer/components/Data/DataStore.tsx +++ b/src/renderer/components/Data/DataStore.tsx @@ -28,7 +28,14 @@ export default function DataStore() { if (error) return 'An error has occurred.'; if (isLoading) return ; return ( - <> + - + ); } From 663c20934f029b25c969ca44f0ef576455b8973b Mon Sep 17 00:00:00 2001 From: ali asaria Date: Thu, 16 Jan 2025 09:04:12 -0500 Subject: [PATCH 35/45] allow the output terminal to display an initial message --- src/renderer/App.tsx | 2 +- src/renderer/components/OutputTerminal/index.tsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 0c2e0a17..b7e981fe 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -171,7 +171,7 @@ export default function App() { width: '100%', }} > - {' '} + { const OutputTerminal = ({ logEndpoint = chatAPI.Endpoints.ServerInfo.StreamLog(), lineAnimationDelay = 100, + initialMessage = '', }) => { const terminalRef = useRef(null); let term: Terminal | null = null; @@ -70,6 +71,8 @@ const OutputTerminal = ({ resizeObserver.observe(terminalRef.current); } + term?.writeln(initialMessage); + const eventSource = new EventSource(logEndpoint); eventSource.onmessage = (event) => { if (term !== null) { From bf1b90b444f700ea0217dbc8fe4819dc59231028 Mon Sep 17 00:00:00 2001 From: ali asaria Date: Thu, 16 Jan 2025 09:04:56 -0500 Subject: [PATCH 36/45] remove unused imports --- src/renderer/components/Connect/LocalConnection.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/renderer/components/Connect/LocalConnection.tsx b/src/renderer/components/Connect/LocalConnection.tsx index cddc944d..d4487b37 100644 --- a/src/renderer/components/Connect/LocalConnection.tsx +++ b/src/renderer/components/Connect/LocalConnection.tsx @@ -15,8 +15,6 @@ import { useCheckLocalConnection } from 'renderer/lib/transformerlab-api-sdk'; import LargeTooltip from './LargeTooltip'; import LogViewer from './LogViewer'; -import { BsFillFileEarmarkPersonFill } from 'react-icons/bs'; -import { error, log } from 'console'; // Runs a callback every delay milliseconds, up to repetitions times. // If the callback returns true, the interval is cleared. From 89a6aa4f45c2268f8ca39c28ba7ff0e4ad1dcedf Mon Sep 17 00:00:00 2001 From: ali asaria Date: Thu, 16 Jan 2025 09:18:04 -0500 Subject: [PATCH 37/45] add a counter to the login screen to show elapsed seconds --- .../components/Connect/LocalConnection.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/renderer/components/Connect/LocalConnection.tsx b/src/renderer/components/Connect/LocalConnection.tsx index d4487b37..060b90ef 100644 --- a/src/renderer/components/Connect/LocalConnection.tsx +++ b/src/renderer/components/Connect/LocalConnection.tsx @@ -589,6 +589,24 @@ function InstallStepper({ setServer }) { }; // This function is called if specific strings in the Log are sent + const [elapsedTime, setElapsedTime] = useState(0); + const [intervalId, setIntervalId] = useState(null); + + useEffect(() => { + let id; + if (userRequestedInstall) { + id = setInterval(() => { + setElapsedTime((prevTime) => prevTime + 1); + }, 1000); + setIntervalId(id); + } else { + clearInterval(intervalId); + setElapsedTime(0); + } + + return () => clearInterval(id); + }, [userRequestedInstall]); + return ( )} {userRequestedInstall ? 'Connecting...' : 'Connect'} + {userRequestedInstall && ( + {elapsedTime}s + )} From 331b9152e5c89c04e2f164d332612d206109866a Mon Sep 17 00:00:00 2001 From: ali asaria Date: Thu, 16 Jan 2025 09:43:26 -0500 Subject: [PATCH 38/45] display an alertbox that shows up if the install is taking too long --- .../components/Connect/LocalConnection.tsx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/Connect/LocalConnection.tsx b/src/renderer/components/Connect/LocalConnection.tsx index 060b90ef..dc7abdc1 100644 --- a/src/renderer/components/Connect/LocalConnection.tsx +++ b/src/renderer/components/Connect/LocalConnection.tsx @@ -2,14 +2,16 @@ import { Alert, Button, CircularProgress, + Modal, Sheet, + Snackbar, Step, StepIndicator, Stepper, Tooltip, Typography, } from '@mui/joy'; -import { CheckCircle2, InfoIcon } from 'lucide-react'; +import { CheckCircle2, InfoIcon, TimerIcon } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useCheckLocalConnection } from 'renderer/lib/transformerlab-api-sdk'; @@ -617,6 +619,38 @@ function InstallStepper({ setServer }) { gap: 1, }} > + {elapsedTime > 15 && ( + } + > + + The initial setup process may take a few minutes as it sets up a + Python ML workspace on your computer. Subsequent connections will be + much faster. + {elapsedTime > 25 && ( + <> +
+
+ If it appears like nothing is happening for a while, check the + terminal for any potential errors. You can safely close the the + application and start it again if things appear stuck for more + than a minute. + + )} +
+
+ )} Date: Fri, 10 Jan 2025 15:50:39 -0500 Subject: [PATCH 39/45] Updated the error messages to better match what was going on, with the model download code commented out for now --- .../Experiment/Train/ImportRecipeModal.tsx | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx index ff2e8731..30a483a1 100644 --- a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx +++ b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx @@ -110,11 +110,38 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { alert("Warning: This recipe does not have an associated dataset") } else { let msg = ""; - if (!response.model.downloaded) { - msg += "Download model " + response.model.path - } if (!response.dataset.downloaded) { - msg += "Download dataset " + response.dataset.path + msg += "\n Download dataset " + response.dataset.path + ", this will be done automatically"; + fetch(chatAPI.Endpoints.Dataset.Download(response.dataset.path)) + .then((response) => { + if (!response.ok) { + console.log(response); + throw new Error(`HTTP Status: ${response.status}`); + } + return response.json(); + }) + .catch((error) => { + alert('Download failed:\n' + error); + }); + } + if (!response.model.downloaded) { + msg += "\n Download model " + response.model.path + ", please go to the model zoo to download it"; + // fetch(chatAPI.downloadModelFromHuggingFace(response.model.path)) + // .then((response) => { + // if (!response.ok) { + // console.log(response); + // throw new Error(`HTTP Status: ${response.status}`); + // } + // return response.json(); + // }) + // .then((response_json) => { + // if (response_json?.status == 'error') { + // throw new Error(response_json.message); + // } + // }) + // .catch((error) => { + // alert('Download failed:\n' + error); + // }); } if (msg) { const alert_msg = "Warning: To use this recipe you will need to: " + msg From 98d05b1f8c2cc10ffe526e6d3ca369759aebd3de Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Mon, 13 Jan 2025 12:01:07 -0500 Subject: [PATCH 40/45] Make importing models work, still need to add the downloading popup --- .../Experiment/Train/ImportRecipeModal.tsx | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx index 30a483a1..785a1c38 100644 --- a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx +++ b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx @@ -125,23 +125,15 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { }); } if (!response.model.downloaded) { - msg += "\n Download model " + response.model.path + ", please go to the model zoo to download it"; - // fetch(chatAPI.downloadModelFromHuggingFace(response.model.path)) - // .then((response) => { - // if (!response.ok) { - // console.log(response); - // throw new Error(`HTTP Status: ${response.status}`); - // } - // return response.json(); - // }) - // .then((response_json) => { - // if (response_json?.status == 'error') { - // throw new Error(response_json.message); - // } - // }) - // .catch((error) => { - // alert('Download failed:\n' + error); - // }); + msg += "\n Download model " + response.model.path + ", this will be done automatically"; + chatAPI.downloadModelFromHuggingFace(response.model.path) + .then((response) => { + if (response.status == "error") { + console.log(response); + throw new Error(`${response.message}`); + } + return response; + }) } if (msg) { const alert_msg = "Warning: To use this recipe you will need to: " + msg From 7c1ab093193f77aca05fe375c42a99421c99fc9a Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Mon, 13 Jan 2025 13:56:38 -0500 Subject: [PATCH 41/45] Make the currently downloading model show up on the Train page with DownloadProgressBox --- .../components/Experiment/Train/TrainLoRA.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/renderer/components/Experiment/Train/TrainLoRA.tsx b/src/renderer/components/Experiment/Train/TrainLoRA.tsx index c86800f8..d5908c70 100644 --- a/src/renderer/components/Experiment/Train/TrainLoRA.tsx +++ b/src/renderer/components/Experiment/Train/TrainLoRA.tsx @@ -47,6 +47,8 @@ import ImportRecipeModal from './ImportRecipeModal'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import ViewOutputModalStreaming from './ViewOutputModalStreaming'; +import CurrentDownloadBox from 'renderer/components/currentDownloadBox'; +import DownloadProgressBox from 'renderer/components/Shared/DownloadProgressBox'; dayjs.extend(relativeTime); var duration = require('dayjs/plugin/duration'); dayjs.extend(duration); @@ -103,6 +105,9 @@ export default function TrainLoRA({ experimentInfo }) { const [templateID, setTemplateID] = useState('-1'); const [currentPlugin, setCurrentPlugin] = useState(''); + const [jobId, setJobId] = useState(null); + const [downloadingModelName, setDownloadingModelName] = useState(null); + const { data, error, isLoading, mutate } = useSWR( chatAPI.GET_TRAINING_TEMPLATE_URL(), fetcher @@ -119,6 +124,20 @@ export default function TrainLoRA({ experimentInfo }) { refreshInterval: 2000, }); + useEffect(() => { + fetch(chatAPI.Endpoints.Jobs.GetJobsOfType('DOWNLOAD_MODEL', 'RUNNING')) + .then(async (response) => { + const jobs = await response.json(); + if (jobs.length) { + setJobId(jobs[0]?.id); + setDownloadingModelName(jobs[0]?.job_data.model) + } + }) + .catch((e) => { + console.log(e); + }); + }, []); + //Fetch available training plugins const { data: pluginsData, @@ -174,6 +193,7 @@ export default function TrainLoRA({ experimentInfo }) { overflow: 'hidden', }} > + {/* Train */} }> From 9a0916c3f09f1136ce53aa0c3b8296d283b27dca Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Mon, 13 Jan 2025 14:44:03 -0500 Subject: [PATCH 42/45] Fixed the download progress bar to import clamp, which is needed for certain downloads --- src/renderer/components/Shared/DownloadProgressBox.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/components/Shared/DownloadProgressBox.tsx b/src/renderer/components/Shared/DownloadProgressBox.tsx index 7d4f5b37..fca7dae1 100644 --- a/src/renderer/components/Shared/DownloadProgressBox.tsx +++ b/src/renderer/components/Shared/DownloadProgressBox.tsx @@ -8,6 +8,7 @@ import { } from '@mui/joy'; import { + clamp, formatBytes, } from '../../lib/utils'; import * as chatAPI from '../../lib/transformerlab-api-sdk'; From 3872affa21c3ef79776530a239c9a265302cdd37 Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Mon, 13 Jan 2025 14:44:32 -0500 Subject: [PATCH 43/45] Fixed the training download view so that it actually updates --- .../components/Experiment/Train/TrainLoRA.tsx | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/renderer/components/Experiment/Train/TrainLoRA.tsx b/src/renderer/components/Experiment/Train/TrainLoRA.tsx index d5908c70..32b0a330 100644 --- a/src/renderer/components/Experiment/Train/TrainLoRA.tsx +++ b/src/renderer/components/Experiment/Train/TrainLoRA.tsx @@ -105,9 +105,6 @@ export default function TrainLoRA({ experimentInfo }) { const [templateID, setTemplateID] = useState('-1'); const [currentPlugin, setCurrentPlugin] = useState(''); - const [jobId, setJobId] = useState(null); - const [downloadingModelName, setDownloadingModelName] = useState(null); - const { data, error, isLoading, mutate } = useSWR( chatAPI.GET_TRAINING_TEMPLATE_URL(), fetcher @@ -124,19 +121,14 @@ export default function TrainLoRA({ experimentInfo }) { refreshInterval: 2000, }); - useEffect(() => { - fetch(chatAPI.Endpoints.Jobs.GetJobsOfType('DOWNLOAD_MODEL', 'RUNNING')) - .then(async (response) => { - const jobs = await response.json(); - if (jobs.length) { - setJobId(jobs[0]?.id); - setDownloadingModelName(jobs[0]?.job_data.model) - } - }) - .catch((e) => { - console.log(e); - }); - }, []); + const { + data: downloadJobs, + error: downloadJobsError, + isLoading: downloadJobsIsLoading, + mutate: downloadJobsMutate, + } = useSWR(chatAPI.Endpoints.Jobs.GetJobsOfType('DOWNLOAD_MODEL', 'RUNNING'), fetcher, { + refreshInterval: 2000, + }); //Fetch available training plugins const { @@ -193,7 +185,7 @@ export default function TrainLoRA({ experimentInfo }) { overflow: 'hidden', }} > - + {/* Train */} }> From 8239e991a34120aae9e390c93dee3713ca76543b Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Tue, 14 Jan 2025 11:05:19 -0500 Subject: [PATCH 44/45] hide the progress box if the info is loading --- src/renderer/components/Experiment/Train/TrainLoRA.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/Experiment/Train/TrainLoRA.tsx b/src/renderer/components/Experiment/Train/TrainLoRA.tsx index 32b0a330..215e3216 100644 --- a/src/renderer/components/Experiment/Train/TrainLoRA.tsx +++ b/src/renderer/components/Experiment/Train/TrainLoRA.tsx @@ -185,7 +185,9 @@ export default function TrainLoRA({ experimentInfo }) { overflow: 'hidden', }} > - + { !downloadJobsIsLoading && + + } {/* Train */} }> From ee0e2553e6ddbc5affe93f391e90d0ad6505289f Mon Sep 17 00:00:00 2001 From: sanjaycal Date: Thu, 16 Jan 2025 10:54:55 -0500 Subject: [PATCH 45/45] Switch from alert to confirm to allow the user to not download --- .../Experiment/Train/ImportRecipeModal.tsx | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx index 785a1c38..8853ba08 100644 --- a/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx +++ b/src/renderer/components/Experiment/Train/ImportRecipeModal.tsx @@ -109,35 +109,51 @@ export default function ImportRecipeModal({ open, setOpen, mutate }) { } else if (!response.dataset || ! response.dataset.path) { alert("Warning: This recipe does not have an associated dataset") } else { - let msg = ""; + let msg = "Warning: To use this recipe you will need to download the following:"; + let shouldDownload = false; + if (!response.dataset.downloaded) { - msg += "\n Download dataset " + response.dataset.path + ", this will be done automatically"; - fetch(chatAPI.Endpoints.Dataset.Download(response.dataset.path)) - .then((response) => { - if (!response.ok) { - console.log(response); - throw new Error(`HTTP Status: ${response.status}`); - } - return response.json(); - }) - .catch((error) => { - alert('Download failed:\n' + error); - }); + msg += "\n- Dataset: " + response.dataset.path; + shouldDownload = true; } if (!response.model.downloaded) { - msg += "\n Download model " + response.model.path + ", this will be done automatically"; - chatAPI.downloadModelFromHuggingFace(response.model.path) - .then((response) => { - if (response.status == "error") { - console.log(response); - throw new Error(`${response.message}`); - } - return response; - }) + msg += "\n- Model: " + response.model.path; + shouldDownload = true; } - if (msg) { - const alert_msg = "Warning: To use this recipe you will need to: " + msg - alert(alert_msg); + + if (shouldDownload) { + msg += "\n\nDo you want to download these now?"; + if (confirm(msg)) { // Use confirm() to get Accept/Cancel + if (!response.dataset.downloaded) { + fetch(chatAPI.Endpoints.Dataset.Download(response.dataset.path)) + .then((response) => { + if (!response.ok) { + console.log(response); + throw new Error(`HTTP Status: ${response.status}`); + } + return response.json(); + }) + .catch((error) => { + alert('Dataset download failed:\n' + error); + }); + } + if (!response.model.downloaded) { + chatAPI.downloadModelFromHuggingFace(response.model.path) + .then((response) => { + if (response.status == "error") { + console.log(response); + throw new Error(`${response.message}`); + } + return response; + }) + .catch((error) => { + alert('Model download failed:\n' + error); + }); + } + } else { + // User pressed Cancel + alert("Downloads cancelled. This recipe might not work correctly."); + } } } }