#357 plugin & app can subscribe and emit events (#358)

* feature: event based plugin

* chore: update README.md

* Update yarn script for build plugins (#363)

* Update yarn script for build plugins

* Plugin-core install from npmjs instead of from local

---------

Co-authored-by: Hien To <>

* #360 plugin preferences (#361)

* feature: #360 plugin preferences

* chore: update core-plugin README.md

* chore: create collections on start

* chore: bumb core version

* chore: update README

* chore: notify preferences update

* fix: preference update

---------

Co-authored-by: hiento09 <136591877+hiento09@users.noreply.github.com>
This commit is contained in:
Louis 2023-10-16 17:23:38 +07:00 committed by GitHub
parent 237d94ed1a
commit 27258433d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 4448 additions and 502 deletions

View File

@ -1,6 +1,6 @@
import { core, store, RegisterExtensionPoint, StoreService, DataService } from "@janhq/plugin-core";
import { core, store, RegisterExtensionPoint, StoreService, DataService, PluginService } from "@janhq/plugin-core";
// Provide an async method to manipulate the price provided by the extension point
const PluginName = "data-plugin";
const MODULE_PATH = "data-plugin/dist/cjs/module.js";
/**
@ -12,7 +12,6 @@ const MODULE_PATH = "data-plugin/dist/cjs/module.js";
*
*/
function createCollection({ name, schema }: { name: string; schema?: { [key: string]: any } }): Promise<void> {
console.log("renderer: creating collection:", name, schema);
return core.invokePluginFunc(MODULE_PATH, "createCollection", name, schema);
}
@ -137,8 +136,7 @@ function onStart() {
// Register all the above functions and objects with the relevant extension points
export function init({ register }: { register: RegisterExtensionPoint }) {
onStart();
register(PluginService.OnStart, PluginName, onStart);
register(StoreService.CreateCollection, createCollection.name, createCollection);
register(StoreService.DeleteCollection, deleteCollection.name, deleteCollection);
register(StoreService.InsertOne, insertOne.name, insertOne);

View File

@ -1,20 +1,20 @@
{
"name": "data-plugin",
"version": "1.0.0",
"version": "1.0.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "data-plugin",
"version": "1.0.0",
"version": "1.0.4",
"bundleDependencies": [
"rxdb",
"rxjs"
"pouchdb-node",
"pouchdb-find"
],
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@janhq/plugin-core": "file:../../../../plugin-core",
"@janhq/plugin-core": "^0.1.5",
"pouchdb-find": "^8.0.1",
"pouchdb-node": "^8.0.1"
},
@ -31,10 +31,11 @@
},
"../../../../plugin-core": {
"name": "@janhq/plugin-core",
"version": "0.1.0",
"version": "0.1.6",
"license": "MIT",
"devDependencies": {
"@types/node": "^12.0.2"
"@types/node": "^12.0.2",
"typescript": "^5.2.2"
}
},
"node_modules/@cspotcode/source-map-support": {
@ -177,9 +178,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.8.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz",
"integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==",
"version": "20.8.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz",
"integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==",
"dev": true,
"dependencies": {
"undici-types": "~5.25.1"
@ -397,6 +398,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"inBundle": true,
"dependencies": {
"event-target-shim": "^5.0.0"
},
@ -408,6 +410,7 @@
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz",
"integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==",
"inBundle": true,
"dependencies": {
"buffer": "^5.5.0",
"immediate": "^3.2.3",
@ -674,7 +677,8 @@
"type": "consulting",
"url": "https://feross.org/support"
}
]
],
"inBundle": true
},
"node_modules/binary-extensions": {
"version": "1.13.1",
@ -769,6 +773,7 @@
"url": "https://feross.org/support"
}
],
"inBundle": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
@ -777,7 +782,8 @@
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"inBundle": true
},
"node_modules/cache-base": {
"version": "1.0.1",
@ -809,9 +815,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001547",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz",
"integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==",
"version": "1.0.30001549",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz",
"integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==",
"dev": true,
"funding": [
{
@ -966,6 +972,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz",
"integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==",
"inBundle": true,
"engines": {
"node": ">= 0.10"
}
@ -1083,7 +1090,8 @@
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"inBundle": true
},
"node_modules/cpx": {
"version": "1.5.0",
@ -1158,6 +1166,7 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz",
"integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==",
"inBundle": true,
"dependencies": {
"abstract-leveldown": "~6.2.1",
"inherits": "^2.0.3"
@ -1218,7 +1227,8 @@
"node_modules/double-ended-queue": {
"version": "2.1.0-0",
"resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
"integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ=="
"integrity": "sha512-+BNfZ+deCo8hMNpDqDnvT+c0XpJ5cUa6mqYq89bho2Ifze4URTqRkcwR399hWoTrTkbZ/XJYDgP6rc7pRgffEQ==",
"inBundle": true
},
"node_modules/duplexer": {
"version": "0.1.2",
@ -1227,15 +1237,16 @@
"dev": true
},
"node_modules/electron-to-chromium": {
"version": "1.4.551",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz",
"integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==",
"version": "1.4.554",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.554.tgz",
"integrity": "sha512-Q0umzPJjfBrrj8unkONTgbKQXzXRrH7sVV7D9ea2yBV3Oaogz991yhbpfvo2LMNkJItmruXTEzVpP9cp7vaIiQ==",
"dev": true
},
"node_modules/encoding-down": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz",
"integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==",
"inBundle": true,
"dependencies": {
"abstract-leveldown": "^6.2.1",
"inherits": "^2.0.3",
@ -1250,6 +1261,7 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/end-stream/-/end-stream-0.1.0.tgz",
"integrity": "sha512-Brl10T8kYnc75IepKizW6Y9liyW8ikz1B7n/xoHrJxoVSSjoqPn30sb7XVFfQERK4QfUMYRGs9dhWwtt2eu6uA==",
"inBundle": true,
"dependencies": {
"write-stream": "~0.4.3"
}
@ -1283,6 +1295,7 @@
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
"inBundle": true,
"dependencies": {
"prr": "~1.0.1"
},
@ -1352,6 +1365,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"inBundle": true,
"engines": {
"node": ">=6"
}
@ -1451,6 +1465,7 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.11.0.tgz",
"integrity": "sha512-BQm7iZLFhMWFy5CZ/162sAGjBfdNWb7a8LEqqnzsHFhxT/X/SVj/z2t2nu3aJvjlbQkrAlTUApplPRjWyH4mhA==",
"inBundle": true,
"dependencies": {
"tough-cookie": "^2.3.3 || ^3.0.1 || ^4.0.0"
},
@ -1792,7 +1807,8 @@
"type": "consulting",
"url": "https://feross.org/support"
}
]
],
"inBundle": true
},
"node_modules/ignore-walk": {
"version": "3.0.4",
@ -1806,7 +1822,8 @@
"node_modules/immediate": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz",
"integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q=="
"integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==",
"inBundle": true
},
"node_modules/import-local": {
"version": "3.1.0",
@ -1840,7 +1857,8 @@
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"inBundle": true
},
"node_modules/ini": {
"version": "1.3.8",
@ -2078,7 +2096,8 @@
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"inBundle": true
},
"node_modules/isexe": {
"version": "2.0.0",
@ -2155,6 +2174,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz",
"integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==",
"inBundle": true,
"dependencies": {
"level-js": "^5.0.0",
"level-packager": "^5.1.0",
@ -2172,6 +2192,7 @@
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz",
"integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==",
"inBundle": true,
"dependencies": {
"buffer": "^5.6.0"
},
@ -2183,6 +2204,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz",
"integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==",
"inBundle": true,
"engines": {
"node": ">=6"
}
@ -2191,6 +2213,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz",
"integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==",
"inBundle": true,
"dependencies": {
"errno": "~0.1.1"
},
@ -2202,6 +2225,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz",
"integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==",
"inBundle": true,
"dependencies": {
"inherits": "^2.0.4",
"readable-stream": "^3.4.0",
@ -2215,6 +2239,7 @@
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"inBundle": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@ -2228,6 +2253,7 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz",
"integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==",
"inBundle": true,
"dependencies": {
"abstract-leveldown": "~6.2.3",
"buffer": "^5.5.0",
@ -2239,6 +2265,7 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz",
"integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==",
"inBundle": true,
"dependencies": {
"encoding-down": "^6.3.0",
"levelup": "^4.3.2"
@ -2251,6 +2278,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz",
"integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==",
"inBundle": true,
"dependencies": {
"xtend": "^4.0.2"
},
@ -2262,6 +2290,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/level-write-stream/-/level-write-stream-1.0.0.tgz",
"integrity": "sha512-bBNKOEOMl8msO+uIM9YX/gUO6ckokZ/4pCwTm/lwvs46x6Xs8Zy0sn3Vh37eDqse4mhy4fOMIb/JsSM2nyQFtw==",
"inBundle": true,
"dependencies": {
"end-stream": "~0.1.0"
}
@ -2271,6 +2300,7 @@
"resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz",
"integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==",
"hasInstallScript": true,
"inBundle": true,
"dependencies": {
"abstract-leveldown": "~6.2.1",
"napi-macros": "~2.0.0",
@ -2284,6 +2314,7 @@
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz",
"integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==",
"inBundle": true,
"dependencies": {
"deferred-leveldown": "~5.3.0",
"level-errors": "~2.0.0",
@ -2337,7 +2368,8 @@
"node_modules/ltgt": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz",
"integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA=="
"integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==",
"inBundle": true
},
"node_modules/make-error": {
"version": "1.3.6",
@ -2565,7 +2597,8 @@
"node_modules/napi-macros": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz",
"integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg=="
"integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==",
"inBundle": true
},
"node_modules/needle": {
"version": "2.9.1",
@ -2594,6 +2627,7 @@
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"inBundle": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
@ -2613,6 +2647,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz",
"integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==",
"inBundle": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
@ -3041,6 +3076,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-abstract-mapreduce/-/pouchdb-abstract-mapreduce-8.0.1.tgz",
"integrity": "sha512-BxJRHdfiC8gID8h4DPS0Xy6wsa2VBHRHMv9hsm0BhGTWTqS4k8ivItVSeU2dMoXiTBYp+7SerYmovUQNGSX1GA==",
"inBundle": true,
"dependencies": {
"pouchdb-binary-utils": "8.0.1",
"pouchdb-collate": "8.0.1",
@ -3056,6 +3092,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-binary-utils/-/pouchdb-binary-utils-8.0.1.tgz",
"integrity": "sha512-WsuR/S0aoUlcA0Alt99czkXsfuXWcrYXAcvGiTW02zawVXOafCnb/qHjA09TUaV0oy5HeHmYaNnDckoOUqspeA==",
"inBundle": true,
"dependencies": {
"buffer-from": "1.1.2"
}
@ -3063,22 +3100,26 @@
"node_modules/pouchdb-collate": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-collate/-/pouchdb-collate-8.0.1.tgz",
"integrity": "sha512-DTuNz1UJjBTGZMUlWS1klSE1rPsmHy8IIDie3MFH1ZTz/C+SwGgGwkiAyUDv/n00D18EMLgXq5mu+r7L6K1BwQ=="
"integrity": "sha512-DTuNz1UJjBTGZMUlWS1klSE1rPsmHy8IIDie3MFH1ZTz/C+SwGgGwkiAyUDv/n00D18EMLgXq5mu+r7L6K1BwQ==",
"inBundle": true
},
"node_modules/pouchdb-collections": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-collections/-/pouchdb-collections-8.0.1.tgz",
"integrity": "sha512-TlkQ2GGHJApJgL0b7bJMQcwX6eMfVenLeoK9mqHfC2fJssui+HWJJ5LYKHOWan11SeB90BQVFbO6rHN6CJQeDg=="
"integrity": "sha512-TlkQ2GGHJApJgL0b7bJMQcwX6eMfVenLeoK9mqHfC2fJssui+HWJJ5LYKHOWan11SeB90BQVFbO6rHN6CJQeDg==",
"inBundle": true
},
"node_modules/pouchdb-errors": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-errors/-/pouchdb-errors-8.0.1.tgz",
"integrity": "sha512-H+ZsQxcG/JV3Tn29gnM6c9+lRPCN91ZYOkoIICsLjVRYgOTzN1AvNUD/G5JCB+81aI/u3fxZec0LEaZh6g6NHA=="
"integrity": "sha512-H+ZsQxcG/JV3Tn29gnM6c9+lRPCN91ZYOkoIICsLjVRYgOTzN1AvNUD/G5JCB+81aI/u3fxZec0LEaZh6g6NHA==",
"inBundle": true
},
"node_modules/pouchdb-fetch": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-fetch/-/pouchdb-fetch-8.0.1.tgz",
"integrity": "sha512-Px5HLT8MxqTujc8bpPRKoouznDTJa9XBGqCbhl95q6rhjWRfwZEvXjV92z0B5BALAM6D6avMyG0DjuNfUWnMuA==",
"inBundle": true,
"dependencies": {
"abort-controller": "3.0.0",
"fetch-cookie": "0.11.0",
@ -3089,6 +3130,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-find/-/pouchdb-find-8.0.1.tgz",
"integrity": "sha512-i5criYXMOXlbeRrCrXonqaOY+xiMiOyTLybqvtX/NkUsiD4BxJxkq5AxdSlHdJ9703nWJ0k6S+5C8VrpEj8tsQ==",
"inBundle": true,
"dependencies": {
"pouchdb-abstract-mapreduce": "8.0.1",
"pouchdb-collate": "8.0.1",
@ -3103,6 +3145,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-mapreduce-utils/-/pouchdb-mapreduce-utils-8.0.1.tgz",
"integrity": "sha512-asZcFLy1DA3oe5CeXIRCpfVrBHaHRvSb3Tc/LPD1dZDDtpEkeCuXGtJm+praN0jl41jTBEm0uMdD/YI0J5ZFXw==",
"inBundle": true,
"dependencies": {
"pouchdb-collections": "8.0.1",
"pouchdb-utils": "8.0.1"
@ -3112,6 +3155,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-md5/-/pouchdb-md5-8.0.1.tgz",
"integrity": "sha512-shVcs/K/iilrcAhDEERpLIrGm/cnDVsXiocOzs7kycJEuBqYnLD9nj58VwWDcum26wfa8T9cznvEGE1jlYVNPQ==",
"inBundle": true,
"dependencies": {
"pouchdb-binary-utils": "8.0.1",
"spark-md5": "3.0.2"
@ -3121,6 +3165,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-node/-/pouchdb-node-8.0.1.tgz",
"integrity": "sha512-xYvVmBB/nEZltBYkslE0RKciL7l359BFbG27gyGJlpPHeMUhtVlYHoEiI2gb2x2pEn7hG9B7Odo8keuq4/ot1w==",
"inBundle": true,
"dependencies": {
"abort-controller": "3.0.0",
"buffer-from": "1.1.2",
@ -3143,12 +3188,14 @@
"node_modules/pouchdb-node/node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"inBundle": true
},
"node_modules/pouchdb-node/node_modules/readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
"inBundle": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
@ -3159,12 +3206,14 @@
"node_modules/pouchdb-node/node_modules/string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
"inBundle": true
},
"node_modules/pouchdb-selector-core": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-selector-core/-/pouchdb-selector-core-8.0.1.tgz",
"integrity": "sha512-dHWsnR+mLGyfVld1vSHJI1xKTwS1xk1G2dggjfXfUrLehI+wysjTUOwiSNytyPzG6DpT+o86wyUpwzPwsDCLBw==",
"inBundle": true,
"dependencies": {
"pouchdb-collate": "8.0.1",
"pouchdb-utils": "8.0.1"
@ -3174,6 +3223,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/pouchdb-utils/-/pouchdb-utils-8.0.1.tgz",
"integrity": "sha512-pWgxdk9EHVWJmjQoEvTe+ZlPXyjcuQ/vgLITN+RjGwcYhoQYUE1M0PksQd2dUP3V8lGS4+wrg9lEM/qSJPYcpw==",
"inBundle": true,
"dependencies": {
"clone-buffer": "1.0.0",
"immediate": "3.3.0",
@ -3195,22 +3245,26 @@
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"inBundle": true
},
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw=="
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
"inBundle": true
},
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
"inBundle": true
},
"node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"inBundle": true,
"engines": {
"node": ">=6"
}
@ -3218,7 +3272,8 @@
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"inBundle": true
},
"node_modules/randomatic": {
"version": "3.1.1",
@ -3280,6 +3335,7 @@
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"inBundle": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@ -3293,7 +3349,8 @@
"node_modules/readable-stream/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"inBundle": true
},
"node_modules/readdirp": {
"version": "2.2.1",
@ -3694,7 +3751,8 @@
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"inBundle": true
},
"node_modules/resolve": {
"version": "1.22.8",
@ -4145,7 +4203,8 @@
"node_modules/spark-md5": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz",
"integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="
"integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==",
"inBundle": true
},
"node_modules/split-string": {
"version": "3.1.0",
@ -4235,6 +4294,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"inBundle": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
@ -4242,7 +4302,8 @@
"node_modules/string_decoder/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"inBundle": true
},
"node_modules/string-width": {
"version": "1.0.2",
@ -4405,6 +4466,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz",
"integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==",
"inBundle": true,
"dependencies": {
"inherits": "^2.0.4",
"readable-stream": "2 || 3"
@ -4466,6 +4528,7 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"inBundle": true,
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
@ -4479,7 +4542,8 @@
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"inBundle": true
},
"node_modules/ts-loader": {
"version": "9.5.0",
@ -4655,6 +4719,7 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"inBundle": true,
"engines": {
"node": ">= 4.0.0"
}
@ -4766,6 +4831,7 @@
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"inBundle": true,
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
@ -4783,12 +4849,14 @@
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"inBundle": true
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"inBundle": true,
"bin": {
"uuid": "dist/bin/uuid"
}
@ -4802,7 +4870,8 @@
"node_modules/vuvuzela": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/vuvuzela/-/vuvuzela-1.0.3.tgz",
"integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ=="
"integrity": "sha512-Tm7jR1xTzBbPW+6y1tknKiEhz04Wf/1iZkcTJjSFcpNko43+dFW6+OOeQe9taJIug3NdfUAjFKgUSyQrIKaDvQ==",
"inBundle": true
},
"node_modules/watchpack": {
"version": "2.4.0",
@ -4820,12 +4889,13 @@
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"inBundle": true
},
"node_modules/webpack": {
"version": "5.88.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz",
"integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==",
"version": "5.89.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz",
"integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@ -4949,6 +5019,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"inBundle": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@ -4994,6 +5065,7 @@
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/write-stream/-/write-stream-0.4.3.tgz",
"integrity": "sha512-IJrvkhbAnj89W/GAVdVgbnPiVw5Ntg/B4tc/MUCIEwj/g6JIww1DWJyB/yBMT3yw2/TkT6IUZ0+IYef3flEw8A==",
"inBundle": true,
"dependencies": {
"readable-stream": "~0.0.2"
}
@ -5001,12 +5073,14 @@
"node_modules/write-stream/node_modules/readable-stream": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-0.0.4.tgz",
"integrity": "sha512-azrivNydKRYt7zwLV5wWUK7YzKTWs3q87xSmY6DlHapPrCvaT6ZrukvM5erV+yCSSPmZT8zkSdttOHQpWWm9zw=="
"integrity": "sha512-azrivNydKRYt7zwLV5wWUK7YzKTWs3q87xSmY6DlHapPrCvaT6ZrukvM5erV+yCSSPmZT8zkSdttOHQpWWm9zw==",
"inBundle": true
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"inBundle": true,
"engines": {
"node": ">=0.4"
}

View File

@ -39,7 +39,7 @@
"node_modules"
],
"dependencies": {
"@janhq/plugin-core": "file:../../../../plugin-core",
"@janhq/plugin-core": "^0.1.7",
"pouchdb-find": "^8.0.1",
"pouchdb-node": "^8.0.1"
}

View File

@ -1,23 +1,97 @@
const MODULE_PATH = "inference-plugin/dist/module.js";
import { EventName, InferenceService, NewMessageRequest, PluginService, core, events, store } from "@janhq/plugin-core";
const initModel = async (product) =>
new Promise(async (resolve) => {
if (window.electronAPI) {
window.electronAPI
.invokePluginFunc(MODULE_PATH, "initModel", product)
.then((res) => resolve(res));
}
});
const PluginName = "inference-plugin";
const MODULE_PATH = `${PluginName}/dist/module.js`;
const inferenceUrl = "http://localhost:3928/llama/chat_completion";
const inferenceUrl = () => "http://localhost:3928/llama/chat_completion";
const initModel = async (product) => core.invokePluginFunc(MODULE_PATH, "initModel", product);
const stopModel = () => {
window.electronAPI.invokePluginFunc(MODULE_PATH, "killSubprocess");
core.invokePluginFunc(MODULE_PATH, "killSubprocess");
};
async function handleMessageRequest(data: NewMessageRequest) {
// TODO: Common collections should be able to access via core functions instead of store
const messageHistory =
(await store.findMany("messages", { conversationId: data.conversationId }, [{ createdAt: "asc" }])) ?? [];
const recentMessages = messageHistory
.filter((e) => e.message !== "" && (e.user === "user" || e.user === "assistant"))
.slice(-10)
.map((message) => {
return {
content: message.message.trim(),
role: message.user === "user" ? "user" : "assistant",
};
});
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);
const response = await fetch(inferenceUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
"Access-Control-Allow-Origi": "*",
},
body: JSON.stringify({
messages: recentMessages,
stream: true,
model: "gpt-3.5-turbo",
max_tokens: 500,
}),
});
const stream = response.body;
const decoder = new TextDecoder("utf-8");
const reader = stream?.getReader();
let answer = "";
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: ", ""));
answer += data.choices[0]?.delta?.content ?? "";
if (answer.startsWith("assistant: ")) {
answer = answer.replace("assistant: ", "");
}
message.message = answer;
events.emit(EventName.OnMessageResponseUpdate, message);
}
}
}
message.message = answer.trim();
// TODO: Common collections should be able to access via core functions instead of store
await store.updateOne("messages", message._id, message);
}
const registerListener = () => {
events.on(EventName.OnNewMessageRequest, handleMessageRequest);
};
const onStart = async () => {
registerListener();
};
// Register all the above functions and objects with the relevant extension points
export function init({ register }) {
register("initModel", "initModel", initModel);
register("inferenceUrl", "inferenceUrl", inferenceUrl);
register("stopModel", "stopModel", stopModel);
register(PluginService.OnStart, PluginName, onStart);
register(InferenceService.InitModel, initModel.name, initModel);
register(InferenceService.StopModel, stopModel.name, stopModel);
}

View File

@ -14,6 +14,7 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@janhq/plugin-core": "^0.1.5",
"kill-port-process": "^3.2.0",
"tcp-port-used": "^1.0.2",
"ts-loader": "^9.5.0"
@ -28,6 +29,15 @@
"node": ">=18.0.0"
}
},
"../../../../plugin-core": {
"name": "@janhq/plugin-core",
"version": "0.1.6",
"license": "MIT",
"devDependencies": {
"@types/node": "^12.0.2",
"typescript": "^5.2.2"
}
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@ -37,6 +47,10 @@
"node": ">=10.0.0"
}
},
"node_modules/@janhq/plugin-core": {
"resolved": "../../../../plugin-core",
"link": true
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",

View File

@ -25,6 +25,7 @@
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@janhq/plugin-core": "^0.1.7",
"kill-port-process": "^3.2.0",
"tcp-port-used": "^1.0.2",
"ts-loader": "^9.5.0"

View File

@ -1,22 +1,13 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
/* Modules */
"module": "ES6" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "." /* Specify the base directory to resolve non-relative module names. */,
// "paths": {} /* Specify a set of entries that re-map imports to additional lookup locations. */,
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "resolveJsonModule": true, /* Enable importing .json files. */
"target": "es2016",
"module": "ES6",
"moduleResolution": "node",
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": false /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking all .d.ts files. */
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": false,
"skipLibCheck": true
}
}

View File

@ -1,7 +0,0 @@
export {};
declare global {
interface Window {
electronAPI?: any | undefined;
}
}

View File

@ -1,4 +1,6 @@
import { ModelManagementService, RegisterExtensionPoint, core, store } from "@janhq/plugin-core";
import { ModelManagementService, PluginService, RegisterExtensionPoint, core, store } from "@janhq/plugin-core";
const PluginName = "model-management-plugin";
const MODULE_PATH = "model-management-plugin/dist/module.js";
const getDownloadedModels = () => core.invokePluginFunc(MODULE_PATH, "getDownloadedModels");
@ -81,7 +83,7 @@ function onStart() {
// Register all the above functions and objects with the relevant extension points
export function init({ register }: { register: RegisterExtensionPoint }) {
onStart();
register(PluginService.OnStart, PluginName, onStart);
register(ModelManagementService.GetDownloadedModels, getDownloadedModels.name, getDownloadedModels);
register(ModelManagementService.GetAvailableModels, getAvailableModels.name, getAvailableModels);

View File

@ -14,7 +14,7 @@
"license": "MIT",
"dependencies": {
"@huggingface/hub": "^0.8.5",
"@janhq/plugin-core": "file:../../../../plugin-core",
"@janhq/plugin-core": "^0.1.5",
"ts-loader": "^9.5.0"
},
"devDependencies": {
@ -26,10 +26,11 @@
},
"../../../../plugin-core": {
"name": "@janhq/plugin-core",
"version": "0.1.0",
"version": "0.1.6",
"license": "MIT",
"devDependencies": {
"@types/node": "^12.0.2"
"@types/node": "^12.0.2",
"typescript": "^5.2.2"
}
},
"node_modules/@discoveryjs/json-ext": {

View File

@ -27,7 +27,7 @@
],
"dependencies": {
"@huggingface/hub": "^0.8.5",
"@janhq/plugin-core": "file:../../../../plugin-core",
"@janhq/plugin-core": "^0.1.7",
"ts-loader": "^9.5.0"
},
"bundledDependencies": [

View File

@ -0,0 +1,115 @@
import {
PluginService,
EventName,
NewMessageRequest,
events,
store,
preferences,
RegisterExtensionPoint,
} from "@janhq/plugin-core";
import { Configuration, OpenAIApi } from "azure-openai";
const PluginName = "openai-plugin";
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(PluginName, "apiKey")) ?? "";
const endpoint: string = (await preferences.get(PluginName, "endpoint")) ?? "";
const deploymentName: string = (await preferences.get(PluginName, "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);
};
const onPreferencesUpdate = () => {
setup();
};
// Register all the above functions and objects with the relevant extension points
export function init({ register }: { register: RegisterExtensionPoint }) {
register(PluginService.OnStart, PluginName, onStart);
register(PluginService.OnPreferencesUpdate, PluginName, onPreferencesUpdate);
preferences.registerPreferences<string>(register, PluginName, "apiKey", "API Key", "Azure Project API Key", "");
preferences.registerPreferences<string>(
register,
PluginName,
"endpoint",
"API Endpoint",
"Azure Deployment Endpoint API",
""
);
preferences.registerPreferences<string>(
register,
PluginName,
"deploymentName",
"Deployment Name",
"The deployment name you chose when you deployed the model",
""
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
{
"name": "azure-openai-plugin",
"version": "1.0.0",
"description": "Inference plugin for Azure OpenAI",
"icon": "https://static-assets.jan.ai/openai-icon.jpg",
"main": "dist/index.js",
"author": "Jan",
"license": "MIT",
"activationPoints": [
"init"
],
"scripts": {
"build": "tsc -b . && webpack --config webpack.config.js",
"postinstall": "rimraf ./*.tgz && npm run build && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"",
"build:publish": "npm pack && cpx *.tgz ../../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/plugin-core": "^0.1.7",
"azure-openai": "^0.9.4",
"kill-port-process": "^3.2.0",
"tcp-port-used": "^1.0.2",
"ts-loader": "^9.5.0"
},
"bundledDependencies": [
"tcp-port-used",
"kill-port-process"
],
"engines": {
"node": ">=18.0.0"
},
"files": [
"dist/*",
"package.json",
"README.md"
]
}

View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es2016",
"module": "ES6",
"moduleResolution": "node",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": false,
"skipLibCheck": true
}
}

View File

@ -0,0 +1,25 @@
const path = require("path");
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/,
},
],
},
output: {
filename: "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"],
},
// Add loaders and other configuration as needed for your project
};

View File

@ -22,8 +22,8 @@
"build:core": "cd plugin-core && yarn install && yarn run build",
"build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"",
"build:electron": "yarn workspace jan build",
"build:plugins": "rimraf ./electron/core/pre-install/*.tgz && concurrently \"cd ./electron/core/plugins/data-plugin && npm ci\" \"cd ./electron/core/plugins/inference-plugin && npm ci\" \"cd ./electron/core/plugins/model-management-plugin && npm ci\" \"cd ./electron/core/plugins/monitoring-plugin && npm ci\" && concurrently \"cd ./electron/core/plugins/data-plugin && npm run build:publish\" \"cd ./electron/core/plugins/inference-plugin && npm run build:publish\" \"cd ./electron/core/plugins/model-management-plugin && npm run build:publish\" \"cd ./electron/core/plugins/monitoring-plugin && npm run build:publish\"",
"build:plugins-darwin": "rimraf ./electron/core/pre-install/*.tgz && concurrently \"cd ./electron/core/plugins/data-plugin && npm ci\" \"cd ./electron/core/plugins/inference-plugin && npm ci\" \"cd ./electron/core/plugins/model-management-plugin && npm ci\" \"cd ./electron/core/plugins/monitoring-plugin && npm ci\" && chmod +x ./electron/auto-sign.sh && ./electron/auto-sign.sh && concurrently \"cd ./electron/core/plugins/data-plugin && npm run build:publish\" \"cd ./electron/core/plugins/inference-plugin && npm run build:publish\" \"cd ./electron/core/plugins/model-management-plugin && npm run build:publish\" \"cd ./electron/core/plugins/monitoring-plugin && npm run build:publish\"",
"build:plugins": "rimraf ./electron/core/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./electron/core/plugins/data-plugin && npm install && npm run postinstall\" \"cd ./electron/core/plugins/inference-plugin && npm install && npm run postinstall\" \"cd ./electron/core/plugins/model-management-plugin && npm install && npm run postinstall\" \"cd ./electron/core/plugins/monitoring-plugin && npm install && npm run postinstall\" && concurrently --kill-others-on-fail \"cd ./electron/core/plugins/data-plugin && npm run build:publish\" \"cd ./electron/core/plugins/inference-plugin && npm run build:publish\" \"cd ./electron/core/plugins/model-management-plugin && npm run build:publish\" \"cd ./electron/core/plugins/monitoring-plugin && npm run build:publish\"",
"build:plugins-darwin": "rimraf ./electron/core/pre-install/*.tgz && concurrently \"cd ./electron/core/plugins/data-plugin && npm install && npm run postinstall\" \"cd ./electron/core/plugins/inference-plugin && npm install && npm run postinstall\" \"cd ./electron/core/plugins/model-management-plugin && npm install && npm run postinstall\" \"cd ./electron/core/plugins/monitoring-plugin && npm install && npm run postinstall\" && chmod +x ./electron/auto-sign.sh && ./electron/auto-sign.sh && concurrently \"cd ./electron/core/plugins/data-plugin && npm run build:publish\" \"cd ./electron/core/plugins/inference-plugin && npm run build:publish\" \"cd ./electron/core/plugins/model-management-plugin && npm run build:publish\" \"cd ./electron/core/plugins/monitoring-plugin && npm run build:publish\"",
"build": "yarn build:web && yarn build:electron",
"build:darwin": "yarn build:web && yarn workspace jan build:darwin",
"build:win32": "yarn build:web && yarn workspace jan build:win32",

View File

@ -143,15 +143,20 @@ To register plugin preferences, you can use the preferences object from the @jan
```js
import { PluginService, preferences } from "@janhq/plugin-core";
const PluginName = "your-first-plugin";
const pluginName = "your-first-plugin";
const preferenceKey = "";
const preferenceName = "Your First Preference";
const preferenceDescription = "This is for example only";
const defaultValue = "";
export function init({ register }: { register: RegisterExtensionPoint }) {
// Register preference update handlers. E.g. update plugin instance with new configuration
register(PluginService.OnPreferencesUpdate, PluginName, onPreferencesUpdate);
register(PluginService.OnPreferencesUpdate, pluginName, onPreferencesUpdate);
// Register plugin preferences. E.g. Plugin need apiKey and endpoint to connect to your service
preferences.registerPreferences<string>(register, PluginName, "apiKey", "");
preferences.registerPreferences<string>(register, PluginName, "endpoint", "");
// Register plugin preferences. E.g. Plugin need apiKey to connect to your service
preferences.registerPreferences <
string >
(register, pluginName, preferenceKey, preferenceName, preferenceDescription, defaultValue);
}
```
@ -162,15 +167,13 @@ To retrieve the values of the registered preferences, we're using the get method
```js
import { preferences } from "@janhq/plugin-core";
const PluginName = "your-first-plugin";
const pluginName = "your-first-plugin";
const preferenceKey = "apiKey";
const setup = async () => {
// Retrieve apiKey
const apiKey: string = (await preferences.get(PluginName, "apiKey")) ?? "";
// Retrieve endpoint
const endpoint: string = (await preferences.get(PluginName, "endpoint")) ?? "";
}
const apiKey: string = (await preferences.get(pluginName, preferenceKey)) ?? "";
};
```
### Access Core API

View File

@ -8,7 +8,8 @@ export type CoreService =
| InferenceService
| ModelManagementService
| SystemMonitoringService
| PreferenceService;
| PreferenceService
| PluginService;
/**
* Represents the available methods for the StoreService.

View File

@ -4,13 +4,13 @@ import { store } from "./store";
* Returns the value of the specified preference for the specified plugin.
*
* @param pluginName The name of the plugin.
* @param preferenceName The name of the preference.
* @param preferenceKey The key of the preference.
* @returns A promise that resolves to the value of the preference.
*/
function get(pluginName: string, preferenceName: string): Promise<any> {
function get(pluginName: string, preferenceKey: string): Promise<any> {
return store
.createCollection("preferences", {})
.then(() => store.findOne("preferences", `${pluginName}.${preferenceName}`))
.then(() => store.findOne("preferences", `${pluginName}.${preferenceKey}`))
.then((doc) => doc?.value ?? "");
}
@ -18,20 +18,20 @@ function get(pluginName: string, preferenceName: string): Promise<any> {
* Sets the value of the specified preference for the specified plugin.
*
* @param pluginName The name of the plugin.
* @param preferenceName The name of the preference.
* @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, preferenceName: string, value: any): Promise<any> {
function set(pluginName: string, preferenceKey: string, value: any): Promise<any> {
return store
.createCollection("preferences", {})
.then(() =>
store
.findOne("preferences", `${pluginName}.${preferenceName}`)
.findOne("preferences", `${pluginName}.${preferenceKey}`)
.then((doc) =>
doc
? store.updateOne("preferences", `${pluginName}.${preferenceName}`, { value })
: store.insertOne("preferences", { _id: `${pluginName}.${preferenceName}`, value })
? store.updateOne("preferences", `${pluginName}.${preferenceKey}`, { value })
: store.insertOne("preferences", { _id: `${pluginName}.${preferenceKey}`, value })
)
);
}
@ -51,7 +51,9 @@ function clear(pluginName: string): Promise<void> {
*
* @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>(

View File

@ -6,21 +6,22 @@ import {
extensionPoints,
activationPoints,
} from "@/../../electron/core/plugin-manager/execution/index";
import {
ChartPieIcon,
CommandLineIcon,
PlayIcon,
} from "@heroicons/react/24/outline";
import { ChartPieIcon, CommandLineIcon, PlayIcon } from "@heroicons/react/24/outline";
import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
import classNames from "classnames";
import { PluginService, preferences } from "@janhq/plugin-core";
import { execute } from "../../../electron/core/plugin-manager/execution/extension-manager";
/* eslint-disable @next/next/no-sync-scripts */
export const Preferences = () => {
const [search, setSearch] = useState<string>("");
const [activePlugins, setActivePlugins] = useState<any[]>([]);
const [preferenceItems, setPreferenceItems] = useState<any[]>([]);
const [preferenceValues, setPreferenceValues] = useState<any[]>([]);
const [isTestAvailable, setIsTestAvailable] = useState(false);
const [fileName, setFileName] = useState("");
const experimentRef = useRef(null);
const preferenceRef = useRef(null);
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
@ -31,7 +32,6 @@ export const Preferences = () => {
}
};
const preferenceRef = useRef(null);
useEffect(() => {
async function setupPE() {
// Enable activation point management
@ -54,19 +54,22 @@ export const Preferences = () => {
setTimeout(async () => {
await activationPoints.trigger("init");
if (extensionPoints.get("experimentComponent")) {
const components = await Promise.all(
extensionPoints.execute("experimentComponent")
);
const components = await Promise.all(extensionPoints.execute("experimentComponent"));
if (components.length > 0) {
setIsTestAvailable(true);
}
components.forEach((e) => {
if (preferenceRef.current) {
if (experimentRef.current) {
// @ts-ignore
preferenceRef.current.appendChild(e);
experimentRef.current.appendChild(e);
}
});
}
if (extensionPoints.get("PluginPreferences")) {
const data = await Promise.all(extensionPoints.execute("PluginPreferences"));
setPreferenceItems(Array.isArray(data) ? data : []);
}
}, 500);
};
setupPE().then(() => activePlugins());
@ -86,17 +89,9 @@ export const Preferences = () => {
// Uninstall a plugin on clicking uninstall
const uninstall = async (name: string) => {
//@ts-ignore
// Send the filename of the to be uninstalled plugin
// to the main process for removal
//@ts-ignore
const res = await plugins.uninstall([name]);
console.log(
res
? "Plugin successfully uninstalled"
: "Plugin could not be uninstalled"
);
if (res) window.electronAPI.relaunch();
};
@ -110,6 +105,26 @@ export const Preferences = () => {
// plugins.update(active.map((plg) => plg.name));
};
let timeout: any | undefined = undefined;
function notifyPreferenceUpdate() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => execute(PluginService.OnPreferencesUpdate), 100);
}
useEffect(() => {
if (preferenceItems) {
Promise.all(
preferenceItems.map((e) =>
preferences.get(e.pluginName, e.preferenceKey).then((k) => ({ key: e.preferenceKey, value: k }))
)
).then((data) => {
setPreferenceValues(data);
});
}
}, [preferenceItems]);
return (
<div className="w-full h-screen overflow-scroll">
<div className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-gray-200 bg-white shadow-sm sm:gap-x-6 sm:px-6 px-8">
@ -152,12 +167,9 @@ export const Preferences = () => {
{!fileName ? (
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">Click to upload</span>{" "}
or drag and drop
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
TGZ (MAX 50MB)
<span className="font-semibold">Click to upload</span> or drag and drop
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">TGZ (MAX 50MB)</p>
</div>
) : (
<>{fileName}</>
@ -177,9 +189,7 @@ export const Preferences = () => {
type="submit"
className={classNames(
"rounded-md px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600",
fileName
? "bg-blue-500 hover:bg-blue-300"
: "bg-gray-500"
fileName ? "bg-blue-500 hover:bg-blue-300" : "bg-gray-500"
)}
>
Install Plugin
@ -205,11 +215,7 @@ export const Preferences = () => {
</div>
<div className="grid grid-cols-2 items-stretch gap-4">
{activePlugins
.filter(
(e) =>
search.trim() === "" ||
e.name.toLowerCase().includes(search.toLowerCase())
)
.filter((e) => search.trim() === "" || e.name.toLowerCase().includes(search.toLowerCase()))
.map((e) => (
<div
key={e.name}
@ -218,19 +224,13 @@ export const Preferences = () => {
>
<div className="flex flex-row space-x-2 items-center">
<span className="relative inline-block mt-1">
<img
className="h-14 w-14 rounded-md"
src={e.icon ?? "icons/app_icon.svg"}
alt=""
/>
<img className="h-14 w-14 rounded-md" src={e.icon ?? "icons/app_icon.svg"} alt="" />
</span>
<div className="flex flex-col">
<p className="text-xl font-bold tracking-tight text-gray-900 dark:text-white capitalize">
{e.name.replaceAll("-", " ")}
</p>
<p className="font-normal text-gray-700 dark:text-gray-400">
Version: {e.version}
</p>
<p className="font-normal text-gray-700 dark:text-gray-400">Version: {e.version}</p>
</div>
</div>
@ -266,8 +266,34 @@ export const Preferences = () => {
Test Plugins
</div>
)}
<div className="h-full w-full" ref={preferenceRef}></div>
{/* Content */}
<div className="h-full w-full" ref={experimentRef}></div>
<div className="flex flex-row items-center my-4">
<PlayIcon width={30} />
Preferences
</div>
<div className="h-full w-full flex flex-col" ref={preferenceRef}>
{preferenceItems?.map((e) => (
<div key={e.preferenceKey} className="flex flex-col mb-4">
<div>
<span className="text-[16px] text-gray-600">Setting:</span>{" "}
<span className="text-[16px] text-gray-900">{e.preferenceName}</span>
</div>
<span className="text-[14px] text-gray-400">{e.preferenceDescription}</span>
<div className="flex flex-row space-x-4 items-center mt-2">
<input
className="text-gray-500 w-1/3 rounded-sm border-gray-300 border-[1px] h-8"
defaultValue={preferenceValues.filter((v) => v.key === e.preferenceKey)[0]?.value}
onChange={(event) => {
preferences
.set(e.pluginName, e.preferenceKey, event.target.value)
.then(() => notifyPreferenceUpdate());
}}
></input>
</div>
</div>
))}
</div>
</div>
</main>
</div>

View File

@ -0,0 +1,34 @@
import { addNewMessageAtom, updateMessageAtom } from "@/_helpers/atoms/ChatMessage.atom";
import { toChatMessage } from "@/_models/ChatMessage";
import { events, EventName, NewMessageResponse } from "@janhq/plugin-core";
import { useSetAtom } from "jotai";
import { ReactNode, useEffect } from "react";
export default function EventHandler({ children }: { children: ReactNode }) {
const addNewMessage = useSetAtom(addNewMessageAtom);
const updateMessage = useSetAtom(updateMessageAtom);
function handleNewMessageResponse(message: NewMessageResponse) {
const newResponse = toChatMessage(message);
addNewMessage(newResponse);
}
async function handleMessageResponseUpdate(messageResponse: NewMessageResponse) {
if (messageResponse.conversationId && messageResponse._id && messageResponse.message)
updateMessage(messageResponse._id, messageResponse.conversationId, messageResponse.message);
}
useEffect(() => {
if (window.corePlugin.events) {
events.on(EventName.OnNewMessageResponse, handleNewMessageResponse);
events.on(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate);
}
}, []);
useEffect(() => {
return () => {
events.off(EventName.OnNewMessageResponse, handleNewMessageResponse);
events.off(EventName.OnMessageResponseUpdate, handleMessageResponseUpdate);
};
}, []);
return <> {children}</>;
}

View File

@ -6,12 +6,10 @@ import { appDownloadProgress } from "./JotaiWrapper";
import { DownloadState } from "@/_models/DownloadState";
import { executeSerial } from "../../../electron/core/plugin-manager/execution/extension-manager";
import { ModelManagementService } from "@janhq/plugin-core";
import {
setDownloadStateAtom,
setDownloadStateSuccessAtom,
} from "./atoms/DownloadState.atom";
import { setDownloadStateAtom, setDownloadStateSuccessAtom } from "./atoms/DownloadState.atom";
import { getDownloadedModels } from "@/_hooks/useGetDownloadedModels";
import { downloadedModelAtom } from "./atoms/DownloadedModel.atom";
import EventHandler from "./EventHandler";
type Props = {
children: ReactNode;
@ -25,57 +23,46 @@ export default function EventListenerWrapper({ children }: Props) {
useEffect(() => {
if (window && window.electronAPI) {
window.electronAPI.onFileDownloadUpdate(
(_event: string, state: DownloadState | undefined) => {
if (!state) return;
setDownloadState(state);
}
);
window.electronAPI.onFileDownloadUpdate((_event: string, state: DownloadState | undefined) => {
if (!state) return;
setDownloadState(state);
});
window.electronAPI.onFileDownloadError(
(_event: string, callback: any) => {
console.log("Download error", callback);
}
);
window.electronAPI.onFileDownloadError((_event: string, callback: any) => {
console.log("Download error", callback);
});
window.electronAPI.onFileDownloadSuccess(
(_event: string, callback: any) => {
if (callback && callback.fileName) {
setDownloadStateSuccess(callback.fileName);
window.electronAPI.onFileDownloadSuccess((_event: string, callback: any) => {
if (callback && callback.fileName) {
setDownloadStateSuccess(callback.fileName);
executeSerial(
ModelManagementService.UpdateFinishedDownloadAt,
callback.fileName
).then(() => {
getDownloadedModels().then((models) => {
setDownloadedModels(models);
});
executeSerial(ModelManagementService.UpdateFinishedDownloadAt, callback.fileName).then(() => {
getDownloadedModels().then((models) => {
setDownloadedModels(models);
});
}
});
}
);
});
window.electronAPI.onAppUpdateDownloadUpdate(
(_event: string, progress: any) => {
setProgress(progress.percent);
console.log("app update progress:", progress.percent);
}
);
window.electronAPI.onAppUpdateDownloadUpdate((_event: string, progress: any) => {
setProgress(progress.percent);
console.log("app update progress:", progress.percent);
});
window.electronAPI.onAppUpdateDownloadError(
(_event: string, callback: any) => {
console.log("Download error", callback);
setProgress(-1);
}
);
window.electronAPI.onAppUpdateDownloadError((_event: string, callback: any) => {
console.log("Download error", callback);
setProgress(-1);
});
window.electronAPI.onAppUpdateDownloadSuccess(
(_event: string, callback: any) => {
setProgress(-1);
}
);
window.electronAPI.onAppUpdateDownloadSuccess((_event: string, callback: any) => {
setProgress(-1);
});
}
}, []);
return <div id="eventlistener">{children}</div>;
return (
<div id="eventlistener">
<EventHandler>{children}</EventHandler>
</div>
);
}

View File

@ -31,28 +31,18 @@ const useChatMessages = (offset = 0) => {
if (!hasMore) return;
const getMessages = async () => {
executeSerial(
DataService.GetConversationMessages,
currentConvo._id
).then((data: any) => {
executeSerial(DataService.GetConversationMessages, currentConvo._id).then((data: any) => {
if (!data) {
return;
}
parseMessages(data ?? []).then((newMessages) => {
addOldChatMessages(newMessages);
updateConvoHasMore(currentConvo._id ?? "", false);
setLoading(false);
});
const newMessages = parseMessages(data ?? []);
addOldChatMessages(newMessages);
updateConvoHasMore(currentConvo._id ?? "", false);
setLoading(false);
});
};
getMessages();
}, [
offset,
currentConvo?._id,
convoStates,
addOldChatMessages,
updateConvoHasMore,
]);
}, [offset, currentConvo?._id, convoStates, addOldChatMessages, updateConvoHasMore]);
return {
loading: loading,
@ -61,10 +51,10 @@ const useChatMessages = (offset = 0) => {
};
};
async function parseMessages(messages: RawMessage[]): Promise<ChatMessage[]> {
function parseMessages(messages: RawMessage[]): ChatMessage[] {
const newMessages: ChatMessage[] = [];
for (const m of messages) {
const chatMessage = await toChatMessage(m);
const chatMessage = toChatMessage(m);
newMessages.push(chatMessage);
}
return newMessages;

View File

@ -1,51 +1,19 @@
import { currentPromptAtom } from "@/_helpers/JotaiWrapper";
import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { selectAtom } from "jotai/utils";
import { DataService, InferenceService } from "@janhq/plugin-core";
import {
ChatMessage,
MessageSenderType,
RawMessage,
toChatMessage,
} from "@/_models/ChatMessage";
import { DataService, EventName, events } from "@janhq/plugin-core";
import { RawMessage, toChatMessage } from "@/_models/ChatMessage";
import { executeSerial } from "@/_services/pluginService";
import { useCallback } from "react";
import {
addNewMessageAtom,
updateMessageAtom,
chatMessages,
currentChatMessagesAtom,
} from "@/_helpers/atoms/ChatMessage.atom";
import {
currentConversationAtom,
getActiveConvoIdAtom,
updateConversationAtom,
updateConversationWaitingForResponseAtom,
} from "@/_helpers/atoms/Conversation.atom";
import { Conversation } from "@/_models/Conversation";
import { addNewMessageAtom } from "@/_helpers/atoms/ChatMessage.atom";
import { currentConversationAtom } from "@/_helpers/atoms/Conversation.atom";
export default function useSendChatMessage() {
const currentConvo = useAtomValue(currentConversationAtom);
const updateConversation = useSetAtom(updateConversationAtom);
const addNewMessage = useSetAtom(addNewMessageAtom);
const updateMessage = useSetAtom(updateMessageAtom);
const activeConversationId = useAtomValue(getActiveConvoIdAtom) ?? "";
const updateConvWaiting = useSetAtom(
updateConversationWaitingForResponseAtom
);
const chatMessagesHistory = useAtomValue(
selectAtom(
chatMessages,
useCallback((v) => v[activeConversationId], [activeConversationId])
)
);
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom);
const sendChatMessage = async () => {
setCurrentPrompt("");
const conversationId = activeConversationId;
updateConvWaiting(conversationId, true);
const prompt = currentPrompt.trim();
const newMessage: RawMessage = {
conversationId: currentConvo?._id,
@ -56,188 +24,9 @@ export default function useSendChatMessage() {
const id = await executeSerial(DataService.CreateMessage, newMessage);
newMessage._id = id;
const newChatMessage = await toChatMessage(newMessage);
addNewMessage(newChatMessage);
const messageHistory = chatMessagesHistory ?? [];
const recentMessages = [
...messageHistory.sort((a, b) => parseInt(a.id) - parseInt(b.id)),
newChatMessage,
]
.slice(-10)
.map((message) => {
return {
content: message.text,
role:
message.messageSenderType === MessageSenderType.User
? "user"
: "assistant",
};
});
const url = await executeSerial(InferenceService.InferenceUrl);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
"Access-Control-Allow-Origi": "*",
},
body: JSON.stringify({
messages: recentMessages,
stream: true,
model: "gpt-3.5-turbo",
max_tokens: 500,
}),
});
const stream = response.body;
const decoder = new TextDecoder("utf-8");
const reader = stream?.getReader();
let answer = "";
// Cache received response
const newResponse: RawMessage = {
conversationId: currentConvo?._id,
message: answer,
user: "assistant",
createdAt: new Date().toISOString(),
};
const respId = await executeSerial(DataService.CreateMessage, newResponse);
newResponse._id = respId;
const responseChatMessage = toChatMessage(newResponse);
addNewMessage(responseChatMessage);
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]")) {
updateConvWaiting(conversationId, false);
const data = JSON.parse(line.replace("data: ", ""));
answer += data.choices[0]?.delta?.content ?? "";
if (answer.startsWith("assistant: ")) {
answer = answer.replace("assistant: ", "");
}
updateMessage(
responseChatMessage.id,
responseChatMessage.conversationId,
answer
);
}
}
}
updateMessage(
responseChatMessage.id,
responseChatMessage.conversationId,
answer.trimEnd()
);
await executeSerial(DataService.UpdateMessage, {
...newResponse,
message: answer.trimEnd(),
updatedAt: new Date()
.toISOString()
.replace("T", " ")
.replace(/\.\d+Z$/, ""),
});
const updatedConvo: Conversation = {
...currentConvo,
lastMessage: answer.trim(),
updatedAt: new Date().toISOString(),
};
await executeSerial(DataService.UpdateConversation, updatedConvo);
updateConversation(updatedConvo);
updateConvWaiting(conversationId, false);
newResponse.message = answer.trim();
const messages: RawMessage[] = [newMessage, newResponse];
inferConvoSummary(updatedConvo, messages);
};
const inferConvoSummary = async (
convo: Conversation,
lastMessages: RawMessage[]
) => {
if (convo.summary) return;
const newMessage: RawMessage = {
conversationId: currentConvo?._id,
message: "summary this conversation in 5 words",
user: "user",
createdAt: new Date().toISOString(),
};
const messageHistory = lastMessages.map((m) => toChatMessage(m));
const newChatMessage = toChatMessage(newMessage);
const recentMessages = [...messageHistory, newChatMessage]
.slice(-10)
.map((message) => ({
content: message.text,
role: message.messageSenderType,
}));
console.debug(`Sending ${JSON.stringify(recentMessages)}`);
const url = await executeSerial(InferenceService.InferenceUrl);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream",
"Access-Control-Allow-Origi": "*",
},
body: JSON.stringify({
messages: recentMessages,
stream: true,
model: "gpt-3.5-turbo",
max_tokens: 500,
}),
});
const stream = response.body;
const decoder = new TextDecoder("utf-8");
const reader = stream?.getReader();
let answer = "";
// Cache received response
const newResponse: RawMessage = {
conversationId: currentConvo?._id,
message: answer,
user: "assistant",
createdAt: new Date().toISOString(),
};
while (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: ", ""));
answer += data.choices[0]?.delta?.content ?? "";
if (answer.startsWith("assistant: ")) {
answer = answer.replace("assistant: ", "");
}
}
}
}
const updatedConvo: Conversation = {
...convo,
summary: answer.trim(),
};
console.debug(`Update convo: ${JSON.stringify(updatedConvo)}`);
await executeSerial(DataService.UpdateConversation, updatedConvo);
updateConversation(updatedConvo);
addNewMessage(newChatMessage);
events.emit(EventName.OnNewMessageRequest, newMessage);
};
return {

View File

@ -1,3 +1,4 @@
import { NewMessageResponse } from "@janhq/plugin-core";
export enum MessageType {
Text = "Text",
Image = "Image",
@ -33,12 +34,13 @@ export interface RawMessage {
_id?: string;
conversationId?: string;
user?: string;
avatar?: string;
message?: string;
createdAt?: string;
updatedAt?: string;
}
export const toChatMessage = (m: RawMessage): ChatMessage => {
export const toChatMessage = (m: RawMessage | NewMessageResponse): ChatMessage => {
const createdAt = new Date(m.createdAt ?? "").getTime();
const imageUrls: string[] = [];
const imageUrl = undefined;
@ -47,8 +49,7 @@ export const toChatMessage = (m: RawMessage): ChatMessage => {
}
const messageType = MessageType.Text;
const messageSenderType =
m.user === "user" ? MessageSenderType.User : MessageSenderType.Ai;
const messageSenderType = m.user === "user" ? MessageSenderType.User : MessageSenderType.Ai;
const content = m.message ?? "";
@ -58,9 +59,8 @@ export const toChatMessage = (m: RawMessage): ChatMessage => {
messageType: messageType,
messageSenderType: messageSenderType,
senderUid: m.user?.toString() || "0",
senderName: m.user === "user" ? "You" : "Assistant",
senderAvatarUrl:
m.user === "user" ? "icons/avatar.svg" : "icons/app_icon.svg",
senderName: m.user === "user" ? "You" : m.user && m.user !== "ai" && m.user !== "assistant" ? m.user : "Assistant",
senderAvatarUrl: m.avatar ? m.avatar : m.user === "user" ? "icons/avatar.svg" : "icons/app_icon.svg",
text: content,
imageUrls: imageUrls,
createdAt: createdAt,

View File

@ -1,8 +1,21 @@
import { store } from "./storeService";
import { EventEmitter } from "./eventsService";
export const setupCoreServices = () => {
if (typeof window === "undefined") {
console.log("undefine", window);
return;
} else {
console.log("Setting up core services");
}
if (!window.corePlugin) {
window.corePlugin = {
store,
events: new EventEmitter(),
};
}
if (!window.coreAPI) {
// fallback electron API
window.coreAPI = window.electronAPI;
}
};

View File

@ -0,0 +1,40 @@
export class EventEmitter {
private handlers: Map<string, Function[]>;
constructor() {
this.handlers = new Map<string, Function[]>();
}
public on(eventName: string, handler: Function): void {
if (!this.handlers.has(eventName)) {
this.handlers.set(eventName, []);
}
this.handlers.get(eventName)?.push(handler);
}
public off(eventName: string, handler: Function): void {
if (!this.handlers.has(eventName)) {
return;
}
const handlers = this.handlers.get(eventName);
const index = handlers?.indexOf(handler);
if (index !== undefined && index !== -1) {
handlers?.splice(index, 1);
}
}
public emit(eventName: string, args: any): void {
if (!this.handlers.has(eventName)) {
return;
}
const handlers = this.handlers.get(eventName);
handlers?.forEach((handler) => {
handler(args);
});
}
}

View File

@ -1,76 +1,81 @@
"use client";
import { PluginService } from "@janhq/plugin-core";
import { ThemeWrapper } from "./_helpers/ThemeWrapper";
import JotaiWrapper from "./_helpers/JotaiWrapper";
import { ModalWrapper } from "./_helpers/ModalWrapper";
import { useEffect, useState } from "react";
import Image from "next/image";
import {
setup,
plugins,
activationPoints,
} from "../../electron/core/plugin-manager/execution/index";
import {
isCorePluginInstalled,
setupBasePlugins,
} from "./_services/pluginService";
import { setup, plugins, activationPoints, extensionPoints } from "../../electron/core/plugin-manager/execution/index";
import { isCorePluginInstalled, setupBasePlugins } from "./_services/pluginService";
import EventListenerWrapper from "./_helpers/EventListenerWrapper";
import { setupCoreServices } from "./_services/coreService";
import MainContainer from "./_components/MainContainer";
import { executeSerial } from "../../electron/core/plugin-manager/execution/extension-manager";
const Page: React.FC = () => {
const [setupCore, setSetupCore] = useState(false);
const [activated, setActivated] = useState(false);
async function setupPE() {
// Enable activation point management
setup({
importer: (plugin: string) =>
import(/* webpackIgnore: true */ plugin).catch((err) => {
console.log(err);
}),
});
// Register all active plugins with their activation points
await plugins.registerActive();
setTimeout(async () => {
// Trigger activation points
await activationPoints.trigger("init");
if (!isCorePluginInstalled()) {
setupBasePlugins();
return;
}
if (extensionPoints.get(PluginService.OnStart)) {
await executeSerial(PluginService.OnStart);
}
setActivated(true);
}, 500);
}
// Services Setup
useEffect(() => {
// Setup Core Service
setupCoreServices();
async function setupPE() {
// Enable activation point management
setup({
importer: (plugin: string) =>
import(/* webpackIgnore: true */ plugin).catch((err) => {
console.log(err);
}),
});
// Register all active plugins with their activation points
await plugins.registerActive();
setTimeout(async () => {
// Trigger activation points
await activationPoints.trigger("init");
if (!isCorePluginInstalled()) {
setupBasePlugins();
return;
}
setActivated(true);
}, 500);
}
// Electron
if (window && window.electronAPI) {
setupPE();
} else {
// Host
setActivated(true);
}
setSetupCore(true);
}, []);
useEffect(() => {
if (setupCore) {
// Electron
if (window && window.electronAPI) {
setupPE();
} else {
// Host
setActivated(true);
}
}
}, [setupCore]);
return (
<JotaiWrapper>
<EventListenerWrapper>
<ThemeWrapper>
<ModalWrapper>
{activated ? (
<MainContainer />
) : (
<div className="bg-white w-screen h-screen items-center justify-center flex">
<Image width={50} height={50} src="icons/app_icon.svg" alt="" />
</div>
)}
</ModalWrapper>
</ThemeWrapper>
</EventListenerWrapper>
{setupCore && (
<EventListenerWrapper>
<ThemeWrapper>
<ModalWrapper>
{activated ? (
<MainContainer />
) : (
<div className="bg-white w-screen h-screen items-center justify-center flex">
<Image width={50} height={50} src="icons/app_icon.svg" alt="" />
</div>
)}
</ModalWrapper>
</ThemeWrapper>
</EventListenerWrapper>
)}
</JotaiWrapper>
);
};

View File

@ -4,5 +4,6 @@ declare global {
interface Window {
electronAPI?: any | undefined;
corePlugin?: any | undefined;
coreAPI?: any | undefined;
}
}