BennyKok commited on
Commit
38ceaf9
1 Parent(s): 5870c02

add ui api

Browse files
.env.example ADDED
@@ -0,0 +1 @@
 
 
1
+ COMFY_API_TOKEN=
bun.lockb CHANGED
Binary files a/bun.lockb and b/bun.lockb differ
 
components.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils"
16
+ }
17
+ }
package.json CHANGED
@@ -9,9 +9,19 @@
9
  "lint": "next lint"
10
  },
11
  "dependencies": {
 
 
 
 
 
 
 
12
  "react": "^18",
13
  "react-dom": "^18",
14
- "next": "14.0.3"
 
 
 
15
  },
16
  "devDependencies": {
17
  "typescript": "^5",
 
9
  "lint": "next lint"
10
  },
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
+ "class-variance-authority": "^0.7.0",
16
+ "clsx": "^2.1.0",
17
+ "lucide-react": "^0.309.0",
18
+ "next": "14.0.3",
19
  "react": "^18",
20
  "react-dom": "^18",
21
+ "react-hook-form": "^7.49.3",
22
+ "tailwind-merge": "^2.2.0",
23
+ "tailwindcss-animate": "^1.0.7",
24
+ "zod": "^3.22.4"
25
  },
26
  "devDependencies": {
27
  "typescript": "^5",
src/app/globals.css CHANGED
@@ -1,27 +1,76 @@
1
  @tailwind base;
2
  @tailwind components;
3
  @tailwind utilities;
 
 
 
 
 
4
 
5
- :root {
6
- --foreground-rgb: 0, 0, 0;
7
- --background-start-rgb: 214, 219, 220;
8
- --background-end-rgb: 255, 255, 255;
9
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- @media (prefers-color-scheme: dark) {
12
- :root {
13
- --foreground-rgb: 255, 255, 255;
14
- --background-start-rgb: 0, 0, 0;
15
- --background-end-rgb: 0, 0, 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  }
17
  }
18
-
19
- body {
20
- color: rgb(var(--foreground-rgb));
21
- background: linear-gradient(
22
- to bottom,
23
- transparent,
24
- rgb(var(--background-end-rgb))
25
- )
26
- rgb(var(--background-start-rgb));
27
- }
 
1
  @tailwind base;
2
  @tailwind components;
3
  @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 240 10% 3.9%;
9
 
10
+ --card: 0 0% 100%;
11
+ --card-foreground: 240 10% 3.9%;
12
+
13
+ --popover: 0 0% 100%;
14
+ --popover-foreground: 240 10% 3.9%;
15
+
16
+ --primary: 240 5.9% 10%;
17
+ --primary-foreground: 0 0% 98%;
18
+
19
+ --secondary: 240 4.8% 95.9%;
20
+ --secondary-foreground: 240 5.9% 10%;
21
+
22
+ --muted: 240 4.8% 95.9%;
23
+ --muted-foreground: 240 3.8% 46.1%;
24
+
25
+ --accent: 240 4.8% 95.9%;
26
+ --accent-foreground: 240 5.9% 10%;
27
+
28
+ --destructive: 0 84.2% 60.2%;
29
+ --destructive-foreground: 0 0% 98%;
30
 
31
+ --border: 240 5.9% 90%;
32
+ --input: 240 5.9% 90%;
33
+ --ring: 240 10% 3.9%;
34
+
35
+ --radius: 0.5rem;
36
+ }
37
+
38
+ .dark {
39
+ --background: 240 10% 3.9%;
40
+ --foreground: 0 0% 98%;
41
+
42
+ --card: 240 10% 3.9%;
43
+ --card-foreground: 0 0% 98%;
44
+
45
+ --popover: 240 10% 3.9%;
46
+ --popover-foreground: 0 0% 98%;
47
+
48
+ --primary: 0 0% 98%;
49
+ --primary-foreground: 240 5.9% 10%;
50
+
51
+ --secondary: 240 3.7% 15.9%;
52
+ --secondary-foreground: 0 0% 98%;
53
+
54
+ --muted: 240 3.7% 15.9%;
55
+ --muted-foreground: 240 5% 64.9%;
56
+
57
+ --accent: 240 3.7% 15.9%;
58
+ --accent-foreground: 0 0% 98%;
59
+
60
+ --destructive: 0 62.8% 30.6%;
61
+ --destructive-foreground: 0 0% 98%;
62
+
63
+ --border: 240 3.7% 15.9%;
64
+ --input: 240 3.7% 15.9%;
65
+ --ring: 240 4.9% 83.9%;
66
  }
67
  }
68
+
69
+ @layer base {
70
+ * {
71
+ @apply border-border;
72
+ }
73
+ body {
74
+ @apply bg-background text-foreground;
75
+ }
76
+ }
 
src/app/layout.tsx CHANGED
@@ -4,6 +4,8 @@ import './globals.css'
4
 
5
  const inter = Inter({ subsets: ['latin'] })
6
 
 
 
7
  export const metadata: Metadata = {
8
  title: 'Create Next App',
9
  description: 'Generated by create next app',
 
4
 
5
  const inter = Inter({ subsets: ['latin'] })
6
 
7
+ export const runtime = 'edge'
8
+
9
  export const metadata: Metadata = {
10
  title: 'Create Next App',
11
  description: 'Generated by create next app',
src/app/page.tsx CHANGED
@@ -1,113 +1,99 @@
1
- import Image from 'next/image'
 
 
 
 
 
 
 
 
 
2
 
3
  export default function Home() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  return (
5
  <main className="flex min-h-screen flex-col items-center justify-between p-24">
6
- <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
7
- <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30">
8
- Get started by editing&nbsp;
9
- <code className="font-mono font-bold">src/app/page.tsx</code>
10
- </p>
11
- <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none">
12
- <a
13
- className="pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0"
14
- href="https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
15
- target="_blank"
16
- rel="noopener noreferrer"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  >
18
- By{' '}
19
- <Image
20
- src="/vercel.svg"
21
- alt="Vercel Logo"
22
- className="dark:invert"
23
- width={100}
24
- height={24}
25
- priority
26
  />
27
- </a>
28
- </div>
29
- </div>
30
-
31
- <div className="relative flex place-items-center before:absolute before:h-[300px] before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]">
32
- <Image
33
- className="relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert"
34
- src="/next.svg"
35
- alt="Next.js Logo"
36
- width={180}
37
- height={37}
38
- priority
39
- />
40
- </div>
41
-
42
- <div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left">
43
- <a
44
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
45
- className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
46
- target="_blank"
47
- rel="noopener noreferrer"
48
- >
49
- <h2 className={`mb-3 text-2xl font-semibold`}>
50
- Docs{' '}
51
- <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
52
- -&gt;
53
- </span>
54
- </h2>
55
- <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
56
- Find in-depth information about Next.js features and API.
57
- </p>
58
- </a>
59
-
60
- <a
61
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
62
- className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
63
- target="_blank"
64
- rel="noopener noreferrer"
65
- >
66
- <h2 className={`mb-3 text-2xl font-semibold`}>
67
- Learn{' '}
68
- <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
69
- -&gt;
70
- </span>
71
- </h2>
72
- <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
73
- Learn about Next.js in an interactive course with&nbsp;quizzes!
74
- </p>
75
- </a>
76
-
77
- <a
78
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
79
- className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
80
- target="_blank"
81
- rel="noopener noreferrer"
82
- >
83
- <h2 className={`mb-3 text-2xl font-semibold`}>
84
- Templates{' '}
85
- <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
86
- -&gt;
87
- </span>
88
- </h2>
89
- <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
90
- Explore starter templates for Next.js.
91
- </p>
92
- </a>
93
 
94
- <a
95
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
96
- className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
97
- target="_blank"
98
- rel="noopener noreferrer"
99
- >
100
- <h2 className={`mb-3 text-2xl font-semibold`}>
101
- Deploy{' '}
102
- <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
103
- -&gt;
104
- </span>
105
- </h2>
106
- <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
107
- Instantly deploy your Next.js site to a shareable URL with Vercel.
108
- </p>
109
- </a>
110
- </div>
 
111
  </main>
112
- )
113
  }
 
1
+ "use client";
2
+
3
+ import { LoadingIcon } from "@/components/LoadingIcon";
4
+ import { Button } from "@/components/ui/button";
5
+ import { Card, CardContent, CardHeader } from "@/components/ui/card";
6
+ import { Input } from "@/components/ui/input";
7
+ import { Label } from "@/components/ui/label";
8
+ import { Skeleton } from "@/components/ui/skeleton";
9
+ import { checkStatus, generate } from "@/server/generate";
10
+ import { useEffect, useState } from "react";
11
 
12
  export default function Home() {
13
+ const [prompt, setPrompt] = useState("");
14
+ const [image, setImage] = useState("");
15
+ const [loading, setLoading] = useState(false);
16
+ const [runId, setRunId] = useState("");
17
+ const [status, setStatus] = useState("preparing");
18
+
19
+ // Polling in frontend to check for the
20
+ useEffect(() => {
21
+ if (!runId) return;
22
+ const interval = setInterval(() => {
23
+ checkStatus(runId).then((res) => {
24
+ if (res) setStatus(res.status);
25
+ if (res && res.status === "success") {
26
+ console.log(res.outputs[0].data);
27
+ setImage(res.outputs[0].data.images[0].url);
28
+ setLoading(false);
29
+ clearInterval(interval);
30
+ }
31
+ });
32
+ }, 2000);
33
+ return () => clearInterval(interval);
34
+ }, [runId]);
35
+
36
  return (
37
  <main className="flex min-h-screen flex-col items-center justify-between p-24">
38
+ <Card className="w-full max-w-[500px]">
39
+ <CardHeader>
40
+ Comfy Deploy - Vector Line Art Tool
41
+ <div className="text-xs text-foreground opacity-50">
42
+ Lora -{" "}
43
+ <a href="https://civitai.com/models/256144/stick-line-vector-illustration">
44
+ stick-line-vector-illustration
45
+ </a>
46
+ </div>
47
+ </CardHeader>
48
+ <CardContent>
49
+ <form
50
+ className="grid w-full items-center gap-1.5"
51
+ onSubmit={(e) => {
52
+ if (loading) return;
53
+
54
+ e.preventDefault();
55
+ setLoading(true);
56
+ generate(prompt).then((res) => {
57
+ console.log(res);
58
+ if (!res) return;
59
+ setRunId(res.run_id);
60
+ });
61
+ setStatus("preparing");
62
+ }}
63
  >
64
+ <Label htmlFor="picture">Image prompt</Label>
65
+ <Input
66
+ id="picture"
67
+ type="text"
68
+ value={prompt}
69
+ onChange={(e) => setPrompt(e.target.value)}
 
 
70
  />
71
+ <Button
72
+ type="submit"
73
+ className="flex gap-2"
74
+ disabled={loading}
75
+ >
76
+ Generate {loading && <LoadingIcon />}
77
+ </Button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ <div className="border border-gray-200 w-full square h-[400px] rounded-lg relative">
80
+ {!loading && image && (
81
+ <img
82
+ className="w-full h-full object-contain"
83
+ src={image}
84
+ alt="Generated image"
85
+ ></img>
86
+ )}
87
+ {loading && (
88
+ <div className="absolute top-0 left-0 w-full h-full flex items-center justify-center gap-2">
89
+ {status} <LoadingIcon />
90
+ </div>
91
+ )}
92
+ {loading && <Skeleton className="w-full h-full" />}
93
+ </div>
94
+ </form>
95
+ </CardContent>
96
+ </Card>
97
  </main>
98
+ );
99
  }
src/components/LoadingIcon.tsx ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { LoaderIcon } from "lucide-react";
4
+ import * as React from "react";
5
+
6
+ export function LoadingIcon() {
7
+ return <LoaderIcon size={14} className="animate-spin" />;
8
+ }
src/components/ui/button.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button"
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+ )
54
+ Button.displayName = "Button"
55
+
56
+ export { Button, buttonVariants }
src/components/ui/card.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLParagraphElement,
34
+ React.HTMLAttributes<HTMLHeadingElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <h3
37
+ ref={ref}
38
+ className={cn(
39
+ "text-2xl font-semibold leading-none tracking-tight",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ CardTitle.displayName = "CardTitle"
46
+
47
+ const CardDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <p
52
+ ref={ref}
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ CardDescription.displayName = "CardDescription"
58
+
59
+ const CardContent = React.forwardRef<
60
+ HTMLDivElement,
61
+ React.HTMLAttributes<HTMLDivElement>
62
+ >(({ className, ...props }, ref) => (
63
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
64
+ ))
65
+ CardContent.displayName = "CardContent"
66
+
67
+ const CardFooter = React.forwardRef<
68
+ HTMLDivElement,
69
+ React.HTMLAttributes<HTMLDivElement>
70
+ >(({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn("flex items-center p-6 pt-0", className)}
74
+ {...props}
75
+ />
76
+ ))
77
+ CardFooter.displayName = "CardFooter"
78
+
79
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
src/components/ui/form.tsx ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { Slot } from "@radix-ui/react-slot"
4
+ import {
5
+ Controller,
6
+ ControllerProps,
7
+ FieldPath,
8
+ FieldValues,
9
+ FormProvider,
10
+ useFormContext,
11
+ } from "react-hook-form"
12
+
13
+ import { cn } from "@/lib/utils"
14
+ import { Label } from "@/components/ui/label"
15
+
16
+ const Form = FormProvider
17
+
18
+ type FormFieldContextValue<
19
+ TFieldValues extends FieldValues = FieldValues,
20
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
21
+ > = {
22
+ name: TName
23
+ }
24
+
25
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
26
+ {} as FormFieldContextValue
27
+ )
28
+
29
+ const FormField = <
30
+ TFieldValues extends FieldValues = FieldValues,
31
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
32
+ >({
33
+ ...props
34
+ }: ControllerProps<TFieldValues, TName>) => {
35
+ return (
36
+ <FormFieldContext.Provider value={{ name: props.name }}>
37
+ <Controller {...props} />
38
+ </FormFieldContext.Provider>
39
+ )
40
+ }
41
+
42
+ const useFormField = () => {
43
+ const fieldContext = React.useContext(FormFieldContext)
44
+ const itemContext = React.useContext(FormItemContext)
45
+ const { getFieldState, formState } = useFormContext()
46
+
47
+ const fieldState = getFieldState(fieldContext.name, formState)
48
+
49
+ if (!fieldContext) {
50
+ throw new Error("useFormField should be used within <FormField>")
51
+ }
52
+
53
+ const { id } = itemContext
54
+
55
+ return {
56
+ id,
57
+ name: fieldContext.name,
58
+ formItemId: `${id}-form-item`,
59
+ formDescriptionId: `${id}-form-item-description`,
60
+ formMessageId: `${id}-form-item-message`,
61
+ ...fieldState,
62
+ }
63
+ }
64
+
65
+ type FormItemContextValue = {
66
+ id: string
67
+ }
68
+
69
+ const FormItemContext = React.createContext<FormItemContextValue>(
70
+ {} as FormItemContextValue
71
+ )
72
+
73
+ const FormItem = React.forwardRef<
74
+ HTMLDivElement,
75
+ React.HTMLAttributes<HTMLDivElement>
76
+ >(({ className, ...props }, ref) => {
77
+ const id = React.useId()
78
+
79
+ return (
80
+ <FormItemContext.Provider value={{ id }}>
81
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
82
+ </FormItemContext.Provider>
83
+ )
84
+ })
85
+ FormItem.displayName = "FormItem"
86
+
87
+ const FormLabel = React.forwardRef<
88
+ React.ElementRef<typeof LabelPrimitive.Root>,
89
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
90
+ >(({ className, ...props }, ref) => {
91
+ const { error, formItemId } = useFormField()
92
+
93
+ return (
94
+ <Label
95
+ ref={ref}
96
+ className={cn(error && "text-destructive", className)}
97
+ htmlFor={formItemId}
98
+ {...props}
99
+ />
100
+ )
101
+ })
102
+ FormLabel.displayName = "FormLabel"
103
+
104
+ const FormControl = React.forwardRef<
105
+ React.ElementRef<typeof Slot>,
106
+ React.ComponentPropsWithoutRef<typeof Slot>
107
+ >(({ ...props }, ref) => {
108
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109
+
110
+ return (
111
+ <Slot
112
+ ref={ref}
113
+ id={formItemId}
114
+ aria-describedby={
115
+ !error
116
+ ? `${formDescriptionId}`
117
+ : `${formDescriptionId} ${formMessageId}`
118
+ }
119
+ aria-invalid={!!error}
120
+ {...props}
121
+ />
122
+ )
123
+ })
124
+ FormControl.displayName = "FormControl"
125
+
126
+ const FormDescription = React.forwardRef<
127
+ HTMLParagraphElement,
128
+ React.HTMLAttributes<HTMLParagraphElement>
129
+ >(({ className, ...props }, ref) => {
130
+ const { formDescriptionId } = useFormField()
131
+
132
+ return (
133
+ <p
134
+ ref={ref}
135
+ id={formDescriptionId}
136
+ className={cn("text-sm text-muted-foreground", className)}
137
+ {...props}
138
+ />
139
+ )
140
+ })
141
+ FormDescription.displayName = "FormDescription"
142
+
143
+ const FormMessage = React.forwardRef<
144
+ HTMLParagraphElement,
145
+ React.HTMLAttributes<HTMLParagraphElement>
146
+ >(({ className, children, ...props }, ref) => {
147
+ const { error, formMessageId } = useFormField()
148
+ const body = error ? String(error?.message) : children
149
+
150
+ if (!body) {
151
+ return null
152
+ }
153
+
154
+ return (
155
+ <p
156
+ ref={ref}
157
+ id={formMessageId}
158
+ className={cn("text-sm font-medium text-destructive", className)}
159
+ {...props}
160
+ >
161
+ {body}
162
+ </p>
163
+ )
164
+ })
165
+ FormMessage.displayName = "FormMessage"
166
+
167
+ export {
168
+ useFormField,
169
+ Form,
170
+ FormItem,
171
+ FormLabel,
172
+ FormControl,
173
+ FormDescription,
174
+ FormMessage,
175
+ FormField,
176
+ }
src/components/ui/input.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export interface InputProps
6
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
7
+
8
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
+ ({ className, type, ...props }, ref) => {
10
+ return (
11
+ <input
12
+ type={type}
13
+ className={cn(
14
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
15
+ className
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+ )
23
+ Input.displayName = "Input"
24
+
25
+ export { Input }
src/components/ui/label.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const labelVariants = cva(
10
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11
+ )
12
+
13
+ const Label = React.forwardRef<
14
+ React.ElementRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
+ VariantProps<typeof labelVariants>
17
+ >(({ className, ...props }, ref) => (
18
+ <LabelPrimitive.Root
19
+ ref={ref}
20
+ className={cn(labelVariants(), className)}
21
+ {...props}
22
+ />
23
+ ))
24
+ Label.displayName = LabelPrimitive.Root.displayName
25
+
26
+ export { Label }
src/components/ui/skeleton.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cn } from "@/lib/utils"
2
+
3
+ function Skeleton({
4
+ className,
5
+ ...props
6
+ }: React.HTMLAttributes<HTMLDivElement>) {
7
+ return (
8
+ <div
9
+ className={cn("animate-pulse rounded-md bg-muted", className)}
10
+ {...props}
11
+ />
12
+ )
13
+ }
14
+
15
+ export { Skeleton }
src/lib/comfy-deploy.ts ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { z } from "zod";
2
+
3
+ const runTypes = z.object({
4
+ run_id: z.string(),
5
+ });
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
+
17
+ export class ComfyDeployClient {
18
+ apiBase: string = "https://www.comfydeploy.com/api";
19
+ apiToken: string;
20
+
21
+ constructor({ apiBase, apiToken }: { apiBase?: string; apiToken: string }) {
22
+ if (apiBase)
23
+ this.apiBase = `${apiBase}/api`;
24
+ this.apiToken = apiToken;
25
+ }
26
+
27
+ async run({
28
+ deployment_id,
29
+ inputs,
30
+ }: {
31
+ deployment_id: string;
32
+ inputs?: Record<string, string>;
33
+ }) {
34
+ return fetch(`${this.apiBase}/run`, {
35
+ method: "POST",
36
+ headers: {
37
+ "Content-Type": "application/json",
38
+ authorization: `Bearer ${this.apiToken}`,
39
+ },
40
+ body: JSON.stringify({
41
+ deployment_id: deployment_id,
42
+ inputs: inputs,
43
+ }),
44
+ cache: "no-store"
45
+ })
46
+ .then((response) => response.json())
47
+ .then((json) => runTypes.parse(json))
48
+ .catch((err) => {
49
+ console.error(err);
50
+ return null;
51
+ });
52
+ }
53
+
54
+ async getRun(run_id: string) {
55
+ return await fetch(`${this.apiBase}/run?run_id=${run_id}`, {
56
+ method: "GET",
57
+ headers: {
58
+ "Content-Type": "application/json",
59
+ authorization: `Bearer ${this.apiToken}`,
60
+ },
61
+ cache: "no-store"
62
+ })
63
+ .then((response) => response.json())
64
+ .then((json) => runOutputTypes.parse(json))
65
+ .catch((err) => {
66
+ console.error(err);
67
+ return null;
68
+ });
69
+ }
70
+
71
+ async runSync(props: {
72
+ deployment_id: string;
73
+ inputs?: Record<string, string>;
74
+ }) {
75
+ const runResult = await this.run(props);
76
+ if (!runResult) return null;
77
+
78
+ // 5 minutes
79
+ const timeout = 60 * 5;
80
+ const interval = 1000;
81
+
82
+ let run: Awaited<ReturnType<typeof this.getRun>> = null;
83
+ for (let i = 0; i < timeout; i++) {
84
+ run = await this.getRun(runResult.run_id);
85
+ if (run && run.status == "succuss") {
86
+ break;
87
+ }
88
+ await new Promise((resolve) => setTimeout(resolve, interval));
89
+ }
90
+
91
+ if (!run) {
92
+ return {
93
+ id: runResult.run_id,
94
+ };
95
+ }
96
+
97
+ return run;
98
+ }
99
+ }
src/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { type ClassValue, clsx } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
src/server/generate.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { ComfyDeployClient } from "@/lib/comfy-deploy"
4
+
5
+ const client = new ComfyDeployClient({
6
+ apiToken: process.env.COMFY_API_TOKEN!,
7
+ })
8
+
9
+ export async function generate(prompt: string){
10
+ return await client.run({
11
+ deployment_id: "8dc98937-9842-425b-a562-970329d07639",
12
+ inputs: {
13
+ "input_text": prompt
14
+ }
15
+ })
16
+ }
17
+
18
+ export async function checkStatus(run_id: string){
19
+ return await client.getRun(run_id)
20
+ }
tailwind.config.ts CHANGED
@@ -1,20 +1,80 @@
1
- import type { Config } from 'tailwindcss'
2
 
3
- const config: Config = {
 
4
  content: [
5
- './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
6
- './src/components/**/*.{js,ts,jsx,tsx,mdx}',
7
- './src/app/**/*.{js,ts,jsx,tsx,mdx}',
8
- ],
 
 
9
  theme: {
 
 
 
 
 
 
 
10
  extend: {
11
- backgroundImage: {
12
- 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13
- 'gradient-conic':
14
- 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  },
16
  },
17
  },
18
- plugins: [],
19
- }
20
- export default config
 
 
1
+ import type { Config } from "tailwindcss"
2
 
3
+ const config = {
4
+ darkMode: ["class"],
5
  content: [
6
+ './pages/**/*.{ts,tsx}',
7
+ './components/**/*.{ts,tsx}',
8
+ './app/**/*.{ts,tsx}',
9
+ './src/**/*.{ts,tsx}',
10
+ ],
11
+ prefix: "",
12
  theme: {
13
+ container: {
14
+ center: true,
15
+ padding: "2rem",
16
+ screens: {
17
+ "2xl": "1400px",
18
+ },
19
+ },
20
  extend: {
21
+ colors: {
22
+ border: "hsl(var(--border))",
23
+ input: "hsl(var(--input))",
24
+ ring: "hsl(var(--ring))",
25
+ background: "hsl(var(--background))",
26
+ foreground: "hsl(var(--foreground))",
27
+ primary: {
28
+ DEFAULT: "hsl(var(--primary))",
29
+ foreground: "hsl(var(--primary-foreground))",
30
+ },
31
+ secondary: {
32
+ DEFAULT: "hsl(var(--secondary))",
33
+ foreground: "hsl(var(--secondary-foreground))",
34
+ },
35
+ destructive: {
36
+ DEFAULT: "hsl(var(--destructive))",
37
+ foreground: "hsl(var(--destructive-foreground))",
38
+ },
39
+ muted: {
40
+ DEFAULT: "hsl(var(--muted))",
41
+ foreground: "hsl(var(--muted-foreground))",
42
+ },
43
+ accent: {
44
+ DEFAULT: "hsl(var(--accent))",
45
+ foreground: "hsl(var(--accent-foreground))",
46
+ },
47
+ popover: {
48
+ DEFAULT: "hsl(var(--popover))",
49
+ foreground: "hsl(var(--popover-foreground))",
50
+ },
51
+ card: {
52
+ DEFAULT: "hsl(var(--card))",
53
+ foreground: "hsl(var(--card-foreground))",
54
+ },
55
+ },
56
+ borderRadius: {
57
+ lg: "var(--radius)",
58
+ md: "calc(var(--radius) - 2px)",
59
+ sm: "calc(var(--radius) - 4px)",
60
+ },
61
+ keyframes: {
62
+ "accordion-down": {
63
+ from: { height: "0" },
64
+ to: { height: "var(--radix-accordion-content-height)" },
65
+ },
66
+ "accordion-up": {
67
+ from: { height: "var(--radix-accordion-content-height)" },
68
+ to: { height: "0" },
69
+ },
70
+ },
71
+ animation: {
72
+ "accordion-down": "accordion-down 0.2s ease-out",
73
+ "accordion-up": "accordion-up 0.2s ease-out",
74
  },
75
  },
76
  },
77
+ plugins: [require("tailwindcss-animate")],
78
+ } satisfies Config
79
+
80
+ export default config