@amaster.ai/components-templates 1.3.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 (40) hide show
  1. package/README.md +193 -0
  2. package/bin/amaster.js +2 -0
  3. package/components/ai-assistant/example.md +34 -0
  4. package/components/ai-assistant/package.json +34 -0
  5. package/components/ai-assistant/template/ai-assistant.tsx +88 -0
  6. package/components/ai-assistant/template/components/Markdown.tsx +70 -0
  7. package/components/ai-assistant/template/components/chat-assistant-message.tsx +190 -0
  8. package/components/ai-assistant/template/components/chat-banner.tsx +17 -0
  9. package/components/ai-assistant/template/components/chat-display-mode-switcher.tsx +70 -0
  10. package/components/ai-assistant/template/components/chat-floating-button.tsx +56 -0
  11. package/components/ai-assistant/template/components/chat-floating-card.tsx +43 -0
  12. package/components/ai-assistant/template/components/chat-header.tsx +66 -0
  13. package/components/ai-assistant/template/components/chat-input.tsx +143 -0
  14. package/components/ai-assistant/template/components/chat-messages.tsx +81 -0
  15. package/components/ai-assistant/template/components/chat-recommends.tsx +36 -0
  16. package/components/ai-assistant/template/components/chat-speech-button.tsx +43 -0
  17. package/components/ai-assistant/template/components/chat-user-message.tsx +26 -0
  18. package/components/ai-assistant/template/components/ui-renderer-lazy.tsx +307 -0
  19. package/components/ai-assistant/template/components/ui-renderer.tsx +34 -0
  20. package/components/ai-assistant/template/components/voice-input.tsx +43 -0
  21. package/components/ai-assistant/template/hooks/useAssistantStore.tsx +36 -0
  22. package/components/ai-assistant/template/hooks/useAutoScroll.ts +90 -0
  23. package/components/ai-assistant/template/hooks/useConversationProcessor.ts +649 -0
  24. package/components/ai-assistant/template/hooks/useDisplayMode.tsx +74 -0
  25. package/components/ai-assistant/template/hooks/useDraggable.ts +125 -0
  26. package/components/ai-assistant/template/hooks/usePosition.ts +206 -0
  27. package/components/ai-assistant/template/hooks/useSpeak.ts +50 -0
  28. package/components/ai-assistant/template/hooks/useVoiceInput.ts +172 -0
  29. package/components/ai-assistant/template/i18n.ts +114 -0
  30. package/components/ai-assistant/template/index.ts +6 -0
  31. package/components/ai-assistant/template/inline-ai-assistant.tsx +78 -0
  32. package/components/ai-assistant/template/mock/mock-data.ts +643 -0
  33. package/components/ai-assistant/template/types.ts +72 -0
  34. package/index.js +13 -0
  35. package/package.json +67 -0
  36. package/packages/cli/dist/index.d.ts +3 -0
  37. package/packages/cli/dist/index.d.ts.map +1 -0
  38. package/packages/cli/dist/index.js +335 -0
  39. package/packages/cli/dist/index.js.map +1 -0
  40. package/packages/cli/package.json +35 -0
@@ -0,0 +1,307 @@
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ Renderer,
4
+ StateProvider,
5
+ VisibilityProvider,
6
+ ValidationProvider,
7
+ ActionProvider,
8
+ defineRegistry
9
+ } from "@json-render/react";
10
+ import { defineCatalog } from "@json-render/core";
11
+ import { schema } from "@json-render/react/schema";
12
+ import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
13
+ import { shadcnComponents } from "@json-render/shadcn";
14
+ import { z } from "zod";
15
+ import {
16
+ BarChart,
17
+ Bar,
18
+ LineChart,
19
+ Line,
20
+ PieChart,
21
+ Pie,
22
+ Cell,
23
+ XAxis,
24
+ YAxis,
25
+ CartesianGrid,
26
+ Tooltip as RechartsTooltip,
27
+ Legend,
28
+ ResponsiveContainer,
29
+ AreaChart,
30
+ Area,
31
+ } from "recharts";
32
+ import { cn } from "@/lib/utils";
33
+
34
+ const CHART_COLORS = [
35
+ "#6366F1",
36
+ "#8B5CF6",
37
+ "#EC4899",
38
+ "#10B981",
39
+ "#F59E0B",
40
+ "#3B82F6",
41
+ ];
42
+
43
+ const catalog = defineCatalog(schema, {
44
+ components: {
45
+ Card: shadcnComponentDefinitions.Card,
46
+ Stack: shadcnComponentDefinitions.Stack,
47
+ Grid: shadcnComponentDefinitions.Grid,
48
+ Heading: shadcnComponentDefinitions.Heading,
49
+ Text: shadcnComponentDefinitions.Text,
50
+ Button: shadcnComponentDefinitions.Button,
51
+ Badge: shadcnComponentDefinitions.Badge,
52
+ Alert: shadcnComponentDefinitions.Alert,
53
+ Progress: shadcnComponentDefinitions.Progress,
54
+ Table: shadcnComponentDefinitions.Table,
55
+ Input: shadcnComponentDefinitions.Input,
56
+ Textarea: shadcnComponentDefinitions.Textarea,
57
+ Select: shadcnComponentDefinitions.Select,
58
+ Checkbox: shadcnComponentDefinitions.Checkbox,
59
+ Radio: shadcnComponentDefinitions.Radio,
60
+ Switch: shadcnComponentDefinitions.Switch,
61
+ Slider: shadcnComponentDefinitions.Slider,
62
+ Tabs: shadcnComponentDefinitions.Tabs,
63
+ Accordion: shadcnComponentDefinitions.Accordion,
64
+ Collapsible: shadcnComponentDefinitions.Collapsible,
65
+ Dialog: shadcnComponentDefinitions.Dialog,
66
+ Drawer: shadcnComponentDefinitions.Drawer,
67
+ Tooltip: shadcnComponentDefinitions.Tooltip,
68
+ Popover: shadcnComponentDefinitions.Popover,
69
+ DropdownMenu: shadcnComponentDefinitions.DropdownMenu,
70
+ Separator: shadcnComponentDefinitions.Separator,
71
+ Avatar: shadcnComponentDefinitions.Avatar,
72
+ Image: shadcnComponentDefinitions.Image,
73
+ Link: shadcnComponentDefinitions.Link,
74
+ Skeleton: shadcnComponentDefinitions.Skeleton,
75
+ Spinner: shadcnComponentDefinitions.Spinner,
76
+ Carousel: shadcnComponentDefinitions.Carousel,
77
+ Pagination: shadcnComponentDefinitions.Pagination,
78
+ Toggle: shadcnComponentDefinitions.Toggle,
79
+ ToggleGroup: shadcnComponentDefinitions.ToggleGroup,
80
+ ButtonGroup: shadcnComponentDefinitions.ButtonGroup,
81
+ BarChart: {
82
+ props: z.object({
83
+ data: z.array(z.record(z.any())),
84
+ xKey: z.string(),
85
+ yKey: z.string(),
86
+ height: z.number().optional(),
87
+ }),
88
+ },
89
+ LineChart: {
90
+ props: z.object({
91
+ data: z.array(z.record(z.any())),
92
+ xKey: z.string(),
93
+ yKey: z.string(),
94
+ height: z.number().optional(),
95
+ }),
96
+ },
97
+ PieChart: {
98
+ props: z.object({
99
+ data: z.array(z.record(z.any())),
100
+ nameKey: z.string(),
101
+ valueKey: z.string(),
102
+ height: z.number().optional(),
103
+ }),
104
+ },
105
+ AreaChart: {
106
+ props: z.object({
107
+ data: z.array(z.record(z.any())),
108
+ xKey: z.string(),
109
+ yKey: z.string(),
110
+ height: z.number().optional(),
111
+ }),
112
+ },
113
+ },
114
+ actions: {},
115
+ });
116
+
117
+ const { registry, handlers } = defineRegistry(catalog, {
118
+ components: {
119
+ Card: shadcnComponents.Card,
120
+ Stack: shadcnComponents.Stack,
121
+ Grid: shadcnComponents.Grid,
122
+ Heading: shadcnComponents.Heading,
123
+ Text: shadcnComponents.Text,
124
+ Button: shadcnComponents.Button,
125
+ Badge: shadcnComponents.Badge,
126
+ Alert: shadcnComponents.Alert,
127
+ Progress: shadcnComponents.Progress,
128
+ Table: shadcnComponents.Table,
129
+ Input: shadcnComponents.Input,
130
+ Textarea: shadcnComponents.Textarea,
131
+ Select: shadcnComponents.Select,
132
+ Checkbox: shadcnComponents.Checkbox,
133
+ Radio: shadcnComponents.Radio,
134
+ Switch: shadcnComponents.Switch,
135
+ Slider: shadcnComponents.Slider,
136
+ Tabs: shadcnComponents.Tabs,
137
+ Accordion: shadcnComponents.Accordion,
138
+ Collapsible: shadcnComponents.Collapsible,
139
+ Dialog: shadcnComponents.Dialog,
140
+ Drawer: shadcnComponents.Drawer,
141
+ Tooltip: shadcnComponents.Tooltip,
142
+ Popover: shadcnComponents.Popover,
143
+ DropdownMenu: shadcnComponents.DropdownMenu,
144
+ Separator: shadcnComponents.Separator,
145
+ Avatar: shadcnComponents.Avatar,
146
+ Image: shadcnComponents.Image,
147
+ Link: shadcnComponents.Link,
148
+ Skeleton: shadcnComponents.Skeleton,
149
+ Spinner: shadcnComponents.Spinner,
150
+ Carousel: shadcnComponents.Carousel,
151
+ Pagination: shadcnComponents.Pagination,
152
+ Toggle: shadcnComponents.Toggle,
153
+ ToggleGroup: shadcnComponents.ToggleGroup,
154
+ ButtonGroup: shadcnComponents.ButtonGroup,
155
+ BarChart: ({ props }) => {
156
+ const { data, xKey, yKey, height = 300 } = props;
157
+ return (
158
+ <div style={{ width: "100%", height }}>
159
+ <ResponsiveContainer width="100%" height="100%">
160
+ <BarChart data={data}>
161
+ <CartesianGrid strokeDasharray="3 3" />
162
+ <XAxis dataKey={xKey} />
163
+ <YAxis />
164
+ <RechartsTooltip />
165
+ <Bar dataKey={yKey} fill={CHART_COLORS[0]} />
166
+ </BarChart>
167
+ </ResponsiveContainer>
168
+ </div>
169
+ );
170
+ },
171
+ LineChart: ({ props }) => {
172
+ const { data, xKey, yKey, height = 300 } = props;
173
+ return (
174
+ <div style={{ width: "100%", height }}>
175
+ <ResponsiveContainer width="100%" height="100%">
176
+ <LineChart data={data}>
177
+ <CartesianGrid strokeDasharray="3 3" />
178
+ <XAxis dataKey={xKey} />
179
+ <YAxis />
180
+ <RechartsTooltip />
181
+ <Legend />
182
+ <Line
183
+ type="monotone"
184
+ dataKey={yKey}
185
+ stroke={CHART_COLORS[0]}
186
+ strokeWidth={2}
187
+ />
188
+ </LineChart>
189
+ </ResponsiveContainer>
190
+ </div>
191
+ );
192
+ },
193
+ PieChart: ({ props }) => {
194
+ const { data, nameKey, valueKey, height = 300 } = props;
195
+ return (
196
+ <div style={{ width: "100%", height }}>
197
+ <ResponsiveContainer width="100%" height="100%">
198
+ <PieChart>
199
+ <Pie
200
+ data={data}
201
+ cx="50%"
202
+ cy="50%"
203
+ labelLine={false}
204
+ label={({ name, percent }) =>
205
+ `${name} ${(percent * 100).toFixed(0)}%`
206
+ }
207
+ outerRadius={80}
208
+ fill="#8884d8"
209
+ dataKey={valueKey}
210
+ nameKey={nameKey}
211
+ >
212
+ {data.map((entry: any, index: number) => (
213
+ <Cell
214
+ key={`cell-${index}`}
215
+ fill={CHART_COLORS[index % CHART_COLORS.length]}
216
+ />
217
+ ))}
218
+ </Pie>
219
+ <RechartsTooltip />
220
+ </PieChart>
221
+ </ResponsiveContainer>
222
+ </div>
223
+ );
224
+ },
225
+ AreaChart: ({ props }) => {
226
+ const { data, xKey, yKey, height = 300 } = props;
227
+ return (
228
+ <div style={{ width: "100%", height }}>
229
+ <ResponsiveContainer width="100%" height="100%">
230
+ <AreaChart data={data}>
231
+ <CartesianGrid strokeDasharray="3 3" />
232
+ <XAxis dataKey={xKey} />
233
+ <YAxis />
234
+ <RechartsTooltip />
235
+ <Area
236
+ type="monotone"
237
+ dataKey={yKey}
238
+ stroke={CHART_COLORS[0]}
239
+ fill={CHART_COLORS[0]}
240
+ fillOpacity={0.3}
241
+ />
242
+ </AreaChart>
243
+ </ResponsiveContainer>
244
+ </div>
245
+ );
246
+ },
247
+ },
248
+ actions: {},
249
+ });
250
+
251
+ interface UIRendererLazyProps {
252
+ className?: string;
253
+ spec: {
254
+ root: string;
255
+ elements: Record<
256
+ string,
257
+ {
258
+ type: string;
259
+ props?: Record<string, unknown>;
260
+ children?: string[];
261
+ }
262
+ >;
263
+ };
264
+ }
265
+
266
+ const UIRendererLazy: React.FC<UIRendererLazyProps> = ({
267
+ spec,
268
+ className,
269
+ }): ReactNode => {
270
+ return (
271
+ <StateProvider>
272
+ <VisibilityProvider>
273
+ <ValidationProvider>
274
+ <ActionProvider
275
+ handlers={{
276
+ submitLogin: async (params) => {
277
+ console.log("Login submitted with params:", params);
278
+ },
279
+ confirmDelete: async (params: any) => {
280
+ const confirmed = window.confirm(
281
+ `Are you sure you want to delete item with ID: ${params?.id}?`,
282
+ );
283
+ if (confirmed) {
284
+ console.log(`Item with ID: ${params?.id} has been deleted.`);
285
+ } else {
286
+ console.log("Delete action cancelled.");
287
+ }
288
+ },
289
+ deleteCancel: async (params: any) => {
290
+ console.log("Delete action cancelled.");
291
+ },
292
+ viewDetails: async (params: any) => {
293
+ console.log(`Viewing details for item with ID: ${params?.id}`);
294
+ },
295
+ }}
296
+ >
297
+ <div className={cn("max-w-full w-full", className)}>
298
+ <Renderer spec={spec as any} registry={registry} />
299
+ </div>
300
+ </ActionProvider>
301
+ </ValidationProvider>
302
+ </VisibilityProvider>
303
+ </StateProvider>
304
+ );
305
+ };
306
+
307
+ export default UIRendererLazy;
@@ -0,0 +1,34 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+ import { cn } from "@/lib/utils";
3
+ import { lazy, Suspense } from "react";
4
+
5
+ const UIRendererLazy = lazy(() => import("./ui-renderer-lazy"));
6
+
7
+ interface UIRendererProps {
8
+ className?: string;
9
+ spec: {
10
+ root: string;
11
+ elements: Record<
12
+ string,
13
+ {
14
+ type: string;
15
+ props?: Record<string, unknown>;
16
+ children?: string[];
17
+ }
18
+ >;
19
+ };
20
+ }
21
+
22
+ export const UIRenderer: React.FC<UIRendererProps> = ({ spec, className }) => {
23
+ if (!spec || !spec.root || !spec.elements) {
24
+ return <div>Invalid spec</div>;
25
+ }
26
+
27
+ return (
28
+ <Suspense fallback={<Skeleton className={cn('w-full h-[120px] bg-gray-200', className)} />}>
29
+ <UIRendererLazy spec={spec} className={className} />
30
+ </Suspense>
31
+ );
32
+ };
33
+
34
+ export default UIRenderer;
@@ -0,0 +1,43 @@
1
+ import type React from "react";
2
+ import { Mic, StopCircle } from "lucide-react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { cn } from "@/lib/utils";
5
+ import { useVoiceInput } from "../hooks/useVoiceInput";
6
+
7
+ const VoiceInputButton: React.FC<{
8
+ onChange: (text: string) => void;
9
+ disabled?: boolean;
10
+ value?: string;
11
+ }> = ({ onChange, disabled, value }) => {
12
+ const {
13
+ start,
14
+ stop,
15
+ running,
16
+ stoppable,
17
+ statusText,
18
+ status,
19
+ disabled: finalDisabled,
20
+ } = useVoiceInput({
21
+ onChange,
22
+ disabled,
23
+ value,
24
+ });
25
+
26
+ return (
27
+ <Button
28
+ variant="ghost"
29
+ onClick={stoppable ? stop : status === "idle" ? start : undefined}
30
+ disabled={finalDisabled}
31
+ className={cn("h-8 w-8 rounded-lg cursor-pointer text-xs ", {
32
+ "bg-gradient-to-r from-blue-500 to-blue-800 hover:from-blue-400 hover:to-blue-600 text-white animate-pulse":
33
+ running,
34
+ "w-auto": statusText,
35
+ })}
36
+ >
37
+ {running ? <StopCircle /> : <Mic />}
38
+ {statusText}
39
+ </Button>
40
+ );
41
+ };
42
+
43
+ export default VoiceInputButton;
@@ -0,0 +1,36 @@
1
+ import { useState } from "react";
2
+ import { useConversationProcessor } from "./useConversationProcessor";
3
+ import { useAutoScroll } from "./useAutoScroll";
4
+
5
+ export const useAssistantStore = () => {
6
+ const [inputValue, setInputValue] = useState("");
7
+
8
+ const chatStreamHook = useConversationProcessor();
9
+ const { conversations, isLoading, sendMessage, resetConversation } =
10
+ chatStreamHook;
11
+
12
+ const autoScrollHook = useAutoScroll(conversations, isLoading);
13
+ const { scrollAreaRef, messagesEndRef, scrollToBottom } = autoScrollHook;
14
+
15
+ const handleSendMessage = async (value?: string) => {
16
+ const message = value !== undefined ? value : inputValue;
17
+ if (!message.trim() || isLoading) {
18
+ return;
19
+ }
20
+ setInputValue("");
21
+ scrollToBottom(true);
22
+ await sendMessage(message.trim());
23
+ };
24
+
25
+ return {
26
+ conversations,
27
+ isLoading,
28
+ inputValue,
29
+ setInputValue,
30
+ sendMessage: handleSendMessage,
31
+ reset: resetConversation,
32
+ scrollAreaRef,
33
+ messagesEndRef,
34
+ scrollToBottom
35
+ };
36
+ };
@@ -0,0 +1,90 @@
1
+ import { useEffect, useRef } from "react";
2
+ import type { Conversation } from "../types";
3
+
4
+ export const useAutoScroll = (
5
+ conversations: Conversation[],
6
+ isLoading: boolean,
7
+ ) => {
8
+ const scrollAreaRef = useRef<HTMLDivElement>(null);
9
+ const messagesEndRef = useRef<HTMLDivElement>(null);
10
+ const userScrolledRef = useRef(false);
11
+ const lastScrollTopRef = useRef(0);
12
+
13
+ const scrollToBottom = (force = false) => {
14
+ if (userScrolledRef.current && !force) return;
15
+ if (force) {
16
+ userScrolledRef.current = false;
17
+ }
18
+ if (messagesEndRef.current) {
19
+ messagesEndRef.current.scrollIntoView({
20
+ behavior: "smooth",
21
+ block: "end",
22
+ });
23
+ }
24
+ };
25
+
26
+ useEffect(() => {
27
+ scrollToBottom();
28
+ const timer = setTimeout(scrollToBottom, 100);
29
+
30
+ return () => clearTimeout(timer);
31
+ }, [conversations]);
32
+
33
+ useEffect(() => {
34
+ if (!isLoading) {
35
+ const timer = setTimeout(() => {
36
+ userScrolledRef.current = false;
37
+ }, 500);
38
+ return () => clearTimeout(timer);
39
+ }
40
+ return undefined;
41
+ }, [isLoading]);
42
+
43
+ useEffect(() => {
44
+ const scrollArea = scrollAreaRef.current;
45
+ if (!scrollArea) return;
46
+
47
+ const viewport = scrollArea.querySelector(
48
+ "[data-radix-scroll-area-viewport]",
49
+ ) as HTMLElement | null;
50
+ const scrollElement = viewport || scrollArea;
51
+
52
+ const handleScroll = () => {
53
+ const currentScrollTop = scrollElement.scrollTop;
54
+ const scrollHeight = scrollElement.scrollHeight;
55
+ const clientHeight = scrollElement.clientHeight;
56
+
57
+ const isAtBottom = scrollHeight - currentScrollTop - clientHeight < 50;
58
+
59
+ if (!isAtBottom && currentScrollTop < lastScrollTopRef.current) {
60
+ userScrolledRef.current = true;
61
+ }
62
+
63
+ if (isAtBottom) {
64
+ userScrolledRef.current = false;
65
+ }
66
+
67
+ lastScrollTopRef.current = currentScrollTop;
68
+ };
69
+
70
+ const handleWheel = () => {
71
+ if (isLoading) {
72
+ userScrolledRef.current = true;
73
+ }
74
+ };
75
+
76
+ scrollElement.addEventListener("scroll", handleScroll, { passive: true });
77
+ scrollElement.addEventListener("wheel", handleWheel, { passive: true });
78
+
79
+ return () => {
80
+ scrollElement.removeEventListener("scroll", handleScroll);
81
+ scrollElement.removeEventListener("wheel", handleWheel);
82
+ };
83
+ }, [isLoading]);
84
+
85
+ return {
86
+ scrollAreaRef,
87
+ messagesEndRef,
88
+ scrollToBottom
89
+ };
90
+ };