@lantos1618/better-ui 0.1.0
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/LICENSE +21 -0
- package/README.md +190 -0
- package/lib/aui/README.md +136 -0
- package/lib/aui/__tests__/aui-complete.test.ts +251 -0
- package/lib/aui/__tests__/aui-comprehensive.test.ts +376 -0
- package/lib/aui/__tests__/aui-concise.test.ts +278 -0
- package/lib/aui/__tests__/aui-integration.test.ts +309 -0
- package/lib/aui/__tests__/aui-simple.test.ts +116 -0
- package/lib/aui/__tests__/aui.test.ts +269 -0
- package/lib/aui/__tests__/concise-api.test.ts +165 -0
- package/lib/aui/__tests__/core.test.ts +265 -0
- package/lib/aui/__tests__/simple-api.test.ts +200 -0
- package/lib/aui/ai-assistant.ts +408 -0
- package/lib/aui/ai-control.ts +353 -0
- package/lib/aui/client/use-aui.ts +55 -0
- package/lib/aui/client-control.ts +551 -0
- package/lib/aui/client-executor.ts +417 -0
- package/lib/aui/components/ToolRenderer.tsx +22 -0
- package/lib/aui/core.ts +137 -0
- package/lib/aui/demo.tsx +89 -0
- package/lib/aui/examples/ai-complete-demo.tsx +359 -0
- package/lib/aui/examples/ai-control-demo.tsx +356 -0
- package/lib/aui/examples/ai-control-tools.ts +308 -0
- package/lib/aui/examples/concise-api.tsx +153 -0
- package/lib/aui/examples/index.tsx +163 -0
- package/lib/aui/examples/quick-demo.tsx +91 -0
- package/lib/aui/examples/simple-demo.tsx +71 -0
- package/lib/aui/examples/simple-tools.tsx +160 -0
- package/lib/aui/examples/user-api.tsx +208 -0
- package/lib/aui/examples/user-requested.tsx +174 -0
- package/lib/aui/examples/weather-search-tools.tsx +119 -0
- package/lib/aui/examples.tsx +367 -0
- package/lib/aui/hooks/useAUITool.ts +142 -0
- package/lib/aui/hooks/useAUIToolEnhanced.ts +343 -0
- package/lib/aui/hooks/useAUITools.ts +195 -0
- package/lib/aui/index.ts +156 -0
- package/lib/aui/provider.tsx +45 -0
- package/lib/aui/server-control.ts +386 -0
- package/lib/aui/server-executor.ts +165 -0
- package/lib/aui/server.ts +167 -0
- package/lib/aui/tool-registry.ts +380 -0
- package/lib/aui/tools/advanced-examples.tsx +86 -0
- package/lib/aui/tools/ai-complete.ts +375 -0
- package/lib/aui/tools/api-tools.tsx +230 -0
- package/lib/aui/tools/data-tools.tsx +232 -0
- package/lib/aui/tools/dom-tools.tsx +202 -0
- package/lib/aui/tools/examples.ts +43 -0
- package/lib/aui/tools/file-tools.tsx +202 -0
- package/lib/aui/tools/form-tools.tsx +233 -0
- package/lib/aui/tools/index.ts +8 -0
- package/lib/aui/tools/navigation-tools.tsx +172 -0
- package/lib/aui/tools/notification-tools.ts +213 -0
- package/lib/aui/tools/state-tools.tsx +209 -0
- package/lib/aui/types.ts +47 -0
- package/lib/aui/vercel-ai.ts +100 -0
- package/package.json +51 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import aui from '../index';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
// Navigation control - AI can navigate the app
|
|
5
|
+
export const navigationTool = aui
|
|
6
|
+
.tool('navigate')
|
|
7
|
+
.input(z.object({
|
|
8
|
+
action: z.enum(['goto', 'back', 'forward', 'reload', 'open']),
|
|
9
|
+
url: z.string().optional(),
|
|
10
|
+
target: z.enum(['_self', '_blank', '_parent', '_top']).optional()
|
|
11
|
+
}))
|
|
12
|
+
.clientExecute(async ({ input }) => {
|
|
13
|
+
switch (input.action) {
|
|
14
|
+
case 'goto':
|
|
15
|
+
if (!input.url) throw new Error('URL required for goto action');
|
|
16
|
+
window.location.href = input.url;
|
|
17
|
+
break;
|
|
18
|
+
case 'back':
|
|
19
|
+
window.history.back();
|
|
20
|
+
break;
|
|
21
|
+
case 'forward':
|
|
22
|
+
window.history.forward();
|
|
23
|
+
break;
|
|
24
|
+
case 'reload':
|
|
25
|
+
window.location.reload();
|
|
26
|
+
break;
|
|
27
|
+
case 'open':
|
|
28
|
+
if (!input.url) throw new Error('URL required for open action');
|
|
29
|
+
window.open(input.url, input.target || '_blank');
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
return { success: true, action: input.action, url: input.url };
|
|
33
|
+
})
|
|
34
|
+
.describe('Control browser navigation')
|
|
35
|
+
.tag('ai', 'navigation', 'client');
|
|
36
|
+
|
|
37
|
+
// State management - AI can manage application state
|
|
38
|
+
export const stateTool = aui
|
|
39
|
+
.tool('state-manager')
|
|
40
|
+
.input(z.object({
|
|
41
|
+
action: z.enum(['set', 'get', 'update', 'delete', 'clear']),
|
|
42
|
+
key: z.string().optional(),
|
|
43
|
+
value: z.any().optional(),
|
|
44
|
+
namespace: z.string().optional()
|
|
45
|
+
}))
|
|
46
|
+
.clientExecute(async ({ input }) => {
|
|
47
|
+
const storage = input.namespace === 'session' ? sessionStorage : localStorage;
|
|
48
|
+
|
|
49
|
+
switch (input.action) {
|
|
50
|
+
case 'set':
|
|
51
|
+
if (!input.key) throw new Error('Key required for set action');
|
|
52
|
+
storage.setItem(input.key, JSON.stringify(input.value));
|
|
53
|
+
return { success: true, key: input.key, value: input.value };
|
|
54
|
+
|
|
55
|
+
case 'get':
|
|
56
|
+
if (!input.key) throw new Error('Key required for get action');
|
|
57
|
+
const value = storage.getItem(input.key);
|
|
58
|
+
return { success: true, key: input.key, value: value ? JSON.parse(value) : null };
|
|
59
|
+
|
|
60
|
+
case 'update':
|
|
61
|
+
if (!input.key) throw new Error('Key required for update action');
|
|
62
|
+
const existing = storage.getItem(input.key);
|
|
63
|
+
const current = existing ? JSON.parse(existing) : {};
|
|
64
|
+
const updated = { ...current, ...input.value };
|
|
65
|
+
storage.setItem(input.key, JSON.stringify(updated));
|
|
66
|
+
return { success: true, key: input.key, value: updated };
|
|
67
|
+
|
|
68
|
+
case 'delete':
|
|
69
|
+
if (!input.key) throw new Error('Key required for delete action');
|
|
70
|
+
storage.removeItem(input.key);
|
|
71
|
+
return { success: true, key: input.key, deleted: true };
|
|
72
|
+
|
|
73
|
+
case 'clear':
|
|
74
|
+
storage.clear();
|
|
75
|
+
return { success: true, cleared: true };
|
|
76
|
+
|
|
77
|
+
default:
|
|
78
|
+
throw new Error('Unknown action');
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
.describe('Manage client-side state storage')
|
|
82
|
+
.tag('ai', 'state', 'client');
|
|
83
|
+
|
|
84
|
+
// API calls - AI can make API requests
|
|
85
|
+
export const apiTool = aui
|
|
86
|
+
.tool('api-call')
|
|
87
|
+
.input(z.object({
|
|
88
|
+
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
|
|
89
|
+
url: z.string(),
|
|
90
|
+
headers: z.record(z.string()).optional(),
|
|
91
|
+
body: z.any().optional(),
|
|
92
|
+
queryParams: z.record(z.string()).optional()
|
|
93
|
+
}))
|
|
94
|
+
.execute(async ({ input }) => {
|
|
95
|
+
const url = new URL(input.url, typeof window !== 'undefined' ? window.location.origin : 'http://localhost:3000');
|
|
96
|
+
|
|
97
|
+
if (input.queryParams) {
|
|
98
|
+
Object.entries(input.queryParams).forEach(([key, value]) => {
|
|
99
|
+
url.searchParams.append(key, value);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const options: RequestInit = {
|
|
104
|
+
method: input.method,
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/json',
|
|
107
|
+
...input.headers
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (input.body && input.method !== 'GET') {
|
|
112
|
+
options.body = JSON.stringify(input.body);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const response = await fetch(url.toString(), options);
|
|
116
|
+
const data = await response.json();
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
status: response.status,
|
|
120
|
+
statusText: response.statusText,
|
|
121
|
+
data,
|
|
122
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
123
|
+
};
|
|
124
|
+
})
|
|
125
|
+
.describe('Make HTTP API requests')
|
|
126
|
+
.tag('ai', 'api', 'network');
|
|
127
|
+
|
|
128
|
+
// File operations - AI can handle files
|
|
129
|
+
export const fileTool = aui
|
|
130
|
+
.tool('file-handler')
|
|
131
|
+
.input(z.object({
|
|
132
|
+
action: z.enum(['read', 'download', 'upload']),
|
|
133
|
+
file: z.any().optional(),
|
|
134
|
+
url: z.string().optional(),
|
|
135
|
+
filename: z.string().optional(),
|
|
136
|
+
content: z.string().optional(),
|
|
137
|
+
mimeType: z.string().optional()
|
|
138
|
+
}))
|
|
139
|
+
.clientExecute(async ({ input }) => {
|
|
140
|
+
switch (input.action) {
|
|
141
|
+
case 'download':
|
|
142
|
+
if (!input.content || !input.filename) {
|
|
143
|
+
throw new Error('Content and filename required for download');
|
|
144
|
+
}
|
|
145
|
+
const blob = new Blob([input.content], {
|
|
146
|
+
type: input.mimeType || 'text/plain'
|
|
147
|
+
});
|
|
148
|
+
const url = URL.createObjectURL(blob);
|
|
149
|
+
const a = document.createElement('a');
|
|
150
|
+
a.href = url;
|
|
151
|
+
a.download = input.filename;
|
|
152
|
+
a.click();
|
|
153
|
+
URL.revokeObjectURL(url);
|
|
154
|
+
return { success: true, filename: input.filename };
|
|
155
|
+
|
|
156
|
+
case 'read':
|
|
157
|
+
if (!input.file) throw new Error('File required for read action');
|
|
158
|
+
return new Promise((resolve) => {
|
|
159
|
+
const reader = new FileReader();
|
|
160
|
+
reader.onload = (e) => {
|
|
161
|
+
resolve({
|
|
162
|
+
success: true,
|
|
163
|
+
content: e.target?.result,
|
|
164
|
+
filename: input.file.name,
|
|
165
|
+
size: input.file.size,
|
|
166
|
+
type: input.file.type
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
reader.readAsText(input.file);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
case 'upload':
|
|
173
|
+
// This would typically upload to a server
|
|
174
|
+
return { success: true, message: 'Upload handler not implemented' };
|
|
175
|
+
|
|
176
|
+
default:
|
|
177
|
+
throw new Error('Unknown action');
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
.describe('Handle file operations')
|
|
181
|
+
.tag('ai', 'file', 'client');
|
|
182
|
+
|
|
183
|
+
// Notification system - AI can send notifications
|
|
184
|
+
export const notificationTool = aui
|
|
185
|
+
.tool('notification')
|
|
186
|
+
.input(z.object({
|
|
187
|
+
type: z.enum(['info', 'success', 'warning', 'error', 'toast']),
|
|
188
|
+
title: z.string(),
|
|
189
|
+
message: z.string().optional(),
|
|
190
|
+
duration: z.number().optional(),
|
|
191
|
+
position: z.enum(['top', 'bottom', 'top-right', 'top-left', 'bottom-right', 'bottom-left']).optional()
|
|
192
|
+
}))
|
|
193
|
+
.clientExecute(async ({ input }) => {
|
|
194
|
+
// Check if browser supports notifications
|
|
195
|
+
if (input.type !== 'toast' && 'Notification' in window) {
|
|
196
|
+
if (Notification.permission === 'default') {
|
|
197
|
+
await Notification.requestPermission();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (Notification.permission === 'granted') {
|
|
201
|
+
new Notification(input.title, {
|
|
202
|
+
body: input.message,
|
|
203
|
+
icon: `/icon-${input.type}.png`,
|
|
204
|
+
tag: input.type,
|
|
205
|
+
});
|
|
206
|
+
return { success: true, type: 'notification' };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Fallback to toast
|
|
211
|
+
const toast = document.createElement('div');
|
|
212
|
+
toast.className = `fixed z-50 p-4 rounded-lg shadow-lg ${
|
|
213
|
+
input.type === 'error' ? 'bg-red-500' :
|
|
214
|
+
input.type === 'warning' ? 'bg-yellow-500' :
|
|
215
|
+
input.type === 'success' ? 'bg-green-500' :
|
|
216
|
+
'bg-blue-500'
|
|
217
|
+
} text-white`;
|
|
218
|
+
|
|
219
|
+
// Position the toast
|
|
220
|
+
const position = input.position || 'top-right';
|
|
221
|
+
if (position.includes('top')) toast.style.top = '20px';
|
|
222
|
+
if (position.includes('bottom')) toast.style.bottom = '20px';
|
|
223
|
+
if (position.includes('right')) toast.style.right = '20px';
|
|
224
|
+
if (position.includes('left')) toast.style.left = '20px';
|
|
225
|
+
if (!position.includes('left') && !position.includes('right')) {
|
|
226
|
+
toast.style.left = '50%';
|
|
227
|
+
toast.style.transform = 'translateX(-50%)';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
toast.innerHTML = `
|
|
231
|
+
<div class="font-semibold">${input.title}</div>
|
|
232
|
+
${input.message ? `<div class="text-sm mt-1">${input.message}</div>` : ''}
|
|
233
|
+
`;
|
|
234
|
+
|
|
235
|
+
document.body.appendChild(toast);
|
|
236
|
+
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
toast.remove();
|
|
239
|
+
}, input.duration || 3000);
|
|
240
|
+
|
|
241
|
+
return { success: true, type: 'toast' };
|
|
242
|
+
})
|
|
243
|
+
.describe('Show notifications to the user')
|
|
244
|
+
.tag('ai', 'notification', 'ui');
|
|
245
|
+
|
|
246
|
+
// Analytics tracking - AI can track events
|
|
247
|
+
export const analyticsTool = aui
|
|
248
|
+
.tool('analytics')
|
|
249
|
+
.input(z.object({
|
|
250
|
+
action: z.enum(['track', 'identify', 'page', 'group']),
|
|
251
|
+
event: z.string().optional(),
|
|
252
|
+
properties: z.record(z.any()).optional(),
|
|
253
|
+
userId: z.string().optional(),
|
|
254
|
+
traits: z.record(z.any()).optional()
|
|
255
|
+
}))
|
|
256
|
+
.execute(async ({ input }) => {
|
|
257
|
+
// This would integrate with your analytics provider
|
|
258
|
+
console.log('Analytics event:', input);
|
|
259
|
+
|
|
260
|
+
// Simulate sending to analytics service
|
|
261
|
+
const analyticsData = {
|
|
262
|
+
timestamp: new Date().toISOString(),
|
|
263
|
+
...input
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// In production, this would send to Google Analytics, Segment, etc.
|
|
267
|
+
return {
|
|
268
|
+
success: true,
|
|
269
|
+
tracked: analyticsData
|
|
270
|
+
};
|
|
271
|
+
})
|
|
272
|
+
.describe('Track analytics events')
|
|
273
|
+
.tag('ai', 'analytics', 'tracking');
|
|
274
|
+
|
|
275
|
+
// Database query builder - AI can build and execute queries
|
|
276
|
+
export const queryBuilderTool = aui
|
|
277
|
+
.tool('query-builder')
|
|
278
|
+
.input(z.object({
|
|
279
|
+
table: z.string(),
|
|
280
|
+
operation: z.enum(['select', 'insert', 'update', 'delete']),
|
|
281
|
+
columns: z.array(z.string()).optional(),
|
|
282
|
+
where: z.record(z.any()).optional(),
|
|
283
|
+
data: z.record(z.any()).optional(),
|
|
284
|
+
orderBy: z.object({
|
|
285
|
+
column: z.string(),
|
|
286
|
+
direction: z.enum(['asc', 'desc'])
|
|
287
|
+
}).optional(),
|
|
288
|
+
limit: z.number().optional(),
|
|
289
|
+
offset: z.number().optional()
|
|
290
|
+
}))
|
|
291
|
+
.execute(async ({ input }) => {
|
|
292
|
+
// Build SQL-like query (this is a simulation)
|
|
293
|
+
let query = '';
|
|
294
|
+
|
|
295
|
+
switch (input.operation) {
|
|
296
|
+
case 'select':
|
|
297
|
+
query = `SELECT ${input.columns?.join(', ') || '*'} FROM ${input.table}`;
|
|
298
|
+
if (input.where) {
|
|
299
|
+
const conditions = Object.entries(input.where)
|
|
300
|
+
.map(([key, value]) => `${key} = '${value}'`)
|
|
301
|
+
.join(' AND ');
|
|
302
|
+
query += ` WHERE ${conditions}`;
|
|
303
|
+
}
|
|
304
|
+
if (input.orderBy) {
|
|
305
|
+
query += ` ORDER BY ${input.orderBy.column} ${input.orderBy.direction.toUpperCase()}`;
|
|
306
|
+
}
|
|
307
|
+
if (input.limit) query += ` LIMIT ${input.limit}`;
|
|
308
|
+
if (input.offset) query += ` OFFSET ${input.offset}`;
|
|
309
|
+
break;
|
|
310
|
+
|
|
311
|
+
case 'insert':
|
|
312
|
+
if (!input.data) throw new Error('Data required for insert');
|
|
313
|
+
const columns = Object.keys(input.data);
|
|
314
|
+
const values = Object.values(input.data).map(v => `'${v}'`);
|
|
315
|
+
query = `INSERT INTO ${input.table} (${columns.join(', ')}) VALUES (${values.join(', ')})`;
|
|
316
|
+
break;
|
|
317
|
+
|
|
318
|
+
case 'update':
|
|
319
|
+
if (!input.data) throw new Error('Data required for update');
|
|
320
|
+
const updates = Object.entries(input.data)
|
|
321
|
+
.map(([key, value]) => `${key} = '${value}'`)
|
|
322
|
+
.join(', ');
|
|
323
|
+
query = `UPDATE ${input.table} SET ${updates}`;
|
|
324
|
+
if (input.where) {
|
|
325
|
+
const conditions = Object.entries(input.where)
|
|
326
|
+
.map(([key, value]) => `${key} = '${value}'`)
|
|
327
|
+
.join(' AND ');
|
|
328
|
+
query += ` WHERE ${conditions}`;
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
|
|
332
|
+
case 'delete':
|
|
333
|
+
query = `DELETE FROM ${input.table}`;
|
|
334
|
+
if (input.where) {
|
|
335
|
+
const conditions = Object.entries(input.where)
|
|
336
|
+
.map(([key, value]) => `${key} = '${value}'`)
|
|
337
|
+
.join(' AND ');
|
|
338
|
+
query += ` WHERE ${conditions}`;
|
|
339
|
+
}
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// In production, this would execute against a real database
|
|
344
|
+
return {
|
|
345
|
+
success: true,
|
|
346
|
+
query,
|
|
347
|
+
operation: input.operation,
|
|
348
|
+
table: input.table,
|
|
349
|
+
// Simulated results
|
|
350
|
+
results: input.operation === 'select' ? [
|
|
351
|
+
{ id: 1, name: 'Sample 1' },
|
|
352
|
+
{ id: 2, name: 'Sample 2' }
|
|
353
|
+
] : { affectedRows: 1 }
|
|
354
|
+
};
|
|
355
|
+
})
|
|
356
|
+
.describe('Build and execute database queries')
|
|
357
|
+
.tag('ai', 'database', 'query');
|
|
358
|
+
|
|
359
|
+
// Export all AI control tools
|
|
360
|
+
export const aiCompleteTools = {
|
|
361
|
+
navigationTool,
|
|
362
|
+
stateTool,
|
|
363
|
+
apiTool,
|
|
364
|
+
fileTool,
|
|
365
|
+
notificationTool,
|
|
366
|
+
analyticsTool,
|
|
367
|
+
queryBuilderTool
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Register all tools
|
|
371
|
+
Object.values(aiCompleteTools).forEach(tool => {
|
|
372
|
+
// Tools are auto-registered when created with aui.tool()
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
export default aiCompleteTools;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { createAITool } from '../ai-control';
|
|
4
|
+
|
|
5
|
+
export const apiGet = createAITool('api.get')
|
|
6
|
+
.describe('Make a GET request to an API endpoint')
|
|
7
|
+
.tag('api', 'network', 'data')
|
|
8
|
+
.input(z.object({
|
|
9
|
+
url: z.string(),
|
|
10
|
+
headers: z.record(z.string()).optional(),
|
|
11
|
+
params: z.record(z.any()).optional()
|
|
12
|
+
}))
|
|
13
|
+
.execute(async ({ input, ctx }) => {
|
|
14
|
+
const url = new URL(input.url);
|
|
15
|
+
if (input.params) {
|
|
16
|
+
Object.entries(input.params).forEach(([key, value]) => {
|
|
17
|
+
url.searchParams.append(key, String(value));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const response = await ctx!.fetch(url.toString(), {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: input.headers
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const contentType = response.headers.get('content-type');
|
|
27
|
+
const data = contentType?.includes('application/json')
|
|
28
|
+
? await response.json()
|
|
29
|
+
: await response.text();
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
status: response.status,
|
|
33
|
+
statusText: response.statusText,
|
|
34
|
+
data,
|
|
35
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
36
|
+
};
|
|
37
|
+
})
|
|
38
|
+
.render(({ data }) => (
|
|
39
|
+
<div className="font-mono text-sm">
|
|
40
|
+
<div>Status: {data.status} {data.statusText}</div>
|
|
41
|
+
<pre className="bg-gray-100 p-2 rounded mt-2">
|
|
42
|
+
{typeof data.data === 'object'
|
|
43
|
+
? JSON.stringify(data.data, null, 2)
|
|
44
|
+
: data.data}
|
|
45
|
+
</pre>
|
|
46
|
+
</div>
|
|
47
|
+
));
|
|
48
|
+
|
|
49
|
+
export const apiPost = createAITool('api.post')
|
|
50
|
+
.describe('Make a POST request to an API endpoint')
|
|
51
|
+
.tag('api', 'network', 'data')
|
|
52
|
+
.input(z.object({
|
|
53
|
+
url: z.string(),
|
|
54
|
+
body: z.any(),
|
|
55
|
+
headers: z.record(z.string()).optional(),
|
|
56
|
+
json: z.boolean().optional().default(true)
|
|
57
|
+
}))
|
|
58
|
+
.execute(async ({ input, ctx }) => {
|
|
59
|
+
const headers: HeadersInit = {
|
|
60
|
+
...input.headers
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let body: any = input.body;
|
|
64
|
+
if (input.json) {
|
|
65
|
+
headers['Content-Type'] = 'application/json';
|
|
66
|
+
body = JSON.stringify(input.body);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const response = await ctx!.fetch(input.url, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers,
|
|
72
|
+
body
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const contentType = response.headers.get('content-type');
|
|
76
|
+
const data = contentType?.includes('application/json')
|
|
77
|
+
? await response.json()
|
|
78
|
+
: await response.text();
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
status: response.status,
|
|
82
|
+
statusText: response.statusText,
|
|
83
|
+
data,
|
|
84
|
+
headers: Object.fromEntries(response.headers.entries())
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const apiPut = createAITool('api.put')
|
|
89
|
+
.describe('Make a PUT request to an API endpoint')
|
|
90
|
+
.tag('api', 'network', 'data')
|
|
91
|
+
.input(z.object({
|
|
92
|
+
url: z.string(),
|
|
93
|
+
body: z.any(),
|
|
94
|
+
headers: z.record(z.string()).optional()
|
|
95
|
+
}))
|
|
96
|
+
.execute(async ({ input, ctx }) => {
|
|
97
|
+
const response = await ctx!.fetch(input.url, {
|
|
98
|
+
method: 'PUT',
|
|
99
|
+
headers: {
|
|
100
|
+
'Content-Type': 'application/json',
|
|
101
|
+
...input.headers
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify(input.body)
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const data = await response.json().catch(() => response.text());
|
|
107
|
+
return {
|
|
108
|
+
status: response.status,
|
|
109
|
+
data
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export const apiDelete = createAITool('api.delete')
|
|
114
|
+
.describe('Make a DELETE request to an API endpoint')
|
|
115
|
+
.tag('api', 'network', 'data')
|
|
116
|
+
.input(z.object({
|
|
117
|
+
url: z.string(),
|
|
118
|
+
headers: z.record(z.string()).optional()
|
|
119
|
+
}))
|
|
120
|
+
.execute(async ({ input, ctx }) => {
|
|
121
|
+
const response = await ctx!.fetch(input.url, {
|
|
122
|
+
method: 'DELETE',
|
|
123
|
+
headers: input.headers
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
status: response.status,
|
|
128
|
+
success: response.ok
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export const apiGraphQL = createAITool('api.graphql')
|
|
133
|
+
.describe('Execute a GraphQL query or mutation')
|
|
134
|
+
.tag('api', 'graphql', 'data')
|
|
135
|
+
.input(z.object({
|
|
136
|
+
endpoint: z.string(),
|
|
137
|
+
query: z.string(),
|
|
138
|
+
variables: z.record(z.any()).optional(),
|
|
139
|
+
operationName: z.string().optional(),
|
|
140
|
+
headers: z.record(z.string()).optional()
|
|
141
|
+
}))
|
|
142
|
+
.execute(async ({ input, ctx }) => {
|
|
143
|
+
const response = await ctx!.fetch(input.endpoint, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
headers: {
|
|
146
|
+
'Content-Type': 'application/json',
|
|
147
|
+
...input.headers
|
|
148
|
+
},
|
|
149
|
+
body: JSON.stringify({
|
|
150
|
+
query: input.query,
|
|
151
|
+
variables: input.variables,
|
|
152
|
+
operationName: input.operationName
|
|
153
|
+
})
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const result = await response.json();
|
|
157
|
+
return {
|
|
158
|
+
data: result.data,
|
|
159
|
+
errors: result.errors,
|
|
160
|
+
status: response.status
|
|
161
|
+
};
|
|
162
|
+
})
|
|
163
|
+
.render(({ data }) => (
|
|
164
|
+
<div className="space-y-2">
|
|
165
|
+
{data.errors && (
|
|
166
|
+
<div className="text-red-600">
|
|
167
|
+
Errors: {JSON.stringify(data.errors)}
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
<pre className="bg-gray-100 p-2 rounded">
|
|
171
|
+
{JSON.stringify(data.data, null, 2)}
|
|
172
|
+
</pre>
|
|
173
|
+
</div>
|
|
174
|
+
));
|
|
175
|
+
|
|
176
|
+
export const apiWebSocket = createAITool('api.websocket')
|
|
177
|
+
.describe('Establish WebSocket connection')
|
|
178
|
+
.tag('api', 'websocket', 'realtime')
|
|
179
|
+
.input(z.object({
|
|
180
|
+
url: z.string(),
|
|
181
|
+
protocols: z.array(z.string()).optional()
|
|
182
|
+
}))
|
|
183
|
+
.clientExecute(async ({ input }) => {
|
|
184
|
+
const ws = new WebSocket(input.url, input.protocols);
|
|
185
|
+
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
ws.onopen = () => {
|
|
188
|
+
resolve({
|
|
189
|
+
connected: true,
|
|
190
|
+
url: input.url,
|
|
191
|
+
readyState: ws.readyState,
|
|
192
|
+
send: (data: string) => ws.send(data),
|
|
193
|
+
close: () => ws.close(),
|
|
194
|
+
onMessage: (handler: (event: MessageEvent) => void) => {
|
|
195
|
+
ws.onmessage = handler;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
ws.onerror = (error) => {
|
|
201
|
+
reject(new Error(`WebSocket connection failed: ${error}`));
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
export const apiSSE = createAITool('api.sse')
|
|
207
|
+
.describe('Connect to Server-Sent Events')
|
|
208
|
+
.tag('api', 'sse', 'realtime')
|
|
209
|
+
.input(z.object({
|
|
210
|
+
url: z.string(),
|
|
211
|
+
withCredentials: z.boolean().optional()
|
|
212
|
+
}))
|
|
213
|
+
.clientExecute(async ({ input }) => {
|
|
214
|
+
const eventSource = new EventSource(input.url, {
|
|
215
|
+
withCredentials: input.withCredentials
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
connected: true,
|
|
220
|
+
url: input.url,
|
|
221
|
+
readyState: eventSource.readyState,
|
|
222
|
+
onMessage: (handler: (event: MessageEvent) => void) => {
|
|
223
|
+
eventSource.onmessage = handler;
|
|
224
|
+
},
|
|
225
|
+
onEvent: (event: string, handler: (event: MessageEvent) => void) => {
|
|
226
|
+
eventSource.addEventListener(event, handler);
|
|
227
|
+
},
|
|
228
|
+
close: () => eventSource.close()
|
|
229
|
+
};
|
|
230
|
+
});
|