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