Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

efactor app directories and enforce ts strict mode #201

Merged
merged 55 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
c91e6b8
refactor: move Electron app to main directory and enforce ts strict mode
louis-jan Sep 25, 2023
a473e8e
chore: add pre-install plugins
louis-jan Sep 25, 2023
2267629
remove duplicated initModel function
Sep 25, 2023
4a9e345
chore: correct module path
louis-jan Sep 25, 2023
ff7fd8a
fix: dynamic import does not work with ts
louis-jan Sep 25, 2023
e22bc05
chore: web should be able to run on target host browser
louis-jan Sep 25, 2023
bc16c05
fix: history panel, should display conversations rather just blank state
louis-jan Sep 25, 2023
d0510dd
chore: init default model
louis-jan Sep 25, 2023
fc20cae
chore: pluggin in ts
louis-jan Sep 25, 2023
c5e8a4d
fix: pre-pack model management
louis-jan Sep 25, 2023
a296be1
fix: compiled core should not include plugins
louis-jan Sep 25, 2023
444b5d9
chore: refactor - invoke plugin function
louis-jan Sep 25, 2023
e325f48
refactor download/delete file
Sep 26, 2023
422baec
update prebuild lib
Sep 26, 2023
41be6ea
chore: yarn workspace
louis-jan Sep 26, 2023
6150180
chore: update yarn workspace
louis-jan Sep 26, 2023
3b6a3c4
chore: yarn workspace with nohoist
louis-jan Sep 26, 2023
d2d11d2
fix: llama-cpp-import
louis-jan Sep 26, 2023
d86e6bd
chore: fix data-plugin wrong module path
louis-jan Sep 26, 2023
126bb6f
chore: correct build step
louis-jan Sep 26, 2023
15f2ba4
chore: - separate inference service (#212)
namchuai Sep 26, 2023
9b4ef2c
chore: update core plugins
louis-jan Sep 26, 2023
eae5a75
chore: hide installation prompt and fix model load - management plugin
louis-jan Sep 26, 2023
32779fd
chore: remove legacy files; update readme
Sep 26, 2023
9f5907a
fix: refresh page lost the download state
Sep 26, 2023
a47a149
fix: ai prompt not passed to plugin
Sep 26, 2023
be13590
chore: module import fix for production
louis-jan Sep 26, 2023
583577a
chore: auto updater
louis-jan Sep 27, 2023
1390cf6
chore: package is public
louis-jan Sep 27, 2023
c811ffc
chore: fix yarn workspace config
louis-jan Sep 27, 2023
7154738
update: model management uses Q4_K_M
louis-jan Sep 27, 2023
9dc4593
chore: fix yarn scripts for publishing
louis-jan Sep 27, 2023
c8021c8
chore: app updater - progress update message
louis-jan Sep 27, 2023
dd834c9
chore: user confirms update action
louis-jan Sep 27, 2023
d2be2b0
adding some state for changing page
Sep 28, 2023
918c7e3
chore: refactor plugins into yarn workspace - a single command to pub…
louis-jan Sep 28, 2023
7aba085
chore update readme (#218)
hiento09 Sep 28, 2023
2e61778
change app name and app icon
Sep 28, 2023
c3c367f
remove: go-to-nowhere actions
louis-jan Sep 28, 2023
6aba03c
chore: bundle core plugins from root and scan default plugins
louis-jan Sep 28, 2023
4951a18
fix: app crashes on different field name lookup
louis-jan Sep 28, 2023
10e0932
chore: css fix
louis-jan Sep 28, 2023
ec15204
chore: bind download progress to app ui
louis-jan Sep 28, 2023
ce95440
chore: bind active model
louis-jan Sep 28, 2023
f6d88ee
chore: simplify app splash-screen only centered jan icon
louis-jan Sep 28, 2023
54a99ea
feature: system monitoring plugin (#196)
hiro-v Sep 28, 2023
eac8bf5
chore: add build:plugins step to README
louis-jan Sep 28, 2023
92a5b66
chore: model searching and fix model name
louis-jan Sep 28, 2023
9ee7bc4
fix: plugin file selected appearance
louis-jan Sep 28, 2023
f8b8d93
fix: create new conversation does not work
louis-jan Sep 28, 2023
c1a0965
fix: delete conversation not update state - messages still exist
louis-jan Sep 28, 2023
1c79b93
chore: fix asset path prefix
louis-jan Sep 28, 2023
64a50e0
Add CICD for macos (#221)
hiento09 Sep 28, 2023
313b8a9
chore: fix production plugin path
louis-jan Sep 28, 2023
b0c6630
chore: add shell open url in external browser
louis-jan Sep 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
# Jan inference
models/**
error.log
app/electron/core/*/node_modules
app/electron/core/*/dist
app/electron/core/*/package-lock.json
node_modules
package-lock.json
*.tgz
app/yarn.lock
app/dist
yarn.lock
dist
build
.DS_Store
1,455 changes: 0 additions & 1,455 deletions app/electron/core/base-plugin/package-lock.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { definePresetEps, setImporter } from "./import-manager.js";

export * as extensionPoints from "./extension-manager.js";
export * as activationPoints from "./activation-manager.js";
export * as plugins from "./facade";
export * as plugins from "./facade.js";
export { default as ExtensionPoint } from "./ExtensionPoint.js";

if (typeof window === "undefined" || !window.pluggableElectronIpc)
Expand Down
144 changes: 71 additions & 73 deletions app/electron/main.ts → electron/main.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,83 @@
// @ts-nocheck
const {
import {
app,
BrowserWindow,
screen: electronScreen,
screen as electronScreen,
dialog,
ipcMain,
} = require("electron");
const isDev = require("electron-is-dev");
const path = require("path");
const pe = require("pluggable-electron/main");
const fs = require("fs");
const { mkdir, writeFile } = require("fs/promises");
const { Readable } = require("stream");
const { finished } = require("stream/promises");
const request = require("request");
const progress = require("request-progress");
} from "electron";
import { resolve, join } from "path";
import { readdirSync, createWriteStream, unlink, lstatSync } from "fs";
import isDev = require("electron-is-dev");
import request = require("request");
import progress = require("request-progress");
import { init, getStore } from "./core/plugin-manager/pluginMgr";

let modelSession = undefined;
let modelName = "llama-2-7b-chat.gguf.q4_0.bin";
let mainWindow;

let window;
const _importDynamic = new Function('modulePath', 'return import(modulePath)')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const createMainWindow = () => {
window = new BrowserWindow({
mainWindow = new BrowserWindow({
width: electronScreen.getPrimaryDisplay().workArea.width,
height: electronScreen.getPrimaryDisplay().workArea.height,
show: false,
backgroundColor: "white",
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
preload: path.resolve(app.getAppPath(), "electron/preload.js"),
preload: join(__dirname, "preload.js"),
},
});

// TODO: add options for model configuration
ipcMain.handle("initModel", async (event, product, importer) => {
if (!product.fileName) {
await dialog.showMessageBox({
message: "Selected model does not have file name..",
});

return;
}

console.info(`Initializing model: ${product.name}..`);
_importDynamic(
isDev
? join(__dirname, "../node_modules/node-llama-cpp/dist/index.js")
: resolve(
app.getAppPath(),
"./../../app.asar.unpacked/node_modules/node-llama-cpp/dist/index.js"
)
)
.then(({ LlamaContext, LlamaChatSession, LlamaModel }) => {
const modelPath = join(app.getPath("userData"), product.fileName);
// TODO: check if file is already there
const model = new LlamaModel({
modelPath: modelPath,
});
const context = new LlamaContext({ model });
modelSession = new LlamaChatSession({ context });
console.info(`Init model ${product.name} successfully!`);
})
.catch(async (e) => {
console.error(e);
await dialog.showMessageBox({
message: "Failed to import LLM module",
});
});
});

ipcMain.handle("invokePluginFunc", async (event, plugin, method, ...args) => {
const plg = pe
.getStore()
const plg = getStore()
.getActivePlugins()
.filter((p) => p.name === plugin)[0];
const pluginPath = path.join(
const pluginPath = join(
app.getPath("userData"),
"plugins",
plg.name,
"dist/module.js",
"dist/module.js"
);
return await import(
/* webpackIgnore: true */
Expand All @@ -65,54 +99,54 @@ const createMainWindow = () => {

const startURL = isDev
? "http://localhost:3000"
: `file://${path.join(__dirname, "../out/index.html")}`;
: `file://${join(__dirname, "../out/index.html")}`;

window.loadURL(startURL);
mainWindow.loadURL(startURL);

window.once("ready-to-show", () => window.show());
window.on("closed", () => {
mainWindow.once("ready-to-show", () => mainWindow.show());
mainWindow.on("closed", () => {
if (process.platform !== "darwin") app.quit();
});

window.webContents.openDevTools();
mainWindow.webContents.openDevTools();
};

app.whenReady().then(() => {
createMainWindow();
setupPlugins();

ipcMain.handle("userData", async (event) => {
return path.resolve(__dirname, "../");
return join(__dirname, "../");
});

ipcMain.handle("downloadModel", async (event, url) => {
const userDataPath = app.getPath("userData");
const destination = path.resolve(userDataPath, modelName);
const destination = resolve(userDataPath, modelName);

progress(request(url), {})
.on("progress", function (state) {
window.webContents.send("model-download-update", {
mainWindow.webContents.send("model-download-update", {
...state,
modelId: modelName,
});
})
.on("error", function (err) {
window.webContents.send("model-download-error", err);
mainWindow.webContents.send("model-download-error", err);
})
.on("end", function () {
app.relaunch();
app.exit();
// Do something after request finishes
})
.pipe(fs.createWriteStream(destination));
.pipe(createWriteStream(destination));
});

ipcMain.handle("deleteModel", async (event, modelFileName) => {
const userDataPath = app.getPath("userData");
const fullPath = path.join(userDataPath, modelFileName);
const fullPath = join(userDataPath, modelFileName);

let result = "NULL";
fs.unlink(fullPath, function (err) {
unlink(fullPath, function (err) {
if (err && err.code == "ENOENT") {
console.info("File doesn't exist, won't remove it.");
result = "FILE_NOT_EXIST";
Expand All @@ -128,50 +162,14 @@ app.whenReady().then(() => {
return result;
});

// TODO: add options for model configuration
ipcMain.handle("initModel", async (event, product) => {
if (!product.fileName) {
await dialog.showMessageBox({
message: "Selected model does not have file name..",
});

return;
}

console.info(`Initializing model: ${product.name}..`);
import(
isDev
? "../node_modules/node-llama-cpp/dist/index.js"
: path.resolve(
app.getAppPath(),
"./../../app.asar.unpacked/node_modules/node-llama-cpp/dist/index.js",
)
)
.then(({ LlamaContext, LlamaChatSession, LlamaModel }) => {
const modelPath = path.join(app.getPath("userData"), product.fileName);
// TODO: check if file is already there
const model = new LlamaModel({
modelPath: modelPath,
});
const context = new LlamaContext({ model });
modelSession = new LlamaChatSession({ context });
console.info(`Init model ${product.name} successfully!`);
})
.catch(async (e) => {
await dialog.showMessageBox({
message: "Failed to import LLM module",
});
});
});

ipcMain.handle("getDownloadedModels", async (event) => {
const userDataPath = app.getPath("userData");

const allBinariesName = [];
var files = fs.readdirSync(userDataPath);
var files = readdirSync(userDataPath);
for (var i = 0; i < files.length; i++) {
var filename = path.join(userDataPath, files[i]);
var stat = fs.lstatSync(filename);
var filename = join(userDataPath, files[i]);
var stat = lstatSync(filename);
if (stat.isDirectory()) {
// ignore
} else if (filename.endsWith(".bin")) {
Expand Down Expand Up @@ -203,19 +201,19 @@ app.on("window-all-closed", () => {
});

function setupPlugins() {
pe.init({
init({
// Function to check from the main process that user wants to install a plugin
confirmInstall: async (plugins) => {
const answer = await dialog.showMessageBox({
message: `Are you sure you want to install the plugin ${plugins.join(
", ",
", "
)}`,
buttons: ["Ok", "Cancel"],
cancelId: 1,
});
return answer.response == 0;
},
// Path to install plugin to
pluginsPath: path.join(app.getPath("userData"), "plugins"),
pluginsPath: join(app.getPath("userData"), "plugins"),
});
}
43 changes: 43 additions & 0 deletions electron/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "jan-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "./build/main.js",
"author": "Jan",
"license": "MIT",
"build": {
"appId": "jan.ai.app",
"productName": "Jan Desktop App",
"asarUnpack": [
"node_modules",
"core"
],
"files": [
"out/**/*",
"build/**/*",
"core"
],
"extends": null,
"mac": {
"type": "distribution"
}
},
"scripts": {
"dev": "tsc -p . && electron .",
"electron:start": "concurrently \"yarn dev\" \"wait-on http://localhost:3000 && tsc -p . && electron .\"",
"electron:build:all": "yarn build && electron-builder build --publish never -mwl"
},
"dependencies": {
"electron-is-dev": "^2.0.0",
"pluggable-electron": "^0.6.0",
"request": "^2.88.2",
"request-progress": "^3.0.0",
"node-llama-cpp": "^2.4.1"
},
"devDependencies": {
"concurrently": "^8.2.1",
"electron": "^26.2.1",
"electron-builder": "^24.6.4",
"wait-on": "^7.0.1"
}
}
18 changes: 10 additions & 8 deletions app/electron/preload.js → electron/preload.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
/* eslint-disable react-hooks/rules-of-hooks */
// Make Pluggable Electron's facade available to the renderer on window.plugins
//@ts-ignore
const useFacade = require("pluggable-electron/facade");
useFacade();

//@ts-ignore
const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld("electronAPI", {
invokePluginFunc: (plugin, method, ...args) =>
invokePluginFunc: (plugin: any, method: any, ...args: any[]) =>
ipcRenderer.invoke("invokePluginFunc", plugin, method, ...args),

userData: () => ipcRenderer.invoke("userData"),

sendInquiry: (question) => ipcRenderer.invoke("sendInquiry", question),
sendInquiry: (question: string) =>
ipcRenderer.invoke("sendInquiry", question),

initModel: (product) => ipcRenderer.invoke("initModel", product),
initModel: (product: any) => ipcRenderer.invoke("initModel", product),

getDownloadedModels: () => ipcRenderer.invoke("getDownloadedModels"),

getAvailableModels: () => ipcRenderer.invoke("getAvailableModels"),

deleteModel: (path) => ipcRenderer.invoke("deleteModel", path),
deleteModel: (path: string) => ipcRenderer.invoke("deleteModel", path),

downloadModel: (url) => ipcRenderer.invoke("downloadModel", url),
downloadModel: (url: string) => ipcRenderer.invoke("downloadModel", url),

onModelDownloadUpdate: (callback) =>
onModelDownloadUpdate: (callback: any) =>
ipcRenderer.on("model-download-update", callback),

onModelDownloadError: (callback) =>
onModelDownloadError: (callback: any) =>
ipcRenderer.on("model-download-error", callback),
});
13 changes: 13 additions & 0 deletions electron/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"sourceMap": true,
"strict": true,
"outDir": "./build",
"rootDir": "./",
"noEmitOnError": true,
"allowJs": true,
"typeRoots": ["node_modules/@types"]
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
plugins,
extensionPoints,
activationPoints,
} from "../../electron/core/plugin-manager/execution/index";
} from "../../../electron/core/plugin-manager/execution/index";

import {
ChartPieIcon,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Image from "next/image";
import { SidebarButton } from "../SidebarButton";
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
import { executeSerial } from "../../../../electron/core/plugin-manager/execution/extension-manager";
import { ModelManagementService } from "../../../shared/coreService";
import useCreateConversation from "@/_hooks/useCreateConversation";

Expand Down
Loading