nickkao commited on
Commit
1db49be
·
1 Parent(s): 5834327

feat: controlnet example

Browse files
.env.example CHANGED
@@ -1,4 +1,5 @@
1
  COMFY_API_URL="http://127.0.0.1:3000"
2
  COMFY_API_TOKEN=""
3
  COMFY_DEPLOYMENT_ID=""
4
- COMFY_DEPLOYMENT_ID_IMG_2_IMG=""
 
 
1
  COMFY_API_URL="http://127.0.0.1:3000"
2
  COMFY_API_TOKEN=""
3
  COMFY_DEPLOYMENT_ID=""
4
+ COMFY_DEPLOYMENT_ID_IMG_2_IMG=""
5
+ COMFY_DEPLOYMENT_ID_CONTROLNET=""
bun.lockb CHANGED
Binary files a/bun.lockb and b/bun.lockb differ
 
package.json CHANGED
@@ -11,6 +11,8 @@
11
  "dependencies": {
12
  "@hookform/resolvers": "^3.3.4",
13
  "@radix-ui/react-label": "^2.0.2",
 
 
14
  "@radix-ui/react-slot": "^1.0.2",
15
  "@radix-ui/react-tabs": "^1.0.4",
16
  "class-variance-authority": "^0.7.0",
 
11
  "dependencies": {
12
  "@hookform/resolvers": "^3.3.4",
13
  "@radix-ui/react-label": "^2.0.2",
14
+ "@radix-ui/react-select": "^2.0.0",
15
+ "@radix-ui/react-separator": "^1.0.3",
16
  "@radix-ui/react-slot": "^1.0.2",
17
  "@radix-ui/react-tabs": "^1.0.4",
18
  "class-variance-authority": "^0.7.0",
src/app/page.tsx CHANGED
@@ -10,19 +10,32 @@ import {
10
  checkStatus,
11
  generate,
12
  generate_img,
 
13
  getUploadUrl,
14
  } from "@/server/generate";
15
  import { useEffect, useState } from "react";
16
 
 
 
 
 
 
 
 
 
 
 
 
17
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
18
 
19
  export default function Page() {
20
  return (
21
  <main className="flex min-h-screen flex-col items-center justify-between mt-10">
22
- <Tabs defaultValue="txt2img" className="w-full max-w-[500px]">
23
- <TabsList className="grid w-full grid-cols-2">
24
  <TabsTrigger value="txt2img">txt2img</TabsTrigger>
25
  <TabsTrigger value="img2img">img2img</TabsTrigger>
 
26
  </TabsList>
27
  <TabsContent value="txt2img">
28
  <Txt2img />
@@ -30,6 +43,9 @@ export default function Page() {
30
  <TabsContent value="img2img">
31
  <Img2img />
32
  </TabsContent>
 
 
 
33
  </Tabs>
34
  </main>
35
  );
@@ -60,7 +76,7 @@ function Txt2img() {
60
  }, [runId]);
61
 
62
  return (
63
- <Card className="w-full max-w-[500px]">
64
  <CardHeader>
65
  Comfy Deploy - Vector Line Art Tool
66
  <div className="text-xs text-foreground opacity-50">
@@ -107,7 +123,8 @@ function Txt2img() {
107
  className="w-full h-full object-contain"
108
  src={image}
109
  alt="Generated image"
110
- ></img>
 
111
  )}
112
  {!image && status && (
113
  <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center gap-2">
@@ -152,7 +169,7 @@ function Img2img() {
152
  }, [runId]);
153
 
154
  return (
155
- <Card className="w-full max-w-[500px]">
156
  <CardHeader>Comfy Deploy - Scribble to Anime Girl</CardHeader>
157
  <CardContent>
158
  <form
@@ -215,7 +232,8 @@ function Img2img() {
215
  className="w-full h-full object-contain"
216
  src={image}
217
  alt="Generated image"
218
- ></img>
 
219
  )}
220
  {!image && status && (
221
  <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center gap-2">
@@ -229,3 +247,173 @@ function Img2img() {
229
  </Card>
230
  );
231
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  checkStatus,
11
  generate,
12
  generate_img,
13
+ generate_img_with_controlnet,
14
  getUploadUrl,
15
  } from "@/server/generate";
16
  import { useEffect, useState } from "react";
17
 
18
+ import {
19
+ Select,
20
+ SelectContent,
21
+ SelectGroup,
22
+ SelectItem,
23
+ SelectLabel,
24
+ SelectTrigger,
25
+ SelectValue,
26
+ } from "@/components/ui/select";
27
+ import { Separator } from "@/components/ui/separator";
28
+
29
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
30
 
31
  export default function Page() {
32
  return (
33
  <main className="flex min-h-screen flex-col items-center justify-between mt-10">
34
+ <Tabs defaultValue="txt2img" className="w-full max-w-[600px]">
35
+ <TabsList className="grid w-full grid-cols-3">
36
  <TabsTrigger value="txt2img">txt2img</TabsTrigger>
37
  <TabsTrigger value="img2img">img2img</TabsTrigger>
38
+ <TabsTrigger value="controlpose">Controlpose</TabsTrigger>
39
  </TabsList>
40
  <TabsContent value="txt2img">
41
  <Txt2img />
 
43
  <TabsContent value="img2img">
44
  <Img2img />
45
  </TabsContent>
46
+ <TabsContent value="controlpose">
47
+ <OpenposeToImage />
48
+ </TabsContent>
49
  </Tabs>
50
  </main>
51
  );
 
76
  }, [runId]);
77
 
78
  return (
79
+ <Card className="w-full max-w-[600px]">
80
  <CardHeader>
81
  Comfy Deploy - Vector Line Art Tool
82
  <div className="text-xs text-foreground opacity-50">
 
123
  className="w-full h-full object-contain"
124
  src={image}
125
  alt="Generated image"
126
+ >
127
+ </img>
128
  )}
129
  {!image && status && (
130
  <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center gap-2">
 
169
  }, [runId]);
170
 
171
  return (
172
+ <Card className="w-full max-w-[600px]">
173
  <CardHeader>Comfy Deploy - Scribble to Anime Girl</CardHeader>
174
  <CardContent>
175
  <form
 
232
  className="w-full h-full object-contain"
233
  src={image}
234
  alt="Generated image"
235
+ >
236
+ </img>
237
  )}
238
  {!image && status && (
239
  <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center gap-2">
 
247
  </Card>
248
  );
249
  }
250
+
251
+ const poses = {
252
+ arms_on_hips: {
253
+ url:
254
+ "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(1).png",
255
+ name: "Arms on Hips",
256
+ },
257
+ waving: {
258
+ url:
259
+ "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(2).png",
260
+ name: "Waving",
261
+ },
262
+ legs_together_sideways: {
263
+ url:
264
+ "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(3).png",
265
+ name: "Legs together, body at an angle",
266
+ },
267
+ excited_jump: {
268
+ url:
269
+ "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(4).png",
270
+ name: "excited jump",
271
+ },
272
+ pointing_to_the_stars: {
273
+ url:
274
+ "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(5).png",
275
+ name: "Pointing to the stars",
276
+ },
277
+ };
278
+
279
+ function OpenposeToImage() {
280
+ const [prompt, setPrompt] = useState("");
281
+ const [poseImageUrl, setPoseImageUrl] = useState(
282
+ "https://pub-6230db03dc3a4861a9c3e55145ceda44.r2.dev/openpose-pose%20(1).png",
283
+ );
284
+ const [poseLoading, setPoseLoading] = useState(false);
285
+ const [image, setImage] = useState("");
286
+ const [loading, setLoading] = useState(false);
287
+ const [runId, setRunId] = useState("");
288
+ const [status, setStatus] = useState<string>();
289
+
290
+ const handleSelectChange = (value: keyof typeof poses) => {
291
+ setPoseImageUrl(poses[value].url); // Update image based on selection
292
+ };
293
+
294
+ // Polling in frontend to check for the
295
+ useEffect(() => {
296
+ if (!runId) return;
297
+ const interval = setInterval(() => {
298
+ checkStatus(runId).then((res) => {
299
+ if (res) setStatus(res.status);
300
+ if (res && res.status === "success") {
301
+ console.log(res.outputs[0]?.data);
302
+ setImage(res.outputs[0]?.data?.images[0].url);
303
+ setLoading(false);
304
+ clearInterval(interval);
305
+ }
306
+ });
307
+ }, 2000);
308
+ return () => clearInterval(interval);
309
+ }, [runId]);
310
+
311
+ return (
312
+ <Card className="w-full max-w-[600px]">
313
+ <CardHeader>
314
+ Comfy Deploy - Pose Creator Tool
315
+ <div className="text-xs text-foreground opacity-50">
316
+ OpenPose -{" "}
317
+ <a href="https://civitai.com/models/13647/super-pose-book-vol1-controlnet">
318
+ pose book
319
+ </a>
320
+ </div>
321
+ </CardHeader>
322
+ <CardContent>
323
+ <form
324
+ className="grid w-full items-center gap-1.5"
325
+ onSubmit={(e) => {
326
+ if (loading) return;
327
+
328
+ e.preventDefault();
329
+ setLoading(true);
330
+ generate_img_with_controlnet(poseImageUrl, prompt).then((res) => {
331
+ console.log('here', res);
332
+ if (!res) {
333
+ setStatus("error");
334
+ setLoading(false);
335
+ return;
336
+ }
337
+ setRunId(res.run_id);
338
+ });
339
+ setStatus("preparing");
340
+ }}
341
+ >
342
+ <Select
343
+ defaultValue={"Arms on Hips"}
344
+ onValueChange={(value) => {
345
+ handleSelectChange(value as keyof typeof poses);
346
+ setPoseLoading(true); // Start loading when a new pose is selected
347
+ }}
348
+ >
349
+ <Label htmlFor="picture">Pose</Label>
350
+ <SelectTrigger className="w-[180px]">
351
+ <SelectValue placeholder="Select a Pose" />
352
+ </SelectTrigger>
353
+ <SelectContent>
354
+ <SelectGroup>
355
+ <SelectLabel>Poses</SelectLabel>
356
+ {Object.entries(poses).map(([poseName, attr]) => (
357
+ <SelectItem key={poseName} value={poseName}>
358
+ {attr.name}
359
+ </SelectItem>
360
+ ))}
361
+ </SelectGroup>
362
+ </SelectContent>
363
+ </Select>
364
+ <Label htmlFor="picture">Image prompt</Label>
365
+ <Input
366
+ id="picture"
367
+ type="text"
368
+ value={prompt}
369
+ onChange={(e) => setPrompt(e.target.value)}
370
+ />
371
+ <Button type="submit" className="flex gap-2" disabled={loading}>
372
+ Generate {loading && <LoadingIcon />}
373
+ </Button>
374
+
375
+ <div className="flex gap-4">
376
+ <div className="w-full rounded-lg relative">
377
+ {/* Pose Image */}
378
+ {poseLoading && (
379
+ <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center">
380
+ <LoadingIcon />
381
+ </div>
382
+ )}
383
+ {poseImageUrl && (
384
+ <img
385
+ className="w-full h-full object-contain"
386
+ src={poseImageUrl}
387
+ alt="Selected pose"
388
+ onLoad={() => setPoseLoading(false)}
389
+ >
390
+ </img>
391
+ )}
392
+ </div>
393
+ <Separator
394
+ orientation="vertical"
395
+ className="border-gray-200"
396
+ decorative
397
+ />
398
+ <div className="border border-gray-200 w-full square h-[400px] rounded-lg relative">
399
+ {!loading && image && (
400
+ <img
401
+ className="w-full h-full object-contain"
402
+ src={image}
403
+ alt="Generated image"
404
+ >
405
+ </img>
406
+ )}
407
+ {!image && status && (
408
+ <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center gap-2">
409
+ {status} <LoadingIcon />
410
+ </div>
411
+ )}
412
+ {loading && <Skeleton className="w-full h-full" />}
413
+ </div>
414
+ </div>
415
+ </form>
416
+ </CardContent>
417
+ </Card>
418
+ );
419
+ }
src/components/ui/select.tsx ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Select = SelectPrimitive.Root
10
+
11
+ const SelectGroup = SelectPrimitive.Group
12
+
13
+ const SelectValue = SelectPrimitive.Value
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ))
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34
+
35
+ const SelectScrollUpButton = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
38
+ >(({ className, ...props }, ref) => (
39
+ <SelectPrimitive.ScrollUpButton
40
+ ref={ref}
41
+ className={cn(
42
+ "flex cursor-default items-center justify-center py-1",
43
+ className
44
+ )}
45
+ {...props}
46
+ >
47
+ <ChevronUp className="h-4 w-4" />
48
+ </SelectPrimitive.ScrollUpButton>
49
+ ))
50
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
51
+
52
+ const SelectScrollDownButton = React.forwardRef<
53
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
54
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
55
+ >(({ className, ...props }, ref) => (
56
+ <SelectPrimitive.ScrollDownButton
57
+ ref={ref}
58
+ className={cn(
59
+ "flex cursor-default items-center justify-center py-1",
60
+ className
61
+ )}
62
+ {...props}
63
+ >
64
+ <ChevronDown className="h-4 w-4" />
65
+ </SelectPrimitive.ScrollDownButton>
66
+ ))
67
+ SelectScrollDownButton.displayName =
68
+ SelectPrimitive.ScrollDownButton.displayName
69
+
70
+ const SelectContent = React.forwardRef<
71
+ React.ElementRef<typeof SelectPrimitive.Content>,
72
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
73
+ >(({ className, children, position = "popper", ...props }, ref) => (
74
+ <SelectPrimitive.Portal>
75
+ <SelectPrimitive.Content
76
+ ref={ref}
77
+ className={cn(
78
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
79
+ position === "popper" &&
80
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
81
+ className
82
+ )}
83
+ position={position}
84
+ {...props}
85
+ >
86
+ <SelectScrollUpButton />
87
+ <SelectPrimitive.Viewport
88
+ className={cn(
89
+ "p-1",
90
+ position === "popper" &&
91
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
92
+ )}
93
+ >
94
+ {children}
95
+ </SelectPrimitive.Viewport>
96
+ <SelectScrollDownButton />
97
+ </SelectPrimitive.Content>
98
+ </SelectPrimitive.Portal>
99
+ ))
100
+ SelectContent.displayName = SelectPrimitive.Content.displayName
101
+
102
+ const SelectLabel = React.forwardRef<
103
+ React.ElementRef<typeof SelectPrimitive.Label>,
104
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
105
+ >(({ className, ...props }, ref) => (
106
+ <SelectPrimitive.Label
107
+ ref={ref}
108
+ className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
109
+ {...props}
110
+ />
111
+ ))
112
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
113
+
114
+ const SelectItem = React.forwardRef<
115
+ React.ElementRef<typeof SelectPrimitive.Item>,
116
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <SelectPrimitive.Item
119
+ ref={ref}
120
+ className={cn(
121
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ className
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127
+ <SelectPrimitive.ItemIndicator>
128
+ <Check className="h-4 w-4" />
129
+ </SelectPrimitive.ItemIndicator>
130
+ </span>
131
+
132
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
133
+ </SelectPrimitive.Item>
134
+ ))
135
+ SelectItem.displayName = SelectPrimitive.Item.displayName
136
+
137
+ const SelectSeparator = React.forwardRef<
138
+ React.ElementRef<typeof SelectPrimitive.Separator>,
139
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
140
+ >(({ className, ...props }, ref) => (
141
+ <SelectPrimitive.Separator
142
+ ref={ref}
143
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
144
+ {...props}
145
+ />
146
+ ))
147
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
148
+
149
+ export {
150
+ Select,
151
+ SelectGroup,
152
+ SelectValue,
153
+ SelectTrigger,
154
+ SelectContent,
155
+ SelectLabel,
156
+ SelectItem,
157
+ SelectSeparator,
158
+ SelectScrollUpButton,
159
+ SelectScrollDownButton,
160
+ }
src/components/ui/separator.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Separator = React.forwardRef<
9
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
11
+ >(
12
+ (
13
+ { className, orientation = "horizontal", decorative = true, ...props },
14
+ ref
15
+ ) => (
16
+ <SeparatorPrimitive.Root
17
+ ref={ref}
18
+ decorative={decorative}
19
+ orientation={orientation}
20
+ className={cn(
21
+ "shrink-0 bg-border",
22
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ )
28
+ )
29
+ Separator.displayName = SeparatorPrimitive.Root.displayName
30
+
31
+ export { Separator }
src/lib/comfy-deploy.ts CHANGED
@@ -6,11 +6,11 @@ const runTypes = z.object({
6
 
7
  const runOutputTypes = z.object({
8
  id: z.string(),
9
- status: z.enum(["success", "failed", "running", "uploading"]),
10
  outputs: z.array(
11
  z.object({
12
  data: z.any(),
13
- })
14
  ),
15
  });
16
 
@@ -18,15 +18,16 @@ const uploadFileTypes = z.object({
18
  upload_url: z.string(),
19
  file_id: z.string(),
20
  download_url: z.string(),
21
- })
22
 
23
  export class ComfyDeployClient {
24
  apiBase: string = "https://www.comfydeploy.com/api";
25
  apiToken: string;
26
 
27
  constructor({ apiBase, apiToken }: { apiBase?: string; apiToken: string }) {
28
- if (apiBase)
29
  this.apiBase = `${apiBase}/api`;
 
30
  this.apiToken = apiToken;
31
  }
32
 
@@ -47,12 +48,16 @@ export class ComfyDeployClient {
47
  deployment_id: deployment_id,
48
  inputs: inputs,
49
  }),
50
- cache: "no-store"
51
  })
52
- .then((response) => response.json())
53
- .then((json) => runTypes.parse(json))
 
 
 
 
54
  .catch((err) => {
55
- console.error(err);
56
  return null;
57
  });
58
  }
@@ -64,7 +69,7 @@ export class ComfyDeployClient {
64
  "Content-Type": "application/json",
65
  authorization: `Bearer ${this.apiToken}`,
66
  },
67
- cache: "no-store"
68
  })
69
  .then((response) => response.json())
70
  .then((json) => runOutputTypes.parse(json))
@@ -110,13 +115,13 @@ export class ComfyDeployClient {
110
  };
111
  const url = new URL(`${this.apiBase}/upload-url`);
112
  url.search = new URLSearchParams(obj).toString();
113
-
114
  return await fetch(url.href, {
115
  method: "GET",
116
  headers: {
117
  authorization: `Bearer ${this.apiToken}`,
118
  },
119
- cache: "no-store"
120
  })
121
  .then((response) => response.json())
122
  .then((json) => uploadFileTypes.parse(json))
@@ -125,4 +130,4 @@ export class ComfyDeployClient {
125
  return null;
126
  });
127
  }
128
- }
 
6
 
7
  const runOutputTypes = z.object({
8
  id: z.string(),
9
+ status: z.enum(["success", "failed", "running", "uploading", "not-started"]),
10
  outputs: z.array(
11
  z.object({
12
  data: z.any(),
13
+ }),
14
  ),
15
  });
16
 
 
18
  upload_url: z.string(),
19
  file_id: z.string(),
20
  download_url: z.string(),
21
+ });
22
 
23
  export class ComfyDeployClient {
24
  apiBase: string = "https://www.comfydeploy.com/api";
25
  apiToken: string;
26
 
27
  constructor({ apiBase, apiToken }: { apiBase?: string; apiToken: string }) {
28
+ if (apiBase) {
29
  this.apiBase = `${apiBase}/api`;
30
+ }
31
  this.apiToken = apiToken;
32
  }
33
 
 
48
  deployment_id: deployment_id,
49
  inputs: inputs,
50
  }),
51
+ cache: "no-store",
52
  })
53
+ .then((response) => {
54
+ console.log('response', response)
55
+ return response.json()})
56
+ .then((json) => {
57
+ console.log('json', json)
58
+ return runTypes.parse(json)})
59
  .catch((err) => {
60
+ console.error('err', err);
61
  return null;
62
  });
63
  }
 
69
  "Content-Type": "application/json",
70
  authorization: `Bearer ${this.apiToken}`,
71
  },
72
+ cache: "no-store",
73
  })
74
  .then((response) => response.json())
75
  .then((json) => runOutputTypes.parse(json))
 
115
  };
116
  const url = new URL(`${this.apiBase}/upload-url`);
117
  url.search = new URLSearchParams(obj).toString();
118
+
119
  return await fetch(url.href, {
120
  method: "GET",
121
  headers: {
122
  authorization: `Bearer ${this.apiToken}`,
123
  },
124
+ cache: "no-store",
125
  })
126
  .then((response) => response.json())
127
  .then((json) => uploadFileTypes.parse(json))
 
130
  return null;
131
  });
132
  }
133
+ }
src/server/generate.tsx CHANGED
@@ -25,6 +25,16 @@ export async function generate_img(input_image: string){
25
  })
26
  }
27
 
 
 
 
 
 
 
 
 
 
 
28
  export async function checkStatus(run_id: string){
29
  return await client.getRun(run_id)
30
  }
@@ -35,4 +45,4 @@ export async function getUploadUrl(type: string, file_size: number){
35
  } catch (error) {
36
  console.log(error)
37
  }
38
- }
 
25
  })
26
  }
27
 
28
+ export async function generate_img_with_controlnet(input_openpose_url: string, prompt: string){
29
+ return await client.run({
30
+ deployment_id: process.env.COMFY_DEPLOYMENT_ID_CONTROLNET!,
31
+ inputs: {
32
+ "positive_prompt": prompt,
33
+ "openpose": input_openpose_url
34
+ }
35
+ })
36
+ }
37
+
38
  export async function checkStatus(run_id: string){
39
  return await client.getRun(run_id)
40
  }
 
45
  } catch (error) {
46
  console.log(error)
47
  }
48
+ }