@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,367 @@
1
+ import { z } from 'zod';
2
+ import aui from './index';
3
+ import { createAITool } from './ai-control';
4
+ import React from 'react';
5
+
6
+ export const weatherTool = aui
7
+ .tool('weather')
8
+ .input(z.object({
9
+ city: z.string(),
10
+ units: z.enum(['celsius', 'fahrenheit']).default('celsius').optional()
11
+ }))
12
+ .execute(async ({ input }) => {
13
+ const mockTemp = Math.floor(Math.random() * 30) + 10;
14
+ const conditions = ['sunny', 'cloudy', 'rainy', 'partly cloudy'];
15
+ const condition = conditions[Math.floor(Math.random() * conditions.length)];
16
+
17
+ return {
18
+ temp: input.units === 'fahrenheit' ? Math.floor(mockTemp * 9/5 + 32) : mockTemp,
19
+ city: input.city,
20
+ condition,
21
+ units: input.units || 'celsius'
22
+ };
23
+ })
24
+ .render(({ data }) => (
25
+ <div className="weather-widget p-4 bg-blue-50 rounded-lg">
26
+ <h3 className="text-lg font-bold">{data.city}</h3>
27
+ <p className="text-2xl">{data.temp}°{data.units === 'celsius' ? 'C' : 'F'}</p>
28
+ <p className="text-gray-600">{data.condition}</p>
29
+ </div>
30
+ ));
31
+
32
+ export const searchTool = aui
33
+ .tool('search')
34
+ .input(z.object({
35
+ query: z.string(),
36
+ limit: z.number().default(10).optional(),
37
+ filters: z.object({
38
+ category: z.string().optional(),
39
+ dateRange: z.object({
40
+ from: z.string().optional(),
41
+ to: z.string().optional()
42
+ }).optional()
43
+ }).optional()
44
+ }))
45
+ .execute(async ({ input, ctx }) => {
46
+ const results = Array.from({ length: input.limit || 10 }, (_, i) => ({
47
+ id: i + 1,
48
+ title: `Result ${i + 1} for "${input.query}"`,
49
+ description: `This is a search result for ${input.query}`,
50
+ url: `https://example.com/result-${i + 1}`,
51
+ relevance: Math.random()
52
+ })).sort((a, b) => b.relevance - a.relevance);
53
+
54
+ return {
55
+ query: input.query,
56
+ results,
57
+ totalCount: 100,
58
+ filters: input.filters
59
+ };
60
+ })
61
+ .clientExecute(async ({ input, ctx }) => {
62
+ const cached = ctx.cache.get(input.query);
63
+ if (cached) return cached;
64
+
65
+ const response = await ctx.fetch('/api/tools/search', {
66
+ method: 'POST',
67
+ headers: { 'Content-Type': 'application/json' },
68
+ body: JSON.stringify(input)
69
+ });
70
+
71
+ const data = await response.json();
72
+ ctx.cache.set(input.query, data);
73
+ return data;
74
+ })
75
+ .render(({ data, loading, error }) => {
76
+ if (loading) return <div>Searching...</div>;
77
+ if (error) return <div>Error: {error.message}</div>;
78
+
79
+ return (
80
+ <div className="search-results">
81
+ <h3>Results for &quot;{data.query}&quot; ({data.totalCount} total)</h3>
82
+ <ul>
83
+ {data.results.map((result: any) => (
84
+ <li key={result.id} className="mb-2">
85
+ <a href={result.url} className="text-blue-600 hover:underline">
86
+ {result.title}
87
+ </a>
88
+ <p className="text-sm text-gray-600">{result.description}</p>
89
+ </li>
90
+ ))}
91
+ </ul>
92
+ </div>
93
+ );
94
+ });
95
+
96
+ export const dataVisualizationTool = createAITool('data-viz', {
97
+ permissions: {
98
+ allowClientExecution: true,
99
+ allowServerExecution: true
100
+ },
101
+ audit: true
102
+ })
103
+ .input(z.object({
104
+ type: z.enum(['chart', 'table', 'metric']),
105
+ data: z.array(z.record(z.any())),
106
+ config: z.object({
107
+ title: z.string().optional(),
108
+ xAxis: z.string().optional(),
109
+ yAxis: z.string().optional(),
110
+ color: z.string().optional()
111
+ }).optional()
112
+ }))
113
+ .describe('Visualize data in various formats')
114
+ .tag('visualization', 'data', 'chart')
115
+ .execute(async ({ input }) => {
116
+ return {
117
+ type: input.type,
118
+ processedData: input.data,
119
+ config: {
120
+ title: input.config?.title || 'Data Visualization',
121
+ ...input.config
122
+ }
123
+ };
124
+ })
125
+ .render(({ data }) => {
126
+ switch (data.type) {
127
+ case 'metric':
128
+ const value = data.processedData[0]?.value || 0;
129
+ return (
130
+ <div className="metric-display p-6 bg-gradient-to-r from-blue-500 to-purple-500 text-white rounded-lg">
131
+ <h3 className="text-sm opacity-90">{data.config.title}</h3>
132
+ <p className="text-4xl font-bold">{value}</p>
133
+ </div>
134
+ );
135
+
136
+ case 'table':
137
+ return (
138
+ <div className="table-container">
139
+ <h3 className="text-lg font-bold mb-2">{data.config.title}</h3>
140
+ <table className="w-full border-collapse">
141
+ <thead>
142
+ <tr>
143
+ {Object.keys(data.processedData[0] || {}).map(key => (
144
+ <th key={key} className="border p-2 bg-gray-100">{key}</th>
145
+ ))}
146
+ </tr>
147
+ </thead>
148
+ <tbody>
149
+ {data.processedData.map((row, i) => (
150
+ <tr key={i}>
151
+ {Object.values(row).map((val, j) => (
152
+ <td key={j} className="border p-2">{String(val)}</td>
153
+ ))}
154
+ </tr>
155
+ ))}
156
+ </tbody>
157
+ </table>
158
+ </div>
159
+ );
160
+
161
+ default:
162
+ return (
163
+ <div className="chart-placeholder p-8 bg-gray-100 rounded-lg text-center">
164
+ <p>Chart: {data.config.title}</p>
165
+ <p className="text-sm text-gray-600">
166
+ {data.processedData.length} data points
167
+ </p>
168
+ </div>
169
+ );
170
+ }
171
+ });
172
+
173
+ export const userInteractionTool = createAITool('user-interaction', {
174
+ permissions: {
175
+ allowClientExecution: true
176
+ },
177
+ audit: true
178
+ })
179
+ .input(z.object({
180
+ type: z.enum(['alert', 'confirm', 'prompt', 'notification']),
181
+ message: z.string(),
182
+ title: z.string().optional(),
183
+ options: z.object({
184
+ confirmText: z.string().optional(),
185
+ cancelText: z.string().optional(),
186
+ defaultValue: z.string().optional(),
187
+ timeout: z.number().optional()
188
+ }).optional()
189
+ }))
190
+ .describe('Interact with users through various UI patterns')
191
+ .tag('ui', 'interaction', 'user')
192
+ .clientExecute(async ({ input }) => {
193
+ switch (input.type) {
194
+ case 'alert':
195
+ alert(input.message);
196
+ return { type: 'alert', acknowledged: true };
197
+
198
+ case 'confirm':
199
+ const confirmed = confirm(input.message);
200
+ return { type: 'confirm', confirmed };
201
+
202
+ case 'prompt':
203
+ const response = prompt(input.message, input.options?.defaultValue);
204
+ return { type: 'prompt', response };
205
+
206
+ case 'notification':
207
+ if ('Notification' in window && Notification.permission === 'granted') {
208
+ new Notification(input.title || 'Notification', {
209
+ body: input.message,
210
+ icon: '/icon.png'
211
+ });
212
+ }
213
+ return { type: 'notification', sent: true };
214
+
215
+ default:
216
+ throw new Error('Unknown interaction type');
217
+ }
218
+ });
219
+
220
+ export const workflowTool = aui
221
+ .tool('workflow')
222
+ .input(z.object({
223
+ steps: z.array(z.object({
224
+ id: z.string(),
225
+ tool: z.string(),
226
+ input: z.any(),
227
+ dependsOn: z.array(z.string()).optional()
228
+ }))
229
+ }))
230
+ .describe('Execute multi-step workflows with tool orchestration')
231
+ .tag('workflow', 'orchestration', 'automation')
232
+ .execute(async ({ input, ctx }) => {
233
+ const results = new Map<string, any>();
234
+ const completed = new Set<string>();
235
+
236
+ const executeStep = async (step: any) => {
237
+ if (step.dependsOn) {
238
+ for (const dep of step.dependsOn) {
239
+ if (!completed.has(dep)) {
240
+ const depStep = input.steps.find((s: any) => s.id === dep);
241
+ if (depStep) await executeStep(depStep);
242
+ }
243
+ }
244
+ }
245
+
246
+ if (!completed.has(step.id)) {
247
+ const tool = aui.get(step.tool);
248
+ if (tool) {
249
+ const result = await tool.run(step.input, ctx);
250
+ results.set(step.id, result);
251
+ completed.add(step.id);
252
+ }
253
+ }
254
+ };
255
+
256
+ for (const step of input.steps) {
257
+ await executeStep(step);
258
+ }
259
+
260
+ return {
261
+ workflow: 'completed',
262
+ steps: Array.from(results.entries()).map(([id, result]) => ({
263
+ id,
264
+ result
265
+ }))
266
+ };
267
+ });
268
+
269
+ export const analyticsTrackingTool = createAITool('analytics', {
270
+ permissions: {
271
+ allowClientExecution: true
272
+ },
273
+ audit: true,
274
+ rateLimit: {
275
+ requestsPerMinute: 100
276
+ }
277
+ })
278
+ .input(z.object({
279
+ event: z.string(),
280
+ properties: z.record(z.any()).optional(),
281
+ userId: z.string().optional(),
282
+ timestamp: z.string().optional()
283
+ }))
284
+ .describe('Track analytics events and user behavior')
285
+ .tag('analytics', 'tracking', 'metrics')
286
+ .clientExecute(async ({ input }) => {
287
+ console.log('Analytics Event:', input);
288
+
289
+ if (typeof window !== 'undefined' && (window as any).gtag) {
290
+ (window as any).gtag('event', input.event, input.properties);
291
+ }
292
+
293
+ return {
294
+ tracked: true,
295
+ event: input.event,
296
+ timestamp: input.timestamp || new Date().toISOString()
297
+ };
298
+ });
299
+
300
+ export const stateManagementTool = aui
301
+ .tool('state-manager')
302
+ .input(z.object({
303
+ action: z.enum(['get', 'set', 'update', 'subscribe']),
304
+ key: z.string(),
305
+ value: z.any().optional(),
306
+ updater: z.function().optional()
307
+ }))
308
+ .describe('Manage application state across components')
309
+ .tag('state', 'management', 'store')
310
+ .execute(async ({ input }) => {
311
+ const globalState = (global as any).__appState || ((global as any).__appState = new Map());
312
+
313
+ switch (input.action) {
314
+ case 'get':
315
+ return { value: globalState.get(input.key) };
316
+
317
+ case 'set':
318
+ globalState.set(input.key, input.value);
319
+ return { success: true, key: input.key };
320
+
321
+ case 'update':
322
+ const current = globalState.get(input.key);
323
+ const updated = input.updater ? input.updater(current) : { ...current, ...input.value };
324
+ globalState.set(input.key, updated);
325
+ return { success: true, key: input.key, value: updated };
326
+
327
+ case 'subscribe':
328
+ return {
329
+ success: true,
330
+ message: 'Subscription would be handled by React context or state management library'
331
+ };
332
+
333
+ default:
334
+ throw new Error('Unknown state action');
335
+ }
336
+ })
337
+ .clientExecute(async ({ input, ctx }) => {
338
+ const stateCache = ctx.cache;
339
+
340
+ switch (input.action) {
341
+ case 'get':
342
+ return { value: stateCache.get(input.key) };
343
+
344
+ case 'set':
345
+ stateCache.set(input.key, input.value);
346
+ return { success: true, key: input.key };
347
+
348
+ case 'update':
349
+ const current = stateCache.get(input.key);
350
+ const updated = { ...current, ...input.value };
351
+ stateCache.set(input.key, updated);
352
+ return { success: true, key: input.key, value: updated };
353
+
354
+ default:
355
+ return { success: false, message: 'Unknown action' };
356
+ }
357
+ });
358
+
359
+ export const allExampleTools = [
360
+ weatherTool,
361
+ searchTool,
362
+ dataVisualizationTool,
363
+ userInteractionTool,
364
+ workflowTool,
365
+ analyticsTrackingTool,
366
+ stateManagementTool
367
+ ];
@@ -0,0 +1,142 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback, useRef } from 'react';
4
+ import type { AUITool, AUIContext } from '../index';
5
+
6
+ export interface UseAUIToolOptions {
7
+ cache?: boolean;
8
+ cacheTime?: number;
9
+ onSuccess?: (data: any) => void;
10
+ onError?: (error: Error) => void;
11
+ }
12
+
13
+ export function useAUITool<TInput = any, TOutput = any>(
14
+ tool: AUITool<TInput, TOutput>,
15
+ options: UseAUIToolOptions = {}
16
+ ) {
17
+ const [data, setData] = useState<TOutput | null>(null);
18
+ const [loading, setLoading] = useState(false);
19
+ const [error, setError] = useState<Error | null>(null);
20
+
21
+ const cacheRef = useRef(new Map<string, { data: TOutput; timestamp: number }>());
22
+ const contextRef = useRef<AUIContext>({
23
+ cache: new Map(),
24
+ fetch: typeof window !== 'undefined' ? window.fetch.bind(window) : fetch,
25
+ });
26
+
27
+ const execute = useCallback(async (input: TInput) => {
28
+ setLoading(true);
29
+ setError(null);
30
+
31
+ try {
32
+ // Check cache if enabled
33
+ if (options.cache) {
34
+ const cacheKey = JSON.stringify(input);
35
+ const cached = cacheRef.current.get(cacheKey);
36
+
37
+ if (cached) {
38
+ const age = Date.now() - cached.timestamp;
39
+ const maxAge = options.cacheTime || 60000; // Default 1 minute
40
+
41
+ if (age < maxAge) {
42
+ setData(cached.data);
43
+ setLoading(false);
44
+ options.onSuccess?.(cached.data);
45
+ return cached.data;
46
+ }
47
+ }
48
+ }
49
+
50
+ // Execute the tool
51
+ const result = await tool.run(input, contextRef.current);
52
+
53
+ // Cache the result if caching is enabled
54
+ if (options.cache) {
55
+ const cacheKey = JSON.stringify(input);
56
+ cacheRef.current.set(cacheKey, {
57
+ data: result,
58
+ timestamp: Date.now(),
59
+ });
60
+ }
61
+
62
+ setData(result);
63
+ options.onSuccess?.(result);
64
+ return result;
65
+ } catch (err) {
66
+ const error = err instanceof Error ? err : new Error(String(err));
67
+ setError(error);
68
+ options.onError?.(error);
69
+ throw error;
70
+ } finally {
71
+ setLoading(false);
72
+ }
73
+ }, [tool, options]);
74
+
75
+ const reset = useCallback(() => {
76
+ setData(null);
77
+ setError(null);
78
+ setLoading(false);
79
+ }, []);
80
+
81
+ const clearCache = useCallback(() => {
82
+ cacheRef.current.clear();
83
+ contextRef.current.cache.clear();
84
+ }, []);
85
+
86
+ return {
87
+ data,
88
+ loading,
89
+ error,
90
+ execute,
91
+ reset,
92
+ clearCache,
93
+ };
94
+ }
95
+
96
+ // Hook for executing tools by name
97
+ export function useAUI() {
98
+ const [loading, setLoading] = useState<Record<string, boolean>>({});
99
+ const [errors, setErrors] = useState<Record<string, Error | null>>({});
100
+ const [data, setData] = useState<Record<string, any>>({});
101
+
102
+ const contextRef = useRef<AUIContext>({
103
+ cache: new Map(),
104
+ fetch: typeof window !== 'undefined' ? window.fetch.bind(window) : fetch,
105
+ });
106
+
107
+ const execute = useCallback(async (toolName: string, input: any) => {
108
+ setLoading(prev => ({ ...prev, [toolName]: true }));
109
+ setErrors(prev => ({ ...prev, [toolName]: null }));
110
+
111
+ try {
112
+ const response = await fetch('/api/aui/execute', {
113
+ method: 'POST',
114
+ headers: { 'Content-Type': 'application/json' },
115
+ body: JSON.stringify({ tool: toolName, input }),
116
+ });
117
+
118
+ if (!response.ok) {
119
+ const error = await response.json();
120
+ throw new Error(error.error || 'Tool execution failed');
121
+ }
122
+
123
+ const result = await response.json();
124
+ setData(prev => ({ ...prev, [toolName]: result.data }));
125
+ return result.data;
126
+ } catch (err) {
127
+ const error = err instanceof Error ? err : new Error(String(err));
128
+ setErrors(prev => ({ ...prev, [toolName]: error }));
129
+ throw error;
130
+ } finally {
131
+ setLoading(prev => ({ ...prev, [toolName]: false }));
132
+ }
133
+ }, []);
134
+
135
+ return {
136
+ execute,
137
+ loading,
138
+ errors,
139
+ data,
140
+ context: contextRef.current,
141
+ };
142
+ }