diff --git a/.gitignore b/.gitignore index dd5a59ef..e6ff4765 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .vscode *.spec */build -*/dist \ No newline at end of file +*/dist +client/resources/server \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index afd32b90..44acc791 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -24,12 +24,15 @@ "@electron-forge/maker-zip": "^6.2.1", "@electron-forge/plugin-auto-unpack-natives": "^6.2.1", "@electron-forge/plugin-webpack": "^6.2.1", + "@types/chmodr": "^1.0.0", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/parser": "^5.60.0", "@vercel/webpack-asset-relocator-loader": "^1.7.3", "babel-loader": "^9.1.2", + "chmodr": "^1.2.0", + "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "electron": "25.2.0", "eslint": "^8.43.0", @@ -2295,6 +2298,15 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/chmodr": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/chmodr/-/chmodr-1.0.0.tgz", + "integrity": "sha512-S+X+Gy8V1uijitezjzXuan5vHbjllKgnC6q4VrD30HF2WRF6oIwQ/Wfjzvn5tGIIsl4VtRnBQbzqyzBo02juhw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -3935,6 +3947,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chmodr": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/chmodr/-/chmodr-1.2.0.tgz", + "integrity": "sha512-Y5uI7Iq/Az6HgJEL6pdw7THVd7jbVOTPwsmcPOBjQL8e3N+pz872kzK5QxYGEy21iRys+iHWV0UZQXDFJo1hyA==", + "dev": true + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -4288,6 +4306,114 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.0.tgz", + "integrity": "sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", diff --git a/client/package.json b/client/package.json index ed028dc7..959af268 100644 --- a/client/package.json +++ b/client/package.json @@ -27,12 +27,15 @@ "@electron-forge/maker-zip": "^6.2.1", "@electron-forge/plugin-auto-unpack-natives": "^6.2.1", "@electron-forge/plugin-webpack": "^6.2.1", + "@types/chmodr": "^1.0.0", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "@typescript-eslint/eslint-plugin": "^5.60.0", "@typescript-eslint/parser": "^5.60.0", "@vercel/webpack-asset-relocator-loader": "^1.7.3", "babel-loader": "^9.1.2", + "chmodr": "^1.2.0", + "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "electron": "25.2.0", "eslint": "^8.43.0", diff --git a/client/permissions-plugin.ts b/client/permissions-plugin.ts new file mode 100644 index 00000000..e65365a5 --- /dev/null +++ b/client/permissions-plugin.ts @@ -0,0 +1,24 @@ +import chmodr from 'chmodr' +import * as path from 'path' + +interface PluginOptions { + resourcePath: string +} + +class PermissionsPlugin { + options: PluginOptions + + constructor(options: PluginOptions) { + this.options = options + } + + apply(compiler: any) { + compiler.hooks.afterEmit.tap('PermissionsPlugin', () => { + chmodr(path.join(this.options.resourcePath), 0o755, err => { + // this fails on the first call to suppress the error + }) + }) + } +} + +export default PermissionsPlugin diff --git a/client/src/index.ts b/client/src/index.ts index 2e22f74f..a4694de5 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -1,4 +1,6 @@ import { app, BrowserWindow } from 'electron' +import { spawn } from 'child_process' +import * as path from 'path' // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on // whether you're running in development or production). @@ -24,6 +26,17 @@ const createWindow = (): void => { transparent: true, }) + // Start the executable + let pyExecutable = path.join(__dirname, '../renderer/resources/server') + console.log(`Starting ${pyExecutable}`) + let pyProcess = spawn(pyExecutable) + pyProcess.stdout.on('data', data => { + console.log(`server: ${data}`) + }) + pyProcess.stderr.on('data', data => { + console.error(`server: ${data}`) + }) + // and load the index.html of the app. mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY) diff --git a/client/webpack.plugins.ts b/client/webpack.plugins.ts index ca78adc1..0e7187f5 100644 --- a/client/webpack.plugins.ts +++ b/client/webpack.plugins.ts @@ -1,10 +1,19 @@ import type IForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin' +import * as path from 'path' +import PermissionsPlugin from './permissions-plugin' // eslint-disable-next-line @typescript-eslint/no-var-requires const ForkTsCheckerWebpackPlugin: typeof IForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin') +const CopyWebpackPlugin = require('copy-webpack-plugin') export const plugins = [ new ForkTsCheckerWebpackPlugin({ logger: 'webpack-infrastructure', }), + new CopyWebpackPlugin({ + patterns: [{ from: 'resources', to: 'resources' }], + }), + new PermissionsPlugin({ + resourcePath: '.webpack/renderer/resources/server', + }), ] diff --git a/server/build.py b/server/build.py index 3b173db7..38f79652 100644 --- a/server/build.py +++ b/server/build.py @@ -1,12 +1,12 @@ import site import os +import shutil from PyInstaller.__main__ import run as pyi_run -# Get the directory of site-packages and llama_cpp +# the llama_cpp directory is not included if not explicitly added site_packages_dir = site.getsitepackages()[0] llama_cpp_dir = os.path.join(site_packages_dir, "llama_cpp") -# Prepare the arguments for PyInstaller args = [ "server.py", "--paths", @@ -16,5 +16,7 @@ args = [ "--onefile", ] -# Generate the .spec file and run PyInstaller +# generate the .spec file and run PyInstaller pyi_run(args) + +shutil.copy2("dist/server", "../client/resources/server") diff --git a/server/server.py b/server/server.py index 1d5b4c54..9fe3fd76 100644 --- a/server/server.py +++ b/server/server.py @@ -2,7 +2,7 @@ import json import os from llama_cpp import Llama from flask import Flask, Response, stream_with_context, request -from flask_cors import CORS, cross_origin +from flask_cors import CORS app = Flask(__name__) CORS(app) # enable CORS for all routes