Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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