openkbs 0.0.53 → 0.0.55

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 (27) hide show
  1. package/README.md +1490 -202
  2. package/package.json +2 -1
  3. package/src/actions.js +345 -1
  4. package/src/index.js +17 -1
  5. package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/app/instructions.txt +44 -9
  6. package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/actions.js +43 -42
  7. package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/handler.js +14 -8
  8. package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Frontend/contentRender.js +95 -12
  9. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/README.md +64 -0
  10. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/app/instructions.txt +160 -0
  11. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/app/settings.json +7 -0
  12. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/actions.js +258 -0
  13. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onRequest.js +13 -0
  14. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onRequest.json +3 -0
  15. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onResponse.js +13 -0
  16. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onResponse.json +3 -0
  17. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Frontend/contentRender.js +170 -0
  18. package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Frontend/contentRender.json +3 -0
  19. package/templates/.openkbs/knowledge/metadata.json +1 -1
  20. package/templates/CLAUDE.md +593 -222
  21. package/templates/app/instructions.txt +13 -1
  22. package/templates/app/settings.json +5 -6
  23. package/templates/src/Events/actions.js +43 -9
  24. package/templates/src/Events/handler.js +24 -25
  25. package/templates/webpack.contentRender.config.js +8 -2
  26. package/version.json +3 -3
  27. package/MODIFY.md +0 -132
@@ -1,6 +1,11 @@
1
1
  import React, { useEffect } from "react";
2
2
  import JsonView from '@uiw/react-json-view';
3
- import { Chip } from '@mui/material';
3
+ import { Box, Tooltip, Chip } from '@mui/material';
4
+ import SearchIcon from '@mui/icons-material/Search';
5
+ import LanguageIcon from '@mui/icons-material/Language';
6
+ import ImageIcon from '@mui/icons-material/Image';
7
+ import YouTubeIcon from '@mui/icons-material/YouTube';
8
+ import ArticleIcon from '@mui/icons-material/Article';
4
9
 
5
10
  const extractJSONFromText = (text) => {
6
11
  let braceCount = 0, startIndex = text.indexOf('{');
@@ -19,24 +24,102 @@ const extractJSONFromText = (text) => {
19
24
  return null;
20
25
  }
21
26
 
22
- const parseCommands = (text) => {
23
- return text.match(/\/\w+\("([^"]+)"\)/g) || [];
24
- }
27
+ // Command patterns for XML+JSON format
28
+ const COMMAND_PATTERNS = [
29
+ /<googleSearch>[\s\S]*?<\/googleSearch>/,
30
+ /<youtubeSearch>[\s\S]*?<\/youtubeSearch>/,
31
+ /<googleImageSearch>[\s\S]*?<\/googleImageSearch>/,
32
+ /<webpageToText>[\s\S]*?<\/webpageToText>/,
33
+ /<documentToText>[\s\S]*?<\/documentToText>/,
34
+ /<imageToText>[\s\S]*?<\/imageToText>/
35
+ ];
36
+
37
+ // Icon mapping for commands
38
+ const commandIcons = {
39
+ googleSearch: SearchIcon,
40
+ youtubeSearch: YouTubeIcon,
41
+ googleImageSearch: ImageIcon,
42
+ webpageToText: LanguageIcon,
43
+ documentToText: ArticleIcon,
44
+ imageToText: ImageIcon
45
+ };
46
+
47
+ // Parse commands from content
48
+ const parseCommands = (content) => {
49
+ const commands = [];
50
+ const regex = /<(\w+)>([\s\S]*?)<\/\1>/g;
51
+ let match;
52
+ while ((match = regex.exec(content)) !== null) {
53
+ try {
54
+ commands.push({
55
+ name: match[1],
56
+ data: JSON.parse(match[2].trim())
57
+ });
58
+ } catch (e) {
59
+ commands.push({ name: match[1], data: match[2].trim() });
60
+ }
61
+ }
62
+ return commands;
63
+ };
64
+
65
+ // Render command as icon with tooltip
66
+ const CommandIcon = ({ command }) => {
67
+ const Icon = commandIcons[command.name] || SearchIcon;
68
+ return (
69
+ <Tooltip
70
+ title={
71
+ <Box sx={{ p: 1 }}>
72
+ <Box sx={{ fontWeight: 'bold', color: '#4CAF50', mb: 0.5 }}>{command.name}</Box>
73
+ <pre style={{ margin: 0, fontSize: '10px' }}>
74
+ {typeof command.data === 'object'
75
+ ? JSON.stringify(command.data, null, 2)
76
+ : command.data}
77
+ </pre>
78
+ </Box>
79
+ }
80
+ arrow
81
+ >
82
+ <Box sx={{
83
+ display: 'inline-flex',
84
+ width: 32, height: 32,
85
+ borderRadius: '50%',
86
+ backgroundColor: 'rgba(76, 175, 80, 0.1)',
87
+ border: '2px solid rgba(76, 175, 80, 0.3)',
88
+ alignItems: 'center',
89
+ justifyContent: 'center',
90
+ mx: 0.5,
91
+ cursor: 'pointer',
92
+ '&:hover': {
93
+ backgroundColor: 'rgba(76, 175, 80, 0.2)',
94
+ transform: 'scale(1.1)'
95
+ },
96
+ transition: 'all 0.2s'
97
+ }}>
98
+ <Icon sx={{ fontSize: 16, color: '#4CAF50' }} />
99
+ </Box>
100
+ </Tooltip>
101
+ );
102
+ };
25
103
 
26
104
  // do NOT useState() directly in this function, it is not a React component
27
105
  const onRenderChatMessage = async (params) => {
28
106
  const { content } = params.messages[params.msgIndex];
29
107
  const JSONData = extractJSONFromText(content);
30
- const commands = parseCommands(content);
31
-
108
+
109
+ // Render JOB_COMPLETED as JSON view
32
110
  if (JSONData?.type === 'JOB_COMPLETED') {
33
111
  return <JsonView value={JSONData} />
34
112
  }
35
-
36
- if (commands.length > 0) {
37
- return commands.map((cmd, i) => (
38
- <Chip key={i} label={cmd} size="small" />
39
- ));
113
+
114
+ // Check for commands in content
115
+ const hasCommand = COMMAND_PATTERNS.some(p => p.test(content));
116
+ if (hasCommand) {
117
+ const commands = parseCommands(content);
118
+ return (
119
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
120
+ {commands.map((cmd, i) => <CommandIcon key={i} command={cmd} />)}
121
+ </Box>
122
+ );
40
123
  }
41
124
 
42
125
  // return undefined to use default message render
@@ -61,4 +144,4 @@ const Header = ({ setRenderSettings }) => {
61
144
 
62
145
  const exports = { onRenderChatMessage, Header };
63
146
  window.contentRender = exports;
64
- export default exports;
147
+ export default exports;
@@ -0,0 +1,64 @@
1
+ # AI Marketing Agent
2
+
3
+ A comprehensive AI marketing assistant that can create content, generate images and videos, search the web, send emails, and schedule tasks.
4
+
5
+ ## Features
6
+
7
+ - **AI Image Generation**: Gemini 2.5 Flash Image and GPT-Image-1 models
8
+ - **AI Video Generation**: Sora 2 and Sora 2 Pro models
9
+ - **Web Search**: Google Search and Image Search
10
+ - **Email Marketing**: Send emails directly from chat
11
+ - **Task Scheduling**: Schedule future reminders and tasks
12
+ - **Web Publishing**: Create and publish HTML landing pages
13
+ - **Memory System**: Persistent storage for user preferences and content
14
+
15
+ ## Command Format
16
+
17
+ All commands use XML tags with JSON content:
18
+
19
+ ```xml
20
+ <commandName>
21
+ {
22
+ "param1": "value1",
23
+ "param2": "value2"
24
+ }
25
+ </commandName>
26
+ ```
27
+
28
+ ## Available Commands
29
+
30
+ ### Content Creation
31
+ - `<createAIImage>` - Generate images with AI
32
+ - `<createAIVideo>` - Generate videos with Sora 2
33
+ - `<publishWebPage>` - Publish HTML landing pages
34
+
35
+ ### Search & Research
36
+ - `<googleSearch>` - Web search
37
+ - `<googleImageSearch>` - Image search
38
+ - `<webpageToText>` - Extract text from webpages
39
+ - `<viewImage>` - View image in context
40
+
41
+ ### Communication
42
+ - `<sendMail>` - Send emails
43
+
44
+ ### Task Management
45
+ - `<scheduleTask>` - Schedule future tasks
46
+ - `<getScheduledTasks/>` - List scheduled tasks
47
+
48
+ ### Memory
49
+ - `<setMemory>` - Save to memory
50
+ - `<deleteItem>` - Delete from memory
51
+
52
+ ## Architecture
53
+
54
+ - `src/Events/actions.js` - Command implementations
55
+ - `src/Events/onRequest.js` - User message handler
56
+ - `src/Events/onResponse.js` - LLM response handler
57
+ - `src/Frontend/contentRender.js` - UI customization
58
+ - `app/instructions.txt` - LLM instructions
59
+
60
+ ## Deployment
61
+
62
+ ```bash
63
+ openkbs push
64
+ ```
@@ -0,0 +1,160 @@
1
+ You are an AI Marketing Assistant that helps users create content, images, videos, and marketing materials.
2
+
3
+ ## Current Time
4
+ - UTC: {{openkbsDateNow}}
5
+ - Local: {{openkbsDate:en-US:UTC}}
6
+
7
+ ## Core Capabilities
8
+ 1. **AI Image Generation** - Create images with Gemini or GPT-Image
9
+ 2. **AI Video Generation** - Create videos with Sora 2
10
+ 3. **Web Search** - Search Google for information and images
11
+ 4. **Email Marketing** - Send marketing emails
12
+ 5. **Task Scheduling** - Schedule future reminders and tasks
13
+ 6. **Web Publishing** - Create and publish landing pages
14
+ 7. **Memory** - Remember user preferences and content ideas
15
+
16
+ ## AVAILABLE COMMANDS
17
+ To execute a command, output it and wait for the system response.
18
+
19
+ <setMemory>
20
+ {
21
+ "itemId": "memory_key_name",
22
+ "value": "any value or object",
23
+ "expirationInMinutes": 1440
24
+ }
25
+ </setMemory>
26
+ Description: """
27
+ Save information to memory. itemId must start with 'memory_'. Optional expiration.
28
+ """
29
+
30
+ <deleteItem>
31
+ {
32
+ "itemId": "memory_key_name"
33
+ }
34
+ </deleteItem>
35
+ Description: """
36
+ Delete a memory item by its itemId.
37
+ """
38
+
39
+ <createAIImage>
40
+ {
41
+ "model": "gemini-2.5-flash-image",
42
+ "aspect_ratio": "16:9",
43
+ "prompt": "detailed image description"
44
+ }
45
+ </createAIImage>
46
+ Description: """
47
+ Generate AI images.
48
+ - gemini-2.5-flash-image: aspect_ratio (1:1, 16:9, 9:16, 3:2, 4:3, etc.), supports imageUrls for editing
49
+ - gpt-image-1: size (1024x1024, 1536x1024, 1024x1536), better for text in images
50
+ """
51
+
52
+ <createAIVideo>
53
+ {
54
+ "model": "sora-2",
55
+ "size": "1280x720",
56
+ "seconds": 8,
57
+ "prompt": "detailed video description"
58
+ }
59
+ </createAIVideo>
60
+ Description: """
61
+ Generate AI videos with Sora 2.
62
+ - model: sora-2 (fast) or sora-2-pro (higher quality)
63
+ - size: 1280x720 (landscape) or 720x1280 (portrait)
64
+ - seconds: 4, 8, or 12
65
+ - input_reference_url: optional reference image
66
+ """
67
+
68
+ <continueVideoPolling>
69
+ {
70
+ "videoId": "video_id_here"
71
+ }
72
+ </continueVideoPolling>
73
+ Description: """
74
+ Check status of pending video generation.
75
+ """
76
+
77
+ <viewImage>
78
+ {
79
+ "url": "https://example.com/image.jpg"
80
+ }
81
+ </viewImage>
82
+ Description: """
83
+ Add an image to the conversation for visual analysis.
84
+ """
85
+
86
+ <googleSearch>
87
+ {
88
+ "query": "search query"
89
+ }
90
+ </googleSearch>
91
+ Description: """
92
+ Search Google for information.
93
+ """
94
+
95
+ <googleImageSearch>
96
+ {
97
+ "query": "search query",
98
+ "limit": 5
99
+ }
100
+ </googleImageSearch>
101
+ Description: """
102
+ Search Google for images.
103
+ """
104
+
105
+ <webpageToText>
106
+ {
107
+ "url": "https://example.com/page"
108
+ }
109
+ </webpageToText>
110
+ Description: """
111
+ Extract text content from a webpage.
112
+ """
113
+
114
+ <sendMail>
115
+ {
116
+ "to": "email@example.com",
117
+ "subject": "Subject line",
118
+ "body": "Email body content"
119
+ }
120
+ </sendMail>
121
+ Description: """
122
+ Send an email.
123
+ """
124
+
125
+ <scheduleTask>
126
+ {
127
+ "delay": "2h",
128
+ "message": "Task description"
129
+ }
130
+ </scheduleTask>
131
+ Description: """
132
+ Schedule a future task.
133
+ - delay: minutes (number), "2h" (hours), or "1d" (days)
134
+ - time: specific UTC time "2025-01-15T14:00:00Z"
135
+ Task will trigger a new chat at scheduled time.
136
+ """
137
+
138
+ <getScheduledTasks/>
139
+ Description: """
140
+ List all scheduled tasks.
141
+ """
142
+
143
+ <publishWebPage>
144
+ <!DOCTYPE html>
145
+ <html>
146
+ <head><title>Page Title</title></head>
147
+ <body>
148
+ <h1>Content</h1>
149
+ </body>
150
+ </html>
151
+ </publishWebPage>
152
+ Description: """
153
+ Publish an HTML landing page. Title must be in English for filename generation.
154
+ """
155
+
156
+ ## Guidelines
157
+ - Always confirm actions with the user before executing
158
+ - Save important user information to memory
159
+ - When creating images/videos, provide detailed prompts
160
+ - For scheduled tasks, confirm the time with the user
@@ -0,0 +1,7 @@
1
+ {
2
+ "userId": "public",
3
+ "kbTitle": "AI Marketing Agent",
4
+ "kbDescription": "AI assistant for marketing content creation, image/video generation, and task scheduling",
5
+ "model": "anthropic.claude-3-5-sonnet-20241022-v2:0",
6
+ "inputTools": ["speechToText"]
7
+ }
@@ -0,0 +1,258 @@
1
+ // Memory helpers
2
+ const setMemoryValue = async (itemId, value, expirationInMinutes) => {
3
+ const body = {
4
+ value,
5
+ lastUpdated: new Date().toISOString()
6
+ };
7
+ if (expirationInMinutes) {
8
+ body.exp = new Date(Date.now() + expirationInMinutes * 60000).toISOString();
9
+ }
10
+ try {
11
+ await openkbs.updateItem({ itemType: 'memory', itemId, body });
12
+ } catch {
13
+ await openkbs.createItem({ itemType: 'memory', itemId, body });
14
+ }
15
+ };
16
+
17
+ // Upload generated image helper
18
+ const uploadGeneratedImage = async (base64Data, meta) => {
19
+ const fileName = `image-${Date.now()}-${Math.random().toString(36).substring(7)}.png`;
20
+ const uploadResult = await openkbs.uploadImage(base64Data, fileName, 'image/png');
21
+ return { type: 'CHAT_IMAGE', data: { imageUrl: uploadResult.url }, ...meta };
22
+ };
23
+
24
+ export const getActions = (meta) => [
25
+ // Memory Management
26
+ [/<setMemory>([\s\S]*?)<\/setMemory>/s, async (match) => {
27
+ try {
28
+ const data = JSON.parse(match[1].trim());
29
+ if (!data.itemId?.startsWith('memory_')) {
30
+ return { type: "MEMORY_ERROR", error: "itemId must start with 'memory_'", ...meta };
31
+ }
32
+ await setMemoryValue(data.itemId, data.value, data.expirationInMinutes);
33
+ return { type: "MEMORY_UPDATED", itemId: data.itemId, ...meta };
34
+ } catch (e) {
35
+ return { type: "MEMORY_ERROR", error: e.message, ...meta };
36
+ }
37
+ }],
38
+
39
+ [/<deleteItem>([\s\S]*?)<\/deleteItem>/s, async (match) => {
40
+ try {
41
+ const data = JSON.parse(match[1].trim());
42
+ await openkbs.deleteItem(data.itemId);
43
+ return { type: "ITEM_DELETED", itemId: data.itemId, ...meta };
44
+ } catch (e) {
45
+ return { type: "DELETE_ERROR", error: e.message, ...meta };
46
+ }
47
+ }],
48
+
49
+ // AI Image Generation
50
+ [/<createAIImage>([\s\S]*?)<\/createAIImage>/s, async (match) => {
51
+ try {
52
+ const data = JSON.parse(match[1].trim());
53
+ const model = data.model || "gemini-2.5-flash-image";
54
+ const params = { model, n: 1 };
55
+
56
+ if (data.imageUrls?.length > 0) params.imageUrls = data.imageUrls;
57
+
58
+ if (model === 'gpt-image-1') {
59
+ const validSizes = ["1024x1024", "1536x1024", "1024x1536", "auto"];
60
+ params.size = validSizes.includes(data.size) ? data.size : "1024x1024";
61
+ params.quality = "high";
62
+ } else if (model === 'gemini-2.5-flash-image') {
63
+ const validRatios = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
64
+ params.aspect_ratio = validRatios.includes(data.aspect_ratio) ? data.aspect_ratio : "1:1";
65
+ }
66
+
67
+ const image = await openkbs.generateImage(data.prompt, params);
68
+ return await uploadGeneratedImage(image[0].b64_json, meta);
69
+ } catch (e) {
70
+ return { error: e.message || 'Image creation failed', ...meta };
71
+ }
72
+ }],
73
+
74
+ // AI Video Generation
75
+ [/<createAIVideo>([\s\S]*?)<\/createAIVideo>/s, async (match) => {
76
+ try {
77
+ const data = JSON.parse(match[1].trim());
78
+ const params = {
79
+ video_model: data.model || "sora-2",
80
+ seconds: [4, 8, 12].includes(data.seconds) ? data.seconds : 8
81
+ };
82
+
83
+ if (data.input_reference_url) {
84
+ params.input_reference_url = data.input_reference_url;
85
+ } else {
86
+ params.size = ['720x1280', '1280x720'].includes(data.size) ? data.size : '1280x720';
87
+ }
88
+
89
+ const videoData = await openkbs.generateVideo(data.prompt, params);
90
+
91
+ if (videoData?.[0]?.status === 'pending') {
92
+ return { type: 'VIDEO_PENDING', data: { videoId: videoData[0].video_id }, ...meta };
93
+ }
94
+ if (videoData?.[0]?.video_url) {
95
+ return { type: 'CHAT_VIDEO', data: { videoUrl: videoData[0].video_url }, ...meta };
96
+ }
97
+ return { error: 'Video generation failed', ...meta };
98
+ } catch (e) {
99
+ return { error: e.message, ...meta };
100
+ }
101
+ }],
102
+
103
+ [/<continueVideoPolling>([\s\S]*?)<\/continueVideoPolling>/s, async (match) => {
104
+ try {
105
+ const data = JSON.parse(match[1].trim());
106
+ const videoData = await openkbs.checkVideoStatus(data.videoId);
107
+
108
+ if (videoData?.[0]?.status === 'completed' && videoData[0].video_url) {
109
+ return { type: 'CHAT_VIDEO', data: { videoUrl: videoData[0].video_url }, ...meta };
110
+ } else if (videoData?.[0]?.status === 'pending') {
111
+ return { type: 'VIDEO_PENDING', data: { videoId: data.videoId }, ...meta };
112
+ }
113
+ return { error: 'Video generation failed', ...meta };
114
+ } catch (e) {
115
+ return { error: e.message, ...meta };
116
+ }
117
+ }],
118
+
119
+ // View Image (adds to LLM vision context)
120
+ [/<viewImage>([\s\S]*?)<\/viewImage>/s, async (match) => {
121
+ try {
122
+ const data = JSON.parse(match[1].trim());
123
+ return {
124
+ data: [
125
+ { type: "text", text: `Viewing: ${data.url}` },
126
+ { type: "image_url", image_url: { url: data.url } }
127
+ ],
128
+ ...meta
129
+ };
130
+ } catch (e) {
131
+ return { type: "VIEW_IMAGE_ERROR", error: e.message, ...meta };
132
+ }
133
+ }],
134
+
135
+ // Web scraping
136
+ [/<webpageToText>([\s\S]*?)<\/webpageToText>/s, async (match) => {
137
+ try {
138
+ const data = JSON.parse(match[1].trim());
139
+ let response = await openkbs.webpageToText(data.url);
140
+ if (response?.content?.length > 5000) {
141
+ response.content = response.content.substring(0, 5000);
142
+ }
143
+ return { data: response, ...meta };
144
+ } catch (e) {
145
+ return { error: e.message, ...meta };
146
+ }
147
+ }],
148
+
149
+ // Google Search
150
+ [/<googleSearch>([\s\S]*?)<\/googleSearch>/s, async (match) => {
151
+ try {
152
+ const data = JSON.parse(match[1].trim());
153
+ const response = await openkbs.googleSearch(data.query);
154
+ const results = response?.map(({ title, link, snippet, pagemap }) => ({
155
+ title, link, snippet, image: pagemap?.metatags?.[0]?.["og:image"]
156
+ }));
157
+ return { data: results, ...meta };
158
+ } catch (e) {
159
+ return { error: e.message, ...meta };
160
+ }
161
+ }],
162
+
163
+ // Google Image Search
164
+ [/<googleImageSearch>([\s\S]*?)<\/googleImageSearch>/s, async (match) => {
165
+ try {
166
+ const data = JSON.parse(match[1].trim());
167
+ const response = await openkbs.googleSearch(data.query, { searchType: 'image' });
168
+ const results = response?.map(({ title, link, pagemap }) => ({
169
+ title, link, image: pagemap?.cse_image?.[0]?.src || link
170
+ }))?.slice(0, data.limit || 10);
171
+ return { data: results, ...meta };
172
+ } catch (e) {
173
+ return { error: e.message, ...meta };
174
+ }
175
+ }],
176
+
177
+ // Send Email
178
+ [/<sendMail>([\s\S]*?)<\/sendMail>/s, async (match) => {
179
+ try {
180
+ const data = JSON.parse(match[1].trim());
181
+ await openkbs.sendMail(data.to, data.subject, data.body);
182
+ return { type: 'EMAIL_SENT', data: { to: data.to, subject: data.subject }, ...meta };
183
+ } catch (e) {
184
+ return { error: e.message, ...meta };
185
+ }
186
+ }],
187
+
188
+ // Schedule Task
189
+ [/<scheduleTask>([\s\S]*?)<\/scheduleTask>/s, async (match) => {
190
+ try {
191
+ const data = JSON.parse(match[1].trim());
192
+ let scheduledTime;
193
+
194
+ if (data.time) {
195
+ let isoTimeStr = data.time.replace(' ', 'T');
196
+ if (!isoTimeStr.includes('Z') && !isoTimeStr.includes('+')) isoTimeStr += 'Z';
197
+ scheduledTime = new Date(isoTimeStr).getTime();
198
+ } else if (data.delay) {
199
+ let delayMs = 0;
200
+ if (data.delay.endsWith('h')) delayMs = parseFloat(data.delay) * 3600000;
201
+ else if (data.delay.endsWith('d')) delayMs = parseFloat(data.delay) * 86400000;
202
+ else delayMs = parseFloat(data.delay) * 60000;
203
+ scheduledTime = Date.now() + delayMs;
204
+ } else {
205
+ scheduledTime = Date.now() + 3600000;
206
+ }
207
+
208
+ const response = await openkbs.kb({
209
+ action: 'createScheduledTask',
210
+ scheduledTime: Math.floor(scheduledTime / 60000) * 60000,
211
+ taskPayload: { message: `[SCHEDULED_TASK] ${data.message}`, createdAt: Date.now() },
212
+ description: data.message.substring(0, 50)
213
+ });
214
+
215
+ return { type: 'TASK_SCHEDULED', data: { scheduledTime: new Date(scheduledTime).toISOString(), taskId: response.taskId }, ...meta };
216
+ } catch (e) {
217
+ return { error: e.message, ...meta };
218
+ }
219
+ }],
220
+
221
+ // Get Scheduled Tasks
222
+ [/<getScheduledTasks\s*\/>/s, async () => {
223
+ try {
224
+ const response = await openkbs.kb({ action: 'getScheduledTasks' });
225
+ return { type: 'SCHEDULED_TASKS_LIST', data: response, ...meta };
226
+ } catch (e) {
227
+ return { error: e.message, ...meta };
228
+ }
229
+ }],
230
+
231
+ // Web Page Publishing
232
+ [/<publishWebPage>([\s\S]*?)<\/publishWebPage>/s, async (match) => {
233
+ try {
234
+ const htmlContent = match[1].trim();
235
+ const titleMatch = htmlContent.match(/<title>(.*?)<\/title>/i);
236
+ const title = titleMatch ? titleMatch[1] : 'Page';
237
+ const filename = `${title.toLowerCase().replace(/[^a-z0-9]+/g, '_')}_${Date.now()}.html`;
238
+
239
+ const presignedUrl = await openkbs.kb({
240
+ action: 'createPresignedURL',
241
+ namespace: 'files',
242
+ fileName: filename,
243
+ fileType: 'text/html',
244
+ presignedOperation: 'putObject'
245
+ });
246
+
247
+ const htmlBuffer = Buffer.from(htmlContent, 'utf8');
248
+ await axios.put(presignedUrl, htmlBuffer, {
249
+ headers: { 'Content-Type': 'text/html', 'Content-Length': htmlBuffer.length }
250
+ });
251
+
252
+ const publicUrl = `https://web.file.vpc1.us/files/${openkbs.kbId}/${filename}`;
253
+ return { type: 'WEB_PAGE_PUBLISHED', data: { url: publicUrl, title }, ...meta };
254
+ } catch (e) {
255
+ return { type: 'PUBLISH_ERROR', error: e.message, ...meta };
256
+ }
257
+ }]
258
+ ];
@@ -0,0 +1,13 @@
1
+ import {getActions} from './actions.js';
2
+
3
+ export const handler = async (event) => {
4
+ const actions = getActions({ _meta_actions: [] });
5
+
6
+ for (let [regex, action] of actions) {
7
+ const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
8
+ const match = lastMessage?.match(regex);
9
+ if (match) return await action(match);
10
+ }
11
+
12
+ return { type: 'CONTINUE' }
13
+ };
@@ -0,0 +1,13 @@
1
+ import {getActions} from './actions.js';
2
+
3
+ export const handler = async (event) => {
4
+ const actions = getActions({ _meta_actions: ["REQUEST_CHAT_MODEL"] });
5
+
6
+ for (let [regex, action] of actions) {
7
+ const lastMessage = event.payload.messages[event.payload.messages.length - 1].content;
8
+ const match = lastMessage?.match(regex);
9
+ if (match) return await action(match);
10
+ }
11
+
12
+ return { type: 'CONTINUE' }
13
+ };