@lantos1618/better-ui 0.2.3 → 0.4.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/README.md +271 -354
- package/dist/ThemeProvider-BYeqWMsn.d.mts +187 -0
- package/dist/ThemeProvider-BaVZaDBO.d.ts +187 -0
- package/dist/auth/index.d.mts +56 -0
- package/dist/auth/index.d.ts +56 -0
- package/dist/auth/index.js +104 -0
- package/dist/auth/index.mjs +67 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/components/index.d.mts +258 -0
- package/dist/components/index.d.ts +258 -0
- package/dist/components/index.js +1977 -0
- package/dist/components/index.mjs +1922 -0
- package/dist/index.d.mts +75 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +587 -0
- package/dist/index.mjs +557 -0
- package/dist/persistence/index.d.mts +11 -0
- package/dist/persistence/index.d.ts +11 -0
- package/dist/persistence/index.js +66 -0
- package/dist/persistence/index.mjs +41 -0
- package/dist/react/index.d.mts +91 -0
- package/dist/react/index.d.ts +91 -0
- package/dist/react/index.js +284 -0
- package/dist/react/index.mjs +257 -0
- package/dist/tool-Ca2x-VNK.d.mts +361 -0
- package/dist/tool-Ca2x-VNK.d.ts +361 -0
- package/dist/types-CAOfGUPH.d.mts +31 -0
- package/dist/types-CAOfGUPH.d.ts +31 -0
- package/package.json +85 -30
- package/src/theme.css +101 -0
- 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/README.md
CHANGED
|
@@ -1,457 +1,374 @@
|
|
|
1
|
-
# Better UI
|
|
1
|
+
# Better UI
|
|
2
2
|
|
|
3
|
-
> A
|
|
3
|
+
> A minimal, type-safe AI-first UI framework for building tools
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@lantos1618/better-ui)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
|
-
[](https://nextjs.org/)
|
|
9
8
|
|
|
10
|
-
##
|
|
9
|
+
## What is Better UI?
|
|
11
10
|
|
|
12
|
-
Better UI
|
|
11
|
+
Better UI provides a clean, fluent API for creating tools that AI assistants can execute. Define input/output schemas with Zod, implement server and client logic separately, and render results with React components.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
**Key differentiators**:
|
|
14
|
+
- **View integration** - tools render their own results in UI (no other framework does this)
|
|
15
|
+
- **Multi-provider support** - OpenAI, Anthropic, Google Gemini, OpenRouter
|
|
16
|
+
- **Streaming tool views** - progressive partial data rendering
|
|
17
|
+
- **Pre-made chat components** - drop-in `<Chat />` with automatic tool view rendering
|
|
18
|
+
- **Server infrastructure** - rate limiting, caching, Next.js/Express adapters
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
- **🔒 Type Safety**: Full TypeScript + Zod schema validation
|
|
18
|
-
- **🎨 AI-Native**: Built specifically for AI assistants to control applications
|
|
19
|
-
- **⚡ Performance**: Smart client-side caching and optimization
|
|
20
|
-
- **🔧 Extensible**: Easy to create custom tools for any use case
|
|
21
|
-
|
|
22
|
-
## 📦 Installation
|
|
20
|
+
## Installation
|
|
23
21
|
|
|
24
22
|
```bash
|
|
25
|
-
npm install @lantos1618/better-ui
|
|
26
|
-
# or
|
|
27
|
-
yarn add @lantos1618/better-ui
|
|
28
|
-
# or
|
|
29
|
-
bun add @lantos1618/better-ui
|
|
23
|
+
npm install @lantos1618/better-ui zod
|
|
30
24
|
```
|
|
31
25
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
### 1. Simple Tool (2 methods only!)
|
|
26
|
+
## Quick Start
|
|
35
27
|
|
|
36
|
-
```
|
|
37
|
-
import {
|
|
28
|
+
```typescript
|
|
29
|
+
import { tool } from '@lantos1618/better-ui';
|
|
38
30
|
import { z } from 'zod';
|
|
39
31
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
|
|
32
|
+
const weather = tool({
|
|
33
|
+
name: 'weather',
|
|
34
|
+
description: 'Get weather for a city',
|
|
35
|
+
input: z.object({ city: z.string() }),
|
|
36
|
+
output: z.object({ temp: z.number(), condition: z.string() }),
|
|
37
|
+
});
|
|
46
38
|
|
|
47
|
-
|
|
39
|
+
// Server implementation (runs on server)
|
|
40
|
+
weather.server(async ({ city }) => {
|
|
41
|
+
const data = await weatherAPI.get(city);
|
|
42
|
+
return { temp: data.temp, condition: data.condition };
|
|
43
|
+
});
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const cached = ctx.cache.get(input.query);
|
|
57
|
-
return cached || ctx.fetch('/api/tools/search', { body: input });
|
|
58
|
-
})
|
|
59
|
-
.render(({ data }) => <SearchResults results={data} />);
|
|
45
|
+
// View for rendering results (our differentiator!)
|
|
46
|
+
weather.view((data) => (
|
|
47
|
+
<div className="weather-card">
|
|
48
|
+
<span>{data.temp}</span>
|
|
49
|
+
<span>{data.condition}</span>
|
|
50
|
+
</div>
|
|
51
|
+
));
|
|
60
52
|
```
|
|
61
53
|
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
Better UI seamlessly integrates with Vercel's AI SDK for building chat interfaces:
|
|
65
|
-
|
|
66
|
-
### Basic Chat Integration
|
|
54
|
+
## Drop-in Chat UI
|
|
67
55
|
|
|
68
56
|
```tsx
|
|
69
|
-
|
|
70
|
-
import { openai } from '@ai-sdk/openai';
|
|
71
|
-
import { streamText, convertToCoreMessages } from 'ai';
|
|
72
|
-
import { aui } from '@lantos1618/better-ui';
|
|
73
|
-
import { z } from 'zod';
|
|
74
|
-
|
|
75
|
-
// Define your tools
|
|
76
|
-
const weatherTool = aui
|
|
77
|
-
.tool('getWeather')
|
|
78
|
-
.description('Get current weather for a city')
|
|
79
|
-
.input(z.object({
|
|
80
|
-
city: z.string().describe('City name'),
|
|
81
|
-
unit: z.enum(['celsius', 'fahrenheit']).optional()
|
|
82
|
-
}))
|
|
83
|
-
.execute(async ({ input }) => {
|
|
84
|
-
// Fetch real weather data
|
|
85
|
-
const response = await fetch(`https://api.weather.com/v1/weather?city=${input.city}`);
|
|
86
|
-
return response.json();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const stockTool = aui
|
|
90
|
-
.tool('getStockPrice')
|
|
91
|
-
.description('Get current stock price')
|
|
92
|
-
.input(z.object({
|
|
93
|
-
symbol: z.string().describe('Stock symbol (e.g., AAPL)')
|
|
94
|
-
}))
|
|
95
|
-
.execute(async ({ input }) => {
|
|
96
|
-
const response = await fetch(`https://api.stocks.com/v1/quote/${input.symbol}`);
|
|
97
|
-
return response.json();
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Convert to AI SDK format
|
|
101
|
-
const tools = {
|
|
102
|
-
getWeather: weatherTool.toAISDKTool(),
|
|
103
|
-
getStockPrice: stockTool.toAISDKTool(),
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
export async function POST(req: Request) {
|
|
107
|
-
const { messages } = await req.json();
|
|
108
|
-
|
|
109
|
-
const result = await streamText({
|
|
110
|
-
model: openai('gpt-4'),
|
|
111
|
-
messages: convertToCoreMessages(messages),
|
|
112
|
-
tools,
|
|
113
|
-
maxToolRoundtrips: 5,
|
|
114
|
-
});
|
|
57
|
+
import { Chat } from '@lantos1618/better-ui/components';
|
|
115
58
|
|
|
116
|
-
|
|
59
|
+
function App() {
|
|
60
|
+
return (
|
|
61
|
+
<Chat
|
|
62
|
+
endpoint="/api/chat"
|
|
63
|
+
tools={{ weather, search, counter }}
|
|
64
|
+
className="h-[600px]"
|
|
65
|
+
placeholder="Ask something..."
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
117
68
|
}
|
|
118
69
|
```
|
|
119
70
|
|
|
120
|
-
|
|
71
|
+
Or compose your own layout:
|
|
121
72
|
|
|
122
73
|
```tsx
|
|
123
|
-
|
|
124
|
-
'use client';
|
|
125
|
-
|
|
126
|
-
import { useChat } from 'ai/react';
|
|
127
|
-
import { ToolExecutorProvider, ToolRenderer } from '@lantos1618/better-ui/client';
|
|
128
|
-
|
|
129
|
-
export default function ChatPage() {
|
|
130
|
-
const { messages, input, handleInputChange, handleSubmit } = useChat();
|
|
74
|
+
import { ChatProvider, Thread, Composer } from '@lantos1618/better-ui/components';
|
|
131
75
|
|
|
76
|
+
function App() {
|
|
132
77
|
return (
|
|
133
|
-
<
|
|
78
|
+
<ChatProvider endpoint="/api/chat" tools={tools}>
|
|
134
79
|
<div className="flex flex-col h-screen">
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
<div key={message.id} className="mb-4">
|
|
138
|
-
<div className="font-bold">{message.role}:</div>
|
|
139
|
-
<div>{message.content}</div>
|
|
140
|
-
|
|
141
|
-
{/* Render tool calls with Better UI */}
|
|
142
|
-
{message.toolInvocations?.map((toolCall) => (
|
|
143
|
-
<ToolRenderer
|
|
144
|
-
key={toolCall.toolCallId}
|
|
145
|
-
toolCall={toolCall}
|
|
146
|
-
tool={tools[toolCall.toolName]}
|
|
147
|
-
/>
|
|
148
|
-
))}
|
|
149
|
-
</div>
|
|
150
|
-
))}
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
<form onSubmit={handleSubmit} className="p-4 border-t">
|
|
154
|
-
<input
|
|
155
|
-
value={input}
|
|
156
|
-
onChange={handleInputChange}
|
|
157
|
-
placeholder="Ask about weather or stocks..."
|
|
158
|
-
className="w-full p-2 border rounded"
|
|
159
|
-
/>
|
|
160
|
-
</form>
|
|
80
|
+
<Thread className="flex-1 overflow-y-auto" />
|
|
81
|
+
<Composer placeholder="Type a message..." />
|
|
161
82
|
</div>
|
|
162
|
-
</
|
|
83
|
+
</ChatProvider>
|
|
163
84
|
);
|
|
164
85
|
}
|
|
165
86
|
```
|
|
166
87
|
|
|
167
|
-
|
|
88
|
+
Tool results in chat automatically render using the tool's `.view()` component.
|
|
168
89
|
|
|
169
|
-
|
|
170
|
-
// lib/ai-tools.ts
|
|
171
|
-
import { aui } from '@lantos1618/better-ui';
|
|
172
|
-
import { z } from 'zod';
|
|
90
|
+
## Multi-Provider Support
|
|
173
91
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
.clientExecute(async ({ input, ctx }) => {
|
|
193
|
-
// Client-side caching
|
|
194
|
-
const cacheKey = JSON.stringify(input);
|
|
195
|
-
const cached = ctx.cache.get(cacheKey);
|
|
196
|
-
if (cached && Date.now() - cached.timestamp < 60000) {
|
|
197
|
-
return cached.data;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const result = await ctx.fetch('/api/search', {
|
|
201
|
-
method: 'POST',
|
|
202
|
-
body: JSON.stringify(input)
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
ctx.cache.set(cacheKey, { data: result, timestamp: Date.now() });
|
|
206
|
-
return result;
|
|
207
|
-
})
|
|
208
|
-
.render(({ data }) => (
|
|
209
|
-
<div className="grid gap-2">
|
|
210
|
-
{data.results.map((item) => (
|
|
211
|
-
<div key={item.id} className="p-2 border rounded">
|
|
212
|
-
<h3>{item.title}</h3>
|
|
213
|
-
<p>{item.description}</p>
|
|
214
|
-
</div>
|
|
215
|
-
))}
|
|
216
|
-
</div>
|
|
217
|
-
));
|
|
218
|
-
|
|
219
|
-
// Chart generation tool
|
|
220
|
-
export const createChartTool = aui
|
|
221
|
-
.tool('createChart')
|
|
222
|
-
.description('Generate interactive charts')
|
|
223
|
-
.input(z.object({
|
|
224
|
-
type: z.enum(['line', 'bar', 'pie', 'scatter']),
|
|
225
|
-
data: z.array(z.object({
|
|
226
|
-
label: z.string(),
|
|
227
|
-
value: z.number(),
|
|
228
|
-
})),
|
|
229
|
-
title: z.string().optional(),
|
|
230
|
-
}))
|
|
231
|
-
.execute(async ({ input }) => input)
|
|
232
|
-
.render(({ data }) => (
|
|
233
|
-
<ChartComponent type={data.type} data={data.data} title={data.title} />
|
|
234
|
-
));
|
|
235
|
-
|
|
236
|
-
// Form generation tool
|
|
237
|
-
export const generateFormTool = aui
|
|
238
|
-
.tool('generateForm')
|
|
239
|
-
.description('Create dynamic forms')
|
|
240
|
-
.input(z.object({
|
|
241
|
-
fields: z.array(z.object({
|
|
242
|
-
name: z.string(),
|
|
243
|
-
type: z.enum(['text', 'number', 'email', 'select', 'checkbox']),
|
|
244
|
-
label: z.string(),
|
|
245
|
-
required: z.boolean().optional(),
|
|
246
|
-
options: z.array(z.string()).optional(),
|
|
247
|
-
})),
|
|
248
|
-
submitUrl: z.string(),
|
|
249
|
-
}))
|
|
250
|
-
.execute(async ({ input }) => input)
|
|
251
|
-
.render(({ data }) => (
|
|
252
|
-
<DynamicForm fields={data.fields} submitUrl={data.submitUrl} />
|
|
253
|
-
));
|
|
92
|
+
```typescript
|
|
93
|
+
import { createProvider } from '@lantos1618/better-ui';
|
|
94
|
+
|
|
95
|
+
// OpenAI
|
|
96
|
+
const provider = createProvider({ provider: 'openai', model: 'gpt-4o' });
|
|
97
|
+
|
|
98
|
+
// Anthropic
|
|
99
|
+
const provider = createProvider({ provider: 'anthropic', model: 'claude-4-sonnet' });
|
|
100
|
+
|
|
101
|
+
// Google Gemini
|
|
102
|
+
const provider = createProvider({ provider: 'google', model: 'gemini-2.5-pro' });
|
|
103
|
+
|
|
104
|
+
// OpenRouter (access any model)
|
|
105
|
+
const provider = createProvider({
|
|
106
|
+
provider: 'openrouter',
|
|
107
|
+
model: 'anthropic/claude-4-sonnet',
|
|
108
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
109
|
+
});
|
|
254
110
|
```
|
|
255
111
|
|
|
256
|
-
|
|
112
|
+
Use with the chat handler:
|
|
257
113
|
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
import {
|
|
261
|
-
|
|
114
|
+
```typescript
|
|
115
|
+
import { streamText, convertToModelMessages } from 'ai';
|
|
116
|
+
import { createProvider } from '@lantos1618/better-ui';
|
|
117
|
+
|
|
118
|
+
const provider = createProvider({ provider: 'openai', model: 'gpt-4o' });
|
|
262
119
|
|
|
263
120
|
export async function POST(req: Request) {
|
|
264
121
|
const { messages } = await req.json();
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
messages,
|
|
122
|
+
const result = streamText({
|
|
123
|
+
model: provider.model(),
|
|
124
|
+
messages: convertToModelMessages(messages),
|
|
269
125
|
tools: {
|
|
270
|
-
|
|
271
|
-
createChart: createChartTool.toAISDKTool(),
|
|
272
|
-
generateForm: generateFormTool.toAISDKTool(),
|
|
273
|
-
},
|
|
274
|
-
toolChoice: 'auto', // Let AI decide when to use tools
|
|
275
|
-
async onToolCall({ toolCall, toolResult }) {
|
|
276
|
-
// Log tool usage for analytics
|
|
277
|
-
console.log(`Tool ${toolCall.toolName} called with:`, toolCall.args);
|
|
278
|
-
console.log(`Result:`, toolResult);
|
|
126
|
+
weather: weatherTool.toAITool(),
|
|
279
127
|
},
|
|
280
128
|
});
|
|
281
|
-
|
|
282
|
-
return result.toDataStreamResponse();
|
|
129
|
+
return result.toUIMessageStreamResponse();
|
|
283
130
|
}
|
|
284
131
|
```
|
|
285
132
|
|
|
286
|
-
##
|
|
133
|
+
## Streaming Tool Views
|
|
287
134
|
|
|
288
|
-
|
|
135
|
+
Tools can stream partial results progressively:
|
|
289
136
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
- `typeTool` - Type text into inputs
|
|
293
|
-
- `scrollTool` - Scroll to elements
|
|
294
|
-
- `selectTool` - Select dropdown options
|
|
137
|
+
```typescript
|
|
138
|
+
import { useToolStream } from '@lantos1618/better-ui';
|
|
295
139
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
140
|
+
// Define a streaming tool
|
|
141
|
+
const analysis = tool({
|
|
142
|
+
name: 'analysis',
|
|
143
|
+
input: z.object({ query: z.string() }),
|
|
144
|
+
output: z.object({ summary: z.string(), score: z.number() }),
|
|
145
|
+
});
|
|
299
146
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
147
|
+
analysis.stream(async ({ query }, { stream }) => {
|
|
148
|
+
stream({ summary: 'Analyzing...' }); // Partial update
|
|
149
|
+
const result = await analyzeData(query);
|
|
150
|
+
stream({ summary: result.summary }); // More data
|
|
151
|
+
return { summary: result.summary, score: result.score }; // Final
|
|
152
|
+
});
|
|
303
153
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
154
|
+
// In a component:
|
|
155
|
+
function AnalysisWidget({ query }) {
|
|
156
|
+
const { data, streaming, loading, execute } = useToolStream(analysis);
|
|
307
157
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
158
|
+
return (
|
|
159
|
+
<div>
|
|
160
|
+
<button onClick={() => execute({ query })}>Analyze</button>
|
|
161
|
+
{loading && <p>Starting...</p>}
|
|
162
|
+
{streaming && <p>Streaming: {data?.summary}</p>}
|
|
163
|
+
{data?.score && <p>Score: {data.score}</p>}
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
311
168
|
|
|
312
|
-
##
|
|
169
|
+
## Core Concepts
|
|
313
170
|
|
|
314
|
-
###
|
|
171
|
+
### 1. Tool Definition
|
|
315
172
|
|
|
316
173
|
```typescript
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
.
|
|
321
|
-
.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
.stream(handler: StreamHandler) // Streaming support (optional)
|
|
174
|
+
const myTool = tool({
|
|
175
|
+
name: 'myTool',
|
|
176
|
+
description: 'What this tool does',
|
|
177
|
+
input: z.object({ /* input schema */ }),
|
|
178
|
+
output: z.object({ /* output schema */ }),
|
|
179
|
+
tags: ['category', 'type'],
|
|
180
|
+
cache: { ttl: 60000 }, // optional caching
|
|
181
|
+
});
|
|
326
182
|
```
|
|
327
183
|
|
|
328
|
-
###
|
|
329
|
-
|
|
330
|
-
```tsx
|
|
331
|
-
// Provider for tool execution
|
|
332
|
-
<ToolExecutorProvider tools={tools}>
|
|
333
|
-
<App />
|
|
334
|
-
</ToolExecutorProvider>
|
|
335
|
-
|
|
336
|
-
// Render tool results
|
|
337
|
-
<ToolRenderer toolCall={toolCall} tool={tool} />
|
|
184
|
+
### 2. Server Implementation
|
|
338
185
|
|
|
339
|
-
|
|
340
|
-
const executor = useToolExecutor();
|
|
341
|
-
const result = await executor.execute(toolCall);
|
|
186
|
+
The `.server()` method defines logic that runs on the server (API routes, server components):
|
|
342
187
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
188
|
+
```typescript
|
|
189
|
+
myTool.server(async ({ query }, ctx) => {
|
|
190
|
+
// Direct access to databases, secrets, file system
|
|
191
|
+
const results = await db.search(query);
|
|
192
|
+
return { results };
|
|
193
|
+
});
|
|
346
194
|
```
|
|
347
195
|
|
|
348
|
-
###
|
|
196
|
+
### 3. Client Implementation (Optional)
|
|
197
|
+
|
|
198
|
+
The `.client()` method defines what happens when called from the browser. If not specified, auto-fetches to `/api/tools/execute`.
|
|
349
199
|
|
|
350
200
|
```typescript
|
|
351
|
-
|
|
201
|
+
myTool.client(async ({ query }, ctx) => {
|
|
202
|
+
const cached = ctx.cache.get(query);
|
|
203
|
+
if (cached) return cached;
|
|
352
204
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
205
|
+
return ctx.fetch('/api/search', {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
body: JSON.stringify({ query })
|
|
208
|
+
});
|
|
357
209
|
});
|
|
358
|
-
|
|
359
|
-
const response = await assistant.chat('What\'s the weather in NYC?');
|
|
360
210
|
```
|
|
361
211
|
|
|
362
|
-
|
|
212
|
+
### 4. View (Our Differentiator)
|
|
363
213
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
Client/Server Split State Management
|
|
214
|
+
The `.view()` method defines how to render the tool's results:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
myTool.view((data, { loading, error, streaming }) => {
|
|
218
|
+
if (loading) return <Spinner />;
|
|
219
|
+
if (error) return <Error message={error.message} />;
|
|
220
|
+
if (streaming) return <PartialResults data={data} />;
|
|
221
|
+
return <Results items={data.results} />;
|
|
222
|
+
});
|
|
374
223
|
```
|
|
375
224
|
|
|
376
|
-
|
|
225
|
+
### 5. Streaming
|
|
377
226
|
|
|
378
|
-
|
|
379
|
-
# Run all tests
|
|
380
|
-
npm test
|
|
227
|
+
The `.stream()` method enables progressive partial updates:
|
|
381
228
|
|
|
382
|
-
|
|
383
|
-
|
|
229
|
+
```typescript
|
|
230
|
+
myTool.stream(async ({ query }, { stream }) => {
|
|
231
|
+
stream({ status: 'searching...' });
|
|
232
|
+
const results = await search(query);
|
|
233
|
+
stream({ results, status: 'done' });
|
|
234
|
+
return { results, status: 'done', count: results.length };
|
|
235
|
+
});
|
|
236
|
+
```
|
|
384
237
|
|
|
385
|
-
|
|
386
|
-
npm run type-check
|
|
238
|
+
## Fluent Builder Alternative
|
|
387
239
|
|
|
388
|
-
|
|
389
|
-
|
|
240
|
+
```typescript
|
|
241
|
+
const search = tool('search')
|
|
242
|
+
.description('Search the database')
|
|
243
|
+
.input(z.object({ query: z.string() }))
|
|
244
|
+
.output(z.object({ results: z.array(z.string()) }))
|
|
245
|
+
.server(async ({ query }) => ({ results: await db.search(query) }))
|
|
246
|
+
.stream(async ({ query }, { stream }) => {
|
|
247
|
+
stream({ results: [] });
|
|
248
|
+
const results = await db.search(query);
|
|
249
|
+
return { results };
|
|
250
|
+
})
|
|
251
|
+
.view((data) => <ResultsList items={data.results} />);
|
|
390
252
|
```
|
|
391
253
|
|
|
392
|
-
##
|
|
254
|
+
## React Hooks
|
|
393
255
|
|
|
394
|
-
###
|
|
256
|
+
### `useTool(tool, input?, options?)`
|
|
395
257
|
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
|
|
258
|
+
```typescript
|
|
259
|
+
import { useTool } from '@lantos1618/better-ui';
|
|
260
|
+
|
|
261
|
+
const {
|
|
262
|
+
data, // Result data
|
|
263
|
+
loading, // Loading state
|
|
264
|
+
error, // Error if any
|
|
265
|
+
execute, // Execute function
|
|
266
|
+
reset, // Reset state
|
|
267
|
+
executed, // Has been executed
|
|
268
|
+
} = useTool(myTool, initialInput, {
|
|
269
|
+
auto: false,
|
|
270
|
+
onSuccess: (data) => {},
|
|
271
|
+
onError: (error) => {},
|
|
272
|
+
});
|
|
273
|
+
```
|
|
399
274
|
|
|
400
|
-
|
|
401
|
-
vercel
|
|
275
|
+
### `useToolStream(tool, options?)`
|
|
402
276
|
|
|
403
|
-
|
|
404
|
-
|
|
277
|
+
```typescript
|
|
278
|
+
import { useToolStream } from '@lantos1618/better-ui';
|
|
279
|
+
|
|
280
|
+
const {
|
|
281
|
+
data, // Progressive partial data
|
|
282
|
+
finalData, // Complete validated data (when done)
|
|
283
|
+
streaming, // True while receiving partial updates
|
|
284
|
+
loading, // True before first chunk
|
|
285
|
+
error,
|
|
286
|
+
execute,
|
|
287
|
+
reset,
|
|
288
|
+
} = useToolStream(myTool);
|
|
405
289
|
```
|
|
406
290
|
|
|
407
|
-
###
|
|
291
|
+
### `useTools(tools, options?)`
|
|
408
292
|
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
OPENAI_API_KEY=your_api_key
|
|
293
|
+
```typescript
|
|
294
|
+
import { useTools } from '@lantos1618/better-ui';
|
|
412
295
|
|
|
413
|
-
|
|
414
|
-
DATABASE_URL=your_db_url
|
|
415
|
-
REDIS_URL=your_redis_url
|
|
416
|
-
```
|
|
296
|
+
const tools = useTools({ weather, search });
|
|
417
297
|
|
|
418
|
-
|
|
298
|
+
await tools.weather.execute({ city: 'London' });
|
|
299
|
+
await tools.search.execute({ query: 'restaurants' });
|
|
419
300
|
|
|
420
|
-
|
|
301
|
+
tools.weather.data; // Weather result
|
|
302
|
+
tools.search.loading; // Search loading state
|
|
303
|
+
```
|
|
421
304
|
|
|
422
|
-
|
|
423
|
-
- [Dashboard](./examples/dashboard) - Analytics dashboard with AI controls
|
|
424
|
-
- [Form Builder](./examples/form-builder) - Dynamic form generation
|
|
425
|
-
- [Data Explorer](./examples/data-explorer) - Database exploration tool
|
|
305
|
+
## Chat Components
|
|
426
306
|
|
|
427
|
-
|
|
307
|
+
Import from `@lantos1618/better-ui/components`:
|
|
428
308
|
|
|
429
|
-
|
|
309
|
+
| Component | Description |
|
|
310
|
+
|-----------|-------------|
|
|
311
|
+
| `Chat` | All-in-one chat component (ChatProvider + Thread + Composer) |
|
|
312
|
+
| `ChatProvider` | Context provider wrapping AI SDK's `useChat` |
|
|
313
|
+
| `Thread` | Message list with auto-scroll |
|
|
314
|
+
| `Message` | Single message with automatic tool view rendering |
|
|
315
|
+
| `Composer` | Input form with send button |
|
|
316
|
+
| `ToolResult` | Renders a tool's `.View` in chat context |
|
|
430
317
|
|
|
431
|
-
|
|
432
|
-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
433
|
-
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
434
|
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
435
|
-
5. Open a Pull Request
|
|
318
|
+
All components accept `className` for styling customization.
|
|
436
319
|
|
|
437
|
-
##
|
|
320
|
+
## Providers
|
|
438
321
|
|
|
439
|
-
|
|
322
|
+
| Provider | Package Required | Example Model |
|
|
323
|
+
|----------|-----------------|---------------|
|
|
324
|
+
| OpenAI | `@ai-sdk/openai` (included) | `gpt-4o`, `gpt-5.2` |
|
|
325
|
+
| Anthropic | `@ai-sdk/anthropic` (optional) | `claude-4-sonnet` |
|
|
326
|
+
| Google | `@ai-sdk/google` (optional) | `gemini-2.5-pro` |
|
|
327
|
+
| OpenRouter | `@ai-sdk/openai` (included) | `anthropic/claude-4-sonnet` |
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
# Optional providers
|
|
331
|
+
npm install @ai-sdk/anthropic # For Anthropic
|
|
332
|
+
npm install @ai-sdk/google # For Google Gemini
|
|
333
|
+
```
|
|
440
334
|
|
|
441
|
-
##
|
|
335
|
+
## Project Structure
|
|
442
336
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
337
|
+
```
|
|
338
|
+
src/
|
|
339
|
+
tool.tsx # Core tool() API with streaming
|
|
340
|
+
react/
|
|
341
|
+
useTool.ts # React hooks (useTool, useTools)
|
|
342
|
+
useToolStream.ts # Streaming hook
|
|
343
|
+
components/
|
|
344
|
+
Chat.tsx # All-in-one chat component
|
|
345
|
+
ChatProvider.tsx # Chat context provider
|
|
346
|
+
Thread.tsx # Message list
|
|
347
|
+
Message.tsx # Single message
|
|
348
|
+
Composer.tsx # Input form
|
|
349
|
+
ToolResult.tsx # Tool view renderer
|
|
350
|
+
providers/
|
|
351
|
+
openai.ts # OpenAI adapter
|
|
352
|
+
anthropic.ts # Anthropic adapter
|
|
353
|
+
google.ts # Google Gemini adapter
|
|
354
|
+
openrouter.ts # OpenRouter adapter
|
|
355
|
+
adapters/
|
|
356
|
+
nextjs.ts # Next.js route handlers
|
|
357
|
+
express.ts # Express middleware
|
|
358
|
+
index.ts # Main exports
|
|
359
|
+
```
|
|
448
360
|
|
|
449
|
-
##
|
|
361
|
+
## Development
|
|
450
362
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
363
|
+
```bash
|
|
364
|
+
npm install
|
|
365
|
+
npm run dev # Run dev server
|
|
366
|
+
npm run build:lib # Build library
|
|
367
|
+
npm run build # Build everything
|
|
368
|
+
npm run type-check # TypeScript check
|
|
369
|
+
npm test # Run tests
|
|
370
|
+
```
|
|
454
371
|
|
|
455
|
-
|
|
372
|
+
## License
|
|
456
373
|
|
|
457
|
-
|
|
374
|
+
MIT
|