mirror of
				https://github.com/open-webui/assistant
				synced 2025-06-26 18:15:57 +00:00 
			
		
		
		
	init
This commit is contained in:
		
						commit
						f3096270a3
					
				
							
								
								
									
										16
									
								
								.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								.eslintrc.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| { | ||||
|   "env": { | ||||
|     "browser": true, | ||||
|     "es6": true, | ||||
|     "node": true | ||||
|   }, | ||||
|   "extends": [ | ||||
|     "eslint:recommended", | ||||
|     "plugin:@typescript-eslint/eslint-recommended", | ||||
|     "plugin:@typescript-eslint/recommended", | ||||
|     "plugin:import/recommended", | ||||
|     "plugin:import/electron", | ||||
|     "plugin:import/typescript" | ||||
|   ], | ||||
|   "parser": "@typescript-eslint/parser" | ||||
| } | ||||
							
								
								
									
										97
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | ||||
| .vite | ||||
| node_modules | ||||
| out | ||||
| _test | ||||
| 
 | ||||
| # Logs | ||||
| logs | ||||
| *.log | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
| lerna-debug.log* | ||||
| 
 | ||||
| # Diagnostic reports (https://nodejs.org/api/report.html) | ||||
| report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | ||||
| 
 | ||||
| # Runtime data | ||||
| pids | ||||
| *.pid | ||||
| *.seed | ||||
| *.pid.lock | ||||
| .DS_Store | ||||
| 
 | ||||
| # Directory for instrumented libs generated by jscoverage/JSCover | ||||
| lib-cov | ||||
| 
 | ||||
| # Coverage directory used by tools like istanbul | ||||
| coverage | ||||
| *.lcov | ||||
| 
 | ||||
| # nyc test coverage | ||||
| .nyc_output | ||||
| 
 | ||||
| # node-waf configuration | ||||
| .lock-wscript | ||||
| 
 | ||||
| # Compiled binary addons (https://nodejs.org/api/addons.html) | ||||
| build/Release | ||||
| 
 | ||||
| # Dependency directories | ||||
| node_modules/ | ||||
| jspm_packages/ | ||||
| 
 | ||||
| # TypeScript v1 declaration files | ||||
| typings/ | ||||
| 
 | ||||
| # TypeScript cache | ||||
| *.tsbuildinfo | ||||
| 
 | ||||
| # Optional npm cache directory | ||||
| .npm | ||||
| 
 | ||||
| # Optional eslint cache | ||||
| .eslintcache | ||||
| 
 | ||||
| # Optional REPL history | ||||
| .node_repl_history | ||||
| 
 | ||||
| # Output of 'npm pack' | ||||
| *.tgz | ||||
| 
 | ||||
| # Yarn Integrity file | ||||
| .yarn-integrity | ||||
| 
 | ||||
| # dotenv environment variables file | ||||
| .env | ||||
| .env.test | ||||
| 
 | ||||
| # parcel-bundler cache (https://parceljs.org/) | ||||
| .cache | ||||
| 
 | ||||
| # next.js build output | ||||
| .next | ||||
| 
 | ||||
| # nuxt.js build output | ||||
| .nuxt | ||||
| 
 | ||||
| # vuepress build output | ||||
| .vuepress/dist | ||||
| 
 | ||||
| # Serverless directories | ||||
| .serverless/ | ||||
| 
 | ||||
| # FuseBox cache | ||||
| .fusebox/ | ||||
| 
 | ||||
| # DynamoDB Local files | ||||
| .dynamodb/ | ||||
| 
 | ||||
| # Webpack | ||||
| .webpack/ | ||||
| 
 | ||||
| # Vite | ||||
| .vite/ | ||||
| 
 | ||||
| # Electron-Forge | ||||
| out/ | ||||
							
								
								
									
										57
									
								
								forge.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								forge.config.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | ||||
| import type { ForgeConfig } from "@electron-forge/shared-types"; | ||||
| import { MakerSquirrel } from "@electron-forge/maker-squirrel"; | ||||
| import { MakerZIP } from "@electron-forge/maker-zip"; | ||||
| import { MakerDeb } from "@electron-forge/maker-deb"; | ||||
| import { MakerRpm } from "@electron-forge/maker-rpm"; | ||||
| import { VitePlugin } from "@electron-forge/plugin-vite"; | ||||
| import { FusesPlugin } from "@electron-forge/plugin-fuses"; | ||||
| import { FuseV1Options, FuseVersion } from "@electron/fuses"; | ||||
| 
 | ||||
| const config: ForgeConfig = { | ||||
|   packagerConfig: { | ||||
|     asar: true, | ||||
|   }, | ||||
|   rebuildConfig: {}, | ||||
|   makers: [ | ||||
|     new MakerSquirrel({}), | ||||
|     new MakerZIP({}, ["darwin"]), | ||||
|     new MakerRpm({}), | ||||
|     new MakerDeb({}), | ||||
|   ], | ||||
|   plugins: [ | ||||
|     new VitePlugin({ | ||||
|       // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
 | ||||
|       // If you are familiar with Vite configuration, it will look really familiar.
 | ||||
|       build: [ | ||||
|         { | ||||
|           // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
 | ||||
|           entry: "src/main.ts", | ||||
|           config: "vite.main.config.ts", | ||||
|         }, | ||||
|         { | ||||
|           entry: "src/preload.ts", | ||||
|           config: "vite.preload.config.ts", | ||||
|         }, | ||||
|       ], | ||||
|       renderer: [ | ||||
|         { | ||||
|           name: "main_window", | ||||
|           config: "vite.renderer.config.ts", | ||||
|         }, | ||||
|       ], | ||||
|     }), | ||||
|     // Fuses are used to enable/disable various Electron functionality
 | ||||
|     // at package time, before code signing the application
 | ||||
|     new FusesPlugin({ | ||||
|       version: FuseVersion.V1, | ||||
|       [FuseV1Options.RunAsNode]: false, | ||||
|       [FuseV1Options.EnableCookieEncryption]: true, | ||||
|       [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, | ||||
|       [FuseV1Options.EnableNodeCliInspectArguments]: false, | ||||
|       [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, | ||||
|       [FuseV1Options.OnlyLoadAppFromAsar]: true, | ||||
|     }), | ||||
|   ], | ||||
| }; | ||||
| 
 | ||||
| export default config; | ||||
							
								
								
									
										31
									
								
								forge.env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								forge.env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| export {}; // Make this a module
 | ||||
| 
 | ||||
| declare global { | ||||
|   // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite
 | ||||
|   // plugin that tells the Electron app where to look for the Vite-bundled app code (depending on
 | ||||
|   // whether you're running in development or production).
 | ||||
|   const MAIN_WINDOW_VITE_DEV_SERVER_URL: string; | ||||
|   const MAIN_WINDOW_VITE_NAME: string; | ||||
| 
 | ||||
|   namespace NodeJS { | ||||
|     interface Process { | ||||
|       // Used for hot reload after preload scripts.
 | ||||
|       viteDevServers: Record<string, import('vite').ViteDevServer>; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   type VitePluginConfig = ConstructorParameters<typeof import('@electron-forge/plugin-vite').VitePlugin>[0]; | ||||
| 
 | ||||
|   interface VitePluginRuntimeKeys { | ||||
|     VITE_DEV_SERVER_URL: `${string}_VITE_DEV_SERVER_URL`; | ||||
|     VITE_NAME: `${string}_VITE_NAME`; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| declare module 'vite' { | ||||
|   interface ConfigEnv<K extends keyof VitePluginConfig = keyof VitePluginConfig> { | ||||
|     root: string; | ||||
|     forgeConfig: VitePluginConfig; | ||||
|     forgeConfigSelf: VitePluginConfig[K][number]; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										11
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <meta charset="UTF-8" /> | ||||
|     <title>Hello World!</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="app"></div> | ||||
|     <script type="module" src="/src/renderer.ts"></script> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										10735
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10735
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										49
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| { | ||||
|   "name": "open-webui-assistant", | ||||
|   "productName": "open-webui-assistant", | ||||
|   "version": "1.0.0", | ||||
|   "description": "My Electron application description", | ||||
|   "main": ".vite/build/main.js", | ||||
|   "scripts": { | ||||
|     "start": "electron-forge start", | ||||
|     "package": "electron-forge package", | ||||
|     "make": "electron-forge make", | ||||
|     "publish": "electron-forge publish", | ||||
|     "lint": "eslint --ext .ts,.tsx ." | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@electron-forge/cli": "^7.3.0", | ||||
|     "@electron-forge/maker-deb": "^7.3.0", | ||||
|     "@electron-forge/maker-rpm": "^7.3.0", | ||||
|     "@electron-forge/maker-squirrel": "^7.3.0", | ||||
|     "@electron-forge/maker-zip": "^7.3.0", | ||||
|     "@electron-forge/plugin-auto-unpack-natives": "^7.3.0", | ||||
|     "@electron-forge/plugin-fuses": "^7.3.0", | ||||
|     "@electron-forge/plugin-vite": "^7.3.0", | ||||
|     "@electron/fuses": "^1.7.0", | ||||
|     "@typescript-eslint/eslint-plugin": "^5.62.0", | ||||
|     "@typescript-eslint/parser": "^5.62.0", | ||||
|     "@vitejs/plugin-vue": "^5.0.4", | ||||
|     "autoprefixer": "^10.4.18", | ||||
|     "electron": "29.1.1", | ||||
|     "eslint": "^8.57.0", | ||||
|     "eslint-plugin-import": "^2.29.1", | ||||
|     "postcss": "^8.4.35", | ||||
|     "tailwindcss": "^3.4.1", | ||||
|     "ts-node": "^10.9.2", | ||||
|     "typescript": "~4.5.4", | ||||
|     "vite": "^5.1.6" | ||||
|   }, | ||||
|   "keywords": [], | ||||
|   "author": { | ||||
|     "name": "Timothy J. Baek", | ||||
|     "email": "timothyjrbeck@gmail.com" | ||||
|   }, | ||||
|   "license": "MIT", | ||||
|   "dependencies": { | ||||
|     "@electron/rebuild": "^3.6.0", | ||||
|     "@nut-tree/nut-js": "^3.1.2", | ||||
|     "electron-squirrel-startup": "^1.0.0", | ||||
|     "vue": "^3.4.21" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										6
									
								
								postcss.config.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								postcss.config.cjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| module.exports = { | ||||
|   plugins: { | ||||
|     tailwindcss: {}, | ||||
|     autoprefixer: {}, | ||||
|   }, | ||||
| }; | ||||
							
								
								
									
										74
									
								
								src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/App.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | ||||
| <script setup> | ||||
| 
 | ||||
| import { ref, onBeforeMount } from 'vue'; | ||||
| 
 | ||||
| 
 | ||||
| const url = ref('http://localhost:3000') | ||||
| const token = ref('your_jwt') | ||||
| 
 | ||||
| const submitHandler = () => { | ||||
|     console.log(url.value, token.value) | ||||
| 
 | ||||
| 
 | ||||
|     if (url.value.endsWith('/')) { | ||||
|         url.value = url.value.substring(0, url.value.length - 1); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     window.electron.saveConfig({ | ||||
|         url: url.value, | ||||
|         token: token.value | ||||
|     }) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| onBeforeMount(async () => { | ||||
|     console.log('hi') | ||||
|     const res = await window.electron.loadConfig() | ||||
| 
 | ||||
|     if (res) { | ||||
|         url.value = res.url | ||||
|         token.value = res.token | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| }) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <div class=" h-screen w-screen px-3 flex justify-center"> | ||||
|         <div class=" my-auto w-full flex flex-col gap-2"> | ||||
|             <div class="flex justify-between items-center"> | ||||
|                 <div class=" text-sm font-semibold">Open WebUI Assistant</div> | ||||
| 
 | ||||
|                 <button class="bg-neutral-700 hover:bg-neutral-800 transition text-white text-xs px-3 py-1 rounded-lg" | ||||
|                     @click="submitHandler">Save</button> | ||||
|             </div> | ||||
|             <div class="flex flex-col gap-1.5"> | ||||
| 
 | ||||
| 
 | ||||
|                 <input v-model="url" | ||||
|                     class=" w-full bg-gray-100 hover:bg-gray-200 transition rounded-lg py-1 px-2 text-sm outline-none" | ||||
|                     placeholder="Open WebUI URL" /> | ||||
|                 <input v-model="token" | ||||
|                     class=" w-full bg-gray-100 hover:bg-gray-200 transition rounded-lg py-1 px-2 text-sm outline-none" | ||||
|                     placeholder="Open WebUI Token" /> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|             </div> | ||||
|         </div> | ||||
| 
 | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
| </template> | ||||
| 
 | ||||
| 
 | ||||
| <style> | ||||
| @import './index.css'; | ||||
| </style> | ||||
							
								
								
									
										3
									
								
								src/index.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/index.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| @tailwind base; | ||||
| @tailwind components; | ||||
| @tailwind utilities; | ||||
							
								
								
									
										202
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/main.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,202 @@ | ||||
| import { | ||||
|   app, | ||||
|   BrowserWindow, | ||||
|   globalShortcut, | ||||
|   clipboard, | ||||
|   ipcMain, | ||||
| } from "electron"; | ||||
| import path from "path"; | ||||
| 
 | ||||
| import { keyboard, Key } from "@nut-tree/nut-js"; | ||||
| import { splitStream } from "./utils"; | ||||
| 
 | ||||
| keyboard.config.autoDelayMs = 0; | ||||
| 
 | ||||
| let config = { | ||||
|   url: "", | ||||
|   token: "", | ||||
| }; | ||||
| 
 | ||||
| // Handle creating/removing shortcuts on Windows when installing/uninstalling.
 | ||||
| if (require("electron-squirrel-startup")) { | ||||
|   app.quit(); | ||||
| } | ||||
| 
 | ||||
| const createWindow = () => { | ||||
|   // Create the browser window.
 | ||||
|   const mainWindow = new BrowserWindow({ | ||||
|     width: 300, | ||||
|     height: 150, | ||||
|     webPreferences: { | ||||
|       preload: path.join(__dirname, "preload.js"), | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   // and load the index.html of the app.
 | ||||
|   if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { | ||||
|     mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); | ||||
|   } else { | ||||
|     mainWindow.loadFile( | ||||
|       path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   // Open the DevTools.
 | ||||
|   mainWindow.webContents.openDevTools(); | ||||
| }; | ||||
| 
 | ||||
| const updateConfig = async (_config) => { | ||||
|   config = { ...config, ..._config }; | ||||
|   return config; | ||||
| }; | ||||
| 
 | ||||
| const typeWord = async (word: string) => { | ||||
|   for (let i = 0; i < word.length; i++) { | ||||
|     if (word[i] === "\n") { | ||||
|       await keyboard.type(Key.Return); | ||||
|     } else { | ||||
|       await keyboard.type(word[i]); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const generateResponse = async (prompt: string) => { | ||||
|   console.log("generateResponse"); | ||||
|   const res = await fetch(`${config.url}/ollama/api/chat`, { | ||||
|     method: "POST", | ||||
|     headers: { | ||||
|       Accept: "application/json", | ||||
|       "Content-Type": "application/json", | ||||
|       Authorization: `Bearer ${config.token}`, | ||||
|     }, | ||||
|     body: JSON.stringify({ | ||||
|       model: "mistral:latest", | ||||
|       messages: [ | ||||
|         { | ||||
|           role: "user", | ||||
|           content: prompt, | ||||
|         }, | ||||
|       ], | ||||
|       stream: true, | ||||
|     }), | ||||
|   }).catch((err) => { | ||||
|     console.log(err); | ||||
|     return null; | ||||
|   }); | ||||
| 
 | ||||
|   if (res && res.ok) { | ||||
|     const reader = res.body | ||||
|       .pipeThrough(new TextDecoderStream()) | ||||
|       .pipeThrough(splitStream("\n")) | ||||
|       .getReader(); | ||||
| 
 | ||||
|     while (true) { | ||||
|       const { value, done } = await reader.read(); | ||||
|       if (done) { | ||||
|         break; | ||||
|       } | ||||
| 
 | ||||
|       try { | ||||
|         let lines = value.split("\n"); | ||||
| 
 | ||||
|         for (const line of lines) { | ||||
|           if (line !== "") { | ||||
|             console.log(line); | ||||
|             let data = JSON.parse(line); | ||||
| 
 | ||||
|             if ("detail" in data) { | ||||
|               throw data; | ||||
|             } | ||||
| 
 | ||||
|             if ("id" in data) { | ||||
|               console.log(data); | ||||
|             } else { | ||||
|               if (data.done == false) { | ||||
|                 await typeWord(data.message.content); | ||||
|               } | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } catch (error) { | ||||
|         console.log(error); | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| function sleep(ms) { | ||||
|   return new Promise((resolve) => { | ||||
|     setTimeout(resolve, ms); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| const shortcutHandler = async () => { | ||||
|   console.log("shortcutHandler"); | ||||
|   keyboard.config.autoDelayMs = 10; | ||||
| 
 | ||||
|   let i = 0; | ||||
|   while (i !== 5) { | ||||
|     if (process.platform !== "darwin") { | ||||
|       await keyboard.type(Key.LeftControl, Key.C); | ||||
|     } else { | ||||
|       await keyboard.type(Key.LeftSuper, Key.C); | ||||
|     } | ||||
|     i++; | ||||
|   } | ||||
| 
 | ||||
|   await sleep(100); | ||||
| 
 | ||||
|   const prompt = await clipboard.readText(); | ||||
|   console.log(prompt); | ||||
| 
 | ||||
|   if (config.url !== "" && config.token !== "") { | ||||
|     keyboard.config.autoDelayMs = 0; | ||||
| 
 | ||||
|     await generateResponse(prompt); | ||||
|   } else { | ||||
|     keyboard.config.autoDelayMs = 100; | ||||
| 
 | ||||
|     console.log(config); | ||||
|     await typeWord("Open WebUI URL/Token Required!"); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // This method will be called when Electron has finished
 | ||||
| // initialization and is ready to create browser windows.
 | ||||
| // Some APIs can only be used after this event occurs.
 | ||||
| app | ||||
|   .whenReady() | ||||
|   .then(() => { | ||||
|     ipcMain.handle("load-config", (event, arg) => { | ||||
|       return config; | ||||
|     }); | ||||
| 
 | ||||
|     ipcMain.on("save-config", (event, data) => { | ||||
|       console.log(data); | ||||
|       updateConfig(data); | ||||
|     }); | ||||
| 
 | ||||
|     globalShortcut.register("Alt+CommandOrControl+O", shortcutHandler); | ||||
|   }) | ||||
|   .then(createWindow); | ||||
| 
 | ||||
| // Quit when all windows are closed, except on macOS. There, it's common
 | ||||
| // for applications and their menu bar to stay active until the user quits
 | ||||
| // explicitly with Cmd + Q.
 | ||||
| app.on("window-all-closed", () => { | ||||
|   if (process.platform !== "darwin") { | ||||
|     app.quit(); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| app.on("activate", () => { | ||||
|   // On OS X it's common to re-create a window in the app when the
 | ||||
|   // dock icon is clicked and there are no other windows open.
 | ||||
|   if (BrowserWindow.getAllWindows().length === 0) { | ||||
|     createWindow(); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| // In this file you can include the rest of your app's specific main process
 | ||||
| // code. You can also put them in separate files and import them here.
 | ||||
							
								
								
									
										13
									
								
								src/preload.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/preload.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | ||||
| // See the Electron documentation for details on how to use preload scripts:
 | ||||
| // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
 | ||||
| 
 | ||||
| const { contextBridge, ipcRenderer } = require("electron"); | ||||
| 
 | ||||
| contextBridge.exposeInMainWorld("electron", { | ||||
|   on(event, callback) { | ||||
|     ipcRenderer.on(event, callback); | ||||
|   }, | ||||
| 
 | ||||
|   loadConfig: () => ipcRenderer.invoke("load-config"), | ||||
|   saveConfig: (data) => ipcRenderer.send("save-config", data), | ||||
| }); | ||||
							
								
								
									
										4
									
								
								src/renderer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/renderer.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| import { createApp } from "vue"; | ||||
| import App from "./App.vue"; | ||||
| 
 | ||||
| createApp(App).mount("#app"); | ||||
							
								
								
									
										14
									
								
								src/utils/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/utils/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| export const splitStream = (splitOn) => { | ||||
|   let buffer = ""; | ||||
|   return new TransformStream({ | ||||
|     transform(chunk, controller) { | ||||
|       buffer += chunk; | ||||
|       const parts = buffer.split(splitOn); | ||||
|       parts.slice(0, -1).forEach((part) => controller.enqueue(part)); | ||||
|       buffer = parts[parts.length - 1]; | ||||
|     }, | ||||
|     flush(controller) { | ||||
|       if (buffer) controller.enqueue(buffer); | ||||
|     }, | ||||
|   }); | ||||
| }; | ||||
							
								
								
									
										8
									
								
								tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| /** @type {import('tailwindcss').Config} */ | ||||
| module.exports = { | ||||
|   content: ["./src/**/*.{html,js,vue}"], | ||||
|   theme: { | ||||
|     extend: {}, | ||||
|   }, | ||||
|   plugins: [], | ||||
| }; | ||||
							
								
								
									
										15
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "target": "ESNext", | ||||
|     "module": "commonjs", | ||||
|     "allowJs": true, | ||||
|     "skipLibCheck": true, | ||||
|     "esModuleInterop": true, | ||||
|     "noImplicitAny": true, | ||||
|     "sourceMap": true, | ||||
|     "baseUrl": ".", | ||||
|     "outDir": "dist", | ||||
|     "moduleResolution": "node", | ||||
|     "resolveJsonModule": true | ||||
|   } | ||||
| } | ||||
							
								
								
									
										93
									
								
								vite.base.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								vite.base.config.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| import { builtinModules } from 'node:module'; | ||||
| import type { AddressInfo } from 'node:net'; | ||||
| import type { ConfigEnv, Plugin, UserConfig } from 'vite'; | ||||
| import pkg from './package.json'; | ||||
| 
 | ||||
| export const builtins = ['electron', ...builtinModules.map((m) => [m, `node:${m}`]).flat()]; | ||||
| 
 | ||||
| export const external = [...builtins, ...Object.keys('dependencies' in pkg ? (pkg.dependencies as Record<string, unknown>) : {})]; | ||||
| 
 | ||||
| export function getBuildConfig(env: ConfigEnv<'build'>): UserConfig { | ||||
|   const { root, mode, command } = env; | ||||
| 
 | ||||
|   return { | ||||
|     root, | ||||
|     mode, | ||||
|     build: { | ||||
|       // Prevent multiple builds from interfering with each other.
 | ||||
|       emptyOutDir: false, | ||||
|       // 🚧 Multiple builds may conflict.
 | ||||
|       outDir: '.vite/build', | ||||
|       watch: command === 'serve' ? {} : null, | ||||
|       minify: command === 'build', | ||||
|     }, | ||||
|     clearScreen: false, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function getDefineKeys(names: string[]) { | ||||
|   const define: { [name: string]: VitePluginRuntimeKeys } = {}; | ||||
| 
 | ||||
|   return names.reduce((acc, name) => { | ||||
|     const NAME = name.toUpperCase(); | ||||
|     const keys: VitePluginRuntimeKeys = { | ||||
|       VITE_DEV_SERVER_URL: `${NAME}_VITE_DEV_SERVER_URL`, | ||||
|       VITE_NAME: `${NAME}_VITE_NAME`, | ||||
|     }; | ||||
| 
 | ||||
|     return { ...acc, [name]: keys }; | ||||
|   }, define); | ||||
| } | ||||
| 
 | ||||
| export function getBuildDefine(env: ConfigEnv<'build'>) { | ||||
|   const { command, forgeConfig } = env; | ||||
|   const names = forgeConfig.renderer.filter(({ name }) => name != null).map(({ name }) => name!); | ||||
|   const defineKeys = getDefineKeys(names); | ||||
|   const define = Object.entries(defineKeys).reduce((acc, [name, keys]) => { | ||||
|     const { VITE_DEV_SERVER_URL, VITE_NAME } = keys; | ||||
|     const def = { | ||||
|       [VITE_DEV_SERVER_URL]: command === 'serve' ? JSON.stringify(process.env[VITE_DEV_SERVER_URL]) : undefined, | ||||
|       [VITE_NAME]: JSON.stringify(name), | ||||
|     }; | ||||
|     return { ...acc, ...def }; | ||||
|   }, {} as Record<string, any>); | ||||
| 
 | ||||
|   return define; | ||||
| } | ||||
| 
 | ||||
| export function pluginExposeRenderer(name: string): Plugin { | ||||
|   const { VITE_DEV_SERVER_URL } = getDefineKeys([name])[name]; | ||||
| 
 | ||||
|   return { | ||||
|     name: '@electron-forge/plugin-vite:expose-renderer', | ||||
|     configureServer(server) { | ||||
|       process.viteDevServers ??= {}; | ||||
|       // Expose server for preload scripts hot reload.
 | ||||
|       process.viteDevServers[name] = server; | ||||
| 
 | ||||
|       server.httpServer?.once('listening', () => { | ||||
|         const addressInfo = server.httpServer!.address() as AddressInfo; | ||||
|         // Expose env constant for main process use.
 | ||||
|         process.env[VITE_DEV_SERVER_URL] = `http://localhost:${addressInfo?.port}`; | ||||
|       }); | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function pluginHotRestart(command: 'reload' | 'restart'): Plugin { | ||||
|   return { | ||||
|     name: '@electron-forge/plugin-vite:hot-restart', | ||||
|     closeBundle() { | ||||
|       if (command === 'reload') { | ||||
|         for (const server of Object.values(process.viteDevServers)) { | ||||
|           // Preload scripts hot reload.
 | ||||
|           server.ws.send({ type: 'full-reload' }); | ||||
|         } | ||||
|       } else { | ||||
|         // Main process hot restart.
 | ||||
|         // https://github.com/electron/forge/blob/v7.2.0/packages/api/core/src/api/start.ts#L216-L223
 | ||||
|         process.stdin.emit('data', 'rs'); | ||||
|       } | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										30
									
								
								vite.main.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vite.main.config.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| import type { ConfigEnv, UserConfig } from 'vite'; | ||||
| import { defineConfig, mergeConfig } from 'vite'; | ||||
| import { getBuildConfig, getBuildDefine, external, pluginHotRestart } from './vite.base.config'; | ||||
| 
 | ||||
| // https://vitejs.dev/config
 | ||||
| export default defineConfig((env) => { | ||||
|   const forgeEnv = env as ConfigEnv<'build'>; | ||||
|   const { forgeConfigSelf } = forgeEnv; | ||||
|   const define = getBuildDefine(forgeEnv); | ||||
|   const config: UserConfig = { | ||||
|     build: { | ||||
|       lib: { | ||||
|         entry: forgeConfigSelf.entry!, | ||||
|         fileName: () => '[name].js', | ||||
|         formats: ['cjs'], | ||||
|       }, | ||||
|       rollupOptions: { | ||||
|         external, | ||||
|       }, | ||||
|     }, | ||||
|     plugins: [pluginHotRestart('restart')], | ||||
|     define, | ||||
|     resolve: { | ||||
|       // Load the Node.js entry.
 | ||||
|       mainFields: ['module', 'jsnext:main', 'jsnext'], | ||||
|     }, | ||||
|   }; | ||||
| 
 | ||||
|   return mergeConfig(getBuildConfig(forgeEnv), config); | ||||
| }); | ||||
							
								
								
									
										29
									
								
								vite.preload.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vite.preload.config.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| import type { ConfigEnv, UserConfig } from 'vite'; | ||||
| import { defineConfig, mergeConfig } from 'vite'; | ||||
| import { getBuildConfig, external, pluginHotRestart } from './vite.base.config'; | ||||
| 
 | ||||
| // https://vitejs.dev/config
 | ||||
| export default defineConfig((env) => { | ||||
|   const forgeEnv = env as ConfigEnv<'build'>; | ||||
|   const { forgeConfigSelf } = forgeEnv; | ||||
|   const config: UserConfig = { | ||||
|     build: { | ||||
|       rollupOptions: { | ||||
|         external, | ||||
|         // Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`.
 | ||||
|         input: forgeConfigSelf.entry!, | ||||
|         output: { | ||||
|           format: 'cjs', | ||||
|           // It should not be split chunks.
 | ||||
|           inlineDynamicImports: true, | ||||
|           entryFileNames: '[name].js', | ||||
|           chunkFileNames: '[name].js', | ||||
|           assetFileNames: '[name].[ext]', | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     plugins: [pluginHotRestart('reload')], | ||||
|   }; | ||||
| 
 | ||||
|   return mergeConfig(getBuildConfig(forgeEnv), config); | ||||
| }); | ||||
							
								
								
									
										25
									
								
								vite.renderer.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vite.renderer.config.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| import type { ConfigEnv, UserConfig } from "vite"; | ||||
| import { defineConfig } from "vite"; | ||||
| import vue from "@vitejs/plugin-vue"; | ||||
| import { pluginExposeRenderer } from "./vite.base.config"; | ||||
| 
 | ||||
| // https://vitejs.dev/config
 | ||||
| export default defineConfig((env) => { | ||||
|   const forgeEnv = env as ConfigEnv<"renderer">; | ||||
|   const { root, mode, forgeConfigSelf } = forgeEnv; | ||||
|   const name = forgeConfigSelf.name ?? ""; | ||||
| 
 | ||||
|   return { | ||||
|     root, | ||||
|     mode, | ||||
|     base: "./", | ||||
|     build: { | ||||
|       outDir: `.vite/renderer/${name}`, | ||||
|     }, | ||||
|     plugins: [pluginExposeRenderer(name), vue()], | ||||
|     resolve: { | ||||
|       preserveSymlinks: true, | ||||
|     }, | ||||
|     clearScreen: false, | ||||
|   } as UserConfig; | ||||
| }); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user