openkbs 0.0.82 → 0.0.83
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/package.json +1 -1
- package/src/actions.js +58 -0
- package/src/index.js +6 -0
- package/templates/.claude/skills/openkbs/metadata.json +1 -1
- package/templates/.claude/skills/openkbs/patterns/file-upload.md +59 -12
- package/templates/.claude/skills/openkbs/reference/backend-sdk.md +170 -16
- package/templates/.claude/skills/openkbs/reference/frontend-sdk.md +106 -3
- package/version.json +3 -3
package/package.json
CHANGED
package/src/actions.js
CHANGED
|
@@ -307,6 +307,63 @@ async function loginAction() {
|
|
|
307
307
|
});
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
+
async function authAction(kbJWT) {
|
|
311
|
+
try {
|
|
312
|
+
// Decode JWT to validate format (without verification - that's done server-side)
|
|
313
|
+
const parts = kbJWT.split('.');
|
|
314
|
+
if (parts.length !== 3) {
|
|
315
|
+
return console.red('Invalid token format. Expected a JWT token.');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
|
|
319
|
+
|
|
320
|
+
// Basic validation
|
|
321
|
+
if (!payload.kbUserId || !payload.myUserId) {
|
|
322
|
+
return console.red('Invalid token. Missing required fields (kbUserId, myUserId).');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!payload.serverURL) {
|
|
326
|
+
return console.red('Invalid token. Missing serverURL field. Token must be from a hosted studio.');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (payload.kbUserId !== payload.myUserId) {
|
|
330
|
+
return console.red('Only KB owners can use this command. The token must be from the KB owner.');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
console.log(`Server: ${payload.serverURL}`);
|
|
334
|
+
|
|
335
|
+
console.log('Exchanging kbJWT for clientJWT...');
|
|
336
|
+
|
|
337
|
+
// Call lambda-auth to exchange the token
|
|
338
|
+
const response = await fetch('https://auth.openkbs.com/exchangeKbToken', {
|
|
339
|
+
method: 'POST',
|
|
340
|
+
headers: { 'Content-Type': 'application/json' },
|
|
341
|
+
body: JSON.stringify({ token: kbJWT })
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
const error = await response.json();
|
|
346
|
+
return console.red(`Authentication failed: ${error.error || error.message || 'Unknown error'}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const data = await response.json();
|
|
350
|
+
|
|
351
|
+
if (!data.clientJWT) {
|
|
352
|
+
return console.red('Authentication failed: No clientJWT received.');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Save the clientJWT
|
|
356
|
+
await fs.ensureDir(path.dirname(jwtPath));
|
|
357
|
+
await fs.writeFile(jwtPath, data.clientJWT, 'utf8');
|
|
358
|
+
|
|
359
|
+
console.green('Authentication successful! CLI is now authenticated.');
|
|
360
|
+
console.log('You can now use all OpenKBS CLI commands.');
|
|
361
|
+
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.red(`Authentication failed: ${error.message}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
310
367
|
async function pullAction(location = 'origin', targetFile) {
|
|
311
368
|
try {
|
|
312
369
|
// Remove './' prefix if present
|
|
@@ -2759,6 +2816,7 @@ module.exports = {
|
|
|
2759
2816
|
signAction,
|
|
2760
2817
|
serviceAction,
|
|
2761
2818
|
loginAction,
|
|
2819
|
+
authAction,
|
|
2762
2820
|
pullAction,
|
|
2763
2821
|
pushAction,
|
|
2764
2822
|
cloneAction,
|
package/src/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
signAction,
|
|
7
7
|
serviceAction,
|
|
8
8
|
loginAction,
|
|
9
|
+
authAction,
|
|
9
10
|
pullAction,
|
|
10
11
|
pushAction,
|
|
11
12
|
cloneAction,
|
|
@@ -51,6 +52,11 @@ program
|
|
|
51
52
|
.description('Login to OpenKBS and store session locally.')
|
|
52
53
|
.action(loginAction);
|
|
53
54
|
|
|
55
|
+
program
|
|
56
|
+
.command('auth <kbJWT>')
|
|
57
|
+
.description('Authenticate CLI using a kbJWT token (for container/CI environments)')
|
|
58
|
+
.action(authAction);
|
|
59
|
+
|
|
54
60
|
program
|
|
55
61
|
.command('create <app-name>')
|
|
56
62
|
.description('Create a new KB application')
|
|
@@ -157,25 +157,72 @@ const uploadHtmlContent = async (htmlContent, filename) => {
|
|
|
157
157
|
|
|
158
158
|
## Frontend File Upload (contentRender.js)
|
|
159
159
|
|
|
160
|
+
**IMPORTANT:** `openkbs` is passed as a **prop** to components - it's NOT global!
|
|
161
|
+
|
|
160
162
|
```javascript
|
|
161
|
-
//
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
163
|
+
// File upload button component - receives openkbs as prop
|
|
164
|
+
const FileUploadButton = ({ openkbs, setSystemAlert }) => {
|
|
165
|
+
const [uploading, setUploading] = useState(false);
|
|
166
|
+
const [progress, setProgress] = useState(0);
|
|
167
|
+
const fileInputRef = useRef(null);
|
|
168
|
+
|
|
169
|
+
const handleUpload = async (e) => {
|
|
170
|
+
const file = e.target.files?.[0];
|
|
171
|
+
if (!file) return;
|
|
172
|
+
|
|
173
|
+
setUploading(true);
|
|
174
|
+
try {
|
|
175
|
+
// Upload file to storage with progress callback
|
|
176
|
+
await openkbs.Files.uploadFileAPI(file, 'files', (percent) => {
|
|
177
|
+
setProgress(percent);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
setSystemAlert?.({ severity: 'success', message: `Uploaded ${file.name}` });
|
|
181
|
+
} catch (error) {
|
|
182
|
+
setSystemAlert?.({ severity: 'error', message: error.message });
|
|
183
|
+
} finally {
|
|
184
|
+
setUploading(false);
|
|
185
|
+
setProgress(0);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<div>
|
|
191
|
+
<button onClick={() => fileInputRef.current?.click()} disabled={uploading}>
|
|
192
|
+
{uploading ? `Uploading ${progress}%` : 'Upload File'}
|
|
193
|
+
</button>
|
|
194
|
+
<input
|
|
195
|
+
ref={fileInputRef}
|
|
196
|
+
type="file"
|
|
197
|
+
onChange={handleUpload}
|
|
198
|
+
style={{ display: 'none' }}
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Header must pass openkbs to child components
|
|
205
|
+
const Header = ({ setRenderSettings, openkbs, setSystemAlert }) => {
|
|
206
|
+
return (
|
|
207
|
+
<div>
|
|
208
|
+
<FileUploadButton openkbs={openkbs} setSystemAlert={setSystemAlert} />
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
171
211
|
};
|
|
212
|
+
```
|
|
172
213
|
|
|
173
|
-
|
|
214
|
+
### Other Files API Methods
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
// List files (inside a component that receives openkbs as prop)
|
|
174
218
|
const files = await openkbs.Files.listFiles('files');
|
|
175
|
-
// Returns: [{ Key: 'files/kbId/filename.jpg', Size: 12345 }]
|
|
219
|
+
// Returns: [{ Key: 'files/kbId/filename.jpg', Size: 12345, LastModified }]
|
|
176
220
|
|
|
177
221
|
// Delete file
|
|
178
222
|
await openkbs.Files.deleteRawKBFile('filename.jpg', 'files');
|
|
223
|
+
|
|
224
|
+
// Rename file
|
|
225
|
+
await openkbs.Files.renameFile('old.jpg', 'new.jpg', 'files');
|
|
179
226
|
```
|
|
180
227
|
|
|
181
228
|
## instructions.txt (LLM prompt)
|
|
@@ -364,43 +364,197 @@ await axios.put(presignedUrl, fileBuffer, {
|
|
|
364
364
|
|
|
365
365
|
## VectorDB (Semantic Search)
|
|
366
366
|
|
|
367
|
-
### items() -
|
|
367
|
+
### items() - Create Items with Filterable Attributes
|
|
368
368
|
|
|
369
|
-
|
|
369
|
+
For filtering to work, items must be created with filterable attributes (keyword, integer, boolean, etc.).
|
|
370
370
|
|
|
371
371
|
```javascript
|
|
372
|
-
// Create embeddings
|
|
372
|
+
// 1. Create embeddings from content
|
|
373
373
|
const { embeddings, totalTokens } = await openkbs.createEmbeddings(
|
|
374
|
-
'
|
|
375
|
-
'text-embedding-3-large'
|
|
374
|
+
'Fix login bug - users cannot login with SSO',
|
|
375
|
+
'text-embedding-3-large'
|
|
376
376
|
);
|
|
377
377
|
|
|
378
|
-
// Create item with
|
|
378
|
+
// 2. Create item with filterable attributes
|
|
379
379
|
await openkbs.items({
|
|
380
380
|
action: 'createItem',
|
|
381
|
-
itemType: '
|
|
382
|
-
itemId:
|
|
381
|
+
itemType: 'task',
|
|
382
|
+
itemId: `task_${Date.now()}`,
|
|
383
383
|
attributes: [
|
|
384
384
|
{ attrType: 'itemId', attrName: 'itemId', encrypted: false },
|
|
385
|
-
{ attrType: 'body', attrName: 'body', encrypted: true }
|
|
385
|
+
{ attrType: 'body', attrName: 'body', encrypted: true },
|
|
386
|
+
// Filterable attributes - map user-friendly names to generic types
|
|
387
|
+
{ attrType: 'keyword1', attrName: 'category', encrypted: false },
|
|
388
|
+
{ attrType: 'keyword2', attrName: 'status', encrypted: false },
|
|
389
|
+
{ attrType: 'integer1', attrName: 'priority', encrypted: false },
|
|
390
|
+
{ attrType: 'boolean1', attrName: 'isUrgent', encrypted: false }
|
|
386
391
|
],
|
|
387
|
-
item: {
|
|
392
|
+
item: {
|
|
393
|
+
body: await openkbs.encrypt(JSON.stringify({
|
|
394
|
+
title: 'Fix login bug',
|
|
395
|
+
description: 'Users cannot login with SSO'
|
|
396
|
+
})),
|
|
397
|
+
// Filterable field values
|
|
398
|
+
category: 'bug',
|
|
399
|
+
status: 'open',
|
|
400
|
+
priority: 1,
|
|
401
|
+
isUrgent: true
|
|
402
|
+
},
|
|
388
403
|
totalTokens,
|
|
389
404
|
embeddings: embeddings.slice(0, 3072),
|
|
390
405
|
embeddingModel: 'text-embedding-3-large',
|
|
391
406
|
embeddingDimension: 3072
|
|
392
407
|
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Filterable Attribute Types
|
|
411
|
+
|
|
412
|
+
| attrType | S3 Vectors Type | Operators |
|
|
413
|
+
|----------|-----------------|-----------|
|
|
414
|
+
| keyword1-20 | String | $eq, $ne, $in, $nin |
|
|
415
|
+
| text1-20 | String | $eq, $ne, $in, $nin |
|
|
416
|
+
| integer1-20 | Number | $eq, $ne, $gt, $gte, $lt, $lte |
|
|
417
|
+
| float1-20 | Number | $eq, $ne, $gt, $gte, $lt, $lte |
|
|
418
|
+
| boolean1-9 | Boolean | $eq, $ne |
|
|
419
|
+
| date1-9 | Number | $gt, $gte, $lt, $lte |
|
|
420
|
+
|
|
421
|
+
### Vector Metadata Limits
|
|
393
422
|
|
|
394
|
-
|
|
423
|
+
S3 Vectors has two types of metadata:
|
|
424
|
+
- **Filterable** (keyword1, integer1, etc.): 2KB limit - used for filtering in queries
|
|
425
|
+
- **Non-filterable** (itemData): 40KB limit - stored for retrieval only
|
|
426
|
+
|
|
427
|
+
By default, `itemData` is configured as non-filterable, so full item content (up to 40KB) is stored and returned in search results.
|
|
428
|
+
|
|
429
|
+
### vectorMetadata - Optional Override (Advanced)
|
|
430
|
+
|
|
431
|
+
For items > 40KB, use `vectorMetadata` to control what gets stored in S3 Vectors. The full item is always stored in DynamoDB.
|
|
432
|
+
|
|
433
|
+
```javascript
|
|
434
|
+
// Very large document (> 40KB) - store only essential fields in vector
|
|
435
|
+
await openkbs.items({
|
|
436
|
+
action: 'createItem',
|
|
437
|
+
itemType: 'document',
|
|
438
|
+
itemId: `doc_${Date.now()}`,
|
|
439
|
+
attributes: [...],
|
|
440
|
+
item: {
|
|
441
|
+
body: JSON.stringify({ fileName, fileUrl, hugeText }), // Full in DynamoDB
|
|
442
|
+
standard: 'IFS'
|
|
443
|
+
},
|
|
444
|
+
// Only these fields in S3 Vectors (for items > 40KB)
|
|
445
|
+
vectorMetadata: {
|
|
446
|
+
standard: 'IFS',
|
|
447
|
+
fileName: 'large-doc.pdf',
|
|
448
|
+
textPreview: hugeText.substring(0, 5000)
|
|
449
|
+
},
|
|
450
|
+
totalTokens,
|
|
451
|
+
embeddings,
|
|
452
|
+
embeddingModel: 'text-embedding-3-large',
|
|
453
|
+
embeddingDimension: 3072
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Note**: For most use cases (items < 40KB), `vectorMetadata` is not needed.
|
|
458
|
+
|
|
459
|
+
### searchVectorDBItems - Vector Search with Filter
|
|
460
|
+
|
|
461
|
+
```javascript
|
|
462
|
+
// Simple search (no filter)
|
|
395
463
|
const results = await openkbs.items({
|
|
396
464
|
action: 'searchVectorDBItems',
|
|
397
|
-
queryText: '
|
|
398
|
-
topK: 10
|
|
399
|
-
minScore: 0
|
|
465
|
+
queryText: 'login issues',
|
|
466
|
+
topK: 10
|
|
400
467
|
});
|
|
401
468
|
|
|
402
|
-
//
|
|
403
|
-
|
|
469
|
+
// Search with filter - use user-friendly field names
|
|
470
|
+
const filtered = await openkbs.items({
|
|
471
|
+
action: 'searchVectorDBItems',
|
|
472
|
+
queryText: 'login authentication issues',
|
|
473
|
+
topK: 20,
|
|
474
|
+
minScore: 70,
|
|
475
|
+
itemType: 'task', // Required for filter translation!
|
|
476
|
+
filter: {
|
|
477
|
+
category: 'bug',
|
|
478
|
+
status: 'open'
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Comparison operators
|
|
483
|
+
const highPriority = await openkbs.items({
|
|
484
|
+
action: 'searchVectorDBItems',
|
|
485
|
+
queryText: 'performance problems',
|
|
486
|
+
itemType: 'task',
|
|
487
|
+
filter: {
|
|
488
|
+
priority: { "$lte": 2 },
|
|
489
|
+
status: { "$ne": "done" }
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// Complex filter with $or
|
|
494
|
+
const urgentOrHighPriority = await openkbs.items({
|
|
495
|
+
action: 'searchVectorDBItems',
|
|
496
|
+
queryText: 'critical issues',
|
|
497
|
+
itemType: 'task',
|
|
498
|
+
filter: {
|
|
499
|
+
"$or": [
|
|
500
|
+
{ priority: 1 },
|
|
501
|
+
{ isUrgent: true }
|
|
502
|
+
]
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// $in operator - match any value in array
|
|
507
|
+
const multipleCats = await openkbs.items({
|
|
508
|
+
action: 'searchVectorDBItems',
|
|
509
|
+
queryText: 'issues',
|
|
510
|
+
itemType: 'task',
|
|
511
|
+
filter: {
|
|
512
|
+
category: { "$in": ["bug", "hotfix"] },
|
|
513
|
+
status: { "$nin": ["done", "cancelled"] }
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Response Format
|
|
519
|
+
|
|
520
|
+
```javascript
|
|
521
|
+
{
|
|
522
|
+
items: [
|
|
523
|
+
{
|
|
524
|
+
itemId: 'task_1705123456789',
|
|
525
|
+
body: 'encrypted...', // Use openkbs.decrypt()
|
|
526
|
+
category: 'bug', // Filterable fields returned
|
|
527
|
+
status: 'open',
|
|
528
|
+
priority: 1,
|
|
529
|
+
isUrgent: true,
|
|
530
|
+
score: 0.92, // Similarity score (0-1)
|
|
531
|
+
distance: 0.08,
|
|
532
|
+
totalTokens: 45
|
|
533
|
+
}
|
|
534
|
+
]
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### settings.json - itemTypes Configuration
|
|
539
|
+
|
|
540
|
+
Define attribute mappings in KB settings for automatic filter translation:
|
|
541
|
+
|
|
542
|
+
```json
|
|
543
|
+
{
|
|
544
|
+
"itemTypes": {
|
|
545
|
+
"task": {
|
|
546
|
+
"attributes": [
|
|
547
|
+
{ "attrName": "itemId", "attrType": "itemId", "encrypted": false },
|
|
548
|
+
{ "attrName": "body", "attrType": "body", "encrypted": true },
|
|
549
|
+
{ "attrName": "category", "attrType": "keyword1", "encrypted": false },
|
|
550
|
+
{ "attrName": "status", "attrType": "keyword2", "encrypted": false },
|
|
551
|
+
{ "attrName": "priority", "attrType": "integer1", "encrypted": false },
|
|
552
|
+
{ "attrName": "isUrgent", "attrType": "boolean1", "encrypted": false }
|
|
553
|
+
],
|
|
554
|
+
"embeddingTemplate": "${item.body.title} ${item.body.description}"
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
404
558
|
```
|
|
405
559
|
|
|
406
560
|
## Utilities
|
|
@@ -61,10 +61,10 @@ return JSON.stringify({ type: 'HIDDEN_MESSAGE' });
|
|
|
61
61
|
|
|
62
62
|
## Header Component
|
|
63
63
|
|
|
64
|
-
Customize the chat header
|
|
64
|
+
Customize the chat header. **Important:** Receive `openkbs` and `setSystemAlert` as props!
|
|
65
65
|
|
|
66
66
|
```javascript
|
|
67
|
-
const Header = ({ setRenderSettings }) => {
|
|
67
|
+
const Header = ({ setRenderSettings, openkbs, setSystemAlert }) => {
|
|
68
68
|
useEffect(() => {
|
|
69
69
|
setRenderSettings({
|
|
70
70
|
disableShareButton: true,
|
|
@@ -72,10 +72,99 @@ const Header = ({ setRenderSettings }) => {
|
|
|
72
72
|
});
|
|
73
73
|
}, [setRenderSettings]);
|
|
74
74
|
|
|
75
|
+
// Now you can use openkbs.Files, openkbs.createItem, etc.
|
|
76
|
+
// Pass openkbs to child components that need it
|
|
77
|
+
|
|
75
78
|
return <div>Custom Header</div>;
|
|
76
79
|
};
|
|
77
80
|
```
|
|
78
81
|
|
|
82
|
+
### Available Header Props
|
|
83
|
+
|
|
84
|
+
| Prop | Type | Description |
|
|
85
|
+
|------|------|-------------|
|
|
86
|
+
| `setRenderSettings` | function | Configure UI options |
|
|
87
|
+
| `openkbs` | object | SDK object with Files, createItem, etc. |
|
|
88
|
+
| `setSystemAlert` | function | Show alerts: `{ severity: 'success'/'error', message }` |
|
|
89
|
+
| `setBlockingLoading` | function | Show/hide loading overlay |
|
|
90
|
+
|
|
91
|
+
### Render Settings Options
|
|
92
|
+
|
|
93
|
+
Configure UI behavior via `setRenderSettings()`:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
setRenderSettings({
|
|
97
|
+
// UI toggles
|
|
98
|
+
disableShareButton: true,
|
|
99
|
+
disableBalanceView: true,
|
|
100
|
+
disableChatModelsSelect: true,
|
|
101
|
+
disableEmojiButton: true,
|
|
102
|
+
disableCodeExecuteButton: true,
|
|
103
|
+
disableSentLabel: true,
|
|
104
|
+
disableChatAvatar: true,
|
|
105
|
+
disableAutoscroll: true,
|
|
106
|
+
disableInitialScroll: true,
|
|
107
|
+
disableAutoFocusChatInput: true,
|
|
108
|
+
disableContextItems: true,
|
|
109
|
+
disableCopyButton: true,
|
|
110
|
+
disableTextToSpeechButton: true,
|
|
111
|
+
disablePushNotificationsButton: true,
|
|
112
|
+
disableMultichat: true,
|
|
113
|
+
disableImageUploadText: true,
|
|
114
|
+
|
|
115
|
+
// Layout
|
|
116
|
+
setMessageWidth: () => '85%',
|
|
117
|
+
chatContainerHeight: 600,
|
|
118
|
+
backgroundOpacity: 0.5,
|
|
119
|
+
|
|
120
|
+
// Behavior
|
|
121
|
+
inputLabelsQuickSend: true, // Quick send on label click
|
|
122
|
+
chatTimeoutMs: 30000, // Chat timeout in ms
|
|
123
|
+
onChatTimeout: () => {}, // Callback on timeout
|
|
124
|
+
|
|
125
|
+
// Message transform hook
|
|
126
|
+
onBeforeSendMessage: (message) => message,
|
|
127
|
+
|
|
128
|
+
// Custom components
|
|
129
|
+
sendIcon: () => <span>Send</span>,
|
|
130
|
+
formatMessageTime: (timestamp) => '...'
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### onBeforeSendMessage Hook
|
|
135
|
+
|
|
136
|
+
Transform user messages before sending to the LLM. Useful for adding prefixes, filters, or modifying content.
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
const Header = ({ setRenderSettings, openkbs }) => {
|
|
140
|
+
const [filter, setFilter] = useState('all');
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
setRenderSettings({
|
|
144
|
+
onBeforeSendMessage: (message) => {
|
|
145
|
+
// Add prefix based on filter selection
|
|
146
|
+
if (filter === 'all') return message;
|
|
147
|
+
return `[Category: ${filter}] ${message}`;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}, [setRenderSettings, filter]);
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
|
|
154
|
+
<option value="all">All</option>
|
|
155
|
+
<option value="tech">Tech</option>
|
|
156
|
+
<option value="sales">Sales</option>
|
|
157
|
+
</select>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Use cases:**
|
|
163
|
+
- Add category/filter prefixes to messages
|
|
164
|
+
- Inject context or metadata
|
|
165
|
+
- Transform or sanitize user input
|
|
166
|
+
- Add routing hints for the LLM
|
|
167
|
+
|
|
79
168
|
## Command Rendering Pattern
|
|
80
169
|
|
|
81
170
|
Define commands with icons:
|
|
@@ -205,7 +294,21 @@ The `(fixed)` suffix indicates built-in libraries that don't need bundling.
|
|
|
205
294
|
|
|
206
295
|
## Frontend openkbs Object
|
|
207
296
|
|
|
208
|
-
The `openkbs` object is
|
|
297
|
+
**IMPORTANT:** The `openkbs` object is passed as a **prop** to Header and other components - it is NOT a global variable.
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
// CORRECT - receive openkbs as prop
|
|
301
|
+
const Header = ({ setRenderSettings, openkbs, setSystemAlert }) => {
|
|
302
|
+
// Now you can use openkbs.Files, openkbs.createItem, etc.
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// WRONG - openkbs is not global in frontend
|
|
306
|
+
const Header = ({ setRenderSettings }) => {
|
|
307
|
+
await openkbs.Files.listFiles(); // ERROR: openkbs is undefined
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Always destructure `openkbs` from props before using it.
|
|
209
312
|
|
|
210
313
|
### Item CRUD
|
|
211
314
|
|
package/version.json
CHANGED