From 563f1ac066649c3c31102c0db3a8725471bb2813 Mon Sep 17 00:00:00 2001 From: CM Date: Wed, 30 Oct 2024 02:15:57 +0000 Subject: [PATCH] Localization & UX Switch between different locales Added a menu for future page navigation Added hover text boxes for variable info --- app/background.js | 8 ++ app/background.js.map | 2 +- package-lock.json | 120 ++++++++++++++++++ package.json | 4 + src/background.js | 8 ++ src/components/Home.jsx | 57 ++++++--- src/components/HoverInfo.jsx | 42 +++++++ src/components/PageHeader.jsx | 185 ++++++++++++++++++++++++++++ src/data/locales/da/Home.json | 22 ++++ src/data/locales/da/PageHeader.json | 18 +++ src/data/locales/de/Home.json | 22 ++++ src/data/locales/de/PageHeader.json | 18 +++ src/data/locales/en/Home.json | 22 ++++ src/data/locales/en/PageHeader.json | 18 +++ src/data/locales/es/Home.json | 22 ++++ src/data/locales/es/PageHeader.json | 18 +++ src/data/locales/fr/Home.json | 22 ++++ src/data/locales/fr/PageHeader.json | 18 +++ src/data/locales/it/Home.json | 22 ++++ src/data/locales/it/PageHeader.json | 18 +++ src/data/locales/ja/Home.json | 22 ++++ src/data/locales/ja/PageHeader.json | 18 +++ src/data/locales/ko/Home.json | 22 ++++ src/data/locales/ko/PageHeader.json | 18 +++ src/data/locales/pt/Home.json | 22 ++++ src/data/locales/pt/PageHeader.json | 18 +++ src/data/locales/th/Home.json | 22 ++++ src/data/locales/th/PageHeader.json | 18 +++ src/lib/i18n.js | 57 +++++++++ src/pages/index.astro | 2 + 30 files changed, 868 insertions(+), 17 deletions(-) create mode 100644 src/components/HoverInfo.jsx create mode 100644 src/components/PageHeader.jsx create mode 100644 src/data/locales/da/Home.json create mode 100644 src/data/locales/da/PageHeader.json create mode 100644 src/data/locales/de/Home.json create mode 100644 src/data/locales/de/PageHeader.json create mode 100644 src/data/locales/en/Home.json create mode 100644 src/data/locales/en/PageHeader.json create mode 100644 src/data/locales/es/Home.json create mode 100644 src/data/locales/es/PageHeader.json create mode 100644 src/data/locales/fr/Home.json create mode 100644 src/data/locales/fr/PageHeader.json create mode 100644 src/data/locales/it/Home.json create mode 100644 src/data/locales/it/PageHeader.json create mode 100644 src/data/locales/ja/Home.json create mode 100644 src/data/locales/ja/PageHeader.json create mode 100644 src/data/locales/ko/Home.json create mode 100644 src/data/locales/ko/PageHeader.json create mode 100644 src/data/locales/pt/Home.json create mode 100644 src/data/locales/pt/PageHeader.json create mode 100644 src/data/locales/th/Home.json create mode 100644 src/data/locales/th/PageHeader.json create mode 100644 src/lib/i18n.js diff --git a/app/background.js b/app/background.js index a6cc075..2cd6e7b 100644 --- a/app/background.js +++ b/app/background.js @@ -416,6 +416,14 @@ if (currentOS === "win32" || currentOS === "linux") { createWindow(); }); + electron__WEBPACK_IMPORTED_MODULE_7__.app.on('before-quit', (event) => { + signalHandler(); + }); + + electron__WEBPACK_IMPORTED_MODULE_7__.app.on('will-quit', (event) => { + signalHandler(); + }); + electron__WEBPACK_IMPORTED_MODULE_7__.app.on("window-all-closed", () => { if ((process__WEBPACK_IMPORTED_MODULE_3___default().platform) !== "darwin") { electron__WEBPACK_IMPORTED_MODULE_7__.app.quit(); diff --git a/app/background.js.map b/app/background.js.map index 53b57a3..40f3419 100644 --- a/app/background.js.map +++ b/app/background.js.map @@ -1 +1 @@ -{"version":3,"file":"background.js","mappings":";;;;;;;;;;;;;;;;AAAmC;AACnC;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX,YAAY,iCAAiC;AAC7C,YAAY;AACZ;AACA;AACA;AACA,iBAAiB,0CAAI;AACrB,IAAI,0CAAI;AACR;;;;;;;;;;;ACvBA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;UCAA;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;WCtBA;WACA;WACA;WACA;WACA;WACA,iCAAiC,WAAW;WAC5C;WACA;;;;;WCPA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACNsC;AACd;AACJ;AACU;AACV;AACE;AACQ;AAC9B;AACkF;AAClF;AAC+D;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,gDAAS,CAAC,yCAAG;AAC9B;AACA,OAAO,oDAAa;AACpB,eAAe,gDAAS,CAAC,yCAAG;AAC5B;AACA;AACA;AACA,QAAQ,SAAS;AACjB;AACA;AACA;AACA,cAAc,YAAY;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,oDAAK,iCAAiC,aAAa;AACxE;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,IAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,mDAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,gDAAS;AACxB,KAAK;AACL;AACA,GAAG;AACH;AACA,qBAAqB,8CAAO;AAC5B;AACA;AACA,MAAM,IAAsC;AAC5C;AACA,IAAI,KAAK,EAEN;AACH;AACA,iBAAiB,wDAAc;AAC/B;AACA;AACA,GAAG;AACH;AACA,EAAE,4EAAmB;AACrB;AACA;AACA;AACA;AACA,aAAa;AACb,GAAG;AACH;AACA,aAAa,0CAAI,CAAC,gDAAS;AAC3B,sBAAsB,0CAAI;AAC1B;AACA;AACA;AACA;AACA,OAAO;AACP,KAAK;AACL;AACA;AACA;AACA;AACA,QAAQ,yCAAG;AACX,OAAO;AACP,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAE,6CAAO;AACT;AACA,4BAA4B,gDAAO;AACnC;AACA;AACA;AACA,uCAAuC,gDAAO;AAC9C;AACA,OAAO;AACP;AACA;AACA,QAAQ,2CAAK;AACb,QAAQ;AACR,kEAAkE,OAAO;AACzE;AACA,MAAM;AACN,2CAA2C,YAAY;AACvD;AACA,GAAG;AACH;AACA,EAAE,6CAAO;AACT;AACA,GAAG;AACH;AACA,EAAE,6CAAO;AACT;AACA,GAAG;AACH;AACA,EAAE,6CAAO;AACT,yBAAyB,4CAAM;AAC/B;AACA,oBAAoB,0CAA0C;AAC9D,KAAK;AACL;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA,kBAAkB,kDAAW;AAC7B;AACA;AACA,qBAAqB,yCAAG;AACxB;AACA;AACA,IAAI,yCAAG;AACP;AACA;AACA,EAAE,yCAAG;AACL;AACA,GAAG;AACH,EAAE;AACF,EAAE,yCAAG;AACL;AACA,GAAG;AACH;AACA,EAAE,yCAAG;AACL,QAAQ,yDAAgB;AACxB,MAAM,yCAAG;AACT;AACA,GAAG;AACH;AACA,EAAE,yCAAG;AACL;AACA;AACA;AACA,GAAG;AACH,C","sources":["webpack://electron-bitnet/./src/lib/applicationMenu.js","webpack://electron-bitnet/external commonjs \"electron\"","webpack://electron-bitnet/external commonjs \"express\"","webpack://electron-bitnet/external node-commonjs \"child_process\"","webpack://electron-bitnet/external node-commonjs \"fs\"","webpack://electron-bitnet/external node-commonjs \"os\"","webpack://electron-bitnet/external node-commonjs \"path\"","webpack://electron-bitnet/external node-commonjs \"process\"","webpack://electron-bitnet/external node-commonjs \"url\"","webpack://electron-bitnet/webpack/bootstrap","webpack://electron-bitnet/webpack/runtime/compat get default export","webpack://electron-bitnet/webpack/runtime/define property getters","webpack://electron-bitnet/webpack/runtime/hasOwnProperty shorthand","webpack://electron-bitnet/webpack/runtime/make namespace object","webpack://electron-bitnet/./src/background.js"],"sourcesContent":["import {app, Menu} from 'electron';\r\n\r\n/**\r\n * For configuring the electron window menu\r\n */\r\nexport function initApplicationMenu(mainWindow) {\r\n const template = [\r\n {\r\n label: 'View',\r\n submenu: [\r\n {\r\n label: 'Send to tray',\r\n click() {\r\n mainWindow.minimize();\r\n }\r\n },\r\n { label: 'Reload', role: 'reload' },\r\n { label: 'Dev tools', role: 'toggleDevTools' }\r\n ]\r\n }\r\n ];\r\n const menu = Menu.buildFromTemplate(template);\r\n Menu.setApplicationMenu(menu);\r\n}\r\n","module.exports = require(\"electron\");","module.exports = require(\"express\");","module.exports = require(\"child_process\");","module.exports = require(\"fs\");","module.exports = require(\"os\");","module.exports = require(\"path\");","module.exports = require(\"process\");","module.exports = require(\"url\");","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { spawn } from 'child_process';\r\nimport path from 'path';\r\nimport os from 'os';\r\nimport process from 'process';\r\nimport fs from 'fs';\r\nimport url from \"url\";\r\nimport express from \"express\";\r\n\r\nimport { app, BrowserWindow, Menu, Tray, ipcMain, shell, dialog } from \"electron\";\r\n\r\nimport { initApplicationMenu } from \"./lib/applicationMenu.js\";\r\n\r\nlet mainWindow = null;\r\nlet tray = null;\r\nlet inferenceProcess = null;\r\n\r\nfunction runInference(args) {\r\n let mainPath = path.join(app.getAppPath(), 'bin', 'Release', 'llama-cli.exe');\r\n\r\n if (!fs.existsSync(mainPath)) {\r\n mainPath = path.join(app.getAppPath(), 'bin', 'llama-cli');\r\n }\r\n\r\n const command = [\r\n `\"${mainPath}\"`,\r\n '-m', args.model,\r\n '-n', args.n_predict,\r\n '-t', args.threads,\r\n '-p', `\"${args.prompt}\"`,\r\n '-ngl', '0',\r\n '-c', args.ctx_size,\r\n '--temp', args.temperature,\r\n '-b', '1'\r\n ];\r\n\r\n inferenceProcess = spawn(command[0], command.slice(1), { shell: true });\r\n\r\n inferenceProcess.stdout.on('data', (data) => {\r\n const chars = data.toString();\r\n mainWindow.webContents.send('aiResponse', chars);\r\n });\r\n\r\n inferenceProcess.on('close', (code) => {\r\n mainWindow.webContents.send('aiComplete');\r\n inferenceProcess = null;\r\n });\r\n}\r\n\r\nfunction signalHandler() {\r\n if (inferenceProcess) {\r\n console.log('Terminating inference process...');\r\n try {\r\n inferenceProcess.kill('SIGKILL'); // Use SIGKILL to forcefully terminate the process\r\n inferenceProcess.stdout.removeAllListeners('data');\r\n inferenceProcess.stderr.removeAllListeners('data');\r\n inferenceProcess = null;\r\n console.log('Inference process terminated.');\r\n } catch (error) {\r\n console.error('Failed to terminate inference process:', error);\r\n }\r\n } else {\r\n console.log('No inference process to terminate.');\r\n }\r\n\r\n mainWindow.webContents.send('aiComplete');\r\n inferenceProcess = null;\r\n}\r\n\r\nconst createWindow = async () => {\r\n mainWindow = new BrowserWindow({\r\n minWidth: 480,\r\n minHeight: 695,\r\n maximizable: true,\r\n useContentSize: true,\r\n autoHideMenuBar: true,\r\n webPreferences: {\r\n nodeIntegration: false,\r\n contextIsolation: true,\r\n sandbox: true,\r\n preload: path.join(__dirname, \"preload.js\"),\r\n },\r\n icon: __dirname + \"/img/taskbar.png\",\r\n });\r\n\r\n const expressApp = express();\r\n\r\n let astroDistPath;\r\n if (process.env.NODE_ENV === \"development\") {\r\n astroDistPath = \"astroDist\";\r\n } else {\r\n astroDistPath = path.join(process.resourcesPath, \"astroDist\");\r\n }\r\n\r\n expressApp.use(express.static(astroDistPath));\r\n expressApp.listen(8080, () => {\r\n console.log(\"Express server listening on port 8080\");\r\n });\r\n\r\n initApplicationMenu(mainWindow);\r\n\r\n mainWindow.loadURL(\"http://localhost:8080/index.html\");\r\n\r\n mainWindow.webContents.setWindowOpenHandler(() => {\r\n return { action: \"deny\" };\r\n });\r\n\r\n tray = new Tray(path.join(__dirname, \"img\", \"tray.png\"));\r\n const contextMenu = Menu.buildFromTemplate([\r\n {\r\n label: \"Show App\",\r\n click: function () {\r\n mainWindow?.show();\r\n },\r\n },\r\n {\r\n label: \"Quit\",\r\n click: function () {\r\n tray = null;\r\n app.quit();\r\n },\r\n },\r\n ]);\r\n\r\n tray.setToolTip(\"Electron BitNet\");\r\n\r\n tray.on(\"right-click\", (event, bounds) => {\r\n tray?.popUpContextMenu(contextMenu);\r\n });\r\n\r\n const safeDomains = [\r\n \"https://github.com\",\r\n \"https://react.dev/\",\r\n \"https://astro.build/\",\r\n \"https://www.electronjs.org/\"\r\n ];\r\n\r\n ipcMain.on(\"openURL\", (event, arg) => {\r\n try {\r\n const parsedUrl = new url.URL(arg);\r\n const domain = parsedUrl.hostname;\r\n\r\n const isSafeDomain = safeDomains.some((safeDomain) => {\r\n const safeDomainHostname = new url.URL(safeDomain).hostname;\r\n return safeDomainHostname === domain;\r\n });\r\n\r\n if (isSafeDomain) {\r\n shell.openExternal(arg);\r\n } else {\r\n console.error(`Rejected opening URL with unsafe domain: ${domain}`);\r\n }\r\n } catch (err) {\r\n console.error(`Failed to open URL: ${err.message}`);\r\n }\r\n });\r\n\r\n ipcMain.on(\"runInference\", (event, arg) => {\r\n runInference(arg);\r\n });\r\n\r\n ipcMain.on(\"stopInference\", (event) => {\r\n signalHandler();\r\n });\r\n\r\n ipcMain.handle('openFileDialog', async () => {\r\n const result = await dialog.showOpenDialog({\r\n properties: ['openFile'],\r\n filters: [{ name: 'GGUF Files', extensions: ['gguf'] }]\r\n });\r\n return result.filePaths;\r\n });\r\n\r\n tray.on(\"click\", () => {\r\n mainWindow?.setAlwaysOnTop(true);\r\n mainWindow?.show();\r\n mainWindow?.focus();\r\n mainWindow?.setAlwaysOnTop(false);\r\n });\r\n\r\n tray.on(\"balloon-click\", () => {\r\n mainWindow?.setAlwaysOnTop(true);\r\n mainWindow?.show();\r\n mainWindow?.focus();\r\n mainWindow?.setAlwaysOnTop(false);\r\n });\r\n};\r\n\r\nconst currentOS = os.platform();\r\nif (currentOS === \"win32\" || currentOS === \"linux\") {\r\n // windows + linux setup phase\r\n const gotTheLock = app.requestSingleInstanceLock();\r\n\r\n if (!gotTheLock) {\r\n app.quit();\r\n }\r\n\r\n app.whenReady().then(() => {\r\n createWindow();\r\n });\r\n} else {\r\n app.whenReady().then(() => {\r\n createWindow();\r\n });\r\n\r\n app.on(\"window-all-closed\", () => {\r\n if (process.platform !== \"darwin\") {\r\n app.quit();\r\n }\r\n });\r\n\r\n app.on(\"activate\", () => {\r\n if (mainWindow === null) {\r\n createWindow();\r\n }\r\n });\r\n}"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"background.js","mappings":";;;;;;;;;;;;;;;;AAAmC;AACnC;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX,YAAY,iCAAiC;AAC7C,YAAY;AACZ;AACA;AACA;AACA,iBAAiB,0CAAI;AACrB,IAAI,0CAAI;AACR;;;;;;;;;;;ACvBA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;;;;;ACAA;;;;;;UCAA;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;WCtBA;WACA;WACA;WACA;WACA;WACA,iCAAiC,WAAW;WAC5C;WACA;;;;;WCPA;WACA;WACA;WACA;WACA,yCAAyC,wCAAwC;WACjF;WACA;WACA;;;;;WCPA;;;;;WCAA;WACA;WACA;WACA,uDAAuD,iBAAiB;WACxE;WACA,gDAAgD,aAAa;WAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACNsC;AACd;AACJ;AACU;AACV;AACE;AACQ;AAC9B;AACkF;AAClF;AAC+D;AAC/D;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,gDAAS,CAAC,yCAAG;AAC9B;AACA,OAAO,oDAAa;AACpB,eAAe,gDAAS,CAAC,yCAAG;AAC5B;AACA;AACA;AACA,QAAQ,SAAS;AACjB;AACA;AACA;AACA,cAAc,YAAY;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,oDAAK,iCAAiC,aAAa;AACxE;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,IAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,mDAAa;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,gDAAS;AACxB,KAAK;AACL;AACA,GAAG;AACH;AACA,qBAAqB,8CAAO;AAC5B;AACA;AACA,MAAM,IAAsC;AAC5C;AACA,IAAI,KAAK,EAEN;AACH;AACA,iBAAiB,wDAAc;AAC/B;AACA;AACA,GAAG;AACH;AACA,EAAE,4EAAmB;AACrB;AACA;AACA;AACA;AACA,aAAa;AACb,GAAG;AACH;AACA,aAAa,0CAAI,CAAC,gDAAS;AAC3B,sBAAsB,0CAAI;AAC1B;AACA;AACA;AACA;AACA,OAAO;AACP,KAAK;AACL;AACA;AACA;AACA;AACA,QAAQ,yCAAG;AACX,OAAO;AACP,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAE,6CAAO;AACT;AACA,4BAA4B,gDAAO;AACnC;AACA;AACA;AACA,uCAAuC,gDAAO;AAC9C;AACA,OAAO;AACP;AACA;AACA,QAAQ,2CAAK;AACb,QAAQ;AACR,kEAAkE,OAAO;AACzE;AACA,MAAM;AACN,2CAA2C,YAAY;AACvD;AACA,GAAG;AACH;AACA,EAAE,6CAAO;AACT;AACA,GAAG;AACH;AACA,EAAE,6CAAO;AACT;AACA,GAAG;AACH;AACA,EAAE,6CAAO;AACT,yBAAyB,4CAAM;AAC/B;AACA,oBAAoB,0CAA0C;AAC9D,KAAK;AACL;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA,kBAAkB,kDAAW;AAC7B;AACA;AACA,qBAAqB,yCAAG;AACxB;AACA;AACA,IAAI,yCAAG;AACP;AACA;AACA,EAAE,yCAAG;AACL;AACA,GAAG;AACH,EAAE;AACF,EAAE,yCAAG;AACL;AACA,GAAG;AACH;AACA,EAAE,yCAAG;AACL;AACA,GAAG;AACH;AACA,EAAE,yCAAG;AACL;AACA,GAAG;AACH;AACA,EAAE,yCAAG;AACL,QAAQ,yDAAgB;AACxB,MAAM,yCAAG;AACT;AACA,GAAG;AACH;AACA,EAAE,yCAAG;AACL;AACA;AACA;AACA,GAAG;AACH,C","sources":["webpack://electron-bitnet/./src/lib/applicationMenu.js","webpack://electron-bitnet/external commonjs \"electron\"","webpack://electron-bitnet/external commonjs \"express\"","webpack://electron-bitnet/external node-commonjs \"child_process\"","webpack://electron-bitnet/external node-commonjs \"fs\"","webpack://electron-bitnet/external node-commonjs \"os\"","webpack://electron-bitnet/external node-commonjs \"path\"","webpack://electron-bitnet/external node-commonjs \"process\"","webpack://electron-bitnet/external node-commonjs \"url\"","webpack://electron-bitnet/webpack/bootstrap","webpack://electron-bitnet/webpack/runtime/compat get default export","webpack://electron-bitnet/webpack/runtime/define property getters","webpack://electron-bitnet/webpack/runtime/hasOwnProperty shorthand","webpack://electron-bitnet/webpack/runtime/make namespace object","webpack://electron-bitnet/./src/background.js"],"sourcesContent":["import {app, Menu} from 'electron';\r\n\r\n/**\r\n * For configuring the electron window menu\r\n */\r\nexport function initApplicationMenu(mainWindow) {\r\n const template = [\r\n {\r\n label: 'View',\r\n submenu: [\r\n {\r\n label: 'Send to tray',\r\n click() {\r\n mainWindow.minimize();\r\n }\r\n },\r\n { label: 'Reload', role: 'reload' },\r\n { label: 'Dev tools', role: 'toggleDevTools' }\r\n ]\r\n }\r\n ];\r\n const menu = Menu.buildFromTemplate(template);\r\n Menu.setApplicationMenu(menu);\r\n}\r\n","module.exports = require(\"electron\");","module.exports = require(\"express\");","module.exports = require(\"child_process\");","module.exports = require(\"fs\");","module.exports = require(\"os\");","module.exports = require(\"path\");","module.exports = require(\"process\");","module.exports = require(\"url\");","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","import { spawn } from 'child_process';\r\nimport path from 'path';\r\nimport os from 'os';\r\nimport process from 'process';\r\nimport fs from 'fs';\r\nimport url from \"url\";\r\nimport express from \"express\";\r\n\r\nimport { app, BrowserWindow, Menu, Tray, ipcMain, shell, dialog } from \"electron\";\r\n\r\nimport { initApplicationMenu } from \"./lib/applicationMenu.js\";\r\n\r\nlet mainWindow = null;\r\nlet tray = null;\r\nlet inferenceProcess = null;\r\n\r\nfunction runInference(args) {\r\n let mainPath = path.join(app.getAppPath(), 'bin', 'Release', 'llama-cli.exe');\r\n\r\n if (!fs.existsSync(mainPath)) {\r\n mainPath = path.join(app.getAppPath(), 'bin', 'llama-cli');\r\n }\r\n\r\n const command = [\r\n `\"${mainPath}\"`,\r\n '-m', args.model,\r\n '-n', args.n_predict,\r\n '-t', args.threads,\r\n '-p', `\"${args.prompt}\"`,\r\n '-ngl', '0',\r\n '-c', args.ctx_size,\r\n '--temp', args.temperature,\r\n '-b', '1'\r\n ];\r\n\r\n inferenceProcess = spawn(command[0], command.slice(1), { shell: true });\r\n\r\n inferenceProcess.stdout.on('data', (data) => {\r\n const chars = data.toString();\r\n mainWindow.webContents.send('aiResponse', chars);\r\n });\r\n\r\n inferenceProcess.on('close', (code) => {\r\n mainWindow.webContents.send('aiComplete');\r\n inferenceProcess = null;\r\n });\r\n}\r\n\r\nfunction signalHandler() {\r\n if (inferenceProcess) {\r\n console.log('Terminating inference process...');\r\n try {\r\n inferenceProcess.kill('SIGKILL'); // Use SIGKILL to forcefully terminate the process\r\n inferenceProcess.stdout.removeAllListeners('data');\r\n inferenceProcess.stderr.removeAllListeners('data');\r\n inferenceProcess = null;\r\n console.log('Inference process terminated.');\r\n } catch (error) {\r\n console.error('Failed to terminate inference process:', error);\r\n }\r\n } else {\r\n console.log('No inference process to terminate.');\r\n }\r\n\r\n mainWindow.webContents.send('aiComplete');\r\n inferenceProcess = null;\r\n}\r\n\r\nconst createWindow = async () => {\r\n mainWindow = new BrowserWindow({\r\n minWidth: 480,\r\n minHeight: 695,\r\n maximizable: true,\r\n useContentSize: true,\r\n autoHideMenuBar: true,\r\n webPreferences: {\r\n nodeIntegration: false,\r\n contextIsolation: true,\r\n sandbox: true,\r\n preload: path.join(__dirname, \"preload.js\"),\r\n },\r\n icon: __dirname + \"/img/taskbar.png\",\r\n });\r\n\r\n const expressApp = express();\r\n\r\n let astroDistPath;\r\n if (process.env.NODE_ENV === \"development\") {\r\n astroDistPath = \"astroDist\";\r\n } else {\r\n astroDistPath = path.join(process.resourcesPath, \"astroDist\");\r\n }\r\n\r\n expressApp.use(express.static(astroDistPath));\r\n expressApp.listen(8080, () => {\r\n console.log(\"Express server listening on port 8080\");\r\n });\r\n\r\n initApplicationMenu(mainWindow);\r\n\r\n mainWindow.loadURL(\"http://localhost:8080/index.html\");\r\n\r\n mainWindow.webContents.setWindowOpenHandler(() => {\r\n return { action: \"deny\" };\r\n });\r\n\r\n tray = new Tray(path.join(__dirname, \"img\", \"tray.png\"));\r\n const contextMenu = Menu.buildFromTemplate([\r\n {\r\n label: \"Show App\",\r\n click: function () {\r\n mainWindow?.show();\r\n },\r\n },\r\n {\r\n label: \"Quit\",\r\n click: function () {\r\n tray = null;\r\n app.quit();\r\n },\r\n },\r\n ]);\r\n\r\n tray.setToolTip(\"Electron BitNet\");\r\n\r\n tray.on(\"right-click\", (event, bounds) => {\r\n tray?.popUpContextMenu(contextMenu);\r\n });\r\n\r\n const safeDomains = [\r\n \"https://github.com\",\r\n \"https://react.dev/\",\r\n \"https://astro.build/\",\r\n \"https://www.electronjs.org/\"\r\n ];\r\n\r\n ipcMain.on(\"openURL\", (event, arg) => {\r\n try {\r\n const parsedUrl = new url.URL(arg);\r\n const domain = parsedUrl.hostname;\r\n\r\n const isSafeDomain = safeDomains.some((safeDomain) => {\r\n const safeDomainHostname = new url.URL(safeDomain).hostname;\r\n return safeDomainHostname === domain;\r\n });\r\n\r\n if (isSafeDomain) {\r\n shell.openExternal(arg);\r\n } else {\r\n console.error(`Rejected opening URL with unsafe domain: ${domain}`);\r\n }\r\n } catch (err) {\r\n console.error(`Failed to open URL: ${err.message}`);\r\n }\r\n });\r\n\r\n ipcMain.on(\"runInference\", (event, arg) => {\r\n runInference(arg);\r\n });\r\n\r\n ipcMain.on(\"stopInference\", (event) => {\r\n signalHandler();\r\n });\r\n\r\n ipcMain.handle('openFileDialog', async () => {\r\n const result = await dialog.showOpenDialog({\r\n properties: ['openFile'],\r\n filters: [{ name: 'GGUF Files', extensions: ['gguf'] }]\r\n });\r\n return result.filePaths;\r\n });\r\n\r\n tray.on(\"click\", () => {\r\n mainWindow?.setAlwaysOnTop(true);\r\n mainWindow?.show();\r\n mainWindow?.focus();\r\n mainWindow?.setAlwaysOnTop(false);\r\n });\r\n\r\n tray.on(\"balloon-click\", () => {\r\n mainWindow?.setAlwaysOnTop(true);\r\n mainWindow?.show();\r\n mainWindow?.focus();\r\n mainWindow?.setAlwaysOnTop(false);\r\n });\r\n};\r\n\r\nconst currentOS = os.platform();\r\nif (currentOS === \"win32\" || currentOS === \"linux\") {\r\n // windows + linux setup phase\r\n const gotTheLock = app.requestSingleInstanceLock();\r\n\r\n if (!gotTheLock) {\r\n app.quit();\r\n }\r\n\r\n app.whenReady().then(() => {\r\n createWindow();\r\n });\r\n} else {\r\n app.whenReady().then(() => {\r\n createWindow();\r\n });\r\n\r\n app.on('before-quit', (event) => {\r\n signalHandler();\r\n });\r\n \r\n app.on('will-quit', (event) => {\r\n signalHandler();\r\n });\r\n\r\n app.on(\"window-all-closed\", () => {\r\n if (process.platform !== \"darwin\") {\r\n app.quit();\r\n }\r\n });\r\n\r\n app.on(\"activate\", () => {\r\n if (mainWindow === null) {\r\n createWindow();\r\n }\r\n });\r\n}"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 93658bb..101eaa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,8 @@ "@astrojs/tailwind": "^5.1.2", "@babel/runtime": "^7.25.0", "@hookform/resolvers": "^3.9.0", + "@nanostores/persistent": "^0.10.2", + "@nanostores/react": "^0.8.0", "@radix-ui/react-accordion": "^1.2.1", "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-aspect-ratio": "^1.1.0", @@ -53,11 +55,13 @@ "express": "^4.21.1", "input-otp": "^1.2.4", "lucide-react": "^0.453.0", + "nanostores": "^0.11.3", "next-themes": "^0.3.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.1", + "react-i18next": "^15.1.0", "react-resizable-panels": "^2.1.5", "react-window": "^1.8.10", "recharts": "^2.13.0", @@ -3519,6 +3523,43 @@ "node": ">= 10.0.0" } }, + "node_modules/@nanostores/persistent": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@nanostores/persistent/-/persistent-0.10.2.tgz", + "integrity": "sha512-BEndnLhRC+yP7gXTESepBbSj8XNl8OXK9hu4xAgKC7MWJHKXnEqJMqY47LUyHxK6vYgFnisyHmqq+vq8AUFyIg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0" + } + }, + "node_modules/@nanostores/react": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@nanostores/react/-/react-0.8.0.tgz", + "integrity": "sha512-MhbVB7NQLboq/Z9fRTDen9zib/YCffe6mn+3Xg5MOYByMX5Xx98SOZjk/Nd3yvOd/g7GjlQqwXj0KF2lPb6CEQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0", + "react": ">=18.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -11067,6 +11108,15 @@ "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", "license": "MIT" }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -11151,6 +11201,30 @@ "ms": "^2.0.0" } }, + "node_modules/i18next": { + "version": "23.16.4", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.4.tgz", + "integrity": "sha512-9NIYBVy9cs4wIqzurf7nLXPyf3R78xYbxExVqHLK9od3038rjpyOEzW+XB130kZ1N4PZ9inTtJ471CRJ4Ituyg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -13565,6 +13639,21 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nanostores": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.11.3.tgz", + "integrity": "sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -14633,6 +14722,28 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-i18next": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.1.0.tgz", + "integrity": "sha512-zj3nJynMnZsy2gPZiOTC7XctCY5eQGqT3tcKMmfJWC9FMvgd+960w/adq61j8iPzpwmsXejqID9qC3Mqu1Xu2Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -17359,6 +17470,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", diff --git a/package.json b/package.json index e9b3491..a38a0d8 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ "@astrojs/tailwind": "^5.1.2", "@babel/runtime": "^7.25.0", "@hookform/resolvers": "^3.9.0", + "@nanostores/persistent": "^0.10.2", + "@nanostores/react": "^0.8.0", "@radix-ui/react-accordion": "^1.2.1", "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-aspect-ratio": "^1.1.0", @@ -91,11 +93,13 @@ "express": "^4.21.1", "input-otp": "^1.2.4", "lucide-react": "^0.453.0", + "nanostores": "^0.11.3", "next-themes": "^0.3.0", "react": "^18.3.1", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.1", + "react-i18next": "^15.1.0", "react-resizable-panels": "^2.1.5", "react-window": "^1.8.10", "recharts": "^2.13.0", diff --git a/src/background.js b/src/background.js index c5e385d..512ec9a 100644 --- a/src/background.js +++ b/src/background.js @@ -202,6 +202,14 @@ if (currentOS === "win32" || currentOS === "linux") { createWindow(); }); + app.on('before-quit', (event) => { + signalHandler(); + }); + + app.on('will-quit', (event) => { + signalHandler(); + }); + app.on("window-all-closed", () => { if (process.platform !== "darwin") { app.quit(); diff --git a/src/components/Home.jsx b/src/components/Home.jsx index ce76e01..2dcdf97 100644 --- a/src/components/Home.jsx +++ b/src/components/Home.jsx @@ -1,5 +1,7 @@ import React, { useState, useEffect } from "react"; import { UploadIcon } from "@radix-ui/react-icons"; +import { useTranslation } from "react-i18next"; +import { i18n as i18nInstance, locale } from "@/lib/i18n.js"; import { Card, @@ -16,8 +18,11 @@ import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import ExternalLink from "@/components/ExternalLink.jsx"; +import HoverInfo from "@/components/HoverInfo.jsx"; export default function Home(properties) { + const { t, i18n } = useTranslation(locale.get(), { i18n: i18nInstance }); + const [tokenQuantity, setTokenQuantity] = useState(20); const [model, setModel] = useState(""); // proven compatible: ggml-model-i2_s.gguf const [threads, setThreads] = useState(2); @@ -59,16 +64,19 @@ export default function Home(properties) {
- Electron BitNet Inference Tool + {t("Home:title")} - Microsoft released bitnet.cpp as their official inference framework for 1-bit LLMs (e.g., BitNet b1.58) which runs on CPUs; enjoy! + {t("Home:description")}
- Command Options - + {t("Home:commandOptions")} + - +
@@ -90,7 +101,10 @@ export default function Home(properties) {
- + - + { @@ -116,9 +134,13 @@ export default function Home(properties) { } }} /> - + { @@ -129,7 +151,10 @@ export default function Home(properties) { } }} /> - +