|
"use server" |
|
|
|
import {Ratelimit} from "@upstash/ratelimit" |
|
import {Redis} from "@upstash/redis" |
|
|
|
import { VideoOptions } from "@/types" |
|
|
|
import { generateGradio } from "./generateGradio" |
|
import { generateReplicate } from "./generateReplicate" |
|
import { filterOutBadWords } from "./censorship" |
|
|
|
const videoEngine = `${process.env.VIDEO_ENGINE || ""}` |
|
|
|
|
|
const nodeApi = `${process.env.VIDEO_HOTSHOT_XL_API_NODE || ""}` |
|
|
|
const redis = new Redis({ |
|
url: `${process.env.UPSTASH_REDIS_REST_URL || ""}`, |
|
token: `${process.env.UPSTASH_REDIS_REST_TOKEN || ""}`, |
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const rateLimitAnons = new Ratelimit({ |
|
redis, |
|
limiter: Ratelimit.slidingWindow(2, "60 s"), |
|
analytics: true, |
|
timeout: 1000, |
|
prefix: "production:anon" |
|
}) |
|
|
|
export async function generateAnimation({ |
|
positivePrompt = "", |
|
negativePrompt = "", |
|
size = "512x512", |
|
huggingFaceLora, |
|
replicateLora, |
|
triggerWord, |
|
nbFrames = 8, |
|
duration = 1000, |
|
steps = 30, |
|
key = "", |
|
}: VideoOptions): Promise<string> { |
|
if (!positivePrompt?.length) { |
|
throw new Error(`prompt is too short!`) |
|
} |
|
|
|
const cropped = positivePrompt.slice(0, 30) |
|
|
|
console.log(`user ${key.slice(0, 10)} requested "${cropped}${cropped !== positivePrompt ? "..." : ""}"`) |
|
|
|
|
|
|
|
|
|
|
|
const userRateLimitResult = await rateLimitAnons.limit(key || "anon") |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!userRateLimitResult.success) { |
|
console.log(`blocking user ${key.slice(0, 10)} who requested "${cropped}${cropped !== positivePrompt ? "..." : ""}"`) |
|
throw new Error(`Rate Limit Reached`) |
|
} else { |
|
console.log(`allowing user ${key.slice(0, 10)}: who requested "${cropped}${cropped !== positivePrompt ? "..." : ""}"`) |
|
} |
|
|
|
positivePrompt = filterOutBadWords(positivePrompt) |
|
|
|
|
|
positivePrompt = [ |
|
triggerWord, |
|
positivePrompt, |
|
"beautiful", |
|
"hd" |
|
].join(", ") |
|
|
|
negativePrompt = [ |
|
negativePrompt, |
|
"cropped", |
|
"dark", |
|
"underexposed", |
|
"overexposed", |
|
"watermark", |
|
"watermarked", |
|
].join(", ") |
|
|
|
try { |
|
|
|
if (videoEngine === "VIDEO_HOTSHOT_XL_API_REPLICATE") { |
|
return generateReplicate({ |
|
positivePrompt, |
|
negativePrompt, |
|
size, |
|
huggingFaceLora, |
|
replicateLora, |
|
nbFrames, |
|
duration, |
|
steps, |
|
}) |
|
|
|
} else if (videoEngine === "VIDEO_HOTSHOT_XL_API_NODE") { |
|
|
|
|
|
const res = await fetch(nodeApi, { |
|
method: "POST", |
|
headers: { |
|
"Content-Type": "application/json", |
|
|
|
|
|
}, |
|
body: JSON.stringify({ |
|
prompt: positivePrompt, |
|
lora: huggingFaceLora, |
|
size, |
|
}), |
|
cache: "no-store", |
|
|
|
|
|
}) |
|
|
|
const content = await res.text() |
|
|
|
|
|
if (res.status !== 200) { |
|
console.error(content) |
|
|
|
throw new Error('Failed to fetch data') |
|
} |
|
|
|
return content |
|
} else if (videoEngine === "VIDEO_HOTSHOT_XL_API_GRADIO") { |
|
return generateGradio({ |
|
positivePrompt, |
|
negativePrompt, |
|
size, |
|
huggingFaceLora, |
|
replicateLora, |
|
nbFrames, |
|
duration, |
|
steps, |
|
}) |
|
} else { |
|
throw new Error(`not implemented yet!`) |
|
} |
|
|
|
} catch (err) { |
|
throw new Error(`failed to generate the image ${err}`) |
|
} |
|
} |