feat: JSON Conversational Plugin
This commit is contained in:
parent
9130018291
commit
36c9807ae4
@ -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.
|
||||
@ -45,5 +52,6 @@ export const fs = {
|
||||
readFile,
|
||||
listFiles,
|
||||
mkdir,
|
||||
rmdir,
|
||||
deleteFile,
|
||||
};
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
@ -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),
|
||||
|
||||
|
||||
39
plugins/conversational-json/package.json
Normal file
39
plugins/conversational-json/package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@janhq/conversational-json",
|
||||
"version": "1.0.0",
|
||||
"description": "Conversational Plugin - Stores jan app conversations as JSON",
|
||||
"main": "dist/index.js",
|
||||
"author": "Jan <service@jan.ai>",
|
||||
"license": "MIT",
|
||||
"activationPoints": [
|
||||
"init"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc -b . && webpack --config webpack.config.js",
|
||||
"postinstall": "rimraf *.tgz --glob && npm run build",
|
||||
"build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./main": "./dist/module.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cpx": "^1.5.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@janhq/core": "file:../../core",
|
||||
"ts-loader": "^9.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/*",
|
||||
"package.json",
|
||||
"README.md"
|
||||
],
|
||||
"bundleDependencies": []
|
||||
}
|
||||
87
plugins/conversational-json/src/index.ts
Normal file
87
plugins/conversational-json/src/index.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { PluginType, fs } from "@janhq/core";
|
||||
import { ConversationalPlugin } from "@janhq/core/lib/plugins";
|
||||
import { Conversation } from "@janhq/core/lib/types";
|
||||
|
||||
/**
|
||||
* JSONConversationalPlugin is a ConversationalPlugin implementation that provides
|
||||
* functionality for managing conversations.
|
||||
*/
|
||||
export default class JSONConversationalPlugin implements ConversationalPlugin {
|
||||
/**
|
||||
* Returns the type of the plugin.
|
||||
*/
|
||||
type(): PluginType {
|
||||
return PluginType.Conversational;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the plugin is loaded.
|
||||
*/
|
||||
onLoad() {
|
||||
fs.mkdir("conversations")
|
||||
console.debug("JSONConversationalPlugin loaded")
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the plugin is unloaded.
|
||||
*/
|
||||
onUnload() {
|
||||
console.debug("JSONConversationalPlugin unloaded")
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves to an array of Conversation objects.
|
||||
*/
|
||||
getConversations(): Promise<Conversation[]> {
|
||||
return this.getConversationDocs().then((conversationIds) =>
|
||||
Promise.all(
|
||||
conversationIds.map((conversationId) =>
|
||||
fs
|
||||
.readFile(`conversations/${conversationId}/${conversationId}.json`)
|
||||
.then((data) => {
|
||||
return JSON.parse(data) as Conversation;
|
||||
})
|
||||
)
|
||||
).then((conversations) =>
|
||||
conversations.sort(
|
||||
(a, b) =>
|
||||
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a Conversation object to a Markdown file.
|
||||
* @param conversation The Conversation object to save.
|
||||
*/
|
||||
saveConversation(conversation: Conversation): Promise<void> {
|
||||
return fs
|
||||
.mkdir(`conversations/${conversation._id}`)
|
||||
.then(() =>
|
||||
fs.writeFile(
|
||||
`conversations/${conversation._id}/${conversation._id}.json`,
|
||||
JSON.stringify(conversation)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a conversation with the specified ID.
|
||||
* @param conversationId The ID of the conversation to delete.
|
||||
*/
|
||||
deleteConversation(conversationId: string): Promise<void> {
|
||||
return fs.rmdir(`conversations/${conversationId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves to an array of conversation IDs.
|
||||
* The conversation IDs are the names of the Markdown files in the "conversations" directory.
|
||||
* @private
|
||||
*/
|
||||
private async getConversationDocs(): Promise<string[]> {
|
||||
return fs.listFiles(`conversations`).then((files: string[]) => {
|
||||
return Promise.all(files.filter((file) => file.startsWith("jan-")));
|
||||
});
|
||||
}
|
||||
}
|
||||
14
plugins/conversational-json/tsconfig.json
Normal file
14
plugins/conversational-json/tsconfig.json
Normal file
@ -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
plugins/conversational-json/webpack.config.js
Normal file
31
plugins/conversational-json/webpack.config.js
Normal file
@ -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
|
||||
};
|
||||
@ -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 {
|
||||
/**
|
||||
@ -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")
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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) =>
|
||||
@ -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}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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-")));
|
||||
});
|
||||
}
|
||||
|
||||
@ -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
plugins/inference-plugin/src/helpers/message.ts
Normal file
31
plugins/inference-plugin/src/helpers/message.ts
Normal file
@ -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()}`
|
||||
}
|
||||
@ -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.
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -40,6 +40,9 @@ const Providers = (props: PropsWithChildren) => {
|
||||
useEffect(() => {
|
||||
setupCoreServices()
|
||||
setSetupCore(true)
|
||||
return () => {
|
||||
pluginManager.unload()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -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(
|
||||
@ -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(),
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
} from '@helpers/atoms/Conversation.atom'
|
||||
import { pluginManager } from '@plugin/PluginManager'
|
||||
import { InferencePlugin } from '@janhq/core/lib/plugins'
|
||||
import { generateMessageId } from '@utils/message'
|
||||
|
||||
export default function useSendChatMessage() {
|
||||
const currentConvo = useAtomValue(currentConversationAtom)
|
||||
@ -88,7 +89,7 @@ export default function useSendChatMessage() {
|
||||
} as MessageHistory,
|
||||
])
|
||||
const newMessage: NewMessageRequest = {
|
||||
_id: `message-${Date.now()}`,
|
||||
_id: generateMessageId(),
|
||||
conversationId: convoId,
|
||||
message: prompt,
|
||||
user: 'user',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user