@lantos1618/better-ui 0.9.0 → 0.9.2
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 +537 -398
- package/dist/components/index.js +22 -8
- package/dist/components/index.mjs +22 -8
- package/dist/openapi/index.d.mts +33 -24
- package/dist/openapi/index.d.ts +33 -24
- package/dist/openapi/index.js +81 -2
- package/dist/openapi/index.mjs +79 -1
- package/package.json +3 -1
package/dist/components/index.js
CHANGED
|
@@ -425,6 +425,7 @@ function ChatProvider({ endpoint = "/api/chat", tools, toolStateStore: externalS
|
|
|
425
425
|
|
|
426
426
|
// src/components/Thread.tsx
|
|
427
427
|
var import_react6 = require("react");
|
|
428
|
+
var import_lucide_react = require("lucide-react");
|
|
428
429
|
|
|
429
430
|
// src/components/Message.tsx
|
|
430
431
|
var import_ai2 = require("ai");
|
|
@@ -831,18 +832,29 @@ function Thread({ className, emptyMessage, suggestions }) {
|
|
|
831
832
|
}
|
|
832
833
|
}, [messages, isLoading]);
|
|
833
834
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: scrollRef, className: `${className || ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "p-6 space-y-6", children: [
|
|
834
|
-
messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime5.
|
|
835
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[var(--bui-fg-secondary,#a1a1aa)]
|
|
836
|
-
suggestions && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex flex-
|
|
835
|
+
messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "py-12", children: [
|
|
836
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-center text-sm text-[var(--bui-fg-secondary,#a1a1aa)]", children: emptyMessage || "Send a message to get started" }),
|
|
837
|
+
suggestions && suggestions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mt-8 flex flex-col divide-y divide-[var(--bui-border,#27272a)] border-y border-[var(--bui-border,#27272a)]", children: suggestions.map((suggestion) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
837
838
|
"button",
|
|
838
839
|
{
|
|
839
840
|
onClick: () => sendMessage(suggestion),
|
|
840
|
-
className: "px-
|
|
841
|
-
children:
|
|
841
|
+
className: "group flex items-center justify-between gap-4 px-5 py-4 text-left text-sm text-[var(--bui-fg,#f4f4f5)] transition-colors hover:bg-[var(--bui-bg-hover,#27272a)]",
|
|
842
|
+
children: [
|
|
843
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "flex-1", children: suggestion }),
|
|
844
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
845
|
+
import_lucide_react.ArrowRight,
|
|
846
|
+
{
|
|
847
|
+
size: 14,
|
|
848
|
+
strokeWidth: 1.75,
|
|
849
|
+
"aria-hidden": "true",
|
|
850
|
+
className: "shrink-0 text-[var(--bui-fg-faint,#52525b)] transition-all group-hover:translate-x-0.5 group-hover:text-[var(--bui-fg-secondary,#a1a1aa)]"
|
|
851
|
+
}
|
|
852
|
+
)
|
|
853
|
+
]
|
|
842
854
|
},
|
|
843
855
|
suggestion
|
|
844
856
|
)) })
|
|
845
|
-
] })
|
|
857
|
+
] }),
|
|
846
858
|
messages.map((message, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
847
859
|
Message,
|
|
848
860
|
{
|
|
@@ -865,6 +877,7 @@ function Thread({ className, emptyMessage, suggestions }) {
|
|
|
865
877
|
|
|
866
878
|
// src/components/Composer.tsx
|
|
867
879
|
var import_react7 = require("react");
|
|
880
|
+
var import_lucide_react2 = require("lucide-react");
|
|
868
881
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
869
882
|
function Composer({ className, placeholder = "Ask something..." }) {
|
|
870
883
|
const { sendMessage, isLoading } = useChatContext();
|
|
@@ -892,8 +905,9 @@ function Composer({ className, placeholder = "Ask something..." }) {
|
|
|
892
905
|
{
|
|
893
906
|
type: "submit",
|
|
894
907
|
disabled: isLoading || !input.trim(),
|
|
895
|
-
|
|
896
|
-
|
|
908
|
+
"aria-label": "Send",
|
|
909
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 flex h-8 w-8 items-center justify-center rounded-full bg-[var(--bui-primary,#18181b)] text-[var(--bui-user-fg,#f4f4f5)] transition-all enabled:hover:bg-[var(--bui-primary-hover,#27272a)] enabled:active:scale-95 disabled:bg-[var(--bui-bg-elevated,#27272a)] disabled:text-[var(--bui-fg-faint,#52525b)] disabled:cursor-not-allowed",
|
|
910
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Send, { size: 14, strokeWidth: 1.75 })
|
|
897
911
|
}
|
|
898
912
|
)
|
|
899
913
|
] });
|
|
@@ -367,6 +367,7 @@ function ChatProvider({ endpoint = "/api/chat", tools, toolStateStore: externalS
|
|
|
367
367
|
|
|
368
368
|
// src/components/Thread.tsx
|
|
369
369
|
import { useRef as useRef3, useEffect as useEffect4 } from "react";
|
|
370
|
+
import { ArrowRight } from "lucide-react";
|
|
370
371
|
|
|
371
372
|
// src/components/Message.tsx
|
|
372
373
|
import {
|
|
@@ -777,18 +778,29 @@ function Thread({ className, emptyMessage, suggestions }) {
|
|
|
777
778
|
}
|
|
778
779
|
}, [messages, isLoading]);
|
|
779
780
|
return /* @__PURE__ */ jsx5("div", { ref: scrollRef, className: `${className || ""}`, children: /* @__PURE__ */ jsxs4("div", { className: "p-6 space-y-6", children: [
|
|
780
|
-
messages.length === 0 && /* @__PURE__ */
|
|
781
|
-
/* @__PURE__ */ jsx5("p", { className: "text-[var(--bui-fg-secondary,#a1a1aa)]
|
|
782
|
-
suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx5("div", { className: "flex flex-
|
|
781
|
+
messages.length === 0 && /* @__PURE__ */ jsxs4("div", { className: "py-12", children: [
|
|
782
|
+
/* @__PURE__ */ jsx5("p", { className: "text-center text-sm text-[var(--bui-fg-secondary,#a1a1aa)]", children: emptyMessage || "Send a message to get started" }),
|
|
783
|
+
suggestions && suggestions.length > 0 && /* @__PURE__ */ jsx5("div", { className: "mt-8 flex flex-col divide-y divide-[var(--bui-border,#27272a)] border-y border-[var(--bui-border,#27272a)]", children: suggestions.map((suggestion) => /* @__PURE__ */ jsxs4(
|
|
783
784
|
"button",
|
|
784
785
|
{
|
|
785
786
|
onClick: () => sendMessage(suggestion),
|
|
786
|
-
className: "px-
|
|
787
|
-
children:
|
|
787
|
+
className: "group flex items-center justify-between gap-4 px-5 py-4 text-left text-sm text-[var(--bui-fg,#f4f4f5)] transition-colors hover:bg-[var(--bui-bg-hover,#27272a)]",
|
|
788
|
+
children: [
|
|
789
|
+
/* @__PURE__ */ jsx5("span", { className: "flex-1", children: suggestion }),
|
|
790
|
+
/* @__PURE__ */ jsx5(
|
|
791
|
+
ArrowRight,
|
|
792
|
+
{
|
|
793
|
+
size: 14,
|
|
794
|
+
strokeWidth: 1.75,
|
|
795
|
+
"aria-hidden": "true",
|
|
796
|
+
className: "shrink-0 text-[var(--bui-fg-faint,#52525b)] transition-all group-hover:translate-x-0.5 group-hover:text-[var(--bui-fg-secondary,#a1a1aa)]"
|
|
797
|
+
}
|
|
798
|
+
)
|
|
799
|
+
]
|
|
788
800
|
},
|
|
789
801
|
suggestion
|
|
790
802
|
)) })
|
|
791
|
-
] })
|
|
803
|
+
] }),
|
|
792
804
|
messages.map((message, i) => /* @__PURE__ */ jsx5(
|
|
793
805
|
Message,
|
|
794
806
|
{
|
|
@@ -811,6 +823,7 @@ function Thread({ className, emptyMessage, suggestions }) {
|
|
|
811
823
|
|
|
812
824
|
// src/components/Composer.tsx
|
|
813
825
|
import { useState as useState3 } from "react";
|
|
826
|
+
import { Send } from "lucide-react";
|
|
814
827
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
815
828
|
function Composer({ className, placeholder = "Ask something..." }) {
|
|
816
829
|
const { sendMessage, isLoading } = useChatContext();
|
|
@@ -838,8 +851,9 @@ function Composer({ className, placeholder = "Ask something..." }) {
|
|
|
838
851
|
{
|
|
839
852
|
type: "submit",
|
|
840
853
|
disabled: isLoading || !input.trim(),
|
|
841
|
-
|
|
842
|
-
|
|
854
|
+
"aria-label": "Send",
|
|
855
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 flex h-8 w-8 items-center justify-center rounded-full bg-[var(--bui-primary,#18181b)] text-[var(--bui-user-fg,#f4f4f5)] transition-all enabled:hover:bg-[var(--bui-primary-hover,#27272a)] enabled:active:scale-95 disabled:bg-[var(--bui-bg-elevated,#27272a)] disabled:text-[var(--bui-fg-faint,#52525b)] disabled:cursor-not-allowed",
|
|
856
|
+
children: /* @__PURE__ */ jsx6(Send, { size: 14, strokeWidth: 1.75 })
|
|
843
857
|
}
|
|
844
858
|
)
|
|
845
859
|
] });
|
package/dist/openapi/index.d.mts
CHANGED
|
@@ -1,30 +1,8 @@
|
|
|
1
|
-
import { T as Tool } from '../tool-yZTixiN2.mjs';
|
|
1
|
+
import { T as Tool, g as ToolContext } from '../tool-yZTixiN2.mjs';
|
|
2
2
|
import { J as JsonSchema } from '../schema-DdZf6066.mjs';
|
|
3
3
|
import 'zod';
|
|
4
4
|
import 'react';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* OpenAPI 3.1 spec generator for Better UI tools.
|
|
8
|
-
*
|
|
9
|
-
* Converts a tool registry into a valid OpenAPI document.
|
|
10
|
-
* Each tool becomes a POST endpoint at `/api/tools/{toolName}`.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* import { generateOpenAPISpec } from '@lantos1618/better-ui/openapi';
|
|
15
|
-
* import { tools } from './tools';
|
|
16
|
-
*
|
|
17
|
-
* const spec = generateOpenAPISpec({
|
|
18
|
-
* title: 'My AI Tools API',
|
|
19
|
-
* version: '1.0.0',
|
|
20
|
-
* tools,
|
|
21
|
-
* });
|
|
22
|
-
*
|
|
23
|
-
* // Serve as JSON
|
|
24
|
-
* app.get('/openapi.json', (req, res) => res.json(spec));
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
6
|
interface OpenAPISpecConfig {
|
|
29
7
|
/** API title */
|
|
30
8
|
title: string;
|
|
@@ -109,5 +87,36 @@ declare function generateOpenAPISpec(config: OpenAPISpecConfig): OpenAPISpec;
|
|
|
109
87
|
* ```
|
|
110
88
|
*/
|
|
111
89
|
declare function openAPIHandler(config: OpenAPISpecConfig): (req: Request) => Response;
|
|
90
|
+
interface ToolRouterConfig {
|
|
91
|
+
/** Tool registry */
|
|
92
|
+
tools: Record<string, Tool>;
|
|
93
|
+
/** Base path prefix (default: '/api/tools') */
|
|
94
|
+
basePath?: string;
|
|
95
|
+
/** Optional context passed to every tool execution */
|
|
96
|
+
context?: Partial<ToolContext>;
|
|
97
|
+
/** Called before tool execution — throw to reject */
|
|
98
|
+
onBeforeExecute?: (toolName: string, input: unknown, req: Request) => Promise<void> | void;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create a request handler that routes to individual tool endpoints.
|
|
102
|
+
* Each tool is callable at `POST {basePath}/{toolName}`.
|
|
103
|
+
*
|
|
104
|
+
* Also serves the OpenAPI spec at `GET {basePath}` and a Swagger UI at `GET {basePath}/docs`.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* // Next.js catch-all: app/api/tools/[...path]/route.ts
|
|
109
|
+
* import { toolRouter } from '@lantos1618/better-ui/openapi';
|
|
110
|
+
* const router = toolRouter({ tools });
|
|
111
|
+
* export const GET = router;
|
|
112
|
+
* export const POST = router;
|
|
113
|
+
*
|
|
114
|
+
* // Now callable:
|
|
115
|
+
* // POST /api/tools/weather { "city": "Tokyo" } -> { "result": { "temp": 22, ... } }
|
|
116
|
+
* // GET /api/tools -> OpenAPI JSON spec
|
|
117
|
+
* // GET /api/tools/docs -> Swagger UI
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare function toolRouter(config: ToolRouterConfig): (req: Request) => Promise<Response>;
|
|
112
121
|
|
|
113
|
-
export { type OpenAPISpec, type OpenAPISpecConfig, generateOpenAPISpec, openAPIHandler };
|
|
122
|
+
export { type OpenAPISpec, type OpenAPISpecConfig, type ToolRouterConfig, generateOpenAPISpec, openAPIHandler, toolRouter };
|
package/dist/openapi/index.d.ts
CHANGED
|
@@ -1,30 +1,8 @@
|
|
|
1
|
-
import { T as Tool } from '../tool-yZTixiN2.js';
|
|
1
|
+
import { T as Tool, g as ToolContext } from '../tool-yZTixiN2.js';
|
|
2
2
|
import { J as JsonSchema } from '../schema-DdZf6066.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
import 'react';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* OpenAPI 3.1 spec generator for Better UI tools.
|
|
8
|
-
*
|
|
9
|
-
* Converts a tool registry into a valid OpenAPI document.
|
|
10
|
-
* Each tool becomes a POST endpoint at `/api/tools/{toolName}`.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```typescript
|
|
14
|
-
* import { generateOpenAPISpec } from '@lantos1618/better-ui/openapi';
|
|
15
|
-
* import { tools } from './tools';
|
|
16
|
-
*
|
|
17
|
-
* const spec = generateOpenAPISpec({
|
|
18
|
-
* title: 'My AI Tools API',
|
|
19
|
-
* version: '1.0.0',
|
|
20
|
-
* tools,
|
|
21
|
-
* });
|
|
22
|
-
*
|
|
23
|
-
* // Serve as JSON
|
|
24
|
-
* app.get('/openapi.json', (req, res) => res.json(spec));
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
6
|
interface OpenAPISpecConfig {
|
|
29
7
|
/** API title */
|
|
30
8
|
title: string;
|
|
@@ -109,5 +87,36 @@ declare function generateOpenAPISpec(config: OpenAPISpecConfig): OpenAPISpec;
|
|
|
109
87
|
* ```
|
|
110
88
|
*/
|
|
111
89
|
declare function openAPIHandler(config: OpenAPISpecConfig): (req: Request) => Response;
|
|
90
|
+
interface ToolRouterConfig {
|
|
91
|
+
/** Tool registry */
|
|
92
|
+
tools: Record<string, Tool>;
|
|
93
|
+
/** Base path prefix (default: '/api/tools') */
|
|
94
|
+
basePath?: string;
|
|
95
|
+
/** Optional context passed to every tool execution */
|
|
96
|
+
context?: Partial<ToolContext>;
|
|
97
|
+
/** Called before tool execution — throw to reject */
|
|
98
|
+
onBeforeExecute?: (toolName: string, input: unknown, req: Request) => Promise<void> | void;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create a request handler that routes to individual tool endpoints.
|
|
102
|
+
* Each tool is callable at `POST {basePath}/{toolName}`.
|
|
103
|
+
*
|
|
104
|
+
* Also serves the OpenAPI spec at `GET {basePath}` and a Swagger UI at `GET {basePath}/docs`.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* // Next.js catch-all: app/api/tools/[...path]/route.ts
|
|
109
|
+
* import { toolRouter } from '@lantos1618/better-ui/openapi';
|
|
110
|
+
* const router = toolRouter({ tools });
|
|
111
|
+
* export const GET = router;
|
|
112
|
+
* export const POST = router;
|
|
113
|
+
*
|
|
114
|
+
* // Now callable:
|
|
115
|
+
* // POST /api/tools/weather { "city": "Tokyo" } -> { "result": { "temp": 22, ... } }
|
|
116
|
+
* // GET /api/tools -> OpenAPI JSON spec
|
|
117
|
+
* // GET /api/tools/docs -> Swagger UI
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare function toolRouter(config: ToolRouterConfig): (req: Request) => Promise<Response>;
|
|
112
121
|
|
|
113
|
-
export { type OpenAPISpec, type OpenAPISpecConfig, generateOpenAPISpec, openAPIHandler };
|
|
122
|
+
export { type OpenAPISpec, type OpenAPISpecConfig, type ToolRouterConfig, generateOpenAPISpec, openAPIHandler, toolRouter };
|
package/dist/openapi/index.js
CHANGED
|
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var openapi_exports = {};
|
|
22
22
|
__export(openapi_exports, {
|
|
23
23
|
generateOpenAPISpec: () => generateOpenAPISpec,
|
|
24
|
-
openAPIHandler: () => openAPIHandler
|
|
24
|
+
openAPIHandler: () => openAPIHandler,
|
|
25
|
+
toolRouter: () => toolRouter
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(openapi_exports);
|
|
27
28
|
|
|
@@ -253,8 +254,86 @@ function openAPIHandler(config) {
|
|
|
253
254
|
}
|
|
254
255
|
});
|
|
255
256
|
}
|
|
257
|
+
function toolRouter(config) {
|
|
258
|
+
const basePath = config.basePath ?? "/api/tools";
|
|
259
|
+
return async (req) => {
|
|
260
|
+
const url = new URL(req.url);
|
|
261
|
+
const path = url.pathname;
|
|
262
|
+
if (req.method === "OPTIONS") {
|
|
263
|
+
return new Response(null, {
|
|
264
|
+
headers: {
|
|
265
|
+
"Access-Control-Allow-Origin": "*",
|
|
266
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
267
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
if (req.method === "GET" && (path === basePath || path === basePath + "/")) {
|
|
272
|
+
const spec = generateOpenAPISpec({
|
|
273
|
+
title: "Tool API",
|
|
274
|
+
version: "1.0.0",
|
|
275
|
+
tools: config.tools,
|
|
276
|
+
basePath,
|
|
277
|
+
serverUrl: url.origin
|
|
278
|
+
});
|
|
279
|
+
return new Response(JSON.stringify(spec, null, 2), {
|
|
280
|
+
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (req.method === "GET" && (path === basePath + "/docs" || path === basePath + "/docs/")) {
|
|
284
|
+
const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>API Docs</title>
|
|
285
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
|
286
|
+
</head><body><div id="swagger-ui"></div>
|
|
287
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
288
|
+
<script>SwaggerUIBundle({url:'${basePath}',dom_id:'#swagger-ui',deepLinking:true});</script>
|
|
289
|
+
</body></html>`;
|
|
290
|
+
return new Response(html, {
|
|
291
|
+
headers: { "Content-Type": "text/html", "Access-Control-Allow-Origin": "*" }
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
if (req.method === "POST" && path.startsWith(basePath + "/")) {
|
|
295
|
+
const toolName = path.slice(basePath.length + 1).replace(/\/$/, "");
|
|
296
|
+
if (!toolName) {
|
|
297
|
+
return Response.json({ error: "Missing tool name" }, { status: 400 });
|
|
298
|
+
}
|
|
299
|
+
if (!Object.prototype.hasOwnProperty.call(config.tools, toolName)) {
|
|
300
|
+
return Response.json({ error: "Tool not found" }, { status: 404 });
|
|
301
|
+
}
|
|
302
|
+
const tool = config.tools[toolName];
|
|
303
|
+
let input;
|
|
304
|
+
try {
|
|
305
|
+
input = await req.json();
|
|
306
|
+
} catch {
|
|
307
|
+
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
if (config.onBeforeExecute) {
|
|
311
|
+
await config.onBeforeExecute(toolName, input, req);
|
|
312
|
+
}
|
|
313
|
+
const result = await tool.run(input, {
|
|
314
|
+
isServer: true,
|
|
315
|
+
headers: req.headers,
|
|
316
|
+
...config.context
|
|
317
|
+
});
|
|
318
|
+
return Response.json({ result }, {
|
|
319
|
+
headers: { "Access-Control-Allow-Origin": "*" }
|
|
320
|
+
});
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (error instanceof Error && error.name === "ZodError") {
|
|
323
|
+
return Response.json({ error: error.message }, { status: 400 });
|
|
324
|
+
}
|
|
325
|
+
return Response.json(
|
|
326
|
+
{ error: error instanceof Error ? error.message : "Tool execution failed" },
|
|
327
|
+
{ status: 500 }
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return Response.json({ error: "Not found" }, { status: 404 });
|
|
332
|
+
};
|
|
333
|
+
}
|
|
256
334
|
// Annotate the CommonJS export names for ESM import in node:
|
|
257
335
|
0 && (module.exports = {
|
|
258
336
|
generateOpenAPISpec,
|
|
259
|
-
openAPIHandler
|
|
337
|
+
openAPIHandler,
|
|
338
|
+
toolRouter
|
|
260
339
|
});
|
package/dist/openapi/index.mjs
CHANGED
|
@@ -91,7 +91,85 @@ function openAPIHandler(config) {
|
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
+
function toolRouter(config) {
|
|
95
|
+
const basePath = config.basePath ?? "/api/tools";
|
|
96
|
+
return async (req) => {
|
|
97
|
+
const url = new URL(req.url);
|
|
98
|
+
const path = url.pathname;
|
|
99
|
+
if (req.method === "OPTIONS") {
|
|
100
|
+
return new Response(null, {
|
|
101
|
+
headers: {
|
|
102
|
+
"Access-Control-Allow-Origin": "*",
|
|
103
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
104
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization"
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (req.method === "GET" && (path === basePath || path === basePath + "/")) {
|
|
109
|
+
const spec = generateOpenAPISpec({
|
|
110
|
+
title: "Tool API",
|
|
111
|
+
version: "1.0.0",
|
|
112
|
+
tools: config.tools,
|
|
113
|
+
basePath,
|
|
114
|
+
serverUrl: url.origin
|
|
115
|
+
});
|
|
116
|
+
return new Response(JSON.stringify(spec, null, 2), {
|
|
117
|
+
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (req.method === "GET" && (path === basePath + "/docs" || path === basePath + "/docs/")) {
|
|
121
|
+
const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>API Docs</title>
|
|
122
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
|
123
|
+
</head><body><div id="swagger-ui"></div>
|
|
124
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
125
|
+
<script>SwaggerUIBundle({url:'${basePath}',dom_id:'#swagger-ui',deepLinking:true});</script>
|
|
126
|
+
</body></html>`;
|
|
127
|
+
return new Response(html, {
|
|
128
|
+
headers: { "Content-Type": "text/html", "Access-Control-Allow-Origin": "*" }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (req.method === "POST" && path.startsWith(basePath + "/")) {
|
|
132
|
+
const toolName = path.slice(basePath.length + 1).replace(/\/$/, "");
|
|
133
|
+
if (!toolName) {
|
|
134
|
+
return Response.json({ error: "Missing tool name" }, { status: 400 });
|
|
135
|
+
}
|
|
136
|
+
if (!Object.prototype.hasOwnProperty.call(config.tools, toolName)) {
|
|
137
|
+
return Response.json({ error: "Tool not found" }, { status: 404 });
|
|
138
|
+
}
|
|
139
|
+
const tool = config.tools[toolName];
|
|
140
|
+
let input;
|
|
141
|
+
try {
|
|
142
|
+
input = await req.json();
|
|
143
|
+
} catch {
|
|
144
|
+
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
if (config.onBeforeExecute) {
|
|
148
|
+
await config.onBeforeExecute(toolName, input, req);
|
|
149
|
+
}
|
|
150
|
+
const result = await tool.run(input, {
|
|
151
|
+
isServer: true,
|
|
152
|
+
headers: req.headers,
|
|
153
|
+
...config.context
|
|
154
|
+
});
|
|
155
|
+
return Response.json({ result }, {
|
|
156
|
+
headers: { "Access-Control-Allow-Origin": "*" }
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error instanceof Error && error.name === "ZodError") {
|
|
160
|
+
return Response.json({ error: error.message }, { status: 400 });
|
|
161
|
+
}
|
|
162
|
+
return Response.json(
|
|
163
|
+
{ error: error instanceof Error ? error.message : "Tool execution failed" },
|
|
164
|
+
{ status: 500 }
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return Response.json({ error: "Not found" }, { status: 404 });
|
|
169
|
+
};
|
|
170
|
+
}
|
|
94
171
|
export {
|
|
95
172
|
generateOpenAPISpec,
|
|
96
|
-
openAPIHandler
|
|
173
|
+
openAPIHandler,
|
|
174
|
+
toolRouter
|
|
97
175
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lantos1618/better-ui",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "A minimal, type-safe AI-first UI framework for building tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"@ai-sdk/anthropic": "^3.0.0",
|
|
93
93
|
"@ai-sdk/google": "^3.0.0",
|
|
94
94
|
"jose": "^6.0.0",
|
|
95
|
+
"lucide-react": "^1.9.0",
|
|
95
96
|
"react": "^19.0.0",
|
|
96
97
|
"react-dom": "^19.0.0"
|
|
97
98
|
},
|
|
@@ -123,6 +124,7 @@
|
|
|
123
124
|
"jest": "^29.7.0",
|
|
124
125
|
"jest-environment-jsdom": "^30.3.0",
|
|
125
126
|
"jose": "^6.2.2",
|
|
127
|
+
"lucide-react": "^1.9.0",
|
|
126
128
|
"react": "^19.2.4",
|
|
127
129
|
"react-dom": "^19.2.4",
|
|
128
130
|
"ts-jest": "^29.4.9",
|