| "use server" |
|
|
| import { v4 as uuidv4 } from "uuid" |
| import Replicate from "replicate" |
|
|
| import { RenderRequest, RenderedScene, RenderingEngine } from "@/types" |
| import { generateSeed } from "@/lib/generateSeed" |
| import { sleep } from "@/lib/sleep" |
|
|
| const renderingEngine = `${process.env.RENDERING_ENGINE || ""}` as RenderingEngine |
|
|
| |
| const huggingFaceToken = `${process.env.AUTH_HF_API_TOKEN || ""}` |
| const huggingFaceInferenceEndpointUrl = `${process.env.RENDERING_HF_INFERENCE_ENDPOINT_URL || ""}` |
| const huggingFaceInferenceApiBaseModel = `${process.env.RENDERING_HF_INFERENCE_API_BASE_MODEL || ""}` |
| const huggingFaceInferenceApiRefinerModel = `${process.env.RENDERING_HF_INFERENCE_API_REFINER_MODEL || ""}` |
|
|
| const replicateToken = `${process.env.AUTH_REPLICATE_API_TOKEN || ""}` |
| const replicateModel = `${process.env.RENDERING_REPLICATE_API_MODEL || ""}` |
| const replicateModelVersion = `${process.env.RENDERING_REPLICATE_API_MODEL_VERSION || ""}` |
|
|
| const videochainToken = `${process.env.AUTH_VIDEOCHAIN_API_TOKEN || ""}` |
| const videochainApiUrl = `${process.env.RENDERING_VIDEOCHAIN_API_URL || ""}` |
|
|
| export async function newRender({ |
| prompt, |
| // negativePrompt, |
| width, |
| height, |
| withCache |
| }: { |
| prompt: string |
| // negativePrompt: string[] |
| width: number |
| height: number |
| withCache: boolean |
| }) { |
| if (!prompt) { |
| const error = `cannot call the rendering API without a prompt, aborting..` |
| console.error(error) |
| throw new Error(error) |
| } |
|
|
| let defaulResult: RenderedScene = { |
| renderId: "", |
| status: "error", |
| assetUrl: "", |
| alt: prompt || "", |
| maskUrl: "", |
| error: "failed to fetch the data", |
| segments: [] |
| } |
|
|
| const nbInferenceSteps = 30 |
| const guidanceScale = 9 |
|
|
| try { |
| if (renderingEngine === "REPLICATE") { |
| if (!replicateToken) { |
| throw new Error(`you need to configure your REPLICATE_API_TOKEN in order to use the REPLICATE rendering engine`) |
| } |
| if (!replicateModel) { |
| throw new Error(`you need to configure your REPLICATE_API_MODEL in order to use the REPLICATE rendering engine`) |
| } |
| if (!replicateModelVersion) { |
| throw new Error(`you need to configure your REPLICATE_API_MODEL_VERSION in order to use the REPLICATE rendering engine`) |
| } |
| const replicate = new Replicate({ auth: replicateToken }) |
|
|
| const seed = generateSeed() |
| const prediction = await replicate.predictions.create({ |
| version: replicateModelVersion, |
| input: { |
| prompt: [ |
| "beautiful", |
| |
| prompt, |
| "award winning", |
| "high resolution" |
| ].join(", "), |
| width, |
| height, |
| seed |
| } |
| }) |
| |
| |
| |
| await sleep(4000) |
|
|
| return { |
| renderId: prediction.id, |
| status: "pending", |
| assetUrl: "", |
| alt: prompt, |
| error: prediction.error, |
| maskUrl: "", |
| segments: [] |
| } as RenderedScene |
| } if (renderingEngine === "INFERENCE_ENDPOINT" || renderingEngine === "INFERENCE_API") { |
| if (!huggingFaceToken) { |
| throw new Error(`you need to configure your HF_API_TOKEN in order to use the ${renderingEngine} rendering engine`) |
| } |
| if (renderingEngine === "INFERENCE_ENDPOINT" && !huggingFaceInferenceEndpointUrl) { |
| throw new Error(`you need to configure your RENDERING_HF_INFERENCE_ENDPOINT_URL in order to use the INFERENCE_ENDPOINT rendering engine`) |
| } |
| if (renderingEngine === "INFERENCE_API" && !huggingFaceInferenceApiBaseModel) { |
| throw new Error(`you need to configure your RENDERING_HF_INFERENCE_API_BASE_MODEL in order to use the INFERENCE_API rendering engine`) |
| } |
| if (renderingEngine === "INFERENCE_API" && !huggingFaceInferenceApiRefinerModel) { |
| throw new Error(`you need to configure your RENDERING_HF_INFERENCE_API_REFINER_MODEL in order to use the INFERENCE_API rendering engine`) |
| } |
|
|
| const baseModelUrl = renderingEngine === "INFERENCE_ENDPOINT" |
| ? huggingFaceInferenceEndpointUrl |
| : `https://api-inference.huggingface.co/models/${huggingFaceInferenceApiBaseModel}` |
|
|
| const positivePrompt = [ |
| "beautiful", |
| |
| prompt, |
| "award winning", |
| "high resolution" |
| ].join(", ") |
|
|
| const res = await fetch(baseModelUrl, { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| Authorization: `Bearer ${huggingFaceToken}`, |
| }, |
| body: JSON.stringify({ |
| inputs: positivePrompt, |
| parameters: { |
| num_inference_steps: nbInferenceSteps, |
| guidance_scale: guidanceScale, |
| width, |
| height, |
| }, |
|
|
| |
| use_cache: false, |
| }), |
| cache: "no-store", |
| |
| |
| }) |
| |
| |
| |
| if (res.status !== 200) { |
| const content = await res.text() |
| console.error(content) |
| |
| throw new Error('Failed to fetch data') |
| } |
|
|
| const blob = await res.arrayBuffer() |
|
|
| const contentType = res.headers.get('content-type') |
|
|
| let assetUrl = `data:${contentType};base64,${Buffer.from(blob).toString('base64')}` |
| |
| |
| |
| |
| if (renderingEngine === "INFERENCE_API") { |
| try { |
| const refinerModelUrl = `https://api-inference.huggingface.co/models/${huggingFaceInferenceApiRefinerModel}` |
|
|
|
|
|
|
| const res = await fetch(refinerModelUrl, { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| Authorization: `Bearer ${huggingFaceToken}`, |
| }, |
| body: JSON.stringify({ |
| inputs: Buffer.from(blob).toString('base64'), |
| parameters: { |
| prompt: positivePrompt, |
| num_inference_steps: nbInferenceSteps, |
| guidance_scale: guidanceScale, |
| width, |
| height, |
| }, |
|
|
| |
| use_cache: false, |
| }), |
| cache: "no-store", |
| |
| |
| }) |
| |
| |
| |
| if (res.status !== 200) { |
| const content = await res.json() |
| |
| |
| throw new Error(content?.error || 'Failed to fetch data') |
| } |
|
|
| const refinedBlob = await res.arrayBuffer() |
|
|
| const contentType = res.headers.get('content-type') |
|
|
| assetUrl = `data:${contentType};base64,${Buffer.from(refinedBlob).toString('base64')}` |
| |
| } catch (err) { |
| console.log(`Refiner step failed, but this is not a blocker. Error details: ${err}`) |
| } |
| } |
|
|
|
|
| return { |
| renderId: uuidv4(), |
| status: "completed", |
| assetUrl, |
| alt: prompt, |
| error: "", |
| maskUrl: "", |
| segments: [] |
| } as RenderedScene |
| } else { |
| const res = await fetch(`${videochainApiUrl}${videochainApiUrl.endsWith("/") ? "" : "/"}render`, { |
| method: "POST", |
| headers: { |
| Accept: "application/json", |
| "Content-Type": "application/json", |
| Authorization: `Bearer ${videochainToken}`, |
| }, |
| body: JSON.stringify({ |
| prompt, |
| |
| nbFrames: 1, |
| nbSteps: nbInferenceSteps, |
| actionnables: [], |
| segmentation: "disabled", |
| width, |
| height, |
|
|
| |
| |
| |
| |
| upscalingFactor: 1, |
|
|
| |
| analyze: false, |
|
|
| cache: "ignore" |
| } as Partial<RenderRequest>), |
| cache: 'no-store', |
| |
| |
| }) |
|
|
| if (res.status !== 200) { |
| throw new Error('Failed to fetch data') |
| } |
| |
| const response = (await res.json()) as RenderedScene |
|
|
| return response |
| } |
| } catch (err) { |
| console.error(err) |
| return defaulResult |
| } |
| } |
|
|
| export async function getRender(renderId: string) { |
| if (!renderId) { |
| const error = `cannot call the rendering API without a renderId, aborting..` |
| console.error(error) |
| throw new Error(error) |
| } |
|
|
| let defaulResult: RenderedScene = { |
| renderId: "", |
| status: "pending", |
| assetUrl: "", |
| alt: "", |
| maskUrl: "", |
| error: "failed to fetch the data", |
| segments: [] |
| } |
|
|
| try { |
| if (renderingEngine === "REPLICATE") { |
| if (!replicateToken) { |
| throw new Error(`you need to configure your AUTH_REPLICATE_API_TOKEN in order to use the REPLICATE rendering engine`) |
| } |
| if (!replicateModel) { |
| throw new Error(`you need to configure your RENDERING_REPLICATE_API_MODEL in order to use the REPLICATE rendering engine`) |
| } |
|
|
| const res = await fetch(`https://api.replicate.com/v1/predictions/${renderId}`, { |
| method: "GET", |
| headers: { |
| Authorization: `Token ${replicateToken}`, |
| }, |
| cache: 'no-store', |
| |
| |
| }) |
| |
| |
| if (res.status !== 200) { |
| |
| throw new Error('Failed to fetch data') |
| } |
| |
| const response = (await res.json()) as any |
|
|
| return { |
| renderId, |
| status: response?.error ? "error" : response?.status === "succeeded" ? "completed" : "pending", |
| assetUrl: `${response?.output || ""}`, |
| alt: `${response?.input?.prompt || ""}`, |
| error: `${response?.error || ""}`, |
| maskUrl: "", |
| segments: [] |
| } as RenderedScene |
| } else { |
| const res = await fetch(`${videochainApiUrl}/render/${renderId}`, { |
| method: "GET", |
| headers: { |
| Accept: "application/json", |
| "Content-Type": "application/json", |
| Authorization: `Bearer ${videochainToken}`, |
| }, |
| cache: 'no-store', |
| |
| |
| }) |
| |
| if (res.status !== 200) { |
| throw new Error('Failed to fetch data') |
| } |
| |
| const response = (await res.json()) as RenderedScene |
| return response |
| } |
| } catch (err) { |
| console.error(err) |
| defaulResult.status = "error" |
| defaulResult.error = `${err}` |
| return defaulResult |
| } |
| } |
|
|
| export async function upscaleImage(image: string): Promise<{ |
| assetUrl: string |
| error: string |
| }> { |
| if (!image) { |
| const error = `cannot call the rendering API without an image, aborting..` |
| console.error(error) |
| throw new Error(error) |
| } |
|
|
| let defaulResult = { |
| assetUrl: "", |
| error: "failed to fetch the data", |
| } |
|
|
| try { |
| const res = await fetch(`${videochainApiUrl}/upscale`, { |
| method: "POST", |
| headers: { |
| Accept: "application/json", |
| "Content-Type": "application/json", |
| Authorization: `Bearer ${videochainToken}`, |
| }, |
| cache: 'no-store', |
| body: JSON.stringify({ image, factor: 3 }) |
| |
| |
| }) |
|
|
| if (res.status !== 200) { |
| throw new Error('Failed to fetch data') |
| } |
| |
| const response = (await res.json()) as { |
| assetUrl: string |
| error: string |
| } |
| return response |
| } catch (err) { |
| console.error(err) |
| return defaulResult |
| } |
| } |
|
|