openkbs 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Docs.md CHANGED
@@ -516,6 +516,7 @@ The `contentRender.js` file is the heart of frontend customization. It can expor
516
516
  - `sendButtonRef`: A reference to the send button element.
517
517
  - `sendButtonRippleRef`: A reference to the send button ripple effect.
518
518
  - `setInputValue`: A function to set the value of the input field.
519
+ - `isStreaming`: Returns true if LLM is still generating chat tokens
519
520
  - `renderSettings`: An object containing rendering settings.
520
521
  - `axios`: The axios library for making HTTP requests.
521
522
  - `itemsAPI`: Functions for manipulating KB items.
package/README.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # OpenKBS · [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/open-kbs/openkbs-chat/blob/main/LICENSE) [![npm version](https://img.shields.io/badge/npm-v0.0.20-orange.svg)](https://www.npmjs.com/package/openkbs)
2
2
 
3
- OpenKBS is an extendable open-source platform designed to build, deploy and integrate AI agents anywhere, from websites to IoT devices. Its event-driven architecture enables full customization of backend and frontend components, while the LLM abstraction layer allows seamless switching between language models.
3
+ OpenKBS is an extendable open-source platform designed to build,
4
+ deploy and integrate AI agents anywhere, from websites to IoT devices.
5
+ Its event-driven architecture enables full customization of backend and
6
+ frontend components, while the LLM abstraction layer allows seamless
7
+ switching between language models. With its powerful CLI, OpenKBS turns
8
+ complex tasks into simple prompt commands, letting developers focus on what matters.
4
9
 
5
10
  ## Table of Contents
6
11
 
@@ -8,11 +13,27 @@ OpenKBS is an extendable open-source platform designed to build, deploy and inte
8
13
  - [Create App](#create-app)
9
14
  - [Deploy](#deploy)
10
15
  - [Extend Frontend](#extend-frontend)
16
+ - [Chat Render](#chat-render)
11
17
  - [Setup Local Development](#setup-local-development)
12
18
  - [Use Built-in MUI Components](#use-built-in-mui-components)
13
19
  - [AI-Powered Generation](#ai-powered-frontend-generation)
14
20
  - [Extend Backend](#extend-backend)
21
+ - [Mobile & Desktop App](#mobile--desktop-app)
15
22
  - [Framework Documentation](#framework-documentation)
23
+ - [Directory Structure](#directory-structure)
24
+ - [Backend](#backend)
25
+ - [onRequest and onResponse Handlers](#onrequest-and-onresponse-handlers)
26
+ - [onPublicAPIRequest Handler](#onpublicapirequest-handler)
27
+ - [onAddMessages Event Handler](#onaddmessages-event-handler)
28
+ - [Meta Actions](#meta-actions)
29
+ - [SDK](#sdk)
30
+ - [Application Settings](#application-settings)
31
+ - [LLM Instructions](#llm-instructions)
32
+ - [Execution Environment](#execution-environment)
33
+ - [Frontend](#frontend)
34
+ - [Frontend Module Loading](#frontend-module-loading)
35
+ - [Built-in UI Libraries](#built-in-ui-libraries)
36
+ - [Common Frontend Components and Utilities](#common-frontend-components-and-utilities)
16
37
  - [License](#license)
17
38
  - [Contributing](#contributing)
18
39
  - [Contact](#contact)
@@ -33,6 +54,8 @@ Create a new application using the OpenKBS CLI:
33
54
  openkbs create my-agent
34
55
 
35
56
  cd my-agent
57
+
58
+ git init && git stage . && git commit -m "First commit"
36
59
  ```
37
60
 
38
61
  ## Deploy
@@ -55,8 +78,10 @@ cd my-agent
55
78
 
56
79
  ## Extend Frontend
57
80
 
58
- To improve your application's user interface, you can use libraries like `react-markdown` for example.
81
+ Let's enhance your application with additional libraries and features.
82
+ For example, to properly render chat messages with Markdown, you can integrate `react-markdown`:
59
83
 
84
+ ### Chat Render
60
85
  1. Add `react-markdown` to your dependencies:
61
86
 
62
87
  ```bash
@@ -138,6 +163,823 @@ Enhance your UI with Material-UI components:
138
163
  openkbs push
139
164
  ```
140
165
 
166
+ ### AI-Powered Frontend Generation
167
+
168
+ OpenKBS provides simple AI-powered code generation. Use the `openkbs modify` command followed by your requirement:
169
+
170
+ ```bash
171
+ openkbs modify "Implementing UI to manage renderSettings"
172
+ ```
173
+
174
+ If you need to revert changes:
175
+ ```bash
176
+ git checkout -- .
177
+ ```
178
+
179
+ ## Extend Backend
180
+
181
+ Extend backend functionality using `openkbs modify` followed by your requirements. Add file paths to scope AI changes to specific files:
182
+
183
+ ```bash
184
+ openkbs modify "Implement getContent backend tool that returns text or JSON from a given URL" src/Events/actions.js app/instructions.txt
185
+ openkbs push
186
+ ```
187
+
188
+ This adds a new backend tool in `actions.js` that:
189
+ - Fetches content from URLs
190
+ - Handles JSON and HTML responses
191
+ - Auto-registers in `instructions.txt` (enabling LLM to understand and use it)
192
+ - Available to users and LLM through chat
193
+
194
+ Example usage in chat:
195
+ ```
196
+ /getContent("https://api.example.com/data")
197
+ ```
198
+
199
+ ## Mobile & Desktop App
200
+
201
+ Turn any OpenKBS app into a mobile or desktop app:
202
+
203
+ ### On Mobile (Android/iOS)
204
+ 1. Open your app URL in browser
205
+ 2. Tap browser menu (⋮)
206
+ 3. Select "Add To Home Screen"
207
+
208
+ ### On Desktop
209
+ 1. Click install icon (➕) in address bar
210
+ 2. Select "Install"
211
+
212
+ > 💡 Your app will be available on the home screen with full-screen experience!
213
+
214
+
215
+ ## Framework Documentation
216
+
217
+ ### Directory Structure
218
+
219
+ ```
220
+ src/
221
+ ├── Events/
222
+ │ ├── actions.js // Common actions for onRequest and onResponse
223
+ │ ├── onRequest.js // Handles incoming user messages
224
+ │ ├── onResponse.js // Handles outgoing LLM messages
225
+ │ ├── onPublicAPIRequest.js // Handles public API requests
226
+ │ ├── onAddMessages.js // Handles messages added to the chat (NEW)
227
+ │ ├── onRequest.json // Dependencies for onRequest handler
228
+ │ ├── onResponse.json // Dependencies for onResponse handler
229
+ │ ├── onPublicAPIRequest.json // Dependencies for onPublicAPIRequest handler
230
+ │ └── onAddMessages.json // Dependencies for onAddMessages handler (NEW)
231
+ │── Frontend/
232
+ │ ├── contentRender.js // Custom rendering logic for chat messages
233
+ │ └── contentRender.json // Dependencies for the contentRender module
234
+
235
+ app/
236
+ ├── icon.png // Application icon
237
+ ├── settings.json // Application settings
238
+ └── instructions.txt // LLM instructions
239
+ ```
240
+
241
+ ### Backend
242
+ The OpenKBS backend framework provides a powerful system for developing AI agents with custom tools and functionalities. It leverages a Node.js environment and offers hooks into the chat service through `onRequest` and `onResponse` event handlers. These handlers allow developers to process user input and LLM output, respectively, enabling the execution of custom actions and integration with external services.
243
+
244
+ #### onRequest and onResponse Handlers
245
+
246
+ The core of the OpenKBS backend framework revolves around the `onRequest` and `onResponse` event handlers. These handlers act as middleware, intercepting messages before and after they are processed by the LLM.
247
+
248
+ * **`onRequest` Handler:** This handler is invoked every time a user sends a message to the chat. It provides an opportunity to pre-process the user's input, extract commands and perform actions based on the user's message.
249
+
250
+ * **`onResponse` Handler:** This handler is invoked after the LLM generates a response. It allows post-processing of the LLM's output, execution of commands based on the LLM's intentions.
251
+
252
+
253
+ **Example `onRequest` and `onResponse` Handlers Structure (using common actions):**
254
+
255
+ ```javascript
256
+ // src/Events/actions.js
257
+ export const getActions = (meta) => {
258
+ return [
259
+ // Define your regular expressions and corresponding actions here
260
+ [/\/?yourCommand\("(.*)"\)/, async (match, event) => {
261
+ // Access match groups, event payload, and openkbs object
262
+ // Execute custom logic, API calls, etc.
263
+ // Return an object with action results and meta information
264
+ return { result: 'Your command executed', ...meta };
265
+ }],
266
+ // ... more actions
267
+ ];
268
+ };
269
+
270
+ // src/Events/onRequest.js
271
+ import {getActions} from './actions.js';
272
+ export const handler = async (event) => {
273
+ const actions = getActions({ _meta_actions: [] }); // Initialize meta actions if needed
274
+ for (let [regex, action] of actions) {
275
+ const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
276
+ const match = lastMessage?.match(regex);
277
+ if (match) return await action(match, event); // Execute matching action
278
+ }
279
+ return { type: 'CONTINUE' }; // Continue to the next handler or LLM
280
+ };
281
+
282
+
283
+ // src/Events/onResponse.js
284
+ import {getActions} from './actions.js';
285
+
286
+ export const handler = async (event) => {
287
+ // Example of conditional meta actions based on message count:
288
+ const maxSelfInvokeMessagesCount = 30;
289
+ const actions = getActions({
290
+ _meta_actions: event?.payload?.messages?.length > maxSelfInvokeMessagesCount
291
+ ? ["REQUEST_CHAT_MODEL_EXCEEDED"]
292
+ : ["REQUEST_CHAT_MODEL"]
293
+ });
294
+
295
+ for (let [regex, action] of actions) {
296
+ const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
297
+ const match = lastMessage?.match(regex);
298
+ if (match) return await action(match, event);
299
+ }
300
+
301
+ return { type: 'CONTINUE' }
302
+ };
303
+
304
+ ```
305
+
306
+ The `onRequest` and `onResponse` handlers are the core of customizing your OpenKBS agent's behavior. They act as middleware, intercepting messages before they reach the LLM (`onRequest`) and after the LLM generates a response (`onResponse`). This enables you to implement custom logic, interact with external APIs, and control the flow of the conversation.
307
+
308
+ **Example:**
309
+
310
+ ```javascript
311
+ // src/Events/actions.js
312
+ export const getActions = (meta) => [
313
+
314
+ [/\/?textToImage\("(.*)"\)/, async (match) => {
315
+ const response = await openkbs.textToImage(match[1], { serviceId: 'stability.sd3Medium' });
316
+ const imageSrc = `data:${response.ContentType};base64,${response.base64Data}`;
317
+ return { type: 'SAVED_CHAT_IMAGE', imageSrc, ...meta };
318
+ }],
319
+
320
+ [/\/?googleSearch\("(.*)"\)/, async (match) => {
321
+ const q = match[1];
322
+ const searchParams = match[2] && JSON.parse(match[2]) || {};
323
+ const params = {
324
+ q,
325
+ ...searchParams,
326
+ key: '{{secrets.googlesearch_api_key}}',
327
+ cx: '{{secrets.googlesearch_engine_id}}'
328
+ };
329
+ const response = (await axios.get('https://www.googleapis.com/customsearch/v1', { params }))?.data?.items;
330
+ const data = response?.map(({ title, link, snippet, pagemap }) => ({
331
+ title,
332
+ link,
333
+ snippet,
334
+ image: pagemap?.metatags?.[0]?.["og:image"]
335
+ }));
336
+ return { data, ...meta };
337
+ }],
338
+
339
+ [/\/?webpageToText\("(.*)"\)/, async (match) => {
340
+ let response = await openkbs.webpageToText(match[1]);
341
+ if (response?.content?.length > 5000) {
342
+ response.content = response.content.substring(0, 5000);
343
+ }
344
+ return { data: response, ...meta };
345
+ }],
346
+
347
+ [/\/?documentToText\("(.*)"\)/, async (match) => {
348
+ let response = await openkbs.documentToText(match[1]);
349
+ if (response?.text?.length > 5000) {
350
+ response.text = response.text.substring(0, 5000);
351
+ }
352
+ return { data: response, ...meta };
353
+ }],
354
+
355
+ [/\/?imageToText\("(.*)"\)/, async (match) => {
356
+ let response = await openkbs.imageToText(match[1]);
357
+ if (response?.detections?.[0]?.txt) {
358
+ response = { detections: response?.detections?.[0]?.txt };
359
+ }
360
+ return { data: response, ...meta };
361
+ }],
362
+
363
+ [/\/?textToSpeech\("(.*)"\s*,\s*"(.*)"\)/, async (match) => {
364
+ const response = await openkbs.textToSpeech(match[2], {
365
+ languageCode: match[1]
366
+ });
367
+ return { data: response, ...meta };
368
+ }],
369
+ ];
370
+ ```
371
+
372
+
373
+ ```javascript
374
+ // src/Events/onRequest.js
375
+ import {getActions} from './actions.js';
376
+
377
+
378
+ export const handler = async (event) => {
379
+ const actions = getActions({});
380
+ for (let [regex, action] of actions) {
381
+ const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
382
+ const match = lastMessage?.match(regex);
383
+ if (match) return await action(match);
384
+ }
385
+
386
+ return { type: 'CONTINUE' }
387
+ };
388
+ ```
389
+
390
+ ```javascript
391
+ // src/Events/onResponse.js
392
+ import {getActions} from './actions.js';
393
+
394
+ export const handler = async (event) => {
395
+ const actions = getActions({_meta_actions: ["REQUEST_CHAT_MODEL"]});
396
+
397
+ for (let [regex, action] of actions) {
398
+ const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
399
+ const match = lastMessage?.match(regex);
400
+ if (match) return await action(match);
401
+ }
402
+
403
+ return { type: 'CONTINUE' }
404
+ };
405
+ ```
406
+
407
+ #### onPublicAPIRequest Handler
408
+
409
+ The `onPublicAPIRequest` handler serves as a bridge between publicly accessible APIs and your OpenKBS application. This enables external systems, webhooks, or even client-side JavaScript to interact with your application's backend, particularly for storing data in the OpenKBS NoSQL Items service via `openkbs.items` (managed NoSQL service). This is achieved without requiring authentication for these specific API requests.
410
+
411
+ **How it works:**
412
+
413
+ 1. **Public API Endpoint:** The `onPublicAPIRequest` handler is associated with a dedicated public API endpoint (e.g., `/publicAPIRequest`)
414
+ 2. **Payload** could be any JSON object
415
+ 3. **Handler Logic:** Inside the `onPublicAPIRequest` handler, you receive this payload as an argument. Your code then processes the payload and performs the necessary actions using the OpenKBS SDK for example.
416
+ 4. **Data Storage:** The `openkbs.items` function is typically used within this handler to create, update, or delete items in the OpenKBS NoSQL Items service. You can use encryption for sensitive data within this handler.
417
+ 5. **Response:** The handler returns a response to the external system that initiated the request.
418
+
419
+ **Example `onPublicAPIRequest` Handler:**
420
+
421
+ ```javascript
422
+ // src/Events/onPublicAPIRequest.js
423
+ module.exports = {
424
+ handler: async ({ payload }) => {
425
+ const { item, attributes, itemType, action, kbId } = payload;
426
+
427
+ if (!kbId) return { error: "kbId is not provided" }
428
+
429
+ try {
430
+ const myItem = {};
431
+ for (const attribute of attributes) {
432
+ const { attrName, encrypted } = attribute;
433
+ if (encrypted && item[attrName] !== undefined) {
434
+ myItem[attrName] = await openkbs.encrypt(item[attrName]);
435
+ } else {
436
+ myItem[attrName] = item[attrName];
437
+ }
438
+ }
439
+
440
+ // Perform the action on the Items API
441
+ return await openkbs.items({ action, itemType, attributes, item: myItem, kbId });
442
+
443
+ } catch (error) {
444
+ console.error("Error in onPublicAPIRequest:", error);
445
+ return { error: error.message }; // Return error information
446
+ }
447
+ }
448
+ };
449
+
450
+ ```
451
+
452
+
453
+ **Example Client-Side JavaScript to Create an Item:**
454
+
455
+ ```javascript
456
+ // Example creating a "feedback" item
457
+ const createFeedback = async (kbId, name, text) => (
458
+ await fetch('https://chat.openkbs.com/publicAPIRequest', {
459
+ method: 'POST',
460
+ headers: { 'Content-Type': 'application/json' },
461
+ body: JSON.stringify({
462
+ action: "createItem",
463
+ kbId,
464
+ itemType: "feedback",
465
+ attributes: [
466
+ { attrType: "keyword1", attrName: "name", encrypted: true },
467
+ { attrType: "text1", attrName: "feedbackText", encrypted: false }
468
+ ],
469
+ item: { name, feedbackText: text }
470
+ })
471
+ })
472
+ ).json();
473
+ ```
474
+
475
+ Remember to carefully consider security implications and implement necessary precautions, as this is public API.
476
+
477
+ #### onAddMessages Event Handler
478
+
479
+ The `onAddMessages` handler allows you to intercept and process messages *as they are added to the chat*.
480
+ This handler is triggered *after* the `onRequest` handler but *before* the message is sent to the LLM.
481
+ It's particularly useful for scenarios where a third-party system or service sends messages directly to your OpenKBS app to perform an action.
482
+ Unlike `onPublicAPIRequest`, this handler requires an `apiKey`, which can be created in the 'Access' section of your OpenKBS app.
483
+
484
+ **Example: User moderation:**
485
+
486
+ **1. Third-Party Service API request:**
487
+
488
+ ```javascript
489
+ // Example of a third-party system sending a chat message to OpenKBS
490
+ axios.post('https://chat.openkbs.com/', {
491
+ action: "chatAddMessages",
492
+ chatId: 'NSFW_CHAT_ID', // the chat id created to log and process NSFW message
493
+ messages: [{
494
+ role: "system",
495
+ content: JSON.stringify({
496
+ labels: ['adult', 'explicit'],
497
+ fileName: 'image.jpg',
498
+ path: '/uploads/image.jpg'
499
+ }),
500
+ msgId: `${Date.now()}-000000`
501
+ }],
502
+ apiKey: "YOUR_API_KEY",
503
+ kbId: "YOUR_KB_ID"
504
+ }, {
505
+ headers: { 'Content-Type': 'application/json' }
506
+ });
507
+ ```
508
+
509
+ **2. `onAddMessages` Handler:**
510
+
511
+ ```javascript
512
+ // src/Events/onAddMessages.js
513
+ import * as actions from './actions.js';
514
+
515
+ export const handler = async (event) => {
516
+ const { messages, chatId } = event.payload;
517
+ let msgData;
518
+
519
+ // NSFW Chat Handler
520
+ if (chatId === 'NSFW_CHAT_ID') { // Check if the message is for the NSFW chat
521
+ try {
522
+ msgData = JSON.parse(messages[0].content); // Parse the message content (expecting JSON)
523
+ const { data } = await actions.getUser([null, msgData.kbId]); // Get user information
524
+ await actions.warnAccount([null, data.user.accountId, msgData?.labels]); // Issue a warning
525
+ await actions.deleteFile([null, msgData.path]); // Delete the offending file
526
+
527
+ // Return a system message confirming the action
528
+ return [
529
+ ...messages,
530
+ {
531
+ role: 'system',
532
+ msgId: Date.now() + '000000',
533
+ content: `### 👮‍♀️ System Actions:\nWarning issued and content removed`
534
+ }
535
+ ];
536
+ } catch (e) {
537
+ console.error("Error processing NSFW content:", e);
538
+ }
539
+ }
540
+
541
+ return messages; // Return messages unchanged if no action is taken
542
+ };
543
+
544
+ ```
545
+
546
+ **Dependencies (onRequest.json, onResponse.json, etc.):**
547
+
548
+ These files specify the NPM package dependencies required for the respective event handlers. They follow the standard `package.json` format.
549
+
550
+ ```json
551
+ // src/Events/*.json
552
+ {
553
+ "dependencies": {
554
+ "your-package": "^1.0.0"
555
+ }
556
+ }
557
+ ```
558
+
559
+
560
+ #### Meta Actions
561
+
562
+ Meta actions provide a way to control the flow of the conversation and instruct the OpenKBS platform to perform specific actions. These actions are typically triggered within the `onResponse` handler based on the LLM's output. Here are some key meta actions:
563
+
564
+ * **`REQUEST_CHAT_MODEL`:** This meta action instructs the platform to send the current conversation to the LLM for a response after the current event handler execution is completed. It's essential for continuing the conversation loop.
565
+
566
+ * **`SAVED_CHAT_IMAGE`:** This meta action indicates that the LLM generated or processed an image which should be saved in the chat history. It's used in conjunction with actions that process or generate images. Requires the `imageSrc` in the return object.
567
+
568
+
569
+
570
+ **Example Meta Action Usage:**
571
+
572
+ ```javascript
573
+ // src/Events/onResponse.js
574
+ // ... inside an action ...
575
+ if (actionMatch) {
576
+ return { data: 'YourActionResponse', ...meta, _meta_actions: ['REQUEST_CHAT_MODEL'] };
577
+ }
578
+ ```
579
+
580
+
581
+ #### SDK
582
+
583
+ The `openkbs` object provides a set of utility functions and services to interact with the OpenKBS platform and external APIs. It's available within the event handlers. Here are some commonly used functions:
584
+
585
+ * **`openkbs.textToImage(prompt, params)`:** Generates an image from a text prompt using a specified or default image generation service. Returns an object containing the image content type and base64 encoded data.
586
+
587
+ * **`openkbs.speechToText(audioURL, params)`:** Transcribes audio from a URL to text.
588
+
589
+ * **`openkbs.webpageToText(pageURL, params)`:** Extracts text content from a given webpage URL.
590
+
591
+ * **`openkbs.googleSearch(q, params)`:** Performs a Google search using the provided query and parameters.
592
+
593
+ * **`openkbs.documentToText(documentURL, params)`:** Extracts text from various document formats.
594
+
595
+ * **`openkbs.imageToText(imageUrl, params)`:** Extracts text from an image.
596
+
597
+ * **`openkbs.translate(text, to)`:** Translates text to the specified target language.
598
+
599
+ * **`openkbs.detectLanguage(text, params)`:** Detects the language of the provided text.
600
+
601
+ * **`openkbs.textToSpeech(text, params)`:** Converts text to speech. Returns `response.audioContent` which automatically plays in the chat interface.
602
+
603
+ * **`openkbs.encrypt(plaintext)`:** Encrypts data using the provided AES key.
604
+
605
+ * **`openkbs.decrypt(ciphertext)`:** Decrypts data encrypted with the provided AES key.
606
+
607
+ * **`openkbs.items(data)`:** Interacts with the Items API for creating, updating, and deleting items.
608
+
609
+ * **`openkbs.chats(data)`:** Interacts with the Chats API.
610
+
611
+ * **`openkbs.kb(data)`:** Interacts with the Knowledge Base API.
612
+
613
+ * **`openkbs.clientHeaders`:** Exposes client headers for accessing information like IP address, location, etc. (e.g., `openkbs.clientHeaders['x-forwarded-for']`).
614
+
615
+ **Example SDK Usage:**
616
+
617
+ ```javascript
618
+ // ... inside an action ...
619
+ const image = await openkbs.textToImage('a cat sitting on a mat');
620
+ // ... use image.base64Data and image.ContentType ...
621
+
622
+
623
+ //Encrypt submitted user data
624
+ const encryptedValue = await openkbs.encrypt(userData);
625
+
626
+ ```
627
+
628
+ #### Application Settings
629
+ `app/settings.json`
630
+
631
+ This file contains essential configuration settings for the AI agent.
632
+
633
+ ```json
634
+ {
635
+ "userId": "public",
636
+ "chatVendor": "your-vendor",
637
+ "kbDescription": "Description of your KB",
638
+ "kbTitle": "Title of your KB",
639
+ "model": "your-llm-model",
640
+ "inputTools": [
641
+ "speechToText"
642
+ ],
643
+ "embeddingModel": "your-embedding-model",
644
+ "embeddingDimension": 1536,
645
+ "searchEngine": "your-search-engine",
646
+ "itemTypes": { }
647
+ }
648
+ ```
649
+
650
+ #### LLM Instructions
651
+ `app/instructions.txt`
652
+ This file contains the instructions for the LLM, guiding its behavior and interaction with custom functionalities.
653
+ Clear and specific instructions ensure the LLM effectively utilizes provided actions and commands.
654
+
655
+ **Example Instructions:**
656
+
657
+ ```
658
+ You are an AI assistant.
659
+
660
+ You can execute the following commands:
661
+
662
+ /googleSearch("query")
663
+ Description: """
664
+ Get results from Google Search API.
665
+ """
666
+ $InputLabel = """Let me Search in Google!"""
667
+ $InputValue = """Search in google for the latest news"""
668
+
669
+ /someCommand("param")
670
+ ...
671
+
672
+ ```
673
+
674
+ Command definitions may include \$InputLabel and \$InputValue which are invisiable to the LLM:
675
+
676
+ `$InputLabel` - Text displayed as a selectable option in the chat interface.
677
+
678
+ `$InputValue` - Text automatically inserted in the chat input when \$InputLabel is selected.
679
+
680
+ These features provide quick command access and pre-populate inputs, enhancing user interaction.
681
+
682
+
683
+ #### Execution Environment
684
+
685
+ The OpenKBS backend provides a pre-configured execution environment for your event handlers, including a set of globally available objects and libraries. This eliminates the need to explicitly declare these as dependencies in your `onRequest.json` or `onResponse.json` files. These predefined resources facilitate various operations, from interacting with AWS services to manipulating data and making HTTP requests.
686
+
687
+ Here's a breakdown of the key objects and utilities available within the OpenKBS backend environment:
688
+
689
+ **Key Objects and Utilities:**
690
+
691
+ * **`openkbs`:** The OpenKBS SDK, documented previously, provides utility functions for interacting with the OpenKBS platform and various external services.
692
+
693
+ * **`AWS: AWS_SDK`:** The AWS SDK provides access to a wide range of AWS services directly within your event handlers. This allows integration with S3, DynamoDB, Lambda, and other AWS resources. Pre-configured and ready to use.
694
+
695
+ * **`axios`:** Powerful HTTP client for making requests to external APIs and services. Simplifies handling responses and errors compared to the built-in `https` module.
696
+
697
+ * **`cheerio`:** A fast and flexible HTML parser implemented on top of the `parse5` parser. Enables server-side DOM manipulation and data extraction from HTML content.
698
+
699
+ * **`Decimal`:** The Decimal.js library enables arbitrary-precision decimal arithmetic, avoiding floating-point inaccuracies common in JavaScript.
700
+
701
+ * **`crypto`:** Node.js crypto module for performing cryptographic operations like hashing, encryption, and decryption.
702
+
703
+ * **`jwt`:** The `jsonwebtoken` library provides functions for creating, signing, and verifying JSON Web Tokens (JWTs), essential for secure authentication and authorization.
704
+
705
+ * **`JSON5`:** A more permissive JSON parser that supports comments, trailing commas, single quotes, and other convenient features not found in standard JSON. Useful for parsing configuration files or user input.
706
+
707
+ ### Frontend
708
+
709
+ The OpenKBS frontend framework is built using React and provides a flexible and extensible platform for building custom chat interfaces. It allows developers to customize the appearance and behavior of the chat through a `contentRender` module, which can be dynamically loaded and used to extend the core platform.
710
+
711
+
712
+ #### Frontend Module Loading
713
+
714
+ The frontend framework dynamically loads the `contentRender.js` module. This module can export several functions and components to customize the chat interface. The framework uses a global variable called `window.contentRender` to access the functions exported by this module.
715
+
716
+ #### contentRender
717
+
718
+ The `contentRender.js` file is the heart of frontend customization. It can export several key functions:
719
+
720
+ - **`onRenderChatMessage(params)`:** This function is called every time a chat message is rendered. It receives an object with various parameters, including:
721
+ - `msgIndex`: The index of the message being rendered.
722
+ - `messages`: The entire array of chat messages.
723
+ - `setMessages`: A function to update the `messages` state.
724
+ - `iframeRef`: A reference to the iframe element.
725
+ - `KB`: The Knowledge Base object containing application settings.
726
+ - `chatContainerRef`: A reference to the chat container element.
727
+ - `RequestChatAPI`: A function to send a message to the chat API.
728
+ - `setSystemAlert`: A function to display system alerts.
729
+ - `setBlockingLoading`: A function to display a loading indicator.
730
+ - `blockingLoading`: A boolean indicating if the loading indicator is active.
731
+ - `sendButtonRef`: A reference to the send button element.
732
+ - `sendButtonRippleRef`: A reference to the send button ripple effect.
733
+ - `setInputValue`: A function to set the value of the input field.
734
+ - `renderSettings`: An object containing rendering settings.
735
+ - `axios`: The axios library for making HTTP requests.
736
+ - `itemsAPI`: Functions for manipulating KB items.
737
+ - `createEmbeddingItem`: Functions to create embeddings.
738
+ - `indexedDB`: IndexedDB wrapper to access data.
739
+ - `chatAPI`: API to access chat data.
740
+ - `generateMsgId`: Generates a unique message ID.
741
+ - `kbUserData`: Function to get KB user data.
742
+ - `executeNodejs`: Execute custom JavaScript code inside a VM.
743
+
744
+ This function should return a React component representing the rendered message. If not defined, the default rendering mechanism is used.
745
+
746
+ - **`Header(props)`:** This React component is rendered at the top of the chat interface. It receives the same `params` object as `onRenderChatMessage`. It can be used to add custom UI elements or controls to the chat header. If not defined, the standard OpenKBS chat header is displayed.
747
+
748
+ - **`onDeleteChatMessage(params)`:** This async function is triggered when a chat message is deleted. This function receives a `params` object similar to the `onRenderChatMessage` function but also includes `chatId`, `message` (the message being deleted), and can be used to perform cleanup actions related to custom rendered content. If not defined, a default delete message function is executed.
749
+
750
+ **Example `contentRender.js`:**
751
+
752
+ ```javascript
753
+ import React from 'react';
754
+
755
+ const onRenderChatMessage = async (params) => {
756
+ const { content, role } = params.messages[params.msgIndex];
757
+ if (role === 'assistant' && content.startsWith('```json')) {
758
+ try {
759
+ const jsonData = JSON.parse(content.replace('```json', '').replace('```', ''));
760
+ return <pre>{JSON.stringify(jsonData, null, 2)}</pre>;
761
+ } catch (e) {
762
+ console.error('Error parsing JSON:', e);
763
+ return null;
764
+ }
765
+ }
766
+ };
767
+
768
+ const Header = ({ setRenderSettings }) => {
769
+ // Custom header content
770
+ return (
771
+ <div>
772
+ <h1>Custom Chat Header</h1>
773
+ </div>
774
+ );
775
+ };
776
+
777
+ const onDeleteChatMessage = async (params) => {
778
+ // Perform cleanup or other actions on chat message delete
779
+ const { chatId, message, itemsAPI, KB, setBlockingLoading } = params;
780
+ // Perform action before the message is deleted
781
+ };
782
+
783
+ const exports = { onRenderChatMessage, Header, onDeleteChatMessage };
784
+ window.contentRender = exports;
785
+ export default exports;
786
+ ```
787
+
788
+ `contentRender.json` specifies the dependencies required for the `contentRender.js` module. It's structured like a standard `package.json` file.
789
+
790
+ ```json
791
+ {
792
+ "dependencies": {
793
+ "react": "^18.2.0 (fixed)",
794
+ "react-dom": "^18.2.0 (fixed)",
795
+ "@mui/material": "^5.16.1 (fixed)",
796
+ "@mui/icons-material": "^5.16.1 (fixed)",
797
+ "@emotion/react": "^11.10.6 (fixed)",
798
+ "@emotion/styled": "^11.10.6 (fixed)"
799
+ }
800
+ }
801
+ ```
802
+
803
+ #### Built-in UI Libraries
804
+
805
+ The dependencies marked as `(fixed)` are not installed as additional dependencies but are inherited from the base framework `openkbs-ui`. This ensures consistency across applications and reduces the need for redundant installations. These fixed dependencies include:
806
+
807
+ - **`react` and `react-dom`:** Core libraries for building user interfaces with React.
808
+ - **`@mui/material` and `@mui/icons-material`:** Material-UI components and icons for building modern, responsive UIs.
809
+ - **`@emotion/react` and `@emotion/styled`:** Libraries for writing CSS styles with JavaScript, used by Material-UI for styling components.
810
+
811
+ #### Common Frontend Components and Utilities
812
+
813
+ These components and utilities are accessible directly within your `onRenderChatMessage` function, streamlining your custom development process.
814
+
815
+ *msgIndex*
816
+ ```javascript
817
+ const onRenderChatMessage = async (params) => {
818
+ const { msgIndex, messages } = params;
819
+ console.log(`Rendering message at index: ${msgIndex}`);
820
+ const currentMessage = messages[msgIndex];
821
+ // Further processing...
822
+ };
823
+ ```
824
+
825
+ *messages*
826
+ ```javascript
827
+ const onRenderChatMessage = async (params) => {
828
+ const { messages } = params;
829
+ messages.forEach((message, index) => {
830
+ console.log(`Message ${index}: ${message.content}`);
831
+ });
832
+ // Further processing...
833
+ };
834
+ ```
835
+
836
+ *setMessages*
837
+ ```javascript
838
+ const onRenderChatMessage = async (params) => {
839
+ const { setMessages, messages } = params;
840
+ const newMessage = { content: "New message", role: "user" };
841
+ setMessages([...messages, newMessage]);
842
+ };
843
+ ```
844
+
845
+ *KB*
846
+ ```javascript
847
+ const onRenderChatMessage = async (params) => {
848
+ const { KB } = params;
849
+ console.log(`Knowledge Base ID: ${KB.kbId}`);
850
+ // Use KB settings...
851
+ };
852
+ ```
853
+
854
+ *chatContainerRef*
855
+ ```javascript
856
+ const onRenderChatMessage = async (params) => {
857
+ const { chatContainerRef } = params;
858
+ if (chatContainerRef.current) {
859
+ // ...
860
+ }
861
+ };
862
+ ```
863
+
864
+ *RequestChatAPI*
865
+ ```javascript
866
+ const onRenderChatMessage = async (params) => {
867
+ const { RequestChatAPI, messages } = params;
868
+ const newMessage = { role: "user", content: "Hello, world!" };
869
+ await RequestChatAPI([...messages, newMessage]);
870
+ };
871
+ ```
872
+
873
+ *setSystemAlert*
874
+ ```javascript
875
+ const onRenderChatMessage = async (params) => {
876
+ const { setSystemAlert } = params;
877
+ setSystemAlert({ msg: "This is a system alert", type: "info", duration: 3000 });
878
+ };
879
+ ```
880
+
881
+ *setBlockingLoading*
882
+ ```javascript
883
+ const onRenderChatMessage = async (params) => {
884
+ const { setBlockingLoading } = params;
885
+ setBlockingLoading(true);
886
+ // Perform some async operation...
887
+ setBlockingLoading(false);
888
+ };
889
+ ```
890
+
891
+ *blockingLoading*
892
+ ```javascript
893
+ const onRenderChatMessage = async (params) => {
894
+ const { blockingLoading } = params;
895
+ if (blockingLoading) {
896
+ console.log("Loading is currently active");
897
+ }
898
+ };
899
+ ```
900
+
901
+ *sendButtonRef*
902
+ ```javascript
903
+ const onRenderChatMessage = async (params) => {
904
+ const { sendButtonRef } = params;
905
+ if (sendButtonRef.current) {
906
+ sendButtonRef.current.disabled = true; // Disable the send button
907
+ }
908
+ };
909
+ ```
910
+
911
+ *sendButtonRippleRef*
912
+ ```javascript
913
+ const onRenderChatMessage = async (params) => {
914
+ const { sendButtonRippleRef } = params;
915
+ if (sendButtonRippleRef.current) {
916
+ sendButtonRippleRef.current.pulsate(); // Trigger ripple effect
917
+ }
918
+ };
919
+ ```
920
+
921
+ *setInputValue*
922
+ ```javascript
923
+ const onRenderChatMessage = async (params) => {
924
+ const { setInputValue } = params;
925
+ setInputValue("Pre-filled input value");
926
+ };
927
+ ```
928
+
929
+ *renderSettings*
930
+ ```javascript
931
+ const onRenderChatMessage = async (params) => {
932
+ const { renderSettings } = params;
933
+ console.log(`Current render settings: ${JSON.stringify(renderSettings)}`);
934
+ };
935
+ ```
936
+
937
+ *axios*
938
+ ```javascript
939
+ const onRenderChatMessage = async (params) => {
940
+ const { axios } = params;
941
+ const response = await axios.get("https://api.example.com/data");
942
+ console.log(response.data);
943
+ };
944
+ ```
945
+
946
+ *itemsAPI*
947
+ ```javascript
948
+ const onRenderChatMessage = async (params) => {
949
+ const { itemsAPI } = params;
950
+ const item = await itemsAPI.getItem("itemId");
951
+ console.log(`Fetched item: ${JSON.stringify(item)}`);
952
+ };
953
+ ```
954
+
955
+ *indexedDB*
956
+ ```javascript
957
+ const onRenderChatMessage = async (params) => {
958
+ const { indexedDB } = params;
959
+ const items = await indexedDB.db["items"].toArray();
960
+ console.log(`IndexedDB items: ${JSON.stringify(items)}`);
961
+ };
962
+ ```
963
+
964
+ *generateMsgId*
965
+ ```javascript
966
+ const onRenderChatMessage = async (params) => {
967
+ const { generateMsgId } = params;
968
+ const newMsgId = generateMsgId();
969
+ console.log(`Generated message ID: ${newMsgId}`);
970
+ };
971
+ ```
972
+
973
+ *kbUserData*
974
+ ```javascript
975
+ const onRenderChatMessage = async (params) => {
976
+ const { kbUserData } = params;
977
+ const userData = kbUserData();
978
+ console.log(`User data: ${JSON.stringify(userData)}`);
979
+ };
980
+ ```
981
+
982
+
141
983
  ## License
142
984
 
143
985
  This project is licensed under the MIT License. For more details, please refer to the [LICENSE](https://github.com/open-kbs/openkbs-chat/blob/main/LICENSE) file.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openkbs",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "OpenKBS - Command Line Interface",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/actions.js CHANGED
@@ -8,7 +8,8 @@ const {
8
8
  fetchLocalKBData, fetchKBJWT, createAccountIdFromPublicKey, signPayload, getUserProfile, getKB,
9
9
  fetchAndSaveSettings, downloadFiles, downloadIcon, updateKB, uploadFiles, generateKey, generateMnemonic,
10
10
  reset, bold, red, yellow, green, createKB, saveLocalKBData, listKBs, deleteKBFile,
11
- deleteKB, buildPackage, replacePlaceholderInFiles, buildNodePackage, initByTemplateAction, modifyKB
11
+ deleteKB, buildPackage, replacePlaceholderInFiles, buildNodePackage, initByTemplateAction, modifyKB,
12
+ listKBsSharedWithMe
12
13
  } = require("./utils");
13
14
 
14
15
  const TEMPLATE_DIR = path.join(__dirname, '../templates');
@@ -312,8 +313,11 @@ async function registerKBAndPush(options) {
312
313
  async function lsAction(kbId, prop) {
313
314
  try {
314
315
  const apps = await listKBs();
316
+ const sharedApps = await listKBsSharedWithMe();
317
+
315
318
  if (kbId) {
316
- const app = apps.find(app => app.kbId === kbId);
319
+ // Look in both owned and shared apps
320
+ const app = [...apps, ...sharedApps].find(app => app.kbId === kbId);
317
321
  if (app) {
318
322
  if (prop) {
319
323
  if (app[prop] === undefined) return console.red('Non existing property ' + prop);
@@ -325,11 +329,19 @@ async function lsAction(kbId, prop) {
325
329
  console.red(`KB with ID ${kbId} not found on the remote service.`);
326
330
  }
327
331
  } else {
328
- const maxTitleLength = Math.max(...apps.map(app => app.kbTitle.length));
329
- apps.forEach(app => {
332
+ // Combine and mark owned vs shared apps
333
+ const allApps = [
334
+ ...apps.map(app => ({ ...app, type: 'owned' })),
335
+ ...sharedApps.map(app => ({ ...app, type: 'shared' }))
336
+ ];
337
+
338
+ const maxTitleLength = Math.max(...allApps.map(app => app.kbTitle.length));
339
+
340
+ allApps.forEach(app => {
330
341
  const date = new Date(app.createdAt).toISOString().replace('T', ' ').replace(/\..+/, '');
331
342
  const paddedTitle = app.kbTitle.padEnd(maxTitleLength, ' ');
332
- console.log(`${date} ${paddedTitle} ${app.kbId}`);
343
+ const typeIndicator = app.type === 'shared' ? '[shared]' : ' ';
344
+ console.log(`${date} ${paddedTitle} ${app.kbId} ${typeIndicator}`);
333
345
  });
334
346
  }
335
347
  } catch (error) {
package/src/utils.js CHANGED
@@ -160,7 +160,7 @@ function makePostRequest(url, data) {
160
160
  }
161
161
 
162
162
  if (res.statusCode >= 200 && res.statusCode < 300) {
163
- const data = JSON.parse(body);
163
+ const data = body?.length ? JSON.parse(body) : body;
164
164
  resolve(data);
165
165
  } else {
166
166
  try {
@@ -334,18 +334,20 @@ async function modifyKB(kbToken, kbData, prompt, files, options) {
334
334
  try {
335
335
  const fileContentString = fileContents.map(file => `${file.filePath}\n---\n${file.content}`).join('\n\n\n');
336
336
  const { onRequestHandler, onResponseHandler, chatModel, instructions, verbose, preserveChat } = options;
337
-
338
- const payload = {
337
+ const operationParams = {
339
338
  operation: 'modify',
339
+ ...(chatModel && { operationModel: chatModel }),
340
+ ...(instructions && { operationInstructions: instructions }),
341
+ ...(onRequestHandler && { operationRequestHandler: await fs.readFile(onRequestHandler, 'utf8') }),
342
+ ...(onResponseHandler && { operationResponseHandler: await fs.readFile(onResponseHandler, 'utf8') })
343
+ }
344
+ const payload = {
340
345
  token: kbToken,
341
346
  message: encrypt(prompt + '\n\n###\n\n' + fileContentString, key),
342
347
  chatTitle: 'operation request',
343
348
  encrypted: true,
344
349
  AESKey: key,
345
- ...(chatModel && { operationModel: chatModel }),
346
- ...(instructions && { operationInstructions: instructions }),
347
- ...(onRequestHandler && { operationRequestHandler: await fs.readFile(onRequestHandler, 'utf8') }),
348
- ...(onResponseHandler && { operationResponseHandler: await fs.readFile(onResponseHandler, 'utf8') })
350
+ ...operationParams
349
351
  };
350
352
 
351
353
  startLoading();
@@ -374,7 +376,8 @@ async function modifyKB(kbToken, kbData, prompt, files, options) {
374
376
  message: encrypt(message, key),
375
377
  chatId: createdChatId,
376
378
  encrypted: true,
377
- AESKey: key
379
+ AESKey: key,
380
+ ...operationParams
378
381
  });
379
382
  };
380
383
 
@@ -598,6 +601,19 @@ async function listKBs() {
598
601
  try {
599
602
  const clientJWT = await getClientJWT();
600
603
  const encryptedKBData = await makePostRequest(KB_API_URL, { token: clientJWT, action: 'list' });
604
+
605
+ return encryptedKBData.map(KBData => decryptKBFields(KBData))
606
+ } catch (error) {
607
+ console.error('API request error:', error);
608
+ throw error;
609
+ }
610
+ }
611
+
612
+ async function listKBsSharedWithMe() {
613
+ try {
614
+ const clientJWT = await getClientJWT();
615
+ const encryptedKBData = await makePostRequest(KB_API_URL, { token: clientJWT, action: 'getKBsSharedWithMe' });
616
+
601
617
  return encryptedKBData.map(KBData => decryptKBFields(KBData))
602
618
  } catch (error) {
603
619
  console.error('API request error:', error);
@@ -1040,5 +1056,5 @@ module.exports = {
1040
1056
  KB_API_URL, AUTH_API_URL, decryptKBFields, fetchLocalKBData, fetchKBJWT, createAccountIdFromPublicKey, signPayload,
1041
1057
  listFiles, getUserProfile, getKB, fetchAndSaveSettings, downloadIcon, downloadFiles, updateKB, uploadFiles, generateKey,
1042
1058
  generateMnemonic, reset, bold, red, yellow, green, cyan, createKB, getClientJWT, saveLocalKBData, listKBs, deleteKBFile,
1043
- deleteKB, buildPackage, replacePlaceholderInFiles, buildNodePackage, initByTemplateAction, modifyKB
1059
+ deleteKB, buildPackage, replacePlaceholderInFiles, buildNodePackage, initByTemplateAction, modifyKB, listKBsSharedWithMe
1044
1060
  }