From 6e2210cb22bf2aa20b9898a6d4bfac2ab0115b20 Mon Sep 17 00:00:00 2001 From: NamH Date: Mon, 23 Oct 2023 01:57:56 -0700 Subject: [PATCH] feat: adding create bot functionality (#368) * feat: adding create bot functionality Signed-off-by: James * update the temperature progress bar Signed-off-by: James * chore: remove tgz Signed-off-by: James * update core dependency Signed-off-by: James * fix e2e test Signed-off-by: James --------- Signed-off-by: James Co-authored-by: James --- .gitignore | 1 + electron/tests/explore.e2e.spec.ts | 2 +- electron/tests/my-models.e2e.spec.ts | 2 +- electron/tests/navigation.e2e.spec.ts | 15 +- electron/tests/settings.e2e.spec.ts | 2 +- package.json | 1 + plugins/data-plugin/index.ts | 179 +++++++++++++++-- plugins/data-plugin/package.json | 10 +- plugins/inference-plugin/index.ts | 58 ++++-- plugins/inference-plugin/package.json | 4 +- plugins/model-management-plugin/package.json | 4 +- plugins/monitoring-plugin/package.json | 4 +- plugins/openai-plugin/package.json | 4 +- web/app/_components/Avatar/index.tsx | 23 +++ web/app/_components/BotInfo/index.tsx | 56 ++++++ .../_components/BotInfoContainer/index.tsx | 71 +++++++ .../_components/BotListContainer/index.tsx | 59 ++++++ web/app/_components/BotListModal/index.tsx | 48 +++++ web/app/_components/BotPreview/index.tsx | 26 +++ web/app/_components/BotSetting/index.tsx | 133 +++++++++++++ web/app/_components/CenterContainer/index.tsx | 12 ++ .../_components/ConversationalCard/index.tsx | 3 +- .../_components/CreateBotContainer/index.tsx | 181 ++++++++++++++++++ .../CreateBotPromptInput/index.tsx | 49 +++++ .../CustomBotTemperature/index.tsx | 35 ++++ .../_components/DiscordContainer/index.tsx | 18 -- .../DraggableProgressBar/index.tsx | 42 ++++ web/app/_components/DropdownBox/index.tsx | 40 ++++ web/app/_components/DropdownList/index.tsx | 9 +- .../_components/EmptyChatContainer/index.tsx | 2 +- .../ExploreModelContainer/index.tsx | 1 - web/app/_components/HistoryItem/index.tsx | 19 +- web/app/_components/InputToolbar/index.tsx | 75 +++++++- web/app/_components/JanLogo/index.tsx | 19 -- web/app/_components/LeftContainer/index.tsx | 55 ++++-- .../_components/LeftHeaderAction/index.tsx | 47 +++++ web/app/_components/LeftRibbonNav/index.tsx | 122 ++++++++++++ web/app/_components/LeftSidebar/index.tsx | 20 -- web/app/_components/MainChat/index.tsx | 6 +- web/app/_components/MainChatHeader/index.tsx | 11 -- web/app/_components/MainContainer/index.tsx | 53 ++--- web/app/_components/MainHeader/index.tsx | 67 +++++++ web/app/_components/MainView/index.tsx | 29 ++- web/app/_components/ModelInfo/index.tsx | 42 ---- web/app/_components/ModelMenu/index.tsx | 21 -- web/app/_components/NewChatButton/index.tsx | 45 ----- web/app/_components/PrimaryButton/index.tsx | 8 +- web/app/_components/ProgressSetting/index.tsx | 42 ++++ web/app/_components/RightContainer/index.tsx | 60 +++++- .../_components/SidebarEmptyHistory/index.tsx | 9 +- web/app/_components/SidebarMenu/index.tsx | 36 ---- .../_components/SimpleControlNetMessage.tsx | 3 +- web/app/_components/TabModelDetail/index.tsx | 35 ---- .../_components/TextAreaWithTitle/index.tsx | 48 +++++ .../_components/TextInputWithTitle/index.tsx | 40 ++++ web/app/_components/ToggleSwitch/index.tsx | 51 +++++ web/app/_components/UserToolbar/index.tsx | 29 --- web/app/_helpers/ModalWrapper.tsx | 12 +- web/app/_helpers/atoms/Bot.atom.ts | 4 + web/app/_helpers/atoms/Conversation.atom.ts | 7 +- .../_helpers/atoms/LeftSideBarExpand.atom.ts | 4 + web/app/_helpers/atoms/MainView.atom.ts | 14 ++ web/app/_helpers/atoms/Modal.atom.ts | 1 + web/app/_hooks/useCreateBot.ts | 27 +++ web/app/_hooks/useCreateConversation.ts | 30 ++- web/app/_hooks/useDeleteBot.ts | 25 +++ web/app/_hooks/useGetBots.ts | 29 +++ web/app/_hooks/useInitModel.ts | 2 +- web/app/_hooks/useUpdateBot.ts | 38 ++++ web/app/_models/Bot.ts | 32 ++++ web/app/_models/Conversation.ts | 1 + web/app/_utils/converter.ts | 12 ++ web/app/page.tsx | 40 ++-- web/package.json | 8 +- web/public/icons/Cog.svg | 4 - web/public/icons/Search_gray.svg | 3 - web/public/icons/chat-bubble-oval-left.svg | 3 - web/public/icons/code.svg | 5 - web/public/icons/download.svg | 3 - web/public/icons/ic_sidebar_off.svg | 9 + web/public/icons/ic_sidebar_on.svg | 9 + web/public/icons/ico_Discord.svg | 4 - web/public/icons/janai_logo.svg | 1 - web/public/icons/loading.svg | 35 ---- web/public/icons/play.svg | 3 - web/public/icons/refresh.svg | 3 - web/public/icons/unicorn_angle-down.svg | 3 - web/public/icons/unicorn_arrow.svg | 3 - web/public/icons/unicorn_clipboard-alt.svg | 3 - .../icons/unicorn_exclamation-circle.svg | 3 - web/public/icons/unicorn_trash.svg | 3 - web/public/icons/unicorn_twitter.svg | 3 - web/public/icons/upload_photo.svg | 9 - 93 files changed, 1903 insertions(+), 578 deletions(-) create mode 100644 web/app/_components/Avatar/index.tsx create mode 100644 web/app/_components/BotInfo/index.tsx create mode 100644 web/app/_components/BotInfoContainer/index.tsx create mode 100644 web/app/_components/BotListContainer/index.tsx create mode 100644 web/app/_components/BotListModal/index.tsx create mode 100644 web/app/_components/BotPreview/index.tsx create mode 100644 web/app/_components/BotSetting/index.tsx create mode 100644 web/app/_components/CenterContainer/index.tsx create mode 100644 web/app/_components/CreateBotContainer/index.tsx create mode 100644 web/app/_components/CreateBotPromptInput/index.tsx create mode 100644 web/app/_components/CustomBotTemperature/index.tsx delete mode 100644 web/app/_components/DiscordContainer/index.tsx create mode 100644 web/app/_components/DraggableProgressBar/index.tsx create mode 100644 web/app/_components/DropdownBox/index.tsx delete mode 100644 web/app/_components/JanLogo/index.tsx create mode 100644 web/app/_components/LeftHeaderAction/index.tsx create mode 100644 web/app/_components/LeftRibbonNav/index.tsx delete mode 100644 web/app/_components/LeftSidebar/index.tsx delete mode 100644 web/app/_components/MainChatHeader/index.tsx create mode 100644 web/app/_components/MainHeader/index.tsx delete mode 100644 web/app/_components/ModelInfo/index.tsx delete mode 100644 web/app/_components/ModelMenu/index.tsx delete mode 100644 web/app/_components/NewChatButton/index.tsx create mode 100644 web/app/_components/ProgressSetting/index.tsx delete mode 100644 web/app/_components/SidebarMenu/index.tsx delete mode 100644 web/app/_components/TabModelDetail/index.tsx create mode 100644 web/app/_components/TextAreaWithTitle/index.tsx create mode 100644 web/app/_components/TextInputWithTitle/index.tsx create mode 100644 web/app/_components/ToggleSwitch/index.tsx delete mode 100644 web/app/_components/UserToolbar/index.tsx create mode 100644 web/app/_helpers/atoms/Bot.atom.ts create mode 100644 web/app/_hooks/useCreateBot.ts create mode 100644 web/app/_hooks/useDeleteBot.ts create mode 100644 web/app/_hooks/useGetBots.ts create mode 100644 web/app/_hooks/useUpdateBot.ts create mode 100644 web/app/_models/Bot.ts delete mode 100644 web/public/icons/Cog.svg delete mode 100644 web/public/icons/Search_gray.svg delete mode 100644 web/public/icons/chat-bubble-oval-left.svg delete mode 100644 web/public/icons/code.svg delete mode 100644 web/public/icons/download.svg create mode 100644 web/public/icons/ic_sidebar_off.svg create mode 100644 web/public/icons/ic_sidebar_on.svg delete mode 100644 web/public/icons/ico_Discord.svg delete mode 100644 web/public/icons/janai_logo.svg delete mode 100644 web/public/icons/loading.svg delete mode 100644 web/public/icons/play.svg delete mode 100644 web/public/icons/refresh.svg delete mode 100644 web/public/icons/unicorn_angle-down.svg delete mode 100644 web/public/icons/unicorn_arrow.svg delete mode 100644 web/public/icons/unicorn_clipboard-alt.svg delete mode 100644 web/public/icons/unicorn_exclamation-circle.svg delete mode 100644 web/public/icons/unicorn_trash.svg delete mode 100644 web/public/icons/unicorn_twitter.svg delete mode 100644 web/public/icons/upload_photo.svg diff --git a/.gitignore b/.gitignore index 6ca8c8c06..a190afdbf 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ package-lock.json *.log plugin-core/lib +core/lib/** diff --git a/electron/tests/explore.e2e.spec.ts b/electron/tests/explore.e2e.spec.ts index f6db19c4b..6df823fc1 100644 --- a/electron/tests/explore.e2e.spec.ts +++ b/electron/tests/explore.e2e.spec.ts @@ -35,7 +35,7 @@ test.afterAll(async () => { }); test("explores models", async () => { - await page.getByRole("button", { name: "Explore Models" }).first().click(); + await page.getByTestId("Explore Models").first().click(); const header = await page .getByRole("heading") .filter({ hasText: "Explore Models" }) diff --git a/electron/tests/my-models.e2e.spec.ts b/electron/tests/my-models.e2e.spec.ts index 848c40769..c52f1a0aa 100644 --- a/electron/tests/my-models.e2e.spec.ts +++ b/electron/tests/my-models.e2e.spec.ts @@ -35,7 +35,7 @@ test.afterAll(async () => { }); test("shows my models", async () => { - await page.getByRole("button", { name: "My Models" }).first().click(); + await page.getByTestId("My Models").first().click(); const header = await page .getByRole("heading") .filter({ hasText: "My Models" }) diff --git a/electron/tests/navigation.e2e.spec.ts b/electron/tests/navigation.e2e.spec.ts index 0152551b7..554a7b4d7 100644 --- a/electron/tests/navigation.e2e.spec.ts +++ b/electron/tests/navigation.e2e.spec.ts @@ -44,31 +44,26 @@ test("renders left navigation panel", async () => { expect(chatSection).toBe(false); // Home actions - const newChatBtn = await page - .getByRole("button", { name: "New Chat" }) + const createBotBtn = await page + .getByRole("button", { name: "Create bot" }) .first() .isEnabled(); const exploreBtn = await page .getByRole("button", { name: "Explore Models" }) .first() .isEnabled(); - const discordBtn = await page - .getByRole("button", { name: "Discord" }) - .first() - .isEnabled(); const myModelsBtn = await page - .getByRole("button", { name: "My Models" }) + .getByTestId("My Models") .first() .isEnabled(); const settingsBtn = await page - .getByRole("button", { name: "Settings" }) + .getByTestId("Settings") .first() .isEnabled(); expect( [ - newChatBtn, + createBotBtn, exploreBtn, - discordBtn, myModelsBtn, settingsBtn, ].filter((e) => !e).length diff --git a/electron/tests/settings.e2e.spec.ts b/electron/tests/settings.e2e.spec.ts index 28c477a7e..dacefd380 100644 --- a/electron/tests/settings.e2e.spec.ts +++ b/electron/tests/settings.e2e.spec.ts @@ -35,7 +35,7 @@ test.afterAll(async () => { }); test("shows settings", async () => { - await page.getByRole("button", { name: "Settings" }).first().click(); + await page.getByTestId("Settings").first().click(); const pluginList = await page.getByTestId("plugin-item").count(); expect(pluginList).toBe(4); diff --git a/package.json b/package.json index 8dbe5f86c..4ae433b2c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dev:electron": "yarn workspace jan dev", "dev:web": "yarn workspace jan-web dev", "dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"", + "test-local": "yarn lint && yarn build && yarn test", "build:core": "cd core && yarn install && yarn run build", "build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"", "build:electron": "yarn workspace jan build", diff --git a/plugins/data-plugin/index.ts b/plugins/data-plugin/index.ts index cc3d11e17..f9716291a 100644 --- a/plugins/data-plugin/index.ts +++ b/plugins/data-plugin/index.ts @@ -1,4 +1,11 @@ -import { core, store, RegisterExtensionPoint, StoreService, DataService, PluginService } from "@janhq/core"; +import { + invokePluginFunc, + store, + RegisterExtensionPoint, + StoreService, + DataService, + PluginService, +} from "@janhq/core"; /** * Create a collection on data store @@ -8,8 +15,14 @@ import { core, store, RegisterExtensionPoint, StoreService, DataService, PluginS * @returns Promise * */ -function createCollection({ name, schema }: { name: string; schema?: { [key: string]: any } }): Promise { - return core.invokePluginFunc(MODULE_PATH, "createCollection", name, schema); +function createCollection({ + name, + schema, +}: { + name: string; + schema?: { [key: string]: any }; +}): Promise { + return invokePluginFunc(MODULE_PATH, "createCollection", name, schema); } /** @@ -20,7 +33,7 @@ function createCollection({ name, schema }: { name: string; schema?: { [key: str * */ function deleteCollection(name: string): Promise { - return core.invokePluginFunc(MODULE_PATH, "deleteCollection", name); + return invokePluginFunc(MODULE_PATH, "deleteCollection", name); } /** @@ -31,8 +44,14 @@ function deleteCollection(name: string): Promise { * @returns Promise * */ -function insertOne({ collectionName, value }: { collectionName: string; value: any }): Promise { - return core.invokePluginFunc(MODULE_PATH, "insertOne", collectionName, value); +function insertOne({ + collectionName, + value, +}: { + collectionName: string; + value: any; +}): Promise { + return invokePluginFunc(MODULE_PATH, "insertOne", collectionName, value); } /** @@ -44,8 +63,16 @@ function insertOne({ collectionName, value }: { collectionName: string; value: a * @returns Promise * */ -function updateOne({ collectionName, key, value }: { collectionName: string; key: string; value: any }): Promise { - return core.invokePluginFunc(MODULE_PATH, "updateOne", collectionName, key, value); +function updateOne({ + collectionName, + key, + value, +}: { + collectionName: string; + key: string; + value: any; +}): Promise { + return invokePluginFunc(MODULE_PATH, "updateOne", collectionName, key, value); } /** @@ -64,7 +91,13 @@ function updateMany({ value: any; selector?: { [key: string]: any }; }): Promise { - return core.invokePluginFunc(MODULE_PATH, "updateMany", collectionName, value, selector); + return invokePluginFunc( + MODULE_PATH, + "updateMany", + collectionName, + value, + selector + ); } /** @@ -75,8 +108,14 @@ function updateMany({ * @returns Promise * */ -function deleteOne({ collectionName, key }: { collectionName: string; key: string }): Promise { - return core.invokePluginFunc(MODULE_PATH, "deleteOne", collectionName, key); +function deleteOne({ + collectionName, + key, +}: { + collectionName: string; + key: string; +}): Promise { + return invokePluginFunc(MODULE_PATH, "deleteOne", collectionName, key); } /** @@ -94,7 +133,7 @@ function deleteMany({ collectionName: string; selector?: { [key: string]: any }; }): Promise { - return core.invokePluginFunc(MODULE_PATH, "deleteMany", collectionName, selector); + return invokePluginFunc(MODULE_PATH, "deleteMany", collectionName, selector); } /** @@ -103,8 +142,14 @@ function deleteMany({ * @param {string} key - The key of the record to retrieve. * @returns {Promise} A promise that resolves when the record is retrieved. */ -function findOne({ collectionName, key }: { collectionName: string; key: string }): Promise { - return core.invokePluginFunc(MODULE_PATH, "findOne", collectionName, key); +function findOne({ + collectionName, + key, +}: { + collectionName: string; + key: string; +}): Promise { + return invokePluginFunc(MODULE_PATH, "findOne", collectionName, key); } /** @@ -123,19 +168,28 @@ function findMany({ selector: { [key: string]: any }; sort?: [{ [key: string]: any }]; }): Promise { - return core.invokePluginFunc(MODULE_PATH, "findMany", collectionName, selector, sort); + return invokePluginFunc( + MODULE_PATH, + "findMany", + collectionName, + selector, + sort + ); } function onStart() { createCollection({ name: "conversations", schema: {} }); createCollection({ name: "messages", schema: {} }); + createCollection({ name: "bots", schema: {} }); } // Register all the above functions and objects with the relevant extension points +// prettier-ignore export function init({ register }: { register: RegisterExtensionPoint }) { register(PluginService.OnStart, PLUGIN_NAME, onStart); register(StoreService.CreateCollection, createCollection.name, createCollection); register(StoreService.DeleteCollection, deleteCollection.name, deleteCollection); + register(StoreService.InsertOne, insertOne.name, insertOne); register(StoreService.UpdateOne, updateOne.name, updateOne); register(StoreService.UpdateMany, updateMany.name, updateMany); @@ -144,19 +198,34 @@ export function init({ register }: { register: RegisterExtensionPoint }) { register(StoreService.FindOne, findOne.name, findOne); register(StoreService.FindMany, findMany.name, findMany); + // for conversations management register(DataService.GetConversations, getConversations.name, getConversations); + register(DataService.GetConversationById,getConversationById.name,getConversationById); register(DataService.CreateConversation, createConversation.name, createConversation); register(DataService.UpdateConversation, updateConversation.name, updateConversation); - register(DataService.UpdateMessage, updateMessage.name, updateMessage); register(DataService.DeleteConversation, deleteConversation.name, deleteConversation); + + // for messages management + register(DataService.UpdateMessage, updateMessage.name, updateMessage); register(DataService.CreateMessage, createMessage.name, createMessage); register(DataService.GetConversationMessages, getConversationMessages.name, getConversationMessages); + + // for bots management + register(DataService.CreateBot, createBot.name, createBot); + register(DataService.GetBots, getBots.name, getBots); + register(DataService.GetBotById, getBotById.name, getBotById); + register(DataService.DeleteBot, deleteBot.name, deleteBot); + register(DataService.UpdateBot, updateBot.name, updateBot); } function getConversations(): Promise { return store.findMany("conversations", {}, [{ updatedAt: "desc" }]); } +function getConversationById(id: string): Promise { + return store.findOne("conversations", id); +} + function createConversation(conversation: any): Promise { return store.insertOne("conversations", conversation); } @@ -174,9 +243,83 @@ function updateMessage(message: any): Promise { } function deleteConversation(id: any) { - return store.deleteOne("conversations", id).then(() => store.deleteMany("messages", { conversationId: id })); + return store + .deleteOne("conversations", id) + .then(() => store.deleteMany("messages", { conversationId: id })); } function getConversationMessages(conversationId: any) { - return store.findMany("messages", { conversationId }, [{ createdAt: "desc" }]); + return store.findMany("messages", { conversationId }, [ + { createdAt: "desc" }, + ]); +} + +function createBot(bot: any): Promise { + console.debug("Creating bot", JSON.stringify(bot, null, 2)); + return store + .insertOne("bots", bot) + .then(() => { + console.debug("Bot created", JSON.stringify(bot, null, 2)); + return Promise.resolve(); + }) + .catch((err) => { + console.error("Error creating bot", err); + return Promise.reject(err); + }); +} + +function getBots(): Promise { + console.debug("Getting bots"); + return store + .findMany("bots", { name: { $gt: null } }) + .then((bots) => { + console.debug("Bots retrieved", JSON.stringify(bots, null, 2)); + return Promise.resolve(bots); + }) + .catch((err) => { + console.error("Error getting bots", err); + return Promise.reject(err); + }); +} + +function deleteBot(id: string): Promise { + console.debug("Deleting bot", id); + return store + .deleteOne("bots", id) + .then(() => { + console.debug("Bot deleted", id); + return Promise.resolve(); + }) + .catch((err) => { + console.error("Error deleting bot", err); + return Promise.reject(err); + }); +} + +function updateBot(bot: any): Promise { + console.debug("Updating bot", JSON.stringify(bot, null, 2)); + return store + .updateOne("bots", bot._id, bot) + .then(() => { + console.debug("Bot updated"); + return Promise.resolve(); + }) + .catch((err) => { + console.error("Error updating bot", err); + return Promise.reject(err); + }); +} + +function getBotById(botId: string): Promise { + console.debug("Getting bot", botId); + return store + .findOne("bots", botId) + .then((bot) => { + console.debug("Bot retrieved", JSON.stringify(bot, null, 2)); + return Promise.resolve(bot); + }) + .catch((err) => { + console.error("Error getting bot", err); + return Promise.reject(err); + }); } diff --git a/plugins/data-plugin/package.json b/plugins/data-plugin/package.json index d41614a4a..96bc77423 100644 --- a/plugins/data-plugin/package.json +++ b/plugins/data-plugin/package.json @@ -12,7 +12,7 @@ ], "scripts": { "build": "tsc -b ./config/tsconfig.esm.json && tsc -b ./config/tsconfig.cjs.json && webpack --config webpack.config.js", - "postinstall": "rimraf ./data-plugin*.tgz && npm run build", + "postinstall": "rimraf *.tgz --glob && npm run build", "build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install" }, "exports": { @@ -40,8 +40,12 @@ "node_modules" ], "dependencies": { - "@janhq/core": "^0.1.3", + "@janhq/core": "^0.1.6", "pouchdb-find": "^8.0.1", "pouchdb-node": "^8.0.1" - } + }, + "bundleDependencies": [ + "pouchdb-node", + "pouchdb-find" + ] } diff --git a/plugins/inference-plugin/index.ts b/plugins/inference-plugin/index.ts index e86511404..d8f73a218 100644 --- a/plugins/inference-plugin/index.ts +++ b/plugins/inference-plugin/index.ts @@ -15,8 +15,18 @@ const stopModel = () => { invokePluginFunc(MODULE_PATH, "killSubprocess"); }; -function requestInference(recentMessages: any[]): Observable { +function requestInference(recentMessages: any[], bot?: any): Observable { return new Observable((subscriber) => { + const requestBody = JSON.stringify({ + messages: recentMessages, + stream: true, + model: "gpt-3.5-turbo", + max_tokens: bot?.maxTokens ?? 2048, + frequency_penalty: bot?.frequencyPenalty ?? 0, + presence_penalty: bot?.presencePenalty ?? 0, + temperature: bot?.customTemperature ?? 0, + }); + console.debug(`Request body: ${requestBody}`); fetch(INFERENCE_URL, { method: "POST", headers: { @@ -24,12 +34,7 @@ function requestInference(recentMessages: any[]): Observable { Accept: "text/event-stream", "Access-Control-Allow-Origin": "*", }, - body: JSON.stringify({ - messages: recentMessages, - stream: true, - model: "gpt-3.5-turbo", - max_tokens: 500, - }), + body: requestBody, }) .then(async (response) => { const stream = response.body; @@ -62,22 +67,39 @@ function requestInference(recentMessages: any[]): Observable { }); } -async function retrieveLastTenMessages(conversationId: string) { +async function retrieveLastTenMessages(conversationId: string, bot?: any) { // TODO: Common collections should be able to access via core functions instead of store const messageHistory = (await store.findMany("messages", { conversationId }, [{ createdAt: "asc" }])) ?? []; - return messageHistory + + let 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", - }; - }); + .slice(-9) + .map((message) => ({ + content: message.message.trim(), + role: message.user === "user" ? "user" : "assistant", + })); + + if (bot && bot.systemPrompt) { + // append bot's system prompt + recentMessages = [{ + content: `[INST] ${bot.systemPrompt}`, + role: 'system' + },...recentMessages]; + } + + console.debug(`Last 10 messages: ${JSON.stringify(recentMessages, null, 2)}`); + + return recentMessages; } async function handleMessageRequest(data: NewMessageRequest) { - const recentMessages = await retrieveLastTenMessages(data.conversationId); + const conversation = await store.findOne("conversations", data.conversationId); + let bot = undefined; + if (conversation.botId != null) { + bot = await store.findOne("bots", conversation.botId); + } + + const recentMessages = await retrieveLastTenMessages(data.conversationId, bot); const message = { ...data, message: "", @@ -91,7 +113,7 @@ async function handleMessageRequest(data: NewMessageRequest) { message._id = id; events.emit(EventName.OnNewMessageResponse, message); - requestInference(recentMessages).subscribe({ + requestInference(recentMessages, bot).subscribe({ next: (content) => { message.message = content; events.emit(EventName.OnMessageResponseUpdate, message); diff --git a/plugins/inference-plugin/package.json b/plugins/inference-plugin/package.json index 7a88aa185..2dacb6eac 100644 --- a/plugins/inference-plugin/package.json +++ b/plugins/inference-plugin/package.json @@ -12,7 +12,7 @@ ], "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "postinstall": "rimraf ./*.tgz && npm run build && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"", + "postinstall": "rimraf *.tgz --glob && npm run build && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"", "build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install" }, "exports": { @@ -26,7 +26,7 @@ "webpack-cli": "^5.1.4" }, "dependencies": { - "@janhq/core": "^0.1.3", + "@janhq/core": "^0.1.6", "kill-port-process": "^3.2.0", "rxjs": "^7.8.1", "tcp-port-used": "^1.0.2", diff --git a/plugins/model-management-plugin/package.json b/plugins/model-management-plugin/package.json index 7d21e6d08..bc4b81d20 100644 --- a/plugins/model-management-plugin/package.json +++ b/plugins/model-management-plugin/package.json @@ -12,7 +12,7 @@ ], "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "postinstall": "rimraf ./*.tgz && npm run build", + "postinstall": "rimraf *.tgz --glob && npm run build", "build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install" }, "devDependencies": { @@ -27,7 +27,7 @@ "README.md" ], "dependencies": { - "@janhq/core": "^0.1.3", + "@janhq/core": "^0.1.6", "ts-loader": "^9.5.0" } } diff --git a/plugins/monitoring-plugin/package.json b/plugins/monitoring-plugin/package.json index 79cf15743..45efb846b 100644 --- a/plugins/monitoring-plugin/package.json +++ b/plugins/monitoring-plugin/package.json @@ -12,7 +12,7 @@ ], "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "postinstall": "rimraf ./*.tgz && npm run build", + "postinstall": "rimraf *.tgz --glob && npm run build", "build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install" }, "devDependencies": { @@ -21,7 +21,7 @@ "webpack-cli": "^5.1.4" }, "dependencies": { - "@janhq/core": "^0.1.3", + "@janhq/core": "^0.1.6", "systeminformation": "^5.21.8", "ts-loader": "^9.5.0" }, diff --git a/plugins/openai-plugin/package.json b/plugins/openai-plugin/package.json index 7fb71112a..a0bbc188a 100644 --- a/plugins/openai-plugin/package.json +++ b/plugins/openai-plugin/package.json @@ -12,7 +12,7 @@ ], "scripts": { "build": "tsc -b . && webpack --config webpack.config.js", - "postinstall": "rimraf ./*.tgz && npm run build && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"", + "postinstall": "rimraf *.tgz --glob && npm run build && rimraf dist/nitro/* && cpx \"nitro/**\" \"dist/nitro\"", "build:publish": "npm pack && cpx *.tgz ../../electron/core/pre-install" }, "exports": { @@ -26,7 +26,7 @@ "webpack-cli": "^5.1.4" }, "dependencies": { - "@janhq/core": "^0.1.3", + "@janhq/core": "^0.1.6", "azure-openai": "^0.9.4", "kill-port-process": "^3.2.0", "tcp-port-used": "^1.0.2", diff --git a/web/app/_components/Avatar/index.tsx b/web/app/_components/Avatar/index.tsx new file mode 100644 index 000000000..4b7259e3e --- /dev/null +++ b/web/app/_components/Avatar/index.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import SecondaryButton from '../SecondaryButton' + +type Props = { + allowEdit?: boolean +} + +const Avatar: React.FC = ({ allowEdit = false }) => ( +
+ + + + + + {allowEdit ?? } +
+) + +export default Avatar diff --git a/web/app/_components/BotInfo/index.tsx b/web/app/_components/BotInfo/index.tsx new file mode 100644 index 000000000..9085536c0 --- /dev/null +++ b/web/app/_components/BotInfo/index.tsx @@ -0,0 +1,56 @@ +import { activeBotAtom } from "@/_helpers/atoms/Bot.atom"; +import { + MainViewState, + setMainViewStateAtom, +} from "@/_helpers/atoms/MainView.atom"; +import useCreateConversation from "@/_hooks/useCreateConversation"; +import useDeleteBot from "@/_hooks/useDeleteBot"; +import { useAtomValue, useSetAtom } from "jotai"; +import React from "react"; +import PrimaryButton from "../PrimaryButton"; +import ExpandableHeader from "../ExpandableHeader"; + +const BotInfo: React.FC = () => { + const { deleteBot } = useDeleteBot(); + const { createConvoByBot } = useCreateConversation(); + const setMainView = useSetAtom(setMainViewStateAtom); + const botInfo = useAtomValue(activeBotAtom); + if (!botInfo) return null; + + const onNewChatClicked = () => { + if (!botInfo) { + alert("No bot selected"); + return; + } + + createConvoByBot(botInfo); + }; + + const onDeleteBotClick = async () => { + // TODO: display confirmation diaglog + const result = await deleteBot(botInfo._id); + if (result === "success") { + setMainView(MainViewState.Welcome); + } + }; + + return ( +
+ {}} /> + +
+ + + {botInfo.description} +
+ + +
+ ); +}; + +export default BotInfo; diff --git a/web/app/_components/BotInfoContainer/index.tsx b/web/app/_components/BotInfoContainer/index.tsx new file mode 100644 index 000000000..df8e05e2c --- /dev/null +++ b/web/app/_components/BotInfoContainer/index.tsx @@ -0,0 +1,71 @@ +import { activeBotAtom } from '@/_helpers/atoms/Bot.atom' +import { useAtomValue, useSetAtom } from 'jotai' +import React from 'react' +import Avatar from '../Avatar' +import PrimaryButton from '../PrimaryButton' +import useCreateConversation from '@/_hooks/useCreateConversation' +import useDeleteBot from '@/_hooks/useDeleteBot' +import { + setMainViewStateAtom, + MainViewState, +} from '@/_helpers/atoms/MainView.atom' + +const BotInfoContainer: React.FC = () => { + const activeBot = useAtomValue(activeBotAtom) + const setMainView = useSetAtom(setMainViewStateAtom) + const { deleteBot } = useDeleteBot() + const { createConvoByBot } = useCreateConversation() + + const onNewChatClicked = () => { + if (!activeBot) { + alert('No bot selected') + return + } + + createConvoByBot(activeBot) + } + + const onDeleteBotClick = async () => { + if (!activeBot) { + alert('No bot selected') + return + } + + // TODO: display confirmation diaglog + const result = await deleteBot(activeBot._id) + if (result === 'success') { + setMainView(MainViewState.Welcome) + } + } + + if (!activeBot) return null + + return ( +
+
+ +

+ {activeBot?.name} +

+
+ + +
+

{activeBot?.description}

+

System prompt

+

{activeBot?.systemPrompt}

+
+
+ ) +} + +export default BotInfoContainer diff --git a/web/app/_components/BotListContainer/index.tsx b/web/app/_components/BotListContainer/index.tsx new file mode 100644 index 000000000..93a96f06b --- /dev/null +++ b/web/app/_components/BotListContainer/index.tsx @@ -0,0 +1,59 @@ +import { activeBotAtom } from '@/_helpers/atoms/Bot.atom' +import { showingBotListModalAtom } from '@/_helpers/atoms/Modal.atom' +import useGetBots from '@/_hooks/useGetBots' +import { Bot } from '@/_models/Bot' +import { useAtom, useSetAtom } from 'jotai' +import React, { useEffect, useState } from 'react' +import Avatar from '../Avatar' +import { + MainViewState, + setMainViewStateAtom, +} from '@/_helpers/atoms/MainView.atom' + +const BotListContainer: React.FC = () => { + const [open, setOpen] = useAtom(showingBotListModalAtom) + const setMainView = useSetAtom(setMainViewStateAtom) + const [activeBot, setActiveBot] = useAtom(activeBotAtom) + const [bots, setBots] = useState([]) + const { getAllBots } = useGetBots() + + useEffect(() => { + if (open) { + getAllBots().then((res) => { + setBots(res) + }) + } + }, [open]) + + const onBotSelected = (bot: Bot) => { + if (bot._id !== activeBot?._id) { + setMainView(MainViewState.BotInfo) + setActiveBot(bot) + } + setOpen(false) + } + + return ( +
+
    + {bots.map((bot) => ( +
  • onBotSelected(bot)} + > + + +
    +

    {bot.name}

    +

    {bot._id}

    +
    +
  • + ))} +
+
+ ) +} + +export default BotListContainer diff --git a/web/app/_components/BotListModal/index.tsx b/web/app/_components/BotListModal/index.tsx new file mode 100644 index 000000000..213986fd9 --- /dev/null +++ b/web/app/_components/BotListModal/index.tsx @@ -0,0 +1,48 @@ +import { showingBotListModalAtom } from '@/_helpers/atoms/Modal.atom' +import { Dialog, Transition } from '@headlessui/react' +import { useAtom } from 'jotai' +import React, { Fragment } from 'react' +import BotListContainer from '../BotListContainer' + +const BotListModal: React.FC = () => { + const [open, setOpen] = useAtom(showingBotListModalAtom) + + return ( + + + +
+ + +
+
+ + +

Your bots

+ +
+
+
+
+
+
+ ) +} + +export default BotListModal diff --git a/web/app/_components/BotPreview/index.tsx b/web/app/_components/BotPreview/index.tsx new file mode 100644 index 000000000..00695724d --- /dev/null +++ b/web/app/_components/BotPreview/index.tsx @@ -0,0 +1,26 @@ +import Image from "next/image"; + +const BotPreview: React.FC = () => { + return ( +
+
+ +
+
+
+ Context cleared +
+
+
+ ); +}; + +export default BotPreview; diff --git a/web/app/_components/BotSetting/index.tsx b/web/app/_components/BotSetting/index.tsx new file mode 100644 index 000000000..faf364a9e --- /dev/null +++ b/web/app/_components/BotSetting/index.tsx @@ -0,0 +1,133 @@ +import { activeBotAtom } from '@/_helpers/atoms/Bot.atom' +import { useAtomValue } from 'jotai' +import React, { useEffect, useState } from 'react' +import ExpandableHeader from '../ExpandableHeader' +import { useDebouncedCallback } from 'use-debounce' +import useUpdateBot from '@/_hooks/useUpdateBot' +import ProgressSetting from '../ProgressSetting' +import { set } from 'react-hook-form' + +const delayBeforeUpdateInMs = 1000 + +const BotSetting: React.FC = () => { + const activeBot = useAtomValue(activeBotAtom) + const [temperature, setTemperature] = useState(0) + const [maxTokens, setMaxTokens] = useState(0) + const [frequencyPenalty, setFrequencyPenalty] = useState(0) + const [presencePenalty, setPresencePenalty] = useState(0) + + useEffect(() => { + if (!activeBot) return + setMaxTokens(activeBot.maxTokens ?? 0) + setTemperature(activeBot.customTemperature ?? 0) + setFrequencyPenalty(activeBot.frequencyPenalty ?? 0) + setPresencePenalty(activeBot.presencePenalty ?? 0) + }, [activeBot?._id]) + + const { updateBot } = useUpdateBot() + + const debouncedTemperature = useDebouncedCallback((value) => { + if (!activeBot) return + if (activeBot.customTemperature === value) return + updateBot(activeBot, { customTemperature: value }) + }, delayBeforeUpdateInMs) + + const debouncedMaxToken = useDebouncedCallback((value) => { + if (!activeBot) return + if (activeBot.maxTokens === value) return + updateBot(activeBot, { maxTokens: value }) + }, delayBeforeUpdateInMs) + + const debouncedFreqPenalty = useDebouncedCallback((value) => { + if (!activeBot) return + if (activeBot.frequencyPenalty === value) return + updateBot(activeBot, { frequencyPenalty: value }) + }, delayBeforeUpdateInMs) + + const debouncedPresencePenalty = useDebouncedCallback((value) => { + if (!activeBot) return + if (activeBot.presencePenalty === value) return + updateBot(activeBot, { presencePenalty: value }) + }, delayBeforeUpdateInMs) + + const debouncedSystemPrompt = useDebouncedCallback((value) => { + if (!activeBot) return + if (activeBot.systemPrompt === value) return + updateBot(activeBot, { systemPrompt: value }) + }, delayBeforeUpdateInMs) + + if (!activeBot) return null + + return ( +
+ {}} + /> + +
+ {/* System prompt */} +
+ +
+