diff --git a/plugin-core/README.md b/plugin-core/README.md index 1066f879f..806406e79 100644 --- a/plugin-core/README.md +++ b/plugin-core/README.md @@ -34,20 +34,7 @@ export function init({ register }: { register: RegisterExtensionPoint }) { } ``` -### Access Core API - -To access the Core API in your plugin, you can follow the code examples and explanations provided below. - -##### Import Core API and Store Module - -In your main entry code (e.g., `index.ts`), start by importing the necessary modules and functions from the `@janhq/plugin-core` library. - -```js -// index.ts -import { store, core } from "@janhq/plugin-core"; -``` - -#### Interact with Local Data Storage +### Interact with Local Data Storage The Core API allows you to interact with local data storage. Here are a couple of examples of how you can use it: @@ -56,6 +43,8 @@ The Core API allows you to interact with local data storage. Here are a couple o You can use the store.insertOne function to insert data into a specific collection in the local data store. ```js +import { store } from "@janhq/plugin-core"; + function insertData() { store.insertOne("conversations", { name: "meow" }); // Insert a new document with { name: "meow" } into the "conversations" collection. @@ -70,6 +59,8 @@ store.getOne(collectionName, key) retrieves a single document that matches the p store.getMany(collectionName, selector, sort) retrieves multiple documents that match the provided selector in the specified collection. ```js +import { store } from "@janhq/plugin-core"; + function getData() { const selector = { name: "meow" }; const data = store.findMany("conversations", selector); @@ -108,6 +99,93 @@ function deleteData() { } ``` +### Events + +You can subscribe to NewMessageRequest events by defining a function to handle the event and registering it with the events object: + +```js +import { events } from "@janhq/plugin-core"; + +function handleMessageRequest(message: NewMessageRequest) { + // Your logic here. For example: + // const response = openai.createChatCompletion({...}) +} +function registerListener() { + events.on(EventName.OnNewMessageRequest, handleMessageRequest); +} +// Register the listener function with the relevant extension points. +export function init({ register }) { + registerListener(); +} +``` + +In this example, we're defining a function called handleMessageRequest that takes a NewMessageRequest object as its argument. We're also defining a function called registerListener that registers the handleMessageRequest function as a listener for NewMessageRequest events using the on method of the events object. + +```js +import { events } from "@janhq/plugin-core"; + +function handleMessageRequest(data: NewMessageRequest) { + // Your logic here. For example: + const response = openai.createChatCompletion({...}) + const message: NewMessageResponse = { + ...data, + message: response.data.choices[0].message.content + } + // Now emit event so the app can display in the conversation + events.emit(EventName.OnNewMessageResponse, message) +} +``` + +### Preferences + +To register plugin preferences, you can use the preferences object from the @janhq/plugin-core package. Here's an example of how to register and retrieve plugin preferences: + +```js +import { PluginService, preferences } from "@janhq/plugin-core"; + +const PluginName = "your-first-plugin"; + +export function init({ register }: { register: RegisterExtensionPoint }) { + // Register preference update handlers. E.g. update plugin instance with new configuration + register(PluginService.OnPreferencesUpdate, PluginName, onPreferencesUpdate); + + // Register plugin preferences. E.g. Plugin need apiKey and endpoint to connect to your service + preferences.registerPreferences(register, PluginName, "apiKey", ""); + preferences.registerPreferences(register, PluginName, "endpoint", ""); +} +``` + +In this example, we're registering preference update handlers and plugin preferences using the preferences object. We're also defining a PluginName constant to use as the name of the plugin. + +To retrieve the values of the registered preferences, we're using the get method of the preferences object and passing in the name of the plugin and the name of the preference. + +```js +import { preferences } from "@janhq/plugin-core"; + +const PluginName = "your-first-plugin"; + +const setup = async () => { + // Retrieve apiKey + const apiKey: string = (await preferences.get(PluginName, "apiKey")) ?? ""; + + // Retrieve endpoint + const endpoint: string = (await preferences.get(PluginName, "endpoint")) ?? ""; +} +``` + +### Access Core API + +To access the Core API in your plugin, you can follow the code examples and explanations provided below. + +##### Import Core API and Store Module + +In your main entry code (e.g., `index.ts`), start by importing the necessary modules and functions from the `@janhq/plugin-core` library. + +```js +// index.ts +import { core } from "@janhq/plugin-core"; +``` + #### Perform File Operations The Core API also provides functions to perform file operations. Here are a couple of examples: @@ -132,7 +210,7 @@ function deleteModel(filePath: string) { } ``` -### Execute plugin module in main process +#### Execute plugin module in main process To execute a plugin module in the main process of your application, you can follow the steps outlined below. @@ -180,8 +258,8 @@ function getConvMessages(id: number) { } module.exports = { - getConvMessages -} + getConvMessages, +}; ``` ## CoreService API @@ -224,7 +302,6 @@ The `DataService` enum represents methods related to managing conversations and The `InferenceService` enum exports: -- `InferenceUrl`: The URL for the inference server. - `InitModel`: Initializes a model for inference. - `StopModel`: Stops a running inference model. @@ -258,4 +335,11 @@ The `SystemMonitoringService` enum includes methods for monitoring system resour - `GetResourcesInfo`: Gets information about system resources. - `GetCurrentLoad`: Gets the current system load. +## PluginService + +The `PluginService` enum includes plugin cycle handlers: + +- `OnStart`: Handler for starting. E.g. Create a collection. +- `OnPreferencesUpdate`: Handler for preferences update. E.g. Update instances with new configurations. + For more detailed information on each of these components, please refer to the source code. diff --git a/plugin-core/core.ts b/plugin-core/core.ts index 1d0ba942c..b096d1b9a 100644 --- a/plugin-core/core.ts +++ b/plugin-core/core.ts @@ -7,11 +7,7 @@ * @returns Promise * */ -const invokePluginFunc: ( - plugin: string, - method: string, - ...args: any[] -) => Promise = (plugin, method, ...args) => +const invokePluginFunc: (plugin: string, method: string, ...args: any[]) => Promise = (plugin, method, ...args) => window.coreAPI?.invokePluginFunc(plugin, method, ...args) ?? window.electronAPI?.invokePluginFunc(plugin, method, ...args); diff --git a/plugin-core/events.ts b/plugin-core/events.ts new file mode 100644 index 000000000..664c23080 --- /dev/null +++ b/plugin-core/events.ts @@ -0,0 +1,71 @@ +/** + * The `EventName` enumeration contains the names of all the available events in the Jan platform. + */ +export enum EventName { + OnNewConversation = "onNewConversation", + OnNewMessageRequest = "onNewMessageRequest", + OnNewMessageResponse = "onNewMessageResponse", + OnMessageResponseUpdate = "onMessageResponseUpdate", +} + +/** + * The `NewMessageRequest` type defines the shape of a new message request object. + */ +export type NewMessageRequest = { + _id?: string; + conversationId?: string; + user?: string; + avatar?: string; + message?: string; + createdAt?: string; + updatedAt?: string; +}; + +/** + * The `NewMessageRequest` type defines the shape of a new message request object. + */ +export type NewMessageResponse = { + _id?: string; + conversationId?: string; + user?: string; + avatar?: string; + message?: string; + createdAt?: string; + updatedAt?: string; +}; + +/** + * Adds an observer for an event. + * + * @param eventName The name of the event to observe. + * @param handler The handler function to call when the event is observed. + */ +const on: (eventName: string, handler: Function) => void = (eventName, handler) => { + window.corePlugin?.events?.on(eventName, handler); +}; + +/** + * Removes an observer for an event. + * + * @param eventName The name of the event to stop observing. + * @param handler The handler function to call when the event is observed. + */ +const off: (eventName: string, handler: Function) => void = (eventName, handler) => { + window.corePlugin?.events?.off(eventName, handler); +}; + +/** + * Emits an event. + * + * @param eventName The name of the event to emit. + * @param object The object to pass to the event callback. + */ +const emit: (eventName: string, object: any) => void = (eventName, object) => { + window.corePlugin?.events?.emit(eventName, object); +}; + +export const events = { + on, + off, + emit, +}; diff --git a/plugin-core/index.ts b/plugin-core/index.ts index d9f9db8ab..b2f379d2d 100644 --- a/plugin-core/index.ts +++ b/plugin-core/index.ts @@ -107,11 +107,6 @@ export enum DataService { * @enum {string} */ export enum InferenceService { - /** - * The URL for the inference server. - */ - InferenceUrl = "inferenceUrl", - /** * Initializes a model for inference. */ @@ -216,6 +211,32 @@ export enum SystemMonitoringService { 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 @@ -227,3 +248,15 @@ export { store } from "./store"; * @module */ export { core, RegisterExtensionPoint } from "./core"; + +/** + * Events module exports. + * @module + */ +export { events, EventName, NewMessageRequest, NewMessageResponse } from "./events"; + +/** + * Preferences module exports. + * @module + */ +export { preferences } from "./preferences"; diff --git a/plugin-core/package-lock.json b/plugin-core/package-lock.json index 9c3b1041c..d7c80a77f 100644 --- a/plugin-core/package-lock.json +++ b/plugin-core/package-lock.json @@ -1,15 +1,16 @@ { "name": "@janhq/plugin-core", - "version": "0.1.0", + "version": "0.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "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/@types/node": { @@ -17,6 +18,19 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", "dev": true + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } } } } diff --git a/plugin-core/preferences.ts b/plugin-core/preferences.ts new file mode 100644 index 000000000..0ff1969ce --- /dev/null +++ b/plugin-core/preferences.ts @@ -0,0 +1,82 @@ +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. + * @returns A promise that resolves to the value of the preference. + */ +function get(pluginName: string, preferenceName: string): Promise { + return store + .createCollection("preferences", {}) + .then(() => store.findOne("preferences", `${pluginName}.${preferenceName}`)) + .then((doc) => doc?.value ?? ""); +} + +/** + * 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 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 { + return store + .createCollection("preferences", {}) + .then(() => + store + .findOne("preferences", `${pluginName}.${preferenceName}`) + .then((doc) => + doc + ? store.updateOne("preferences", `${pluginName}.${preferenceName}`, { value }) + : store.insertOne("preferences", { _id: `${pluginName}.${preferenceName}`, 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 { + 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 preferenceName The name of the preference. + * @param defaultValue The default value of the preference. + */ +function registerPreferences( + 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, +}; diff --git a/plugin-core/types/index.d.ts b/plugin-core/types/index.d.ts index e342c3e36..3b0ade151 100644 --- a/plugin-core/types/index.d.ts +++ b/plugin-core/types/index.d.ts @@ -3,6 +3,7 @@ export {}; declare global { interface CorePlugin { store?: any | undefined; + events?: any | undefined; } interface Window { corePlugin?: CorePlugin;