Merge branch 'main' into jan-539-restructure-docs
This commit is contained in:
commit
5533680da1
6
.github/workflows/jan-electron-build.yml
vendored
6
.github/workflows/jan-electron-build.yml
vendored
@ -52,7 +52,7 @@ jobs:
|
|||||||
- name: Install yarn dependencies
|
- name: Install yarn dependencies
|
||||||
run: |
|
run: |
|
||||||
yarn install
|
yarn install
|
||||||
yarn build:pull-plugins
|
yarn build:plugins
|
||||||
env:
|
env:
|
||||||
APP_PATH: "."
|
APP_PATH: "."
|
||||||
DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
|
DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
|
||||||
@ -104,7 +104,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
yarn config set network-timeout 300000
|
yarn config set network-timeout 300000
|
||||||
yarn install
|
yarn install
|
||||||
yarn build:pull-plugins
|
yarn build:plugins
|
||||||
|
|
||||||
- name: Build and publish app
|
- name: Build and publish app
|
||||||
run: |
|
run: |
|
||||||
@ -153,7 +153,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
yarn config set network-timeout 300000
|
yarn config set network-timeout 300000
|
||||||
yarn install
|
yarn install
|
||||||
yarn build:pull-plugins
|
yarn build:plugins
|
||||||
|
|
||||||
- name: Build and publish app
|
- name: Build and publish app
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@ -44,9 +44,10 @@ jobs:
|
|||||||
- name: Linter and test
|
- name: Linter and test
|
||||||
run: |
|
run: |
|
||||||
yarn config set network-timeout 300000
|
yarn config set network-timeout 300000
|
||||||
|
yarn build:core
|
||||||
yarn install
|
yarn install
|
||||||
yarn lint
|
yarn lint
|
||||||
yarn build:pull-plugins
|
yarn build:plugins
|
||||||
yarn build:test
|
yarn build:test
|
||||||
yarn test
|
yarn test
|
||||||
env:
|
env:
|
||||||
@ -75,8 +76,9 @@ jobs:
|
|||||||
- name: Linter and test
|
- name: Linter and test
|
||||||
run: |
|
run: |
|
||||||
yarn config set network-timeout 300000
|
yarn config set network-timeout 300000
|
||||||
|
yarn build:core
|
||||||
yarn install
|
yarn install
|
||||||
yarn build:pull-plugins
|
yarn build:plugins
|
||||||
yarn build:test-win32
|
yarn build:test-win32
|
||||||
yarn test
|
yarn test
|
||||||
|
|
||||||
@ -103,7 +105,8 @@ jobs:
|
|||||||
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
|
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
|
||||||
echo -e "Display ID: $DISPLAY"
|
echo -e "Display ID: $DISPLAY"
|
||||||
yarn config set network-timeout 300000
|
yarn config set network-timeout 300000
|
||||||
|
yarn build:core
|
||||||
yarn install
|
yarn install
|
||||||
yarn build:pull-plugins
|
yarn build:plugins
|
||||||
yarn build:test-linux
|
yarn build:test-linux
|
||||||
yarn test
|
yarn test
|
||||||
18
.github/workflows/jan-plugins.yml
vendored
18
.github/workflows/jan-plugins.yml
vendored
@ -54,6 +54,11 @@ jobs:
|
|||||||
for dir in $(cat /tmp/change_dir.txt)
|
for dir in $(cat /tmp/change_dir.txt)
|
||||||
do
|
do
|
||||||
echo "$dir"
|
echo "$dir"
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
echo "Directory $dir does not exist, plugin might be removed, skipping..."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
# Extract current version
|
# Extract current version
|
||||||
current_version=$(jq -r '.version' $dir/package.json)
|
current_version=$(jq -r '.version' $dir/package.json)
|
||||||
|
|
||||||
@ -80,6 +85,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: "20.x"
|
node-version: "20.x"
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
- name: Build core module
|
||||||
|
run: |
|
||||||
|
cd core
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
|
||||||
- name: Publish npm packages
|
- name: Publish npm packages
|
||||||
run: |
|
run: |
|
||||||
@ -87,6 +97,10 @@ jobs:
|
|||||||
for dir in $(cat /tmp/change_dir.txt)
|
for dir in $(cat /tmp/change_dir.txt)
|
||||||
do
|
do
|
||||||
echo $dir
|
echo $dir
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
echo "Directory $dir does not exist, plugin might be removed, skipping..."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
cd $dir
|
cd $dir
|
||||||
npm install
|
npm install
|
||||||
if [[ $dir == 'data-plugin' ]]; then
|
if [[ $dir == 'data-plugin' ]]; then
|
||||||
@ -112,6 +126,10 @@ jobs:
|
|||||||
for dir in $(cat /tmp/change_dir.txt)
|
for dir in $(cat /tmp/change_dir.txt)
|
||||||
do
|
do
|
||||||
echo "$dir"
|
echo "$dir"
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
echo "Directory $dir does not exist, plugin might be removed, skipping..."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
version=$(jq -r '.version' plugins/$dir/package.json)
|
version=$(jq -r '.version' plugins/$dir/package.json)
|
||||||
git config --global user.email "service@jan.ai"
|
git config --global user.email "service@jan.ai"
|
||||||
git config --global user.name "Service Account"
|
git config --global user.name "Service Account"
|
||||||
|
|||||||
12
README.md
12
README.md
@ -96,7 +96,7 @@ Note: This instruction is tested on MacOS only.
|
|||||||
|
|
||||||
1. **Clone the Repository:**
|
1. **Clone the Repository:**
|
||||||
|
|
||||||
```
|
```bash
|
||||||
git clone https://github.com/janhq/jan
|
git clone https://github.com/janhq/jan
|
||||||
git checkout DESIRED_BRANCH
|
git checkout DESIRED_BRANCH
|
||||||
cd jan
|
cd jan
|
||||||
@ -104,9 +104,12 @@ Note: This instruction is tested on MacOS only.
|
|||||||
|
|
||||||
2. **Install dependencies:**
|
2. **Install dependencies:**
|
||||||
|
|
||||||
```
|
```bash
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
|
# Build core module
|
||||||
|
yarn build:core
|
||||||
|
|
||||||
# Packing base plugins
|
# Packing base plugins
|
||||||
yarn build:plugins
|
yarn build:plugins
|
||||||
```
|
```
|
||||||
@ -127,6 +130,11 @@ Note: This instruction is tested on MacOS only.
|
|||||||
git clone https://github.com/janhq/jan
|
git clone https://github.com/janhq/jan
|
||||||
cd jan
|
cd jan
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
|
# Build core module
|
||||||
|
yarn build:core
|
||||||
|
|
||||||
|
# Package base plugins
|
||||||
yarn build:plugins
|
yarn build:plugins
|
||||||
|
|
||||||
# Build the app
|
# Build the app
|
||||||
|
|||||||
304
core/index.ts
304
core/index.ts
@ -1,304 +0,0 @@
|
|||||||
/**
|
|
||||||
* CoreService exports
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type CoreService =
|
|
||||||
| StoreService
|
|
||||||
| DataService
|
|
||||||
| InferenceService
|
|
||||||
| ModelManagementService
|
|
||||||
| SystemMonitoringService
|
|
||||||
| PreferenceService
|
|
||||||
| PluginService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the available methods for the StoreService.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
export enum StoreService {
|
|
||||||
/**
|
|
||||||
* Creates a new collection in the database store.
|
|
||||||
*/
|
|
||||||
CreateCollection = "createCollection",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an existing collection from the database store.
|
|
||||||
*/
|
|
||||||
DeleteCollection = "deleteCollection",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a new value into an existing collection in the database store.
|
|
||||||
*/
|
|
||||||
InsertOne = "insertOne",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing value in an existing collection in the database store.
|
|
||||||
*/
|
|
||||||
UpdateOne = "updateOne",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates multiple records in a collection in the database store.
|
|
||||||
*/
|
|
||||||
UpdateMany = "updateMany",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an existing value from an existing collection in the database store.
|
|
||||||
*/
|
|
||||||
DeleteOne = "deleteOne",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete multiple records in a collection in the database store.
|
|
||||||
*/
|
|
||||||
DeleteMany = "deleteMany",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve multiple records from a collection in the data store
|
|
||||||
*/
|
|
||||||
FindMany = "findMany",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a record from a collection in the data store.
|
|
||||||
*/
|
|
||||||
FindOne = "findOne",
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DataService exports.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
export enum DataService {
|
|
||||||
/**
|
|
||||||
* Gets a list of conversations.
|
|
||||||
*/
|
|
||||||
GetConversations = "getConversations",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new conversation.
|
|
||||||
*/
|
|
||||||
CreateConversation = "createConversation",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing conversation.
|
|
||||||
*/
|
|
||||||
UpdateConversation = "updateConversation",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an existing conversation.
|
|
||||||
*/
|
|
||||||
DeleteConversation = "deleteConversation",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new message in an existing conversation.
|
|
||||||
*/
|
|
||||||
CreateMessage = "createMessage",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing message in an existing conversation.
|
|
||||||
*/
|
|
||||||
UpdateMessage = "updateMessage",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of messages for an existing conversation.
|
|
||||||
*/
|
|
||||||
GetConversationMessages = "getConversationMessages",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a conversation matching an ID.
|
|
||||||
*/
|
|
||||||
GetConversationById = "getConversationById",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new conversation using the prompt instruction.
|
|
||||||
*/
|
|
||||||
CreateBot = "createBot",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets all created bots.
|
|
||||||
*/
|
|
||||||
GetBots = "getBots",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a bot matching an ID.
|
|
||||||
*/
|
|
||||||
GetBotById = "getBotById",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a bot matching an ID.
|
|
||||||
*/
|
|
||||||
DeleteBot = "deleteBot",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a bot matching an ID.
|
|
||||||
*/
|
|
||||||
UpdateBot = "updateBot",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the plugin manifest.
|
|
||||||
*/
|
|
||||||
GetPluginManifest = "getPluginManifest",
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* InferenceService exports.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
export enum InferenceService {
|
|
||||||
/**
|
|
||||||
* Initializes a model for inference.
|
|
||||||
*/
|
|
||||||
InitModel = "initModel",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops a running inference model.
|
|
||||||
*/
|
|
||||||
StopModel = "stopModel",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Single inference response.
|
|
||||||
*/
|
|
||||||
InferenceRequest = "inferenceRequest",
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ModelManagementService exports.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
export enum ModelManagementService {
|
|
||||||
/**
|
|
||||||
* Deletes a downloaded model.
|
|
||||||
*/
|
|
||||||
DeleteModel = "deleteModel",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads a model from the server.
|
|
||||||
*/
|
|
||||||
DownloadModel = "downloadModel",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets configued models from the database.
|
|
||||||
*/
|
|
||||||
GetConfiguredModels = "getConfiguredModels",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores a model in the database.
|
|
||||||
*/
|
|
||||||
StoreModel = "storeModel",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the finished download time for a model in the database.
|
|
||||||
*/
|
|
||||||
UpdateFinishedDownloadAt = "updateFinishedDownloadAt",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a list of finished download models from the database.
|
|
||||||
*/
|
|
||||||
GetFinishedDownloadModels = "getFinishedDownloadModels",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a download model from the database.
|
|
||||||
*/
|
|
||||||
DeleteDownloadModel = "deleteDownloadModel",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a model by its ID from the database.
|
|
||||||
*/
|
|
||||||
GetModelById = "getModelById",
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PreferenceService exports.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
export enum PreferenceService {
|
|
||||||
/**
|
|
||||||
* The experiment component for which preferences are being managed.
|
|
||||||
*/
|
|
||||||
ExperimentComponent = "experimentComponent",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the plugin preferences.
|
|
||||||
*/
|
|
||||||
PluginPreferences = "pluginPreferences",
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SystemMonitoringService exports.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
export enum SystemMonitoringService {
|
|
||||||
/**
|
|
||||||
* Gets information about system resources.
|
|
||||||
*/
|
|
||||||
GetResourcesInfo = "getResourcesInfo",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current system load.
|
|
||||||
*/
|
|
||||||
GetCurrentLoad = "getCurrentLoad",
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PluginService exports.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
export enum PluginService {
|
|
||||||
/**
|
|
||||||
* The plugin is being started.
|
|
||||||
*/
|
|
||||||
OnStart = "pluginOnStart",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The plugin is being started.
|
|
||||||
*/
|
|
||||||
OnPreferencesUpdate = "pluginPreferencesUpdate",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The plugin is being stopped.
|
|
||||||
*/
|
|
||||||
OnStop = "pluginOnStop",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The plugin is being destroyed.
|
|
||||||
*/
|
|
||||||
OnDestroy = "pluginOnDestroy",
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store module exports.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
export { store } from "./store";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated This object is deprecated and should not be used.
|
|
||||||
* Use individual functions instead.
|
|
||||||
*/
|
|
||||||
export { core } from "./core";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Core module exports.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
export {
|
|
||||||
RegisterExtensionPoint,
|
|
||||||
deleteFile,
|
|
||||||
downloadFile,
|
|
||||||
invokePluginFunc,
|
|
||||||
} from "./core";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Events module exports.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
export {
|
|
||||||
events,
|
|
||||||
EventName,
|
|
||||||
NewMessageRequest,
|
|
||||||
NewMessageResponse,
|
|
||||||
} from "./events";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preferences module exports.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
export { preferences } from "./preferences";
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/core",
|
"name": "@janhq/core",
|
||||||
"version": "0.1.9",
|
"version": "0.1.10",
|
||||||
"description": "Plugin core lib",
|
"description": "Plugin core lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"jan",
|
"jan",
|
||||||
@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./lib/index.js",
|
".": "./lib/index.js",
|
||||||
"./store": "./lib/store.js"
|
"./plugin": "./lib/plugins/index.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"lib",
|
"lib",
|
||||||
|
|||||||
@ -1,84 +0,0 @@
|
|||||||
import { store } from "./store";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the specified preference for the specified plugin.
|
|
||||||
*
|
|
||||||
* @param pluginName The name of the plugin.
|
|
||||||
* @param preferenceKey The key of the preference.
|
|
||||||
* @returns A promise that resolves to the value of the preference.
|
|
||||||
*/
|
|
||||||
function get(pluginName: string, preferenceKey: string): Promise<any> {
|
|
||||||
return store
|
|
||||||
.createCollection("preferences", {})
|
|
||||||
.then(() => store.findOne("preferences", `${pluginName}.${preferenceKey}`))
|
|
||||||
.then((doc) => doc?.value ?? "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the value of the specified preference for the specified plugin.
|
|
||||||
*
|
|
||||||
* @param pluginName The name of the plugin.
|
|
||||||
* @param preferenceKey The key of the preference.
|
|
||||||
* @param value The value of the preference.
|
|
||||||
* @returns A promise that resolves when the preference has been set.
|
|
||||||
*/
|
|
||||||
function set(pluginName: string, preferenceKey: string, value: any): Promise<any> {
|
|
||||||
return store
|
|
||||||
.createCollection("preferences", {})
|
|
||||||
.then(() =>
|
|
||||||
store
|
|
||||||
.findOne("preferences", `${pluginName}.${preferenceKey}`)
|
|
||||||
.then((doc) =>
|
|
||||||
doc
|
|
||||||
? store.updateOne("preferences", `${pluginName}.${preferenceKey}`, { value })
|
|
||||||
: store.insertOne("preferences", { _id: `${pluginName}.${preferenceKey}`, value })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears all preferences for the specified plugin.
|
|
||||||
*
|
|
||||||
* @param pluginName The name of the plugin.
|
|
||||||
* @returns A promise that resolves when the preferences have been cleared.
|
|
||||||
*/
|
|
||||||
function clear(pluginName: string): Promise<void> {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a preference with the specified default value.
|
|
||||||
*
|
|
||||||
* @param register The function to use for registering the preference.
|
|
||||||
* @param pluginName The name of the plugin.
|
|
||||||
* @param preferenceKey The key of the preference.
|
|
||||||
* @param preferenceName The name of the preference.
|
|
||||||
* @param preferenceDescription The description of the preference.
|
|
||||||
* @param defaultValue The default value of the preference.
|
|
||||||
*/
|
|
||||||
function registerPreferences<T>(
|
|
||||||
register: Function,
|
|
||||||
pluginName: string,
|
|
||||||
preferenceKey: string,
|
|
||||||
preferenceName: string,
|
|
||||||
preferenceDescription: string,
|
|
||||||
defaultValue: T
|
|
||||||
) {
|
|
||||||
register("PluginPreferences", `${pluginName}.${preferenceKey}`, () => ({
|
|
||||||
pluginName,
|
|
||||||
preferenceKey,
|
|
||||||
preferenceName,
|
|
||||||
preferenceDescription,
|
|
||||||
defaultValue,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object that provides methods for getting, setting, and clearing preferences.
|
|
||||||
*/
|
|
||||||
export const preferences = {
|
|
||||||
get,
|
|
||||||
set,
|
|
||||||
clear,
|
|
||||||
registerPreferences,
|
|
||||||
};
|
|
||||||
@ -7,7 +7,23 @@
|
|||||||
* @returns Promise<any>
|
* @returns Promise<any>
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const invokePluginFunc: (plugin: string, method: string, ...args: any[]) => Promise<any> = (plugin, method, ...args) =>
|
const executeOnMain: (
|
||||||
|
plugin: string,
|
||||||
|
method: string,
|
||||||
|
...args: any[]
|
||||||
|
) => Promise<any> = (plugin, method, ...args) =>
|
||||||
|
window.coreAPI?.invokePluginFunc(plugin, method, ...args) ??
|
||||||
|
window.electronAPI?.invokePluginFunc(plugin, method, ...args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This object is deprecated and should not be used.
|
||||||
|
* Use individual functions instead.
|
||||||
|
*/
|
||||||
|
const invokePluginFunc: (
|
||||||
|
plugin: string,
|
||||||
|
method: string,
|
||||||
|
...args: any[]
|
||||||
|
) => Promise<any> = (plugin, method, ...args) =>
|
||||||
window.coreAPI?.invokePluginFunc(plugin, method, ...args) ??
|
window.coreAPI?.invokePluginFunc(plugin, method, ...args) ??
|
||||||
window.electronAPI?.invokePluginFunc(plugin, method, ...args);
|
window.electronAPI?.invokePluginFunc(plugin, method, ...args);
|
||||||
|
|
||||||
@ -17,8 +33,12 @@ const invokePluginFunc: (plugin: string, method: string, ...args: any[]) => Prom
|
|||||||
* @param {string} fileName - The name to use for the downloaded file.
|
* @param {string} fileName - The name to use for the downloaded file.
|
||||||
* @returns {Promise<any>} A promise that resolves when the file is downloaded.
|
* @returns {Promise<any>} A promise that resolves when the file is downloaded.
|
||||||
*/
|
*/
|
||||||
const downloadFile: (url: string, fileName: string) => Promise<any> = (url, fileName) =>
|
const downloadFile: (url: string, fileName: string) => Promise<any> = (
|
||||||
window.coreAPI?.downloadFile(url, fileName) ?? window.electronAPI?.downloadFile(url, fileName);
|
url,
|
||||||
|
fileName
|
||||||
|
) =>
|
||||||
|
window.coreAPI?.downloadFile(url, fileName) ??
|
||||||
|
window.electronAPI?.downloadFile(url, fileName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a file from the local file system.
|
* Deletes a file from the local file system.
|
||||||
@ -51,6 +71,7 @@ export type RegisterExtensionPoint = (
|
|||||||
*/
|
*/
|
||||||
export const core = {
|
export const core = {
|
||||||
invokePluginFunc,
|
invokePluginFunc,
|
||||||
|
executeOnMain,
|
||||||
downloadFile,
|
downloadFile,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
appDataPath,
|
appDataPath,
|
||||||
@ -59,4 +80,10 @@ export const core = {
|
|||||||
/**
|
/**
|
||||||
* Functions exports
|
* Functions exports
|
||||||
*/
|
*/
|
||||||
export { invokePluginFunc, downloadFile, deleteFile, appDataPath };
|
export {
|
||||||
|
invokePluginFunc,
|
||||||
|
executeOnMain,
|
||||||
|
downloadFile,
|
||||||
|
deleteFile,
|
||||||
|
appDataPath,
|
||||||
|
};
|
||||||
@ -6,12 +6,16 @@ export enum EventName {
|
|||||||
OnNewMessageRequest = "onNewMessageRequest",
|
OnNewMessageRequest = "onNewMessageRequest",
|
||||||
OnNewMessageResponse = "onNewMessageResponse",
|
OnNewMessageResponse = "onNewMessageResponse",
|
||||||
OnMessageResponseUpdate = "onMessageResponseUpdate",
|
OnMessageResponseUpdate = "onMessageResponseUpdate",
|
||||||
OnMessageResponseFinished = "OnMessageResponseFinished",
|
OnMessageResponseFinished = "onMessageResponseFinished",
|
||||||
OnDownloadUpdate = "onDownloadUpdate",
|
OnDownloadUpdate = "onDownloadUpdate",
|
||||||
OnDownloadSuccess = "onDownloadSuccess",
|
OnDownloadSuccess = "onDownloadSuccess",
|
||||||
OnDownloadError = "onDownloadError",
|
OnDownloadError = "onDownloadError",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MessageHistory = {
|
||||||
|
role: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* The `NewMessageRequest` type defines the shape of a new message request object.
|
* The `NewMessageRequest` type defines the shape of a new message request object.
|
||||||
*/
|
*/
|
||||||
@ -23,6 +27,7 @@ export type NewMessageRequest = {
|
|||||||
message?: string;
|
message?: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
|
history?: MessageHistory[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
57
core/src/fs.ts
Normal file
57
core/src/fs.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Writes data to a file at the specified path.
|
||||||
|
* @param {string} path - The path to the file.
|
||||||
|
* @param {string} data - The data to write to the file.
|
||||||
|
* @returns {Promise<any>} A Promise that resolves when the file is written successfully.
|
||||||
|
*/
|
||||||
|
const writeFile: (path: string, data: string) => Promise<any> = (path, data) =>
|
||||||
|
window.coreAPI?.writeFile(path, data) ??
|
||||||
|
window.electronAPI?.writeFile(path, data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the contents of a file at the specified path.
|
||||||
|
* @param {string} path - The path of the file to read.
|
||||||
|
* @returns {Promise<any>} A Promise that resolves with the contents of the file.
|
||||||
|
*/
|
||||||
|
const readFile: (path: string) => Promise<any> = (path) =>
|
||||||
|
window.coreAPI?.readFile(path) ?? window.electronAPI?.readFile(path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List the directory files
|
||||||
|
* @param {string} path - The path of the directory to list files.
|
||||||
|
* @returns {Promise<any>} A Promise that resolves with the contents of the directory.
|
||||||
|
*/
|
||||||
|
const listFiles: (path: string) => Promise<any> = (path) =>
|
||||||
|
window.coreAPI?.listFiles(path) ?? window.electronAPI?.listFiles(path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a directory at the specified path.
|
||||||
|
* @param {string} path - The path of the directory to create.
|
||||||
|
* @returns {Promise<any>} A Promise that resolves when the directory is created successfully.
|
||||||
|
*/
|
||||||
|
const mkdir: (path: string) => Promise<any> = (path) =>
|
||||||
|
window.coreAPI?.mkdir(path) ?? window.electronAPI?.mkdir(path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a directory at the specified path.
|
||||||
|
* @param {string} path - The path of the directory to remove.
|
||||||
|
* @returns {Promise<any>} A Promise that resolves when the directory is removed successfully.
|
||||||
|
*/
|
||||||
|
const rmdir: (path: string) => Promise<any> = (path) =>
|
||||||
|
window.coreAPI?.rmdir(path) ?? window.electronAPI?.rmdir(path);
|
||||||
|
/**
|
||||||
|
* Deletes a file from the local file system.
|
||||||
|
* @param {string} path - The path of the file to delete.
|
||||||
|
* @returns {Promise<any>} A Promise that resolves when the file is deleted.
|
||||||
|
*/
|
||||||
|
const deleteFile: (path: string) => Promise<any> = (path) =>
|
||||||
|
window.coreAPI?.deleteFile(path) ?? window.electronAPI?.deleteFile(path);
|
||||||
|
|
||||||
|
export const fs = {
|
||||||
|
writeFile,
|
||||||
|
readFile,
|
||||||
|
listFiles,
|
||||||
|
mkdir,
|
||||||
|
rmdir,
|
||||||
|
deleteFile,
|
||||||
|
};
|
||||||
40
core/src/index.ts
Normal file
40
core/src/index.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* @deprecated This object is deprecated and should not be used.
|
||||||
|
* Use individual functions instead.
|
||||||
|
*/
|
||||||
|
export { core, deleteFile, invokePluginFunc } from "./core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core module exports.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
export { downloadFile, executeOnMain } from "./core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events module exports.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
export { events } from "./events";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events types exports.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
export {
|
||||||
|
EventName,
|
||||||
|
NewMessageRequest,
|
||||||
|
NewMessageResponse,
|
||||||
|
MessageHistory,
|
||||||
|
} from "./events";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filesystem module exports.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
export { fs } from "./fs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin base module export.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
export { JanPlugin, PluginType } from "./plugin";
|
||||||
13
core/src/plugin.ts
Normal file
13
core/src/plugin.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export enum PluginType {
|
||||||
|
Conversational = "conversational",
|
||||||
|
Inference = "inference",
|
||||||
|
Preference = "preference",
|
||||||
|
SystemMonitoring = "systemMonitoring",
|
||||||
|
Model = "model",
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class JanPlugin {
|
||||||
|
abstract type(): PluginType;
|
||||||
|
abstract onLoad(): void;
|
||||||
|
abstract onUnload(): void;
|
||||||
|
}
|
||||||
32
core/src/plugins/conversational.ts
Normal file
32
core/src/plugins/conversational.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { JanPlugin } from "../plugin";
|
||||||
|
import { Conversation } from "../types/index";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class for conversational plugins.
|
||||||
|
* @abstract
|
||||||
|
* @extends JanPlugin
|
||||||
|
*/
|
||||||
|
export abstract class ConversationalPlugin extends JanPlugin {
|
||||||
|
/**
|
||||||
|
* Returns a list of conversations.
|
||||||
|
* @abstract
|
||||||
|
* @returns {Promise<any[]>} A promise that resolves to an array of conversations.
|
||||||
|
*/
|
||||||
|
abstract getConversations(): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a conversation.
|
||||||
|
* @abstract
|
||||||
|
* @param {Conversation} conversation - The conversation to save.
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the conversation is saved.
|
||||||
|
*/
|
||||||
|
abstract saveConversation(conversation: Conversation): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a conversation.
|
||||||
|
* @abstract
|
||||||
|
* @param {string} conversationId - The ID of the conversation to delete.
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the conversation is deleted.
|
||||||
|
*/
|
||||||
|
abstract deleteConversation(conversationId: string): Promise<void>;
|
||||||
|
}
|
||||||
20
core/src/plugins/index.ts
Normal file
20
core/src/plugins/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Conversational plugin. Persists and retrieves conversations.
|
||||||
|
* @module
|
||||||
|
*/
|
||||||
|
export { ConversationalPlugin } from "./conversational";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inference plugin. Start, stop and inference models.
|
||||||
|
*/
|
||||||
|
export { InferencePlugin } from "./inference";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitoring plugin for system monitoring.
|
||||||
|
*/
|
||||||
|
export { MonitoringPlugin } from "./monitoring";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model plugin for managing models.
|
||||||
|
*/
|
||||||
|
export { ModelPlugin } from "./model";
|
||||||
25
core/src/plugins/inference.ts
Normal file
25
core/src/plugins/inference.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { NewMessageRequest } from "../events";
|
||||||
|
import { JanPlugin } from "../plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class representing an Inference Plugin for Jan.
|
||||||
|
*/
|
||||||
|
export abstract class InferencePlugin extends JanPlugin {
|
||||||
|
/**
|
||||||
|
* Initializes the model for the plugin.
|
||||||
|
* @param modelFileName - The name of the file containing the model.
|
||||||
|
*/
|
||||||
|
abstract initModel(modelFileName: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the model for the plugin.
|
||||||
|
*/
|
||||||
|
abstract stopModel(): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes an inference request.
|
||||||
|
* @param data - The data for the inference request.
|
||||||
|
* @returns The result of the inference request.
|
||||||
|
*/
|
||||||
|
abstract inferenceRequest(data: NewMessageRequest): Promise<any>;
|
||||||
|
}
|
||||||
44
core/src/plugins/model.ts
Normal file
44
core/src/plugins/model.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Represents a plugin for managing machine learning models.
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
|
import { JanPlugin } from "../plugin";
|
||||||
|
import { Model, ModelCatalog } from "../types/index";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract class representing a plugin for managing machine learning models.
|
||||||
|
*/
|
||||||
|
export abstract class ModelPlugin extends JanPlugin {
|
||||||
|
/**
|
||||||
|
* Downloads a model.
|
||||||
|
* @param model - The model to download.
|
||||||
|
* @returns A Promise that resolves when the model has been downloaded.
|
||||||
|
*/
|
||||||
|
abstract downloadModel(model: Model): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a model.
|
||||||
|
* @param filePath - The file path of the model to delete.
|
||||||
|
* @returns A Promise that resolves when the model has been deleted.
|
||||||
|
*/
|
||||||
|
abstract deleteModel(filePath: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a model.
|
||||||
|
* @param model - The model to save.
|
||||||
|
* @returns A Promise that resolves when the model has been saved.
|
||||||
|
*/
|
||||||
|
abstract saveModel(model: Model): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of downloaded models.
|
||||||
|
* @returns A Promise that resolves with an array of downloaded models.
|
||||||
|
*/
|
||||||
|
abstract getDownloadedModels(): Promise<Model[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of configured models.
|
||||||
|
* @returns A Promise that resolves with an array of configured models.
|
||||||
|
*/
|
||||||
|
abstract getConfiguredModels(): Promise<ModelCatalog[]>;
|
||||||
|
}
|
||||||
19
core/src/plugins/monitoring.ts
Normal file
19
core/src/plugins/monitoring.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { JanPlugin } from "../plugin";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class for monitoring plugins.
|
||||||
|
* @extends JanPlugin
|
||||||
|
*/
|
||||||
|
export abstract class MonitoringPlugin extends JanPlugin {
|
||||||
|
/**
|
||||||
|
* Returns information about the system resources.
|
||||||
|
* @returns {Promise<any>} A promise that resolves with the system resources information.
|
||||||
|
*/
|
||||||
|
abstract getResourcesInfo(): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current system load.
|
||||||
|
* @returns {Promise<any>} A promise that resolves with the current system load.
|
||||||
|
*/
|
||||||
|
abstract getCurrentLoad(): Promise<any>;
|
||||||
|
}
|
||||||
91
core/src/types/index.ts
Normal file
91
core/src/types/index.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
export interface Conversation {
|
||||||
|
_id: string;
|
||||||
|
modelId?: string;
|
||||||
|
botId?: string;
|
||||||
|
name: string;
|
||||||
|
message?: string;
|
||||||
|
summary?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
messages: Message[];
|
||||||
|
}
|
||||||
|
export interface Message {
|
||||||
|
message?: string;
|
||||||
|
user?: string;
|
||||||
|
_id: string;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Model {
|
||||||
|
/**
|
||||||
|
* Combination of owner and model name.
|
||||||
|
* Being used as file name. MUST be unique.
|
||||||
|
*/
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
quantMethod: string;
|
||||||
|
bits: number;
|
||||||
|
size: number;
|
||||||
|
maxRamRequired: number;
|
||||||
|
usecase: string;
|
||||||
|
downloadLink: string;
|
||||||
|
modelFile?: string;
|
||||||
|
/**
|
||||||
|
* For tracking download info
|
||||||
|
*/
|
||||||
|
startDownloadAt?: number;
|
||||||
|
finishDownloadAt?: number;
|
||||||
|
productId: string;
|
||||||
|
productName: string;
|
||||||
|
shortDescription: string;
|
||||||
|
longDescription: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
author: string;
|
||||||
|
version: string;
|
||||||
|
modelUrl: string;
|
||||||
|
createdAt: number;
|
||||||
|
updatedAt?: number;
|
||||||
|
status: string;
|
||||||
|
releaseDate: number;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
export interface ModelCatalog {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
shortDescription: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
longDescription: string;
|
||||||
|
author: string;
|
||||||
|
version: string;
|
||||||
|
modelUrl: string;
|
||||||
|
createdAt: number;
|
||||||
|
updatedAt?: number;
|
||||||
|
status: string;
|
||||||
|
releaseDate: number;
|
||||||
|
tags: string[];
|
||||||
|
availableVersions: ModelVersion[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Model type which will be stored in the database
|
||||||
|
*/
|
||||||
|
export type ModelVersion = {
|
||||||
|
/**
|
||||||
|
* Combination of owner and model name.
|
||||||
|
* Being used as file name. Should be unique.
|
||||||
|
*/
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
quantMethod: string;
|
||||||
|
bits: number;
|
||||||
|
size: number;
|
||||||
|
maxRamRequired: number;
|
||||||
|
usecase: string;
|
||||||
|
downloadLink: string;
|
||||||
|
productId: string;
|
||||||
|
/**
|
||||||
|
* For tracking download state
|
||||||
|
*/
|
||||||
|
startDownloadAt?: number;
|
||||||
|
finishDownloadAt?: number;
|
||||||
|
};
|
||||||
129
core/store.ts
129
core/store.ts
@ -1,129 +0,0 @@
|
|||||||
/**
|
|
||||||
* Creates, reads, updates, and deletes data in a data store.
|
|
||||||
* @module
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new collection in the data store.
|
|
||||||
* @param {string} name - The name of the collection to create.
|
|
||||||
* @param { [key: string]: any } schema - schema of the collection to create, include fields and their types
|
|
||||||
* @returns {Promise<void>} A promise that resolves when the collection is created.
|
|
||||||
*/
|
|
||||||
function createCollection(
|
|
||||||
name: string,
|
|
||||||
schema: { [key: string]: any }
|
|
||||||
): Promise<void> {
|
|
||||||
return window.corePlugin?.store?.createCollection(name, schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a collection from the data store.
|
|
||||||
* @param {string} name - The name of the collection to delete.
|
|
||||||
* @returns {Promise<void>} A promise that resolves when the collection is deleted.
|
|
||||||
*/
|
|
||||||
function deleteCollection(name: string): Promise<void> {
|
|
||||||
return window.corePlugin?.store?.deleteCollection(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts a value into a collection in the data store.
|
|
||||||
* @param {string} collectionName - The name of the collection to insert the value into.
|
|
||||||
* @param {any} value - The value to insert into the collection.
|
|
||||||
* @returns {Promise<any>} A promise that resolves with the inserted value.
|
|
||||||
*/
|
|
||||||
function insertOne(collectionName: string, value: any): Promise<any> {
|
|
||||||
return window.corePlugin?.store?.insertOne(collectionName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a record from a collection in the data store.
|
|
||||||
* @param {string} collectionName - The name of the collection containing the record to retrieve.
|
|
||||||
* @param {string} key - The key of the record to retrieve.
|
|
||||||
* @returns {Promise<any>} A promise that resolves when the record is retrieved.
|
|
||||||
*/
|
|
||||||
function findOne(collectionName: string, key: string): Promise<any> {
|
|
||||||
return window.corePlugin?.store?.findOne(collectionName, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves all records that match a selector in a collection in the data store.
|
|
||||||
* @param {string} collectionName - The name of the collection to retrieve.
|
|
||||||
* @param {{ [key: string]: any }} selector - The selector to use to get records from the collection.
|
|
||||||
* @param {[{ [key: string]: any }]} sort - The sort options to use to retrieve records.
|
|
||||||
* @returns {Promise<any>} A promise that resolves when all records are retrieved.
|
|
||||||
*/
|
|
||||||
function findMany(
|
|
||||||
collectionName: string,
|
|
||||||
selector?: { [key: string]: any },
|
|
||||||
sort?: [{ [key: string]: any }]
|
|
||||||
): Promise<any> {
|
|
||||||
return window.corePlugin?.store?.findMany(collectionName, selector, sort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the value of a record in a collection in the data store.
|
|
||||||
* @param {string} collectionName - The name of the collection containing the record to update.
|
|
||||||
* @param {string} key - The key of the record to update.
|
|
||||||
* @param {any} value - The new value for the record.
|
|
||||||
* @returns {Promise<void>} A promise that resolves when the record is updated.
|
|
||||||
*/
|
|
||||||
function updateOne(
|
|
||||||
collectionName: string,
|
|
||||||
key: string,
|
|
||||||
value: any
|
|
||||||
): Promise<void> {
|
|
||||||
return window.corePlugin?.store?.updateOne(collectionName, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates all records that match a selector in a collection in the data store.
|
|
||||||
* @param {string} collectionName - The name of the collection containing the records to update.
|
|
||||||
* @param {{ [key: string]: any }} selector - The selector to use to get the records to update.
|
|
||||||
* @param {any} value - The new value for the records.
|
|
||||||
* @returns {Promise<void>} A promise that resolves when the records are updated.
|
|
||||||
*/
|
|
||||||
function updateMany(
|
|
||||||
collectionName: string,
|
|
||||||
value: any,
|
|
||||||
selector?: { [key: string]: any }
|
|
||||||
): Promise<void> {
|
|
||||||
return window.corePlugin?.store?.updateMany(collectionName, selector, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a single record from a collection in the data store.
|
|
||||||
* @param {string} collectionName - The name of the collection containing the record to delete.
|
|
||||||
* @param {string} key - The key of the record to delete.
|
|
||||||
* @returns {Promise<void>} A promise that resolves when the record is deleted.
|
|
||||||
*/
|
|
||||||
function deleteOne(collectionName: string, key: string): Promise<void> {
|
|
||||||
return window.corePlugin?.store?.deleteOne(collectionName, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all records with a matching key from a collection in the data store.
|
|
||||||
* @param {string} collectionName - The name of the collection to delete the records from.
|
|
||||||
* @param {{ [key: string]: any }} selector - The selector to use to get the records to delete.
|
|
||||||
* @returns {Promise<void>} A promise that resolves when the records are deleted.
|
|
||||||
*/
|
|
||||||
function deleteMany(
|
|
||||||
collectionName: string,
|
|
||||||
selector?: { [key: string]: any }
|
|
||||||
): Promise<void> {
|
|
||||||
return window.corePlugin?.store?.deleteMany(collectionName, selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exports the data store operations as an object.
|
|
||||||
*/
|
|
||||||
export const store = {
|
|
||||||
createCollection,
|
|
||||||
deleteCollection,
|
|
||||||
insertOne,
|
|
||||||
findOne,
|
|
||||||
findMany,
|
|
||||||
updateOne,
|
|
||||||
updateMany,
|
|
||||||
deleteOne,
|
|
||||||
deleteMany,
|
|
||||||
};
|
|
||||||
@ -7,7 +7,9 @@
|
|||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"declaration": true
|
"declaration": true,
|
||||||
|
"rootDir": "./src"
|
||||||
},
|
},
|
||||||
|
"include": ["./src"],
|
||||||
"exclude": ["lib", "node_modules", "**/*.test.ts", "**/__mocks__/*"]
|
"exclude": ["lib", "node_modules", "**/*.test.ts", "**/__mocks__/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,22 @@ export default function Dropdown() {
|
|||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeDefaultSystem = (systems) => {
|
||||||
|
const userAgent = navigator.userAgent;
|
||||||
|
if (userAgent.includes("Windows")) {
|
||||||
|
// windows user
|
||||||
|
setDefaultSystem(systems[2]);
|
||||||
|
} else if (userAgent.includes("Linux")) {
|
||||||
|
// linux user
|
||||||
|
setDefaultSystem(systems[3]);
|
||||||
|
} else if (userAgent.includes("Mac OS") && userAgent.includes("Intel")) {
|
||||||
|
// mac intel user
|
||||||
|
setDefaultSystem(systems[1]);
|
||||||
|
} else {
|
||||||
|
// mac user and also default
|
||||||
|
setDefaultSystem(systems[0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateDownloadLinks = async () => {
|
const updateDownloadLinks = async () => {
|
||||||
try {
|
try {
|
||||||
@ -67,6 +83,7 @@ export default function Dropdown() {
|
|||||||
"Failed to extract appname from file name:",
|
"Failed to extract appname from file name:",
|
||||||
firstAssetName
|
firstAssetName
|
||||||
);
|
);
|
||||||
|
changeDefaultSystem(systems);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +103,7 @@ export default function Dropdown() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setSystems(updatedSystems);
|
setSystems(updatedSystems);
|
||||||
setDefaultSystem(updatedSystems[0]);
|
changeDefaultSystem(updatedSystems);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update download links:", error);
|
console.error("Failed to update download links:", error);
|
||||||
}
|
}
|
||||||
@ -101,11 +118,7 @@ export default function Dropdown() {
|
|||||||
className="cursor-pointer relative inline-flex items-center rounded-l-md border-0 px-3.5 py-2.5 text-base font-semibold text-white bg-blue-600 hover:bg-blue-500 hover:text-white"
|
className="cursor-pointer relative inline-flex items-center rounded-l-md border-0 px-3.5 py-2.5 text-base font-semibold text-white bg-blue-600 hover:bg-blue-500 hover:text-white"
|
||||||
href={defaultSystem.href}
|
href={defaultSystem.href}
|
||||||
>
|
>
|
||||||
<img
|
<img src={defaultSystem.logo} alt="Logo" className="h-5 mr-3 -mt-1" />
|
||||||
src={require("@site/static/img/apple-logo-white.png").default}
|
|
||||||
alt="Logo"
|
|
||||||
className="h-5 mr-3 -mt-1"
|
|
||||||
/>
|
|
||||||
{defaultSystem.name}
|
{defaultSystem.name}
|
||||||
</a>
|
</a>
|
||||||
<Menu as="div" className="relative -ml-px block">
|
<Menu as="div" className="relative -ml-px block">
|
||||||
@ -125,7 +138,10 @@ export default function Dropdown() {
|
|||||||
<Menu.Items className="absolute right-0 z-10 mt-1 w-72 text-left origin-top-right rounded-md bg-blue-600 shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none overflow-hidden">
|
<Menu.Items className="absolute right-0 z-10 mt-1 w-72 text-left origin-top-right rounded-md bg-blue-600 shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none overflow-hidden">
|
||||||
<div className="overflow-hidden">
|
<div className="overflow-hidden">
|
||||||
{systems.map((system) => (
|
{systems.map((system) => (
|
||||||
<Menu.Item key={system.name}>
|
<Menu.Item
|
||||||
|
key={system.name}
|
||||||
|
onClick={() => setDefaultSystem(system)}
|
||||||
|
>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<a
|
<a
|
||||||
href={system.href}
|
href={system.href}
|
||||||
|
|||||||
@ -129,13 +129,13 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container mt-10 mb-20 px-48 text-center">
|
<div className="container mt-10 mb-20 text-center">
|
||||||
<h2>AI that you control</h2>
|
<h2>AI that you control</h2>
|
||||||
<p className="text-base mt-2 w-full lg:w-2/5 mx-auto leading-relaxed">
|
<p className="text-base mt-2 w-full lg:w-2/5 mx-auto leading-relaxed">
|
||||||
Jan runs Large Language Models locally on Windows, Mac and Linux.
|
Jan runs Large Language Models locally on Windows, Mac and Linux.
|
||||||
Available on Desktop and Cloud-Native.
|
Available on Desktop and Cloud-Native.
|
||||||
</p>
|
</p>
|
||||||
<div className="grid text-left lg:grid-cols-2 mt-16 gap-16">
|
<div className="grid text-left lg:grid-cols-2 lg:px-48 mt-16 gap-16">
|
||||||
{features.map((feat, i) => {
|
{features.map((feat, i) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-x-4" key={i}>
|
<div className="flex gap-x-4" key={i}>
|
||||||
|
|||||||
118
electron/handlers/fs.ts
Normal file
118
electron/handlers/fs.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { app, ipcMain } from "electron";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles file system operations.
|
||||||
|
*/
|
||||||
|
export function handleFs() {
|
||||||
|
/**
|
||||||
|
* Reads a file from the user data directory.
|
||||||
|
* @param event - The event object.
|
||||||
|
* @param path - The path of the file to read.
|
||||||
|
* @returns A promise that resolves with the contents of the file.
|
||||||
|
*/
|
||||||
|
ipcMain.handle("readFile", async (event, path: string): Promise<string> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(join(app.getPath("userData"), path), "utf8", (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data to a file in the user data directory.
|
||||||
|
* @param event - The event object.
|
||||||
|
* @param path - The path of the file to write to.
|
||||||
|
* @param data - The data to write to the file.
|
||||||
|
* @returns A promise that resolves when the file has been written.
|
||||||
|
*/
|
||||||
|
ipcMain.handle(
|
||||||
|
"writeFile",
|
||||||
|
async (event, path: string, data: string): Promise<void> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.writeFile(
|
||||||
|
join(app.getPath("userData"), path),
|
||||||
|
data,
|
||||||
|
"utf8",
|
||||||
|
(err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a directory in the user data directory.
|
||||||
|
* @param event - The event object.
|
||||||
|
* @param path - The path of the directory to create.
|
||||||
|
* @returns A promise that resolves when the directory has been created.
|
||||||
|
*/
|
||||||
|
ipcMain.handle("mkdir", async (event, path: string): Promise<void> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.mkdir(
|
||||||
|
join(app.getPath("userData"), path),
|
||||||
|
{ recursive: true },
|
||||||
|
(err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a directory in the user data directory.
|
||||||
|
* @param event - The event object.
|
||||||
|
* @param path - The path of the directory to remove.
|
||||||
|
* @returns A promise that resolves when the directory is removed successfully.
|
||||||
|
*/
|
||||||
|
ipcMain.handle("rmdir", async (event, path: string): Promise<void> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.rmdir(
|
||||||
|
join(app.getPath("userData"), path),
|
||||||
|
{ recursive: true },
|
||||||
|
(err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists the files in a directory in the user data directory.
|
||||||
|
* @param event - The event object.
|
||||||
|
* @param path - The path of the directory to list files from.
|
||||||
|
* @returns A promise that resolves with an array of file names.
|
||||||
|
*/
|
||||||
|
ipcMain.handle(
|
||||||
|
"listFiles",
|
||||||
|
async (event, path: string): Promise<string[]> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readdir(join(app.getPath("userData"), path), (err, files) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(files);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import { rmdir, unlink, createWriteStream } from "fs";
|
|||||||
import { init } from "./core/plugin/index";
|
import { init } from "./core/plugin/index";
|
||||||
import { setupMenu } from "./utils/menu";
|
import { setupMenu } from "./utils/menu";
|
||||||
import { dispose } from "./utils/disposable";
|
import { dispose } from "./utils/disposable";
|
||||||
|
import { handleFs } from "./handlers/fs";
|
||||||
|
|
||||||
const pacote = require("pacote");
|
const pacote = require("pacote");
|
||||||
const request = require("request");
|
const request = require("request");
|
||||||
@ -127,6 +128,7 @@ function handleAppUpdates() {
|
|||||||
* Handles various IPC messages from the renderer process.
|
* Handles various IPC messages from the renderer process.
|
||||||
*/
|
*/
|
||||||
function handleIPCs() {
|
function handleIPCs() {
|
||||||
|
handleFs();
|
||||||
/**
|
/**
|
||||||
* Handles the "setNativeThemeLight" IPC message by setting the native theme source to "light".
|
* Handles the "setNativeThemeLight" IPC message by setting the native theme source to "light".
|
||||||
* This will change the appearance of the app to the light theme.
|
* This will change the appearance of the app to the light theme.
|
||||||
|
|||||||
@ -1,7 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Exposes a set of APIs to the renderer process via the contextBridge object.
|
||||||
|
* @remarks
|
||||||
|
* This module is used to make Pluggable Electron's facade available to the renderer on window.plugins.
|
||||||
|
* @module preload
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes a set of APIs to the renderer process via the contextBridge object.
|
||||||
|
* @remarks
|
||||||
|
* This module is used to make Pluggable Electron's facade available to the renderer on window.plugins.
|
||||||
|
* @function useFacade
|
||||||
|
* @memberof module:preload
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exposes a set of APIs to the renderer process via the contextBridge object.
|
||||||
|
* @remarks
|
||||||
|
* This module is used to make Pluggable Electron's facade available to the renderer on window.plugins.
|
||||||
|
* @namespace electronAPI
|
||||||
|
* @memberof module:preload
|
||||||
|
* @property {Function} invokePluginFunc - Invokes a plugin function with the given arguments.
|
||||||
|
* @property {Function} setNativeThemeLight - Sets the native theme to light.
|
||||||
|
* @property {Function} setNativeThemeDark - Sets the native theme to dark.
|
||||||
|
* @property {Function} setNativeThemeSystem - Sets the native theme to system.
|
||||||
|
* @property {Function} basePlugins - Returns the base plugins.
|
||||||
|
* @property {Function} pluginPath - Returns the plugin path.
|
||||||
|
* @property {Function} appDataPath - Returns the app data path.
|
||||||
|
* @property {Function} reloadPlugins - Reloads the plugins.
|
||||||
|
* @property {Function} appVersion - Returns the app version.
|
||||||
|
* @property {Function} openExternalUrl - Opens the given URL in the default browser.
|
||||||
|
* @property {Function} relaunch - Relaunches the app.
|
||||||
|
* @property {Function} openAppDirectory - Opens the app directory.
|
||||||
|
* @property {Function} deleteFile - Deletes the file at the given path.
|
||||||
|
* @property {Function} readFile - Reads the file at the given path.
|
||||||
|
* @property {Function} writeFile - Writes the given data to the file at the given path.
|
||||||
|
* @property {Function} listFiles - Lists the files in the directory at the given path.
|
||||||
|
* @property {Function} mkdir - Creates a directory at the given path.
|
||||||
|
* @property {Function} rmdir - Removes a directory at the given path recursively.
|
||||||
|
* @property {Function} installRemotePlugin - Installs the remote plugin with the given name.
|
||||||
|
* @property {Function} downloadFile - Downloads the file at the given URL to the given path.
|
||||||
|
* @property {Function} pauseDownload - Pauses the download of the file with the given name.
|
||||||
|
* @property {Function} resumeDownload - Resumes the download of the file with the given name.
|
||||||
|
* @property {Function} abortDownload - Aborts the download of the file with the given name.
|
||||||
|
* @property {Function} onFileDownloadUpdate - Registers a callback to be called when a file download is updated.
|
||||||
|
* @property {Function} onFileDownloadError - Registers a callback to be called when a file download encounters an error.
|
||||||
|
* @property {Function} onFileDownloadSuccess - Registers a callback to be called when a file download is completed successfully.
|
||||||
|
* @property {Function} onAppUpdateDownloadUpdate - Registers a callback to be called when an app update download is updated.
|
||||||
|
* @property {Function} onAppUpdateDownloadError - Registers a callback to be called when an app update download encounters an error.
|
||||||
|
* @property {Function} onAppUpdateDownloadSuccess - Registers a callback to be called when an app update download is completed successfully.
|
||||||
|
*/
|
||||||
|
|
||||||
// Make Pluggable Electron's facade available to the renderer on window.plugins
|
// Make Pluggable Electron's facade available to the renderer on window.plugins
|
||||||
import { useFacade } from "./core/plugin/facade";
|
import { useFacade } from "./core/plugin/facade";
|
||||||
|
|
||||||
useFacade();
|
useFacade();
|
||||||
//@ts-ignore
|
|
||||||
const { contextBridge, ipcRenderer } = require("electron");
|
const { contextBridge, ipcRenderer } = require("electron");
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld("electronAPI", {
|
contextBridge.exposeInMainWorld("electronAPI", {
|
||||||
@ -32,6 +86,17 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
|||||||
|
|
||||||
deleteFile: (filePath: string) => ipcRenderer.invoke("deleteFile", filePath),
|
deleteFile: (filePath: string) => ipcRenderer.invoke("deleteFile", filePath),
|
||||||
|
|
||||||
|
readFile: (path: string) => ipcRenderer.invoke("readFile", path),
|
||||||
|
|
||||||
|
writeFile: (path: string, data: string) =>
|
||||||
|
ipcRenderer.invoke("writeFile", path, data),
|
||||||
|
|
||||||
|
listFiles: (path: string) => ipcRenderer.invoke("listFiles", path),
|
||||||
|
|
||||||
|
mkdir: (path: string) => ipcRenderer.invoke("mkdir", path),
|
||||||
|
|
||||||
|
rmdir: (path: string) => ipcRenderer.invoke("rmdir", path),
|
||||||
|
|
||||||
installRemotePlugin: (pluginName: string) =>
|
installRemotePlugin: (pluginName: string) =>
|
||||||
ipcRenderer.invoke("installRemotePlugin", pluginName),
|
ipcRenderer.invoke("installRemotePlugin", pluginName),
|
||||||
|
|
||||||
|
|||||||
17
package.json
17
package.json
@ -27,9 +27,9 @@
|
|||||||
"build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"",
|
"build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"",
|
||||||
"build:electron": "yarn workspace jan build",
|
"build:electron": "yarn workspace jan build",
|
||||||
"build:electron:test": "yarn workspace jan build:test",
|
"build:electron:test": "yarn workspace jan build:test",
|
||||||
"build:pull-plugins": "rimraf ./electron/core/pre-install/*.tgz && cd ./electron/core/pre-install && npm pack @janhq/inference-plugin @janhq/data-plugin @janhq/model-management-plugin @janhq/monitoring-plugin",
|
"build:pull-plugins": "rimraf ./electron/core/pre-install/*.tgz && cd ./electron/core/pre-install && npm pack @janhq/inference-plugin @janhq/monitoring-plugin",
|
||||||
"build:plugins": "rimraf ./electron/core/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./plugins/data-plugin && npm install && npm run postinstall\" \"cd ./plugins/inference-plugin && npm install --ignore-scripts && npm run postinstall:dev\" \"cd ./plugins/model-management-plugin && npm install && npm run postinstall\" \"cd ./plugins/monitoring-plugin && npm install && npm run postinstall\" && concurrently --kill-others-on-fail \"cd ./plugins/data-plugin && npm run build:publish\" \"cd ./plugins/inference-plugin && npm run build:publish\" \"cd ./plugins/model-management-plugin && npm run build:publish\" \"cd ./plugins/monitoring-plugin && npm run build:publish\"",
|
"build:plugins": "rimraf ./electron/core/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./plugins/conversational-plugin && npm install && npm run postinstall && npm run build:publish\" \"cd ./plugins/inference-plugin && npm install --ignore-scripts && npm run postinstall:dev && npm run build:publish\" \"cd ./plugins/model-plugin && npm install && npm run postinstall && npm run build:publish\" \"cd ./plugins/monitoring-plugin && npm install && npm run postinstall && npm run build:publish\"",
|
||||||
"build:plugins-web": "rimraf ./electron/core/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./plugins/data-plugin && npm install && npm run build:deps && npm run postinstall\" \"cd ./plugins/inference-plugin && npm install && npm run postinstall\" \"cd ./plugins/model-management-plugin && npm install && npm run postinstall\" \"cd ./plugins/monitoring-plugin && npm install && npm run postinstall\" && concurrently --kill-others-on-fail \"cd ./plugins/data-plugin && npm run build:publish\" \"cd ./plugins/inference-plugin && npm run build:publish\" \"cd ./plugins/model-management-plugin && npm run build:publish\" \"cd ./plugins/monitoring-plugin && npm run build:publish\"",
|
"build:plugins-web": "rimraf ./electron/core/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./plugins/conversational-plugin && npm install && npm run build:deps && npm run postinstall\" \"cd ./plugins/inference-plugin && npm install && npm run postinstall\" \"cd ./plugins/model-plugin && npm install && npm run postinstall\" \"cd ./plugins/monitoring-plugin && npm install && npm run postinstall\" && concurrently --kill-others-on-fail \"cd ./plugins/conversational-plugin && npm run build:publish\" \"cd ./plugins/inference-plugin && npm run build:publish\" \"cd ./plugins/model-plugin && npm run build:publish\" \"cd ./plugins/monitoring-plugin && npm run build:publish\"",
|
||||||
"build": "yarn build:web && yarn build:electron",
|
"build": "yarn build:web && yarn build:electron",
|
||||||
"build:test": "yarn build:web && yarn build:electron:test",
|
"build:test": "yarn build:web && yarn build:electron:test",
|
||||||
"build:test-darwin": "yarn build:web && yarn workspace jan build:test-darwin",
|
"build:test-darwin": "yarn build:web && yarn workspace jan build:test-darwin",
|
||||||
@ -42,15 +42,18 @@
|
|||||||
"build:publish-darwin": "yarn build:web && yarn workspace jan build:publish-darwin",
|
"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-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-web && 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\"",
|
"build:web-plugins": "yarn build:web && yarn build:plugins-web && mkdir -p \"./web/out/plugins/conversational-plugin\" && cp \"./plugins/conversational-plugin/dist/index.js\" \"./web/out/plugins/conversational-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-plugin\" && cp \"./plugins/model-plugin/dist/index.js\" \"./web/out/plugins/model-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",
|
"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"
|
"start:server": "yarn server:prod && node server/build/main.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^8.2.1",
|
"concurrently": "^8.2.1",
|
||||||
"cpx": "^1.5.0",
|
"cpx": "^1.5.0",
|
||||||
"wait-on": "^7.0.1",
|
"rimraf": "^3.0.2",
|
||||||
"rimraf": "^3.0.2"
|
"wait-on": "^7.0.1"
|
||||||
},
|
},
|
||||||
"version": "0.0.0"
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@janhq/core": "file:core"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
plugins/conversational-json/package.json
Normal file
39
plugins/conversational-json/package.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "@janhq/conversational-json",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Conversational Plugin - Stores jan app conversations as JSON",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"author": "Jan <service@jan.ai>",
|
||||||
|
"license": "MIT",
|
||||||
|
"activationPoints": [
|
||||||
|
"init"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -b . && webpack --config webpack.config.js",
|
||||||
|
"postinstall": "rimraf *.tgz --glob && npm run build",
|
||||||
|
"build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": "./dist/index.js",
|
||||||
|
"./main": "./dist/module.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cpx": "^1.5.0",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"webpack": "^5.88.2",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@janhq/core": "file:../../core",
|
||||||
|
"ts-loader": "^9.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/*",
|
||||||
|
"package.json",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"bundleDependencies": []
|
||||||
|
}
|
||||||
87
plugins/conversational-json/src/index.ts
Normal file
87
plugins/conversational-json/src/index.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { PluginType, fs } from "@janhq/core";
|
||||||
|
import { ConversationalPlugin } from "@janhq/core/lib/plugins";
|
||||||
|
import { Conversation } from "@janhq/core/lib/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSONConversationalPlugin is a ConversationalPlugin implementation that provides
|
||||||
|
* functionality for managing conversations.
|
||||||
|
*/
|
||||||
|
export default class JSONConversationalPlugin implements ConversationalPlugin {
|
||||||
|
/**
|
||||||
|
* Returns the type of the plugin.
|
||||||
|
*/
|
||||||
|
type(): PluginType {
|
||||||
|
return PluginType.Conversational;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is loaded.
|
||||||
|
*/
|
||||||
|
onLoad() {
|
||||||
|
fs.mkdir("conversations")
|
||||||
|
console.debug("JSONConversationalPlugin loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is unloaded.
|
||||||
|
*/
|
||||||
|
onUnload() {
|
||||||
|
console.debug("JSONConversationalPlugin unloaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Promise that resolves to an array of Conversation objects.
|
||||||
|
*/
|
||||||
|
getConversations(): Promise<Conversation[]> {
|
||||||
|
return this.getConversationDocs().then((conversationIds) =>
|
||||||
|
Promise.all(
|
||||||
|
conversationIds.map((conversationId) =>
|
||||||
|
fs
|
||||||
|
.readFile(`conversations/${conversationId}/${conversationId}.json`)
|
||||||
|
.then((data) => {
|
||||||
|
return JSON.parse(data) as Conversation;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).then((conversations) =>
|
||||||
|
conversations.sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a Conversation object to a Markdown file.
|
||||||
|
* @param conversation The Conversation object to save.
|
||||||
|
*/
|
||||||
|
saveConversation(conversation: Conversation): Promise<void> {
|
||||||
|
return fs
|
||||||
|
.mkdir(`conversations/${conversation._id}`)
|
||||||
|
.then(() =>
|
||||||
|
fs.writeFile(
|
||||||
|
`conversations/${conversation._id}/${conversation._id}.json`,
|
||||||
|
JSON.stringify(conversation)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a conversation with the specified ID.
|
||||||
|
* @param conversationId The ID of the conversation to delete.
|
||||||
|
*/
|
||||||
|
deleteConversation(conversationId: string): Promise<void> {
|
||||||
|
return fs.rmdir(`conversations/${conversationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Promise that resolves to an array of conversation IDs.
|
||||||
|
* The conversation IDs are the names of the Markdown files in the "conversations" directory.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private async getConversationDocs(): Promise<string[]> {
|
||||||
|
return fs.listFiles(`conversations`).then((files: string[]) => {
|
||||||
|
return Promise.all(files.filter((file) => file.startsWith("jan-")));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
}
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["./src"]
|
||||||
}
|
}
|
||||||
@ -1,10 +1,9 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
const packageJson = require("./package.json");
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
entry: "./index.ts", // Adjust the entry point to match your project's main file
|
entry: "./src/index.ts", // Adjust the entry point to match your project's main file
|
||||||
mode: "production",
|
mode: "production",
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@ -20,14 +19,13 @@ module.exports = {
|
|||||||
path: path.resolve(__dirname, "dist"),
|
path: path.resolve(__dirname, "dist"),
|
||||||
library: { type: "module" }, // Specify ESM output format
|
library: { type: "module" }, // Specify ESM output format
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [new webpack.DefinePlugin({})],
|
||||||
new webpack.DefinePlugin({
|
|
||||||
PLUGIN_NAME: JSON.stringify(packageJson.name),
|
|
||||||
MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js"],
|
extensions: [".ts", ".js"],
|
||||||
},
|
},
|
||||||
|
// Do not minify the output, otherwise it breaks the class registration
|
||||||
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
|
},
|
||||||
// Add loaders and other configuration as needed for your project
|
// Add loaders and other configuration as needed for your project
|
||||||
};
|
};
|
||||||
@ -1,10 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/azure-openai-plugin",
|
"name": "@janhq/conversational-plugin",
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"description": "Inference plugin for Azure OpenAI",
|
"description": "Conversational Plugin - Stores jan app conversations",
|
||||||
"icon": "https://static-assets.jan.ai/openai-icon.jpg",
|
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/module.js",
|
|
||||||
"author": "Jan <service@jan.ai>",
|
"author": "Jan <service@jan.ai>",
|
||||||
"requiredVersion": "^0.3.1",
|
"requiredVersion": "^0.3.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -13,7 +11,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -b . && webpack --config webpack.config.js",
|
"build": "tsc -b . && webpack --config webpack.config.js",
|
||||||
"postinstall": "rimraf *.tgz --glob && npm run build && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"",
|
"postinstall": "rimraf *.tgz --glob && npm run build",
|
||||||
"build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install"
|
"build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
@ -27,16 +25,9 @@
|
|||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@janhq/core": "^0.1.6",
|
"@janhq/core": "file:../../core",
|
||||||
"azure-openai": "^0.9.4",
|
|
||||||
"kill-port-process": "^3.2.0",
|
|
||||||
"tcp-port-used": "^1.0.2",
|
|
||||||
"ts-loader": "^9.5.0"
|
"ts-loader": "^9.5.0"
|
||||||
},
|
},
|
||||||
"bundledDependencies": [
|
|
||||||
"tcp-port-used",
|
|
||||||
"kill-port-process"
|
|
||||||
],
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
@ -44,5 +35,6 @@
|
|||||||
"dist/*",
|
"dist/*",
|
||||||
"package.json",
|
"package.json",
|
||||||
"README.md"
|
"README.md"
|
||||||
]
|
],
|
||||||
|
"bundleDependencies": []
|
||||||
}
|
}
|
||||||
214
plugins/conversational-plugin/src/index.ts
Normal file
214
plugins/conversational-plugin/src/index.ts
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import { PluginType, fs } from "@janhq/core";
|
||||||
|
import { ConversationalPlugin } from "@janhq/core/lib/plugins";
|
||||||
|
import { Message, Conversation } from "@janhq/core/lib/types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JanConversationalPlugin is a ConversationalPlugin implementation that provides
|
||||||
|
* functionality for managing conversations.
|
||||||
|
*/
|
||||||
|
export default class JanConversationalPlugin implements ConversationalPlugin {
|
||||||
|
/**
|
||||||
|
* Returns the type of the plugin.
|
||||||
|
*/
|
||||||
|
type(): PluginType {
|
||||||
|
return PluginType.Conversational;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is loaded.
|
||||||
|
*/
|
||||||
|
onLoad() {
|
||||||
|
console.debug("JanConversationalPlugin loaded");
|
||||||
|
fs.mkdir("conversations");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is unloaded.
|
||||||
|
*/
|
||||||
|
onUnload() {
|
||||||
|
console.debug("JanConversationalPlugin unloaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Promise that resolves to an array of Conversation objects.
|
||||||
|
*/
|
||||||
|
getConversations(): Promise<Conversation[]> {
|
||||||
|
return this.getConversationDocs().then((conversationIds) =>
|
||||||
|
Promise.all(
|
||||||
|
conversationIds.map((conversationId) =>
|
||||||
|
this.loadConversationFromMarkdownFile(
|
||||||
|
`conversations/${conversationId}/${conversationId}.md`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).then((conversations) =>
|
||||||
|
conversations.sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a Conversation object to a Markdown file.
|
||||||
|
* @param conversation The Conversation object to save.
|
||||||
|
*/
|
||||||
|
saveConversation(conversation: Conversation): Promise<void> {
|
||||||
|
return this.writeMarkdownToFile(conversation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a conversation with the specified ID.
|
||||||
|
* @param conversationId The ID of the conversation to delete.
|
||||||
|
*/
|
||||||
|
deleteConversation(conversationId: string): Promise<void> {
|
||||||
|
return fs.rmdir(`conversations/${conversationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Promise that resolves to an array of conversation IDs.
|
||||||
|
* The conversation IDs are the names of the Markdown files in the "conversations" directory.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private async getConversationDocs(): Promise<string[]> {
|
||||||
|
return fs.listFiles("conversations").then((files: string[]) => {
|
||||||
|
return Promise.all(files.filter((file) => file.startsWith("jan-")));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a Markdown string and returns a Conversation object.
|
||||||
|
* @param markdown The Markdown string to parse.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private parseConversationMarkdown(markdown: string): Conversation {
|
||||||
|
const conversation: Conversation = {
|
||||||
|
_id: "",
|
||||||
|
name: "",
|
||||||
|
messages: [],
|
||||||
|
};
|
||||||
|
var currentMessage: Message | undefined = undefined;
|
||||||
|
for (const line of markdown.split("\n")) {
|
||||||
|
const trimmedLine = line.trim();
|
||||||
|
if (trimmedLine.startsWith("- _id:")) {
|
||||||
|
conversation._id = trimmedLine.replace("- _id:", "").trim();
|
||||||
|
} else if (trimmedLine.startsWith("- modelId:")) {
|
||||||
|
conversation.modelId = trimmedLine.replace("- modelId:", "").trim();
|
||||||
|
} else if (trimmedLine.startsWith("- name:")) {
|
||||||
|
conversation.name = trimmedLine.replace("- name:", "").trim();
|
||||||
|
} else if (trimmedLine.startsWith("- lastMessage:")) {
|
||||||
|
conversation.message = trimmedLine.replace("- lastMessage:", "").trim();
|
||||||
|
} else if (trimmedLine.startsWith("- summary:")) {
|
||||||
|
conversation.summary = trimmedLine.replace("- summary:", "").trim();
|
||||||
|
} else if (
|
||||||
|
trimmedLine.startsWith("- createdAt:") &&
|
||||||
|
currentMessage === undefined
|
||||||
|
) {
|
||||||
|
conversation.createdAt = trimmedLine.replace("- createdAt:", "").trim();
|
||||||
|
} else if (trimmedLine.startsWith("- updatedAt:")) {
|
||||||
|
conversation.updatedAt = trimmedLine.replace("- updatedAt:", "").trim();
|
||||||
|
} else if (trimmedLine.startsWith("- botId:")) {
|
||||||
|
conversation.botId = trimmedLine.replace("- botId:", "").trim();
|
||||||
|
} else if (trimmedLine.startsWith("- user:")) {
|
||||||
|
if (currentMessage)
|
||||||
|
currentMessage.user = trimmedLine.replace("- user:", "").trim();
|
||||||
|
} else if (trimmedLine.startsWith("- createdAt:")) {
|
||||||
|
if (currentMessage)
|
||||||
|
currentMessage.createdAt = trimmedLine
|
||||||
|
.replace("- createdAt:", "")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
currentMessage.updatedAt = currentMessage.createdAt;
|
||||||
|
} else if (trimmedLine.startsWith("- message:")) {
|
||||||
|
if (currentMessage)
|
||||||
|
currentMessage.message = trimmedLine.replace("- message:", "").trim();
|
||||||
|
} else if (trimmedLine.startsWith("- Message ")) {
|
||||||
|
const messageMatch = trimmedLine.match(/- Message (m-\d+):/);
|
||||||
|
if (messageMatch) {
|
||||||
|
if (currentMessage) {
|
||||||
|
conversation.messages.push(currentMessage);
|
||||||
|
}
|
||||||
|
currentMessage = { _id: messageMatch[1] };
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
currentMessage?.message &&
|
||||||
|
!trimmedLine.startsWith("## Messages")
|
||||||
|
) {
|
||||||
|
currentMessage.message = currentMessage.message + "\n" + line.trim();
|
||||||
|
} else if (trimmedLine.startsWith("## Messages")) {
|
||||||
|
currentMessage = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentMessage) {
|
||||||
|
conversation.messages.push(currentMessage);
|
||||||
|
}
|
||||||
|
return conversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a Conversation object from a Markdown file.
|
||||||
|
* @param filePath The path to the Markdown file.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private async loadConversationFromMarkdownFile(
|
||||||
|
filePath: string
|
||||||
|
): Promise<Conversation | undefined> {
|
||||||
|
try {
|
||||||
|
const markdown: string = await fs.readFile(filePath);
|
||||||
|
return this.parseConversationMarkdown(markdown);
|
||||||
|
} catch (err) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a Markdown string from a Conversation object.
|
||||||
|
* @param conversation The Conversation object to generate Markdown from.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private generateMarkdown(conversation: Conversation): string {
|
||||||
|
// Generate the Markdown content based on the Conversation object
|
||||||
|
const conversationMetadata = `
|
||||||
|
- _id: ${conversation._id}
|
||||||
|
- modelId: ${conversation.modelId}
|
||||||
|
- name: ${conversation.name}
|
||||||
|
- lastMessage: ${conversation.message}
|
||||||
|
- summary: ${conversation.summary}
|
||||||
|
- createdAt: ${conversation.createdAt}
|
||||||
|
- updatedAt: ${conversation.updatedAt}
|
||||||
|
- botId: ${conversation.botId}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const messages = conversation.messages.map(
|
||||||
|
(message) => `
|
||||||
|
- Message ${message._id}:
|
||||||
|
- createdAt: ${message.createdAt}
|
||||||
|
- user: ${message.user}
|
||||||
|
- message: ${message.message?.trim()}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
return `## Conversation Metadata
|
||||||
|
${conversationMetadata}
|
||||||
|
## Messages
|
||||||
|
${messages.map((msg) => msg.trim()).join("\n")}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a Conversation object to a Markdown file.
|
||||||
|
* @param conversation The Conversation object to write to a Markdown file.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private async writeMarkdownToFile(conversation: Conversation) {
|
||||||
|
// Generate the Markdown content
|
||||||
|
const markdownContent = this.generateMarkdown(conversation);
|
||||||
|
await fs.mkdir(`conversations/${conversation._id}`);
|
||||||
|
// Write the content to a Markdown file
|
||||||
|
await fs.writeFile(
|
||||||
|
`conversations/${conversation._id}/${conversation._id}.md`,
|
||||||
|
markdownContent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,10 +4,11 @@
|
|||||||
"module": "ES6",
|
"module": "ES6",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"rootDir": "./src",
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
}
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["./src"]
|
||||||
}
|
}
|
||||||
@ -1,10 +1,9 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
const packageJson = require("./package.json");
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
entry: "./src/index.ts",
|
entry: "./src/index.ts", // Adjust the entry point to match your project's main file
|
||||||
mode: "production",
|
mode: "production",
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@ -15,21 +14,18 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
PLUGIN_NAME: JSON.stringify(packageJson.name),
|
|
||||||
MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
output: {
|
output: {
|
||||||
filename: "index.js",
|
filename: "index.js", // Adjust the output file name as needed
|
||||||
path: path.resolve(__dirname, "dist"),
|
path: path.resolve(__dirname, "dist"),
|
||||||
library: { type: "module" },
|
library: { type: "module" }, // Specify ESM output format
|
||||||
},
|
},
|
||||||
|
plugins: [new webpack.DefinePlugin({})],
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js"],
|
extensions: [".ts", ".js"],
|
||||||
},
|
},
|
||||||
|
// Do not minify the output, otherwise it breaks the class registration
|
||||||
optimization: {
|
optimization: {
|
||||||
minimize: false,
|
minimize: false,
|
||||||
},
|
},
|
||||||
|
// Add loaders and other configuration as needed for your project
|
||||||
};
|
};
|
||||||
3
plugins/data-plugin/@types/global.d.ts
vendored
3
plugins/data-plugin/@types/global.d.ts
vendored
@ -1,3 +0,0 @@
|
|||||||
declare const PLUGIN_NAME: string;
|
|
||||||
declare const MODULE_PATH: string;
|
|
||||||
declare const PLUGIN_CATALOG: string;
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
## 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: Plugin & npm module manifest.
|
|
||||||
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./../dist/cjs",
|
|
||||||
"module": "commonjs"
|
|
||||||
},
|
|
||||||
"files": ["../module.ts"]
|
|
||||||
}
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./../dist/esm",
|
|
||||||
"module": "esnext"
|
|
||||||
},
|
|
||||||
"include": ["@types/*"],
|
|
||||||
"files": ["../@types/global.d.ts", "../index.ts"]
|
|
||||||
}
|
|
||||||
@ -1,345 +0,0 @@
|
|||||||
import {
|
|
||||||
invokePluginFunc,
|
|
||||||
store,
|
|
||||||
RegisterExtensionPoint,
|
|
||||||
StoreService,
|
|
||||||
DataService,
|
|
||||||
PluginService,
|
|
||||||
} from "@janhq/core";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a collection on data store
|
|
||||||
*
|
|
||||||
* @param name name of the collection to create
|
|
||||||
* @param schema schema of the collection to create, include fields and their types
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function createCollection({
|
|
||||||
name,
|
|
||||||
schema,
|
|
||||||
}: {
|
|
||||||
name: string;
|
|
||||||
schema?: { [key: string]: any };
|
|
||||||
}): Promise<void> {
|
|
||||||
return invokePluginFunc(MODULE_PATH, "createCollection", name, schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a collection
|
|
||||||
*
|
|
||||||
* @param name name of the collection to delete
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function deleteCollection(name: string): Promise<void> {
|
|
||||||
return invokePluginFunc(MODULE_PATH, "deleteCollection", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a value to a collection
|
|
||||||
*
|
|
||||||
* @param collectionName name of the collection
|
|
||||||
* @param value value to insert
|
|
||||||
* @returns Promise<any>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function insertOne({
|
|
||||||
collectionName,
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
collectionName: string;
|
|
||||||
value: any;
|
|
||||||
}): Promise<any> {
|
|
||||||
return invokePluginFunc(MODULE_PATH, "insertOne", collectionName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update value of a collection's record
|
|
||||||
*
|
|
||||||
* @param collectionName name of the collection
|
|
||||||
* @param key key of the record to update
|
|
||||||
* @param value value to update
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function updateOne({
|
|
||||||
collectionName,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
}: {
|
|
||||||
collectionName: string;
|
|
||||||
key: string;
|
|
||||||
value: any;
|
|
||||||
}): Promise<void> {
|
|
||||||
return invokePluginFunc(MODULE_PATH, "updateOne", collectionName, key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates all records that match a selector in a collection in the data store.
|
|
||||||
* @param collectionName - The name of the collection containing the records to update.
|
|
||||||
* @param selector - The selector to use to get the records to update.
|
|
||||||
* @param value - The new value for the records.
|
|
||||||
* @returns {Promise<void>} A promise that resolves when the records are updated.
|
|
||||||
*/
|
|
||||||
function updateMany({
|
|
||||||
collectionName,
|
|
||||||
value,
|
|
||||||
selector,
|
|
||||||
}: {
|
|
||||||
collectionName: string;
|
|
||||||
value: any;
|
|
||||||
selector?: { [key: string]: any };
|
|
||||||
}): Promise<void> {
|
|
||||||
return invokePluginFunc(
|
|
||||||
MODULE_PATH,
|
|
||||||
"updateMany",
|
|
||||||
collectionName,
|
|
||||||
value,
|
|
||||||
selector
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a collection's record
|
|
||||||
*
|
|
||||||
* @param collectionName name of the collection
|
|
||||||
* @param key key of the record to delete
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function deleteOne({
|
|
||||||
collectionName,
|
|
||||||
key,
|
|
||||||
}: {
|
|
||||||
collectionName: string;
|
|
||||||
key: string;
|
|
||||||
}): Promise<void> {
|
|
||||||
return invokePluginFunc(MODULE_PATH, "deleteOne", collectionName, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all records with a matching key from a collection in the data store.
|
|
||||||
*
|
|
||||||
* @param collectionName name of the collection
|
|
||||||
* @param selector selector to use to get the records to delete.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function deleteMany({
|
|
||||||
collectionName,
|
|
||||||
selector,
|
|
||||||
}: {
|
|
||||||
collectionName: string;
|
|
||||||
selector?: { [key: string]: any };
|
|
||||||
}): Promise<void> {
|
|
||||||
return invokePluginFunc(MODULE_PATH, "deleteMany", collectionName, selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a record from a collection in the data store.
|
|
||||||
* @param {string} collectionName - The name of the collection containing the record to retrieve.
|
|
||||||
* @param {string} key - The key of the record to retrieve.
|
|
||||||
* @returns {Promise<any>} A promise that resolves when the record is retrieved.
|
|
||||||
*/
|
|
||||||
function findOne({
|
|
||||||
collectionName,
|
|
||||||
key,
|
|
||||||
}: {
|
|
||||||
collectionName: string;
|
|
||||||
key: string;
|
|
||||||
}): Promise<any> {
|
|
||||||
return invokePluginFunc(MODULE_PATH, "findOne", collectionName, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets records in a collection in the data store using a selector.
|
|
||||||
* @param {string} collectionName - The name of the collection containing the record to get the value from.
|
|
||||||
* @param {{ [key: string]: any }} selector - The selector to use to get the value from the record.
|
|
||||||
* @param {[{ [key: string]: any }]} sort - The sort options to use to retrieve records.
|
|
||||||
* @returns {Promise<any>} A promise that resolves with the selected value.
|
|
||||||
*/
|
|
||||||
function findMany({
|
|
||||||
collectionName,
|
|
||||||
selector,
|
|
||||||
sort,
|
|
||||||
}: {
|
|
||||||
collectionName: string;
|
|
||||||
selector: { [key: string]: any };
|
|
||||||
sort?: [{ [key: string]: any }];
|
|
||||||
}): Promise<any> {
|
|
||||||
return invokePluginFunc(
|
|
||||||
MODULE_PATH,
|
|
||||||
"findMany",
|
|
||||||
collectionName,
|
|
||||||
selector,
|
|
||||||
sort
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onStart() {
|
|
||||||
createCollection({ name: "conversations", schema: {} });
|
|
||||||
createCollection({ name: "messages", schema: {} });
|
|
||||||
createCollection({ name: "bots", schema: {} });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register all the above functions and objects with the relevant extension points
|
|
||||||
// prettier-ignore
|
|
||||||
export function init({ register }: { register: RegisterExtensionPoint }) {
|
|
||||||
register(PluginService.OnStart, PLUGIN_NAME, onStart);
|
|
||||||
register(StoreService.CreateCollection, createCollection.name, createCollection);
|
|
||||||
register(StoreService.DeleteCollection, deleteCollection.name, deleteCollection);
|
|
||||||
|
|
||||||
register(StoreService.InsertOne, insertOne.name, insertOne);
|
|
||||||
register(StoreService.UpdateOne, updateOne.name, updateOne);
|
|
||||||
register(StoreService.UpdateMany, updateMany.name, updateMany);
|
|
||||||
register(StoreService.DeleteOne, deleteOne.name, deleteOne);
|
|
||||||
register(StoreService.DeleteMany, deleteMany.name, deleteMany);
|
|
||||||
register(StoreService.FindOne, findOne.name, findOne);
|
|
||||||
register(StoreService.FindMany, findMany.name, findMany);
|
|
||||||
|
|
||||||
// for conversations management
|
|
||||||
register(DataService.GetConversations, getConversations.name, getConversations);
|
|
||||||
register(DataService.GetConversationById,getConversationById.name,getConversationById);
|
|
||||||
register(DataService.CreateConversation, createConversation.name, createConversation);
|
|
||||||
register(DataService.UpdateConversation, updateConversation.name, updateConversation);
|
|
||||||
register(DataService.DeleteConversation, deleteConversation.name, deleteConversation);
|
|
||||||
|
|
||||||
// for messages management
|
|
||||||
register(DataService.UpdateMessage, updateMessage.name, updateMessage);
|
|
||||||
register(DataService.CreateMessage, createMessage.name, createMessage);
|
|
||||||
register(DataService.GetConversationMessages, getConversationMessages.name, getConversationMessages);
|
|
||||||
|
|
||||||
// for bots management
|
|
||||||
register(DataService.CreateBot, createBot.name, createBot);
|
|
||||||
register(DataService.GetBots, getBots.name, getBots);
|
|
||||||
register(DataService.GetBotById, getBotById.name, getBotById);
|
|
||||||
register(DataService.DeleteBot, deleteBot.name, deleteBot);
|
|
||||||
register(DataService.UpdateBot, updateBot.name, updateBot);
|
|
||||||
|
|
||||||
// for plugin manifest
|
|
||||||
register(DataService.GetPluginManifest, getPluginManifest.name, getPluginManifest)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConversations(): Promise<any> {
|
|
||||||
return store.findMany("conversations", {}, [{ updatedAt: "desc" }]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConversationById(id: string): Promise<any> {
|
|
||||||
return store.findOne("conversations", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createConversation(conversation: any): Promise<number | undefined> {
|
|
||||||
return store.insertOne("conversations", conversation);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateConversation(conversation: any): Promise<void> {
|
|
||||||
return store.updateOne("conversations", conversation._id, conversation);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMessage(message: any): Promise<number | undefined> {
|
|
||||||
return store.insertOne("messages", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMessage(message: any): Promise<void> {
|
|
||||||
return store.updateOne("messages", message._id, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteConversation(id: any) {
|
|
||||||
return store
|
|
||||||
.deleteOne("conversations", id)
|
|
||||||
.then(() => store.deleteMany("messages", { conversationId: id }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConversationMessages(conversationId: any) {
|
|
||||||
return store.findMany("messages", { conversationId }, [
|
|
||||||
{ createdAt: "desc" },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBot(bot: any): Promise<void> {
|
|
||||||
console.debug("Creating bot", JSON.stringify(bot, null, 2));
|
|
||||||
return store
|
|
||||||
.insertOne("bots", bot)
|
|
||||||
.then(() => {
|
|
||||||
console.debug("Bot created", JSON.stringify(bot, null, 2));
|
|
||||||
return Promise.resolve();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Error creating bot", err);
|
|
||||||
return Promise.reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBots(): Promise<any> {
|
|
||||||
console.debug("Getting bots");
|
|
||||||
return store
|
|
||||||
.findMany("bots", { name: { $gt: null } })
|
|
||||||
.then((bots) => {
|
|
||||||
console.debug("Bots retrieved", JSON.stringify(bots, null, 2));
|
|
||||||
return Promise.resolve(bots);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Error getting bots", err);
|
|
||||||
return Promise.reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteBot(id: string): Promise<any> {
|
|
||||||
console.debug("Deleting bot", id);
|
|
||||||
return store
|
|
||||||
.deleteOne("bots", id)
|
|
||||||
.then(() => {
|
|
||||||
console.debug("Bot deleted", id);
|
|
||||||
return Promise.resolve();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Error deleting bot", err);
|
|
||||||
return Promise.reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBot(bot: any): Promise<void> {
|
|
||||||
console.debug("Updating bot", JSON.stringify(bot, null, 2));
|
|
||||||
return store
|
|
||||||
.updateOne("bots", bot._id, bot)
|
|
||||||
.then(() => {
|
|
||||||
console.debug("Bot updated");
|
|
||||||
return Promise.resolve();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Error updating bot", err);
|
|
||||||
return Promise.reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBotById(botId: string): Promise<any> {
|
|
||||||
console.debug("Getting bot", botId);
|
|
||||||
return store
|
|
||||||
.findOne("bots", botId)
|
|
||||||
.then((bot) => {
|
|
||||||
console.debug("Bot retrieved", JSON.stringify(bot, null, 2));
|
|
||||||
return Promise.resolve(bot);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error("Error getting bot", err);
|
|
||||||
return Promise.reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the plugin manifest by importing the remote model catalog and clearing the cache to get the latest version.
|
|
||||||
* A timestamp is added to the URL to prevent caching.
|
|
||||||
* @returns A Promise that resolves with the plugin manifest.
|
|
||||||
*/
|
|
||||||
function getPluginManifest(): Promise<any> {
|
|
||||||
// Clear cache to get the latest model catalog
|
|
||||||
delete require.cache[
|
|
||||||
require.resolve(/* webpackIgnore: true */ PLUGIN_CATALOG)
|
|
||||||
];
|
|
||||||
// Import the remote model catalog
|
|
||||||
// Add a timestamp to the URL to prevent caching
|
|
||||||
return import(
|
|
||||||
/* webpackIgnore: true */ PLUGIN_CATALOG + `?t=${Date.now()}`
|
|
||||||
).then((module) => module.default);
|
|
||||||
}
|
|
||||||
@ -1,246 +0,0 @@
|
|||||||
var PouchDB = require("pouchdb-node");
|
|
||||||
PouchDB.plugin(require("pouchdb-find"));
|
|
||||||
var path = require("path");
|
|
||||||
var { app } = require("electron");
|
|
||||||
var fs = require("fs");
|
|
||||||
|
|
||||||
const dbs: Record<string, any> = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a collection on data store
|
|
||||||
*
|
|
||||||
* @param name name of the collection to create
|
|
||||||
* @param schema schema of the collection to create, include fields and their types
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function createCollection(name: string, schema?: { [key: string]: any }): Promise<void> {
|
|
||||||
return new Promise<void>((resolve) => {
|
|
||||||
const dbPath = path.join(appPath(), "databases");
|
|
||||||
if (!fs.existsSync(dbPath)) fs.mkdirSync(dbPath);
|
|
||||||
const db = new PouchDB(`${path.join(dbPath, name)}`);
|
|
||||||
dbs[name] = db;
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a collection
|
|
||||||
*
|
|
||||||
* @param name name of the collection to delete
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function deleteCollection(name: string): Promise<void> {
|
|
||||||
// Do nothing with Unstructured Database
|
|
||||||
return dbs[name].destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Insert a value to a collection
|
|
||||||
*
|
|
||||||
* @param collectionName name of the collection
|
|
||||||
* @param value value to insert
|
|
||||||
* @returns Promise<any>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function insertOne(collectionName: string, value: any): Promise<any> {
|
|
||||||
if (!value._id) return dbs[collectionName].post(value).then((doc) => doc.id);
|
|
||||||
return dbs[collectionName].put(value).then((doc) => doc.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update value of a collection's record
|
|
||||||
*
|
|
||||||
* @param collectionName name of the collection
|
|
||||||
* @param key key of the record to update
|
|
||||||
* @param value value to update
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function updateOne(collectionName: string, key: string, value: any): Promise<void> {
|
|
||||||
console.debug(`updateOne ${collectionName}: ${key} - ${JSON.stringify(value)}`);
|
|
||||||
return dbs[collectionName].get(key).then((doc) => {
|
|
||||||
return dbs[collectionName].put({
|
|
||||||
_id: key,
|
|
||||||
_rev: doc._rev,
|
|
||||||
...value,
|
|
||||||
},
|
|
||||||
{ force: true });
|
|
||||||
}).then((res: any) => {
|
|
||||||
console.info(`updateOne ${collectionName} result: ${JSON.stringify(res)}`);
|
|
||||||
}).catch((err: any) => {
|
|
||||||
console.error(`updateOne ${collectionName} error: ${err}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update value of a collection's records
|
|
||||||
*
|
|
||||||
* @param collectionName name of the collection
|
|
||||||
* @param selector selector of records to update
|
|
||||||
* @param value value to update
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function updateMany(collectionName: string, value: any, selector?: { [key: string]: any }): Promise<any> {
|
|
||||||
// Creates keys from selector for indexing
|
|
||||||
const keys = selector ? Object.keys(selector) : [];
|
|
||||||
|
|
||||||
// At a basic level, there are two steps to running a query: createIndex()
|
|
||||||
// (to define which fields to index) and find() (to query the index).
|
|
||||||
return (
|
|
||||||
keys.length > 0
|
|
||||||
? dbs[collectionName].createIndex({
|
|
||||||
// There is selector so we need to create index
|
|
||||||
index: { fields: keys },
|
|
||||||
})
|
|
||||||
: Promise.resolve()
|
|
||||||
) // No selector, so no need to create index
|
|
||||||
.then(() =>
|
|
||||||
dbs[collectionName].find({
|
|
||||||
// Find documents using Mango queries
|
|
||||||
selector,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then((data) => {
|
|
||||||
const docs = data.docs.map((doc) => {
|
|
||||||
// Update doc with new value
|
|
||||||
return (doc = {
|
|
||||||
...doc,
|
|
||||||
...value,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return dbs[collectionName].bulkDocs(docs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a collection's record
|
|
||||||
*
|
|
||||||
* @param collectionName name of the collection
|
|
||||||
* @param key key of the record to delete
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function deleteOne(collectionName: string, key: string): Promise<void> {
|
|
||||||
return findOne(collectionName, key).then((doc) => dbs[collectionName].remove(doc));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a collection records by selector
|
|
||||||
*
|
|
||||||
* @param {string} collectionName name of the collection
|
|
||||||
* @param {{ [key: string]: any }} selector selector for retrieving records.
|
|
||||||
* @returns Promise<void>
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function deleteMany(collectionName: string, selector?: { [key: string]: any }): Promise<void> {
|
|
||||||
// Creates keys from selector for indexing
|
|
||||||
const keys = selector ? Object.keys(selector) : [];
|
|
||||||
|
|
||||||
// At a basic level, there are two steps to running a query: createIndex()
|
|
||||||
// (to define which fields to index) and find() (to query the index).
|
|
||||||
return (
|
|
||||||
keys.length > 0
|
|
||||||
? dbs[collectionName].createIndex({
|
|
||||||
// There is selector so we need to create index
|
|
||||||
index: { fields: keys },
|
|
||||||
})
|
|
||||||
: Promise.resolve()
|
|
||||||
) // No selector, so no need to create index
|
|
||||||
.then(() =>
|
|
||||||
dbs[collectionName].find({
|
|
||||||
// Find documents using Mango queries
|
|
||||||
selector,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.then((data) => {
|
|
||||||
return Promise.all(
|
|
||||||
// Remove documents
|
|
||||||
data.docs.map((doc) => {
|
|
||||||
return dbs[collectionName].remove(doc);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a record from a collection in the data store.
|
|
||||||
* @param {string} collectionName - The name of the collection containing the record to retrieve.
|
|
||||||
* @param {string} key - The key of the record to retrieve.
|
|
||||||
* @returns {Promise<any>} A promise that resolves when the record is retrieved.
|
|
||||||
*/
|
|
||||||
function findOne(collectionName: string, key: string): Promise<any> {
|
|
||||||
return dbs[collectionName].get(key).catch(() => undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets records in a collection in the data store using a selector.
|
|
||||||
* @param {string} collectionName - The name of the collection containing records to retrieve.
|
|
||||||
* @param {{ [key: string]: any }} selector - The selector to use to retrieve records.
|
|
||||||
* @param {[{ [key: string]: any }]} sort - The sort options to use to retrieve records.
|
|
||||||
* @returns {Promise<any>} A promise that resolves with the selected records.
|
|
||||||
*/
|
|
||||||
function findMany(
|
|
||||||
collectionName: string,
|
|
||||||
selector?: { [key: string]: any },
|
|
||||||
sort?: [{ [key: string]: any }]
|
|
||||||
): Promise<any> {
|
|
||||||
const keys = selector ? Object.keys(selector) : [];
|
|
||||||
const sortKeys = sort ? sort.flatMap((e) => (e ? Object.keys(e) : undefined)) : [];
|
|
||||||
|
|
||||||
// Note that we are specifying that the field must be greater than or equal to null
|
|
||||||
// which is a workaround for the fact that the Mango query language requires us to have a selector.
|
|
||||||
// In CouchDB collation order, null is the "lowest" value, and so this will return all documents regardless of their field value.
|
|
||||||
sortKeys.forEach((key) => {
|
|
||||||
if (!keys.includes(key)) {
|
|
||||||
selector = { ...selector, [key]: { $gt: null } };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// There is no selector & sort, so we can just use allDocs() to get all the documents.
|
|
||||||
if (sortKeys.concat(keys).length === 0) {
|
|
||||||
return dbs[collectionName]
|
|
||||||
.allDocs({
|
|
||||||
include_docs: true,
|
|
||||||
endkey: "_design",
|
|
||||||
inclusive_end: false,
|
|
||||||
})
|
|
||||||
.then((data) => data.rows.map((row) => row.doc));
|
|
||||||
}
|
|
||||||
// At a basic level, there are two steps to running a query: createIndex()
|
|
||||||
// (to define which fields to index) and find() (to query the index).
|
|
||||||
return dbs[collectionName]
|
|
||||||
.createIndex({
|
|
||||||
// Create index for selector & sort
|
|
||||||
index: { fields: sortKeys.concat(keys) },
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// Find documents using Mango queries
|
|
||||||
return dbs[collectionName].find({
|
|
||||||
selector,
|
|
||||||
sort,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.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,
|
|
||||||
insertOne,
|
|
||||||
findOne,
|
|
||||||
findMany,
|
|
||||||
updateOne,
|
|
||||||
updateMany,
|
|
||||||
deleteOne,
|
|
||||||
deleteMany,
|
|
||||||
};
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@janhq/data-plugin",
|
|
||||||
"version": "1.0.19",
|
|
||||||
"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",
|
|
||||||
"module": "dist/cjs/module.js",
|
|
||||||
"author": "Jan <service@jan.ai>",
|
|
||||||
"license": "AGPL-3.0",
|
|
||||||
"supportCloudNative": true,
|
|
||||||
"url": "/plugins/data-plugin/index.js",
|
|
||||||
"activationPoints": [
|
|
||||||
"init"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc -b ./config/tsconfig.esm.json && tsc -b ./config/tsconfig.cjs.json && webpack --config webpack.config.js",
|
|
||||||
"build:deps": "electron-rebuild -f -w leveldown@5.6.0 --arch=arm64 -v 26.2.1 && node-gyp -C ./node_modules/leveldown clean && mkdir -p ./node_modules/leveldown/prebuilds/darwin-arm64 && cp ./node_modules/leveldown/bin/darwin-arm64-116/leveldown.node ./node_modules/leveldown/prebuilds/darwin-arm64/node.napi.node",
|
|
||||||
"postinstall": "rimraf *.tgz --glob && npm run build",
|
|
||||||
"build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
"import": "./dist/esm/index.js",
|
|
||||||
"require": "./dist/cjs/module.js",
|
|
||||||
"default": "./dist/esm/index.js"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"cpx": "^1.5.0",
|
|
||||||
"node-pre-gyp": "^0.17.0",
|
|
||||||
"rimraf": "^3.0.2",
|
|
||||||
"ts-loader": "^9.4.4",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"typescript": "^5.2.2",
|
|
||||||
"webpack": "^5.88.2",
|
|
||||||
"webpack-cli": "^5.1.4"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist/**",
|
|
||||||
"package.json",
|
|
||||||
"node_modules"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"@janhq/core": "^0.1.7",
|
|
||||||
"electron": "26.2.1",
|
|
||||||
"electron-rebuild": "^3.2.9",
|
|
||||||
"node-gyp": "^9.4.1",
|
|
||||||
"pouchdb-find": "^8.0.1",
|
|
||||||
"pouchdb-node": "^8.0.1"
|
|
||||||
},
|
|
||||||
"bundleDependencies": [
|
|
||||||
"pouchdb-node",
|
|
||||||
"pouchdb-find"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
const webpack = require("webpack");
|
|
||||||
const packageJson = require("./package.json");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
experiments: { outputModule: true },
|
|
||||||
entry: "./index.ts", // Adjust the entry point to match your project's main file
|
|
||||||
mode: "production",
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.tsx?$/,
|
|
||||||
use: "ts-loader",
|
|
||||||
exclude: /node_modules/,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
PLUGIN_NAME: JSON.stringify(packageJson.name),
|
|
||||||
MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
|
||||||
PLUGIN_CATALOG: JSON.stringify(
|
|
||||||
"https://cdn.jsdelivr.net/npm/@janhq/plugin-catalog@latest/dist/index.js"
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
output: {
|
|
||||||
filename: "esm/index.js", // Adjust the output file name as needed
|
|
||||||
path: path.resolve(__dirname, "dist"),
|
|
||||||
library: { type: "module" }, // Specify ESM output format
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: [".ts", ".js"],
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
minimize: false,
|
|
||||||
},
|
|
||||||
// Add loaders and other configuration as needed for your project
|
|
||||||
};
|
|
||||||
3
plugins/inference-plugin/@types/global.d.ts
vendored
3
plugins/inference-plugin/@types/global.d.ts
vendored
@ -1,3 +0,0 @@
|
|||||||
declare const PLUGIN_NAME: string;
|
|
||||||
declare const MODULE_PATH: string;
|
|
||||||
declare const INFERENCE_URL: string;
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
import {
|
|
||||||
EventName,
|
|
||||||
InferenceService,
|
|
||||||
NewMessageRequest,
|
|
||||||
PluginService,
|
|
||||||
events,
|
|
||||||
store,
|
|
||||||
invokePluginFunc,
|
|
||||||
} from "@janhq/core";
|
|
||||||
import { Observable } from "rxjs";
|
|
||||||
|
|
||||||
const initModel = async (product) =>
|
|
||||||
invokePluginFunc(MODULE_PATH, "initModel", product);
|
|
||||||
|
|
||||||
const stopModel = () => {
|
|
||||||
invokePluginFunc(MODULE_PATH, "killSubprocess");
|
|
||||||
};
|
|
||||||
|
|
||||||
function requestInference(
|
|
||||||
recentMessages: any[],
|
|
||||||
bot?: any
|
|
||||||
): Observable<string> {
|
|
||||||
return new Observable((subscriber) => {
|
|
||||||
const requestBody = JSON.stringify({
|
|
||||||
messages: recentMessages,
|
|
||||||
stream: true,
|
|
||||||
model: "gpt-3.5-turbo",
|
|
||||||
max_tokens: bot?.maxTokens ?? 2048,
|
|
||||||
frequency_penalty: bot?.frequencyPenalty ?? 0,
|
|
||||||
presence_penalty: bot?.presencePenalty ?? 0,
|
|
||||||
temperature: bot?.customTemperature ?? 0,
|
|
||||||
});
|
|
||||||
console.debug(`Request body: ${requestBody}`);
|
|
||||||
fetch(INFERENCE_URL, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Accept: "text/event-stream",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
body: requestBody,
|
|
||||||
})
|
|
||||||
.then(async (response) => {
|
|
||||||
const stream = response.body;
|
|
||||||
const decoder = new TextDecoder("utf-8");
|
|
||||||
const reader = stream?.getReader();
|
|
||||||
let content = "";
|
|
||||||
|
|
||||||
while (true && reader) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
if (done) {
|
|
||||||
console.log("SSE stream closed");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const text = decoder.decode(value);
|
|
||||||
const lines = text.trim().split("\n");
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.startsWith("data: ") && !line.includes("data: [DONE]")) {
|
|
||||||
const data = JSON.parse(line.replace("data: ", ""));
|
|
||||||
content += data.choices[0]?.delta?.content ?? "";
|
|
||||||
if (content.startsWith("assistant: ")) {
|
|
||||||
content = content.replace("assistant: ", "");
|
|
||||||
}
|
|
||||||
subscriber.next(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscriber.complete();
|
|
||||||
})
|
|
||||||
.catch((err) => subscriber.error(err));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function retrieveLastTenMessages(conversationId: string, bot?: any) {
|
|
||||||
// TODO: Common collections should be able to access via core functions instead of store
|
|
||||||
const messageHistory =
|
|
||||||
(await store.findMany("messages", { conversationId }, [
|
|
||||||
{ createdAt: "asc" },
|
|
||||||
])) ?? [];
|
|
||||||
|
|
||||||
let recentMessages = messageHistory
|
|
||||||
.filter(
|
|
||||||
(e) => e.message !== "" && (e.user === "user" || e.user === "assistant")
|
|
||||||
)
|
|
||||||
.slice(-9)
|
|
||||||
.map((message) => ({
|
|
||||||
content: message.message.trim(),
|
|
||||||
role: message.user === "user" ? "user" : "assistant",
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (bot && bot.systemPrompt) {
|
|
||||||
// append bot's system prompt
|
|
||||||
recentMessages = [
|
|
||||||
{
|
|
||||||
content: `[INST] ${bot.systemPrompt}`,
|
|
||||||
role: "system",
|
|
||||||
},
|
|
||||||
...recentMessages,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug(`Last 10 messages: ${JSON.stringify(recentMessages, null, 2)}`);
|
|
||||||
|
|
||||||
return recentMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleMessageRequest(data: NewMessageRequest) {
|
|
||||||
const conversation = await store.findOne(
|
|
||||||
"conversations",
|
|
||||||
data.conversationId
|
|
||||||
);
|
|
||||||
let bot = undefined;
|
|
||||||
if (conversation.botId != null) {
|
|
||||||
bot = await store.findOne("bots", conversation.botId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const recentMessages = await retrieveLastTenMessages(
|
|
||||||
data.conversationId,
|
|
||||||
bot
|
|
||||||
);
|
|
||||||
const message = {
|
|
||||||
...data,
|
|
||||||
message: "",
|
|
||||||
user: "assistant",
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
_id: undefined,
|
|
||||||
};
|
|
||||||
// TODO: Common collections should be able to access via core functions instead of store
|
|
||||||
const id = await store.insertOne("messages", message);
|
|
||||||
|
|
||||||
message._id = id;
|
|
||||||
events.emit(EventName.OnNewMessageResponse, message);
|
|
||||||
|
|
||||||
requestInference(recentMessages, bot).subscribe({
|
|
||||||
next: (content) => {
|
|
||||||
message.message = content;
|
|
||||||
events.emit(EventName.OnMessageResponseUpdate, message);
|
|
||||||
},
|
|
||||||
complete: async () => {
|
|
||||||
message.message = message.message.trim();
|
|
||||||
// TODO: Common collections should be able to access via core functions instead of store
|
|
||||||
await store.updateOne("messages", message._id, message);
|
|
||||||
events.emit("OnMessageResponseFinished", message);
|
|
||||||
// events.emit(EventName.OnMessageResponseFinished, message);
|
|
||||||
},
|
|
||||||
error: async (err) => {
|
|
||||||
message.message =
|
|
||||||
message.message.trim() + "\n" + "Error occurred: " + err.message;
|
|
||||||
events.emit(EventName.OnMessageResponseUpdate, message);
|
|
||||||
// TODO: Common collections should be able to access via core functions instead of store
|
|
||||||
await store.updateOne("messages", message._id, message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function inferenceRequest(data: NewMessageRequest): Promise<any> {
|
|
||||||
const message = {
|
|
||||||
...data,
|
|
||||||
message: "",
|
|
||||||
user: "assistant",
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
const recentMessages = await retrieveLastTenMessages(data.conversationId);
|
|
||||||
requestInference([
|
|
||||||
...recentMessages,
|
|
||||||
{ role: "user", content: data.message },
|
|
||||||
]).subscribe({
|
|
||||||
next: (content) => {
|
|
||||||
message.message = content;
|
|
||||||
},
|
|
||||||
complete: async () => {
|
|
||||||
resolve(message);
|
|
||||||
},
|
|
||||||
error: async (err) => {
|
|
||||||
reject(err);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerListener = () => {
|
|
||||||
events.on(EventName.OnNewMessageRequest, handleMessageRequest);
|
|
||||||
};
|
|
||||||
|
|
||||||
const killSubprocess = () => {
|
|
||||||
invokePluginFunc(MODULE_PATH, "killSubprocess");
|
|
||||||
};
|
|
||||||
|
|
||||||
const onStart = async () => {
|
|
||||||
// Try killing any existing subprocesses related to Nitro
|
|
||||||
killSubprocess();
|
|
||||||
|
|
||||||
registerListener();
|
|
||||||
};
|
|
||||||
// Register all the above functions and objects with the relevant extension points
|
|
||||||
export function init({ register }) {
|
|
||||||
register(PluginService.OnStart, PLUGIN_NAME, onStart);
|
|
||||||
register(InferenceService.InitModel, initModel.name, initModel);
|
|
||||||
register(InferenceService.StopModel, stopModel.name, stopModel);
|
|
||||||
register(
|
|
||||||
InferenceService.InferenceRequest,
|
|
||||||
inferenceRequest.name,
|
|
||||||
inferenceRequest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -2,7 +2,6 @@
|
|||||||
"name": "@janhq/inference-plugin",
|
"name": "@janhq/inference-plugin",
|
||||||
"version": "1.0.21",
|
"version": "1.0.21",
|
||||||
"description": "Inference Plugin, powered by @janhq/nitro, bring a high-performance Llama model inference in pure C++.",
|
"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",
|
"main": "dist/index.js",
|
||||||
"module": "dist/module.js",
|
"module": "dist/module.js",
|
||||||
"author": "Jan <service@jan.ai>",
|
"author": "Jan <service@jan.ai>",
|
||||||
@ -35,7 +34,7 @@
|
|||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@janhq/core": "^0.1.6",
|
"@janhq/core": "file:../../core",
|
||||||
"download-cli": "^1.1.1",
|
"download-cli": "^1.1.1",
|
||||||
"kill-port": "^2.0.1",
|
"kill-port": "^2.0.1",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
|
|||||||
2
plugins/inference-plugin/src/@types/global.d.ts
vendored
Normal file
2
plugins/inference-plugin/src/@types/global.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
declare const MODULE: string;
|
||||||
|
declare const INFERENCE_URL: string;
|
||||||
3
plugins/inference-plugin/src/helpers/message.ts
Normal file
3
plugins/inference-plugin/src/helpers/message.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const generateMessageId = () => {
|
||||||
|
return `m-${Date.now()}`
|
||||||
|
}
|
||||||
52
plugins/inference-plugin/src/helpers/sse.ts
Normal file
52
plugins/inference-plugin/src/helpers/sse.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Observable } from "rxjs";
|
||||||
|
/**
|
||||||
|
* Sends a request to the inference server to generate a response based on the recent messages.
|
||||||
|
* @param recentMessages - An array of recent messages to use as context for the inference.
|
||||||
|
* @returns An Observable that emits the generated response as a string.
|
||||||
|
*/
|
||||||
|
export function requestInference(recentMessages: any[]): Observable<string> {
|
||||||
|
return new Observable((subscriber) => {
|
||||||
|
const requestBody = JSON.stringify({
|
||||||
|
messages: recentMessages,
|
||||||
|
stream: true,
|
||||||
|
model: "gpt-3.5-turbo",
|
||||||
|
max_tokens: 2048,
|
||||||
|
});
|
||||||
|
fetch(INFERENCE_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "text/event-stream",
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
},
|
||||||
|
body: requestBody,
|
||||||
|
})
|
||||||
|
.then(async (response) => {
|
||||||
|
const stream = response.body;
|
||||||
|
const decoder = new TextDecoder("utf-8");
|
||||||
|
const reader = stream?.getReader();
|
||||||
|
let content = "";
|
||||||
|
|
||||||
|
while (true && reader) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const text = decoder.decode(value);
|
||||||
|
const lines = text.trim().split("\n");
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.startsWith("data: ") && !line.includes("data: [DONE]")) {
|
||||||
|
const data = JSON.parse(line.replace("data: ", ""));
|
||||||
|
content += data.choices[0]?.delta?.content ?? "";
|
||||||
|
if (content.startsWith("assistant: ")) {
|
||||||
|
content = content.replace("assistant: ", "");
|
||||||
|
}
|
||||||
|
subscriber.next(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subscriber.complete();
|
||||||
|
})
|
||||||
|
.catch((err) => subscriber.error(err));
|
||||||
|
});
|
||||||
|
}
|
||||||
141
plugins/inference-plugin/src/index.ts
Normal file
141
plugins/inference-plugin/src/index.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* @file This file exports a class that implements the InferencePlugin interface from the @janhq/core package.
|
||||||
|
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
||||||
|
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
||||||
|
* @version 1.0.0
|
||||||
|
* @module inference-plugin/src/index
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
EventName,
|
||||||
|
MessageHistory,
|
||||||
|
NewMessageRequest,
|
||||||
|
PluginType,
|
||||||
|
events,
|
||||||
|
executeOnMain,
|
||||||
|
} from "@janhq/core";
|
||||||
|
import { InferencePlugin } from "@janhq/core/lib/plugins";
|
||||||
|
import { requestInference } from "./helpers/sse";
|
||||||
|
import { generateMessageId } from "./helpers/message";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that implements the InferencePlugin interface from the @janhq/core package.
|
||||||
|
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
||||||
|
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
||||||
|
*/
|
||||||
|
export default class JanInferencePlugin implements InferencePlugin {
|
||||||
|
/**
|
||||||
|
* Returns the type of the plugin.
|
||||||
|
* @returns {PluginType} The type of the plugin.
|
||||||
|
*/
|
||||||
|
type(): PluginType {
|
||||||
|
return PluginType.Inference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to events emitted by the @janhq/core package.
|
||||||
|
*/
|
||||||
|
onLoad(): void {
|
||||||
|
events.on(EventName.OnNewMessageRequest, this.handleMessageRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the model inference.
|
||||||
|
*/
|
||||||
|
onUnload(): void {
|
||||||
|
this.stopModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the model with the specified file name.
|
||||||
|
* @param {string} modelFileName - The name of the model file.
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the model is initialized.
|
||||||
|
*/
|
||||||
|
initModel(modelFileName: string): Promise<void> {
|
||||||
|
return executeOnMain(MODULE, "initModel", modelFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the model.
|
||||||
|
* @returns {Promise<void>} A promise that resolves when the model is stopped.
|
||||||
|
*/
|
||||||
|
stopModel(): Promise<void> {
|
||||||
|
return executeOnMain(MODULE, "killSubprocess");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a single response inference request.
|
||||||
|
* @param {NewMessageRequest} data - The data for the inference request.
|
||||||
|
* @returns {Promise<any>} A promise that resolves with the inference response.
|
||||||
|
*/
|
||||||
|
async inferenceRequest(data: NewMessageRequest): Promise<any> {
|
||||||
|
const message = {
|
||||||
|
...data,
|
||||||
|
message: "",
|
||||||
|
user: "assistant",
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
const prompts: [MessageHistory] = [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: data.message,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const recentMessages = await (data.history ?? prompts);
|
||||||
|
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
requestInference([
|
||||||
|
...recentMessages,
|
||||||
|
{ role: "user", content: data.message },
|
||||||
|
]).subscribe({
|
||||||
|
next: (content) => {
|
||||||
|
message.message = content;
|
||||||
|
},
|
||||||
|
complete: async () => {
|
||||||
|
resolve(message);
|
||||||
|
},
|
||||||
|
error: async (err) => {
|
||||||
|
reject(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a new message request by making an inference request and emitting events.
|
||||||
|
* @param {NewMessageRequest} data - The data for the new message request.
|
||||||
|
*/
|
||||||
|
private async handleMessageRequest(data: NewMessageRequest) {
|
||||||
|
const prompts: [MessageHistory] = [
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: data.message,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const recentMessages = await (data.history ?? prompts);
|
||||||
|
const message = {
|
||||||
|
...data,
|
||||||
|
message: "",
|
||||||
|
user: "assistant",
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
_id: generateMessageId(),
|
||||||
|
};
|
||||||
|
events.emit(EventName.OnNewMessageResponse, message);
|
||||||
|
|
||||||
|
requestInference(recentMessages).subscribe({
|
||||||
|
next: (content) => {
|
||||||
|
message.message = content;
|
||||||
|
events.emit(EventName.OnMessageResponseUpdate, message);
|
||||||
|
},
|
||||||
|
complete: async () => {
|
||||||
|
message.message = message.message.trim();
|
||||||
|
events.emit(EventName.OnMessageResponseFinished, message);
|
||||||
|
},
|
||||||
|
error: async (err) => {
|
||||||
|
message.message =
|
||||||
|
message.message.trim() + "\n" + "Error occurred: " + err.message;
|
||||||
|
events.emit(EventName.OnMessageResponseUpdate, message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,8 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
}
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["./src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ const packageJson = require("./package.json");
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
entry: "./index.ts", // Adjust the entry point to match your project's main file
|
entry: "./src/index.ts", // Adjust the entry point to match your project's main file
|
||||||
mode: "production",
|
mode: "production",
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@ -17,8 +17,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
PLUGIN_NAME: JSON.stringify(packageJson.name),
|
MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
||||||
MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
|
||||||
INFERENCE_URL: JSON.stringify(process.env.INFERENCE_URL || "http://127.0.0.1:3928/inferences/llamacpp/chat_completion"),
|
INFERENCE_URL: JSON.stringify(process.env.INFERENCE_URL || "http://127.0.0.1:3928/inferences/llamacpp/chat_completion"),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,178 +0,0 @@
|
|||||||
import {
|
|
||||||
ModelManagementService,
|
|
||||||
PluginService,
|
|
||||||
RegisterExtensionPoint,
|
|
||||||
downloadFile,
|
|
||||||
deleteFile,
|
|
||||||
store,
|
|
||||||
EventName,
|
|
||||||
events
|
|
||||||
} from "@janhq/core";
|
|
||||||
import { parseToModel } from "./helper";
|
|
||||||
|
|
||||||
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<string> {
|
|
||||||
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);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a list of configured models from the model catalog URL.
|
|
||||||
* @returns A Promise that resolves to an array of configured models.
|
|
||||||
*/
|
|
||||||
async function getConfiguredModels(): Promise<any> {
|
|
||||||
// Add a timestamp to the URL to prevent caching
|
|
||||||
return import(
|
|
||||||
/* webpackIgnore: true */ MODEL_CATALOG_URL + `?t=${Date.now()}`
|
|
||||||
).then((module) =>
|
|
||||||
module.default.map((e) => {
|
|
||||||
return parseToModel(e);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store a model in the database when user start downloading it
|
|
||||||
*
|
|
||||||
* @param model Product
|
|
||||||
*/
|
|
||||||
function storeModel(model: any) {
|
|
||||||
return store.findOne("models", model._id).then((doc) => {
|
|
||||||
if (doc) {
|
|
||||||
return store.updateOne("models", model._id, model);
|
|
||||||
} else {
|
|
||||||
return store.insertOne("models", model);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the finished download time of a model
|
|
||||||
*
|
|
||||||
* @param model Product
|
|
||||||
*/
|
|
||||||
function updateFinishedDownloadAt(_id: string): Promise<any> {
|
|
||||||
return store.updateMany(
|
|
||||||
"models",
|
|
||||||
{ _id },
|
|
||||||
{ time: Date.now(), finishDownloadAt: 1 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves all finished models from the database.
|
|
||||||
*
|
|
||||||
* @returns A promise that resolves with an array of finished models.
|
|
||||||
*/
|
|
||||||
function getFinishedDownloadModels(): Promise<any> {
|
|
||||||
return store.findMany("models", { finishDownloadAt: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a model from the database.
|
|
||||||
*
|
|
||||||
* @param modelId The ID of the model to delete.
|
|
||||||
* @returns A promise that resolves when the model is deleted.
|
|
||||||
*/
|
|
||||||
function deleteDownloadModel(modelId: string): Promise<any> {
|
|
||||||
return store.deleteOne("models", modelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a model from the database by ID.
|
|
||||||
*
|
|
||||||
* @param modelId The ID of the model to retrieve.
|
|
||||||
* @returns A promise that resolves with the model.
|
|
||||||
*/
|
|
||||||
function getModelById(modelId: string): Promise<any> {
|
|
||||||
return store.findOne("models", modelId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onStart() {
|
|
||||||
store.createCollection("models", {});
|
|
||||||
if (!(window as any)?.electronAPI) {
|
|
||||||
fetchDownloadProgress(null, null).then((fileName: string) => fileName && checkDownloadProgress(fileName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register all the above functions and objects with the relevant extension points
|
|
||||||
export function init({ register }: { register: RegisterExtensionPoint }) {
|
|
||||||
register(PluginService.OnStart, PLUGIN_NAME, onStart);
|
|
||||||
|
|
||||||
register(
|
|
||||||
ModelManagementService.DownloadModel,
|
|
||||||
downloadModel.name,
|
|
||||||
downloadModel
|
|
||||||
);
|
|
||||||
register(ModelManagementService.DeleteModel, deleteModel.name, deleteModel);
|
|
||||||
register(
|
|
||||||
ModelManagementService.GetConfiguredModels,
|
|
||||||
getConfiguredModels.name,
|
|
||||||
getConfiguredModels
|
|
||||||
);
|
|
||||||
|
|
||||||
register(ModelManagementService.StoreModel, storeModel.name, storeModel);
|
|
||||||
register(
|
|
||||||
ModelManagementService.UpdateFinishedDownloadAt,
|
|
||||||
updateFinishedDownloadAt.name,
|
|
||||||
updateFinishedDownloadAt
|
|
||||||
);
|
|
||||||
|
|
||||||
register(
|
|
||||||
ModelManagementService.DeleteDownloadModel,
|
|
||||||
deleteDownloadModel.name,
|
|
||||||
deleteDownloadModel
|
|
||||||
);
|
|
||||||
register(
|
|
||||||
ModelManagementService.GetModelById,
|
|
||||||
getModelById.name,
|
|
||||||
getModelById
|
|
||||||
);
|
|
||||||
register(
|
|
||||||
ModelManagementService.GetFinishedDownloadModels,
|
|
||||||
getFinishedDownloadModels.name,
|
|
||||||
getFinishedDownloadModels
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
module.exports = {};
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es2016",
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"outDir": "./dist",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"strict": false,
|
|
||||||
"skipLibCheck": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/model-management-plugin",
|
"name": "@janhq/model-plugin",
|
||||||
"version": "1.0.13",
|
"version": "1.0.13",
|
||||||
"description": "Model Management Plugin provides model exploration and seamless downloads",
|
"description": "Model Management Plugin provides model exploration and seamless downloads",
|
||||||
"icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/queue-list.svg",
|
"icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/queue-list.svg",
|
||||||
@ -8,7 +8,7 @@
|
|||||||
"author": "Jan <service@jan.ai>",
|
"author": "Jan <service@jan.ai>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"supportCloudNative": true,
|
"supportCloudNative": true,
|
||||||
"url": "/plugins/model-management-plugin/index.js",
|
"url": "/plugins/model-plugin/index.js",
|
||||||
"activationPoints": [
|
"activationPoints": [
|
||||||
"init"
|
"init"
|
||||||
],
|
],
|
||||||
@ -29,7 +29,7 @@
|
|||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@janhq/core": "^0.1.6",
|
"@janhq/core": "file:../../core",
|
||||||
"ts-loader": "^9.5.0"
|
"ts-loader": "^9.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
48
plugins/model-plugin/src/helpers/cloudNative.ts
Normal file
48
plugins/model-plugin/src/helpers/cloudNative.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { EventName, events } from "@janhq/core";
|
||||||
|
|
||||||
|
export async function pollDownloadProgress(fileName: string) {
|
||||||
|
if (
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
typeof (window as any).electronAPI === "undefined"
|
||||||
|
) {
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
notifyProgress(fileName, intervalId);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function notifyProgress(
|
||||||
|
fileName: string,
|
||||||
|
intervalId: NodeJS.Timeout
|
||||||
|
): Promise<string> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
105
plugins/model-plugin/src/index.ts
Normal file
105
plugins/model-plugin/src/index.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { PluginType, fs, downloadFile } from "@janhq/core";
|
||||||
|
import { ModelPlugin } from "@janhq/core/lib/plugins";
|
||||||
|
import { Model, ModelCatalog } from "@janhq/core/lib/types";
|
||||||
|
import { pollDownloadProgress } from "./helpers/cloudNative";
|
||||||
|
import { parseToModel } from "./helpers/modelParser";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A plugin for managing machine learning models.
|
||||||
|
*/
|
||||||
|
export default class JanModelPlugin implements ModelPlugin {
|
||||||
|
/**
|
||||||
|
* Implements type from JanPlugin.
|
||||||
|
* @override
|
||||||
|
* @returns The type of the plugin.
|
||||||
|
*/
|
||||||
|
type(): PluginType {
|
||||||
|
return PluginType.Model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is loaded.
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
onLoad(): void {
|
||||||
|
/** Cloud Native
|
||||||
|
* TODO: Fetch all downloading progresses?
|
||||||
|
**/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is unloaded.
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
onUnload(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads a machine learning model.
|
||||||
|
* @param model - The model to download.
|
||||||
|
* @returns A Promise that resolves when the model is downloaded.
|
||||||
|
*/
|
||||||
|
async downloadModel(model: Model): Promise<void> {
|
||||||
|
await fs.mkdir("models");
|
||||||
|
downloadFile(model.downloadLink, `models/${model._id}`);
|
||||||
|
/** Cloud Native
|
||||||
|
* MARK: Poll Downloading Progress
|
||||||
|
**/
|
||||||
|
pollDownloadProgress(model._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a machine learning model.
|
||||||
|
* @param filePath - The path to the model file to delete.
|
||||||
|
* @returns A Promise that resolves when the model is deleted.
|
||||||
|
*/
|
||||||
|
deleteModel(filePath: string): Promise<void> {
|
||||||
|
return fs
|
||||||
|
.deleteFile(`models/${filePath}`)
|
||||||
|
.then(() => fs.deleteFile(`models/m-${filePath}.json`));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a machine learning model.
|
||||||
|
* @param model - The model to save.
|
||||||
|
* @returns A Promise that resolves when the model is saved.
|
||||||
|
*/
|
||||||
|
async saveModel(model: Model): Promise<void> {
|
||||||
|
await fs.writeFile(`models/m-${model._id}.json`, JSON.stringify(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all downloaded models.
|
||||||
|
* @returns A Promise that resolves with an array of all models.
|
||||||
|
*/
|
||||||
|
getDownloadedModels(): Promise<Model[]> {
|
||||||
|
return fs
|
||||||
|
.listFiles("models")
|
||||||
|
.then((files: string[]) => {
|
||||||
|
return Promise.all(
|
||||||
|
files
|
||||||
|
.filter((file) => /^m-.*\.json$/.test(file))
|
||||||
|
.map(async (file) => {
|
||||||
|
const model: Model = JSON.parse(
|
||||||
|
await fs.readFile(`models/${file}`)
|
||||||
|
);
|
||||||
|
return model;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((e) => fs.mkdir("models").then(() => []));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets all available models.
|
||||||
|
* @returns A Promise that resolves with an array of all models.
|
||||||
|
*/
|
||||||
|
getConfiguredModels(): Promise<ModelCatalog[]> {
|
||||||
|
// Add a timestamp to the URL to prevent caching
|
||||||
|
return import(
|
||||||
|
/* webpackIgnore: true */ MODEL_CATALOG_URL + `?t=${Date.now()}`
|
||||||
|
).then((module) =>
|
||||||
|
module.default.map((e) => {
|
||||||
|
return parseToModel(e);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
}
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["./src"]
|
||||||
}
|
}
|
||||||
@ -4,7 +4,7 @@ const packageJson = require("./package.json");
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
entry: "./index.ts", // Adjust the entry point to match your project's main file
|
entry: "./src/index.ts", // Adjust the entry point to match your project's main file
|
||||||
mode: "production",
|
mode: "production",
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
2
plugins/monitoring-plugin/@types/global.d.ts
vendored
2
plugins/monitoring-plugin/@types/global.d.ts
vendored
@ -1,2 +0,0 @@
|
|||||||
declare const PLUGIN_NAME: string;
|
|
||||||
declare const MODULE_PATH: string;
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
import { core, SystemMonitoringService } from "@janhq/core";
|
|
||||||
|
|
||||||
// Provide an async method to manipulate the price provided by the extension point
|
|
||||||
const getResourcesInfo = () => core.invokePluginFunc(MODULE_PATH, "getResourcesInfo");
|
|
||||||
|
|
||||||
const getCurrentLoad = () => core.invokePluginFunc(MODULE_PATH, "getCurrentLoad");
|
|
||||||
|
|
||||||
// Register all the above functions and objects with the relevant extension points
|
|
||||||
export function init({ register }) {
|
|
||||||
register(SystemMonitoringService.GetResourcesInfo, getResourcesInfo.name, getResourcesInfo);
|
|
||||||
register(SystemMonitoringService.GetCurrentLoad, getCurrentLoad.name, getCurrentLoad);
|
|
||||||
}
|
|
||||||
@ -23,16 +23,16 @@
|
|||||||
"webpack-cli": "^5.1.4"
|
"webpack-cli": "^5.1.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@janhq/core": "^0.1.6",
|
"@janhq/core": "file:../../core",
|
||||||
"systeminformation": "^5.21.8",
|
"systeminformation": "^5.21.8",
|
||||||
"ts-loader": "^9.5.0"
|
"ts-loader": "^9.5.0"
|
||||||
},
|
},
|
||||||
"bundledDependencies": [
|
|
||||||
"systeminformation"
|
|
||||||
],
|
|
||||||
"files": [
|
"files": [
|
||||||
"dist/*",
|
"dist/*",
|
||||||
"package.json",
|
"package.json",
|
||||||
"README.md"
|
"README.md"
|
||||||
|
],
|
||||||
|
"bundleDependencies": [
|
||||||
|
"systeminformation"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
1
plugins/monitoring-plugin/src/@types/global.d.ts
vendored
Normal file
1
plugins/monitoring-plugin/src/@types/global.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare const MODULE: string;
|
||||||
43
plugins/monitoring-plugin/src/index.ts
Normal file
43
plugins/monitoring-plugin/src/index.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { PluginType } from "@janhq/core";
|
||||||
|
import { MonitoringPlugin } from "@janhq/core/lib/plugins";
|
||||||
|
import { executeOnMain } from "@janhq/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JanMonitoringPlugin is a plugin that provides system monitoring functionality.
|
||||||
|
* It implements the MonitoringPlugin interface from the @janhq/core package.
|
||||||
|
*/
|
||||||
|
export default class JanMonitoringPlugin implements MonitoringPlugin {
|
||||||
|
/**
|
||||||
|
* Returns the type of the plugin.
|
||||||
|
* @returns The PluginType.SystemMonitoring value.
|
||||||
|
*/
|
||||||
|
type(): PluginType {
|
||||||
|
return PluginType.SystemMonitoring;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is loaded.
|
||||||
|
*/
|
||||||
|
onLoad(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is unloaded.
|
||||||
|
*/
|
||||||
|
onUnload(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns information about the system resources.
|
||||||
|
* @returns A Promise that resolves to an object containing information about the system resources.
|
||||||
|
*/
|
||||||
|
getResourcesInfo(): Promise<any> {
|
||||||
|
return executeOnMain(MODULE, "getResourcesInfo");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns information about the current system load.
|
||||||
|
* @returns A Promise that resolves to an object containing information about the current system load.
|
||||||
|
*/
|
||||||
|
getCurrentLoad(): Promise<any> {
|
||||||
|
return executeOnMain(MODULE, "getCurrentLoad");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,8 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
}
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["./src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ const packageJson = require("./package.json");
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
entry: "./index.ts", // Adjust the entry point to match your project's main file
|
entry: "./src/index.ts", // Adjust the entry point to match your project's main file
|
||||||
mode: "production",
|
mode: "production",
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@ -22,8 +22,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
PLUGIN_NAME: JSON.stringify(packageJson.name),
|
MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
||||||
MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|||||||
2
plugins/openai-plugin/@types/global.d.ts
vendored
2
plugins/openai-plugin/@types/global.d.ts
vendored
@ -1,2 +0,0 @@
|
|||||||
declare const PLUGIN_NAME: string;
|
|
||||||
declare const MODULE_PATH: string;
|
|
||||||
@ -1,114 +0,0 @@
|
|||||||
import {
|
|
||||||
PluginService,
|
|
||||||
EventName,
|
|
||||||
NewMessageRequest,
|
|
||||||
events,
|
|
||||||
store,
|
|
||||||
preferences,
|
|
||||||
RegisterExtensionPoint,
|
|
||||||
} from "@janhq/core";
|
|
||||||
import { Configuration, OpenAIApi } from "azure-openai";
|
|
||||||
|
|
||||||
const setRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
||||||
XMLHttpRequest.prototype.setRequestHeader = function newSetRequestHeader(key: string, val: string) {
|
|
||||||
if (key.toLocaleLowerCase() === "user-agent") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setRequestHeader.apply(this, [key, val]);
|
|
||||||
};
|
|
||||||
|
|
||||||
var openai: OpenAIApi | undefined = undefined;
|
|
||||||
|
|
||||||
const setup = async () => {
|
|
||||||
const apiKey: string = (await preferences.get(PLUGIN_NAME, "apiKey")) ?? "";
|
|
||||||
const endpoint: string = (await preferences.get(PLUGIN_NAME, "endpoint")) ?? "";
|
|
||||||
const deploymentName: string = (await preferences.get(PLUGIN_NAME, "deploymentName")) ?? "";
|
|
||||||
try {
|
|
||||||
openai = new OpenAIApi(
|
|
||||||
new Configuration({
|
|
||||||
azure: {
|
|
||||||
apiKey, //Your API key goes here
|
|
||||||
endpoint, //Your endpoint goes here. It is like: "https://endpointname.openai.azure.com/"
|
|
||||||
deploymentName, //Your deployment name goes here. It is like "chatgpt"
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
openai = undefined;
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function onStart() {
|
|
||||||
setup();
|
|
||||||
registerListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleMessageRequest(data: NewMessageRequest) {
|
|
||||||
if (!openai) {
|
|
||||||
const message = {
|
|
||||||
...data,
|
|
||||||
message: "Your API key is not set. Please set it in the plugin preferences.",
|
|
||||||
user: "GPT-3",
|
|
||||||
avatar: "https://static-assets.jan.ai/openai-icon.jpg",
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
_id: undefined,
|
|
||||||
};
|
|
||||||
const id = await store.insertOne("messages", message);
|
|
||||||
message._id = id;
|
|
||||||
events.emit(EventName.OnNewMessageResponse, message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = {
|
|
||||||
...data,
|
|
||||||
message: "",
|
|
||||||
user: "GPT-3",
|
|
||||||
avatar: "https://static-assets.jan.ai/openai-icon.jpg",
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
_id: undefined,
|
|
||||||
};
|
|
||||||
const id = await store.insertOne("messages", message);
|
|
||||||
|
|
||||||
message._id = id;
|
|
||||||
events.emit(EventName.OnNewMessageResponse, message);
|
|
||||||
const response = await openai.createChatCompletion({
|
|
||||||
messages: [{ role: "user", content: data.message }],
|
|
||||||
model: "gpt-3.5-turbo",
|
|
||||||
});
|
|
||||||
message.message = response.data.choices[0].message.content;
|
|
||||||
events.emit(EventName.OnMessageResponseUpdate, message);
|
|
||||||
await store.updateOne("messages", message._id, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerListener = () => {
|
|
||||||
events.on(EventName.OnNewMessageRequest, handleMessageRequest);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Preference update - reconfigure OpenAI
|
|
||||||
const onPreferencesUpdate = () => {
|
|
||||||
setup();
|
|
||||||
};
|
|
||||||
// Register all the above functions and objects with the relevant extension points
|
|
||||||
export function init({ register }: { register: RegisterExtensionPoint }) {
|
|
||||||
register(PluginService.OnStart, PLUGIN_NAME, onStart);
|
|
||||||
register(PluginService.OnPreferencesUpdate, PLUGIN_NAME, onPreferencesUpdate);
|
|
||||||
|
|
||||||
preferences.registerPreferences<string>(register, PLUGIN_NAME, "apiKey", "API Key", "Azure Project API Key", "");
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"endpoint",
|
|
||||||
"API Endpoint",
|
|
||||||
"Azure Deployment Endpoint API",
|
|
||||||
""
|
|
||||||
);
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"deploymentName",
|
|
||||||
"Deployment Name",
|
|
||||||
"The deployment name you chose when you deployed the model",
|
|
||||||
""
|
|
||||||
);
|
|
||||||
}
|
|
||||||
2
plugins/retrieval-plugin/@types/global.d.ts
vendored
2
plugins/retrieval-plugin/@types/global.d.ts
vendored
@ -1,2 +0,0 @@
|
|||||||
declare const PLUGIN_NAME: string;
|
|
||||||
declare const MODULE_PATH: string;
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
# 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<any>`.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { core } from "@janhq/core";
|
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
|
||||||
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!
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "retrieval-plugin",
|
|
||||||
"version": "1.0.3",
|
|
||||||
"description": "Retrieval plugin for Jan app (experimental)",
|
|
||||||
"icon": "https://raw.githubusercontent.com/tailwindlabs/heroicons/88e98b0c2b458553fbadccddc2d2f878edc0387b/src/20/solid/circle-stack.svg",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"module": "dist/module.js",
|
|
||||||
"requiredVersion": "^0.3.1",
|
|
||||||
"author": "Jan <service@jan.ai>",
|
|
||||||
"license": "MIT",
|
|
||||||
"activationPoints": [
|
|
||||||
"init"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc -b . && webpack --config webpack.config.js",
|
|
||||||
"bundle": "npm pack"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"webpack": "^5.88.2",
|
|
||||||
"webpack-cli": "^5.1.4"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@janhq/core": "^0.1.1",
|
|
||||||
"faiss-node": "^0.5.1",
|
|
||||||
"install": "^0.13.0",
|
|
||||||
"langchain": "^0.0.169",
|
|
||||||
"npm": "^10.2.0",
|
|
||||||
"pdf-parse": "^1.1.1",
|
|
||||||
"ts-loader": "^9.5.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist/*",
|
|
||||||
"package.json",
|
|
||||||
"README.md"
|
|
||||||
],
|
|
||||||
"bundleDependencies": [
|
|
||||||
"pdf-parse",
|
|
||||||
"langchain",
|
|
||||||
"faiss-node"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
/**
|
|
||||||
* The entrypoint for the plugin.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
EventName,
|
|
||||||
NewMessageRequest,
|
|
||||||
PluginService,
|
|
||||||
RegisterExtensionPoint,
|
|
||||||
invokePluginFunc,
|
|
||||||
events,
|
|
||||||
preferences,
|
|
||||||
store,
|
|
||||||
} from "@janhq/core";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register event listener.
|
|
||||||
*/
|
|
||||||
const registerListener = () => {
|
|
||||||
events.on(EventName.OnNewMessageRequest, inferenceRequest);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the `ingest` function from the `module.js` file using the `invokePluginFunc` method.
|
|
||||||
* "ingest" is the name of the function to invoke.
|
|
||||||
* @returns {Promise<any>} A promise that resolves with the result of the `run` function.
|
|
||||||
*/
|
|
||||||
function onStart(): Promise<void> {
|
|
||||||
registerListener();
|
|
||||||
ingest();
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the document ingestion directory path from the `preferences` module and invokes the `ingest` function
|
|
||||||
* from the specified module with the directory path and additional options.
|
|
||||||
* The additional options are retrieved from the `preferences` module using the `PLUGIN_NAME` constant.
|
|
||||||
*/
|
|
||||||
async function ingest() {
|
|
||||||
const path = await preferences.get(PLUGIN_NAME, "ingestDocumentDirectoryPath");
|
|
||||||
|
|
||||||
// TODO: Hiro - Add support for custom embeddings
|
|
||||||
const customizedEmbedding = undefined;
|
|
||||||
|
|
||||||
if (path && path.length > 0) {
|
|
||||||
const openAPIKey = await preferences.get(PLUGIN_NAME, "openAIApiKey");
|
|
||||||
const azureOpenAIBasePath = await preferences.get(PLUGIN_NAME, "azureOpenAIBasePath");
|
|
||||||
const azureOpenAIApiInstanceName = await preferences.get(PLUGIN_NAME, "azureOpenAIApiInstanceName");
|
|
||||||
invokePluginFunc(MODULE_PATH, "ingest", path, customizedEmbedding, {
|
|
||||||
openAIApiKey: openAPIKey?.length > 0 ? openAPIKey : undefined,
|
|
||||||
azureOpenAIApiKey: await preferences.get(PLUGIN_NAME, "azureOpenAIApiKey"),
|
|
||||||
azureOpenAIApiVersion: await preferences.get(PLUGIN_NAME, "azureOpenAIApiVersion"),
|
|
||||||
azureOpenAIApiInstanceName: azureOpenAIApiInstanceName?.length > 0 ? azureOpenAIApiInstanceName : undefined,
|
|
||||||
azureOpenAIApiDeploymentName: await preferences.get(PLUGIN_NAME, "azureOpenAIApiDeploymentNameRag"),
|
|
||||||
azureOpenAIBasePath: azureOpenAIBasePath?.length > 0 ? azureOpenAIBasePath : undefined,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the document ingestion directory path from the `preferences` module and invokes the `ingest` function
|
|
||||||
* from the specified module with the directory path and additional options.
|
|
||||||
* The additional options are retrieved from the `preferences` module using the `PLUGIN_NAME` constant.
|
|
||||||
*/
|
|
||||||
async function inferenceRequest(data: NewMessageRequest): Promise<any> {
|
|
||||||
// TODO: Hiro - Add support for custom embeddings
|
|
||||||
const customLLM = undefined;
|
|
||||||
const message = {
|
|
||||||
...data,
|
|
||||||
message: "",
|
|
||||||
user: "RAG",
|
|
||||||
createdAt: new Date().toISOString(),
|
|
||||||
_id: undefined,
|
|
||||||
};
|
|
||||||
const id = await store.insertOne("messages", message);
|
|
||||||
message._id = id;
|
|
||||||
events.emit(EventName.OnNewMessageResponse, message);
|
|
||||||
|
|
||||||
const openAPIKey = await preferences.get(PLUGIN_NAME, "openAIApiKey");
|
|
||||||
const azureOpenAIBasePath = await preferences.get(PLUGIN_NAME, "azureOpenAIBasePath");
|
|
||||||
const azureOpenAIApiInstanceName = await preferences.get(PLUGIN_NAME, "azureOpenAIApiInstanceName");
|
|
||||||
invokePluginFunc(MODULE_PATH, "chatWithDocs", data.message, customLLM, {
|
|
||||||
openAIApiKey: openAPIKey?.length > 0 ? openAPIKey : undefined,
|
|
||||||
azureOpenAIApiKey: await preferences.get(PLUGIN_NAME, "azureOpenAIApiKey"),
|
|
||||||
azureOpenAIApiVersion: await preferences.get(PLUGIN_NAME, "azureOpenAIApiVersion"),
|
|
||||||
azureOpenAIApiInstanceName: azureOpenAIApiInstanceName?.length > 0 ? azureOpenAIApiInstanceName : undefined,
|
|
||||||
azureOpenAIApiDeploymentName: await preferences.get(PLUGIN_NAME, "azureOpenAIApiDeploymentNameChat"),
|
|
||||||
azureOpenAIBasePath: azureOpenAIBasePath?.length > 0 ? azureOpenAIBasePath : undefined,
|
|
||||||
modelName: "gpt-3.5-turbo-16k",
|
|
||||||
temperature: 0.2,
|
|
||||||
}).then(async (text) => {
|
|
||||||
console.log("RAG Response:", text);
|
|
||||||
message.message = text;
|
|
||||||
|
|
||||||
events.emit(EventName.OnMessageResponseUpdate, message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Initializes the plugin by registering the extension functions with the given register function.
|
|
||||||
* @param {Function} options.register - The function to use for registering the extension functions
|
|
||||||
*/
|
|
||||||
export function init({ register }: { register: RegisterExtensionPoint }) {
|
|
||||||
register(PluginService.OnStart, PLUGIN_NAME, onStart);
|
|
||||||
register(PluginService.OnPreferencesUpdate, PLUGIN_NAME, ingest);
|
|
||||||
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"ingestDocumentDirectoryPath",
|
|
||||||
"Document Ingest Directory Path",
|
|
||||||
"The URL of the directory containing the documents to ingest",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"openAIApiKey",
|
|
||||||
"Open API Key",
|
|
||||||
"OpenAI API Key",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"azureOpenAIApiKey",
|
|
||||||
"Azure API Key",
|
|
||||||
"Azure Project API Key",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"azureOpenAIApiVersion",
|
|
||||||
"Azure API Version",
|
|
||||||
"Azure Project API Version",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"azureOpenAIApiInstanceName",
|
|
||||||
"Azure Instance Name",
|
|
||||||
"Azure Project Instance Name",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"azureOpenAIApiDeploymentNameChat",
|
|
||||||
"Azure Chat Model Deployment Name",
|
|
||||||
"Azure Project Chat Model Deployment Name (e.g. gpt-3.5-turbo-16k)",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"azureOpenAIApiDeploymentNameRag",
|
|
||||||
"Azure Text Embedding Model Deployment Name",
|
|
||||||
"Azure Project Text Embedding Model Deployment Name (e.g. text-embedding-ada-002)",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
preferences.registerPreferences<string>(
|
|
||||||
register,
|
|
||||||
PLUGIN_NAME,
|
|
||||||
"azureOpenAIBasePath",
|
|
||||||
"Azure Base Path",
|
|
||||||
"Azure Project Base Path",
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
const { app } = require("electron");
|
|
||||||
const { DirectoryLoader } = require("langchain/document_loaders/fs/directory");
|
|
||||||
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
|
|
||||||
const { PDFLoader } = require("langchain/document_loaders/fs/pdf");
|
|
||||||
const { CharacterTextSplitter } = require("langchain/text_splitter");
|
|
||||||
const { FaissStore } = require("langchain/vectorstores/faiss");
|
|
||||||
const { ChatOpenAI } = require("langchain/chat_models/openai");
|
|
||||||
const { RetrievalQAChain } = require("langchain/chains");
|
|
||||||
|
|
||||||
var db: any | undefined = undefined;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ingests documents from the specified directory
|
|
||||||
* If an `embedding` object is not provided, uses OpenAIEmbeddings.
|
|
||||||
* The resulting embeddings are stored in the database using Faiss.
|
|
||||||
* @param docDir - The directory containing the documents to ingest.
|
|
||||||
* @param embedding - An optional object used to generate embeddings for the documents.
|
|
||||||
* @param config - An optional configuration object used to create a new `OpenAIEmbeddings` object.
|
|
||||||
*/
|
|
||||||
async function ingest(docDir: string, embedding?: any, config?: any) {
|
|
||||||
const loader = new DirectoryLoader(docDir, {
|
|
||||||
".pdf": (path) => new PDFLoader(path),
|
|
||||||
});
|
|
||||||
const docs = await loader.load();
|
|
||||||
const textSplitter = new CharacterTextSplitter();
|
|
||||||
const docsQA = await textSplitter.splitDocuments(docs);
|
|
||||||
const embeddings = embedding ?? new OpenAIEmbeddings({ ...config });
|
|
||||||
db = await FaissStore.fromDocuments(await docsQA, embeddings);
|
|
||||||
console.log("Documents are ingested");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates an answer to a given question using the specified `llm` or a new `ChatOpenAI`.
|
|
||||||
* The function uses the `RetrievalQAChain` class to retrieve the most relevant document from the database and generate an answer.
|
|
||||||
* @param question - The question to generate an answer for.
|
|
||||||
* @param llm - An optional object used to generate the answer.
|
|
||||||
* @param config - An optional configuration object used to create a new `ChatOpenAI` object, can be ignored if llm is specified.
|
|
||||||
* @returns A Promise that resolves with the generated answer.
|
|
||||||
*/
|
|
||||||
async function chatWithDocs(question: string, llm?: any, config?: any): Promise<any> {
|
|
||||||
const llm_question_answer =
|
|
||||||
llm ??
|
|
||||||
new ChatOpenAI({
|
|
||||||
temperature: 0.2,
|
|
||||||
...config,
|
|
||||||
});
|
|
||||||
const qa = RetrievalQAChain.fromLLM(llm_question_answer, db.asRetriever(), {
|
|
||||||
verbose: true,
|
|
||||||
});
|
|
||||||
const answer = await qa.run(question);
|
|
||||||
return answer;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
ingest,
|
|
||||||
chatWithDocs,
|
|
||||||
};
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ModelTable from '../ModelTable'
|
import ModelTable from '../ModelTable'
|
||||||
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
|
import { activeModelAtom } from '@helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const ActiveModelTable: React.FC = () => {
|
const ActiveModelTable: React.FC = () => {
|
||||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
|
|
||||||
if (!activeModel) return null
|
if (!activeModel) return null
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,13 @@ import ModelDownloadButton from '../ModelDownloadButton'
|
|||||||
import ModelDownloadingButton from '../ModelDownloadingButton'
|
import ModelDownloadingButton from '../ModelDownloadingButton'
|
||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
|
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
|
||||||
|
import { Model } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: AssistantModel
|
model: Model
|
||||||
isRecommend: boolean
|
isRecommend: boolean
|
||||||
required?: string
|
required?: string
|
||||||
onDownloadClick?: (model: AssistantModel) => void
|
onDownloadClick?: (model: Model) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AvailableModelCard: React.FC<Props> = ({
|
const AvailableModelCard: React.FC<Props> = ({
|
||||||
@ -53,7 +54,7 @@ const AvailableModelCard: React.FC<Props> = ({
|
|||||||
description={model.shortDescription}
|
description={model.shortDescription}
|
||||||
isRecommend={isRecommend}
|
isRecommend={isRecommend}
|
||||||
name={model.name}
|
name={model.name}
|
||||||
type={model.type}
|
type={'LLM'}
|
||||||
/>
|
/>
|
||||||
{downloadButton}
|
{downloadButton}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ChatItem from '../ChatItem'
|
import ChatItem from '../ChatItem'
|
||||||
import useChatMessages from '@hooks/useChatMessages'
|
import { useAtomValue } from 'jotai'
|
||||||
|
import { getCurrentChatMessagesAtom } from '@helpers/atoms/ChatMessage.atom'
|
||||||
|
|
||||||
const ChatBody: React.FC = () => {
|
const ChatBody: React.FC = () => {
|
||||||
const { messages } = useChatMessages()
|
const messages = useAtomValue(getCurrentChatMessagesAtom)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-1 flex-col-reverse overflow-y-auto [&>*:nth-child(odd)]:bg-background">
|
<div className="flex h-full flex-1 flex-col-reverse overflow-y-auto [&>*:nth-child(odd)]:bg-background">
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import React from 'react'
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import useCreateConversation from '@hooks/useCreateConversation'
|
import useCreateConversation from '@hooks/useCreateConversation'
|
||||||
import { PlayIcon } from '@heroicons/react/24/outline'
|
import { PlayIcon } from '@heroicons/react/24/outline'
|
||||||
|
import { Model } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: AssistantModel
|
model: Model
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConversationalCard: React.FC<Props> = ({ model }) => {
|
const ConversationalCard: React.FC<Props> = ({ model }) => {
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
import { Model } from '@janhq/core/lib/types'
|
||||||
import ConversationalCard from '../ConversationalCard'
|
import ConversationalCard from '../ConversationalCard'
|
||||||
import { ChatBubbleBottomCenterTextIcon } from '@heroicons/react/24/outline'
|
import { ChatBubbleBottomCenterTextIcon } from '@heroicons/react/24/outline'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
models: AssistantModel[]
|
models: Model[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConversationalList: React.FC<Props> = ({ models }) => (
|
const ConversationalList: React.FC<Props> = ({ models }) => (
|
||||||
|
|||||||
@ -12,15 +12,11 @@ import { v4 as uuidv4 } from 'uuid'
|
|||||||
import DraggableProgressBar from '../DraggableProgressBar'
|
import DraggableProgressBar from '../DraggableProgressBar'
|
||||||
import { useSetAtom } from 'jotai'
|
import { useSetAtom } from 'jotai'
|
||||||
import { activeBotAtom } from '@helpers/atoms/Bot.atom'
|
import { activeBotAtom } from '@helpers/atoms/Bot.atom'
|
||||||
import {
|
import { rightSideBarExpandStateAtom } from '@helpers/atoms/SideBarExpand.atom'
|
||||||
rightSideBarExpandStateAtom,
|
|
||||||
} from '@helpers/atoms/SideBarExpand.atom'
|
|
||||||
import {
|
import {
|
||||||
MainViewState,
|
MainViewState,
|
||||||
setMainViewStateAtom,
|
setMainViewStateAtom,
|
||||||
} from '@helpers/atoms/MainView.atom'
|
} from '@helpers/atoms/MainView.atom'
|
||||||
import { DataService } from '@janhq/core'
|
|
||||||
import { executeSerial } from '@services/pluginService'
|
|
||||||
|
|
||||||
const CreateBotContainer: React.FC = () => {
|
const CreateBotContainer: React.FC = () => {
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
@ -30,7 +26,7 @@ const CreateBotContainer: React.FC = () => {
|
|||||||
|
|
||||||
const createBot = async (bot: Bot) => {
|
const createBot = async (bot: Bot) => {
|
||||||
try {
|
try {
|
||||||
await executeSerial(DataService.CreateBot, bot)
|
// await executeSerial(DataService.CreateBot, bot)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(err)
|
alert(err)
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
|
import { Model } from '@janhq/core/lib/types'
|
||||||
import DownloadModelContent from '../DownloadModelContent'
|
import DownloadModelContent from '../DownloadModelContent'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: AssistantModel
|
model: Model
|
||||||
isRecommend: boolean
|
isRecommend: boolean
|
||||||
required?: string
|
required?: string
|
||||||
transferred?: number
|
transferred?: number
|
||||||
onDeleteClick?: (model: AssistantModel) => void
|
onDeleteClick?: (model: Model) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const DownloadedModelCard: React.FC<Props> = ({
|
const DownloadedModelCard: React.FC<Props> = ({
|
||||||
@ -22,7 +23,7 @@ const DownloadedModelCard: React.FC<Props> = ({
|
|||||||
description={model.shortDescription}
|
description={model.shortDescription}
|
||||||
isRecommend={isRecommend}
|
isRecommend={isRecommend}
|
||||||
name={model.name}
|
name={model.name}
|
||||||
type={model.type}
|
type={'LLM'}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col justify-center">
|
<div className="flex flex-col justify-center">
|
||||||
<button onClick={() => onDeleteClick?.(model)}>Delete</button>
|
<button onClick={() => onDeleteClick?.(model)}>Delete</button>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import SearchBar from '../SearchBar'
|
|
||||||
import ModelTable from '../ModelTable'
|
import ModelTable from '../ModelTable'
|
||||||
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
import ExploreModelItemHeader from '../ExploreModelItemHeader'
|
import ExploreModelItemHeader from '../ExploreModelItemHeader'
|
||||||
import { Button } from '@uikit'
|
import { Button } from '@uikit'
|
||||||
import ModelVersionList from '../ModelVersionList'
|
import ModelVersionList from '../ModelVersionList'
|
||||||
import { Fragment, forwardRef, useEffect, useState } from 'react'
|
import { forwardRef, useEffect, useState } from 'react'
|
||||||
import SimpleTag from '../SimpleTag'
|
import SimpleTag from '../SimpleTag'
|
||||||
import {
|
import {
|
||||||
MiscellanousTag,
|
MiscellanousTag,
|
||||||
@ -18,9 +18,10 @@ import {
|
|||||||
import { displayDate } from '@utils/datetime'
|
import { displayDate } from '@utils/datetime'
|
||||||
import useGetMostSuitableModelVersion from '@hooks/useGetMostSuitableModelVersion'
|
import useGetMostSuitableModelVersion from '@hooks/useGetMostSuitableModelVersion'
|
||||||
import { toGigabytes } from '@utils/converter'
|
import { toGigabytes } from '@utils/converter'
|
||||||
|
import { ModelCatalog } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: Product
|
model: ModelCatalog
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
||||||
|
|||||||
@ -13,10 +13,11 @@ import {
|
|||||||
} from '@helpers/atoms/MainView.atom'
|
} from '@helpers/atoms/MainView.atom'
|
||||||
import ConfirmationModal from '../ConfirmationModal'
|
import ConfirmationModal from '../ConfirmationModal'
|
||||||
import { showingCancelDownloadModalAtom } from '@helpers/atoms/Modal.atom'
|
import { showingCancelDownloadModalAtom } from '@helpers/atoms/Modal.atom'
|
||||||
|
import { ModelCatalog, ModelVersion } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
suitableModel: ModelVersion
|
suitableModel: ModelVersion
|
||||||
exploreModel: Product
|
exploreModel: ModelCatalog
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExploreModelItemHeader: React.FC<Props> = ({
|
const ExploreModelItemHeader: React.FC<Props> = ({
|
||||||
|
|||||||
@ -10,9 +10,9 @@ import {
|
|||||||
} from '@helpers/atoms/MainView.atom'
|
} from '@helpers/atoms/MainView.atom'
|
||||||
import { displayDate } from '@utils/datetime'
|
import { displayDate } from '@utils/datetime'
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
|
import { activeModelAtom } from '@helpers/atoms/Model.atom'
|
||||||
import useStartStopModel from '@hooks/useStartStopModel'
|
import useStartStopModel from '@hooks/useStartStopModel'
|
||||||
import useGetModelById from '@hooks/useGetModelById'
|
import { downloadedModelAtom } from '@helpers/atoms/DownloadedModel.atom'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
conversation: Conversation
|
conversation: Conversation
|
||||||
@ -29,12 +29,12 @@ const HistoryItem: React.FC<Props> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const activeConvoId = useAtomValue(getActiveConvoIdAtom)
|
const activeConvoId = useAtomValue(getActiveConvoIdAtom)
|
||||||
const isSelected = activeConvoId === conversation._id
|
const isSelected = activeConvoId === conversation._id
|
||||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
const { startModel } = useStartStopModel()
|
const { startModel } = useStartStopModel()
|
||||||
const { getModelById } = useGetModelById()
|
|
||||||
|
|
||||||
const setMainViewState = useSetAtom(setMainViewStateAtom)
|
const setMainViewState = useSetAtom(setMainViewStateAtom)
|
||||||
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
|
const setActiveConvoId = useSetAtom(setActiveConvoIdAtom)
|
||||||
|
const models = useAtomValue(downloadedModelAtom)
|
||||||
|
|
||||||
const onClick = async () => {
|
const onClick = async () => {
|
||||||
if (conversation.modelId == null) {
|
if (conversation.modelId == null) {
|
||||||
@ -42,7 +42,7 @@ const HistoryItem: React.FC<Props> = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const model = await getModelById(conversation.modelId)
|
const model = models.find((e) => e._id === conversation.modelId)
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
if (activeModel == null) {
|
if (activeModel == null) {
|
||||||
// if there's no active model, we simply load conversation's model
|
// if there's no active model, we simply load conversation's model
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { useAtomValue, useSetAtom } from 'jotai'
|
|||||||
import SecondaryButton from '../SecondaryButton'
|
import SecondaryButton from '../SecondaryButton'
|
||||||
import { PlusIcon } from '@heroicons/react/24/outline'
|
import { PlusIcon } from '@heroicons/react/24/outline'
|
||||||
import useCreateConversation from '@hooks/useCreateConversation'
|
import useCreateConversation from '@hooks/useCreateConversation'
|
||||||
import { activeAssistantModelAtom, stateModel } from '@helpers/atoms/Model.atom'
|
import { activeModelAtom, stateModel } from '@helpers/atoms/Model.atom'
|
||||||
import {
|
import {
|
||||||
currentConvoStateAtom,
|
currentConvoStateAtom,
|
||||||
getActiveConvoIdAtom,
|
getActiveConvoIdAtom,
|
||||||
@ -18,7 +18,7 @@ import { userConversationsAtom } from '@helpers/atoms/Conversation.atom'
|
|||||||
import { showingModalNoActiveModel } from '@helpers/atoms/Modal.atom'
|
import { showingModalNoActiveModel } from '@helpers/atoms/Modal.atom'
|
||||||
|
|
||||||
const InputToolbar: React.FC = () => {
|
const InputToolbar: React.FC = () => {
|
||||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
const currentConvoState = useAtomValue(currentConvoStateAtom)
|
const currentConvoState = useAtomValue(currentConvoStateAtom)
|
||||||
const { inputState, currentConvo } = useGetInputState()
|
const { inputState, currentConvo } = useGetInputState()
|
||||||
const { requestCreateConvo } = useCreateConversation()
|
const { requestCreateConvo } = useCreateConversation()
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { MagnifyingGlassIcon, PlusIcon } from '@heroicons/react/24/outline'
|
|||||||
import useCreateConversation from '@hooks/useCreateConversation'
|
import useCreateConversation from '@hooks/useCreateConversation'
|
||||||
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
|
||||||
import { Button } from '@uikit'
|
import { Button } from '@uikit'
|
||||||
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
|
import { activeModelAtom } from '@helpers/atoms/Model.atom'
|
||||||
import { showingModalNoActiveModel } from '@helpers/atoms/Modal.atom'
|
import { showingModalNoActiveModel } from '@helpers/atoms/Modal.atom'
|
||||||
import {
|
import {
|
||||||
FeatureToggleContext,
|
FeatureToggleContext,
|
||||||
@ -20,7 +20,7 @@ import {
|
|||||||
const LeftHeaderAction: React.FC = () => {
|
const LeftHeaderAction: React.FC = () => {
|
||||||
const setMainView = useSetAtom(setMainViewStateAtom)
|
const setMainView = useSetAtom(setMainViewStateAtom)
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
const { requestCreateConvo } = useCreateConversation()
|
const { requestCreateConvo } = useCreateConversation()
|
||||||
const setShowModalNoActiveModel = useSetAtom(showingModalNoActiveModel)
|
const setShowModalNoActiveModel = useSetAtom(showingModalNoActiveModel)
|
||||||
const { experimentalFeatureEnabed } = useContext(FeatureToggleContext)
|
const { experimentalFeatureEnabed } = useContext(FeatureToggleContext)
|
||||||
|
|||||||
@ -4,16 +4,17 @@ import { useAtomValue } from 'jotai'
|
|||||||
import ModelActionButton, { ModelActionType } from '../ModelActionButton'
|
import ModelActionButton, { ModelActionType } from '../ModelActionButton'
|
||||||
import useStartStopModel from '@hooks/useStartStopModel'
|
import useStartStopModel from '@hooks/useStartStopModel'
|
||||||
import useDeleteModel from '@hooks/useDeleteModel'
|
import useDeleteModel from '@hooks/useDeleteModel'
|
||||||
import { activeAssistantModelAtom, stateModel } from '@helpers/atoms/Model.atom'
|
import { activeModelAtom, stateModel } from '@helpers/atoms/Model.atom'
|
||||||
import { toGigabytes } from '@utils/converter'
|
import { toGigabytes } from '@utils/converter'
|
||||||
|
import { Model } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: AssistantModel
|
model: Model
|
||||||
}
|
}
|
||||||
|
|
||||||
const ModelRow: React.FC<Props> = ({ model }) => {
|
const ModelRow: React.FC<Props> = ({ model }) => {
|
||||||
const { startModel, stopModel } = useStartStopModel()
|
const { startModel, stopModel } = useStartStopModel()
|
||||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
const { deleteModel } = useDeleteModel()
|
const { deleteModel } = useDeleteModel()
|
||||||
const { loading, model: currentModelState } = useAtomValue(stateModel)
|
const { loading, model: currentModelState } = useAtomValue(stateModel)
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/20/solid'
|
|||||||
import { useAtom, useAtomValue } from 'jotai'
|
import { useAtom, useAtomValue } from 'jotai'
|
||||||
import { selectedModelAtom } from '@helpers/atoms/Model.atom'
|
import { selectedModelAtom } from '@helpers/atoms/Model.atom'
|
||||||
import { downloadedModelAtom } from '@helpers/atoms/DownloadedModel.atom'
|
import { downloadedModelAtom } from '@helpers/atoms/DownloadedModel.atom'
|
||||||
|
import { Model } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
function classNames(...classes: any) {
|
function classNames(...classes: any) {
|
||||||
return classes.filter(Boolean).join(' ')
|
return classes.filter(Boolean).join(' ')
|
||||||
@ -19,7 +20,7 @@ const SelectModels: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [downloadedModels])
|
}, [downloadedModels])
|
||||||
|
|
||||||
const onModelSelected = (model: AssistantModel) => {
|
const onModelSelected = (model: Model) => {
|
||||||
setSelectedModel(model)
|
setSelectedModel(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ModelRow from '../ModelRow'
|
import ModelRow from '../ModelRow'
|
||||||
import ModelTableHeader from '../ModelTableHeader'
|
import ModelTableHeader from '../ModelTableHeader'
|
||||||
|
import { Model } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
models: AssistantModel[]
|
models: Model[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableHeaders = ['MODEL', 'FORMAT', 'SIZE', 'STATUS', 'ACTIONS']
|
const tableHeaders = ['MODEL', 'FORMAT', 'SIZE', 'STATUS', 'ACTIONS']
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { formatDownloadPercentage, toGigabytes } from '@utils/converter'
|
import { formatDownloadPercentage, toGigabytes } from '@utils/converter'
|
||||||
import Image from 'next/image'
|
|
||||||
import useDownloadModel from '@hooks/useDownloadModel'
|
import useDownloadModel from '@hooks/useDownloadModel'
|
||||||
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
|
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
|
||||||
import { atom, useAtomValue } from 'jotai'
|
import { atom, useAtomValue } from 'jotai'
|
||||||
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
|
||||||
import SimpleTag from '../SimpleTag'
|
import SimpleTag from '../SimpleTag'
|
||||||
import { RamRequired, UsecaseTag } from '../SimpleTag/TagType'
|
import { RamRequired, UsecaseTag } from '../SimpleTag/TagType'
|
||||||
|
import { ModelCatalog, ModelVersion } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: Product
|
model: ModelCatalog
|
||||||
modelVersion: ModelVersion
|
modelVersion: ModelVersion
|
||||||
isRecommended: boolean
|
isRecommended: boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ModelVersionItem from '../ModelVersionItem'
|
import ModelVersionItem from '../ModelVersionItem'
|
||||||
|
import { ModelCatalog, ModelVersion } from '@janhq/core/lib/types'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: Product
|
model: ModelCatalog
|
||||||
versions: ModelVersion[]
|
versions: ModelVersion[]
|
||||||
recommendedVersion: string
|
recommendedVersion: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import useGetAppVersion from '@hooks/useGetAppVersion'
|
|||||||
import useGetSystemResources from '@hooks/useGetSystemResources'
|
import useGetSystemResources from '@hooks/useGetSystemResources'
|
||||||
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
|
import { modelDownloadStateAtom } from '@helpers/atoms/DownloadState.atom'
|
||||||
import { formatDownloadPercentage } from '@utils/converter'
|
import { formatDownloadPercentage } from '@utils/converter'
|
||||||
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
|
import { activeModelAtom } from '@helpers/atoms/Model.atom'
|
||||||
|
|
||||||
const MonitorBar: React.FC = () => {
|
const MonitorBar: React.FC = () => {
|
||||||
const progress = useAtomValue(appDownloadProgress)
|
const progress = useAtomValue(appDownloadProgress)
|
||||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
const { version } = useGetAppVersion()
|
const { version } = useGetAppVersion()
|
||||||
const { ram, cpu } = useGetSystemResources()
|
const { ram, cpu } = useGetSystemResources()
|
||||||
const modelDownloadStates = useAtomValue(modelDownloadStateAtom)
|
const modelDownloadStates = useAtomValue(modelDownloadStateAtom)
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useContext, useEffect, useRef, useState } from 'react'
|
||||||
import { plugins, extensionPoints } from '@plugin'
|
|
||||||
import {
|
import {
|
||||||
ChartPieIcon,
|
ChartPieIcon,
|
||||||
CommandLineIcon,
|
CommandLineIcon,
|
||||||
@ -9,10 +8,9 @@ import {
|
|||||||
|
|
||||||
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid'
|
import { MagnifyingGlassIcon } from '@heroicons/react/20/solid'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { DataService, PluginService, preferences } from '@janhq/core'
|
|
||||||
import { execute } from '@plugin/extension-manager'
|
|
||||||
import LoadingIndicator from './LoadingIndicator'
|
import LoadingIndicator from './LoadingIndicator'
|
||||||
import { executeSerial } from '@services/pluginService'
|
import { FeatureToggleContext } from '@helpers/FeatureToggleWrapper'
|
||||||
|
import { pluginManager } from '@plugin/PluginManager'
|
||||||
|
|
||||||
export const Preferences = () => {
|
export const Preferences = () => {
|
||||||
const [search, setSearch] = useState<string>('')
|
const [search, setSearch] = useState<string>('')
|
||||||
@ -25,14 +23,22 @@ export const Preferences = () => {
|
|||||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||||
const experimentRef = useRef(null)
|
const experimentRef = useRef(null)
|
||||||
const preferenceRef = useRef(null)
|
const preferenceRef = useRef(null)
|
||||||
|
const { experimentalFeatureEnabed } = useContext(FeatureToggleContext)
|
||||||
/**
|
/**
|
||||||
* Loads the plugin catalog module from a CDN and sets it as the plugin catalog state.
|
* Loads the plugin catalog module from a CDN and sets it as the plugin catalog state.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
executeSerial(DataService.GetPluginManifest).then((data: any) => {
|
if (!window.electronAPI) {
|
||||||
setPluginCatalog(data)
|
return
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Get plugin manifest
|
||||||
|
import(/* webpackIgnore: true */ PLUGIN_CATALOG + `?t=${Date.now()}`).then(
|
||||||
|
(data) => {
|
||||||
|
if (Array.isArray(data.default) && experimentalFeatureEnabed)
|
||||||
|
setPluginCatalog(data.default)
|
||||||
|
}
|
||||||
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,39 +50,8 @@ export const Preferences = () => {
|
|||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getActivePlugins = async () => {
|
const getActivePlugins = async () => {
|
||||||
const plgs = await plugins.getActive()
|
const plgs = await pluginManager.getActive()
|
||||||
setActivePlugins(plgs)
|
setActivePlugins(plgs)
|
||||||
|
|
||||||
if (extensionPoints.get('experimentComponent')) {
|
|
||||||
const components = await Promise.all(
|
|
||||||
extensionPoints.execute('experimentComponent', {})
|
|
||||||
)
|
|
||||||
if (components.length > 0) {
|
|
||||||
setIsTestAvailable(true)
|
|
||||||
}
|
|
||||||
components.forEach((e) => {
|
|
||||||
if (experimentRef.current) {
|
|
||||||
// @ts-ignore
|
|
||||||
experimentRef.current.appendChild(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (extensionPoints.get('PluginPreferences')) {
|
|
||||||
const data = await Promise.all(
|
|
||||||
extensionPoints.execute('PluginPreferences', {})
|
|
||||||
)
|
|
||||||
setPreferenceItems(Array.isArray(data) ? data : [])
|
|
||||||
Promise.all(
|
|
||||||
(Array.isArray(data) ? data : []).map((e) =>
|
|
||||||
preferences
|
|
||||||
.get(e.pluginName, e.preferenceKey)
|
|
||||||
.then((k) => ({ key: e.preferenceKey, value: k }))
|
|
||||||
)
|
|
||||||
).then((data) => {
|
|
||||||
setPreferenceValues(data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
getActivePlugins()
|
getActivePlugins()
|
||||||
}, [])
|
}, [])
|
||||||
@ -93,7 +68,7 @@ export const Preferences = () => {
|
|||||||
|
|
||||||
// Send the filename of the to be installed plugin
|
// Send the filename of the to be installed plugin
|
||||||
// to the main process for installation
|
// to the main process for installation
|
||||||
const installed = await plugins.install([pluginFile])
|
const installed = await pluginManager.install([pluginFile])
|
||||||
if (installed) window.coreAPI?.relaunch()
|
if (installed) window.coreAPI?.relaunch()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +80,7 @@ export const Preferences = () => {
|
|||||||
const uninstall = async (name: string) => {
|
const uninstall = async (name: string) => {
|
||||||
// Send the filename of the to be uninstalled plugin
|
// Send the filename of the to be uninstalled plugin
|
||||||
// to the main process for removal
|
// to the main process for removal
|
||||||
const res = await plugins.uninstall([name])
|
const res = await pluginManager.uninstall([name])
|
||||||
if (res) window.coreAPI?.relaunch()
|
if (res) window.coreAPI?.relaunch()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +106,7 @@ export const Preferences = () => {
|
|||||||
const downloadTarball = async (pluginName: string) => {
|
const downloadTarball = async (pluginName: string) => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const pluginPath = await window.coreAPI?.installRemotePlugin(pluginName)
|
const pluginPath = await window.coreAPI?.installRemotePlugin(pluginName)
|
||||||
const installed = await plugins.install([pluginPath])
|
const installed = await pluginManager.install([pluginPath])
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
if (installed) window.coreAPI.relaunch()
|
if (installed) window.coreAPI.relaunch()
|
||||||
}
|
}
|
||||||
@ -144,11 +119,6 @@ export const Preferences = () => {
|
|||||||
if (timeout) {
|
if (timeout) {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
}
|
}
|
||||||
if (extensionPoints.get(PluginService.OnPreferencesUpdate))
|
|
||||||
timeout = setTimeout(
|
|
||||||
() => execute(PluginService.OnPreferencesUpdate, {}),
|
|
||||||
100
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -408,11 +378,7 @@ export const Preferences = () => {
|
|||||||
(v) => v.key === e.preferenceKey
|
(v) => v.key === e.preferenceKey
|
||||||
)[0]?.value
|
)[0]?.value
|
||||||
}
|
}
|
||||||
onChange={(event) => {
|
onChange={(event) => {}}
|
||||||
preferences
|
|
||||||
.set(e.pluginName, e.preferenceKey, event.target.value)
|
|
||||||
.then(() => notifyPreferenceUpdate())
|
|
||||||
}}
|
|
||||||
></input>
|
></input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
MainViewState,
|
MainViewState,
|
||||||
setMainViewStateAtom,
|
setMainViewStateAtom,
|
||||||
} from '@helpers/atoms/MainView.atom'
|
} from '@helpers/atoms/MainView.atom'
|
||||||
import { activeAssistantModelAtom } from '@helpers/atoms/Model.atom'
|
import { activeModelAtom } from '@helpers/atoms/Model.atom'
|
||||||
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@hooks/useGetDownloadedModels'
|
||||||
import { Button } from '@uikit'
|
import { Button } from '@uikit'
|
||||||
import { MessageCircle } from 'lucide-react'
|
import { MessageCircle } from 'lucide-react'
|
||||||
@ -18,7 +18,7 @@ enum ActionButton {
|
|||||||
|
|
||||||
const SidebarEmptyHistory: React.FC = () => {
|
const SidebarEmptyHistory: React.FC = () => {
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
const activeModel = useAtomValue(activeAssistantModelAtom)
|
const activeModel = useAtomValue(activeModelAtom)
|
||||||
const setMainView = useSetAtom(setMainViewStateAtom)
|
const setMainView = useSetAtom(setMainViewStateAtom)
|
||||||
const { requestCreateConvo } = useCreateConversation()
|
const { requestCreateConvo } = useCreateConversation()
|
||||||
const [action, setAction] = useState(ActionButton.DownloadModel)
|
const [action, setAction] = useState(ActionButton.DownloadModel)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user