openkbs 0.0.53 → 0.0.59

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.
Files changed (28) hide show
  1. package/README.md +1490 -202
  2. package/package.json +2 -1
  3. package/src/actions.js +1282 -1
  4. package/src/index.js +77 -1
  5. package/src/utils.js +5 -2
  6. package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/app/instructions.txt +44 -9
  7. package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/actions.js +43 -42
  8. package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/handler.js +14 -8
  9. package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Frontend/contentRender.js +95 -12
  10. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/README.md +64 -0
  11. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/app/instructions.txt +160 -0
  12. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/app/settings.json +7 -0
  13. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/actions.js +258 -0
  14. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onRequest.js +13 -0
  15. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onRequest.json +3 -0
  16. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onResponse.js +13 -0
  17. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onResponse.json +3 -0
  18. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Frontend/contentRender.js +170 -0
  19. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Frontend/contentRender.json +3 -0
  20. package/templates/.openkbs/knowledge/metadata.json +1 -1
  21. package/templates/CLAUDE.md +593 -222
  22. package/templates/app/instructions.txt +13 -1
  23. package/templates/app/settings.json +5 -6
  24. package/templates/src/Events/actions.js +43 -9
  25. package/templates/src/Events/handler.js +24 -25
  26. package/templates/webpack.contentRender.config.js +8 -2
  27. package/version.json +3 -3
  28. package/MODIFY.md +0 -132
package/README.md CHANGED
@@ -1,6 +1,5 @@
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
-
4
3
  OpenKBS is an extendable AI service designed to build,
5
4
  deploy and integrate AI agents and applications.
6
5
 
@@ -26,8 +25,11 @@ deploy and integrate AI agents and applications.
26
25
  - [SDK](#sdk)
27
26
  - [Managing Secrets](#managing-secrets)
28
27
  - [Application Settings](#application-settings)
28
+ - [Message Types and Content Formats](#message-types-and-content-formats)
29
+ - [Supported AI Models](#supported-ai-models)
29
30
  - [LLM Instructions](#llm-instructions)
30
31
  - [Execution Environment](#execution-environment)
32
+ - [MCP Integration](#mcp-integration)
31
33
  - [Frontend](#frontend)
32
34
  - [Frontend Module Loading](#frontend-module-loading)
33
35
  - [Built-in UI Libraries](#built-in-ui-libraries)
@@ -169,40 +171,7 @@ Enhance your UI with Material-UI components:
169
171
 
170
172
  ```bash
171
173
  openkbs push
172
- ```
173
-
174
- ### AI-Powered Frontend Generation
175
-
176
- OpenKBS provides simple AI-powered code generation. Use the `openkbs modify` command followed by your requirement:
177
-
178
- ```bash
179
- openkbs modify "Implementing UI to manage renderSettings"
180
- ```
181
-
182
- If you need to revert changes:
183
- ```bash
184
- git checkout -- .
185
- ```
186
-
187
- ## Extend Backend
188
-
189
- Extend backend functionality using `openkbs modify` followed by your requirements. Add file paths to scope AI changes to specific files:
190
-
191
- ```bash
192
- openkbs modify "Implement getContent backend tool that returns text or JSON from a given URL" src/Events/actions.js app/instructions.txt
193
- openkbs push
194
- ```
195
-
196
- This adds a new backend tool in `actions.js` that:
197
- - Fetches content from URLs
198
- - Handles JSON and HTML responses
199
- - Auto-registers in `instructions.txt` (enabling LLM to understand and use it)
200
- - Available to users and LLM through chat
201
-
202
- Example usage in chat:
203
- ```
204
- /getContent("https://api.example.com/data")
205
- ```
174
+ `````
206
175
 
207
176
  ## Mobile & Desktop App
208
177
 
@@ -231,11 +200,13 @@ src/
231
200
  │ ├── onRequest.js // Handles incoming user messages
232
201
  │ ├── onResponse.js // Handles outgoing LLM messages
233
202
  │ ├── onPublicAPIRequest.js // Handles public API requests
234
- │ ├── onAddMessages.js // Handles messages added to the chat (NEW)
203
+ │ ├── onAddMessages.js // Handles messages added to the chat
204
+ │ ├── onCronjob.js // Scheduled task handler (runs on cron schedule)
235
205
  │ ├── onRequest.json // Dependencies for onRequest handler
236
- │ ├── onResponse.json // Dependencies for onResponse handler
206
+ │ ├── onResponse.json // Dependencies for onResponse handler
237
207
  │ ├── onPublicAPIRequest.json // Dependencies for onPublicAPIRequest handler
238
- └── onAddMessages.json // Dependencies for onAddMessages handler (NEW)
208
+ ├── onAddMessages.json // Dependencies for onAddMessages handler
209
+ │ └── onCronjob.json // Dependencies for onCronjob handler
239
210
  │── Frontend/
240
211
  │ ├── contentRender.js // Custom rendering logic for chat messages
241
212
  │ └── contentRender.json // Dependencies for the contentRender module
@@ -262,129 +233,274 @@ The core of the OpenKBS backend framework revolves around the `onRequest` and `o
262
233
 
263
234
  ```javascript
264
235
  // src/Events/actions.js
265
- export const getActions = (meta) => {
266
- return [
267
- // Define your regular expressions and corresponding actions here
268
- [/\/?yourCommand\("(.*)"\)/, async (match, event) => {
269
- // Access match groups, event payload, and openkbs object
270
- // Execute custom logic, API calls, etc.
271
- // Return an object with action results and meta information
272
- return { result: 'Your command executed', ...meta };
273
- }],
274
- // ... more actions
275
- ];
276
- };
236
+ export const getActions = (meta) => [
237
+ // Commands use XML tags with JSON content
238
+ [/<myCommand>([\s\S]*?)<\/myCommand>/s, async (match) => {
239
+ const data = JSON.parse(match[1].trim());
240
+ // Execute custom logic, API calls, etc.
241
+ return { result: data.param, ...meta };
242
+ }]
243
+ ];
277
244
 
278
245
  // src/Events/onRequest.js
279
246
  import {getActions} from './actions.js';
280
247
  export const handler = async (event) => {
281
- const actions = getActions({ _meta_actions: [] }); // Initialize meta actions if needed
282
- for (let [regex, action] of actions) {
283
- const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
248
+ const actions = getActions({ _meta_actions: [] });
249
+ const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
250
+ for (const [regex, action] of actions) {
284
251
  const match = lastMessage?.match(regex);
285
- if (match) return await action(match, event); // Execute matching action
252
+ if (match) return await action(match, event);
286
253
  }
287
- return { type: 'CONTINUE' }; // Continue to the next handler or LLM
254
+ return { type: 'CONTINUE' };
288
255
  };
289
256
 
290
-
291
257
  // src/Events/onResponse.js
292
258
  import {getActions} from './actions.js';
293
-
294
259
  export const handler = async (event) => {
295
- // Example of conditional meta actions based on message count:
296
260
  const maxSelfInvokeMessagesCount = 30;
297
261
  const actions = getActions({
298
262
  _meta_actions: event?.payload?.messages?.length > maxSelfInvokeMessagesCount
299
- ? ["REQUEST_CHAT_MODEL_EXCEEDED"]
300
- : ["REQUEST_CHAT_MODEL"]
263
+ ? [] : ["REQUEST_CHAT_MODEL"]
301
264
  });
302
-
303
- for (let [regex, action] of actions) {
304
- const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
265
+ const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
266
+ for (const [regex, action] of actions) {
305
267
  const match = lastMessage?.match(regex);
306
268
  if (match) return await action(match, event);
307
269
  }
308
-
309
- return { type: 'CONTINUE' }
270
+ return { type: 'CONTINUE' };
310
271
  };
311
-
312
272
  ```
313
273
 
314
274
  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.
315
275
 
276
+ #### Command Format
277
+
278
+ Commands use XML tags with JSON content inside. This format is cleaner and more flexible than regex-based parsing:
279
+
280
+ ```xml
281
+ <commandName>
282
+ {
283
+ "param1": "value1",
284
+ "param2": "value2"
285
+ }
286
+ </commandName>
287
+ ```
288
+
289
+ For commands without parameters, use self-closing tags:
290
+ ```xml
291
+ <commandName/>
292
+ ```
293
+
316
294
  **Example:**
317
295
 
318
296
  ```javascript
319
297
  // src/Events/actions.js
298
+
299
+ // Helper function for uploading generated images
300
+ const uploadGeneratedImage = async (base64Data, meta) => {
301
+ const fileName = `image-${Date.now()}-${Math.random().toString(36).substring(7)}.png`;
302
+ const uploadResult = await openkbs.uploadImage(base64Data, fileName, 'image/png');
303
+ return {
304
+ type: 'CHAT_IMAGE',
305
+ data: { imageUrl: uploadResult.url },
306
+ ...meta
307
+ };
308
+ };
309
+
320
310
  export const getActions = (meta) => [
321
-
322
- [/\/?textToImage\("(.*)"\)/, async (match) => {
323
- const response = await openkbs.textToImage(match[1], { serviceId: 'stability.sd3Medium' });
324
- const imageSrc = `data:${response.ContentType};base64,${response.base64Data}`;
325
- return { type: 'SAVED_CHAT_IMAGE', imageSrc, ...meta };
311
+
312
+ // AI Image Generation (supports multiple models)
313
+ [/<createAIImage>([\s\S]*?)<\/createAIImage>/s, async (match) => {
314
+ try {
315
+ const data = JSON.parse(match[1].trim());
316
+ const model = data.model || "gemini-2.5-flash-image";
317
+ const params = { model, n: 1 };
318
+
319
+ if (data.imageUrls?.length > 0) {
320
+ params.imageUrls = data.imageUrls;
321
+ }
322
+
323
+ if (model === 'gpt-image-1') {
324
+ const validSizes = ["1024x1024", "1536x1024", "1024x1536", "auto"];
325
+ params.size = validSizes.includes(data.size) ? data.size : "1024x1024";
326
+ params.quality = "high";
327
+ } else if (model === 'gemini-2.5-flash-image') {
328
+ const validAspectRatios = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
329
+ params.aspect_ratio = validAspectRatios.includes(data.aspect_ratio) ? data.aspect_ratio : "1:1";
330
+ }
331
+
332
+ const image = await openkbs.generateImage(data.prompt, params);
333
+ return await uploadGeneratedImage(image[0].b64_json, meta);
334
+ } catch (error) {
335
+ return { error: error.message || 'Image creation failed', ...meta };
336
+ }
326
337
  }],
327
338
 
328
- [/\/?googleSearch\("(.*)"\)/, async (match) => {
329
- const q = match[1];
330
- const searchParams = match[2] && JSON.parse(match[2]) || {};
331
- const params = {
332
- q,
333
- ...searchParams,
334
- key: '{{secrets.googlesearch_api_key}}',
335
- cx: '{{secrets.googlesearch_engine_id}}'
336
- };
337
- const response = (await axios.get('https://www.googleapis.com/customsearch/v1', { params }))?.data?.items;
338
- const data = response?.map(({ title, link, snippet, pagemap }) => ({
339
- title,
340
- link,
341
- snippet,
342
- image: pagemap?.metatags?.[0]?.["og:image"]
343
- }));
344
- return { data, ...meta };
339
+ // AI Video Generation (Sora 2)
340
+ [/<createAIVideo>([\s\S]*?)<\/createAIVideo>/s, async (match) => {
341
+ try {
342
+ const data = JSON.parse(match[1].trim());
343
+ const params = {
344
+ video_model: data.model || "sora-2",
345
+ seconds: [4, 8, 12].includes(data.seconds) ? data.seconds : 8
346
+ };
347
+
348
+ if (data.input_reference_url) {
349
+ params.input_reference_url = data.input_reference_url;
350
+ } else {
351
+ const validSizes = ['720x1280', '1280x720'];
352
+ params.size = validSizes.includes(data.size) ? data.size : '1280x720';
353
+ }
354
+
355
+ const videoData = await openkbs.generateVideo(data.prompt, params);
356
+
357
+ if (videoData?.[0]?.status === 'pending') {
358
+ return {
359
+ type: 'VIDEO_PENDING',
360
+ data: { videoId: videoData[0].video_id, message: 'Video generation in progress...' },
361
+ ...meta
362
+ };
363
+ }
364
+
365
+ if (videoData?.[0]?.video_url) {
366
+ return { type: 'CHAT_VIDEO', data: { videoUrl: videoData[0].video_url }, ...meta };
367
+ }
368
+ return { error: 'Video generation failed', ...meta };
369
+ } catch (error) {
370
+ return { error: error.message || 'Video creation failed', ...meta };
371
+ }
345
372
  }],
346
373
 
347
- [/\/?webpageToText\("(.*)"\)/, async (match) => {
348
- let response = await openkbs.webpageToText(match[1]);
349
- if (response?.content?.length > 5000) {
350
- response.content = response.content.substring(0, 5000);
374
+ // Continue video polling
375
+ [/<continueVideoPolling>([\s\S]*?)<\/continueVideoPolling>/s, async (match) => {
376
+ try {
377
+ const data = JSON.parse(match[1].trim());
378
+ const videoData = await openkbs.checkVideoStatus(data.videoId);
379
+
380
+ if (videoData?.[0]?.status === 'completed' && videoData[0].video_url) {
381
+ return { type: 'CHAT_VIDEO', data: { videoUrl: videoData[0].video_url }, ...meta };
382
+ } else if (videoData?.[0]?.status === 'pending') {
383
+ return { type: 'VIDEO_PENDING', data: { videoId: data.videoId, message: 'Still generating...' }, ...meta };
384
+ }
385
+ return { error: 'Video generation failed', ...meta };
386
+ } catch (error) {
387
+ return { error: error.message, ...meta };
351
388
  }
352
- return { data: response, ...meta };
353
389
  }],
354
390
 
355
- [/\/?documentToText\("(.*)"\)/, async (match) => {
356
- let response = await openkbs.documentToText(match[1]);
357
- if (response?.text?.length > 5000) {
358
- response.text = response.text.substring(0, 5000);
391
+ // Google Search
392
+ [/<googleSearch>([\s\S]*?)<\/googleSearch>/s, async (match) => {
393
+ try {
394
+ const data = JSON.parse(match[1].trim());
395
+ const response = await openkbs.googleSearch(data.query);
396
+ const results = response?.map(({ title, link, snippet, pagemap }) => ({
397
+ title, link, snippet,
398
+ image: pagemap?.metatags?.[0]?.["og:image"]
399
+ }));
400
+ return { data: results, ...meta };
401
+ } catch (e) {
402
+ return { error: e.message, ...meta };
359
403
  }
360
- return { data: response, ...meta };
361
404
  }],
362
405
 
363
- [/\/?imageToText\("(.*)"\)/, async (match) => {
364
- let response = await openkbs.imageToText(match[1]);
365
- if (response?.detections?.[0]?.txt) {
366
- response = { detections: response?.detections?.[0]?.txt };
406
+ // Web scraping
407
+ [/<webpageToText>([\s\S]*?)<\/webpageToText>/s, async (match) => {
408
+ try {
409
+ const data = JSON.parse(match[1].trim());
410
+ let response = await openkbs.webpageToText(data.url);
411
+ if (response?.content?.length > 5000) {
412
+ response.content = response.content.substring(0, 5000);
413
+ }
414
+ return { data: response, ...meta };
415
+ } catch (e) {
416
+ return { error: e.message, ...meta };
367
417
  }
368
- return { data: response, ...meta };
369
418
  }],
370
419
 
371
- [/\/?textToSpeech\("(.*)"\s*,\s*"(.*)"\)/, async (match) => {
372
- const response = await openkbs.textToSpeech(match[2], {
373
- languageCode: match[1]
374
- });
375
- return { data: response, ...meta };
420
+ // Exchange rates with period support
421
+ [/<getExchangeRates>([\s\S]*?)<\/getExchangeRates>/s, async (match) => {
422
+ try {
423
+ const data = JSON.parse(match[1].trim());
424
+ // period can be: 'latest', 'YYYY-MM-DD' (historical), or 'YYYY-MM-DD..YYYY-MM-DD' (time series)
425
+ let response = await openkbs.getExchangeRates({
426
+ base: data.base,
427
+ symbols: data.symbols,
428
+ period: data.period || 'latest'
429
+ });
430
+ return { data: response, ...meta };
431
+ } catch (e) {
432
+ return { error: e.message, ...meta };
433
+ }
434
+ }],
435
+
436
+ // Send email
437
+ [/<sendMail>([\s\S]*?)<\/sendMail>/s, async (match) => {
438
+ try {
439
+ const data = JSON.parse(match[1].trim());
440
+ const response = await openkbs.sendMail(data.to, data.subject, data.body);
441
+ return { type: 'EMAIL_SENT', data: { email: data.to, subject: data.subject, response }, ...meta };
442
+ } catch (e) {
443
+ return { error: e.message, ...meta };
444
+ }
376
445
  }],
377
446
 
378
- // example: checkVAT("BG123456789")
379
- [/\/?checkVAT\("(.*)"\)/, async (match) => {
380
- let response = await openkbs.checkVAT(match[1]);
381
- return { data: response, ...meta };
447
+ // Schedule task
448
+ [/<scheduleTask>([\s\S]*?)<\/scheduleTask>/s, async (match) => {
449
+ try {
450
+ const data = JSON.parse(match[1].trim());
451
+ let scheduledTime;
452
+
453
+ if (data.time) {
454
+ let isoTimeStr = data.time.replace(' ', 'T');
455
+ if (!isoTimeStr.includes('Z') && !isoTimeStr.includes('+')) isoTimeStr += 'Z';
456
+ scheduledTime = new Date(isoTimeStr).getTime();
457
+ } else if (data.delay) {
458
+ let delayMs = 0;
459
+ if (data.delay.endsWith('h')) delayMs = parseFloat(data.delay) * 3600000;
460
+ else if (data.delay.endsWith('d')) delayMs = parseFloat(data.delay) * 86400000;
461
+ else delayMs = parseFloat(data.delay) * 60000;
462
+ scheduledTime = Date.now() + delayMs;
463
+ } else {
464
+ scheduledTime = Date.now() + 3600000;
465
+ }
466
+
467
+ const response = await openkbs.kb({
468
+ action: 'createScheduledTask',
469
+ scheduledTime: Math.floor(scheduledTime / 60000) * 60000,
470
+ taskPayload: { message: `[SCHEDULED_TASK] ${data.message}`, createdAt: Date.now() },
471
+ description: data.message.substring(0, 50)
472
+ });
473
+
474
+ return { type: 'TASK_SCHEDULED', data: { scheduledTime: new Date(scheduledTime).toISOString(), taskId: response.taskId }, ...meta };
475
+ } catch (e) {
476
+ return { error: e.message, ...meta };
477
+ }
478
+ }],
479
+
480
+ // Get scheduled tasks
481
+ [/<getScheduledTasks\s*\/>/s, async () => {
482
+ try {
483
+ const response = await openkbs.kb({ action: 'getScheduledTasks' });
484
+ return { type: 'SCHEDULED_TASKS_LIST', data: response, ...meta };
485
+ } catch (e) {
486
+ return { error: e.message, ...meta };
487
+ }
382
488
  }],
383
489
 
384
- // example: getExchangeRates("USD", "EUR,GBP")
385
- [/\/?getExchangeRates\("(.*)"\s*,\s*"(.*)"\)/, async (match) => {
386
- let response = await openkbs.getExchangeRates(match[1], match[2]);
387
- return { data: response, ...meta };
490
+ // View image - adds image to LLM vision context
491
+ [/<viewImage>([\s\S]*?)<\/viewImage>/s, async (match) => {
492
+ try {
493
+ const data = JSON.parse(match[1].trim());
494
+ return {
495
+ data: [
496
+ { type: "text", text: `Viewing image: ${data.url}` },
497
+ { type: "image_url", image_url: { url: data.url } }
498
+ ],
499
+ ...meta
500
+ };
501
+ } catch (e) {
502
+ return { error: e.message, ...meta };
503
+ }
388
504
  }],
389
505
  ];
390
506
  ```
@@ -394,15 +510,13 @@ export const getActions = (meta) => [
394
510
  // src/Events/onRequest.js
395
511
  import {getActions} from './actions.js';
396
512
 
397
-
398
513
  export const handler = async (event) => {
399
- const actions = getActions({});
514
+ const actions = getActions({ _meta_actions: [] });
400
515
  for (let [regex, action] of actions) {
401
516
  const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
402
517
  const match = lastMessage?.match(regex);
403
518
  if (match) return await action(match);
404
519
  }
405
-
406
520
  return { type: 'CONTINUE' }
407
521
  };
408
522
  ```
@@ -412,14 +526,12 @@ export const handler = async (event) => {
412
526
  import {getActions} from './actions.js';
413
527
 
414
528
  export const handler = async (event) => {
415
- const actions = getActions({_meta_actions: ["REQUEST_CHAT_MODEL"]});
416
-
529
+ const actions = getActions({ _meta_actions: ["REQUEST_CHAT_MODEL"] });
417
530
  for (let [regex, action] of actions) {
418
531
  const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
419
532
  const match = lastMessage?.match(regex);
420
533
  if (match) return await action(match);
421
534
  }
422
-
423
535
  return { type: 'CONTINUE' }
424
536
  };
425
537
  ```
@@ -563,7 +675,79 @@ export const handler = async (event) => {
563
675
 
564
676
  ```
565
677
 
566
- **Dependencies (onRequest.json, onResponse.json, etc.):**
678
+ #### onCronjob Handler
679
+
680
+ The `onCronjob` handler enables scheduled task execution for your agent. It runs automatically based on a cron schedule you define, without requiring user interaction.
681
+
682
+ **How it works:**
683
+ 1. Create `src/Events/onCronjob.js` with your handler logic
684
+ 2. Define the schedule using `handler.CRON_SCHEDULE` at the end of the file
685
+ 3. Deploy with `openkbs push` - the cronjob is automatically registered
686
+
687
+ **Cron Schedule Format:**
688
+ Standard cron syntax: `minute hour day month weekday`
689
+
690
+ - `* * * * *` - Every minute
691
+ - `*/5 * * * *` - Every 5 minutes
692
+ - `0 * * * *` - Every hour
693
+ - `0 0 * * *` - Daily at midnight
694
+ - `0 9 * * 1-5` - Weekdays at 9 AM
695
+
696
+ **Example `onCronjob.js`:**
697
+
698
+ ```javascript
699
+ // src/Events/onCronjob.js
700
+
701
+ export const handler = async (event) => {
702
+ try {
703
+ // Fetch external data
704
+ const response = await fetch('https://api.example.com/status');
705
+ const data = await response.json();
706
+
707
+ // Process and store results
708
+ await openkbs.createItem({
709
+ itemType: 'status_check',
710
+ itemId: `status_${Date.now()}`,
711
+ body: { timestamp: new Date().toISOString(), ...data }
712
+ });
713
+
714
+ // Optionally trigger a chat for notifications
715
+ if (data.alertLevel > 5) {
716
+ await openkbs.chats({
717
+ chatTitle: `Alert: ${data.message}`,
718
+ message: JSON.stringify([
719
+ { type: "text", text: `ALERT: ${data.message}\nLevel: ${data.alertLevel}` }
720
+ ])
721
+ });
722
+ }
723
+
724
+ return {
725
+ success: true,
726
+ processed: data,
727
+ timestamp: new Date().toISOString()
728
+ };
729
+ } catch (error) {
730
+ return {
731
+ success: false,
732
+ error: error.message,
733
+ timestamp: new Date().toISOString()
734
+ };
735
+ }
736
+ };
737
+
738
+ // IMPORTANT: Define the cron schedule at the end of the file
739
+ handler.CRON_SCHEDULE = "*/5 * * * *"; // Runs every 5 minutes
740
+ ```
741
+
742
+ **Key Points:**
743
+ - The `CRON_SCHEDULE` must be defined as `handler.CRON_SCHEDULE = "pattern"` at the end of the file
744
+ - The handler receives an empty `event` object (no user payload)
745
+ - Use `openkbs.chats()` to create new chat sessions for notifications
746
+ - All SDK methods (`openkbs.*`) are available
747
+ - Return an object with results for logging purposes
748
+ - To disable the cronjob, simply remove or rename the `onCronjob.js` file and redeploy
749
+
750
+ **Dependencies (onRequest.json, onResponse.json, onCronjob.json, etc.):**
567
751
 
568
752
  These files specify the NPM package dependencies required for the respective event handlers. They follow the standard `package.json` format.
569
753
 
@@ -598,33 +782,84 @@ if (actionMatch) {
598
782
  ```
599
783
 
600
784
 
601
- #### SDK
785
+ #### SDK (Backend `openkbs` object)
602
786
 
603
- 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:
787
+ The `openkbs` object is a global object available in all backend event handlers (`onRequest`, `onResponse`, `onAddMessages`, `onPublicAPIRequest`, `onCronjob`). It provides access to OpenKBS services, external APIs, and data storage.
604
788
 
605
- * **`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.
789
+ **Properties:**
790
+ ```javascript
791
+ openkbs.kbId // Current Knowledge Base ID
792
+ openkbs.clientHeaders // Client headers (e.g., openkbs.clientHeaders['x-forwarded-for'] for IP)
793
+ openkbs.AESKey // Encryption key for this KB
794
+ openkbs.chatJWT // JWT token for current chat session
795
+ ```
606
796
 
607
- * **`openkbs.speechToText(audioURL, params)`:** Transcribes audio from a URL to text.
797
+ ##### Image & Video Generation
798
+
799
+ * **`openkbs.generateImage(prompt, params)`:** Generates images using AI models. Returns array with `b64_json` data.
800
+ - `params.model`: `"gemini-2.5-flash-image"` (default, supports image editing) or `"gpt-image-1"` (better for text in images)
801
+ - `params.imageUrls`: Array of reference image URLs (gemini only)
802
+ - `params.aspect_ratio`: For gemini: `"1:1"`, `"16:9"`, `"9:16"`, `"3:2"`, `"2:3"`, `"4:3"`, `"3:4"`, `"4:5"`, `"5:4"`, `"21:9"`
803
+ - `params.size`: For gpt-image-1: `"1024x1024"`, `"1536x1024"`, `"1024x1536"`, `"auto"`
804
+ - `params.n`: Number of images (default: 1)
805
+
806
+ * **`openkbs.generateVideo(prompt, params)`:** Generates videos using Sora 2. Returns array with `video_url` or `status: 'pending'`.
807
+ - `params.video_model`: `"sora-2"` (default, fast) or `"sora-2-pro"` (higher quality)
808
+ - `params.seconds`: `4`, `8` (default), or `12`
809
+ - `params.size`: `"1280x720"` (landscape) or `"720x1280"` (portrait)
810
+ - `params.input_reference_url`: Optional reference image URL
811
+
812
+ * **`openkbs.checkVideoStatus(videoId)`:** Check status of pending video generation.
813
+
814
+ * **`openkbs.uploadImage(base64Data, fileName, mimeType)`:** Upload image to storage. Returns `{ url }`.
815
+
816
+ ##### Deep Research
817
+
818
+ * **`openkbs.deepResearch(input, params)`:** Autonomous research agent powered by Google Gemini. Performs multi-step research: plans → searches web → reads sources → iterates → synthesizes report.
819
+ - `input`: Research query or topic (string)
820
+ - `params.previous_interaction_id`: Optional - for follow-up questions on completed research
821
+ - Returns `{ status, interaction_id, output, usage, prepaid_credits }`
822
+ - Status can be `'completed'`, `'in_progress'`, or `'failed'`
823
+ - **Important:** Takes 5-20 minutes (up to 60 min for complex topics)
824
+ - **Pricing:** Requires minimum upfront charge of 50 credits (~€0.50). Additional usage charged on completion.
825
+
826
+ * **`openkbs.checkDeepResearchStatus(interactionId, prepaidCredits)`:** Check status of pending deep research.
827
+ - `interactionId`: From previous `deepResearch()` response
828
+ - `prepaidCredits`: From previous response (for correct billing)
829
+ - Returns same format as `deepResearch()`
830
+
831
+ ##### Search & Content Extraction
832
+
833
+ * **`openkbs.googleSearch(query, params)`:** Performs Google search. Optional `params.searchType: 'image'` for image search.
834
+
835
+ * **`openkbs.webpageToText(pageURL, params)`:** Extracts text content from a webpage.
608
836
 
609
- * **`openkbs.webpageToText(pageURL, params)`:** Extracts text content from a given webpage URL.
837
+ * **`openkbs.documentToText(documentURL, params)`:** Extracts text from documents (PDF, DOC, etc.).
610
838
 
611
- * **`openkbs.googleSearch(q, params)`:** Performs a Google search using the provided query and parameters.
839
+ * **`openkbs.imageToText(imageUrl, params)`:** OCR - extracts text from images.
612
840
 
613
- * **`openkbs.sendMail(email, subject, content)`:** Sends an email to the specified recipient
841
+ ##### Communication
614
842
 
615
- * **`openkbs.checkVAT(vatNumber)`:** Validates a VAT number against official databases
843
+ * **`openkbs.sendMail(email, subject, content)`:** Sends an email to the specified recipient.
616
844
 
617
- * **`openkbs.getExchangeRates(base, symbols)`:** Retrieves current exchange rates
845
+ * **`openkbs.textToSpeech(text, params)`:** Converts text to speech. Returns `response.audioContent`.
618
846
 
619
- * **`openkbs.documentToText(documentURL, params)`:** Extracts text from various document formats.
847
+ * **`openkbs.speechToText(audioURL, params)`:** Transcribes audio from a URL to text.
848
+
849
+ ##### Data & Utilities
620
850
 
621
- * **`openkbs.imageToText(imageUrl, params)`:** Extracts text from an image.
851
+ * **`openkbs.getExchangeRates({ base, symbols, period })`:** Retrieves exchange rates.
852
+ - `base`: Base currency (e.g., `"EUR"`)
853
+ - `symbols`: Target currencies (e.g., `"USD,GBP"`)
854
+ - `period`: `"latest"` (default), `"YYYY-MM-DD"` (historical), or `"YYYY-MM-DD..YYYY-MM-DD"` (time series)
855
+
856
+ * **`openkbs.checkVAT(vatNumber)`:** Validates a VAT number against official databases.
622
857
 
623
858
  * **`openkbs.translate(text, to)`:** Translates text to the specified target language.
624
859
 
625
860
  * **`openkbs.detectLanguage(text, params)`:** Detects the language of the provided text.
626
861
 
627
- * **`openkbs.textToSpeech(text, params)`:** Converts text to speech. Returns `response.audioContent` which automatically plays in the chat interface.
862
+ ##### Encryption & Storage
628
863
 
629
864
  * **`openkbs.encrypt(plaintext)`:** Encrypts data using the provided AES key.
630
865
 
@@ -632,25 +867,162 @@ The `openkbs` object provides a set of utility functions and services to interac
632
867
 
633
868
  * **`openkbs.items(data)`:** Interacts with the Items API for creating, updating, and deleting items.
634
869
 
870
+ * **`openkbs.createItem({ itemType, itemId, body })`:** Create a new item.
871
+
872
+ * **`openkbs.updateItem({ itemType, itemId, body })`:** Update an existing item.
873
+
874
+ * **`openkbs.deleteItem(itemId)`:** Delete an item by ID.
875
+
876
+ * **`openkbs.getItem(itemId)`:** Get a single item by ID. Returns item with auto-decrypted body.
877
+
878
+ * **`openkbs.fetchItems({ limit, itemType, beginsWith, from, to, field })`:** Fetch multiple items with filters.
879
+ - `limit`: Max items to return (default: 1000)
880
+ - `itemType`: Filter by item type
881
+ - `beginsWith`: Filter by itemId prefix
882
+ - `from`, `to`: Date range filters
883
+ - `field`: Field name for range query
884
+
635
885
  * **`openkbs.chats(data)`:** Interacts with the Chats API.
636
886
 
637
- * **`openkbs.kb(data)`:** Interacts with the Knowledge Base API.
887
+ * **`openkbs.kb(data)`:** Interacts with the Knowledge Base API. Actions include:
888
+ - `createScheduledTask`: Schedule a future task
889
+ - `getScheduledTasks`: List all scheduled tasks
890
+ - `deleteScheduledTask`: Delete a scheduled task
891
+ - `createPresignedURL`: Get URL for file upload
638
892
 
639
- * **`openkbs.clientHeaders`:** Exposes client headers for accessing information like IP address, location, etc. (e.g., `openkbs.clientHeaders['x-forwarded-for']`).
893
+ ##### Scheduled Tasks API
640
894
 
641
- * **`openkbs.createEmbeddings(input, model)`:** Create embeddings from input text
895
+ Scheduled tasks allow your agent to execute actions at specific times in the future. When a scheduled task fires, it creates a new chat session with the task message.
896
+
897
+ **Create a scheduled task:**
898
+ ```javascript
899
+ const response = await openkbs.kb({
900
+ action: 'createScheduledTask',
901
+ scheduledTime: Date.now() + 3600000, // Unix timestamp in ms (1 hour from now)
902
+ taskPayload: {
903
+ message: '[SCHEDULED_TASK] Send weekly report',
904
+ source: 'marketing_agent',
905
+ customData: { reportType: 'weekly' }
906
+ },
907
+ description: 'Weekly report task' // Short description for listing
908
+ });
909
+ // Returns: { taskId: 'task_123...' }
910
+ ```
911
+
912
+ **List scheduled tasks:**
913
+ ```javascript
914
+ const tasks = await openkbs.kb({ action: 'getScheduledTasks' });
915
+ // Returns: { items: [{ timestamp, taskPayload, description, status }] }
916
+ ```
917
+
918
+ **Delete a scheduled task:**
919
+ ```javascript
920
+ await openkbs.kb({
921
+ action: 'deleteScheduledTask',
922
+ timestamp: 1704067200000 // The scheduledTime of the task to delete
923
+ });
924
+ ```
925
+
926
+ **How it works:**
927
+ 1. Task is stored with the specified `scheduledTime`
928
+ 2. At the scheduled time, system creates a new chat with `taskPayload.message` as the initial message
929
+ 3. Your agent's `onRequest`/`onResponse` handlers process the message normally
930
+ 4. Use `[SCHEDULED_TASK]` prefix in message to help your agent identify scheduled tasks
931
+
932
+ * **`openkbs.createEmbeddings(input, model)`:** Create embeddings from input text.
933
+ - `model`: `"text-embedding-3-large"` (default) or other OpenAI embedding models
934
+ - Returns: `{ embeddings, totalTokens, dimension }`
935
+
936
+ * **`openkbs.parseJSONFromText(text)`:** Utility to extract JSON object from text. Returns parsed object or null.
937
+
938
+ * **`openkbs.textToImage(prompt, params)`:** (Legacy) Generates image using Stability AI. Use `generateImage` for newer models.
939
+ - `params.serviceId`: `"stability.sd35Large"` (default) or `"stability.sd3Medium"`
940
+ - Returns: `{ ContentType, base64Data }`
642
941
 
643
942
  **Example SDK Usage:**
644
943
 
645
944
  ```javascript
646
- // ... inside an action ...
647
- const image = await openkbs.textToImage('a cat sitting on a mat');
648
- // ... use image.base64Data and image.ContentType ...
945
+ // Generate an image with Gemini
946
+ const images = await openkbs.generateImage('a sunset over mountains', {
947
+ model: 'gemini-2.5-flash-image',
948
+ aspect_ratio: '16:9'
949
+ });
950
+ const base64 = images[0].b64_json;
649
951
 
952
+ // Upload the generated image
953
+ const result = await openkbs.uploadImage(base64, 'sunset.png', 'image/png');
954
+ console.log(result.url);
650
955
 
651
- //Encrypt submitted user data
652
- const encryptedValue = await openkbs.encrypt(userData);
956
+ // Generate a video with Sora 2
957
+ const video = await openkbs.generateVideo('a cat playing with yarn', {
958
+ video_model: 'sora-2',
959
+ seconds: 8,
960
+ size: '1280x720'
961
+ });
962
+
963
+ // Check video status if pending
964
+ if (video[0]?.status === 'pending') {
965
+ const status = await openkbs.checkVideoStatus(video[0].video_id);
966
+ if (status[0]?.video_url) {
967
+ console.log('Video ready:', status[0].video_url);
968
+ }
969
+ }
970
+
971
+ // Get exchange rates for a date range
972
+ const rates = await openkbs.getExchangeRates({
973
+ base: 'EUR',
974
+ symbols: 'USD,GBP,JPY',
975
+ period: '2024-01-01..2024-01-31'
976
+ });
977
+
978
+ // Item CRUD operations
979
+ await openkbs.createItem({
980
+ itemType: 'memory',
981
+ itemId: 'memory_user_settings',
982
+ body: { theme: 'dark', notifications: true }
983
+ });
984
+
985
+ const item = await openkbs.getItem('memory_user_settings');
986
+ console.log(item.item.body); // { theme: 'dark', notifications: true }
987
+
988
+ await openkbs.updateItem({
989
+ itemType: 'memory',
990
+ itemId: 'memory_user_settings',
991
+ body: { theme: 'light', notifications: false }
992
+ });
653
993
 
994
+ // Fetch multiple items
995
+ const items = await openkbs.fetchItems({
996
+ itemType: 'memory',
997
+ beginsWith: 'memory_',
998
+ limit: 100
999
+ });
1000
+
1001
+ // Schedule a task
1002
+ await openkbs.kb({
1003
+ action: 'createScheduledTask',
1004
+ scheduledTime: Date.now() + 3600000, // 1 hour from now
1005
+ taskPayload: { message: 'Reminder to check analytics' },
1006
+ description: 'Analytics reminder'
1007
+ });
1008
+
1009
+ // Create embeddings
1010
+ const { embeddings, totalTokens } = await openkbs.createEmbeddings(
1011
+ 'Text to embed',
1012
+ 'text-embedding-3-large'
1013
+ );
1014
+
1015
+ // Access client info
1016
+ const clientIP = openkbs.clientHeaders['x-forwarded-for'];
1017
+ const userAgent = openkbs.clientHeaders['user-agent'];
1018
+
1019
+ // Encrypt/decrypt data
1020
+ const encryptedValue = await openkbs.encrypt(JSON.stringify(userData));
1021
+ const decryptedValue = JSON.parse(await openkbs.decrypt(encryptedValue));
1022
+
1023
+ // Parse JSON from LLM response
1024
+ const jsonData = openkbs.parseJSONFromText('Some text {"key": "value"} more text');
1025
+ // Returns: { key: "value" }
654
1026
  ```
655
1027
 
656
1028
  #### Managing Secrets
@@ -686,36 +1058,220 @@ This file contains essential configuration settings for the AI agent.
686
1058
  }
687
1059
  ```
688
1060
 
1061
+ #### Message Types and Content Formats
1062
+
1063
+ OpenKBS supports multiple message content types for rich interactions with AI models. Messages can be sent as plain text or as structured content arrays.
1064
+
1065
+ ##### Plain Text Messages
1066
+
1067
+ The simplest format - just a string:
1068
+
1069
+ ```javascript
1070
+ {
1071
+ "role": "user",
1072
+ "content": "Hello, how are you?"
1073
+ }
1074
+ ```
1075
+
1076
+ ##### Structured Content Arrays
1077
+
1078
+ For rich content (images, documents, videos), use a JSON array as the content:
1079
+
1080
+ ```javascript
1081
+ {
1082
+ role: "user",
1083
+ content: JSON.stringify([
1084
+ { type: "text", text: "Analyze this:" },
1085
+ { type: "image_url", image_url: { url: "https://example.com/image.jpg" } }
1086
+ ])
1087
+ }
1088
+ ```
1089
+
1090
+ ##### Supported Content Types
1091
+
1092
+ **text** - Plain text content
1093
+ ```javascript
1094
+ { type: "text", text: "Your message here" }
1095
+ ```
1096
+
1097
+ **image_url** - Images (JPEG, PNG, GIF, WebP, SVG) and PDFs
1098
+ ```javascript
1099
+ { type: "image_url", image_url: { url: "https://example.com/photo.jpg" } }
1100
+ { type: "image_url", image_url: { url: "https://example.com/document.pdf" } }
1101
+ ```
1102
+
1103
+ **file_url** - Video files (Gemini models only)
1104
+ ```javascript
1105
+ { type: "file_url", file_url: { url: "https://example.com/video.mp4", mimeType: "video/mp4" } }
1106
+ { type: "file_url", file_url: { url: "https://youtube.com/watch?v=VIDEO_ID" } }
1107
+ ```
1108
+
1109
+ ##### Content Type Examples
1110
+
1111
+ **Analyze an image:**
1112
+ ```javascript
1113
+ content: JSON.stringify([
1114
+ { type: "text", text: "What's in this image?" },
1115
+ { type: "image_url", image_url: { url: "https://example.com/photo.jpg" } }
1116
+ ])
1117
+ ```
1118
+
1119
+ **Compare multiple images:**
1120
+ ```javascript
1121
+ content: JSON.stringify([
1122
+ { type: "text", text: "Compare these:" },
1123
+ { type: "image_url", image_url: { url: "https://example.com/image1.jpg" } },
1124
+ { type: "image_url", image_url: { url: "https://example.com/image2.jpg" } }
1125
+ ])
1126
+ ```
1127
+
1128
+ **Analyze a video (Gemini only):**
1129
+ ```javascript
1130
+ content: JSON.stringify([
1131
+ { type: "text", text: "Describe this video:" },
1132
+ { type: "file_url", file_url: { url: "https://example.com/video.mp4", mimeType: "video/mp4" } }
1133
+ ])
1134
+ ```
1135
+
1136
+ #### Supported AI Models
1137
+
1138
+ OpenKBS provides a unified API for multiple AI providers. Configure your model in `app/settings.json`:
1139
+
1140
+ ```json
1141
+ {
1142
+ "model": "claude-sonnet-4-5-20250929"
1143
+ }
1144
+ ```
1145
+
1146
+ ##### Model Capabilities
1147
+ **Anthropic Claude** (Vision, PDF)
1148
+ **OpenAI GPT** (Vision, PDF, Video)
1149
+ **Google Gemini** (Vision, PDF, Video)
1150
+ **Other Models** (Text only)
1151
+
1152
+ ##### Usage in Actions
1153
+
1154
+ When building custom actions that return content to the LLM, use the same content type format:
1155
+
1156
+ ```javascript
1157
+ // In actions.js - returning an image for analysis
1158
+ [/<viewImage>([\s\S]*?)<\/viewImage>/s, async (match) => {
1159
+ const data = JSON.parse(match[1].trim());
1160
+ return {
1161
+ data: [
1162
+ { type: "text", text: `Viewing image: ${data.url}` },
1163
+ { type: "image_url", image_url: { url: data.url } }
1164
+ ],
1165
+ _meta_actions: ["REQUEST_CHAT_MODEL"]
1166
+ };
1167
+ }]
1168
+
1169
+ // Returning a video for analysis (Gemini only)
1170
+ [/<viewVideo>([\s\S]*?)<\/viewVideo>/s, async (match) => {
1171
+ const data = JSON.parse(match[1].trim());
1172
+ return {
1173
+ data: [
1174
+ { type: "text", text: `Analyzing video: ${data.url}` },
1175
+ { type: "file_url", file_url: { url: data.url, mimeType: "video/mp4" } }
1176
+ ],
1177
+ _meta_actions: ["REQUEST_CHAT_MODEL"]
1178
+ };
1179
+ }]
1180
+ ```
1181
+
689
1182
  #### LLM Instructions
690
1183
  `app/instructions.txt`
691
1184
  This file contains the instructions for the LLM, guiding its behavior and interaction with custom functionalities.
692
1185
  Clear and specific instructions ensure the LLM effectively utilizes provided actions and commands.
693
1186
 
1187
+ **Command Format:**
1188
+ Commands use XML tags with JSON content. The LLM outputs these as regular text, and the backend parses and executes them.
1189
+
694
1190
  **Example Instructions:**
695
1191
 
696
1192
  ```
697
1193
  You are an AI assistant.
698
1194
 
699
- You can execute the following commands:
1195
+ LIST OF AVAILABLE COMMANDS:
1196
+ To execute a command, output it as text and wait for system response.
700
1197
 
701
- /googleSearch("query")
1198
+ <googleSearch>
1199
+ {
1200
+ "query": "search query"
1201
+ }
1202
+ </googleSearch>
702
1203
  Description: """
703
- Get results from Google Search API.
1204
+ Get results from the Google Search API.
704
1205
  """
705
1206
  $InputLabel = """Let me Search in Google!"""
706
- $InputValue = """Search in google for the latest news"""
1207
+ $InputValue = """Search for latest news"""
1208
+
1209
+ <createAIImage>
1210
+ {
1211
+ "model": "gemini-2.5-flash-image",
1212
+ "aspect_ratio": "16:9",
1213
+ "prompt": "image description"
1214
+ }
1215
+ </createAIImage>
1216
+ Description: """
1217
+ Generate AI images. Models: gemini-2.5-flash-image (supports image editing, aspect ratios), gpt-image-1 (better for text).
1218
+ """
1219
+
1220
+ <createAIVideo>
1221
+ {
1222
+ "model": "sora-2",
1223
+ "size": "1280x720",
1224
+ "seconds": 8,
1225
+ "prompt": "video description"
1226
+ }
1227
+ </createAIVideo>
1228
+ Description: """
1229
+ Generate AI videos with Sora 2. Duration: 4, 8, or 12 seconds.
1230
+ """
707
1231
 
708
- /someCommand("param")
1232
+ <getExchangeRates>
1233
+ {
1234
+ "base": "EUR",
1235
+ "symbols": "USD,GBP",
1236
+ "period": "latest"
1237
+ }
1238
+ </getExchangeRates>
1239
+ Description: """
1240
+ Get exchange rates. Period: 'latest', 'YYYY-MM-DD' (historical), or 'YYYY-MM-DD..YYYY-MM-DD' (time series).
1241
+ """
1242
+
1243
+ <scheduleTask>
1244
+ {
1245
+ "delay": "2h",
1246
+ "message": "Task description"
1247
+ }
1248
+ </scheduleTask>
1249
+ Description: """
1250
+ Schedule a future task. Use delay (minutes, "2h", "1d") or specific time (UTC).
1251
+ """
1252
+
1253
+ <getScheduledTasks/>
1254
+ Description: """
1255
+ List all scheduled tasks.
1256
+ """
1257
+
1258
+ <viewImage>
1259
+ {
1260
+ "url": "https://example.com/image.jpg"
1261
+ }
1262
+ </viewImage>
1263
+ Description: """
1264
+ Add an image to the vision context. Use when you have an image URL that needs to be analyzed.
1265
+ The image will be added to the conversation for visual analysis.
1266
+ """
709
1267
 
710
1268
  $Comment = """
711
1269
  Any instructions or comments placed here will be removed before sending to the LLM.
712
1270
  These can span multiple lines and contain any characters, code, or formatting.
713
1271
  """
714
- ...
715
-
716
1272
  ```
717
1273
 
718
- Command definitions may include \$InputLabel and \$InputValue which are invisiable to the LLM:
1274
+ Command definitions may include \$InputLabel and \$InputValue which are invisible to the LLM:
719
1275
 
720
1276
  `$InputLabel` - Text displayed as a selectable option in the chat interface.
721
1277
 
@@ -723,7 +1279,7 @@ Command definitions may include \$InputLabel and \$InputValue which are invisiab
723
1279
 
724
1280
  These features provide quick command access and pre-populate inputs, enhancing user interaction.
725
1281
 
726
- `zipMessages` and `unzipMessages` command instructions allow the LLM to manage the chat size by summarizing or restoring portions of the conversation, optimizing context retention and token usage.
1282
+ `zipMessages` and `unzipMessages` command instructions allow the LLM to manage the chat size by summarizing or restoring portions of the conversation, optimizing context retention and token usage.
727
1283
 
728
1284
  Instructions:
729
1285
  ```
@@ -738,12 +1294,41 @@ Include message IDs with optional summaries to maintain context while using fewe
738
1294
  ```
739
1295
  /unzipMessages([{ "MSG_ID" : 1234567890123 }])
740
1296
  Description: """
741
- Uncompresses the message associated with the provided `MSG_ID`, restoring its original content to the chat.
1297
+ Uncompresses the message associated with the provided `MSG_ID`, restoring its original content to the chat.
742
1298
  """
743
1299
  ```
744
1300
 
745
1301
  **Important:** `MSG_ID` value must be a unique identifier present anywhere in the message content. The OpenKBS platform automatically handles the zipping and unzipping of messages based on these commands. No custom implementation is required.
746
1302
 
1303
+ #### Instruction Variables
1304
+
1305
+ OpenKBS provides dynamic variables that can be used in `app/instructions.txt` to inject runtime information:
1306
+
1307
+ - `{{kbId}}` - Current Knowledge Base ID → `abc123xyz`
1308
+ - `{{openkbsDateNow}}` - Current UTC time (ISO format) → `2025-01-15T14:30:00.000Z`
1309
+ - `{{openkbsTimestamp}}` - Current Unix timestamp (ms) → `1736948200000`
1310
+ - `{{openkbsDate:locale:timezone}}` - Formatted date with locale and timezone → `15/01/2025, 16:30:00`
1311
+ - `{{openkbsDateTZ:timezone}}` - Formatted date with timezone (en-GB locale) → `15/01/2025, 16:30:00`
1312
+
1313
+ **Example usage in instructions.txt:**
1314
+
1315
+ ```
1316
+ You are a helpful assistant.
1317
+
1318
+ ## Current Time Information
1319
+ - UTC Time: {{openkbsDateNow}}
1320
+ - Local Time (Bulgaria): {{openkbsDate:bg-BG:Europe/Sofia}}
1321
+ - US Eastern Time: {{openkbsDate:en-US:America/New_York}}
1322
+ - Unix Timestamp: {{openkbsTimestamp}}
1323
+ - Agent ID: {{kbId}}
1324
+
1325
+ Use the current time to provide time-aware responses.
1326
+ ```
1327
+
1328
+ **Locale examples:** `en-US`, `en-GB`, `bg-BG`, `de-DE`, `fr-FR`, `ja-JP`
1329
+
1330
+ **Timezone examples:** `UTC`, `Europe/Sofia`, `America/New_York`, `Asia/Tokyo`, `Europe/London`
1331
+
747
1332
  #### Execution Environment
748
1333
 
749
1334
  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:
@@ -768,6 +1353,53 @@ Here's a breakdown of the key objects and utilities available within the OpenKBS
768
1353
 
769
1354
  * **`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.
770
1355
 
1356
+ #### MCP Integration
1357
+
1358
+ Connect your agent to external tools using [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).
1359
+
1360
+ **Configure in `app/settings.json`:**
1361
+ ```json
1362
+ {
1363
+ "options": {
1364
+ "mcpServers": {
1365
+ "brave-search": {},
1366
+ "github": {},
1367
+ "slack": {}
1368
+ }
1369
+ }
1370
+ }
1371
+ ```
1372
+
1373
+ **Add required secrets** (Settings → Secrets):
1374
+ - `brave-search`: `BRAVE_API_KEY`
1375
+ - `github`: `GITHUB_PERSONAL_ACCESS_TOKEN`
1376
+ - `slack`: `SLACK_BOT_TOKEN`, `SLACK_TEAM_ID`
1377
+
1378
+ **How it works:**
1379
+ 1. Tools auto-discovered and injected into system prompt
1380
+ 2. LLM outputs: `<mcp_brave-search_brave_web_search>{"query": "..."}</mcp_brave-search_brave_web_search>`
1381
+ 3. Handler in `actions.js`:
1382
+
1383
+ ```javascript
1384
+ [/<mcp_([a-z0-9-]+)_([a-z0-9_]+)>([\s\S]*?)<\/mcp_\1_\2>/s, async (match) => {
1385
+ const [, server, tool, argsJson] = match;
1386
+ const result = await openkbs.mcp.callTool(server, tool, JSON.parse(argsJson || '{}'));
1387
+ return { data: result?.content || [], _meta_actions: ['REQUEST_CHAT_MODEL'] };
1388
+ }]
1389
+ ```
1390
+
1391
+ **Custom MCP Server:**
1392
+ ```json
1393
+ {
1394
+ "options": {
1395
+ "mcpServerUrl": "https://your-mcp-server.com",
1396
+ "mcpServers": { "your-server": {} }
1397
+ }
1398
+ }
1399
+ ```
1400
+
1401
+ Full tutorial: [MCP Integration Tutorial](https://openkbs.com/tutorials/mcp-integration/)
1402
+
771
1403
  ### Frontend
772
1404
 
773
1405
  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.
@@ -781,70 +1413,233 @@ The frontend framework dynamically loads the `contentRender.js` module. This mod
781
1413
 
782
1414
  The `contentRender.js` file is the heart of frontend customization. It can export several key functions:
783
1415
 
784
- - **`onRenderChatMessage(params)`:** This function is called every time a chat message is rendered. It receives an object with various parameters, including:
785
- - `msgIndex`: The index of the message being rendered.
786
- - `messages`: The entire array of chat messages.
787
- - `setMessages`: A function to update the `messages` state.
788
- - `iframeRef`: A reference to the iframe element.
789
- - `KB`: The Knowledge Base object containing application settings.
790
- - `chatContainerRef`: A reference to the chat container element.
791
- - `RequestChatAPI`: A function to send a message to the chat API.
792
- - `setSystemAlert`: A function to display system alerts.
793
- - `setBlockingLoading`: A function to display a loading indicator.
794
- - `blockingLoading`: A boolean indicating if the loading indicator is active.
795
- - `sendButtonRef`: A reference to the send button element.
796
- - `sendButtonRippleRef`: A reference to the send button ripple effect.
797
- - `setInputValue`: A function to set the value of the input field.
798
- - `renderSettings`: An object containing rendering settings.
799
- - `axios`: The axios library for making HTTP requests.
800
- - `itemsAPI`: Functions for manipulating KB items.
801
- - `createEmbeddingItem`: Functions to create embeddings.
802
- - `indexedDB`: IndexedDB wrapper to access data.
803
- - `chatAPI`: API to access chat data.
804
- - `generateMsgId`: Generates a unique message ID.
805
- - `kbUserData`: Function to get KB user data.
806
- - `executeNodejs`: Execute custom JavaScript code inside a VM.
807
-
808
- This function should return a React component representing the rendered message. If not defined, the default rendering mechanism is used.
1416
+ - **`onRenderChatMessage(params)`:** This function is called every time a chat message is rendered. It receives an object with various parameters:
1417
+
1418
+ **Message Context:**
1419
+ - `msgIndex` - Index of the message being rendered
1420
+ - `messages` - Entire array of chat messages
1421
+ - `setMessages` - Function to update the messages state
1422
+ - `KB` - Knowledge Base object containing application settings
1423
+ - `kbUserData` - Function returning KB user data (chatUsername, etc.)
1424
+
1425
+ **UI Controls:**
1426
+ - `setSystemAlert` - Display system alerts: `setSystemAlert({ severity: 'success', message: 'Done!' })`
1427
+ - `setBlockingLoading` - Show/hide loading indicator: `setBlockingLoading(true)`
1428
+ - `blockingLoading` - Boolean indicating if loading indicator is active
1429
+ - `setInputValue` - Set the chat input field value
1430
+ - `blockAutoscroll` - Control auto-scrolling behavior
1431
+
1432
+ **API & Communication:**
1433
+ - `RequestChatAPI` - Send message to chat API: `await RequestChatAPI([...messages, newMsg])`
1434
+ - `axios` - Axios library for HTTP requests
1435
+ - `chatAPI` - API to access chat data
1436
+ - `itemsAPI` - Functions for manipulating KB items
1437
+
1438
+ **DOM References:**
1439
+ - `iframeRef` - Reference to iframe element
1440
+ - `chatContainerRef` - Reference to chat container element
1441
+ - `sendButtonRef` - Reference to send button element
1442
+ - `sendButtonRippleRef` - Reference to send button ripple effect
1443
+ - `newChatButtonRef` - Reference to new chat button
1444
+
1445
+ **Utilities & Libraries:**
1446
+ - `generateMsgId` - Generates unique message ID
1447
+ - `markdownHandler` - Markdown rendering utilities
1448
+ - `createEmbeddingItem` - Functions to create embeddings
1449
+ - `executeNodejs` - Execute custom JavaScript code inside a VM
1450
+ - `initDB` - Initialize IndexedDB
1451
+ - `indexedDB` - IndexedDB wrapper for local data access
1452
+ - `Files` - File management utilities (same as `openkbs.Files`)
1453
+ - `uploadFileAPI` - Upload files to storage
1454
+
1455
+ **Rendering Libraries (pre-loaded):**
1456
+ - `theme` - MUI theme object
1457
+ - `ReactPrismjs` - Syntax highlighting component
1458
+ - `CopyToClipboard` - Clipboard copy component
1459
+ - `APIResponseComponent` - Standard API response renderer
1460
+ - `CodeViewer` - Code display component
1461
+ - `textDetectionsImageFiles` - OCR detection results
1462
+
1463
+ **Return Value:** Return a React component to render the message, or `null` to use default rendering. Return `JSON.stringify({ type: 'HIDDEN_MESSAGE' })` to hide a message completely.
1464
+
1465
+ **Example Usage:**
1466
+ ```javascript
1467
+ const onRenderChatMessage = async (params) => {
1468
+ const { content, role } = params.messages[params.msgIndex];
1469
+ const { msgIndex, messages, setSystemAlert, RequestChatAPI,
1470
+ kbUserData, generateMsgId, markdownHandler } = params;
1471
+
1472
+ // Hide system messages
1473
+ if (role === 'system') {
1474
+ return JSON.stringify({ type: 'HIDDEN_MESSAGE' });
1475
+ }
1476
+
1477
+ // Custom rendering for specific content
1478
+ if (content.includes('<myCommand>')) {
1479
+ return <MyCustomComponent content={content} />;
1480
+ }
1481
+
1482
+ // Send a follow-up message
1483
+ const sendFollowUp = async () => {
1484
+ await RequestChatAPI([...messages, {
1485
+ role: 'user',
1486
+ content: 'Follow up message',
1487
+ userId: kbUserData().chatUsername,
1488
+ msgId: generateMsgId()
1489
+ }]);
1490
+ };
1491
+
1492
+ return null; // Use default rendering
1493
+ };
1494
+ ```
809
1495
 
810
1496
  - **`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.
811
1497
 
812
1498
  - **`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.
813
1499
 
814
- **Example `contentRender.js`:**
1500
+ **Example `contentRender.js` with Command Rendering:**
815
1501
 
816
1502
  ```javascript
817
1503
  import React from 'react';
1504
+ import { Box, Tooltip, Typography, Zoom } from '@mui/material';
1505
+ import SearchIcon from '@mui/icons-material/Search';
1506
+ import ImageIcon from '@mui/icons-material/Image';
1507
+ import VideoLibraryIcon from '@mui/icons-material/VideoLibrary';
1508
+
1509
+ // Define command patterns to detect
1510
+ const COMMAND_PATTERNS = [
1511
+ /<createAIImage>[\s\S]*?<\/createAIImage>/,
1512
+ /<createAIVideo>[\s\S]*?<\/createAIVideo>/,
1513
+ /<googleSearch>[\s\S]*?<\/googleSearch>/
1514
+ ];
818
1515
 
819
- const onRenderChatMessage = async (params) => {
820
- const { content, role } = params.messages[params.msgIndex];
821
- if (role === 'assistant' && content.startsWith('```json')) {
822
- try {
823
- const jsonData = JSON.parse(content.replace('```json', '').replace('```', ''));
824
- return <pre>{JSON.stringify(jsonData, null, 2)}</pre>;
825
- } catch (e) {
826
- console.error('Error parsing JSON:', e);
827
- return null;
1516
+ // Icon mapping for commands
1517
+ const commandIcons = {
1518
+ createAIImage: ImageIcon,
1519
+ createAIVideo: VideoLibraryIcon,
1520
+ googleSearch: SearchIcon
1521
+ };
1522
+
1523
+ // Parse commands from content
1524
+ const parseCommands = (content) => {
1525
+ const commands = [];
1526
+ const regex = /<(\w+)>([\s\S]*?)<\/\1>/g;
1527
+ let match;
1528
+ while ((match = regex.exec(content)) !== null) {
1529
+ try {
1530
+ commands.push({
1531
+ name: match[1],
1532
+ data: JSON.parse(match[2].trim())
1533
+ });
1534
+ } catch (e) {}
828
1535
  }
829
- }
1536
+ return commands;
830
1537
  };
831
1538
 
832
- const Header = ({ setRenderSettings }) => {
833
- // Custom header content
834
- return (
835
- <div>
836
- <h1>Custom Chat Header</h1>
837
- </div>
838
- );
1539
+ // Render command as icon with tooltip
1540
+ const CommandIcon = ({ command }) => {
1541
+ const Icon = commandIcons[command.name] || SearchIcon;
1542
+ return (
1543
+ <Tooltip title={<pre>{JSON.stringify(command.data, null, 2)}</pre>} arrow>
1544
+ <Box sx={{
1545
+ display: 'inline-flex',
1546
+ width: 32, height: 32,
1547
+ borderRadius: '50%',
1548
+ backgroundColor: 'rgba(76, 175, 80, 0.1)',
1549
+ border: '2px solid rgba(76, 175, 80, 0.3)',
1550
+ alignItems: 'center',
1551
+ justifyContent: 'center',
1552
+ mx: 0.5
1553
+ }}>
1554
+ <Icon sx={{ fontSize: 16, color: '#4CAF50' }} />
1555
+ </Box>
1556
+ </Tooltip>
1557
+ );
839
1558
  };
840
1559
 
841
- const onDeleteChatMessage = async (params) => {
842
- // Perform cleanup or other actions on chat message delete
843
- const { chatId, message, itemsAPI, KB, setBlockingLoading } = params;
844
- // Perform action before the message is deleted
1560
+ const onRenderChatMessage = async (params) => {
1561
+ const { content, role } = params.messages[params.msgIndex];
1562
+
1563
+ // Check for commands in content
1564
+ const hasCommand = COMMAND_PATTERNS.some(p => p.test(content));
1565
+ if (hasCommand) {
1566
+ const commands = parseCommands(content);
1567
+ return (
1568
+ <Box>
1569
+ {commands.map((cmd, i) => <CommandIcon key={i} command={cmd} />)}
1570
+ </Box>
1571
+ );
1572
+ }
1573
+
1574
+ return null; // Use default rendering
845
1575
  };
846
1576
 
847
- const exports = { onRenderChatMessage, Header, onDeleteChatMessage };
1577
+ const Header = ({ setRenderSettings, openkbs, setSystemAlert }) => {
1578
+ const [panelOpen, setPanelOpen] = React.useState(false);
1579
+
1580
+ React.useEffect(() => {
1581
+ setRenderSettings({
1582
+ disableBalanceView: false,
1583
+ disableEmojiButton: true,
1584
+ backgroundOpacity: 0.02
1585
+ });
1586
+ }, [setRenderSettings]);
1587
+
1588
+ return (
1589
+ <>
1590
+ {/* Settings button */}
1591
+ <Box
1592
+ onClick={() => setPanelOpen(true)}
1593
+ sx={{
1594
+ position: 'absolute',
1595
+ top: 90, left: 340,
1596
+ width: 40, height: 40,
1597
+ borderRadius: '50%',
1598
+ backgroundColor: 'white',
1599
+ border: '1px solid #e0e0e0',
1600
+ display: 'flex',
1601
+ alignItems: 'center',
1602
+ justifyContent: 'center',
1603
+ cursor: 'pointer',
1604
+ zIndex: 1200
1605
+ }}
1606
+ >
1607
+ ⚙️
1608
+ </Box>
1609
+
1610
+ {/* Simple settings panel */}
1611
+ {panelOpen && (
1612
+ <Box
1613
+ onClick={() => setPanelOpen(false)}
1614
+ sx={{
1615
+ position: 'fixed',
1616
+ inset: 0,
1617
+ backgroundColor: 'rgba(0,0,0,0.5)',
1618
+ zIndex: 1300,
1619
+ display: 'flex',
1620
+ alignItems: 'center',
1621
+ justifyContent: 'center'
1622
+ }}
1623
+ >
1624
+ <Box
1625
+ onClick={e => e.stopPropagation()}
1626
+ sx={{
1627
+ width: 400,
1628
+ backgroundColor: 'white',
1629
+ borderRadius: 2,
1630
+ p: 3
1631
+ }}
1632
+ >
1633
+ <Typography variant="h6">Settings</Typography>
1634
+ <Typography>Your custom admin panel here</Typography>
1635
+ </Box>
1636
+ </Box>
1637
+ )}
1638
+ </>
1639
+ );
1640
+ };
1641
+
1642
+ const exports = { onRenderChatMessage, Header };
848
1643
  window.contentRender = exports;
849
1644
  export default exports;
850
1645
  ```
@@ -1043,6 +1838,499 @@ const onRenderChatMessage = async (params) => {
1043
1838
  };
1044
1839
  ```
1045
1840
 
1841
+ #### Header Component Props
1842
+
1843
+ The `Header` component receives all available props from the OpenKBS chat interface. These props enable full control over chat behavior, UI state, and data access.
1844
+
1845
+ **All Available Props:**
1846
+
1847
+ - `messages` (Array) - Current chat messages array
1848
+ - `setMessages` (Function) - Update messages array (e.g., add welcome message)
1849
+ - `KB` (Object) - Current Knowledge Base data
1850
+ - `openkbs` (Object) - OpenKBS SDK object (see Frontend SDK section)
1851
+ - `setRenderSettings` (Function) - Configure UI rendering options
1852
+ - `setSystemAlert` (Function) - Show alert notifications
1853
+ - `setBlockingLoading` (Function) - Show/hide full-screen loading overlay
1854
+ - `RequestChatAPI` (Function) - Send messages to chat API
1855
+ - `chatAPI` (Object) - Chat API utilities
1856
+ - `itemsAPI` (Object) - Items API (low-level)
1857
+ - `Files` (Object) - Files API module
1858
+ - `indexedDB` (Object) - IndexedDB interface for local storage
1859
+ - `initDB` (Function) - Initialize IndexedDB
1860
+ - `kbUserData` (Function) - Get current user data
1861
+ - `navigateToChat` (Function) - Navigate to different chat
1862
+ - `setIsContextItemsOpen` (Function) - Toggle context items panel
1863
+ - `isContextItemsOpen` (Boolean) - Context items panel state
1864
+ - `chatContainerRef` (Ref) - Reference to chat container element
1865
+ - `renderSettings` (Object) - Current render settings
1866
+ - `blockingLoading` (Boolean) - Loading state
1867
+ - `blockAutoscroll` (Boolean) - Auto-scroll state
1868
+ - `axios` (Object) - Axios HTTP client
1869
+
1870
+ **Common Usage Patterns:**
1871
+
1872
+ *setRenderSettings* - Configure UI behavior:
1873
+ ```javascript
1874
+ const Header = ({ setRenderSettings }) => {
1875
+ useEffect(() => {
1876
+ setRenderSettings({
1877
+ disableBalanceView: false,
1878
+ disableEmojiButton: true,
1879
+ disableChatModelsSelect: true,
1880
+ disableShareButton: true,
1881
+ disableMultichat: true,
1882
+ disableMobileLeftButton: true,
1883
+ disableTextToSpeechButton: true,
1884
+ disableInitialScroll: true,
1885
+ backgroundOpacity: 0.02,
1886
+ customStreamingLoader: true,
1887
+ inputLabelsQuickSend: true,
1888
+ setMessageWidth: (content) => content.includes('<html') ? '90%' : undefined
1889
+ });
1890
+ }, [setRenderSettings]);
1891
+ };
1892
+ ```
1893
+
1894
+ *setSystemAlert* - Show notifications:
1895
+ ```javascript
1896
+ const Header = ({ setSystemAlert }) => {
1897
+ const showSuccess = () => {
1898
+ setSystemAlert({
1899
+ msg: 'Operation completed successfully',
1900
+ type: 'success', // 'success', 'error', 'warning', 'info'
1901
+ duration: 3000 // milliseconds
1902
+ });
1903
+ };
1904
+
1905
+ const showError = (error) => {
1906
+ setSystemAlert({
1907
+ msg: error.message,
1908
+ type: 'error',
1909
+ duration: 5000
1910
+ });
1911
+ };
1912
+ };
1913
+ ```
1914
+
1915
+ *setBlockingLoading* - Full-screen loading overlay:
1916
+ ```javascript
1917
+ const Header = ({ setBlockingLoading, openkbs }) => {
1918
+ const loadData = async () => {
1919
+ setBlockingLoading(true); // Show loading
1920
+ // Or with custom text:
1921
+ // setBlockingLoading({ text: 'Loading files...' });
1922
+
1923
+ try {
1924
+ const files = await openkbs.Files.listFiles('files');
1925
+ // Process files...
1926
+ } finally {
1927
+ setBlockingLoading(false); // Hide loading
1928
+ }
1929
+ };
1930
+ };
1931
+ ```
1932
+
1933
+ *RequestChatAPI* - Send messages programmatically:
1934
+ ```javascript
1935
+ const Header = ({ RequestChatAPI, messages, kbUserData, navigateToChat }) => {
1936
+ const sendQuickMessage = (prompt) => {
1937
+ navigateToChat(null); // Start new chat
1938
+ RequestChatAPI([{
1939
+ role: 'user',
1940
+ content: prompt,
1941
+ userId: kbUserData().chatUsername,
1942
+ msgId: `${Date.now()}-${Math.floor(100000 + Math.random() * 900000)}`
1943
+ }]);
1944
+ };
1945
+
1946
+ return (
1947
+ <button onClick={() => sendQuickMessage('Help me create a marketing plan')}>
1948
+ Quick Action
1949
+ </button>
1950
+ );
1951
+ };
1952
+ ```
1953
+
1954
+ *setMessages* - Initialize welcome message:
1955
+ ```javascript
1956
+ const Header = ({ messages, setMessages, openkbs }) => {
1957
+ useEffect(() => {
1958
+ const initWelcome = async () => {
1959
+ // Only for new chats
1960
+ if (!messages || messages.length === 0) {
1961
+ const profile = await openkbs.getItem('memory_profile');
1962
+
1963
+ if (!profile?.item) {
1964
+ setMessages([{
1965
+ msgId: `${Date.now()}-${Math.floor(100000 + Math.random() * 900000)}`,
1966
+ role: 'assistant',
1967
+ content: 'Welcome! Tell me about your business to get started.'
1968
+ }]);
1969
+ }
1970
+ }
1971
+ };
1972
+
1973
+ initWelcome();
1974
+ }, [messages, setMessages, openkbs]);
1975
+ };
1976
+ ```
1977
+
1978
+ *navigateToChat* - Chat navigation:
1979
+ ```javascript
1980
+ const Header = ({ navigateToChat }) => {
1981
+ // Navigate to specific chat
1982
+ const goToChat = (chatId) => navigateToChat(chatId);
1983
+
1984
+ // Start new chat
1985
+ const newChat = () => navigateToChat(null);
1986
+ };
1987
+ ```
1988
+
1989
+ **Complete Header Example:**
1990
+
1991
+ ```javascript
1992
+ import React, { useEffect, useState } from 'react';
1993
+ import { IconButton, Dialog, Box, Button } from '@mui/material';
1994
+ import SettingsIcon from '@mui/icons-material/Settings';
1995
+
1996
+ const Header = ({
1997
+ setRenderSettings,
1998
+ openkbs,
1999
+ setSystemAlert,
2000
+ setBlockingLoading,
2001
+ messages,
2002
+ setMessages,
2003
+ RequestChatAPI,
2004
+ kbUserData,
2005
+ navigateToChat
2006
+ }) => {
2007
+ const [panelOpen, setPanelOpen] = useState(false);
2008
+ const [files, setFiles] = useState([]);
2009
+
2010
+ // Configure UI on mount
2011
+ useEffect(() => {
2012
+ setRenderSettings({
2013
+ disableEmojiButton: true,
2014
+ disableChatModelsSelect: true,
2015
+ backgroundOpacity: 0.02
2016
+ });
2017
+ }, [setRenderSettings]);
2018
+
2019
+ // Initialize welcome message for new chats
2020
+ useEffect(() => {
2021
+ if (!messages?.length && openkbs) {
2022
+ setMessages([{
2023
+ msgId: `${Date.now()}-000000`,
2024
+ role: 'assistant',
2025
+ content: 'Welcome! How can I help you today?'
2026
+ }]);
2027
+ }
2028
+ }, [messages, setMessages, openkbs]);
2029
+
2030
+ // Load files when panel opens
2031
+ const loadFiles = async () => {
2032
+ setBlockingLoading(true);
2033
+ try {
2034
+ const result = await openkbs.Files.listFiles('files');
2035
+ setFiles(result || []);
2036
+ } catch (e) {
2037
+ setSystemAlert({ msg: e.message, type: 'error', duration: 5000 });
2038
+ } finally {
2039
+ setBlockingLoading(false);
2040
+ }
2041
+ };
2042
+
2043
+ // Quick action handler
2044
+ const quickAction = (prompt) => {
2045
+ navigateToChat(null);
2046
+ RequestChatAPI([{
2047
+ role: 'user',
2048
+ content: prompt,
2049
+ userId: kbUserData().chatUsername,
2050
+ msgId: `${Date.now()}-${Math.floor(Math.random() * 900000)}`
2051
+ }]);
2052
+ };
2053
+
2054
+ return (
2055
+ <>
2056
+ <IconButton
2057
+ onClick={() => { setPanelOpen(true); loadFiles(); }}
2058
+ sx={{ position: 'absolute', top: 90, left: 340, zIndex: 1200 }}
2059
+ >
2060
+ <SettingsIcon />
2061
+ </IconButton>
2062
+
2063
+ <Button
2064
+ onClick={() => quickAction('Create a marketing plan')}
2065
+ sx={{ position: 'absolute', top: 90, left: 400, zIndex: 1200 }}
2066
+ >
2067
+ Quick Action
2068
+ </Button>
2069
+
2070
+ <Dialog open={panelOpen} onClose={() => setPanelOpen(false)}>
2071
+ <Box sx={{ p: 2 }}>
2072
+ <h3>Files ({files.length})</h3>
2073
+ {files.map((f, i) => <div key={i}>{f.name}</div>)}
2074
+ </Box>
2075
+ </Dialog>
2076
+ </>
2077
+ );
2078
+ };
2079
+
2080
+ const exports = { Header };
2081
+ window.contentRender = exports;
2082
+ export default exports;
2083
+ ```
2084
+
2085
+ #### Frontend SDK (`openkbs` object)
2086
+
2087
+ The `openkbs` object is passed to `Header` and `onRenderChatMessage` functions, providing access to item storage, file management, and KB sharing APIs.
2088
+
2089
+ **Properties:**
2090
+ ```javascript
2091
+ const Header = ({ openkbs }) => {
2092
+ console.log(openkbs.kbId); // Current KB ID
2093
+ console.log(openkbs.KBData); // Full KB configuration
2094
+ console.log(openkbs.Files); // Files API module
2095
+ console.log(openkbs.itemsAPI); // Items API module (low-level)
2096
+ console.log(openkbs.KBAPI); // KB API module
2097
+ };
2098
+ ```
2099
+
2100
+ ##### Item CRUD Operations
2101
+
2102
+ ```javascript
2103
+ // Create an item
2104
+ await openkbs.createItem({
2105
+ itemType: 'memory',
2106
+ itemId: 'memory_user_preferences',
2107
+ body: { theme: 'dark', language: 'en' } // Auto-encrypted
2108
+ });
2109
+
2110
+ // Update an item
2111
+ await openkbs.updateItem({
2112
+ itemType: 'memory',
2113
+ itemId: 'memory_user_preferences',
2114
+ body: { theme: 'light', language: 'bg' }
2115
+ });
2116
+
2117
+ // Get a single item (auto-decrypted)
2118
+ const result = await openkbs.getItem('memory_user_preferences');
2119
+ console.log(result.item.body); // { theme: 'light', language: 'bg' }
2120
+
2121
+ // Delete an item
2122
+ await openkbs.deleteItem('memory_user_preferences');
2123
+
2124
+ // Fetch multiple items with filters
2125
+ const items = await openkbs.fetchItems({
2126
+ itemType: 'memory', // Filter by item type
2127
+ limit: 100, // Max items to return
2128
+ beginsWith: 'memory_', // Filter by ID prefix
2129
+ from: '2024-01-01', // Range start (for date fields)
2130
+ to: '2024-12-31', // Range end
2131
+ field: 'createdAt', // Field for range query
2132
+ });
2133
+
2134
+ // Items are returned with decrypted bodies
2135
+ items.items.forEach(({ item, meta }) => {
2136
+ console.log(meta.itemId, item.body);
2137
+ });
2138
+ ```
2139
+
2140
+ ##### Encryption Utilities
2141
+
2142
+ ```javascript
2143
+ // Encrypt sensitive data
2144
+ const encrypted = await openkbs.encrypt('sensitive data');
2145
+
2146
+ // Decrypt data
2147
+ const decrypted = await openkbs.decrypt(encrypted);
2148
+ ```
2149
+
2150
+ ##### Files API (`openkbs.Files`)
2151
+
2152
+ ```javascript
2153
+ // List files in a namespace
2154
+ const files = await openkbs.Files.listFiles('files');
2155
+
2156
+ // Upload a file
2157
+ const file = new File(['content'], 'example.txt', { type: 'text/plain' });
2158
+ await openkbs.Files.uploadFileAPI(file, 'files', (progress) => {
2159
+ console.log(`Upload progress: ${progress}%`);
2160
+ });
2161
+
2162
+ // Create presigned URL for upload/download
2163
+ const url = await openkbs.Files.createPresignedURL(
2164
+ 'files', // namespace
2165
+ 'putObject', // 'putObject' or 'getObject'
2166
+ 'myfile.pdf', // filename
2167
+ 'application/pdf' // content type
2168
+ );
2169
+
2170
+ // Delete a file
2171
+ await openkbs.Files.deleteRawKBFile('filename.txt', 'files');
2172
+
2173
+ // Rename a file
2174
+ await openkbs.Files.renameFile(
2175
+ 'files/kbId/old-name.txt', // old path
2176
+ 'new-name.txt', // new name
2177
+ 'files' // namespace
2178
+ );
2179
+
2180
+ // Secrets management
2181
+ const secrets = await openkbs.Files.listSecrets();
2182
+ await openkbs.Files.createSecretWithKBToken('API_KEY', 'secret-value');
2183
+ await openkbs.Files.deleteSecret('API_KEY');
2184
+
2185
+ // File versions
2186
+ const versions = await openkbs.Files.listVersions('myfile.js', 'functions');
2187
+ await openkbs.Files.restoreVersion('version-id', 'myfile.js', 'functions');
2188
+ ```
2189
+
2190
+ ##### KB API (`openkbs.KBAPI`)
2191
+
2192
+ ```javascript
2193
+ // Get KB configuration
2194
+ const kb = await openkbs.KBAPI.getKB();
2195
+
2196
+ // Share KB with another user (by email)
2197
+ await openkbs.KBAPI.shareKBWith('user@example.com');
2198
+
2199
+ // Get list of users KB is shared with
2200
+ const shares = await openkbs.KBAPI.getKBShares();
2201
+ // Returns: { sharedWith: ['user1@example.com', 'user2@example.com'] }
2202
+
2203
+ // Remove share
2204
+ await openkbs.KBAPI.unshareKBWith('user@example.com');
2205
+
2206
+ // API Keys management
2207
+ const keys = await openkbs.KBAPI.getAPIKeys();
2208
+ const newKey = await openkbs.KBAPI.createAPIKey('My API Key', { resources: '*' });
2209
+ await openkbs.KBAPI.deleteAPIKey('api-key-id');
2210
+
2211
+ // Update KB variable
2212
+ await openkbs.KBAPI.updateVariable('customSetting', 'value');
2213
+
2214
+ // Search items (semantic search if embeddings enabled)
2215
+ const results = await openkbs.KBAPI.searchItems('search query', openkbs.kbId, 100);
2216
+ ```
2217
+
2218
+ ##### Complete Header Example with Admin Panel
2219
+
2220
+ ```javascript
2221
+ import React, { useState, useEffect } from 'react';
2222
+ import { Box, IconButton, Dialog, Tabs, Tab, List, ListItem,
2223
+ ListItemText, TextField, Button, Typography } from '@mui/material';
2224
+ import SettingsIcon from '@mui/icons-material/Settings';
2225
+
2226
+ const Header = ({ openkbs, setRenderSettings, setSystemAlert, setBlockingLoading }) => {
2227
+ const [open, setOpen] = useState(false);
2228
+ const [tab, setTab] = useState(0);
2229
+ const [files, setFiles] = useState([]);
2230
+ const [shares, setShares] = useState([]);
2231
+ const [email, setEmail] = useState('');
2232
+
2233
+ useEffect(() => {
2234
+ setRenderSettings({
2235
+ disableEmojiButton: true,
2236
+ backgroundOpacity: 0.02
2237
+ });
2238
+ }, []);
2239
+
2240
+ // Load files
2241
+ const loadFiles = async () => {
2242
+ setBlockingLoading(true);
2243
+ try {
2244
+ const result = await openkbs.Files.listFiles('files');
2245
+ setFiles(result || []);
2246
+ } finally {
2247
+ setBlockingLoading(false);
2248
+ }
2249
+ };
2250
+
2251
+ // Load shares
2252
+ const loadShares = async () => {
2253
+ const result = await openkbs.KBAPI.getKBShares();
2254
+ setShares(result?.sharedWith || []);
2255
+ };
2256
+
2257
+ // Share with user
2258
+ const shareWith = async () => {
2259
+ try {
2260
+ await openkbs.KBAPI.shareKBWith(email);
2261
+ setSystemAlert({ msg: `Shared with ${email}`, type: 'success', duration: 3000 });
2262
+ setEmail('');
2263
+ loadShares();
2264
+ } catch (e) {
2265
+ setSystemAlert({ msg: e.message, type: 'error', duration: 5000 });
2266
+ }
2267
+ };
2268
+
2269
+ useEffect(() => {
2270
+ if (open && tab === 0) loadFiles();
2271
+ if (open && tab === 1) loadShares();
2272
+ }, [open, tab]);
2273
+
2274
+ return (
2275
+ <>
2276
+ <IconButton
2277
+ onClick={() => setOpen(true)}
2278
+ sx={{ position: 'absolute', top: 80, right: 20, zIndex: 1200 }}
2279
+ >
2280
+ <SettingsIcon />
2281
+ </IconButton>
2282
+
2283
+ <Dialog open={open} onClose={() => setOpen(false)} maxWidth="md" fullWidth>
2284
+ <Box sx={{ p: 2 }}>
2285
+ <Typography variant="h6">Settings</Typography>
2286
+ <Tabs value={tab} onChange={(e, v) => setTab(v)}>
2287
+ <Tab label="Files" />
2288
+ <Tab label="Sharing" />
2289
+ </Tabs>
2290
+
2291
+ {tab === 0 && (
2292
+ <List>
2293
+ {files.map((file, i) => (
2294
+ <ListItem key={i}>
2295
+ <ListItemText primary={file.name} />
2296
+ </ListItem>
2297
+ ))}
2298
+ </List>
2299
+ )}
2300
+
2301
+ {tab === 1 && (
2302
+ <Box>
2303
+ <Box sx={{ display: 'flex', gap: 1, my: 2 }}>
2304
+ <TextField
2305
+ fullWidth
2306
+ label="Email"
2307
+ value={email}
2308
+ onChange={(e) => setEmail(e.target.value)}
2309
+ />
2310
+ <Button variant="contained" onClick={shareWith}>
2311
+ Share
2312
+ </Button>
2313
+ </Box>
2314
+ <List>
2315
+ {shares.map((s, i) => (
2316
+ <ListItem key={i}>
2317
+ <ListItemText primary={s} />
2318
+ </ListItem>
2319
+ ))}
2320
+ </List>
2321
+ </Box>
2322
+ )}
2323
+ </Box>
2324
+ </Dialog>
2325
+ </>
2326
+ );
2327
+ };
2328
+
2329
+ const exports = { Header };
2330
+ window.contentRender = exports;
2331
+ export default exports;
2332
+ ```
2333
+
1046
2334
  ## API
1047
2335
 
1048
2336
  OpenKBS provides APIs to interact programmatically with your application. These APIs allow you to perform actions like starting new chats, retrieving chat messages, and managing data within your application. Data exchanged with these APIs is encrypted and decrypted using AES-256 encryption.
@@ -1387,4 +2675,4 @@ We welcome contributions from the community! Please feel free to submit issues,
1387
2675
 
1388
2676
  ## Contact
1389
2677
 
1390
- For more information, visit our [official website](https://openkbs.com) or join our community discussions on [GitHub](https://github.com/open-kbs/openkbs/discussions).
2678
+ For more information, visit our [official website](https://openkbs.com) or join our community discussions on [GitHub](https://github.com/open-kbs/openkbs/discussions).