From 80c4f53927438028d09bb8fb9bbd8fc0ddc961ef Mon Sep 17 00:00:00 2001 From: Louis <133622055+louis-jan@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:11:20 +0700 Subject: [PATCH 01/42] chore: enable back bot function for edge-release (#474) --- web/app/_components/LeftHeaderAction/index.tsx | 4 ++-- web/containers/Sidebar/Left.tsx | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/web/app/_components/LeftHeaderAction/index.tsx b/web/app/_components/LeftHeaderAction/index.tsx index 9c99e3260..c506cb139 100644 --- a/web/app/_components/LeftHeaderAction/index.tsx +++ b/web/app/_components/LeftHeaderAction/index.tsx @@ -34,12 +34,12 @@ const LeftHeaderAction: React.FC = () => { className="flex-1" icon={} /> - {/* } - /> */} + /> ) } diff --git a/web/containers/Sidebar/Left.tsx b/web/containers/Sidebar/Left.tsx index 5510538db..e837058f3 100644 --- a/web/containers/Sidebar/Left.tsx +++ b/web/containers/Sidebar/Left.tsx @@ -88,11 +88,11 @@ export const SidebarLeft = () => { icon: , state: MainViewState.MyModel, }, - // { - // name: 'Bot', - // icon: , - // state: MainViewState.CreateBot, - // }, + { + name: 'Bot', + icon: , + state: MainViewState.CreateBot, + }, { name: 'Settings', icon: , From 7fbb5822f7829eea4401c33c5fef2410a4d692b0 Mon Sep 17 00:00:00 2001 From: Louis <133622055+louis-jan@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:17:41 +0700 Subject: [PATCH 02/42] chore: update plugin sdk - add appDataPath (#492) --- core/core.ts | 10 +++++++++- electron/main.ts | 9 +++++++++ electron/preload.ts | 2 ++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/core/core.ts b/core/core.ts index 84b2524e6..1820d5b9a 100644 --- a/core/core.ts +++ b/core/core.ts @@ -28,6 +28,13 @@ const downloadFile: (url: string, fileName: string) => Promise = (url, file const deleteFile: (path: string) => Promise = (path) => window.coreAPI?.deleteFile(path) ?? window.electronAPI?.deleteFile(path); +/** + * Retrieves the path to the app data directory using the `coreAPI` object. + * If the `coreAPI` object is not available, the function returns `undefined`. + * @returns A Promise that resolves with the path to the app data directory, or `undefined` if the `coreAPI` object is not available. + */ +const appDataPath: () => Promise = () => window.coreAPI?.appDataPath(); + /** Register extension point function type definition * */ @@ -46,9 +53,10 @@ export const core = { invokePluginFunc, downloadFile, deleteFile, + appDataPath, }; /** * Functions exports */ -export { invokePluginFunc, downloadFile, deleteFile }; +export { invokePluginFunc, downloadFile, deleteFile, appDataPath }; diff --git a/electron/main.ts b/electron/main.ts index 3a2da0aca..88d981c14 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -193,6 +193,15 @@ function handleIPCs() { return join(app.getPath("userData"), "plugins"); }); + /** + * Retrieves the path to the app data directory using the `coreAPI` object. + * If the `coreAPI` object is not available, the function returns `undefined`. + * @returns A Promise that resolves with the path to the app data directory, or `undefined` if the `coreAPI` object is not available. + */ + ipcMain.handle("appDataPath", async (_event) => { + return app.getPath("userData"); + }); + /** * Returns the version of the app. * @param _event - The IPC event object. diff --git a/electron/preload.ts b/electron/preload.ts index dac5aef6f..ceaede049 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -19,6 +19,8 @@ contextBridge.exposeInMainWorld("electronAPI", { pluginPath: () => ipcRenderer.invoke("pluginPath"), + appDataPath: () => ipcRenderer.invoke("appDataPath"), + reloadPlugins: () => ipcRenderer.invoke("reloadPlugins"), appVersion: () => ipcRenderer.invoke("appVersion"), From b9dcdb737c54b31a687a7e46c288ac78dd35d3bc Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 30 Oct 2023 08:18:07 +0000 Subject: [PATCH 03/42] janhq/jan: Update tag build 0.1.9 --- core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/package.json b/core/package.json index c74f87800..d80e89a6c 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/core", - "version": "0.1.8", + "version": "0.1.9", "description": "Plugin core lib", "keywords": [ "jan", From 6bf5b7d906e2e193e36fcb5f9ad26376bb6726b3 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Oct 2023 19:36:03 +0700 Subject: [PATCH 04/42] #255: Read plugins manifest from CDN --- electron/core/plugin-manager/execution/facade.js | 5 ++++- plugins/data-plugin/package.json | 2 ++ plugins/inference-plugin/package.json | 2 ++ plugins/model-management-plugin/package.json | 2 ++ plugins/monitoring-plugin/package.json | 2 ++ 5 files changed, 12 insertions(+), 1 deletion(-) diff --git a/electron/core/plugin-manager/execution/facade.js b/electron/core/plugin-manager/execution/facade.js index c4f62f995..df75ce731 100644 --- a/electron/core/plugin-manager/execution/facade.js +++ b/electron/core/plugin-manager/execution/facade.js @@ -65,7 +65,10 @@ export async function getActive() { return; } // eslint-disable-next-line no-undef - const plgList = await window.pluggableElectronIpc.getActive(); + const plgList = await window.pluggableElectronIpc?.getActive() ?? + import( + /* webpackIgnore: true */ PLUGIN_CATALOG + `?t=${Date.now()}` + ).then((data) => data.default.filter((e) => e.supportCloudNative)); return plgList.map( (plugin) => new Plugin( diff --git a/plugins/data-plugin/package.json b/plugins/data-plugin/package.json index a685039b1..0845009c8 100644 --- a/plugins/data-plugin/package.json +++ b/plugins/data-plugin/package.json @@ -7,6 +7,8 @@ "module": "dist/cjs/module.js", "author": "Jan ", "license": "MIT", + "supportCloudNative": true, + "url": "/plugins/data-plugin/index.js", "activationPoints": [ "init" ], diff --git a/plugins/inference-plugin/package.json b/plugins/inference-plugin/package.json index 9d2fbd50b..30b0c073a 100644 --- a/plugins/inference-plugin/package.json +++ b/plugins/inference-plugin/package.json @@ -7,6 +7,8 @@ "module": "dist/module.js", "author": "Jan ", "license": "MIT", + "supportCloudNative": true, + "url": "/plugins/inference-plugin/index.js", "activationPoints": [ "init" ], diff --git a/plugins/model-management-plugin/package.json b/plugins/model-management-plugin/package.json index 21ce35f38..c122456c3 100644 --- a/plugins/model-management-plugin/package.json +++ b/plugins/model-management-plugin/package.json @@ -7,6 +7,8 @@ "module": "dist/module.js", "author": "Jan ", "license": "MIT", + "supportCloudNative": true, + "url": "/plugins/model-management-plugin/index.js", "activationPoints": [ "init" ], diff --git a/plugins/monitoring-plugin/package.json b/plugins/monitoring-plugin/package.json index 49f9e4b46..eb7cf9123 100644 --- a/plugins/monitoring-plugin/package.json +++ b/plugins/monitoring-plugin/package.json @@ -7,6 +7,8 @@ "module": "dist/module.js", "author": "Jan ", "license": "MIT", + "supportCloudNative": true, + "url": "/plugins/monitoring-plugin/index.js", "activationPoints": [ "init" ], From 01f230ef8e963fbf6d0c71472f35fc1214980d58 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Oct 2023 19:45:18 +0700 Subject: [PATCH 05/42] Skip no-undef lint --- electron/core/plugin-manager/execution/facade.js | 1 + 1 file changed, 1 insertion(+) diff --git a/electron/core/plugin-manager/execution/facade.js b/electron/core/plugin-manager/execution/facade.js index df75ce731..f5abea125 100644 --- a/electron/core/plugin-manager/execution/facade.js +++ b/electron/core/plugin-manager/execution/facade.js @@ -67,6 +67,7 @@ export async function getActive() { // eslint-disable-next-line no-undef const plgList = await window.pluggableElectronIpc?.getActive() ?? import( + // eslint-disable-next-line no-undef /* webpackIgnore: true */ PLUGIN_CATALOG + `?t=${Date.now()}` ).then((data) => data.default.filter((e) => e.supportCloudNative)); return plgList.map( From 8f586b453bc9373ba8b406da0978633e22cd1988 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Oct 2023 19:50:28 +0700 Subject: [PATCH 06/42] Fix async call --- electron/core/plugin-manager/execution/facade.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/core/plugin-manager/execution/facade.js b/electron/core/plugin-manager/execution/facade.js index f5abea125..992445511 100644 --- a/electron/core/plugin-manager/execution/facade.js +++ b/electron/core/plugin-manager/execution/facade.js @@ -66,7 +66,7 @@ export async function getActive() { } // eslint-disable-next-line no-undef const plgList = await window.pluggableElectronIpc?.getActive() ?? - import( + await import( // eslint-disable-next-line no-undef /* webpackIgnore: true */ PLUGIN_CATALOG + `?t=${Date.now()}` ).then((data) => data.default.filter((e) => e.supportCloudNative)); From e667dd405612efde081a5e46b3e66a05ff844754 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Oct 2023 20:21:56 +0700 Subject: [PATCH 07/42] revert change --- electron/core/plugin-manager/execution/facade.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/electron/core/plugin-manager/execution/facade.js b/electron/core/plugin-manager/execution/facade.js index 992445511..fca7001af 100644 --- a/electron/core/plugin-manager/execution/facade.js +++ b/electron/core/plugin-manager/execution/facade.js @@ -65,11 +65,7 @@ export async function getActive() { return; } // eslint-disable-next-line no-undef - const plgList = await window.pluggableElectronIpc?.getActive() ?? - await import( - // eslint-disable-next-line no-undef - /* webpackIgnore: true */ PLUGIN_CATALOG + `?t=${Date.now()}` - ).then((data) => data.default.filter((e) => e.supportCloudNative)); + const plgList = await window.pluggableElectronIpc?.getActive(); return plgList.map( (plugin) => new Plugin( From a61f98c17a7c63519851935437cf4f39fd066a10 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Oct 2023 20:22:33 +0700 Subject: [PATCH 08/42] revert change --- electron/core/plugin-manager/execution/facade.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/core/plugin-manager/execution/facade.js b/electron/core/plugin-manager/execution/facade.js index fca7001af..c4f62f995 100644 --- a/electron/core/plugin-manager/execution/facade.js +++ b/electron/core/plugin-manager/execution/facade.js @@ -65,7 +65,7 @@ export async function getActive() { return; } // eslint-disable-next-line no-undef - const plgList = await window.pluggableElectronIpc?.getActive(); + const plgList = await window.pluggableElectronIpc.getActive(); return plgList.map( (plugin) => new Plugin( From 4f9d883c6bf03ee2b8461f3b3f1ab9159e9bae8d Mon Sep 17 00:00:00 2001 From: Louis <133622055+louis-jan@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:30:56 +0700 Subject: [PATCH 09/42] chore: update plugins license (#496) --- plugins/data-plugin/package.json | 2 +- plugins/inference-plugin/package.json | 2 +- plugins/model-management-plugin/package.json | 2 +- plugins/monitoring-plugin/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/data-plugin/package.json b/plugins/data-plugin/package.json index 0845009c8..61d06316d 100644 --- a/plugins/data-plugin/package.json +++ b/plugins/data-plugin/package.json @@ -6,7 +6,7 @@ "main": "dist/esm/index.js", "module": "dist/cjs/module.js", "author": "Jan ", - "license": "MIT", + "license": "AGPL-3.0", "supportCloudNative": true, "url": "/plugins/data-plugin/index.js", "activationPoints": [ diff --git a/plugins/inference-plugin/package.json b/plugins/inference-plugin/package.json index 30b0c073a..947f97d6b 100644 --- a/plugins/inference-plugin/package.json +++ b/plugins/inference-plugin/package.json @@ -6,7 +6,7 @@ "main": "dist/index.js", "module": "dist/module.js", "author": "Jan ", - "license": "MIT", + "license": "AGPL-3.0", "supportCloudNative": true, "url": "/plugins/inference-plugin/index.js", "activationPoints": [ diff --git a/plugins/model-management-plugin/package.json b/plugins/model-management-plugin/package.json index c122456c3..c74cb393b 100644 --- a/plugins/model-management-plugin/package.json +++ b/plugins/model-management-plugin/package.json @@ -6,7 +6,7 @@ "main": "dist/index.js", "module": "dist/module.js", "author": "Jan ", - "license": "MIT", + "license": "AGPL-3.0", "supportCloudNative": true, "url": "/plugins/model-management-plugin/index.js", "activationPoints": [ diff --git a/plugins/monitoring-plugin/package.json b/plugins/monitoring-plugin/package.json index eb7cf9123..295ad81d8 100644 --- a/plugins/monitoring-plugin/package.json +++ b/plugins/monitoring-plugin/package.json @@ -6,7 +6,7 @@ "main": "dist/index.js", "module": "dist/module.js", "author": "Jan ", - "license": "MIT", + "license": "AGPL-3.0", "supportCloudNative": true, "url": "/plugins/monitoring-plugin/index.js", "activationPoints": [ From 2711552614eeb5344574b348310f83cdc3dc10dc Mon Sep 17 00:00:00 2001 From: Louis <133622055+louis-jan@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:52:14 +0700 Subject: [PATCH 10/42] chore: update plugin readme (#497) --- plugins/data-plugin/README.md | 7 +-- plugins/inference-plugin/README.md | 4 ++ plugins/model-management-plugin/README.md | 77 +++++++++++++++++++++++ plugins/monitoring-plugin/README.md | 77 +++++++++++++++++++++++ 4 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 plugins/model-management-plugin/README.md create mode 100644 plugins/monitoring-plugin/README.md diff --git a/plugins/data-plugin/README.md b/plugins/data-plugin/README.md index 2197d9ad2..fc0cf4c34 100644 --- a/plugins/data-plugin/README.md +++ b/plugins/data-plugin/README.md @@ -1,8 +1,5 @@ -## Database handler plugin for Jan App - -**Notice**: please only install dependencies and run build using npm and not yarn. +## Jan data handler plugin - index.ts: Main entry point for the plugin. - module.ts: Defines the plugin module which would be executed by the main node process. -- package.json: Defines the plugin metadata. -- tsconfig.json: Defines the typescript configuration. +- package.json: Plugin & npm module manifest. diff --git a/plugins/inference-plugin/README.md b/plugins/inference-plugin/README.md index ae70eb4ec..5b3b13ad7 100644 --- a/plugins/inference-plugin/README.md +++ b/plugins/inference-plugin/README.md @@ -1,3 +1,7 @@ +# Jan inference plugin + +Created using Jan app example + # Create a Jan Plugin using Typescript Use this template to bootstrap the creation of a TypeScript Jan plugin. ๐Ÿš€ diff --git a/plugins/model-management-plugin/README.md b/plugins/model-management-plugin/README.md new file mode 100644 index 000000000..9bab5b40a --- /dev/null +++ b/plugins/model-management-plugin/README.md @@ -0,0 +1,77 @@ +# Jan Model Management plugin + +Created using Jan app example + +# Create a Jan Plugin using Typescript + +Use this template to bootstrap the creation of a TypeScript Jan plugin. ๐Ÿš€ + +## Create Your Own Plugin + +To create your own plugin, you can use this repository as a template! Just follow the below instructions: + +1. Click the Use this template button at the top of the repository +2. Select Create a new repository +3. Select an owner and name for your new repository +4. Click Create repository +5. Clone your new repository + +## Initial Setup + +After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin. + +> [!NOTE] +> +> You'll need to have a reasonably modern version of +> [Node.js](https://nodejs.org) handy. If you are using a version manager like +> [`nodenv`](https://github.com/nodenv/nodenv) or +> [`nvm`](https://github.com/nvm-sh/nvm), you can run `nodenv install` in the +> root of your repository to install the version specified in +> [`package.json`](./package.json). Otherwise, 20.x or later should work! + +1. :hammer_and_wrench: Install the dependencies + + ```bash + npm install + ``` + +1. :building_construction: Package the TypeScript for distribution + + ```bash + npm run bundle + ``` + +1. :white_check_mark: Check your artifact + + There will be a tgz file in your plugin directory now + +## Update the Plugin Metadata + +The [`package.json`](package.json) file defines metadata about your plugin, such as +plugin name, main entry, description and version. + +When you copy this repository, update `package.json` with the name, description for your plugin. + +## Update the Plugin Code + +The [`src/`](./src/) directory is the heart of your plugin! This contains the +source code that will be run when your plugin extension functions are invoked. You can replace the +contents of this directory with your own code. + +There are a few things to keep in mind when writing your plugin code: + +- Most Jan Plugin Extension functions are processed asynchronously. + In `index.ts`, you will see that the extension function will return a `Promise`. + + ```typescript + import { core } from "@janhq/core"; + + function onStart(): Promise { + return core.invokePluginFunc(MODULE_PATH, "run", 0); + } + ``` + + For more information about the Jan Plugin Core module, see the + [documentation](https://github.com/janhq/jan/blob/main/core/README.md). + +So, what are you waiting for? Go ahead and start customizing your plugin! diff --git a/plugins/monitoring-plugin/README.md b/plugins/monitoring-plugin/README.md new file mode 100644 index 000000000..a048a4417 --- /dev/null +++ b/plugins/monitoring-plugin/README.md @@ -0,0 +1,77 @@ +# Jan Monitoring plugin + +Created using Jan app example + +# Create a Jan Plugin using Typescript + +Use this template to bootstrap the creation of a TypeScript Jan plugin. ๐Ÿš€ + +## Create Your Own Plugin + +To create your own plugin, you can use this repository as a template! Just follow the below instructions: + +1. Click the Use this template button at the top of the repository +2. Select Create a new repository +3. Select an owner and name for your new repository +4. Click Create repository +5. Clone your new repository + +## Initial Setup + +After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin. + +> [!NOTE] +> +> You'll need to have a reasonably modern version of +> [Node.js](https://nodejs.org) handy. If you are using a version manager like +> [`nodenv`](https://github.com/nodenv/nodenv) or +> [`nvm`](https://github.com/nvm-sh/nvm), you can run `nodenv install` in the +> root of your repository to install the version specified in +> [`package.json`](./package.json). Otherwise, 20.x or later should work! + +1. :hammer_and_wrench: Install the dependencies + + ```bash + npm install + ``` + +1. :building_construction: Package the TypeScript for distribution + + ```bash + npm run bundle + ``` + +1. :white_check_mark: Check your artifact + + There will be a tgz file in your plugin directory now + +## Update the Plugin Metadata + +The [`package.json`](package.json) file defines metadata about your plugin, such as +plugin name, main entry, description and version. + +When you copy this repository, update `package.json` with the name, description for your plugin. + +## Update the Plugin Code + +The [`src/`](./src/) directory is the heart of your plugin! This contains the +source code that will be run when your plugin extension functions are invoked. You can replace the +contents of this directory with your own code. + +There are a few things to keep in mind when writing your plugin code: + +- Most Jan Plugin Extension functions are processed asynchronously. + In `index.ts`, you will see that the extension function will return a `Promise`. + + ```typescript + import { core } from "@janhq/core"; + + function onStart(): Promise { + return core.invokePluginFunc(MODULE_PATH, "run", 0); + } + ``` + + For more information about the Jan Plugin Core module, see the + [documentation](https://github.com/janhq/jan/blob/main/core/README.md). + +So, what are you waiting for? Go ahead and start customizing your plugin! From b7e45dabad4219480e6b237f4dcc16f77287c7f0 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 30 Oct 2023 22:22:27 +0700 Subject: [PATCH 11/42] Add button new conversation in history list --- web/app/_components/HistoryList/index.tsx | 74 ++++++++++++++++------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/web/app/_components/HistoryList/index.tsx b/web/app/_components/HistoryList/index.tsx index 3cbb482f6..3dab9f411 100644 --- a/web/app/_components/HistoryList/index.tsx +++ b/web/app/_components/HistoryList/index.tsx @@ -1,46 +1,74 @@ import HistoryItem from '../HistoryItem' -import { useEffect } from 'react' +import { Fragment, useEffect } from 'react' import ExpandableHeader from '../ExpandableHeader' -import { useAtomValue } from 'jotai' +import { useAtomValue, useSetAtom } from 'jotai' import { searchAtom } from '@helpers/JotaiWrapper' import useGetUserConversations from '@hooks/useGetUserConversations' import SidebarEmptyHistory from '../SidebarEmptyHistory' import { userConversationsAtom } from '@helpers/atoms/Conversation.atom' import { twMerge } from 'tailwind-merge' +import { Button } from '@uikit' +import { activeAssistantModelAtom, stateModel } from '@helpers/atoms/Model.atom' +import useCreateConversation from '@hooks/useCreateConversation' +import { showingModalNoActiveModel } from '@helpers/atoms/Modal.atom' +import { PlusIcon } from '@heroicons/react/24/outline' const HistoryList: React.FC = () => { const conversations = useAtomValue(userConversationsAtom) const searchText = useAtomValue(searchAtom) const { getUserConversations } = useGetUserConversations() + const activeModel = useAtomValue(activeAssistantModelAtom) + const { requestCreateConvo } = useCreateConversation() + const setShowModalNoActiveModel = useSetAtom(showingModalNoActiveModel) + + const onNewConversationClick = () => { + if (activeModel) { + requestCreateConvo(activeModel) + } else { + setShowModalNoActiveModel(true) + } + } useEffect(() => { getUserConversations() + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return (
-
    - {conversations.length > 0 ? ( - conversations - .filter( - (e) => - searchText.trim() === '' || - e.name?.toLowerCase().includes(searchText.toLowerCase().trim()) - ) - .map((convo) => ( - - )) - ) : ( - - )} -
+ {conversations.length > 0 ? ( + +
    + {conversations + .filter( + (e) => + searchText.trim() === '' || + e.name + ?.toLowerCase() + .includes(searchText.toLowerCase().trim()) + ) + .map((convo, i) => ( + + ))} +
+ +
+ ) : ( + + )}
) } From fd87f8ad7fcdbe412061dfef1abfb2afb19b29f7 Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Mon, 30 Oct 2023 22:32:34 +0700 Subject: [PATCH 12/42] Correct version and license (#498) Co-authored-by: Hien To --- plugins/data-plugin/README.md | 1 + plugins/data-plugin/package.json | 2 +- plugins/inference-plugin/README.md | 1 + plugins/inference-plugin/package.json | 2 +- plugins/model-management-plugin/README.md | 1 + plugins/model-management-plugin/package.json | 2 +- plugins/monitoring-plugin/README.md | 1 + plugins/monitoring-plugin/package.json | 2 +- 8 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/data-plugin/README.md b/plugins/data-plugin/README.md index fc0cf4c34..802883fab 100644 --- a/plugins/data-plugin/README.md +++ b/plugins/data-plugin/README.md @@ -3,3 +3,4 @@ - index.ts: Main entry point for the plugin. - module.ts: Defines the plugin module which would be executed by the main node process. - package.json: Plugin & npm module manifest. + diff --git a/plugins/data-plugin/package.json b/plugins/data-plugin/package.json index 61d06316d..478115f60 100644 --- a/plugins/data-plugin/package.json +++ b/plugins/data-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/data-plugin", - "version": "1.0.16", + "version": "1.0.17", "description": "The Data Connector provides easy access to a data API using the PouchDB engine. It offers accessible data management capabilities.", "icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/circle-stack.svg", "main": "dist/esm/index.js", diff --git a/plugins/inference-plugin/README.md b/plugins/inference-plugin/README.md index 5b3b13ad7..455783efb 100644 --- a/plugins/inference-plugin/README.md +++ b/plugins/inference-plugin/README.md @@ -75,3 +75,4 @@ There are a few things to keep in mind when writing your plugin code: [documentation](https://github.com/janhq/jan/blob/main/core/README.md). So, what are you waiting for? Go ahead and start customizing your plugin! + diff --git a/plugins/inference-plugin/package.json b/plugins/inference-plugin/package.json index 947f97d6b..1e0310e91 100644 --- a/plugins/inference-plugin/package.json +++ b/plugins/inference-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/inference-plugin", - "version": "1.0.14", + "version": "1.0.15", "description": "Inference Plugin, powered by @janhq/nitro, bring a high-performance Llama model inference in pure C++.", "icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/command-line.svg", "main": "dist/index.js", diff --git a/plugins/model-management-plugin/README.md b/plugins/model-management-plugin/README.md index 9bab5b40a..516bbec8b 100644 --- a/plugins/model-management-plugin/README.md +++ b/plugins/model-management-plugin/README.md @@ -75,3 +75,4 @@ There are a few things to keep in mind when writing your plugin code: [documentation](https://github.com/janhq/jan/blob/main/core/README.md). So, what are you waiting for? Go ahead and start customizing your plugin! + diff --git a/plugins/model-management-plugin/package.json b/plugins/model-management-plugin/package.json index c74cb393b..07cc6a780 100644 --- a/plugins/model-management-plugin/package.json +++ b/plugins/model-management-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/model-management-plugin", - "version": "1.0.9", + "version": "1.0.10", "description": "Model Management Plugin provides model exploration and seamless downloads", "icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/queue-list.svg", "main": "dist/index.js", diff --git a/plugins/monitoring-plugin/README.md b/plugins/monitoring-plugin/README.md index a048a4417..1617b9b13 100644 --- a/plugins/monitoring-plugin/README.md +++ b/plugins/monitoring-plugin/README.md @@ -75,3 +75,4 @@ There are a few things to keep in mind when writing your plugin code: [documentation](https://github.com/janhq/jan/blob/main/core/README.md). So, what are you waiting for? Go ahead and start customizing your plugin! + diff --git a/plugins/monitoring-plugin/package.json b/plugins/monitoring-plugin/package.json index 295ad81d8..5b39550e8 100644 --- a/plugins/monitoring-plugin/package.json +++ b/plugins/monitoring-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/monitoring-plugin", - "version": "1.0.6", + "version": "1.0.7", "description": "Utilizing systeminformation, it provides essential System and OS information retrieval", "icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/cpu-chip.svg", "main": "dist/index.js", From de81d7d7cae01e5e16486ba48a51517c18ed4d07 Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 30 Oct 2023 15:37:17 +0000 Subject: [PATCH 13/42] janhq/jan: Update tag build 1.0.18 for data-plugin --- plugins/data-plugin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/data-plugin/package.json b/plugins/data-plugin/package.json index 478115f60..a75dfae0d 100644 --- a/plugins/data-plugin/package.json +++ b/plugins/data-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/data-plugin", - "version": "1.0.17", + "version": "1.0.18", "description": "The Data Connector provides easy access to a data API using the PouchDB engine. It offers accessible data management capabilities.", "icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/circle-stack.svg", "main": "dist/esm/index.js", From 7ccdb8360d4d8b8e4e46fdaa762576a3e8160eb2 Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 30 Oct 2023 15:37:19 +0000 Subject: [PATCH 14/42] janhq/jan: Update tag build 1.0.16 for inference-plugin --- plugins/inference-plugin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inference-plugin/package.json b/plugins/inference-plugin/package.json index 1e0310e91..5bae4bab5 100644 --- a/plugins/inference-plugin/package.json +++ b/plugins/inference-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/inference-plugin", - "version": "1.0.15", + "version": "1.0.16", "description": "Inference Plugin, powered by @janhq/nitro, bring a high-performance Llama model inference in pure C++.", "icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/command-line.svg", "main": "dist/index.js", From d9f261732af9b40d06abd39c14bed2df4b76e276 Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 30 Oct 2023 15:37:21 +0000 Subject: [PATCH 15/42] janhq/jan: Update tag build 1.0.11 for model-management-plugin --- plugins/model-management-plugin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/model-management-plugin/package.json b/plugins/model-management-plugin/package.json index 07cc6a780..ad343f08e 100644 --- a/plugins/model-management-plugin/package.json +++ b/plugins/model-management-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/model-management-plugin", - "version": "1.0.10", + "version": "1.0.11", "description": "Model Management Plugin provides model exploration and seamless downloads", "icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/queue-list.svg", "main": "dist/index.js", From e1235fb160ee72fbf771b27aa45621b42dcd1f66 Mon Sep 17 00:00:00 2001 From: Service Account Date: Mon, 30 Oct 2023 15:37:23 +0000 Subject: [PATCH 16/42] janhq/jan: Update tag build 1.0.8 for monitoring-plugin --- plugins/monitoring-plugin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/monitoring-plugin/package.json b/plugins/monitoring-plugin/package.json index 5b39550e8..894c7aee6 100644 --- a/plugins/monitoring-plugin/package.json +++ b/plugins/monitoring-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@janhq/monitoring-plugin", - "version": "1.0.7", + "version": "1.0.8", "description": "Utilizing systeminformation, it provides essential System and OS information retrieval", "icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/cpu-chip.svg", "main": "dist/index.js", From 28c969759f7c8d2ca2276e66d53cec555b1fdeca Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 30 Oct 2023 22:41:42 +0700 Subject: [PATCH 17/42] Move new conversation sticky to top --- web/app/_components/HistoryList/index.tsx | 67 ++++++------------- web/app/_components/InputToolbar/index.tsx | 14 +--- .../_components/LeftHeaderAction/index.tsx | 52 ++++++++++---- web/app/_components/SecondaryButton/index.tsx | 8 ++- web/screens/Chat/index.tsx | 6 +- 5 files changed, 70 insertions(+), 77 deletions(-) diff --git a/web/app/_components/HistoryList/index.tsx b/web/app/_components/HistoryList/index.tsx index 3dab9f411..880521597 100644 --- a/web/app/_components/HistoryList/index.tsx +++ b/web/app/_components/HistoryList/index.tsx @@ -1,33 +1,17 @@ import HistoryItem from '../HistoryItem' -import { Fragment, useEffect } from 'react' +import { useEffect } from 'react' import ExpandableHeader from '../ExpandableHeader' -import { useAtomValue, useSetAtom } from 'jotai' +import { useAtomValue } from 'jotai' import { searchAtom } from '@helpers/JotaiWrapper' import useGetUserConversations from '@hooks/useGetUserConversations' import SidebarEmptyHistory from '../SidebarEmptyHistory' import { userConversationsAtom } from '@helpers/atoms/Conversation.atom' import { twMerge } from 'tailwind-merge' -import { Button } from '@uikit' -import { activeAssistantModelAtom, stateModel } from '@helpers/atoms/Model.atom' -import useCreateConversation from '@hooks/useCreateConversation' -import { showingModalNoActiveModel } from '@helpers/atoms/Modal.atom' -import { PlusIcon } from '@heroicons/react/24/outline' const HistoryList: React.FC = () => { const conversations = useAtomValue(userConversationsAtom) const searchText = useAtomValue(searchAtom) const { getUserConversations } = useGetUserConversations() - const activeModel = useAtomValue(activeAssistantModelAtom) - const { requestCreateConvo } = useCreateConversation() - const setShowModalNoActiveModel = useSetAtom(showingModalNoActiveModel) - - const onNewConversationClick = () => { - if (activeModel) { - requestCreateConvo(activeModel) - } else { - setShowModalNoActiveModel(true) - } - } useEffect(() => { getUserConversations() @@ -35,37 +19,26 @@ const HistoryList: React.FC = () => { }, []) return ( -
+
{conversations.length > 0 ? ( - -
    - {conversations - .filter( - (e) => - searchText.trim() === '' || - e.name - ?.toLowerCase() - .includes(searchText.toLowerCase().trim()) - ) - .map((convo, i) => ( - - ))} -
- -
+
    + {conversations + .filter( + (e) => + searchText.trim() === '' || + e.name?.toLowerCase().includes(searchText.toLowerCase().trim()) + ) + .map((convo, i) => ( + + ))} +
) : ( )} diff --git a/web/app/_components/InputToolbar/index.tsx b/web/app/_components/InputToolbar/index.tsx index 4895d90cf..40cd140af 100644 --- a/web/app/_components/InputToolbar/index.tsx +++ b/web/app/_components/InputToolbar/index.tsx @@ -43,15 +43,7 @@ const InputToolbar: React.FC = () => { } if (!activeConvoId) { - return ( -
- } - /> -
- ) + return null } if ( (activeConvoId && inputState === 'model-mismatch') || @@ -94,13 +86,13 @@ const InputToolbar: React.FC = () => {
)} -
+ {/*
} /> -
+
*/} {/* My text input */}
diff --git a/web/app/_components/LeftHeaderAction/index.tsx b/web/app/_components/LeftHeaderAction/index.tsx index c506cb139..e8e978bc5 100644 --- a/web/app/_components/LeftHeaderAction/index.tsx +++ b/web/app/_components/LeftHeaderAction/index.tsx @@ -2,22 +2,37 @@ import React from 'react' import SecondaryButton from '../SecondaryButton' -import { useSetAtom } from 'jotai' +import { useSetAtom, useAtomValue } from 'jotai' import { MainViewState, setMainViewStateAtom, } from '@helpers/atoms/MainView.atom' import { MagnifyingGlassIcon, PlusIcon } from '@heroicons/react/24/outline' +import useCreateConversation from '@hooks/useCreateConversation' import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels' +import { Button } from '@uikit' +import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom' +import { showingModalNoActiveModel } from '@helpers/atoms/Modal.atom' const LeftHeaderAction: React.FC = () => { const setMainView = useSetAtom(setMainViewStateAtom) const { downloadedModels } = useGetDownloadedModels() + const activeModel = useAtomValue(activeAssistantModelAtom) + const { requestCreateConvo } = useCreateConversation() + const setShowModalNoActiveModel = useSetAtom(showingModalNoActiveModel) const onExploreClick = () => { setMainView(MainViewState.ExploreModel) } + const onNewConversationClick = () => { + if (activeModel) { + requestCreateConvo(activeModel) + } else { + setShowModalNoActiveModel(true) + } + } + const onCreateBotClicked = () => { if (downloadedModels.length === 0) { alert('You need to download at least one model to create a bot.') @@ -27,19 +42,28 @@ const LeftHeaderAction: React.FC = () => { } return ( -
- } - /> - } - /> +
+
+ } + /> + } + /> +
+
) } diff --git a/web/app/_components/SecondaryButton/index.tsx b/web/app/_components/SecondaryButton/index.tsx index 2491edeba..be1d85146 100644 --- a/web/app/_components/SecondaryButton/index.tsx +++ b/web/app/_components/SecondaryButton/index.tsx @@ -16,7 +16,13 @@ const SecondaryButton: React.FC = ({ className, icon, }) => ( - diff --git a/web/screens/Chat/index.tsx b/web/screens/Chat/index.tsx index fe3f0217c..5472ce067 100644 --- a/web/screens/Chat/index.tsx +++ b/web/screens/Chat/index.tsx @@ -10,10 +10,8 @@ const ChatScreen = () => { return (
-
- - -
+ +
From ff68f03036cf2d3e3ade259e7f65d12d3b4a2701 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 30 Oct 2023 23:15:18 +0700 Subject: [PATCH 18/42] Download new version should show in status bar --- web/app/_components/ProgressBar/index.tsx | 2 +- web/containers/BottomBar/index.tsx | 24 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/web/app/_components/ProgressBar/index.tsx b/web/app/_components/ProgressBar/index.tsx index 8981f671a..66f1e1f72 100644 --- a/web/app/_components/ProgressBar/index.tsx +++ b/web/app/_components/ProgressBar/index.tsx @@ -6,7 +6,7 @@ type Props = { } const ProgressBar: React.FC = ({ used, total }) => ( -
+
Updating diff --git a/web/containers/BottomBar/index.tsx b/web/containers/BottomBar/index.tsx index 3b927c44e..19e288cc2 100644 --- a/web/containers/BottomBar/index.tsx +++ b/web/containers/BottomBar/index.tsx @@ -6,6 +6,8 @@ import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom' import { formatDownloadPercentage } from '@utils/converter' import { activeAssistantModelAtom, stateModel } from '@helpers/atoms/Model.atom' import useGetAppVersion from '@hooks/useGetAppVersion' +import ProgressBar from '@/_components/ProgressBar' +import { appDownloadProgress } from '@helpers/JotaiWrapper' const BottomBar = () => { const activeModel = useAtomValue(activeAssistantModelAtom) @@ -13,6 +15,7 @@ const BottomBar = () => { const { ram, cpu } = useGetSystemResources() const modelDownloadStates = useAtomValue(modelDownloadStateAtom) const appVersion = useGetAppVersion() + const progress = useAtomValue(appDownloadProgress) const downloadStates: DownloadState[] = [] for (const [, value] of Object.entries(modelDownloadStates)) { @@ -21,7 +24,22 @@ const BottomBar = () => { return (
-
+
+
+ {progress && progress >= 0 ? ( + + ) : null} + {downloadStates.length > 0 && ( + + )} +
+ {stateModelStartStop.state === 'start' && stateModelStartStop.loading && ( {
-

Jan v{appVersion?.version ?? ''}

+

+ Jan v{appVersion?.version ?? ''} +

) From 6be67895dd2a8a388674c1206b70446a59d95e19 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 16 Oct 2023 12:34:39 +0700 Subject: [PATCH 19/42] 255: Cloud native --- .dockerignore | 1 + Dockerfile | 39 ++++ .../core/plugin-manager/execution/facade.js | 5 +- package.json | 12 +- plugins/data-plugin/module.ts | 9 +- plugins/inference-plugin/module.ts | 38 ++-- .../nitro/nitro_start_linux.sh | 2 +- plugins/model-management-plugin/index.ts | 48 ++++- server/main.ts | 179 ++++++++++++++++++ server/nodemon.json | 5 + server/package.json | 26 +++ server/tsconfig.json | 19 ++ web/app/_components/SidebarFooter/index.tsx | 4 +- web/app/page.tsx | 8 +- web/containers/Providers/index.tsx | 2 +- web/containers/Sidebar/Left.tsx | 4 +- web/helpers/EventHandler.tsx | 28 +++ web/next.config.js | 3 + web/package.json | 1 + web/public/plugins/plugin.json | 50 +++++ .../Settings/CorePlugins/PluginsCatalog.tsx | 7 +- web/services/cloudNativeService.ts | 54 ++++++ web/services/coreService.ts | 22 ++- web/types/index.d.ts | 7 +- 24 files changed, 528 insertions(+), 45 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 server/main.ts create mode 100644 server/nodemon.json create mode 100644 server/package.json create mode 100644 server/tsconfig.json create mode 100644 web/public/plugins/plugin.json create mode 100644 web/services/cloudNativeService.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..600e365ec --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +**/node_modules \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..949a92673 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM node:20-bullseye AS base + +# 1. Install dependencies only when needed +FROM base AS deps +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ +RUN yarn install + +# # 2. Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +# This will do the trick, use the corresponding env file for each environment. +RUN yarn workspace server install +RUN yarn server:prod + +# 3. Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +# RUN addgroup -g 1001 -S nodejs; +COPY --from=builder /app/server/build ./ + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder /app/server/node_modules ./node_modules +COPY --from=builder /app/server/package.json ./package.json + +EXPOSE 4000 3928 + +ENV PORT 4000 +ENV APPDATA /app/data + +CMD ["node", "main.js"] \ No newline at end of file diff --git a/electron/core/plugin-manager/execution/facade.js b/electron/core/plugin-manager/execution/facade.js index c4f62f995..df09bd5aa 100644 --- a/electron/core/plugin-manager/execution/facade.js +++ b/electron/core/plugin-manager/execution/facade.js @@ -7,6 +7,7 @@ import Plugin from "./Plugin"; import { register } from "./activation-manager"; +import plugins from "../../../../web/public/plugins/plugin.json" /** * @typedef {Object.} installOptions The {@link https://www.npmjs.com/package/pacote|pacote options} @@ -65,7 +66,7 @@ export async function getActive() { return; } // eslint-disable-next-line no-undef - const plgList = await window.pluggableElectronIpc.getActive(); + const plgList = await window.pluggableElectronIpc?.getActive() ?? plugins; return plgList.map( (plugin) => new Plugin( @@ -90,7 +91,7 @@ export async function registerActive() { return; } // eslint-disable-next-line no-undef - const plgList = await window.pluggableElectronIpc.getActive(); + const plgList = await getActive() plgList.forEach((plugin) => register( new Plugin( diff --git a/package.json b/package.json index 53c046c72..e3205fed8 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,16 @@ "workspaces": { "packages": [ "electron", - "web" + "web", + "server" ], "nohoist": [ "electron", "electron/**", "web", - "web/**" + "web/**", + "server", + "server/**" ] }, "scripts": { @@ -32,7 +35,10 @@ "build:publish": "yarn build:web && yarn workspace jan build:publish", "build:publish-darwin": "yarn build:web && yarn workspace jan build:publish-darwin", "build:publish-win32": "yarn build:web && yarn workspace jan build:publish-win32", - "build:publish-linux": "yarn build:web && yarn workspace jan build:publish-linux" + "build:publish-linux": "yarn build:web && yarn workspace jan build:publish-linux", + "build:web-plugins": "yarn build:web && yarn build:plugins && mkdir -p \"./web/out/plugins/data-plugin\" && cp \"./plugins/data-plugin/dist/esm/index.js\" \"./web/out/plugins/data-plugin\" && mkdir -p \"./web/out/plugins/inference-plugin\" && cp \"./plugins/inference-plugin/dist/index.js\" \"./web/out/plugins/inference-plugin\" && mkdir -p \"./web/out/plugins/model-management-plugin\" && cp \"./plugins/model-management-plugin/dist/index.js\" \"./web/out/plugins/model-management-plugin\" && mkdir -p \"./web/out/plugins/monitoring-plugin\" && cp \"./plugins/monitoring-plugin/dist/index.js\" \"./web/out/plugins/monitoring-plugin\"", + "server:prod": "yarn workspace server build && yarn build:web-plugins && cpx \"web/out/**\" \"server/build/renderer/\" && mkdir -p ./server/build/@janhq && cp -r ./plugins/* ./server/build/@janhq", + "start:server": "yarn server:prod && node server/build/main.js" }, "devDependencies": { "concurrently": "^8.2.1", diff --git a/plugins/data-plugin/module.ts b/plugins/data-plugin/module.ts index b5497e978..21878d0f7 100644 --- a/plugins/data-plugin/module.ts +++ b/plugins/data-plugin/module.ts @@ -16,7 +16,7 @@ const dbs: Record = {}; */ function createCollection(name: string, schema?: { [key: string]: any }): Promise { return new Promise((resolve) => { - const dbPath = path.join(app.getPath("userData"), "databases"); + const dbPath = path.join(appPath(), "databases"); if (!fs.existsSync(dbPath)) fs.mkdirSync(dbPath); const db = new PouchDB(`${path.join(dbPath, name)}`); dbs[name] = db; @@ -226,6 +226,13 @@ function findMany( .then((data) => data.docs); // Return documents } +function appPath() { + if (app) { + return app.getPath("userData"); + } + return process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share"); +} + module.exports = { createCollection, deleteCollection, diff --git a/plugins/inference-plugin/module.ts b/plugins/inference-plugin/module.ts index b851f0c4e..552936eaf 100644 --- a/plugins/inference-plugin/module.ts +++ b/plugins/inference-plugin/module.ts @@ -23,25 +23,22 @@ const initModel = (fileName) => { let binaryFolder = path.join(__dirname, "nitro"); // Current directory by default let binaryName; - if (process.platform === "win32") { - // Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries - binaryName = "nitro_start_windows.bat"; - } else if (process.platform === "darwin") { - // Mac OS platform - binaryName = - process.arch === "arm64" - ? "nitro_mac_arm64" - : "nitro_mac_intel"; - } else { - // Linux - // Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries - binaryName = "nitro_start_linux.sh"; // For other platforms - } + if (process.platform === "win32") { + // Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries + binaryName = "nitro_start_windows.bat"; + } else if (process.platform === "darwin") { + // Mac OS platform + binaryName = process.arch === "arm64" ? "nitro_mac_arm64" : "nitro_mac_intel"; + } else { + // Linux + // Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries + binaryName = "nitro_start_linux.sh"; // For other platforms + } const binaryPath = path.join(binaryFolder, binaryName); - // Execute the binary - subprocess = spawn(binaryPath, { cwd: binaryFolder }); + // Execute the binary + subprocess = spawn(binaryPath,["0.0.0.0", PORT], { cwd: binaryFolder }); // Handle subprocess output subprocess.stdout.on("data", (data) => { @@ -61,7 +58,7 @@ const initModel = (fileName) => { }) .then(() => tcpPortUsed.waitUntilUsed(PORT, 300, 30000)) .then(() => { - const llama_model_path = path.join(app.getPath("userData"), fileName); + const llama_model_path = path.join(appPath(), fileName); const config = { llama_model_path, @@ -107,6 +104,13 @@ function killSubprocess() { } } +function appPath() { + if (app) { + return app.getPath("userData"); + } + return process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share"); +} + module.exports = { initModel, killSubprocess, diff --git a/plugins/inference-plugin/nitro/nitro_start_linux.sh b/plugins/inference-plugin/nitro/nitro_start_linux.sh index 19ac36f21..bba993800 100755 --- a/plugins/inference-plugin/nitro/nitro_start_linux.sh +++ b/plugins/inference-plugin/nitro/nitro_start_linux.sh @@ -3,4 +3,4 @@ #!/bin/bash # Attempt to run the nitro_linux_amd64_cuda file and if it fails, run nitro_linux_amd64 -./nitro_linux_amd64_cuda || (echo "nitro_linux_amd64_cuda encountered an error, attempting to run nitro_linux_amd64..." && ./nitro_linux_amd64) +./nitro_linux_amd64_cuda "$@" || (echo "nitro_linux_amd64_cuda encountered an error, attempting to run nitro_linux_amd64..." && ./nitro_linux_amd64 "$@") diff --git a/plugins/model-management-plugin/index.ts b/plugins/model-management-plugin/index.ts index f20814fd3..43348b086 100644 --- a/plugins/model-management-plugin/index.ts +++ b/plugins/model-management-plugin/index.ts @@ -5,11 +5,56 @@ import { downloadFile, deleteFile, store, + EventName, + events } from "@janhq/core"; import { parseToModel } from "./helper"; -const downloadModel = (product) => +const downloadModel = (product) => { downloadFile(product.downloadUrl, product.fileName); + checkDownloadProgress(product.fileName); +} + +async function checkDownloadProgress(fileName: string) { + if (typeof window !== "undefined" && typeof (window as any).electronAPI === "undefined") { + const intervalId = setInterval(() => { + fetchDownloadProgress(fileName, intervalId); + }, 3000); + } +} + +async function fetchDownloadProgress(fileName: string, intervalId: NodeJS.Timeout): Promise { + const response = await fetch("/api/v1/downloadProgress", { + method: 'POST', + body: JSON.stringify({ fileName: fileName }), + headers: { 'Content-Type': 'application/json', 'Authorization': '' } + }); + + if (!response.ok) { + events.emit(EventName.OnDownloadError, null); + clearInterval(intervalId); + return; + } + const json = await response.json(); + if (isEmptyObject(json)) { + if (!fileName && intervalId) { + clearInterval(intervalId); + } + return Promise.resolve(""); + } + if (json.success === true) { + events.emit(EventName.OnDownloadSuccess, json); + clearInterval(intervalId); + return Promise.resolve(""); + } else { + events.emit(EventName.OnDownloadUpdate, json); + return Promise.resolve(json.fileName); + } +} + +function isEmptyObject(ojb: any): boolean { + return Object.keys(ojb).length === 0; +} const deleteModel = (path) => deleteFile(path); @@ -87,6 +132,7 @@ function getModelById(modelId: string): Promise { function onStart() { store.createCollection("models", {}); + fetchDownloadProgress(null, null).then((fileName: string) => fileName && checkDownloadProgress(fileName)); } // Register all the above functions and objects with the relevant extension points diff --git a/server/main.ts b/server/main.ts new file mode 100644 index 000000000..b98b4097c --- /dev/null +++ b/server/main.ts @@ -0,0 +1,179 @@ +import express, { Express, Request, Response, NextFunction } from 'express' +import cors from "cors"; +import { resolve } from "path"; +const fs = require("fs"); +const progress = require("request-progress"); +const path = require("path"); +const request = require("request"); + +// Create app dir +const userDataPath = appPath(); +if (!fs.existsSync(userDataPath)) fs.mkdirSync(userDataPath); + +interface ProgressState { + percent?: number; + speed?: number; + size?: { + total: number; + transferred: number; + }; + time?: { + elapsed: number; + remaining: number; + }; + success?: boolean | undefined; + fileName: string; +} + +const options: cors.CorsOptions = { origin: "*" }; +const requiredModules: Record = {}; +const port = process.env.PORT || 4000; +const dataDir = __dirname; +type DownloadProgress = Record; +const downloadProgress: DownloadProgress = {}; +const app: Express = express() +app.use(express.static(dataDir + '/renderer')) +app.use(cors(options)) +app.use(express.json()); + +/** + * Execute a plugin module function via API call + * + * @param modulePath path to module name to import + * @param method function name to execute. The methods "deleteFile" and "downloadFile" will call the server function {@link deleteFile}, {@link downloadFile} instead of the plugin function. + * @param args arguments to pass to the function + * @returns Promise + * + */ +app.post('/api/v1/invokeFunction', (req: Request, res: Response, next: NextFunction): void => { + const method = req.body["method"]; + const args = req.body["args"]; + switch (method) { + case "deleteFile": + deleteFile(args).then(() => res.json(Object())).catch((err: any) => next(err)); + break; + case "downloadFile": + downloadFile(args.downloadUrl, args.fileName).then(() => res.json(Object())).catch((err: any) => next(err)); + break; + default: + const result = invokeFunction(req.body["modulePath"], method, args) + if (typeof result === "undefined") { + res.json(Object()) + } else { + result?.then((result: any) => { + res.json(result) + }).catch((err: any) => next(err)); + } + } +}); + +app.post('/api/v1/downloadProgress', (req: Request, res: Response): void => { + const fileName = req.body["fileName"]; + if (fileName && downloadProgress[fileName]) { + res.json(downloadProgress[fileName]) + return; + } else { + const obj = downloadingFile(); + if (obj) { + res.json(obj) + return; + } + } + res.json(Object()); +}); + +app.use((err: Error, req: Request, res: Response, next: NextFunction): void => { + console.error("ErrorHandler", req.url, req.body, err); + res.status(500); + res.json({ error: err?.message ?? "Internal Server Error" }) +}); + +app.listen(port, () => console.log(`Application is running on port ${port}`)); + + +async function invokeFunction(modulePath: string, method: string, args: any): Promise { + console.log(modulePath, method, args); + const module = require(/* webpackIgnore: true */ path.join( + dataDir, + "", + modulePath + )); + requiredModules[modulePath] = module; + if (typeof module[method] === "function") { + return module[method](...args); + } else { + return Promise.resolve(); + } +} + +function downloadModel(downloadUrl: string, fileName: string): void { + const userDataPath = appPath(); + const destination = resolve(userDataPath, fileName); + console.log("Download file", fileName, "to", destination); + progress(request(downloadUrl), {}) + .on("progress", function (state: any) { + downloadProgress[fileName] = { + ...state, + fileName, + success: undefined + }; + console.log("downloading file", fileName, (state.percent * 100).toFixed(2) + '%'); + }) + .on("error", function (err: Error) { + downloadProgress[fileName] = { + ...downloadProgress[fileName], + success: false, + fileName: fileName, + }; + }) + .on("end", function () { + downloadProgress[fileName] = { + success: true, + fileName: fileName, + }; + }) + .pipe(fs.createWriteStream(destination)); +} + +function deleteFile(filePath: string): Promise { + const userDataPath = appPath(); + const fullPath = resolve(userDataPath, filePath); + return new Promise((resolve, reject) => { + fs.unlink(fullPath, function (err: any) { + if (err && err.code === "ENOENT") { + reject(Error(`File does not exist: ${err}`)); + } else if (err) { + reject(Error(`File delete error: ${err}`)); + } else { + console.log(`Delete file ${filePath} from ${fullPath}`) + resolve(); + } + }); + }) +} + +function downloadingFile(): ProgressState | undefined { + const obj = Object.values(downloadProgress).find(obj => obj && typeof obj.success === "undefined") + return obj +} + + +async function downloadFile(downloadUrl: string, fileName: string): Promise { + return new Promise((resolve, reject) => { + const obj = downloadingFile(); + if (obj) { + reject(Error(obj.fileName + " is being downloaded!")) + return; + }; + (async () => { + downloadModel(downloadUrl, fileName); + })().catch(e => { + console.error("downloadModel", fileName, e); + }); + resolve(); + }); +} + +function appPath(): string { + return process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + "/.local/share") +} \ No newline at end of file diff --git a/server/nodemon.json b/server/nodemon.json new file mode 100644 index 000000000..fa415fa52 --- /dev/null +++ b/server/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": [ + "main.ts" + ] +} \ No newline at end of file diff --git a/server/package.json b/server/package.json new file mode 100644 index 000000000..895cae2b9 --- /dev/null +++ b/server/package.json @@ -0,0 +1,26 @@ +{ + "name": "server", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "cors": "^2.8.5", + "electron": "^26.2.1", + "express": "^4.18.2", + "request": "^2.88.2", + "request-progress": "^3.0.0" + }, + "devDependencies": { + "@types/cors": "^2.8.14", + "@types/express": "^4.17.18", + "@types/node": "^20.8.2", + "nodemon": "^3.0.1", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + }, + "scripts": { + "build": "tsc --project ./", + "dev": "nodemon main.ts", + "prod": "node build/main.js" + } +} diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 000000000..a79afcdfe --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "noImplicitAny": true, + "sourceMap": true, + "strict": true, + "outDir": "./build", + "rootDir": "./", + "noEmitOnError": true, + "baseUrl": ".", + "allowJs": true, + "paths": { "*": ["node_modules/*"] }, + "typeRoots": ["node_modules/@types"], + "esModuleInterop": true + }, + "include": ["./**/*.ts"], + "exclude": ["core", "build", "dist", "tests"] + } \ No newline at end of file diff --git a/web/app/_components/SidebarFooter/index.tsx b/web/app/_components/SidebarFooter/index.tsx index 42d7c3f3b..1b2430a5f 100644 --- a/web/app/_components/SidebarFooter/index.tsx +++ b/web/app/_components/SidebarFooter/index.tsx @@ -6,14 +6,14 @@ const SidebarFooter: React.FC = () => ( - window.electronAPI?.openExternalUrl('https://discord.gg/AsJ8krTT3N') + window.coreAPI?.openExternalUrl('https://discord.gg/AsJ8krTT3N') } className="flex-1" /> - window.electronAPI?.openExternalUrl('https://twitter.com/janhq_') + window.coreAPI?.openExternalUrl('https://twitter.com/janhq_') } className="flex-1" /> diff --git a/web/app/page.tsx b/web/app/page.tsx index d7bfca79b..2d23fc8e5 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -18,6 +18,9 @@ import React from 'react' import BaseLayout from '@containers/Layout' +import { ToastContainer } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; + const Page: React.FC = () => { const viewState = useAtomValue(getMainViewStateAtom) @@ -53,7 +56,10 @@ const Page: React.FC = () => { break } - return {children} + return + {children} +
+
} export default Page diff --git a/web/containers/Providers/index.tsx b/web/containers/Providers/index.tsx index 623ef9a5e..e8c185d79 100644 --- a/web/containers/Providers/index.tsx +++ b/web/containers/Providers/index.tsx @@ -57,7 +57,7 @@ const Providers = (props: PropsWithChildren) => { useEffect(() => { if (setupCore) { // Electron - if (window && window.electronAPI) { + if (window && window.coreAPI) { setupPE() } else { // Host diff --git a/web/containers/Sidebar/Left.tsx b/web/containers/Sidebar/Left.tsx index e837058f3..52500222e 100644 --- a/web/containers/Sidebar/Left.tsx +++ b/web/containers/Sidebar/Left.tsx @@ -170,7 +170,7 @@ export const SidebarLeft = () => {