Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 58 additions & 28 deletions src/components/onboarding/workflow-import.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ import { api } from "@/lib/api";
import { cn } from "@/lib/utils";
import { comfyui_hash } from "@/utils/comfydeploy-hash";
import { defaultWorkflowTemplates } from "@/utils/default-workflow";
import { useNavigate, useSearch } from "@tanstack/react-router";
import { CheckCircle2, Circle, CircleCheckBig } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { useNavigate } from "@tanstack/react-router";
import { CheckCircle2, Circle, CircleCheckBig, Lightbulb } from "lucide-react";
import { useQueryState } from "nuqs";
import { useCallback, useEffect, useRef, useState } from "react";
import { toast } from "sonner";
import { FileURLRender } from "../workflows/OutputRender";

// Add these interfaces
export interface StepValidation {
Expand Down Expand Up @@ -647,12 +647,37 @@ function DefaultOption({
);

if (selectedTemplate && validation.importOption === "default") {
setValidation({
const updatedValidation = {
...validation,
workflowJson: selectedTemplate.workflowJson,
workflowApi: selectedTemplate.workflowApi,
importJson: "",
});
hasEnvironment: selectedTemplate.hasEnvironment || false,
};

// If the template has environment data, include those properties
if (selectedTemplate.hasEnvironment) {
try {
const workflowData = JSON.parse(selectedTemplate.workflowJson);
const environment = workflowData.environment;

if (environment) {
Object.assign(updatedValidation, {
docker_command_steps: environment.docker_command_steps,
gpuType: environment.gpu,
comfyUiHash: environment.comfyui_version,
install_custom_node_with_gpu:
environment.install_custom_node_with_gpu,
base_docker_image: environment.base_docker_image,
python_version: environment.python_version,
});
}
} catch (error) {
console.error("Error parsing workflow JSON:", error);
}
}

setValidation(updatedValidation);
}
}, [workflowSelected, validation.importOption]);

Expand All @@ -668,40 +693,45 @@ function DefaultOption({
Select a workflow as your starting point.{" "}
</span>

<div className="mt-4 grid grid-cols-3 gap-4">
<div className="mt-4 flex flex-row gap-4">
{defaultWorkflowTemplates.map((template, index) => (
<button
key={template.workflowId}
type="button"
className={cn(
"w-full rounded-lg border p-4 text-left transition-all",
"group relative h-[350px] w-full overflow-hidden rounded-lg border text-left transition-all duration-500 ease-in-out",
workflowSelected === template.workflowId
? "border-2 border-gray-500 ring-gray-500 ring-offset-2"
: "border-gray-200 hover:border-gray-300",
? "w-1/2 opacity-100 shadow-lg"
: "w-1/4 opacity-70 grayscale hover:grayscale-0",
)}
onClick={() => setWorkflowSelected(template.workflowId)}
aria-pressed={workflowSelected === template.workflowId}
>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-2">
{workflowSelected === template.workflowId ? (
<CircleCheckBig className="h-4 w-4" />
) : (
<Circle className="h-4 w-4" />
<FileURLRender
url={template.workflowImageUrl}
imgClasses="h-[350px] max-w-full w-full object-cover absolute inset-0 group-hover:scale-105 transition-all duration-500 pointer-events-none"
/>
<div className="absolute right-0 bottom-0 left-0 flex flex-col gap-2 bg-gradient-to-t from-background/95 via-background/80 to-transparent p-4">
<div className="flex flex-row items-center justify-between">
<div className="flex items-center gap-2">
{workflowSelected === template.workflowId ? (
<CircleCheckBig className="h-4 w-4" />
) : (
<Circle className="h-4 w-4" />
)}
<h3 className="font-medium text-shadow">
{template.workflowName}
</h3>
</div>
{template.hasEnvironment && (
<Badge variant="yellow" className="whitespace-nowrap">
<Lightbulb className="h-3 w-3" />
With Preset
</Badge>
)}
<h3 className="font-medium">{template.workflowName}</h3>
</div>

<div className="relative aspect-video w-full overflow-hidden rounded-md">
<div className="absolute inset-0 bg-gradient-to-r from-background to-15% to-transparent" />
<img
src={template.workflowImageUrl}
className="h-full w-full object-cover"
alt={`${template.workflowName} example`}
/>
</div>

<p className="text-sm text-muted-foreground">
<p className="line-clamp-1 text-muted-foreground text-sm backdrop-blur-[2px]">
{template.workflowDescription}
</p>
</div>
Expand Down Expand Up @@ -764,8 +794,8 @@ function ImportOptions({

const json = JSON.parse(text);

var environment = json.environment;
var workflowAPIJson = json.workflow_api;
const environment = json.environment;
const workflowAPIJson = json.workflow_api;

if (!environment) {
setValidation((prev) => ({
Expand Down
2 changes: 2 additions & 0 deletions src/components/run/SharePageComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ function RunDisplay({ runId }: { runId?: string }) {
imgClasses="max-h-[60vh] object-contain shadow-md max-w-full"
lazyLoading={false}
isMainView={true}
canDownload={true}
/>
</div>
) : (
Expand All @@ -620,6 +621,7 @@ function RunDisplay({ runId }: { runId?: string }) {
columns={totalUrlCount > 4 ? 3 : 2}
displayCount={totalUrlCount > 9 ? 9 : totalUrlCount}
isMainView={totalUrlCount === 1}
canDownload={true}
/>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/user-filter-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export function UserFilterSelect({ onFilterChange }: UserFilterSelectProps) {
{selectedUsers.length}
</Badge>
) : (
"Filter by user"
"Filter"
)}
</Button>
</DropdownMenuTrigger>
Expand Down
50 changes: 24 additions & 26 deletions src/components/workflow-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,32 +180,30 @@ export function WorkflowList() {
</Button>
</div>

<AdminAndMember>
<Tooltip>
<TooltipTrigger>
{sub && (
<Badge
className={cn(
sub?.features.workflowLimited
? "border-gray-400 text-gray-500"
: "",
)}
>
<div className="flex items-center gap-2 px-2 text-xs">
{sub?.features.currentWorkflowCount}/
{sub?.features.workflowLimit}
</div>
</Badge>
)}
</TooltipTrigger>
<TooltipContent>
<p>
Current workflows: {sub?.features.currentWorkflowCount} / Max:{" "}
{sub?.features.workflowLimit}
</p>
</TooltipContent>
</Tooltip>
</AdminAndMember>
<Tooltip>
<TooltipTrigger>
{sub && (
<Badge
className={cn(
sub?.features.workflowLimited
? "border-gray-400 text-gray-500"
: "",
)}
>
<div className="flex items-center gap-2 px-2 text-xs">
{sub?.features.currentWorkflowCount}/
{sub?.features.workflowLimit}
</div>
</Badge>
)}
</TooltipTrigger>
<TooltipContent>
<p>
Current workflows: {sub?.features.currentWorkflowCount} / Max:{" "}
{sub?.features.workflowLimit}
</p>
</TooltipContent>
</Tooltip>
</div>
</div>
<ScrollArea className="fab-workflow-list flex-grow" ref={parentRef}>
Expand Down
79 changes: 25 additions & 54 deletions src/components/workflows/OutputRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type fileURLRenderProps = {
isMainView?: boolean;
canFullScreen?: boolean;
isSmallView?: boolean;
canDownload?: boolean;
};

function _FileURLRender({
Expand Down Expand Up @@ -357,7 +358,27 @@ export function FileURLRender(props: fileURLRenderProps) {
</Dialog>
</>
) : (
<_FileURLRender {...props} />
<div className={cn("group !shadow-none relative", props.imgClasses)}>
<_FileURLRender {...props} />
{props.canDownload && (
<div className="absolute top-2 right-2">
<Button
size="icon"
className="opacity-0 shadow-md transition-opacity duration-300 group-hover:opacity-100"
hideLoading
onClick={async (e) => {
e.stopPropagation();
await downloadImage({
url: props.url,
fileName: props.url.split("/").pop(),
});
}}
>
<Download className="h-4 w-4 " />
</Button>
</div>
)}
</div>
)}
</ErrorBoundary>
);
Expand Down Expand Up @@ -390,8 +411,6 @@ function FileURLRenderMulti({
columns?: number;
isMainView?: boolean;
}) {
const [openOnIndex, setOpenOnIndex] = useState<number | null>(null);

if (!canExpandToView) {
if (columns > 1 && urls.length > 1) {
return (
Expand All @@ -402,6 +421,7 @@ function FileURLRenderMulti({
url={url.url}
imgClasses={imgClasses}
isMainView={isMainView}
canDownload={canDownload}
/>
))}
</div>
Expand All @@ -416,6 +436,7 @@ function FileURLRenderMulti({
url={url.url}
imgClasses={imgClasses}
isMainView={isMainView}
canDownload={canDownload}
/>
))}
</>
Expand All @@ -425,47 +446,6 @@ function FileURLRenderMulti({
// Render the image list directly instead of using a nested component
return (
<>
<Dialog
open={openOnIndex !== null}
onOpenChange={() => setOpenOnIndex(null)}
>
<DialogContent className="max-h-fit max-w-fit">
<DialogHeader>
<DialogTitle />
</DialogHeader>
{urls.length === 1 && (
<FileURLRender
url={urls[0].url}
imgClasses="max-w-full rounded-[8px] max-h-[80vh]"
lazyLoading={lazyLoading}
isMainView={isMainView}
/>
)}
{urls.length > 1 && (
<Carousel className=" mx-9" opts={{ startIndex: openOnIndex || 0 }}>
<CarouselContent>
{urls.map((image, index) => (
<CarouselItem
key={`${runId || ""}-${image.url}-${index}`}
className="flex aspect-square max-h-[80vh] max-w-full items-center justify-center"
>
<FileURLRender
url={image.url}
imgClasses="max-w-full rounded-[8px] max-h-[80vh]"
lazyLoading={lazyLoading}
/>
</CarouselItem>
))}
</CarouselContent>
<>
<CarouselPrevious />
<CarouselNext />
</>
</Carousel>
)}
</DialogContent>
</Dialog>

{columns > 1 ? (
<div className={cn("grid grid-cols-1 gap-2", `grid-cols-${columns}`)}>
{urls.map((urlImage, i) => {
Expand All @@ -475,9 +455,6 @@ function FileURLRenderMulti({
urlImage={urlImage}
imgClasses={imgClasses}
lazyLoading={lazyLoading}
onClick={() => {
setOpenOnIndex(i);
}}
canDownload={canDownload}
/>
);
Expand All @@ -491,9 +468,6 @@ function FileURLRenderMulti({
urlImage={urlImage}
imgClasses={imgClasses}
lazyLoading={lazyLoading}
onClick={() => {
setOpenOnIndex(i);
}}
canDownload={canDownload}
/>
))}
Expand All @@ -507,13 +481,11 @@ function MediaDisplay({
urlImage,
imgClasses,
lazyLoading,
onClick,
canDownload,
canDownload = false,
}: {
urlImage: any;
imgClasses: string;
lazyLoading: boolean;
onClick: () => void;
canDownload: boolean;
}) {
const [moveDialogOpen, setMoveDialogOpen] = useState(false);
Expand Down Expand Up @@ -617,7 +589,6 @@ function MediaDisplay({
fileName: urlImage.filename,
});
}}
disabled={!canDownload}
>
<div className="flex w-full items-center justify-between">
Download <Download className="h-3.5 w-3.5" />
Expand Down
1 change: 0 additions & 1 deletion src/components/workflows/WorkflowComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ export function RunDetails(props: {
run={run as any}
imgClasses="max-w-[230px] w-full h-[230px] object-cover object-center rounded-[8px]"
canExpandToView={true}
canDownload={true}
columns={2}
/>
</ScrollArea>
Expand Down
1 change: 0 additions & 1 deletion src/components/workspace/workspace-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import {
} from "../ui/select";
import { Progress } from "../ui/progress";
import { toast } from "sonner";
import { defaultWorkflowTemplates } from "@/utils/default-workflow";
import { sendEventToCD, sendWorkflow } from "./sendEventToCD";
import { Label } from "../ui/label";
import Cookies from "js-cookie";
Expand Down
Loading