@lantos1618/better-ui 0.2.2 → 0.3.1
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/README.md +231 -148
- package/dist/index.d.mts +314 -0
- package/dist/index.d.ts +314 -0
- package/dist/index.js +522 -0
- package/dist/index.mjs +491 -0
- package/package.json +59 -20
- package/lib/aui/README.md +0 -136
- package/lib/aui/__tests__/aui-complete.test.ts +0 -251
- package/lib/aui/__tests__/aui-comprehensive.test.ts +0 -376
- package/lib/aui/__tests__/aui-concise.test.ts +0 -278
- package/lib/aui/__tests__/aui-integration.test.ts +0 -309
- package/lib/aui/__tests__/aui-simple.test.ts +0 -116
- package/lib/aui/__tests__/aui.test.ts +0 -269
- package/lib/aui/__tests__/concise-api.test.ts +0 -165
- package/lib/aui/__tests__/core.test.ts +0 -265
- package/lib/aui/__tests__/simple-api.test.ts +0 -200
- package/lib/aui/ai-assistant.ts +0 -408
- package/lib/aui/ai-control.ts +0 -353
- package/lib/aui/client/use-aui.ts +0 -55
- package/lib/aui/client-control.ts +0 -551
- package/lib/aui/client-executor.ts +0 -417
- package/lib/aui/components/ToolRenderer.tsx +0 -22
- package/lib/aui/core.ts +0 -137
- package/lib/aui/demo.tsx +0 -89
- package/lib/aui/examples/ai-complete-demo.tsx +0 -359
- package/lib/aui/examples/ai-control-demo.tsx +0 -356
- package/lib/aui/examples/ai-control-tools.ts +0 -308
- package/lib/aui/examples/concise-api.tsx +0 -153
- package/lib/aui/examples/index.tsx +0 -163
- package/lib/aui/examples/quick-demo.tsx +0 -91
- package/lib/aui/examples/simple-demo.tsx +0 -71
- package/lib/aui/examples/simple-tools.tsx +0 -160
- package/lib/aui/examples/user-api.tsx +0 -208
- package/lib/aui/examples/user-requested.tsx +0 -174
- package/lib/aui/examples/weather-search-tools.tsx +0 -119
- package/lib/aui/examples.tsx +0 -367
- package/lib/aui/hooks/useAUITool.ts +0 -142
- package/lib/aui/hooks/useAUIToolEnhanced.ts +0 -343
- package/lib/aui/hooks/useAUITools.ts +0 -195
- package/lib/aui/index.ts +0 -156
- package/lib/aui/provider.tsx +0 -45
- package/lib/aui/server-control.ts +0 -386
- package/lib/aui/server-executor.ts +0 -165
- package/lib/aui/server.ts +0 -167
- package/lib/aui/tool-registry.ts +0 -380
- package/lib/aui/tools/advanced-examples.tsx +0 -86
- package/lib/aui/tools/ai-complete.ts +0 -375
- package/lib/aui/tools/api-tools.tsx +0 -230
- package/lib/aui/tools/data-tools.tsx +0 -232
- package/lib/aui/tools/dom-tools.tsx +0 -202
- package/lib/aui/tools/examples.ts +0 -43
- package/lib/aui/tools/file-tools.tsx +0 -202
- package/lib/aui/tools/form-tools.tsx +0 -233
- package/lib/aui/tools/index.ts +0 -8
- package/lib/aui/tools/navigation-tools.tsx +0 -172
- package/lib/aui/tools/notification-tools.ts +0 -213
- package/lib/aui/tools/state-tools.tsx +0 -209
- package/lib/aui/types.ts +0 -47
- package/lib/aui/vercel-ai.ts +0 -100
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
// src/tool.tsx
|
|
2
|
+
import { memo } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
var Tool = class {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.name = config.name;
|
|
7
|
+
this.description = config.description;
|
|
8
|
+
this.inputSchema = config.input;
|
|
9
|
+
this.outputSchema = config.output;
|
|
10
|
+
this.tags = config.tags || [];
|
|
11
|
+
this.cacheConfig = config.cache;
|
|
12
|
+
this.clientFetchConfig = config.clientFetch;
|
|
13
|
+
this._initView();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Define server-side implementation
|
|
17
|
+
* Runs on server (API routes, server components, etc.)
|
|
18
|
+
*/
|
|
19
|
+
server(handler) {
|
|
20
|
+
this._server = handler;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Define client-side implementation
|
|
25
|
+
* Runs in browser. If not specified, auto-fetches to /api/tools/{name}
|
|
26
|
+
*/
|
|
27
|
+
client(handler) {
|
|
28
|
+
this._client = handler;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Define view component for rendering results
|
|
33
|
+
* Our differentiator from TanStack AI
|
|
34
|
+
*/
|
|
35
|
+
view(component) {
|
|
36
|
+
this._view = component;
|
|
37
|
+
this._initView();
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Execute the tool
|
|
42
|
+
* Automatically uses server or client handler based on environment
|
|
43
|
+
*
|
|
44
|
+
* SECURITY: Server handlers only run on server. Client automatically
|
|
45
|
+
* fetches from /api/tools/execute if no client handler is defined.
|
|
46
|
+
*/
|
|
47
|
+
async run(input, ctx) {
|
|
48
|
+
const validated = this.inputSchema.parse(input);
|
|
49
|
+
const isServer = ctx?.isServer ?? typeof window === "undefined";
|
|
50
|
+
const context = {
|
|
51
|
+
cache: ctx?.cache || /* @__PURE__ */ new Map(),
|
|
52
|
+
fetch: ctx?.fetch || globalThis.fetch?.bind(globalThis),
|
|
53
|
+
isServer,
|
|
54
|
+
// Only include server-sensitive fields when actually on server
|
|
55
|
+
...isServer ? {
|
|
56
|
+
env: ctx?.env,
|
|
57
|
+
headers: ctx?.headers,
|
|
58
|
+
cookies: ctx?.cookies,
|
|
59
|
+
user: ctx?.user,
|
|
60
|
+
session: ctx?.session
|
|
61
|
+
} : {
|
|
62
|
+
// Client-only fields
|
|
63
|
+
optimistic: ctx?.optimistic
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
if (this.cacheConfig && context.cache) {
|
|
67
|
+
const cacheKey = this.cacheConfig.key ? this.cacheConfig.key(validated) : `${this.name}:${JSON.stringify(validated)}`;
|
|
68
|
+
const cached = context.cache.get(cacheKey);
|
|
69
|
+
if (cached && cached.expiry > Date.now()) {
|
|
70
|
+
return cached.data;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let result;
|
|
74
|
+
if (context.isServer) {
|
|
75
|
+
if (!this._server) {
|
|
76
|
+
throw new Error(`Tool "${this.name}" has no server implementation`);
|
|
77
|
+
}
|
|
78
|
+
result = await this._server(validated, context);
|
|
79
|
+
} else {
|
|
80
|
+
if (this._client) {
|
|
81
|
+
result = await this._client(validated, context);
|
|
82
|
+
} else if (this._server) {
|
|
83
|
+
result = await this._defaultClientFetch(validated, context);
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error(`Tool "${this.name}" has no implementation`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (this.outputSchema) {
|
|
89
|
+
try {
|
|
90
|
+
result = this.outputSchema.parse(result);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(`Output validation failed for tool "${this.name}":`, error);
|
|
93
|
+
if (error instanceof Error && "errors" in error) {
|
|
94
|
+
console.error("Validation errors:", error.errors);
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (this.cacheConfig && context.cache) {
|
|
100
|
+
const cacheKey = this.cacheConfig.key ? this.cacheConfig.key(validated) : `${this.name}:${JSON.stringify(validated)}`;
|
|
101
|
+
context.cache.set(cacheKey, {
|
|
102
|
+
data: result,
|
|
103
|
+
expiry: Date.now() + this.cacheConfig.ttl
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Make the tool callable directly: await weather({ city: 'London' })
|
|
110
|
+
*/
|
|
111
|
+
async call(input, ctx) {
|
|
112
|
+
return this.run(input, ctx);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Default client fetch when no .client() is defined
|
|
116
|
+
*
|
|
117
|
+
* SECURITY: This ensures server handlers never run on the client.
|
|
118
|
+
* The server-side /api/tools/execute endpoint handles execution safely.
|
|
119
|
+
*/
|
|
120
|
+
async _defaultClientFetch(input, ctx) {
|
|
121
|
+
const endpoint = this.clientFetchConfig?.endpoint || "/api/tools/execute";
|
|
122
|
+
const response = await ctx.fetch(endpoint, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: { "Content-Type": "application/json" },
|
|
125
|
+
body: JSON.stringify({ tool: this.name, input })
|
|
126
|
+
});
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
const error = await response.json().catch(() => ({}));
|
|
129
|
+
throw new Error(error.message || error.error || `Tool execution failed: ${response.statusText}`);
|
|
130
|
+
}
|
|
131
|
+
const data = await response.json();
|
|
132
|
+
return data.result ?? data.data ?? data;
|
|
133
|
+
}
|
|
134
|
+
_initView() {
|
|
135
|
+
const viewFn = this._view;
|
|
136
|
+
const name = this.name;
|
|
137
|
+
const ViewComponent = (props) => {
|
|
138
|
+
if (!viewFn) {
|
|
139
|
+
if (props.loading) return null;
|
|
140
|
+
if (props.error) return null;
|
|
141
|
+
if (!props.data) return null;
|
|
142
|
+
return /* @__PURE__ */ jsx("pre", { children: JSON.stringify(props.data, null, 2) });
|
|
143
|
+
}
|
|
144
|
+
if (!props.data && !props.loading && !props.error) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return viewFn(props.data, {
|
|
148
|
+
loading: props.loading,
|
|
149
|
+
error: props.error,
|
|
150
|
+
onAction: props.onAction
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
ViewComponent.displayName = `${name}View`;
|
|
154
|
+
this.View = memo(ViewComponent, (prevProps, nextProps) => {
|
|
155
|
+
if (prevProps.data !== nextProps.data) return false;
|
|
156
|
+
if (prevProps.loading !== nextProps.loading) return false;
|
|
157
|
+
if (prevProps.error !== nextProps.error) return false;
|
|
158
|
+
return true;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Check if tool has a view
|
|
163
|
+
*/
|
|
164
|
+
get hasView() {
|
|
165
|
+
return !!this._view;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Check if tool has server implementation
|
|
169
|
+
*/
|
|
170
|
+
get hasServer() {
|
|
171
|
+
return !!this._server;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if tool has custom client implementation
|
|
175
|
+
*/
|
|
176
|
+
get hasClient() {
|
|
177
|
+
return !!this._client;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Convert to plain object (for serialization)
|
|
181
|
+
*
|
|
182
|
+
* SECURITY: This intentionally excludes handlers and schemas to prevent
|
|
183
|
+
* accidental exposure of server logic or validation details.
|
|
184
|
+
*/
|
|
185
|
+
toJSON() {
|
|
186
|
+
return {
|
|
187
|
+
name: this.name,
|
|
188
|
+
description: this.description,
|
|
189
|
+
tags: this.tags,
|
|
190
|
+
hasServer: this.hasServer,
|
|
191
|
+
hasClient: this.hasClient,
|
|
192
|
+
hasView: this.hasView,
|
|
193
|
+
hasCache: !!this.cacheConfig
|
|
194
|
+
// Intentionally NOT included: handlers, schemas, clientFetchConfig
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Convert to AI SDK format (Vercel AI SDK v5 compatible)
|
|
199
|
+
*/
|
|
200
|
+
toAITool() {
|
|
201
|
+
return {
|
|
202
|
+
description: this.description || this.name,
|
|
203
|
+
inputSchema: this.inputSchema,
|
|
204
|
+
execute: async (input) => {
|
|
205
|
+
return this.run(input, { isServer: true });
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
function tool(nameOrConfig) {
|
|
211
|
+
if (typeof nameOrConfig === "string") {
|
|
212
|
+
return new ToolBuilder(nameOrConfig);
|
|
213
|
+
}
|
|
214
|
+
return new Tool(nameOrConfig);
|
|
215
|
+
}
|
|
216
|
+
var ToolBuilder = class {
|
|
217
|
+
constructor(name) {
|
|
218
|
+
this._tags = [];
|
|
219
|
+
this._name = name;
|
|
220
|
+
}
|
|
221
|
+
description(desc) {
|
|
222
|
+
this._description = desc;
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Define input schema - enables type inference for handlers
|
|
227
|
+
*
|
|
228
|
+
* NOTE: Uses type assertion internally. This is safe because:
|
|
229
|
+
* 1. The schema is stored and used correctly at runtime
|
|
230
|
+
* 2. The return type correctly reflects the new generic parameter
|
|
231
|
+
* 3. TypeScript doesn't support "this type mutation" in fluent builders
|
|
232
|
+
*/
|
|
233
|
+
input(schema) {
|
|
234
|
+
this._input = schema;
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Define output schema - enables type inference for results
|
|
239
|
+
*/
|
|
240
|
+
output(schema) {
|
|
241
|
+
this._output = schema;
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
tags(...tags) {
|
|
245
|
+
this._tags.push(...tags);
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
248
|
+
cache(config) {
|
|
249
|
+
this._cache = config;
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
252
|
+
/** Configure auto-fetch endpoint for client-side execution */
|
|
253
|
+
clientFetch(config) {
|
|
254
|
+
this._clientFetch = config;
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
server(handler) {
|
|
258
|
+
this._serverHandler = handler;
|
|
259
|
+
return this;
|
|
260
|
+
}
|
|
261
|
+
client(handler) {
|
|
262
|
+
this._clientHandler = handler;
|
|
263
|
+
return this;
|
|
264
|
+
}
|
|
265
|
+
view(component) {
|
|
266
|
+
this._viewComponent = component;
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Build the final Tool instance
|
|
271
|
+
*/
|
|
272
|
+
build() {
|
|
273
|
+
if (!this._input) {
|
|
274
|
+
throw new Error(`Tool "${this._name}" requires an input schema`);
|
|
275
|
+
}
|
|
276
|
+
const t = new Tool({
|
|
277
|
+
name: this._name,
|
|
278
|
+
description: this._description,
|
|
279
|
+
input: this._input,
|
|
280
|
+
output: this._output,
|
|
281
|
+
tags: this._tags,
|
|
282
|
+
cache: this._cache,
|
|
283
|
+
clientFetch: this._clientFetch
|
|
284
|
+
});
|
|
285
|
+
if (this._serverHandler) t.server(this._serverHandler);
|
|
286
|
+
if (this._clientHandler) t.client(this._clientHandler);
|
|
287
|
+
if (this._viewComponent) t.view(this._viewComponent);
|
|
288
|
+
return t;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Auto-build when accessing Tool methods
|
|
292
|
+
*/
|
|
293
|
+
async run(input, ctx) {
|
|
294
|
+
return this.build().run(input, ctx);
|
|
295
|
+
}
|
|
296
|
+
get View() {
|
|
297
|
+
return this.build().View;
|
|
298
|
+
}
|
|
299
|
+
toJSON() {
|
|
300
|
+
return this.build().toJSON();
|
|
301
|
+
}
|
|
302
|
+
toAITool() {
|
|
303
|
+
return this.build().toAITool();
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// src/react/useTool.ts
|
|
308
|
+
import { useState, useCallback, useEffect, useRef } from "react";
|
|
309
|
+
function useTool(tool2, initialInput, options = {}) {
|
|
310
|
+
const [data, setData] = useState(null);
|
|
311
|
+
const [loading, setLoading] = useState(false);
|
|
312
|
+
const [error, setError] = useState(null);
|
|
313
|
+
const [executed, setExecuted] = useState(false);
|
|
314
|
+
const inputRef = useRef(initialInput);
|
|
315
|
+
const optionsRef = useRef(options);
|
|
316
|
+
optionsRef.current = options;
|
|
317
|
+
const executionIdRef = useRef(0);
|
|
318
|
+
const pendingCountRef = useRef(0);
|
|
319
|
+
const execute = useCallback(
|
|
320
|
+
async (input) => {
|
|
321
|
+
const finalInput = input ?? inputRef.current;
|
|
322
|
+
if (finalInput === void 0) {
|
|
323
|
+
const err = new Error("No input provided to tool");
|
|
324
|
+
setError(err);
|
|
325
|
+
optionsRef.current.onError?.(err);
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const currentExecutionId = ++executionIdRef.current;
|
|
329
|
+
pendingCountRef.current++;
|
|
330
|
+
setLoading(true);
|
|
331
|
+
setError(null);
|
|
332
|
+
try {
|
|
333
|
+
const context = {
|
|
334
|
+
cache: /* @__PURE__ */ new Map(),
|
|
335
|
+
fetch: globalThis.fetch?.bind(globalThis),
|
|
336
|
+
isServer: false,
|
|
337
|
+
...optionsRef.current.context
|
|
338
|
+
};
|
|
339
|
+
const result = await tool2.run(finalInput, context);
|
|
340
|
+
if (currentExecutionId === executionIdRef.current) {
|
|
341
|
+
setData(result);
|
|
342
|
+
setExecuted(true);
|
|
343
|
+
optionsRef.current.onSuccess?.(result);
|
|
344
|
+
}
|
|
345
|
+
return result;
|
|
346
|
+
} catch (err) {
|
|
347
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
348
|
+
if (currentExecutionId === executionIdRef.current) {
|
|
349
|
+
setError(error2);
|
|
350
|
+
optionsRef.current.onError?.(error2);
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
} finally {
|
|
354
|
+
pendingCountRef.current--;
|
|
355
|
+
if (pendingCountRef.current === 0) {
|
|
356
|
+
setLoading(false);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
[tool2]
|
|
361
|
+
);
|
|
362
|
+
const reset = useCallback(() => {
|
|
363
|
+
setData(null);
|
|
364
|
+
setError(null);
|
|
365
|
+
setLoading(false);
|
|
366
|
+
setExecuted(false);
|
|
367
|
+
}, []);
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
if (options.auto && initialInput !== void 0) {
|
|
370
|
+
inputRef.current = initialInput;
|
|
371
|
+
execute(initialInput);
|
|
372
|
+
}
|
|
373
|
+
}, [options.auto, initialInput, execute]);
|
|
374
|
+
return {
|
|
375
|
+
data,
|
|
376
|
+
loading,
|
|
377
|
+
error,
|
|
378
|
+
execute,
|
|
379
|
+
reset,
|
|
380
|
+
executed
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function useTools(tools, options = {}) {
|
|
384
|
+
const toolsRef = useRef(tools);
|
|
385
|
+
const optionsRef = useRef(options);
|
|
386
|
+
optionsRef.current = options;
|
|
387
|
+
if (process.env.NODE_ENV !== "production") {
|
|
388
|
+
const prevKeys = Object.keys(toolsRef.current);
|
|
389
|
+
const currKeys = Object.keys(tools);
|
|
390
|
+
if (prevKeys.length !== currKeys.length || !currKeys.every((k) => prevKeys.includes(k))) {
|
|
391
|
+
console.warn(
|
|
392
|
+
"useTools: The tools object keys changed between renders. This may cause unexpected behavior. Define tools outside the component or memoize with useMemo."
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
toolsRef.current = tools;
|
|
396
|
+
}
|
|
397
|
+
const [state, setState] = useState(() => {
|
|
398
|
+
const initial = {};
|
|
399
|
+
for (const name of Object.keys(tools)) {
|
|
400
|
+
initial[name] = {
|
|
401
|
+
data: null,
|
|
402
|
+
loading: false,
|
|
403
|
+
error: null,
|
|
404
|
+
executed: false
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
return initial;
|
|
408
|
+
});
|
|
409
|
+
const createExecute = useCallback(
|
|
410
|
+
(toolName, tool2) => {
|
|
411
|
+
return async (input) => {
|
|
412
|
+
if (input === void 0) {
|
|
413
|
+
const err = new Error("No input provided to tool");
|
|
414
|
+
setState((prev) => ({
|
|
415
|
+
...prev,
|
|
416
|
+
[toolName]: { ...prev[toolName], error: err }
|
|
417
|
+
}));
|
|
418
|
+
optionsRef.current.onError?.(err);
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
setState((prev) => ({
|
|
422
|
+
...prev,
|
|
423
|
+
[toolName]: { ...prev[toolName], loading: true, error: null }
|
|
424
|
+
}));
|
|
425
|
+
try {
|
|
426
|
+
const context = {
|
|
427
|
+
cache: /* @__PURE__ */ new Map(),
|
|
428
|
+
fetch: globalThis.fetch?.bind(globalThis),
|
|
429
|
+
isServer: false,
|
|
430
|
+
...optionsRef.current.context
|
|
431
|
+
};
|
|
432
|
+
const result = await tool2.run(input, context);
|
|
433
|
+
setState((prev) => ({
|
|
434
|
+
...prev,
|
|
435
|
+
[toolName]: {
|
|
436
|
+
data: result,
|
|
437
|
+
loading: false,
|
|
438
|
+
error: null,
|
|
439
|
+
executed: true
|
|
440
|
+
}
|
|
441
|
+
}));
|
|
442
|
+
optionsRef.current.onSuccess?.(result);
|
|
443
|
+
return result;
|
|
444
|
+
} catch (err) {
|
|
445
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
446
|
+
setState((prev) => ({
|
|
447
|
+
...prev,
|
|
448
|
+
[toolName]: { ...prev[toolName], loading: false, error }
|
|
449
|
+
}));
|
|
450
|
+
optionsRef.current.onError?.(error);
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
},
|
|
455
|
+
[]
|
|
456
|
+
);
|
|
457
|
+
const createReset = useCallback((toolName) => {
|
|
458
|
+
return () => {
|
|
459
|
+
setState((prev) => ({
|
|
460
|
+
...prev,
|
|
461
|
+
[toolName]: {
|
|
462
|
+
data: null,
|
|
463
|
+
loading: false,
|
|
464
|
+
error: null,
|
|
465
|
+
executed: false
|
|
466
|
+
}
|
|
467
|
+
}));
|
|
468
|
+
};
|
|
469
|
+
}, []);
|
|
470
|
+
const results = {};
|
|
471
|
+
for (const [name, tool2] of Object.entries(tools)) {
|
|
472
|
+
const toolName = name;
|
|
473
|
+
const toolState = state[toolName];
|
|
474
|
+
results[toolName] = {
|
|
475
|
+
data: toolState?.data ?? null,
|
|
476
|
+
loading: toolState?.loading ?? false,
|
|
477
|
+
error: toolState?.error ?? null,
|
|
478
|
+
executed: toolState?.executed ?? false,
|
|
479
|
+
execute: createExecute(toolName, tool2),
|
|
480
|
+
reset: createReset(toolName)
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return results;
|
|
484
|
+
}
|
|
485
|
+
export {
|
|
486
|
+
Tool,
|
|
487
|
+
ToolBuilder,
|
|
488
|
+
tool,
|
|
489
|
+
useTool,
|
|
490
|
+
useTools
|
|
491
|
+
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lantos1618/better-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A
|
|
5
|
-
"main": "
|
|
6
|
-
"
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "A minimal, type-safe AI-first UI framework for building tools",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
7
15
|
"files": [
|
|
8
|
-
"
|
|
16
|
+
"dist",
|
|
9
17
|
"README.md",
|
|
10
18
|
"LICENSE"
|
|
11
19
|
],
|
|
@@ -14,39 +22,70 @@
|
|
|
14
22
|
"framework",
|
|
15
23
|
"ai",
|
|
16
24
|
"react",
|
|
17
|
-
"
|
|
25
|
+
"tools",
|
|
26
|
+
"typescript"
|
|
18
27
|
],
|
|
19
28
|
"author": "Lyndon Leong",
|
|
20
29
|
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/lantos1618/better-ui"
|
|
33
|
+
},
|
|
21
34
|
"scripts": {
|
|
22
35
|
"dev": "next dev",
|
|
23
|
-
"build": "
|
|
36
|
+
"build": "npm run build:lib && npm run build:next",
|
|
37
|
+
"build:lib": "tsup src/index.ts --format cjs,esm --dts --clean --tsconfig tsconfig.lib.json",
|
|
38
|
+
"build:next": "next build",
|
|
24
39
|
"start": "next start",
|
|
25
40
|
"lint": "eslint .",
|
|
26
41
|
"test": "jest",
|
|
27
|
-
"type-check": "tsc --noEmit"
|
|
42
|
+
"type-check": "tsc --noEmit",
|
|
43
|
+
"prepublishOnly": "npm run build:lib"
|
|
28
44
|
},
|
|
29
45
|
"dependencies": {
|
|
30
|
-
"@ai-sdk/openai": "^2.0.
|
|
31
|
-
"@
|
|
32
|
-
"ai": "^5.0.
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"react
|
|
37
|
-
"
|
|
46
|
+
"@ai-sdk/openai": "^2.0.77",
|
|
47
|
+
"@ai-sdk/react": "^2.0.107",
|
|
48
|
+
"ai": "^5.0.107",
|
|
49
|
+
"zod": "^3.24.0"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": "^19.0.0",
|
|
53
|
+
"react-dom": "^19.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"react": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"react-dom": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
38
62
|
},
|
|
39
63
|
"devDependencies": {
|
|
64
|
+
"@dnd-kit/core": "^6.3.1",
|
|
65
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
40
66
|
"@eslint/eslintrc": "^3.3.1",
|
|
41
67
|
"@jest/globals": "^29.7.0",
|
|
68
|
+
"@tailwindcss/postcss": "^4.1.17",
|
|
69
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
70
|
+
"@testing-library/react": "^16.3.0",
|
|
42
71
|
"@types/jest": "^30.0.0",
|
|
43
72
|
"@types/node": "^20",
|
|
44
|
-
"@types/react": "^
|
|
45
|
-
"@types/react-dom": "^
|
|
46
|
-
"
|
|
47
|
-
"eslint
|
|
73
|
+
"@types/react": "^19",
|
|
74
|
+
"@types/react-dom": "^19",
|
|
75
|
+
"autoprefixer": "^10.4.22",
|
|
76
|
+
"eslint": "^9",
|
|
77
|
+
"eslint-config-next": "^16.0.1",
|
|
78
|
+
"framer-motion": "^12.23.12",
|
|
48
79
|
"jest": "^29.7.0",
|
|
80
|
+
"jest-environment-jsdom": "^30.2.0",
|
|
81
|
+
"lucide-react": "^0.543.0",
|
|
82
|
+
"next": "^16.0.1",
|
|
83
|
+
"postcss": "^8.5.6",
|
|
84
|
+
"react": "^19.2.1",
|
|
85
|
+
"react-dom": "^19.2.1",
|
|
86
|
+
"tailwindcss": "^4.1.17",
|
|
49
87
|
"ts-jest": "^29.4.1",
|
|
88
|
+
"tsup": "^8.5.1",
|
|
50
89
|
"typescript": "^5"
|
|
51
90
|
}
|
|
52
91
|
}
|