mirror of
https://github.com/open-webui/assistant
synced 2025-05-10 15:00:33 +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