diff --git a/frontend/editor3d/app/Classes/ApiManager/EngineApi.ts b/frontend/editor3d/app/Classes/ApiManager/EngineApi.ts index 605d6da288..c8a5c9ad85 100644 --- a/frontend/editor3d/app/Classes/ApiManager/EngineApi.ts +++ b/frontend/editor3d/app/Classes/ApiManager/EngineApi.ts @@ -36,4 +36,141 @@ export class EngineApi extends ApiManager { }; }); } + + public async uploadSceneSnapshot({ + screenshot, + sceneMediaToken, + }: { + screenshot: string; // base64 encoded PNG + sceneMediaToken?: string; + }): Promise> { + const endpoint = `${this.ApiTargets.BaseApi}/v1/image_studio/scene_snapshot`; + + const formData = new FormData(); + formData.append("screenshot", screenshot); + if (sceneMediaToken) { + formData.append("scene_media_token", sceneMediaToken); + } + + return this.post< + FormData, + { success?: boolean; snapshot_media_token?: string; BadInput?: string } + >({ + endpoint, + body: formData, + headers: { + "Content-Type": "multipart/form-data", + }, + }) + .then((response) => { + return { + success: response.success ?? false, + data: response.snapshot_media_token, + errorMessage: response.BadInput, + }; + }) + .catch((err) => { + return { + success: false, + errorMessage: err.message, + }; + }); + } + + public async enqueueImageGeneration({ + prompt, + sceneMediaToken, + additionalImages, + }: { + prompt: string; + sceneMediaToken: string; + additionalImages?: string[]; + }): Promise> { + const endpoint = `${this.ApiTargets.BaseApi}/v1/image_studio/prompt`; + + const body = { + prompt, + scene_media_token: sceneMediaToken, + additional_images: additionalImages, + }; + + return this.post< + { + prompt: string; + scene_media_token: string; + additional_images?: string[]; + }, + { success?: boolean; job_token?: string; BadInput?: string } + >({ + endpoint, + body, + }) + .then((response) => { + return { + success: response.success ?? false, + data: response.job_token, + errorMessage: response.BadInput, + }; + }) + .catch((err) => { + return { + success: false, + errorMessage: err.message, + }; + }); + } + + public async pollJobSession(jobToken: string): Promise> { + const endpoint = `${this.ApiTargets.BaseApi}/v1/jobs/session/${jobToken}`; + + return this.get<{ + success?: boolean; + status?: string; + result?: any; + BadInput?: string; + }>({ + endpoint, + }) + .then((response) => { + return { + success: response.success ?? false, + data: response.result, + errorMessage: response.BadInput, + }; + }) + .catch((err) => { + return { + success: false, + errorMessage: err.message, + }; + }); + } + + public async pollStudioSessionJobs( + jobToken: string, + ): Promise> { + const endpoint = `${this.ApiTargets.BaseApi}/v1/image_studio/session_jobs/${jobToken}`; + + return this.get<{ + success?: boolean; + status?: string; + result?: any; + BadInput?: string; + }>({ + endpoint, + }) + .then((response) => { + return { + success: response.success ?? false, + data: response.result, + errorMessage: response.BadInput, + }; + }) + .catch((err) => { + return { + success: false, + errorMessage: err.message, + }; + }); + } } diff --git a/frontend/editor3d/app/pages/PageEnigma/Editor/editor.ts b/frontend/editor3d/app/pages/PageEnigma/Editor/editor.ts index d685aca0a1..605c4a6f0d 100644 --- a/frontend/editor3d/app/pages/PageEnigma/Editor/editor.ts +++ b/frontend/editor3d/app/pages/PageEnigma/Editor/editor.ts @@ -641,6 +641,18 @@ class Editor { document.body.appendChild(this.stats.dom); } + public snapShotOfCurrentFrame(shouldDownload: boolean = true) { + const snapshot = this.renderer?.domElement.toDataURL("image/png"); + + if (snapshot) { + const link = document.createElement("a"); + link.download = "scene-snapshot.png"; + link.href = snapshot; + link.click(); + } + return snapshot; + } + public async newScene(sceneTitleInput: string) { this.activeScene.clear(); this.audio_engine = new AudioEngine(); @@ -948,14 +960,14 @@ class Editor { this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9 ? 1024 : this.render_camera_aspect_ratio === - CameraAspectRatio.VERTICAL_9_16 + CameraAspectRatio.VERTICAL_9_16 ? 576 : 1000; const height = this.render_camera_aspect_ratio === CameraAspectRatio.HORIZONTAL_16_9 ? 576 : this.render_camera_aspect_ratio === - CameraAspectRatio.VERTICAL_9_16 + CameraAspectRatio.VERTICAL_9_16 ? 1024 : 1000; diff --git a/frontend/editor3d/app/pages/PageEnigma/comps/PromptBox/PromptBox.tsx b/frontend/editor3d/app/pages/PageEnigma/comps/PromptBox/PromptBox.tsx index 6229bb1998..6a151ae636 100644 --- a/frontend/editor3d/app/pages/PageEnigma/comps/PromptBox/PromptBox.tsx +++ b/frontend/editor3d/app/pages/PageEnigma/comps/PromptBox/PromptBox.tsx @@ -1,4 +1,7 @@ import { useState, useRef, useEffect } from "react"; +import { useSignals } from "@preact/signals-react/runtime"; +import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext"; +import { useContext } from "react"; import { faPlus, @@ -13,8 +16,11 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { PopoverItem, PopoverMenu } from "~/components/reusable/Popover"; import { Button } from "~/components"; - +import { EngineApi } from "~/Classes/ApiManager/EngineApi"; +import { scene } from "~/signals/scene"; export const PromptBox = () => { + useSignals(); + const editorEngine = useContext(EngineContext); const [prompt, setPrompt] = useState(""); const [isEnqueueing, setisEnqueueing] = useState(false); const [aspectRatioList, setAspectRatioList] = useState([ @@ -116,6 +122,18 @@ export const PromptBox = () => { const handleEnqueue = async () => { if (!prompt.trim()) return; + if (editorEngine) { + editorEngine.positive_prompt = prompt; + } + + const engineApi = new EngineApi(); + + engineApi.enqueueImageGeneration({ + prompt: prompt, + sceneMediaToken: scene.value.token || "", + additionalImages: [], + }); + setisEnqueueing(true); try { // generate logic here @@ -133,6 +151,24 @@ export const PromptBox = () => { } }; + const handleSaveFrame = () => { + if (editorEngine) { + const snapshot = editorEngine.snapShotOfCurrentFrame(false); + + const engineApi = new EngineApi(); + + engineApi.uploadSceneSnapshot({ + screenshot: snapshot || "", + sceneMediaToken: scene.value.token || "", + }); + } + }; + const handleDownloadFrame = () => { + if (editorEngine) { + editorEngine.snapShotOfCurrentFrame(true); + } + }; + return (
{ className="flex items-center border-none bg-[#5F5F68]/60 px-3 text-sm text-white backdrop-blur-lg hover:bg-[#5F5F68]/90" variant="secondary" icon={faDownload} + onClick={handleSaveFrame} > Download frame @@ -210,6 +247,7 @@ export const PromptBox = () => { className="flex items-center border-none bg-[#5F5F68]/60 px-3 text-sm text-white backdrop-blur-lg hover:bg-[#5F5F68]/90" variant="secondary" icon={faSave} + onClick={handleDownloadFrame} > Save frame