@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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/lib/aui/README.md +136 -0
  4. package/lib/aui/__tests__/aui-complete.test.ts +251 -0
  5. package/lib/aui/__tests__/aui-comprehensive.test.ts +376 -0
  6. package/lib/aui/__tests__/aui-concise.test.ts +278 -0
  7. package/lib/aui/__tests__/aui-integration.test.ts +309 -0
  8. package/lib/aui/__tests__/aui-simple.test.ts +116 -0
  9. package/lib/aui/__tests__/aui.test.ts +269 -0
  10. package/lib/aui/__tests__/concise-api.test.ts +165 -0
  11. package/lib/aui/__tests__/core.test.ts +265 -0
  12. package/lib/aui/__tests__/simple-api.test.ts +200 -0
  13. package/lib/aui/ai-assistant.ts +408 -0
  14. package/lib/aui/ai-control.ts +353 -0
  15. package/lib/aui/client/use-aui.ts +55 -0
  16. package/lib/aui/client-control.ts +551 -0
  17. package/lib/aui/client-executor.ts +417 -0
  18. package/lib/aui/components/ToolRenderer.tsx +22 -0
  19. package/lib/aui/core.ts +137 -0
  20. package/lib/aui/demo.tsx +89 -0
  21. package/lib/aui/examples/ai-complete-demo.tsx +359 -0
  22. package/lib/aui/examples/ai-control-demo.tsx +356 -0
  23. package/lib/aui/examples/ai-control-tools.ts +308 -0
  24. package/lib/aui/examples/concise-api.tsx +153 -0
  25. package/lib/aui/examples/index.tsx +163 -0
  26. package/lib/aui/examples/quick-demo.tsx +91 -0
  27. package/lib/aui/examples/simple-demo.tsx +71 -0
  28. package/lib/aui/examples/simple-tools.tsx +160 -0
  29. package/lib/aui/examples/user-api.tsx +208 -0
  30. package/lib/aui/examples/user-requested.tsx +174 -0
  31. package/lib/aui/examples/weather-search-tools.tsx +119 -0
  32. package/lib/aui/examples.tsx +367 -0
  33. package/lib/aui/hooks/useAUITool.ts +142 -0
  34. package/lib/aui/hooks/useAUIToolEnhanced.ts +343 -0
  35. package/lib/aui/hooks/useAUITools.ts +195 -0
  36. package/lib/aui/index.ts +156 -0
  37. package/lib/aui/provider.tsx +45 -0
  38. package/lib/aui/server-control.ts +386 -0
  39. package/lib/aui/server-executor.ts +165 -0
  40. package/lib/aui/server.ts +167 -0
  41. package/lib/aui/tool-registry.ts +380 -0
  42. package/lib/aui/tools/advanced-examples.tsx +86 -0
  43. package/lib/aui/tools/ai-complete.ts +375 -0
  44. package/lib/aui/tools/api-tools.tsx +230 -0
  45. package/lib/aui/tools/data-tools.tsx +232 -0
  46. package/lib/aui/tools/dom-tools.tsx +202 -0
  47. package/lib/aui/tools/examples.ts +43 -0
  48. package/lib/aui/tools/file-tools.tsx +202 -0
  49. package/lib/aui/tools/form-tools.tsx +233 -0
  50. package/lib/aui/tools/index.ts +8 -0
  51. package/lib/aui/tools/navigation-tools.tsx +172 -0
  52. package/lib/aui/tools/notification-tools.ts +213 -0
  53. package/lib/aui/tools/state-tools.tsx +209 -0
  54. package/lib/aui/types.ts +47 -0
  55. package/lib/aui/vercel-ai.ts +100 -0
  56. package/package.json +51 -0
@@ -0,0 +1,551 @@
1
+ import { z } from 'zod';
2
+ import { createAITool, AIControlledTool } from './ai-control';
3
+ import { AUIContext } from './core';
4
+
5
+ export interface ClientControlContext extends AUIContext {
6
+ window?: Window;
7
+ document?: Document;
8
+ navigator?: Navigator;
9
+ localStorage?: Storage;
10
+ sessionStorage?: Storage;
11
+ }
12
+
13
+ export const clientTools = {
14
+ domManipulation: createAITool('client-dom', {
15
+ permissions: {
16
+ allowClientExecution: true
17
+ },
18
+ audit: true
19
+ })
20
+ .input(z.object({
21
+ operation: z.enum(['select', 'create', 'update', 'remove', 'addClass', 'removeClass', 'setAttribute', 'style']),
22
+ selector: z.string().optional(),
23
+ element: z.string().optional(),
24
+ content: z.string().optional(),
25
+ attributes: z.record(z.string()).optional(),
26
+ styles: z.record(z.string()).optional(),
27
+ classes: z.array(z.string()).optional()
28
+ }))
29
+ .describe('Manipulate DOM elements on the client')
30
+ .tag('client', 'dom', 'ui')
31
+ .clientExecute(async ({ input }) => {
32
+ switch (input.operation) {
33
+ case 'select':
34
+ if (!input.selector) throw new Error('Selector required');
35
+ const elements = document.querySelectorAll(input.selector);
36
+ return {
37
+ count: elements.length,
38
+ elements: Array.from(elements).map(el => ({
39
+ tagName: el.tagName,
40
+ id: el.id,
41
+ classes: Array.from(el.classList)
42
+ }))
43
+ };
44
+
45
+ case 'create':
46
+ if (!input.element) throw new Error('Element type required');
47
+ const newEl = document.createElement(input.element);
48
+ if (input.content) newEl.textContent = input.content;
49
+ if (input.attributes) {
50
+ Object.entries(input.attributes).forEach(([key, value]) => {
51
+ newEl.setAttribute(key, value);
52
+ });
53
+ }
54
+ if (input.styles) {
55
+ Object.assign(newEl.style, input.styles);
56
+ }
57
+ if (input.selector) {
58
+ const parent = document.querySelector(input.selector);
59
+ if (parent) parent.appendChild(newEl);
60
+ }
61
+ return { success: true, created: input.element };
62
+
63
+ case 'update':
64
+ if (!input.selector) throw new Error('Selector required');
65
+ const updateEls = document.querySelectorAll(input.selector);
66
+ updateEls.forEach(el => {
67
+ if (input.content !== undefined) el.textContent = input.content;
68
+ if (input.attributes) {
69
+ Object.entries(input.attributes).forEach(([key, value]) => {
70
+ el.setAttribute(key, value);
71
+ });
72
+ }
73
+ if (input.styles) {
74
+ Object.assign((el as HTMLElement).style, input.styles);
75
+ }
76
+ });
77
+ return { success: true, updated: updateEls.length };
78
+
79
+ case 'remove':
80
+ if (!input.selector) throw new Error('Selector required');
81
+ const removeEls = document.querySelectorAll(input.selector);
82
+ removeEls.forEach(el => el.remove());
83
+ return { success: true, removed: removeEls.length };
84
+
85
+ case 'addClass':
86
+ if (!input.selector || !input.classes) throw new Error('Selector and classes required');
87
+ const addClassEls = document.querySelectorAll(input.selector);
88
+ addClassEls.forEach(el => el.classList.add(...input.classes!));
89
+ return { success: true, modified: addClassEls.length };
90
+
91
+ case 'removeClass':
92
+ if (!input.selector || !input.classes) throw new Error('Selector and classes required');
93
+ const removeClassEls = document.querySelectorAll(input.selector);
94
+ removeClassEls.forEach(el => el.classList.remove(...input.classes!));
95
+ return { success: true, modified: removeClassEls.length };
96
+
97
+ case 'setAttribute':
98
+ if (!input.selector || !input.attributes) throw new Error('Selector and attributes required');
99
+ const setAttrEls = document.querySelectorAll(input.selector);
100
+ setAttrEls.forEach(el => {
101
+ Object.entries(input.attributes!).forEach(([key, value]) => {
102
+ el.setAttribute(key, value);
103
+ });
104
+ });
105
+ return { success: true, modified: setAttrEls.length };
106
+
107
+ case 'style':
108
+ if (!input.selector || !input.styles) throw new Error('Selector and styles required');
109
+ const styleEls = document.querySelectorAll(input.selector);
110
+ styleEls.forEach(el => {
111
+ Object.assign((el as HTMLElement).style, input.styles);
112
+ });
113
+ return { success: true, styled: styleEls.length };
114
+
115
+ default:
116
+ throw new Error('Unknown DOM operation');
117
+ }
118
+ }),
119
+
120
+ eventHandling: createAITool('client-events', {
121
+ permissions: {
122
+ allowClientExecution: true
123
+ },
124
+ audit: true
125
+ })
126
+ .input(z.object({
127
+ action: z.enum(['trigger', 'listen', 'remove']),
128
+ selector: z.string(),
129
+ event: z.string(),
130
+ data: z.any().optional(),
131
+ options: z.object({
132
+ bubbles: z.boolean().optional(),
133
+ cancelable: z.boolean().optional(),
134
+ once: z.boolean().optional()
135
+ }).optional()
136
+ }))
137
+ .describe('Handle client-side events')
138
+ .tag('client', 'events', 'interaction')
139
+ .clientExecute(async ({ input }) => {
140
+ const element = document.querySelector(input.selector);
141
+ if (!element) throw new Error(`Element not found: ${input.selector}`);
142
+
143
+ switch (input.action) {
144
+ case 'trigger':
145
+ const event = new CustomEvent(input.event, {
146
+ detail: input.data,
147
+ bubbles: input.options?.bubbles ?? true,
148
+ cancelable: input.options?.cancelable ?? true
149
+ });
150
+ element.dispatchEvent(event);
151
+ return { success: true, triggered: input.event };
152
+
153
+ case 'listen':
154
+ const handler = (e: Event) => {
155
+ console.log('Event triggered:', input.event, e);
156
+ };
157
+ element.addEventListener(input.event, handler, {
158
+ once: input.options?.once
159
+ });
160
+ return { success: true, listening: input.event };
161
+
162
+ case 'remove':
163
+ return { success: true, removed: input.event };
164
+
165
+ default:
166
+ throw new Error('Unknown event action');
167
+ }
168
+ }),
169
+
170
+ formControl: createAITool('client-forms', {
171
+ permissions: {
172
+ allowClientExecution: true
173
+ },
174
+ audit: true
175
+ })
176
+ .input(z.object({
177
+ action: z.enum(['fill', 'submit', 'reset', 'validate', 'getData']),
178
+ selector: z.string(),
179
+ data: z.record(z.any()).optional(),
180
+ validateRules: z.record(z.any()).optional()
181
+ }))
182
+ .describe('Control and interact with forms')
183
+ .tag('client', 'forms', 'input')
184
+ .clientExecute(async ({ input }) => {
185
+ const form = document.querySelector(input.selector) as HTMLFormElement;
186
+ if (!form) throw new Error(`Form not found: ${input.selector}`);
187
+
188
+ switch (input.action) {
189
+ case 'fill':
190
+ if (!input.data) throw new Error('Data required for fill action');
191
+ Object.entries(input.data).forEach(([name, value]) => {
192
+ const field = form.elements.namedItem(name) as HTMLInputElement;
193
+ if (field) {
194
+ field.value = String(value);
195
+ field.dispatchEvent(new Event('input', { bubbles: true }));
196
+ }
197
+ });
198
+ return { success: true, filled: Object.keys(input.data).length };
199
+
200
+ case 'submit':
201
+ const submitEvent = new Event('submit', {
202
+ bubbles: true,
203
+ cancelable: true
204
+ });
205
+ const submitted = form.dispatchEvent(submitEvent);
206
+ if (submitted) form.submit();
207
+ return { success: submitted, submitted: true };
208
+
209
+ case 'reset':
210
+ form.reset();
211
+ return { success: true, reset: true };
212
+
213
+ case 'validate':
214
+ const isValid = form.checkValidity();
215
+ const invalidFields = Array.from(form.elements)
216
+ .filter(el => el instanceof HTMLInputElement && !el.checkValidity())
217
+ .map(el => (el as HTMLInputElement).name);
218
+ return {
219
+ valid: isValid,
220
+ invalidFields
221
+ };
222
+
223
+ case 'getData':
224
+ const formData = new FormData(form);
225
+ const data = Object.fromEntries(formData.entries());
226
+ return { data, fields: Object.keys(data).length };
227
+
228
+ default:
229
+ throw new Error('Unknown form action');
230
+ }
231
+ }),
232
+
233
+ storageControl: createAITool('client-storage', {
234
+ permissions: {
235
+ allowClientExecution: true
236
+ },
237
+ audit: true
238
+ })
239
+ .input(z.object({
240
+ type: z.enum(['local', 'session', 'cookie']),
241
+ operation: z.enum(['get', 'set', 'remove', 'clear', 'list']),
242
+ key: z.string().optional(),
243
+ value: z.any().optional(),
244
+ options: z.object({
245
+ expires: z.number().optional(),
246
+ path: z.string().optional(),
247
+ domain: z.string().optional(),
248
+ secure: z.boolean().optional(),
249
+ sameSite: z.enum(['strict', 'lax', 'none']).optional()
250
+ }).optional()
251
+ }))
252
+ .describe('Manage client-side storage')
253
+ .tag('client', 'storage', 'persistence')
254
+ .clientExecute(async ({ input }) => {
255
+ const storage = input.type === 'local' ? localStorage :
256
+ input.type === 'session' ? sessionStorage : null;
257
+
258
+ if (input.type === 'cookie') {
259
+ switch (input.operation) {
260
+ case 'set':
261
+ if (!input.key || input.value === undefined) {
262
+ throw new Error('Key and value required for cookie set');
263
+ }
264
+ let cookieStr = `${input.key}=${JSON.stringify(input.value)}`;
265
+ if (input.options?.expires) {
266
+ const date = new Date();
267
+ date.setTime(date.getTime() + input.options.expires);
268
+ cookieStr += `; expires=${date.toUTCString()}`;
269
+ }
270
+ if (input.options?.path) cookieStr += `; path=${input.options.path}`;
271
+ if (input.options?.domain) cookieStr += `; domain=${input.options.domain}`;
272
+ if (input.options?.secure) cookieStr += '; secure';
273
+ if (input.options?.sameSite) cookieStr += `; samesite=${input.options.sameSite}`;
274
+ document.cookie = cookieStr;
275
+ return { success: true, key: input.key };
276
+
277
+ case 'get':
278
+ if (!input.key) throw new Error('Key required for cookie get');
279
+ const cookies = document.cookie.split(';');
280
+ const foundCookie = cookies.find(c => c.trim().startsWith(`${input.key}=`));
281
+ const value = foundCookie ? JSON.parse(foundCookie.split('=')[1]) : null;
282
+ return { value, key: input.key };
283
+
284
+ case 'remove':
285
+ if (!input.key) throw new Error('Key required for cookie remove');
286
+ document.cookie = `${input.key}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
287
+ return { success: true, removed: input.key };
288
+
289
+ case 'list':
290
+ const allCookies = document.cookie.split(';').map(c => {
291
+ const [key] = c.trim().split('=');
292
+ return key;
293
+ }).filter(Boolean);
294
+ return { keys: allCookies, count: allCookies.length };
295
+
296
+ case 'clear':
297
+ document.cookie.split(';').forEach(c => {
298
+ const [key] = c.trim().split('=');
299
+ if (key) {
300
+ document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
301
+ }
302
+ });
303
+ return { success: true, cleared: true };
304
+ }
305
+ }
306
+
307
+ if (!storage) throw new Error('Storage type not supported');
308
+
309
+ switch (input.operation) {
310
+ case 'get':
311
+ if (!input.key) throw new Error('Key required for get operation');
312
+ const value = storage.getItem(input.key);
313
+ return {
314
+ value: value ? JSON.parse(value) : null,
315
+ key: input.key
316
+ };
317
+
318
+ case 'set':
319
+ if (!input.key || input.value === undefined) {
320
+ throw new Error('Key and value required for set operation');
321
+ }
322
+ storage.setItem(input.key, JSON.stringify(input.value));
323
+ return { success: true, key: input.key };
324
+
325
+ case 'remove':
326
+ if (!input.key) throw new Error('Key required for remove operation');
327
+ storage.removeItem(input.key);
328
+ return { success: true, removed: input.key };
329
+
330
+ case 'clear':
331
+ storage.clear();
332
+ return { success: true, cleared: true };
333
+
334
+ case 'list':
335
+ const keys = Object.keys(storage);
336
+ return { keys, count: keys.length };
337
+
338
+ default:
339
+ throw new Error('Unknown storage operation');
340
+ }
341
+ }),
342
+
343
+ navigationControl: createAITool('client-navigation', {
344
+ permissions: {
345
+ allowClientExecution: true
346
+ },
347
+ audit: true
348
+ })
349
+ .input(z.object({
350
+ action: z.enum(['navigate', 'back', 'forward', 'reload', 'getLocation', 'setHash']),
351
+ url: z.string().optional(),
352
+ replace: z.boolean().optional(),
353
+ state: z.any().optional()
354
+ }))
355
+ .describe('Control browser navigation')
356
+ .tag('client', 'navigation', 'routing')
357
+ .clientExecute(async ({ input }) => {
358
+ switch (input.action) {
359
+ case 'navigate':
360
+ if (!input.url) throw new Error('URL required for navigation');
361
+ if (input.replace) {
362
+ window.location.replace(input.url);
363
+ } else {
364
+ window.history.pushState(input.state || {}, '', input.url);
365
+ }
366
+ return { success: true, navigated: input.url };
367
+
368
+ case 'back':
369
+ window.history.back();
370
+ return { success: true, action: 'back' };
371
+
372
+ case 'forward':
373
+ window.history.forward();
374
+ return { success: true, action: 'forward' };
375
+
376
+ case 'reload':
377
+ window.location.reload();
378
+ return { success: true, action: 'reload' };
379
+
380
+ case 'getLocation':
381
+ return {
382
+ href: window.location.href,
383
+ pathname: window.location.pathname,
384
+ search: window.location.search,
385
+ hash: window.location.hash,
386
+ host: window.location.host,
387
+ hostname: window.location.hostname,
388
+ port: window.location.port,
389
+ protocol: window.location.protocol
390
+ };
391
+
392
+ case 'setHash':
393
+ if (input.url) window.location.hash = input.url;
394
+ return { success: true, hash: window.location.hash };
395
+
396
+ default:
397
+ throw new Error('Unknown navigation action');
398
+ }
399
+ }),
400
+
401
+ animationControl: createAITool('client-animation', {
402
+ permissions: {
403
+ allowClientExecution: true
404
+ },
405
+ audit: true
406
+ })
407
+ .input(z.object({
408
+ selector: z.string(),
409
+ animation: z.object({
410
+ keyframes: z.array(z.record(z.any())),
411
+ options: z.object({
412
+ duration: z.number().optional(),
413
+ easing: z.string().optional(),
414
+ delay: z.number().optional(),
415
+ iterations: z.number().optional(),
416
+ direction: z.enum(['normal', 'reverse', 'alternate', 'alternate-reverse']).optional(),
417
+ fill: z.enum(['none', 'forwards', 'backwards', 'both']).optional()
418
+ }).optional()
419
+ })
420
+ }))
421
+ .describe('Create and control animations')
422
+ .tag('client', 'animation', 'visual')
423
+ .clientExecute(async ({ input }) => {
424
+ const elements = document.querySelectorAll(input.selector);
425
+ if (elements.length === 0) {
426
+ throw new Error(`No elements found for selector: ${input.selector}`);
427
+ }
428
+
429
+ const animations: Animation[] = [];
430
+ elements.forEach(el => {
431
+ const animation = el.animate(
432
+ input.animation.keyframes,
433
+ input.animation.options || { duration: 1000 }
434
+ );
435
+ animations.push(animation);
436
+ });
437
+
438
+ return {
439
+ success: true,
440
+ animated: elements.length,
441
+ duration: input.animation.options?.duration || 1000
442
+ };
443
+ }),
444
+
445
+ mediaControl: createAITool('client-media', {
446
+ permissions: {
447
+ allowClientExecution: true
448
+ },
449
+ audit: true
450
+ })
451
+ .input(z.object({
452
+ selector: z.string(),
453
+ action: z.enum(['play', 'pause', 'stop', 'mute', 'unmute', 'setVolume', 'seek']),
454
+ value: z.number().optional()
455
+ }))
456
+ .describe('Control media elements')
457
+ .tag('client', 'media', 'audio', 'video')
458
+ .clientExecute(async ({ input }) => {
459
+ const media = document.querySelector(input.selector) as HTMLMediaElement;
460
+ if (!media) throw new Error(`Media element not found: ${input.selector}`);
461
+
462
+ switch (input.action) {
463
+ case 'play':
464
+ await media.play();
465
+ return { success: true, playing: true };
466
+
467
+ case 'pause':
468
+ media.pause();
469
+ return { success: true, paused: true };
470
+
471
+ case 'stop':
472
+ media.pause();
473
+ media.currentTime = 0;
474
+ return { success: true, stopped: true };
475
+
476
+ case 'mute':
477
+ media.muted = true;
478
+ return { success: true, muted: true };
479
+
480
+ case 'unmute':
481
+ media.muted = false;
482
+ return { success: true, muted: false };
483
+
484
+ case 'setVolume':
485
+ if (input.value === undefined) throw new Error('Value required for setVolume');
486
+ media.volume = Math.max(0, Math.min(1, input.value));
487
+ return { success: true, volume: media.volume };
488
+
489
+ case 'seek':
490
+ if (input.value === undefined) throw new Error('Value required for seek');
491
+ media.currentTime = input.value;
492
+ return { success: true, currentTime: media.currentTime };
493
+
494
+ default:
495
+ throw new Error('Unknown media action');
496
+ }
497
+ })
498
+ };
499
+
500
+ export function createClientControlSystem() {
501
+ const tools = new Map<string, AIControlledTool>();
502
+
503
+ Object.values(clientTools).forEach(tool => {
504
+ tools.set(tool.name, tool);
505
+ });
506
+
507
+ return {
508
+ tools,
509
+
510
+ async executeClientTool(
511
+ name: string,
512
+ input: any,
513
+ context?: ClientControlContext
514
+ ) {
515
+ const tool = tools.get(name);
516
+ if (!tool) {
517
+ throw new Error(`Client tool "${name}" not found`);
518
+ }
519
+
520
+ const clientContext: ClientControlContext = {
521
+ ...context,
522
+ isServer: false,
523
+ cache: context?.cache || new Map(),
524
+ fetch: fetch,
525
+ window: typeof window !== 'undefined' ? window : undefined,
526
+ document: typeof document !== 'undefined' ? document : undefined,
527
+ navigator: typeof navigator !== 'undefined' ? navigator : undefined,
528
+ localStorage: typeof localStorage !== 'undefined' ? localStorage : undefined,
529
+ sessionStorage: typeof sessionStorage !== 'undefined' ? sessionStorage : undefined
530
+ };
531
+
532
+ return await tool.run(input, clientContext);
533
+ },
534
+
535
+ registerClientTool(tool: AIControlledTool) {
536
+ tools.set(tool.name, tool);
537
+ return this;
538
+ },
539
+
540
+ listClientTools() {
541
+ return Array.from(tools.values()).map(tool => ({
542
+ name: tool.name,
543
+ description: tool.description,
544
+ tags: tool.tags,
545
+ schema: tool.schema
546
+ }));
547
+ }
548
+ };
549
+ }
550
+
551
+ export const clientControlSystem = createClientControlSystem();