Compare commits

..
15 changed files with 246 additions and 45 deletions
@@ -28,7 +28,7 @@ export const Button = ({
function getVariantClassNames(variant: string) {
switch (variant) {
case "secondary": {
return "bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
return "bg-brand-secondary hover:bg-brand-secondary-700 text-white focus-visible:outline-brand-secondary";
}
case "action": {
return "bg-action hover:bg-action-900 text-white focus-visible:outline-action";
@@ -25,6 +25,8 @@ interface PopoverMenuProps {
panelClassName?: string;
onAction?: (action: string) => void;
panelTitle?: string;
position?: "top" | "bottom";
align?: "start" | "center" | "end";
}
export const PopoverMenu = ({
@@ -38,8 +40,11 @@ export const PopoverMenu = ({
triggerLabel,
children,
buttonClassName,
panelClassName,
onAction,
panelTitle,
position = "top",
align = "start",
}: PopoverMenuProps) => {
const selectedItem = items.find((item) => item.selected);
@@ -61,6 +66,17 @@ export const PopoverMenu = ({
buttonClassName,
);
const positionClasses = {
top: "bottom-full",
bottom: "top-full",
};
const alignClasses = {
start: "left-0",
center: "left-1/2 -translate-x-1/2",
end: "right-0",
};
return (
<Popover className="relative">
{({ close }) => (
@@ -71,19 +87,40 @@ export const PopoverMenu = ({
{mode === "default" && triggerLabel ? triggerLabel : null}
</Popover.Button>
<div className="absolute bottom-full left-0 transform-gpu">
<div
className={twMerge(
"absolute transform-gpu",
positionClasses[position],
alignClasses[align],
)}
>
<Transition
enter="transition duration-100 ease-out"
enterFrom="translate-y-2 opacity-0"
enterFrom={
position === "bottom"
? "translate-y-2 opacity-0"
: "-translate-y-2 opacity-0"
}
enterTo="translate-y-0 opacity-100"
leave="transition duration-100 ease-in"
leaveFrom="translate-y-0 opacity-100"
leaveTo="translate-y-2 opacity-0"
leaveTo={
position === "bottom"
? "translate-y-2 opacity-0"
: "-translate-y-2 opacity-0"
}
>
<Popover.Panel className="origin-bottom">
<Popover.Panel
className={twMerge(
position === "bottom" ? "origin-top" : "origin-bottom",
)}
>
<div
className={twMerge(
"z-10 mb-2 min-w-48 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
"z-10 min-w-48 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
position === "top" && "mb-2",
position === "bottom" && "mt-2",
panelClassName,
)}
>
{panelTitle && (
@@ -0,0 +1,34 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { Button } from "~/components";
import { twMerge } from "tailwind-merge";
interface ToggleButtonProps {
isActive: boolean;
icon?: IconDefinition;
activeIcon?: IconDefinition;
onClick: () => void;
className?: string;
}
export const ToggleButton = ({
isActive,
icon,
activeIcon,
onClick,
className,
}: ToggleButtonProps) => {
return (
<Button
className={twMerge(
"flex h-9 w-9 items-center border-2 border-transparent text-sm text-white backdrop-blur-lg transition-all",
isActive
? "border-white/20 bg-brand-primary/40 hover:border-white/30 hover:bg-brand-primary/40"
: "bg-[#5F5F68]/60 hover:bg-[#5F5F68]/90",
className,
)}
variant="secondary"
icon={isActive && activeIcon ? activeIcon : icon}
onClick={onClick}
/>
);
};
@@ -0,0 +1 @@
export * from "./ToggleButton";
@@ -7,6 +7,7 @@ interface TooltipProps {
content: React.ReactNode;
position: "top" | "bottom" | "left" | "right";
className?: string;
delay?: number;
}
export const Tooltip = ({
@@ -14,6 +15,7 @@ export const Tooltip = ({
content,
position,
className,
delay = 300,
}: TooltipProps) => {
const [isShowing, setIsShowing] = useState(false);
const triggerRef = useRef<HTMLDivElement>(null);
@@ -63,7 +65,7 @@ export const Tooltip = ({
{children}
<Transition
show={isShowing}
enter="transition ease-out duration-200 delay-300"
enter={`transition ease-out duration-200 delay-${delay}`}
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition ease-in duration-150"
@@ -74,7 +76,7 @@ export const Tooltip = ({
ref={tooltipRef}
style={getStyleForPosition()}
className={twMerge(
"pointer-events-none absolute z-10 w-max rounded-lg bg-ui-controls px-2.5 py-1.5 text-sm font-medium text-white shadow-xl",
"pointer-events-none absolute z-10 w-max rounded-lg bg-[#5F5F68] px-2.5 py-1.5 text-sm font-medium text-white shadow-xl",
className ? className : "",
)}
>
@@ -24,6 +24,7 @@ export * from "./Select";
export * from "./Slider";
export * from "./Tabs";
export * from "./Textarea";
export * from "./ToggleButton";
export * from "./Tooltip";
export * from "./TransitionDialogue";
export * from "./Typography";
@@ -1,10 +1,10 @@
import { useLocation, useParams } from "@remix-run/react";
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
import { ButtonLink } from "~/components";
import { faChevronLeft, faImages } from "@fortawesome/pro-solid-svg-icons";
import { Button, ButtonLink } from "~/components";
import { AuthButtons } from "./AuthButtons";
import { SceneTitleInput } from "./SceneTitleInput";
import { MyMoviesButton } from "./MyMoviesButton";
import { getCurrentLocationWithoutParams } from "~/utilities";
import { Activity } from "~/pages/PageEnigma/comps/GenerateModals/Activity";
function isEditorPath(path: string) {
if (path === "/") return true;
@@ -47,8 +47,13 @@ export const TopBar = ({ pageName }: Props) => {
<SceneTitleInput pageName={pageName} />
</div>
<div className="flex justify-end gap-2.5">
<MyMoviesButton />
<div className="flex justify-end gap-5">
<div className="flex gap-2">
<Button variant="secondary" icon={faImages} disabled>
My Library
</Button>
<Activity />
</div>
<div className="flex justify-end gap-2">
<AuthButtons />
</div>
+1
View File
@@ -13,4 +13,5 @@ export enum JobType {
TextToSpeech = "text_to_speech",
VoiceConversion = "voice_conversion",
VideoStyleTransfer = "workflow",
ImageGeneration = "image_generation",
}
@@ -0,0 +1,75 @@
import { useSignals } from "@preact/signals-react/runtime";
import { faBell, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { PopoverMenu } from "~/components/reusable/Popover/Popover";
import { CompletedCard } from "./CompletedCard";
import { InProgressCard } from "./InProgressCard";
import { H3, P, Tooltip } from "~/components";
import { activeImageGenerationJobs, userMovies } from "~/signals";
export function Activity() {
useSignals();
return (
<Tooltip content="Activity" position="bottom">
<PopoverMenu
mode="default"
buttonClassName="h-[38px] w-[38px] !p-0"
panelClassName="w-[360px] p-2"
position="bottom"
align="end"
triggerIcon={
<div>
<FontAwesomeIcon icon={faBell} />
{activeImageGenerationJobs.value &&
activeImageGenerationJobs.value.length > 0 && (
<div className="bg-blue-500 absolute -right-1 -top-1 h-3 w-3 animate-pulse rounded-full" />
)}
</div>
}
>
<div className="max-h-[480px] overflow-y-auto">
{/* In progress */}
{activeImageGenerationJobs.value &&
activeImageGenerationJobs.value.length > 0 && (
<div>
{activeImageGenerationJobs.value.map((movieJob) => (
<InProgressCard key={movieJob.job_token} movie={movieJob} />
))}
</div>
)}
{!userMovies.value && !activeImageGenerationJobs.value ? (
<div className="flex h-48 w-full flex-col justify-center gap-4 p-4 text-center align-middle">
<FontAwesomeIcon
icon={faSpinnerThird}
spin
size="2x"
className="text-gray-400"
/>
<H3 className="text-gray-300">Retrieving Activities</H3>
</div>
) : userMovies.value &&
userMovies.value.length === 0 &&
!activeImageGenerationJobs.value ? (
<div className="flex h-48 w-full flex-col justify-center gap-4 p-4 text-center align-middle">
<H3 className="text-gray-300">No activities yet</H3>
<P className="text-gray-400">
Try generating something new from the featured scenes
</P>
</div>
) : (
<div>
{/* Completed */}
<div className="flex flex-col">
{userMovies.value?.map((movie) => (
<CompletedCard key={movie.token} movie={movie} />
))}
</div>
</div>
)}
</div>
</PopoverMenu>
</Tooltip>
);
}
@@ -19,7 +19,7 @@ export function getStyleName(typeInput: string) {
interface Props {
movie: MediaInfo;
setMovieId: (id: string) => void;
setMovieId?: (id: string) => void;
}
export function CompletedCard({ movie, setMovieId }: Props) {
@@ -37,20 +37,23 @@ export function CompletedCard({ movie, setMovieId }: Props) {
);
const styleName =
movie.maybe_style_name && getStyleName(movie.maybe_style_name);
const handleClick = () => {
setMovieId?.(movie.token);
};
return (
<div
className="flex w-full items-center justify-between px-5 py-3 text-start transition-all duration-150 hover:cursor-pointer hover:bg-brand-secondary/40"
onClick={() => {
setMovieId(movie.token);
}}
className="flex w-full items-center justify-between rounded-lg p-2 text-start transition-all duration-150 hover:cursor-pointer hover:bg-white/10"
onClick={handleClick}
>
<div className="flex gap-4">
<div className="flex h-32 w-32 justify-center overflow-hidden rounded-lg bg-black/60">
<div className="flex aspect-square h-14 w-14 justify-center overflow-hidden rounded-lg border border-[#A9A9A9]/50 bg-black/60">
<img
src={
loadError ? "/resources/images/movie-placeholder.png" : imageUrl
}
className="h-full object-contain"
className="h-full w-full object-cover"
alt={movie.maybe_title ?? "unknown"}
crossOrigin="anonymous"
onError={() => setLoadError(true)}
@@ -60,16 +63,16 @@ export function CompletedCard({ movie, setMovieId }: Props) {
<div className="flex flex-col justify-center gap-1">
<div className="font-medium">{movie.maybe_title || "Untitled"}</div>
<div>
{styleName && (
{/* {styleName && (
<div className="text-sm text-white/60">{styleName}</div>
)}
)} */}
<div className="text-sm text-white/60">
{dayjs(movie.updated_at).format("MMM D, YYYY HH:mm:ss")}
</div>
</div>
</div>
</div>
<div className="pr-5">
{/* <div className="pr-5">
<button
onClick={(event) => {
event.preventDefault();
@@ -83,7 +86,7 @@ export function CompletedCard({ movie, setMovieId }: Props) {
<FontAwesomeIcon icon={faArrowDownToLine} className="mr-2" />
Download
</button>
</div>
</div> */}
</div>
);
}
@@ -58,19 +58,19 @@ export function InProgressCard({ movie }: Props) {
}, []);
return (
<div className="flex w-full items-center justify-between px-5 py-3 text-start transition-all duration-150">
<div className="flex w-full items-center justify-between rounded-lg p-2 text-start transition-all duration-150 hover:bg-white/10">
<div className="flex gap-4">
<div className="flex h-32 w-32 items-center justify-center rounded-lg bg-white/10">
<div className="flex aspect-square h-14 w-14 items-center justify-center overflow-hidden rounded-lg border border-[#A9A9A9]/50 bg-black/60">
{(movie.status.status === JobState.STARTED ||
movie.status.status === JobState.PENDING) && (
<FontAwesomeIcon icon={faSpinnerThird} spin size={"2x"} />
<FontAwesomeIcon icon={faSpinnerThird} spin size={"lg"} />
)}
{(movie.status.status === JobState.COMPLETE_FAILURE ||
movie.status.status === JobState.ATTEMPT_FAILED) && (
<FontAwesomeIcon icon={faCircleExclamation} size={"2x"} />
<FontAwesomeIcon icon={faCircleExclamation} size={"lg"} />
)}
</div>
<div className="flex flex-col justify-center gap-2">
<div className="flex flex-col justify-center gap-1">
<div className="font-medium">
{movie.request.maybe_model_title || "Untitled"}
</div>
@@ -94,7 +94,7 @@ export function InProgressCard({ movie }: Props) {
<Tooltip content="Cancel" position="top">
<button
onClick={() => deleteJob(movie)}
className="text-xl text-white/50 transition-all duration-150 hover:text-white/90"
className="text-[15px] font-medium text-white/50 transition-all duration-150 hover:text-white/100"
>
<FontAwesomeIcon icon={faClose} />
</button>
@@ -13,6 +13,10 @@ import {
faDownload,
faSpinnerThird,
faTimes,
faCheck,
faSpellCheck,
faMessageCheck,
faMessageXmark,
} from "@fortawesome/pro-solid-svg-icons";
import {
faRectangleWide,
@@ -23,9 +27,14 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { PopoverItem, PopoverMenu } from "~/components/reusable/Popover";
import { Button } from "~/components";
import { Button, ToggleButton, Tooltip } from "~/components";
import { CameraAspectRatio } from "~/pages/PageEnigma/enums";
import { cameraAspectRatio, disableHotkeyInput, DomLevels, enableHotkeyInput } from "~/pages/PageEnigma/signals";
import {
cameraAspectRatio,
disableHotkeyInput,
DomLevels,
enableHotkeyInput,
} from "~/pages/PageEnigma/signals";
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
@@ -45,6 +54,7 @@ export const PromptBox = () => {
const editorEngine = useContext(EngineContext);
const [prompt, setPrompt] = useState("");
const [isEnqueueing, setisEnqueueing] = useState(false);
const [useSystemPrompt, setUseSystemPrompt] = useState(true);
const [referenceImages, setReferenceImages] = useState<ReferenceImage[]>([]);
const [aspectRatioList, setAspectRatioList] = useState<PopoverItem[]>([
{
@@ -384,11 +394,11 @@ export const PromptBox = () => {
onPaste={handlePaste}
onKeyDown={handleKeyDown}
onFocus={() => {
console.log("disabling hotkeys")
console.log("disabling hotkeys");
disableHotkeyInput(DomLevels.INPUT);
}}
onBlur={() => {
console.log("enabling hotkeys")
console.log("enabling hotkeys");
enableHotkeyInput(DomLevels.INPUT);
}}
/>
@@ -420,16 +430,39 @@ export const PromptBox = () => {
mode="toggle"
panelTitle="Camera"
/>
<Tooltip
content={
useSystemPrompt
? "Use system prompt: ON"
: "Use system prompt: OFF"
}
position="top"
className="z-50"
delay={100}
>
<ToggleButton
isActive={useSystemPrompt}
icon={faMessageXmark}
activeIcon={faMessageCheck}
onClick={() => setUseSystemPrompt(!useSystemPrompt)}
/>
</Tooltip>
</div>
<div className="flex items-center gap-2">
<Button
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={handleDownloadFrame}
<Tooltip
content="Download frame"
position="top"
className="z-50"
delay={100}
>
Download frame
</Button>
<Button
className="flex h-9 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={handleDownloadFrame}
/>
</Tooltip>
<Button
className="flex items-center border-none bg-[#5F5F68]/60 px-3 text-sm text-white backdrop-blur-lg hover:bg-[#5F5F68]/90"
variant="secondary"
@@ -27,7 +27,7 @@ export const SidePanel = () => {
<>
<div
className={[
"fixed z-30 flex border-l border-[#363636] bg-ui-panel transition-all duration-100",
"fixed flex border-l border-[#363636] bg-ui-panel transition-all duration-100",
].join(" ")}
style={{
top: 64,
@@ -30,7 +30,7 @@ export const SidePanelMenu = ({
return (
<div
className={twMerge(
"fixed z-30 bg-assets-background",
"fixed bg-assets-background",
"right-0 top-[64px] w-[84px] overflow-auto border-l px-2 py-2",
sidePanelVisible.value ? "border-transparent" : "border-[#363636]",
)}
@@ -49,9 +49,9 @@ export const SidePanelMenu = ({
? "border-[#363636] bg-brand-secondary-900/60 opacity-100 hover:bg-brand-secondary-900/60"
: "opacity-60",
tab.title === TabTitles.STYLIZE &&
"bg-brand-primary font-medium opacity-90 hover:border-white/25 hover:bg-brand-primary hover:opacity-100",
"bg-brand-primary font-medium opacity-90 hover:border-white/25 hover:bg-brand-primary hover:opacity-100",
tab.title === selectedTab.title &&
tab.title === TabTitles.STYLIZE
tab.title === TabTitles.STYLIZE
? "border-white/50 opacity-100 hover:border-white/50"
: "",
])}
@@ -33,6 +33,15 @@ export const activeWorkflowJobs = computed(() => {
});
});
export const activeImageGenerationJobs = computed(() => {
if (!activeJobs.value) {
return undefined;
}
return activeJobs.value.filter((job) => {
return job.request.inference_category === JobType.ImageGeneration;
});
});
export const activeAudioJobs = computed(() => {
if (!activeJobs.value) {
return undefined;