@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,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 "{data.query}" ({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
|
+
}
|