├── .editorconfig ├── .erb ├── configs │ ├── .eslintrc │ ├── postcss.config.js │ ├── webpack.config.base.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 ├── 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 ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 1-Bug_report.md │ ├── 2-Question.md │ └── 3-Feature_request.md ├── config.yml ├── stale.yml └── workflows │ ├── codeql-analysis.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .husky ├── post-checkout ├── post-commit ├── post-merge ├── pre-commit └── pre-push ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── assets ├── assets.d.ts ├── entitlements.mac.plist ├── icon.icns ├── icon.ico ├── icon.png ├── icon.svg ├── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ └── 96x96.png └── painting.icns ├── outputs └── txt2img-samples │ ├── iguana.png │ ├── naked-person.png │ └── samples │ └── 00000.png ├── package-lock.json ├── package.json ├── release └── app │ ├── package-lock.json │ └── package.json ├── src ├── __tests__ │ └── App.test.tsx ├── main │ ├── main.ts │ ├── menu.ts │ ├── preload.ts │ └── util.ts └── renderer │ ├── App.css │ ├── App.tsx │ ├── Sparkle.tsx │ ├── index.ejs │ ├── index.tsx │ └── preload.d.ts ├── stable_diffusion ├── configs │ └── v1-inference.yaml ├── text2image.bin └── txt2img ├── tailwind.config.js └── 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/postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require('tailwindcss'); 2 | const autoprefixer = require('autoprefixer'); 3 | 4 | module.exports = { 5 | plugins: [tailwindcss, autoprefixer], 6 | }; 7 | -------------------------------------------------------------------------------- /.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 webpackPaths from './webpack.paths'; 7 | import { dependencies as externals } from '../../release/app/package.json'; 8 | 9 | const configuration: webpack.Configuration = { 10 | externals: [...Object.keys(externals || {})], 11 | 12 | stats: 'errors-only', 13 | 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.[jt]sx?$/, 18 | exclude: /node_modules/, 19 | use: { 20 | loader: 'ts-loader', 21 | options: { 22 | // Remove this line to enable type checking in webpack builds 23 | transpileOnly: true, 24 | }, 25 | }, 26 | }, 27 | ], 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.srcPath, 32 | // https://github.com/webpack/webpack/issues/1114 33 | library: { 34 | type: 'commonjs2', 35 | }, 36 | }, 37 | 38 | /** 39 | * Determine the array of extensions that should be used to resolve modules. 40 | */ 41 | resolve: { 42 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 43 | modules: [webpackPaths.srcPath, 'node_modules'], 44 | }, 45 | 46 | plugins: [ 47 | new webpack.EnvironmentPlugin({ 48 | NODE_ENV: 'production', 49 | }), 50 | ], 51 | }; 52 | 53 | export default configuration; 54 | -------------------------------------------------------------------------------- /.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 | }, 34 | 35 | optimization: { 36 | minimizer: [ 37 | new TerserPlugin({ 38 | parallel: true, 39 | }), 40 | ], 41 | }, 42 | 43 | plugins: [ 44 | new BundleAnalyzerPlugin({ 45 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 46 | }), 47 | 48 | /** 49 | * Create global constants which can be configured at compile time. 50 | * 51 | * Useful for allowing different behaviour between development builds and 52 | * release builds 53 | * 54 | * NODE_ENV should be production so that modules do not perform certain 55 | * development checks 56 | */ 57 | new webpack.EnvironmentPlugin({ 58 | NODE_ENV: 'production', 59 | DEBUG_PROD: false, 60 | START_MINIMIZED: false, 61 | }), 62 | 63 | new webpack.DefinePlugin({ 64 | 'process.type': '"main"', 65 | }), 66 | ], 67 | 68 | /** 69 | * Disables webpack processing of __dirname and __filename. 70 | * If you run the bundle in node.js it falls back to these values of node.js. 71 | * https://github.com/webpack/webpack/issues/2010 72 | */ 73 | node: { 74 | __dirname: false, 75 | __filename: false, 76 | }, 77 | }; 78 | 79 | export default merge(baseConfig, configuration); 80 | -------------------------------------------------------------------------------- /.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 | }, 28 | 29 | plugins: [ 30 | new BundleAnalyzerPlugin({ 31 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 32 | }), 33 | 34 | /** 35 | * Create global constants which can be configured at compile time. 36 | * 37 | * Useful for allowing different behaviour between development builds and 38 | * release builds 39 | * 40 | * NODE_ENV should be production so that modules do not perform certain 41 | * development checks 42 | * 43 | * By default, use 'development' as NODE_ENV. This can be overriden with 44 | * 'staging', for example, by changing the ENV variables in the npm scripts 45 | */ 46 | new webpack.EnvironmentPlugin({ 47 | NODE_ENV: 'development', 48 | }), 49 | 50 | new webpack.LoaderOptionsPlugin({ 51 | debug: true, 52 | }), 53 | ], 54 | 55 | /** 56 | * Disables webpack processing of __dirname and __filename. 57 | * If you run the bundle in node.js it falls back to these values of node.js. 58 | * https://github.com/webpack/webpack/issues/2010 59 | */ 60 | node: { 61 | __dirname: false, 62 | __filename: false, 63 | }, 64 | 65 | watch: true, 66 | }; 67 | 68 | export default merge(baseConfig, configuration); 69 | -------------------------------------------------------------------------------- /.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.config.renderer.dev.ts: -------------------------------------------------------------------------------- 1 | import 'webpack-dev-server'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import webpack from 'webpack'; 5 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 6 | import chalk from 'chalk'; 7 | import { merge } from 'webpack-merge'; 8 | import { execSync, spawn } from 'child_process'; 9 | import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | 14 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 15 | // at the dev webpack config is not accidentally run in a production environment 16 | if (process.env.NODE_ENV === 'production') { 17 | checkNodeEnv('development'); 18 | } 19 | 20 | const port = process.env.PORT || 1212; 21 | const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json'); 22 | const skipDLLs = 23 | module.parent?.filename.includes('webpack.config.renderer.dev.dll') || 24 | module.parent?.filename.includes('webpack.config.eslint'); 25 | 26 | /** 27 | * Warn if the DLL is not built 28 | */ 29 | if ( 30 | !skipDLLs && 31 | !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest)) 32 | ) { 33 | console.log( 34 | chalk.black.bgYellow.bold( 35 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"' 36 | ) 37 | ); 38 | execSync('npm run postinstall'); 39 | } 40 | 41 | const configuration: webpack.Configuration = { 42 | devtool: 'inline-source-map', 43 | 44 | mode: 'development', 45 | 46 | target: ['web', 'electron-renderer'], 47 | 48 | entry: [ 49 | `webpack-dev-server/client?http://localhost:${port}/dist`, 50 | 'webpack/hot/only-dev-server', 51 | path.join(webpackPaths.srcRendererPath, 'index.tsx'), 52 | ], 53 | 54 | output: { 55 | path: webpackPaths.distRendererPath, 56 | publicPath: '/', 57 | filename: 'renderer.dev.js', 58 | library: { 59 | type: 'umd', 60 | }, 61 | }, 62 | 63 | module: { 64 | rules: [ 65 | { 66 | test: /\.s?css$/, 67 | use: [ 68 | 'style-loader', 69 | { 70 | loader: 'css-loader', 71 | options: { 72 | modules: true, 73 | sourceMap: true, 74 | importLoaders: 1, 75 | }, 76 | }, 77 | 'sass-loader', 78 | ], 79 | include: /\.module\.s?(c|a)ss$/, 80 | }, 81 | { 82 | test: /\.s?css$/, 83 | use: [ 84 | 'style-loader', 85 | 'css-loader', 86 | 'sass-loader', 87 | 88 | { 89 | loader: 'postcss-loader', 90 | options: { 91 | postcssOptions: { 92 | plugins: [require('tailwindcss'), require('autoprefixer')], 93 | }, 94 | }, 95 | }, 96 | ], 97 | exclude: /\.module\.s?(c|a)ss$/, 98 | }, 99 | // Fonts 100 | { 101 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 102 | type: 'asset/resource', 103 | }, 104 | // Images 105 | { 106 | test: /\.(png|jpg|jpeg|gif)$/i, 107 | type: 'asset/resource', 108 | }, 109 | // SVG 110 | { 111 | test: /\.svg$/, 112 | use: [ 113 | { 114 | loader: '@svgr/webpack', 115 | options: { 116 | prettier: false, 117 | svgo: false, 118 | svgoConfig: { 119 | plugins: [{ removeViewBox: false }], 120 | }, 121 | titleProp: true, 122 | ref: true, 123 | }, 124 | }, 125 | 'file-loader', 126 | ], 127 | }, 128 | ], 129 | }, 130 | plugins: [ 131 | ...(skipDLLs 132 | ? [] 133 | : [ 134 | new webpack.DllReferencePlugin({ 135 | context: webpackPaths.dllPath, 136 | manifest: require(manifest), 137 | sourceType: 'var', 138 | }), 139 | ]), 140 | 141 | new webpack.NoEmitOnErrorsPlugin(), 142 | 143 | /** 144 | * Create global constants which can be configured at compile time. 145 | * 146 | * Useful for allowing different behaviour between development builds and 147 | * release builds 148 | * 149 | * NODE_ENV should be production so that modules do not perform certain 150 | * development checks 151 | * 152 | * By default, use 'development' as NODE_ENV. This can be overriden with 153 | * 'staging', for example, by changing the ENV variables in the npm scripts 154 | */ 155 | new webpack.EnvironmentPlugin({ 156 | NODE_ENV: 'development', 157 | }), 158 | 159 | new webpack.LoaderOptionsPlugin({ 160 | debug: true, 161 | }), 162 | 163 | new ReactRefreshWebpackPlugin(), 164 | 165 | new HtmlWebpackPlugin({ 166 | filename: path.join('index.html'), 167 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 168 | minify: { 169 | collapseWhitespace: true, 170 | removeAttributeQuotes: true, 171 | removeComments: true, 172 | }, 173 | isBrowser: false, 174 | env: process.env.NODE_ENV, 175 | isDevelopment: process.env.NODE_ENV !== 'production', 176 | nodeModules: webpackPaths.appNodeModulesPath, 177 | }), 178 | ], 179 | 180 | node: { 181 | __dirname: false, 182 | __filename: false, 183 | }, 184 | 185 | devServer: { 186 | port, 187 | compress: true, 188 | hot: true, 189 | headers: { 'Access-Control-Allow-Origin': '*' }, 190 | static: { 191 | publicPath: '/', 192 | }, 193 | historyApiFallback: { 194 | verbose: true, 195 | }, 196 | setupMiddlewares(middlewares) { 197 | console.log('Starting preload.js builder...'); 198 | const preloadProcess = spawn('npm', ['run', 'start:preload'], { 199 | shell: true, 200 | stdio: 'inherit', 201 | }) 202 | .on('close', (code: number) => process.exit(code!)) 203 | .on('error', (spawnError) => console.error(spawnError)); 204 | 205 | console.log('Starting Main Process...'); 206 | let args = ['run', 'start:main']; 207 | if (process.env.MAIN_ARGS) { 208 | args = args.concat( 209 | ['--', ...process.env.MAIN_ARGS.matchAll(/"[^"]+"|[^\s"]+/g)].flat() 210 | ); 211 | } 212 | spawn('npm', args, { 213 | shell: true, 214 | stdio: 'inherit', 215 | }) 216 | .on('close', (code: number) => { 217 | preloadProcess.kill(); 218 | process.exit(code!); 219 | }) 220 | .on('error', (spawnError) => console.error(spawnError)); 221 | return middlewares; 222 | }, 223 | }, 224 | }; 225 | 226 | export default merge(baseConfig, configuration); 227 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 8 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; 11 | import { merge } from 'webpack-merge'; 12 | import TerserPlugin from 'terser-webpack-plugin'; 13 | import baseConfig from './webpack.config.base'; 14 | import webpackPaths from './webpack.paths'; 15 | import checkNodeEnv from '../scripts/check-node-env'; 16 | import deleteSourceMaps from '../scripts/delete-source-maps'; 17 | 18 | checkNodeEnv('production'); 19 | deleteSourceMaps(); 20 | 21 | const configuration: webpack.Configuration = { 22 | devtool: 'source-map', 23 | 24 | mode: 'production', 25 | 26 | target: ['web', 'electron-renderer'], 27 | 28 | entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')], 29 | 30 | output: { 31 | path: webpackPaths.distRendererPath, 32 | publicPath: './', 33 | filename: 'renderer.js', 34 | library: { 35 | type: 'umd', 36 | }, 37 | }, 38 | 39 | module: { 40 | rules: [ 41 | { 42 | test: /\.s?(a|c)ss$/, 43 | use: [ 44 | MiniCssExtractPlugin.loader, 45 | { 46 | loader: 'css-loader', 47 | options: { 48 | modules: true, 49 | sourceMap: true, 50 | importLoaders: 1, 51 | }, 52 | }, 53 | 'sass-loader', 54 | ], 55 | include: /\.module\.s?(c|a)ss$/, 56 | }, 57 | { 58 | test: /\.s?(a|c)ss$/, 59 | use: [ 60 | MiniCssExtractPlugin.loader, 61 | 'css-loader', 62 | 'sass-loader', 63 | { 64 | loader: 'postcss-loader', 65 | options: { 66 | postcssOptions: { 67 | plugins: [require('tailwindcss'), require('autoprefixer')], 68 | }, 69 | }, 70 | }, 71 | ], 72 | exclude: /\.module\.s?(c|a)ss$/, 73 | }, 74 | // Fonts 75 | { 76 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 77 | type: 'asset/resource', 78 | }, 79 | // Images 80 | { 81 | test: /\.(png|jpg|jpeg|gif)$/i, 82 | type: 'asset/resource', 83 | }, 84 | // SVG 85 | { 86 | test: /\.svg$/, 87 | use: [ 88 | { 89 | loader: '@svgr/webpack', 90 | options: { 91 | prettier: false, 92 | svgo: false, 93 | svgoConfig: { 94 | plugins: [{ removeViewBox: false }], 95 | }, 96 | titleProp: true, 97 | ref: true, 98 | }, 99 | }, 100 | 'file-loader', 101 | ], 102 | }, 103 | ], 104 | }, 105 | 106 | optimization: { 107 | minimize: true, 108 | minimizer: [ 109 | new TerserPlugin({ 110 | parallel: true, 111 | }), 112 | new CssMinimizerPlugin(), 113 | ], 114 | }, 115 | 116 | plugins: [ 117 | /** 118 | * Create global constants which can be configured at compile time. 119 | * 120 | * Useful for allowing different behaviour between development builds and 121 | * release builds 122 | * 123 | * NODE_ENV should be production so that modules do not perform certain 124 | * development checks 125 | */ 126 | new webpack.EnvironmentPlugin({ 127 | NODE_ENV: 'production', 128 | DEBUG_PROD: false, 129 | }), 130 | 131 | new MiniCssExtractPlugin({ 132 | filename: 'style.css', 133 | }), 134 | 135 | new BundleAnalyzerPlugin({ 136 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 137 | }), 138 | 139 | new HtmlWebpackPlugin({ 140 | filename: 'index.html', 141 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 142 | minify: { 143 | collapseWhitespace: true, 144 | removeAttributeQuotes: true, 145 | removeComments: true, 146 | }, 147 | isBrowser: false, 148 | isDevelopment: process.env.NODE_ENV !== 'production', 149 | }), 150 | 151 | new webpack.DefinePlugin({ 152 | 'process.type': '"renderer"', 153 | }), 154 | ], 155 | }; 156 | 157 | export default merge(baseConfig, configuration); 158 | -------------------------------------------------------------------------------- /.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 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /.erb/img/erb-banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/.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 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import rimraf from 'rimraf'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const foldersToRemove = [ 5 | webpackPaths.distPath, 6 | webpackPaths.buildPath, 7 | webpackPaths.dllPath, 8 | ]; 9 | 10 | foldersToRemove.forEach((folder) => { 11 | rimraf.sync(folder); 12 | }); 13 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import rimraf from 'rimraf'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | export default function deleteSourceMaps() { 6 | rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map')); 7 | rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map')); 8 | } 9 | -------------------------------------------------------------------------------- /.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 | require('dotenv').config(); 4 | 5 | exports.default = async function notarizeMacos(context) { 6 | const { electronPlatformName, appOutDir } = context; 7 | if (electronPlatformName !== 'darwin') { 8 | return; 9 | } 10 | 11 | // if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 12 | // console.warn( 13 | // 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set' 14 | // ); 15 | // return; 16 | // } 17 | 18 | const appName = context.packager.appInfo.productFilename; 19 | 20 | await notarize({ 21 | appBundleId: build.appId, 22 | appPath: `${appOutDir}/${appName}.app`, 23 | appleId: process.env.APPLE_ID, 24 | appleIdPassword: process.env.APPLE_ID_PASSWORD, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /.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 | rules: { 4 | // A temporary hack related to IDE not resolving correct package.json 5 | 'import/no-extraneous-dependencies': 'off', 6 | 'import/no-unresolved': 'error', 7 | // Since React 17 and typescript 4.1 you can safely disable the rule 8 | 'react/react-in-jsx-scope': 'off', 9 | }, 10 | parserOptions: { 11 | ecmaVersion: 2020, 12 | sourceType: 'module', 13 | project: './tsconfig.json', 14 | tsconfigRootDir: __dirname, 15 | createDefaultProgram: true, 16 | }, 17 | settings: { 18 | 'import/resolver': { 19 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 20 | node: {}, 21 | webpack: { 22 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 23 | }, 24 | typescript: {}, 25 | }, 26 | 'import/parsers': { 27 | '@typescript-eslint/parser': ['.ts', '.tsx'], 28 | }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | stable_diffusion/txt2img filter=lfs diff=lfs merge=lfs -text 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [] 4 | patreon: 5 | open_collective: 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: You're having technical issues. 🐞 4 | labels: 'bug' 5 | --- 6 | 7 | 8 | 9 | ## Prerequisites 10 | 11 | 12 | 13 | - [ ] Using npm 14 | - [ ] Using an up-to-date [`main` branch](https://github.com/electron-react-boilerplate/electron-react-boilerplate/tree/main) 15 | - [ ] Using latest version of devtools. [Check the docs for how to update](https://electron-react-boilerplate.js.org/docs/dev-tools/) 16 | - [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400) 17 | - [ ] For issue in production release, add devtools output of `DEBUG_PROD=true npm run build && npm start` 18 | 19 | ## Expected Behavior 20 | 21 | 22 | 23 | ## Current Behavior 24 | 25 | 26 | 27 | ## Steps to Reproduce 28 | 29 | 30 | 31 | 32 | 1. 33 | 34 | 2. 35 | 36 | 3. 37 | 38 | 4. 39 | 40 | ## Possible Solution (Not obligatory) 41 | 42 | 43 | 44 | ## Context 45 | 46 | 47 | 48 | 49 | 50 | ## Your Environment 51 | 52 | 53 | 54 | - Node version : 55 | - electron-react-boilerplate version or branch : 56 | - Operating System and version : 57 | - Link to your project : 58 | 59 | 68 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question.❓ 4 | labels: 'question' 5 | --- 6 | 7 | ## Summary 8 | 9 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: You want something added to the boilerplate. 🎉 4 | labels: 'enhancement' 5 | --- 6 | 7 | 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/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | # To enable auto publishing to github, update your electron publisher 11 | # config in package.json > "build" and remove the conditional below 12 | runs-on: ${{ matrix.os }} 13 | 14 | strategy: 15 | matrix: 16 | os: [macos-latest] 17 | 18 | steps: 19 | - name: Checkout git repo 20 | uses: actions/checkout@v1 21 | 22 | - name: Install Node and NPM 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 16 26 | cache: npm 27 | 28 | - name: Install and build 29 | run: | 30 | npm install 31 | npm run postinstall 32 | npm run build 33 | 34 | - name: Publish releases 35 | env: 36 | # These values are used for auto updates signing 37 | APPLE_ID: ${{ secrets.APPLE_ID }} 38 | APPLE_ID_PASS: ${{ secrets.APPLE_ID_PASS }} 39 | CSC_LINK: ${{ secrets.CSC_LINK }} 40 | CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} 41 | # This is used for uploading release assets to github 42 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | run: | 44 | npm exec electron-builder -- --publish always --win --mac --linux 45 | -------------------------------------------------------------------------------- /.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@v1 16 | 17 | - name: Install Node.js and NPM 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: 16 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 | .env 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Coverage directory used by tools like istanbul 12 | coverage 13 | .eslintcache 14 | 15 | # Dependency directory 16 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 17 | node_modules 18 | 19 | # OSX 20 | .DS_Store 21 | 22 | release/app/dist 23 | release/build 24 | .erb/dll 25 | 26 | .idea 27 | npm-debug.log.* 28 | *.css.d.ts 29 | *.sass.d.ts 30 | *.scss.d.ts 31 | -------------------------------------------------------------------------------- /.husky/post-checkout: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-checkout'.\n"; exit 2; } 3 | git lfs post-checkout "$@" 4 | -------------------------------------------------------------------------------- /.husky/post-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-commit'.\n"; exit 2; } 3 | git lfs post-commit "$@" 4 | -------------------------------------------------------------------------------- /.husky/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-merge'.\n"; exit 2; } 3 | git lfs post-merge "$@" 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/pre-push'.\n"; exit 2; } 3 | git lfs pre-push "$@" 4 | -------------------------------------------------------------------------------- /.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": false, 16 | "javascript.format.enable": false, 17 | "typescript.format.enable": false, 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 | } 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Electron React Boilerplate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CHARL-E 2 | ### Stable Diffusion on your Mac in a 1 click install. 3 | 4 | CHARL-E packages Stable Diffusion into a simple app. No complex setup, dependencies, or internet required — just download and say what you want to see. 5 | 6 | Learn more at: 7 | [charl-e](https://www.charl-e.com) 8 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | export const ReactComponent: React.FC>; 5 | 6 | const content: string; 7 | export default content; 8 | } 9 | 10 | declare module '*.png' { 11 | const content: string; 12 | export default content; 13 | } 14 | 15 | declare module '*.jpg' { 16 | const content: string; 17 | export default content; 18 | } 19 | 20 | declare module '*.scss' { 21 | const content: Styles; 22 | export default content; 23 | } 24 | 25 | declare module '*.sass' { 26 | const content: Styles; 27 | export default content; 28 | } 29 | 30 | declare module '*.css' { 31 | const content: Styles; 32 | export default content; 33 | } 34 | -------------------------------------------------------------------------------- /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.cs.disable-library-validation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icon.png -------------------------------------------------------------------------------- /assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/128x128.png -------------------------------------------------------------------------------- /assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/16x16.png -------------------------------------------------------------------------------- /assets/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/24x24.png -------------------------------------------------------------------------------- /assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/256x256.png -------------------------------------------------------------------------------- /assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/32x32.png -------------------------------------------------------------------------------- /assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/48x48.png -------------------------------------------------------------------------------- /assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/512x512.png -------------------------------------------------------------------------------- /assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/64x64.png -------------------------------------------------------------------------------- /assets/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/icons/96x96.png -------------------------------------------------------------------------------- /assets/painting.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/assets/painting.icns -------------------------------------------------------------------------------- /outputs/txt2img-samples/iguana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/outputs/txt2img-samples/iguana.png -------------------------------------------------------------------------------- /outputs/txt2img-samples/naked-person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/outputs/txt2img-samples/naked-person.png -------------------------------------------------------------------------------- /outputs/txt2img-samples/samples/00000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbh123/charl-e/ea691a8a600f7e3fe449e37bca01b4022329013f/outputs/txt2img-samples/samples/00000.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./src/main/main.ts", 3 | "version": "0.0.6", 4 | "name": "charl-e", 5 | "scripts": { 6 | "build": "concurrently \"npm run build:main\" \"npm run build:renderer\"", 7 | "build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts", 8 | "build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts", 9 | "postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts", 10 | "lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx", 11 | "package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never", 12 | "prepare": "husky install", 13 | "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app", 14 | "start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer --trace-warnings", 15 | "start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only .", 16 | "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts", 17 | "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts", 18 | "test": "jest", 19 | "publish": "electron-builder -p always" 20 | }, 21 | "lint-staged": { 22 | "*.{js,jsx,ts,tsx}": [ 23 | "cross-env NODE_ENV=development eslint --cache" 24 | ], 25 | "*.json,.{eslintrc,prettierrc}": [ 26 | "prettier --ignore-path .eslintignore --parser json --write" 27 | ], 28 | "*.{css,scss}": [ 29 | "prettier --ignore-path .eslintignore --single-quote --write" 30 | ], 31 | "*.{html,md,yml}": [ 32 | "prettier --ignore-path .eslintignore --single-quote --write" 33 | ] 34 | }, 35 | "browserslist": [], 36 | "prettier": { 37 | "singleQuote": true, 38 | "overrides": [ 39 | { 40 | "files": [ 41 | ".prettierrc", 42 | ".eslintrc" 43 | ], 44 | "options": { 45 | "parser": "json" 46 | } 47 | } 48 | ] 49 | }, 50 | "jest": { 51 | "moduleDirectories": [ 52 | "node_modules", 53 | "release/app/node_modules" 54 | ], 55 | "moduleFileExtensions": [ 56 | "js", 57 | "jsx", 58 | "ts", 59 | "tsx", 60 | "json" 61 | ], 62 | "moduleNameMapper": { 63 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/.erb/mocks/fileMock.js", 64 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 65 | }, 66 | "setupFiles": [ 67 | "./.erb/scripts/check-build-exists.ts" 68 | ], 69 | "testEnvironment": "jsdom", 70 | "testEnvironmentOptions": { 71 | "url": "http://localhost/" 72 | }, 73 | "testPathIgnorePatterns": [ 74 | "release/app/dist" 75 | ], 76 | "transform": { 77 | "\\.(ts|tsx|js|jsx)$": "ts-jest" 78 | } 79 | }, 80 | "dependencies": { 81 | "@headlessui/react": "^1.7.0", 82 | "@tailwindcss/forms": "^0.5.3", 83 | "electron-debug": "^3.2.0", 84 | "electron-dl": "^3.3.1", 85 | "electron-log": "^4.4.8", 86 | "electron-store": "^8.1.0", 87 | "electron-updater": "^5.2.1", 88 | "react": "^18.2.0", 89 | "react-confetti": "^6.1.0", 90 | "react-confetti-explosion": "^1.0.3", 91 | "react-dom": "^18.2.0", 92 | "react-hot-toast": "^2.4.0", 93 | "react-router-dom": "^6.3.0", 94 | "styled-components": "^5.3.5", 95 | "update-electron-app": "^2.0.1" 96 | }, 97 | "devDependencies": { 98 | "@heroicons/react": "^2.0.10", 99 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", 100 | "@svgr/webpack": "^6.3.1", 101 | "@teamsupercell/typings-for-css-modules-loader": "^2.5.1", 102 | "@testing-library/jest-dom": "^5.16.5", 103 | "@testing-library/react": "^13.3.0", 104 | "@types/jest": "^28.1.7", 105 | "@types/node": "18.7.6", 106 | "@types/react": "^18.0.17", 107 | "@types/react-dom": "^18.0.6", 108 | "@types/react-test-renderer": "^18.0.0", 109 | "@types/terser-webpack-plugin": "^5.0.4", 110 | "@types/webpack-bundle-analyzer": "^4.4.2", 111 | "@typescript-eslint/eslint-plugin": "^5.33.1", 112 | "@typescript-eslint/parser": "^5.33.1", 113 | "autoprefixer": "^10.4.8", 114 | "browserslist-config-erb": "^0.0.3", 115 | "chalk": "^4.1.2", 116 | "concurrently": "^7.3.0", 117 | "core-js": "^3.24.1", 118 | "cross-env": "^7.0.3", 119 | "css-loader": "^6.7.1", 120 | "css-minimizer-webpack-plugin": "^4.0.0", 121 | "detect-port": "^1.3.0", 122 | "electron": "^20.0.2", 123 | "electron-builder": "^23.3.3", 124 | "electron-devtools-installer": "^3.2.0", 125 | "electron-notarize": "^1.2.1", 126 | "electron-rebuild": "^3.2.9", 127 | "electronmon": "^2.0.2", 128 | "eslint": "^8.22.0", 129 | "eslint-config-airbnb-base": "^15.0.0", 130 | "eslint-config-erb": "^4.0.3", 131 | "eslint-import-resolver-typescript": "^3.4.1", 132 | "eslint-import-resolver-webpack": "^0.13.2", 133 | "eslint-plugin-compat": "^4.0.2", 134 | "eslint-plugin-import": "^2.26.0", 135 | "eslint-plugin-jest": "^26.8.3", 136 | "eslint-plugin-jsx-a11y": "^6.6.1", 137 | "eslint-plugin-promise": "^6.0.0", 138 | "eslint-plugin-react": "^7.30.1", 139 | "eslint-plugin-react-hooks": "^4.6.0", 140 | "file-loader": "^6.2.0", 141 | "html-webpack-plugin": "^5.5.0", 142 | "husky": "^8.0.1", 143 | "identity-obj-proxy": "^3.0.0", 144 | "jest": "^28.1.3", 145 | "jest-environment-jsdom": "^28.1.3", 146 | "lint-staged": "^13.0.3", 147 | "mini-css-extract-plugin": "^2.6.1", 148 | "postcss": "^8.4.16", 149 | "postcss-loader": "^7.0.1", 150 | "prettier": "^2.7.1", 151 | "react-refresh": "^0.14.0", 152 | "react-test-renderer": "^18.2.0", 153 | "rimraf": "^3.0.2", 154 | "sass": "^1.54.4", 155 | "sass-loader": "^13.0.2", 156 | "style-loader": "^3.3.1", 157 | "tailwindcss": "^3.1.8", 158 | "terser-webpack-plugin": "^5.3.5", 159 | "ts-jest": "^28.0.8", 160 | "ts-loader": "^9.3.1", 161 | "ts-node": "^10.9.1", 162 | "typescript": "^4.7.4", 163 | "url-loader": "^4.1.1", 164 | "webpack": "^5.74.0", 165 | "webpack-bundle-analyzer": "^4.5.0", 166 | "webpack-cli": "^4.10.0", 167 | "webpack-dev-server": "^4.10.0", 168 | "webpack-merge": "^5.8.0" 169 | }, 170 | "build": { 171 | "publish": [ 172 | { 173 | "provider": "github", 174 | "owner": "cbh123", 175 | "repo": "charl-e" 176 | } 177 | ], 178 | "productName": "CHARL-E", 179 | "appId": "org.erb.ElectronReact", 180 | "asar": true, 181 | "icon": "./assets/painting.icns", 182 | "asarUnpack": "**\\*.{node,dll}", 183 | "files": [ 184 | "dist", 185 | "node_modules", 186 | "package.json" 187 | ], 188 | "afterSign": ".erb/scripts/notarize.js", 189 | "mac": { 190 | "target": { 191 | "target": "default", 192 | "arch": [ 193 | "arm64" 194 | ] 195 | }, 196 | "type": "distribution", 197 | "hardenedRuntime": true, 198 | "entitlements": "assets/entitlements.mac.plist", 199 | "entitlementsInherit": "assets/entitlements.mac.plist", 200 | "gatekeeperAssess": false 201 | }, 202 | "dmg": { 203 | "contents": [ 204 | { 205 | "x": 130, 206 | "y": 220 207 | }, 208 | { 209 | "x": 410, 210 | "y": 220, 211 | "type": "link", 212 | "path": "/Applications" 213 | } 214 | ] 215 | }, 216 | "win": { 217 | "target": [ 218 | "nsis" 219 | ] 220 | }, 221 | "linux": { 222 | "target": [ 223 | "AppImage" 224 | ], 225 | "category": "Development" 226 | }, 227 | "directories": { 228 | "app": "release/app", 229 | "buildResources": "assets", 230 | "output": "release/build" 231 | }, 232 | "extraResources": [ 233 | "./assets/**", 234 | "./stable_diffusion/**" 235 | ] 236 | }, 237 | "devEngines": { 238 | "node": ">=14.x", 239 | "npm": ">=7.x" 240 | }, 241 | "electronmon": { 242 | "patterns": [ 243 | "!**/**", 244 | "src/main/*" 245 | ], 246 | "logLevel": "quiet" 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /release/app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "charl-e", 3 | "version": "0.0.6", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "charl-e", 9 | "version": "0.0.6", 10 | "hasInstallScript": true 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "charl-e", 3 | "version": "0.0.6", 4 | "description": "Stable Diffusion on your Mac", 5 | "author": { 6 | "name": "Charlie Holtz", 7 | "email": "choltz@hey.com", 8 | "url": "https://charl-e.com" 9 | }, 10 | "main": "./dist/main/main.js", 11 | "scripts": { 12 | "electron-rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 13 | "postinstall": "npm run electron-rebuild && npm run link-modules", 14 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 15 | }, 16 | "dependencies": {} 17 | } 18 | -------------------------------------------------------------------------------- /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/main.ts: -------------------------------------------------------------------------------- 1 | /* eslint global-require: off, no-console: off, promise/always-return: off */ 2 | 3 | /** 4 | * This module executes inside of electron's main process. You can start 5 | * electron renderer process from here and communicate with the other processes 6 | * through IPC. 7 | * 8 | * When running `npm run build` or `npm run build:main`, this file is compiled to 9 | * `./src/main.js` using webpack. This gives us some performance wins. 10 | */ 11 | import path from 'path'; 12 | import { app, BrowserWindow, shell, ipcMain, protocol } from 'electron'; 13 | import { autoUpdater } from 'electron-updater'; 14 | import MenuBuilder from './menu'; 15 | import { resolveHtmlPath } from './util'; 16 | const electronDl = require('electron-dl'); 17 | const Store = require('electron-store'); 18 | 19 | const fs = require('fs'); 20 | const os = require('os'); 21 | require('dotenv').config(); 22 | 23 | const CONFIG_DIR = `./stable_diffusion/configs/v1-inference.yaml`; 24 | const APPROX_WEIGHT_SIZE = 4100000000; 25 | const DEFAULT_OPTIONS = { 26 | '--ckpt': `${app.getPath('userData')}/stable_diffusion/models/model.ckpt`, 27 | '--plms': 'off', 28 | '--ddim_steps': '5', 29 | '--n_samples': '1', 30 | '--outdir': `${os.homedir()}/Desktop/charl-e/samples`, 31 | '--seed': '42', 32 | }; 33 | 34 | const store = new Store(); 35 | let charle: any = null; 36 | process.env.PYTORCH_ENABLE_MPS_FALLBACK = '1'; 37 | 38 | export default class AppUpdater { 39 | constructor() { 40 | const log = require('electron-log'); 41 | log.transports.file.level = 'debug'; 42 | autoUpdater.logger = log; 43 | autoUpdater.checkForUpdatesAndNotify(); 44 | } 45 | } 46 | 47 | function getLatestImage(filepath: string) { 48 | if (!fs.existsSync(filepath)) { 49 | console.log('making dir', filepath); 50 | fs.mkdirSync(filepath, { recursive: true }); 51 | } 52 | const dirents = fs.readdirSync(filepath, { withFileTypes: true }); 53 | 54 | const fileNames = dirents 55 | .filter((dirent: any) => dirent.isFile()) 56 | .map(function (fileName: any) { 57 | return { 58 | name: fileName.name, 59 | time: fs.statSync(`${filepath}/${fileName.name}`).mtime.getTime(), 60 | }; 61 | }) 62 | .sort(function (a, b) { 63 | return a.time - b.time; 64 | }) 65 | .map((file: any) => file.name) 66 | .map((file: string) => `${filepath}/${file}`) 67 | .filter((file: string) => file.endsWith('.png')); 68 | 69 | return { 70 | latestImage: fileNames.at(-1), 71 | outDir: filepath, 72 | history: store.get('history', []), 73 | }; 74 | } 75 | 76 | function getPaths(filepath: string) { 77 | // We're in prod 78 | if (fs.existsSync(filepath)) { 79 | return { 80 | config: `${process.resourcesPath}/stable_diffusion/configs/v1-inference.yaml`, 81 | executable: filepath, 82 | }; 83 | // we're in dev 84 | } 85 | return { 86 | config: CONFIG_DIR, 87 | executable: './stable_diffusion/text2image.bin', 88 | }; 89 | } 90 | 91 | let mainWindow: BrowserWindow | null = null; 92 | 93 | ipcMain.on('ipc-example', async (event, arg) => { 94 | const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`; 95 | console.log(msgTemplate(arg)); 96 | event.reply('ipc-example', msgTemplate('pong')); 97 | }); 98 | 99 | if (process.env.NODE_ENV === 'production') { 100 | const sourceMapSupport = require('source-map-support'); 101 | sourceMapSupport.install(); 102 | } 103 | 104 | const isDebug = 105 | process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; 106 | 107 | if (isDebug) { 108 | require('electron-debug')(); 109 | } 110 | 111 | const installExtensions = async () => { 112 | const installer = require('electron-devtools-installer'); 113 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 114 | const extensions = ['REACT_DEVELOPER_TOOLS']; 115 | 116 | return installer 117 | .default( 118 | extensions.map((name) => installer[name]), 119 | forceDownload 120 | ) 121 | .catch(console.log); 122 | }; 123 | 124 | const installWeights = async (mainWindow: BrowserWindow) => { 125 | if ( 126 | fs.existsSync( 127 | `${app.getPath('userData')}/stable_diffusion/models/model.ckpt` 128 | ) 129 | ) { 130 | return; 131 | } 132 | //delete if old weights exist 133 | if ( 134 | fs.existsSync(`${process.resourcesPath}/stable_diffusion/models/model.ckpt`) 135 | ) { 136 | fs.unlinkSync( 137 | `${process.resourcesPath}/stable_diffusion/models/model.ckpt` 138 | ); 139 | } 140 | 141 | await electronDl.download( 142 | mainWindow, 143 | 'https://charle.s3.amazonaws.com/checkpoint_liberty_with_aug.pth', 144 | { 145 | directory: `${os.homedir()}/.cache/torch/hub/checkpoints/`, 146 | filename: 'checkpoint_liberty_with_aug.pth', 147 | onCompleted: () => { 148 | console.log('Download checkpoint complete'); 149 | mainWindow.webContents.send( 150 | 'stdout-message', 151 | `Download Checkpoint Complete` 152 | ); 153 | }, 154 | } 155 | ); 156 | 157 | await electronDl.download( 158 | mainWindow, 159 | 'https://me.cmdr2.org/stable-diffusion-ui/sd-v1-4.ckpt', 160 | { 161 | directory: `${app.getPath('userData')}/stable_diffusion/models/`, 162 | filename: 'model.ckpt', 163 | onProgress: (progress: any) => { 164 | console.log(progress); 165 | mainWindow.webContents.send('download-progress', progress); 166 | mainWindow.webContents.send( 167 | 'stdout-message', 168 | `Download weights: ${progress.transferredBytes} / ${progress.totalBytes}` 169 | ); 170 | }, 171 | onCompleted: (progress: any) => { 172 | console.log('Download complete'); 173 | mainWindow.webContents.send('download-complete', progress); 174 | }, 175 | } 176 | ); 177 | }; 178 | 179 | const objectToList = (args: Object) => { 180 | if (args['--plms'] == 'off') { 181 | delete args['--plms']; 182 | } 183 | const res: string[] = Object.entries(args) 184 | .flat() 185 | .filter((item) => item !== 'on'); 186 | return res; 187 | }; 188 | 189 | const savePrompt = async ({ prompt, image, command }) => { 190 | const history = store.get('history') ? store.get('history') : []; 191 | store.set('history', [ 192 | { prompt: prompt, command: command, image: image }, 193 | ...history, 194 | ]); 195 | }; 196 | 197 | const createWindow = async () => { 198 | if (isDebug) { 199 | await installExtensions(); 200 | } 201 | 202 | const RESOURCES_PATH = app.isPackaged 203 | ? path.join(process.resourcesPath, 'assets') 204 | : path.join(__dirname, '../../assets'); 205 | 206 | const getAssetPath = (...paths: string[]): string => { 207 | return path.join(RESOURCES_PATH, ...paths); 208 | }; 209 | 210 | mainWindow = new BrowserWindow({ 211 | show: false, 212 | width: 1024, 213 | height: 728, 214 | icon: getAssetPath('painting.icns'), 215 | webPreferences: { 216 | sandbox: false, 217 | preload: app.isPackaged 218 | ? path.join(__dirname, 'preload.js') 219 | : path.join(__dirname, '../../.erb/dll/preload.js'), 220 | }, 221 | }); 222 | 223 | mainWindow.webContents.on('did-finish-load', () => { 224 | const currentOptions = store.get('options'); 225 | 226 | if (currentOptions == null) { 227 | store.set('options', DEFAULT_OPTIONS); 228 | } 229 | mainWindow!.webContents.send('loaded-options', store.get('options')); 230 | 231 | // if the weights don't exist, download them 232 | if ( 233 | !fs.existsSync( 234 | `${app.getPath('userData')}/stable_diffusion/models/model.ckpt` 235 | ) 236 | ) { 237 | mainWindow!.webContents.send('no-weights', {}); 238 | installWeights(mainWindow!); 239 | // if the weights do exist, make sure they're downloaded 240 | } else { 241 | if ( 242 | fs.statSync( 243 | `${app.getPath('userData')}/stable_diffusion/models/model.ckpt` 244 | ).size < APPROX_WEIGHT_SIZE 245 | ) { 246 | mainWindow!.webContents.send('partial-weights', {}); 247 | } 248 | } 249 | 250 | mainWindow!.webContents.send( 251 | 'image-dir', 252 | getLatestImage(store.get('options')['--outdir']) 253 | ); 254 | }); 255 | 256 | ipcMain.on('open-file', (_event, file) => { 257 | shell.showItemInFolder(file.replace('media-loader:/', '')); 258 | }); 259 | 260 | ipcMain.on('reset-options', (_event, _args) => { 261 | store.set('options', DEFAULT_OPTIONS); 262 | mainWindow!.webContents.send('loaded-options', DEFAULT_OPTIONS); 263 | }); 264 | 265 | ipcMain.on('redownload-weights', (_event, _args) => { 266 | // delete what we currently have 267 | if ( 268 | fs.existsSync( 269 | `${app.getPath('userData')}/stable_diffusion/models/model.ckpt` 270 | ) 271 | ) { 272 | fs.unlinkSync( 273 | `${app.getPath('userData')}/stable_diffusion/models/model.ckpt` 274 | ); 275 | } 276 | 277 | //delete if old weights exist 278 | if ( 279 | fs.existsSync( 280 | `${process.resourcesPath}/stable_diffusion/models/model.ckpt` 281 | ) 282 | ) { 283 | fs.unlinkSync( 284 | `${process.resourcesPath}/stable_diffusion/models/model.ckpt` 285 | ); 286 | } 287 | installWeights(mainWindow!); 288 | }); 289 | 290 | ipcMain.on('save-options', (_event, args) => { 291 | store.set('options', args); 292 | mainWindow!.webContents.send( 293 | 'stdout-message', 294 | `Options Saved: ${JSON.stringify(args)}` 295 | ); 296 | 297 | mainWindow!.webContents.send('loaded-options', store.get('options')); 298 | }); 299 | 300 | ipcMain.on('run-prompt', (_event, { prompt }) => { 301 | mainWindow!.webContents.send('initializing', true); 302 | 303 | const { config, executable } = getPaths( 304 | `${process.resourcesPath}/stable_diffusion/txt2img` 305 | ); 306 | 307 | const args = objectToList(store.get('options')); 308 | const execArgs = ['--prompt', prompt, ...args, '--config', config]; 309 | 310 | mainWindow!.webContents.send('stdout-message', `Params: ${execArgs}`); 311 | 312 | const child = require('child_process'); 313 | charle = child.spawn(executable, execArgs, { 314 | stdio: ['pipe', 'pipe', 'pipe', 'ipc'], 315 | }); 316 | 317 | charle.stderr.on('data', (data: any) => { 318 | if (data.toString().includes('Sampler: ')) { 319 | const pct = data.toString().split(':')[1].split('%')[0].trim(); 320 | mainWindow!.webContents.send('initializing', false); 321 | mainWindow!.webContents.send('loading-update', pct); 322 | } 323 | mainWindow!.webContents.send('stdout-message', data.toString()); 324 | }); 325 | 326 | charle.on('close', () => { 327 | console.log('KILLEd!!! ', charle.killed); 328 | console.log('stats', charle.exitCode); 329 | 330 | if (charle.killed) { 331 | mainWindow!.webContents.send('killed', true); 332 | } else if (charle.exitCode === 1) { 333 | mainWindow!.webContents.send('error', true); 334 | mainWindow!.webContents.send('killed', true); 335 | } else if (charle.exitCode === 0) { 336 | const outpathLocation = execArgs.findIndex((x) => x == '--outdir'); 337 | const outpath = execArgs[outpathLocation + 1]; 338 | console.log( 339 | `Sending ${getLatestImage(outpath).latestImage} to frontend` 340 | ); 341 | mainWindow!.webContents.send( 342 | 'image-load', 343 | getLatestImage(outpath).latestImage 344 | ); 345 | savePrompt({ 346 | prompt: prompt, 347 | image: getLatestImage(outpath).latestImage, 348 | command: execArgs, 349 | }); 350 | 351 | mainWindow!.webContents.send('image-dir', getLatestImage(outpath)); 352 | } else { 353 | mainWindow!.webContents.send('error', true); 354 | } 355 | }); 356 | }); 357 | 358 | ipcMain.on('cancel-run', (_event, _value) => { 359 | charle.kill(); 360 | }); 361 | 362 | mainWindow.loadURL(resolveHtmlPath('index.html')); 363 | 364 | mainWindow.on('ready-to-show', () => { 365 | if (!mainWindow) { 366 | throw new Error('"mainWindow" is not defined'); 367 | } 368 | if (process.env.START_MINIMIZED) { 369 | mainWindow.minimize(); 370 | } else { 371 | mainWindow.show(); 372 | } 373 | }); 374 | 375 | mainWindow.on('closed', () => { 376 | mainWindow = null; 377 | }); 378 | 379 | const menuBuilder = new MenuBuilder(mainWindow); 380 | menuBuilder.buildMenu(); 381 | 382 | // Open urls in the user's browser 383 | mainWindow.webContents.setWindowOpenHandler((edata) => { 384 | shell.openExternal(edata.url); 385 | return { action: 'deny' }; 386 | }); 387 | 388 | // Remove this if your app does not use auto updates 389 | // eslint-disable-next-line 390 | new AppUpdater(); 391 | }; 392 | 393 | console.log(app.getPath('userData')); 394 | 395 | /** 396 | * Add event listeners... 397 | */ 398 | 399 | app.on('window-all-closed', () => { 400 | // Respect the OSX convention of having the application in memory even 401 | // after all windows have been closed 402 | if (process.platform !== 'darwin') { 403 | app.quit(); 404 | } 405 | }); 406 | 407 | app 408 | .whenReady() 409 | .then(() => { 410 | // Custom protocol 411 | protocol.registerFileProtocol('media-loader', (request, callback) => { 412 | const url = request.url.replace('media-loader://', ''); 413 | try { 414 | return callback(url); 415 | } catch (err) { 416 | console.error(err); 417 | return callback('404'); 418 | } 419 | }); 420 | 421 | createWindow(); 422 | app.on('activate', () => { 423 | // On macOS it's common to re-create a window in the app when the 424 | // dock icon is clicked and there are no other windows open. 425 | if (mainWindow === null) createWindow(); 426 | }); 427 | }) 428 | .catch(console.log); 429 | -------------------------------------------------------------------------------- /src/main/menu.ts: -------------------------------------------------------------------------------- 1 | import { 2 | app, 3 | Menu, 4 | shell, 5 | BrowserWindow, 6 | MenuItemConstructorOptions, 7 | } from 'electron'; 8 | 9 | interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { 10 | selector?: string; 11 | submenu?: DarwinMenuItemConstructorOptions[] | Menu; 12 | } 13 | 14 | export default class MenuBuilder { 15 | mainWindow: BrowserWindow; 16 | 17 | constructor(mainWindow: BrowserWindow) { 18 | this.mainWindow = mainWindow; 19 | } 20 | 21 | buildMenu(): Menu { 22 | if ( 23 | process.env.NODE_ENV === 'development' || 24 | process.env.DEBUG_PROD === 'true' 25 | ) { 26 | this.setupDevelopmentEnvironment(); 27 | } 28 | 29 | const template = 30 | process.platform === 'darwin' 31 | ? this.buildDarwinTemplate() 32 | : this.buildDefaultTemplate(); 33 | 34 | const menu = Menu.buildFromTemplate(template); 35 | Menu.setApplicationMenu(menu); 36 | 37 | return menu; 38 | } 39 | 40 | setupDevelopmentEnvironment(): void { 41 | this.mainWindow.webContents.on('context-menu', (_, props) => { 42 | const { x, y } = props; 43 | 44 | Menu.buildFromTemplate([ 45 | { 46 | label: 'Inspect element', 47 | click: () => { 48 | this.mainWindow.webContents.inspectElement(x, y); 49 | }, 50 | }, 51 | ]).popup({ window: this.mainWindow }); 52 | }); 53 | } 54 | 55 | buildDarwinTemplate(): MenuItemConstructorOptions[] { 56 | const subMenuAbout: DarwinMenuItemConstructorOptions = { 57 | label: 'Electron', 58 | submenu: [ 59 | { 60 | label: 'About CHARL-E', 61 | selector: 'orderFrontStandardAboutPanel:', 62 | }, 63 | { type: 'separator' }, 64 | { label: 'Services', submenu: [] }, 65 | { type: 'separator' }, 66 | { 67 | label: 'Hide CHARL-E', 68 | accelerator: 'Command+H', 69 | selector: 'hide:', 70 | }, 71 | { 72 | label: 'Hide Others', 73 | accelerator: 'Command+Shift+H', 74 | selector: 'hideOtherApplications:', 75 | }, 76 | { label: 'Show All', selector: 'unhideAllApplications:' }, 77 | { type: 'separator' }, 78 | { 79 | label: 'Quit', 80 | accelerator: 'Command+Q', 81 | click: () => { 82 | app.quit(); 83 | }, 84 | }, 85 | ], 86 | }; 87 | const subMenuEdit: DarwinMenuItemConstructorOptions = { 88 | label: 'Edit', 89 | submenu: [ 90 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }, 91 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' }, 92 | { type: 'separator' }, 93 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }, 94 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }, 95 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }, 96 | { 97 | label: 'Select All', 98 | accelerator: 'Command+A', 99 | selector: 'selectAll:', 100 | }, 101 | ], 102 | }; 103 | const subMenuViewDev: MenuItemConstructorOptions = { 104 | label: 'View', 105 | submenu: [ 106 | { 107 | label: 'Reload', 108 | accelerator: 'Command+R', 109 | click: () => { 110 | this.mainWindow.webContents.reload(); 111 | }, 112 | }, 113 | { 114 | label: 'Toggle Full Screen', 115 | accelerator: 'Ctrl+Command+F', 116 | click: () => { 117 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 118 | }, 119 | }, 120 | { 121 | label: 'Toggle Developer Tools', 122 | accelerator: 'Alt+Command+I', 123 | click: () => { 124 | this.mainWindow.webContents.toggleDevTools(); 125 | }, 126 | }, 127 | ], 128 | }; 129 | const subMenuViewProd: MenuItemConstructorOptions = { 130 | label: 'View', 131 | submenu: [ 132 | { 133 | label: 'Toggle Full Screen', 134 | accelerator: 'Ctrl+Command+F', 135 | click: () => { 136 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 137 | }, 138 | }, 139 | ], 140 | }; 141 | const subMenuWindow: DarwinMenuItemConstructorOptions = { 142 | label: 'Window', 143 | submenu: [ 144 | { 145 | label: 'Minimize', 146 | accelerator: 'Command+M', 147 | selector: 'performMiniaturize:', 148 | }, 149 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, 150 | { type: 'separator' }, 151 | { label: 'Bring All to Front', selector: 'arrangeInFront:' }, 152 | ], 153 | }; 154 | const subMenuHelp: MenuItemConstructorOptions = { 155 | label: 'Help', 156 | submenu: [ 157 | { 158 | label: 'Learn More', 159 | click() { 160 | shell.openExternal('https://electronjs.org'); 161 | }, 162 | }, 163 | { 164 | label: 'Documentation', 165 | click() { 166 | shell.openExternal( 167 | 'https://github.com/electron/electron/tree/main/docs#readme' 168 | ); 169 | }, 170 | }, 171 | { 172 | label: 'Community Discussions', 173 | click() { 174 | shell.openExternal('https://www.electronjs.org/community'); 175 | }, 176 | }, 177 | { 178 | label: 'Search Issues', 179 | click() { 180 | shell.openExternal('https://github.com/electron/electron/issues'); 181 | }, 182 | }, 183 | ], 184 | }; 185 | 186 | const subMenuView = 187 | process.env.NODE_ENV === 'development' || 188 | process.env.DEBUG_PROD === 'true' 189 | ? subMenuViewDev 190 | : subMenuViewProd; 191 | 192 | return [subMenuAbout, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp]; 193 | } 194 | 195 | buildDefaultTemplate() { 196 | const templateDefault = [ 197 | { 198 | label: '&File', 199 | submenu: [ 200 | { 201 | label: '&Open', 202 | accelerator: 'Ctrl+O', 203 | }, 204 | { 205 | label: '&Close', 206 | accelerator: 'Ctrl+W', 207 | click: () => { 208 | this.mainWindow.close(); 209 | }, 210 | }, 211 | ], 212 | }, 213 | { 214 | label: '&View', 215 | submenu: 216 | process.env.NODE_ENV === 'development' || 217 | process.env.DEBUG_PROD === 'true' 218 | ? [ 219 | { 220 | label: '&Reload', 221 | accelerator: 'Ctrl+R', 222 | click: () => { 223 | this.mainWindow.webContents.reload(); 224 | }, 225 | }, 226 | { 227 | label: 'Toggle &Full Screen', 228 | accelerator: 'F11', 229 | click: () => { 230 | this.mainWindow.setFullScreen( 231 | !this.mainWindow.isFullScreen() 232 | ); 233 | }, 234 | }, 235 | { 236 | label: 'Toggle &Developer Tools', 237 | accelerator: 'Alt+Ctrl+I', 238 | click: () => { 239 | this.mainWindow.webContents.toggleDevTools(); 240 | }, 241 | }, 242 | ] 243 | : [ 244 | { 245 | label: 'Toggle &Full Screen', 246 | accelerator: 'F11', 247 | click: () => { 248 | this.mainWindow.setFullScreen( 249 | !this.mainWindow.isFullScreen() 250 | ); 251 | }, 252 | }, 253 | ], 254 | }, 255 | { 256 | label: 'Help', 257 | submenu: [ 258 | { 259 | label: 'Learn More', 260 | click() { 261 | shell.openExternal('https://electronjs.org'); 262 | }, 263 | }, 264 | { 265 | label: 'Documentation', 266 | click() { 267 | shell.openExternal( 268 | 'https://github.com/electron/electron/tree/main/docs#readme' 269 | ); 270 | }, 271 | }, 272 | { 273 | label: 'Community Discussions', 274 | click() { 275 | shell.openExternal('https://www.electronjs.org/community'); 276 | }, 277 | }, 278 | { 279 | label: 'Search Issues', 280 | click() { 281 | shell.openExternal('https://github.com/electron/electron/issues'); 282 | }, 283 | }, 284 | ], 285 | }, 286 | ]; 287 | 288 | return templateDefault; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/main/preload.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge, ipcRenderer, IpcRendererEvent, shell } from 'electron'; 2 | 3 | export type Channels = 'ipc-example'; 4 | 5 | contextBridge.exposeInMainWorld('electron', { 6 | shellAPI: shell, 7 | ipcRenderer: { 8 | runPrompt: ({ prompt, args }) => 9 | ipcRenderer.send('run-prompt', { prompt, args }), 10 | sendMessage(channel: Channels, args: unknown[]) { 11 | ipcRenderer.send(channel, args); 12 | }, 13 | on(channel: Channels, func: (...args: unknown[]) => void) { 14 | const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => 15 | func(...args); 16 | ipcRenderer.on(channel, subscription); 17 | 18 | return () => ipcRenderer.removeListener(channel, subscription); 19 | }, 20 | once(channel: Channels, func: (...args: unknown[]) => void) { 21 | ipcRenderer.once(channel, (_event, ...args) => func(...args)); 22 | }, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | 5 | export function resolveHtmlPath(htmlFileName: string) { 6 | if (process.env.NODE_ENV === 'development') { 7 | const port = process.env.PORT || 1212; 8 | const url = new URL(`http://localhost:${port}`); 9 | url.pathname = htmlFileName; 10 | return url.href; 11 | } 12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/App.css: -------------------------------------------------------------------------------- 1 | /* 2 | * @NOTE: Prepend a `~` to css file paths that are in your node_modules 3 | * See https://github.com/webpack-contrib/sass-loader#imports 4 | */ 5 | 6 | @tailwind base; 7 | @tailwind components; 8 | @tailwind utilities; 9 | 10 | .background-animate { 11 | background-size: 400%; 12 | 13 | -webkit-animation: AnimationName 3s ease infinite; 14 | -moz-animation: AnimationName 3s ease infinite; 15 | animation: AnimationName 3s ease infinite; 16 | } 17 | 18 | @keyframes AnimationName { 19 | 0%, 20 | 100% { 21 | background-position: 0% 50%; 22 | } 23 | 50% { 24 | background-position: 100% 50%; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; 2 | import 'tailwindcss/tailwind.css'; 3 | import './App.css'; 4 | import { useState, useEffect } from 'react'; 5 | import Confetti from 'react-confetti'; 6 | import Sparkles from './Sparkle'; 7 | import toast, { Toaster } from 'react-hot-toast'; 8 | const shell = window.electron.shellAPI; 9 | 10 | function Options(props: any) { 11 | const [outDir, setOutDir] = useState(props.options['--outdir']); 12 | const [ddimSteps, setDdimSteps] = useState(props.options['--ddim_steps']); 13 | const [numSamples, setNumSamples] = useState(props.options['--n_samples']); 14 | const [seed, setSeed] = useState(props.options['--seed']); 15 | const [plms, setPlms] = useState(props.options['--plms']); 16 | const [weights, setWeights] = useState(props.options['--ckpt']); 17 | 18 | const reset = () => { 19 | window.electron.ipcRenderer.sendMessage('reset-options'); 20 | props.setShowOptions(false); 21 | toast.success('Options Reset'); 22 | }; 23 | 24 | const redownload = () => { 25 | window.electron.ipcRenderer.sendMessage('redownload-weights'); 26 | }; 27 | 28 | const handleOptionsSubmit = (e) => { 29 | e.preventDefault(); 30 | const formData = new FormData(e.target); 31 | 32 | window.electron.ipcRenderer.sendMessage( 33 | 'save-options', 34 | Object.fromEntries(formData) 35 | ); 36 | 37 | props.setShowOptions(false); 38 | toast.success('Options Saved'); 39 | }; 40 | 41 | useEffect(() => {}, [props.outDir]); // 👈️ add state variables you want to track 42 | 43 | return ( 44 |
49 |

CHARL-E Options

50 |
51 | 57 |
58 | setOutDir(e.target.value)} 65 | placeholder={outDir} 66 | required 67 | /> 68 |
69 |
70 | 71 |
72 | 79 | {ddimSteps} 80 | setDdimSteps(e.target.value)} 89 | required 90 | className="block w-full rounded-md border-gray-300 focus:border-blue-500 focus:ring-blue-500 sm:text-sm" 91 | /> 92 |
93 | 94 |
95 | 101 | setPlms(plms === 'on' ? 'off' : 'on')} 108 | className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" 109 | /> 110 |
111 | 112 |
113 | 119 |
120 | setSeed(e.target.value)} 127 | placeholder={seed} 128 | required 129 | /> 130 |
131 |
132 | 133 |
134 | 151 |
152 | setWeights(e.target.value)} 159 | placeholder={weights} 160 | required 161 | /> 162 |
163 | 164 | 172 |
173 |
174 | 182 | 189 |
190 |
191 | ); 192 | } 193 | export function Prompt() { 194 | const [prompt, setPrompt] = useState(''); 195 | const [loading, setLoading] = useState(false); 196 | const [error, setError] = useState(false); 197 | const [queue, setQueue] = useState([]); 198 | 199 | const handlePrompt = (prompt: string) => { 200 | if (!loading) { 201 | setPrompt(prompt); 202 | } 203 | }; 204 | 205 | const handleSubmit = async (e) => { 206 | e.preventDefault(); 207 | 208 | if (prompt === '') { 209 | return; 210 | } 211 | 212 | const newQ = prompt.split(';'); 213 | setQueue(newQ); 214 | console.log(queue); 215 | 216 | if (!loading) { 217 | const newQueue = queue; 218 | var next = newQueue.shift(); 219 | setQueue(newQueue); 220 | console.log('queue is now', newQueue, 'running with ', next); 221 | window.electron.ipcRenderer.sendMessage('run-prompt', { 222 | prompt: next, 223 | }); 224 | } 225 | setLoading(true); 226 | setError(false); 227 | }; 228 | 229 | const handleCancel = async () => { 230 | window.electron.ipcRenderer.sendMessage('cancel-run', {}); 231 | setLoading(false); 232 | }; 233 | 234 | window.electron.ipcRenderer.on('image-load', (file) => { 235 | setLoading(false); 236 | if (queue.length > 0) { 237 | const next = queue.shift(); 238 | setQueue(queue); 239 | console.log('queue is now', queue); 240 | window.electron.ipcRenderer.sendMessage('run-prompt', { 241 | prompt: next, 242 | }); 243 | } 244 | }); 245 | 246 | window.electron.ipcRenderer.on('error', (error) => { 247 | setError(true); 248 | setLoading(false); 249 | }); 250 | 251 | return ( 252 |
253 | {error && ( 254 |

255 | Error detected! Do you have the latest MacOS? If it still doesn't 256 | work, you can email me the logs choltz@hey.com. 257 |

258 | )} 259 | {/* // PROMPT */} 260 |
261 |
262 |
263 |