Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00c5636f44 | ||
|
|
7492f1c007 | ||
|
|
1273b06977 |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 320 KiB |
|
After Width: | Height: | Size: 564 KiB |
|
After Width: | Height: | Size: 366 KiB |
|
After Width: | Height: | Size: 452 KiB |
|
After Width: | Height: | Size: 337 KiB |
|
After Width: | Height: | Size: 378 KiB |
|
After Width: | Height: | Size: 302 KiB |
|
After Width: | Height: | Size: 446 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 344 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 299 KiB |
|
After Width: | Height: | Size: 396 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 521 KiB |
|
After Width: | Height: | Size: 318 KiB |
|
After Width: | Height: | Size: 396 KiB |
|
After Width: | Height: | Size: 316 KiB |
@@ -140,7 +140,11 @@ export const TopBar = ({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center gap-2 font-medium">
|
||||
<SceneTitleInput pageName={pageName} />
|
||||
{currentAppTabId === "3D" ? (
|
||||
<SceneTitleInput pageName={pageName} />
|
||||
) : (
|
||||
<h1>{currentAppTabId === "2D" ? "Canvas" : "Generate"}</h1>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3.5">
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
|
||||
function getRandomImages(count: number) {
|
||||
const images = Array.from({ length: count }, (_, i) => ({
|
||||
id: i + 1,
|
||||
src: `/resources/images/generate/image${i + 1}.png`,
|
||||
alt: `Generated image ${i + 1}`,
|
||||
}));
|
||||
|
||||
// Shuffle the array
|
||||
return [...images].sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
interface ImageColumnProps {
|
||||
images: Array<{ id: number; src: string; alt: string }>;
|
||||
delay: number;
|
||||
reverse?: boolean;
|
||||
isLoaded: boolean;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
function ImageColumn({
|
||||
images,
|
||||
delay,
|
||||
reverse = false,
|
||||
duration = 100,
|
||||
isLoaded,
|
||||
}: ImageColumnProps) {
|
||||
const columnRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!columnRef.current) return;
|
||||
|
||||
const column = columnRef.current;
|
||||
const animation = column.animate(
|
||||
[
|
||||
{ transform: reverse ? "translateY(-50%)" : "translateY(0)" },
|
||||
{ transform: reverse ? "translateY(0)" : "translateY(-50%)" },
|
||||
],
|
||||
{
|
||||
duration: duration * 1000,
|
||||
iterations: Infinity,
|
||||
easing: "linear",
|
||||
delay: delay + 1000,
|
||||
fill: "both",
|
||||
},
|
||||
);
|
||||
|
||||
if (!isLoaded) {
|
||||
animation.pause();
|
||||
}
|
||||
|
||||
return () => {
|
||||
animation.cancel();
|
||||
};
|
||||
}, [delay, duration, isLoaded, reverse]);
|
||||
|
||||
return (
|
||||
<div className="relative h-screen overflow-hidden">
|
||||
<div
|
||||
ref={columnRef}
|
||||
className="absolute left-0 right-0 flex flex-col gap-6"
|
||||
style={{
|
||||
transform: reverse ? "translateY(-50%)" : "translateY(0)",
|
||||
willChange: "transform",
|
||||
}}
|
||||
>
|
||||
{[...images, ...images].map((image, i) => (
|
||||
<div
|
||||
key={`${image.id}-${i}`}
|
||||
className={`relative aspect-[4/5] w-full overflow-hidden rounded-lg shadow-lg transition-all duration-700 ease-out ${
|
||||
isLoaded
|
||||
? "translate-y-0 opacity-100"
|
||||
: "translate-y-12 opacity-0"
|
||||
}`}
|
||||
style={{
|
||||
transitionDelay: `${delay + (i % images.length) * 50}ms`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={image.src}
|
||||
alt={image.alt}
|
||||
className="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function GalleryBackground() {
|
||||
const [images, _setImages] = useState(() => getRandomImages(20));
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// delay for smooth animation
|
||||
const timer = setTimeout(() => {
|
||||
setIsLoaded(true);
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
// Columns for masonry layout
|
||||
const columns = {
|
||||
col1: images.filter((_, i) => i % 5 === 0),
|
||||
col2: images.filter((_, i) => i % 5 === 1),
|
||||
col3: images.filter((_, i) => i % 5 === 2),
|
||||
col4: images.filter((_, i) => i % 5 === 3),
|
||||
col5: images.filter((_, i) => i % 5 === 4),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="pointer-events-none fixed inset-0 z-[1] overflow-hidden bg-[radial-gradient(50%_50%_at_50%_50%,rgba(0,0,0,0.00)_49%,rgba(0,0,0,0.60)_100%)]" />
|
||||
<div className="fixed inset-0 z-0 overflow-hidden">
|
||||
<div
|
||||
className={`h-full w-full transition-opacity duration-1000 ${isLoaded ? "opacity-30" : "opacity-0"}`}
|
||||
>
|
||||
<div className="absolute inset-0 grid grid-cols-5 gap-6 px-6 opacity-35">
|
||||
<ImageColumn
|
||||
images={columns.col1}
|
||||
delay={0}
|
||||
isLoaded={isLoaded}
|
||||
duration={100}
|
||||
/>
|
||||
<ImageColumn
|
||||
images={columns.col2}
|
||||
delay={columns.col1.length * 50}
|
||||
isLoaded={isLoaded}
|
||||
reverse={true}
|
||||
duration={100}
|
||||
/>
|
||||
<ImageColumn
|
||||
images={columns.col3}
|
||||
delay={(columns.col1.length + columns.col2.length) * 50}
|
||||
isLoaded={isLoaded}
|
||||
duration={125}
|
||||
/>
|
||||
<ImageColumn
|
||||
images={columns.col4}
|
||||
delay={
|
||||
(columns.col1.length +
|
||||
columns.col2.length +
|
||||
columns.col3.length) *
|
||||
50
|
||||
}
|
||||
isLoaded={isLoaded}
|
||||
reverse={true}
|
||||
duration={160}
|
||||
/>
|
||||
<ImageColumn
|
||||
images={columns.col5}
|
||||
delay={
|
||||
(columns.col1.length +
|
||||
columns.col2.length +
|
||||
columns.col3.length +
|
||||
columns.col4.length) *
|
||||
50
|
||||
}
|
||||
isLoaded={isLoaded}
|
||||
duration={110}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +1,27 @@
|
||||
import { useState, useRef } from "react";
|
||||
import { JobContextType } from "libs/common/src/lib/types";
|
||||
import { PromptBoxVideo } from "@storyteller/ui-promptbox";
|
||||
import BackgroundGallery from "./BackgroundGallery";
|
||||
import { PopoverMenu, type PopoverItem } from "@storyteller/ui-popover";
|
||||
import { faFilm, faClock } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
const ImageToVideo = () => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [selectedModel, setSelectedModel] = useState<string>("Kling 2.0");
|
||||
const [useSystemPrompt, setUseSystemPrompt] = useState(false);
|
||||
|
||||
const [isImageBeingProcessed, setIsImageBeingProcessed] = useState(false);
|
||||
const [selectedImage, setSelectedImage] = useState<File | null>(null);
|
||||
const [imagePreviewUrl, setImagePreviewUrl] = useState<string>("");
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleClose = () => setIsDialogOpen(false);
|
||||
const handleAddImage = (image: File) => {
|
||||
setSelectedImage(image);
|
||||
setIsImageBeingProcessed(true);
|
||||
setImagePreviewUrl(URL.createObjectURL(image));
|
||||
|
||||
// TODO: Implement actual image processing here
|
||||
// For now, we'll just simulate processing
|
||||
setTimeout(() => {
|
||||
setIsImageBeingProcessed(false);
|
||||
}, 3000);
|
||||
setTimeout(() => setIsImageBeingProcessed(false), 3000);
|
||||
};
|
||||
|
||||
const jobContext: JobContextType = {
|
||||
@@ -32,13 +31,41 @@ const ImageToVideo = () => {
|
||||
clearJobTokens: () => {},
|
||||
};
|
||||
|
||||
const modelList: PopoverItem[] = [
|
||||
{
|
||||
label: "Kling 2.0",
|
||||
icon: <FontAwesomeIcon icon={faFilm} className="h-4 w-4" />,
|
||||
selected: selectedModel === "Kling 2.0",
|
||||
description: "High quality model",
|
||||
badges: [{ label: "2 min.", icon: <FontAwesomeIcon icon={faClock} /> }],
|
||||
},
|
||||
{
|
||||
label: "Runway",
|
||||
icon: <FontAwesomeIcon icon={faFilm} className="h-4 w-4" />,
|
||||
selected: selectedModel === "Runway",
|
||||
description: "Fast and high-quality model",
|
||||
badges: [{ label: "2 min.", icon: <FontAwesomeIcon icon={faClock} /> }],
|
||||
},
|
||||
];
|
||||
|
||||
const handleModelSelect = (item: PopoverItem) => {
|
||||
setSelectedModel(item.label);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex h-[calc(100vh-56px)] w-full rounded-lg bg-ui-background"
|
||||
className="flex h-[calc(100vh-56px)] w-full rounded-lg bg-[#121212]"
|
||||
>
|
||||
<div className="h-full w-full p-4">
|
||||
<div className="flex h-full w-full flex-col items-center justify-center rounded-md bg-gray-800">
|
||||
<div className="flex h-full w-full flex-col items-center justify-center rounded-md pb-12">
|
||||
<div className="relative z-20 mb-12 flex flex-col items-center justify-center text-center drop-shadow-xl">
|
||||
<span className="text-7xl font-bold">Generate Video</span>
|
||||
<span className="pt-2 text-xl opacity-80">
|
||||
Add an image, prompt, then generate
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<PromptBoxVideo
|
||||
useJobContext={() => {
|
||||
// TODO: Implement job context logic
|
||||
@@ -50,6 +77,21 @@ const ImageToVideo = () => {
|
||||
console.log("Enqueue pressed");
|
||||
}}
|
||||
/>
|
||||
|
||||
<BackgroundGallery />
|
||||
|
||||
<div className="absolute bottom-6 left-6 z-20 flex items-center gap-2">
|
||||
<PopoverMenu
|
||||
items={modelList}
|
||||
onSelect={handleModelSelect}
|
||||
mode="hoverSelect"
|
||||
panelTitle="Select Model"
|
||||
panelClassName="min-w-[280px]"
|
||||
buttonClassName="bg-transparent p-0 text-lg hover:bg-transparent text-white/80 hover:text-white"
|
||||
showIconsInList
|
||||
triggerLabel="Model"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { ReactNode, useRef, useState, useEffect } from "react";
|
||||
import {
|
||||
Popover,
|
||||
Transition,
|
||||
PopoverButton,
|
||||
PopoverPanel,
|
||||
} from "@headlessui/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faChevronUp, faCircleCheck } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
export interface PopoverItem {
|
||||
label: string;
|
||||
@@ -9,6 +16,11 @@ export interface PopoverItem {
|
||||
icon?: ReactNode;
|
||||
action?: string;
|
||||
disabled?: boolean;
|
||||
description?: string;
|
||||
badges?: Array<{
|
||||
label: string;
|
||||
icon?: ReactNode;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface PopoverMenuProps {
|
||||
@@ -19,8 +31,8 @@ interface PopoverMenuProps {
|
||||
showAddButton?: boolean;
|
||||
disableAddButton?: boolean;
|
||||
showIconsInList?: boolean;
|
||||
mode?: "default" | "toggle" | "button";
|
||||
triggerLabel?: string;
|
||||
mode?: "default" | "toggle" | "button" | "hoverSelect";
|
||||
triggerLabel?: string | ReactNode;
|
||||
children?: ReactNode;
|
||||
buttonClassName?: string;
|
||||
panelClassName?: string;
|
||||
@@ -81,12 +93,97 @@ export const PopoverMenu = ({
|
||||
end: "right-0",
|
||||
};
|
||||
|
||||
// Track hover state and refs
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const popoverButtonRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
// Cleanup timeout on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (hoverTimeoutRef.current) {
|
||||
clearTimeout(hoverTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleButtonMouseEnter = (open: boolean, openFn: () => void) => {
|
||||
if (mode !== "hoverSelect") return;
|
||||
|
||||
setIsHovering(true);
|
||||
if (hoverTimeoutRef.current) {
|
||||
clearTimeout(hoverTimeoutRef.current);
|
||||
hoverTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
if (!open) {
|
||||
hoverTimeoutRef.current = setTimeout(() => {
|
||||
openFn();
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonMouseLeave = (closeFn: () => void) => {
|
||||
if (mode !== "hoverSelect") return;
|
||||
|
||||
setIsHovering(false);
|
||||
|
||||
if (hoverTimeoutRef.current) {
|
||||
clearTimeout(hoverTimeoutRef.current);
|
||||
}
|
||||
|
||||
hoverTimeoutRef.current = setTimeout(() => {
|
||||
if (!isHovering) {
|
||||
closeFn();
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handlePanelMouseEnter = () => {
|
||||
if (mode !== "hoverSelect") return;
|
||||
setIsHovering(true);
|
||||
if (hoverTimeoutRef.current) {
|
||||
clearTimeout(hoverTimeoutRef.current);
|
||||
hoverTimeoutRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handlePanelMouseLeave = (closeFn: () => void) => {
|
||||
if (mode !== "hoverSelect") return;
|
||||
setIsHovering(false);
|
||||
|
||||
if (hoverTimeoutRef.current) {
|
||||
clearTimeout(hoverTimeoutRef.current);
|
||||
}
|
||||
|
||||
hoverTimeoutRef.current = setTimeout(() => {
|
||||
closeFn();
|
||||
}, 300);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
{({ close }) => (
|
||||
<>
|
||||
<Popover.Button className={className}>
|
||||
<>
|
||||
<div className="relative inline-block">
|
||||
<Popover>
|
||||
{({ open, close }) => (
|
||||
<>
|
||||
<PopoverButton
|
||||
className={className}
|
||||
onMouseEnter={() =>
|
||||
handleButtonMouseEnter(open, () => {
|
||||
if (popoverButtonRef.current && !open) {
|
||||
popoverButtonRef.current.click();
|
||||
}
|
||||
})
|
||||
}
|
||||
onMouseLeave={() => handleButtonMouseLeave(close)}
|
||||
onClick={(e) => {
|
||||
if (mode === "hoverSelect" && open) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
ref={popoverButtonRef}
|
||||
>
|
||||
{triggerIcon}
|
||||
{mode === "toggle" && selectedItem ? (
|
||||
<span className="truncate">{selectedItem.label}</span>
|
||||
@@ -94,17 +191,19 @@ export const PopoverMenu = ({
|
||||
{mode === "default" && triggerLabel ? (
|
||||
<span className="truncate">{triggerLabel}</span>
|
||||
) : null}
|
||||
</>
|
||||
</Popover.Button>
|
||||
{mode === "hoverSelect" && selectedItem ? (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="opacity-70">{triggerLabel}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate">{selectedItem.label}</span>
|
||||
<FontAwesomeIcon icon={faChevronUp} className="text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</PopoverButton>
|
||||
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute transform-gpu",
|
||||
positionClasses[position],
|
||||
alignClasses[align]
|
||||
)}
|
||||
>
|
||||
<Transition
|
||||
show={open}
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom={
|
||||
position === "bottom"
|
||||
@@ -120,18 +219,23 @@ export const PopoverMenu = ({
|
||||
: "-translate-y-2 opacity-0"
|
||||
}
|
||||
>
|
||||
<Popover.Panel
|
||||
<PopoverPanel
|
||||
static
|
||||
className={twMerge(
|
||||
"absolute transform-gpu z-10",
|
||||
positionClasses[position],
|
||||
alignClasses[align],
|
||||
position === "bottom" ? "origin-top" : "origin-bottom"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"z-10 min-w-48 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
|
||||
position === "top" && "mb-2",
|
||||
position === "bottom" && "mt-2",
|
||||
"z-10 w-56 mt-2 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
|
||||
position === "top" ? "mb-2" : "mt-2",
|
||||
panelClassName
|
||||
)}
|
||||
onMouseEnter={handlePanelMouseEnter}
|
||||
onMouseLeave={() => handlePanelMouseLeave(close)}
|
||||
>
|
||||
{panelTitle && (
|
||||
<div className="mb-2 mt-0.5 flex justify-between px-1.5 text-sm font-normal text-white opacity-70">
|
||||
@@ -151,6 +255,91 @@ export const PopoverMenu = ({
|
||||
)}
|
||||
{mode === "default" && children ? (
|
||||
<div className="text-sm text-white">{children}</div>
|
||||
) : mode === "hoverSelect" ? (
|
||||
<div className="flex flex-col gap-0 text-sm text-white">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => {
|
||||
if (!item.disabled) {
|
||||
handleItemClick(item, close);
|
||||
}
|
||||
}}
|
||||
className={twMerge(
|
||||
"group flex cursor-pointer items-start gap-2 rounded-lg px-2 py-2 transition-all",
|
||||
item.selected
|
||||
? "bg-black/40 border-l-4 border-primary"
|
||||
: "hover:bg-black/20",
|
||||
item.disabled
|
||||
? "!cursor-not-allowed opacity-50"
|
||||
: ""
|
||||
)}
|
||||
style={{ minHeight: 48 }}
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<div className="flex items-start gap-2 grow">
|
||||
{showIconsInList && (
|
||||
<span className="mt-1 flex h-5 w-5 items-center justify-center text-lg text-white/80">
|
||||
{item.icon}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex flex-1 flex-col min-w-0">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<span className="truncate font-semibold text-white text-base">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{item.description && (
|
||||
<div className="truncate text-xs text-white/60 mt-0.5">
|
||||
{item.description}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row gap-1 flex-wrap mt-1.5">
|
||||
{item.badges &&
|
||||
Array.isArray(item.badges) &&
|
||||
item.badges.map((badge, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-1 min-w-0"
|
||||
>
|
||||
<span className="inline-flex items-center rounded bg-black/40 px-1.5 py-0.5 text-xs font-medium text-white gap-1">
|
||||
{badge?.icon && (
|
||||
<span>{badge.icon}</span>
|
||||
)}
|
||||
{badge?.label || ""}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{item.selected && (
|
||||
<span className="text-primary text-xl font-bold bg-white rounded-full p-0 h-4 w-4 flex items-center justify-center mr-1">
|
||||
<FontAwesomeIcon icon={faCircleCheck} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{showAddButton && onAdd && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={twMerge(
|
||||
"w-full mb-0.5 mt-2 border-none py-1",
|
||||
disableAddButton
|
||||
? "cursor-not-allowed bg-[#7B7B84]/50 opacity-50"
|
||||
: "bg-[#7B7B84] hover:bg-[#8c8c96]"
|
||||
)}
|
||||
onClick={onAdd}
|
||||
disabled={disableAddButton}
|
||||
>
|
||||
+ Add
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-0 text-sm text-white">
|
||||
{items.map((item, index) => (
|
||||
@@ -203,11 +392,11 @@ export const PopoverMenu = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</PopoverPanel>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -445,7 +445,7 @@ export const PromptBoxVideo = ({
|
||||
>
|
||||
{content}
|
||||
</Modal>
|
||||
<div className="absolute bottom-4 left-1/2 flex -translate-x-1/2 flex-col gap-3">
|
||||
<div className="relative z-20 flex flex-col gap-3">
|
||||
{(referenceImages.length > 0 || uploadingImages.length > 0) && (
|
||||
<div className="flex w-full gap-2">
|
||||
{referenceImages.map((image) => (
|
||||
@@ -523,7 +523,7 @@ export const PromptBoxVideo = ({
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Choose the image from assets (+) and describe your what you want to happen in the video..."
|
||||
placeholder="Choose the image from assets (+) and describe what you want to happen in the video..."
|
||||
className="text-md mb-2 max-h-[5.5em] flex-1 resize-none overflow-y-auto rounded bg-transparent pb-2 pr-2 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
|
||||