@lantos1618/better-ui 0.2.3 → 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 +183 -367
  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 -26
  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
@@ -1,232 +0,0 @@
1
- import { z } from 'zod';
2
- import { createAITool } from '../ai-control';
3
-
4
- export const dataFetch = createAITool('data.fetch')
5
- .describe('Fetch and process data from various sources')
6
- .tag('data', 'fetch', 'async')
7
- .input(z.object({
8
- source: z.string(),
9
- transform: z.enum(['json', 'text', 'csv', 'xml']).optional(),
10
- cache: z.boolean().optional()
11
- }))
12
- .execute(async ({ input, ctx }) => {
13
- const cacheKey = `data_${input.source}`;
14
-
15
- if (input.cache && ctx?.cache.has(cacheKey)) {
16
- return ctx.cache.get(cacheKey);
17
- }
18
-
19
- const response = await ctx!.fetch(input.source);
20
- let data: any;
21
-
22
- switch (input.transform) {
23
- case 'json':
24
- data = await response.json();
25
- break;
26
- case 'csv':
27
- const text = await response.text();
28
- data = text.split('\n').map(line => line.split(','));
29
- break;
30
- case 'xml':
31
- data = await response.text();
32
- break;
33
- default:
34
- data = await response.text();
35
- }
36
-
37
- if (input.cache) {
38
- ctx?.cache.set(cacheKey, data);
39
- }
40
-
41
- return { data, source: input.source, cached: false };
42
- });
43
-
44
- export const dataTransform = createAITool('data.transform')
45
- .describe('Transform data using various operations')
46
- .tag('data', 'transform', 'utility')
47
- .input(z.object({
48
- data: z.any(),
49
- operation: z.enum(['filter', 'map', 'reduce', 'sort', 'group']),
50
- params: z.any()
51
- }))
52
- .execute(async ({ input }) => {
53
- let result: any;
54
-
55
- switch (input.operation) {
56
- case 'filter':
57
- if (Array.isArray(input.data)) {
58
- result = input.data.filter((item: any) => {
59
- return Object.entries(input.params).every(([key, value]) => item[key] === value);
60
- });
61
- }
62
- break;
63
-
64
- case 'map':
65
- if (Array.isArray(input.data)) {
66
- result = input.data.map((item: any) => {
67
- const mapped: any = {};
68
- Object.entries(input.params).forEach(([newKey, oldKey]) => {
69
- mapped[newKey] = item[oldKey as string];
70
- });
71
- return mapped;
72
- });
73
- }
74
- break;
75
-
76
- case 'sort':
77
- if (Array.isArray(input.data)) {
78
- const { field, order = 'asc' } = input.params;
79
- result = [...input.data].sort((a, b) => {
80
- const aVal = a[field];
81
- const bVal = b[field];
82
- return order === 'asc' ? (aVal > bVal ? 1 : -1) : (aVal < bVal ? 1 : -1);
83
- });
84
- }
85
- break;
86
-
87
- case 'group':
88
- if (Array.isArray(input.data)) {
89
- const { by } = input.params;
90
- result = input.data.reduce((acc: any, item: any) => {
91
- const key = item[by];
92
- if (!acc[key]) acc[key] = [];
93
- acc[key].push(item);
94
- return acc;
95
- }, {});
96
- }
97
- break;
98
-
99
- default:
100
- result = input.data;
101
- }
102
-
103
- return { result, operation: input.operation };
104
- });
105
-
106
- export const dataValidate = createAITool('data.validate')
107
- .describe('Validate data against a schema')
108
- .tag('data', 'validation', 'utility')
109
- .input(z.object({
110
- data: z.any(),
111
- schema: z.any(),
112
- strict: z.boolean().optional()
113
- }))
114
- .execute(async ({ input }) => {
115
- try {
116
- const validated = input.schema.parse(input.data);
117
- return { valid: true, data: validated, errors: [] };
118
- } catch (error: any) {
119
- return {
120
- valid: false,
121
- data: input.data,
122
- errors: error.errors || [{ message: error.message }]
123
- };
124
- }
125
- });
126
-
127
- export const dataAggregate = createAITool('data.aggregate')
128
- .describe('Perform aggregation operations on data')
129
- .tag('data', 'analytics', 'utility')
130
- .input(z.object({
131
- data: z.array(z.any()),
132
- operation: z.enum(['sum', 'avg', 'min', 'max', 'count', 'distinct']),
133
- field: z.string().optional()
134
- }))
135
- .execute(async ({ input }) => {
136
- let result: any;
137
-
138
- switch (input.operation) {
139
- case 'sum':
140
- result = input.data.reduce((sum, item) =>
141
- sum + (input.field ? item[input.field] : item), 0);
142
- break;
143
-
144
- case 'avg':
145
- const sum = input.data.reduce((s, item) =>
146
- s + (input.field ? item[input.field] : item), 0);
147
- result = sum / input.data.length;
148
- break;
149
-
150
- case 'min':
151
- result = Math.min(...input.data.map(item =>
152
- input.field ? item[input.field] : item));
153
- break;
154
-
155
- case 'max':
156
- result = Math.max(...input.data.map(item =>
157
- input.field ? item[input.field] : item));
158
- break;
159
-
160
- case 'count':
161
- result = input.data.length;
162
- break;
163
-
164
- case 'distinct':
165
- const values = input.data.map(item =>
166
- input.field ? item[input.field] : item);
167
- result = [...new Set(values)];
168
- break;
169
- }
170
-
171
- return { result, operation: input.operation, field: input.field };
172
- });
173
-
174
- export const dataPaginate = createAITool('data.paginate')
175
- .describe('Paginate data')
176
- .tag('data', 'pagination', 'utility')
177
- .input(z.object({
178
- data: z.array(z.any()),
179
- page: z.number().min(1),
180
- pageSize: z.number().min(1),
181
- includeMetadata: z.boolean().optional()
182
- }))
183
- .execute(async ({ input }) => {
184
- const start = (input.page - 1) * input.pageSize;
185
- const end = start + input.pageSize;
186
- const items = input.data.slice(start, end);
187
- const totalPages = Math.ceil(input.data.length / input.pageSize);
188
-
189
- const result: any = { items };
190
-
191
- if (input.includeMetadata) {
192
- result.metadata = {
193
- page: input.page,
194
- pageSize: input.pageSize,
195
- total: input.data.length,
196
- totalPages,
197
- hasNext: input.page < totalPages,
198
- hasPrev: input.page > 1
199
- };
200
- }
201
-
202
- return result;
203
- });
204
-
205
- export const dataSearch = createAITool('data.search')
206
- .describe('Search through data')
207
- .tag('data', 'search', 'utility')
208
- .input(z.object({
209
- data: z.array(z.any()),
210
- query: z.string(),
211
- fields: z.array(z.string()).optional(),
212
- fuzzy: z.boolean().optional()
213
- }))
214
- .execute(async ({ input }) => {
215
- const query = input.query.toLowerCase();
216
-
217
- const results = input.data.filter(item => {
218
- const fieldsToSearch = input.fields || Object.keys(item);
219
-
220
- return fieldsToSearch.some(field => {
221
- const value = String(item[field]).toLowerCase();
222
-
223
- if (input.fuzzy) {
224
- return value.includes(query) || query.includes(value);
225
- } else {
226
- return value === query;
227
- }
228
- });
229
- });
230
-
231
- return { results, count: results.length, query: input.query };
232
- });
@@ -1,202 +0,0 @@
1
- import React from 'react';
2
- import { z } from 'zod';
3
- import { createAITool } from '../ai-control';
4
-
5
- export const domClick = createAITool('dom.click')
6
- .describe('Click on a DOM element')
7
- .tag('dom', 'interaction', 'client')
8
- .input(z.object({
9
- selector: z.string().describe('CSS selector for the element'),
10
- wait: z.number().optional().describe('Wait time in ms before clicking')
11
- }))
12
- .clientExecute(async ({ input }) => {
13
- if (input.wait) {
14
- await new Promise(resolve => setTimeout(resolve, input.wait));
15
- }
16
- const element = document.querySelector(input.selector) as HTMLElement;
17
- if (!element) throw new Error(`Element not found: ${input.selector}`);
18
- element.click();
19
- return { success: true, selector: input.selector, timestamp: Date.now() };
20
- })
21
- .render(({ data }) => (
22
- <span className="text-green-600">✓ Clicked {data.selector}</span>
23
- ));
24
-
25
- export const domType = createAITool('dom.type')
26
- .describe('Type text into an input element')
27
- .tag('dom', 'input', 'client')
28
- .input(z.object({
29
- selector: z.string(),
30
- text: z.string(),
31
- clear: z.boolean().optional().describe('Clear field before typing'),
32
- delay: z.number().optional().describe('Delay between keystrokes in ms')
33
- }))
34
- .clientExecute(async ({ input }) => {
35
- const element = document.querySelector(input.selector) as HTMLInputElement;
36
- if (!element) throw new Error(`Element not found: ${input.selector}`);
37
-
38
- if (input.clear) {
39
- element.value = '';
40
- }
41
-
42
- if (input.delay) {
43
- for (const char of input.text) {
44
- element.value += char;
45
- element.dispatchEvent(new Event('input', { bubbles: true }));
46
- await new Promise(resolve => setTimeout(resolve, input.delay));
47
- }
48
- } else {
49
- element.value = input.clear ? input.text : element.value + input.text;
50
- element.dispatchEvent(new Event('input', { bubbles: true }));
51
- }
52
-
53
- element.dispatchEvent(new Event('change', { bubbles: true }));
54
- return { success: true, selector: input.selector, text: input.text };
55
- })
56
- .render(({ data }) => (
57
- <span className="text-blue-600">✓ Typed &quot;{data.text}&quot; into {data.selector}</span>
58
- ));
59
-
60
- export const domSelect = createAITool('dom.select')
61
- .describe('Select an option from a dropdown')
62
- .tag('dom', 'input', 'client')
63
- .input(z.object({
64
- selector: z.string(),
65
- value: z.string().optional(),
66
- index: z.number().optional(),
67
- text: z.string().optional()
68
- }))
69
- .clientExecute(async ({ input }) => {
70
- const element = document.querySelector(input.selector) as HTMLSelectElement;
71
- if (!element) throw new Error(`Element not found: ${input.selector}`);
72
-
73
- if (input.value !== undefined) {
74
- element.value = input.value;
75
- } else if (input.index !== undefined) {
76
- element.selectedIndex = input.index;
77
- } else if (input.text) {
78
- const option = Array.from(element.options).find(opt => opt.text === input.text);
79
- if (option) element.value = option.value;
80
- }
81
-
82
- element.dispatchEvent(new Event('change', { bubbles: true }));
83
- return { success: true, selector: input.selector, value: element.value };
84
- });
85
-
86
- export const domScroll = createAITool('dom.scroll')
87
- .describe('Scroll to an element or position')
88
- .tag('dom', 'navigation', 'client')
89
- .input(z.object({
90
- selector: z.string().optional(),
91
- x: z.number().optional(),
92
- y: z.number().optional(),
93
- behavior: z.enum(['auto', 'smooth']).optional()
94
- }))
95
- .clientExecute(async ({ input }) => {
96
- if (input.selector) {
97
- const element = document.querySelector(input.selector);
98
- if (!element) throw new Error(`Element not found: ${input.selector}`);
99
- element.scrollIntoView({ behavior: input.behavior || 'smooth' });
100
- } else {
101
- window.scrollTo({
102
- left: input.x || 0,
103
- top: input.y || 0,
104
- behavior: input.behavior || 'smooth'
105
- });
106
- }
107
- return { success: true, scrolled: true };
108
- });
109
-
110
- export const domWaitFor = createAITool('dom.waitFor')
111
- .describe('Wait for an element to appear')
112
- .tag('dom', 'utility', 'client')
113
- .input(z.object({
114
- selector: z.string(),
115
- timeout: z.number().optional().default(5000),
116
- interval: z.number().optional().default(100)
117
- }))
118
- .clientExecute(async ({ input }) => {
119
- const startTime = Date.now();
120
-
121
- const timeout = input.timeout || 5000;
122
- const interval = input.interval || 100;
123
-
124
- while (Date.now() - startTime < timeout) {
125
- const element = document.querySelector(input.selector);
126
- if (element) {
127
- return { success: true, found: true, selector: input.selector };
128
- }
129
- await new Promise(resolve => setTimeout(resolve, interval));
130
- }
131
-
132
- throw new Error(`Element ${input.selector} not found after ${timeout}ms`);
133
- });
134
-
135
- export const domGetText = createAITool('dom.getText')
136
- .describe('Get text content from elements')
137
- .tag('dom', 'read', 'client')
138
- .input(z.object({
139
- selector: z.string(),
140
- all: z.boolean().optional()
141
- }))
142
- .clientExecute(async ({ input }) => {
143
- if (input.all) {
144
- const elements = document.querySelectorAll(input.selector);
145
- const texts = Array.from(elements).map(el => el.textContent || '');
146
- return { texts, count: texts.length };
147
- } else {
148
- const element = document.querySelector(input.selector);
149
- if (!element) throw new Error(`Element not found: ${input.selector}`);
150
- return { text: element.textContent || '' };
151
- }
152
- });
153
-
154
- export const domGetAttribute = createAITool('dom.getAttribute')
155
- .describe('Get attribute value from an element')
156
- .tag('dom', 'read', 'client')
157
- .input(z.object({
158
- selector: z.string(),
159
- attribute: z.string()
160
- }))
161
- .clientExecute(async ({ input }) => {
162
- const element = document.querySelector(input.selector);
163
- if (!element) throw new Error(`Element not found: ${input.selector}`);
164
- return { value: element.getAttribute(input.attribute) };
165
- });
166
-
167
- export const domSetAttribute = createAITool('dom.setAttribute')
168
- .describe('Set attribute value on an element')
169
- .tag('dom', 'modify', 'client')
170
- .input(z.object({
171
- selector: z.string(),
172
- attribute: z.string(),
173
- value: z.string()
174
- }))
175
- .clientExecute(async ({ input }) => {
176
- const element = document.querySelector(input.selector);
177
- if (!element) throw new Error(`Element not found: ${input.selector}`);
178
- element.setAttribute(input.attribute, input.value);
179
- return { success: true, selector: input.selector, attribute: input.attribute };
180
- });
181
-
182
- export const domToggleClass = createAITool('dom.toggleClass')
183
- .describe('Toggle CSS class on elements')
184
- .tag('dom', 'style', 'client')
185
- .input(z.object({
186
- selector: z.string(),
187
- className: z.string(),
188
- action: z.enum(['add', 'remove', 'toggle']).optional()
189
- }))
190
- .clientExecute(async ({ input }) => {
191
- const elements = document.querySelectorAll(input.selector);
192
- elements.forEach(element => {
193
- if (input.action === 'add') {
194
- element.classList.add(input.className);
195
- } else if (input.action === 'remove') {
196
- element.classList.remove(input.className);
197
- } else {
198
- element.classList.toggle(input.className);
199
- }
200
- });
201
- return { success: true, affected: elements.length };
202
- });
@@ -1,43 +0,0 @@
1
- import aui, { z } from '../index';
2
-
3
- export const weatherTool = aui
4
- .tool('weather')
5
- .input(z.object({ city: z.string() }))
6
- .execute(async ({ input }) => ({
7
- city: input.city,
8
- temperature: Math.floor(Math.random() * 30 + 50),
9
- condition: ['Sunny', 'Cloudy', 'Rainy'][Math.floor(Math.random() * 3)]
10
- }));
11
-
12
- export const searchTool = aui
13
- .tool('search')
14
- .input(z.object({ query: z.string() }))
15
- .execute(async ({ input }) => {
16
- await new Promise(resolve => setTimeout(resolve, 500));
17
- return Array.from({ length: 5 }, (_, i) => ({
18
- id: i + 1,
19
- title: `Result ${i + 1} for "${input.query}"`,
20
- url: `https://example.com/${i + 1}`
21
- }));
22
- });
23
-
24
- export const calculatorTool = aui
25
- .tool('calculator')
26
- .input(z.object({ expression: z.string() }))
27
- .execute(async ({ input }) => {
28
- try {
29
- // Simple safe evaluation for demo
30
- const result = Function('"use strict"; return (' + input.expression + ')')();
31
- return { expression: input.expression, result };
32
- } catch {
33
- throw new Error('Invalid expression');
34
- }
35
- });
36
-
37
- export const dataFetcherTool = aui
38
- .tool('dataFetcher')
39
- .input(z.object({ url: z.string().url() }))
40
- .execute(async ({ input }) => {
41
- const response = await fetch(input.url);
42
- return response.json();
43
- });
@@ -1,202 +0,0 @@
1
- import { z } from 'zod';
2
- import { createAITool } from '../ai-control';
3
-
4
- export const fileUpload = createAITool('file.upload')
5
- .describe('Handle file uploads')
6
- .tag('file', 'upload', 'client')
7
- .input(z.object({
8
- accept: z.string().optional(),
9
- multiple: z.boolean().optional(),
10
- maxSize: z.number().optional()
11
- }))
12
- .clientExecute(async ({ input }) => {
13
- return new Promise((resolve, reject) => {
14
- const fileInput = document.createElement('input');
15
- fileInput.type = 'file';
16
- if (input.accept) fileInput.accept = input.accept;
17
- if (input.multiple) fileInput.multiple = true;
18
-
19
- fileInput.onchange = async (e) => {
20
- const files = Array.from((e.target as HTMLInputElement).files || []);
21
-
22
- if (input.maxSize) {
23
- const oversized = files.filter(file => file.size > input.maxSize!);
24
- if (oversized.length > 0) {
25
- reject(new Error(`Files exceed max size: ${oversized.map(f => f.name).join(', ')}`));
26
- return;
27
- }
28
- }
29
-
30
- const fileData = await Promise.all(files.map(async file => ({
31
- name: file.name,
32
- size: file.size,
33
- type: file.type,
34
- lastModified: file.lastModified,
35
- content: await file.text().catch(() => null)
36
- })));
37
-
38
- resolve({ files: fileData, count: fileData.length });
39
- };
40
-
41
- fileInput.click();
42
- });
43
- });
44
-
45
- export const fileDownload = createAITool('file.download')
46
- .describe('Download a file')
47
- .tag('file', 'download', 'client')
48
- .input(z.object({
49
- content: z.string(),
50
- filename: z.string(),
51
- type: z.string().optional().default('text/plain')
52
- }))
53
- .clientExecute(async ({ input }) => {
54
- const blob = new Blob([input.content], { type: input.type });
55
- const url = URL.createObjectURL(blob);
56
-
57
- const link = document.createElement('a');
58
- link.href = url;
59
- link.download = input.filename;
60
- document.body.appendChild(link);
61
- link.click();
62
- document.body.removeChild(link);
63
-
64
- URL.revokeObjectURL(url);
65
-
66
- return { downloaded: true, filename: input.filename };
67
- });
68
-
69
- export const fileRead = createAITool('file.read')
70
- .describe('Read file from input element')
71
- .tag('file', 'read', 'client')
72
- .input(z.object({
73
- selector: z.string(),
74
- readAs: z.enum(['text', 'dataURL', 'arrayBuffer']).optional().default('text')
75
- }))
76
- .clientExecute(async ({ input }) => {
77
- const fileInput = document.querySelector(input.selector) as HTMLInputElement;
78
- if (!fileInput) throw new Error(`Input not found: ${input.selector}`);
79
- if (!fileInput.files || fileInput.files.length === 0) {
80
- throw new Error('No files selected');
81
- }
82
-
83
- const file = fileInput.files[0];
84
- const reader = new FileReader();
85
-
86
- return new Promise((resolve, reject) => {
87
- reader.onload = (e) => {
88
- resolve({
89
- name: file.name,
90
- size: file.size,
91
- type: file.type,
92
- content: e.target?.result,
93
- lastModified: file.lastModified
94
- });
95
- };
96
-
97
- reader.onerror = () => reject(new Error('Failed to read file'));
98
-
99
- switch (input.readAs) {
100
- case 'dataURL':
101
- reader.readAsDataURL(file);
102
- break;
103
- case 'arrayBuffer':
104
- reader.readAsArrayBuffer(file);
105
- break;
106
- default:
107
- reader.readAsText(file);
108
- }
109
- });
110
- });
111
-
112
- export const fileDragDrop = createAITool('file.dragDrop')
113
- .describe('Setup drag and drop file handling')
114
- .tag('file', 'upload', 'client')
115
- .input(z.object({
116
- selector: z.string(),
117
- accept: z.string().optional()
118
- }))
119
- .clientExecute(async ({ input }) => {
120
- const dropZone = document.querySelector(input.selector) as HTMLElement;
121
- if (!dropZone) throw new Error(`Element not found: ${input.selector}`);
122
-
123
- const dragId = Math.random().toString(36).substring(7);
124
-
125
- return {
126
- dragId,
127
- onDrop: (callback: (files: File[]) => void) => {
128
- const handleDragOver = (e: DragEvent) => {
129
- e.preventDefault();
130
- dropZone.classList.add('dragging');
131
- };
132
-
133
- const handleDragLeave = () => {
134
- dropZone.classList.remove('dragging');
135
- };
136
-
137
- const handleDrop = (e: DragEvent) => {
138
- e.preventDefault();
139
- dropZone.classList.remove('dragging');
140
-
141
- const files = Array.from(e.dataTransfer?.files || []);
142
-
143
- if (input.accept) {
144
- const acceptedTypes = input.accept.split(',').map(t => t.trim());
145
- const validFiles = files.filter(file => {
146
- return acceptedTypes.some(type => {
147
- if (type.startsWith('.')) {
148
- return file.name.endsWith(type);
149
- }
150
- return file.type.match(type.replace('*', '.*'));
151
- });
152
- });
153
- callback(validFiles);
154
- } else {
155
- callback(files);
156
- }
157
- };
158
-
159
- dropZone.addEventListener('dragover', handleDragOver);
160
- dropZone.addEventListener('dragleave', handleDragLeave);
161
- dropZone.addEventListener('drop', handleDrop);
162
-
163
- return () => {
164
- dropZone.removeEventListener('dragover', handleDragOver);
165
- dropZone.removeEventListener('dragleave', handleDragLeave);
166
- dropZone.removeEventListener('drop', handleDrop);
167
- };
168
- }
169
- };
170
- });
171
-
172
- export const filePreview = createAITool('file.preview')
173
- .describe('Preview file content')
174
- .tag('file', 'preview', 'client')
175
- .input(z.object({
176
- file: z.any(),
177
- targetSelector: z.string()
178
- }))
179
- .clientExecute(async ({ input }) => {
180
- const target = document.querySelector(input.targetSelector) as HTMLElement;
181
- if (!target) throw new Error(`Element not found: ${input.targetSelector}`);
182
-
183
- const file = input.file;
184
-
185
- if (file.type.startsWith('image/')) {
186
- const img = document.createElement('img');
187
- img.src = URL.createObjectURL(new Blob([file.content]));
188
- img.style.maxWidth = '100%';
189
- target.innerHTML = '';
190
- target.appendChild(img);
191
- } else if (file.type.startsWith('text/') || file.type === 'application/json') {
192
- const pre = document.createElement('pre');
193
- pre.textContent = file.content;
194
- pre.style.cssText = 'white-space: pre-wrap; word-wrap: break-word;';
195
- target.innerHTML = '';
196
- target.appendChild(pre);
197
- } else {
198
- target.innerHTML = `<div>File: ${file.name} (${file.type})</div>`;
199
- }
200
-
201
- return { previewed: true, file: file.name };
202
- });