mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-31 07:45:55 -04:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b6890fa12 | ||
|
|
f456795fb1 | ||
|
|
63d41f7d2c |
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user