mycontext-cli 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/README.md +440 -0
- package/dist/cli/src/agents/implementations/CodeGenSubAgent.d.ts +43 -0
- package/dist/cli/src/agents/implementations/CodeGenSubAgent.d.ts.map +1 -0
- package/dist/cli/src/agents/implementations/CodeGenSubAgent.js +1440 -0
- package/dist/cli/src/agents/implementations/CodeGenSubAgent.js.map +1 -0
- package/dist/cli/src/agents/implementations/DocsSubAgent.d.ts +35 -0
- package/dist/cli/src/agents/implementations/DocsSubAgent.d.ts.map +1 -0
- package/dist/cli/src/agents/implementations/DocsSubAgent.js +351 -0
- package/dist/cli/src/agents/implementations/DocsSubAgent.js.map +1 -0
- package/dist/cli/src/agents/implementations/QASubAgent.d.ts +31 -0
- package/dist/cli/src/agents/implementations/QASubAgent.d.ts.map +1 -0
- package/dist/cli/src/agents/implementations/QASubAgent.js +190 -0
- package/dist/cli/src/agents/implementations/QASubAgent.js.map +1 -0
- package/dist/cli/src/agents/interfaces/SubAgent.d.ts +157 -0
- package/dist/cli/src/agents/interfaces/SubAgent.d.ts.map +1 -0
- package/dist/cli/src/agents/interfaces/SubAgent.js +7 -0
- package/dist/cli/src/agents/interfaces/SubAgent.js.map +1 -0
- package/dist/cli/src/agents/orchestrator/SubAgentOrchestrator.d.ts +59 -0
- package/dist/cli/src/agents/orchestrator/SubAgentOrchestrator.d.ts.map +1 -0
- package/dist/cli/src/agents/orchestrator/SubAgentOrchestrator.js +305 -0
- package/dist/cli/src/agents/orchestrator/SubAgentOrchestrator.js.map +1 -0
- package/dist/cli/src/agents/personalities/definitions.d.ts +34 -0
- package/dist/cli/src/agents/personalities/definitions.d.ts.map +1 -0
- package/dist/cli/src/agents/personalities/definitions.js +360 -0
- package/dist/cli/src/agents/personalities/definitions.js.map +1 -0
- package/dist/cli/src/cli.d.ts +3 -0
- package/dist/cli/src/cli.d.ts.map +1 -0
- package/dist/cli/src/cli.js +286 -0
- package/dist/cli/src/cli.js.map +1 -0
- package/dist/cli/src/commands/auth.d.ts +23 -0
- package/dist/cli/src/commands/auth.d.ts.map +1 -0
- package/dist/cli/src/commands/auth.js +212 -0
- package/dist/cli/src/commands/auth.js.map +1 -0
- package/dist/cli/src/commands/generate-components.d.ts +28 -0
- package/dist/cli/src/commands/generate-components.d.ts.map +1 -0
- package/dist/cli/src/commands/generate-components.js +680 -0
- package/dist/cli/src/commands/generate-components.js.map +1 -0
- package/dist/cli/src/commands/generate.d.ts +108 -0
- package/dist/cli/src/commands/generate.d.ts.map +1 -0
- package/dist/cli/src/commands/generate.js +1984 -0
- package/dist/cli/src/commands/generate.js.map +1 -0
- package/dist/cli/src/commands/init.d.ts +13 -0
- package/dist/cli/src/commands/init.d.ts.map +1 -0
- package/dist/cli/src/commands/init.js +91 -0
- package/dist/cli/src/commands/init.js.map +1 -0
- package/dist/cli/src/commands/list.d.ts +17 -0
- package/dist/cli/src/commands/list.d.ts.map +1 -0
- package/dist/cli/src/commands/list.js +209 -0
- package/dist/cli/src/commands/list.js.map +1 -0
- package/dist/cli/src/commands/preview.d.ts +23 -0
- package/dist/cli/src/commands/preview.d.ts.map +1 -0
- package/dist/cli/src/commands/preview.js +1200 -0
- package/dist/cli/src/commands/preview.js.map +1 -0
- package/dist/cli/src/commands/status.d.ts +21 -0
- package/dist/cli/src/commands/status.d.ts.map +1 -0
- package/dist/cli/src/commands/status.js +287 -0
- package/dist/cli/src/commands/status.js.map +1 -0
- package/dist/cli/src/commands/validate.d.ts +22 -0
- package/dist/cli/src/commands/validate.d.ts.map +1 -0
- package/dist/cli/src/commands/validate.js +259 -0
- package/dist/cli/src/commands/validate.js.map +1 -0
- package/dist/cli/src/types/index.d.ts +152 -0
- package/dist/cli/src/types/index.d.ts.map +1 -0
- package/dist/cli/src/types/index.js +3 -0
- package/dist/cli/src/types/index.js.map +1 -0
- package/dist/cli/src/utils/apiKeyManager.d.ts +137 -0
- package/dist/cli/src/utils/apiKeyManager.d.ts.map +1 -0
- package/dist/cli/src/utils/apiKeyManager.js +471 -0
- package/dist/cli/src/utils/apiKeyManager.js.map +1 -0
- package/dist/cli/src/utils/errorHandler.d.ts +105 -0
- package/dist/cli/src/utils/errorHandler.d.ts.map +1 -0
- package/dist/cli/src/utils/errorHandler.js +332 -0
- package/dist/cli/src/utils/errorHandler.js.map +1 -0
- package/dist/cli/src/utils/fileSystem.d.ts +58 -0
- package/dist/cli/src/utils/fileSystem.d.ts.map +1 -0
- package/dist/cli/src/utils/fileSystem.js +230 -0
- package/dist/cli/src/utils/fileSystem.js.map +1 -0
- package/dist/cli/src/utils/githubModels.d.ts +53 -0
- package/dist/cli/src/utils/githubModels.d.ts.map +1 -0
- package/dist/cli/src/utils/githubModels.js +239 -0
- package/dist/cli/src/utils/githubModels.js.map +1 -0
- package/dist/cli/src/utils/spinner.d.ts +28 -0
- package/dist/cli/src/utils/spinner.d.ts.map +1 -0
- package/dist/cli/src/utils/spinner.js +112 -0
- package/dist/cli/src/utils/spinner.js.map +1 -0
- package/dist/cli/src/utils/xaiClient.d.ts +59 -0
- package/dist/cli/src/utils/xaiClient.d.ts.map +1 -0
- package/dist/cli/src/utils/xaiClient.js +244 -0
- package/dist/cli/src/utils/xaiClient.js.map +1 -0
- package/dist/lib/analytics/usage-tracker.d.ts +125 -0
- package/dist/lib/analytics/usage-tracker.d.ts.map +1 -0
- package/dist/lib/analytics/usage-tracker.js +429 -0
- package/dist/lib/analytics/usage-tracker.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,1440 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CodeGenSubAgent Implementation
|
|
4
|
+
*
|
|
5
|
+
* Specialized sub-agent for generating production-ready React components and TypeScript code.
|
|
6
|
+
* Uses Claude Code for optimal code generation capabilities.
|
|
7
|
+
* Enhanced with shadcn/ui primitives and modern React patterns for Next.js 14+.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.CodeGenSubAgent = void 0;
|
|
44
|
+
const definitions_1 = require("../personalities/definitions");
|
|
45
|
+
const generate_components_1 = require("../../commands/generate-components");
|
|
46
|
+
// shadcn/ui component mapping for intelligent imports
|
|
47
|
+
const SHADCN_COMPONENTS = {
|
|
48
|
+
// Layout & Structure
|
|
49
|
+
layout: ["Card", "Separator", "AspectRatio"],
|
|
50
|
+
form: [
|
|
51
|
+
"Button",
|
|
52
|
+
"Input",
|
|
53
|
+
"Label",
|
|
54
|
+
"Form",
|
|
55
|
+
"Select",
|
|
56
|
+
"Checkbox",
|
|
57
|
+
"RadioGroup",
|
|
58
|
+
"Textarea",
|
|
59
|
+
"Switch",
|
|
60
|
+
],
|
|
61
|
+
navigation: ["NavigationMenu", "Breadcrumb", "Pagination", "Tabs"],
|
|
62
|
+
feedback: ["Alert", "AlertDialog", "Dialog", "Toast", "Progress", "Skeleton"],
|
|
63
|
+
data: ["Table", "DataTable", "Command", "Combobox"],
|
|
64
|
+
overlay: ["Popover", "HoverCard", "Tooltip", "Sheet", "Drawer"],
|
|
65
|
+
media: ["Avatar", "Badge", "Calendar", "Carousel"],
|
|
66
|
+
utility: [
|
|
67
|
+
"ScrollArea",
|
|
68
|
+
"Resizable",
|
|
69
|
+
"Collapsible",
|
|
70
|
+
"Accordion",
|
|
71
|
+
"ContextMenu",
|
|
72
|
+
"Menubar",
|
|
73
|
+
"DropdownMenu",
|
|
74
|
+
"Toggle",
|
|
75
|
+
"ToggleGroup",
|
|
76
|
+
"Slider",
|
|
77
|
+
"Sonner",
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
class CodeGenSubAgent {
|
|
81
|
+
constructor() {
|
|
82
|
+
this.name = "CodeGenSubAgent";
|
|
83
|
+
this.description = "Expert React/TypeScript developer specializing in production-ready Next.js 14+ components with shadcn/ui";
|
|
84
|
+
const personality = (0, definitions_1.getSubAgentPersonality)(this.name);
|
|
85
|
+
if (!personality) {
|
|
86
|
+
throw new Error(`Personality not found for ${this.name}`);
|
|
87
|
+
}
|
|
88
|
+
this.personality = personality.personality;
|
|
89
|
+
this.llmProvider = personality.llmProvider;
|
|
90
|
+
this.expertise = personality.expertise;
|
|
91
|
+
}
|
|
92
|
+
async run(input) {
|
|
93
|
+
const { component, group, options, context } = input;
|
|
94
|
+
// Use the existing GenerateComponentsCommand logic
|
|
95
|
+
const generateCommand = new generate_components_1.GenerateComponentsCommand();
|
|
96
|
+
// Handle both string and object inputs
|
|
97
|
+
let componentName;
|
|
98
|
+
if (typeof component === "string") {
|
|
99
|
+
componentName = component;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
componentName = component.name;
|
|
103
|
+
}
|
|
104
|
+
// Generate the component code using the enhanced method
|
|
105
|
+
const code = await this.generateProductionReadyComponent(component, group, options);
|
|
106
|
+
// Calculate metadata
|
|
107
|
+
const estimatedLines = code.split("\n").length;
|
|
108
|
+
const dependencies = this.extractDependencies(code);
|
|
109
|
+
const shadcnComponents = this.extractShadcnComponents(code);
|
|
110
|
+
return {
|
|
111
|
+
code,
|
|
112
|
+
metadata: {
|
|
113
|
+
componentName,
|
|
114
|
+
group,
|
|
115
|
+
dependencies,
|
|
116
|
+
estimatedLines,
|
|
117
|
+
shadcnComponents,
|
|
118
|
+
qualityScore: this.calculateQualityScore(code),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async validate(input) {
|
|
123
|
+
// Handle both structured component objects and simple string descriptions
|
|
124
|
+
if (typeof input.component === "string") {
|
|
125
|
+
// Simple string input - just need the string and group
|
|
126
|
+
return !!(input.component && input.group);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Structured component object - validate all required fields
|
|
130
|
+
if (!input.component || !input.component.name) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (!input.group) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
// For structured input, require type and userStories
|
|
137
|
+
if (!input.component.type || !input.component.userStories) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
async getStatus() {
|
|
144
|
+
return {
|
|
145
|
+
name: this.name,
|
|
146
|
+
status: "idle",
|
|
147
|
+
errorCount: 0,
|
|
148
|
+
successCount: 0,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async generateProductionReadyComponent(component, group, options) {
|
|
152
|
+
// Check if X.AI should be used for generation
|
|
153
|
+
if (options?.useXAI) {
|
|
154
|
+
return await this.generateWithXAI(component, group, options);
|
|
155
|
+
}
|
|
156
|
+
// Handle both string and object inputs
|
|
157
|
+
let name, description, type, userStories, actionFunctions, dependencies, tags;
|
|
158
|
+
if (typeof component === "string") {
|
|
159
|
+
// Simple string input - create a basic component structure
|
|
160
|
+
name = this.generateComponentName(component);
|
|
161
|
+
description = component;
|
|
162
|
+
type = "form"; // Default type
|
|
163
|
+
userStories = [
|
|
164
|
+
`As a user, I want to use ${name} to ${component.toLowerCase()}`,
|
|
165
|
+
];
|
|
166
|
+
actionFunctions = ["handleSubmit", "handleChange"];
|
|
167
|
+
dependencies = ["react", "tailwindcss"];
|
|
168
|
+
tags = ["ui", "component"];
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Structured component object
|
|
172
|
+
({
|
|
173
|
+
name,
|
|
174
|
+
description,
|
|
175
|
+
type,
|
|
176
|
+
userStories,
|
|
177
|
+
actionFunctions,
|
|
178
|
+
dependencies,
|
|
179
|
+
tags,
|
|
180
|
+
} = component);
|
|
181
|
+
}
|
|
182
|
+
// Determine shadcn/ui components to import based on type
|
|
183
|
+
const shadcnImports = this.getShadcnImportsForType(type);
|
|
184
|
+
const customImports = this.getCustomImportsForType(type);
|
|
185
|
+
const reactImports = this.getReactImportsForType(type);
|
|
186
|
+
return `"use client";
|
|
187
|
+
|
|
188
|
+
import React${reactImports.length > 0 ? `, { ${reactImports.join(", ")} }` : ""} from "react";
|
|
189
|
+
import { cn } from "@/lib/utils";
|
|
190
|
+
${shadcnImports
|
|
191
|
+
.map((imp) => `import { ${imp} } from "@/components/ui/${imp.toLowerCase()}";`)
|
|
192
|
+
.join("\n")}
|
|
193
|
+
${customImports.length > 0 ? customImports.join("\n") : ""}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* ${name} Component
|
|
197
|
+
*
|
|
198
|
+
* ${description}
|
|
199
|
+
*
|
|
200
|
+
* User Stories:
|
|
201
|
+
${userStories.map((story) => ` * - ${story}`).join("\n")}
|
|
202
|
+
*
|
|
203
|
+
* @component
|
|
204
|
+
* @example
|
|
205
|
+
* \`\`\`tsx
|
|
206
|
+
* <${name}
|
|
207
|
+
* className="custom-styles"
|
|
208
|
+
* ${this.generateExampleProps(type)}
|
|
209
|
+
* />
|
|
210
|
+
* \`\`\`
|
|
211
|
+
*/
|
|
212
|
+
|
|
213
|
+
${this.generateTypeDefinitions(type, name)}
|
|
214
|
+
|
|
215
|
+
export interface ${name}Props {
|
|
216
|
+
/** Additional CSS classes */
|
|
217
|
+
className?: string;
|
|
218
|
+
${this.generateDetailedPropsForType(type)}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* ${name} - ${description}
|
|
223
|
+
*
|
|
224
|
+
* @param props - Component props
|
|
225
|
+
* @returns Production-ready ${type} component
|
|
226
|
+
*/
|
|
227
|
+
export function ${name}({
|
|
228
|
+
className,
|
|
229
|
+
${this.generateEnhancedPropsDestructuring(type)}
|
|
230
|
+
...props
|
|
231
|
+
}: ${name}Props) {
|
|
232
|
+
${this.generateProductionComponentLogic(type, actionFunctions, name)}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
${this.generateAccessibleComponentJSX(type, name, shadcnImports)}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
${this.generateProductionActionFunctions(actionFunctions, name, type)}
|
|
240
|
+
|
|
241
|
+
export default ${name};
|
|
242
|
+
`;
|
|
243
|
+
}
|
|
244
|
+
getReactImportsForType(type) {
|
|
245
|
+
const imports = [];
|
|
246
|
+
switch (type) {
|
|
247
|
+
case "form":
|
|
248
|
+
imports.push("useState", "useCallback", "useMemo");
|
|
249
|
+
break;
|
|
250
|
+
case "layout":
|
|
251
|
+
imports.push("ReactNode", "FC");
|
|
252
|
+
break;
|
|
253
|
+
case "data":
|
|
254
|
+
imports.push("useState", "useEffect", "useMemo");
|
|
255
|
+
break;
|
|
256
|
+
case "navigation":
|
|
257
|
+
imports.push("useState", "useEffect");
|
|
258
|
+
break;
|
|
259
|
+
case "feedback":
|
|
260
|
+
imports.push("useState", "useEffect");
|
|
261
|
+
break;
|
|
262
|
+
default:
|
|
263
|
+
imports.push("useState");
|
|
264
|
+
}
|
|
265
|
+
return [...new Set(imports)];
|
|
266
|
+
}
|
|
267
|
+
getShadcnImportsForType(type) {
|
|
268
|
+
const imports = [];
|
|
269
|
+
switch (type) {
|
|
270
|
+
case "form":
|
|
271
|
+
imports.push("Button", "Input", "Label", "Form", "FormControl", "FormField", "FormItem", "FormLabel", "FormMessage", "Select", "SelectContent", "SelectItem", "SelectTrigger", "SelectValue", "Checkbox", "Alert", "AlertDescription");
|
|
272
|
+
break;
|
|
273
|
+
case "layout":
|
|
274
|
+
imports.push("Card", "CardContent", "CardDescription", "CardHeader", "CardTitle", "Separator");
|
|
275
|
+
break;
|
|
276
|
+
case "card":
|
|
277
|
+
imports.push("Card", "CardContent", "CardDescription", "CardHeader", "CardTitle", "CardFooter", "Button");
|
|
278
|
+
break;
|
|
279
|
+
case "button":
|
|
280
|
+
imports.push("Button");
|
|
281
|
+
break;
|
|
282
|
+
case "navigation":
|
|
283
|
+
imports.push("NavigationMenu", "NavigationMenuContent", "NavigationMenuItem", "NavigationMenuLink", "NavigationMenuList", "NavigationMenuTrigger", "Breadcrumb", "BreadcrumbItem", "BreadcrumbLink", "BreadcrumbList", "BreadcrumbSeparator", "Tabs", "TabsContent", "TabsList", "TabsTrigger");
|
|
284
|
+
break;
|
|
285
|
+
case "feedback":
|
|
286
|
+
imports.push("Alert", "AlertDescription", "AlertTitle", "Dialog", "DialogContent", "DialogDescription", "DialogHeader", "DialogTitle", "DialogTrigger", "Toast", "Progress");
|
|
287
|
+
break;
|
|
288
|
+
case "data":
|
|
289
|
+
imports.push("Table", "TableBody", "TableCell", "TableHead", "TableHeader", "TableRow", "Command", "CommandEmpty", "CommandGroup", "CommandInput", "CommandItem", "CommandList");
|
|
290
|
+
break;
|
|
291
|
+
case "overlay":
|
|
292
|
+
imports.push("Popover", "PopoverContent", "PopoverTrigger", "Tooltip", "TooltipContent", "TooltipProvider", "TooltipTrigger", "Sheet", "SheetContent", "SheetDescription", "SheetHeader", "SheetTitle", "SheetTrigger");
|
|
293
|
+
break;
|
|
294
|
+
case "media":
|
|
295
|
+
imports.push("Avatar", "AvatarFallback", "AvatarImage", "Badge");
|
|
296
|
+
break;
|
|
297
|
+
default:
|
|
298
|
+
imports.push("Card", "CardContent", "CardHeader", "CardTitle", "Button");
|
|
299
|
+
}
|
|
300
|
+
return [...new Set(imports)];
|
|
301
|
+
}
|
|
302
|
+
getCustomImportsForType(type) {
|
|
303
|
+
const imports = [];
|
|
304
|
+
if (type === "form") {
|
|
305
|
+
imports.push('import { useForm } from "react-hook-form";');
|
|
306
|
+
imports.push('import { zodResolver } from "@hookform/resolvers/zod";');
|
|
307
|
+
imports.push('import * as z from "zod";');
|
|
308
|
+
}
|
|
309
|
+
if (type === "navigation") {
|
|
310
|
+
imports.push('import Link from "next/link";');
|
|
311
|
+
imports.push('import { usePathname } from "next/navigation";');
|
|
312
|
+
}
|
|
313
|
+
if (type === "data") {
|
|
314
|
+
imports.push('import { useMemo } from "react";');
|
|
315
|
+
}
|
|
316
|
+
return imports;
|
|
317
|
+
}
|
|
318
|
+
generateTypeDefinitions(type, name) {
|
|
319
|
+
switch (type) {
|
|
320
|
+
case "form":
|
|
321
|
+
return `
|
|
322
|
+
/** Form data schema */
|
|
323
|
+
export const ${name.toLowerCase()}Schema = z.object({
|
|
324
|
+
email: z.string().email("Please enter a valid email address"),
|
|
325
|
+
password: z.string().min(8, "Password must be at least 8 characters long"),
|
|
326
|
+
confirmPassword: z.string().optional(),
|
|
327
|
+
}).refine((data) => {
|
|
328
|
+
if (data.confirmPassword !== undefined) {
|
|
329
|
+
return data.password === data.confirmPassword;
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
}, {
|
|
333
|
+
message: "Passwords don't match",
|
|
334
|
+
path: ["confirmPassword"],
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
/** Inferred form data type */
|
|
338
|
+
export type ${name}FormData = z.infer<typeof ${name.toLowerCase()}Schema>;
|
|
339
|
+
|
|
340
|
+
/** Form submission result */
|
|
341
|
+
export interface ${name}SubmissionResult {
|
|
342
|
+
success: boolean;
|
|
343
|
+
message?: string;
|
|
344
|
+
errors?: Record<string, string>;
|
|
345
|
+
}`;
|
|
346
|
+
case "navigation":
|
|
347
|
+
return `
|
|
348
|
+
/** Navigation item interface */
|
|
349
|
+
export interface NavigationItem {
|
|
350
|
+
/** Display label */
|
|
351
|
+
label: string;
|
|
352
|
+
/** Route path */
|
|
353
|
+
href: string;
|
|
354
|
+
/** Optional icon component */
|
|
355
|
+
icon?: React.ComponentType<{ className?: string }>;
|
|
356
|
+
/** Whether the item is disabled */
|
|
357
|
+
disabled?: boolean;
|
|
358
|
+
/** Nested items for dropdowns */
|
|
359
|
+
children?: NavigationItem[];
|
|
360
|
+
}`;
|
|
361
|
+
case "data":
|
|
362
|
+
return `
|
|
363
|
+
/** Generic data row interface */
|
|
364
|
+
export interface DataRow {
|
|
365
|
+
id: string | number;
|
|
366
|
+
[key: string]: any;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Column definition */
|
|
370
|
+
export interface ColumnDef<T = DataRow> {
|
|
371
|
+
/** Column key */
|
|
372
|
+
key: keyof T;
|
|
373
|
+
/** Display label */
|
|
374
|
+
label: string;
|
|
375
|
+
/** Custom render function */
|
|
376
|
+
render?: (value: any, row: T) => React.ReactNode;
|
|
377
|
+
/** Whether column is sortable */
|
|
378
|
+
sortable?: boolean;
|
|
379
|
+
/** Column width */
|
|
380
|
+
width?: string;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Sort configuration */
|
|
384
|
+
export interface SortConfig {
|
|
385
|
+
key: keyof DataRow;
|
|
386
|
+
direction: "asc" | "desc";
|
|
387
|
+
}`;
|
|
388
|
+
default:
|
|
389
|
+
return "";
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
generateDetailedPropsForType(type) {
|
|
393
|
+
switch (type) {
|
|
394
|
+
case "form":
|
|
395
|
+
return `
|
|
396
|
+
/** Form submission handler */
|
|
397
|
+
onSubmit?: (data: ${type}FormData) => Promise<${type}SubmissionResult> | ${type}SubmissionResult;
|
|
398
|
+
/** Loading state */
|
|
399
|
+
loading?: boolean;
|
|
400
|
+
/** Error message */
|
|
401
|
+
error?: string;
|
|
402
|
+
/** Form variant */
|
|
403
|
+
variant?: "default" | "login" | "signup" | "contact";
|
|
404
|
+
/** Initial form values */
|
|
405
|
+
defaultValues?: Partial<${type}FormData>;
|
|
406
|
+
/** Whether to show password confirmation field */
|
|
407
|
+
showConfirmPassword?: boolean;
|
|
408
|
+
/** Custom validation rules */
|
|
409
|
+
validationRules?: Partial<Record<keyof ${type}FormData, z.ZodSchema>>;`;
|
|
410
|
+
case "layout":
|
|
411
|
+
return `
|
|
412
|
+
/** Main content */
|
|
413
|
+
children: React.ReactNode;
|
|
414
|
+
/** Sidebar content */
|
|
415
|
+
sidebar?: React.ReactNode;
|
|
416
|
+
/** Header content */
|
|
417
|
+
header?: React.ReactNode;
|
|
418
|
+
/** Footer content */
|
|
419
|
+
footer?: React.ReactNode;
|
|
420
|
+
/** Layout variant */
|
|
421
|
+
variant?: "default" | "sidebar" | "centered" | "fullscreen";
|
|
422
|
+
/** Whether sidebar is collapsible */
|
|
423
|
+
collapsibleSidebar?: boolean;
|
|
424
|
+
/** Sidebar initial state */
|
|
425
|
+
sidebarDefaultOpen?: boolean;`;
|
|
426
|
+
case "card":
|
|
427
|
+
return `
|
|
428
|
+
/** Card title */
|
|
429
|
+
title?: string;
|
|
430
|
+
/** Card description */
|
|
431
|
+
description?: string;
|
|
432
|
+
/** Card content */
|
|
433
|
+
content?: React.ReactNode;
|
|
434
|
+
/** Card footer actions */
|
|
435
|
+
actions?: React.ReactNode;
|
|
436
|
+
/** Card variant */
|
|
437
|
+
variant?: "default" | "elevated" | "outlined" | "ghost";
|
|
438
|
+
/** Whether card is interactive */
|
|
439
|
+
interactive?: boolean;
|
|
440
|
+
/** Click handler for interactive cards */
|
|
441
|
+
onClick?: () => void;
|
|
442
|
+
/** Card size */
|
|
443
|
+
size?: "sm" | "default" | "lg";`;
|
|
444
|
+
case "button":
|
|
445
|
+
return `
|
|
446
|
+
/** Button variant */
|
|
447
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
|
448
|
+
/** Button size */
|
|
449
|
+
size?: "default" | "sm" | "lg" | "icon";
|
|
450
|
+
/** Whether button is disabled */
|
|
451
|
+
disabled?: boolean;
|
|
452
|
+
/** Loading state */
|
|
453
|
+
loading?: boolean;
|
|
454
|
+
/** Click handler */
|
|
455
|
+
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
456
|
+
/** Button content */
|
|
457
|
+
children: React.ReactNode;
|
|
458
|
+
/** Button type */
|
|
459
|
+
type?: "button" | "submit" | "reset";
|
|
460
|
+
/** ARIA label for accessibility */
|
|
461
|
+
"aria-label"?: string;`;
|
|
462
|
+
case "navigation":
|
|
463
|
+
return `
|
|
464
|
+
/** Navigation items */
|
|
465
|
+
items: NavigationItem[];
|
|
466
|
+
/** Currently active item path */
|
|
467
|
+
activeItem?: string;
|
|
468
|
+
/** Navigation orientation */
|
|
469
|
+
orientation?: "horizontal" | "vertical";
|
|
470
|
+
/** Whether to show icons */
|
|
471
|
+
showIcons?: boolean;
|
|
472
|
+
/** Custom item renderer */
|
|
473
|
+
renderItem?: (item: NavigationItem, isActive: boolean) => React.ReactNode;
|
|
474
|
+
/** Navigation variant */
|
|
475
|
+
variant?: "default" | "pills" | "underline";`;
|
|
476
|
+
case "feedback":
|
|
477
|
+
return `
|
|
478
|
+
/** Alert title */
|
|
479
|
+
title?: string;
|
|
480
|
+
/** Alert message */
|
|
481
|
+
message?: string;
|
|
482
|
+
/** Alert variant */
|
|
483
|
+
variant?: "default" | "destructive" | "success" | "warning";
|
|
484
|
+
/** Close handler */
|
|
485
|
+
onClose?: () => void;
|
|
486
|
+
/** Whether alert is dismissible */
|
|
487
|
+
dismissible?: boolean;
|
|
488
|
+
/** Auto-dismiss timeout in milliseconds */
|
|
489
|
+
autoDissmissAfter?: number;
|
|
490
|
+
/** Custom icon */
|
|
491
|
+
icon?: React.ReactNode;`;
|
|
492
|
+
case "data":
|
|
493
|
+
return `
|
|
494
|
+
/** Table data */
|
|
495
|
+
data: DataRow[];
|
|
496
|
+
/** Column definitions */
|
|
497
|
+
columns: ColumnDef[];
|
|
498
|
+
/** Whether table is loading */
|
|
499
|
+
loading?: boolean;
|
|
500
|
+
/** Whether to show pagination */
|
|
501
|
+
pagination?: boolean;
|
|
502
|
+
/** Items per page */
|
|
503
|
+
pageSize?: number;
|
|
504
|
+
/** Sort configuration */
|
|
505
|
+
sortable?: boolean;
|
|
506
|
+
/** Initial sort */
|
|
507
|
+
defaultSort?: SortConfig;
|
|
508
|
+
/** Row selection handler */
|
|
509
|
+
onRowSelect?: (selectedRows: DataRow[]) => void;
|
|
510
|
+
/** Empty state message */
|
|
511
|
+
emptyMessage?: string;
|
|
512
|
+
/** Custom row renderer */
|
|
513
|
+
renderRow?: (row: DataRow, index: number) => React.ReactNode;`;
|
|
514
|
+
default:
|
|
515
|
+
return `
|
|
516
|
+
/** Component children */
|
|
517
|
+
children?: React.ReactNode;
|
|
518
|
+
/** Component variant */
|
|
519
|
+
variant?: "default";`;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
generateExampleProps(type) {
|
|
523
|
+
switch (type) {
|
|
524
|
+
case "form":
|
|
525
|
+
return `onSubmit={handleSubmit}\n * loading={false}\n * variant="default"`;
|
|
526
|
+
case "layout":
|
|
527
|
+
return `variant="sidebar"\n * header={<Header />}\n * sidebar={<Sidebar />}`;
|
|
528
|
+
case "card":
|
|
529
|
+
return `title="Card Title"\n * variant="elevated"\n * interactive`;
|
|
530
|
+
case "button":
|
|
531
|
+
return `variant="default"\n * size="lg"\n * onClick={handleClick}`;
|
|
532
|
+
case "navigation":
|
|
533
|
+
return `items={navigationItems}\n * variant="pills"\n * orientation="horizontal"`;
|
|
534
|
+
case "feedback":
|
|
535
|
+
return `variant="success"\n * title="Success"\n * dismissible`;
|
|
536
|
+
case "data":
|
|
537
|
+
return `data={tableData}\n * columns={columns}\n * pagination`;
|
|
538
|
+
default:
|
|
539
|
+
return `variant="default"`;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
generateEnhancedPropsDestructuring(type) {
|
|
543
|
+
switch (type) {
|
|
544
|
+
case "form":
|
|
545
|
+
return `
|
|
546
|
+
onSubmit,
|
|
547
|
+
loading = false,
|
|
548
|
+
error,
|
|
549
|
+
variant = "default",
|
|
550
|
+
defaultValues,
|
|
551
|
+
showConfirmPassword = false,
|
|
552
|
+
validationRules,`;
|
|
553
|
+
case "layout":
|
|
554
|
+
return `
|
|
555
|
+
children,
|
|
556
|
+
sidebar,
|
|
557
|
+
header,
|
|
558
|
+
footer,
|
|
559
|
+
variant = "default",
|
|
560
|
+
collapsibleSidebar = false,
|
|
561
|
+
sidebarDefaultOpen = true,`;
|
|
562
|
+
case "card":
|
|
563
|
+
return `
|
|
564
|
+
title,
|
|
565
|
+
description,
|
|
566
|
+
content,
|
|
567
|
+
actions,
|
|
568
|
+
variant = "default",
|
|
569
|
+
interactive = false,
|
|
570
|
+
onClick,
|
|
571
|
+
size = "default",`;
|
|
572
|
+
case "button":
|
|
573
|
+
return `
|
|
574
|
+
variant = "default",
|
|
575
|
+
size = "default",
|
|
576
|
+
disabled = false,
|
|
577
|
+
loading = false,
|
|
578
|
+
onClick,
|
|
579
|
+
children,
|
|
580
|
+
type = "button",
|
|
581
|
+
"aria-label": ariaLabel,`;
|
|
582
|
+
case "navigation":
|
|
583
|
+
return `
|
|
584
|
+
items,
|
|
585
|
+
activeItem,
|
|
586
|
+
orientation = "horizontal",
|
|
587
|
+
showIcons = true,
|
|
588
|
+
renderItem,
|
|
589
|
+
variant = "default",`;
|
|
590
|
+
case "feedback":
|
|
591
|
+
return `
|
|
592
|
+
title,
|
|
593
|
+
message,
|
|
594
|
+
variant = "default",
|
|
595
|
+
onClose,
|
|
596
|
+
dismissible = false,
|
|
597
|
+
autoDissmissAfter,
|
|
598
|
+
icon,`;
|
|
599
|
+
case "data":
|
|
600
|
+
return `
|
|
601
|
+
data,
|
|
602
|
+
columns,
|
|
603
|
+
loading = false,
|
|
604
|
+
pagination = false,
|
|
605
|
+
pageSize = 10,
|
|
606
|
+
sortable = false,
|
|
607
|
+
defaultSort,
|
|
608
|
+
onRowSelect,
|
|
609
|
+
emptyMessage = "No data available",
|
|
610
|
+
renderRow,`;
|
|
611
|
+
default:
|
|
612
|
+
return `
|
|
613
|
+
children,
|
|
614
|
+
variant = "default",`;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
generateProductionComponentLogic(type, actionFunctions, componentName) {
|
|
618
|
+
const logic = [];
|
|
619
|
+
if (type === "form") {
|
|
620
|
+
logic.push(`
|
|
621
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
622
|
+
|
|
623
|
+
const form = useForm<${componentName}FormData>({
|
|
624
|
+
resolver: zodResolver(${componentName.toLowerCase()}Schema),
|
|
625
|
+
defaultValues: defaultValues || {
|
|
626
|
+
email: "",
|
|
627
|
+
password: "",
|
|
628
|
+
...(showConfirmPassword && { confirmPassword: "" }),
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
const handleFormSubmit = useCallback(async (values: ${componentName}FormData) => {
|
|
633
|
+
if (!onSubmit) return;
|
|
634
|
+
|
|
635
|
+
setIsSubmitting(true);
|
|
636
|
+
try {
|
|
637
|
+
const result = await onSubmit(values);
|
|
638
|
+
if (result.success) {
|
|
639
|
+
form.reset();
|
|
640
|
+
} else if (result.errors) {
|
|
641
|
+
Object.entries(result.errors).forEach(([field, message]) => {
|
|
642
|
+
form.setError(field as keyof ${componentName}FormData, { message });
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
} catch (error) {
|
|
646
|
+
console.error("Form submission error:", error);
|
|
647
|
+
form.setError("root", { message: "An unexpected error occurred" });
|
|
648
|
+
} finally {
|
|
649
|
+
setIsSubmitting(false);
|
|
650
|
+
}
|
|
651
|
+
}, [onSubmit, form]);`);
|
|
652
|
+
}
|
|
653
|
+
if (type === "navigation") {
|
|
654
|
+
logic.push(`
|
|
655
|
+
const pathname = usePathname();
|
|
656
|
+
const [openDropdowns, setOpenDropdowns] = useState<Set<string>>(new Set());
|
|
657
|
+
|
|
658
|
+
const currentActiveItem = useMemo(() => {
|
|
659
|
+
return activeItem || pathname;
|
|
660
|
+
}, [activeItem, pathname]);
|
|
661
|
+
|
|
662
|
+
const toggleDropdown = useCallback((itemLabel: string) => {
|
|
663
|
+
setOpenDropdowns(prev => {
|
|
664
|
+
const newSet = new Set(prev);
|
|
665
|
+
if (newSet.has(itemLabel)) {
|
|
666
|
+
newSet.delete(itemLabel);
|
|
667
|
+
} else {
|
|
668
|
+
newSet.add(itemLabel);
|
|
669
|
+
}
|
|
670
|
+
return newSet;
|
|
671
|
+
});
|
|
672
|
+
}, []);`);
|
|
673
|
+
}
|
|
674
|
+
if (type === "data") {
|
|
675
|
+
logic.push(`
|
|
676
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
677
|
+
const [sortConfig, setSortConfig] = useState<SortConfig | null>(defaultSort || null);
|
|
678
|
+
const [selectedRows, setSelectedRows] = useState<Set<string | number>>(new Set());
|
|
679
|
+
|
|
680
|
+
const sortedData = useMemo(() => {
|
|
681
|
+
if (!sortConfig) return data;
|
|
682
|
+
|
|
683
|
+
return [...data].sort((a, b) => {
|
|
684
|
+
const aValue = a[sortConfig.key];
|
|
685
|
+
const bValue = b[sortConfig.key];
|
|
686
|
+
|
|
687
|
+
if (aValue < bValue) return sortConfig.direction === "asc" ? -1 : 1;
|
|
688
|
+
if (aValue > bValue) return sortConfig.direction === "asc" ? 1 : -1;
|
|
689
|
+
return 0;
|
|
690
|
+
});
|
|
691
|
+
}, [data, sortConfig]);
|
|
692
|
+
|
|
693
|
+
const paginatedData = useMemo(() => {
|
|
694
|
+
if (!pagination) return sortedData;
|
|
695
|
+
|
|
696
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
697
|
+
return sortedData.slice(startIndex, startIndex + pageSize);
|
|
698
|
+
}, [sortedData, pagination, currentPage, pageSize]);
|
|
699
|
+
|
|
700
|
+
const handleSort = useCallback((key: keyof DataRow) => {
|
|
701
|
+
if (!sortable) return;
|
|
702
|
+
|
|
703
|
+
setSortConfig(prevSort => {
|
|
704
|
+
if (prevSort?.key === key) {
|
|
705
|
+
return {
|
|
706
|
+
key,
|
|
707
|
+
direction: prevSort.direction === "asc" ? "desc" : "asc"
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
return { key, direction: "asc" };
|
|
711
|
+
});
|
|
712
|
+
}, [sortable]);
|
|
713
|
+
|
|
714
|
+
const handleRowSelection = useCallback((rowId: string | number) => {
|
|
715
|
+
setSelectedRows(prev => {
|
|
716
|
+
const newSet = new Set(prev);
|
|
717
|
+
if (newSet.has(rowId)) {
|
|
718
|
+
newSet.delete(rowId);
|
|
719
|
+
} else {
|
|
720
|
+
newSet.add(rowId);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (onRowSelect) {
|
|
724
|
+
const selectedData = data.filter(row => newSet.has(row.id));
|
|
725
|
+
onRowSelect(selectedData);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return newSet;
|
|
729
|
+
});
|
|
730
|
+
}, [data, onRowSelect]);`);
|
|
731
|
+
}
|
|
732
|
+
if (type === "feedback" && logic.length === 0) {
|
|
733
|
+
logic.push(`
|
|
734
|
+
useEffect(() => {
|
|
735
|
+
if (autoDissmissAfter && onClose) {
|
|
736
|
+
const timer = setTimeout(onClose, autoDissmissAfter);
|
|
737
|
+
return () => clearTimeout(timer);
|
|
738
|
+
}
|
|
739
|
+
}, [autoDissmissAfter, onClose]);`);
|
|
740
|
+
}
|
|
741
|
+
return logic.join("\n");
|
|
742
|
+
}
|
|
743
|
+
generateAccessibleComponentJSX(type, name, shadcnImports) {
|
|
744
|
+
switch (type) {
|
|
745
|
+
case "form":
|
|
746
|
+
return `<Form {...form}>
|
|
747
|
+
<form
|
|
748
|
+
onSubmit={form.handleSubmit(handleFormSubmit)}
|
|
749
|
+
className={cn("space-y-6", className)}
|
|
750
|
+
noValidate
|
|
751
|
+
{...props}
|
|
752
|
+
>
|
|
753
|
+
<FormField
|
|
754
|
+
control={form.control}
|
|
755
|
+
name="email"
|
|
756
|
+
render={({ field }) => (
|
|
757
|
+
<FormItem>
|
|
758
|
+
<FormLabel>Email Address</FormLabel>
|
|
759
|
+
<FormControl>
|
|
760
|
+
<Input
|
|
761
|
+
type="email"
|
|
762
|
+
placeholder="Enter your email address"
|
|
763
|
+
autoComplete="email"
|
|
764
|
+
aria-describedby="email-error"
|
|
765
|
+
{...field}
|
|
766
|
+
/>
|
|
767
|
+
</FormControl>
|
|
768
|
+
<FormMessage id="email-error" />
|
|
769
|
+
</FormItem>
|
|
770
|
+
)}
|
|
771
|
+
/>
|
|
772
|
+
|
|
773
|
+
<FormField
|
|
774
|
+
control={form.control}
|
|
775
|
+
name="password"
|
|
776
|
+
render={({ field }) => (
|
|
777
|
+
<FormItem>
|
|
778
|
+
<FormLabel>Password</FormLabel>
|
|
779
|
+
<FormControl>
|
|
780
|
+
<Input
|
|
781
|
+
type="password"
|
|
782
|
+
placeholder="Enter your password"
|
|
783
|
+
autoComplete="current-password"
|
|
784
|
+
aria-describedby="password-error"
|
|
785
|
+
{...field}
|
|
786
|
+
/>
|
|
787
|
+
</FormControl>
|
|
788
|
+
<FormMessage id="password-error" />
|
|
789
|
+
</FormItem>
|
|
790
|
+
)}
|
|
791
|
+
/>
|
|
792
|
+
|
|
793
|
+
{showConfirmPassword && (
|
|
794
|
+
<FormField
|
|
795
|
+
control={form.control}
|
|
796
|
+
name="confirmPassword"
|
|
797
|
+
render={({ field }) => (
|
|
798
|
+
<FormItem>
|
|
799
|
+
<FormLabel>Confirm Password</FormLabel>
|
|
800
|
+
<FormControl>
|
|
801
|
+
<Input
|
|
802
|
+
type="password"
|
|
803
|
+
placeholder="Confirm your password"
|
|
804
|
+
autoComplete="new-password"
|
|
805
|
+
aria-describedby="confirm-password-error"
|
|
806
|
+
{...field}
|
|
807
|
+
/>
|
|
808
|
+
</FormControl>
|
|
809
|
+
<FormMessage id="confirm-password-error" />
|
|
810
|
+
</FormItem>
|
|
811
|
+
)}
|
|
812
|
+
/>
|
|
813
|
+
)}
|
|
814
|
+
|
|
815
|
+
{error && (
|
|
816
|
+
<Alert variant="destructive">
|
|
817
|
+
<AlertDescription>{error}</AlertDescription>
|
|
818
|
+
</Alert>
|
|
819
|
+
)}
|
|
820
|
+
|
|
821
|
+
{form.formState.errors.root && (
|
|
822
|
+
<Alert variant="destructive">
|
|
823
|
+
<AlertDescription>{form.formState.errors.root.message}</AlertDescription>
|
|
824
|
+
</Alert>
|
|
825
|
+
)}
|
|
826
|
+
|
|
827
|
+
<Button
|
|
828
|
+
type="submit"
|
|
829
|
+
disabled={loading || isSubmitting}
|
|
830
|
+
className="w-full"
|
|
831
|
+
aria-describedby={loading || isSubmitting ? "submit-loading" : undefined}
|
|
832
|
+
>
|
|
833
|
+
{loading || isSubmitting ? (
|
|
834
|
+
<>
|
|
835
|
+
<span className="sr-only" id="submit-loading">Loading...</span>
|
|
836
|
+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2" />
|
|
837
|
+
Submitting...
|
|
838
|
+
</>
|
|
839
|
+
) : (
|
|
840
|
+
"Submit"
|
|
841
|
+
)}
|
|
842
|
+
</Button>
|
|
843
|
+
</form>
|
|
844
|
+
</Form>`;
|
|
845
|
+
case "layout":
|
|
846
|
+
return `<div className={cn("min-h-screen flex flex-col", className)} {...props}>
|
|
847
|
+
{header && (
|
|
848
|
+
<header
|
|
849
|
+
className="border-b bg-background sticky top-0 z-40"
|
|
850
|
+
role="banner"
|
|
851
|
+
>
|
|
852
|
+
<div className="container mx-auto px-4 py-4">
|
|
853
|
+
{header}
|
|
854
|
+
</div>
|
|
855
|
+
</header>
|
|
856
|
+
)}
|
|
857
|
+
|
|
858
|
+
<div className="flex-1 flex">
|
|
859
|
+
{sidebar && (
|
|
860
|
+
<aside
|
|
861
|
+
className={cn(
|
|
862
|
+
"border-r bg-background",
|
|
863
|
+
variant === "sidebar" ? "w-64" : "w-auto"
|
|
864
|
+
)}
|
|
865
|
+
role="complementary"
|
|
866
|
+
aria-label="Sidebar navigation"
|
|
867
|
+
>
|
|
868
|
+
<div className="p-4">
|
|
869
|
+
{sidebar}
|
|
870
|
+
</div>
|
|
871
|
+
</aside>
|
|
872
|
+
)}
|
|
873
|
+
|
|
874
|
+
<main
|
|
875
|
+
className="flex-1 focus:outline-none"
|
|
876
|
+
role="main"
|
|
877
|
+
tabIndex={-1}
|
|
878
|
+
>
|
|
879
|
+
<div className={cn(
|
|
880
|
+
"container mx-auto px-4 py-8",
|
|
881
|
+
variant === "centered" && "max-w-4xl",
|
|
882
|
+
variant === "fullscreen" && "max-w-none px-0 py-0"
|
|
883
|
+
)}>
|
|
884
|
+
{children}
|
|
885
|
+
</div>
|
|
886
|
+
</main>
|
|
887
|
+
</div>
|
|
888
|
+
|
|
889
|
+
{footer && (
|
|
890
|
+
<footer
|
|
891
|
+
className="border-t bg-background"
|
|
892
|
+
role="contentinfo"
|
|
893
|
+
>
|
|
894
|
+
<div className="container mx-auto px-4 py-4 text-center text-sm text-muted-foreground">
|
|
895
|
+
{footer}
|
|
896
|
+
</div>
|
|
897
|
+
</footer>
|
|
898
|
+
)}
|
|
899
|
+
</div>`;
|
|
900
|
+
case "card":
|
|
901
|
+
return `<Card
|
|
902
|
+
className={cn(
|
|
903
|
+
"transition-all duration-200",
|
|
904
|
+
variant === "elevated" && "shadow-lg hover:shadow-xl",
|
|
905
|
+
variant === "outlined" && "border-2",
|
|
906
|
+
variant === "ghost" && "border-none shadow-none",
|
|
907
|
+
interactive && "cursor-pointer hover:bg-accent/50",
|
|
908
|
+
size === "sm" && "p-4",
|
|
909
|
+
size === "lg" && "p-8",
|
|
910
|
+
className
|
|
911
|
+
)}
|
|
912
|
+
onClick={interactive ? onClick : undefined}
|
|
913
|
+
role={interactive ? "button" : undefined}
|
|
914
|
+
tabIndex={interactive ? 0 : undefined}
|
|
915
|
+
onKeyDown={interactive ? (e) => {
|
|
916
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
917
|
+
e.preventDefault();
|
|
918
|
+
onClick?.();
|
|
919
|
+
}
|
|
920
|
+
} : undefined}
|
|
921
|
+
{...props}
|
|
922
|
+
>
|
|
923
|
+
{(title || description) && (
|
|
924
|
+
<CardHeader className={size === "sm" ? "pb-3" : undefined}>
|
|
925
|
+
{title && (
|
|
926
|
+
<CardTitle className={size === "sm" ? "text-base" : "text-lg"}>
|
|
927
|
+
{title}
|
|
928
|
+
</CardTitle>
|
|
929
|
+
)}
|
|
930
|
+
{description && (
|
|
931
|
+
<CardDescription>{description}</CardDescription>
|
|
932
|
+
)}
|
|
933
|
+
</CardHeader>
|
|
934
|
+
)}
|
|
935
|
+
|
|
936
|
+
{content && (
|
|
937
|
+
<CardContent className={cn(
|
|
938
|
+
size === "sm" && "pt-0 pb-3",
|
|
939
|
+
!title && !description && "pt-6"
|
|
940
|
+
)}>
|
|
941
|
+
{content}
|
|
942
|
+
</CardContent>
|
|
943
|
+
)}
|
|
944
|
+
|
|
945
|
+
{actions && (
|
|
946
|
+
<CardFooter className={cn(
|
|
947
|
+
"flex justify-end space-x-2",
|
|
948
|
+
size === "sm" && "pt-0"
|
|
949
|
+
)}>
|
|
950
|
+
{actions}
|
|
951
|
+
</CardFooter>
|
|
952
|
+
)}
|
|
953
|
+
</Card>`;
|
|
954
|
+
case "button":
|
|
955
|
+
return `<Button
|
|
956
|
+
variant={variant}
|
|
957
|
+
size={size}
|
|
958
|
+
disabled={disabled || loading}
|
|
959
|
+
onClick={onClick}
|
|
960
|
+
type={type}
|
|
961
|
+
className={cn(loading && "cursor-wait", className)}
|
|
962
|
+
aria-label={ariaLabel}
|
|
963
|
+
aria-disabled={disabled || loading}
|
|
964
|
+
{...props}
|
|
965
|
+
>
|
|
966
|
+
{loading ? (
|
|
967
|
+
<>
|
|
968
|
+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-current mr-2" />
|
|
969
|
+
<span className="sr-only">Loading...</span>
|
|
970
|
+
Loading...
|
|
971
|
+
</>
|
|
972
|
+
) : (
|
|
973
|
+
children
|
|
974
|
+
)}
|
|
975
|
+
</Button>`;
|
|
976
|
+
case "navigation":
|
|
977
|
+
return `<NavigationMenu
|
|
978
|
+
className={cn("", className)}
|
|
979
|
+
orientation={orientation}
|
|
980
|
+
{...props}
|
|
981
|
+
>
|
|
982
|
+
<NavigationMenuList className={cn(
|
|
983
|
+
orientation === "vertical" && "flex-col space-x-0 space-y-1"
|
|
984
|
+
)}>
|
|
985
|
+
{items.map((item, index) => (
|
|
986
|
+
<NavigationMenuItem key={item.href || index}>
|
|
987
|
+
{item.children ? (
|
|
988
|
+
<>
|
|
989
|
+
<NavigationMenuTrigger
|
|
990
|
+
className={cn(
|
|
991
|
+
variant === "pills" && "rounded-full",
|
|
992
|
+
variant === "underline" && "border-b-2 border-transparent data-[state=open]:border-primary"
|
|
993
|
+
)}
|
|
994
|
+
onClick={() => toggleDropdown(item.label)}
|
|
995
|
+
aria-expanded={openDropdowns.has(item.label)}
|
|
996
|
+
>
|
|
997
|
+
{showIcons && item.icon && (
|
|
998
|
+
<item.icon className="w-4 h-4 mr-2" />
|
|
999
|
+
)}
|
|
1000
|
+
{item.label}
|
|
1001
|
+
</NavigationMenuTrigger>
|
|
1002
|
+
<NavigationMenuContent>
|
|
1003
|
+
<div className="grid gap-3 p-4 w-[400px]">
|
|
1004
|
+
{item.children.map((child, childIndex) => (
|
|
1005
|
+
<NavigationMenuLink
|
|
1006
|
+
key={child.href || childIndex}
|
|
1007
|
+
asChild
|
|
1008
|
+
>
|
|
1009
|
+
<Link
|
|
1010
|
+
href={child.href}
|
|
1011
|
+
className={cn(
|
|
1012
|
+
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
|
|
1013
|
+
child.disabled && "pointer-events-none opacity-50"
|
|
1014
|
+
)}
|
|
1015
|
+
>
|
|
1016
|
+
<div className="text-sm font-medium leading-none">
|
|
1017
|
+
{child.label}
|
|
1018
|
+
</div>
|
|
1019
|
+
</Link>
|
|
1020
|
+
</NavigationMenuLink>
|
|
1021
|
+
))}
|
|
1022
|
+
</div>
|
|
1023
|
+
</NavigationMenuContent>
|
|
1024
|
+
</>
|
|
1025
|
+
) : (
|
|
1026
|
+
<NavigationMenuLink
|
|
1027
|
+
asChild
|
|
1028
|
+
className={cn(
|
|
1029
|
+
currentActiveItem === item.href && "bg-accent text-accent-foreground",
|
|
1030
|
+
variant === "pills" && "rounded-full",
|
|
1031
|
+
variant === "underline" && currentActiveItem === item.href && "border-b-2 border-primary"
|
|
1032
|
+
)}
|
|
1033
|
+
>
|
|
1034
|
+
<Link
|
|
1035
|
+
href={item.href}
|
|
1036
|
+
className={cn(
|
|
1037
|
+
"block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
|
|
1038
|
+
item.disabled && "pointer-events-none opacity-50"
|
|
1039
|
+
)}
|
|
1040
|
+
>
|
|
1041
|
+
{showIcons && item.icon && (
|
|
1042
|
+
<item.icon className="w-4 h-4 mr-2 inline-block" />
|
|
1043
|
+
)}
|
|
1044
|
+
{item.label}
|
|
1045
|
+
</Link>
|
|
1046
|
+
</NavigationMenuLink>
|
|
1047
|
+
)}
|
|
1048
|
+
</NavigationMenuItem>
|
|
1049
|
+
))}
|
|
1050
|
+
</NavigationMenuList>
|
|
1051
|
+
</NavigationMenu>`;
|
|
1052
|
+
case "feedback":
|
|
1053
|
+
return `<Alert
|
|
1054
|
+
variant={variant}
|
|
1055
|
+
className={cn("relative", className)}
|
|
1056
|
+
role={variant === "destructive" ? "alert" : "status"}
|
|
1057
|
+
aria-live={variant === "destructive" ? "assertive" : "polite"}
|
|
1058
|
+
{...props}
|
|
1059
|
+
>
|
|
1060
|
+
{icon || (
|
|
1061
|
+
<div className="w-4 h-4">
|
|
1062
|
+
{variant === "success" && "✓"}
|
|
1063
|
+
{variant === "destructive" && "⚠"}
|
|
1064
|
+
{variant === "warning" && "⚠"}
|
|
1065
|
+
</div>
|
|
1066
|
+
)}
|
|
1067
|
+
|
|
1068
|
+
<div className="flex-1">
|
|
1069
|
+
{title && <AlertTitle>{title}</AlertTitle>}
|
|
1070
|
+
{message && <AlertDescription>{message}</AlertDescription>}
|
|
1071
|
+
</div>
|
|
1072
|
+
|
|
1073
|
+
{dismissible && onClose && (
|
|
1074
|
+
<Button
|
|
1075
|
+
variant="ghost"
|
|
1076
|
+
size="sm"
|
|
1077
|
+
onClick={onClose}
|
|
1078
|
+
className="absolute top-2 right-2 h-6 w-6 p-0"
|
|
1079
|
+
aria-label="Close alert"
|
|
1080
|
+
>
|
|
1081
|
+
×
|
|
1082
|
+
</Button>
|
|
1083
|
+
)}
|
|
1084
|
+
</Alert>`;
|
|
1085
|
+
case "data":
|
|
1086
|
+
return `<div className={cn("space-y-4", className)} {...props}>
|
|
1087
|
+
{loading ? (
|
|
1088
|
+
<div className="flex items-center justify-center p-8">
|
|
1089
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
|
1090
|
+
<span className="sr-only">Loading data...</span>
|
|
1091
|
+
</div>
|
|
1092
|
+
) : (
|
|
1093
|
+
<>
|
|
1094
|
+
<div className="rounded-md border overflow-hidden">
|
|
1095
|
+
<Table>
|
|
1096
|
+
<TableHeader>
|
|
1097
|
+
<TableRow>
|
|
1098
|
+
{onRowSelect && (
|
|
1099
|
+
<TableHead className="w-12">
|
|
1100
|
+
<span className="sr-only">Select row</span>
|
|
1101
|
+
</TableHead>
|
|
1102
|
+
)}
|
|
1103
|
+
{columns.map((column) => (
|
|
1104
|
+
<TableHead
|
|
1105
|
+
key={String(column.key)}
|
|
1106
|
+
className={cn(
|
|
1107
|
+
sortable && column.sortable && "cursor-pointer hover:bg-muted",
|
|
1108
|
+
column.width && \`w-[\${column.width}]\`
|
|
1109
|
+
)}
|
|
1110
|
+
onClick={() => column.sortable && handleSort(column.key)}
|
|
1111
|
+
role={column.sortable ? "button" : undefined}
|
|
1112
|
+
tabIndex={column.sortable ? 0 : undefined}
|
|
1113
|
+
onKeyDown={column.sortable ? (e) => {
|
|
1114
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1115
|
+
e.preventDefault();
|
|
1116
|
+
handleSort(column.key);
|
|
1117
|
+
}
|
|
1118
|
+
} : undefined}
|
|
1119
|
+
>
|
|
1120
|
+
<div className="flex items-center space-x-2">
|
|
1121
|
+
<span>{column.label}</span>
|
|
1122
|
+
{sortable && column.sortable && sortConfig?.key === column.key && (
|
|
1123
|
+
<span className="text-xs">
|
|
1124
|
+
{sortConfig.direction === "asc" ? "↑" : "↓"}
|
|
1125
|
+
</span>
|
|
1126
|
+
)}
|
|
1127
|
+
</div>
|
|
1128
|
+
</TableHead>
|
|
1129
|
+
))}
|
|
1130
|
+
</TableRow>
|
|
1131
|
+
</TableHeader>
|
|
1132
|
+
<TableBody>
|
|
1133
|
+
{paginatedData.length === 0 ? (
|
|
1134
|
+
<TableRow>
|
|
1135
|
+
<TableCell
|
|
1136
|
+
colSpan={columns.length + (onRowSelect ? 1 : 0)}
|
|
1137
|
+
className="text-center py-8 text-muted-foreground"
|
|
1138
|
+
>
|
|
1139
|
+
{emptyMessage}
|
|
1140
|
+
</TableCell>
|
|
1141
|
+
</TableRow>
|
|
1142
|
+
) : (
|
|
1143
|
+
paginatedData.map((row, index) => (
|
|
1144
|
+
renderRow ? renderRow(row, index) : (
|
|
1145
|
+
<TableRow
|
|
1146
|
+
key={row.id}
|
|
1147
|
+
className={cn(
|
|
1148
|
+
onRowSelect && selectedRows.has(row.id) && "bg-muted"
|
|
1149
|
+
)}
|
|
1150
|
+
>
|
|
1151
|
+
{onRowSelect && (
|
|
1152
|
+
<TableCell>
|
|
1153
|
+
<Checkbox
|
|
1154
|
+
checked={selectedRows.has(row.id)}
|
|
1155
|
+
onCheckedChange={() => handleRowSelection(row.id)}
|
|
1156
|
+
aria-label={\`Select row \${index + 1}\`}
|
|
1157
|
+
/>
|
|
1158
|
+
</TableCell>
|
|
1159
|
+
)}
|
|
1160
|
+
{columns.map((column) => (
|
|
1161
|
+
<TableCell key={String(column.key)}>
|
|
1162
|
+
{column.render
|
|
1163
|
+
? column.render(row[column.key], row)
|
|
1164
|
+
: String(row[column.key] || "")
|
|
1165
|
+
}
|
|
1166
|
+
</TableCell>
|
|
1167
|
+
))}
|
|
1168
|
+
</TableRow>
|
|
1169
|
+
)
|
|
1170
|
+
))
|
|
1171
|
+
)}
|
|
1172
|
+
</TableBody>
|
|
1173
|
+
</Table>
|
|
1174
|
+
</div>
|
|
1175
|
+
|
|
1176
|
+
{pagination && data.length > pageSize && (
|
|
1177
|
+
<div className="flex items-center justify-between">
|
|
1178
|
+
<p className="text-sm text-muted-foreground">
|
|
1179
|
+
Showing {((currentPage - 1) * pageSize) + 1} to {Math.min(currentPage * pageSize, data.length)} of {data.length} results
|
|
1180
|
+
</p>
|
|
1181
|
+
<div className="flex items-center space-x-2">
|
|
1182
|
+
<Button
|
|
1183
|
+
variant="outline"
|
|
1184
|
+
size="sm"
|
|
1185
|
+
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
|
|
1186
|
+
disabled={currentPage === 1}
|
|
1187
|
+
aria-label="Previous page"
|
|
1188
|
+
>
|
|
1189
|
+
Previous
|
|
1190
|
+
</Button>
|
|
1191
|
+
<Button
|
|
1192
|
+
variant="outline"
|
|
1193
|
+
size="sm"
|
|
1194
|
+
onClick={() => setCurrentPage(prev => prev + 1)}
|
|
1195
|
+
disabled={currentPage * pageSize >= data.length}
|
|
1196
|
+
aria-label="Next page"
|
|
1197
|
+
>
|
|
1198
|
+
Next
|
|
1199
|
+
</Button>
|
|
1200
|
+
</div>
|
|
1201
|
+
</div>
|
|
1202
|
+
)}
|
|
1203
|
+
</>
|
|
1204
|
+
)}
|
|
1205
|
+
</div>`;
|
|
1206
|
+
default:
|
|
1207
|
+
return `<Card className={cn("p-4", className)} {...props}>
|
|
1208
|
+
<CardHeader>
|
|
1209
|
+
<CardTitle>${name}</CardTitle>
|
|
1210
|
+
</CardHeader>
|
|
1211
|
+
<CardContent>
|
|
1212
|
+
<p className="text-sm text-muted-foreground">
|
|
1213
|
+
This is a production-ready ${type} component with accessibility features and TypeScript support.
|
|
1214
|
+
</p>
|
|
1215
|
+
{children}
|
|
1216
|
+
</CardContent>
|
|
1217
|
+
</Card>`;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
generateProductionActionFunctions(actionFunctions, componentName, type) {
|
|
1221
|
+
if (actionFunctions.length === 0)
|
|
1222
|
+
return "";
|
|
1223
|
+
const functions = actionFunctions.map((func) => {
|
|
1224
|
+
switch (func) {
|
|
1225
|
+
case "handleSubmit":
|
|
1226
|
+
return type === "form"
|
|
1227
|
+
? `// Form submission is handled within the component`
|
|
1228
|
+
: `
|
|
1229
|
+
/**
|
|
1230
|
+
* Handles form submission with validation and error handling
|
|
1231
|
+
* @param data - Form data to submit
|
|
1232
|
+
* @returns Promise with submission result
|
|
1233
|
+
*/
|
|
1234
|
+
export async function handleSubmit(data: Record<string, any>): Promise<{ success: boolean; message?: string }> {
|
|
1235
|
+
try {
|
|
1236
|
+
// TODO: Implement actual submission logic
|
|
1237
|
+
console.log("Submitting data:", data);
|
|
1238
|
+
|
|
1239
|
+
// Simulate API call
|
|
1240
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1241
|
+
|
|
1242
|
+
return { success: true, message: "Data submitted successfully" };
|
|
1243
|
+
} catch (error) {
|
|
1244
|
+
console.error("Submission error:", error);
|
|
1245
|
+
return { success: false, message: "Failed to submit data" };
|
|
1246
|
+
}
|
|
1247
|
+
}`;
|
|
1248
|
+
case "handleLogin":
|
|
1249
|
+
return `
|
|
1250
|
+
/**
|
|
1251
|
+
* Handles user authentication with proper validation
|
|
1252
|
+
* @param email - User email address
|
|
1253
|
+
* @param password - User password
|
|
1254
|
+
* @returns Promise with authentication result
|
|
1255
|
+
*/
|
|
1256
|
+
export async function handleLogin(email: string, password: string): Promise<{
|
|
1257
|
+
success: boolean;
|
|
1258
|
+
user?: { id: string; email: string; name: string };
|
|
1259
|
+
error?: string
|
|
1260
|
+
}> {
|
|
1261
|
+
try {
|
|
1262
|
+
// TODO: Implement actual authentication logic
|
|
1263
|
+
const response = await fetch("/api/auth/login", {
|
|
1264
|
+
method: "POST",
|
|
1265
|
+
headers: { "Content-Type": "application/json" },
|
|
1266
|
+
body: JSON.stringify({ email, password }),
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
if (!response.ok) {
|
|
1270
|
+
throw new Error("Authentication failed");
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const result = await response.json();
|
|
1274
|
+
return { success: true, user: result.user };
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
console.error("Login error:", error);
|
|
1277
|
+
return { success: false, error: "Invalid credentials" };
|
|
1278
|
+
}
|
|
1279
|
+
}`;
|
|
1280
|
+
case "validateEmail":
|
|
1281
|
+
return `
|
|
1282
|
+
/**
|
|
1283
|
+
* Validates email address format and availability
|
|
1284
|
+
* @param email - Email address to validate
|
|
1285
|
+
* @returns Validation result with detailed feedback
|
|
1286
|
+
*/
|
|
1287
|
+
export async function validateEmail(email: string): Promise<{
|
|
1288
|
+
isValid: boolean;
|
|
1289
|
+
isAvailable?: boolean;
|
|
1290
|
+
message?: string;
|
|
1291
|
+
}> {
|
|
1292
|
+
const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
|
|
1293
|
+
|
|
1294
|
+
if (!emailRegex.test(email)) {
|
|
1295
|
+
return { isValid: false, message: "Please enter a valid email address" };
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
try {
|
|
1299
|
+
// TODO: Check email availability
|
|
1300
|
+
const response = await fetch(\`/api/auth/check-email?email=\${encodeURIComponent(email)}\`);
|
|
1301
|
+
const { available } = await response.json();
|
|
1302
|
+
|
|
1303
|
+
return {
|
|
1304
|
+
isValid: true,
|
|
1305
|
+
isAvailable: available,
|
|
1306
|
+
message: available ? "Email is available" : "Email is already registered"
|
|
1307
|
+
};
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
console.error("Email validation error:", error);
|
|
1310
|
+
return { isValid: true, message: "Unable to verify email availability" };
|
|
1311
|
+
}
|
|
1312
|
+
}`;
|
|
1313
|
+
default:
|
|
1314
|
+
return `
|
|
1315
|
+
/**
|
|
1316
|
+
* ${func} - Custom action function
|
|
1317
|
+
* TODO: Implement ${func} logic with proper TypeScript types and error handling
|
|
1318
|
+
*/
|
|
1319
|
+
export async function ${func}(...args: any[]): Promise<{ success: boolean; data?: any; error?: string }> {
|
|
1320
|
+
try {
|
|
1321
|
+
console.log("${func} called with args:", args);
|
|
1322
|
+
|
|
1323
|
+
// TODO: Implement actual logic
|
|
1324
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1325
|
+
|
|
1326
|
+
return { success: true };
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
console.error("${func} error:", error);
|
|
1329
|
+
return { success: false, error: "Operation failed" };
|
|
1330
|
+
}
|
|
1331
|
+
}`;
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
return functions.join("\n");
|
|
1335
|
+
}
|
|
1336
|
+
extractDependencies(code) {
|
|
1337
|
+
const dependencies = [];
|
|
1338
|
+
// Extract import statements
|
|
1339
|
+
const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
|
|
1340
|
+
let match;
|
|
1341
|
+
while ((match = importRegex.exec(code)) !== null) {
|
|
1342
|
+
const importPath = match[1];
|
|
1343
|
+
if (importPath.startsWith("@/") ||
|
|
1344
|
+
importPath.startsWith("./") ||
|
|
1345
|
+
importPath.startsWith("../")) {
|
|
1346
|
+
dependencies.push(importPath);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
// Extract shadcn/ui component dependencies
|
|
1350
|
+
const shadcnComponents = this.extractShadcnComponents(code);
|
|
1351
|
+
shadcnComponents.forEach((comp) => {
|
|
1352
|
+
dependencies.push(`@/components/ui/${comp.toLowerCase()}`);
|
|
1353
|
+
});
|
|
1354
|
+
// Remove duplicates
|
|
1355
|
+
return [...new Set(dependencies)];
|
|
1356
|
+
}
|
|
1357
|
+
extractShadcnComponents(code) {
|
|
1358
|
+
const components = [];
|
|
1359
|
+
// Check for shadcn/ui component usage
|
|
1360
|
+
Object.values(SHADCN_COMPONENTS)
|
|
1361
|
+
.flat()
|
|
1362
|
+
.forEach((comp) => {
|
|
1363
|
+
if (code.includes(comp)) {
|
|
1364
|
+
components.push(comp);
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
return [...new Set(components)];
|
|
1368
|
+
}
|
|
1369
|
+
calculateQualityScore(code) {
|
|
1370
|
+
let score = 70; // Base score
|
|
1371
|
+
// Bonus for using shadcn/ui components
|
|
1372
|
+
const shadcnComponents = this.extractShadcnComponents(code);
|
|
1373
|
+
score += Math.min(shadcnComponents.length * 3, 15);
|
|
1374
|
+
// Bonus for proper TypeScript usage
|
|
1375
|
+
if (code.includes("interface") && code.includes("Props"))
|
|
1376
|
+
score += 10;
|
|
1377
|
+
if (code.includes("export type") || code.includes("export interface"))
|
|
1378
|
+
score += 5;
|
|
1379
|
+
// Bonus for React patterns and hooks
|
|
1380
|
+
if (code.includes("useState") || code.includes("useEffect"))
|
|
1381
|
+
score += 5;
|
|
1382
|
+
if (code.includes("useCallback") || code.includes("useMemo"))
|
|
1383
|
+
score += 5;
|
|
1384
|
+
// Bonus for accessibility features
|
|
1385
|
+
if (code.includes("aria-") || code.includes("role="))
|
|
1386
|
+
score += 10;
|
|
1387
|
+
if (code.includes("tabIndex") || code.includes("onKeyDown"))
|
|
1388
|
+
score += 5;
|
|
1389
|
+
// Bonus for error handling and validation
|
|
1390
|
+
if (code.includes("try") && code.includes("catch"))
|
|
1391
|
+
score += 5;
|
|
1392
|
+
if (code.includes("zodResolver") || code.includes("z.object"))
|
|
1393
|
+
score += 5;
|
|
1394
|
+
// Bonus for semantic HTML and proper structure
|
|
1395
|
+
if (code.includes("role=") && code.includes("aria-"))
|
|
1396
|
+
score += 5;
|
|
1397
|
+
if (code.includes("sr-only"))
|
|
1398
|
+
score += 3;
|
|
1399
|
+
// Bonus for production patterns
|
|
1400
|
+
if (code.includes("useCallback") && code.includes("useMemo"))
|
|
1401
|
+
score += 5;
|
|
1402
|
+
if (code.includes("React.memo") || code.includes("forwardRef"))
|
|
1403
|
+
score += 3;
|
|
1404
|
+
// Cap at 100
|
|
1405
|
+
return Math.min(score, 100);
|
|
1406
|
+
}
|
|
1407
|
+
generateComponentName(description) {
|
|
1408
|
+
// Convert description to PascalCase component name
|
|
1409
|
+
return description
|
|
1410
|
+
.split(" ")
|
|
1411
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
1412
|
+
.join("")
|
|
1413
|
+
.replace(/[^a-zA-Z0-9]/g, "");
|
|
1414
|
+
}
|
|
1415
|
+
async generateWithXAI(component, group, options) {
|
|
1416
|
+
try {
|
|
1417
|
+
const xaiApiKey = process.env.XAI_API_KEY;
|
|
1418
|
+
if (!xaiApiKey) {
|
|
1419
|
+
throw new Error("X.AI API key not found");
|
|
1420
|
+
}
|
|
1421
|
+
const { XAIClient } = await Promise.resolve().then(() => __importStar(require("../../utils/xaiClient")));
|
|
1422
|
+
const xaiClient = new XAIClient({
|
|
1423
|
+
apiKey: xaiApiKey,
|
|
1424
|
+
temperature: options.temperature || 0.6,
|
|
1425
|
+
maxTokens: options.maxTokens || 8000,
|
|
1426
|
+
});
|
|
1427
|
+
return await xaiClient.generateComponent(component, group?.name || "general");
|
|
1428
|
+
}
|
|
1429
|
+
catch (error) {
|
|
1430
|
+
console.warn("X.AI generation failed, falling back to local generation:", error);
|
|
1431
|
+
// Fallback to local generation
|
|
1432
|
+
return this.generateProductionReadyComponent(component, group, {
|
|
1433
|
+
...options,
|
|
1434
|
+
useXAI: false,
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
exports.CodeGenSubAgent = CodeGenSubAgent;
|
|
1440
|
+
//# sourceMappingURL=CodeGenSubAgent.js.map
|