snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
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

This commit is contained in:
upstream-mirror
2026-09-23 16:12:52 +05:00
commit a774ac673a
4541 changed files with 770499 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
import { IRequest } from 'itty-router'
import { getProvider } from '../providers'
import type { GenerateParams } from '../providers'
/**
* Request body for the /api/generate endpoint.
*/
interface GenerateRequest {
/** The provider:model string, e.g. "stable-diffusion:sdxl" */
model: string
/** The text prompt describing the desired image */
prompt: string
/** Optional negative prompt */
negativePrompt?: string
/** Number of inference steps (default: 20) */
steps?: number
/** Classifier-free guidance scale (default: 7) */
cfgScale?: number
/** Seed for reproducibility (default: random) */
seed?: number
/** ControlNet mode if applicable */
controlNetMode?: string
/** ControlNet strength (0-100) */
controlNetStrength?: number
/** Reference image URL for ControlNet */
referenceImageUrl?: string
}
/**
* POST /api/generate
*
* Calls an AI image generation provider and returns the generated image.
* Supports multiple providers via the model string format "provider:model".
*
* Returns: { imageUrl: string, seed: number }
*/
export async function handleGenerate(request: IRequest, env: Env) {
const body = (await request.json()) as GenerateRequest
if (!body.prompt) {
return new Response(JSON.stringify({ error: 'prompt is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
const [providerName, modelId] = (body.model ?? 'flux:flux-dev').split(':')
try {
const provider = getProvider(providerName)
const params: GenerateParams = {
modelId: modelId ?? '',
prompt: body.prompt,
negativePrompt: body.negativePrompt,
steps: body.steps ?? 20,
cfgScale: body.cfgScale ?? 7,
seed: body.seed ?? null,
controlNetMode: body.controlNetMode,
controlNetStrength: body.controlNetStrength,
referenceImageUrl: body.referenceImageUrl,
}
let result = await provider.generate(params, env)
// Optionally persist the image to R2.
if (env.IMAGE_BUCKET && result.imageUrl?.startsWith('data:')) {
const imageId = `gen_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
const blob = dataUrlToBlob(result.imageUrl)
await env.IMAGE_BUCKET.put(imageId, blob, {
httpMetadata: { contentType: 'image/png' },
})
result = { ...result, imageUrl: `/api/images/${imageId}` }
}
return new Response(JSON.stringify(result), {
headers: { 'Content-Type': 'application/json' },
})
} catch (e: any) {
console.error('Generate error:', e)
return new Response(JSON.stringify({ error: e.message ?? 'Generation failed' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
}
// ---------------------------------------------------------------------------
// Utilities
// ---------------------------------------------------------------------------
function dataUrlToBlob(dataUrl: string): ArrayBuffer {
const [header, base64] = dataUrl.split(',')
if (header.includes('base64')) {
const binary = atob(base64)
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes.buffer as ArrayBuffer
}
// For SVG data URLs, just encode as UTF-8
return new TextEncoder().encode(decodeURIComponent(base64)).buffer as ArrayBuffer
}

View File

@@ -0,0 +1,116 @@
import { IRequest } from 'itty-router'
import { resolveImage } from '../providers/types'
interface GenerateTextRequest {
input?: string
prompt: string
}
/**
* POST /api/generate-text
*
* Takes an optional input (image or text) and a prompt, then calls
* google/gemini-3.5-flash on Replicate to generate text.
* Falls back to a placeholder if no API token is configured.
*/
export async function handleGenerateText(request: IRequest, env: Env) {
const body = (await request.json()) as GenerateTextRequest
if (!body.prompt) {
return new Response(JSON.stringify({ error: 'prompt is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
const apiToken = env.REPLICATE_API_TOKEN
if (!apiToken) {
return new Response(JSON.stringify(generateTextPlaceholder(body)), {
headers: { 'Content-Type': 'application/json' },
})
}
// Coerce input to string so downstream .startsWith() never crashes
const inputStr = body.input != null ? String(body.input) : null
// Detect whether the input is an image or text
const isImage =
inputStr != null &&
(inputStr.startsWith('data:image/') ||
inputStr.startsWith('/api/images/') ||
inputStr.startsWith('https://') ||
inputStr.startsWith('http://'))
try {
// Build the prompt — if input is text, prepend it to the prompt
let prompt = body.prompt
if (inputStr && !isImage) {
prompt = `Context:\n${inputStr}\n\n${body.prompt}`
}
// Build the images array if we have an image input
const images: string[] = []
if (inputStr && isImage) {
const { dataUrl } = await resolveImage(inputStr, env)
images.push(dataUrl)
}
const input: Record<string, unknown> = {
prompt,
max_output_tokens: 1024,
}
if (images.length > 0) {
input.images = images
}
const response = await fetch(
'https://api.replicate.com/v1/models/google/gemini-3.5-flash/predictions',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiToken}`,
Prefer: 'wait',
},
body: JSON.stringify({ input }),
}
)
if (!response.ok) {
const err = await response.text()
throw new Error(`Replicate error: ${response.status} ${err}`)
}
const result = (await response.json()) as { output?: string | string[] }
const output = Array.isArray(result.output) ? result.output.join('') : result.output
if (!output) throw new Error('No output from text generation')
return new Response(JSON.stringify({ text: output }), {
headers: { 'Content-Type': 'application/json' },
})
} catch (e: any) {
console.error('Generate text error:', e)
return new Response(JSON.stringify({ error: e.message ?? 'Text generation failed' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
}
function generateTextPlaceholder(body: GenerateTextRequest): { text: string } {
const inputStr = body.input != null ? String(body.input) : null
const isImage =
inputStr != null &&
(inputStr.startsWith('data:image/') ||
inputStr.startsWith('/api/images/') ||
inputStr.startsWith('https://') ||
inputStr.startsWith('http://'))
const inputDesc = inputStr
? isImage
? '[image provided]'
: `[text: "${inputStr.slice(0, 40)}${inputStr.length > 40 ? '...' : ''}"]`
: '[no input]'
return {
text: `[Placeholder] Prompt: "${body.prompt.slice(0, 60)}${body.prompt.length > 60 ? '...' : ''}" | Input: ${inputDesc} — Set REPLICATE_API_TOKEN for real text generation.`,
}
}

View File

@@ -0,0 +1,68 @@
import { IRequest } from 'itty-router'
/**
* POST /api/images/:imageId
*
* Upload a generated image to R2 for persistence.
*/
export async function handleImageUpload(request: IRequest, env: Env) {
const { imageId } = request.params
const contentType = request.headers.get('content-type') ?? 'image/png'
if (!contentType.startsWith('image/')) {
return new Response(JSON.stringify({ error: 'Invalid content type' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
// Don't overwrite existing images.
if (await env.IMAGE_BUCKET.head(imageId)) {
return new Response(JSON.stringify({ ok: true }), {
headers: { 'Content-Type': 'application/json' },
})
}
await env.IMAGE_BUCKET.put(imageId, request.body, {
httpMetadata: { contentType },
})
return new Response(JSON.stringify({ ok: true }), {
headers: { 'Content-Type': 'application/json' },
})
}
/**
* GET /api/images/:imageId
*
* Download a generated image from R2 with caching.
*/
export async function handleImageDownload(request: IRequest, env: Env, ctx: ExecutionContext) {
const { imageId } = request.params
// Check edge cache first.
// Use caches.open() to avoid DOM/Workers CacheStorage type conflict.
const cache = await caches.open('images')
const cacheKey = new Request(request.url, { headers: request.headers })
const cachedResponse = await cache.match(cacheKey)
if (cachedResponse) {
return cachedResponse
}
const object = await env.IMAGE_BUCKET.get(imageId)
if (!object) {
return new Response('Not found', { status: 404 })
}
const headers = new Headers()
headers.set('content-type', object.httpMetadata?.contentType ?? 'image/png')
// Generated images are immutable — cache them aggressively.
headers.set('cache-control', 'public, max-age=31536000, immutable')
const response = new Response(object.body, { headers })
// Cache in the edge for future requests.
ctx.waitUntil(cache.put(cacheKey, response.clone()))
return response
}

View File

@@ -0,0 +1,101 @@
import { IRequest } from 'itty-router'
import { resolveImage } from '../providers/types'
interface IPAdapterRequest {
imageUrl: string
prompt: string
scale: number
steps: number
}
/**
* POST /api/ip-adapter
*
* Generates an image guided by a reference image and text prompt using
* IP-Adapter SDXL on Replicate. Falls back to a placeholder if no API key.
*/
export async function handleIPAdapter(request: IRequest, env: Env) {
const body = (await request.json()) as IPAdapterRequest
if (!body.imageUrl) {
return new Response(JSON.stringify({ error: 'imageUrl is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
const apiKey = env.REPLICATE_API_TOKEN
if (!apiKey) {
return new Response(JSON.stringify(ipAdapterPlaceholder(body)), {
headers: { 'Content-Type': 'application/json' },
})
}
try {
const { dataUrl } = await resolveImage(body.imageUrl, env)
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: '904dc004af1dba5c9b13fc9e41635aeb2f9a177896a396ab3393f3f6493dbdd4',
input: {
image: dataUrl,
prompt: body.prompt || 'best quality, high quality',
scale: body.scale ?? 0.6,
num_inference_steps: body.steps ?? 30,
},
}),
})
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 IP-Adapter')
// 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('IP-Adapter error:', e)
return new Response(JSON.stringify({ error: e.message ?? 'IP-Adapter failed' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
}
function ipAdapterPlaceholder(params: IPAdapterRequest) {
const hue = Math.floor(Math.random() * 360)
const prompt = (params.prompt || 'IP-Adapter').slice(0, 30)
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},50%,40%)"/>
<stop offset="100%" stop-color="hsl(${(hue + 80) % 360},45%,55%)"/>
</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">${prompt}</text>
<text x="512" y="530" text-anchor="middle" fill="rgba(255,255,255,0.4)" font-family="sans-serif" font-size="14">IP-Adapter · scale ${params.scale} · placeholder</text>
</svg>`
return { imageUrl: `data:image/svg+xml,${encodeURIComponent(svg)}` }
}

View File

@@ -0,0 +1,119 @@
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)}` }
}

View File

@@ -0,0 +1,57 @@
import { IRequest } from 'itty-router'
import { getUpscaleProvider } from '../providers'
import type { UpscaleParams } from '../providers'
interface UpscaleRequest {
/** URL of the image to upscale */
imageUrl: string
/** Scale factor (2 or 4) */
scale: number
/** Upscale method */
method: string
}
/**
* POST /api/upscale
*
* Upscales an image using an AI upscaler. Falls back to a placeholder
* if no API key is configured.
*/
export async function handleUpscale(request: IRequest, env: Env) {
const body = (await request.json()) as UpscaleRequest
if (!body.imageUrl) {
return new Response(JSON.stringify({ error: 'imageUrl is required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
})
}
try {
const provider = getUpscaleProvider(body.method)
const params: UpscaleParams = {
imageUrl: body.imageUrl,
scale: body.scale,
method: body.method,
}
if (!provider.upscale) {
return new Response(
JSON.stringify({ error: `Provider "${provider.name}" does not support upscaling` }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
)
}
const result = await provider.upscale(params, env)
return new Response(JSON.stringify(result), {
headers: { 'Content-Type': 'application/json' },
})
} catch (e: any) {
console.error('Upscale error:', e)
return new Response(JSON.stringify({ error: e.message ?? 'Upscale failed' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
})
}
}