File size: 4,560 Bytes
9a42933 ee5bd94 7448744 6c5a5b1 1cf03f7 827f345 9a42933 8f35fb6 7448744 a9e45ba 6c5a5b1 ee5bd94 6eef442 ee5bd94 6eef442 ee5bd94 9a42933 7448744 8bc9511 7448744 ee5bd94 7448744 9a42933 ee5bd94 6eef442 ee5bd94 6eef442 ee5bd94 6eef442 ee5bd94 827f345 7448744 8bc9511 7448744 6c5a5b1 7448744 6c5a5b1 b56210f 9a42933 6c5a5b1 1cf03f7 7448744 6c5a5b1 5c5e659 a9e45ba 5c5e659 7448744 5c5e659 a9e45ba 1cf03f7 7448744 5c5e659 9a42933 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
"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 officialApi = `${process.env.VIDEO_HOTSHOT_XL_API_OFFICIAL || ""}`
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 || ""}`,
})
// Create a global ratelimiter for all users, that allows 14 requests per 60 seconds
// 14 is roughly the number of requests that can be handled by the server
/*
const rateLimitGlobal = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(14, "60 s"),
analytics: true,
timeout: 1000,
prefix: "production"
})
*/
// Create a new ratelimiter for anonymous users, that allows 2 requests per minute
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 globalRateLimitResult = rateLimitGlobal.limit("global")
// this waits for 3 seconds before failing the request
// we don't wait more because it is frustrating for someone to wait a failure
const userRateLimitResult = await rateLimitAnons.limit(key || "anon")
// const rateLimitResult = await rateLimitAnons.blockUntilReady(key, 3_000)
// admin / developers will have this key:
// eff8e7ca506627fe15dda5e0e512fcaad70b6d520f37cc76597fdb4f2d83a1a3
// result.limit
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)
// pimp the prompt
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") {
// TODO: support other API to avoid duplicate work?
// (are the other API supporting custom LoRAs?)
const res = await fetch(nodeApi, {
method: "POST",
headers: {
"Content-Type": "application/json",
// access isn't secured for now, the free lunch is open
// Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
prompt: positivePrompt,
lora: huggingFaceLora,
size,
}),
cache: "no-store",
// we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
// next: { revalidate: 1 }
})
const content = await res.text()
// Recommendation: handle errors
if (res.status !== 200) {
console.error(content)
// This will activate the closest `error.js` Error Boundary
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}`)
}
} |