BennyKok's picture
feat: add ws demo
182af0c
"use client";
import { LoadingIcon } from "@/components/LoadingIcon";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
checkStatus,
generate,
generate_img,
generate_img_with_controlnet,
getUploadUrl,
} from "@/server/generate";
import { useEffect, useState } from "react";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ImageGenerationResult } from "@/components/ImageGenerationResult";
import { WebsocketDemo } from "@/components/WebsocketDemo";
export default function Page() {
return (
<main className="flex min-h-screen flex-col items-center justify-between mt-10">
<Tabs defaultValue="ws" className="w-full max-w-[600px]">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="ws">Realtime</TabsTrigger>
<TabsTrigger value="txt2img">txt2img</TabsTrigger>
<TabsTrigger value="img2img">img2img</TabsTrigger>
<TabsTrigger value="controlpose">Controlpose</TabsTrigger>
</TabsList>
<TabsContent value="ws">
<WebsocketDemo />
</TabsContent>
<TabsContent value="txt2img">
<Txt2img />
</TabsContent>
<TabsContent value="img2img">
<Img2img />
</TabsContent>
<TabsContent value="controlpose">
<OpenposeToImage />
</TabsContent>
</Tabs>
</main>
);
}
function Txt2img() {
const [prompt, setPrompt] = useState("");
const [loading, setLoading] = useState(false);
const [runIds, setRunIds] = useState<string[]>([]);
return (
<Card className="w-full max-w-[600px]">
<CardHeader>
Comfy Deploy - Vector Line Art Tool
<div className="text-xs text-foreground opacity-50">
Lora -{" "}
<a href="https://civitai.com/models/256144/stick-line-vector-illustration">
stick-line-vector-illustration
</a>
</div>
</CardHeader>
<CardContent>
<form
className="grid w-full items-center gap-1.5"
onSubmit={(e) => {
e.preventDefault();
if (loading) return;
setLoading(true);
const promises = Array(4).fill(null).map(() => {
return generate(prompt)
.then((res) => {
if (res) {
setRunIds((ids) => [...ids, res.run_id]);
}
return res;
})
.catch((error) => {
console.error(error);
});
});
Promise.all(promises).finally(() => {
setLoading(false);
});
}}
>
<Label htmlFor="picture">Image prompt</Label>
<Input
id="picture"
type="text"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
/>
<Button type="submit" className="flex gap-2" disabled={loading}>
Generate {loading && <LoadingIcon />}
</Button>
<div className="grid grid-cols-2 gap-4">
{runIds.map((runId, index) => (
<ImageGenerationResult key={index} runId={runId} />
))}
</div>
</form>
</CardContent>
</Card>
);
}
function Img2img() {
const [prompt, setPrompt] = useState<File>();
const [image, setImage] = useState("");
const [loading, setLoading] = useState(false);
const [runId, setRunId] = useState("");
const [status, setStatus] = useState<string>();
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return;
setPrompt(e.target.files[0]);
};
// Polling in frontend to check for the
useEffect(() => {
if (!runId) return;
const interval = setInterval(() => {
checkStatus(runId).then((res) => {
if (res) setStatus(res.status);
if (res && res.status === "success") {
console.log(res.outputs[0]?.data);
setImage(res.outputs[0]?.data?.images[0].url);
setLoading(false);
clearInterval(interval);
}
});
}, 2000);
return () => clearInterval(interval);
}, [runId]);
return (
<Card className="w-full max-w-[600px]">
<CardHeader>Comfy Deploy - Scribble to Anime Girl</CardHeader>
<CardContent>
<form
className="grid w-full items-center gap-1.5"
onSubmit={(e) => {
e.preventDefault();
if (loading) return;
if (!prompt) return;
setImage("");
setStatus("getting url for upload");
console.log(prompt?.type, prompt?.size);
getUploadUrl(prompt?.type, prompt?.size).then((res) => {
if (!res) return;
setStatus("uploading input");
console.log(res);
fetch(res.upload_url, {
method: "PUT",
body: prompt,
headers: {
"Content-Type": prompt.type,
"x-amz-acl": "public-read",
"Content-Length": prompt.size.toString(),
},
}).then((_res) => {
if (_res.ok) {
setStatus("uploaded input");
setLoading(true);
generate_img(res.download_url).then((res) => {
console.log(res);
if (!res) {
setStatus("error");
setLoading(false);
return;
}
setRunId(res.run_id);
});
setStatus("preparing");
}
});
});
}}
>
<Label htmlFor="picture">Image prompt</Label>
<Input id="picture" type="file" onChange={handleFileChange} />
<Button type="submit" className="flex gap-2" disabled={loading}>
Generate {loading && <LoadingIcon />}
</Button>
{runId && <ImageGenerationResult key={runId} runId={runId} className="aspect-square"/>}
</form>
</CardContent>
</Card>
);
}
const poses = {
arms_on_hips: {
url: "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(1).png",
name: "Arms on Hips",
},
waving: {
url: "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(2).png",
name: "Waving",
},
legs_together_sideways: {
url: "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(3).png",
name: "Legs together, body at an angle",
},
excited_jump: {
url: "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(4).png",
name: "excited jump",
},
pointing_to_the_stars: {
url: "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(5).png",
name: "Pointing to the stars",
},
};
function OpenposeToImage() {
const [prompt, setPrompt] = useState("");
const [poseImageUrl, setPoseImageUrl] = useState(
"https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(1).png",
);
const [poseLoading, setPoseLoading] = useState(false);
const [image, setImage] = useState("");
const [loading, setLoading] = useState(false);
const [runId, setRunId] = useState("");
const [status, setStatus] = useState<string>();
const handleSelectChange = (value: keyof typeof poses) => {
setPoseImageUrl(poses[value].url); // Update image based on selection
};
// Polling in frontend to check for the
useEffect(() => {
if (!runId) return;
const interval = setInterval(() => {
checkStatus(runId).then((res) => {
if (res) setStatus(res.status);
if (res && res.status === "success") {
console.log(res.outputs[0]?.data);
setImage(res.outputs[0]?.data?.images[0].url);
setLoading(false);
clearInterval(interval);
}
});
}, 2000);
return () => clearInterval(interval);
}, [runId]);
return (
<Card className="w-full max-w-[600px]">
<CardHeader>
Comfy Deploy - Pose Creator Tool
<div className="text-xs text-foreground opacity-50">
OpenPose -{" "}
<a href="https://civitai.com/models/13647/super-pose-book-vol1-controlnet">
pose book
</a>
</div>
</CardHeader>
<CardContent>
<form
className="grid w-full items-center gap-1.5"
onSubmit={(e) => {
if (loading) return;
e.preventDefault();
setLoading(true);
generate_img_with_controlnet(poseImageUrl, prompt).then((res) => {
console.log("here", res);
if (!res) {
setStatus("error");
setLoading(false);
return;
}
setRunId(res.run_id);
});
setStatus("preparing");
}}
>
<Select
defaultValue={"Arms on Hips"}
onValueChange={(value) => {
handleSelectChange(value as keyof typeof poses);
setPoseLoading(true); // Start loading when a new pose is selected
}}
>
<Label htmlFor="picture">Pose</Label>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select a Pose" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Poses</SelectLabel>
{Object.entries(poses).map(([poseName, attr]) => (
<SelectItem key={poseName} value={poseName}>
{attr.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<Label htmlFor="picture">Image prompt</Label>
<Input
id="picture"
type="text"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
/>
<Button type="submit" className="flex gap-2" disabled={loading}>
Generate {loading && <LoadingIcon />}
</Button>
<div className="grid grid-cols-2 gap-4">
<div className="w-full rounded-lg relative">
{/* Pose Image */}
{poseLoading && (
<div className="absolute top-0 left-0 w-full h-full flex items-center justify-center">
<LoadingIcon />
</div>
)}
{poseImageUrl && (
<img
className="w-full h-full object-contain"
src={poseImageUrl}
alt="Selected pose"
onLoad={() => setPoseLoading(false)}
></img>
)}
</div>
{/* <Separator
orientation="vertical"
className="border-gray-200"
decorative
/> */}
<div className="w-full h-full">
{runId && <ImageGenerationResult key={runId} runId={runId} className="aspect-[768/1152]"/>}
</div>
</div>
</form>
</CardContent>
</Card>
);
}