├── .editorconfig ├── .erb ├── configs │ ├── .eslintrc │ ├── webpack.config.base.ts │ ├── webpack.config.cloud.dev.ts │ ├── webpack.config.cloud.prod.ts │ ├── webpack.config.eslint.ts │ ├── webpack.config.main.prod.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.renderer.dev.ts │ ├── webpack.config.renderer.prod.ts │ └── webpack.paths.ts ├── img │ ├── erb-banner.svg │ ├── erb-logo.png │ └── palette-sponsor-banner.svg ├── mocks │ └── fileMock.js └── scripts │ ├── .eslintrc │ ├── check-build-exists.ts │ ├── check-native-dep.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── clean.js │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── link-modules.ts │ └── notarize.js ├── .eslintignore ├── .eslintrc.js ├── .github ├── FUNDING.yml ├── config.yml ├── stale.yml └── workflows │ ├── codeql-analysis.yml │ ├── lint.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .prettierrc.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── assets ├── Transformer-Lab_Logo.svg ├── Transformer-Lab_Logo_Reverse.svg ├── assets.d.ts ├── entitlements.mac.plist ├── icon.png ├── screenshot.png ├── transformer-lab-logo-on-white.svg └── transformerlab-demo-jan2025.gif ├── package-lock.json ├── package.json ├── release └── app │ ├── package-lock.json │ └── package.json ├── scripts ├── bump_version.js ├── openapi.json ├── orval │ ├── .gitignore │ ├── generateSDK.sh │ └── orval.config.js └── server.js ├── src ├── __tests__ │ └── App.test.tsx ├── main │ ├── file-watcher.js │ ├── get-platform.js │ ├── main.ts │ ├── menu.ts │ ├── preload-cloud.ts │ ├── preload.ts │ ├── shell_commands │ │ └── shellCommands.js │ ├── ssh-client.js │ └── util.ts └── renderer │ ├── App.tsx │ ├── components │ ├── AutoUpdateModal.tsx │ ├── Computer.tsx │ ├── Connect │ │ ├── DetailedTooltipsForEachStep.json │ │ ├── LargeTooltip.tsx │ │ ├── LocalConnection.tsx │ │ ├── LogViewer.tsx │ │ ├── LoginModal.tsx │ │ └── XtermJS.tsx │ ├── Data │ │ ├── Data.tsx │ │ ├── DataStore.tsx │ │ ├── DatasetCard.tsx │ │ ├── DatasetInfoModal.tsx │ │ ├── DatasetPreviewEditImage.tsx │ │ ├── DatasetPreviewWithTemplate.tsx │ │ ├── DatasetTable.tsx │ │ ├── EditDatasetModal.tsx │ │ ├── GeneratedDatasets.tsx │ │ ├── LocalDatasets.tsx │ │ ├── NewDatasetModal.tsx │ │ └── PreviewDatasetModal.tsx │ ├── DownloadFirstModelModal.tsx │ ├── Experiment │ │ ├── Api.tsx │ │ ├── Documents │ │ │ └── index.tsx │ │ ├── DynamicPluginForm.tsx │ │ ├── Embeddings.tsx │ │ ├── Eval │ │ │ ├── Chart.tsx │ │ │ ├── Eval.tsx │ │ │ ├── EvalJobsTable.tsx │ │ │ ├── EvalModal.tsx │ │ │ ├── EvalTasksTable.tsx │ │ │ ├── ResultsModal.tsx │ │ │ ├── ViewCSVModal.tsx │ │ │ ├── ViewOutputModalStreaming.tsx │ │ │ └── ViewPlotModal.tsx │ │ ├── ExperimentNotes.tsx │ │ ├── Export │ │ │ ├── Export.tsx │ │ │ ├── ExportDetailsModal.tsx │ │ │ ├── ExportJobsTable.tsx │ │ │ ├── PluginSettingsModal.tsx │ │ │ └── ViewOutputModalStreaming.tsx │ │ ├── Foundation │ │ │ ├── CurrentFoundationInfo.tsx │ │ │ ├── InferenceEngineModal.tsx │ │ │ ├── ModelDetails.tsx │ │ │ ├── ModelProvenanceTimeline.tsx │ │ │ ├── RunModelButton.tsx │ │ │ ├── SelectAModel.tsx │ │ │ ├── SelectEmbeddingModel.tsx │ │ │ └── index.tsx │ │ ├── Generate │ │ │ ├── Generate.tsx │ │ │ ├── GenerateJobsTable.tsx │ │ │ ├── GenerateModal.tsx │ │ │ ├── GenerateTasksTable.tsx │ │ │ ├── ResultsModal.tsx │ │ │ ├── ViewCSVModal.tsx │ │ │ └── ViewOutputModalStreaming.tsx │ │ ├── Interact │ │ │ ├── AddMCPServerDialog.tsx │ │ │ ├── Batched │ │ │ │ ├── Batched.tsx │ │ │ │ ├── NewBatchPromptModal.tsx │ │ │ │ └── NewChatForm.tsx │ │ │ ├── ChatBubble.tsx │ │ │ ├── ChatPage.tsx │ │ │ ├── ChatSettingsOnLeftHandSide.tsx │ │ │ ├── ChatSubmit.tsx │ │ │ ├── CompletionsPage.tsx │ │ │ ├── Interact.tsx │ │ │ ├── MainGenerationConfigKnobs.tsx │ │ │ ├── ModelLayerVisualization.tsx │ │ │ ├── PreviousMessageList.tsx │ │ │ ├── PromptSettingsModal.tsx │ │ │ ├── RenderLogProbs.tsx │ │ │ ├── SystemMessageBox.tsx │ │ │ ├── TemplatedCompletion.tsx │ │ │ ├── TemplatedPromptModal.tsx │ │ │ ├── ThinSlider.tsx │ │ │ ├── Tokenize.tsx │ │ │ ├── VisualizeGeneration.tsx │ │ │ ├── VisualizeLogProbs.tsx │ │ │ ├── interactUtils.js │ │ │ ├── styles.css │ │ │ └── thinslider.css │ │ ├── Prompt.tsx │ │ ├── Rag │ │ │ ├── ConfigurePlugin.tsx │ │ │ ├── PickADocumentMenu.tsx │ │ │ ├── Query.tsx │ │ │ └── index.tsx │ │ ├── Recipes │ │ │ ├── ListRecipes.tsx │ │ │ ├── RecipeCard.tsx │ │ │ ├── RecipeDependencies.tsx │ │ │ ├── SelectedRecipe.tsx │ │ │ └── index.tsx │ │ ├── SelectButton.tsx │ │ ├── SelectExperimentMenu.tsx │ │ ├── Settings.tsx │ │ ├── Train │ │ │ ├── DownloadButton.tsx │ │ │ ├── ImportRecipeModal.tsx │ │ │ ├── JobProgress.tsx │ │ │ ├── LoRATrainingRunButton.tsx │ │ │ ├── TensorboardModal.tsx │ │ │ ├── TrainLoRA.tsx │ │ │ ├── TrainingJobDetailsModal.tsx │ │ │ ├── TrainingModalDataTemplatingTab.tsx │ │ │ ├── TrainingModalLoRA.tsx │ │ │ ├── TraningModalDataTab.tsx │ │ │ ├── ViewOutputModal.tsx │ │ │ └── ViewOutputModalStreaming.tsx │ │ ├── Widgets │ │ │ ├── CustomEvaluationWidget.tsx │ │ │ ├── CustomGEvalWidget.tsx │ │ │ └── ModelProviderWidget.tsx │ │ └── Workflows │ │ │ ├── WorkflowList │ │ │ ├── NewNodeModal.tsx │ │ │ ├── NewWorkflowModal.tsx │ │ │ ├── WorkflowCanvas.tsx │ │ │ └── WorkflowList.tsx │ │ │ ├── WorkflowRuns │ │ │ ├── JobDetails.tsx │ │ │ ├── WorkflowRunDisplay.tsx │ │ │ └── WorkflowRuns.tsx │ │ │ ├── WorkflowTriggers │ │ │ ├── TriggerDisplay.tsx │ │ │ └── WorkflowTriggers.tsx │ │ │ ├── index.tsx │ │ │ └── nodes │ │ │ ├── CustomNode.tsx │ │ │ └── StartNode.tsx │ ├── Header.tsx │ ├── HelpVideoModal.tsx │ ├── Logs.tsx │ ├── MainAppPanel.tsx │ ├── ModelCurrentlyPlayingBar.tsx │ ├── ModelZoo │ │ ├── ImportModelsBar.tsx │ │ ├── ImportModelsModal.tsx │ │ ├── LocalModels.tsx │ │ ├── LocalModelsTable.tsx │ │ ├── ModelDetailsModal.tsx │ │ ├── ModelGroups.tsx │ │ ├── ModelStore.tsx │ │ └── ModelZoo.tsx │ ├── Nav │ │ ├── ColorSchemeToggle.tsx │ │ ├── NavItem.tsx │ │ ├── Sidebar.tsx │ │ └── SubNavItem.tsx │ ├── OutputTerminal │ │ └── index.tsx │ ├── Plugins │ │ ├── LocalPlugins.tsx │ │ ├── NewPluginModal.tsx │ │ ├── PluginCard.tsx │ │ ├── PluginDetails.tsx │ │ ├── PluginGallery.tsx │ │ ├── Plugins.tsx │ │ └── PreviewPluginModal.tsx │ ├── Settings │ │ ├── AIProviderModal.tsx │ │ ├── AIProvidersSettings.tsx │ │ ├── TransformerLabSettings.tsx │ │ └── ViewJobsTab.tsx │ ├── Shared │ │ ├── Documents │ │ │ └── index.tsx │ │ ├── DownloadProgressBox.tsx │ │ ├── DraggableEllipsis.tsx │ │ ├── HexLogo.tsx │ │ ├── HexLogoSpinner.tsx │ │ ├── ListArchitectures.tsx │ │ ├── OneTimePopup.tsx │ │ ├── TinyAMDLogo.tsx │ │ ├── TinyButton.tsx │ │ ├── TinyCircle.tsx │ │ ├── TinyMLXLogo.tsx │ │ ├── TinyNVIDIALogo.tsx │ │ └── fairyfloss.tmTheme.js │ └── Welcome │ │ ├── Welcome.tsx │ │ └── img │ │ └── lab.jpg │ ├── img │ ├── attention.png │ ├── hex.svg │ └── show-available-fields.png │ ├── index.ejs │ ├── index.tsx │ ├── lib │ ├── api-client │ │ ├── allEndpoints.json │ │ ├── chat.ts │ │ ├── endpoints.ts │ │ ├── functions.ts │ │ ├── hooks.ts │ │ └── urls.ts │ ├── create-emotion-cache.ts │ ├── secretPurpleTheme.ts │ ├── theme.ts │ ├── transformerlab-api-sdk.ts │ └── utils.ts │ ├── preload.d.ts │ ├── store.js │ └── styles.css └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin'; 7 | import webpackPaths from './webpack.paths'; 8 | import { dependencies as externals } from '../../release/app/package.json'; 9 | 10 | const configuration: webpack.Configuration = { 11 | externals: [...Object.keys(externals || {})], 12 | 13 | stats: 'errors-only', 14 | 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.[jt]sx?$/, 19 | exclude: /node_modules/, 20 | use: { 21 | loader: 'ts-loader', 22 | options: { 23 | // Remove this line to enable type checking in webpack builds 24 | transpileOnly: true, 25 | compilerOptions: { 26 | module: 'esnext', 27 | }, 28 | }, 29 | }, 30 | }, 31 | ], 32 | }, 33 | 34 | output: { 35 | path: webpackPaths.srcPath, 36 | // https://github.com/webpack/webpack/issues/1114 37 | library: { 38 | type: 'commonjs2', 39 | }, 40 | }, 41 | 42 | /** 43 | * Determine the array of extensions that should be used to resolve modules. 44 | */ 45 | resolve: { 46 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 47 | modules: [webpackPaths.srcPath, 'node_modules'], 48 | // There is no need to add aliases here, the paths in tsconfig get mirrored 49 | plugins: [new TsconfigPathsPlugins()], 50 | }, 51 | 52 | plugins: [ 53 | new webpack.EnvironmentPlugin({ 54 | NODE_ENV: 'production', 55 | VERSION: process.env.npm_package_version, 56 | }), 57 | ], 58 | }; 59 | 60 | export default configuration; 61 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const configuration: webpack.Configuration = { 19 | devtool: 'source-map', 20 | 21 | mode: 'production', 22 | 23 | target: 'electron-main', 24 | 25 | entry: { 26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.distMainPath, 32 | filename: '[name].js', 33 | library: { 34 | type: 'umd', 35 | }, 36 | }, 37 | 38 | optimization: { 39 | minimizer: [ 40 | new TerserPlugin({ 41 | parallel: true, 42 | }), 43 | ], 44 | }, 45 | 46 | plugins: [ 47 | new BundleAnalyzerPlugin({ 48 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 49 | analyzerPort: 8888, 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'production', 63 | DEBUG_PROD: false, 64 | START_MINIMIZED: false, 65 | }), 66 | 67 | new webpack.DefinePlugin({ 68 | 'process.type': '"browser"', 69 | }), 70 | ], 71 | 72 | /** 73 | * Disables webpack processing of __dirname and __filename. 74 | * If you run the bundle in node.js it falls back to these values of node.js. 75 | * https://github.com/webpack/webpack/issues/2010 76 | */ 77 | node: { 78 | __dirname: false, 79 | __filename: false, 80 | }, 81 | }; 82 | 83 | export default merge(baseConfig, configuration); 84 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.preload.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 5 | import baseConfig from './webpack.config.base'; 6 | import webpackPaths from './webpack.paths'; 7 | import checkNodeEnv from '../scripts/check-node-env'; 8 | 9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 10 | // at the dev webpack config is not accidentally run in a production environment 11 | if (process.env.NODE_ENV === 'production') { 12 | checkNodeEnv('development'); 13 | } 14 | 15 | const configuration: webpack.Configuration = { 16 | devtool: 'inline-source-map', 17 | 18 | mode: 'development', 19 | 20 | target: 'electron-preload', 21 | 22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'), 23 | 24 | output: { 25 | path: webpackPaths.dllPath, 26 | filename: 'preload.js', 27 | library: { 28 | type: 'umd', 29 | }, 30 | }, 31 | 32 | plugins: [ 33 | new BundleAnalyzerPlugin({ 34 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 35 | }), 36 | 37 | /** 38 | * Create global constants which can be configured at compile time. 39 | * 40 | * Useful for allowing different behaviour between development builds and 41 | * release builds 42 | * 43 | * NODE_ENV should be production so that modules do not perform certain 44 | * development checks 45 | * 46 | * By default, use 'development' as NODE_ENV. This can be overriden with 47 | * 'staging', for example, by changing the ENV variables in the npm scripts 48 | */ 49 | new webpack.EnvironmentPlugin({ 50 | NODE_ENV: 'development', 51 | }), 52 | 53 | new webpack.LoaderOptionsPlugin({ 54 | debug: true, 55 | }), 56 | ], 57 | 58 | /** 59 | * Disables webpack processing of __dirname and __filename. 60 | * If you run the bundle in node.js it falls back to these values of node.js. 61 | * https://github.com/webpack/webpack/issues/2010 62 | */ 63 | node: { 64 | __dirname: false, 65 | __filename: false, 66 | }, 67 | 68 | watch: true, 69 | }; 70 | 71 | export default merge(baseConfig, configuration); 72 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | const distCloudPath = path.join(releasePath, 'cloud'); 21 | 22 | const buildPath = path.join(releasePath, 'build'); 23 | 24 | export default { 25 | rootPath, 26 | dllPath, 27 | srcPath, 28 | srcMainPath, 29 | srcRendererPath, 30 | releasePath, 31 | appPath, 32 | appPackagePath, 33 | appNodeModulesPath, 34 | srcNodeModulesPath, 35 | distPath, 36 | distMainPath, 37 | distRendererPath, 38 | distCloudPath, 39 | buildPath, 40 | }; 41 | -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformerlab/transformerlab-app/4240e3a491cf0eb24b64fb575c1190fe7c9c4d61/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"' 14 | ) 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"' 22 | ) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency) 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.' 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":' 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold( 42 | 'cd ./release/app && npm install your-package' 43 | )} 44 | Read more about native dependencies at: 45 | ${chalk.bold( 46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure' 47 | )} 48 | `); 49 | process.exit(1); 50 | } 51 | } catch (e) { 52 | console.log('Native dependencies could not be checked'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | // This is a hacked in place to check the version of Node but it works to prevent users from using Node 23 and above 8 | // because electron build breaks on Node 23. Remove this once electron is fixed. 9 | if (process.versions.node.split('.')[0] >= 23) { 10 | console.error( 11 | `Node.js version 23 and above are not supported. Current version: ${process.version}`, 12 | ); 13 | process.exit(1); 14 | } 15 | 16 | if (port !== String(availablePort)) { 17 | throw new Error( 18 | chalk.whiteBright.bgRed.bold( 19 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`, 20 | ), 21 | ); 22 | } else { 23 | process.exit(0); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import { rimrafSync } from 'rimraf'; 2 | import fs from 'fs'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | const foldersToRemove = [ 6 | webpackPaths.distPath, 7 | webpackPaths.buildPath, 8 | webpackPaths.dllPath, 9 | ]; 10 | 11 | foldersToRemove.forEach((folder) => { 12 | if (fs.existsSync(folder)) rimrafSync(folder); 13 | }); 14 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { rimrafSync } from 'rimraf'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | export default function deleteSourceMaps() { 7 | if (fs.existsSync(webpackPaths.distMainPath)) 8 | rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), { 9 | glob: true, 10 | }); 11 | if (fs.existsSync(webpackPaths.distRendererPath)) 12 | rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), { 13 | glob: true, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath } = webpackPaths; 5 | const { appNodeModulesPath } = webpackPaths; 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize'); 2 | const { build } = require('../../package.json'); 3 | 4 | exports.default = async function notarizeMacos(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | if (process.env.CI !== 'true') { 11 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 12 | return; 13 | } 14 | 15 | if (!('APPLE_API_KEY' in process.env && 'APPLE_API_KEY_ID' in process.env)) { 16 | console.warn( 17 | 'Skipping notarizing step. APPLE_API_KEY and APPLE_API_KEY_ID env variables must be set' 18 | ); 19 | return; 20 | } 21 | 22 | const appName = context.packager.appInfo.productFilename; 23 | 24 | await notarize({ 25 | appPath: `${appOutDir}/${appName}.app`, 26 | appleApiKey: process.env.APPLE_API_KEY, 27 | appleApiKeyId: process.env.APPLE_API_KEY_ID, 28 | appleApiIssuer: process.env.APPLE_API_ISSUER, 29 | tool: 'notarytool', 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | plugins: ['@typescript-eslint'], 4 | rules: { 5 | // A temporary hack related to IDE not resolving correct package.json 6 | 'import/no-extraneous-dependencies': 'off', 7 | 'react/react-in-jsx-scope': 'off', 8 | 'react/jsx-filename-extension': 'off', 9 | 'import/extensions': 'off', 10 | 'import/no-unresolved': 'off', 11 | 'import/no-import-module-exports': 'off', 12 | 'no-shadow': 'off', 13 | '@typescript-eslint/no-shadow': 'error', 14 | 'no-unused-vars': 'off', 15 | '@typescript-eslint/no-unused-vars': 'error', 16 | 'no-plusplus': 'off', 17 | 'react/prop-types': 'off', 18 | 'no-restricted-exports': 'off', 19 | 20 | // TODO: Fix prop types! 21 | 'react/function-component-definition': 'off', 22 | }, 23 | parserOptions: { 24 | ecmaVersion: 2020, 25 | sourceType: 'module', 26 | project: './tsconfig.json', 27 | tsconfigRootDir: __dirname, 28 | createDefaultProgram: true, 29 | }, 30 | settings: { 31 | 'import/resolver': { 32 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 33 | node: {}, 34 | webpack: { 35 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 36 | }, 37 | typescript: {}, 38 | }, 39 | 'import/parsers': { 40 | '@typescript-eslint/parser': ['.ts', '.tsx'], 41 | }, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [transformerlab] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | requiredHeaders: 2 | - Prerequisites 3 | - Expected Behavior 4 | - Current Behavior 5 | - Possible Solution 6 | - Your Environment 7 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - discussion 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '44 16 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Install modules 11 | run: npm install 12 | - name: Run ESLint 13 | run: npm run lint 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | os: [macos-latest, windows-latest, ubuntu-latest] 12 | 13 | steps: 14 | - name: Check out Git repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Install Node.js and NPM 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18 21 | cache: npm 22 | 23 | - name: npm install 24 | run: | 25 | npm install 26 | 27 | - name: npm test 28 | env: 29 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | run: | 31 | npm run package 32 | npm run lint 33 | npm exec tsc 34 | npm test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | release/cloud/* 24 | .erb/dll 25 | 26 | .idea 27 | npm-debug.log.* 28 | *.css.d.ts 29 | *.sass.d.ts 30 | *.scss.d.ts 31 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["run", "start"], 11 | "env": { 12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223" 13 | } 14 | }, 15 | { 16 | "name": "Electron: Renderer", 17 | "type": "chrome", 18 | "request": "attach", 19 | "port": 9223, 20 | "webRoot": "${workspaceFolder}", 21 | "timeout": 15000 22 | } 23 | ], 24 | "compounds": [ 25 | { 26 | "name": "Electron: All", 27 | "configurations": ["Electron: Main", "Electron: Renderer"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".eslintrc": "jsonc", 4 | ".prettierrc": "jsonc", 5 | ".eslintignore": "ignore" 6 | }, 7 | 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "html", 12 | "typescriptreact" 13 | ], 14 | 15 | "javascript.validate.enable": true, 16 | "javascript.format.enable": true, 17 | "typescript.format.enable": true, 18 | 19 | "search.exclude": { 20 | ".git": true, 21 | ".eslintcache": true, 22 | ".erb/dll": true, 23 | "release/{build,app/dist}": true, 24 | "node_modules": true, 25 | "npm-debug.log.*": true, 26 | "test/**/__snapshots__": true, 27 | "package-lock.json": true, 28 | "*.{css,sass,scss}.d.ts": true 29 | }, 30 | "editor.formatOnSave": true, 31 | "prettier.enable": true, 32 | "[javascript]": { 33 | "editor.defaultFormatter": "esbenp.prettier-vscode" 34 | }, 35 | "[typescript]": { 36 | "editor.defaultFormatter": "esbenp.prettier-vscode" 37 | }, 38 | "[typescriptreact]": { 39 | "editor.defaultFormatter": "esbenp.prettier-vscode" 40 | }, 41 | "[javascriptreact]": { 42 | "editor.defaultFormatter": "esbenp.prettier-vscode" 43 | }, 44 | "[html]": { 45 | "editor.defaultFormatter": "esbenp.prettier-vscode" 46 | }, 47 | "[json]": { 48 | "editor.defaultFormatter": "esbenp.prettier-vscode" 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at electronreactboilerplate@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | import React = require('react'); 5 | 6 | export const ReactComponent: React.FC>; 7 | 8 | const content: string; 9 | export default content; 10 | } 11 | 12 | declare module '*.png' { 13 | const content: string; 14 | export default content; 15 | } 16 | 17 | declare module '*.jpg' { 18 | const content: string; 19 | export default content; 20 | } 21 | 22 | declare module '*.scss' { 23 | const content: Styles; 24 | export default content; 25 | } 26 | 27 | declare module '*.sass' { 28 | const content: Styles; 29 | export default content; 30 | } 31 | 32 | declare module '*.css' { 33 | const content: Styles; 34 | export default content; 35 | } 36 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformerlab/transformerlab-app/4240e3a491cf0eb24b64fb575c1190fe7c9c4d61/assets/icon.png -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformerlab/transformerlab-app/4240e3a491cf0eb24b64fb575c1190fe7c9c4d61/assets/screenshot.png -------------------------------------------------------------------------------- /assets/transformerlab-demo-jan2025.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transformerlab/transformerlab-app/4240e3a491cf0eb24b64fb575c1190fe7c9c4d61/assets/transformerlab-demo-jan2025.gif -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transformerlab", 3 | "version": "0.18.0", 4 | "description": "A tool to play with and train LLMs", 5 | "license": "AGPL-3.0", 6 | "author": { 7 | "name": "Ali Asaria", 8 | "email": "aliasaria", 9 | "url": "https://github.com/aliasaria" 10 | }, 11 | "main": "./dist/main/main.js", 12 | "scripts": { 13 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 14 | "postinstall": "npm run rebuild && npm run link-modules", 15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 16 | }, 17 | "dependencies": { 18 | "ssh2": "^1.14.0" 19 | } 20 | } -------------------------------------------------------------------------------- /scripts/bump_version.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { execSync } = require('child_process'); 3 | const { version } = require('os'); 4 | const readline = require('node:readline'); 5 | 6 | const rl = readline.createInterface({ 7 | input: process.stdin, 8 | output: process.stdout, 9 | }); 10 | 11 | const files = [ 12 | 'package.json', 13 | 'package-lock.json', 14 | 'release/app/package.json', 15 | 'release/app/package-lock.json', 16 | ]; 17 | 18 | function getVersion() { 19 | const data = fs.readFileSync('package.json', 'utf-8'); 20 | const json = JSON.parse(data); 21 | return json.version; 22 | } 23 | 24 | function bumpVersion(filePath, newVersion) { 25 | console.log(`Bumping version in ${filePath}`); 26 | const data = fs.readFileSync(filePath, 'utf-8'); 27 | const json = JSON.parse(data); 28 | json.version = newVersion; 29 | fs.writeFileSync(filePath, JSON.stringify(json, null, 2)); 30 | } 31 | 32 | // Get argument which can be either 'major', 'minor', or 'patch' 33 | const versionPart = process.argv[2]; 34 | if (versionPart === undefined) { 35 | console.error( 36 | 'No argument provided. Must provide either "major", "minor", or "patch"' 37 | ); 38 | process.exit(1); 39 | } 40 | 41 | if (versionPart && !['major', 'minor', 'patch'].includes(versionPart)) { 42 | console.error( 43 | 'Invalid argument. Must be either "major", "minor", or "patch"' 44 | ); 45 | process.exit(1); 46 | } 47 | 48 | const currentVersion = getVersion(); 49 | const versionParts = currentVersion.split('.'); 50 | // Bump the major version 51 | if (versionPart === 'major') { 52 | versionParts[0] = parseInt(versionParts[0]) + 1; 53 | versionParts[1] = 0; 54 | versionParts[2] = 0; 55 | } 56 | // Bump the minor version 57 | if (versionPart === 'minor') { 58 | versionParts[1] = parseInt(versionParts[1]) + 1; 59 | versionParts[2] = 0; 60 | } 61 | // Bump the patch version 62 | if (versionPart === 'patch') { 63 | versionParts[2] = parseInt(versionParts[2]) + 1; 64 | } 65 | const newVersion = versionParts.join('.'); 66 | console.log(`Bumping version from ${currentVersion} to ${newVersion}`); 67 | 68 | rl.question(`Do you want to continue? (y/n) `, (answer) => { 69 | rl.close(); 70 | if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') { 71 | console.log('Exiting process...'); 72 | process.exit(1); 73 | } else { 74 | files.forEach((file) => { 75 | bumpVersion(file, newVersion); 76 | }); 77 | 78 | // Add the updated files to git, commit the changes, and tag the new commit 79 | execSync(`git add ${files.join(' ')}`); 80 | execSync(`git commit -m "Bump version to ${newVersion}"`); 81 | execSync(`git tag v${newVersion}`); 82 | 83 | console.log( 84 | 'A new commit and tag have been created. Please push the changes to the remote repository to trigger a new build.' 85 | ); 86 | } 87 | }); 88 | -------------------------------------------------------------------------------- /scripts/orval/.gitignore: -------------------------------------------------------------------------------- 1 | tmp/* 2 | out/* 3 | -------------------------------------------------------------------------------- /scripts/orval/generateSDK.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if FastChat is running on port 8338 by looking at the root 4 | # if it is not, quit and log an error: 5 | if [ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8338)" != "200" ]; then 6 | echo "FastChat is not running on port 8338. Please start Transformer Lab and try again." 7 | exit 1 8 | fi 9 | 10 | # make a tmp dir: 11 | mkdir -p ./tmp 12 | 13 | # now fetch the openapi spec 14 | curl -o ./tmp/openapi.json http://localhost:8338/openapi.json 15 | 16 | # generate the SDK using orval in the format like 17 | # $ orval --input ./petstore.yaml --output ./src/petstore.ts 18 | npx orval --config ./orval.config.js 19 | -------------------------------------------------------------------------------- /scripts/orval/orval.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transformerLab: { 3 | input: './tmp/openapi.json', 4 | output: { 5 | client: 'swr', 6 | target: './out/', 7 | httpClient: 'fetch', 8 | baseUrl: false, 9 | mode: 'tags-split', 10 | docs: { 11 | out: './docs', 12 | disableSources: true, 13 | }, 14 | }, 15 | hooks: { 16 | afterAllFilesWrite: 'prettier --write', 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /scripts/server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const url = require('url'); 5 | 6 | // Port to run the server on 7 | const PORT = 4567; 8 | 9 | // Directory containing your HTML files 10 | const DIRECTORY = './release/cloud'; // Change this to your directory path if needed 11 | 12 | // Create the HTTP server 13 | const server = http.createServer((req, res) => { 14 | // Parse the URL to get the pathname 15 | const parsedUrl = url.parse(req.url); 16 | let pathname = parsedUrl.pathname; 17 | 18 | // Convert URL path to file path 19 | // Handle root path 20 | if (pathname === '/') { 21 | pathname = '/index.html'; 22 | } 23 | 24 | // Create the full file path 25 | const filePath = path.join(DIRECTORY, pathname); 26 | 27 | // Read the file 28 | fs.readFile(filePath, (err, data) => { 29 | if (err) { 30 | // If file not found or error reading file 31 | if (err.code === 'ENOENT') { 32 | // File not found 33 | res.writeHead(404, { 'Content-Type': 'text/html' }); 34 | res.end( 35 | '

404 Not Found

The requested file was not found on the server.

', 36 | ); 37 | } else { 38 | // Server error 39 | res.writeHead(500, { 'Content-Type': 'text/html' }); 40 | res.end( 41 | '

500 Internal Server Error

Error reading the requested file.

', 42 | ); 43 | } 44 | return; 45 | } 46 | 47 | // Determine the content type based on file extension 48 | const ext = path.extname(filePath); 49 | let contentType = 'text/html'; 50 | 51 | switch (ext) { 52 | case '.js': 53 | contentType = 'text/javascript'; 54 | break; 55 | case '.css': 56 | contentType = 'text/css'; 57 | break; 58 | case '.json': 59 | contentType = 'application/json'; 60 | break; 61 | case '.png': 62 | contentType = 'image/png'; 63 | break; 64 | case '.jpg': 65 | case '.jpeg': 66 | contentType = 'image/jpeg'; 67 | break; 68 | case '.gif': 69 | contentType = 'image/gif'; 70 | break; 71 | case '.svg': 72 | contentType = 'image/svg+xml'; 73 | break; 74 | } 75 | 76 | // Serve the file 77 | res.writeHead(200, { 'Content-Type': contentType }); 78 | res.end(data); 79 | }); 80 | }); 81 | 82 | // Start the server 83 | server.listen(PORT, () => { 84 | console.log(`Server running at http://localhost:${PORT}/`); 85 | console.log(`Serving files from: ${path.resolve(DIRECTORY)}`); 86 | }); 87 | -------------------------------------------------------------------------------- /src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import App from '../renderer/App'; 4 | 5 | describe('App', () => { 6 | it('should render', () => { 7 | expect(render()).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/main/get-platform.js: -------------------------------------------------------------------------------- 1 | import { platform } from 'os'; 2 | 3 | export default () => { 4 | switch (platform()) { 5 | case 'aix': 6 | case 'freebsd': 7 | case 'linux': 8 | case 'openbsd': 9 | case 'android': 10 | return 'linux'; 11 | case 'darwin': 12 | case 'sunos': 13 | return 'mac'; 14 | case 'win32': 15 | return 'win'; 16 | default: 17 | return 'unknown'; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/main/preload.ts: -------------------------------------------------------------------------------- 1 | // Disable no-unused-vars, broken for spread args 2 | /* eslint no-unused-vars: off */ 3 | import { 4 | contextBridge, 5 | ipcRenderer, 6 | IpcRendererEvent, 7 | webFrame, 8 | } from 'electron'; 9 | 10 | webFrame.setZoomFactor(1); 11 | 12 | export type Channels = 13 | | 'getStoreValue' 14 | | 'setStoreValue' 15 | | 'deleteStoreValue' 16 | | 'openURL' 17 | | 'server:checkSystemRequirements' 18 | | 'server:checkIfInstalledLocally' 19 | | 'server:checkLocalVersion' 20 | | 'server:startLocalServer' 21 | | 'server:InstallLocally' 22 | | 'server:install_conda' 23 | | 'server:install_create-conda-environment' 24 | | 'server:install_install-dependencies' 25 | | 'server:checkIfCondaExists' 26 | | 'server:checkIfCondaEnvironmentExists' 27 | | 'server:checkIfUvicornExists' 28 | | 'server:checkDependencies' 29 | | 'serverLog:startListening' 30 | | 'serverLog:stopListening' 31 | | 'serverLog:update'; 32 | 33 | const electronHandler = { 34 | ipcRenderer: { 35 | sendMessage(channel: Channels, ...args: unknown[]) { 36 | ipcRenderer.send(channel, ...args); 37 | }, 38 | on(channel: Channels, func: (...args: unknown[]) => void) { 39 | const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => 40 | func(...args); 41 | ipcRenderer.on(channel, subscription); 42 | 43 | return () => { 44 | ipcRenderer.removeListener(channel, subscription); 45 | }; 46 | }, 47 | once(channel: Channels, func: (...args: unknown[]) => void) { 48 | ipcRenderer.once(channel, (_event, ...args) => func(...args)); 49 | }, 50 | invoke(channel: Channels, ...args: unknown[]) { 51 | return ipcRenderer.invoke(channel, ...args); 52 | }, 53 | removeAllListeners: (channel: string) => 54 | ipcRenderer.removeAllListeners(channel), 55 | }, 56 | }; 57 | 58 | export type ElectronHandler = typeof electronHandler; 59 | 60 | contextBridge.exposeInMainWorld('electron', electronHandler); 61 | 62 | contextBridge.exposeInMainWorld('platform', { 63 | appmode: 'electron', 64 | environment: process.env.NODE_ENV, // Webpack's EnvironmentPlugin will replace this with 'production' or 'development' 65 | version: process.env.npm_package_version, 66 | }); 67 | 68 | contextBridge.exposeInMainWorld('storage', { 69 | get: (key: string) => { 70 | return ipcRenderer.invoke('getStoreValue', key); 71 | }, 72 | set: (key: string, value: string) => { 73 | return ipcRenderer.invoke('setStoreValue', key, value); 74 | }, 75 | delete: (key: string) => { 76 | console.log('inv delete', key); 77 | return ipcRenderer.invoke('deleteStoreValue', key); 78 | }, 79 | }); 80 | 81 | contextBridge.exposeInMainWorld('sshClient', { 82 | connect: (data) => ipcRenderer.invoke('ssh:connect', data), 83 | data: (data) => ipcRenderer.send('ssh:data', data), 84 | 85 | onData: (data) => ipcRenderer.on('ssh:data', data), 86 | onSSHConnected: (callback) => ipcRenderer.on('ssh:connected', callback), 87 | 88 | removeAllListeners: () => { 89 | ipcRenderer.removeAllListeners('ssh:data'); 90 | ipcRenderer.removeAllListeners('ssh:connected'); 91 | }, 92 | }); 93 | 94 | contextBridge.exposeInMainWorld('autoUpdater', { 95 | onMessage: (data) => ipcRenderer.on('autoUpdater', data), 96 | removeAllListeners: () => ipcRenderer.removeAllListeners('autoUpdater'), 97 | requestUpdate: () => ipcRenderer.invoke('autoUpdater:requestUpdate'), 98 | }); 99 | -------------------------------------------------------------------------------- /src/main/shell_commands/shellCommands.js: -------------------------------------------------------------------------------- 1 | export const updateAndInstallCommand = ` 2 | TFL_FILENAME="transformerlab_api_v0.1.0.zip" 3 | TFL_URL="https://transformerlab-binaries.s3.amazonaws.com/\${TFL_FILENAME}" 4 | TFL_DEST_DIR="\${HOME}/.transformerlab/src/" 5 | echo "Check if \${TFL_DEST_DIR} exists" 6 | mkdir -p "\${TFL_DEST_DIR}" 7 | echo "Downloading \${TFL_URL} to \${TFL_DEST_DIR}" 8 | curl "\${TFL_URL}" --output "\${TFL_DEST_DIR}\${TFL_FILENAME}" 9 | unzip -o "\${TFL_DEST_DIR}\${TFL_FILENAME}" -d "$TFL_DEST_DIR" 10 | echo "Starting API server" 11 | cd "\${TFL_DEST_DIR}" || exit 12 | 13 | if [ -f .DEPENDENCIES_INSTALLED ]; then 14 | echo "Dependencies already installed. Skipping." 15 | echo "To reinstall dependencies, delete the .DEPENDENCIES_INSTALLED file and run this script again." 16 | else 17 | ./init.sh 18 | touch .DEPENDENCIES_INSTALLED 19 | fi 20 | `; 21 | 22 | export const installOnlyIfNotInstalledCommand = ` 23 | TFL_FILENAME="main.zip" 24 | TFL_URL="https://github.com/transformerlab/transformerlab-api/archive/refs/heads/main.zip" 25 | TFL_DIR="\${HOME}/.transformerlab" 26 | TFL_DEST_DIR="\${HOME}/.transformerlab/src" 27 | echo "Check if \${TFL_DEST_DIR} exists" 28 | # Check if the Install directory exists, if so, do nothing 29 | if [[ ! -d "\${TFL_DEST_DIR}" ]] 30 | then 31 | mkdir -p "\${TFL_DEST_DIR}" 32 | echo "Downloading \${TFL_URL} to \${TFL_DIR}" 33 | curl -L "\${TFL_URL}" --output "\${TFL_DIR}\${TFL_FILENAME}" 34 | unzip -o "\${TFL_DIR}\${TFL_FILENAME}" -d "$TFL_DEST_DIR" 35 | echo "Starting API server" 36 | cd "\${TFL_DEST_DIR}" || exit 37 | 38 | if [ -f .DEPENDENCIES_INSTALLED ]; then 39 | echo "Dependencies already installed. Skipping." 40 | echo "To reinstall dependencies, delete the .DEPENDENCIES_INSTALLED file and run this script again." 41 | else 42 | ./init.sh 43 | touch .DEPENDENCIES_INSTALLED 44 | fi 45 | else 46 | echo "Install directory already exists. Skipping." 47 | fi 48 | `; 49 | 50 | export const runCommand = ` 51 | conda activate transformerlab 52 | TFL_DEST_DIR="\${HOME}/.transformerlab/src/" 53 | cd "\${TFL_DEST_DIR}" || exit 54 | ./run.sh 55 | `; 56 | 57 | export const runCommandInBackground = ` 58 | conda activate transformerlab 59 | TFL_DEST_DIR="\${HOME}/.transformerlab/src/" 60 | cd "\${TFL_DEST_DIR}" || exit 61 | if [ -f pid.nohup ]; then 62 | echo "PID file exists, killing process" 63 | kill $(cat pid.nohup) 64 | fi 65 | nohup ./run.sh > /dev/null 2>&1 & 66 | # Write the PID to a file 67 | echo $! > pid.nohup 68 | `; 69 | -------------------------------------------------------------------------------- /src/renderer/components/AutoUpdateModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | 3 | import { Modal, ModalClose, Sheet, Typography } from '@mui/joy'; 4 | import HexLogoSpinner from './Shared/HexLogoSpinner'; 5 | 6 | export default function AutoUpdateModal({ }) { 7 | const [open, setOpen] = React.useState(true); 8 | const [message, setMessage] = React.useState('Looking for updates'); 9 | 10 | useEffect(() => { 11 | window.autoUpdater.requestUpdate(); 12 | window.autoUpdater.onMessage((event, message) => { 13 | setMessage(message); 14 | 15 | if (message === 'Update not available.') { 16 | setOpen(false); 17 | } 18 | 19 | if (message === 'Update error') { 20 | setTimeout(() => { 21 | setOpen(false); 22 | }, 2000); 23 | } 24 | }); 25 | 26 | return () => { 27 | window.autoUpdater.removeAllListeners(); 28 | }; 29 | }, []); 30 | 31 | return ( 32 | setOpen(false)} 35 | sx={{ 36 | display: 'flex', 37 | justifyContent: 'center', 38 | alignItems: 'center', 39 | zIndex: 10000, 40 | }} 41 | > 42 | 54 | 55 | 56 | Auto Update 57 | 58 | 59 | {/* {message} */} 60 | 61 | {message === 'Checking for update...' && <>Checking for Updates...} 62 | {message === 'Update available' && ( 63 | <>Update Available, Downloading... 64 | )} 65 | {message.startsWith('Download speed') && ( 66 | <> 67 | Update Available, Downloading... 68 |
69 | {message} 70 | 71 | )} 72 | {message === 'Downloading update...' && <>Downloading Updates...} 73 | {message === 'Update not available.' && <>Update not available.} 74 | {message === 'Update error' && <>Auto Update Error} 75 |
76 | 77 | Please note that auto update does not currently work on Windows -- 78 | please download the app from our website to update. 79 | 80 |
81 |
82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /src/renderer/components/Connect/DetailedTooltipsForEachStep.json: -------------------------------------------------------------------------------- 1 | [ 2 | "In this step we check if Transformer Lab is already installed on your local machine. The Transformer Lab Engine is stored at ~/.transformerlab/ which you can see if you type `cd ~/.transformerlab/` in your terminal.", 3 | "In this step we check the current version of the Transformer Lab Engine versus the most recent version in Github. If the version is outdated, we will prompt you to update the Transformer Lab Engine.", 4 | "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.", 5 | "Now that Conda is installed, we create a 'Conda Environment' just for Transformer Lab. This is where all the Python dependencies will be installed.", 6 | "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.", 7 | "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.", 8 | "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." 9 | ] 10 | -------------------------------------------------------------------------------- /src/renderer/components/Connect/LargeTooltip.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Chip, Typography } from '@mui/joy'; 2 | 3 | import DetailedTooltips from './DetailedTooltipsForEachStep.json'; 4 | 5 | export default function LargeTooltip({ stepNumber }) { 6 | return ( 7 | 16 | 17 |
18 |
19 | 20 | More Information: 21 | 22 |
23 | 24 | {DetailedTooltips[stepNumber]} 25 | 26 |
27 |
28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/renderer/components/Connect/LogViewer.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Sheet } from '@mui/joy'; 2 | import { useEffect, useRef } from 'react'; 3 | 4 | import '@xterm/xterm/css/xterm.css'; 5 | import { Terminal } from '@xterm/xterm'; 6 | import { FitAddon } from '@xterm/addon-fit'; 7 | 8 | export default function LogViewer({ 9 | triggerStrings = [], 10 | triggerFunction = () => { }, 11 | }) { 12 | const terminalRef = useRef(null); 13 | let term: Terminal | null = null; 14 | 15 | const fitAddon = new FitAddon(); 16 | 17 | function handleResize() { 18 | fitAddon.fit(); 19 | } 20 | 21 | useEffect(() => { 22 | // see if you can find any DOM elements with class "xterm" and remove them 23 | // I don't know why they are left behind, but this is a workaround 24 | const xtermElements = document.getElementsByClassName('xterm'); 25 | if (xtermElements.length > 0) { 26 | for (let i = 0; i < xtermElements.length; i++) { 27 | xtermElements[i].remove(); 28 | } 29 | } 30 | 31 | if (term != null) { 32 | term.dispose; 33 | console.log('disposed terminal'); 34 | } else { 35 | term = new Terminal(); 36 | term.loadAddon(fitAddon); 37 | 38 | term.open(terminalRef.current); 39 | fitAddon.fit(); 40 | 41 | setTimeout(() => { 42 | window.electron.ipcRenderer.sendMessage('serverLog:startListening'); 43 | }, 100); 44 | window.electron.ipcRenderer.removeAllListeners('serverLog:onUpdate'); 45 | console.log('Listening for server log updates'); 46 | window.electron.ipcRenderer.on('serverLog:update', (data: any) => { 47 | // append data to the log-viewer div 48 | if (term != null) { 49 | term.writeln(`${data}`); 50 | } 51 | 52 | // Go through each trigger string and see if it is in the data 53 | if (triggerStrings.length > 0) { 54 | for (let i = 0; i < triggerStrings.length; i++) { 55 | if (data.includes(triggerStrings[i])) { 56 | triggerFunction(data); 57 | } 58 | } 59 | } 60 | }); 61 | window.addEventListener('resize', handleResize); 62 | } 63 | 64 | return () => { 65 | //send message to stop the server log listening service: 66 | window.electron.ipcRenderer.sendMessage('serverLog:stopListening'); 67 | window.electron.ipcRenderer.removeAllListeners( 68 | 'serverLog:startListening' 69 | ); 70 | window.electron.ipcRenderer.removeAllListeners('serverLog:onUpdate'); 71 | console.log('Stopped listening for server log updates'); 72 | window.removeEventListener('resize', handleResize); 73 | }; 74 | }, []); 75 | 76 | return ( 77 | 78 | 87 | 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /src/renderer/components/Data/Data.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-is-valid */ 2 | import Sheet from '@mui/joy/Sheet'; 3 | import { Tab, TabList, TabPanel, Tabs } from '@mui/joy'; 4 | import { StoreIcon } from 'lucide-react'; 5 | 6 | import DataStore from './DataStore'; 7 | import LocalDatasets from './LocalDatasets'; 8 | import GeneratedDatasets from './GeneratedDatasets' 9 | 10 | export default function Data() { 11 | return ( 12 | 13 | 24 | 25 | Local Datasets 26 | Generated Datasets 27 | 28 | 29 |   Dataset Store 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/renderer/components/Data/DataStore.tsx: -------------------------------------------------------------------------------- 1 | import useSWR from 'swr'; 2 | import { useState } from 'react'; 3 | 4 | import { 5 | FormControl, 6 | FormLabel, 7 | Grid, 8 | Input, 9 | LinearProgress, 10 | Sheet, 11 | Box, 12 | } from '@mui/joy'; 13 | import DatasetCard from './DatasetCard'; 14 | import { SearchIcon } from 'lucide-react'; 15 | import { filterByFilters } from 'renderer/lib/utils'; 16 | 17 | import * as chatAPI from '../../lib/transformerlab-api-sdk'; 18 | 19 | const fetcher = (url) => fetch(url).then((res) => res.json()); 20 | 21 | export default function DataStore() { 22 | const [searchText, setSearchText] = useState(''); 23 | const { data, error, isLoading, mutate } = useSWR( 24 | chatAPI.Endpoints.Dataset.Gallery(), 25 | fetcher 26 | ); 27 | 28 | if (error) return 'Failed to load datasets from the gallery. Please check your connection or try again later.'; 29 | if (isLoading) return ; 30 | return ( 31 | 39 | *': { 48 | minWidth: { 49 | xs: '120px', 50 | md: '160px', 51 | }, 52 | }, 53 | }} 54 | > 55 | 56 | setSearchText(e.target.value)} 60 | startDecorator={} 61 | /> 62 | 63 | 64 | 77 | 78 | {data && 79 | data.data && 80 | filterByFilters(data.data, searchText).map((row) => ( 81 | 82 | 92 | 93 | ))} 94 | 95 | 96 | 97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /src/renderer/components/Data/DatasetInfoModal.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | import useSWR from 'swr'; 3 | 4 | import { 5 | Button, 6 | Divider, 7 | Table, 8 | Input, 9 | Modal, 10 | ModalClose, 11 | ModalDialog, 12 | Sheet, 13 | Typography, 14 | CircularProgress, 15 | DialogContent, 16 | DialogTitle, 17 | Box, 18 | } from '@mui/joy'; 19 | 20 | import * as chatAPI from '../../lib/transformerlab-api-sdk'; 21 | 22 | const fetcher = (url) => fetch(url).then((res) => res.json()); 23 | 24 | export default function DatasetInfoModal({ dataset_id, open, setOpen }) { 25 | const { data, error, isLoading, mutate } = useSWR( 26 | chatAPI.Endpoints.Dataset.Info(dataset_id), 27 | fetcher 28 | ); 29 | 30 | return ( 31 | { 34 | setOpen(false); 35 | }} 36 | > 37 | 38 | 39 | 50 | Dataset Info 51 | 52 | {isLoading && } 53 | {data && ( //Style keeps the data from overflowing, makes it fit in the box 54 |
55 |                 {JSON.stringify(data, null, 2)}
56 |               
57 | )} 58 |
59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/renderer/components/Data/EditDatasetModal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Divider, 3 | Modal, 4 | ModalClose, 5 | ModalDialog, 6 | Sheet, 7 | Typography, 8 | Box, 9 | Button, 10 | } from '@mui/joy'; 11 | import DatasetPreviewEditImage from './DatasetPreviewEditImage'; 12 | import { useState, useCallback } from 'react'; 13 | 14 | export default function EditDatasetModal({ 15 | datasetId, 16 | open, 17 | setOpen, 18 | template = 'default', 19 | }) { 20 | const [modifiedRows, setModifiedRows] = useState(new Map()); 21 | const [newDatasetId, setNewDatasetId] = useState(''); 22 | const [showWarningModal, setShowWarningModal] = useState(false); 23 | 24 | // Save edits logic 25 | const saveEditsWithName = useCallback(async () => { 26 | console.log('Saving dataset', newDatasetId, modifiedRows); 27 | setShowWarningModal(false); 28 | setOpen(false); 29 | }, [newDatasetId, modifiedRows, setOpen]); 30 | 31 | const handleDiscard = () => { 32 | setShowWarningModal(false); 33 | setOpen(false); 34 | }; 35 | 36 | const handleCancel = () => { 37 | setShowWarningModal(false); 38 | }; 39 | 40 | return ( 41 | <> 42 | setOpen(false)}> 43 | 44 | 45 | 46 | Edit {datasetId} 47 | 48 | 49 | 58 | 59 | setOpen(false)} 65 | /> 66 | 67 | 68 | 69 | 70 | 71 | {/* Custom Warning Modal */} 72 | 73 | 74 | Unsaved Changes 75 | 76 | 77 | You have unsaved changes. What would you like to do? 78 | 79 | 80 | 83 | 86 | 89 | 90 | 91 | 92 | 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/renderer/components/Data/PreviewDatasetModal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Divider, 3 | Modal, 4 | ModalClose, 5 | ModalDialog, 6 | Sheet, 7 | Typography, 8 | Box, 9 | } from '@mui/joy'; 10 | 11 | import DatasetTable from './DatasetTable'; 12 | import useSWR from 'swr'; 13 | import * as chatAPI from '../../lib/transformerlab-api-sdk'; 14 | import DatasetPreviewEditImage from './DatasetPreviewEditImage'; 15 | import { useAPI } from 'renderer/lib/transformerlab-api-sdk'; 16 | 17 | const fetcher = (url) => 18 | fetch(url) 19 | .then((res) => res.json()) 20 | .then((data) => data); 21 | 22 | export default function PreviewDatasetModal({ 23 | dataset_id, 24 | open, 25 | setOpen, 26 | viewType = 'preview', 27 | }) { 28 | const { data, error, isLoading } = useAPI( 29 | 'datasets', 30 | ['info'], 31 | { dataset_id }, 32 | { enabled: open }, 33 | ); 34 | 35 | const isImageDataset = data?.features?.image?._type === 'Image'; 36 | 37 | return ( 38 | { 41 | setOpen(false); 42 | }} 43 | > 44 | 45 | 46 | 47 | Preview {dataset_id} 48 | 49 | 50 | 59 | 60 | {viewType === 'edit' ? ( 61 | 62 | ) : ( 63 | 64 | )} 65 | 66 | 67 | 68 | 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /src/renderer/components/Experiment/Api.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable jsx-a11y/anchor-is-valid */ 2 | import * as React from 'react'; 3 | 4 | import Sheet from '@mui/joy/Sheet'; 5 | 6 | import { Button, Stack, Typography } from '@mui/joy'; 7 | import { ExternalLinkIcon } from 'lucide-react'; 8 | 9 | export default function Api() { 10 | return ( 11 | 19 | 20 |
21 | API Documentation 22 |
23 | 33 |
34 |
35 |