Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .eslintrc.json +3 -0
- .gitattributes +1 -0
- .gitignore +37 -0
- .prettierrc +1 -0
- app/api/generate-logo/route.ts +146 -0
- app/components/Footer.tsx +20 -0
- app/components/Header.tsx +24 -0
- app/components/InfoToolTip.tsx +9 -0
- app/components/Spinner.tsx +54 -0
- app/components/ui/button.tsx +57 -0
- app/components/ui/input.tsx +22 -0
- app/components/ui/select.tsx +159 -0
- app/components/ui/textarea.tsx +22 -0
- app/components/ui/toast.tsx +129 -0
- app/favicon.ico +0 -0
- app/globals.css +68 -0
- app/layout.tsx +58 -0
- app/lib/domain.ts +8 -0
- app/lib/utils.ts +6 -0
- app/page.tsx +296 -0
- components.json +21 -0
- lib/utils.ts +6 -0
- middleware.ts +23 -0
- next.config.mjs +13 -0
- package-lock.json +0 -0
- package.json +47 -0
- postcss.config.mjs +8 -0
- public/Github.svg +3 -0
- public/abstract.svg +0 -0
- public/flashy.svg +0 -0
- public/generate-icon.svg +9 -0
- public/insan.png +0 -0
- public/minimal.svg +0 -0
- public/modern.svg +10 -0
- public/og-image.png +3 -0
- public/playful.svg +0 -0
- public/side.svg +59 -0
- public/solo.svg +32 -0
- public/stack.svg +59 -0
- public/tech.svg +0 -0
- public/together-ai-logo.svg +22 -0
- public/together-ai-logo1.svg +0 -0
- public/twitter.svg +3 -0
- tailwind.config.ts +75 -0
- tsconfig.json +27 -0
- 整理项目结构.py +64 -0
- 输出.txt +0 -0
.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
|
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
|
|