fengmiguoji commited on
Commit
c0a5c18
·
verified ·
1 Parent(s): 35a732e

Upload folder using huggingface_hub

Browse files
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": ["next/core-web-vitals", "next/typescript"]
3
+ }
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ public/og-image.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+ .yarn/install-state.gz
8
+
9
+ # testing
10
+ /coverage
11
+
12
+ # next.js
13
+ /.next/
14
+ /out/
15
+ .env
16
+
17
+ # production
18
+ /build
19
+
20
+ # misc
21
+ .DS_Store
22
+ *.pem
23
+
24
+ # debug
25
+ npm-debug.log*
26
+ yarn-debug.log*
27
+ yarn-error.log*
28
+
29
+ # local env files
30
+ .env*.local
31
+
32
+ # vercel
33
+ .vercel
34
+
35
+ # typescript
36
+ *.tsbuildinfo
37
+ next-env.d.ts
.prettierrc ADDED
@@ -0,0 +1 @@
 
 
1
+ {"plugins": ["prettier-plugin-tailwindcss"]}
app/api/generate-logo/route.ts ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { z } from "zod";
2
+ import { Ratelimit } from "@upstash/ratelimit";
3
+ import { Redis } from "@upstash/redis";
4
+ import dedent from 'dedent';
5
+
6
+ let ratelimit: Ratelimit | undefined;
7
+
8
+ export async function POST(req: Request) {
9
+ const json = await req.json();
10
+ const data = z
11
+ .object({
12
+ userAPIKey: z.string().optional(),
13
+ companyName: z.string(),
14
+ selectedStyle: z.string(),
15
+ selectedPrimaryColor: z.string(),
16
+ selectedBackgroundColor: z.string(),
17
+ additionalInfo: z.string().optional(),
18
+ })
19
+ .parse(json);
20
+
21
+ // Add rate limiting if Upstash API keys are set & no BYOK, otherwise skip
22
+ if (process.env.UPSTASH_REDIS_REST_URL && !data.userAPIKey) {
23
+ ratelimit = new Ratelimit({
24
+ redis: Redis.fromEnv(),
25
+ // Allow 3 requests per 2 months on prod
26
+ limiter: Ratelimit.fixedWindow(3, "60 d"),
27
+ analytics: true,
28
+ prefix: "logocreator",
29
+ });
30
+ }
31
+
32
+ const apiKey = data.userAPIKey || process.env.SILICONFLOW_API_KEY;
33
+ const headers = {
34
+ "Authorization": `Bearer ${apiKey}`,
35
+ "Content-Type": "application/json",
36
+ };
37
+
38
+ if (!apiKey) {
39
+ return new Response("Missing Siliconflow API Key", {
40
+ status: 401,
41
+ headers: { "Content-Type": "text/plain" },
42
+ });
43
+ }
44
+
45
+
46
+ if (ratelimit) {
47
+ const identifier = "anonymous";
48
+ const { success, remaining } = await ratelimit.limit(identifier);
49
+
50
+ if (!success) {
51
+ return new Response(
52
+ "You've used up all your free credits. Please wait or try again later.",
53
+ {
54
+ status: 429,
55
+ headers: { "Content-Type": "text/plain" },
56
+ },
57
+ );
58
+ }
59
+ }
60
+
61
+ const flashyStyle =
62
+ "Flashy, attention grabbing, bold, futuristic, and eye-catching. Use vibrant neon colors with metallic, shiny, and glossy accents.";
63
+
64
+ const techStyle =
65
+ "highly detailed, sharp focus, cinematic, photorealistic, Minimalist, clean, sleek, neutral color pallete with subtle accents, clean lines, shadows, and flat.";
66
+
67
+ const modernStyle =
68
+ "modern, forward-thinking, flat design, geometric shapes, clean lines, natural colors with subtle accents, use strategic negative space to create visual interest.";
69
+
70
+ const playfulStyle =
71
+ "playful, lighthearted, bright bold colors, rounded shapes, lively.";
72
+
73
+ const abstractStyle =
74
+ "abstract, artistic, creative, unique shapes, patterns, and textures to create a visually interesting and wild logo.";
75
+
76
+ const minimalStyle =
77
+ "minimal, simple, timeless, versatile, single color logo, use negative space, flat design with minimal details, Light, soft, and subtle.";
78
+
79
+ const styleLookup: Record<string, string> = {
80
+ Flashy: flashyStyle,
81
+ Tech: techStyle,
82
+ Modern: modernStyle,
83
+ Playful: playfulStyle,
84
+ Abstract: abstractStyle,
85
+ Minimal: minimalStyle,
86
+ };
87
+
88
+ const prompt = dedent`A single logo, high-quality, award-winning professional design, made for both digital and print media, only contains a few vector shapes, ${styleLookup[data.selectedStyle]}
89
+
90
+ Primary color is ${data.selectedPrimaryColor.toLowerCase()} and background color is ${data.selectedBackgroundColor.toLowerCase()}. The company name is ${data.companyName}, make sure to include the company name in the logo. ${data.additionalInfo ? `Additional info: ${data.additionalInfo}` : ""}`;
91
+
92
+ interface SiliconFlowPayload {
93
+ model: string;
94
+ prompt: string;
95
+ image_size: string;
96
+ batch_size: number;
97
+ num_inference_steps: number;
98
+ guidance_scale: number;
99
+ }
100
+
101
+ const payload: SiliconFlowPayload = {
102
+ model: "black-forest-labs/FLUX.1-schnell",
103
+ prompt,
104
+ image_size: "1024x1024",
105
+ batch_size: 1,
106
+ num_inference_steps: 20,
107
+ guidance_scale: 7.5,
108
+ };
109
+
110
+
111
+ try {
112
+ const response = await fetch(
113
+ "https://api.siliconflow.cn/v1/images/generations",
114
+ {
115
+ method: "POST",
116
+ headers,
117
+ body: JSON.stringify(payload),
118
+ }
119
+ );
120
+
121
+ if (!response.ok) {
122
+ const errorText = await response.text()
123
+ const message = `Siliconflow API Error - Status: ${response.status}, Text: ${errorText}`
124
+ return new Response(message, {
125
+ status: response.status,
126
+ headers: { "Content-Type": "text/plain" },
127
+ });
128
+ }
129
+
130
+
131
+ const json = await response.json() as any;
132
+ return Response.json(
133
+ { url: json.images[0].url, }, { status: 200 }
134
+ );
135
+ } catch (error) {
136
+ return new Response(
137
+ `Failed to call SiliconFlow API, ${error}`,
138
+ {
139
+ status: 500,
140
+ headers: { "Content-Type": "text/plain" },
141
+ },
142
+ );
143
+ }
144
+ }
145
+
146
+ export const runtime = "edge";
app/components/Footer.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Image from "next/image";
2
+
3
+ const Footer = () => (
4
+ <div className="flex flex-col items-center justify-between bg-[#343434] px-4 py-4 md:flex-row">
5
+ <div className="mb-2 flex-grow pl-0 text-center md:mb-0 md:pl-0 lg:pl-48">
6
+ <span className="text-sm text-[#6F6F6F]">
7
+ Powered by{" "}
8
+ <a href="https://dub.sh/together-ai" className="underline">
9
+ Together.ai
10
+ </a>{" "}
11
+ &{" "}
12
+ <a href="https://dub.sh/flux-playground" className="underline">
13
+ Flux
14
+ </a>
15
+ </span>
16
+ </div>
17
+ </div>
18
+ );
19
+
20
+ export default Footer;
app/components/Header.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Image from "next/image";
2
+ import Link from "next/link";
3
+
4
+
5
+ export default function Header({ className }: { className: string }) {
6
+ return (
7
+ <header className={`relative w-full ${className}`}>
8
+ <div className="flex items-center justify-between bg-[#343434] px-4 py-2 md:mt-4">
9
+ <div className="flex flex-grow justify-start xl:justify-center">
10
+ <Link href="https://dub.sh/together-ai" className="flex items-center">
11
+ <Image
12
+ src="/together-ai-logo1.svg"
13
+ alt="together.ai"
14
+ width={400}
15
+ height={120}
16
+ className="w-[220px] md:w-[330px] lg:w-[390px]"
17
+ priority
18
+ />
19
+ </Link>
20
+ </div>
21
+ </div>
22
+ </header>
23
+ );
24
+ }
app/components/InfoToolTip.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { Info } from "lucide-react";
2
+
3
+ export default function InfoTooltip({ content }: { content: string }) {
4
+ return (
5
+ <span className="ml-2 cursor-default text-[#6F6F6F]" title={content}>
6
+ <Info size={11} />
7
+ </span>
8
+ );
9
+ }
app/components/Spinner.tsx ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReactNode } from "react";
2
+
3
+ export default function Spinner({
4
+ loading = true,
5
+ children,
6
+ className = "",
7
+ }: {
8
+ loading?: boolean;
9
+ children?: ReactNode;
10
+ className?: string;
11
+ }) {
12
+ if (!loading) return children;
13
+
14
+ const spinner = (
15
+ <>
16
+ <style jsx>
17
+ {`
18
+ @keyframes spinner {
19
+ from {
20
+ opacity: 1;
21
+ }
22
+ to {
23
+ opacity: 0.25;
24
+ }
25
+ }
26
+ `}
27
+ </style>
28
+ <span className={`relative inline-flex size-4 ${className}`}>
29
+ {Array.from(Array(8).keys()).map((i) => (
30
+ <span
31
+ key={i}
32
+ className="absolute left-[calc(50%-12.5%/2)] top-0 h-[100%] w-[12.5%] animate-[spinner_800ms_linear_infinite] before:block before:h-[30%] before:w-[100%] before:rounded-full before:bg-current"
33
+ style={{
34
+ transform: `rotate(${45 * i}deg)`,
35
+ animationDelay: `calc(-${8 - i} / 8 * 800ms)`,
36
+ }}
37
+ />
38
+ ))}
39
+ </span>
40
+ </>
41
+ );
42
+
43
+ if (!children) return spinner;
44
+
45
+ return (
46
+ <span className="relative flex h-full items-center justify-center">
47
+ <span className="invisible">{children}</span>
48
+
49
+ <span className="absolute inset-0 flex items-center justify-center">
50
+ {spinner}
51
+ </span>
52
+ </span>
53
+ );
54
+ }
app/components/ui/button.tsx ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline focus-visible:outline-offset-2 focus-visible:outline-white disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16
+ outline:
17
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20
+ ghost: "hover:bg-accent hover:text-accent-foreground",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2",
25
+ sm: "h-8 rounded-md px-3 text-xs",
26
+ lg: "h-12 rounded-md px-8",
27
+ icon: "h-9 w-9",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ },
35
+ );
36
+
37
+ export interface ButtonProps
38
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39
+ VariantProps<typeof buttonVariants> {
40
+ asChild?: boolean;
41
+ }
42
+
43
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : "button";
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ );
53
+ },
54
+ );
55
+ Button.displayName = "Button";
56
+
57
+ export { Button, buttonVariants };
app/components/ui/input.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
+ ({ className, type, ...props }, ref) => {
7
+ return (
8
+ <input
9
+ type={type}
10
+ className={cn(
11
+ "flex h-11 w-full rounded-md border border-input bg-[#343434] px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ className,
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ );
18
+ },
19
+ );
20
+ Input.displayName = "Input";
21
+
22
+ export { Input };
app/components/ui/select.tsx ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-[#343434] px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring 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("px-2 py-1.5 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-2 pr-8 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 right-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
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
132
+ </SelectPrimitive.Item>
133
+ ));
134
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
135
+
136
+ const SelectSeparator = React.forwardRef<
137
+ React.ElementRef<typeof SelectPrimitive.Separator>,
138
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
139
+ >(({ className, ...props }, ref) => (
140
+ <SelectPrimitive.Separator
141
+ ref={ref}
142
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
143
+ {...props}
144
+ />
145
+ ));
146
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
147
+
148
+ export {
149
+ Select,
150
+ SelectGroup,
151
+ SelectValue,
152
+ SelectTrigger,
153
+ SelectContent,
154
+ SelectLabel,
155
+ SelectItem,
156
+ SelectSeparator,
157
+ SelectScrollUpButton,
158
+ SelectScrollDownButton,
159
+ };
app/components/ui/textarea.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const Textarea = React.forwardRef<
6
+ HTMLTextAreaElement,
7
+ React.ComponentProps<"textarea">
8
+ >(({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ className={cn(
12
+ "flex min-h-[60px] w-full rounded-md border border-input bg-[#343434] px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
13
+ className,
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ );
19
+ });
20
+ Textarea.displayName = "Textarea";
21
+
22
+ export { Textarea };
app/components/ui/toast.tsx ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as ToastPrimitives from "@radix-ui/react-toast";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import { X } from "lucide-react";
7
+
8
+ import { cn } from "@/lib/utils";
9
+
10
+ const ToastProvider = ToastPrimitives.Provider;
11
+
12
+ const ToastViewport = React.forwardRef<
13
+ React.ElementRef<typeof ToastPrimitives.Viewport>,
14
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
15
+ >(({ className, ...props }, ref) => (
16
+ <ToastPrimitives.Viewport
17
+ ref={ref}
18
+ className={cn(
19
+ "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:right-0 sm:flex-col md:max-w-[420px]",
20
+ className,
21
+ )}
22
+ {...props}
23
+ />
24
+ ));
25
+ ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
26
+
27
+ const toastVariants = cva(
28
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full",
29
+ {
30
+ variants: {
31
+ variant: {
32
+ default: "border bg-background text-foreground",
33
+ destructive:
34
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ },
40
+ },
41
+ );
42
+
43
+ const Toast = React.forwardRef<
44
+ React.ElementRef<typeof ToastPrimitives.Root>,
45
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
46
+ VariantProps<typeof toastVariants>
47
+ >(({ className, variant, ...props }, ref) => {
48
+ return (
49
+ <ToastPrimitives.Root
50
+ ref={ref}
51
+ className={cn(toastVariants({ variant }), className)}
52
+ {...props}
53
+ />
54
+ );
55
+ });
56
+ Toast.displayName = ToastPrimitives.Root.displayName;
57
+
58
+ const ToastAction = React.forwardRef<
59
+ React.ElementRef<typeof ToastPrimitives.Action>,
60
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
61
+ >(({ className, ...props }, ref) => (
62
+ <ToastPrimitives.Action
63
+ ref={ref}
64
+ className={cn(
65
+ "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
66
+ className,
67
+ )}
68
+ {...props}
69
+ />
70
+ ));
71
+ ToastAction.displayName = ToastPrimitives.Action.displayName;
72
+
73
+ const ToastClose = React.forwardRef<
74
+ React.ElementRef<typeof ToastPrimitives.Close>,
75
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
76
+ >(({ className, ...props }, ref) => (
77
+ <ToastPrimitives.Close
78
+ ref={ref}
79
+ className={cn(
80
+ "absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
81
+ className,
82
+ )}
83
+ toast-close=""
84
+ {...props}
85
+ >
86
+ <X className="h-4 w-4" />
87
+ </ToastPrimitives.Close>
88
+ ));
89
+ ToastClose.displayName = ToastPrimitives.Close.displayName;
90
+
91
+ const ToastTitle = React.forwardRef<
92
+ React.ElementRef<typeof ToastPrimitives.Title>,
93
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
94
+ >(({ className, ...props }, ref) => (
95
+ <ToastPrimitives.Title
96
+ ref={ref}
97
+ className={cn("text-sm font-semibold [&+div]:text-xs", className)}
98
+ {...props}
99
+ />
100
+ ));
101
+ ToastTitle.displayName = ToastPrimitives.Title.displayName;
102
+
103
+ const ToastDescription = React.forwardRef<
104
+ React.ElementRef<typeof ToastPrimitives.Description>,
105
+ React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
106
+ >(({ className, ...props }, ref) => (
107
+ <ToastPrimitives.Description
108
+ ref={ref}
109
+ className={cn("text-sm opacity-90", className)}
110
+ {...props}
111
+ />
112
+ ));
113
+ ToastDescription.displayName = ToastPrimitives.Description.displayName;
114
+
115
+ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
116
+
117
+ type ToastActionElement = React.ReactElement<typeof ToastAction>;
118
+
119
+ export {
120
+ type ToastProps,
121
+ type ToastActionElement,
122
+ ToastProvider,
123
+ ToastViewport,
124
+ Toast,
125
+ ToastTitle,
126
+ ToastDescription,
127
+ ToastClose,
128
+ ToastAction,
129
+ };
app/favicon.ico ADDED
app/globals.css ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 0 0% 3.9%;
9
+ --card: 0 0% 100%;
10
+ --card-foreground: 0 0% 3.9%;
11
+ --popover: 0 0% 100%;
12
+ --popover-foreground: 0 0% 3.9%;
13
+ --primary: 0 0% 9%;
14
+ --primary-foreground: 0 0% 98%;
15
+ --secondary: 0 0% 96.1%;
16
+ --secondary-foreground: 0 0% 9%;
17
+ --muted: 0 0% 96.1%;
18
+ --muted-foreground: 0 0% 45.1%;
19
+ --accent: 0 0% 96.1%;
20
+ --accent-foreground: 0 0% 9%;
21
+ --destructive: 0 84.2% 60.2%;
22
+ --destructive-foreground: 0 0% 98%;
23
+ --border: 0 0% 89.8%;
24
+ --input: 0 0% 89.8%;
25
+ --ring: 0 0% 3.9%;
26
+ --chart-1: 12 76% 61%;
27
+ --chart-2: 173 58% 39%;
28
+ --chart-3: 197 37% 24%;
29
+ --chart-4: 43 74% 66%;
30
+ --chart-5: 27 87% 67%;
31
+ --radius: 0.5rem;
32
+ }
33
+ .dark {
34
+ --background: 0 0% 3.9%;
35
+ --foreground: 0 0% 98%;
36
+ --card: 0 0% 3.9%;
37
+ --card-foreground: 0 0% 98%;
38
+ --popover: 0 0% 3.9%;
39
+ --popover-foreground: 0 0% 98%;
40
+ --primary: 0 0% 98%;
41
+ --primary-foreground: 0 0% 9%;
42
+ --secondary: 0 0% 14.9%;
43
+ --secondary-foreground: 0 0% 98%;
44
+ --muted: 0 0% 14.9%;
45
+ --muted-foreground: 0 0% 63.9%;
46
+ --accent: 0 0% 14.9%;
47
+ --accent-foreground: 0 0% 98%;
48
+ --destructive: 0 62.8% 30.6%;
49
+ --destructive-foreground: 0 0% 98%;
50
+ --border: 0 0% 20.39%;
51
+ --input: 0 0% 20.39%;
52
+ --ring: 0 0% 83.1%;
53
+ --chart-1: 220 70% 50%;
54
+ --chart-2: 160 60% 45%;
55
+ --chart-3: 30 80% 55%;
56
+ --chart-4: 280 65% 60%;
57
+ --chart-5: 340 75% 55%;
58
+ }
59
+ }
60
+
61
+ @layer base {
62
+ * {
63
+ @apply border-border;
64
+ }
65
+ body {
66
+ @apply bg-background text-foreground;
67
+ }
68
+ }
app/layout.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import { Jura } from "next/font/google";
3
+ import "./globals.css";
4
+ import { Toaster } from "@/app/components/ui/toaster";
5
+
6
+ const jura = Jura({
7
+ subsets: ["latin"],
8
+ variable: "--font-jura",
9
+ });
10
+
11
+ const title = "Logo-creator.io – Generate a logo";
12
+ const description = "Generate a logo for your company";
13
+ const url = "https://www.logo-creator.io/";
14
+ const ogimage = "https://www.logo-creator.io/og-image.png";
15
+ const sitename = "logo-creator.io";
16
+
17
+ export const metadata: Metadata = {
18
+ metadataBase: new URL(url),
19
+ title,
20
+ description,
21
+ icons: {
22
+ icon: "/favicon.ico",
23
+ },
24
+ openGraph: {
25
+ images: [ogimage],
26
+ title,
27
+ description,
28
+ url: url,
29
+ siteName: sitename,
30
+ locale: "en_US",
31
+ type: "website",
32
+ },
33
+ twitter: {
34
+ card: "summary_large_image",
35
+ images: [ogimage],
36
+ title,
37
+ description,
38
+ },
39
+ };
40
+
41
+ export default function RootLayout({
42
+ children,
43
+ }: Readonly<{
44
+ children: React.ReactNode;
45
+ }>) {
46
+ return (
47
+ <html lang="en" className="h-full">
48
+ <head>
49
+ <meta name="color-scheme" content="dark" />
50
+ </head>
51
+ <body
52
+ className={`${jura.variable} dark min-h-full bg-[#343434] font-jura antialiased`}
53
+ >
54
+ {children}
55
+ </body>
56
+ </html>
57
+ );
58
+ }
app/lib/domain.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ export const domain =
2
+ process.env.NEXT_PUBLIC_VERCEL_ENV === "production"
3
+ ? "https://www.logo-creator.io"
4
+ : process.env.VERCEL_BRANCH_URL
5
+ ? `https://${process.env.VERCEL_BRANCH_URL}`
6
+ : process.env.NEXT_PUBLIC_VERCEL_URL
7
+ ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
8
+ : "http://localhost:3000";
app/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
+ }
app/page.tsx ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import Spinner from "@/app/components/Spinner";
4
+ import { Button } from "@/app/components/ui/button";
5
+ import { Input } from "@/app/components/ui/input";
6
+ import { Textarea } from "@/app/components/ui/textarea";
7
+ import { motion } from "framer-motion";
8
+ import {
9
+ Select,
10
+ SelectContent,
11
+ SelectGroup,
12
+ SelectItem,
13
+ SelectTrigger,
14
+ SelectValue,
15
+ } from "@/components/ui/select";
16
+ import * as RadioGroup from "@radix-ui/react-radio-group";
17
+ import { DownloadIcon, RefreshCwIcon } from "lucide-react";
18
+ import Image from "next/image";
19
+ import { useState } from "react";
20
+ import Header from "./components/Header";
21
+ import Footer from "./components/Footer";
22
+ import { domain } from "@/app/lib/domain";
23
+
24
+ const logoStyles = [
25
+ { name: "科技", icon: "/tech.svg" },
26
+ { name: "炫彩", icon: "/flashy.svg" },
27
+ { name: "现代", icon: "/modern.svg" },
28
+ { name: "趣味", icon: "/playful.svg" },
29
+ { name: "抽象", icon: "/abstract.svg" },
30
+ { name: "简约", icon: "/minimal.svg" },
31
+ ];
32
+
33
+ const primaryColors = [
34
+ { name: "蓝色", color: "#0F6FFF" },
35
+ { name: "红色", color: "#FF0000" },
36
+ { name: "绿色", color: "#00FF00" },
37
+ { name: "黄色", color: "#FFFF00" },
38
+ ];
39
+
40
+ const backgroundColors = [
41
+ { name: "白色", color: "#FFFFFF" },
42
+ { name: "灰色", color: "#CCCCCC" },
43
+ { name: "黑色", color: "#000000" },
44
+ ];
45
+
46
+ export default function Page() {
47
+ const [companyName, setCompanyName] = useState("");
48
+ const [selectedStyle, setSelectedStyle] = useState(logoStyles[0].name);
49
+ const [selectedPrimaryColor, setSelectedPrimaryColor] = useState(
50
+ primaryColors[0].name,
51
+ );
52
+ const [selectedBackgroundColor, setSelectedBackgroundColor] = useState(
53
+ backgroundColors[0].name,
54
+ );
55
+ const [additionalInfo, setAdditionalInfo] = useState("");
56
+ const [isLoading, setIsLoading] = useState(false);
57
+ const [generatedImage, setGeneratedImage] = useState("");
58
+
59
+ async function generateLogo() {
60
+ setIsLoading(true);
61
+
62
+ const res = await fetch("/api/generate-logo", {
63
+ method: "POST",
64
+ body: JSON.stringify({
65
+ companyName,
66
+ selectedStyle,
67
+ selectedPrimaryColor,
68
+ selectedBackgroundColor,
69
+ additionalInfo,
70
+ }),
71
+ });
72
+
73
+ if (res.ok) {
74
+ const json = await res.json() as any;
75
+ setGeneratedImage(json.url);
76
+ } else {
77
+ alert(`Error: ${res.statusText} `);
78
+ }
79
+ setIsLoading(false);
80
+ }
81
+
82
+ return (
83
+ <div className="flex h-screen flex-col overflow-y-auto overflow-x-hidden bg-[#343434] md:flex-row">
84
+ <Header className="block md:hidden" />
85
+
86
+ <div className="flex w-full flex-col md:flex-row">
87
+ <div className="relative flex h-full w-full flex-col bg-[#2C2C2C] text-[#F3F3F3] md:max-w-sm">
88
+ <form
89
+ onSubmit={(e) => {
90
+ e.preventDefault();
91
+ setGeneratedImage("");
92
+ generateLogo();
93
+ }}
94
+ className="flex h-full w-full flex-col"
95
+ >
96
+ <fieldset className="flex grow flex-col">
97
+ <div className="flex-grow overflow-y-auto">
98
+ <div className="px-8 pb-0 pt-4 md:px-6 md:pt-6">
99
+ <div className="mb-6">
100
+ <label
101
+ htmlFor="company-name"
102
+ className="mb-2 block text-xs font-bold uppercase text-[#6F6F6F]"
103
+ >
104
+ 公司名称
105
+ </label>
106
+ <Input
107
+ value={companyName}
108
+ onChange={(e) => setCompanyName(e.target.value)}
109
+ placeholder="例如:Sam's Burgers"
110
+ required
111
+ />
112
+ </div>
113
+
114
+ <div className="mb-6">
115
+ <label className="mb-2 flex items-center text-xs font-bold uppercase text-[#6F6F6F]">
116
+ 风格
117
+ </label>
118
+ <RadioGroup.Root
119
+ value={selectedStyle}
120
+ onValueChange={setSelectedStyle}
121
+ className="grid grid-cols-3 gap-3"
122
+ >
123
+ {logoStyles.map((logoStyle) => (
124
+ <RadioGroup.Item
125
+ value={logoStyle.name}
126
+ key={logoStyle.name}
127
+ className="group text-[#6F6F6F] focus-visible:outline-none data-[state=checked]:text-white"
128
+ >
129
+ <Image
130
+ src={logoStyle.icon}
131
+ alt={logoStyle.name}
132
+ width={96}
133
+ height={96}
134
+ className="w-full rounded-md border border-transparent group-focus-visible:outline group-focus-visible:outline-offset-2 group-focus-visible:outline-gray-400 group-data-[state=checked]:border-white"
135
+ />
136
+ <span className="text-xs">{logoStyle.name}</span>
137
+ </RadioGroup.Item>
138
+ ))}
139
+ </RadioGroup.Root>
140
+ </div>
141
+
142
+ <div className="mb-[25px] flex flex-col md:flex-row md:space-x-3">
143
+ <div className="mb-4 flex-1 md:mb-0">
144
+ <label className="mb-1 block text-xs font-bold uppercase text-[#6F6F6F]">
145
+ 主要颜色
146
+ </label>
147
+ <Select
148
+ value={selectedPrimaryColor}
149
+ onValueChange={setSelectedPrimaryColor}
150
+ >
151
+ <SelectTrigger>
152
+ <SelectValue placeholder="选择颜色" />
153
+ </SelectTrigger>
154
+ <SelectContent>
155
+ <SelectGroup>
156
+ {primaryColors.map((color) => (
157
+ <SelectItem key={color.color} value={color.name}>
158
+ <span className="flex items-center">
159
+ <span
160
+ style={{ backgroundColor: color.color }}
161
+ className="mr-2 size-4 rounded-sm bg-white"
162
+ />
163
+ {color.name}
164
+ </span>
165
+ </SelectItem>
166
+ ))}
167
+ </SelectGroup>
168
+ </SelectContent>
169
+ </Select>
170
+ </div>
171
+ <div className="flex-1">
172
+ <label className="mb-1 block items-center text-xs font-bold uppercase text-[#6F6F6F]">
173
+ 背景色
174
+ </label>
175
+ <Select
176
+ value={selectedBackgroundColor}
177
+ onValueChange={setSelectedBackgroundColor}
178
+ >
179
+ <SelectTrigger>
180
+ <SelectValue placeholder="选择颜色" />
181
+ </SelectTrigger>
182
+ <SelectContent>
183
+ <SelectGroup>
184
+ {backgroundColors.map((color) => (
185
+ <SelectItem key={color.color} value={color.name}>
186
+ <span className="flex items-center">
187
+ <span
188
+ style={{ backgroundColor: color.color }}
189
+ className="mr-2 size-4 rounded-sm bg-white"
190
+ />
191
+ {color.name}
192
+ </span>
193
+ </SelectItem>
194
+ ))}
195
+ </SelectGroup>
196
+ </SelectContent>
197
+ </Select>
198
+ </div>
199
+ </div>
200
+ <div className="mb-1">
201
+ <div className="mt-1">
202
+ <div className="mb-1">
203
+ <label
204
+ htmlFor="additional-info"
205
+ className="mb-2 flex items-center text-xs font-bold uppercase text-[#6F6F6F]"
206
+ >
207
+ 附加信息
208
+ </label>
209
+ <Textarea
210
+ value={additionalInfo}
211
+ onChange={(e) => setAdditionalInfo(e.target.value)}
212
+ placeholder="输入关于你的logo的其他信息"
213
+ />
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ <div className="px-8 py-4 md:px-6 md:py-6">
220
+ <Button
221
+ size="lg"
222
+ className="w-full text-base font-bold"
223
+ type="submit"
224
+ disabled={isLoading}
225
+ >
226
+ {isLoading ? (
227
+ <div className="loader mr-2" />
228
+ ) : (
229
+ <Image
230
+ src="/generate-icon.svg"
231
+ alt="Generate Icon"
232
+ width={16}
233
+ height={16}
234
+ className="mr-2"
235
+ />
236
+ )}
237
+ {isLoading ? "生成中..." : "生成Logo"}
238
+ </Button>
239
+ </div>
240
+ </fieldset>
241
+ </form>
242
+ </div>
243
+
244
+ <div className="flex w-full flex-col pt-12 md:pt-0">
245
+ <Header className="hidden md:block" />
246
+ <div className="relative flex flex-grow items-center justify-center px-4">
247
+ <div className="relative aspect-square w-full max-w-lg">
248
+ {generatedImage ? (
249
+ <>
250
+ <Image
251
+ className={`${isLoading ? "animate-pulse" : ""}`}
252
+ width={512}
253
+ height={512}
254
+ src={generatedImage}
255
+ alt="Generated logo"
256
+ />
257
+ <div
258
+ className={`pointer-events-none absolute inset-0 transition ${isLoading ? "bg-black/50 duration-500" : "bg-black/0 duration-0"}`}
259
+ />
260
+
261
+ <div className="absolute -right-12 top-0 flex flex-col gap-2">
262
+ <Button size="icon" variant="secondary" asChild>
263
+ <a href={generatedImage} download="logo.png">
264
+ <DownloadIcon />
265
+ </a>
266
+ </Button>
267
+ <Button
268
+ size="icon"
269
+ onClick={generateLogo}
270
+ variant="secondary"
271
+ >
272
+ <Spinner loading={isLoading}>
273
+ <RefreshCwIcon />
274
+ </Spinner>
275
+ </Button>
276
+ </div>
277
+ </>
278
+ ) : (
279
+ <Spinner loading={isLoading} className="size-8 text-white">
280
+ <div className="flex aspect-square w-full flex-col items-center justify-center rounded-xl bg-[#2C2C2C]">
281
+ <h4 className="text-center text-base leading-tight text-white">
282
+ 在10秒内
283
+ <br />
284
+ 生成你的专属 Logo
285
+ </h4>
286
+ </div>
287
+ </Spinner>
288
+ )}
289
+ </div>
290
+ </div>
291
+ <Footer />
292
+ </div>
293
+ </div>
294
+ </div>
295
+ );
296
+ }
components.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
lib/utils.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
middleware.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NextResponse, NextFetchEvent } from "next/server";
2
+ import type { NextRequest } from "next/server";
3
+
4
+ export default async function middleware(
5
+ req: NextRequest,
6
+ evt: NextFetchEvent,
7
+ ) {
8
+ const country = req.geo?.country;
9
+ // Check for Russian traffic first as there's too much spam from Russia
10
+ if (country === "RU") {
11
+ return new NextResponse("Access Denied", { status: 403 });
12
+ }
13
+
14
+ // If not from Russia, proceed without any further authentication
15
+ return NextResponse.next();
16
+ }
17
+
18
+ export const config = {
19
+ matcher: [
20
+ // Skip Next.js internals and static files
21
+ "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
22
+ ],
23
+ };
next.config.mjs ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ images: {
4
+ remotePatterns: [
5
+ {
6
+ protocol: 'https',
7
+ hostname: 'sc-maas.oss-cn-shanghai.aliyuncs.com',
8
+ },
9
+ ],
10
+ },
11
+ };
12
+
13
+ export default nextConfig;
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "logo-maker",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@radix-ui/react-dialog": "^1.1.2",
13
+ "@radix-ui/react-radio-group": "^1.2.1",
14
+ "@radix-ui/react-select": "^2.1.2",
15
+ "@radix-ui/react-slot": "^1.1.0",
16
+ "@radix-ui/react-toast": "^1.2.2",
17
+ "@radix-ui/react-tooltip": "^1.1.3",
18
+ "@upstash/ratelimit": "^2.0.4",
19
+ "axios": "^1.7.7",
20
+ "class-variance-authority": "^0.7.0",
21
+ "clsx": "^2.1.1",
22
+ "dedent": "^1.5.3",
23
+ "form-data-encoder": "1.7.2",
24
+ "framer-motion": "^11.11.15",
25
+ "lucide-react": "^0.453.0",
26
+ "next": "^14.2.15",
27
+ "next-plausible": "^3.12.4",
28
+ "react": "^18",
29
+ "react-dom": "^18",
30
+ "shadcn": "^1.0.0",
31
+ "tailwind-merge": "^2.5.4",
32
+ "tailwindcss-animate": "^1.0.7"
33
+ },
34
+ "devDependencies": {
35
+ "@shadcn/ui": "^0.0.4",
36
+ "@types/node": "^20",
37
+ "@types/react": "^18",
38
+ "@types/react-dom": "^18",
39
+ "eslint": "^8",
40
+ "eslint-config-next": "14.2.15",
41
+ "postcss": "^8",
42
+ "prettier": "^3.3.3",
43
+ "prettier-plugin-tailwindcss": "^0.6.8",
44
+ "tailwindcss": "^3.4.1",
45
+ "typescript": "^5"
46
+ }
47
+ }
postcss.config.mjs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
public/Github.svg ADDED
public/abstract.svg ADDED
public/flashy.svg ADDED
public/generate-icon.svg ADDED
public/insan.png ADDED
public/minimal.svg ADDED
public/modern.svg ADDED
public/og-image.png ADDED

Git LFS Details

  • SHA256: c259ca75e0ccfc62549204f92f9e471c54a3d40719958389acefad3ebf4dc649
  • Pointer size: 132 Bytes
  • Size of remote file: 1.11 MB
public/playful.svg ADDED
public/side.svg ADDED
public/solo.svg ADDED
public/stack.svg ADDED
public/tech.svg ADDED
public/together-ai-logo.svg ADDED
public/together-ai-logo1.svg ADDED
public/twitter.svg ADDED
tailwind.config.ts ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Config } from "tailwindcss";
2
+ import tailwindAnimate from "tailwindcss-animate";
3
+
4
+ const config: Config = {
5
+ darkMode: ["class"],
6
+ content: [
7
+ "./pages/**/*.{js,ts,jsx,tsx,mdx}",
8
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
9
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
10
+ ],
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ gray: {
15
+ "700": "#2C2C2C",
16
+ "800": "#343434",
17
+ "900": "#1F1F1F",
18
+ },
19
+ blue: {
20
+ "500": "#0F6FFF",
21
+ },
22
+ background: "hsl(var(--background))",
23
+ foreground: "hsl(var(--foreground))",
24
+ card: {
25
+ DEFAULT: "hsl(var(--card))",
26
+ foreground: "hsl(var(--card-foreground))",
27
+ },
28
+ popover: {
29
+ DEFAULT: "hsl(var(--popover))",
30
+ foreground: "hsl(var(--popover-foreground))",
31
+ },
32
+ primary: {
33
+ DEFAULT: "hsl(var(--primary))",
34
+ foreground: "hsl(var(--primary-foreground))",
35
+ },
36
+ secondary: {
37
+ DEFAULT: "hsl(var(--secondary))",
38
+ foreground: "hsl(var(--secondary-foreground))",
39
+ },
40
+ muted: {
41
+ DEFAULT: "hsl(var(--muted))",
42
+ foreground: "hsl(var(--muted-foreground))",
43
+ },
44
+ accent: {
45
+ DEFAULT: "hsl(var(--accent))",
46
+ foreground: "hsl(var(--accent-foreground))",
47
+ },
48
+ destructive: {
49
+ DEFAULT: "hsl(var(--destructive))",
50
+ foreground: "hsl(var(--destructive-foreground))",
51
+ },
52
+ border: "hsl(var(--border))",
53
+ input: "hsl(var(--input))",
54
+ ring: "hsl(var(--ring))",
55
+ chart: {
56
+ "1": "hsl(var(--chart-1))",
57
+ "2": "hsl(var(--chart-2))",
58
+ "3": "hsl(var(--chart-3))",
59
+ "4": "hsl(var(--chart-4))",
60
+ "5": "hsl(var(--chart-5))",
61
+ },
62
+ },
63
+ fontFamily: {
64
+ jura: ["var(--font-jura)"],
65
+ },
66
+ borderRadius: {
67
+ lg: "var(--radius)",
68
+ md: "calc(var(--radius) - 2px)",
69
+ sm: "calc(var(--radius) - 4px)",
70
+ },
71
+ },
72
+ },
73
+ plugins: [tailwindAnimate],
74
+ };
75
+ export default config;
tsconfig.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["dom", "dom.iterable", "esnext"],
4
+ "allowJs": true,
5
+ "skipLibCheck": true,
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "esModuleInterop": true,
9
+ "module": "esnext",
10
+ "moduleResolution": "bundler",
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "jsx": "preserve",
14
+ "incremental": true,
15
+ "plugins": [
16
+ {
17
+ "name": "next"
18
+ }
19
+ ],
20
+ "paths": {
21
+ "@/*": ["./*"],
22
+ "@/components/*": ["./app/components/*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
整理项目结构.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ def collect_code_files_to_txt(
4
+ repo_path: str,
5
+ output_file: str,
6
+ exclude_extensions=None,
7
+ encoding='utf-8'
8
+ ):
9
+ """
10
+ 将指定目录及其子目录下的所有文件内容整合到同一个文本文件中,
11
+ 但会排除某些不想处理的后缀(比如图片、音频等)。
12
+
13
+ :param repo_path: 项目根目录路径
14
+ :param output_file: 输出的txt文件路径
15
+ :param exclude_extensions: 需要排除的文件后缀列表(如 ['.svg','.png','.jpg']),
16
+ :param encoding: 打开文件使用的编码,默认为 utf-8。
17
+ """
18
+
19
+ with open(output_file, 'w', encoding=encoding) as out_f:
20
+ for root, dirs, files in os.walk(repo_path):
21
+ # 排除一些目录
22
+ if "__pycache__" in dirs:
23
+ dirs.remove("__pycache__")
24
+ if "node_modules" in dirs:
25
+ dirs.remove("node_modules")
26
+ if "models" in dirs:
27
+ dirs.remove("models")
28
+
29
+ for filename in files:
30
+ # 如果排除列表不为空,则检查是否需要跳过
31
+ if exclude_extensions is not None:
32
+ _, ext = os.path.splitext(filename)
33
+ if ext.lower() in exclude_extensions:
34
+ continue # 跳过这些扩展名
35
+
36
+ full_path = os.path.join(root, filename)
37
+
38
+ try:
39
+ with open(full_path, 'r', encoding=encoding) as code_f:
40
+ content = code_f.read()
41
+ except Exception as e:
42
+ print(f"无法读取文件: {full_path}, 原因: {e}")
43
+ continue
44
+
45
+ out_f.write(f"=== File Path: {full_path} ===\n")
46
+ out_f.write(content)
47
+ out_f.write("\n\n")
48
+
49
+ if __name__ == "__main__":
50
+ script_dir = os.path.dirname(os.path.abspath(__file__))
51
+ repo_dir = script_dir
52
+ output_txt = os.path.join(script_dir, "输出.txt")
53
+
54
+ # 这里我们专门排除 .svg, .png, .jpg
55
+ exclude_list = ['.svg', '.png', '.jpg', '.jpeg', '.gif']
56
+
57
+ collect_code_files_to_txt(
58
+ repo_path=repo_dir,
59
+ output_file=output_txt,
60
+ exclude_extensions=exclude_list,
61
+ encoding='utf-8'
62
+ )
63
+
64
+ print(f"文件收集完成,结果已保存到:{output_txt}")
输出.txt ADDED
The diff for this file is too large to render. See raw diff