UNPKG

wireframe-mcp

Version:
1,137 lines (1,115 loc) 47.2 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; const server = new Server({ name: 'figma-wireframe-bridge', version: '1.0.0', }); // Token-based communication setup const EXPORT_DIR = path.join(os.homedir(), '.figma-exports'); const TOKENS_FILE = path.join(EXPORT_DIR, 'tokens.json'); const LATEST_EXPORT_FILE = path.join(EXPORT_DIR, 'latest-wireframe.json'); // Ensure export directory exists function ensureExportDirectory() { if (!fs.existsSync(EXPORT_DIR)) { fs.mkdirSync(EXPORT_DIR, { recursive: true }); } } // Generate unique token function generateToken() { const timestamp = Date.now().toString(36); const random = Math.random().toString(36).substring(2, 8); return `figma_${timestamp}_${random}`; } // Save token mapping function saveTokenMapping(token, componentData) { try { ensureExportDirectory(); let tokens = {}; if (fs.existsSync(TOKENS_FILE)) { tokens = JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf8')); } tokens[token] = { component: componentData, created: new Date().toISOString(), expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() // 24 hours }; fs.writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2)); } catch (error) { console.error('Error saving token mapping:', error); } } // Get component from wireframe data function getComponentFromWireframe(componentId) { try { console.log('🔍 Searching for component:', componentId); // Check real-time export first const realTimeData = readRealTimeExport(); console.log('🔍 Real-time data structure:', { hasData: !!realTimeData, hasWireframe: !!(realTimeData && realTimeData.wireframe), hasComponents: !!(realTimeData && realTimeData.wireframe && realTimeData.wireframe.components), wireframeKeys: realTimeData?.wireframe ? Object.keys(realTimeData.wireframe) : 'no wireframe' }); if (realTimeData && realTimeData.wireframe && realTimeData.wireframe.components) { console.log('🔍 Checking real-time export for component:', componentId); console.log('🔍 Components array length:', realTimeData.wireframe.components.length); console.log('🔍 Available components:', realTimeData.wireframe.components.map((c) => ({ component: c.component, cleanName: c.cleanName, id: c.id }))); const component = realTimeData.wireframe.components.find((comp) => { const matches = comp.id === componentId || comp.component === componentId || comp.cleanName === componentId || comp.tokenId === componentId || comp.name === componentId; console.log(`🔍 Comparing "${componentId}" with:`, { id: comp.id, component: comp.component, cleanName: comp.cleanName, tokenId: comp.tokenId, name: comp.name, matches }); return matches; }); if (component) { console.log('✅ Found component in real-time export:', componentId); return component; } else { console.log('❌ Component not found in real-time export:', componentId); } } // Check latest wireframe const wireframeData = readLatestWireframe(); if (wireframeData && wireframeData.components) { console.log('🔍 Checking latest wireframe for component:', componentId); const component = wireframeData.components.find((comp) => comp.id === componentId || comp.component === componentId || comp.cleanName === componentId || comp.tokenId === componentId || comp.name === componentId); if (component) { console.log('✅ Found component in latest wireframe:', componentId); return component; } else { console.log('❌ Component not found in latest wireframe:', componentId); } } console.log('❌ Component not found anywhere:', componentId); return null; } catch (error) { console.error('❌ Error reading wireframe component:', error); return null; } } // Get list of available components function getAvailableComponents() { try { const components = []; // Check real-time export first const realTimeData = readRealTimeExport(); if (realTimeData && realTimeData.wireframe && realTimeData.wireframe.components) { console.log('📋 Found components in real-time export:', realTimeData.wireframe.components.length); components.push(...realTimeData.wireframe.components.map((comp) => ({ id: comp.id || comp.tokenId || comp.component || comp.cleanName, name: comp.component || comp.cleanName || comp.name, type: comp.suggestedComponentType || 'component', tokenId: comp.tokenId, originalId: comp.id }))); } // Check latest wireframe if no real-time data if (components.length === 0) { const wireframeData = readLatestWireframe(); if (wireframeData && wireframeData.components) { console.log('📋 Found components in latest wireframe:', wireframeData.components.length); components.push(...wireframeData.components.map((comp) => ({ id: comp.id, name: comp.name, type: comp.type }))); } } if (components.length === 0) { console.log('❌ No components found in any export data'); } else { console.log('✅ Available components:', components.map(c => `${c.name} (${c.id})`)); } return components; } catch (error) { console.error('❌ Error getting available components:', error); return []; } } // Get component data by token function getComponentByToken(token) { try { // Check if token exists in tokens file if (!fs.existsSync(TOKENS_FILE)) { console.log('❌ No tokens file found'); return null; } const tokens = JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf8')); const tokenData = tokens[token]; if (!tokenData) { console.log('❌ Token not found:', token); return null; } // Check if token is expired if (new Date(tokenData.expires) < new Date()) { console.log('❌ Token expired:', token); delete tokens[token]; fs.writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2)); return null; } console.log('✅ Token found and valid:', token); return tokenData.component; } catch (error) { console.error('❌ Error reading token mapping:', error); return null; } } // Process a new token from Figma export function processFigmaToken(token, componentData) { try { ensureExportDirectory(); const tokenData = { component: componentData, created: new Date().toISOString(), expires: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() // 24 hours }; let tokens = {}; if (fs.existsSync(TOKENS_FILE)) { tokens = JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf8')); } tokens[token] = tokenData; fs.writeFileSync(TOKENS_FILE, JSON.stringify(tokens, null, 2)); console.log(`Token ${token} processed and saved successfully`); return true; } catch (error) { console.error('Error processing token:', error); return false; } } // Read real-time export data function readRealTimeExport() { try { // Check for real-time export data in the expected location const realTimeFile = path.join(EXPORT_DIR, 'real-time-export.json'); console.log('🔍 Looking for real-time export at:', realTimeFile); console.log('🔍 Export directory exists:', fs.existsSync(EXPORT_DIR)); console.log('🔍 Real-time file exists:', fs.existsSync(realTimeFile)); if (!fs.existsSync(realTimeFile)) { console.log('❌ No real-time export file found at:', realTimeFile); return null; } const data = fs.readFileSync(realTimeFile, 'utf8'); const exportData = JSON.parse(data); console.log('✅ Real-time export data found:', exportData.wireframe ? 'Yes' : 'No'); if (exportData.wireframe && exportData.wireframe.components) { console.log('📋 Available component IDs:', exportData.wireframe.components.map((comp) => comp.id)); } return exportData; } catch (error) { console.error('❌ Error reading real-time export:', error); return null; } } // Read latest wireframe export function readLatestWireframe() { try { if (fs.existsSync(LATEST_EXPORT_FILE)) { const data = fs.readFileSync(LATEST_EXPORT_FILE, 'utf8'); return JSON.parse(data); } } catch (error) { console.error('Error reading wireframe export:', error); } return null; } // Tool: Enhanced Figma → Code workflow with real data server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'import_figma_wireframe', description: 'Import the latest wireframe design data from Figma plugin export', inputSchema: { type: 'object', properties: { refresh: { type: 'boolean', description: 'Force refresh of the latest export file', default: false } } } }, { name: 'extract_figma_design', description: 'Extract comprehensive design data from a Figma component blueprint', inputSchema: { type: 'object', properties: { component_data: { type: 'object', description: 'The Figma component data to extract design information from' } }, required: ['component_data'] } }, { name: 'generate_code_from_blueprint', description: 'Generate code implementation from a Figma component blueprint', inputSchema: { type: 'object', properties: { blueprint: { type: 'object', description: 'The component blueprint containing design data' }, framework: { type: 'string', description: 'Target framework (react, vue, svelte, html, etc.)', default: 'react' } }, required: ['blueprint'] } }, { name: 'analyze_design_system', description: 'Analyze the design system from the latest wireframe export', inputSchema: { type: 'object', properties: { aspect: { type: 'string', description: 'Aspect to analyze (colors, typography, spacing, all)', enum: ['colors', 'typography', 'spacing', 'all'], default: 'all' } } } }, { name: 'generate_component_code', description: 'Generate code for a specific component from the wireframe', inputSchema: { type: 'object', properties: { componentId: { type: 'string', description: 'ID of the component to generate code for' }, framework: { type: 'string', description: 'Target framework (react, vue, svelte, html, etc.)', default: 'react' } }, required: ['componentId'] } }, { name: 'debug_component_matching', description: 'Debug component matching and show all available component data', inputSchema: { type: 'object', properties: { componentId: { type: 'string', description: 'Optional component ID to debug specifically' } } } } ] }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (!args) { throw new Error('No arguments provided'); } switch (name) { case 'import_figma_wireframe': return await importFigmaWireframe(args.refresh || false); case 'extract_figma_design': return await extractFigmaDesign(args.component_data); case 'generate_code_from_blueprint': return await generateCodeFromBlueprint(args.blueprint, args.framework || 'react'); case 'generate_component_code': return await generateComponentCode(args.componentId, args.framework || 'react'); case 'analyze_design_system': return await analyzeDesignSystem(args.aspect || 'all'); case 'debug_component_matching': return await debugComponentMatching(args.componentId); default: throw new Error(`Unknown tool: ${name}`); } }); async function importFigmaWireframe(refresh = false) { try { ensureExportDirectory(); // Check for real-time export data first const realTimeData = readRealTimeExport(); const wireframeData = realTimeData || readLatestWireframe(); if (!wireframeData) { return { content: [ { type: 'text', text: '❌ **No Figma designs found**\n\n**Setup:**\n1. Export from Figma plugin → "⚡ Real-time Export"\n2. Or save to `~/.figma-exports/latest-wireframe.json`\n\n**Then:** Run `import_figma_wireframe` again' } ] }; } const data = wireframeData.wireframe || wireframeData; const { metadata, designSystem, components, context } = data; let output = `📦 **Figma Wireframe Imported**\n\n`; output += `**Project:** ${metadata?.figmaFileId || 'N/A'} | v${metadata?.version || 'N/A'}\n`; output += `**Components:** ${components?.length || 0} total (${components?.filter((c) => c.type !== 'page' && !c.name?.toLowerCase().includes('page')).length || 0} UI)\n`; output += `**Design System:** ${designSystem?.colors?.length || 0} colors, ${designSystem?.typography?.length || 0} fonts, ${designSystem?.spacing?.length || 0} spacing, ${designSystem?.shadows?.length || 0} shadows\n`; output += `**Context:** ${context?.aiPrompts?.length || 0} prompts, ${context?.implementationNotes?.length || 0} notes\n\n`; output += `**Available Actions:**\n`; output += `• \`generate_component_code\` - Generate React/Vue/HTML from any component\n`; output += `• \`analyze_design_system\` - View design tokens and system details\n`; output += `• \`extract_figma_design\` - Get detailed component data\n\n`; output += `*Updated: ${new Date(metadata?.timestamp || Date.now()).toLocaleString()}*`; return { content: [ { type: 'text', text: output } ] }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Import failed:** ${error instanceof Error ? error.message : 'Unknown error'}` } ] }; } } async function extractFigmaDesign(componentData) { try { // Check if we have a token in the component data if (componentData.token) { // Try to get the actual component data from the token const actualData = getComponentByToken(componentData.token); if (actualData) { componentData = actualData; } } // If componentData is still a token string, try to resolve it if (typeof componentData === 'string' && componentData.startsWith('figma_')) { const actualData = getComponentByToken(componentData); if (actualData) { componentData = actualData; } } // Ensure we have valid component data if (!componentData || !componentData.component) { return { content: [ { type: 'text', text: `❌ **Invalid component data provided**\n\n` + `Please ensure you're passing valid Figma component data or a valid token.` } ] }; } const component = componentData.component; const name = component.component || component.name || 'Unknown Component'; return { content: [ { type: 'text', text: `✅ **Successfully extracted design data for "${name}"** **Component Details:** - **Type:** ${component.type || 'component'} - **Size:** ${component.size?.width || 'auto'} × ${component.size?.height || 'auto'} - **Layout System:** ${component.layout?.autoLayout?.enabled ? 'auto-layout' : 'standard'} - **Colors:** ${component.enhancedVisuals?.fills?.length || 0} defined - **Typography:** ${component.preciseTypography ? 'Advanced' : 'Basic'} styles - **Accessibility:** ${component.semantic?.role ? 'Configured' : 'Not configured'} - **Responsive:** ${component.responsive ? 'Yes' : 'No'} **Raw Data:** \`\`\`json ${JSON.stringify(componentData, null, 2)} \`\`\` **Extracted at:** ${new Date().toISOString()} **Source:** Figma Wireframe Plugin` } ] }; } catch (error) { return { content: [ { type: 'text', text: `❌ Error extracting Figma design: ${error instanceof Error ? error.message : 'Unknown error'}` } ] }; } } async function generateCodeFromBlueprint(blueprint, framework = 'react') { try { // Ensure we have valid blueprint data if (!blueprint || !blueprint.component) { return { content: [ { type: 'text', text: `❌ **Invalid blueprint data provided**\n\n` + `Please ensure you're passing valid Figma component blueprint data.` } ] }; } const component = blueprint.component; const name = component.component || component.name || 'Component'; const className = name.replace(/[^a-zA-Z0-9]/g, ''); let code = ''; const fileExtension = framework === 'html' ? 'html' : 'jsx'; switch (framework.toLowerCase()) { case 'react': code = generateReactComponentCode(component, className); break; case 'vue': code = generateVueComponentCode(component, className); break; case 'html': code = generateHTMLComponentCode(component, className); break; default: code = generateReactComponentCode(component, className); } return { content: [ { type: 'text', text: `✅ **Generated ${framework} component from blueprint** **Component:** ${name} **File:** \`${className}.${fileExtension}\` \`\`\`${framework === 'html' ? 'html' : 'jsx'} ${code} \`\`\` **Component Details:** - **Type:** ${component.type || 'component'} - **Size:** ${component.size?.width || 'auto'} × ${component.size?.height || 'auto'} - **Styling:** ${Object.keys(component.enhancedVisuals || {}).length} style properties - **Framework:** ${framework} **Next Steps:** 1. Save this code to your project 2. Install any required dependencies 3. Import and use the component *Blueprint-based code generation provides the most accurate component implementation!*` } ] }; } catch (error) { return { content: [ { type: 'text', text: `❌ Error generating code from blueprint: ${error instanceof Error ? error.message : 'Unknown error'}` } ] }; } } async function generateComponentCode(componentId, framework = 'react') { try { console.log('🎯 Generating code for component ID:', componentId); // First try to get component from wireframe data let componentData = getComponentFromWireframe(componentId); // If not found in wireframe, try token-based lookup if (!componentData) { console.log('🔍 Trying token-based lookup for:', componentId); componentData = getComponentByToken(componentId); } if (componentData) { console.log('✅ Found component data:', { name: componentData.name, type: componentData.type, hasEnhancedVisuals: !!componentData.enhancedVisuals, hasChildren: componentData.children?.length || 0, stylingKeys: Object.keys(componentData.styling || {}).length }); } if (!componentData) { // Get list of available components for error message const availableComponents = getAvailableComponents(); const availableList = availableComponents.length > 0 ? availableComponents.map(comp => `- ${comp.name} (${comp.id})`).join('\n') : '- No components available'; return { content: [ { type: 'text', text: `❌ **Component not found:** \`${componentId}\`\n\n**Available:**\n${availableList}\n\n**Tip:** Use token from Figma export or check available components above` } ] }; } const name = componentData.name || componentData.component || componentData.cleanName || 'Component'; let className = name.replace(/[^a-zA-Z0-9]/g, '') || 'Component'; // Ensure component name starts with a capital letter and doesn't start with a number if (/^\d/.test(className)) { className = 'Component' + className; } if (!/^[A-Z]/.test(className)) { className = className.charAt(0).toUpperCase() + className.slice(1); } let code = ''; const fileExtension = 'jsx'; // Always generate JSX for now switch (framework.toLowerCase()) { case 'react': code = generateReactComponentCode(componentData, className); break; case 'vue': code = generateVueComponentCode(componentData, className); break; case 'html': code = generateHTMLComponentCode(componentData, className); break; default: code = generateReactComponentCode(componentData, className); } // Enhanced component information const semanticInfo = componentData.semantic ? { role: componentData.semantic.role || 'div', isInteractive: componentData.semantic.isInteractive || false, isContainer: componentData.semantic.isContainer || false, hasHoverState: componentData.semantic.hasHoverState || false } : null; const figmaInfo = componentData.componentRelationships?.mainComponent ? { isInstance: true, mainComponent: componentData.componentRelationships.mainComponent.name } : null; const designSystemInfo = componentData.designTokens ? { hasTokens: true, tokenCount: Object.keys(componentData.designTokens).length } : null; return { content: [ { type: 'text', text: `⚡ **${framework.toUpperCase()} Component Generated** **Component:** \`${className}\` (${name}) **Token:** \`${componentId}\` (24h expiry) ${semanticInfo ? `**Semantic:** ${semanticInfo.role}${semanticInfo.isInteractive ? ' (interactive)' : ''}${semanticInfo.hasHoverState ? ' (hover states)' : ''}` : ''} ${figmaInfo ? `**Figma:** Instance of "${figmaInfo.mainComponent}"` : ''} ${designSystemInfo ? `**Design System:** ${designSystemInfo.tokenCount} tokens` : ''} \`\`\`${framework === 'html' ? 'html' : 'jsx'} ${code} \`\`\` **Specs:** ${componentData.type || 'component'} | ${componentData.props?.width || 'auto'}×${componentData.props?.height || 'auto'} | ${Object.keys(componentData.styling || {}).length} styles ${componentData.children?.length ? `**Children:** ${componentData.children.length} nested components` : ''} **Usage:** Save as \`${className}.${fileExtension}\` and import into your project.` } ] }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Generation failed:** ${error instanceof Error ? error.message : 'Unknown error'}` } ] }; } } async function analyzeDesignSystem(aspect = 'all') { try { const wireframeData = readLatestWireframe(); if (!wireframeData) { return { content: [ { type: 'text', text: '📋 **No Figma designs available**\n\nPlease use `import_figma_wireframe` first to load your designs from Figma.' } ] }; } const data = wireframeData.wireframe || wireframeData; const { designSystem } = data; let output = `🎨 **Design System**\n\n`; if (aspect === 'all' || aspect === 'colors') { output += `**Colors (${designSystem?.colors?.length || 0}):**\n`; if (designSystem?.colors?.length) { designSystem.colors.forEach((color) => { output += `- **${color.name}** (${color.value})\n`; }); } else { output += '- No colors defined\n'; } output += '\n'; } if (aspect === 'all' || aspect === 'typography') { output += `**Typography (${designSystem?.typography?.length || 0}):**\n`; if (designSystem?.typography?.length) { designSystem.typography.forEach((typography) => { output += `- **${typography.name}** (${typography.value})\n`; }); } else { output += '- No typography defined\n'; } output += '\n'; } if (aspect === 'all' || aspect === 'spacing') { output += `**Spacing (${designSystem?.spacing?.length || 0}):**\n`; if (designSystem?.spacing?.length) { designSystem.spacing.forEach((spacing) => { output += `- **${spacing.name}** (${spacing.value})\n`; }); } else { output += '- No spacing defined\n'; } output += '\n'; } output += `**Update Flow:**\n`; output += `1. Modify in Figma → Export → \`import_figma_wireframe\` → \`generate_component_code\`\n\n`; output += `**Commands:** \`generate_component_code\` | \`import_figma_wireframe\``; return { content: [ { type: 'text', text: output } ] }; } catch (error) { return { content: [ { type: 'text', text: `❌ **Analysis failed:** ${error instanceof Error ? error.message : 'Unknown error'}` } ] }; } } async function debugComponentMatching(componentId) { try { let output = `🔧 **Debug: Component Matching**\n\n`; // Check real-time export const realTimeData = readRealTimeExport(); if (realTimeData && realTimeData.wireframe && realTimeData.wireframe.components) { output += `**Real-time Export Data:**\n`; output += `- Components found: ${realTimeData.wireframe.components.length}\n`; output += `- Token: ${realTimeData.token || 'None'}\n\n`; output += `**Available Components:**\n`; realTimeData.wireframe.components.forEach((comp, index) => { output += `${index + 1}. **${comp.component || comp.name || 'Unnamed'}**\n`; output += ` - ID: ${comp.id || 'None'}\n`; output += ` - Component: ${comp.component || 'None'}\n`; output += ` - Clean Name: ${comp.cleanName || 'None'}\n`; output += ` - Token ID: ${comp.tokenId || 'None'}\n`; output += ` - Has Enhanced Visuals: ${!!comp.enhancedVisuals}\n`; output += ` - Type: ${comp.suggestedComponentType || 'Unknown'}\n`; if (comp.size) { output += ` - Size: ${comp.size.width}x${comp.size.height}\n`; } output += `\n`; }); } else { output += `**Real-time Export Data:** None found\n\n`; } // Check tokens if (fs.existsSync(TOKENS_FILE)) { const tokens = JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf8')); const tokenKeys = Object.keys(tokens); output += `**Token Storage:**\n`; output += `- Total tokens: ${tokenKeys.length}\n`; output += `- Recent tokens: ${tokenKeys.slice(-5).join(', ')}\n\n`; if (componentId && tokens[componentId]) { output += `**Token "${componentId}" Data:**\n`; const tokenData = tokens[componentId]; output += `- Created: ${tokenData.created}\n`; output += `- Expires: ${tokenData.expires}\n`; output += `- Component Name: ${tokenData.component?.component || tokenData.component?.name || 'Unknown'}\n`; output += `- Has Enhanced Visuals: ${!!tokenData.component?.enhancedVisuals}\n\n`; } } else { output += `**Token Storage:** No tokens file found\n\n`; } if (componentId) { output += `**Matching Test for "${componentId}":**\n`; const foundComponent = getComponentFromWireframe(componentId); if (foundComponent) { output += `✅ Component found!\n`; output += `- Name: ${foundComponent.component || foundComponent.name}\n`; output += `- Type: ${foundComponent.suggestedComponentType}\n`; output += `- Has Enhanced Visuals: ${!!foundComponent.enhancedVisuals}\n`; } else { output += `❌ Component not found\n`; // Try token lookup const tokenComponent = getComponentByToken(componentId); if (tokenComponent) { output += `✅ Found via token lookup!\n`; output += `- Name: ${tokenComponent.component || tokenComponent.name}\n`; } else { output += `❌ Not found via token lookup either\n`; } } } return { content: [ { type: 'text', text: output } ] }; } catch (error) { return { content: [ { type: 'text', text: `❌ Debug error: ${error instanceof Error ? error.message : 'Unknown error'}` } ] }; } } // Code generation functions function generateReactComponentCode(component, className) { // Handle both direct component data and nested component structures const actualComponent = component; const { name, styling, props, codeHints, enhancedVisuals, designTokens, size, position, children, precisePosition, layout, semantic, figmaMetadata } = actualComponent; console.log('🎨 Generating code for component:', { name: actualComponent.component || actualComponent.name, hasEnhancedVisuals: !!enhancedVisuals, enhancedVisualsKeys: enhancedVisuals ? Object.keys(enhancedVisuals) : [], hasSize: !!size, hasPrecisePosition: !!precisePosition, isInteractive: semantic?.isInteractive, role: semantic?.role }); // Extract styling from enhancedVisuals if available const visualStyles = enhancedVisuals ? extractStylesFromEnhancedVisuals(enhancedVisuals) : {}; const combinedStyles = { ...visualStyles, ...styling }; // Get dimensions from multiple possible sources to handle various data structures const width = size?.width || enhancedVisuals?.renderBounds?.width || precisePosition?.visualBounds?.width || layout?.size?.width || actualComponent.size?.width || 'auto'; const height = size?.height || enhancedVisuals?.renderBounds?.height || precisePosition?.visualBounds?.height || layout?.size?.height || actualComponent.size?.height || 'auto'; // Generate child components if they exist const childComponents = children && children.length > 0 ? generateChildComponents(children, className) : ''; const childElements = children && children.length > 0 ? generateChildElements(children) : '{children}'; // Determine the appropriate HTML element based on semantic role const semanticRole = semantic?.role || 'div'; const isInteractive = semantic?.isInteractive || false; const isContainer = semantic?.isContainer || false; // Generate appropriate props based on component type const componentProps = []; if (isInteractive) { componentProps.push('onClick?: () => void;'); componentProps.push('onMouseEnter?: () => void;'); componentProps.push('onMouseLeave?: () => void;'); } if (semanticRole === 'button') { componentProps.push('type?: "button" | "submit" | "reset";'); componentProps.push('disabled?: boolean;'); } // Generate accessibility attributes const accessibilityProps = []; if (semanticRole === 'button') { accessibilityProps.push('role="button"'); accessibilityProps.push('tabIndex={0}'); } if (isInteractive) { accessibilityProps.push('onKeyDown={(e) => e.key === "Enter" && onClick?.()}'); } return `import React from 'react'; import './${className}.css'; ${childComponents} interface ${className}Props { ${Object.keys(props || {}).map(prop => `${prop}?: ${getTypeForProp(prop, props[prop])};`).join('\n ')} ${componentProps.join('\n ')} children?: React.ReactNode; className?: string; } export const ${className}: React.FC<${className}Props> = ({ ${Object.keys(props || {}).join(',\n ')}, ${isInteractive ? 'onClick, onMouseEnter, onMouseLeave,' : ''} children, className = '' }) => { return ( <${semanticRole === 'button' ? 'button' : 'div'} className={\`${className.toLowerCase()}-component \${className}\`} ${accessibilityProps.join('\n ')} ${isInteractive ? 'onClick={onClick}' : ''} ${isInteractive ? 'onMouseEnter={onMouseEnter}' : ''} ${isInteractive ? 'onMouseLeave={onMouseLeave}' : ''} style={{ width: ${typeof width === 'number' ? `'${width}px'` : width === 'auto' ? "'auto'" : `'${width}'`}, height: ${typeof height === 'number' ? `'${height}px'` : height === 'auto' ? "'auto'" : `'${height}'`}, position: 'relative', ${Object.entries(combinedStyles).map(([key, value]) => `${key}: '${value}'`).join(',\n ')} }} > ${childElements} </${semanticRole === 'button' ? 'button' : 'div'}> ); }; export default ${className};`; } function generateVueComponentCode(component, className) { const { name, styling, props, codeHints, enhancedVisuals, size } = component; // Extract styling from enhancedVisuals if available const visualStyles = enhancedVisuals ? extractStylesFromEnhancedVisuals(enhancedVisuals) : {}; const combinedStyles = { ...visualStyles, ...styling }; // Get dimensions from size or enhancedVisuals const width = size?.width || enhancedVisuals?.renderBounds?.width || 'auto'; const height = size?.height || enhancedVisuals?.renderBounds?.height || 'auto'; return `<template> <div class="${className.toLowerCase()}-component" :style="componentStyles" > <slot /> </div> </template> <script setup lang="ts"> import { computed } from 'vue'; interface Props { ${Object.keys(props || {}).map(prop => `${prop}?: ${typeof props[prop]};`).join('\n ')} className?: string; } const props = withDefaults(defineProps<Props>(), { ${Object.keys(props || {}).map(prop => `${prop}: undefined`).join(',\n ')}, className: '' }); const componentStyles = computed(() => ({ width: '${width}px', height: '${height}px', ${Object.entries(combinedStyles).map(([key, value]) => `${key}: '${value}'`).join(',\n ')} })); </script> <style scoped> .${className.toLowerCase()}-component { /* Component styles will be applied via inline styles */ } </style>`; } function generateHTMLComponentCode(component, className) { const { name, styling, props, codeHints, enhancedVisuals, size } = component; // Extract styling from enhancedVisuals if available const visualStyles = enhancedVisuals ? extractStylesFromEnhancedVisuals(enhancedVisuals) : {}; const combinedStyles = { ...visualStyles, ...styling }; // Get dimensions from size or enhancedVisuals const width = size?.width || enhancedVisuals?.renderBounds?.width || 'auto'; const height = size?.height || enhancedVisuals?.renderBounds?.height || 'auto'; return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>${name}</title> <style> .${className.toLowerCase()}-component { width: ${width}px; height: ${height}px; ${Object.entries(combinedStyles).map(([key, value]) => `${key}: ${value};`).join('\n ')} } </style> </head> <body> <div class="${className.toLowerCase()}-component"> <!-- Component content goes here --> </div> </body> </html>`; } function generateChildComponents(children, parentClassName) { return children.map((child, index) => { const childName = child.name || child.component || `Child${index + 1}`; const childClassName = `${parentClassName}${childName.replace(/[^a-zA-Z0-9]/g, '')}`; const childStyles = child.enhancedVisuals ? extractStylesFromEnhancedVisuals(child.enhancedVisuals) : {}; return `const ${childClassName}: React.FC = () => { return ( <div style={{ position: 'absolute', left: '${child.position?.x || 0}px', top: '${child.position?.y || 0}px', width: '${child.size?.width || 'auto'}px', height: '${child.size?.height || 'auto'}px', ${Object.entries(childStyles).map(([key, value]) => `${key}: '${value}'`).join(',\n ')} }} /> ); };`; }).join('\n\n'); } function generateChildElements(children) { return children.map((child, index) => { const childName = child.name || child.component || `Child${index + 1}`; const childClassName = `${childName.replace(/[^a-zA-Z0-9]/g, '')}`; return `<${childClassName} />`; }).join('\n '); } function extractStylesFromEnhancedVisuals(enhancedVisuals) { const styles = {}; console.log('🎨 Extracting styles from enhancedVisuals:', { hasFills: !!(enhancedVisuals.fills && enhancedVisuals.fills.length > 0), hasStrokes: !!(enhancedVisuals.strokes && enhancedVisuals.strokes.length > 0), hasEffects: !!(enhancedVisuals.effects && enhancedVisuals.effects.length > 0), hasBorderRadius: !!enhancedVisuals.borderRadius, opacity: enhancedVisuals.opacity }); // Extract fills (backgrounds) if (enhancedVisuals.fills && enhancedVisuals.fills.length > 0) { const fill = enhancedVisuals.fills[0]; if (fill.visible) { if (fill.type === 'SOLID') { styles.backgroundColor = fill.color; if (fill.opacity !== 1) { styles.backgroundColor = fill.color.replace('1)', `${fill.opacity})`); } } else if (fill.type === 'GRADIENT_LINEAR') { styles.background = `linear-gradient(${fill.gradientTransform || 'to bottom'}, ${fill.gradientStops?.map(stop => `${stop.color} ${stop.position}%`).join(', ')})`; } } } // Extract border radius if (enhancedVisuals.borderRadius) { if (enhancedVisuals.borderRadius.uniform) { styles.borderRadius = `${enhancedVisuals.borderRadius.uniform}px`; } else if (enhancedVisuals.borderRadius.topLeft || enhancedVisuals.borderRadius.topRight || enhancedVisuals.borderRadius.bottomLeft || enhancedVisuals.borderRadius.bottomRight) { styles.borderRadius = `${enhancedVisuals.borderRadius.topLeft || 0}px ${enhancedVisuals.borderRadius.topRight || 0}px ${enhancedVisuals.borderRadius.bottomRight || 0}px ${enhancedVisuals.borderRadius.bottomLeft || 0}px`; } } // Extract strokes if (enhancedVisuals.strokes && enhancedVisuals.strokes.length > 0) { const stroke = enhancedVisuals.strokes[0]; if (stroke.visible) { const strokeColor = stroke.opacity !== 1 ? stroke.color.replace('1)', `${stroke.opacity})`) : stroke.color; styles.border = `${enhancedVisuals.strokeWeight || 1}px solid ${strokeColor}`; } } // Extract effects (shadows, glows) if (enhancedVisuals.effects && enhancedVisuals.effects.length > 0) { const shadows = enhancedVisuals.effects .filter((effect) => effect.visible) .map((effect) => { if (effect.type === 'DROP_SHADOW') { return `${effect.offset?.x || 0}px ${effect.offset?.y || 0}px ${effect.radius || 0}px ${effect.color}`; } else if (effect.type === 'INNER_SHADOW') { return `inset ${effect.offset?.x || 0}px ${effect.offset?.y || 0}px ${effect.radius || 0}px ${effect.color}`; } return ''; }) .filter(Boolean); if (shadows.length > 0) { styles.boxShadow = shadows.join(', '); } } // Extract opacity if (enhancedVisuals.opacity !== undefined && enhancedVisuals.opacity !== 1) { styles.opacity = enhancedVisuals.opacity; } // Extract blend mode if (enhancedVisuals.blendMode && enhancedVisuals.blendMode !== 'PASS_THROUGH') { styles.mixBlendMode = enhancedVisuals.blendMode.toLowerCase().replace('_', '-'); } // Extract backdrop blur if (enhancedVisuals.backdropBlur && enhancedVisuals.backdropBlur > 0) { styles.backdropFilter = `blur(${enhancedVisuals.backdropBlur}px)`; } // Extract typography if available if (enhancedVisuals.typography) { if (enhancedVisuals.typography.fontFamily) { styles.fontFamily = enhancedVisuals.typography.fontFamily; } if (enhancedVisuals.typography.fontSize) { styles.fontSize = `${enhancedVisuals.typography.fontSize}px`; } if (enhancedVisuals.typography.fontWeight) { styles.fontWeight = enhancedVisuals.typography.fontWeight; } if (enhancedVisuals.typography.lineHeight) { styles.lineHeight = enhancedVisuals.typography.lineHeight; } } return styles; } function getTypeForProp(propName, propValue) { if (typeof propValue === 'string') return 'string'; if (typeof propValue === 'number') return 'number'; if (typeof propValue === 'boolean') return 'boolean'; if (Array.isArray(propValue)) return 'any[]'; if (typeof propValue === 'object') return 'object'; // Smart type inference based on prop name if (propName.includes('onClick') || propName.includes('onPress')) return '() => void'; if (propName.includes('onChange')) return '(value: any) => void'; if (propName.includes('disabled') || propName.includes('visible') || propName.includes('active')) return 'boolean'; if (propName.includes('width') || propName.includes('height') || propName.includes('size')) return 'number'; if (propName.includes('color') || propName.includes('text') || propName.includes('title')) return 'string'; return 'any'; } // Start the MCP server const transport = new StdioServerTransport(); server.connect(transport);