@lantos1618/better-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +190 -0
- package/lib/aui/README.md +136 -0
- package/lib/aui/__tests__/aui-complete.test.ts +251 -0
- package/lib/aui/__tests__/aui-comprehensive.test.ts +376 -0
- package/lib/aui/__tests__/aui-concise.test.ts +278 -0
- package/lib/aui/__tests__/aui-integration.test.ts +309 -0
- package/lib/aui/__tests__/aui-simple.test.ts +116 -0
- package/lib/aui/__tests__/aui.test.ts +269 -0
- package/lib/aui/__tests__/concise-api.test.ts +165 -0
- package/lib/aui/__tests__/core.test.ts +265 -0
- package/lib/aui/__tests__/simple-api.test.ts +200 -0
- package/lib/aui/ai-assistant.ts +408 -0
- package/lib/aui/ai-control.ts +353 -0
- package/lib/aui/client/use-aui.ts +55 -0
- package/lib/aui/client-control.ts +551 -0
- package/lib/aui/client-executor.ts +417 -0
- package/lib/aui/components/ToolRenderer.tsx +22 -0
- package/lib/aui/core.ts +137 -0
- package/lib/aui/demo.tsx +89 -0
- package/lib/aui/examples/ai-complete-demo.tsx +359 -0
- package/lib/aui/examples/ai-control-demo.tsx +356 -0
- package/lib/aui/examples/ai-control-tools.ts +308 -0
- package/lib/aui/examples/concise-api.tsx +153 -0
- package/lib/aui/examples/index.tsx +163 -0
- package/lib/aui/examples/quick-demo.tsx +91 -0
- package/lib/aui/examples/simple-demo.tsx +71 -0
- package/lib/aui/examples/simple-tools.tsx +160 -0
- package/lib/aui/examples/user-api.tsx +208 -0
- package/lib/aui/examples/user-requested.tsx +174 -0
- package/lib/aui/examples/weather-search-tools.tsx +119 -0
- package/lib/aui/examples.tsx +367 -0
- package/lib/aui/hooks/useAUITool.ts +142 -0
- package/lib/aui/hooks/useAUIToolEnhanced.ts +343 -0
- package/lib/aui/hooks/useAUITools.ts +195 -0
- package/lib/aui/index.ts +156 -0
- package/lib/aui/provider.tsx +45 -0
- package/lib/aui/server-control.ts +386 -0
- package/lib/aui/server-executor.ts +165 -0
- package/lib/aui/server.ts +167 -0
- package/lib/aui/tool-registry.ts +380 -0
- package/lib/aui/tools/advanced-examples.tsx +86 -0
- package/lib/aui/tools/ai-complete.ts +375 -0
- package/lib/aui/tools/api-tools.tsx +230 -0
- package/lib/aui/tools/data-tools.tsx +232 -0
- package/lib/aui/tools/dom-tools.tsx +202 -0
- package/lib/aui/tools/examples.ts +43 -0
- package/lib/aui/tools/file-tools.tsx +202 -0
- package/lib/aui/tools/form-tools.tsx +233 -0
- package/lib/aui/tools/index.ts +8 -0
- package/lib/aui/tools/navigation-tools.tsx +172 -0
- package/lib/aui/tools/notification-tools.ts +213 -0
- package/lib/aui/tools/state-tools.tsx +209 -0
- package/lib/aui/types.ts +47 -0
- package/lib/aui/vercel-ai.ts +100 -0
- package/package.json +51 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AUITool, AUIContext } from './core';
|
|
3
|
+
import { AIControlledTool, aiControlSystem } from './ai-control';
|
|
4
|
+
import { clientControlSystem } from './client-control';
|
|
5
|
+
import { globalToolRegistry, ToolMetadata } from './tool-registry';
|
|
6
|
+
import { ClientExecutor } from './client-executor';
|
|
7
|
+
|
|
8
|
+
export interface AIAssistantConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
systemPrompt?: string;
|
|
12
|
+
tools?: string[]; // Tool names this assistant can use
|
|
13
|
+
capabilities?: {
|
|
14
|
+
canExecuteServerTools?: boolean;
|
|
15
|
+
canExecuteClientTools?: boolean;
|
|
16
|
+
canAccessDatabase?: boolean;
|
|
17
|
+
canAccessFileSystem?: boolean;
|
|
18
|
+
canMakeNetworkRequests?: boolean;
|
|
19
|
+
};
|
|
20
|
+
maxTokens?: number;
|
|
21
|
+
temperature?: number;
|
|
22
|
+
model?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AIToolCall {
|
|
26
|
+
id: string;
|
|
27
|
+
tool: string;
|
|
28
|
+
input: any;
|
|
29
|
+
status: 'pending' | 'executing' | 'completed' | 'failed';
|
|
30
|
+
result?: any;
|
|
31
|
+
error?: Error;
|
|
32
|
+
startTime?: Date;
|
|
33
|
+
endTime?: Date;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AIConversationMessage {
|
|
37
|
+
role: 'user' | 'assistant' | 'system' | 'tool';
|
|
38
|
+
content: string;
|
|
39
|
+
toolCalls?: AIToolCall[];
|
|
40
|
+
timestamp: Date;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class AIAssistant {
|
|
44
|
+
private config: AIAssistantConfig;
|
|
45
|
+
private conversation: AIConversationMessage[] = [];
|
|
46
|
+
private availableTools: Map<string, AUITool | AIControlledTool> = new Map();
|
|
47
|
+
private executor: ClientExecutor;
|
|
48
|
+
private pendingToolCalls: Map<string, AIToolCall> = new Map();
|
|
49
|
+
|
|
50
|
+
constructor(config: AIAssistantConfig) {
|
|
51
|
+
this.config = config;
|
|
52
|
+
this.executor = new ClientExecutor();
|
|
53
|
+
this.initializeTools();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private initializeTools(): void {
|
|
57
|
+
if (this.config.tools) {
|
|
58
|
+
this.config.tools.forEach(toolName => {
|
|
59
|
+
const tool = globalToolRegistry.get(toolName) ||
|
|
60
|
+
aiControlSystem.get(toolName) ||
|
|
61
|
+
clientControlSystem.tools.get(toolName);
|
|
62
|
+
|
|
63
|
+
if (tool) {
|
|
64
|
+
this.availableTools.set(toolName, tool);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async processMessage(message: string, context?: Partial<AUIContext>): Promise<{
|
|
71
|
+
response: string;
|
|
72
|
+
toolCalls?: AIToolCall[];
|
|
73
|
+
}> {
|
|
74
|
+
// Add user message to conversation
|
|
75
|
+
this.conversation.push({
|
|
76
|
+
role: 'user',
|
|
77
|
+
content: message,
|
|
78
|
+
timestamp: new Date(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Parse message for tool calls
|
|
82
|
+
const toolCalls = await this.extractToolCalls(message);
|
|
83
|
+
|
|
84
|
+
if (toolCalls.length > 0) {
|
|
85
|
+
// Execute tool calls
|
|
86
|
+
const results = await this.executeToolCalls(toolCalls, context);
|
|
87
|
+
|
|
88
|
+
// Generate response based on tool results
|
|
89
|
+
const response = this.generateToolResponse(results);
|
|
90
|
+
|
|
91
|
+
// Add assistant response to conversation
|
|
92
|
+
this.conversation.push({
|
|
93
|
+
role: 'assistant',
|
|
94
|
+
content: response,
|
|
95
|
+
toolCalls: results,
|
|
96
|
+
timestamp: new Date(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return { response, toolCalls: results };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Generate regular response
|
|
103
|
+
const response = await this.generateResponse(message);
|
|
104
|
+
|
|
105
|
+
this.conversation.push({
|
|
106
|
+
role: 'assistant',
|
|
107
|
+
content: response,
|
|
108
|
+
timestamp: new Date(),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return { response };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async extractToolCalls(message: string): Promise<AIToolCall[]> {
|
|
115
|
+
const toolCalls: AIToolCall[] = [];
|
|
116
|
+
|
|
117
|
+
// Simple pattern matching for tool calls
|
|
118
|
+
// Format: @toolname{input}
|
|
119
|
+
const pattern = /@(\w+)\{([^}]+)\}/g;
|
|
120
|
+
let match;
|
|
121
|
+
|
|
122
|
+
while ((match = pattern.exec(message)) !== null) {
|
|
123
|
+
const [, toolName, inputStr] = match;
|
|
124
|
+
|
|
125
|
+
if (this.availableTools.has(toolName)) {
|
|
126
|
+
try {
|
|
127
|
+
const input = JSON.parse(inputStr);
|
|
128
|
+
toolCalls.push({
|
|
129
|
+
id: `tc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
130
|
+
tool: toolName,
|
|
131
|
+
input,
|
|
132
|
+
status: 'pending',
|
|
133
|
+
});
|
|
134
|
+
} catch {
|
|
135
|
+
// Invalid JSON, skip
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return toolCalls;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private async executeToolCalls(
|
|
144
|
+
toolCalls: AIToolCall[],
|
|
145
|
+
context?: Partial<AUIContext>
|
|
146
|
+
): Promise<AIToolCall[]> {
|
|
147
|
+
const results = await Promise.all(
|
|
148
|
+
toolCalls.map(async (call) => {
|
|
149
|
+
const tool = this.availableTools.get(call.tool);
|
|
150
|
+
if (!tool) {
|
|
151
|
+
return {
|
|
152
|
+
...call,
|
|
153
|
+
status: 'failed' as const,
|
|
154
|
+
error: new Error(`Tool ${call.tool} not available`),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
call.status = 'executing';
|
|
159
|
+
call.startTime = new Date();
|
|
160
|
+
this.pendingToolCalls.set(call.id, call);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const result = await tool.run(call.input, {
|
|
164
|
+
...context,
|
|
165
|
+
cache: context?.cache || new Map(),
|
|
166
|
+
fetch: context?.fetch || fetch,
|
|
167
|
+
isServer: context?.isServer ?? (typeof window === 'undefined'),
|
|
168
|
+
} as AUIContext);
|
|
169
|
+
|
|
170
|
+
call.status = 'completed';
|
|
171
|
+
call.result = result;
|
|
172
|
+
call.endTime = new Date();
|
|
173
|
+
} catch (error) {
|
|
174
|
+
call.status = 'failed';
|
|
175
|
+
call.error = error as Error;
|
|
176
|
+
call.endTime = new Date();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.pendingToolCalls.delete(call.id);
|
|
180
|
+
return call;
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return results;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private generateToolResponse(toolCalls: AIToolCall[]): string {
|
|
188
|
+
const successful = toolCalls.filter(tc => tc.status === 'completed');
|
|
189
|
+
const failed = toolCalls.filter(tc => tc.status === 'failed');
|
|
190
|
+
|
|
191
|
+
let response = '';
|
|
192
|
+
|
|
193
|
+
if (successful.length > 0) {
|
|
194
|
+
response += `Successfully executed ${successful.length} tool(s):\n`;
|
|
195
|
+
successful.forEach(tc => {
|
|
196
|
+
response += `- ${tc.tool}: ${JSON.stringify(tc.result)}\n`;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (failed.length > 0) {
|
|
201
|
+
response += `\nFailed to execute ${failed.length} tool(s):\n`;
|
|
202
|
+
failed.forEach(tc => {
|
|
203
|
+
response += `- ${tc.tool}: ${tc.error?.message}\n`;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return response || 'No tools were executed.';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private async generateResponse(message: string): Promise<string> {
|
|
211
|
+
// Simple response generation
|
|
212
|
+
// In a real implementation, this would call an LLM API
|
|
213
|
+
return `I understand you said: "${message}". How can I help you with that?`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
getAvailableTools(): ToolMetadata[] {
|
|
217
|
+
return Array.from(this.availableTools.keys()).map(name => {
|
|
218
|
+
const tool = this.availableTools.get(name)!;
|
|
219
|
+
return globalToolRegistry.getMetadata(name) || {
|
|
220
|
+
name: tool.name,
|
|
221
|
+
description: tool.description,
|
|
222
|
+
tags: tool.tags,
|
|
223
|
+
category: 'custom',
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
getConversation(): AIConversationMessage[] {
|
|
229
|
+
return [...this.conversation];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
clearConversation(): void {
|
|
233
|
+
this.conversation = [];
|
|
234
|
+
if (this.config.systemPrompt) {
|
|
235
|
+
this.conversation.push({
|
|
236
|
+
role: 'system',
|
|
237
|
+
content: this.config.systemPrompt,
|
|
238
|
+
timestamp: new Date(),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
addTool(toolName: string): boolean {
|
|
244
|
+
const tool = globalToolRegistry.get(toolName) ||
|
|
245
|
+
aiControlSystem.get(toolName) ||
|
|
246
|
+
clientControlSystem.tools.get(toolName);
|
|
247
|
+
|
|
248
|
+
if (tool) {
|
|
249
|
+
this.availableTools.set(toolName, tool);
|
|
250
|
+
if (!this.config.tools) {
|
|
251
|
+
this.config.tools = [];
|
|
252
|
+
}
|
|
253
|
+
this.config.tools.push(toolName);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
removeTool(toolName: string): boolean {
|
|
261
|
+
if (this.availableTools.has(toolName)) {
|
|
262
|
+
this.availableTools.delete(toolName);
|
|
263
|
+
if (this.config.tools) {
|
|
264
|
+
this.config.tools = this.config.tools.filter(t => t !== toolName);
|
|
265
|
+
}
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
getPendingToolCalls(): AIToolCall[] {
|
|
272
|
+
return Array.from(this.pendingToolCalls.values());
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
cancelToolCall(id: string): boolean {
|
|
276
|
+
if (this.pendingToolCalls.has(id)) {
|
|
277
|
+
const call = this.pendingToolCalls.get(id)!;
|
|
278
|
+
call.status = 'failed';
|
|
279
|
+
call.error = new Error('Cancelled by user');
|
|
280
|
+
call.endTime = new Date();
|
|
281
|
+
this.pendingToolCalls.delete(id);
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Helper function to create a tool-calling format for LLMs
|
|
289
|
+
export function formatToolForLLM(tool: AUITool | AIControlledTool): {
|
|
290
|
+
name: string;
|
|
291
|
+
description: string;
|
|
292
|
+
parameters: any;
|
|
293
|
+
} {
|
|
294
|
+
const metadata = globalToolRegistry.getMetadata(tool.name);
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
name: tool.name,
|
|
298
|
+
description: tool.description || metadata?.description || '',
|
|
299
|
+
parameters: tool.schema ? schemaToOpenAIFormat(tool.schema) : {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {},
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function schemaToOpenAIFormat(schema: z.ZodType<any>): any {
|
|
307
|
+
if (schema instanceof z.ZodObject) {
|
|
308
|
+
const shape = schema.shape;
|
|
309
|
+
const properties: Record<string, any> = {};
|
|
310
|
+
const required: string[] = [];
|
|
311
|
+
|
|
312
|
+
Object.entries(shape).forEach(([key, value]: [string, any]) => {
|
|
313
|
+
properties[key] = schemaToOpenAIFormat(value);
|
|
314
|
+
if (!value.isOptional()) {
|
|
315
|
+
required.push(key);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
type: 'object',
|
|
321
|
+
properties,
|
|
322
|
+
required: required.length > 0 ? required : undefined,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (schema instanceof z.ZodString) {
|
|
327
|
+
return { type: 'string' };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (schema instanceof z.ZodNumber) {
|
|
331
|
+
return { type: 'number' };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (schema instanceof z.ZodBoolean) {
|
|
335
|
+
return { type: 'boolean' };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (schema instanceof z.ZodArray) {
|
|
339
|
+
return {
|
|
340
|
+
type: 'array',
|
|
341
|
+
items: schemaToOpenAIFormat(schema.element),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (schema instanceof z.ZodEnum) {
|
|
346
|
+
return {
|
|
347
|
+
type: 'string',
|
|
348
|
+
enum: schema.options,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return { type: 'string' };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Pre-configured assistants
|
|
356
|
+
export const assistants = {
|
|
357
|
+
webDeveloper: new AIAssistant({
|
|
358
|
+
name: 'Web Developer Assistant',
|
|
359
|
+
description: 'Helps with web development tasks',
|
|
360
|
+
systemPrompt: 'You are a helpful web development assistant. You can manipulate the DOM, handle forms, manage storage, and control navigation.',
|
|
361
|
+
tools: [
|
|
362
|
+
'client-dom',
|
|
363
|
+
'client-events',
|
|
364
|
+
'client-forms',
|
|
365
|
+
'client-storage',
|
|
366
|
+
'client-navigation',
|
|
367
|
+
],
|
|
368
|
+
capabilities: {
|
|
369
|
+
canExecuteClientTools: true,
|
|
370
|
+
canMakeNetworkRequests: true,
|
|
371
|
+
},
|
|
372
|
+
}),
|
|
373
|
+
|
|
374
|
+
dataAnalyst: new AIAssistant({
|
|
375
|
+
name: 'Data Analyst Assistant',
|
|
376
|
+
description: 'Helps with data analysis and database operations',
|
|
377
|
+
systemPrompt: 'You are a data analysis assistant. You can query databases, process data, and generate insights.',
|
|
378
|
+
tools: [
|
|
379
|
+
'db-control',
|
|
380
|
+
'api-control',
|
|
381
|
+
],
|
|
382
|
+
capabilities: {
|
|
383
|
+
canExecuteServerTools: true,
|
|
384
|
+
canAccessDatabase: true,
|
|
385
|
+
canMakeNetworkRequests: true,
|
|
386
|
+
},
|
|
387
|
+
}),
|
|
388
|
+
|
|
389
|
+
uiDesigner: new AIAssistant({
|
|
390
|
+
name: 'UI Designer Assistant',
|
|
391
|
+
description: 'Helps with UI design and animations',
|
|
392
|
+
systemPrompt: 'You are a UI design assistant. You can create animations, control media, and manipulate visual elements.',
|
|
393
|
+
tools: [
|
|
394
|
+
'client-dom',
|
|
395
|
+
'client-animation',
|
|
396
|
+
'client-media',
|
|
397
|
+
'ui-control',
|
|
398
|
+
],
|
|
399
|
+
capabilities: {
|
|
400
|
+
canExecuteClientTools: true,
|
|
401
|
+
},
|
|
402
|
+
}),
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// Export a function to create custom assistants
|
|
406
|
+
export function createAIAssistant(config: AIAssistantConfig): AIAssistant {
|
|
407
|
+
return new AIAssistant(config);
|
|
408
|
+
}
|