mcp-dndgrid 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/PROJECT_SUMMARY.md +482 -0
- package/QUICKSTART.md +223 -0
- package/README.md +365 -0
- package/STATUS.md +315 -0
- package/USAGE_GUIDE.md +547 -0
- package/dist/chunk-CMGEAPA5.js +157 -0
- package/dist/chunk-CMGEAPA5.js.map +1 -0
- package/dist/chunk-QZHBI6ZI.js +5281 -0
- package/dist/chunk-QZHBI6ZI.js.map +1 -0
- package/dist/chunk-SEGVTWSK.js +44 -0
- package/dist/chunk-SEGVTWSK.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +248012 -0
- package/dist/index.js.map +1 -0
- package/dist/stdio-FWYJXSU7.js +101 -0
- package/dist/stdio-FWYJXSU7.js.map +1 -0
- package/dist/template-JDMAVVX7.js +9 -0
- package/dist/template-JDMAVVX7.js.map +1 -0
- package/examples/claude_desktop_config.example.json +12 -0
- package/examples/example-complex-editor.tsx +107 -0
- package/examples/example-dashboard.tsx +65 -0
- package/examples/example-ide-layout.tsx +53 -0
- package/examples/test-generator.ts +37 -0
- package/examples/test-parser.ts +121 -0
- package/examples/test-scenarios.md +496 -0
- package/package.json +42 -0
- package/src/index.ts +16 -0
- package/src/server.ts +314 -0
- package/src/tools/analyze-layout.ts +193 -0
- package/src/tools/apply-template.ts +125 -0
- package/src/tools/generate-layout.ts +235 -0
- package/src/tools/interactive-builder.ts +100 -0
- package/src/tools/validate-layout.ts +113 -0
- package/src/types/layout.ts +48 -0
- package/src/types/template.ts +181 -0
- package/src/utils/ast-parser.ts +264 -0
- package/src/utils/code-generator.ts +123 -0
- package/src/utils/layout-analyzer.ts +105 -0
- package/src/utils/layout-builder.ts +127 -0
- package/src/utils/validator.ts +263 -0
- package/stderr.log +1 -0
- package/stdout.log +0 -0
- package/test-mcp.js +27 -0
- package/tsconfig.json +29 -0
- package/tsup.config.ts +16 -0
package/src/server.ts
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import {
|
|
3
|
+
CallToolRequestSchema,
|
|
4
|
+
ListToolsRequestSchema,
|
|
5
|
+
ListResourcesRequestSchema,
|
|
6
|
+
ReadResourceRequestSchema,
|
|
7
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import {
|
|
9
|
+
validateLayout,
|
|
10
|
+
ValidateLayoutInputSchema,
|
|
11
|
+
} from './tools/validate-layout.js';
|
|
12
|
+
import {
|
|
13
|
+
analyzeLayout,
|
|
14
|
+
AnalyzeLayoutInputSchema,
|
|
15
|
+
} from './tools/analyze-layout.js';
|
|
16
|
+
import {
|
|
17
|
+
applyTemplate,
|
|
18
|
+
ApplyTemplateInputSchema,
|
|
19
|
+
} from './tools/apply-template.js';
|
|
20
|
+
import {
|
|
21
|
+
generateLayout,
|
|
22
|
+
GenerateLayoutInputSchema,
|
|
23
|
+
} from './tools/generate-layout.js';
|
|
24
|
+
import {
|
|
25
|
+
interactiveBuilder,
|
|
26
|
+
InteractiveBuilderInputSchema,
|
|
27
|
+
} from './tools/interactive-builder.js';
|
|
28
|
+
|
|
29
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* DndGrid MCP Server
|
|
33
|
+
* Provides tools and resources for generating and analyzing DndGrid layouts
|
|
34
|
+
*/
|
|
35
|
+
export class DndGridMCPServer {
|
|
36
|
+
private server: Server;
|
|
37
|
+
|
|
38
|
+
constructor() {
|
|
39
|
+
this.server = new Server(
|
|
40
|
+
{
|
|
41
|
+
name: 'dndgrid-mcp',
|
|
42
|
+
version: '0.1.0',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
capabilities: {
|
|
46
|
+
tools: {},
|
|
47
|
+
resources: {},
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
this.setupHandlers();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private setupHandlers(): void {
|
|
56
|
+
// List available tools
|
|
57
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
58
|
+
console.error('ListToolsRequest received');
|
|
59
|
+
return {
|
|
60
|
+
tools: [
|
|
61
|
+
{
|
|
62
|
+
name: 'validate-layout',
|
|
63
|
+
description: 'Validate DndGrid layout structure and constraints',
|
|
64
|
+
inputSchema: zodToJsonSchema(ValidateLayoutInputSchema) as any,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'analyze-layout',
|
|
68
|
+
description: 'Analyze existing DndGrid code and suggest improvements',
|
|
69
|
+
inputSchema: zodToJsonSchema(AnalyzeLayoutInputSchema) as any,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'apply-template',
|
|
73
|
+
description: 'Apply a pre-defined layout template',
|
|
74
|
+
inputSchema: zodToJsonSchema(ApplyTemplateInputSchema) as any,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'generate-layout',
|
|
78
|
+
description: 'Generate DndGrid layout code from natural language requirements',
|
|
79
|
+
inputSchema: zodToJsonSchema(GenerateLayoutInputSchema) as any,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'interactive-builder',
|
|
83
|
+
description: 'Build layout interactively through guided steps',
|
|
84
|
+
inputSchema: zodToJsonSchema(InteractiveBuilderInputSchema) as any,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Handle tool calls
|
|
91
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
92
|
+
const { name, arguments: args } = request.params;
|
|
93
|
+
console.error(`CallToolRequest received: ${name}`);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
switch (name) {
|
|
97
|
+
case 'validate-layout':
|
|
98
|
+
return await validateLayout(args as any);
|
|
99
|
+
case 'analyze-layout':
|
|
100
|
+
return await analyzeLayout(args as any);
|
|
101
|
+
case 'apply-template':
|
|
102
|
+
return await applyTemplate(args as any);
|
|
103
|
+
case 'generate-layout':
|
|
104
|
+
return await generateLayout(args as any);
|
|
105
|
+
case 'interactive-builder':
|
|
106
|
+
return await interactiveBuilder(args as any);
|
|
107
|
+
default:
|
|
108
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: 'text',
|
|
115
|
+
text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// List available resources
|
|
123
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
124
|
+
return {
|
|
125
|
+
resources: [
|
|
126
|
+
{
|
|
127
|
+
uri: 'dndgrid://docs/architecture',
|
|
128
|
+
name: 'DndGrid Architecture',
|
|
129
|
+
description: 'Complete architecture documentation',
|
|
130
|
+
mimeType: 'text/markdown',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
uri: 'dndgrid://templates/list',
|
|
134
|
+
name: 'Available Templates',
|
|
135
|
+
description: 'List of all available layout templates',
|
|
136
|
+
mimeType: 'application/json',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
uri: 'dndgrid://docs/best-practices',
|
|
140
|
+
name: 'Best Practices',
|
|
141
|
+
description: 'Performance and integration guidelines',
|
|
142
|
+
mimeType: 'text/markdown',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Read resource content
|
|
149
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
150
|
+
const { uri } = request.params;
|
|
151
|
+
|
|
152
|
+
if (uri === 'dndgrid://docs/architecture') {
|
|
153
|
+
return {
|
|
154
|
+
contents: [
|
|
155
|
+
{
|
|
156
|
+
uri,
|
|
157
|
+
mimeType: 'text/markdown',
|
|
158
|
+
text: this.getArchitectureDoc(),
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (uri === 'dndgrid://templates/list') {
|
|
165
|
+
const { BUILTIN_TEMPLATES } = await import('./types/template.js');
|
|
166
|
+
return {
|
|
167
|
+
contents: [
|
|
168
|
+
{
|
|
169
|
+
uri,
|
|
170
|
+
mimeType: 'application/json',
|
|
171
|
+
text: JSON.stringify(BUILTIN_TEMPLATES, null, 2),
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (uri === 'dndgrid://docs/best-practices') {
|
|
178
|
+
return {
|
|
179
|
+
contents: [
|
|
180
|
+
{
|
|
181
|
+
uri,
|
|
182
|
+
mimeType: 'text/markdown',
|
|
183
|
+
text: this.getBestPracticesDoc(),
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get architecture documentation
|
|
195
|
+
*/
|
|
196
|
+
private getArchitectureDoc(): string {
|
|
197
|
+
return `# DndGrid Architecture
|
|
198
|
+
|
|
199
|
+
## Overview
|
|
200
|
+
|
|
201
|
+
DndGrid is a tree-based drag-and-drop grid system for React applications.
|
|
202
|
+
|
|
203
|
+
## Core Components
|
|
204
|
+
|
|
205
|
+
### DndGridContainer
|
|
206
|
+
- Root component
|
|
207
|
+
- Props: \`width\`, \`height\`, \`children\`
|
|
208
|
+
- Manages global state
|
|
209
|
+
|
|
210
|
+
### DndGridSplit
|
|
211
|
+
- Divides space into two sections
|
|
212
|
+
- Props: \`direction\` ('horizontal' | 'vertical'), \`ratio\` (0-1), \`children\` (exactly 2)
|
|
213
|
+
- Creates nested layouts
|
|
214
|
+
|
|
215
|
+
### DndGridItem
|
|
216
|
+
- Leaf node containing user components
|
|
217
|
+
- Props: \`children\`
|
|
218
|
+
- Can be dragged and rearranged
|
|
219
|
+
|
|
220
|
+
## Key Concepts
|
|
221
|
+
|
|
222
|
+
### Flat Rendering
|
|
223
|
+
- All items rendered at same depth
|
|
224
|
+
- Preserves React component state during DnD
|
|
225
|
+
- Logical tree != rendering tree
|
|
226
|
+
|
|
227
|
+
### Tree Structure
|
|
228
|
+
- Binary tree: each Split has 2 children
|
|
229
|
+
- ID system: binary indexing
|
|
230
|
+
- Supports unlimited nesting (recommended < 4 levels)
|
|
231
|
+
|
|
232
|
+
### Next.js Compatibility
|
|
233
|
+
- Requires \`"use client"\` directive for App Router
|
|
234
|
+
- All DndGrid components are client components
|
|
235
|
+
|
|
236
|
+
## Performance
|
|
237
|
+
|
|
238
|
+
- **Recommended:** < 20 items, < 4 depth levels
|
|
239
|
+
- **Maximum:** < 50 items, < 6 depth levels
|
|
240
|
+
- Flat rendering optimizes reconciliation
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get best practices documentation
|
|
246
|
+
*/
|
|
247
|
+
private getBestPracticesDoc(): string {
|
|
248
|
+
return `# DndGrid Best Practices
|
|
249
|
+
|
|
250
|
+
## Performance
|
|
251
|
+
|
|
252
|
+
### Item Count
|
|
253
|
+
- Keep below 20 items for optimal performance
|
|
254
|
+
- Maximum 50 items
|
|
255
|
+
|
|
256
|
+
### Tree Depth
|
|
257
|
+
- Recommended: < 4 levels
|
|
258
|
+
- Maximum: 6 levels
|
|
259
|
+
|
|
260
|
+
### Split Ratios
|
|
261
|
+
- Avoid extreme ratios (< 0.1 or > 0.9)
|
|
262
|
+
- Use 0.2-0.8 range for better UX
|
|
263
|
+
|
|
264
|
+
## Next.js Integration
|
|
265
|
+
|
|
266
|
+
### App Router
|
|
267
|
+
\`\`\`tsx
|
|
268
|
+
"use client";
|
|
269
|
+
|
|
270
|
+
import { DndGridContainer, DndGridSplit, DndGridItem } from 'zerojin/components';
|
|
271
|
+
\`\`\`
|
|
272
|
+
|
|
273
|
+
### Pages Router
|
|
274
|
+
No "use client" directive needed
|
|
275
|
+
|
|
276
|
+
## Common Patterns
|
|
277
|
+
|
|
278
|
+
### IDE Layout
|
|
279
|
+
- Sidebar: 20%
|
|
280
|
+
- Editor: 56%
|
|
281
|
+
- Terminal: 24%
|
|
282
|
+
|
|
283
|
+
### Dashboard (2x2)
|
|
284
|
+
- Equal splits: 50/50
|
|
285
|
+
|
|
286
|
+
### Three Column
|
|
287
|
+
- Left: 20%
|
|
288
|
+
- Center: 60%
|
|
289
|
+
- Right: 20%
|
|
290
|
+
|
|
291
|
+
## Troubleshooting
|
|
292
|
+
|
|
293
|
+
### State Reset on DnD
|
|
294
|
+
- Cause: Server Component in Next.js App Router
|
|
295
|
+
- Fix: Add "use client" to all DndGrid components
|
|
296
|
+
|
|
297
|
+
### Performance Issues
|
|
298
|
+
- Check item count and depth
|
|
299
|
+
- Simplify nested structures
|
|
300
|
+
- Consider lazy loading
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Start the MCP server
|
|
306
|
+
*/
|
|
307
|
+
async start(): Promise<void> {
|
|
308
|
+
const transport = new (
|
|
309
|
+
await import('@modelcontextprotocol/sdk/server/stdio.js')
|
|
310
|
+
).StdioServerTransport();
|
|
311
|
+
await this.server.connect(transport);
|
|
312
|
+
console.error('DndGrid MCP Server started');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ASTParser } from '../utils/ast-parser.js';
|
|
3
|
+
import { Validator } from '../utils/validator.js';
|
|
4
|
+
import { LayoutAnalyzer } from '../utils/layout-analyzer.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Input schema for analyze-layout tool
|
|
8
|
+
*/
|
|
9
|
+
export const AnalyzeLayoutInputSchema = z.object({
|
|
10
|
+
code: z.string().describe('DndGrid code to analyze'),
|
|
11
|
+
analyzePerformance: z.boolean().optional().default(true).describe('Include performance analysis'),
|
|
12
|
+
checkBestPractices: z.boolean().optional().default(true).describe('Check against best practices'),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type AnalyzeLayoutInput = z.infer<typeof AnalyzeLayoutInputSchema>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* analyze-layout tool implementation
|
|
19
|
+
*/
|
|
20
|
+
export async function analyzeLayout(args: AnalyzeLayoutInput) {
|
|
21
|
+
const { code, analyzePerformance, checkBestPractices } = args;
|
|
22
|
+
|
|
23
|
+
let output = `# DndGrid Layout Analysis\n\n`;
|
|
24
|
+
|
|
25
|
+
// Parse the code
|
|
26
|
+
const parser = new ASTParser();
|
|
27
|
+
const layout = parser.parse(code);
|
|
28
|
+
|
|
29
|
+
if (!layout) {
|
|
30
|
+
output += `❌ **Failed to parse layout**\n\n`;
|
|
31
|
+
output += `The code does not contain a valid DndGrid structure.\n`;
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: 'text' as const, text: output }],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Structure analysis
|
|
38
|
+
output += `## Structure\n\n`;
|
|
39
|
+
const metadata = LayoutAnalyzer.calculateMetadata(layout);
|
|
40
|
+
output += `- **Container:** ${layout.width}x${layout.height}px\n`;
|
|
41
|
+
output += `- **Items:** ${metadata.itemCount}\n`;
|
|
42
|
+
output += `- **Splits:** ${metadata.splitCount}\n`;
|
|
43
|
+
output += `- **Max Depth:** ${metadata.maxDepth} levels\n`;
|
|
44
|
+
output += `- **Components:** ${LayoutAnalyzer.collectComponents(layout.child).join(', ')}\n\n`;
|
|
45
|
+
|
|
46
|
+
// Performance analysis
|
|
47
|
+
if (analyzePerformance) {
|
|
48
|
+
output += `## Performance\n\n`;
|
|
49
|
+
output += `**Score:** ${metadata.estimatedPerformance}\n\n`;
|
|
50
|
+
|
|
51
|
+
const recommendations: string[] = [];
|
|
52
|
+
|
|
53
|
+
if (metadata.itemCount > 20) {
|
|
54
|
+
recommendations.push(`Reduce item count from ${metadata.itemCount} to < 20 for better performance`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (metadata.maxDepth > 4) {
|
|
58
|
+
recommendations.push(`Reduce tree depth from ${metadata.maxDepth} to < 4 levels`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (metadata.splitCount > metadata.itemCount) {
|
|
62
|
+
recommendations.push('More splits than items - consider simplifying structure');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (recommendations.length > 0) {
|
|
66
|
+
output += `**Recommendations:**\n\n`;
|
|
67
|
+
recommendations.forEach((rec, i) => {
|
|
68
|
+
output += `${i + 1}. ${rec}\n`;
|
|
69
|
+
});
|
|
70
|
+
output += '\n';
|
|
71
|
+
} else {
|
|
72
|
+
output += `✅ No performance issues detected\n\n`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Best practices
|
|
77
|
+
if (checkBestPractices) {
|
|
78
|
+
output += `## Best Practices Check\n\n`;
|
|
79
|
+
|
|
80
|
+
const checks = [];
|
|
81
|
+
|
|
82
|
+
// Check for "use client"
|
|
83
|
+
if (code.includes('"use client"') || code.includes("'use client'")) {
|
|
84
|
+
checks.push({ rule: 'Next.js "use client" directive', passed: true });
|
|
85
|
+
} else {
|
|
86
|
+
checks.push({
|
|
87
|
+
rule: 'Next.js "use client" directive',
|
|
88
|
+
passed: false,
|
|
89
|
+
message: 'Add "use client"; for Next.js App Router compatibility',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check imports
|
|
94
|
+
if (code.includes('zerojin/components')) {
|
|
95
|
+
checks.push({ rule: 'Correct import path', passed: true });
|
|
96
|
+
} else {
|
|
97
|
+
checks.push({
|
|
98
|
+
rule: 'Correct import path',
|
|
99
|
+
passed: false,
|
|
100
|
+
message: 'Import from "zerojin/components"',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check for reasonable dimensions
|
|
105
|
+
if (layout.width >= 400 && layout.height >= 300) {
|
|
106
|
+
checks.push({ rule: 'Reasonable container dimensions', passed: true });
|
|
107
|
+
} else {
|
|
108
|
+
checks.push({
|
|
109
|
+
rule: 'Reasonable container dimensions',
|
|
110
|
+
passed: false,
|
|
111
|
+
message: 'Container should be at least 400x300px',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
checks.forEach((check) => {
|
|
116
|
+
const icon = check.passed ? '✅' : '❌';
|
|
117
|
+
output += `${icon} ${check.rule}\n`;
|
|
118
|
+
if (!check.passed && check.message) {
|
|
119
|
+
output += ` - ${check.message}\n`;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
output += '\n';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Validation
|
|
126
|
+
const validator = new Validator();
|
|
127
|
+
const validation = validator.validate(layout, true);
|
|
128
|
+
|
|
129
|
+
if (validation.errors.length > 0 || validation.warnings.length > 0) {
|
|
130
|
+
output += `## Issues\n\n`;
|
|
131
|
+
|
|
132
|
+
if (validation.errors.length > 0) {
|
|
133
|
+
output += `**Errors:**\n\n`;
|
|
134
|
+
validation.errors.forEach((err, i) => {
|
|
135
|
+
output += `${i + 1}. [${err.type}] ${err.message}\n`;
|
|
136
|
+
});
|
|
137
|
+
output += '\n';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (validation.warnings.length > 0) {
|
|
141
|
+
output += `**Warnings:**\n\n`;
|
|
142
|
+
validation.warnings.forEach((warn, i) => {
|
|
143
|
+
output += `${i + 1}. ${warn.message}\n`;
|
|
144
|
+
});
|
|
145
|
+
output += '\n';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Refactoring opportunities
|
|
150
|
+
output += `## Refactoring Opportunities\n\n`;
|
|
151
|
+
|
|
152
|
+
const opportunities = findRefactoringOpportunities(layout);
|
|
153
|
+
if (opportunities.length > 0) {
|
|
154
|
+
opportunities.forEach((opp, i) => {
|
|
155
|
+
output += `${i + 1}. **${opp.description}** (${opp.impact})\n`;
|
|
156
|
+
if (opp.example) {
|
|
157
|
+
output += `\`\`\`\n${opp.example}\n\`\`\`\n`;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
output += `✅ No obvious refactoring opportunities\n`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: 'text' as const, text: output }],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function findRefactoringOpportunities(layout: any) {
|
|
170
|
+
const opportunities: Array<{
|
|
171
|
+
description: string;
|
|
172
|
+
impact: 'high' | 'medium' | 'low';
|
|
173
|
+
example?: string;
|
|
174
|
+
}> = [];
|
|
175
|
+
|
|
176
|
+
const metadata = LayoutAnalyzer.calculateMetadata(layout);
|
|
177
|
+
|
|
178
|
+
if (metadata.maxDepth > 5) {
|
|
179
|
+
opportunities.push({
|
|
180
|
+
description: 'Deep nesting detected - consider flattening structure',
|
|
181
|
+
impact: 'high',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (metadata.itemCount > 30) {
|
|
186
|
+
opportunities.push({
|
|
187
|
+
description: 'High item count - consider lazy loading or pagination',
|
|
188
|
+
impact: 'medium',
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return opportunities;
|
|
193
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BUILTIN_TEMPLATES, type TemplateNode } from '../types/template.js';
|
|
3
|
+
import type { LayoutTree, LayoutNode } from '../types/layout.js';
|
|
4
|
+
import { CodeGenerator } from '../utils/code-generator.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Input schema for apply-template tool
|
|
8
|
+
*/
|
|
9
|
+
export const ApplyTemplateInputSchema = z.object({
|
|
10
|
+
templateName: z
|
|
11
|
+
.enum(['ide-layout', 'dashboard-2x2', 'three-column', 'split-view'])
|
|
12
|
+
.describe('Template to apply'),
|
|
13
|
+
components: z
|
|
14
|
+
.record(z.string())
|
|
15
|
+
.describe('Mapping of template slots to component names'),
|
|
16
|
+
width: z.number().optional().default(1200).describe('Container width'),
|
|
17
|
+
height: z.number().optional().default(800).describe('Container height'),
|
|
18
|
+
framework: z
|
|
19
|
+
.enum(['react', 'nextjs-app', 'nextjs-pages'])
|
|
20
|
+
.optional()
|
|
21
|
+
.default('nextjs-app')
|
|
22
|
+
.describe('Target framework'),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type ApplyTemplateInput = z.infer<typeof ApplyTemplateInputSchema>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* apply-template tool implementation
|
|
29
|
+
*/
|
|
30
|
+
export async function applyTemplate(args: ApplyTemplateInput) {
|
|
31
|
+
const { templateName, components, width, height, framework } = args;
|
|
32
|
+
|
|
33
|
+
const template = BUILTIN_TEMPLATES[templateName];
|
|
34
|
+
if (!template) {
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: 'text' as const,
|
|
39
|
+
text: `❌ Template "${templateName}" not found.\n\nAvailable templates: ${Object.keys(BUILTIN_TEMPLATES).join(', ')}`,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Validate that all required slots are provided
|
|
46
|
+
const missingSlots = template.slots.filter((slot) => !components[slot]);
|
|
47
|
+
if (missingSlots.length > 0) {
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: 'text' as const,
|
|
52
|
+
text: `❌ Missing component mappings for slots: ${missingSlots.join(', ')}\n\nRequired slots: ${template.slots.join(', ')}`,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Apply components to template slots
|
|
59
|
+
const layoutNode = applyComponentsToSlots(template.tree, components);
|
|
60
|
+
|
|
61
|
+
// Build LayoutTree
|
|
62
|
+
const layout: LayoutTree = {
|
|
63
|
+
type: 'container',
|
|
64
|
+
width,
|
|
65
|
+
height,
|
|
66
|
+
child: layoutNode,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Generate code
|
|
70
|
+
const generator = new CodeGenerator({
|
|
71
|
+
framework,
|
|
72
|
+
width,
|
|
73
|
+
height,
|
|
74
|
+
});
|
|
75
|
+
const code = generator.generate(layout);
|
|
76
|
+
|
|
77
|
+
// Format output
|
|
78
|
+
let output = `# Applied Template: ${template.name}\n\n`;
|
|
79
|
+
output += `${template.description}\n\n`;
|
|
80
|
+
output += `## Preview\n\n\`\`\`\n${template.preview}\n\`\`\`\n\n`;
|
|
81
|
+
output += `## Component Mapping\n\n`;
|
|
82
|
+
template.slots.forEach((slot) => {
|
|
83
|
+
output += `- **${slot}:** ${components[slot]}\n`;
|
|
84
|
+
});
|
|
85
|
+
output += `\n## Generated Code\n\n\`\`\`tsx\n${code}\`\`\`\n`;
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
content: [{ type: 'text' as const, text: output }],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Apply component mappings to template slots
|
|
94
|
+
*/
|
|
95
|
+
function applyComponentsToSlots(
|
|
96
|
+
templateNode: TemplateNode,
|
|
97
|
+
components: Record<string, string>
|
|
98
|
+
): LayoutNode {
|
|
99
|
+
if (templateNode.type === 'item') {
|
|
100
|
+
if (!templateNode.slot) {
|
|
101
|
+
throw new Error('Template item node missing slot');
|
|
102
|
+
}
|
|
103
|
+
const component = components[templateNode.slot];
|
|
104
|
+
if (!component) {
|
|
105
|
+
throw new Error(`No component provided for slot: ${templateNode.slot}`);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
type: 'item',
|
|
109
|
+
component,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Split node
|
|
114
|
+
if (!templateNode.primary || !templateNode.secondary) {
|
|
115
|
+
throw new Error('Template split node missing children');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
type: 'split',
|
|
120
|
+
direction: templateNode.direction!,
|
|
121
|
+
ratio: templateNode.ratio!,
|
|
122
|
+
primary: applyComponentsToSlots(templateNode.primary, components),
|
|
123
|
+
secondary: applyComponentsToSlots(templateNode.secondary, components),
|
|
124
|
+
};
|
|
125
|
+
}
|