15 KiB
15 KiB
SVG-Based Wireframe Generation - Integration Guide
This guide explains how to implement and integrate the SVG-based wireframe generation system that converts natural language prompts into precise, scalable vector graphics.
🎯 Why SVG Instead of JSON?
Advantages of SVG Approach:
- Precise Positioning: Exact coordinates and dimensions
- Better Performance: Direct rendering without parsing overhead
- Scalable Graphics: Vector-based, resolution-independent
- Rich Styling: Colors, gradients, shadows, and effects
- Standard Format: Widely supported across platforms
Comparison:
| Aspect | JSON Approach | SVG Approach |
|---|---|---|
| Precision | Approximate positioning | Exact positioning |
| Performance | Slower (parsing + generation) | Faster (direct rendering) |
| Styling | Limited color options | Full CSS styling support |
| Complexity | Simple shapes only | Complex paths and effects |
| Maintenance | Frontend logic heavy | Backend logic heavy |
🏗️ System Architecture
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend │ │ Claude AI │
│ (React) │◄──►│ (Flask) │◄──►│ (API) │
│ │ │ │ │ │
│ • tldraw Canvas │ │ • Prompt │ │ • Natural │
│ • SVG Parser │ │ Processing │ │ Language │
│ • Response │ │ • SVG Generation │ │ Analysis │
│ Handler │ │ • Response │ │ • Layout │
└─────────────────┘ │ Routing │ │ Generation │
└──────────────────┘ └─────────────────┘
🔄 Data Flow
1. User Input
User types: "Dashboard with header, sidebar, and 3 stats cards"
2. Frontend Request
const response = await fetch('/generate-wireframe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: userPrompt })
})
3. Backend Processing
# Flask backend receives prompt
@app.route('/generate-wireframe', methods=['POST'])
def generate_wireframe():
prompt = request.json.get('prompt')
# Send to Claude AI
claude_response = call_claude_api(prompt)
# Generate SVG from AI response
svg_content = generate_svg_wireframe(claude_response)
# Return SVG with proper content type
return svg_content, 200, {'Content-Type': 'image/svg+xml'}
4. SVG Response
<svg width="800" height="600" viewBox="0 0 800 600">
<defs>
<filter id="shadow" y="-40%" x="-40%" width="180%" height="180%">
<feDropShadow dx="1" dy="1" stdDeviation="1.2" flood-opacity=".5"/>
</filter>
</defs>
<g>
<!-- Header -->
<rect x="0" y="0" width="800" height="60" fill="#f0f0f0"/>
<text x="20" y="35" font-family="Arial" font-size="16">Dashboard Header</text>
<!-- Sidebar -->
<rect x="0" y="60" width="200" height="540" fill="#e0e0e0"/>
<text x="20" y="85" font-family="Arial" font-size="14">Navigation</text>
<!-- Stats Cards -->
<rect x="220" y="80" width="160" height="120" fill="#ffffff" filter="url(#shadow)"/>
<text x="240" y="100" font-family="Arial" font-size="12">Stats Card 1</text>
<rect x="400" y="80" width="160" height="120" fill="#ffffff" filter="url(#shadow)"/>
<text x="420" y="100" font-family="Arial" font-size="12">Stats Card 2</text>
<rect x="580" y="80" width="160" height="120" fill="#ffffff" filter="url(#shadow)"/>
<text x="600" y="100" font-family="Arial" font-size="12">Stats Card 3</text>
</g>
</svg>
5. Frontend Rendering
// Check response type
const contentType = response.headers.get('content-type')
if (contentType && contentType.includes('image/svg+xml')) {
// Handle SVG response
const svgString = await response.text()
await parseSVGAndRender(editor, svgString)
} else {
// Fallback to JSON
const data = await response.json()
await generateWireframeFromSpec(editor, data.wireframe)
}
🔧 Implementation Steps
Step 1: Backend SVG Generation
1.1 Install Dependencies
pip install flask flask-cors anthropic
1.2 Create SVG Generator
import xml.etree.ElementTree as ET
def generate_svg_wireframe(layout_spec):
"""Generate SVG wireframe from layout specification"""
# Create SVG root element
svg = ET.Element('svg', {
'width': '800',
'height': '600',
'viewBox': '0 0 800 600',
'xmlns': 'http://www.w3.org/2000/svg'
})
# Add definitions (filters, gradients)
defs = ET.SubElement(svg, 'defs')
shadow_filter = ET.SubElement(defs, 'filter', {
'id': 'shadow',
'y': '-40%', 'x': '-40%',
'width': '180%', 'height': '180%'
})
ET.SubElement(shadow_filter, 'feDropShadow', {
'dx': '1', 'dy': '1',
'stdDeviation': '1.2',
'flood-opacity': '.5'
})
# Create main group
main_group = ET.SubElement(svg, 'g')
# Generate layout elements
generate_header(main_group, layout_spec.get('header', {}))
generate_sidebar(main_group, layout_spec.get('sidebar', {}))
generate_main_content(main_group, layout_spec.get('main_content', {}))
generate_footer(main_group, layout_spec.get('footer', {}))
return ET.tostring(svg, encoding='unicode')
def generate_header(group, header_spec):
"""Generate header section"""
if not header_spec.get('enabled', False):
return
# Header background
ET.SubElement(group, 'rect', {
'x': '0', 'y': '0',
'width': '800', 'height': '60',
'fill': '#f0f0f0'
})
# Header text
ET.SubElement(group, 'text', {
'x': '20', 'y': '35',
'font-family': 'Arial',
'font-size': '16',
'fill': '#333333'
}).text = header_spec.get('title', 'Header')
1.3 Update Flask Endpoint
@app.route('/generate-wireframe', methods=['POST'])
def generate_wireframe():
try:
prompt = request.json.get('prompt')
if not prompt:
return jsonify({'error': 'Prompt is required'}), 400
# Call Claude AI
claude_response = call_claude_api(prompt)
# Parse AI response and generate SVG
layout_spec = parse_claude_response(claude_response)
svg_content = generate_svg_wireframe(layout_spec)
# Return SVG with proper headers
response = make_response(svg_content)
response.headers['Content-Type'] = 'image/svg+xml'
response.headers['Cache-Control'] = 'no-cache'
return response
except Exception as e:
logger.error(f"Error generating wireframe: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
Step 2: Frontend SVG Parsing
2.1 SVG Parser Functions
const parseSVGAndRender = async (editor: Editor, svgString: string) => {
try {
// Parse SVG string
const parser = new DOMParser()
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml')
const svgElement = svgDoc.querySelector('svg')
if (!svgElement) {
throw new Error('Invalid SVG content')
}
// Get dimensions
const viewBox = svgElement.getAttribute('viewBox')?.split(' ').map(Number) || [0, 0, 800, 600]
const [, , svgWidth, svgHeight] = viewBox
// Create main frame
editor.createShape({
id: createShapeId(),
type: "frame",
x: 50, y: 50,
props: {
w: Math.max(800, svgWidth),
h: Math.max(600, svgHeight),
name: "SVG Wireframe",
},
})
// Render SVG elements
await renderSVGElements(editor, svgElement, 50, 50, svgWidth, svgHeight)
} catch (error) {
console.error('SVG parsing error:', error)
// Fallback to basic wireframe
await generateFallbackWireframe(editor, "SVG parsing failed")
}
}
2.2 Element Renderers
const renderSVGRect = async (editor: Editor, element: SVGElement, offsetX: number, offsetY: number) => {
const x = parseFloat(element.getAttribute('x') || '0') + offsetX
const y = parseFloat(element.getAttribute('y') || '0') + offsetY
const width = parseFloat(element.getAttribute('width') || '100')
const height = parseFloat(element.getAttribute('height') || '100')
const fill = element.getAttribute('fill') || 'none'
const stroke = element.getAttribute('stroke') || 'black'
editor.createShape({
id: createShapeId(),
type: "geo",
x, y,
props: {
w: Math.max(10, width),
h: Math.max(10, height),
geo: "rectangle",
fill: fill === 'none' ? 'none' : 'semi',
color: mapColorToTldraw(stroke),
},
})
}
🎨 SVG Styling and Effects
Shadows and Filters
<defs>
<filter id="shadow" y="-40%" x="-40%" width="180%" height="180%">
<feDropShadow dx="1" dy="1" stdDeviation="1.2" flood-opacity=".5"/>
</filter>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="3" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
Gradients
<defs>
<linearGradient id="headerGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#4facfe;stop-opacity:1" />
<stop offset="100%" style="stop-color:#00f2fe;stop-opacity:1" />
</linearGradient>
</defs>
<rect x="0" y="0" width="800" height="60" fill="url(#headerGradient)"/>
Text Styling
<text x="20" y="35"
font-family="Arial, sans-serif"
font-size="16"
font-weight="bold"
fill="#333333"
text-anchor="start">
Dashboard Header
</text>
🔄 Response Type Detection
Content-Type Based Routing
const generateFromPrompt = async (prompt: string) => {
try {
const response = await fetch('/generate-wireframe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
})
// Detect response type
const contentType = response.headers.get('content-type')
if (contentType && contentType.includes('image/svg+xml')) {
// SVG response - parse and render
const svgString = await response.text()
await parseSVGAndRender(editor, svgString)
} else {
// JSON response - fallback processing
const data = await response.json()
await generateWireframeFromSpec(editor, data.wireframe)
}
} catch (error) {
console.error('Generation error:', error)
await generateFallbackWireframe(editor, prompt)
}
}
🧪 Testing and Validation
Backend Testing
def test_svg_generation():
"""Test SVG generation functionality"""
# Test layout specification
layout_spec = {
'header': {'enabled': True, 'title': 'Test Header'},
'sidebar': {'enabled': True, 'width': 200},
'main_content': {'sections': []},
'footer': {'enabled': True, 'height': 60}
}
# Generate SVG
svg_content = generate_svg_wireframe(layout_spec)
# Validate SVG structure
assert '<svg' in svg_content
assert 'width="800"' in svg_content
assert 'height="600"' in svg_content
assert 'Test Header' in svg_content
print("✅ SVG generation test passed")
if __name__ == '__main__':
test_svg_generation()
Frontend Testing
const testSVGParsing = async () => {
const testSVG = `
<svg width="100" height="100" viewBox="0 0 100 100">
<rect x="10" y="10" width="80" height="80" fill="#f0f0f0"/>
<text x="20" y="60">Test</text>
</svg>
`
try {
await parseSVGAndRender(mockEditor, testSVG)
console.log('✅ SVG parsing test passed')
} catch (error) {
console.error('❌ SVG parsing test failed:', error)
}
}
🚀 Performance Optimization
SVG Optimization Techniques
- Minimize DOM Elements: Use groups for related elements
- Optimize Paths: Simplify complex paths
- Reduce Attributes: Use CSS classes for common styles
- Compression: Gzip SVG responses
Caching Strategies
from functools import lru_cache
@lru_cache(maxsize=100)
def generate_cached_svg(prompt_hash):
"""Cache SVG generation for repeated prompts"""
return generate_svg_wireframe(get_cached_layout(prompt_hash))
🔮 Future Enhancements
Advanced SVG Features
- Animations: CSS animations and transitions
- Interactivity: Click handlers and hover effects
- Responsive Design: ViewBox scaling and media queries
- Accessibility: ARIA labels and screen reader support
Integration Possibilities
- Design Systems: Consistent component libraries
- Export Options: PNG, PDF, and other formats
- Collaboration: Real-time editing and version control
- Analytics: Usage tracking and performance metrics
📋 Implementation Checklist
- Backend SVG generation functions
- Frontend SVG parsing and rendering
- Response type detection and routing
- Error handling and fallback mechanisms
- Testing and validation
- Performance optimization
- Documentation and examples
🆘 Troubleshooting
Common Issues
- SVG Not Rendering: Check content-type headers
- Parsing Errors: Validate SVG XML structure
- Performance Issues: Optimize SVG complexity
- CORS Problems: Configure proper origins
Debug Tips
- Use browser dev tools to inspect SVG responses
- Check network tab for content-type headers
- Validate SVG content with online validators
- Monitor console for parsing errors
This integration guide provides a comprehensive approach to implementing SVG-based wireframe generation. The system offers better performance, precision, and styling capabilities compared to JSON-based approaches.