` to render any HTML element.
+3. Update `App.tsx` to register it in `shapeUtils`.
+4. Add it to the **ControlsPalette**.
+
+---
+
+## ๐ธ Preview
+
+* Palette on the left with draggable controllers.
+* tldraw canvas on the right.
+* Controls behave just like Balsamiq but **real & interactive**.
+
+---
+Got it โ
I see your **Prompt-to-Wireframe (tldraw)** app running locally โ it already generates wireframes on the canvas. Now you want to **integrate the interactive controllers (button, forms, data grid, date picker, etc.)** into this environment.
+
+Hereโs how you can integrate the two:
+
+---
+
+## ๐น Integration Plan
+
+1. **Extend your current tldraw setup**
+
+ * Right now your app renders `` with AI-generated wireframes.
+ * Youโll register your **10 custom controllers (shapes)** into the same editor.
+
+2. **Add Controllers Palette**
+
+ * Create a sidebar/panel with the controllers (like Balsamiqโs top bar).
+ * Each controller button inserts its shape into the tldraw canvas.
+
+3. **Register Custom Shapes**
+
+ * In your `App.tsx` (or wherever `` is rendered), pass `shapeUtils` with all the controllers you built:
+
+ ```tsx
+ import {
+ ButtonShapeUtil,
+ CheckboxShapeUtil,
+ ComboBoxShapeUtil,
+ DataGridShapeUtil,
+ DatePickerShapeUtil,
+ FormShapeUtil,
+ InputShapeUtil,
+ RadioGroupShapeUtil,
+ TextAreaShapeUtil,
+ ToggleShapeUtil,
+ } from "./shapes";
+
+
+ ```
+
+4. **Connect Palette โ Shape Creation**
+ Example for a button in your palette:
+
+ ```tsx
+ function ControlsPalette({ editor }) {
+ return (
+
+
+
+ );
+ }
+ ```
+
+ Add similar buttons for checkbox, date picker, grid, etc.
+
+5. **Combine With Prompt-to-Wireframe Flow**
+
+ * When your AI generates wireframes, they appear as usual.
+ * The user can then drag in **interactive controllers** to replace/augment them.
+ * Example: AI generates a rectangle with label "DATA TABLE" โ user deletes it and inserts a real **DataGridShape**.
+
+---
+
+## ๐น Updated Project Structure
+
+```
+src/
+ โโ shapes/ # all 10 controllers
+ โ โโ ButtonShape.tsx
+ โ โโ CheckboxShape.tsx
+ โ โโ ...
+ โโ components/
+ โ โโ ControlsPalette.tsx
+ โ โโ WireframeGenerator.tsx # your existing AI integration
+ โโ App.tsx
+ โโ main.tsx
+```
+
+---
+
+## ๐น User Flow After Integration
+
+1. User enters a **prompt** โ AI generates a wireframe layout (as in your screenshot).
+2. User sees a **palette of interactive controllers**.
+3. User drags/drops or clicks to insert **real interactive controls** (button, forms, date pickers, data grid).
+4. Wireframe evolves into a **clickable mockup**, not just static boxes.
+
+---
+
+## ๐ License
+
+MIT License ยฉ 2025
+
+---
+
+๐ Do you want me to **include example code for all 10 controllers in the README** (full implementations), or just keep this README as a **setup + usage guide** and document the shape types in a separate file?
diff --git a/services/ai-mockup-service/docs/WIREFRAME_PERSISTENCE_README.md b/services/ai-mockup-service/docs/WIREFRAME_PERSISTENCE_README.md
new file mode 100644
index 0000000..cce60f0
--- /dev/null
+++ b/services/ai-mockup-service/docs/WIREFRAME_PERSISTENCE_README.md
@@ -0,0 +1,214 @@
+# Wireframe Persistence System
+
+This document explains the new wireframe persistence system that automatically saves and loads wireframes to prevent data loss on page refresh.
+
+## Overview
+
+The wireframe persistence system consists of:
+1. **PostgreSQL Database Schema** - Stores wireframes, elements, and versions
+2. **Backend API Endpoints** - Handle CRUD operations for wireframes
+3. **Frontend Auto-save** - Automatically saves wireframes every 30 seconds
+4. **Manual Save Controls** - Manual save button and keyboard shortcuts
+
+## Database Schema
+
+### Tables Created
+
+1. **`wireframes`** - Main wireframe metadata
+ - `id` - Unique identifier
+ - `user_id` - Reference to user
+ - `project_id` - Optional project reference
+ - `name` - Wireframe name
+ - `description` - Wireframe description
+ - `device_type` - mobile/tablet/desktop
+ - `dimensions` - Width and height
+ - `metadata` - Additional data (prompt, generation settings)
+ - `is_active` - Soft delete flag
+
+2. **`wireframe_elements`** - Individual shapes/elements
+ - `id` - Element identifier
+ - `wireframe_id` - Reference to wireframe
+ - `element_type` - Type of element (shape, text, image, group)
+ - `element_data` - Complete TLDraw element data
+ - `position` - X, Y coordinates
+ - `size` - Width and height
+ - `style` - Color, stroke width, fill
+ - `parent_id` - For grouped elements
+ - `z_index` - Layering order
+
+3. **`wireframe_versions`** - Version control
+ - `id` - Version identifier
+ - `wireframe_id` - Reference to wireframe
+ - `version_number` - Sequential version number
+ - `version_name` - Human-readable version name
+ - `snapshot_data` - Complete wireframe state at version
+ - `created_by` - User who created version
+
+## Setup Instructions
+
+### 1. Database Setup
+
+```bash
+# Install PostgreSQL dependencies
+cd backend
+pip install -r requirements.txt
+
+# Copy and configure environment variables
+cp env.example .env
+# Edit .env with your database credentials
+
+# Run database setup script
+python setup_database.py
+```
+
+### 2. Environment Variables
+
+Create a `.env` file in the `backend/` directory:
+
+```env
+# Claude API Configuration
+CLAUDE_API_KEY=your-claude-api-key-here
+
+# Flask Configuration
+FLASK_ENV=development
+PORT=5000
+
+# Database Configuration
+DB_HOST=localhost
+DB_NAME=tech4biz_wireframes
+DB_USER=postgres
+DB_PASSWORD=your-database-password
+DB_PORT=5432
+```
+
+### 3. Start Backend
+
+```bash
+cd backend
+python app.py
+```
+
+## API Endpoints
+
+### Save Wireframe
+```http
+POST /api/wireframes
+Content-Type: application/json
+
+{
+ "wireframe": {
+ "name": "Wireframe Name",
+ "description": "Description",
+ "device_type": "desktop",
+ "dimensions": {"width": 1440, "height": 1024},
+ "metadata": {"prompt": "User prompt"}
+ },
+ "elements": [...],
+ "user_id": "user-uuid",
+ "project_id": "project-uuid"
+}
+```
+
+### Get Wireframe
+```http
+GET /api/wireframes/{wireframe_id}
+```
+
+### Update Wireframe
+```http
+PUT /api/wireframes/{wireframe_id}
+Content-Type: application/json
+
+{
+ "name": "Updated Name",
+ "description": "Updated Description",
+ "elements": [...],
+ "user_id": "user-uuid"
+}
+```
+
+### Delete Wireframe
+```http
+DELETE /api/wireframes/{wireframe_id}
+```
+
+### Get User Wireframes
+```http
+GET /api/wireframes/user/{user_id}
+```
+
+## Frontend Features
+
+### Auto-save
+- Wireframes are automatically saved every 30 seconds
+- Auto-save can be toggled on/off
+- Last save time is displayed
+
+### Manual Save
+- Manual save button in top-right corner
+- Keyboard shortcut: `Ctrl+S` (or `Cmd+S` on Mac)
+
+### Save Status
+- Green indicator shows last save time
+- Auto-save toggle checkbox
+- Manual save button
+
+## Usage
+
+### Creating Wireframes
+1. Generate wireframe using AI prompt
+2. Wireframe is automatically saved to database
+3. Continue editing - changes are auto-saved
+
+### Loading Wireframes
+1. Wireframes are automatically loaded on page refresh
+2. Use API endpoints to load specific wireframes
+3. Version history is maintained
+
+### Keyboard Shortcuts
+- `Ctrl+S` - Save wireframe
+- `Ctrl+K` - Trigger prompt input (planned)
+- `Ctrl+Delete` - Clear canvas
+
+## Data Flow
+
+1. **User creates/edits wireframe** โ TLDraw editor
+2. **Auto-save triggers** โ Every 30 seconds
+3. **Data serialized** โ Convert TLDraw shapes to database format
+4. **API call** โ Send to backend
+5. **Database storage** โ Save to PostgreSQL
+6. **Version created** โ New version entry for tracking
+
+## Benefits
+
+- **No data loss** on page refresh
+- **Automatic backup** every 30 seconds
+- **Version control** for wireframe changes
+- **User isolation** - each user sees only their wireframes
+- **Project organization** - wireframes can be grouped by project
+- **Scalable storage** - PostgreSQL handles large wireframes efficiently
+
+## Troubleshooting
+
+### Database Connection Issues
+- Check PostgreSQL is running
+- Verify database credentials in `.env`
+- Ensure database `tech4biz_wireframes` exists
+
+### Auto-save Not Working
+- Check browser console for errors
+- Verify backend is running on correct port
+- Check network tab for failed API calls
+
+### Wireframes Not Loading
+- Check if wireframe exists in database
+- Verify user_id matches
+- Check API endpoint responses
+
+## Future Enhancements
+
+- **Real-time collaboration** - Multiple users editing same wireframe
+- **Export formats** - PNG, PDF, HTML export
+- **Template library** - Reusable wireframe components
+- **Advanced versioning** - Branch and merge wireframes
+- **Search and filtering** - Find wireframes by content or metadata
diff --git a/services/ai-mockup-service/requirements.txt b/services/ai-mockup-service/requirements.txt
new file mode 100644
index 0000000..65405eb
--- /dev/null
+++ b/services/ai-mockup-service/requirements.txt
@@ -0,0 +1,9 @@
+flask==3.0.0
+flask-cors==4.0.0
+anthropic
+python-dotenv==1.0.0
+psycopg2-binary==2.9.9
+requests==2.31.0
+gunicorn==21.2.0
+PyJWT==2.8.0
+cryptography==41.0.7
diff --git a/services/ai-mockup-service/scripts/quick-start.bat b/services/ai-mockup-service/scripts/quick-start.bat
new file mode 100644
index 0000000..eb5ec08
--- /dev/null
+++ b/services/ai-mockup-service/scripts/quick-start.bat
@@ -0,0 +1,90 @@
+@echo off
+echo ๐ Quick Start - AI Wireframe Generator
+echo ======================================
+echo.
+
+echo ๐ Checking prerequisites...
+echo.
+
+REM Check if Python is installed
+python --version >nul 2>&1
+if errorlevel 1 (
+ echo โ Python is not installed or not in PATH
+ echo Please install Python 3.8+ and try again
+ pause
+ exit /b 1
+)
+
+REM Check if Node.js is installed
+node --version >nul 2>&1
+if errorlevel 1 (
+ echo โ Node.js is not installed or not in PATH
+ echo Please install Node.js 18+ and try again
+ pause
+ exit /b 1
+)
+
+echo โ
Python and Node.js are installed
+echo.
+
+echo ๐ง Setting up backend...
+cd backend
+
+REM Check if .env exists
+if not exist .env (
+ echo ๐ Creating .env file...
+ copy env.example .env
+ echo โ ๏ธ Please edit .env and add your Claude API key
+ echo Then restart this script
+ pause
+ exit /b 1
+)
+
+REM Check if requirements are installed
+pip show flask >nul 2>&1
+if errorlevel 1 (
+ echo ๐ฆ Installing Python dependencies...
+ pip install -r requirements.txt
+ if errorlevel 1 (
+ echo โ Failed to install dependencies
+ pause
+ exit /b 1
+ )
+)
+
+echo โ
Backend setup complete
+echo.
+
+echo ๐ Starting backend in background...
+start "Flask Backend" cmd /k "python run.py"
+
+echo โณ Waiting for backend to start...
+timeout /t 5 /nobreak >nul
+
+echo ๐ Backend should be running on http://localhost:5000
+echo.
+
+echo ๐ Starting frontend...
+cd ..
+start "Next.js Frontend" cmd /k "npm run dev"
+
+echo.
+echo ๐ Both services are starting!
+echo.
+echo ๐ฑ Frontend: http://localhost:3000
+echo ๐ง Backend: http://localhost:5000
+echo.
+echo ๐ก Tips:
+echo - Wait for both services to fully start
+echo - Check the right sidebar for backend status
+echo - Try generating a wireframe with AI
+echo.
+echo Press any key to open the frontend in your browser...
+pause >nul
+
+start http://localhost:3000
+
+echo.
+echo ๐จ Happy wireframing! The AI will help you create professional layouts.
+echo.
+pause
diff --git a/services/ai-mockup-service/scripts/quick-start.sh b/services/ai-mockup-service/scripts/quick-start.sh
new file mode 100644
index 0000000..cb8107f
--- /dev/null
+++ b/services/ai-mockup-service/scripts/quick-start.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+
+echo "๐ Quick Start - AI Wireframe Generator"
+echo "======================================"
+echo
+
+echo "๐ Checking prerequisites..."
+echo
+
+# Check if Python is installed
+if ! command -v python3 &> /dev/null; then
+ echo "โ Python 3 is not installed or not in PATH"
+ echo "Please install Python 3.8+ and try again"
+ exit 1
+fi
+
+# Check if Node.js is installed
+if ! command -v node &> /dev/null; then
+ echo "โ Node.js is not installed or not in PATH"
+ echo "Please install Node.js 18+ and try again"
+ exit 1
+fi
+
+echo "โ
Python and Node.js are installed"
+echo
+
+echo "๐ง Setting up backend..."
+cd backend
+
+# Check if .env exists
+if [ ! -f .env ]; then
+ echo "๐ Creating .env file..."
+ cp env.example .env
+ echo "โ ๏ธ Please edit .env and add your Claude API key"
+ echo " Then restart this script"
+ exit 1
+fi
+
+# Check if requirements are installed
+if ! python3 -c "import flask" &> /dev/null; then
+ echo "๐ฆ Installing Python dependencies..."
+ pip3 install -r requirements.txt
+ if [ $? -ne 0 ]; then
+ echo "โ Failed to install dependencies"
+ exit 1
+ fi
+fi
+
+echo "โ
Backend setup complete"
+echo
+
+echo "๐ Starting backend in background..."
+python3 run.py &
+BACKEND_PID=$!
+
+echo "โณ Waiting for backend to start..."
+sleep 5
+
+echo "๐ Backend should be running on http://localhost:5000"
+echo
+
+echo "๐ Starting frontend..."
+cd ..
+npm run dev &
+FRONTEND_PID=$!
+
+echo
+echo "๐ Both services are starting!"
+echo
+echo "๐ฑ Frontend: http://localhost:3000"
+echo "๐ง Backend: http://localhost:5000"
+echo
+echo "๐ก Tips:"
+echo " - Wait for both services to fully start"
+echo " - Check the right sidebar for backend status"
+echo " - Try generating a wireframe with AI"
+echo
+
+# Function to cleanup background processes
+cleanup() {
+ echo
+ echo "๐ Stopping services..."
+ kill $BACKEND_PID 2>/dev/null
+ kill $FRONTEND_PID 2>/dev/null
+ echo "โ
Services stopped"
+ exit 0
+}
+
+# Set trap to cleanup on script exit
+trap cleanup SIGINT SIGTERM
+
+echo "Press Ctrl+C to stop both services"
+echo
+
+# Wait for user to stop
+wait
diff --git a/services/ai-mockup-service/scripts/test-tldraw-props.tsx b/services/ai-mockup-service/scripts/test-tldraw-props.tsx
new file mode 100644
index 0000000..6550391
--- /dev/null
+++ b/services/ai-mockup-service/scripts/test-tldraw-props.tsx
@@ -0,0 +1,102 @@
+import { Editor, createShapeId } from "@tldraw/tldraw"
+
+// Test function to find the correct tldraw v3 properties
+export function testTldrawProps(editor: Editor) {
+ try {
+ // Test 1: Basic rectangle with minimal properties
+ const rectId1 = createShapeId()
+ editor.createShape({
+ id: rectId1,
+ type: "geo",
+ x: 100,
+ y: 100,
+ props: {
+ w: 100,
+ h: 100,
+ geo: "rectangle",
+ },
+ })
+ console.log("โ
Basic rectangle created successfully")
+
+ // Test 2: Rectangle with fill
+ const rectId2 = createShapeId()
+ editor.createShape({
+ id: rectId2,
+ type: "geo",
+ x: 250,
+ y: 100,
+ props: {
+ w: 100,
+ h: 100,
+ geo: "rectangle",
+ fill: "none",
+ },
+ })
+ console.log("โ
Rectangle with fill created successfully")
+
+ // Test 3: Rectangle with color
+ const rectId3 = createShapeId()
+ editor.createShape({
+ id: rectId3,
+ type: "geo",
+ x: 400,
+ y: 100,
+ props: {
+ w: 100,
+ h: 100,
+ geo: "rectangle",
+ fill: "none",
+ color: "black",
+ },
+ })
+ console.log("โ
Rectangle with color created successfully")
+
+ // Test 4: Text with minimal properties
+ const textId1 = createShapeId()
+ editor.createShape({
+ id: textId1,
+ type: "text",
+ x: 100,
+ y: 250,
+ props: {
+ text: "Test Text",
+ },
+ })
+ console.log("โ
Basic text created successfully")
+
+ // Test 5: Text with size
+ const textId2 = createShapeId()
+ editor.createShape({
+ id: textId2,
+ type: "text",
+ x: 250,
+ y: 250,
+ props: {
+ text: "Test Text",
+ w: 100,
+ h: 50,
+ },
+ })
+ console.log("โ
Text with size created successfully")
+
+ // Test 6: Text with font properties
+ const textId3 = createShapeId()
+ editor.createShape({
+ id: textId3,
+ type: "text",
+ x: 400,
+ y: 250,
+ props: {
+ text: "Test Text",
+ w: 100,
+ h: 50,
+ fontSize: 16,
+ color: "black",
+ },
+ })
+ console.log("โ
Text with font properties created successfully")
+
+ } catch (error) {
+ console.error("โ Error creating shape:", error)
+ }
+}
diff --git a/services/ai-mockup-service/src/README.md b/services/ai-mockup-service/src/README.md
new file mode 100644
index 0000000..639cde5
--- /dev/null
+++ b/services/ai-mockup-service/src/README.md
@@ -0,0 +1,399 @@
+# Prompt to Wireframe - Backend
+
+A Flask-based backend service that generates SVG wireframes from natural language prompts using Claude AI. The system converts user descriptions into precise, scalable vector graphics that can be rendered directly in the frontend.
+
+## ๐ Features
+
+- **AI-Powered Generation**: Uses Claude AI to analyze prompts and create wireframe layouts
+- **SVG Output**: Generates precise SVG wireframes with proper positioning and styling
+- **Flexible Response Types**: Supports both SVG and JSON responses for compatibility
+- **Real-time Processing**: Fast wireframe generation with minimal latency
+- **Scalable Architecture**: Built with Flask for easy deployment and scaling
+
+## ๐๏ธ Architecture
+
+### Backend Stack
+- **Flask 3.0** - Web framework
+- **Claude AI** - Natural language processing
+- **SVG Generation** - Vector graphics creation
+- **Python 3.9+** - Runtime environment
+
+### Response System
+The backend can generate two types of responses:
+
+1. **SVG Response** (Primary)
+ - Direct SVG content
+ - Precise positioning and styling
+ - Better frontend rendering performance
+
+2. **JSON Response** (Fallback)
+ - Structured wireframe specifications
+ - Compatible with existing frontend logic
+ - Used when SVG generation fails
+
+## ๐ Project Structure
+
+```
+backend/
+โโโ app.py # Main Flask application
+โโโ requirements.txt # Python dependencies
+โโโ run.py # Application entry point
+โโโ env.example # Environment variables template
+โโโ start_backend.bat # Windows startup script
+โโโ start_backend.sh # Unix startup script
+โโโ test_api.py # API testing script
+```
+
+## ๐ง Installation
+
+### Prerequisites
+- Python 3.9 or higher
+- pip package manager
+- Claude AI API access
+
+### Setup Steps
+
+1. **Clone the repository**
+ ```bash
+ git clone
+ cd wireframe-tool/tldraw-editor/backend
+ ```
+
+2. **Create virtual environment**
+ ```bash
+ python -m venv venv
+
+ # Windows
+ venv\Scripts\activate
+
+ # Unix/Mac
+ source venv/bin/activate
+ ```
+
+3. **Install dependencies**
+ ```bash
+ pip install -r requirements.txt
+ ```
+
+4. **Configure environment**
+ ```bash
+ cp env.example .env
+ # Edit .env with your Claude AI API key
+ ```
+
+5. **Start the server**
+ ```bash
+ # Windows
+ start_backend.bat
+
+ # Unix/Mac
+ ./start_backend.sh
+
+ # Or directly
+ python run.py
+ ```
+
+## ๐ API Endpoints
+
+### Generate Wireframe
+**POST** `/generate-wireframe`
+
+Generates a wireframe from a natural language prompt.
+
+#### Request Body
+```json
+{
+ "prompt": "Dashboard with header, sidebar, and 3 stats cards"
+}
+```
+
+#### Response Types
+
+**SVG Response** (Preferred)
+```
+Content-Type: image/svg+xml
+
+
+```
+
+**JSON Response** (Fallback)
+```
+Content-Type: application/json
+
+{
+ "success": true,
+ "wireframe": {
+ "layout": { ... },
+ "styling": { ... },
+ "annotations": { ... }
+ }
+}
+```
+
+### Health Check
+**GET** `/health`
+
+Returns server status and health information.
+
+## ๐ฏ SVG Generation
+
+### SVG Structure
+The generated SVGs follow a consistent structure:
+
+```xml
+
+```
+
+### Element Types Supported
+- **Rectangles**: Header, sidebar, content areas, cards
+- **Text**: Labels, titles, descriptions
+- **Groups**: Logical sections and containers
+- **Paths**: Complex shapes and icons
+- **Circles/Ellipses**: Icons and decorative elements
+
+## ๐ค AI Integration
+
+### Claude AI Processing
+The backend uses Claude AI to:
+
+1. **Analyze Prompts**: Understand user requirements
+2. **Generate Layouts**: Create logical wireframe structures
+3. **Apply UX Principles**: Follow design best practices
+4. **Output SVG**: Generate precise vector graphics
+
+### Prompt Processing Flow
+```
+User Prompt โ Claude AI โ Layout Analysis โ SVG Generation โ Response
+```
+
+### Example Prompts
+- "Dashboard with header, left sidebar, 3 stats cards, line chart, and footer"
+- "Landing page with hero section, feature grid, and contact form"
+- "E-commerce product page with image gallery and product details"
+
+## ๐ง Configuration
+
+### Environment Variables
+```bash
+# Claude AI Configuration
+CLAUDE_API_KEY=your_api_key_here
+CLAUDE_MODEL=claude-3-sonnet-20240229
+
+# Server Configuration
+FLASK_ENV=development
+FLASK_DEBUG=True
+PORT=5000
+
+# CORS Configuration
+CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
+```
+
+### API Configuration
+```python
+# app.py
+app.config['CLAUDE_API_KEY'] = os.getenv('CLAUDE_API_KEY')
+app.config['CLAUDE_MODEL'] = os.getenv('CLAUDE_MODEL', 'claude-3-sonnet-20240229')
+app.config['MAX_PROMPT_LENGTH'] = 1000
+```
+
+## ๐ Deployment
+
+### Development
+```bash
+python run.py
+# Server runs on http://localhost:5000
+```
+
+### Production
+```bash
+# Using Gunicorn
+gunicorn -w 4 -b 0.0.0.0:5000 run:app
+
+# Using uWSGI
+uwsgi --http :5000 --module run:app
+```
+
+### Docker Deployment
+```dockerfile
+FROM python:3.9-slim
+WORKDIR /app
+COPY requirements.txt .
+RUN pip install -r requirements.txt
+COPY . .
+EXPOSE 5000
+CMD ["python", "run.py"]
+```
+
+## ๐งช Testing
+
+### API Testing
+```bash
+python test_api.py
+```
+
+### Manual Testing
+```bash
+# Test with curl
+curl -X POST http://localhost:5000/generate-wireframe \
+ -H "Content-Type: application/json" \
+ -d '{"prompt": "Simple dashboard with header and sidebar"}'
+
+# Test health endpoint
+curl http://localhost:5000/health
+```
+
+### Load Testing
+```bash
+# Using Apache Bench
+ab -n 100 -c 10 -p test_data.json \
+ -T application/json \
+ http://localhost:5000/generate-wireframe
+```
+
+## ๐ Monitoring
+
+### Health Checks
+- Server status monitoring
+- API response time tracking
+- Error rate monitoring
+- Resource usage tracking
+
+### Logging
+```python
+import logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+@app.route('/generate-wireframe', methods=['POST'])
+def generate_wireframe():
+ logger.info(f"Received prompt: {request.json.get('prompt', '')[:100]}...")
+ # ... processing logic
+```
+
+## ๐ Security
+
+### API Key Management
+- Secure storage of Claude AI API keys
+- Environment variable protection
+- Key rotation support
+
+### Input Validation
+- Prompt length limits
+- Content sanitization
+- Rate limiting support
+
+### CORS Configuration
+```python
+from flask_cors import CORS
+
+CORS(app, origins=os.getenv('CORS_ORIGINS', '').split(','))
+```
+
+## ๐ Troubleshooting
+
+### Common Issues
+
+1. **Claude AI API Errors**
+ - Verify API key is valid
+ - Check API quota and limits
+ - Ensure proper model access
+
+2. **SVG Generation Failures**
+ - Check prompt complexity
+ - Verify SVG output format
+ - Review error logs
+
+3. **Performance Issues**
+ - Monitor response times
+ - Check server resources
+ - Optimize AI model usage
+
+### Debug Mode
+Enable debug logging:
+```python
+app.config['DEBUG'] = True
+logging.getLogger().setLevel(logging.DEBUG)
+```
+
+## ๐ Performance Optimization
+
+### Caching Strategies
+- Response caching for similar prompts
+- SVG template caching
+- AI response caching
+
+### Async Processing
+- Background SVG generation
+- Queue-based processing
+- WebSocket updates
+
+### Resource Management
+- Connection pooling
+- Memory optimization
+- CPU usage monitoring
+
+## ๐ฎ Future Enhancements
+
+### Planned Features
+- **Template System**: Pre-built wireframe templates
+- **Custom Styling**: User-defined color schemes
+- **Export Options**: PNG, PDF, and other formats
+- **Collaboration**: Real-time editing and sharing
+- **Version Control**: Wireframe history and branching
+
+### Scalability Improvements
+- **Microservices**: Separate AI and SVG services
+- **Load Balancing**: Multiple backend instances
+- **CDN Integration**: Global content delivery
+- **Database Storage**: Wireframe persistence
+
+## ๐ค Contributing
+
+1. Fork the repository
+2. Create a feature branch
+3. Implement your changes
+4. Add tests and documentation
+5. Submit a pull request
+
+### Development Guidelines
+- Follow PEP 8 style guidelines
+- Add type hints for new functions
+- Include docstrings for all functions
+- Write tests for new features
+
+## ๐ License
+
+This project is licensed under the MIT License - see the LICENSE file for details.
+
+## ๐ Support
+
+For support and questions:
+- Create an issue in the repository
+- Check the troubleshooting section
+- Review the frontend documentation
+- Contact the development team
+
+---
+
+**Note**: This backend service is designed to work with the Prompt to Wireframe frontend application, providing SVG wireframe generation capabilities through Claude AI integration.
diff --git a/services/ai-mockup-service/src/SETUP.md b/services/ai-mockup-service/src/SETUP.md
new file mode 100644
index 0000000..1282bc8
--- /dev/null
+++ b/services/ai-mockup-service/src/SETUP.md
@@ -0,0 +1,183 @@
+# ๐ Quick Setup Guide
+
+## Prerequisites
+- **Python 3.8+** installed on your system
+- **Claude API key** from Anthropic
+- **Git** (optional, for cloning)
+
+## ๐ฏ Step-by-Step Setup
+
+### 1. Get Your Claude API Key
+1. Go to [Anthropic Console](https://console.anthropic.com/)
+2. Sign up/Login and create an API key
+3. Copy your API key (starts with `sk-ant-api03-...`)
+
+### 2. Install Python Dependencies
+```bash
+cd backend
+pip install -r requirements.txt
+```
+
+### 3. Configure Environment
+```bash
+# Copy the example environment file
+cp env.example .env
+
+# Edit .env and add your API key
+# Replace "your-claude-api-key-here" with your actual key
+```
+
+**Example .env file:**
+```env
+CLAUDE_API_KEY=sk-ant-api03-your-actual-key-here
+FLASK_ENV=development
+PORT=5000
+```
+
+### 4. Start the Backend
+
+#### **Windows Users:**
+```bash
+# Double-click start_backend.bat
+# OR run in command prompt:
+start_backend.bat
+```
+
+#### **Mac/Linux Users:**
+```bash
+# Make script executable
+chmod +x start_backend.sh
+
+# Run the script
+./start_backend.sh
+```
+
+#### **Manual Start:**
+```bash
+python run.py
+```
+
+### 5. Verify Backend is Running
+- Backend should start on `http://localhost:5000`
+- You should see: "๐ Backend starting on http://localhost:5000"
+- Frontend can now connect to this backend
+
+## ๐งช Testing the Backend
+
+### Run the Test Suite
+```bash
+python test_api.py
+```
+
+This will test:
+- โ
Health endpoint
+- โ
Wireframe generation
+- โ
Error handling
+- โ
API responses
+
+### Manual API Testing
+```bash
+# Health check
+curl http://localhost:5000/api/health
+
+# Generate wireframe
+curl -X POST http://localhost:5000/api/generate-wireframe \
+ -H "Content-Type: application/json" \
+ -d '{"prompt": "Dashboard with header and sidebar"}'
+```
+
+## ๐ง Troubleshooting
+
+### Common Issues
+
+#### **"Module not found" errors**
+```bash
+# Reinstall dependencies
+pip install -r requirements.txt --force-reinstall
+```
+
+#### **"Cannot connect to backend"**
+- Check if backend is running on port 5000
+- Verify no firewall blocking the port
+- Check console for error messages
+
+#### **"Claude API key not configured"**
+- Ensure `.env` file exists in backend folder
+- Verify API key is correct and not placeholder text
+- Restart backend after changing `.env`
+
+#### **Port already in use**
+```bash
+# Find process using port 5000
+netstat -ano | findstr :5000 # Windows
+lsof -i :5000 # Mac/Linux
+
+# Kill the process or change port in .env
+```
+
+### Environment Variables
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `CLAUDE_API_KEY` | Your Anthropic API key | Required |
+| `PORT` | Backend port | 5000 |
+| `FLASK_ENV` | Flask environment | development |
+
+## ๐ฑ Frontend Integration
+
+Once backend is running, your Next.js frontend can:
+
+1. **Send prompts** to `/api/generate-wireframe`
+2. **Receive structured wireframe data** in JSON format
+3. **Render wireframes** using the AI-generated specifications
+4. **Handle errors** gracefully with fallback options
+
+## ๐จ API Response Format
+
+The backend returns structured wireframe data:
+
+```json
+{
+ "success": true,
+ "wireframe": {
+ "layout": {
+ "page": {"width": 1200, "height": 800},
+ "header": {"enabled": true, "height": 72},
+ "sidebar": {"enabled": true, "width": 240},
+ "main_content": {
+ "sections": [
+ {
+ "type": "grid",
+ "rows": 2,
+ "cols": 3,
+ "height": 200
+ }
+ ]
+ },
+ "footer": {"enabled": true, "height": 64}
+ },
+ "styling": {
+ "theme": "modern",
+ "colors": {"primary": "#3B82F6"},
+ "spacing": {"gap": 16, "padding": 20}
+ }
+ }
+}
+```
+
+## ๐ Next Steps
+
+1. **Test the backend** with `python test_api.py`
+2. **Modify the frontend** to use the new API
+3. **Customize wireframe generation** by editing `app.py`
+4. **Add more AI features** like layout optimization
+5. **Deploy to production** with proper environment setup
+
+## ๐ Support
+
+If you encounter issues:
+1. Check the console output for error messages
+2. Verify your Claude API key is valid
+3. Ensure all dependencies are installed
+4. Check if port 5000 is available
+
+Happy wireframing! ๐จโจ
diff --git a/services/ai-mockup-service/src/app.py b/services/ai-mockup-service/src/app.py
new file mode 100644
index 0000000..2d7263b
--- /dev/null
+++ b/services/ai-mockup-service/src/app.py
@@ -0,0 +1,1523 @@
+from flask import Flask, request, jsonify, make_response
+from flask_cors import CORS
+import anthropic
+import json
+import os
+import xml.etree.ElementTree as ET
+import html
+from typing import Dict, Any, List
+import psycopg2
+from psycopg2.extras import RealDictCursor
+import uuid
+from datetime import datetime
+import jwt
+import requests
+from functools import wraps
+
+app = Flask(__name__)
+CORS(app) # Enable CORS for frontend communication
+
+# Initialize Claude client
+client = anthropic.Anthropic(
+ api_key=os.getenv("CLAUDE_API_KEY", "your-claude-api-key-here")
+)
+
+# Database connection configuration
+DB_CONFIG = {
+ 'host': os.getenv('POSTGRES_HOST', 'localhost'),
+ 'database': os.getenv('POSTGRES_DB', 'dev_pipeline'),
+ 'user': os.getenv('POSTGRES_USER', 'pipeline_admin'),
+ 'password': os.getenv('POSTGRES_PASSWORD', 'secure_pipeline_2024'),
+ 'port': os.getenv('POSTGRES_PORT', '5432')
+}
+
+# JWT and User Auth Service Configuration
+JWT_SECRET = os.getenv('JWT_ACCESS_SECRET', 'access-secret-key-2024-tech4biz-secure_pipeline_2024') # Use same secret as user-auth service
+USER_AUTH_SERVICE_URL = os.getenv('USER_AUTH_SERVICE_URL', 'http://user-auth:8011') # Use Docker service name
+JWT_ALGORITHM = 'HS256'
+
+def get_db_connection():
+ """Get database connection"""
+ try:
+ conn = psycopg2.connect(**DB_CONFIG)
+ return conn
+ except Exception as e:
+ print(f"Database connection error: {e}")
+ return None
+
+def verify_jwt_token(token):
+ """Verify JWT token and return user data"""
+ try:
+ # First try to verify with local JWT secret (same as user-auth service)
+ payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
+ return payload
+ except jwt.ExpiredSignatureError:
+ raise Exception("Token has expired")
+ except jwt.InvalidTokenError:
+ # If local verification fails, try to verify with user-auth service
+ try:
+ print(f"Local JWT verification failed, trying user-auth service at {USER_AUTH_SERVICE_URL}")
+ response = requests.get(
+ f"{USER_AUTH_SERVICE_URL}/api/auth/verify",
+ headers={'Authorization': f'Bearer {token}'},
+ timeout=5 # Reduced timeout
+ )
+ print(f"User-auth service response: {response.status_code}")
+
+ if response.status_code == 200:
+ result = response.json()
+ if result.get('success') and result.get('data', {}).get('user'):
+ return result['data']['user']
+ else:
+ raise Exception("Invalid response format from auth service")
+ else:
+ error_text = response.text
+ print(f"Auth service error response: {error_text}")
+ try:
+ error_json = response.json()
+ error_msg = error_json.get('error', 'Unknown error')
+ except:
+ error_msg = error_text
+ raise Exception(f"Auth service error: {error_msg}")
+ except requests.RequestException as req_err:
+ print(f"Request to auth service failed: {req_err}")
+ # Don't raise exception, try to continue with local verification
+ print("Continuing with local verification only")
+ raise Exception(f"Unable to verify token with auth service: {str(req_err)}")
+ except Exception as e:
+ print(f"Error processing auth service response: {e}")
+ raise Exception(f"Token verification failed: {str(e)}")
+ except Exception as e:
+ raise Exception(f"Token verification failed: {str(e)}")
+
+def extract_user_id_from_token(user_data):
+ """Extract user ID from various possible token formats"""
+ if not user_data:
+ return None
+
+ # Try different possible user ID fields
+ user_id = (user_data.get('id') or
+ user_data.get('userId') or
+ user_data.get('user_id') or
+ user_data.get('sub') or # JWT standard subject field
+ user_data.get('user', {}).get('id')) # Nested user object
+
+ return user_id
+
+def require_auth(f):
+ """Decorator to require authentication"""
+ @wraps(f)
+ def decorated_function(*args, **kwargs):
+ auth_header = request.headers.get('Authorization')
+ if not auth_header:
+ return jsonify({"error": "Authorization header required"}), 401
+
+ try:
+ token = auth_header.split(' ')[1] if ' ' in auth_header else auth_header
+ user_data = verify_jwt_token(token)
+ user_id = extract_user_id_from_token(user_data)
+
+ if not user_id:
+ return jsonify({"error": "Invalid token - no user ID found"}), 401
+
+ # Store both user data and user ID for easy access
+ request.user = user_data
+ request.user_id = user_id
+ return f(*args, **kwargs)
+ except Exception as e:
+ return jsonify({"error": str(e)}), 401
+
+ return decorated_function
+
+def optional_auth(f):
+ """Decorator for optional authentication"""
+ @wraps(f)
+ def decorated_function(*args, **kwargs):
+ auth_header = request.headers.get('Authorization')
+ if auth_header:
+ try:
+ token = auth_header.split(' ')[1] if ' ' in auth_header else auth_header
+ user_data = verify_jwt_token(token)
+ user_id = extract_user_id_from_token(user_data)
+
+ if user_id:
+ request.user = user_data
+ request.user_id = user_id
+ except Exception:
+ # Continue without authentication
+ pass
+ return f(*args, **kwargs)
+
+ return decorated_function
+
+# System prompt for SVG wireframe generation
+SYSTEM_PROMPT = """You are an expert UI/UX designer and wireframe architect. Your task is to analyze user prompts and generate detailed wireframe specifications that can be converted to SVG.
+
+Generate a JSON response with the following structure:
+{
+ "layout": {
+ "page": {"width": 1200, "height": 800},
+ "header": {"enabled": true, "height": 72, "elements": ["logo", "nav", "cta"]},
+ "sidebar": {"enabled": false, "width": 240, "position": "left", "elements": []},
+ "hero": {"enabled": false, "height": 200, "elements": []},
+ "main_content": {
+ "sections": [
+ {
+ "type": "grid",
+ "rows": 2,
+ "cols": 3,
+ "height": 200,
+ "elements": ["card1", "card2", "card3", "card4", "card5", "card6"]
+ }
+ ]
+ },
+ "footer": {"enabled": true, "height": 64, "elements": ["links", "copyright"]}
+ },
+ "styling": {
+ "theme": "balsamiq",
+ "colors": {"primary": "#3B82F6", "secondary": "#6B7280", "background": "#FFFFFF", "card": "#F8FAFC"},
+ "spacing": {"gap": 16, "padding": 20}
+ },
+ "annotations": {
+ "title": "Generated Wireframe",
+ "description": "AI-generated wireframe based on user prompt"
+ }
+}
+
+Focus on creating clean, Balsamiq-style wireframes with proper headings and descriptions.
+Return only valid JSON, no additional text."""
+
+def generate_svg_wireframe(layout_spec: Dict[str, Any], device_type: str = "desktop") -> str:
+ """Generate SVG wireframe from layout specification"""
+
+ # Device-specific dimensions
+ device_dimensions = {
+ "mobile": {"width": 375, "height": 812},
+ "tablet": {"width": 768, "height": 1024},
+ "desktop": {"width": 1440, "height": 1024}
+ }
+
+ # Get device dimensions or fallback to layout spec
+ device_config = device_dimensions.get(device_type, device_dimensions["desktop"])
+ page_width = int(device_config["width"])
+ page_height = int(device_config["height"])
+
+ # Create SVG root element
+ svg = ET.Element('svg', {
+ 'width': str(page_width),
+ 'height': str(page_height),
+ 'viewBox': f'0 0 {page_width} {page_height}',
+ 'xmlns': 'http://www.w3.org/2000/svg',
+ 'style': 'font-family: "Comic Sans MS", cursive, sans-serif; background: #f8f9fa;'
+ })
+
+ # Add background
+ ET.SubElement(svg, 'rect', {
+ 'x': '0', 'y': '0',
+ 'width': str(page_width), 'height': str(page_height),
+ 'fill': '#f8f9fa'
+ })
+
+ # Add definitions (filters, patterns)
+ defs = ET.SubElement(svg, 'defs')
+
+ # Hand-drawn style pattern
+ pattern = ET.SubElement(defs, 'pattern', {
+ 'id': 'rough',
+ 'x': '0', 'y': '0',
+ 'width': '4', 'height': '4',
+ 'patternUnits': 'userSpaceOnUse'
+ })
+ ET.SubElement(pattern, 'path', {
+ 'd': 'M0,0 L2,2 M2,0 L4,2',
+ 'stroke': '#ddd',
+ 'stroke-width': '0.5'
+ })
+
+ # Create main group
+ main_group = ET.SubElement(svg, 'g')
+
+ # Get styling information
+ styling = layout_spec.get('styling', {})
+ colors = styling.get('colors', {})
+ spacing = styling.get('spacing', {})
+
+ # Balsamiq-style colors
+ primary_color = '#2c3e50'
+ secondary_color = '#7f8c8d'
+ background_color = '#ffffff'
+ card_color = '#ffffff'
+ text_color = '#2c3e50'
+ border_color = '#34495e'
+
+ gap = int(spacing.get('gap', 16))
+ padding = int(spacing.get('padding', 20))
+
+ # Adjust spacing for mobile
+ if device_type == "mobile":
+ gap = max(8, gap // 2)
+ padding = max(12, padding // 2)
+
+ # Generate layout elements
+ current_y = padding
+
+ # Add device frame for mobile and tablet
+ if device_type in ["mobile", "tablet"]:
+ add_device_frame(main_group, page_width, page_height, device_type)
+ # Adjust content area for frame
+ if device_type == "mobile":
+ padding = int(30)
+ current_y = int(50)
+ else: # tablet
+ padding = int(40)
+ current_y = int(60)
+
+ # Header
+ header_spec = layout_spec.get('layout', {}).get('header', {})
+ if header_spec.get('enabled', False):
+ header_height = int(header_spec.get('height', 72))
+ if device_type == "mobile":
+ header_height = min(header_height, 60)
+ generate_header(main_group, header_spec, page_width - (padding * 2), current_y, header_height,
+ primary_color, background_color, text_color, border_color, padding, device_type)
+ current_y += header_height + gap
+
+ # Content area width calculation
+ content_width = page_width - (padding * 2)
+ content_x = padding
+
+ # Sidebar (only for desktop and tablet)
+ sidebar_spec = layout_spec.get('layout', {}).get('sidebar', {})
+ if sidebar_spec.get('enabled', False) and device_type != "mobile":
+ sidebar_width = int(sidebar_spec.get('width', 240))
+ if device_type == "tablet":
+ sidebar_width = min(sidebar_width, 200)
+ footer_height = int(80 if layout_spec.get('layout', {}).get('footer', {}).get('enabled', False) else 0)
+ sidebar_height = page_height - current_y - padding - footer_height
+ generate_sidebar(main_group, sidebar_spec, padding, current_y, sidebar_width, sidebar_height,
+ secondary_color, background_color, text_color, border_color, device_type)
+ content_x = padding + sidebar_width + gap
+ content_width = page_width - content_x - padding
+
+ # Hero section
+ hero_spec = layout_spec.get('layout', {}).get('hero', {})
+ if hero_spec.get('enabled', False):
+ hero_height = int(hero_spec.get('height', 200))
+ if device_type == "mobile":
+ hero_height = min(hero_height, 150)
+ generate_hero(main_group, hero_spec, content_x, current_y, content_width, hero_height,
+ primary_color, card_color, text_color, border_color, device_type)
+ current_y += hero_height + gap
+
+ # Main content sections
+ main_content = layout_spec.get('layout', {}).get('main_content', {})
+ sections = main_content.get('sections', [])
+
+ for section in sections:
+ section_height = int(section.get('height', 200))
+ if device_type == "mobile":
+ section_height = min(section_height, 300)
+ generate_section(main_group, section, content_x, current_y, content_width, section_height,
+ primary_color, card_color, text_color, border_color, gap, device_type)
+ current_y += section_height + gap
+
+ # Footer
+ footer_spec = layout_spec.get('layout', {}).get('footer', {})
+ if footer_spec.get('enabled', False):
+ footer_height = int(footer_spec.get('height', 64))
+ if device_type == "mobile":
+ footer_height = min(footer_height, 50)
+ footer_y = page_height - footer_height - padding
+ generate_footer(main_group, footer_spec, padding, footer_y, page_width - padding * 2, footer_height,
+ primary_color, background_color, text_color, border_color, device_type)
+
+ # Add device label
+ add_device_label(main_group, device_type, page_width, page_height)
+
+ return ET.tostring(svg, encoding='unicode')
+
+def get_wireframe_spec_from_claude(user_prompt: str, device_type: str) -> Dict[str, Any]:
+ """Get wireframe specification from Claude API"""
+ try:
+ user_message = f"""Generate a wireframe specification for this prompt: "{user_prompt}"
+
+Device type: {device_type}
+
+Please analyze the requirements and create a detailed wireframe layout that follows modern UI/UX principles and Balsamiq wireframe styling. Consider the user's needs and create an intuitive, well-structured interface suitable for {device_type} devices."""
+
+ response = client.messages.create(
+ model="claude-3-5-sonnet-20241022",
+ max_tokens=2000,
+ system=SYSTEM_PROMPT,
+ messages=[{"role": "user", "content": user_message}]
+ )
+
+ claude_response = response.content[0].text
+
+ # Extract JSON from response
+ json_start = claude_response.find('{')
+ json_end = claude_response.rfind('}') + 1
+
+ if json_start != -1 and json_end != -1:
+ json_str = claude_response[json_start:json_end]
+ json_str = json_str.replace('\n', ' ').replace('\r', ' ')
+ return json.loads(json_str)
+ else:
+ print("No JSON found in Claude response, using fallback")
+ return create_fallback_spec(user_prompt)
+
+ except Exception as e:
+ print(f"Claude API error: {e}")
+ return create_fallback_spec(user_prompt)
+
+def add_device_frame(group, width, height, device_type):
+ """Add device frame for mobile and tablet"""
+ if device_type == "mobile":
+ # Mobile frame
+ ET.SubElement(group, 'rect', {
+ 'x': '10', 'y': '10',
+ 'width': str(width - 20), 'height': str(height - 20),
+ 'fill': 'none',
+ 'stroke': '#2c3e50',
+ 'stroke-width': '3',
+ 'rx': '25'
+ })
+ # Home indicator
+ ET.SubElement(group, 'rect', {
+ 'x': str(width // 2 - 30), 'y': str(height - 25),
+ 'width': '60', 'height': '4',
+ 'fill': '#2c3e50',
+ 'rx': '2'
+ })
+ elif device_type == "tablet":
+ # Tablet frame
+ ET.SubElement(group, 'rect', {
+ 'x': '15', 'y': '15',
+ 'width': str(width - 30), 'height': str(height - 30),
+ 'fill': 'none',
+ 'stroke': '#2c3e50',
+ 'stroke-width': '3',
+ 'rx': '15'
+ })
+ # Home button
+ ET.SubElement(group, 'circle', {
+ 'cx': str(width // 2), 'cy': str(height - 35),
+ 'r': '8',
+ 'fill': 'none',
+ 'stroke': '#2c3e50',
+ 'stroke-width': '2'
+ })
+
+def safe_set_text(element, text):
+ """Safely set text content on an XML element, escaping special characters"""
+ if text:
+ element.text = html.escape(str(text), quote=False)
+
+def add_device_label(group, device_type, width, height):
+ """Add device type label"""
+ label_text = device_type.upper()
+ text_element = ET.SubElement(group, 'text', {
+ 'x': str(width - 10), 'y': '25',
+ 'font-size': '12',
+ 'fill': '#7f8c8d',
+ 'text-anchor': 'end',
+ 'font-weight': 'bold'
+ })
+ safe_set_text(text_element, label_text)
+
+def draw_rough_rect(group, x, y, width, height, fill, stroke, stroke_width="2"):
+ """Draw a rough, hand-drawn style rectangle"""
+ # Ensure all parameters are integers for mathematical operations
+ x, y, width, height = int(x), int(y), int(width), int(height)
+
+ # Create slightly irregular path for hand-drawn look
+ path_data = f"M{x},{y} L{x+width-1},{y+1} L{x+width},{y+height-1} L{x+1},{y+height} Z"
+ ET.SubElement(group, 'path', {
+ 'd': path_data,
+ 'fill': fill,
+ 'stroke': stroke,
+ 'stroke-width': stroke_width,
+ 'stroke-linecap': 'round',
+ 'stroke-linejoin': 'round'
+ })
+
+def generate_header(group, header_spec, width, y, height, primary_color, bg_color, text_color, border_color, x_offset, device_type):
+ """Generate header section with Balsamiq style"""
+ # Header background
+ draw_rough_rect(group, x_offset, y, width, height, bg_color, border_color)
+
+ # Header elements
+ elements = header_spec.get('elements', [])
+ if elements:
+ if device_type == "mobile":
+ # Mobile header layout
+ # Logo on left
+ logo_text = ET.SubElement(group, 'text', {
+ 'x': str(x_offset + 10), 'y': str(y + height//2 + 5),
+ 'font-size': '16',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(logo_text, "LOGO")
+
+ # Hamburger menu on right
+ for i in range(3):
+ ET.SubElement(group, 'line', {
+ 'x1': str(x_offset + width - 30), 'y1': str(y + height//2 - 8 + i*6),
+ 'x2': str(x_offset + width - 10), 'y2': str(y + height//2 - 8 + i*6),
+ 'stroke': text_color,
+ 'stroke-width': '2',
+ 'stroke-linecap': 'round'
+ })
+ else:
+ # Desktop/tablet header layout
+ element_width = (width - 40) / len(elements)
+ for i, element in enumerate(elements):
+ element_x = x_offset + 20 + i * element_width
+ element_text = str(element) if not isinstance(element, dict) else element.get('label', 'Element')
+
+ if i == 0: # First element is usually logo
+ draw_rough_rect(group, element_x - 5, y + 10, 80, height - 20, bg_color, border_color)
+ logo_element = ET.SubElement(group, 'text', {
+ 'x': str(element_x + 35), 'y': str(y + height//2 + 5),
+ 'font-size': '14',
+ 'fill': text_color,
+ 'font-weight': 'bold',
+ 'text-anchor': 'middle'
+ })
+ safe_set_text(logo_element, element_text.upper())
+ elif i == len(elements) - 1: # Last element is usually CTA
+ draw_rough_rect(group, element_x, y + 10, 80, height - 20, primary_color, border_color)
+ cta_element = ET.SubElement(group, 'text', {
+ 'x': str(element_x + 40), 'y': str(y + height//2 + 5),
+ 'font-size': '12',
+ 'fill': 'white',
+ 'font-weight': 'bold',
+ 'text-anchor': 'middle'
+ })
+ safe_set_text(cta_element, element_text)
+ else: # Navigation items
+ nav_element = ET.SubElement(group, 'text', {
+ 'x': str(element_x), 'y': str(y + height//2 + 5),
+ 'font-size': '14',
+ 'fill': text_color,
+ 'font-weight': '500'
+ })
+ safe_set_text(nav_element, element_text)
+
+def generate_sidebar(group, sidebar_spec, x, y, width, height, primary_color, bg_color, text_color, border_color, device_type):
+ """Generate sidebar section"""
+ # Sidebar background
+ draw_rough_rect(group, x, y, width, height, bg_color, border_color)
+
+ # Title
+ menu_title = ET.SubElement(group, 'text', {
+ 'x': str(x + 15), 'y': str(y + 25),
+ 'font-size': '16',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(menu_title, "MENU")
+
+ # Menu items
+ elements = sidebar_spec.get('elements', ['Dashboard', 'Products', 'Orders', 'Customers', 'Settings'])
+ for i, element in enumerate(elements[:8]): # Limit to 8 items
+ item_y = y + 50 + i * 35
+ element_text = str(element) if not isinstance(element, dict) else element.get('label', 'Menu Item')
+
+ # Menu item background
+ if i == 0: # Highlight first item as active
+ draw_rough_rect(group, x + 5, item_y - 15, width - 10, 30, primary_color, border_color)
+ text_color_item = 'white'
+ else:
+ text_color_item = text_color
+
+ menu_item = ET.SubElement(group, 'text', {
+ 'x': str(x + 15), 'y': str(item_y),
+ 'font-size': '12',
+ 'fill': text_color_item
+ })
+ safe_set_text(menu_item, element_text)
+
+def generate_hero(group, hero_spec, x, y, width, height, primary_color, bg_color, text_color, border_color, device_type):
+ """Generate hero section"""
+ # Hero background
+ draw_rough_rect(group, x, y, width, height, bg_color, border_color)
+
+ # Hero content layout
+ if device_type == "mobile":
+ # Mobile hero - stacked layout
+ title_y = y + 40
+ subtitle_y = y + 70
+ cta_y = y + 110
+ else:
+ # Desktop/tablet hero - centered layout
+ title_y = y + height//2 - 20
+ subtitle_y = y + height//2
+ cta_y = y + height//2 + 40
+
+ # Title
+ hero_title = ET.SubElement(group, 'text', {
+ 'x': str(x + width//2), 'y': str(title_y),
+ 'font-size': '24' if device_type != "mobile" else '20',
+ 'fill': text_color,
+ 'font-weight': 'bold',
+ 'text-anchor': 'middle'
+ })
+ safe_set_text(hero_title, "HERO TITLE")
+
+ # Subtitle
+ hero_subtitle = ET.SubElement(group, 'text', {
+ 'x': str(x + width//2), 'y': str(subtitle_y),
+ 'font-size': '14' if device_type != "mobile" else '12',
+ 'fill': text_color,
+ 'text-anchor': 'middle'
+ })
+ safe_set_text(hero_subtitle, "Your compelling subtitle goes here")
+
+ # CTA Button
+ button_width = 120 if device_type != "mobile" else 100
+ button_x = x + width//2 - button_width//2
+ draw_rough_rect(group, button_x, cta_y - 15, button_width, 35, primary_color, border_color)
+ cta_button_text = ET.SubElement(group, 'text', {
+ 'x': str(x + width//2), 'y': str(cta_y + 5),
+ 'font-size': '14',
+ 'fill': 'white',
+ 'font-weight': 'bold',
+ 'text-anchor': 'middle'
+ })
+ safe_set_text(cta_button_text, "GET STARTED")
+
+def generate_section(group, section, x, y, width, height, primary_color, card_color, text_color, border_color, gap, device_type):
+ """Generate main content section"""
+ section_type = section.get('type', 'generic')
+
+ if section_type == 'grid':
+ generate_grid_section(group, section, x, y, width, height, primary_color, card_color, text_color, border_color, gap, device_type)
+ elif section_type == 'form':
+ generate_form_section(group, section, x, y, width, height, primary_color, card_color, text_color, border_color, gap, device_type)
+ elif section_type == 'two_column':
+ generate_two_column_section(group, section, x, y, width, height, primary_color, card_color, text_color, border_color, gap, device_type)
+ else:
+ # Generic section
+ draw_rough_rect(group, x, y, width, height, card_color, border_color)
+ section_title = ET.SubElement(group, 'text', {
+ 'x': str(x + 10), 'y': str(y + 20),
+ 'font-size': '14',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(section_title, section_type.upper().replace('_', ' '))
+
+def generate_grid_section(group, section, x, y, width, height, primary_color, card_color, text_color, border_color, gap, device_type):
+ """Generate grid section with Balsamiq style"""
+ rows = int(section.get('rows', 2))
+ cols = int(section.get('cols', 3))
+ elements = section.get('elements', [])
+
+ # Adjust grid for mobile
+ if device_type == "mobile":
+ cols = min(cols, 2) # Max 2 columns on mobile
+ if cols > 2:
+ rows = (len(elements) + 1) // 2 # Recalculate rows
+
+ cell_width = (width - gap * (cols - 1)) / cols
+ cell_height = (height - gap * (rows - 1)) / rows
+
+ for r in range(rows):
+ for c in range(cols):
+ cell_x = x + c * (cell_width + gap)
+ cell_y = y + r * (cell_height + gap)
+ element_index = r * cols + c
+
+ # Cell background
+ draw_rough_rect(group, cell_x, cell_y, cell_width, cell_height, card_color, border_color)
+
+ # Cell content
+ if element_index < len(elements):
+ element_data = elements[element_index]
+ element_text = str(element_data) if not isinstance(element_data, dict) else element_data.get('label', f'Card {element_index + 1}')
+
+ # Card title
+ card_title = ET.SubElement(group, 'text', {
+ 'x': str(cell_x + 10), 'y': str(cell_y + 20),
+ 'font-size': '14',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(card_title, element_text)
+
+ # Card description
+ card_desc = ET.SubElement(group, 'text', {
+ 'x': str(cell_x + 10), 'y': str(cell_y + 40),
+ 'font-size': '10',
+ 'fill': text_color
+ })
+ safe_set_text(card_desc, "Description text here")
+
+ # Placeholder image area
+ if cell_height > 80:
+ image_y = cell_y + 50
+ image_height = min(60, cell_height - 70)
+ draw_rough_rect(group, cell_x + 10, image_y, cell_width - 20, image_height, '#f1f2f6', border_color, "1")
+ # X marks for image placeholder
+ ET.SubElement(group, 'line', {
+ 'x1': str(cell_x + 15), 'y1': str(image_y + 5),
+ 'x2': str(cell_x + cell_width - 15), 'y2': str(image_y + image_height - 5),
+ 'stroke': '#ddd',
+ 'stroke-width': '2'
+ })
+ ET.SubElement(group, 'line', {
+ 'x1': str(cell_x + cell_width - 15), 'y1': str(image_y + 5),
+ 'x2': str(cell_x + 15), 'y2': str(image_y + image_height - 5),
+ 'stroke': '#ddd',
+ 'stroke-width': '2'
+ })
+
+def generate_form_section(group, section, x, y, width, height, primary_color, card_color, text_color, border_color, gap, device_type):
+ """Generate form section"""
+ # Form background
+ draw_rough_rect(group, x, y, width, height, card_color, border_color)
+
+ # Form title
+ form_title = ET.SubElement(group, 'text', {
+ 'x': str(x + 20), 'y': str(y + 30),
+ 'font-size': '18',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(form_title, "CONTACT FORM")
+
+ # Form fields
+ fields = section.get('fields', ['name', 'email', 'message', 'submit'])
+ field_height = 40
+ field_gap = 15
+ start_y = y + 50
+ field_width = width - 40
+
+ if device_type == "mobile":
+ field_width = width - 20
+
+ for i, field in enumerate(fields):
+ field_y = start_y + i * (field_height + field_gap)
+ field_text = str(field) if not isinstance(field, dict) else field.get('label', field)
+
+ if field_text == 'submit':
+ # Submit button
+ button_width = 120
+ button_x = x + 20
+ draw_rough_rect(group, button_x, field_y, button_width, 40, primary_color, border_color)
+ submit_text = ET.SubElement(group, 'text', {
+ 'x': str(button_x + 60), 'y': str(field_y + 25),
+ 'font-size': '14',
+ 'fill': 'white',
+ 'text-anchor': 'middle',
+ 'font-weight': 'bold'
+ })
+ safe_set_text(submit_text, 'SUBMIT')
+ elif field_text == 'message':
+ # Text area
+ draw_rough_rect(group, x + 20, field_y, field_width, 80, 'white', border_color, "1")
+ field_label = ET.SubElement(group, 'text', {
+ 'x': str(x + 30), 'y': str(field_y - 5),
+ 'font-size': '12',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(field_label, field_text.upper())
+ placeholder_text = ET.SubElement(group, 'text', {
+ 'x': str(x + 30), 'y': str(field_y + 20),
+ 'font-size': '10',
+ 'fill': '#7f8c8d'
+ })
+ safe_set_text(placeholder_text, "Your message here...")
+ else:
+ # Regular input field
+ draw_rough_rect(group, x + 20, field_y, field_width, field_height, 'white', border_color, "1")
+ field_label = ET.SubElement(group, 'text', {
+ 'x': str(x + 30), 'y': str(field_y - 5),
+ 'font-size': '12',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(field_label, field_text.upper())
+ placeholder_text = ET.SubElement(group, 'text', {
+ 'x': str(x + 30), 'y': str(field_y + 25),
+ 'font-size': '10',
+ 'fill': '#7f8c8d'
+ })
+ safe_set_text(placeholder_text, f"Enter your {field_text}...")
+
+def generate_two_column_section(group, section, x, y, width, height, primary_color, card_color, text_color, border_color, gap, device_type):
+ """Generate two-column section"""
+ if device_type == "mobile":
+ # Stack columns on mobile
+ column_height = (height - gap) / 2
+
+ # Top column
+ draw_rough_rect(group, x, y, width, column_height, card_color, border_color)
+ section1_text = ET.SubElement(group, 'text', {
+ 'x': str(x + 10), 'y': str(y + 20),
+ 'font-size': '14',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(section1_text, 'SECTION 1')
+
+ # Bottom column
+ column2_y = y + column_height + gap
+ draw_rough_rect(group, x, column2_y, width, column_height, card_color, border_color)
+ section2_text = ET.SubElement(group, 'text', {
+ 'x': str(x + 10), 'y': str(column2_y + 20),
+ 'font-size': '14',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(section2_text, 'SECTION 2')
+ else:
+ # Side-by-side columns
+ column_width = (width - gap) / 2
+
+ # Left column
+ draw_rough_rect(group, x, y, column_width, height, card_color, border_color)
+ left_text = ET.SubElement(group, 'text', {
+ 'x': str(x + 10), 'y': str(y + 20),
+ 'font-size': '14',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(left_text, 'LEFT COLUMN')
+
+ # Right column
+ column2_x = x + column_width + gap
+ draw_rough_rect(group, column2_x, y, column_width, height, card_color, border_color)
+ right_text = ET.SubElement(group, 'text', {
+ 'x': str(column2_x + 10), 'y': str(y + 20),
+ 'font-size': '14',
+ 'fill': text_color,
+ 'font-weight': 'bold'
+ })
+ safe_set_text(right_text, 'RIGHT COLUMN')
+
+def generate_footer(group, footer_spec, x, y, width, height, primary_color, bg_color, text_color, border_color, device_type):
+ """Generate footer section"""
+ # Footer background
+ draw_rough_rect(group, x, y, width, height, bg_color, border_color)
+
+ # Footer elements
+ elements = footer_spec.get('elements', ['Links', 'About', 'Contact', 'Copyright'])
+ if device_type == "mobile":
+ # Stack footer elements vertically on mobile
+ for i, element in enumerate(elements[:3]): # Limit to 3 on mobile
+ element_text = str(element) if not isinstance(element, dict) else element.get('label', 'Footer Item')
+ footer_item = ET.SubElement(group, 'text', {
+ 'x': str(x + 10), 'y': str(y + 15 + i * 15),
+ 'font-size': '10',
+ 'fill': text_color
+ })
+ safe_set_text(footer_item, element_text)
+ else:
+ # Horizontal layout for desktop/tablet
+ if elements:
+ element_width = (width - 40) / len(elements)
+ for i, element in enumerate(elements):
+ element_x = x + 20 + i * element_width
+ element_text = str(element) if not isinstance(element, dict) else element.get('label', 'Footer Item')
+ footer_item = ET.SubElement(group, 'text', {
+ 'x': str(element_x), 'y': str(y + height//2 + 5),
+ 'font-size': '12',
+ 'fill': text_color
+ })
+ safe_set_text(footer_item, element_text)
+
+def create_fallback_spec(prompt: str) -> Dict[str, Any]:
+ """Create a fallback wireframe specification when Claude fails"""
+ prompt_lower = prompt.lower()
+
+ # Check for specific wireframe types
+ is_ecommerce = any(word in prompt_lower for word in ['e-commerce', 'ecommerce', 'product', 'shop', 'store'])
+ is_dashboard = any(word in prompt_lower for word in ['dashboard', 'admin', 'analytics'])
+ is_landing = any(word in prompt_lower for word in ['landing', 'hero', 'banner'])
+ has_form = any(word in prompt_lower for word in ['form', 'signup', 'login', 'contact'])
+
+ # Basic fallback logic
+ has_header = any(word in prompt_lower for word in ['header', 'nav', 'navbar'])
+ has_sidebar = any(word in prompt_lower for word in ['sidebar', 'left nav', 'drawer'])
+ has_hero = any(word in prompt_lower for word in ['hero', 'banner', 'jumbotron'])
+ has_footer = any(word in prompt_lower for word in ['footer', 'bottom'])
+
+ # Extract grid dimensions if specified
+ grid_rows, grid_cols = 2, 3 # Default
+ if 'x' in prompt_lower:
+ try:
+ parts = prompt_lower.split('x')
+ if len(parts) == 2:
+ grid_cols = int(parts[0].strip().split()[-1])
+ grid_rows = int(parts[1].strip().split()[0])
+ except:
+ pass
+
+ # Special handling for e-commerce product pages
+ if is_ecommerce and 'product' in prompt_lower:
+ return {
+ "layout": {
+ "page": {"width": 1440, "height": 1200},
+ "header": {"enabled": True, "height": 80, "elements": ["logo", "search", "nav", "cart", "account"]},
+ "sidebar": {"enabled": False, "width": 240, "position": "left", "elements": []},
+ "hero": {"enabled": False, "height": 200, "elements": []},
+ "main_content": {
+ "sections": [
+ {
+ "type": "two_column",
+ "height": 600,
+ "elements": ["product_gallery", "product_details"]
+ },
+ {
+ "type": "grid",
+ "rows": 1,
+ "cols": 4,
+ "height": 200,
+ "elements": ["specifications", "shipping", "reviews", "related"]
+ }
+ ]
+ },
+ "footer": {"enabled": True, "height": 120, "elements": ["company_info", "customer_service", "legal", "social"]}
+ },
+ "styling": {
+ "theme": "balsamiq",
+ "colors": {"primary": "#4F46E5", "secondary": "#64748B", "accent": "#EF4444", "background": "#FFFFFF", "text": "#1F2937"},
+ "spacing": {"gap": 24, "padding": 32}
+ },
+ "annotations": {
+ "title": "E-commerce Product Page Wireframe",
+ "description": "Product detail page with gallery and product information"
+ }
+ }
+
+ # Special handling for dashboards
+ elif is_dashboard:
+ return {
+ "layout": {
+ "page": {"width": 1440, "height": 1000},
+ "header": {"enabled": True, "height": 80, "elements": ["logo", "nav", "user_menu", "notifications"]},
+ "sidebar": {"enabled": True, "width": 280, "position": "left", "elements": ["dashboard", "analytics", "reports", "settings"]},
+ "hero": {"enabled": False, "height": 200, "elements": []},
+ "main_content": {
+ "sections": [
+ {
+ "type": "grid",
+ "rows": 2,
+ "cols": 4,
+ "height": 300,
+ "elements": ["stats_1", "stats_2", "stats_3", "stats_4", "chart_1", "chart_2", "table_1", "activity"]
+ }
+ ]
+ },
+ "footer": {"enabled": False, "height": 64, "elements": []}
+ },
+ "styling": {
+ "theme": "balsamiq",
+ "colors": {"primary": "#3B82F6", "secondary": "#6B7280", "accent": "#10B981", "background": "#F9FAFB", "text": "#1F2937"},
+ "spacing": {"gap": 24, "padding": 32}
+ },
+ "annotations": {
+ "title": "Dashboard Wireframe",
+ "description": "Analytics dashboard with stats cards and charts"
+ }
+ }
+
+ # Special handling for landing pages
+ elif is_landing:
+ return {
+ "layout": {
+ "page": {"width": 1440, "height": 1000},
+ "header": {"enabled": True, "height": 80, "elements": ["logo", "nav", "cta_button"]},
+ "sidebar": {"enabled": False, "width": 240, "position": "left", "elements": []},
+ "hero": {"enabled": True, "height": 400, "elements": ["title", "subtitle", "cta_button", "hero_image"]},
+ "main_content": {
+ "sections": [
+ {
+ "type": "grid",
+ "rows": 2,
+ "cols": 3,
+ "height": 300,
+ "elements": ["feature_1", "feature_2", "feature_3", "testimonial_1", "testimonial_2", "testimonial_3"]
+ }
+ ]
+ },
+ "footer": {"enabled": True, "height": 120, "elements": ["company", "product", "support", "legal"]}
+ },
+ "styling": {
+ "theme": "balsamiq",
+ "colors": {"primary": "#3B82F6", "secondary": "#6B7280", "accent": "#F59E0B", "background": "#FFFFFF", "text": "#1F2937"},
+ "spacing": {"gap": 32, "padding": 40}
+ },
+ "annotations": {
+ "title": "Landing Page Wireframe",
+ "description": "Modern landing page with hero section and features"
+ }
+ }
+
+ # Default fallback
+ return {
+ "layout": {
+ "page": {"width": 1440, "height": 800},
+ "header": {"enabled": has_header or True, "height": 80, "elements": ["logo", "navigation", "cta"]},
+ "sidebar": {"enabled": has_sidebar, "width": 280, "position": "left", "elements": ["menu", "filters", "options"]},
+ "hero": {"enabled": has_hero, "height": 200, "elements": ["title", "subtitle", "cta"]},
+ "main_content": {
+ "sections": [
+ {
+ "type": "grid",
+ "rows": grid_rows,
+ "cols": grid_cols,
+ "height": 400,
+ "elements": [f"card_{i+1}" for i in range(grid_rows * grid_cols)]
+ }
+ ] + ([{
+ "type": "form",
+ "height": 300,
+ "fields": ["name", "email", "message", "submit"]
+ }] if has_form else [])
+ },
+ "footer": {"enabled": has_footer or True, "height": 80, "elements": ["links", "about", "contact", "copyright"]}
+ },
+ "styling": {
+ "theme": "balsamiq",
+ "colors": {"primary": "#3B82F6", "secondary": "#6B7280", "background": "#FFFFFF", "text": "#1F2937"},
+ "spacing": {"gap": 20, "padding": 24}
+ },
+ "annotations": {
+ "title": "Custom Wireframe",
+ "description": f"Generated from: {prompt[:100]}..."
+ }
+ }
+
+# MAIN ENDPOINTS - Solution 1: Device-specific endpoints that return pure SVG
+
+@app.route('/generate-wireframe/', methods=['POST'])
+def generate_wireframe_by_device(device_type):
+ """Generate wireframe for specific device type, returns SVG directly"""
+ try:
+ data = request.get_json()
+ user_prompt = data.get('prompt', '')
+
+ if not user_prompt:
+ return jsonify({"error": "No prompt provided"}), 400
+
+ if device_type not in ['desktop', 'tablet', 'mobile']:
+ return jsonify({"error": "Invalid device type. Use: desktop, tablet, or mobile"}), 400
+
+ # Get wireframe spec from Claude
+ wireframe_spec = get_wireframe_spec_from_claude(user_prompt, device_type)
+
+ # Generate SVG
+ svg_content = generate_svg_wireframe(wireframe_spec, device_type)
+
+ # Return SVG directly with proper headers
+ response = make_response(svg_content)
+ response.headers['Content-Type'] = 'image/svg+xml'
+ response.headers['Cache-Control'] = 'no-cache'
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as e:
+ print(f"Error generating {device_type} wireframe: {e}")
+ return jsonify({"error": str(e)}), 500
+
+# Keep original endpoint for backward compatibility
+@app.route('/generate-wireframe', methods=['POST'])
+def generate_wireframe():
+ """Original endpoint - defaults to desktop"""
+ try:
+ data = request.get_json()
+ user_prompt = data.get('prompt', '')
+ device_type = data.get('device', 'desktop')
+
+ if not user_prompt:
+ return jsonify({"error": "No prompt provided"}), 400
+
+ if device_type not in ['desktop', 'tablet', 'mobile']:
+ device_type = 'desktop'
+
+ # Get wireframe spec from Claude
+ wireframe_spec = get_wireframe_spec_from_claude(user_prompt, device_type)
+
+ # Generate SVG
+ svg_content = generate_svg_wireframe(wireframe_spec, device_type)
+
+ # Return SVG directly
+ response = make_response(svg_content)
+ response.headers['Content-Type'] = 'image/svg+xml'
+ response.headers['Cache-Control'] = 'no-cache'
+ return response
+
+ except Exception as e:
+ print(f"Error generating wireframe: {e}")
+ return jsonify({"error": str(e)}), 500
+
+# Optional: Endpoint to get all device wireframes (if you still need this)
+@app.route('/generate-all-devices', methods=['POST'])
+def generate_all_devices():
+ """Generate wireframes for all devices and return URLs to fetch them"""
+ try:
+ data = request.get_json()
+ user_prompt = data.get('prompt', '')
+
+ if not user_prompt:
+ return jsonify({"error": "No prompt provided"}), 400
+
+ # Return metadata about where to fetch each device wireframe
+ devices = ['desktop', 'tablet', 'mobile']
+ device_urls = {}
+
+ for device in devices:
+ device_urls[device] = f"/generate-wireframe/{device}"
+
+ return jsonify({
+ "success": True,
+ "message": "Use the provided URLs to fetch SVG wireframes for each device",
+ "device_endpoints": device_urls,
+ "usage": "POST to each endpoint with {'prompt': 'your prompt'} to get SVG",
+ "original_prompt": user_prompt
+ })
+
+ except Exception as e:
+ return jsonify({"error": str(e)}), 500
+
+# Wireframe Persistence Endpoints
+@app.route('/api/wireframes', methods=['POST'])
+@require_auth
+def save_wireframe():
+ """Save wireframe data to database"""
+ try:
+ data = request.get_json()
+
+ # Extract wireframe data
+ wireframe_data = data.get('wireframe', {})
+ elements_data = data.get('elements', [])
+
+ # Get user_id from JWT token - handle both local and remote verification
+ user_id = None
+ if hasattr(request, 'user_id'):
+ user_id = request.user_id
+
+ if not user_id:
+ return jsonify({"error": "User authentication required - no valid user ID found"}), 401
+
+ project_id = data.get('project_id')
+ name = wireframe_data.get('name', 'Untitled Wireframe')
+ description = wireframe_data.get('description', '')
+ device_type = wireframe_data.get('device_type', 'desktop')
+ dimensions = wireframe_data.get('dimensions', {'width': 1440, 'height': 1024})
+ metadata = wireframe_data.get('metadata', {})
+
+ if not user_id:
+ return jsonify({"error": "User authentication required"}), 401
+
+ conn = get_db_connection()
+ if not conn:
+ return jsonify({"error": "Database connection failed"}), 500
+
+ try:
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
+ # Check if this is an update (wireframe has an ID) or new creation
+ wireframe_id = wireframe_data.get('id')
+
+ if wireframe_id:
+ # Update existing wireframe
+ cur.execute("""
+ UPDATE wireframes
+ SET name = %s, description = %s, device_type = %s, dimensions = %s, metadata = %s, updated_at = NOW()
+ WHERE id = %s AND user_id = %s
+ RETURNING id
+ """, (name, description, device_type, json.dumps(dimensions), json.dumps(metadata), wireframe_id, user_id))
+
+ if cur.rowcount == 0:
+ return jsonify({"error": "Wireframe not found or access denied"}), 404
+
+ # Clear existing elements
+ cur.execute("DELETE FROM wireframe_elements WHERE wireframe_id = %s", (wireframe_id,))
+
+ else:
+ # Create new wireframe
+ cur.execute("""
+ INSERT INTO wireframes (user_id, project_id, name, description, device_type, dimensions, metadata)
+ VALUES (%s, %s, %s, %s, %s, %s, %s)
+ RETURNING id
+ """, (user_id, project_id, name, description, device_type, json.dumps(dimensions), json.dumps(metadata)))
+
+ wireframe_id = cur.fetchone()['id']
+
+ # Insert elements
+ for element in elements_data:
+ # Extract TLDraw ID from the element data
+ tldraw_id = element.get('id') or element.get('data', {}).get('id')
+
+ cur.execute("""
+ INSERT INTO wireframe_elements (wireframe_id, element_type, element_data, position, size, style, parent_id, tldraw_id, z_index)
+ VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
+ """, (
+ wireframe_id,
+ element.get('type', 'shape'),
+ json.dumps(element.get('data', {})),
+ json.dumps(element.get('position', {'x': 0, 'y': 0})),
+ json.dumps(element.get('size', {'width': 100, 'height': 100})),
+ json.dumps(element.get('style', {})),
+ element.get('parent_id'), # This can be a string ID from TLDraw
+ tldraw_id, # Store the original TLDraw ID
+ element.get('z_index', 0)
+ ))
+
+ # Create version
+ version_name = 'Updated Version' if wireframe_data.get('id') else 'Initial Version'
+ version_description = 'Wireframe updated' if wireframe_data.get('id') else 'Initial wireframe creation'
+
+ cur.execute("""
+ SELECT create_wireframe_version(%s, %s, %s, %s, %s)
+ """, (
+ wireframe_id,
+ version_name,
+ version_description,
+ json.dumps({'wireframe_id': str(wireframe_id), 'elements': elements_data}),
+ user_id
+ ))
+
+ conn.commit()
+
+ return jsonify({
+ "success": True,
+ "wireframe_id": str(wireframe_id),
+ "message": "Wireframe saved successfully"
+ }), 201
+
+ except Exception as e:
+ conn.rollback()
+ raise e
+ finally:
+ conn.close()
+
+ except Exception as e:
+ print(f"Error saving wireframe: {e}")
+ return jsonify({"error": str(e)}), 500
+
+@app.route('/api/wireframes/', methods=['GET'])
+@require_auth
+def get_wireframe(wireframe_id):
+ """Get wireframe data from database"""
+ try:
+ # Get user_id from JWT token - handle both local and remote verification
+ user_id = None
+ if hasattr(request, 'user_id'):
+ user_id = request.user_id
+
+ if not user_id:
+ return jsonify({"error": "User authentication required - no valid user ID found"}), 401
+
+ conn = get_db_connection()
+ if not conn:
+ return jsonify({"error": "Database connection failed"}), 500
+
+ try:
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
+ # First check if user owns this wireframe
+ cur.execute("""
+ SELECT user_id FROM wireframes WHERE id = %s
+ """, (wireframe_id,))
+
+ wireframe_owner = cur.fetchone()
+ if not wireframe_owner:
+ return jsonify({"error": "Wireframe not found"}), 404
+
+ if wireframe_owner['user_id'] != user_id:
+ return jsonify({"error": "Access denied"}), 403
+
+ # Get wireframe with elements using the function
+ cur.execute("SELECT * FROM get_wireframe_with_elements(%s)", (wireframe_id,))
+ result = cur.fetchone()
+
+ if not result:
+ return jsonify({"error": "Wireframe not found"}), 404
+
+ return jsonify({
+ "success": True,
+ "wireframe": result['wireframe_data'],
+ "elements": result['elements_data']
+ }), 200
+
+ finally:
+ conn.close()
+
+ except Exception as e:
+ print(f"Error getting wireframe: {e}")
+ return jsonify({"error": str(e)}), 500
+
+@app.route('/api/wireframes/user/', methods=['GET'])
+@require_auth
+def get_user_wireframes(user_id):
+ """Get all wireframes for a user"""
+ try:
+ # Get user_id from JWT token - handle both local and remote verification
+ authenticated_user_id = None
+ if hasattr(request, 'user_id'):
+ authenticated_user_id = request.user_id
+
+ if not authenticated_user_id:
+ return jsonify({"error": "User authentication required - no valid user ID found"}), 401
+
+ # Users can only access their own wireframes
+ if authenticated_user_id != user_id:
+ return jsonify({"error": "Access denied"}), 403
+
+ conn = get_db_connection()
+ if not conn:
+ return jsonify({"error": "Database connection failed"}), 500
+
+ try:
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
+ # Get wireframes for the authenticated user
+ cur.execute("""
+ SELECT id, name, description, device_type, dimensions, metadata, created_at, updated_at
+ FROM wireframes
+ WHERE user_id = %s AND is_active = true
+ ORDER BY updated_at DESC
+ """, (authenticated_user_id,))
+
+ wireframes = cur.fetchall()
+
+ return jsonify({
+ "success": True,
+ "wireframes": wireframes,
+ "count": len(wireframes)
+ }), 200
+
+ finally:
+ conn.close()
+
+ except Exception as e:
+ print(f"Error getting user wireframes: {e}")
+ return jsonify({"error": str(e)}), 500
+
+@app.route('/api/wireframes/', methods=['PUT'])
+@require_auth
+def update_wireframe(wireframe_id):
+ """Update existing wireframe data"""
+ try:
+ # Get user_id from JWT token - handle both local and remote verification
+ user_id = None
+ if hasattr(request, 'user_id'):
+ user_id = request.user_id
+
+ if not user_id:
+ return jsonify({"error": "User authentication required - no valid user ID found"}), 401
+
+ data = request.get_json()
+
+ conn = get_db_connection()
+ if not conn:
+ return jsonify({"error": "Database connection failed"}), 500
+
+ try:
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
+ # First check if user owns this wireframe
+ cur.execute("""
+ SELECT user_id FROM wireframes WHERE id = %s
+ """, (wireframe_id,))
+
+ wireframe_owner = cur.fetchone()
+ if not wireframe_owner:
+ return jsonify({"error": "Wireframe not found"}), 404
+
+ if wireframe_owner['user_id'] != user_id:
+ return jsonify({"error": "Access denied"}), 403
+
+ # Update wireframe metadata
+ name = data.get('name', 'Untitled Wireframe')
+ description = data.get('description', '')
+ device_type = data.get('device_type', 'desktop')
+ dimensions = data.get('dimensions', {'width': 1440, 'height': 1024})
+ metadata = data.get('metadata', {})
+
+ cur.execute("""
+ UPDATE wireframes
+ SET name = %s, description = %s, device_type = %s, dimensions = %s, metadata = %s, updated_at = NOW()
+ WHERE id = %s
+ """, (name, description, device_type, json.dumps(dimensions), json.dumps(metadata), wireframe_id))
+
+ # Delete existing elements
+ cur.execute("DELETE FROM wireframe_elements WHERE wireframe_id = %s", (wireframe_id,))
+
+ # Insert new elements
+ elements_data = data.get('elements', [])
+ for element in elements_data:
+ cur.execute("""
+ INSERT INTO wireframe_elements (wireframe_id, element_type, element_data, position, size, style, parent_id, z_index)
+ VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
+ """, (
+ wireframe_id,
+ element.get('type', 'shape'),
+ json.dumps(element.get('data', {})),
+ json.dumps(element.get('position', {'x': 0, 'y': 0})),
+ json.dumps(element.get('size', {'width': 100, 'height': 100})),
+ json.dumps(element.get('style', {})),
+ element.get('parent_id'),
+ element.get('z_index', 0)
+ ))
+
+ # Create new version
+ cur.execute("""
+ SELECT create_wireframe_version(%s, %s, %s, %s, %s)
+ """, (
+ wireframe_id,
+ 'Updated Version',
+ 'Wireframe updated',
+ json.dumps({'wireframe_id': str(wireframe_id), 'elements': elements_data}),
+ user_id
+ ))
+
+ conn.commit()
+
+ return jsonify({
+ "success": True,
+ "wireframe_id": str(wireframe_id),
+ "message": "Wireframe updated successfully"
+ }), 200
+
+ except Exception as e:
+ conn.rollback()
+ raise e
+ finally:
+ conn.close()
+
+ except Exception as e:
+ print(f"Error updating wireframe: {e}")
+ return jsonify({"error": str(e)}), 500
+
+@app.route('/api/wireframes/', methods=['DELETE'])
+@require_auth
+def delete_wireframe(wireframe_id):
+ """Delete a wireframe (soft delete)"""
+ try:
+ user_id = request.user_id
+
+ conn = get_db_connection()
+ if not conn:
+ return jsonify({"error": "Database connection failed"}), 500
+
+ try:
+ with conn.cursor(cursor_factory=RealDictCursor) as cur:
+ # First check if user owns this wireframe
+ cur.execute("""
+ SELECT user_id FROM wireframes WHERE id = %s
+ """, (wireframe_id,))
+
+ wireframe_owner = cur.fetchone()
+ if not wireframe_owner:
+ return jsonify({"error": "Wireframe not found"}), 404
+
+ if wireframe_owner['user_id'] != user_id:
+ return jsonify({"error": "Access denied"}), 403
+
+ # Soft delete wireframe
+ cur.execute("""
+ UPDATE wireframes SET is_active = false, updated_at = NOW() WHERE id = %s
+ """, (wireframe_id,))
+
+ conn.commit()
+
+ return jsonify({
+ "success": True,
+ "message": "Wireframe deleted successfully"
+ }), 200
+
+ finally:
+ conn.close()
+
+ except Exception as e:
+ print(f"Error deleting wireframe: {e}")
+ return jsonify({"error": str(e)}), 500
+
+# ========================================
+# MAIN APPLICATION
+# ========================================
+
+@app.route('/health', methods=['GET'])
+def health_check():
+ """Health check endpoint"""
+ try:
+ # Test database connection
+ conn = get_db_connection()
+ db_status = 'connected' if conn else 'disconnected'
+ if conn:
+ conn.close()
+
+ # Test user-auth service connection
+ auth_status = 'connected'
+ try:
+ response = requests.get(f"{USER_AUTH_SERVICE_URL}/health", timeout=5)
+ if response.status_code != 200:
+ auth_status = 'error'
+ except:
+ auth_status = 'disconnected'
+
+ return jsonify({
+ "status": "healthy",
+ "service": "ai-mockup-service",
+ "version": "1.0.0",
+ "timestamp": datetime.now().isoformat(),
+ "features": {
+ "wireframe_generation": True,
+ "authentication": True,
+ "real_time_updates": True,
+ "user_isolation": True
+ },
+ "services": {
+ "database": db_status,
+ "user_auth": auth_status
+ },
+ "environment": os.getenv('FLASK_ENV', 'development')
+ }), 200
+
+ except Exception as e:
+ return jsonify({
+ "status": "unhealthy",
+ "error": str(e)
+ }), 500
+
+@app.route('/', methods=['GET'])
+def root():
+ """Root endpoint with API documentation"""
+ return jsonify({
+ "message": "AI Mockup Service - Wireframe Generation with Authentication",
+ "version": "1.0.0",
+ "authentication": "JWT Bearer Token Required",
+ "endpoints": {
+ "health": "GET /health",
+ "generate_wireframe": "POST /generate-wireframe",
+ "generate_wireframe_desktop": "POST /generate-wireframe/desktop",
+ "generate_wireframe_tablet": "POST /generate-wireframe/tablet",
+ "generate_wireframe_mobile": "POST /generate-wireframe/mobile",
+ "generate_all_devices": "POST /generate-all-devices",
+ "save_wireframe": "POST /api/wireframes (Auth Required)",
+ "get_wireframe": "GET /api/wireframes/ (Auth Required)",
+ "update_wireframe": "PUT /api/wireframes/ (Auth Required)",
+ "delete_wireframe": "DELETE /api/wireframes/ (Auth Required)",
+ "get_user_wireframes": "GET /api/wireframes/user/ (Auth Required)"
+ },
+ "authentication": {
+ "type": "JWT Bearer Token",
+ "header": "Authorization: Bearer ",
+ "service": USER_AUTH_SERVICE_URL
+ }
+ }), 200
+
+if __name__ == '__main__':
+ # Load environment variables
+ try:
+ from dotenv import load_dotenv
+ load_dotenv()
+ except ImportError:
+ print("python-dotenv not installed, skipping .env file loading")
+
+ port = int(os.environ.get('PORT', 8021))
+ app.run(debug=True, host='0.0.0.0', port=port)
\ No newline at end of file
diff --git a/services/ai-mockup-service/src/env.example b/services/ai-mockup-service/src/env.example
new file mode 100644
index 0000000..8cbe6b0
--- /dev/null
+++ b/services/ai-mockup-service/src/env.example
@@ -0,0 +1,17 @@
+# Claude API Configuration
+CLAUDE_API_KEY=your-claude-api-key-here
+
+# Flask Configuration
+FLASK_ENV=development
+PORT=5000
+
+# Database Configuration
+POSTGRES_HOST=postgres
+POSTGRES_DB=dev_pipeline
+POSTGRES_USER=pipeline_admin
+POSTGRES_PASSWORD=secure_pipeline_2024
+POSTGRES_PORT=5433
+
+# JWT Configuration
+JWT_SECRET=access-secret-key-2024-tech4biz
+JWT_ALGORITHM=HS256
diff --git a/services/ai-mockup-service/src/migrate_database.py b/services/ai-mockup-service/src/migrate_database.py
new file mode 100644
index 0000000..532cbba
--- /dev/null
+++ b/services/ai-mockup-service/src/migrate_database.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+"""
+Database migration script to fix TLDraw ID issues
+"""
+
+import os
+import psycopg2
+from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
+
+def migrate_database():
+ """Migrate the database to fix TLDraw ID issues"""
+
+ # Database connection details
+ db_host = os.getenv('POSTGRES_HOST', 'localhost')
+ db_user = os.getenv('POSTGRES_USER', 'pipeline_admin')
+ db_password = os.getenv('POSTGRES_PASSWORD', 'secure_pipeline_2024')
+ db_port = os.getenv('POSTGRES_PORT', '5433')
+ db_name = os.getenv('POSTGRES_DB', 'dev_pipeline')
+
+ try:
+ conn = psycopg2.connect(
+ host=db_host,
+ user=db_user,
+ password=db_password,
+ port=db_port,
+ database=db_name
+ )
+
+ with conn.cursor() as cur:
+ print("๐ Migrating database to fix TLDraw ID issues...")
+
+ # Check if tldraw_id column exists
+ cur.execute("""
+ SELECT column_name
+ FROM information_schema.columns
+ WHERE table_name = 'wireframe_elements'
+ AND column_name = 'tldraw_id'
+ """)
+
+ if not cur.fetchone():
+ print(" Adding tldraw_id column...")
+ cur.execute("ALTER TABLE wireframe_elements ADD COLUMN tldraw_id VARCHAR(255)")
+
+ # Check if parent_id is already VARCHAR
+ cur.execute("""
+ SELECT data_type
+ FROM information_schema.columns
+ WHERE table_name = 'wireframe_elements'
+ AND column_name = 'parent_id'
+ """)
+
+ column_info = cur.fetchone()
+ if column_info and column_info[0] == 'uuid':
+ print(" Converting parent_id from UUID to VARCHAR...")
+ # Drop the foreign key constraint first
+ cur.execute("""
+ ALTER TABLE wireframe_elements
+ DROP CONSTRAINT IF EXISTS wireframe_elements_parent_id_fkey
+ """)
+
+ # Change the column type
+ cur.execute("""
+ ALTER TABLE wireframe_elements
+ ALTER COLUMN parent_id TYPE VARCHAR(255)
+ """)
+
+ # Update the function
+ print(" Updating get_wireframe_with_elements function...")
+ cur.execute("""
+ CREATE OR REPLACE FUNCTION get_wireframe_with_elements(p_wireframe_id UUID)
+ RETURNS TABLE(
+ wireframe_data JSONB,
+ elements_data JSONB
+ ) AS $$
+ BEGIN
+ RETURN QUERY
+ SELECT
+ to_jsonb(w.*) as wireframe_data,
+ COALESCE(
+ jsonb_agg(
+ jsonb_build_object(
+ 'id', we.id,
+ 'tldraw_id', we.tldraw_id,
+ 'type', we.element_type,
+ 'data', we.element_data,
+ 'position', we.position,
+ 'size', we.size,
+ 'style', we.style,
+ 'parent_id', we.parent_id,
+ 'z_index', we.z_index
+ ) ORDER BY we.z_index, we.created_at
+ ) FILTER (WHERE we.id IS NOT NULL),
+ '[]'::jsonb
+ ) as elements_data
+ FROM wireframes w
+ LEFT JOIN wireframe_elements we ON w.id = we.wireframe_id
+ WHERE w.id = p_wireframe_id
+ GROUP BY w.id, w.user_id, w.project_id, w.name, w.description,
+ w.device_type, w.dimensions, w.metadata, w.is_active,
+ w.created_at, w.updated_at;
+ END;
+ $$ LANGUAGE plpgsql;
+ """)
+
+ conn.commit()
+ print("โ
Database migration completed successfully!")
+
+ conn.close()
+ return True
+
+ except Exception as e:
+ print(f"โ Database migration failed: {e}")
+ return False
+
+if __name__ == "__main__":
+ print("๐ Starting database migration...")
+ success = migrate_database()
+
+ if success:
+ print("\nโ
Migration completed successfully!")
+ print("The database now supports TLDraw string IDs properly.")
+ else:
+ print("\nโ Migration failed!")
+ print("Please check the database connection and try again.")
diff --git a/services/ai-mockup-service/src/run.py b/services/ai-mockup-service/src/run.py
new file mode 100644
index 0000000..81b9ccc
--- /dev/null
+++ b/services/ai-mockup-service/src/run.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+"""
+Startup script for the Wireframe Generator Backend
+"""
+
+import os
+import sys
+from pathlib import Path
+
+def check_dependencies():
+ """Check if required packages are installed"""
+ try:
+ import flask
+ import anthropic
+ import dotenv
+ print("โ
All dependencies are installed")
+ return True
+ except ImportError as e:
+ print(f"โ Missing dependency: {e}")
+ print("Please run: pip install -r requirements.txt")
+ return False
+
+def check_env_file():
+ """Check if .env file exists and has API key"""
+ env_path = Path(".env")
+ if not env_path.exists():
+ print("โ ๏ธ No .env file found")
+ print("Please copy env.example to .env and add your Claude API key")
+ return False
+
+ # Check if API key is set
+ from dotenv import load_dotenv
+ load_dotenv()
+
+ api_key = os.getenv("CLAUDE_API_KEY")
+ if not api_key or api_key == "your-claude-api-key-here":
+ print("โ ๏ธ Claude API key not configured")
+ print("Please add your actual API key to the .env file")
+ return False
+
+ print("โ
Environment configured")
+ return True
+
+def main():
+ """Main startup function"""
+ print("๐ Starting Wireframe Generator Backend...")
+
+ # Check dependencies
+ if not check_dependencies():
+ sys.exit(1)
+
+ # Check environment
+ if not check_env_file():
+ print("\nTo continue without API key (fallback mode), press Enter...")
+ input()
+
+ # Import and run the app
+ try:
+ from app import app
+ port = int(os.environ.get('PORT', 5000))
+
+ print(f"๐ Backend starting on http://localhost:{port}")
+ print("๐ฑ Frontend can connect to this backend")
+ print("๐ Press Ctrl+C to stop the server")
+
+ app.run(debug=True, host='0.0.0.0', port=port)
+
+ except Exception as e:
+ print(f"โ Failed to start backend: {e}")
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()
diff --git a/services/ai-mockup-service/src/setup_database.py b/services/ai-mockup-service/src/setup_database.py
new file mode 100644
index 0000000..f4e2c46
--- /dev/null
+++ b/services/ai-mockup-service/src/setup_database.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+"""
+Database setup script for Tech4biz Wireframe Generator
+This script creates the database and runs the schema files
+"""
+
+import os
+import psycopg2
+from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
+from dotenv import load_dotenv
+
+def setup_database():
+ """Setup the database and create tables"""
+
+ # Load environment variables
+ load_dotenv()
+
+ # Database connection details
+ db_host = os.getenv('POSTGRES_HOST', 'localhost')
+ db_user = os.getenv('POSTGRES_USER', 'pipeline_admin')
+ db_password = os.getenv('POSTGRES_PASSWORD', 'secure_pipeline_2024')
+ db_port = os.getenv('POSTGRES_PORT', '5432') # Changed to 5432 for Docker
+ db_name = os.getenv('POSTGRES_DB', 'dev_pipeline')
+
+ # First connect to postgres to create database
+ try:
+ conn = psycopg2.connect(
+ host=db_host,
+ user=db_user,
+ password=db_password,
+ port=db_port,
+ database='postgres' # Connect to default postgres database first
+ )
+ conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
+
+ with conn.cursor() as cur:
+ # Check if database exists
+ cur.execute("SELECT 1 FROM pg_database WHERE datname = %s", (db_name,))
+ if not cur.fetchone():
+ print(f"Creating database '{db_name}'...")
+ cur.execute(f"CREATE DATABASE {db_name}")
+ print("Database created successfully!")
+ else:
+ print(f"Database '{db_name}' already exists")
+
+ conn.close()
+
+ except Exception as e:
+ print(f"Error creating database: {e}")
+ return False
+
+ # Now connect to the new database and run schema files
+ try:
+ conn = psycopg2.connect(
+ host=db_host,
+ user=db_user,
+ password=db_password,
+ port=db_port,
+ database=db_name
+ )
+
+ with conn.cursor() as cur:
+ print("Running user authentication schema...")
+ schema_file = os.path.join(os.path.dirname(__file__), 'sql', '001_user_auth_schema.sql')
+ with open(schema_file, 'r') as f:
+ schema_sql = f.read()
+ cur.execute(schema_sql)
+
+ print("Running wireframe schema...")
+ schema_file = os.path.join(os.path.dirname(__file__), 'sql', '002_wireframe_schema.sql')
+ with open(schema_file, 'r') as f:
+ schema_sql = f.read()
+ cur.execute(schema_sql)
+
+ conn.commit()
+ print("Database setup completed successfully!")
+
+ conn.close()
+ return True
+
+ except Exception as e:
+ print(f"Error setting up database: {e}")
+ return False
+
+if __name__ == "__main__":
+ print("Setting up Tech4biz Wireframe Generator database...")
+ success = setup_database()
+
+ if success:
+ print("\nโ
Database setup completed successfully!")
+ print("\nNext steps:")
+ print("1. Make sure your .env file has the correct database credentials")
+ print("2. Start the backend server with: python app.py")
+ print("3. The wireframes will now be automatically saved and loaded!")
+ else:
+ print("\nโ Database setup failed!")
+ print("Please check your database connection settings and try again.")
diff --git a/services/ai-mockup-service/src/sql/001_user_auth_schema.sql b/services/ai-mockup-service/src/sql/001_user_auth_schema.sql
new file mode 100644
index 0000000..11fd740
--- /dev/null
+++ b/services/ai-mockup-service/src/sql/001_user_auth_schema.sql
@@ -0,0 +1,190 @@
+-- User Authentication Database Schema
+-- JWT-based authentication with user preferences for template features
+
+-- Drop tables if they exist (for development)
+DROP TABLE IF EXISTS user_feature_preferences CASCADE;
+DROP TABLE IF EXISTS user_sessions CASCADE;
+DROP TABLE IF EXISTS refresh_tokens CASCADE;
+DROP TABLE IF EXISTS users CASCADE;
+DROP TABLE IF EXISTS user_projects CASCADE;
+
+-- Enable UUID extension if not already enabled
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+
+-- Users table - Core user accounts
+CREATE TABLE users (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ username VARCHAR(50) NOT NULL UNIQUE,
+ email VARCHAR(255) NOT NULL UNIQUE,
+ password_hash VARCHAR(255) NOT NULL,
+ first_name VARCHAR(100),
+ last_name VARCHAR(100),
+ role VARCHAR(20) DEFAULT 'user' CHECK (role IN ('user', 'admin', 'moderator')),
+ email_verified BOOLEAN DEFAULT false,
+ is_active BOOLEAN DEFAULT true,
+ last_login TIMESTAMP,
+ created_at TIMESTAMP DEFAULT NOW(),
+ updated_at TIMESTAMP DEFAULT NOW()
+);
+
+-- Refresh tokens table - JWT refresh token management
+CREATE TABLE refresh_tokens (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ user_id UUID REFERENCES users(id) ON DELETE CASCADE,
+ token_hash VARCHAR(255) NOT NULL,
+ expires_at TIMESTAMP NOT NULL,
+ created_at TIMESTAMP DEFAULT NOW(),
+ revoked_at TIMESTAMP,
+ is_revoked BOOLEAN DEFAULT false
+);
+
+-- User sessions table - Track user activity and sessions
+CREATE TABLE user_sessions (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ user_id UUID REFERENCES users(id) ON DELETE CASCADE,
+ session_token VARCHAR(255) UNIQUE,
+ ip_address INET,
+ user_agent TEXT,
+ device_info JSONB,
+ is_active BOOLEAN DEFAULT true,
+ last_activity TIMESTAMP DEFAULT NOW(),
+ created_at TIMESTAMP DEFAULT NOW(),
+ expires_at TIMESTAMP DEFAULT NOW() + INTERVAL '30 days'
+);
+
+-- User feature preferences table - Track which features users have removed/customized
+CREATE TABLE user_feature_preferences (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ user_id UUID REFERENCES users(id) ON DELETE CASCADE,
+ template_type VARCHAR(100) NOT NULL, -- 'healthcare', 'ecommerce', etc.
+ feature_id VARCHAR(100) NOT NULL, -- feature identifier from template-manager
+ preference_type VARCHAR(20) NOT NULL CHECK (preference_type IN ('removed', 'added', 'customized')),
+ custom_data JSONB, -- For storing custom feature modifications
+ created_at TIMESTAMP DEFAULT NOW(),
+ updated_at TIMESTAMP DEFAULT NOW(),
+ UNIQUE(user_id, template_type, feature_id, preference_type)
+);
+
+-- User project tracking - Track user's projects and their selections
+CREATE TABLE user_projects (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ user_id UUID REFERENCES users(id) ON DELETE CASCADE,
+ project_name VARCHAR(200) NOT NULL,
+ project_type VARCHAR(100) NOT NULL,
+ selected_features JSONB, -- Array of selected feature IDs
+ custom_features JSONB, -- Array of user-created custom features
+ project_data JSONB, -- Complete project configuration
+ is_active BOOLEAN DEFAULT true,
+ created_at TIMESTAMP DEFAULT NOW(),
+ updated_at TIMESTAMP DEFAULT NOW()
+);
+
+-- Indexes for performance
+CREATE INDEX idx_users_email ON users(email);
+CREATE INDEX idx_users_username ON users(username);
+CREATE INDEX idx_users_active ON users(is_active);
+CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);
+CREATE INDEX idx_refresh_tokens_expires_at ON refresh_tokens(expires_at);
+CREATE INDEX idx_refresh_tokens_revoked ON refresh_tokens(is_revoked);
+CREATE INDEX idx_user_sessions_user_id ON user_sessions(user_id);
+CREATE INDEX idx_user_sessions_active ON user_sessions(is_active);
+CREATE INDEX idx_user_sessions_token ON user_sessions(session_token);
+CREATE INDEX idx_user_feature_preferences_user_id ON user_feature_preferences(user_id);
+CREATE INDEX idx_user_feature_preferences_template ON user_feature_preferences(template_type);
+CREATE INDEX idx_user_projects_user_id ON user_projects(user_id);
+CREATE INDEX idx_user_projects_active ON user_projects(is_active);
+
+-- Update timestamps trigger function (reuse from template-manager)
+CREATE OR REPLACE FUNCTION update_updated_at_column()
+RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = NOW();
+ RETURN NEW;
+END;
+$$ language 'plpgsql';
+
+-- Apply triggers for updated_at columns
+CREATE TRIGGER update_users_updated_at
+ BEFORE UPDATE ON users
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_user_feature_preferences_updated_at
+ BEFORE UPDATE ON user_feature_preferences
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_user_projects_updated_at
+ BEFORE UPDATE ON user_projects
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+-- Functions for cleanup and maintenance
+CREATE OR REPLACE FUNCTION cleanup_expired_tokens()
+RETURNS INTEGER AS $$
+DECLARE
+ deleted_count INTEGER;
+BEGIN
+ DELETE FROM refresh_tokens
+ WHERE expires_at < NOW() OR is_revoked = true;
+
+ GET DIAGNOSTICS deleted_count = ROW_COUNT;
+
+ RETURN deleted_count;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION cleanup_inactive_sessions()
+RETURNS INTEGER AS $$
+DECLARE
+ deleted_count INTEGER;
+BEGIN
+ UPDATE user_sessions
+ SET is_active = false
+ WHERE expires_at < NOW() OR last_activity < NOW() - INTERVAL '7 days';
+
+ GET DIAGNOSTICS deleted_count = ROW_COUNT;
+
+ RETURN deleted_count;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Insert initial admin user (password: admin123 - change in production!)
+INSERT INTO users (
+ id, username, email, password_hash, first_name, last_name, role, email_verified, is_active
+) VALUES (
+ uuid_generate_v4(),
+ 'admin',
+ 'admin@tech4biz.com',
+ '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', -- bcrypt hash of 'admin123'
+ 'System',
+ 'Administrator',
+ 'admin',
+ true,
+ true
+) ON CONFLICT (email) DO NOTHING;
+
+-- Insert test user for development
+INSERT INTO users (
+ id, username, email, password_hash, first_name, last_name, role, email_verified, is_active
+) VALUES (
+ uuid_generate_v4(),
+ 'testuser',
+ 'test@tech4biz.com',
+ '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', -- bcrypt hash of 'admin123'
+ 'Test',
+ 'User',
+ 'user',
+ true,
+ true
+) ON CONFLICT (email) DO NOTHING;
+
+-- Success message
+SELECT 'User Authentication database schema created successfully!' as message;
+
+-- Display created tables
+SELECT
+ schemaname,
+ tablename,
+ tableowner
+FROM pg_tables
+WHERE schemaname = 'public'
+AND tablename IN ('users', 'refresh_tokens', 'user_sessions', 'user_feature_preferences', 'user_projects')
+ORDER BY tablename;
\ No newline at end of file
diff --git a/services/ai-mockup-service/src/sql/002_wireframe_schema.sql b/services/ai-mockup-service/src/sql/002_wireframe_schema.sql
new file mode 100644
index 0000000..5fd6c75
--- /dev/null
+++ b/services/ai-mockup-service/src/sql/002_wireframe_schema.sql
@@ -0,0 +1,164 @@
+-- Wireframe Storage Database Schema
+-- Extends the user authentication schema to store wireframe data
+
+-- Drop tables if they exist (for development)
+DROP TABLE IF EXISTS wireframe_versions CASCADE;
+DROP TABLE IF EXISTS wireframe_elements CASCADE;
+DROP TABLE IF EXISTS wireframes CASCADE;
+
+-- Wireframes table - Main wireframe storage
+CREATE TABLE wireframes (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ user_id UUID REFERENCES users(id) ON DELETE CASCADE,
+ project_id UUID REFERENCES user_projects(id) ON DELETE SET NULL,
+ name VARCHAR(200) NOT NULL,
+ description TEXT,
+ device_type VARCHAR(20) DEFAULT 'desktop' CHECK (device_type IN ('mobile', 'tablet', 'desktop')),
+ dimensions JSONB NOT NULL, -- {width: number, height: number}
+ metadata JSONB, -- Additional metadata like prompt, generation settings
+ is_active BOOLEAN DEFAULT true,
+ created_at TIMESTAMP DEFAULT NOW(),
+ updated_at TIMESTAMP DEFAULT NOW()
+);
+
+-- Wireframe elements table - Store individual elements/shapes
+CREATE TABLE wireframe_elements (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ wireframe_id UUID REFERENCES wireframes(id) ON DELETE CASCADE,
+ element_type VARCHAR(50) NOT NULL, -- 'shape', 'text', 'image', 'group'
+ element_data JSONB NOT NULL, -- TLDraw element data
+ position JSONB NOT NULL, -- {x: number, y: number}
+ size JSONB, -- {width: number, height: number}
+ style JSONB, -- {color, strokeWidth, etc.}
+ parent_id VARCHAR(255), -- TLDraw uses string IDs like "page:page", not UUIDs
+ tldraw_id VARCHAR(255), -- Store the original TLDraw ID
+ z_index INTEGER DEFAULT 0,
+ created_at TIMESTAMP DEFAULT NOW(),
+ updated_at TIMESTAMP DEFAULT NOW()
+);
+
+-- Wireframe versions table - Version control for wireframes
+CREATE TABLE wireframe_versions (
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
+ wireframe_id UUID REFERENCES wireframes(id) ON DELETE CASCADE,
+ version_number INTEGER NOT NULL,
+ version_name VARCHAR(100),
+ version_description TEXT,
+ snapshot_data JSONB NOT NULL, -- Complete wireframe state at this version
+ created_by UUID REFERENCES users(id) ON DELETE SET NULL,
+ created_at TIMESTAMP DEFAULT NOW(),
+ UNIQUE(wireframe_id, version_number)
+);
+
+-- Indexes for performance
+CREATE INDEX idx_wireframes_user_id ON wireframes(user_id);
+CREATE INDEX idx_wireframes_project_id ON wireframes(project_id);
+CREATE INDEX idx_wireframes_active ON wireframes(is_active);
+CREATE INDEX idx_wireframes_device_type ON wireframes(device_type);
+CREATE INDEX idx_wireframe_elements_wireframe_id ON wireframe_elements(wireframe_id);
+CREATE INDEX idx_wireframe_elements_parent_id ON wireframe_elements(parent_id);
+CREATE INDEX idx_wireframe_elements_type ON wireframe_elements(element_type);
+CREATE INDEX idx_wireframe_versions_wireframe_id ON wireframe_versions(wireframe_id);
+CREATE INDEX idx_wireframe_versions_number ON wireframe_versions(version_number);
+
+-- Apply triggers for updated_at columns
+CREATE TRIGGER update_wireframes_updated_at
+ BEFORE UPDATE ON wireframes
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+CREATE TRIGGER update_wireframe_elements_updated_at
+ BEFORE UPDATE ON wireframe_elements
+ FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+
+-- Functions for wireframe management
+CREATE OR REPLACE FUNCTION create_wireframe_version(
+ p_wireframe_id UUID,
+ p_version_name VARCHAR(100),
+ p_version_description TEXT,
+ p_snapshot_data JSONB,
+ p_created_by UUID
+)
+RETURNS UUID AS $$
+DECLARE
+ next_version INTEGER;
+ new_version_id UUID;
+BEGIN
+ -- Get next version number
+ SELECT COALESCE(MAX(version_number), 0) + 1
+ INTO next_version
+ FROM wireframe_versions
+ WHERE wireframe_id = p_wireframe_id;
+
+ -- Create new version
+ INSERT INTO wireframe_versions (
+ wireframe_id, version_number, version_name,
+ version_description, snapshot_data, created_by
+ ) VALUES (
+ p_wireframe_id, next_version, p_version_name,
+ p_version_description, p_snapshot_data, p_created_by
+ ) RETURNING id INTO new_version_id;
+
+ RETURN new_version_id;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Function to get wireframe with all elements
+CREATE OR REPLACE FUNCTION get_wireframe_with_elements(p_wireframe_id UUID)
+RETURNS TABLE(
+ wireframe_data JSONB,
+ elements_data JSONB
+) AS $$
+BEGIN
+ RETURN QUERY
+ SELECT
+ to_jsonb(w.*) as wireframe_data,
+ COALESCE(
+ jsonb_agg(
+ jsonb_build_object(
+ 'id', we.id,
+ 'tldraw_id', we.tldraw_id,
+ 'type', we.element_type,
+ 'data', we.element_data,
+ 'position', we.position,
+ 'size', we.size,
+ 'style', we.style,
+ 'parent_id', we.parent_id,
+ 'z_index', we.z_index
+ ) ORDER BY we.z_index, we.created_at
+ ) FILTER (WHERE we.id IS NOT NULL),
+ '[]'::jsonb
+ ) as elements_data
+ FROM wireframes w
+ LEFT JOIN wireframe_elements we ON w.id = we.wireframe_id
+ WHERE w.id = p_wireframe_id
+ GROUP BY w.id, w.user_id, w.project_id, w.name, w.description,
+ w.device_type, w.dimensions, w.metadata, w.is_active,
+ w.created_at, w.updated_at;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Insert sample wireframe for testing
+INSERT INTO wireframes (
+ id, user_id, name, description, device_type, dimensions, metadata
+) VALUES (
+ uuid_generate_v4(),
+ (SELECT id FROM users WHERE username = 'testuser' LIMIT 1),
+ 'Sample Wireframe',
+ 'A sample wireframe for testing',
+ 'desktop',
+ '{"width": 1440, "height": 1024}'::jsonb,
+ '{"prompt": "Sample prompt", "generator": "ai"}'::jsonb
+) ON CONFLICT DO NOTHING;
+
+-- Success message
+SELECT 'Wireframe database schema created successfully!' as message;
+
+-- Display created tables
+SELECT
+ schemaname,
+ tablename,
+ tableowner
+FROM pg_tables
+WHERE schemaname = 'public'
+AND tablename IN ('wireframes', 'wireframe_elements', 'wireframe_versions')
+ORDER BY tablename;
diff --git a/services/ai-mockup-service/src/start_backend.py b/services/ai-mockup-service/src/start_backend.py
new file mode 100644
index 0000000..bf26064
--- /dev/null
+++ b/services/ai-mockup-service/src/start_backend.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+"""
+Startup script for the SVG Wireframe Generator Backend
+"""
+
+import os
+from dotenv import load_dotenv
+from app import app
+
+if __name__ == '__main__':
+ # Load environment variables
+ load_dotenv()
+
+ # Get configuration
+ port = int(os.environ.get('PORT', 5000))
+ debug = os.environ.get('FLASK_DEBUG', 'True').lower() == 'true'
+
+ print("๐ Starting SVG Wireframe Generator Backend...")
+ print(f"๐ Port: {port}")
+ print(f"๐ง Debug: {debug}")
+ print(f"๐ URL: http://localhost:{port}")
+ print("=" * 50)
+
+ try:
+ app.run(
+ debug=debug,
+ host='0.0.0.0',
+ port=port,
+ use_reloader=debug
+ )
+ except KeyboardInterrupt:
+ print("\n๐ Server stopped by user")
+ except Exception as e:
+ print(f"โ Error starting server: {e}")
+ exit(1)
diff --git a/services/ai-mockup-service/src/test_api.py b/services/ai-mockup-service/src/test_api.py
new file mode 100644
index 0000000..ce4dbe8
--- /dev/null
+++ b/services/ai-mockup-service/src/test_api.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python3
+"""
+Test script for the wireframe generation API endpoints
+Tests both the universal and device-specific endpoints
+"""
+
+import requests
+import json
+import sys
+import os
+
+# Configuration
+BASE_URL = "http://localhost:5000"
+TEST_PROMPT = "Dashboard with header, left sidebar, 3 stats cards, and footer"
+
+def test_health_endpoint():
+ """Test the health check endpoint"""
+ print("๐ Testing health endpoint...")
+ try:
+ response = requests.get(f"{BASE_URL}/api/health", timeout=10)
+ if response.status_code == 200:
+ data = response.json()
+ print(f"โ
Health check passed: {data}")
+ return True
+ else:
+ print(f"โ Health check failed: {response.status_code}")
+ return False
+ except Exception as e:
+ print(f"โ Health check error: {e}")
+ return False
+
+def test_device_specific_endpoint(device_type):
+ """Test device-specific wireframe generation"""
+ print(f"๐ Testing {device_type} endpoint...")
+ try:
+ response = requests.post(
+ f"{BASE_URL}/generate-wireframe/{device_type}",
+ json={"prompt": TEST_PROMPT},
+ headers={"Content-Type": "application/json"},
+ timeout=30
+ )
+
+ if response.status_code == 200:
+ content_type = response.headers.get('content-type', '')
+ if 'image/svg+xml' in content_type:
+ print(f"โ
{device_type} endpoint: SVG generated successfully")
+ print(f" Content-Type: {content_type}")
+ print(f" Response length: {len(response.text)} characters")
+ return True
+ else:
+ print(f"โ ๏ธ {device_type} endpoint: Unexpected content type: {content_type}")
+ return False
+ else:
+ print(f"โ {device_type} endpoint failed: {response.status_code}")
+ try:
+ error_data = response.json()
+ print(f" Error: {error_data}")
+ except:
+ print(f" Error text: {response.text}")
+ return False
+ except Exception as e:
+ print(f"โ {device_type} endpoint error: {e}")
+ return False
+
+def test_universal_endpoint():
+ """Test the universal wireframe generation endpoint"""
+ print("๐ Testing universal endpoint...")
+ try:
+ response = requests.post(
+ f"{BASE_URL}/generate-wireframe",
+ json={"prompt": TEST_PROMPT, "device": "desktop"},
+ headers={"Content-Type": "application/json"},
+ timeout=30
+ )
+
+ if response.status_code == 200:
+ content_type = response.headers.get('content-type', '')
+ if 'image/svg+xml' in content_type:
+ print(f"โ
Universal endpoint: SVG generated successfully")
+ print(f" Content-Type: {content_type}")
+ print(f" Response length: {len(response.text)} characters")
+ return True
+ else:
+ print(f"โ ๏ธ Universal endpoint: Unexpected content type: {content_type}")
+ return False
+ else:
+ print(f"โ Universal endpoint failed: {response.status_code}")
+ try:
+ error_data = response.json()
+ print(f" Error: {error_data}")
+ except:
+ print(f" Error text: {response.text}")
+ return False
+ except Exception as e:
+ print(f"โ Universal endpoint error: {e}")
+ return False
+
+def test_all_devices_endpoint():
+ """Test the all devices metadata endpoint"""
+ print("๐ Testing all devices endpoint...")
+ try:
+ response = requests.post(
+ f"{BASE_URL}/generate-all-devices",
+ json={"prompt": TEST_PROMPT},
+ headers={"Content-Type": "application/json"},
+ timeout=10
+ )
+
+ if response.status_code == 200:
+ data = response.json()
+ print(f"โ
All devices endpoint: {data.get('message', 'Success')}")
+ if 'device_endpoints' in data:
+ for device, endpoint in data['device_endpoints'].items():
+ print(f" {device}: {endpoint}")
+ return True
+ else:
+ print(f"โ All devices endpoint failed: {response.status_code}")
+ try:
+ error_data = response.json()
+ print(f" Error: {error_data}")
+ except:
+ print(f" Error text: {response.text}")
+ return False
+ except Exception as e:
+ print(f"โ All devices endpoint error: {e}")
+ return False
+
+def main():
+ """Run all tests"""
+ print("๐ Starting API endpoint tests...")
+ print(f"๐ Base URL: {BASE_URL}")
+ print(f"๐ Test Prompt: {TEST_PROMPT}")
+ print("=" * 60)
+
+ # Test health endpoint first
+ if not test_health_endpoint():
+ print("โ Health check failed. Is the backend running?")
+ sys.exit(1)
+
+ print()
+
+ # Test device-specific endpoints
+ devices = ['desktop', 'tablet', 'mobile']
+ device_results = {}
+
+ for device in devices:
+ device_results[device] = test_device_specific_endpoint(device)
+ print()
+
+ # Test universal endpoint
+ universal_result = test_universal_endpoint()
+ print()
+
+ # Test all devices endpoint
+ all_devices_result = test_all_devices_endpoint()
+ print()
+
+ # Summary
+ print("=" * 60)
+ print("๐ Test Results Summary:")
+ print(f" Health Check: {'โ
PASS' if True else 'โ FAIL'}")
+
+ for device, result in device_results.items():
+ status = "โ
PASS" if result else "โ FAIL"
+ print(f" {device.capitalize()} Endpoint: {status}")
+
+ print(f" Universal Endpoint: {'โ
PASS' if universal_result else 'โ FAIL'}")
+ print(f" All Devices Endpoint: {'โ
PASS' if all_devices_result else 'โ FAIL'}")
+
+ # Overall success
+ all_passed = all(device_results.values()) and universal_result and all_devices_result
+ if all_passed:
+ print("\n๐ All tests passed! The API is working correctly.")
+ else:
+ print("\nโ ๏ธ Some tests failed. Check the output above for details.")
+
+ return 0 if all_passed else 1
+
+if __name__ == "__main__":
+ try:
+ exit_code = main()
+ sys.exit(exit_code)
+ except KeyboardInterrupt:
+ print("\n\nโน๏ธ Tests interrupted by user")
+ sys.exit(1)
+ except Exception as e:
+ print(f"\n๐ฅ Unexpected error: {e}")
+ sys.exit(1)
diff --git a/services/ai-mockup-service/src/test_auth.py b/services/ai-mockup-service/src/test_auth.py
new file mode 100644
index 0000000..3540bf0
--- /dev/null
+++ b/services/ai-mockup-service/src/test_auth.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python3
+"""
+Test script for authentication functionality
+Tests JWT token verification with both local and remote auth service
+"""
+
+import requests
+import json
+import sys
+import os
+import jwt
+from datetime import datetime, timedelta
+
+# Configuration
+AI_MOCKUP_URL = "http://localhost:8021"
+USER_AUTH_URL = "http://localhost:8011"
+TEST_USER_EMAIL = "test@example.com"
+TEST_USER_PASSWORD = "testpassword123"
+
+def test_user_auth_service():
+ """Test if user-auth service is running and accessible"""
+ print("๐ Testing user-auth service...")
+ try:
+ response = requests.get(f"{USER_AUTH_URL}/health", timeout=10)
+ if response.status_code == 200:
+ print(f"โ
User-auth service is running: {response.json()}")
+ return True
+ else:
+ print(f"โ User-auth service health check failed: {response.status_code}")
+ return False
+ except Exception as e:
+ print(f"โ User-auth service error: {e}")
+ return False
+
+def test_ai_mockup_service():
+ """Test if ai-mockup service is running and accessible"""
+ print("๐ Testing ai-mockup service...")
+ try:
+ response = requests.get(f"{AI_MOCKUP_URL}/api/health", timeout=10)
+ if response.status_code == 200:
+ print(f"โ
AI-mockup service is running: {response.json()}")
+ return True
+ else:
+ print(f"โ AI-mockup service health check failed: {response.status_code}")
+ return False
+ except Exception as e:
+ print(f"โ AI-mockup service error: {e}")
+ return False
+
+def test_token_verification_endpoint():
+ """Test the token verification endpoint in user-auth service"""
+ print("๐ Testing token verification endpoint...")
+
+ # Create a test JWT token
+ test_payload = {
+ 'userId': 'test-user-id',
+ 'email': TEST_USER_EMAIL,
+ 'username': 'testuser',
+ 'role': 'user',
+ 'exp': datetime.utcnow() + timedelta(hours=1),
+ 'iat': datetime.utcnow(),
+ 'iss': 'tech4biz-auth',
+ 'aud': 'tech4biz-users'
+ }
+
+ # Use the same secret as configured in the services
+ test_secret = 'access-secret-key-2024-tech4biz'
+ test_token = jwt.encode(test_payload, test_secret, algorithm='HS256')
+
+ try:
+ response = requests.get(
+ f"{USER_AUTH_URL}/api/auth/verify",
+ headers={'Authorization': f'Bearer {test_token}'},
+ timeout=10
+ )
+
+ if response.status_code == 200:
+ result = response.json()
+ if result.get('success'):
+ print(f"โ
Token verification endpoint working: {result}")
+ return True
+ else:
+ print(f"โ Token verification failed: {result}")
+ return False
+ else:
+ print(f"โ Token verification endpoint failed: {response.status_code}")
+ try:
+ error_data = response.json()
+ print(f" Error: {error_data}")
+ except:
+ print(f" Error text: {response.text}")
+ return False
+ except Exception as e:
+ print(f"โ Token verification test error: {e}")
+ return False
+
+def test_ai_mockup_with_auth():
+ """Test ai-mockup service with authentication"""
+ print("๐ Testing ai-mockup service with authentication...")
+
+ # Create a test JWT token
+ test_payload = {
+ 'userId': 'test-user-id',
+ 'email': TEST_USER_EMAIL,
+ 'username': 'testuser',
+ 'role': 'user',
+ 'exp': datetime.utcnow() + timedelta(hours=1),
+ 'iat': datetime.utcnow(),
+ 'iss': 'tech4biz-auth',
+ 'aud': 'tech4biz-users'
+ }
+
+ test_secret = 'access-secret-key-2024-tech4biz'
+ test_token = jwt.encode(test_payload, test_secret, algorithm='HS256')
+
+ try:
+ # Test a protected endpoint (assuming there's one)
+ response = requests.get(
+ f"{AI_MOCKUP_URL}/api/protected-endpoint",
+ headers={'Authorization': f'Bearer {test_token}'},
+ timeout=10
+ )
+
+ # This might return 404 if the endpoint doesn't exist, but should not return 401
+ if response.status_code == 401:
+ print(f"โ Authentication still failing: {response.status_code}")
+ try:
+ error_data = response.json()
+ print(f" Error: {error_data}")
+ except:
+ print(f" Error text: {response.text}")
+ return False
+ else:
+ print(f"โ
Authentication working (status: {response.status_code})")
+ return True
+ except Exception as e:
+ print(f"โ AI-mockup auth test error: {e}")
+ return False
+
+def main():
+ """Run all authentication tests"""
+ print("๐ Starting authentication tests...\n")
+
+ tests = [
+ test_user_auth_service,
+ test_ai_mockup_service,
+ test_token_verification_endpoint,
+ test_ai_mockup_with_auth
+ ]
+
+ passed = 0
+ total = len(tests)
+
+ for test in tests:
+ try:
+ if test():
+ passed += 1
+ print()
+ except Exception as e:
+ print(f"โ Test {test.__name__} crashed: {e}\n")
+
+ print(f"๐ Test Results: {passed}/{total} tests passed")
+
+ if passed == total:
+ print("๐ All authentication tests passed!")
+ return 0
+ else:
+ print("โ ๏ธ Some authentication tests failed. Check the logs above.")
+ return 1
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/services/ai-mockup-service/src/test_db_connection.py b/services/ai-mockup-service/src/test_db_connection.py
new file mode 100644
index 0000000..4b416d5
--- /dev/null
+++ b/services/ai-mockup-service/src/test_db_connection.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+"""
+Test database connection and wireframe saving functionality
+"""
+
+import os
+import psycopg2
+from psycopg2.extras import RealDictCursor
+from dotenv import load_dotenv
+import json
+
+def test_database_connection():
+ """Test if we can connect to the database"""
+
+ # Load environment variables
+ load_dotenv()
+
+ # Database connection details
+ db_config = {
+ 'host': os.getenv('POSTGRES_HOST', 'localhost'),
+ 'database': os.getenv('POSTGRES_DB', 'dev_pipeline'),
+ 'user': os.getenv('POSTGRES_USER', 'pipeline_admin'),
+ 'password': os.getenv('POSTGRES_PASSWORD', 'secure_pipeline_2024'),
+ 'port': os.getenv('POSTGRES_PORT', '5433')
+ }
+
+ print("Testing database connection with config:")
+ for key, value in db_config.items():
+ if key == 'password':
+ print(f" {key}: {'*' * len(str(value))}")
+ else:
+ print(f" {key}: {value}")
+
+ try:
+ # Test connection
+ conn = psycopg2.connect(**db_config)
+ print("โ
Database connection successful!")
+
+ # Test if wireframes table exists
+ with conn.cursor() as cur:
+ cur.execute("""
+ SELECT EXISTS (
+ SELECT FROM information_schema.tables
+ WHERE table_schema = 'public'
+ AND table_name = 'wireframes'
+ );
+ """)
+ table_exists = cur.fetchone()[0]
+
+ if table_exists:
+ print("โ
Wireframes table exists!")
+
+ # Test inserting a sample wireframe
+ cur.execute("""
+ INSERT INTO wireframes (user_id, name, description, device_type, dimensions, metadata)
+ VALUES (%s, %s, %s, %s, %s, %s)
+ RETURNING id
+ """, (
+ 'testuser',
+ 'Test Wireframe',
+ 'Test wireframe for connection testing',
+ 'desktop',
+ json.dumps({'width': 1440, 'height': 1024}),
+ json.dumps({'test': True, 'timestamp': '2024-01-01'})
+ ))
+
+ wireframe_id = cur.fetchone()[0]
+ print(f"โ
Test wireframe inserted with ID: {wireframe_id}")
+
+ # Clean up test data
+ cur.execute("DELETE FROM wireframes WHERE id = %s", (wireframe_id,))
+ print("โ
Test wireframe cleaned up")
+
+ else:
+ print("โ Wireframes table does not exist!")
+ print("Please run the database setup script first:")
+ print(" python setup_database.py")
+
+ conn.close()
+ return True
+
+ except Exception as e:
+ print(f"โ Database connection failed: {e}")
+ return False
+
+def test_api_endpoint():
+ """Test if the API endpoint is accessible"""
+ import requests
+
+ try:
+ response = requests.get('http://localhost:5000/api/health')
+ if response.status_code == 200:
+ print("โ
API endpoint is accessible!")
+ print(f"Response: {response.json()}")
+ return True
+ else:
+ print(f"โ API endpoint returned status: {response.status_code}")
+ return False
+ except requests.exceptions.ConnectionError:
+ print("โ Cannot connect to API endpoint. Is the backend running?")
+ print("Start the backend with: python app.py")
+ return False
+ except Exception as e:
+ print(f"โ API test failed: {e}")
+ return False
+
+if __name__ == "__main__":
+ print("Testing Tech4biz Wireframe Generator...")
+ print("=" * 50)
+
+ # Test database connection
+ db_ok = test_database_connection()
+ print()
+
+ # Test API endpoint
+ api_ok = test_api_endpoint()
+ print()
+
+ if db_ok and api_ok:
+ print("๐ All tests passed! The system is ready to use.")
+ else:
+ print("โ Some tests failed. Please fix the issues above.")
+
+ if not db_ok:
+ print("\nTo fix database issues:")
+ print("1. Make sure PostgreSQL is running")
+ print("2. Check your environment variables")
+ print("3. Run: python setup_database.py")
+
+ if not api_ok:
+ print("\nTo fix API issues:")
+ print("1. Start the backend: python app.py")
+ print("2. Make sure it's running on port 5000")
diff --git a/services/ai-mockup-service/src/test_integration.py b/services/ai-mockup-service/src/test_integration.py
new file mode 100644
index 0000000..3dd0ecb
--- /dev/null
+++ b/services/ai-mockup-service/src/test_integration.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python3
+"""
+Test script for AI Mockup Service Authentication and Wireframe Saving
+This script tests the complete flow from authentication to wireframe saving
+"""
+
+import requests
+import json
+import time
+import os
+from datetime import datetime
+
+# Configuration
+AI_MOCKUP_SERVICE_URL = "http://localhost:8021"
+USER_AUTH_SERVICE_URL = "http://localhost:8011"
+POSTGRES_HOST = "localhost"
+POSTGRES_PORT = "5433" # Docker mapped port
+POSTGRES_DB = "dev_pipeline"
+POSTGRES_USER = "pipeline_admin"
+POSTGRES_PASSWORD = "secure_pipeline_2024"
+
+def test_health_checks():
+ """Test health endpoints"""
+ print("๐ Testing health checks...")
+
+ # Test AI Mockup Service health
+ try:
+ response = requests.get(f"{AI_MOCKUP_SERVICE_URL}/health", timeout=10)
+ if response.status_code == 200:
+ print("โ
AI Mockup Service is healthy")
+ print(f" Status: {response.json().get('status')}")
+ print(f" Database: {response.json().get('services', {}).get('database')}")
+ print(f" User Auth: {response.json().get('services', {}).get('user_auth')}")
+ else:
+ print(f"โ AI Mockup Service health check failed: {response.status_code}")
+ return False
+ except Exception as e:
+ print(f"โ AI Mockup Service health check error: {e}")
+ return False
+
+ # Test User Auth Service health
+ try:
+ response = requests.get(f"{USER_AUTH_SERVICE_URL}/health", timeout=10)
+ if response.status_code == 200:
+ print("โ
User Auth Service is healthy")
+ else:
+ print(f"โ User Auth Service health check failed: {response.status_code}")
+ return False
+ except Exception as e:
+ print(f"โ User Auth Service health check error: {e}")
+ return False
+
+ return True
+
+def test_authentication():
+ """Test authentication flow"""
+ print("\n๐ Testing authentication...")
+
+ # Test registration
+ test_user = {
+ "username": f"testuser_{int(time.time())}",
+ "email": f"testuser_{int(time.time())}@example.com",
+ "password": "TestPassword123!",
+ "first_name": "Test",
+ "last_name": "User"
+ }
+
+ try:
+ response = requests.post(f"{USER_AUTH_SERVICE_URL}/api/auth/register",
+ json=test_user, timeout=10)
+ if response.status_code == 201:
+ print("โ
User registration successful")
+ user_data = response.json().get('data', {})
+ user_id = user_data.get('user', {}).get('id')
+ if user_id:
+ print("โ
User ID received")
+
+ # For testing purposes, manually verify the email by updating the database
+ # This bypasses the email verification requirement
+ try:
+ import psycopg2
+ conn = psycopg2.connect(
+ host=POSTGRES_HOST,
+ database=POSTGRES_DB,
+ user=POSTGRES_USER,
+ password=POSTGRES_PASSWORD,
+ port=POSTGRES_PORT
+ )
+ with conn.cursor() as cur:
+ cur.execute("UPDATE users SET email_verified = true WHERE id = %s", (user_id,))
+ conn.commit()
+ conn.close()
+ print("โ
Email verification bypassed for testing")
+ except Exception as e:
+ print(f"โ ๏ธ Could not bypass email verification: {e}")
+ return None, None
+
+ # Now try to login
+ login_response = requests.post(f"{USER_AUTH_SERVICE_URL}/api/auth/login",
+ json={"email": test_user["email"], "password": test_user["password"]},
+ timeout=10)
+
+ if login_response.status_code == 200:
+ login_data = login_response.json().get('data', {})
+ access_token = login_data.get('tokens', {}).get('accessToken')
+ if access_token:
+ print("โ
Access token received")
+ return access_token, test_user
+ else:
+ print("โ No access token in login response")
+ return None, None
+ else:
+ print(f"โ Login failed: {login_response.status_code}")
+ print(f" Response: {login_response.text}")
+ return None, None
+ else:
+ print("โ No user ID in response")
+ return None, None
+ else:
+ print(f"โ User registration failed: {response.status_code}")
+ print(f" Response: {response.text}")
+ return None, None
+ except Exception as e:
+ print(f"โ User registration error: {e}")
+ return None, None
+
+def test_wireframe_generation(access_token):
+ """Test wireframe generation"""
+ print("\n๐จ Testing wireframe generation...")
+
+ headers = {"Authorization": f"Bearer {access_token}"}
+ prompt = "Create a simple landing page with header, hero section, and footer"
+
+ try:
+ response = requests.post(f"{AI_MOCKUP_SERVICE_URL}/generate-wireframe/desktop",
+ json={"prompt": prompt},
+ headers=headers,
+ timeout=30)
+
+ if response.status_code == 200:
+ print("โ
Wireframe generation successful")
+ result = response.json()
+ if result.get('svg'):
+ print("โ
SVG wireframe received")
+ return result.get('svg')
+ else:
+ print("โ No SVG in response")
+ return None
+ else:
+ print(f"โ Wireframe generation failed: {response.status_code}")
+ print(f" Response: {response.text}")
+ return None
+ except Exception as e:
+ print(f"โ Wireframe generation error: {e}")
+ return None
+
+def test_wireframe_saving(access_token, svg_data):
+ """Test wireframe saving"""
+ print("\n๐พ Testing wireframe saving...")
+
+ headers = {"Authorization": f"Bearer {access_token}"}
+
+ wireframe_data = {
+ "wireframe": {
+ "name": f"Test Wireframe {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
+ "description": "Test wireframe created by automated test",
+ "device_type": "desktop",
+ "dimensions": {"width": 1440, "height": 1024},
+ "metadata": {"prompt": "Test prompt", "generator": "test"}
+ },
+ "elements": [
+ {
+ "id": "test-element-1",
+ "type": "shape",
+ "data": {"type": "rectangle", "props": {"w": 200, "h": 100}},
+ "position": {"x": 100, "y": 100},
+ "size": {"width": 200, "height": 100},
+ "style": {"color": "#3B82F6", "fill": "#EFF6FF"},
+ "parent_id": None,
+ "z_index": 0
+ }
+ ]
+ }
+
+ try:
+ response = requests.post(f"{AI_MOCKUP_SERVICE_URL}/api/wireframes",
+ json=wireframe_data,
+ headers=headers,
+ timeout=10)
+
+ if response.status_code == 201:
+ print("โ
Wireframe saved successfully")
+ result = response.json()
+ wireframe_id = result.get('wireframe_id')
+ if wireframe_id:
+ print(f"โ
Wireframe ID: {wireframe_id}")
+ return wireframe_id
+ else:
+ print("โ No wireframe ID in response")
+ return None
+ else:
+ print(f"โ Wireframe saving failed: {response.status_code}")
+ print(f" Response: {response.text}")
+ return None
+ except Exception as e:
+ print(f"โ Wireframe saving error: {e}")
+ return None
+
+def test_wireframe_retrieval(access_token, wireframe_id):
+ """Test wireframe retrieval"""
+ print("\n๐ Testing wireframe retrieval...")
+
+ headers = {"Authorization": f"Bearer {access_token}"}
+
+ try:
+ response = requests.get(f"{AI_MOCKUP_SERVICE_URL}/api/wireframes/{wireframe_id}",
+ headers=headers,
+ timeout=10)
+
+ if response.status_code == 200:
+ print("โ
Wireframe retrieved successfully")
+ result = response.json()
+ if result.get('wireframe') and result.get('elements'):
+ print("โ
Wireframe data and elements received")
+ return True
+ else:
+ print("โ Incomplete wireframe data")
+ return False
+ else:
+ print(f"โ Wireframe retrieval failed: {response.status_code}")
+ print(f" Response: {response.text}")
+ return False
+ except Exception as e:
+ print(f"โ Wireframe retrieval error: {e}")
+ return False
+
+def main():
+ """Run all tests"""
+ print("๐ Starting AI Mockup Service Integration Tests")
+ print("=" * 50)
+
+ # Test 1: Health checks
+ if not test_health_checks():
+ print("\nโ Health checks failed. Please ensure all services are running.")
+ return
+
+ # Test 2: Authentication
+ access_token, user_data = test_authentication()
+ if not access_token:
+ print("\nโ Authentication failed. Please check user-auth service.")
+ return
+
+ # Test 3: Wireframe generation
+ svg_data = test_wireframe_generation(access_token)
+ if not svg_data:
+ print("\nโ Wireframe generation failed.")
+ return
+
+ # Test 4: Wireframe saving
+ wireframe_id = test_wireframe_saving(access_token, svg_data)
+ if not wireframe_id:
+ print("\nโ Wireframe saving failed.")
+ return
+
+ # Test 5: Wireframe retrieval
+ if not test_wireframe_retrieval(access_token, wireframe_id):
+ print("\nโ Wireframe retrieval failed.")
+ return
+
+ print("\n" + "=" * 50)
+ print("๐ All tests passed! Wireframe saving is working correctly.")
+ print(f"โ
User: {user_data.get('username')}")
+ print(f"โ
Wireframe ID: {wireframe_id}")
+ print("โ
Authentication, generation, saving, and retrieval all working")
+
+if __name__ == "__main__":
+ main()
diff --git a/services/ai-mockup-service/src/test_svg.py b/services/ai-mockup-service/src/test_svg.py
new file mode 100644
index 0000000..b71c8d4
--- /dev/null
+++ b/services/ai-mockup-service/src/test_svg.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+"""
+Test script for SVG wireframe generation
+"""
+
+import sys
+import os
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
+from app import generate_svg_wireframe
+
+def test_svg_generation():
+ """Test SVG generation with sample data"""
+
+ # Test layout specification
+ test_layout = {
+ "layout": {
+ "page": {"width": 1200, "height": 800},
+ "header": {"enabled": True, "height": 72, "elements": ["Logo", "Navigation", "CTA"]},
+ "sidebar": {"enabled": True, "width": 240, "position": "left", "elements": ["Menu", "Filters"]},
+ "hero": {"enabled": True, "height": 200, "elements": ["Hero Title", "Hero Subtitle", "Button"]},
+ "main_content": {
+ "sections": [
+ {
+ "type": "grid",
+ "rows": 2,
+ "cols": 3,
+ "height": 200,
+ "elements": ["Card 1", "Card 2", "Card 3", "Card 4", "Card 5", "Card 6"]
+ },
+ {
+ "type": "form",
+ "height": 300,
+ "fields": ["Name", "Email", "Message", "submit"]
+ }
+ ]
+ },
+ "footer": {"enabled": True, "height": 64, "elements": ["Links", "Copyright"]}
+ },
+ "styling": {
+ "theme": "modern",
+ "colors": {
+ "primary": "#3B82F6",
+ "secondary": "#6B7280",
+ "background": "#FFFFFF",
+ "card": "#F8FAFC",
+ "text": "#1F2937"
+ },
+ "spacing": {"gap": 16, "padding": 20}
+ },
+ "annotations": {
+ "title": "Test Wireframe",
+ "description": "Test SVG generation"
+ }
+ }
+
+ try:
+ # Generate SVG
+ svg_content = generate_svg_wireframe(test_layout)
+
+ # Save to file for inspection
+ with open('test_wireframe.svg', 'w', encoding='utf-8') as f:
+ f.write(svg_content)
+
+ print("โ
SVG generation test passed!")
+ print(f"Generated SVG: {len(svg_content)} characters")
+ print("Saved to: test_wireframe.svg")
+
+ # Basic validation
+ assert '