palettepro-ai
v1.6.6
Published
AI-powered makeup palette analysis: grid recognition, personalized look generation, and dashboard management
Maintainers
Readme
PalettePro AI
The easiest way to add interactive makeup palette grids to your e-commerce site
PalettePro AI helps beauty brands display and manage makeup palette grids with an easy-to-integrate NPM package. Perfect for Shopify, WooCommerce, custom e-commerce sites, and beauty tech applications.
Features
✨ Easy Integration - Add a palette widget to your product pages with just a few lines of code 🎨 Interactive Grids - Clickable shade cells with hover effects and selection 📱 Responsive Design - Works beautifully on all devices ⚡ Fast Loading - Optimized for performance 🔐 Secure - API key authentication with rate limiting 🛠️ TypeScript Support - Full type definitions included
Quick Start
1. Install the Package
npm install palettepro-ai2. Get Your API Key
Sign up at paletteproai.com/signup or use the CLI:
npx palettepro login3. Add to Environment Variables
For Vite projects:
VITE_PALETTEPRO_API_KEY=pp_live_your_api_key_hereFor Create React App projects:
REACT_APP_PALETTEPRO_API_KEY=pp_live_your_api_key_here4. Add to Your Product Page
import React, { useState, useEffect } from 'react';
import { GenerateLooksButton } from 'palettepro-ai';
function ProductPage({ product }) {
const [paletteImage, setPaletteImage] = useState(null);
// Convert product image to base64
useEffect(() => {
const loadImage = async () => {
const response = await fetch(product.imageUrl);
const blob = await response.blob();
const reader = new FileReader();
reader.onloadend = () => setPaletteImage(reader.result);
reader.readAsDataURL(blob);
};
loadImage();
}, [product.imageUrl]);
if (!paletteImage) return <div>Loading...</div>;
return (
<div>
<h1>{product.name}</h1>
<img src={product.imageUrl} alt={product.name} />
<GenerateLooksButton
productId={product.id}
productName={product.name}
paletteImage={paletteImage}
apiKey={import.meta.env.VITE_PALETTEPRO_API_KEY}
onLooksGenerated={(data) => {
console.log('Generated looks:', data.looks);
}}
/>
</div>
);
}That's it! The button will:
- ✅ Ask users for skin tone & eye color
- ✅ Auto-detect palette grid using AI
- ✅ Generate 5 personalized makeup looks
- ✅ Display look cards with images
API Reference
PaletteWidget (React Component)
<PaletteWidget
productId="product-123" // Required: Your product identifier
apiKey="pp_live_xxx" // Optional: API key (uses env var by default)
showLabels={true} // Optional: Show shade labels (default: true)
onCellClick={(cell, row, col) => {}} // Optional: Click handler
style={{}} // Optional: Custom styles
className="" // Optional: CSS class name
/>Props:
- productId (string, required): Unique identifier for your product
- apiKey (string, optional): PalettePro API key (defaults to
process.env.REACT_APP_PALETTEPRO_API_KEY) - showLabels (boolean, optional): Display shade labels on cells (default:
true) - onCellClick (function, optional): Callback when a cell is clicked:
(cell, rowIndex, colIndex) => void - style (object, optional): Custom CSS styles for the widget container
- className (string, optional): CSS class name for the widget container
GenerateLooksButton (React Component)
An AI-powered component that generates personalized makeup looks based on palette images and user preferences using Google's Gemini AI. Grid dimensions are automatically detected using AI - no need to manually specify rows and columns!
import { GenerateLooksButton } from 'palettepro-ai';
<GenerateLooksButton
productId="urban-decay-naked-3" // Required: Your product identifier
productName="Urban Decay Naked 3" // Optional: Product name for dashboard
paletteImage={base64ImageString} // Required: Base64-encoded image (see below)
apiKey="pp_live_xxx" // Required: Your API key
onLooksGenerated={(data) => {}} // Optional: Success callback
onError={(error) => {}} // Optional: Error handler
style={{}} // Optional: Custom button styles
className="" // Optional: CSS class name
/>Props:
- productId (string, required): Unique identifier for your product. This is used to save grid configurations per user.
- productName (string, optional): Display name for the product (shown in user dashboard)
- paletteImage (string, required): Base64-encoded image string (NOT a file path - see conversion guide below)
- apiKey (string, required): Your PalettePro API key. Can be passed as prop or set in environment variables (
VITE_PALETTEPRO_API_KEYorREACT_APP_PALETTEPRO_API_KEY) - onLooksGenerated (function, optional): Callback when looks are successfully generated. Receives response object with
looks,colors, anduserPreferences. - onError (function, optional): Error handler callback. Receives error object.
- style (object, optional): Custom CSS styles for the button
- className (string, optional): CSS class name for the button
⚡ Auto-Detection:
- Grid dimensions (rows × columns) are automatically detected using AI on first use
- Bounding box coordinates are calculated to focus on the makeup pans
- Configuration is saved and reused for subsequent requests (no redundant API calls)
- Works on first use - no manual grid setup required!
Converting Images to Base64
CRITICAL: The paletteImage prop must be a base64-encoded string, not a file path. Passing a file path will result in a 500 error.
Here are helper functions to convert images to base64:
// Method 1: Convert image URL to base64
const imageUrlToBase64 = async (imageUrl) => {
const response = await fetch(imageUrl);
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
};
// Method 2: Convert File object to base64 (e.g., from file input)
const fileToBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
};
// Method 3: Convert img element to base64
const imgElementToBase64 = (imgElement) => {
const canvas = document.createElement('canvas');
canvas.width = imgElement.naturalWidth;
canvas.height = imgElement.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(imgElement, 0, 0);
return canvas.toDataURL('image/png');
};Complete Usage Example
import React, { useState, useEffect } from 'react';
import { GenerateLooksButton } from 'palettepro-ai';
function ProductPage({ product }) {
const [base64Image, setBase64Image] = useState(null);
const [generatedLooks, setGeneratedLooks] = useState(null);
useEffect(() => {
// Convert your image to base64 before passing to component
const loadImage = async () => {
try {
const base64 = await imageUrlToBase64(product.image);
setBase64Image(base64);
} catch (error) {
console.error('Failed to load image:', error);
}
};
loadImage();
}, [product.image]);
if (!base64Image) {
return <div>Loading palette...</div>;
}
return (
<div>
<h1>{product.name}</h1>
<img src={product.image} alt={product.name} />
<GenerateLooksButton
productId={product.id}
productName={product.name}
paletteImage={base64Image} // ✅ Correct: base64 string
apiKey={import.meta.env.VITE_PALETTEPRO_API_KEY} // ✅ Required: API key
onLooksGenerated={(data) => {
console.log('Generated looks:', data.looks);
console.log('Palette colors:', data.colors);
console.log('User preferences:', data.userPreferences);
setGeneratedLooks(data.looks);
}}
onError={(error) => {
console.error('Error generating looks:', error);
alert('Failed to generate looks. Please try again.');
}}
/>
{generatedLooks && (
<div className="looks-display">
<h2>Your Personalized Looks</h2>
{generatedLooks.map((look, index) => (
<div key={index} className="look-card">
{look.generatedImageUrl && (
<img src={look.generatedImageUrl} alt={look.title} />
)}
<h3>{look.title}</h3>
<p>{look.description}</p>
<h4>Shades Used:</h4>
<p>Colors: {look.colorNumbers.join(', ')}</p>
</div>
))}
</div>
)}
</div>
);
}
// Helper function (include this in your utils)
const imageUrlToBase64 = async (imageUrl) => {
const response = await fetch(imageUrl);
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
};Key Points:
- ✅ Convert image to base64 before passing to component
- ✅ Pass apiKey prop (or set in environment variables)
- ✅ Use actual product data (product.id, product.name) - NOT hardcoded strings
- ✅ Don't pass rows/cols - they're auto-detected
- ✅ Handle loading state while converting image
Common Errors
❌ WRONG: Missing apiKey
<GenerateLooksButton
productId={product.id}
productName={product.name}
paletteImage={base64Image}
// ❌ Missing apiKey! This will fail with 400 error
/>❌ WRONG: Passing a file path instead of base64
<GenerateLooksButton
productId={product.id}
paletteImage="/images/palette.jpg" // ❌ This will fail with 400 error!
apiKey={apiKey}
/>❌ WRONG: Using placeholder values
<GenerateLooksButton
productId="your-product-id" // ❌ Hardcoded placeholder
productName="Your Product Name" // ❌ Hardcoded placeholder
paletteImage={base64Image}
apiKey={apiKey}
/>❌ WRONG: Passing rows and cols (legacy)
<GenerateLooksButton
productId={product.id}
paletteImage={base64Image}
rows={4} // ❌ Not needed - auto-detected
cols={6} // ❌ Not needed - auto-detected
apiKey={apiKey}
/>✅ CORRECT: Complete setup
const base64 = await imageUrlToBase64(product.image);
<GenerateLooksButton
productId={product.id} // ✅ Use actual product ID
productName={product.name} // ✅ Use actual product name
paletteImage={base64} // ✅ Base64 string (format: data:image/png;base64,...)
apiKey={import.meta.env.VITE_PALETTEPRO_API_KEY} // ✅ API key from env
/>PaletteProClient (Node.js)
const { PaletteProClient } = require('palettepro-ai');
const client = new PaletteProClient(apiKey); // apiKey optional if in envMethods:
getGridConfig(productId)
Get palette grid configuration for a product.
const config = await client.getGridConfig('product-123');
// Returns: { rows, cols, cells, croppedImage, boundingBox, timestamp }Returns null if no configuration exists for the product.
saveGridConfig(productId, config) (Admin only)
Save palette grid configuration for a product. Requires admin API key.
await client.saveGridConfig('product-123', {
rows: 4,
cols: 6,
cells: [[/* cell data */]],
croppedImage: 'data:image/png;base64,...',
boundingBox: { x, y, width, height },
timestamp: Date.now()
});deleteGridConfig(productId) (Admin only)
Delete palette grid configuration. Requires admin API key.
await client.deleteGridConfig('product-123');ping()
Check API connection and health.
const isOnline = await client.ping();
console.log('API is online:', isOnline);CLI Commands
npx palettepro login
Authenticate and get your API key. Interactive command that:
- Sends a verification code to your email
- Verifies the code
- Generates or retrieves your API key
- Optionally saves it to your
.envfile
npx palettepro help
Display help information and available commands.
Environment Variables
# Required: Your PalettePro API key
PALETTEPRO_API_KEY=pp_live_your_api_key_here
# Optional: Custom API URL (defaults to https://www.paletteproai.com)
PALETTEPRO_API_URL=https://www.paletteproai.com
# For Create React App, use REACT_APP_ prefix:
REACT_APP_PALETTEPRO_API_KEY=pp_live_your_api_key_here
REACT_APP_PALETTEPRO_API_URL=https://www.paletteproai.com
# For Vite apps, use VITE_ prefix:
VITE_PALETTEPRO_API_KEY=pp_live_your_api_key_here
VITE_PALETTEPRO_API_URL=https://www.paletteproai.comNote: The GenerateLooksButton component automatically detects whether you're using Vite or Create React App and loads the appropriate environment variables.
Examples
Basic React Integration
import React from 'react';
import { PaletteWidget } from 'palettepro-ai';
function ProductPage({ product }) {
return (
<div className="product-page">
<h1>{product.name}</h1>
<img src={product.image} alt={product.name} />
<PaletteWidget
productId={product.id}
onCellClick={(cell) => {
// Add shade to cart, show details, etc.
console.log('Selected:', cell.label, cell.color);
}}
/>
</div>
);
}Custom Styled Widget
<PaletteWidget
productId="product-123"
style={{
maxWidth: '800px',
margin: '2rem auto',
padding: '1rem',
backgroundColor: '#f9f9f9',
borderRadius: '12px'
}}
className="my-custom-palette"
/>Node.js Backend Example
const express = require('express');
const { PaletteProClient } = require('palettepro-ai');
const app = express();
const paletteClient = new PaletteProClient();
app.get('/api/products/:id/palette', async (req, res) => {
try {
const config = await paletteClient.getGridConfig(req.params.id);
if (!config) {
return res.status(404).json({ error: 'Palette not found' });
}
res.json(config);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000);Next.js Integration
// pages/products/[id].js
import { PaletteWidget } from 'palettepro-ai';
export default function Product({ product }) {
return (
<div>
<h1>{product.name}</h1>
<PaletteWidget
productId={product.id}
onCellClick={(cell) => {
// Track analytics
window.gtag('event', 'shade_selected', {
product_id: product.id,
shade: cell.label
});
}}
/>
</div>
);
}Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<title>My Product</title>
</head>
<body>
<div id="palette-container"></div>
<script type="module">
import { PaletteWidget } from 'https://cdn.jsdelivr.net/npm/palettepro-ai/dist/palettepro.esm.js';
// Render widget
const container = document.getElementById('palette-container');
ReactDOM.render(
<PaletteWidget productId="product-123" />,
container
);
</script>
</body>
</html>Platform-Specific Guides
Shopify
- Install the package in your theme:
npm install palettepro-ai- Add to your product template:
{% if product.metafields.palettepro.enabled %}
<div id="palette-widget"></div>
<script type="module">
import { PaletteWidget } from 'palettepro-ai';
ReactDOM.render(
<PaletteWidget productId="{{ product.id }}" />,
document.getElementById('palette-widget')
);
</script>
{% endif %}WooCommerce
Add to your functions.php:
function add_palettepro_widget() {
if (is_product()) {
global $product;
?>
<div id="palettepro-widget"></div>
<script type="module">
import { PaletteWidget } from 'palettepro-ai';
ReactDOM.render(
React.createElement(PaletteWidget, {
productId: '<?php echo $product->get_id(); ?>'
}),
document.getElementById('palettepro-widget')
);
</script>
<?php
}
}
add_action('woocommerce_after_single_product_summary', 'add_palettepro_widget', 20);Pricing
Visit paletteproai.com/pricing for current pricing plans:
- Starter: 1,000 requests/month, 1 domain - $49/mo
- Professional: 5,000 requests/month, 3 domains - $149/mo
- Enterprise: Unlimited requests, unlimited domains - $499/mo
All plans include:
- API access
- CLI tools
- React components
- Technical support
- Regular updates
Support
- Documentation: paletteproai.com/docs
- Email: [email protected]
- Technical Issues: [email protected]
- Billing: [email protected]
Error Handling
import { PaletteWidget } from 'palettepro-ai';
function ProductPage() {
const handleError = (error) => {
console.error('Palette error:', error);
// Show fallback UI or error message
};
return (
<PaletteWidget
productId="product-123"
onError={handleError}
/>
);
}For backend errors:
const client = new PaletteProClient();
try {
const config = await client.getGridConfig('product-123');
} catch (error) {
if (error.status === 404) {
console.log('No palette found');
} else if (error.status === 429) {
console.log('Rate limited');
} else {
console.error('API error:', error.message);
}
}Rate Limiting
All API requests are rate-limited to 100 requests per 15 minutes per IP address. If you exceed this limit, you'll receive a 429 Too Many Requests response.
Best practices:
- Cache palette configurations on your server
- Implement exponential backoff for retries
- Monitor your usage in the dashboard
Troubleshooting
GenerateLooksButton Errors
500 Error: "Base64 decoding failed"
Error message: Base64 decoding failed for "/images/palette.jpg"
Cause: You're passing a file path instead of base64-encoded image data.
Solution: Convert the image to base64 before passing it to the component:
const base64Image = await imageUrlToBase64('/images/palette.jpg');
<GenerateLooksButton paletteImage={base64Image} {...otherProps} />401 Error: "Invalid API key"
Cause: API key is missing or incorrect.
Solution:
- Check that your API key is properly set in
.env:- For Vite:
VITE_PALETTEPRO_API_KEY=pp_live_... - For Create React App:
REACT_APP_PALETTEPRO_API_KEY=pp_live_...
- For Vite:
- Restart your dev server after changing
.env - Verify your API key in the dashboard
307 CORS/Redirect Errors
Cause: Incorrect API URL in environment variables.
Solution: Use https://www.paletteproai.com (with www):
VITE_PALETTEPRO_API_URL=https://www.paletteproai.com❌ Wrong: https://paletteproai.com (without www)
❌ Wrong: https://api.paletteproai.com
✅ Correct: https://www.paletteproai.com
TypeScript
Full TypeScript definitions are included:
import { PaletteProClient, PaletteWidget } from 'palettepro-ai';
interface GridConfig {
rows: number;
cols: number;
cells: Cell[][];
croppedImage: string;
boundingBox: BoundingBox;
timestamp: number;
}
const client = new PaletteProClient();
const config: GridConfig = await client.getGridConfig('product-123');License
MIT © Solasyn Labs LLC
Links
Made with ❤️ by Solasyn Labs
