@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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/lib/aui/README.md +136 -0
  4. package/lib/aui/__tests__/aui-complete.test.ts +251 -0
  5. package/lib/aui/__tests__/aui-comprehensive.test.ts +376 -0
  6. package/lib/aui/__tests__/aui-concise.test.ts +278 -0
  7. package/lib/aui/__tests__/aui-integration.test.ts +309 -0
  8. package/lib/aui/__tests__/aui-simple.test.ts +116 -0
  9. package/lib/aui/__tests__/aui.test.ts +269 -0
  10. package/lib/aui/__tests__/concise-api.test.ts +165 -0
  11. package/lib/aui/__tests__/core.test.ts +265 -0
  12. package/lib/aui/__tests__/simple-api.test.ts +200 -0
  13. package/lib/aui/ai-assistant.ts +408 -0
  14. package/lib/aui/ai-control.ts +353 -0
  15. package/lib/aui/client/use-aui.ts +55 -0
  16. package/lib/aui/client-control.ts +551 -0
  17. package/lib/aui/client-executor.ts +417 -0
  18. package/lib/aui/components/ToolRenderer.tsx +22 -0
  19. package/lib/aui/core.ts +137 -0
  20. package/lib/aui/demo.tsx +89 -0
  21. package/lib/aui/examples/ai-complete-demo.tsx +359 -0
  22. package/lib/aui/examples/ai-control-demo.tsx +356 -0
  23. package/lib/aui/examples/ai-control-tools.ts +308 -0
  24. package/lib/aui/examples/concise-api.tsx +153 -0
  25. package/lib/aui/examples/index.tsx +163 -0
  26. package/lib/aui/examples/quick-demo.tsx +91 -0
  27. package/lib/aui/examples/simple-demo.tsx +71 -0
  28. package/lib/aui/examples/simple-tools.tsx +160 -0
  29. package/lib/aui/examples/user-api.tsx +208 -0
  30. package/lib/aui/examples/user-requested.tsx +174 -0
  31. package/lib/aui/examples/weather-search-tools.tsx +119 -0
  32. package/lib/aui/examples.tsx +367 -0
  33. package/lib/aui/hooks/useAUITool.ts +142 -0
  34. package/lib/aui/hooks/useAUIToolEnhanced.ts +343 -0
  35. package/lib/aui/hooks/useAUITools.ts +195 -0
  36. package/lib/aui/index.ts +156 -0
  37. package/lib/aui/provider.tsx +45 -0
  38. package/lib/aui/server-control.ts +386 -0
  39. package/lib/aui/server-executor.ts +165 -0
  40. package/lib/aui/server.ts +167 -0
  41. package/lib/aui/tool-registry.ts +380 -0
  42. package/lib/aui/tools/advanced-examples.tsx +86 -0
  43. package/lib/aui/tools/ai-complete.ts +375 -0
  44. package/lib/aui/tools/api-tools.tsx +230 -0
  45. package/lib/aui/tools/data-tools.tsx +232 -0
  46. package/lib/aui/tools/dom-tools.tsx +202 -0
  47. package/lib/aui/tools/examples.ts +43 -0
  48. package/lib/aui/tools/file-tools.tsx +202 -0
  49. package/lib/aui/tools/form-tools.tsx +233 -0
  50. package/lib/aui/tools/index.ts +8 -0
  51. package/lib/aui/tools/navigation-tools.tsx +172 -0
  52. package/lib/aui/tools/notification-tools.ts +213 -0
  53. package/lib/aui/tools/state-tools.tsx +209 -0
  54. package/lib/aui/types.ts +47 -0
  55. package/lib/aui/vercel-ai.ts +100 -0
  56. package/package.json +51 -0
@@ -0,0 +1,209 @@
1
+ import { z } from 'zod';
2
+ import { createAITool } from '../ai-control';
3
+
4
+ const globalState = new Map<string, any>();
5
+ const stateListeners = new Map<string, Set<(value: any) => void>>();
6
+
7
+ export const stateSet = createAITool('state.set')
8
+ .describe('Set a value in global state')
9
+ .tag('state', 'storage', 'client')
10
+ .input(z.object({
11
+ key: z.string(),
12
+ value: z.any(),
13
+ persist: z.boolean().optional()
14
+ }))
15
+ .clientExecute(async ({ input }) => {
16
+ globalState.set(input.key, input.value);
17
+
18
+ if (input.persist && typeof window !== 'undefined') {
19
+ localStorage.setItem(`aui_state_${input.key}`, JSON.stringify(input.value));
20
+ }
21
+
22
+ const listeners = stateListeners.get(input.key);
23
+ if (listeners) {
24
+ listeners.forEach(listener => listener(input.value));
25
+ }
26
+
27
+ return { key: input.key, value: input.value, persisted: input.persist };
28
+ });
29
+
30
+ export const stateGet = createAITool('state.get')
31
+ .describe('Get a value from global state')
32
+ .tag('state', 'storage', 'client')
33
+ .input(z.object({
34
+ key: z.string(),
35
+ fallback: z.any().optional()
36
+ }))
37
+ .clientExecute(async ({ input }) => {
38
+ let value = globalState.get(input.key);
39
+
40
+ if (value === undefined && typeof window !== 'undefined') {
41
+ const stored = localStorage.getItem(`aui_state_${input.key}`);
42
+ if (stored) {
43
+ value = JSON.parse(stored);
44
+ globalState.set(input.key, value);
45
+ }
46
+ }
47
+
48
+ return {
49
+ key: input.key,
50
+ value: value !== undefined ? value : input.fallback,
51
+ found: value !== undefined
52
+ };
53
+ });
54
+
55
+ export const stateDelete = createAITool('state.delete')
56
+ .describe('Delete a value from global state')
57
+ .tag('state', 'storage', 'client')
58
+ .input(z.object({
59
+ key: z.string()
60
+ }))
61
+ .clientExecute(async ({ input }) => {
62
+ const existed = globalState.has(input.key);
63
+ globalState.delete(input.key);
64
+
65
+ if (typeof window !== 'undefined') {
66
+ localStorage.removeItem(`aui_state_${input.key}`);
67
+ }
68
+
69
+ return { key: input.key, deleted: existed };
70
+ });
71
+
72
+ export const stateClear = createAITool('state.clear')
73
+ .describe('Clear all state')
74
+ .tag('state', 'storage', 'client')
75
+ .input(z.object({
76
+ includePersisted: z.boolean().optional()
77
+ }))
78
+ .clientExecute(async ({ input }) => {
79
+ const size = globalState.size;
80
+ globalState.clear();
81
+
82
+ if (input.includePersisted && typeof window !== 'undefined') {
83
+ const keys = Object.keys(localStorage);
84
+ keys.forEach(key => {
85
+ if (key.startsWith('aui_state_')) {
86
+ localStorage.removeItem(key);
87
+ }
88
+ });
89
+ }
90
+
91
+ return { cleared: size };
92
+ });
93
+
94
+ export const stateSubscribe = createAITool('state.subscribe')
95
+ .describe('Subscribe to state changes')
96
+ .tag('state', 'reactive', 'client')
97
+ .input(z.object({
98
+ key: z.string(),
99
+ immediate: z.boolean().optional()
100
+ }))
101
+ .clientExecute(async ({ input }) => {
102
+ if (!stateListeners.has(input.key)) {
103
+ stateListeners.set(input.key, new Set());
104
+ }
105
+
106
+ const subscriptionId = Math.random().toString(36).substring(7);
107
+
108
+ return {
109
+ subscriptionId,
110
+ key: input.key,
111
+ subscribe: (callback: (value: any) => void) => {
112
+ stateListeners.get(input.key)?.add(callback);
113
+
114
+ if (input.immediate) {
115
+ const currentValue = globalState.get(input.key);
116
+ if (currentValue !== undefined) {
117
+ callback(currentValue);
118
+ }
119
+ }
120
+
121
+ return () => {
122
+ stateListeners.get(input.key)?.delete(callback);
123
+ };
124
+ }
125
+ };
126
+ });
127
+
128
+ export const cookieSet = createAITool('cookie.set')
129
+ .describe('Set a cookie')
130
+ .tag('state', 'cookie', 'client')
131
+ .input(z.object({
132
+ name: z.string(),
133
+ value: z.string(),
134
+ expires: z.number().optional(),
135
+ path: z.string().optional(),
136
+ domain: z.string().optional(),
137
+ secure: z.boolean().optional(),
138
+ sameSite: z.enum(['strict', 'lax', 'none']).optional()
139
+ }))
140
+ .clientExecute(async ({ input }) => {
141
+ let cookie = `${input.name}=${encodeURIComponent(input.value)}`;
142
+
143
+ if (input.expires) {
144
+ const date = new Date();
145
+ date.setTime(date.getTime() + input.expires);
146
+ cookie += `; expires=${date.toUTCString()}`;
147
+ }
148
+
149
+ if (input.path) cookie += `; path=${input.path}`;
150
+ if (input.domain) cookie += `; domain=${input.domain}`;
151
+ if (input.secure) cookie += '; secure';
152
+ if (input.sameSite) cookie += `; samesite=${input.sameSite}`;
153
+
154
+ document.cookie = cookie;
155
+
156
+ return { name: input.name, value: input.value, set: true };
157
+ });
158
+
159
+ export const cookieGet = createAITool('cookie.get')
160
+ .describe('Get a cookie value')
161
+ .tag('state', 'cookie', 'client')
162
+ .input(z.object({
163
+ name: z.string()
164
+ }))
165
+ .clientExecute(async ({ input }) => {
166
+ const cookies = document.cookie.split(';');
167
+ for (const cookie of cookies) {
168
+ const [name, value] = cookie.trim().split('=');
169
+ if (name === input.name) {
170
+ return { name: input.name, value: decodeURIComponent(value), found: true };
171
+ }
172
+ }
173
+ return { name: input.name, value: null, found: false };
174
+ });
175
+
176
+ export const sessionSet = createAITool('session.set')
177
+ .describe('Set a value in session storage')
178
+ .tag('state', 'session', 'client')
179
+ .input(z.object({
180
+ key: z.string(),
181
+ value: z.any()
182
+ }))
183
+ .clientExecute(async ({ input }) => {
184
+ if (typeof window === 'undefined') {
185
+ throw new Error('Session storage not available');
186
+ }
187
+
188
+ sessionStorage.setItem(input.key, JSON.stringify(input.value));
189
+ return { key: input.key, value: input.value };
190
+ });
191
+
192
+ export const sessionGet = createAITool('session.get')
193
+ .describe('Get a value from session storage')
194
+ .tag('state', 'session', 'client')
195
+ .input(z.object({
196
+ key: z.string()
197
+ }))
198
+ .clientExecute(async ({ input }) => {
199
+ if (typeof window === 'undefined') {
200
+ throw new Error('Session storage not available');
201
+ }
202
+
203
+ const value = sessionStorage.getItem(input.key);
204
+ return {
205
+ key: input.key,
206
+ value: value ? JSON.parse(value) : null,
207
+ found: value !== null
208
+ };
209
+ });
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+ import { ReactElement } from 'react';
3
+
4
+ export interface AUIContext {
5
+ cache: Map<string, any>;
6
+ fetch: typeof fetch;
7
+ user?: any;
8
+ session?: any;
9
+ env?: Record<string, string>;
10
+ headers?: HeadersInit;
11
+ cookies?: Record<string, string>;
12
+ isServer?: boolean;
13
+ }
14
+
15
+ export type ExecuteFunction<TInput, TOutput> = (params: {
16
+ input: TInput;
17
+ ctx?: AUIContext;
18
+ }) => Promise<TOutput> | TOutput;
19
+
20
+ export type ClientExecuteFunction<TInput, TOutput> = (params: {
21
+ input: TInput;
22
+ ctx: AUIContext;
23
+ }) => Promise<TOutput> | TOutput;
24
+
25
+ export type RenderFunction<TInput, TOutput> = (props: {
26
+ data: TOutput;
27
+ input?: TInput;
28
+ loading?: boolean;
29
+ error?: Error;
30
+ }) => ReactElement;
31
+
32
+ export type MiddlewareFunction<TInput, TOutput> = (params: {
33
+ input: TInput;
34
+ ctx: AUIContext;
35
+ next: () => Promise<TOutput>;
36
+ }) => Promise<TOutput>;
37
+
38
+ export interface ToolConfig<TInput, TOutput> {
39
+ name: string;
40
+ inputSchema?: z.ZodType<TInput>;
41
+ executeHandler?: ExecuteFunction<TInput, TOutput>;
42
+ clientHandler?: ClientExecuteFunction<TInput, TOutput>;
43
+ renderHandler?: RenderFunction<TInput, TOutput>;
44
+ middleware?: Array<MiddlewareFunction<TInput, TOutput>>;
45
+ description?: string;
46
+ tags?: string[];
47
+ }
@@ -0,0 +1,100 @@
1
+ import { AUITool, AUIContext } from './core';
2
+ import aui from './index';
3
+ import { z } from 'zod';
4
+
5
+ export function convertToVercelTool<TInput = any, TOutput = any>(
6
+ auiTool: AUITool<TInput, TOutput>
7
+ ): any {
8
+ const config = auiTool.getConfig();
9
+
10
+ const vercelToolDefinition = {
11
+ description: config.description || `Execute ${config.name} tool`,
12
+ parameters: config.inputSchema || z.any(),
13
+ execute: async (input: any) => {
14
+ const ctx = aui.createContext();
15
+ return await auiTool.run(input as TInput, ctx);
16
+ }
17
+ };
18
+
19
+ return vercelToolDefinition;
20
+ }
21
+
22
+ export function createVercelTools(tools: AUITool[]): Record<string, any> {
23
+ const vercelTools: Record<string, any> = {};
24
+
25
+ tools.forEach(tool => {
26
+ vercelTools[tool.name] = convertToVercelTool(tool);
27
+ });
28
+
29
+ return vercelTools;
30
+ }
31
+
32
+ export function createAUIToolFromVercel<TInput = any, TOutput = any>(
33
+ name: string,
34
+ vercelToolDef: {
35
+ description?: string;
36
+ parameters: z.ZodType<TInput>;
37
+ execute: (input: TInput) => Promise<TOutput> | TOutput;
38
+ }
39
+ ): AUITool<TInput, TOutput> {
40
+ const tool = aui
41
+ .tool(name)
42
+ .input(vercelToolDef.parameters)
43
+ .execute(async ({ input }) => vercelToolDef.execute(input));
44
+
45
+ if (vercelToolDef.description) {
46
+ tool.describe(vercelToolDef.description);
47
+ }
48
+
49
+ return tool;
50
+ }
51
+
52
+ export async function executeToolWithStreaming<TInput, TOutput>(
53
+ tool: AUITool<TInput, TOutput>,
54
+ input: TInput,
55
+ onStream?: (chunk: any) => void
56
+ ): Promise<TOutput> {
57
+ const ctx = aui.createContext();
58
+
59
+ if (onStream) {
60
+ const originalFetch = ctx.fetch;
61
+ ctx.fetch = async (...args) => {
62
+ const response = await originalFetch(...args);
63
+
64
+ if (response.body && response.headers.get('content-type')?.includes('text/event-stream')) {
65
+ const reader = response.body.getReader();
66
+ const decoder = new TextDecoder();
67
+
68
+ while (true) {
69
+ const { done, value } = await reader.read();
70
+ if (done) break;
71
+
72
+ const chunk = decoder.decode(value);
73
+ onStream(chunk);
74
+ }
75
+ }
76
+
77
+ return response;
78
+ };
79
+ }
80
+
81
+ return await tool.run(input, ctx);
82
+ }
83
+
84
+ export const vercelAIIntegration = {
85
+ convertToVercelTool,
86
+ createVercelTools,
87
+ createAUIToolFromVercel,
88
+ executeToolWithStreaming,
89
+
90
+ getAllVercelTools() {
91
+ const tools = aui.getTools();
92
+ return createVercelTools(tools);
93
+ },
94
+
95
+ registerWithVercelAI(tools: AUITool[]) {
96
+ return createVercelTools(tools);
97
+ }
98
+ };
99
+
100
+ export default vercelAIIntegration;
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@lantos1618/better-ui",
3
+ "version": "0.1.0",
4
+ "description": "A modern UI framework for building AI-powered applications",
5
+ "main": "lib/aui/index.ts",
6
+ "types": "lib/aui/index.ts",
7
+ "files": [
8
+ "lib/**/*",
9
+ "README.md",
10
+ "LICENSE"
11
+ ],
12
+ "keywords": [
13
+ "ui",
14
+ "framework",
15
+ "ai",
16
+ "react",
17
+ "nextjs"
18
+ ],
19
+ "author": "Lyndon Leong",
20
+ "license": "MIT",
21
+ "scripts": {
22
+ "dev": "next dev",
23
+ "build": "next build",
24
+ "start": "next start",
25
+ "lint": "eslint .",
26
+ "test": "jest",
27
+ "type-check": "tsc --noEmit"
28
+ },
29
+ "dependencies": {
30
+ "@ai-sdk/openai": "^2.0.20",
31
+ "@sendgrid/mail": "^8.1.5",
32
+ "ai": "^5.0.23",
33
+ "next": "^15.5.0",
34
+ "react": "^18.3.0",
35
+ "react-dom": "^18.3.0",
36
+ "zod": "^3.22.0"
37
+ },
38
+ "devDependencies": {
39
+ "@eslint/eslintrc": "^3.3.1",
40
+ "@jest/globals": "^29.7.0",
41
+ "@types/jest": "^30.0.0",
42
+ "@types/node": "^20",
43
+ "@types/react": "^18",
44
+ "@types/react-dom": "^18",
45
+ "eslint": "^8",
46
+ "eslint-config-next": "14.2.0",
47
+ "jest": "^29.7.0",
48
+ "ts-jest": "^29.4.1",
49
+ "typescript": "^5"
50
+ }
51
+ }