@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,343 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
import { AUITool, AUIContext } from '../index';
|
|
3
|
+
|
|
4
|
+
export interface UseAUIToolOptions<TInput, TOutput> {
|
|
5
|
+
// Caching
|
|
6
|
+
cacheKey?: string | ((input: TInput) => string);
|
|
7
|
+
cacheDuration?: number; // milliseconds
|
|
8
|
+
|
|
9
|
+
// Retry
|
|
10
|
+
retryCount?: number;
|
|
11
|
+
retryDelay?: number | ((attempt: number) => number);
|
|
12
|
+
|
|
13
|
+
// Callbacks
|
|
14
|
+
onSuccess?: (data: TOutput, input: TInput) => void;
|
|
15
|
+
onError?: (error: Error, input: TInput) => void;
|
|
16
|
+
onLoadingChange?: (loading: boolean) => void;
|
|
17
|
+
|
|
18
|
+
// Debounce/Throttle
|
|
19
|
+
debounceMs?: number;
|
|
20
|
+
throttleMs?: number;
|
|
21
|
+
|
|
22
|
+
// Auto-execute
|
|
23
|
+
autoExecute?: boolean;
|
|
24
|
+
autoExecuteInput?: TInput;
|
|
25
|
+
|
|
26
|
+
// Polling
|
|
27
|
+
pollingInterval?: number;
|
|
28
|
+
pollingCondition?: (data: TOutput | null) => boolean;
|
|
29
|
+
|
|
30
|
+
// Context
|
|
31
|
+
context?: Partial<AUIContext>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UseAUIToolResult<TInput, TOutput> {
|
|
35
|
+
execute: (input: TInput) => Promise<TOutput>;
|
|
36
|
+
cancel: () => void;
|
|
37
|
+
reset: () => void;
|
|
38
|
+
data: TOutput | null;
|
|
39
|
+
error: Error | null;
|
|
40
|
+
loading: boolean;
|
|
41
|
+
isSuccess: boolean;
|
|
42
|
+
isError: boolean;
|
|
43
|
+
isIdle: boolean;
|
|
44
|
+
executionCount: number;
|
|
45
|
+
lastExecutedAt: Date | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useAUIToolEnhanced<TInput = any, TOutput = any>(
|
|
49
|
+
tool: AUITool<TInput, TOutput>,
|
|
50
|
+
options: UseAUIToolOptions<TInput, TOutput> = {}
|
|
51
|
+
): UseAUIToolResult<TInput, TOutput> {
|
|
52
|
+
const [data, setData] = useState<TOutput | null>(null);
|
|
53
|
+
const [error, setError] = useState<Error | null>(null);
|
|
54
|
+
const [loading, setLoading] = useState(false);
|
|
55
|
+
const [executionCount, setExecutionCount] = useState(0);
|
|
56
|
+
const [lastExecutedAt, setLastExecutedAt] = useState<Date | null>(null);
|
|
57
|
+
|
|
58
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
59
|
+
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
60
|
+
const throttleTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
61
|
+
const pollingTimerRef = useRef<NodeJS.Timeout | null>(null);
|
|
62
|
+
const cacheRef = useRef<Map<string, { data: TOutput; expires: number }>>(new Map());
|
|
63
|
+
|
|
64
|
+
// Clear timers on unmount
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
return () => {
|
|
67
|
+
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
68
|
+
if (throttleTimerRef.current) clearTimeout(throttleTimerRef.current);
|
|
69
|
+
if (pollingTimerRef.current) clearInterval(pollingTimerRef.current);
|
|
70
|
+
if (abortControllerRef.current) abortControllerRef.current.abort();
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
// Handle loading state changes
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
options.onLoadingChange?.(loading);
|
|
77
|
+
}, [loading, options]);
|
|
78
|
+
|
|
79
|
+
// Get cache key for input
|
|
80
|
+
const getCacheKey = useCallback((input: TInput): string => {
|
|
81
|
+
if (!options.cacheKey) return '';
|
|
82
|
+
if (typeof options.cacheKey === 'function') {
|
|
83
|
+
return options.cacheKey(input);
|
|
84
|
+
}
|
|
85
|
+
return options.cacheKey;
|
|
86
|
+
}, [options]);
|
|
87
|
+
|
|
88
|
+
// Check cache
|
|
89
|
+
const checkCache = useCallback((input: TInput): TOutput | null => {
|
|
90
|
+
if (!options.cacheKey) return null;
|
|
91
|
+
|
|
92
|
+
const key = getCacheKey(input);
|
|
93
|
+
const cached = cacheRef.current.get(key);
|
|
94
|
+
|
|
95
|
+
if (cached && cached.expires > Date.now()) {
|
|
96
|
+
return cached.data;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
}, [getCacheKey, options.cacheKey]);
|
|
101
|
+
|
|
102
|
+
// Set cache
|
|
103
|
+
const setCache = useCallback((input: TInput, data: TOutput) => {
|
|
104
|
+
if (!options.cacheKey) return;
|
|
105
|
+
|
|
106
|
+
const key = getCacheKey(input);
|
|
107
|
+
const expires = Date.now() + (options.cacheDuration || 5 * 60 * 1000);
|
|
108
|
+
cacheRef.current.set(key, { data, expires });
|
|
109
|
+
}, [getCacheKey, options.cacheKey, options.cacheDuration]);
|
|
110
|
+
|
|
111
|
+
// Execute with retry
|
|
112
|
+
const executeWithRetry = useCallback(async (
|
|
113
|
+
input: TInput,
|
|
114
|
+
attempt: number = 0
|
|
115
|
+
): Promise<TOutput> => {
|
|
116
|
+
try {
|
|
117
|
+
const context: AUIContext = {
|
|
118
|
+
cache: new Map(),
|
|
119
|
+
fetch: globalThis.fetch,
|
|
120
|
+
isServer: typeof window === 'undefined',
|
|
121
|
+
...options.context
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const result = await tool.run(input, context);
|
|
125
|
+
return result;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const maxRetries = options.retryCount || 0;
|
|
128
|
+
|
|
129
|
+
if (attempt < maxRetries) {
|
|
130
|
+
const delay = typeof options.retryDelay === 'function'
|
|
131
|
+
? options.retryDelay(attempt)
|
|
132
|
+
: options.retryDelay || Math.pow(2, attempt) * 1000;
|
|
133
|
+
|
|
134
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
135
|
+
return executeWithRetry(input, attempt + 1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}, [tool, options]);
|
|
141
|
+
|
|
142
|
+
// Main execute function
|
|
143
|
+
const execute = useCallback(async (input: TInput): Promise<TOutput> => {
|
|
144
|
+
// Check cache first
|
|
145
|
+
const cached = checkCache(input);
|
|
146
|
+
if (cached) {
|
|
147
|
+
setData(cached);
|
|
148
|
+
setError(null);
|
|
149
|
+
options.onSuccess?.(cached, input);
|
|
150
|
+
return cached;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Cancel previous request
|
|
154
|
+
if (abortControllerRef.current) {
|
|
155
|
+
abortControllerRef.current.abort();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Create new abort controller
|
|
159
|
+
abortControllerRef.current = new AbortController();
|
|
160
|
+
|
|
161
|
+
setLoading(true);
|
|
162
|
+
setError(null);
|
|
163
|
+
setExecutionCount(prev => prev + 1);
|
|
164
|
+
setLastExecutedAt(new Date());
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const result = await executeWithRetry(input);
|
|
168
|
+
|
|
169
|
+
// Check if aborted
|
|
170
|
+
if (abortControllerRef.current?.signal.aborted) {
|
|
171
|
+
throw new Error('Request cancelled');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
setData(result);
|
|
175
|
+
setCache(input, result);
|
|
176
|
+
options.onSuccess?.(result, input);
|
|
177
|
+
|
|
178
|
+
// Start polling if configured
|
|
179
|
+
if (options.pollingInterval && options.pollingCondition?.(result)) {
|
|
180
|
+
if (pollingTimerRef.current) clearInterval(pollingTimerRef.current);
|
|
181
|
+
|
|
182
|
+
pollingTimerRef.current = setInterval(() => {
|
|
183
|
+
execute(input);
|
|
184
|
+
}, options.pollingInterval);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return result;
|
|
188
|
+
} catch (err) {
|
|
189
|
+
const error = err as Error;
|
|
190
|
+
setError(error);
|
|
191
|
+
options.onError?.(error, input);
|
|
192
|
+
throw error;
|
|
193
|
+
} finally {
|
|
194
|
+
setLoading(false);
|
|
195
|
+
}
|
|
196
|
+
}, [
|
|
197
|
+
checkCache,
|
|
198
|
+
executeWithRetry,
|
|
199
|
+
setCache,
|
|
200
|
+
options
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
// Debounced execute
|
|
204
|
+
const debouncedExecute = useCallback((input: TInput): Promise<TOutput> => {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
if (debounceTimerRef.current) {
|
|
207
|
+
clearTimeout(debounceTimerRef.current);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
211
|
+
execute(input).then(resolve).catch(reject);
|
|
212
|
+
}, options.debounceMs);
|
|
213
|
+
});
|
|
214
|
+
}, [execute, options.debounceMs]);
|
|
215
|
+
|
|
216
|
+
// Throttled execute
|
|
217
|
+
const throttledExecute = useCallback((input: TInput): Promise<TOutput> => {
|
|
218
|
+
return new Promise((resolve, reject) => {
|
|
219
|
+
if (throttleTimerRef.current) {
|
|
220
|
+
reject(new Error('Request throttled'));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
throttleTimerRef.current = setTimeout(() => {
|
|
225
|
+
throttleTimerRef.current = null;
|
|
226
|
+
}, options.throttleMs);
|
|
227
|
+
|
|
228
|
+
execute(input).then(resolve).catch(reject);
|
|
229
|
+
});
|
|
230
|
+
}, [execute, options.throttleMs]);
|
|
231
|
+
|
|
232
|
+
// Choose appropriate execute function
|
|
233
|
+
const finalExecute = options.debounceMs
|
|
234
|
+
? debouncedExecute
|
|
235
|
+
: options.throttleMs
|
|
236
|
+
? throttledExecute
|
|
237
|
+
: execute;
|
|
238
|
+
|
|
239
|
+
// Cancel execution
|
|
240
|
+
const cancel = useCallback(() => {
|
|
241
|
+
if (abortControllerRef.current) {
|
|
242
|
+
abortControllerRef.current.abort();
|
|
243
|
+
abortControllerRef.current = null;
|
|
244
|
+
}
|
|
245
|
+
if (debounceTimerRef.current) {
|
|
246
|
+
clearTimeout(debounceTimerRef.current);
|
|
247
|
+
debounceTimerRef.current = null;
|
|
248
|
+
}
|
|
249
|
+
if (pollingTimerRef.current) {
|
|
250
|
+
clearInterval(pollingTimerRef.current);
|
|
251
|
+
pollingTimerRef.current = null;
|
|
252
|
+
}
|
|
253
|
+
setLoading(false);
|
|
254
|
+
}, []);
|
|
255
|
+
|
|
256
|
+
// Reset state
|
|
257
|
+
const reset = useCallback(() => {
|
|
258
|
+
cancel();
|
|
259
|
+
setData(null);
|
|
260
|
+
setError(null);
|
|
261
|
+
setLoading(false);
|
|
262
|
+
setExecutionCount(0);
|
|
263
|
+
setLastExecutedAt(null);
|
|
264
|
+
cacheRef.current.clear();
|
|
265
|
+
}, [cancel]);
|
|
266
|
+
|
|
267
|
+
// Auto-execute on mount
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
if (options.autoExecute && options.autoExecuteInput) {
|
|
270
|
+
finalExecute(options.autoExecuteInput);
|
|
271
|
+
}
|
|
272
|
+
}, [finalExecute, options.autoExecute, options.autoExecuteInput]);
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
execute: finalExecute,
|
|
276
|
+
cancel,
|
|
277
|
+
reset,
|
|
278
|
+
data,
|
|
279
|
+
error,
|
|
280
|
+
loading,
|
|
281
|
+
isSuccess: !loading && !error && data !== null,
|
|
282
|
+
isError: !loading && error !== null,
|
|
283
|
+
isIdle: !loading && !error && data === null,
|
|
284
|
+
executionCount,
|
|
285
|
+
lastExecutedAt
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Batch execution hook
|
|
290
|
+
export function useAUIToolBatch<TInput = any, TOutput = any>(
|
|
291
|
+
tool: AUITool<TInput, TOutput>,
|
|
292
|
+
options: UseAUIToolOptions<TInput, TOutput> = {}
|
|
293
|
+
) {
|
|
294
|
+
const [results, setResults] = useState<Map<string, TOutput>>(new Map());
|
|
295
|
+
const [errors, setErrors] = useState<Map<string, Error>>(new Map());
|
|
296
|
+
const [loading, setLoading] = useState(false);
|
|
297
|
+
|
|
298
|
+
const executeBatch = useCallback(async (inputs: TInput[]): Promise<Map<string, TOutput>> => {
|
|
299
|
+
setLoading(true);
|
|
300
|
+
const newResults = new Map<string, TOutput>();
|
|
301
|
+
const newErrors = new Map<string, Error>();
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const promises = inputs.map(async (input, index) => {
|
|
305
|
+
const key = JSON.stringify(input);
|
|
306
|
+
try {
|
|
307
|
+
const context: AUIContext = {
|
|
308
|
+
cache: new Map(),
|
|
309
|
+
fetch: globalThis.fetch,
|
|
310
|
+
isServer: typeof window === 'undefined',
|
|
311
|
+
...options.context
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const result = await tool.run(input, context);
|
|
315
|
+
newResults.set(key, result);
|
|
316
|
+
return result;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
newErrors.set(key, error as Error);
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await Promise.allSettled(promises);
|
|
324
|
+
|
|
325
|
+
setResults(newResults);
|
|
326
|
+
setErrors(newErrors);
|
|
327
|
+
|
|
328
|
+
return newResults;
|
|
329
|
+
} finally {
|
|
330
|
+
setLoading(false);
|
|
331
|
+
}
|
|
332
|
+
}, [tool, options.context]);
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
executeBatch,
|
|
336
|
+
results,
|
|
337
|
+
errors,
|
|
338
|
+
loading
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Export all hooks
|
|
343
|
+
export { useAUIToolEnhanced as default };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useRef } from 'react';
|
|
4
|
+
import aui, { AUITool, AUIContext } from '../index';
|
|
5
|
+
|
|
6
|
+
export interface UseAUIToolsOptions {
|
|
7
|
+
cache?: boolean;
|
|
8
|
+
optimistic?: boolean;
|
|
9
|
+
retry?: number;
|
|
10
|
+
onSuccess?: (result: any, tool: string) => void;
|
|
11
|
+
onError?: (error: Error, tool: string) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useAUITools(options: UseAUIToolsOptions = {}) {
|
|
15
|
+
const [loading, setLoading] = useState<Record<string, boolean>>({});
|
|
16
|
+
const [errors, setErrors] = useState<Record<string, Error | null>>({});
|
|
17
|
+
const [data, setData] = useState<Record<string, any>>({});
|
|
18
|
+
const cache = useRef(new Map<string, any>());
|
|
19
|
+
|
|
20
|
+
const execute = useCallback(async <TInput = any, TOutput = any>(
|
|
21
|
+
toolName: string,
|
|
22
|
+
input: TInput
|
|
23
|
+
): Promise<TOutput | null> => {
|
|
24
|
+
setLoading(prev => ({ ...prev, [toolName]: true }));
|
|
25
|
+
setErrors(prev => ({ ...prev, [toolName]: null }));
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Check cache if enabled
|
|
29
|
+
if (options.cache) {
|
|
30
|
+
const cacheKey = `${toolName}:${JSON.stringify(input)}`;
|
|
31
|
+
if (cache.current.has(cacheKey)) {
|
|
32
|
+
const cached = cache.current.get(cacheKey);
|
|
33
|
+
setData(prev => ({ ...prev, [toolName]: cached }));
|
|
34
|
+
setLoading(prev => ({ ...prev, [toolName]: false }));
|
|
35
|
+
return cached;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Create context
|
|
40
|
+
const ctx: AUIContext = {
|
|
41
|
+
cache: cache.current,
|
|
42
|
+
fetch: globalThis.fetch,
|
|
43
|
+
isServer: false,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Get tool
|
|
47
|
+
const tool = aui.get(toolName);
|
|
48
|
+
let result: TOutput;
|
|
49
|
+
|
|
50
|
+
if (tool) {
|
|
51
|
+
// Execute locally if tool exists
|
|
52
|
+
result = await tool.run(input, ctx);
|
|
53
|
+
} else {
|
|
54
|
+
// Execute via API
|
|
55
|
+
const response = await fetch(`/api/tools/${toolName}`, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'Content-Type': 'application/json' },
|
|
58
|
+
body: JSON.stringify(input),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
throw new Error(`Failed to execute tool: ${response.statusText}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
result = data.data;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Cache result if enabled
|
|
70
|
+
if (options.cache) {
|
|
71
|
+
const cacheKey = `${toolName}:${JSON.stringify(input)}`;
|
|
72
|
+
cache.current.set(cacheKey, result);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
setData(prev => ({ ...prev, [toolName]: result }));
|
|
76
|
+
options.onSuccess?.(result, toolName);
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const err = error instanceof Error ? error : new Error('Unknown error');
|
|
81
|
+
setErrors(prev => ({ ...prev, [toolName]: err }));
|
|
82
|
+
options.onError?.(err, toolName);
|
|
83
|
+
|
|
84
|
+
// Retry logic
|
|
85
|
+
if (options.retry && options.retry > 0) {
|
|
86
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
87
|
+
return execute(toolName, input);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return null;
|
|
91
|
+
} finally {
|
|
92
|
+
setLoading(prev => ({ ...prev, [toolName]: false }));
|
|
93
|
+
}
|
|
94
|
+
}, [options]);
|
|
95
|
+
|
|
96
|
+
const executeBatch = useCallback(async (
|
|
97
|
+
executions: Array<{ tool: string; input: any }>
|
|
98
|
+
) => {
|
|
99
|
+
setLoading(prev => {
|
|
100
|
+
const newLoading = { ...prev };
|
|
101
|
+
executions.forEach(exec => {
|
|
102
|
+
newLoading[exec.tool] = true;
|
|
103
|
+
});
|
|
104
|
+
return newLoading;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch('/api/aui/batch', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: { 'Content-Type': 'application/json' },
|
|
111
|
+
body: JSON.stringify({ executions }),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
throw new Error('Batch execution failed');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { results } = await response.json();
|
|
119
|
+
|
|
120
|
+
results.forEach((result: any) => {
|
|
121
|
+
if (result.success) {
|
|
122
|
+
setData(prev => ({ ...prev, [result.tool]: result.data }));
|
|
123
|
+
setErrors(prev => ({ ...prev, [result.tool]: null }));
|
|
124
|
+
options.onSuccess?.(result.data, result.tool);
|
|
125
|
+
} else {
|
|
126
|
+
const error = new Error(result.error);
|
|
127
|
+
setErrors(prev => ({ ...prev, [result.tool]: error }));
|
|
128
|
+
options.onError?.(error, result.tool);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
return results;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const err = error instanceof Error ? error : new Error('Batch execution failed');
|
|
135
|
+
executions.forEach(exec => {
|
|
136
|
+
setErrors(prev => ({ ...prev, [exec.tool]: err }));
|
|
137
|
+
});
|
|
138
|
+
throw err;
|
|
139
|
+
} finally {
|
|
140
|
+
setLoading(prev => {
|
|
141
|
+
const newLoading = { ...prev };
|
|
142
|
+
executions.forEach(exec => {
|
|
143
|
+
newLoading[exec.tool] = false;
|
|
144
|
+
});
|
|
145
|
+
return newLoading;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}, [options]);
|
|
149
|
+
|
|
150
|
+
const reset = useCallback((toolName?: string) => {
|
|
151
|
+
if (toolName) {
|
|
152
|
+
setData(prev => ({ ...prev, [toolName]: undefined }));
|
|
153
|
+
setErrors(prev => ({ ...prev, [toolName]: null }));
|
|
154
|
+
setLoading(prev => ({ ...prev, [toolName]: false }));
|
|
155
|
+
} else {
|
|
156
|
+
setData({});
|
|
157
|
+
setErrors({});
|
|
158
|
+
setLoading({});
|
|
159
|
+
cache.current.clear();
|
|
160
|
+
}
|
|
161
|
+
}, []);
|
|
162
|
+
|
|
163
|
+
const clearCache = useCallback(() => {
|
|
164
|
+
cache.current.clear();
|
|
165
|
+
}, []);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
execute,
|
|
169
|
+
executeBatch,
|
|
170
|
+
loading,
|
|
171
|
+
errors,
|
|
172
|
+
data,
|
|
173
|
+
reset,
|
|
174
|
+
clearCache,
|
|
175
|
+
isLoading: (tool: string) => loading[tool] || false,
|
|
176
|
+
getError: (tool: string) => errors[tool] || null,
|
|
177
|
+
getData: (tool: string) => data[tool],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function useAUITool<TInput = any, TOutput = any>(
|
|
182
|
+
toolOrName: string | AUITool<TInput, TOutput>,
|
|
183
|
+
options?: UseAUIToolsOptions
|
|
184
|
+
) {
|
|
185
|
+
const toolName = typeof toolOrName === 'string' ? toolOrName : toolOrName.name;
|
|
186
|
+
const tools = useAUITools(options);
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
execute: (input: TInput) => tools.execute<TInput, TOutput>(toolName, input),
|
|
190
|
+
loading: tools.isLoading(toolName),
|
|
191
|
+
error: tools.getError(toolName),
|
|
192
|
+
data: tools.getData(toolName) as TOutput | undefined,
|
|
193
|
+
reset: () => tools.reset(toolName),
|
|
194
|
+
};
|
|
195
|
+
}
|
package/lib/aui/index.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { AUITool, AUIContext, ToolConfig } from './core';
|
|
2
|
+
|
|
3
|
+
export { AUITool } from './core';
|
|
4
|
+
export type { AUIContext, ToolConfig } from './core';
|
|
5
|
+
|
|
6
|
+
export class AUI {
|
|
7
|
+
private tools = new Map<string, AUITool>();
|
|
8
|
+
|
|
9
|
+
tool(name: string): AUITool {
|
|
10
|
+
const t = new AUITool(name);
|
|
11
|
+
this.tools.set(name, t);
|
|
12
|
+
return t;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get(name: string): AUITool | undefined {
|
|
16
|
+
return this.tools.get(name);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getTool(name: string): AUITool | undefined {
|
|
20
|
+
return this.tools.get(name);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async execute<TInput = any, TOutput = any>(
|
|
24
|
+
name: string,
|
|
25
|
+
input: TInput,
|
|
26
|
+
ctx?: AUIContext
|
|
27
|
+
): Promise<TOutput> {
|
|
28
|
+
const tool = this.get(name);
|
|
29
|
+
if (!tool) throw new Error(`Tool "${name}" not found`);
|
|
30
|
+
return await tool.run(input, ctx || this.createContext());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
createContext(additions?: Partial<AUIContext>): AUIContext {
|
|
34
|
+
return {
|
|
35
|
+
cache: new Map(),
|
|
36
|
+
fetch: globalThis.fetch?.bind(globalThis) || (() => Promise.reject(new Error('Fetch not available'))),
|
|
37
|
+
isServer: typeof window === 'undefined',
|
|
38
|
+
...additions,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
list(): AUITool[] {
|
|
43
|
+
return Array.from(this.tools.values());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getTools(): AUITool[] {
|
|
47
|
+
return this.list();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getToolNames(): string[] {
|
|
51
|
+
return Array.from(this.tools.keys());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
has(name: string): boolean {
|
|
55
|
+
return this.tools.has(name);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clear(): void {
|
|
59
|
+
this.tools.clear();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
remove(name: string): boolean {
|
|
63
|
+
return this.tools.delete(name);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
findByTag(tag: string): AUITool[] {
|
|
67
|
+
return this.list().filter(tool => tool.tags.includes(tag));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
findByTags(...tags: string[]): AUITool[] {
|
|
71
|
+
return this.list().filter(tool =>
|
|
72
|
+
tags.every(tag => tool.tags.includes(tag))
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const aui: AUI = new AUI();
|
|
78
|
+
|
|
79
|
+
export type InferToolInput<T> = T extends AUITool<infer I, any> ? I : never;
|
|
80
|
+
export type InferToolOutput<T> = T extends AUITool<any, infer O> ? O : never;
|
|
81
|
+
|
|
82
|
+
export type ToolDefinition<TInput = any, TOutput = any> = {
|
|
83
|
+
tool: AUITool<TInput, TOutput>;
|
|
84
|
+
input: TInput;
|
|
85
|
+
output: TOutput;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type ExtractTools<T> = T extends { [K in keyof T]: AUITool<infer I, infer O> }
|
|
89
|
+
? { [K in keyof T]: ToolDefinition<InferToolInput<T[K]>, InferToolOutput<T[K]>> }
|
|
90
|
+
: never;
|
|
91
|
+
|
|
92
|
+
// Re-export everything for convenience
|
|
93
|
+
export { z } from 'zod';
|
|
94
|
+
export { useAUITool, useAUI } from './hooks/useAUITool';
|
|
95
|
+
export { useAUITools } from './hooks/useAUITools';
|
|
96
|
+
export { AUIProvider, useAUIContext, useAUIInstance } from './provider';
|
|
97
|
+
export {
|
|
98
|
+
createAITool,
|
|
99
|
+
AIControlledTool,
|
|
100
|
+
aiControlSystem,
|
|
101
|
+
aiTools,
|
|
102
|
+
type AIControlOptions
|
|
103
|
+
} from './ai-control';
|
|
104
|
+
|
|
105
|
+
// Export all pre-built tools
|
|
106
|
+
export * from './tools';
|
|
107
|
+
|
|
108
|
+
// Client control exports
|
|
109
|
+
export {
|
|
110
|
+
clientTools,
|
|
111
|
+
clientControlSystem,
|
|
112
|
+
createClientControlSystem,
|
|
113
|
+
type ClientControlContext
|
|
114
|
+
} from './client-control';
|
|
115
|
+
|
|
116
|
+
// Tool registry and discovery
|
|
117
|
+
export {
|
|
118
|
+
ToolRegistry,
|
|
119
|
+
ToolDiscovery,
|
|
120
|
+
globalToolRegistry,
|
|
121
|
+
toolDiscovery,
|
|
122
|
+
type ToolMetadata
|
|
123
|
+
} from './tool-registry';
|
|
124
|
+
|
|
125
|
+
// Client executor for optimized client-side execution
|
|
126
|
+
export {
|
|
127
|
+
ClientExecutor,
|
|
128
|
+
clientExecutor,
|
|
129
|
+
executeClientTool,
|
|
130
|
+
type ClientExecutorOptions,
|
|
131
|
+
type CacheStrategy
|
|
132
|
+
} from './client-executor';
|
|
133
|
+
|
|
134
|
+
// AI Assistant integration
|
|
135
|
+
export {
|
|
136
|
+
AIAssistant,
|
|
137
|
+
createAIAssistant,
|
|
138
|
+
formatToolForLLM,
|
|
139
|
+
assistants,
|
|
140
|
+
type AIAssistantConfig,
|
|
141
|
+
type AIToolCall,
|
|
142
|
+
type AIConversationMessage
|
|
143
|
+
} from './ai-assistant';
|
|
144
|
+
|
|
145
|
+
// Vercel AI SDK integration
|
|
146
|
+
export {
|
|
147
|
+
convertToVercelTool,
|
|
148
|
+
createVercelTools,
|
|
149
|
+
createAUIToolFromVercel,
|
|
150
|
+
executeToolWithStreaming,
|
|
151
|
+
vercelAIIntegration
|
|
152
|
+
} from './vercel-ai';
|
|
153
|
+
|
|
154
|
+
// Example tools - exported separately to avoid circular dependencies
|
|
155
|
+
|
|
156
|
+
export default aui;
|