Initial commit
This commit is contained in:
391
templates/ai-image-generation.ts
Normal file
391
templates/ai-image-generation.ts
Normal file
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* Cloudflare Workers AI - Image Generation Examples
|
||||
*
|
||||
* This template demonstrates:
|
||||
* - Text-to-image with Flux models (highest quality)
|
||||
* - Stable Diffusion XL
|
||||
* - Image storage in R2
|
||||
* - Base64 and binary responses
|
||||
* - Custom prompts and parameters
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
|
||||
type Bindings = {
|
||||
AI: Ai;
|
||||
BUCKET?: R2Bucket;
|
||||
};
|
||||
|
||||
const app = new Hono<{ Bindings: Bindings }>();
|
||||
|
||||
// ============================================================================
|
||||
// Flux - Text-to-Image (Highest Quality)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Flux 1 Schnell - Fast, high-quality image generation
|
||||
* Best for: Photorealistic images, detailed artwork
|
||||
* Rate limit: 720/min
|
||||
*/
|
||||
|
||||
app.post('/generate/flux', async (c) => {
|
||||
try {
|
||||
const { prompt } = await c.req.json<{ prompt: string }>();
|
||||
|
||||
const imageStream = await c.env.AI.run('@cf/black-forest-labs/flux-1-schnell', {
|
||||
prompt,
|
||||
});
|
||||
|
||||
return new Response(imageStream, {
|
||||
headers: { 'content-type': 'image/png' },
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Stable Diffusion XL
|
||||
// ============================================================================
|
||||
|
||||
app.post('/generate/sdxl', async (c) => {
|
||||
try {
|
||||
const { prompt, num_steps = 20, guidance = 7.5 } = await c.req.json<{
|
||||
prompt: string;
|
||||
num_steps?: number;
|
||||
guidance?: number;
|
||||
}>();
|
||||
|
||||
const imageStream = await c.env.AI.run(
|
||||
'@cf/stabilityai/stable-diffusion-xl-base-1.0',
|
||||
{
|
||||
prompt,
|
||||
num_steps, // More steps = higher quality, slower
|
||||
guidance, // CFG scale: higher = more prompt adherence
|
||||
}
|
||||
);
|
||||
|
||||
return new Response(imageStream, {
|
||||
headers: { 'content-type': 'image/png' },
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// DreamShaper (Artistic/Stylized)
|
||||
// ============================================================================
|
||||
|
||||
app.post('/generate/dreamshaper', async (c) => {
|
||||
try {
|
||||
const { prompt } = await c.req.json<{ prompt: string }>();
|
||||
|
||||
const imageStream = await c.env.AI.run('@cf/lykon/dreamshaper-8-lcm', {
|
||||
prompt,
|
||||
});
|
||||
|
||||
return new Response(imageStream, {
|
||||
headers: { 'content-type': 'image/png' },
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Generate and Store in R2
|
||||
// ============================================================================
|
||||
|
||||
app.post('/generate/save', async (c) => {
|
||||
try {
|
||||
const { prompt, filename } = await c.req.json<{
|
||||
prompt: string;
|
||||
filename?: string;
|
||||
}>();
|
||||
|
||||
if (!c.env.BUCKET) {
|
||||
return c.json({ error: 'R2 bucket not configured' }, 500);
|
||||
}
|
||||
|
||||
// Generate image
|
||||
const imageStream = await c.env.AI.run('@cf/black-forest-labs/flux-1-schnell', {
|
||||
prompt,
|
||||
});
|
||||
|
||||
const imageBytes = await new Response(imageStream).bytes();
|
||||
|
||||
// Generate filename
|
||||
const key = filename || `images/${Date.now()}.png`;
|
||||
|
||||
// Store in R2
|
||||
await c.env.BUCKET.put(key, imageBytes, {
|
||||
httpMetadata: {
|
||||
contentType: 'image/png',
|
||||
},
|
||||
customMetadata: {
|
||||
prompt,
|
||||
generatedAt: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
message: 'Image generated and saved',
|
||||
key,
|
||||
url: `https://your-domain.com/${key}`, // Update with your R2 public URL
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Return Base64 Encoded Image
|
||||
// ============================================================================
|
||||
|
||||
app.post('/generate/base64', async (c) => {
|
||||
try {
|
||||
const { prompt } = await c.req.json<{ prompt: string }>();
|
||||
|
||||
const imageStream = await c.env.AI.run('@cf/black-forest-labs/flux-1-schnell', {
|
||||
prompt,
|
||||
});
|
||||
|
||||
const imageBytes = await new Response(imageStream).bytes();
|
||||
const base64 = btoa(String.fromCharCode(...imageBytes));
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
image: `data:image/png;base64,${base64}`,
|
||||
prompt,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Image-to-Image (Stable Diffusion)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Transform existing images based on prompts
|
||||
* Requires base64-encoded input image
|
||||
*/
|
||||
|
||||
app.post('/generate/img2img', async (c) => {
|
||||
try {
|
||||
const { prompt, image, strength = 0.8 } = await c.req.json<{
|
||||
prompt: string;
|
||||
image: string; // Base64 encoded
|
||||
strength?: number; // 0.0-1.0, higher = more transformation
|
||||
}>();
|
||||
|
||||
// Decode base64 image to array
|
||||
const imageData = Uint8Array.from(atob(image.replace(/^data:image\/\w+;base64,/, '')), (c) =>
|
||||
c.charCodeAt(0)
|
||||
);
|
||||
|
||||
const result = await c.env.AI.run('@cf/runwayml/stable-diffusion-v1-5-img2img', {
|
||||
prompt,
|
||||
image: Array.from(imageData),
|
||||
strength,
|
||||
});
|
||||
|
||||
return new Response(result, {
|
||||
headers: { 'content-type': 'image/png' },
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Batch Generation
|
||||
// ============================================================================
|
||||
|
||||
app.post('/generate/batch', async (c) => {
|
||||
try {
|
||||
const { prompts } = await c.req.json<{ prompts: string[] }>();
|
||||
|
||||
if (!prompts || prompts.length === 0) {
|
||||
return c.json({ error: 'prompts array is required' }, 400);
|
||||
}
|
||||
|
||||
if (prompts.length > 5) {
|
||||
return c.json({ error: 'Maximum 5 prompts per batch' }, 400);
|
||||
}
|
||||
|
||||
const images = await Promise.all(
|
||||
prompts.map(async (prompt) => {
|
||||
const imageStream = await c.env.AI.run('@cf/black-forest-labs/flux-1-schnell', {
|
||||
prompt,
|
||||
});
|
||||
|
||||
const imageBytes = await new Response(imageStream).bytes();
|
||||
const base64 = btoa(String.fromCharCode(...imageBytes));
|
||||
|
||||
return {
|
||||
prompt,
|
||||
image: `data:image/png;base64,${base64}`,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
count: images.length,
|
||||
images,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Prompt Enhancement
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Use LLM to enhance user prompts for better image quality
|
||||
*/
|
||||
|
||||
app.post('/generate/enhanced', async (c) => {
|
||||
try {
|
||||
const { userPrompt } = await c.req.json<{ userPrompt: string }>();
|
||||
|
||||
// Step 1: Enhance prompt with LLM
|
||||
const enhancement = await c.env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'You are a Stable Diffusion prompt expert. Enhance the user prompt for image generation. Add details about style, lighting, quality, composition. Return ONLY the enhanced prompt, no explanations.',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: userPrompt,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const enhancedPrompt = enhancement.response.trim();
|
||||
|
||||
// Step 2: Generate image with enhanced prompt
|
||||
const imageStream = await c.env.AI.run('@cf/black-forest-labs/flux-1-schnell', {
|
||||
prompt: enhancedPrompt,
|
||||
});
|
||||
|
||||
const imageBytes = await new Response(imageStream).bytes();
|
||||
const base64 = btoa(String.fromCharCode(...imageBytes));
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
originalPrompt: userPrompt,
|
||||
enhancedPrompt,
|
||||
image: `data:image/png;base64,${base64}`,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// List Generated Images (from R2)
|
||||
// ============================================================================
|
||||
|
||||
app.get('/images', async (c) => {
|
||||
try {
|
||||
if (!c.env.BUCKET) {
|
||||
return c.json({ error: 'R2 bucket not configured' }, 500);
|
||||
}
|
||||
|
||||
const listed = await c.env.BUCKET.list({
|
||||
prefix: 'images/',
|
||||
limit: 100,
|
||||
});
|
||||
|
||||
const images = listed.objects.map((obj) => ({
|
||||
key: obj.key,
|
||||
size: obj.size,
|
||||
uploaded: obj.uploaded,
|
||||
url: `https://your-domain.com/${obj.key}`,
|
||||
}));
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
count: images.length,
|
||||
images,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Health Check
|
||||
// ============================================================================
|
||||
|
||||
app.get('/health', (c) => {
|
||||
return c.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
export default app;
|
||||
Reference in New Issue
Block a user