Skip to content

Commit

Permalink
feat: JSON Conversational Plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
louis-jan committed Nov 6, 2023
1 parent 9130018 commit 36c9807
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 13 deletions.
8 changes: 8 additions & 0 deletions core/src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ const listFiles: (path: string) => Promise<any> = (path) =>
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.
Expand All @@ -45,5 +52,6 @@ export const fs = {
readFile,
listFiles,
mkdir,
rmdir,
deleteFile,
};
22 changes: 22 additions & 0 deletions electron/handlers/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,28 @@ export function handleFs() {
});
});

/**
* 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.
Expand Down
3 changes: 3 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
* @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.
Expand Down Expand Up @@ -94,6 +95,8 @@ contextBridge.exposeInMainWorld("electronAPI", {

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

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

installRemotePlugin: (pluginName: string) =>
ipcRenderer.invoke("installRemotePlugin", pluginName),

Expand Down
39 changes: 39 additions & 0 deletions plugins/conversational-json/package.json
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>",
"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 changes: 87 additions & 0 deletions plugins/conversational-json/src/index.ts
Original file line number Diff line number Diff line change
@@ -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-")));
});
}
}
14 changes: 14 additions & 0 deletions plugins/conversational-json/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es2016",
"module": "ES6",
"moduleResolution": "node",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": false,
"skipLibCheck": true,
"rootDir": "./src"
},
"include": ["./src"]
}
31 changes: 31 additions & 0 deletions plugins/conversational-json/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const path = require("path");
const webpack = require("webpack");

module.exports = {
experiments: { outputModule: true },
entry: "./src/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
},
plugins: [new webpack.DefinePlugin({})],
resolve: {
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
};
22 changes: 12 additions & 10 deletions plugins/conversational-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Message, Conversation } from "@janhq/core/lib/types";

/**
* JanConversationalPlugin is a ConversationalPlugin implementation that provides
* functionality for managing conversations in a Jan bot.
* functionality for managing conversations.
*/
export default class JanConversationalPlugin implements ConversationalPlugin {
/**
Expand All @@ -18,14 +18,15 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
* Called when the plugin is loaded.
*/
onLoad() {
console.debug("JanConversationalPlugin loaded");
console.debug("JanConversationalPlugin loaded")
fs.mkdir("conversations");
}

/**
* Called when the plugin is unloaded.
*/
onUnload() {
console.debug("JanConversationalPlugin unloaded");
console.debug("JanConversationalPlugin unloaded")
}

/**
Expand All @@ -36,7 +37,7 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
Promise.all(
conversationIds.map((conversationId) =>
this.loadConversationFromMarkdownFile(
`conversations/${conversationId}`
`conversations/${conversationId}/${conversationId}.md`
)
)
).then((conversations) =>
Expand All @@ -61,7 +62,7 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
* @param conversationId The ID of the conversation to delete.
*/
deleteConversation(conversationId: string): Promise<void> {
return fs.deleteFile(`conversations/${conversationId}.md`);
return fs.rmdir(`conversations/${conversationId}`);
}

/**
Expand All @@ -71,9 +72,7 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
*/
private async getConversationDocs(): Promise<string[]> {
return fs.listFiles("conversations").then((files: string[]) => {
return Promise.all(
files.filter((file) => file.startsWith("jan-"))
);
return Promise.all(files.filter((file) => file.startsWith("jan-")));
});
}

Expand Down Expand Up @@ -202,10 +201,13 @@ export default class JanConversationalPlugin implements ConversationalPlugin {
* @private
*/
private async writeMarkdownToFile(conversation: Conversation) {
await fs.mkdir("conversations");
// 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}.md`, markdownContent);
await fs.writeFile(
`conversations/${conversation._id}/${conversation._id}.md`,
markdownContent
);
}
}
31 changes: 31 additions & 0 deletions plugins/inference-plugin/src/helpers/message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ChatMessage } from '@models/ChatMessage'

/**
* Util function to merge two array of messages and remove duplicates.
* Also preserve the order
*
* @param arr1 Message array 1
* @param arr2 Message array 2
* @returns Merged array of messages
*/
export function mergeAndRemoveDuplicates(
arr1: ChatMessage[],
arr2: ChatMessage[]
): ChatMessage[] {
const mergedArray = arr1.concat(arr2)
const uniqueIdMap = new Map<string, boolean>()
const result: ChatMessage[] = []

for (const message of mergedArray) {
if (!uniqueIdMap.has(message.id)) {
uniqueIdMap.set(message.id, true)
result.push(message)
}
}

return result.reverse()
}

export const generateMessageId = () => {
return `m-${Date.now()}`
}
3 changes: 2 additions & 1 deletion plugins/inference-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} 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.
Expand Down Expand Up @@ -117,7 +118,7 @@ export default class JanInferencePlugin implements InferencePlugin {
message: "",
user: "assistant",
createdAt: new Date().toISOString(),
_id: `message-${Date.now()}`,
_id: generateMessageId(),
};
events.emit(EventName.OnNewMessageResponse, message);

Expand Down
3 changes: 3 additions & 0 deletions web/containers/Providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const Providers = (props: PropsWithChildren) => {
useEffect(() => {
setupCoreServices()
setSetupCore(true)
return () => {
pluginManager.unload()
}
}, [])

useEffect(() => {
Expand Down
3 changes: 2 additions & 1 deletion web/hooks/useCreateConversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '@helpers/atoms/Conversation.atom'
import { Model } from '@janhq/core/lib/types'
import { downloadedModelAtom } from '@helpers/atoms/DownloadedModel.atom'
import { generateConversationId } from '@utils/conversation'

const useCreateConversation = () => {
const [userConversations, setUserConversations] = useAtom(
Expand All @@ -31,7 +32,7 @@ const useCreateConversation = () => {
const requestCreateConvo = async (model: Model, bot?: Bot) => {
const conversationName = model.name
const mappedConvo: Conversation = {
_id: `jan-${Date.now()}`,
_id: generateConversationId(),
modelId: model._id,
name: conversationName,
createdAt: new Date().toISOString(),
Expand Down
Loading

0 comments on commit 36c9807

Please sign in to comment.