External AI Backend
Implement your own AI backend to control models, prompts, and costs. Set mode: 'external' and provide callback handlers that proxy requests to your server.
Client Configuration
const config: PexelizeConfig = {
ai: {
mode: 'external',
callbacks: {
onSmartTextGenerate: (params) =>
fetch('/api/ai/text', { method: 'POST', body: JSON.stringify(params) }).then(r => r.json()).then(d => d.text),
onSmartHeadingGenerate: (params) =>
fetch('/api/ai/heading', { method: 'POST', body: JSON.stringify(params) }).then(r => r.json()).then(d => d.heading),
onSmartButtonGenerate: (params) =>
fetch('/api/ai/button', { method: 'POST', body: JSON.stringify(params) }).then(r => r.json()).then(d => d.label),
onImageGenerate: (params) =>
fetch('/api/ai/images/generate', { method: 'POST', body: JSON.stringify(params) }).then(r => r.json()),
onImageSearch: (params) =>
fetch('/api/ai/images/search', { method: 'POST', body: JSON.stringify(params) }).then(r => r.json()),
onAltTextGenerate: (params) =>
fetch('/api/ai/alt-text', { method: 'POST', body: JSON.stringify(params) }).then(r => r.json()),
},
},
};
Node.js Express Server
Complete backend with all 5 endpoint handlers:
import express from 'express';
import OpenAI from 'openai';
import cors from 'cors';
const app = express();
app.use(cors());
app.use(express.json());
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// 1. Smart Text
app.post('/api/ai/text', async (req, res) => {
const { prompt, tone, length, language } = req.body;
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: `Generate ${length} marketing copy in ${language}. Tone: ${tone}. Return HTML.` },
{ role: 'user', content: prompt },
],
max_tokens: length === 'short' ? 150 : length === 'medium' ? 400 : 800,
});
res.json({ text: completion.choices[0].message.content });
});
// 2. Smart Heading
app.post('/api/ai/heading', async (req, res) => {
const { prompt, level, tone, language } = req.body;
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: `Generate a single H${level} heading in ${language}. Tone: ${tone}. Return plain text only.` },
{ role: 'user', content: prompt },
],
max_tokens: 60,
});
res.json({ heading: completion.choices[0].message.content });
});
// 3. Smart Button
app.post('/api/ai/button', async (req, res) => {
const { prompt, maxLength, language } = req.body;
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{ role: 'system', content: `Generate a CTA button label in ${language}. Max ${maxLength} characters. Plain text only.` },
{ role: 'user', content: prompt },
],
max_tokens: 20,
});
res.json({ label: completion.choices[0].message.content });
});
// 4. Image Generation
app.post('/api/ai/images/generate', async (req, res) => {
const { prompt, style, aspectRatio, count } = req.body;
const sizeMap = {
'1:1': '1024x1024',
'16:9': '1792x1024',
'9:16': '1024x1792',
'4:3': '1024x768',
'3:4': '768x1024',
};
const response = await openai.images.generate({
model: 'dall-e-3',
prompt: `${style} style: ${prompt}`,
n: Math.min(count, 1), // DALL-E 3 supports n=1
size: sizeMap[aspectRatio] || '1024x1024',
});
res.json({
images: response.data.map((img) => ({
url: img.url,
width: parseInt(sizeMap[aspectRatio]?.split('x')[0] || '1024'),
height: parseInt(sizeMap[aspectRatio]?.split('x')[1] || '1024'),
})),
});
});
// 5. Image Search (Unsplash)
app.post('/api/ai/images/search', async (req, res) => {
const { query, page, perPage, orientation, color } = req.body;
const url = new URL('https://api.unsplash.com/search/photos');
url.searchParams.set('query', query);
url.searchParams.set('page', String(page));
url.searchParams.set('per_page', String(perPage));
if (orientation) url.searchParams.set('orientation', orientation);
if (color) url.searchParams.set('color', color.replace('#', ''));
const response = await fetch(url.toString(), {
headers: { Authorization: `Client-ID ${process.env.UNSPLASH_ACCESS_KEY}` },
});
const data = await response.json();
res.json({
images: data.results.map((p) => ({
id: p.id,
url: p.urls.regular,
thumbnailUrl: p.urls.thumb,
width: p.width,
height: p.height,
author: p.user.name,
authorUrl: p.user.links.html,
source: 'Unsplash',
})),
totalResults: data.total,
hasMore: page * perPage < data.total,
});
});
// 6. Alt Text
app.post('/api/ai/alt-text', async (req, res) => {
const { imageUrl } = req.body;
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: 'Describe this image in one sentence for use as alt text.' },
{ type: 'image_url', image_url: { url: imageUrl } },
],
},
],
max_tokens: 100,
});
res.json({ altText: completion.choices[0].message.content });
});
app.listen(3001, () => console.log('AI backend running on :3001'));
Python Flask Server
from flask import Flask, request, jsonify
from flask_cors import CORS
from openai import OpenAI
import os, requests
app = Flask(__name__)
CORS(app)
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
@app.post("/api/ai/text")
def smart_text():
body = request.json
completion = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"Generate {body['length']} marketing copy in {body['language']}. Tone: {body['tone']}. Return HTML."},
{"role": "user", "content": body["prompt"]},
],
max_tokens=400,
)
return jsonify(text=completion.choices[0].message.content)
@app.post("/api/ai/heading")
def smart_heading():
body = request.json
completion = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"Generate a single H{body['level']} heading in {body['language']}. Tone: {body['tone']}. Plain text only."},
{"role": "user", "content": body["prompt"]},
],
max_tokens=60,
)
return jsonify(heading=completion.choices[0].message.content)
@app.post("/api/ai/button")
def smart_button():
body = request.json
completion = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": f"Generate a CTA button label in {body['language']}. Max {body['maxLength']} chars. Plain text only."},
{"role": "user", "content": body["prompt"]},
],
max_tokens=20,
)
return jsonify(label=completion.choices[0].message.content)
@app.post("/api/ai/images/generate")
def image_generate():
body = request.json
size_map = {"1:1": "1024x1024", "16:9": "1792x1024", "9:16": "1024x1792", "4:3": "1024x768", "3:4": "768x1024"}
size = size_map.get(body["aspectRatio"], "1024x1024")
response = client.images.generate(
model="dall-e-3",
prompt=f"{body['style']} style: {body['prompt']}",
n=1,
size=size,
)
w, h = size.split("x")
return jsonify(images=[{"url": img.url, "width": int(w), "height": int(h)} for img in response.data])
@app.post("/api/ai/images/search")
def image_search():
body = request.json
params = {"query": body["query"], "page": body["page"], "per_page": body["perPage"]}
if body.get("orientation"):
params["orientation"] = body["orientation"]
if body.get("color"):
params["color"] = body["color"].lstrip("#")
resp = requests.get(
"https://api.unsplash.com/search/photos",
params=params,
headers={"Authorization": f"Client-ID {os.environ['UNSPLASH_ACCESS_KEY']}"},
).json()
return jsonify(
images=[{
"id": p["id"], "url": p["urls"]["regular"], "thumbnailUrl": p["urls"]["thumb"],
"width": p["width"], "height": p["height"],
"author": p["user"]["name"], "authorUrl": p["user"]["links"]["html"], "source": "Unsplash",
} for p in resp["results"]],
totalResults=resp["total"],
hasMore=body["page"] * body["perPage"] < resp["total"],
)
@app.post("/api/ai/alt-text")
def alt_text():
body = request.json
completion = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": [
{"type": "text", "text": "Describe this image in one sentence for use as alt text."},
{"type": "image_url", "image_url": {"url": body["imageUrl"]}},
]}],
max_tokens=100,
)
return jsonify(altText=completion.choices[0].message.content)
if __name__ == "__main__":
app.run(port=3001)
warning
Never expose API keys in client-side code. All AI calls must be proxied through your backend.
tip
Add authentication middleware to your endpoints. The callbacks run in the user's browser, so include session tokens in your fetch headers.