Files
upstream-tldraw/templates/image-pipeline/worker/routes/styleTransfer.ts
upstream-mirror a774ac673a
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

120 lines
3.9 KiB
TypeScript

import { IRequest } from 'itty-router'
import { resolveImage } from '../providers/types'
interface StyleTransferRequest {
styleImageUrl: string
contentImageUrl?: string
prompt?: string
model: string
strength: number
}
/**
* POST /api/style-transfer
*
* Transfers the style of one image onto another (or generates a new image
* in that style) using fofr/style-transfer on Replicate.
* Falls back to a placeholder if no API key.
*/
export async function handleStyleTransfer(request: IRequest, env: Env) {
const body = (await request.json()) as StyleTransferRequest
if (!body.styleImageUrl) {
return new Response(JSON.stringify({ error: 'styleImageUrl is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
const apiKey = env.REPLICATE_API_TOKEN
if (!apiKey) {
return new Response(JSON.stringify(styleTransferPlaceholder(body)), {
headers: { 'Content-Type': 'application/json' },
})
}
try {
const { dataUrl: styleDataUrl } = await resolveImage(body.styleImageUrl, env)
const input: Record<string, unknown> = {
style_image: styleDataUrl,
prompt: body.prompt || '',
style_strength: body.strength ?? 0.5,
}
if (body.contentImageUrl) {
const { dataUrl: contentDataUrl } = await resolveImage(body.contentImageUrl, env)
input.structure_image = contentDataUrl
}
// Map model variant to the Replicate model parameter
const modelMap: Record<string, string> = {
fast: 'fast',
'high-quality': 'high-quality',
realistic: 'realistic',
cinematic: 'cinematic',
animated: 'animated',
}
input.model = modelMap[body.model] ?? 'fast'
const prediction = await fetch('https://api.replicate.com/v1/predictions', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
Prefer: 'wait',
},
body: JSON.stringify({
version: 'f1023890703bc0a5a3a2c21b5e498833be5f6ef6e70e9daf6b9b3a4fd8309cf0',
input,
}),
})
if (!prediction.ok) {
const err = await prediction.text()
throw new Error(`Replicate error: ${prediction.status} ${err}`)
}
const result = (await prediction.json()) as { output?: string[] | string }
const outputUrl = Array.isArray(result.output) ? result.output[0] : result.output
if (!outputUrl) throw new Error('No output from style transfer')
// Persist to R2 if available
let imageUrl = outputUrl
if (env.IMAGE_BUCKET) {
const imageRes = await fetch(outputUrl)
const imageData = await imageRes.arrayBuffer()
const id = crypto.randomUUID()
await env.IMAGE_BUCKET.put(id, imageData, {
httpMetadata: { contentType: imageRes.headers.get('content-type') ?? 'image/png' },
})
imageUrl = `/api/images/${id}`
}
return new Response(JSON.stringify({ imageUrl }), {
headers: { 'Content-Type': 'application/json' },
})
} catch (e: any) {
console.error('Style transfer error:', e)
return new Response(JSON.stringify({ error: e.message ?? 'Style transfer failed' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
}
function styleTransferPlaceholder(params: StyleTransferRequest) {
const hue = Math.floor(Math.random() * 360)
const model = params.model || 'fast'
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024">
<defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="hsl(${hue},55%,35%)"/>
<stop offset="100%" stop-color="hsl(${(hue + 120) % 360},50%,50%)"/>
</linearGradient></defs>
<rect width="1024" height="1024" fill="url(#bg)"/>
<text x="512" y="490" text-anchor="middle" fill="rgba(255,255,255,0.7)" font-family="sans-serif" font-size="22">Style Transfer</text>
<text x="512" y="530" text-anchor="middle" fill="rgba(255,255,255,0.4)" font-family="sans-serif" font-size="14">${model} · strength ${params.strength} · placeholder</text>
</svg>`
return { imageUrl: `data:image/svg+xml,${encodeURIComponent(svg)}` }
}