openkbs 0.0.66 → 0.0.70
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/README.md +1 -0
- package/elastic/README.md +1 -1
- package/elastic/functions.md +5 -5
- package/elastic/pulse.md +2 -2
- package/package.json +2 -2
- package/scripts/deploy.js +68 -0
- package/src/actions.js +59 -0
- package/src/index.js +8 -6
- package/templates/.claude/skills/openkbs/SKILL.md +37 -8
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/README.md +55 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/instructions.txt +40 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/app/settings.json +41 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/openkbs.json +3 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/actions.js +141 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/handler.js +32 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/memoryHelpers.js +91 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onCronjob.js +105 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onPublicAPIRequest.js +165 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onRequest.js +2 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Events/onResponse.js +2 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.js +74 -0
- package/templates/.claude/skills/openkbs/examples/monitoring-bot/src/Frontend/contentRender.json +3 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/index.mjs +228 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/auth/package.json +7 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/index.mjs +287 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/posts/package.json +10 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/functions/settings.json +4 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/openkbs.json +16 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/index.html +658 -0
- package/templates/.claude/skills/openkbs/examples/nodejs-demo/site/settings.json +4 -0
- package/templates/.claude/skills/openkbs/patterns/cronjob-batch-processing.md +278 -0
- package/templates/.claude/skills/openkbs/patterns/cronjob-monitoring.md +341 -0
- package/templates/.claude/skills/openkbs/patterns/file-upload.md +205 -0
- package/templates/.claude/skills/openkbs/patterns/image-generation.md +139 -0
- package/templates/.claude/skills/openkbs/patterns/memory-system.md +264 -0
- package/templates/.claude/skills/openkbs/patterns/public-api-item-proxy.md +254 -0
- package/templates/.claude/skills/openkbs/patterns/scheduled-tasks.md +157 -0
- package/templates/.claude/skills/openkbs/patterns/telegram-webhook.md +424 -0
- package/templates/.claude/skills/openkbs/patterns/telegram.md +222 -0
- package/templates/.claude/skills/openkbs/patterns/vectordb-archive.md +231 -0
- package/templates/.claude/skills/openkbs/patterns/video-generation.md +145 -0
- package/templates/.claude/skills/openkbs/patterns/web-publishing.md +257 -0
- package/templates/.claude/skills/openkbs/reference/backend-sdk.md +13 -2
- package/templates/.claude/skills/openkbs/reference/elastic-services.md +61 -29
- package/templates/platform/README.md +35 -0
- package/templates/platform/agents/assistant/app/icon.png +0 -0
- package/templates/platform/agents/assistant/app/instructions.txt +13 -0
- package/templates/platform/agents/assistant/app/settings.json +13 -0
- package/templates/platform/agents/assistant/src/Events/actions.js +50 -0
- package/templates/platform/agents/assistant/src/Events/handler.js +54 -0
- package/templates/platform/agents/assistant/src/Events/onRequest.js +3 -0
- package/templates/platform/agents/assistant/src/Events/onRequest.json +5 -0
- package/templates/platform/agents/assistant/src/Events/onResponse.js +3 -0
- package/templates/platform/agents/assistant/src/Events/onResponse.json +5 -0
- package/templates/platform/agents/assistant/src/Frontend/contentRender.js +27 -0
- package/templates/platform/agents/assistant/src/Frontend/contentRender.json +10 -0
- package/templates/platform/functions/api/index.mjs +63 -0
- package/templates/platform/functions/api/package.json +4 -0
- package/templates/platform/openkbs.json +19 -0
- package/templates/platform/site/index.html +75 -0
- package/version.json +3 -3
- package/templates/.claude/skills/openkbs/examples/ai-copywriter-agent/scripts/run_job.js +0 -26
- package/templates/.claude/skills/openkbs/examples/ai-copywriter-agent/scripts/utils/agent_client.js +0 -265
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# VectorDB Archive Pattern
|
|
2
|
+
|
|
3
|
+
Complete working code for archiving items to long-term memory with semantic search.
|
|
4
|
+
|
|
5
|
+
## Concept
|
|
6
|
+
|
|
7
|
+
- **Priority items** (memory_, agent_) - Fast, auto-injected, limited capacity
|
|
8
|
+
- **Archive items** - Unlimited, semantic search, for long-term storage
|
|
9
|
+
|
|
10
|
+
When priority storage fills up, archive old items to VectorDB.
|
|
11
|
+
|
|
12
|
+
## actions.js
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
// Archive items to long-term memory (VectorDB)
|
|
16
|
+
[/<archiveItems>([\s\S]*?)<\/archiveItems>/s, async (match) => {
|
|
17
|
+
try {
|
|
18
|
+
const content = match[1].trim();
|
|
19
|
+
const itemIds = JSON.parse(content);
|
|
20
|
+
|
|
21
|
+
if (!Array.isArray(itemIds) || itemIds.length === 0) {
|
|
22
|
+
throw new Error('Must provide an array of itemIds to archive');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const results = [];
|
|
26
|
+
const embeddingModel = 'text-embedding-3-large';
|
|
27
|
+
const embeddingDimension = 3072;
|
|
28
|
+
const timestamp = Date.now();
|
|
29
|
+
|
|
30
|
+
for (const itemId of itemIds) {
|
|
31
|
+
try {
|
|
32
|
+
// 1. Fetch the original item
|
|
33
|
+
const originalItem = await openkbs.getItem(itemId);
|
|
34
|
+
if (!originalItem?.item?.body) {
|
|
35
|
+
results.push({ itemId, status: 'error', error: 'Item not found' });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const body = originalItem.item.body;
|
|
40
|
+
const originalItemType = itemId.split('_')[0];
|
|
41
|
+
|
|
42
|
+
// 2. Build embedding text based on item type
|
|
43
|
+
let embeddingText = '';
|
|
44
|
+
if (originalItemType === 'memory') {
|
|
45
|
+
embeddingText = `${itemId}: ${typeof body.value === 'string' ? body.value : JSON.stringify(body.value)}`;
|
|
46
|
+
} else if (originalItemType === 'telegram') {
|
|
47
|
+
const date = body.date ? new Date(body.date * 1000).toISOString() : '';
|
|
48
|
+
embeddingText = `[${date}] ${body.from || 'Unknown'}: ${body.text || ''}`;
|
|
49
|
+
} else {
|
|
50
|
+
embeddingText = `${itemId}: ${JSON.stringify(body)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Create embeddings
|
|
54
|
+
const { embeddings, totalTokens } = await openkbs.createEmbeddings(embeddingText, embeddingModel);
|
|
55
|
+
|
|
56
|
+
// 4. Create archive item with timestamp for uniqueness
|
|
57
|
+
const archiveItemId = `archive_${timestamp}_${itemId}`;
|
|
58
|
+
const archiveBody = {
|
|
59
|
+
originalItemId: itemId,
|
|
60
|
+
originalItemType: originalItemType,
|
|
61
|
+
content: body,
|
|
62
|
+
archivedAt: new Date().toISOString()
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
await openkbs.items({
|
|
66
|
+
action: 'createItem',
|
|
67
|
+
itemType: 'archive',
|
|
68
|
+
itemId: archiveItemId,
|
|
69
|
+
attributes: [
|
|
70
|
+
{ attrType: 'itemId', attrName: 'itemId', encrypted: false },
|
|
71
|
+
{ attrType: 'body', attrName: 'body', encrypted: true }
|
|
72
|
+
],
|
|
73
|
+
item: { body: await openkbs.encrypt(JSON.stringify(archiveBody)) },
|
|
74
|
+
totalTokens,
|
|
75
|
+
embeddings: embeddings ? embeddings.slice(0, embeddingDimension) : undefined,
|
|
76
|
+
embeddingModel,
|
|
77
|
+
embeddingDimension
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// 5. Delete original item from priority storage
|
|
81
|
+
await openkbs.deleteItem(itemId);
|
|
82
|
+
|
|
83
|
+
results.push({
|
|
84
|
+
itemId,
|
|
85
|
+
archiveItemId,
|
|
86
|
+
status: 'success',
|
|
87
|
+
tokens: totalTokens
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
} catch (e) {
|
|
91
|
+
results.push({ itemId, status: 'error', error: e.message });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const successCount = results.filter(r => r.status === 'success').length;
|
|
96
|
+
const errorCount = results.filter(r => r.status === 'error').length;
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
type: "ITEMS_ARCHIVED",
|
|
100
|
+
summary: `Archived ${successCount} of ${itemIds.length} items (${errorCount} errors)`,
|
|
101
|
+
results,
|
|
102
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
103
|
+
};
|
|
104
|
+
} catch (e) {
|
|
105
|
+
return {
|
|
106
|
+
type: "ARCHIVE_ERROR",
|
|
107
|
+
error: e.message,
|
|
108
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}],
|
|
112
|
+
|
|
113
|
+
// Search long-term archive memory (VectorDB semantic search)
|
|
114
|
+
[/<searchArchive>([\s\S]*?)<\/searchArchive>/s, async (match) => {
|
|
115
|
+
try {
|
|
116
|
+
const content = match[1].trim();
|
|
117
|
+
const data = JSON.parse(content);
|
|
118
|
+
|
|
119
|
+
if (!data.query) {
|
|
120
|
+
throw new Error('Must provide a "query" for semantic search');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const topK = data.topK || 10;
|
|
124
|
+
const minScore = data.minScore || 0;
|
|
125
|
+
|
|
126
|
+
// Call VectorDB search
|
|
127
|
+
const searchResult = await openkbs.items({
|
|
128
|
+
action: 'searchVectorDBItems',
|
|
129
|
+
queryText: data.query,
|
|
130
|
+
topK: topK,
|
|
131
|
+
minScore: minScore
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Format and decrypt results
|
|
135
|
+
const formattedResults = [];
|
|
136
|
+
|
|
137
|
+
for (const item of (searchResult?.items || [])) {
|
|
138
|
+
try {
|
|
139
|
+
let parsed = null;
|
|
140
|
+
if (item.body) {
|
|
141
|
+
const decryptedBody = await openkbs.decrypt(item.body);
|
|
142
|
+
parsed = JSON.parse(decryptedBody);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
formattedResults.push({
|
|
146
|
+
archiveItemId: item.itemId,
|
|
147
|
+
originalItemId: parsed?.originalItemId,
|
|
148
|
+
originalItemType: parsed?.originalItemType,
|
|
149
|
+
content: parsed?.content,
|
|
150
|
+
archivedAt: parsed?.archivedAt,
|
|
151
|
+
score: item.score
|
|
152
|
+
});
|
|
153
|
+
} catch (e) {
|
|
154
|
+
formattedResults.push({
|
|
155
|
+
archiveItemId: item.itemId,
|
|
156
|
+
score: item.score,
|
|
157
|
+
error: 'Failed to decrypt: ' + e.message
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
type: "ARCHIVE_SEARCH_RESULTS",
|
|
164
|
+
query: data.query,
|
|
165
|
+
count: formattedResults.length,
|
|
166
|
+
results: formattedResults,
|
|
167
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
168
|
+
};
|
|
169
|
+
} catch (e) {
|
|
170
|
+
return {
|
|
171
|
+
type: "ARCHIVE_SEARCH_ERROR",
|
|
172
|
+
error: e.message,
|
|
173
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}]
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## settings.json Configuration
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"embeddingModel": "text-embedding-3-large",
|
|
184
|
+
"embeddingDimension": 3072,
|
|
185
|
+
"searchEngine": "VectorDB",
|
|
186
|
+
"itemTypes": {
|
|
187
|
+
"archive": {
|
|
188
|
+
"attributes": [
|
|
189
|
+
{ "attrName": "itemId", "attrType": "itemId", "encrypted": false },
|
|
190
|
+
{ "attrName": "body", "attrType": "body", "encrypted": true }
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"options": {
|
|
195
|
+
"vectorDBMaxTokens": 25000,
|
|
196
|
+
"vectorDBTopK": 30,
|
|
197
|
+
"vectorDBMinScore": 90
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## instructions.txt (LLM prompt)
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
Long-term Memory:
|
|
206
|
+
|
|
207
|
+
Archive items from priority storage:
|
|
208
|
+
<archiveItems>["memory_old_item1", "memory_old_item2"]</archiveItems>
|
|
209
|
+
|
|
210
|
+
Search archived memories semantically:
|
|
211
|
+
<searchArchive>{"query": "what did user say about project X", "topK": 10}</searchArchive>
|
|
212
|
+
|
|
213
|
+
Use archive when:
|
|
214
|
+
- Priority storage is filling up
|
|
215
|
+
- You need to remember things long-term
|
|
216
|
+
- You want semantic search over past conversations
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Key Points
|
|
220
|
+
|
|
221
|
+
1. **Two-tier storage**:
|
|
222
|
+
- Priority items: Fast, limited, auto-injected into context
|
|
223
|
+
- Archive items: Unlimited, semantic search, manual retrieval
|
|
224
|
+
|
|
225
|
+
2. **Archive flow**: Fetch → Create embeddings → Store in VectorDB → Delete original
|
|
226
|
+
|
|
227
|
+
3. **Embedding model** must match settings.json configuration
|
|
228
|
+
|
|
229
|
+
4. **Encrypted storage** - Archive body is encrypted, decrypted on retrieval
|
|
230
|
+
|
|
231
|
+
5. **Score-based search** - Results sorted by semantic similarity score
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Video Generation Pattern
|
|
2
|
+
|
|
3
|
+
Complete working code for AI video generation with async polling.
|
|
4
|
+
|
|
5
|
+
## actions.js
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// Create video - returns pending status, requires polling
|
|
9
|
+
[/<createAIVideo>([\s\S]*?)<\/createAIVideo>/s, async (match) => {
|
|
10
|
+
try {
|
|
11
|
+
const data = JSON.parse(match[1].trim());
|
|
12
|
+
|
|
13
|
+
const videoModel = data.model || 'sora-2';
|
|
14
|
+
const prompt = data.prompt;
|
|
15
|
+
|
|
16
|
+
// Validate seconds (4, 8, or 12 only)
|
|
17
|
+
const validSeconds = [4, 8, 12];
|
|
18
|
+
const seconds = validSeconds.includes(data.seconds) ? data.seconds :
|
|
19
|
+
validSeconds.reduce((prev, curr) =>
|
|
20
|
+
Math.abs(curr - data.seconds) < Math.abs(prev - data.seconds) ? curr : prev);
|
|
21
|
+
|
|
22
|
+
const params = {
|
|
23
|
+
video_model: videoModel,
|
|
24
|
+
seconds: seconds
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Reference image OR size (not both)
|
|
28
|
+
if (data.input_reference_url) {
|
|
29
|
+
params.input_reference_url = data.input_reference_url;
|
|
30
|
+
} else {
|
|
31
|
+
const validSizes = ['720x1280', '1280x720'];
|
|
32
|
+
params.size = validSizes.includes(data.size) ? data.size : '1280x720';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const videoData = await openkbs.generateVideo(prompt, params);
|
|
36
|
+
|
|
37
|
+
// Video generation is async - check status
|
|
38
|
+
if (videoData?.[0]?.status === 'pending') {
|
|
39
|
+
return {
|
|
40
|
+
type: 'VIDEO_PENDING',
|
|
41
|
+
data: {
|
|
42
|
+
videoId: videoData[0].video_id,
|
|
43
|
+
message: 'Video generation in progress. Use continueVideoPolling to check status.'
|
|
44
|
+
},
|
|
45
|
+
...meta
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Immediate completion (rare)
|
|
50
|
+
if (videoData?.[0]?.video_url) {
|
|
51
|
+
return {
|
|
52
|
+
type: 'CHAT_VIDEO',
|
|
53
|
+
data: { videoUrl: videoData[0].video_url },
|
|
54
|
+
...meta
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { error: 'Video generation failed - no response', ...meta };
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return { error: error.message || 'Video creation failed', ...meta };
|
|
61
|
+
}
|
|
62
|
+
}],
|
|
63
|
+
|
|
64
|
+
// Poll for video completion
|
|
65
|
+
[/<continueVideoPolling>([\s\S]*?)<\/continueVideoPolling>/s, async (match) => {
|
|
66
|
+
try {
|
|
67
|
+
const data = JSON.parse(match[1].trim());
|
|
68
|
+
const videoId = data.videoId;
|
|
69
|
+
|
|
70
|
+
const videoData = await openkbs.checkVideoStatus(videoId);
|
|
71
|
+
|
|
72
|
+
if (videoData?.[0]?.status === 'completed' && videoData[0].video_url) {
|
|
73
|
+
return {
|
|
74
|
+
type: 'CHAT_VIDEO',
|
|
75
|
+
data: { videoUrl: videoData[0].video_url },
|
|
76
|
+
...meta
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (videoData?.[0]?.status === 'pending') {
|
|
81
|
+
return {
|
|
82
|
+
type: 'VIDEO_PENDING',
|
|
83
|
+
data: {
|
|
84
|
+
videoId: videoId,
|
|
85
|
+
message: 'Video still generating. Continue polling.'
|
|
86
|
+
},
|
|
87
|
+
...meta
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (videoData?.[0]?.status === 'failed') {
|
|
92
|
+
return { error: 'Video generation failed', ...meta };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { error: 'Unable to get video status', ...meta };
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return { error: error.message || 'Failed to check video status', ...meta };
|
|
98
|
+
}
|
|
99
|
+
}]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## contentRender.js
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// In isVisualResult function
|
|
106
|
+
const isVisualResult = (r) => {
|
|
107
|
+
return (r?.type === 'CHAT_VIDEO' && r?.data?.videoUrl) ||
|
|
108
|
+
r?.type === 'VIDEO_PENDING';
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// In renderVisualResults function
|
|
112
|
+
if (item?.type === 'CHAT_VIDEO' && item?.data?.videoUrl) {
|
|
113
|
+
return (
|
|
114
|
+
<div key={`vid-${idx}`} style={{ flex: '1 1 100%' }}>
|
|
115
|
+
<video
|
|
116
|
+
src={item.data.videoUrl}
|
|
117
|
+
controls
|
|
118
|
+
style={{ width: '100%', maxWidth: 600, borderRadius: 8 }}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## instructions.txt (LLM prompt)
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
To generate a video:
|
|
129
|
+
<createAIVideo>{"prompt": "scene description", "model": "sora-2", "seconds": 8, "size": "1280x720"}</createAIVideo>
|
|
130
|
+
|
|
131
|
+
Video generation takes time. When you receive VIDEO_PENDING, poll with:
|
|
132
|
+
<continueVideoPolling>{"videoId": "the-video-id"}</continueVideoPolling>
|
|
133
|
+
|
|
134
|
+
Models: sora-2 (standard), sora-2-pro (higher quality)
|
|
135
|
+
Duration: 4, 8, or 12 seconds
|
|
136
|
+
Size: 1280x720 (landscape), 720x1280 (portrait)
|
|
137
|
+
Optional: input_reference_url for image-to-video
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Key Points
|
|
141
|
+
|
|
142
|
+
1. **Async by nature** - Video generation returns `VIDEO_PENDING`, not immediate result
|
|
143
|
+
2. **Polling required** - LLM must call `continueVideoPolling` to check completion
|
|
144
|
+
3. **CHAT_VIDEO is core magic** - Renders as video player automatically
|
|
145
|
+
4. **Two commands** - Create and poll are separate for clean flow
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Web Publishing Pattern
|
|
2
|
+
|
|
3
|
+
Complete working code for publishing HTML pages to KB storage.
|
|
4
|
+
|
|
5
|
+
## actions.js
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// Publish HTML webpage
|
|
9
|
+
[/<publishWebPage>([\s\S]*?)<\/publishWebPage>/s, async (match) => {
|
|
10
|
+
try {
|
|
11
|
+
let htmlContent = match[1].trim();
|
|
12
|
+
|
|
13
|
+
// Extract HTML if wrapped in markdown code block
|
|
14
|
+
const codeBlockMatch = htmlContent.match(/```html\s*([\s\S]*?)\s*```/);
|
|
15
|
+
if (codeBlockMatch) {
|
|
16
|
+
htmlContent = codeBlockMatch[1].trim();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Validate HTML content
|
|
20
|
+
if (!htmlContent || !htmlContent.includes('<html')) {
|
|
21
|
+
return {
|
|
22
|
+
type: "PUBLISH_ERROR",
|
|
23
|
+
error: "No valid HTML content found. Must include <html> tag.",
|
|
24
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Fix charset to UTF-8
|
|
29
|
+
htmlContent = htmlContent.replace(
|
|
30
|
+
/<meta\s+charset=["']?[^"'>]*["']?\s*\/?>/gi,
|
|
31
|
+
'<meta charset="UTF-8">'
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Extract title for filename
|
|
35
|
+
const titleMatch = htmlContent.match(/<title>(.*?)<\/title>/i);
|
|
36
|
+
const title = titleMatch ? titleMatch[1].trim() : 'page';
|
|
37
|
+
|
|
38
|
+
// Generate filename from title
|
|
39
|
+
const timestamp = Date.now();
|
|
40
|
+
const baseName = title
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
43
|
+
.replace(/^_+|_+$/g, '') || 'page';
|
|
44
|
+
const filename = `${baseName}_${timestamp}.html`;
|
|
45
|
+
|
|
46
|
+
// Upload embedded images (optional - for CDN URLs)
|
|
47
|
+
const cdnUrlRegex = /https:\/\/cdn\.example\.com\/[^\s"'<>]+/g;
|
|
48
|
+
const cdnUrls = [...new Set(htmlContent.match(cdnUrlRegex) || [])];
|
|
49
|
+
|
|
50
|
+
for (const cdnUrl of cdnUrls) {
|
|
51
|
+
try {
|
|
52
|
+
// Download and re-upload to permanent storage
|
|
53
|
+
const fileResponse = await axios.get(cdnUrl, { responseType: 'arraybuffer' });
|
|
54
|
+
const fileName = cdnUrl.split('/').pop();
|
|
55
|
+
const fileType = fileResponse.headers['content-type'] || 'image/jpeg';
|
|
56
|
+
|
|
57
|
+
const presigned = await openkbs.kb({
|
|
58
|
+
action: 'createPresignedURL',
|
|
59
|
+
namespace: 'files',
|
|
60
|
+
fileName: fileName,
|
|
61
|
+
fileType: fileType,
|
|
62
|
+
presignedOperation: 'putObject'
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await axios.put(presigned, fileResponse.data, {
|
|
66
|
+
headers: { 'Content-Type': fileType, 'Content-Length': fileResponse.data.length }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const permanentUrl = `https://yourdomain.com/media/${fileName}`;
|
|
70
|
+
htmlContent = htmlContent.split(cdnUrl).join(permanentUrl);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// Continue if image upload fails
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Get presigned URL for HTML upload
|
|
77
|
+
const presignedUrl = await openkbs.kb({
|
|
78
|
+
action: 'createPresignedURL',
|
|
79
|
+
namespace: 'files',
|
|
80
|
+
fileName: filename,
|
|
81
|
+
fileType: 'text/html',
|
|
82
|
+
presignedOperation: 'putObject'
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Upload HTML as UTF-8 Buffer
|
|
86
|
+
const htmlBuffer = Buffer.from(htmlContent, 'utf8');
|
|
87
|
+
await axios.put(presignedUrl, htmlBuffer, {
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': 'text/html',
|
|
90
|
+
'Content-Length': htmlBuffer.length
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const publicUrl = `https://yourdomain.com/media/${filename}`;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
type: "WEBSITE_PUBLISHED",
|
|
98
|
+
filename: filename,
|
|
99
|
+
url: publicUrl,
|
|
100
|
+
size: htmlBuffer.length,
|
|
101
|
+
title: title,
|
|
102
|
+
message: `Website published at ${publicUrl}`,
|
|
103
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
} catch (e) {
|
|
107
|
+
return {
|
|
108
|
+
type: "PUBLISH_ERROR",
|
|
109
|
+
error: e.message,
|
|
110
|
+
_meta_actions: ["REQUEST_CHAT_MODEL"]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Helper: Web Publishing Guide
|
|
117
|
+
|
|
118
|
+
Create `webPublishingGuide.js`:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
export const webPublishingGuide = `
|
|
122
|
+
# Web Publishing Guide
|
|
123
|
+
|
|
124
|
+
## HTML Structure
|
|
125
|
+
- Always include <!DOCTYPE html>
|
|
126
|
+
- Use <meta charset="UTF-8">
|
|
127
|
+
- Set viewport for mobile: <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
128
|
+
|
|
129
|
+
## Responsive Design
|
|
130
|
+
- Use flexbox or CSS grid
|
|
131
|
+
- Mobile-first approach
|
|
132
|
+
- Test at 320px, 768px, 1024px widths
|
|
133
|
+
|
|
134
|
+
## Images
|
|
135
|
+
- Use CDN URLs that will be auto-converted to permanent storage
|
|
136
|
+
- Add alt text for accessibility
|
|
137
|
+
- Use lazy loading: loading="lazy"
|
|
138
|
+
|
|
139
|
+
## SEO
|
|
140
|
+
- Unique, descriptive <title>
|
|
141
|
+
- Meta description
|
|
142
|
+
- Semantic HTML (header, main, footer, article)
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
// Command to read guide
|
|
146
|
+
[/<getWebPublishingGuide\s*\/>/s, async () => {
|
|
147
|
+
return {
|
|
148
|
+
type: 'WEB_PUBLISHING_GUIDE',
|
|
149
|
+
content: webPublishingGuide,
|
|
150
|
+
...meta
|
|
151
|
+
};
|
|
152
|
+
}]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Example: Complete Landing Page
|
|
156
|
+
|
|
157
|
+
```html
|
|
158
|
+
<!DOCTYPE html>
|
|
159
|
+
<html lang="en">
|
|
160
|
+
<head>
|
|
161
|
+
<meta charset="UTF-8">
|
|
162
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
163
|
+
<title>Product Launch</title>
|
|
164
|
+
<style>
|
|
165
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
166
|
+
body {
|
|
167
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
168
|
+
line-height: 1.6;
|
|
169
|
+
color: #333;
|
|
170
|
+
}
|
|
171
|
+
.hero {
|
|
172
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
173
|
+
color: white;
|
|
174
|
+
padding: 80px 20px;
|
|
175
|
+
text-align: center;
|
|
176
|
+
}
|
|
177
|
+
.hero h1 { font-size: 2.5rem; margin-bottom: 1rem; }
|
|
178
|
+
.hero p { font-size: 1.2rem; opacity: 0.9; max-width: 600px; margin: 0 auto; }
|
|
179
|
+
.cta {
|
|
180
|
+
display: inline-block;
|
|
181
|
+
margin-top: 2rem;
|
|
182
|
+
padding: 15px 40px;
|
|
183
|
+
background: white;
|
|
184
|
+
color: #667eea;
|
|
185
|
+
text-decoration: none;
|
|
186
|
+
border-radius: 30px;
|
|
187
|
+
font-weight: 600;
|
|
188
|
+
}
|
|
189
|
+
.features {
|
|
190
|
+
display: grid;
|
|
191
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
192
|
+
gap: 30px;
|
|
193
|
+
padding: 60px 20px;
|
|
194
|
+
max-width: 1200px;
|
|
195
|
+
margin: 0 auto;
|
|
196
|
+
}
|
|
197
|
+
.feature {
|
|
198
|
+
text-align: center;
|
|
199
|
+
padding: 20px;
|
|
200
|
+
}
|
|
201
|
+
.feature h3 { margin-bottom: 0.5rem; }
|
|
202
|
+
</style>
|
|
203
|
+
</head>
|
|
204
|
+
<body>
|
|
205
|
+
<section class="hero">
|
|
206
|
+
<h1>Your Product Name</h1>
|
|
207
|
+
<p>A compelling description of what makes your product amazing.</p>
|
|
208
|
+
<a href="#" class="cta">Get Started</a>
|
|
209
|
+
</section>
|
|
210
|
+
<section class="features">
|
|
211
|
+
<div class="feature">
|
|
212
|
+
<h3>Feature One</h3>
|
|
213
|
+
<p>Description of the first key feature.</p>
|
|
214
|
+
</div>
|
|
215
|
+
<div class="feature">
|
|
216
|
+
<h3>Feature Two</h3>
|
|
217
|
+
<p>Description of the second key feature.</p>
|
|
218
|
+
</div>
|
|
219
|
+
<div class="feature">
|
|
220
|
+
<h3>Feature Three</h3>
|
|
221
|
+
<p>Description of the third key feature.</p>
|
|
222
|
+
</div>
|
|
223
|
+
</section>
|
|
224
|
+
</body>
|
|
225
|
+
</html>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## instructions.txt (LLM prompt)
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
Web Publishing:
|
|
232
|
+
|
|
233
|
+
Publish an HTML page:
|
|
234
|
+
<publishWebPage>
|
|
235
|
+
<!DOCTYPE html>
|
|
236
|
+
<html lang="en">
|
|
237
|
+
...full HTML content...
|
|
238
|
+
</html>
|
|
239
|
+
</publishWebPage>
|
|
240
|
+
|
|
241
|
+
Get publishing guidelines:
|
|
242
|
+
<getWebPublishingGuide/>
|
|
243
|
+
|
|
244
|
+
Best practices:
|
|
245
|
+
- Always include DOCTYPE and charset
|
|
246
|
+
- Use inline CSS (no external stylesheets)
|
|
247
|
+
- Images from CDN are auto-converted to permanent URLs
|
|
248
|
+
- Title becomes filename (e.g., "My Page" → my_page_1234567890.html)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Key Points
|
|
252
|
+
|
|
253
|
+
1. **Self-contained HTML**: Include all CSS inline, no external dependencies
|
|
254
|
+
2. **UTF-8 encoding**: Always set charset, upload as Buffer
|
|
255
|
+
3. **Auto-filename**: Generated from `<title>` with timestamp
|
|
256
|
+
4. **Image processing**: CDN URLs auto-converted to permanent storage
|
|
257
|
+
5. **CDN delivery**: Pages served via CloudFront at `/media/`
|
|
@@ -357,8 +357,9 @@ await axios.put(presignedUrl, fileBuffer, {
|
|
|
357
357
|
}
|
|
358
358
|
});
|
|
359
359
|
|
|
360
|
-
// Public URL pattern
|
|
361
|
-
|
|
360
|
+
// Public URL pattern (varies by deployment)
|
|
361
|
+
// For whitelabel: https://yourdomain.file.vpc1.us/files/kbId/filename
|
|
362
|
+
// Generic: https://file.openkbs.com/files/kbId/filename
|
|
362
363
|
```
|
|
363
364
|
|
|
364
365
|
## VectorDB (Semantic Search)
|
|
@@ -418,6 +419,16 @@ const rates = await openkbs.getExchangeRates('USD');
|
|
|
418
419
|
// Returns: { EUR: 0.92, GBP: 0.79, ... }
|
|
419
420
|
```
|
|
420
421
|
|
|
422
|
+
## Global Objects
|
|
423
|
+
|
|
424
|
+
These are available globally in all backend handlers (no import needed):
|
|
425
|
+
|
|
426
|
+
```javascript
|
|
427
|
+
openkbs // OpenKBS SDK (this document)
|
|
428
|
+
axios // HTTP client (axios.get, axios.post, etc.)
|
|
429
|
+
crypto // Node.js crypto module
|
|
430
|
+
```
|
|
431
|
+
|
|
421
432
|
## Properties
|
|
422
433
|
|
|
423
434
|
```javascript
|