Dktattooz commited on
Commit
3cb8048
·
verified ·
1 Parent(s): 144777e

Upload 105 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Dockerfile.txt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:20-alpine
2
+ USER root
3
+
4
+ USER 1000
5
+ WORKDIR /usr/src/app
6
+ # Copy package.json and package-lock.json to the container
7
+ COPY --chown=1000 package.json package-lock.json ./
8
+
9
+ # Copy the rest of the application files to the container
10
+ COPY --chown=1000 . .
11
+
12
+ RUN npm install
13
+ RUN npm run build
14
+
15
+ # Expose the application port (assuming your app runs on port 3000)
16
+ EXPOSE 3000
17
+
18
+ # Start the application
19
+ CMD ["npm", "start"]
Project.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import mongoose from "mongoose";
2
+
3
+ const ProjectSchema = new mongoose.Schema({
4
+ space_id: {
5
+ type: String,
6
+ required: true,
7
+ },
8
+ user_id: {
9
+ type: String,
10
+ required: true,
11
+ },
12
+ prompts: {
13
+ type: [String],
14
+ default: [],
15
+ },
16
+ _createdAt: {
17
+ type: Date,
18
+ default: Date.now,
19
+ },
20
+ _updatedAt: {
21
+ type: Date,
22
+ default: Date.now,
23
+ },
24
+ });
25
+
26
+ export default mongoose.models.Project ||
27
+ mongoose.model("Project", ProjectSchema);
README (2).md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: DeepSite v2
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
6
+ sdk: docker
7
+ pinned: true
8
+ app_port: 3000
9
+ license: mit
10
+ short_description: Generate any application with DeepSeek
11
+ models:
12
+ - deepseek-ai/DeepSeek-V3-0324
13
+ - deepseek-ai/DeepSeek-R1-0528
14
+ ---
15
+
16
+ # DeepSite 🐳
17
+
18
+ DeepSite is a coding platform powered by DeepSeek AI, designed to make coding smarter and more efficient. Tailored for developers, data scientists, and AI engineers, it integrates generative AI into your coding projects to enhance creativity and productivity.
19
+
20
+ ## How to use it locally
21
+
22
+ Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
api.ts ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from "axios";
2
+ import MY_TOKEN_KEY from "./get-cookie-name";
3
+
4
+ export const api = axios.create({
5
+ baseURL: `/api`,
6
+ headers: {
7
+ cache: "no-store",
8
+ },
9
+ });
10
+
11
+ export const apiServer = axios.create({
12
+ baseURL: process.env.NEXT_APP_API_URL as string,
13
+ headers: {
14
+ cache: "no-store",
15
+ },
16
+ });
17
+
18
+ api.interceptors.request.use(
19
+ async (config) => {
20
+ // get the token from cookies
21
+ const cookie_name = MY_TOKEN_KEY();
22
+ const token = document.cookie
23
+ .split("; ")
24
+ .find((row) => row.startsWith(`${cookie_name}=`))
25
+ ?.split("=")[1];
26
+ if (token) {
27
+ config.headers.Authorization = `Bearer ${token}`;
28
+ }
29
+ return config;
30
+ },
31
+ (error) => {
32
+ // Handle the error
33
+ return Promise.reject(error);
34
+ }
35
+ );
app-context.tsx.txt ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ "use client";
3
+
4
+ import { useUser } from "@/hooks/useUser";
5
+ import { usePathname, useRouter } from "next/navigation";
6
+ import { useMount } from "react-use";
7
+ import { UserContext } from "@/components/contexts/user-context";
8
+ import { User } from "@/types";
9
+ import { toast } from "sonner";
10
+ import { useBroadcastChannel } from "@/lib/useBroadcastChannel";
11
+
12
+ export default function AppContext({
13
+ children,
14
+ me: initialData,
15
+ }: {
16
+ children: React.ReactNode;
17
+ me?: {
18
+ user: User | null;
19
+ errCode: number | null;
20
+ };
21
+ }) {
22
+ const { loginFromCode, user, logout, loading, errCode } =
23
+ useUser(initialData);
24
+ const pathname = usePathname();
25
+ const router = useRouter();
26
+
27
+ useMount(() => {
28
+ if (!initialData?.user && !user) {
29
+ if ([401, 403].includes(errCode as number)) {
30
+ logout();
31
+ } else if (pathname.includes("/spaces")) {
32
+ if (errCode) {
33
+ toast.error("An error occured while trying to log in");
34
+ }
35
+ // If we did not manage to log in (probs because api is down), we simply redirect to the home page
36
+ router.push("/");
37
+ }
38
+ }
39
+ });
40
+
41
+ const events: any = {};
42
+
43
+ useBroadcastChannel("auth", (message) => {
44
+ if (pathname.includes("/auth/callback")) return;
45
+
46
+ if (!message.code) return;
47
+ if (message.type === "user-oauth" && message?.code && !events.code) {
48
+ loginFromCode(message.code);
49
+ }
50
+ });
51
+
52
+ return (
53
+ <UserContext value={{ user, loading, logout } as any}>
54
+ {children}
55
+ </UserContext>
56
+ );
57
+ }
arrow.svg ADDED
auth (1).ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server";
2
+
3
+ import { headers } from "next/headers";
4
+
5
+ export async function getAuth() {
6
+ const authList = await headers();
7
+ const host = authList.get("host") ?? "localhost:3000";
8
+ const url = host.includes("/spaces/enzostvs")
9
+ ? "enzostvs-deepsite.hf.space"
10
+ : host;
11
+ const redirect_uri =
12
+ `${host.includes("localhost") ? "http://" : "https://"}` +
13
+ url +
14
+ "/auth/callback";
15
+
16
+ const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
17
+ return loginRedirectUrl;
18
+ }
auth.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { User } from "@/types";
2
+ import { NextResponse } from "next/server";
3
+ import { cookies, headers } from "next/headers";
4
+ import MY_TOKEN_KEY from "./get-cookie-name";
5
+
6
+ // UserResponse = type User & { token: string };
7
+ type UserResponse = User & { token: string };
8
+
9
+ export const isAuthenticated = async (): // req: NextRequest
10
+ Promise<UserResponse | NextResponse<unknown> | undefined> => {
11
+ const authHeaders = await headers();
12
+ const cookieStore = await cookies();
13
+ const token = cookieStore.get(MY_TOKEN_KEY())?.value
14
+ ? `Bearer ${cookieStore.get(MY_TOKEN_KEY())?.value}`
15
+ : authHeaders.get("Authorization");
16
+
17
+ if (!token) {
18
+ return NextResponse.json(
19
+ {
20
+ ok: false,
21
+ message: "Wrong castle fam :(",
22
+ },
23
+ {
24
+ status: 401,
25
+ headers: {
26
+ "Content-Type": "application/json",
27
+ },
28
+ }
29
+ );
30
+ }
31
+
32
+ const user = await fetch("https://huggingface.co/api/whoami-v2", {
33
+ headers: {
34
+ Authorization: token,
35
+ },
36
+ method: "GET",
37
+ })
38
+ .then((res) => res.json())
39
+ .catch(() => {
40
+ return NextResponse.json(
41
+ {
42
+ ok: false,
43
+ message: "Invalid token",
44
+ },
45
+ {
46
+ status: 401,
47
+ headers: {
48
+ "Content-Type": "application/json",
49
+ },
50
+ }
51
+ );
52
+ });
53
+ if (!user || !user.id) {
54
+ return NextResponse.json(
55
+ {
56
+ ok: false,
57
+ message: "Invalid token",
58
+ },
59
+ {
60
+ status: 401,
61
+ headers: {
62
+ "Content-Type": "application/json",
63
+ },
64
+ }
65
+ );
66
+ }
67
+
68
+ return {
69
+ ...user,
70
+ token: token.replace("Bearer ", ""),
71
+ };
72
+ };
avatar.tsx.txt ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Avatar({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
12
+ return (
13
+ <AvatarPrimitive.Root
14
+ data-slot="avatar"
15
+ className={cn(
16
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ function AvatarImage({
25
+ className,
26
+ ...props
27
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
28
+ return (
29
+ <AvatarPrimitive.Image
30
+ data-slot="avatar-image"
31
+ className={cn("aspect-square size-full", className)}
32
+ {...props}
33
+ />
34
+ )
35
+ }
36
+
37
+ function AvatarFallback({
38
+ className,
39
+ ...props
40
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
41
+ return (
42
+ <AvatarPrimitive.Fallback
43
+ data-slot="avatar-fallback"
44
+ className={cn(
45
+ "bg-muted flex size-full items-center justify-center rounded-full",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ export { Avatar, AvatarImage, AvatarFallback }
background_noisy.webp ADDED
banner.png ADDED
button.tsx.txt ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 cursor-pointer justify-center gap-2 whitespace-nowrap rounded-full text-sm font-sans font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
14
+ destructive:
15
+ "bg-red-500 text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 [&_svg]:!text-white",
16
+ outline:
17
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
20
+ ghost:
21
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
22
+ lightGray: "bg-neutral-200/60 hover:bg-neutral-200",
23
+ link: "text-primary underline-offset-4 hover:underline",
24
+ ghostDarker:
25
+ "text-white shadow-xs focus-visible:ring-black/40 bg-black/40 hover:bg-black/70",
26
+ black: "bg-neutral-950 text-neutral-300 hover:brightness-110",
27
+ sky: "bg-sky-500 text-white hover:brightness-110",
28
+ },
29
+ size: {
30
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
31
+ sm: "h-8 rounded-full text-[13px] gap-1.5 px-3",
32
+ lg: "h-10 rounded-full px-6 has-[>svg]:px-4",
33
+ icon: "size-9",
34
+ iconXs: "size-7",
35
+ iconXss: "size-6",
36
+ iconXsss: "size-5",
37
+ xs: "h-6 text-xs rounded-full pl-2 pr-2 gap-1",
38
+ },
39
+ },
40
+ defaultVariants: {
41
+ variant: "default",
42
+ size: "default",
43
+ },
44
+ }
45
+ );
46
+
47
+ function Button({
48
+ className,
49
+ variant,
50
+ size,
51
+ asChild = false,
52
+ ...props
53
+ }: React.ComponentProps<"button"> &
54
+ VariantProps<typeof buttonVariants> & {
55
+ asChild?: boolean;
56
+ }) {
57
+ const Comp = asChild ? Slot : "button";
58
+
59
+ return (
60
+ <Comp
61
+ data-slot="button"
62
+ className={cn(buttonVariants({ variant, size, className }))}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ export { Button, buttonVariants };
checkbox.tsx.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
5
+ import { CheckIcon } from "lucide-react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function Checkbox({
10
+ className,
11
+ ...props
12
+ }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
13
+ return (
14
+ <CheckboxPrimitive.Root
15
+ data-slot="checkbox"
16
+ className={cn(
17
+ "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-3.5 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
18
+ className
19
+ )}
20
+ {...props}
21
+ >
22
+ <CheckboxPrimitive.Indicator
23
+ data-slot="checkbox-indicator"
24
+ className="flex items-center justify-center text-current transition-none"
25
+ >
26
+ <CheckIcon className="size-3" />
27
+ </CheckboxPrimitive.Indicator>
28
+ </CheckboxPrimitive.Root>
29
+ );
30
+ }
31
+
32
+ export { Checkbox };
collapsible.tsx.txt ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
+
5
+ function Collapsible({
6
+ ...props
7
+ }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
8
+ return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
9
+ }
10
+
11
+ function CollapsibleTrigger({
12
+ ...props
13
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
14
+ return (
15
+ <CollapsiblePrimitive.CollapsibleTrigger
16
+ data-slot="collapsible-trigger"
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+
22
+ function CollapsibleContent({
23
+ ...props
24
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
25
+ return (
26
+ <CollapsiblePrimitive.CollapsibleContent
27
+ data-slot="collapsible-content"
28
+ {...props}
29
+ />
30
+ )
31
+ }
32
+
33
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
compare-html-diff.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defaultHTML } from "./consts";
2
+
3
+ export const isTheSameHtml = (currentHtml: string): boolean => {
4
+ const normalize = (html: string): string =>
5
+ html
6
+ .replace(/<!--[\s\S]*?-->/g, "")
7
+ .replace(/\s+/g, " ")
8
+ .trim();
9
+
10
+ return normalize(defaultHTML) === normalize(currentHtml);
11
+ };
consts.ts ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const defaultHTML = `<!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>My app</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta charset="utf-8">
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ </head>
9
+ <body class="flex justify-center items-center h-screen overflow-hidden bg-white font-sans text-center px-6">
10
+ <div class="w-full">
11
+ <span class="text-xs rounded-full mb-2 inline-block px-2 py-1 border border-amber-500/15 bg-amber-500/15 text-amber-500">🔥 New version dropped!</span>
12
+ <h1 class="text-4xl lg:text-6xl font-bold font-sans">
13
+ <span class="text-2xl lg:text-4xl text-gray-400 block font-medium">I'm ready to work,</span>
14
+ Ask me anything.
15
+ </h1>
16
+ </div>
17
+ <img src="https://enzostvs-deepsite.hf.space/arrow.svg" class="absolute bottom-8 left-0 w-[100px] transform rotate-[30deg]" />
18
+ <script></script>
19
+ </body>
20
+ </html>
21
+ `;
content.tsx.txt ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Rocket } from "lucide-react";
2
+ import Image from "next/image";
3
+
4
+ import Loading from "@/components/loading";
5
+ import { Button } from "@/components/ui/button";
6
+ import { Input } from "@/components/ui/input";
7
+ import SpaceIcon from "@/assets/space.svg";
8
+ import { Page } from "@/types";
9
+ import { api } from "@/lib/api";
10
+ import { toast } from "sonner";
11
+ import { useState } from "react";
12
+ import { useRouter } from "next/navigation";
13
+
14
+ export const DeployButtonContent = ({
15
+ pages,
16
+ options,
17
+ prompts,
18
+ }: {
19
+ pages: Page[];
20
+ options?: {
21
+ title?: string;
22
+ description?: string;
23
+ };
24
+ prompts: string[];
25
+ }) => {
26
+ const router = useRouter();
27
+ const [loading, setLoading] = useState(false);
28
+
29
+ const [config, setConfig] = useState({
30
+ title: "",
31
+ });
32
+
33
+ const createSpace = async () => {
34
+ if (!config.title) {
35
+ toast.error("Please enter a title for your space.");
36
+ return;
37
+ }
38
+ setLoading(true);
39
+
40
+ try {
41
+ const res = await api.post("/me/projects", {
42
+ title: config.title,
43
+ pages,
44
+ prompts,
45
+ });
46
+ if (res.data.ok) {
47
+ router.push(`/projects/${res.data.path}?deploy=true`);
48
+ } else {
49
+ toast.error(res?.data?.error || "Failed to create space");
50
+ }
51
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
+ } catch (err: any) {
53
+ toast.error(err.response?.data?.error || err.message);
54
+ } finally {
55
+ setLoading(false);
56
+ }
57
+ };
58
+
59
+ return (
60
+ <>
61
+ <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
62
+ <div className="flex items-center justify-center -space-x-4 mb-3">
63
+ <div className="size-9 rounded-full bg-amber-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
64
+ 🚀
65
+ </div>
66
+ <div className="size-11 rounded-full bg-red-200 shadow-2xl flex items-center justify-center z-2">
67
+ <Image src={SpaceIcon} alt="Space Icon" className="size-7" />
68
+ </div>
69
+ <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
70
+ 👻
71
+ </div>
72
+ </div>
73
+ <p className="text-xl font-semibold text-neutral-950">
74
+ Publish as Space!
75
+ </p>
76
+ <p className="text-sm text-neutral-500 mt-1.5">
77
+ {options?.description ??
78
+ "Save and Publish your project to a Space on the Hub. Spaces are a way to share your project with the world."}
79
+ </p>
80
+ </header>
81
+ <main className="space-y-4 p-6">
82
+ <div>
83
+ <p className="text-sm text-neutral-700 mb-2">
84
+ Choose a title for your space:
85
+ </p>
86
+ <Input
87
+ type="text"
88
+ placeholder="My Awesome Website"
89
+ value={config.title}
90
+ onChange={(e) => setConfig({ ...config, title: e.target.value })}
91
+ className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
92
+ />
93
+ </div>
94
+ <div>
95
+ <p className="text-sm text-neutral-700 mb-2">
96
+ Then, let&apos;s publish it!
97
+ </p>
98
+ <Button
99
+ variant="black"
100
+ onClick={createSpace}
101
+ className="relative w-full"
102
+ disabled={loading}
103
+ >
104
+ Publish Space <Rocket className="size-4" />
105
+ {loading && <Loading className="ml-2 size-4 animate-spin" />}
106
+ </Button>
107
+ </div>
108
+ </main>
109
+ </>
110
+ );
111
+ };
declare.d.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ declare module "*.mp3" {
2
+ const src: string;
3
+ export default src;
4
+ }
dialog.tsx.txt ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { XIcon } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function Dialog({
10
+ ...props
11
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
12
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />
13
+ }
14
+
15
+ function DialogTrigger({
16
+ ...props
17
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
18
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
19
+ }
20
+
21
+ function DialogPortal({
22
+ ...props
23
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
24
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
25
+ }
26
+
27
+ function DialogClose({
28
+ ...props
29
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
30
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
31
+ }
32
+
33
+ function DialogOverlay({
34
+ className,
35
+ ...props
36
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
37
+ return (
38
+ <DialogPrimitive.Overlay
39
+ data-slot="dialog-overlay"
40
+ className={cn(
41
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function DialogContent({
50
+ className,
51
+ children,
52
+ showCloseButton = true,
53
+ ...props
54
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
55
+ showCloseButton?: boolean
56
+ }) {
57
+ return (
58
+ <DialogPortal data-slot="dialog-portal">
59
+ <DialogOverlay />
60
+ <DialogPrimitive.Content
61
+ data-slot="dialog-content"
62
+ className={cn(
63
+ "bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
64
+ className
65
+ )}
66
+ {...props}
67
+ >
68
+ {children}
69
+ {showCloseButton && (
70
+ <DialogPrimitive.Close
71
+ data-slot="dialog-close"
72
+ className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
73
+ >
74
+ <XIcon />
75
+ <span className="sr-only">Close</span>
76
+ </DialogPrimitive.Close>
77
+ )}
78
+ </DialogPrimitive.Content>
79
+ </DialogPortal>
80
+ )
81
+ }
82
+
83
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="dialog-header"
87
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
88
+ {...props}
89
+ />
90
+ )
91
+ }
92
+
93
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
94
+ return (
95
+ <div
96
+ data-slot="dialog-footer"
97
+ className={cn(
98
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
99
+ className
100
+ )}
101
+ {...props}
102
+ />
103
+ )
104
+ }
105
+
106
+ function DialogTitle({
107
+ className,
108
+ ...props
109
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
110
+ return (
111
+ <DialogPrimitive.Title
112
+ data-slot="dialog-title"
113
+ className={cn("text-lg leading-none font-semibold", className)}
114
+ {...props}
115
+ />
116
+ )
117
+ }
118
+
119
+ function DialogDescription({
120
+ className,
121
+ ...props
122
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
123
+ return (
124
+ <DialogPrimitive.Description
125
+ data-slot="dialog-description"
126
+ className={cn("text-muted-foreground text-sm", className)}
127
+ {...props}
128
+ />
129
+ )
130
+ }
131
+
132
+ export {
133
+ Dialog,
134
+ DialogClose,
135
+ DialogContent,
136
+ DialogDescription,
137
+ DialogFooter,
138
+ DialogHeader,
139
+ DialogOverlay,
140
+ DialogPortal,
141
+ DialogTitle,
142
+ DialogTrigger,
143
+ }
dropdown-menu.tsx.txt ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function DropdownMenu({
10
+ ...props
11
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
12
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
13
+ }
14
+
15
+ function DropdownMenuPortal({
16
+ ...props
17
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
18
+ return (
19
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
20
+ );
21
+ }
22
+
23
+ function DropdownMenuTrigger({
24
+ ...props
25
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
26
+ return (
27
+ <DropdownMenuPrimitive.Trigger
28
+ data-slot="dropdown-menu-trigger"
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ function DropdownMenuContent({
35
+ className,
36
+ sideOffset = 4,
37
+ ...props
38
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
39
+ return (
40
+ <DropdownMenuPrimitive.Portal>
41
+ <DropdownMenuPrimitive.Content
42
+ data-slot="dropdown-menu-content"
43
+ sideOffset={sideOffset}
44
+ className={cn(
45
+ "bg-popover text-popover-foreground 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 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ </DropdownMenuPrimitive.Portal>
51
+ );
52
+ }
53
+
54
+ function DropdownMenuGroup({
55
+ ...props
56
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
57
+ return (
58
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
59
+ );
60
+ }
61
+
62
+ function DropdownMenuItem({
63
+ className,
64
+ inset,
65
+ variant = "default",
66
+ ...props
67
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
68
+ inset?: boolean;
69
+ variant?: "default" | "destructive";
70
+ }) {
71
+ return (
72
+ <DropdownMenuPrimitive.Item
73
+ data-slot="dropdown-menu-item"
74
+ data-inset={inset}
75
+ data-variant={variant}
76
+ className={cn(
77
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
78
+ className
79
+ )}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ function DropdownMenuCheckboxItem({
86
+ className,
87
+ children,
88
+ checked,
89
+ ...props
90
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
91
+ return (
92
+ <DropdownMenuPrimitive.CheckboxItem
93
+ data-slot="dropdown-menu-checkbox-item"
94
+ className={cn(
95
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
96
+ className
97
+ )}
98
+ checked={checked}
99
+ {...props}
100
+ >
101
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
102
+ <DropdownMenuPrimitive.ItemIndicator>
103
+ <CheckIcon className="size-4" />
104
+ </DropdownMenuPrimitive.ItemIndicator>
105
+ </span>
106
+ {children}
107
+ </DropdownMenuPrimitive.CheckboxItem>
108
+ );
109
+ }
110
+
111
+ function DropdownMenuRadioGroup({
112
+ ...props
113
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
114
+ return (
115
+ <DropdownMenuPrimitive.RadioGroup
116
+ data-slot="dropdown-menu-radio-group"
117
+ {...props}
118
+ />
119
+ );
120
+ }
121
+
122
+ function DropdownMenuRadioItem({
123
+ className,
124
+ children,
125
+ ...props
126
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
127
+ return (
128
+ <DropdownMenuPrimitive.RadioItem
129
+ data-slot="dropdown-menu-radio-item"
130
+ className={cn(
131
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
132
+ className
133
+ )}
134
+ {...props}
135
+ >
136
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
137
+ <DropdownMenuPrimitive.ItemIndicator>
138
+ <CircleIcon className="size-2 fill-current" />
139
+ </DropdownMenuPrimitive.ItemIndicator>
140
+ </span>
141
+ {children}
142
+ </DropdownMenuPrimitive.RadioItem>
143
+ );
144
+ }
145
+
146
+ function DropdownMenuLabel({
147
+ className,
148
+ inset,
149
+ ...props
150
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
151
+ inset?: boolean;
152
+ }) {
153
+ return (
154
+ <DropdownMenuPrimitive.Label
155
+ data-slot="dropdown-menu-label"
156
+ data-inset={inset}
157
+ className={cn(
158
+ "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
159
+ className
160
+ )}
161
+ {...props}
162
+ />
163
+ );
164
+ }
165
+
166
+ function DropdownMenuSeparator({
167
+ className,
168
+ ...props
169
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
170
+ return (
171
+ <DropdownMenuPrimitive.Separator
172
+ data-slot="dropdown-menu-separator"
173
+ className={cn("bg-border -mx-1 my-1 h-px", className)}
174
+ {...props}
175
+ />
176
+ );
177
+ }
178
+
179
+ function DropdownMenuShortcut({
180
+ className,
181
+ ...props
182
+ }: React.ComponentProps<"span">) {
183
+ return (
184
+ <span
185
+ data-slot="dropdown-menu-shortcut"
186
+ className={cn(
187
+ "text-muted-foreground ml-auto text-xs tracking-widest",
188
+ className
189
+ )}
190
+ {...props}
191
+ />
192
+ );
193
+ }
194
+
195
+ function DropdownMenuSub({
196
+ ...props
197
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
198
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
199
+ }
200
+
201
+ function DropdownMenuSubTrigger({
202
+ className,
203
+ inset,
204
+ children,
205
+ ...props
206
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
207
+ inset?: boolean;
208
+ }) {
209
+ return (
210
+ <DropdownMenuPrimitive.SubTrigger
211
+ data-slot="dropdown-menu-sub-trigger"
212
+ data-inset={inset}
213
+ className={cn(
214
+ "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
215
+ className
216
+ )}
217
+ {...props}
218
+ >
219
+ {children}
220
+ <ChevronRightIcon className="ml-auto size-4" />
221
+ </DropdownMenuPrimitive.SubTrigger>
222
+ );
223
+ }
224
+
225
+ function DropdownMenuSubContent({
226
+ className,
227
+ ...props
228
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
229
+ return (
230
+ <DropdownMenuPrimitive.SubContent
231
+ data-slot="dropdown-menu-sub-content"
232
+ className={cn(
233
+ "bg-popover text-popover-foreground 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 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
234
+ className
235
+ )}
236
+ {...props}
237
+ />
238
+ );
239
+ }
240
+
241
+ export {
242
+ DropdownMenu,
243
+ DropdownMenuPortal,
244
+ DropdownMenuTrigger,
245
+ DropdownMenuContent,
246
+ DropdownMenuGroup,
247
+ DropdownMenuLabel,
248
+ DropdownMenuItem,
249
+ DropdownMenuCheckboxItem,
250
+ DropdownMenuRadioGroup,
251
+ DropdownMenuRadioItem,
252
+ DropdownMenuSeparator,
253
+ DropdownMenuShortcut,
254
+ DropdownMenuSub,
255
+ DropdownMenuSubTrigger,
256
+ DropdownMenuSubContent,
257
+ };
favicon.ico ADDED
fireworks-ai.svg ADDED
follow-up-tooltip.tsx.txt ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Popover,
3
+ PopoverContent,
4
+ PopoverTrigger,
5
+ } from "@/components/ui/popover";
6
+ import { Info } from "lucide-react";
7
+
8
+ export const FollowUpTooltip = () => {
9
+ return (
10
+ <Popover>
11
+ <PopoverTrigger asChild>
12
+ <Info className="size-3 text-neutral-300 cursor-pointer" />
13
+ </PopoverTrigger>
14
+ <PopoverContent
15
+ align="start"
16
+ className="!rounded-2xl !p-0 min-w-xs text-center overflow-hidden"
17
+ >
18
+ <header className="bg-neutral-950 px-4 py-3 border-b border-neutral-700/70">
19
+ <p className="text-base text-neutral-200 font-semibold">
20
+ ⚡ Faster, Smarter Updates
21
+ </p>
22
+ </header>
23
+ <main className="p-4">
24
+ <p className="text-neutral-300 text-sm">
25
+ Using the Diff-Patch system, allow DeepSite to intelligently update
26
+ your project without rewritting the entire codebase.
27
+ </p>
28
+ <p className="text-neutral-500 text-sm mt-2">
29
+ This means faster updates, less data usage, and a more efficient
30
+ development process.
31
+ </p>
32
+ </main>
33
+ </PopoverContent>
34
+ </Popover>
35
+ );
36
+ };
get-cookie-name.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ export default function MY_TOKEN_KEY() {
2
+ return "deepsite-auth-token";
3
+ }
gitignore.txt ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
globals.css ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ @theme inline {
7
+ --color-background: var(--background);
8
+ --color-foreground: var(--foreground);
9
+ --font-sans: var(--font-inter-sans);
10
+ --font-mono: var(--font-ptSans-mono);
11
+ --color-sidebar-ring: var(--sidebar-ring);
12
+ --color-sidebar-border: var(--sidebar-border);
13
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
14
+ --color-sidebar-accent: var(--sidebar-accent);
15
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
16
+ --color-sidebar-primary: var(--sidebar-primary);
17
+ --color-sidebar-foreground: var(--sidebar-foreground);
18
+ --color-sidebar: var(--sidebar);
19
+ --color-chart-5: var(--chart-5);
20
+ --color-chart-4: var(--chart-4);
21
+ --color-chart-3: var(--chart-3);
22
+ --color-chart-2: var(--chart-2);
23
+ --color-chart-1: var(--chart-1);
24
+ --color-ring: var(--ring);
25
+ --color-input: var(--input);
26
+ --color-border: var(--border);
27
+ --color-destructive: var(--destructive);
28
+ --color-accent-foreground: var(--accent-foreground);
29
+ --color-accent: var(--accent);
30
+ --color-muted-foreground: var(--muted-foreground);
31
+ --color-muted: var(--muted);
32
+ --color-secondary-foreground: var(--secondary-foreground);
33
+ --color-secondary: var(--secondary);
34
+ --color-primary-foreground: var(--primary-foreground);
35
+ --color-primary: var(--primary);
36
+ --color-popover-foreground: var(--popover-foreground);
37
+ --color-popover: var(--popover);
38
+ --color-card-foreground: var(--card-foreground);
39
+ --color-card: var(--card);
40
+ --radius-sm: calc(var(--radius) - 4px);
41
+ --radius-md: calc(var(--radius) - 2px);
42
+ --radius-lg: var(--radius);
43
+ --radius-xl: calc(var(--radius) + 4px);
44
+ }
45
+
46
+ :root {
47
+ --radius: 0.625rem;
48
+ --background: oklch(1 0 0);
49
+ --foreground: oklch(0.145 0 0);
50
+ --card: oklch(1 0 0);
51
+ --card-foreground: oklch(0.145 0 0);
52
+ --popover: oklch(1 0 0);
53
+ --popover-foreground: oklch(0.145 0 0);
54
+ --primary: oklch(0.205 0 0);
55
+ --primary-foreground: oklch(0.985 0 0);
56
+ --secondary: oklch(0.97 0 0);
57
+ --secondary-foreground: oklch(0.205 0 0);
58
+ --muted: oklch(0.97 0 0);
59
+ --muted-foreground: oklch(0.556 0 0);
60
+ --accent: oklch(0.97 0 0);
61
+ --accent-foreground: oklch(0.205 0 0);
62
+ --destructive: oklch(0.577 0.245 27.325);
63
+ --border: oklch(0.922 0 0);
64
+ --input: oklch(0.922 0 0);
65
+ --ring: oklch(0.708 0 0);
66
+ --chart-1: oklch(0.646 0.222 41.116);
67
+ --chart-2: oklch(0.6 0.118 184.704);
68
+ --chart-3: oklch(0.398 0.07 227.392);
69
+ --chart-4: oklch(0.828 0.189 84.429);
70
+ --chart-5: oklch(0.769 0.188 70.08);
71
+ --sidebar: oklch(0.985 0 0);
72
+ --sidebar-foreground: oklch(0.145 0 0);
73
+ --sidebar-primary: oklch(0.205 0 0);
74
+ --sidebar-primary-foreground: oklch(0.985 0 0);
75
+ --sidebar-accent: oklch(0.97 0 0);
76
+ --sidebar-accent-foreground: oklch(0.205 0 0);
77
+ --sidebar-border: oklch(0.922 0 0);
78
+ --sidebar-ring: oklch(0.708 0 0);
79
+ }
80
+
81
+ .dark {
82
+ --background: oklch(0.145 0 0);
83
+ --foreground: oklch(0.985 0 0);
84
+ --card: oklch(0.205 0 0);
85
+ --card-foreground: oklch(0.985 0 0);
86
+ --popover: oklch(0.205 0 0);
87
+ --popover-foreground: oklch(0.985 0 0);
88
+ --primary: oklch(0.922 0 0);
89
+ --primary-foreground: oklch(0.205 0 0);
90
+ --secondary: oklch(0.269 0 0);
91
+ --secondary-foreground: oklch(0.985 0 0);
92
+ --muted: oklch(0.269 0 0);
93
+ --muted-foreground: oklch(0.708 0 0);
94
+ --accent: oklch(0.269 0 0);
95
+ --accent-foreground: oklch(0.985 0 0);
96
+ --destructive: oklch(0.704 0.191 22.216);
97
+ --border: oklch(1 0 0 / 10%);
98
+ --input: oklch(1 0 0 / 15%);
99
+ --ring: oklch(0.556 0 0);
100
+ --chart-1: oklch(0.488 0.243 264.376);
101
+ --chart-2: oklch(0.696 0.17 162.48);
102
+ --chart-3: oklch(0.769 0.188 70.08);
103
+ --chart-4: oklch(0.627 0.265 303.9);
104
+ --chart-5: oklch(0.645 0.246 16.439);
105
+ --sidebar: oklch(0.205 0 0);
106
+ --sidebar-foreground: oklch(0.985 0 0);
107
+ --sidebar-primary: oklch(0.488 0.243 264.376);
108
+ --sidebar-primary-foreground: oklch(0.985 0 0);
109
+ --sidebar-accent: oklch(0.269 0 0);
110
+ --sidebar-accent-foreground: oklch(0.985 0 0);
111
+ --sidebar-border: oklch(1 0 0 / 10%);
112
+ --sidebar-ring: oklch(0.556 0 0);
113
+ }
114
+
115
+ @layer base {
116
+ * {
117
+ @apply border-border outline-ring/50;
118
+ }
119
+ body {
120
+ @apply bg-background text-foreground;
121
+ }
122
+ html {
123
+ @apply scroll-smooth;
124
+ }
125
+ }
126
+
127
+ .background__noisy {
128
+ @apply bg-blend-normal pointer-events-none opacity-90;
129
+ background-size: 25ww auto;
130
+ background-image: url("/background_noisy.webp");
131
+ @apply fixed w-screen h-screen -z-1 top-0 left-0;
132
+ }
133
+
134
+ .monaco-editor .margin {
135
+ @apply !bg-neutral-900;
136
+ }
137
+ .monaco-editor .monaco-editor-background {
138
+ @apply !bg-neutral-900;
139
+ }
140
+ .monaco-editor .line-numbers {
141
+ @apply !text-neutral-500;
142
+ }
143
+
144
+ .matched-line {
145
+ @apply bg-sky-500/30;
146
+ }
grid-pattern.tsx.txt ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useId } from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ interface GridPatternProps extends React.SVGProps<SVGSVGElement> {
5
+ width?: number;
6
+ height?: number;
7
+ x?: number;
8
+ y?: number;
9
+ squares?: Array<[x: number, y: number]>;
10
+ strokeDasharray?: string;
11
+ className?: string;
12
+ [key: string]: unknown;
13
+ }
14
+
15
+ export function GridPattern({
16
+ width = 40,
17
+ height = 40,
18
+ x = -1,
19
+ y = -1,
20
+ strokeDasharray = "0",
21
+ squares,
22
+ className,
23
+ ...props
24
+ }: GridPatternProps) {
25
+ const id = useId();
26
+
27
+ return (
28
+ <svg
29
+ aria-hidden="true"
30
+ className={cn(
31
+ "pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-neutral-700 -z-[1]",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ <defs>
37
+ <pattern
38
+ id={id}
39
+ width={width}
40
+ height={height}
41
+ patternUnits="userSpaceOnUse"
42
+ x={x}
43
+ y={y}
44
+ >
45
+ <path
46
+ d={`M.5 ${height}V.5H${width}`}
47
+ fill="none"
48
+ strokeDasharray={strokeDasharray}
49
+ />
50
+ </pattern>
51
+ </defs>
52
+ <rect width="100%" height="100%" strokeWidth={0} fill={`url(#${id})`} />
53
+ {squares && (
54
+ <svg x={x} y={y} className="overflow-visible">
55
+ {squares.map(([x, y]) => (
56
+ <rect
57
+ strokeWidth="0"
58
+ key={`${x}-${y}`}
59
+ width={width - 1}
60
+ height={height - 1}
61
+ x={x * width + 1}
62
+ y={y * height + 1}
63
+ />
64
+ ))}
65
+ </svg>
66
+ )}
67
+ </svg>
68
+ );
69
+ }
groq.svg ADDED
html-tag-to-text.ts ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const htmlTagToText = (tagName: string): string => {
2
+ switch (tagName.toLowerCase()) {
3
+ case "h1":
4
+ return "Heading 1";
5
+ case "h2":
6
+ return "Heading 2";
7
+ case "h3":
8
+ return "Heading 3";
9
+ case "h4":
10
+ return "Heading 4";
11
+ case "h5":
12
+ return "Heading 5";
13
+ case "h6":
14
+ return "Heading 6";
15
+ case "p":
16
+ return "Text Paragraph";
17
+ case "span":
18
+ return "Inline Text";
19
+ case "button":
20
+ return "Button";
21
+ case "input":
22
+ return "Input Field";
23
+ case "select":
24
+ return "Select Dropdown";
25
+ case "textarea":
26
+ return "Text Area";
27
+ case "form":
28
+ return "Form";
29
+ case "table":
30
+ return "Table";
31
+ case "thead":
32
+ return "Table Header";
33
+ case "tbody":
34
+ return "Table Body";
35
+ case "tr":
36
+ return "Table Row";
37
+ case "th":
38
+ return "Table Header Cell";
39
+ case "td":
40
+ return "Table Data Cell";
41
+ case "nav":
42
+ return "Navigation";
43
+ case "header":
44
+ return "Header";
45
+ case "footer":
46
+ return "Footer";
47
+ case "section":
48
+ return "Section";
49
+ case "article":
50
+ return "Article";
51
+ case "aside":
52
+ return "Aside";
53
+ case "div":
54
+ return "Block";
55
+ case "main":
56
+ return "Main Content";
57
+ case "details":
58
+ return "Details";
59
+ case "summary":
60
+ return "Summary";
61
+ case "code":
62
+ return "Code Snippet";
63
+ case "pre":
64
+ return "Preformatted Text";
65
+ case "kbd":
66
+ return "Keyboard Input";
67
+ case "label":
68
+ return "Label";
69
+ case "canvas":
70
+ return "Canvas";
71
+ case "svg":
72
+ return "SVG Graphic";
73
+ case "video":
74
+ return "Video Player";
75
+ case "audio":
76
+ return "Audio Player";
77
+ case "iframe":
78
+ return "Embedded Frame";
79
+ case "link":
80
+ return "Link";
81
+ case "a":
82
+ return "Link";
83
+ case "img":
84
+ return "Image";
85
+ case "ul":
86
+ return "Unordered List";
87
+ case "ol":
88
+ return "Ordered List";
89
+ case "li":
90
+ return "List Item";
91
+ case "blockquote":
92
+ return "Blockquote";
93
+ default:
94
+ return tagName.charAt(0).toUpperCase() + tagName.slice(1);
95
+ }
96
+ };
hyperbolic.svg ADDED
iframe-detector.tsx.txt ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import IframeWarningModal from "./iframe-warning-modal";
5
+
6
+ export default function IframeDetector() {
7
+ const [showWarning, setShowWarning] = useState(false);
8
+
9
+ useEffect(() => {
10
+ // Helper function to check if a hostname is from allowed domains
11
+ const isAllowedDomain = (hostname: string) => {
12
+ const host = hostname.toLowerCase();
13
+ return (
14
+ host.endsWith(".huggingface.co") ||
15
+ host.endsWith(".hf.co") ||
16
+ host === "huggingface.co" ||
17
+ host === "hf.co"
18
+ );
19
+ };
20
+
21
+ // Check if the current window is in an iframe
22
+ const isInIframe = () => {
23
+ try {
24
+ return window.self !== window.top;
25
+ } catch {
26
+ // If we can't access window.top due to cross-origin restrictions,
27
+ // we're likely in an iframe
28
+ return true;
29
+ }
30
+ };
31
+
32
+ // Additional check: compare window location with parent location
33
+ const isEmbedded = () => {
34
+ try {
35
+ return window.location !== window.parent.location;
36
+ } catch {
37
+ // Cross-origin iframe
38
+ return true;
39
+ }
40
+ };
41
+
42
+ // Check if we're in an iframe from a non-allowed domain
43
+ const shouldShowWarning = () => {
44
+ if (!isInIframe() && !isEmbedded()) {
45
+ return false; // Not in an iframe
46
+ }
47
+
48
+ try {
49
+ // Try to get the parent's hostname
50
+ const parentHostname = window.parent.location.hostname;
51
+ return !isAllowedDomain(parentHostname);
52
+ } catch {
53
+ // Cross-origin iframe - try to get referrer instead
54
+ try {
55
+ if (document.referrer) {
56
+ const referrerUrl = new URL(document.referrer);
57
+ return !isAllowedDomain(referrerUrl.hostname);
58
+ }
59
+ } catch {
60
+ // If we can't determine the parent domain, assume it's not allowed
61
+ }
62
+ return true;
63
+ }
64
+ };
65
+
66
+ if (shouldShowWarning()) {
67
+ // Show warning modal instead of redirecting immediately
68
+ setShowWarning(true);
69
+ }
70
+ }, []);
71
+
72
+ return (
73
+ <IframeWarningModal isOpen={showWarning} onOpenChange={setShowWarning} />
74
+ );
75
+ }
iframe-warning-modal.tsx.txt ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from "@/components/ui/dialog";
11
+ import { Button } from "@/components/ui/button";
12
+ import { ExternalLink, AlertTriangle } from "lucide-react";
13
+
14
+ interface IframeWarningModalProps {
15
+ isOpen: boolean;
16
+ onOpenChange: (open: boolean) => void;
17
+ }
18
+
19
+ export default function IframeWarningModal({
20
+ isOpen,
21
+ }: // onOpenChange,
22
+ IframeWarningModalProps) {
23
+ const handleVisitSite = () => {
24
+ window.open("https://deepsite.hf.co", "_blank");
25
+ };
26
+
27
+ return (
28
+ <Dialog open={isOpen} onOpenChange={() => {}}>
29
+ <DialogContent className="sm:max-w-md">
30
+ <DialogHeader>
31
+ <div className="flex items-center gap-2">
32
+ <AlertTriangle className="h-5 w-5 text-red-500" />
33
+ <DialogTitle>Unauthorized Embedding</DialogTitle>
34
+ </div>
35
+ <DialogDescription className="text-left">
36
+ You&apos;re viewing DeepSite through an unauthorized iframe. For the
37
+ best experience and security, please visit the official website
38
+ directly.
39
+ </DialogDescription>
40
+ </DialogHeader>
41
+
42
+ <div className="bg-muted/50 rounded-lg p-4 space-y-2">
43
+ <p className="text-sm font-medium">Why visit the official site?</p>
44
+ <ul className="text-sm text-muted-foreground space-y-1">
45
+ <li>• Better performance and security</li>
46
+ <li>• Full functionality access</li>
47
+ <li>• Latest features and updates</li>
48
+ <li>• Proper authentication support</li>
49
+ </ul>
50
+ </div>
51
+
52
+ <DialogFooter className="flex-col sm:flex-row gap-2">
53
+ <Button onClick={handleVisitSite} className="w-full sm:w-auto">
54
+ <ExternalLink className="mr-2 h-4 w-4" />
55
+ Visit Deepsite.hf.co
56
+ </Button>
57
+ </DialogFooter>
58
+ </DialogContent>
59
+ </Dialog>
60
+ );
61
+ }
index (1).html ADDED
@@ -0,0 +1,562 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Enhanced ID Generator Tool</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <!-- Tailwind CSS -->
8
+ <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
9
+ <!-- Font Awesome -->
10
+ <link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.5.2/css/all.min.css" rel="stylesheet">
11
+ <!-- Google Fonts -->
12
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
13
+ <!-- Chart.js -->
14
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
15
+ <style>
16
+ html, body { font-family: 'Roboto', sans-serif; background: #f3f4f6; color: #222;}
17
+ .shadow-card { box-shadow: 0 6px 24px rgb(0 0 0 / 7%),0 1.5px 5px rgb(0 0 0 / 4%);}
18
+ .admin-divider {border-bottom: 1.5px solid #e5e7eb;}
19
+ .fa {vertical-align: middle;}
20
+ /* Hide scrollbars for PDF export */
21
+ ::-webkit-scrollbar {display: none;}
22
+ * { scrollbar-width: none; -ms-overflow-style: none;}
23
+ /* Remove number input arrows for consistency in PDF export*/
24
+ input[type=number]::-webkit-inner-spin-button,
25
+ input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
26
+ input[type=number] { -moz-appearance: textfield;}
27
+ /* Card table for generated list */
28
+ #generatedCardsTable th, #generatedCardsTable td {padding: 0.25rem 0.5rem; border-right: 1px solid #eee;}
29
+ #generatedCardsTable tr {border-top: 1px solid #eee;}
30
+ /* Hide file input for a better button */
31
+ #importFile {display:none;}
32
+ .table-wrap {overflow-x: auto;}
33
+ @media print {
34
+ html, body {background: white;}
35
+ .print-hide {display: none !important;}
36
+ }
37
+ </style>
38
+ </head>
39
+ <body>
40
+ <!-- HEADER -->
41
+ <header class="flex items-center bg-gradient-to-r from-green-400 to-green-200 p-4 shadow-lg">
42
+ <div class="flex items-center space-x-3">
43
+ <i class="fa fa-id-card text-2xl text-white"></i>
44
+ <span class="text-2xl font-bold text-white tracking-wide">Fake ID Data Tools+</span>
45
+ </div>
46
+ <button onclick="showAdmin(true)" class="ml-auto px-6 py-2 rounded bg-black text-white font-semibold shadow-card print-hide transition hover:bg-green-800 text-sm"><i class="fas fa-user-cog mr-2"></i>Admin Panel</button>
47
+ </header>
48
+ <main class="max-w-5xl mx-auto p-4 space-y-6 bg-white mt-6 rounded-lg shadow-card">
49
+ <!-- CARD GENERATION -->
50
+ <section>
51
+ <div class="flex items-center mb-2">
52
+ <i class="fa fa-credit-card text-green-600 mr-2"></i>
53
+ <h2 class="text-xl font-bold text-green-800">Card Generation</h2>
54
+ </div>
55
+ <div class="rounded bg-green-50 p-4 shadow-card border border-green-200">
56
+ <div class="grid md:grid-cols-6 sm:grid-cols-2 grid-cols-1 gap-3 mb-4">
57
+ <div><label class="block text-xs mb-1 font-semibold">Card Holder Name</label><input id="holderInput" type="text" class="w-full rounded border px-2 py-1" placeholder="Random if blank"></div>
58
+ <div><label class="block text-xs mb-1 font-semibold">Phone Number</label><input id="phoneInput" type="text" class="w-full rounded border px-2 py-1" placeholder="Random if blank"></div>
59
+ <div><label class="block text-xs mb-1 font-semibold">Date of Birth</label><input id="dobInput" type="date" class="w-full rounded border px-2 py-1"></div>
60
+ <div><label class="block text-xs mb-1 font-semibold">Number to Generate <span class="text-gray-400">(max 5000)</span></label><input id="qtyInput" min="1" max="5000" type="number" value="1" class="w-full rounded border px-2 py-1"></div>
61
+ <div><label class="block text-xs mb-1 font-semibold">BIN Source</label><input id="binInput" type="text" class="w-full rounded border px-2 py-1" placeholder="Leave blank for random"></div>
62
+ <div><label class="block text-xs mb-1 font-semibold">Bank Name</label><input id="bankInput" type="text" class="w-full rounded border px-2 py-1" placeholder="Optional"></div>
63
+ </div>
64
+ <div class="grid md:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-3 mb-3">
65
+ <div><label class="block text-xs mb-1 font-semibold">Email</label><input id="emailInput" type="text" class="w-full rounded border px-2 py-1" placeholder="Auto-generated if blank"></div>
66
+ <div><label class="block text-xs mb-1 font-semibold">Address</label><input id="addressInput" type="text" class="w-full rounded border px-2 py-1" placeholder="Optional"></div>
67
+ <div class="flex items-center mt-7">
68
+ <input id="randBinCheck" type="checkbox" class="mr-2"><label for="randBinCheck" class="text-xs font-semibold">System Random BIN</label>
69
+ </div>
70
+ </div>
71
+ <div class="flex flex-wrap space-x-2 mt-2 mb-2 items-center">
72
+ <button id="generateBtn" class="bg-green-600 hover:bg-green-700 text-white font-bold rounded-md px-4 py-2 shadow-card flex items-center mb-2"><i class="fa-solid fa-circle-plus mr-2"></i>Generate Cards</button>
73
+ <label for="importFile" class="bg-blue-400 hover:bg-blue-600 text-white font-bold rounded-md px-4 py-2 shadow-card flex items-center cursor-pointer mb-2"><i class="fa fa-file-upload mr-2"></i>Import Data</label>
74
+ <input type="file" id="importFile" accept=".txt,.csv,.json,.pdf">
75
+ <button onclick="clearHistory()" class="bg-red-400 hover:bg-red-600 text-white font-bold rounded-md px-4 py-2 shadow-card flex items-center mb-2"><i class="fa fa-trash mr-2"></i>Clear History</button>
76
+ <button onclick="exportHistory('csv')" class="bg-gray-800 hover:bg-gray-900 text-white font-bold rounded-md px-3 py-2 shadow-card flex items-center text-xs mb-2"><i class="fa fa-file-csv mr-2"></i>Export CSV</button>
77
+ <button onclick="exportHistory('json')" class="bg-gray-800 hover:bg-gray-900 text-white font-bold rounded-md px-3 py-2 shadow-card flex items-center text-xs mb-2"><i class="fa fa-file-code mr-2"></i>Export JSON</button>
78
+ <button onclick="exportHistory('txt')" class="bg-gray-800 hover:bg-gray-900 text-white font-bold rounded-md px-3 py-2 shadow-card flex items-center text-xs mb-2"><i class="fa fa-file-lines mr-2"></i>Export TXT</button>
79
+ <button onclick="copyAllCards()" class="bg-purple-500 hover:bg-purple-600 text-white font-bold rounded-md px-3 py-2 shadow-card flex items-center text-xs mb-2"><i class="fa fa-copy mr-2"></i>Copy All</button>
80
+ </div>
81
+ </div>
82
+ </section>
83
+ <!-- GENERATED CARDS HISTORY -->
84
+ <section>
85
+ <h3 class="text-lg font-semibold mt-8 mb-1 text-blue-800 flex items-center"><i class="fa fa-history mr-2"></i>Generated Cards History</h3>
86
+ <div class="table-wrap border rounded bg-blue-50 border-blue-200 shadow-card pb-2 pt-2 px-2">
87
+ <input id="historySearch" type="text" class="w-full mb-2 rounded px-2 py-1 border border-blue-100" placeholder="Search History (Name, Card, Bank, etc.)..." oninput="filterHistory()">
88
+ <table id="generatedCardsTable" class="w-full text-xs border-collapse">
89
+ <thead>
90
+ <tr class="bg-blue-100">
91
+ <th>#</th>
92
+ <th>Card Holder</th>
93
+ <th>Number</th>
94
+ <th>MM/YY</th>
95
+ <th>CVV</th>
96
+ <th>Bank</th>
97
+ <th>BIN</th>
98
+ <th>Email</th>
99
+ <th>IP</th>
100
+ <th>Status</th>
101
+ </tr>
102
+ </thead>
103
+ <tbody id="cardHistory"></tbody>
104
+ </table>
105
+ <div id="noCardMsg" class="text-center text-gray-400 py-4">No generated cards yet.</div>
106
+ </div>
107
+ </section>
108
+ <!-- CARD VALIDATOR -->
109
+ <section id="validator-section">
110
+ <h3 class="text-lg font-semibold mt-6 mb-3 flex items-center text-yellow-700"><i class="fa fa-magnifying-glass mr-2"></i>Card Validator (Simulated)</h3>
111
+ <div class="flex flex-col sm:flex-row items-center mb-2">
112
+ <input id="validateCardNumber" type="text" class="w-full sm:w-1/2 rounded border px-2 py-2 mr-4 mb-2 sm:mb-0" placeholder="Paste or type card number">
113
+ <button onclick="validateCard()" class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-6 rounded shadow-card mr-2 flex items-center"><i class="fa fa-check mr-2"></i>Validate</button>
114
+ <button onclick="autoValidateCard()" class="bg-yellow-200 text-black hover:bg-yellow-300 rounded shadow px-3 py-2 font-bold flex items-center" title="Auto Validate"><i class="fa fa-bolt"></i> Auto</button>
115
+ </div>
116
+ <div id="validateStatus" class="font-bold mt-2 mb-3 ml-1"></div>
117
+ </section>
118
+ <!-- SIMULATED ANALYZERS -->
119
+ <section>
120
+ <h3 class="text-lg font-semibold mt-8 mb-3 flex items-center text-indigo-700"><i class="fa fa-flask mr-2"></i>Simulated Analyzers</h3>
121
+ <div class="grid md:grid-cols-3 gap-3">
122
+ <!-- Fraud Score Simulator -->
123
+ <div class="bg-indigo-50 border rounded shadow-card p-3">
124
+ <div class="flex items-center mb-1 text-indigo-700 font-semibold"><i class="fa fa-shield-halved mr-2"></i>Fraud Score Simulator</div>
125
+ <div class="flex mb-2">
126
+ <input id="fraudCardInput" type="text" class="w-full rounded border px-2 py-1" placeholder="Enter card number">
127
+ <button onclick="analyzeFraudScore()" class="ml-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded px-3 flex items-center"><i class="fa fa-vial mr-2"></i>Analyze</button>
128
+ <button onclick="autoAnalyzeFraud()" class="ml-2 bg-indigo-300 hover:bg-indigo-400 text-black rounded px-2 flex items-center" title="Auto Analyze"><i class="fa fa-bolt"></i></button>
129
+ </div>
130
+ <div id="fraudScoreResult" class="mt-2 font-bold"></div>
131
+ </div>
132
+ <!-- IP Address Simulator -->
133
+ <div class="bg-blue-50 border rounded shadow-card p-3">
134
+ <div class="flex items-center mb-1 text-blue-700 font-semibold"><i class="fa fa-globe mr-2"></i>IP Address Simulator</div>
135
+ <div class="flex mb-2">
136
+ <input id="ipInput" type="text" class="w-full rounded border px-2 py-1" placeholder="Enter IPv4 or IPv6">
137
+ <button onclick="analyzeIP()" class="ml-2 bg-blue-600 hover:bg-blue-700 text-white rounded px-3 flex items-center"><i class="fa fa-network-wired mr-2"></i>Analyze</button>
138
+ <button onclick="autoAnalyzeIP()" class="ml-2 bg-blue-300 hover:bg-blue-400 text-black rounded px-2 flex items-center" title="Auto Analyze"><i class="fa fa-bolt"></i></button>
139
+ </div>
140
+ <div id="ipResult" class="mt-2 font-bold"></div>
141
+ </div>
142
+ <!-- Email Analyzer Simulator -->
143
+ <div class="bg-green-50 border rounded shadow-card p-3">
144
+ <div class="flex items-center mb-1 text-green-800 font-semibold"><i class="fa fa-envelope mr-2"></i>Email Analyzer Simulator</div>
145
+ <div class="flex mb-2">
146
+ <input id="emailAnalyzerInput" type="text" class="w-full rounded border px-2 py-1" placeholder="Enter email address">
147
+ <button onclick="analyzeEmail()" class="ml-2 bg-green-700 hover:bg-green-800 text-white rounded px-3 flex items-center"><i class="fa fa-envelope-open-text mr-2"></i>Analyze</button>
148
+ <button onclick="autoAnalyzeEmail()" class="ml-2 bg-green-200 hover:bg-green-300 text-black rounded px-2 flex items-center" title="Auto Analyze"><i class="fa fa-bolt"></i></button>
149
+ </div>
150
+ <div id="emailResult" class="mt-2 font-bold"></div>
151
+ </div>
152
+ </div>
153
+ </section>
154
+ <!-- SSN TOOLS -->
155
+ <section>
156
+ <h3 class="text-lg font-semibold mt-8 mb-3 flex items-center text-blue-900"><i class="fa fa-address-card mr-2"></i>SSN Tools (Fake Data)</h3>
157
+ <div class="flex flex-col md:flex-row gap-4 mb-2">
158
+ <!-- Fake SSN Generator -->
159
+ <div class="bg-blue-100 border rounded shadow-card p-3 flex-1">
160
+ <div class="flex items-center mb-2 text-blue-900 font-semibold"><i class="fa fa-magic mr-2"></i>Fake SSN Generator</div>
161
+ <div class="flex mb-2 items-center gap-2">
162
+ <input id="ssnQtyInput" type="number" min="1" max="5000" value="1" class="rounded border px-2 py-1 w-20">
163
+ <button onclick="generateSSNs()" class="bg-blue-700 hover:bg-blue-900 text-white rounded px-3 flex items-center font-bold"><i class="fa fa-plus mr-2"></i>Generate SSNs</button>
164
+ <button onclick="autoGenerateSSN()" class="bg-blue-300 hover:bg-blue-400 text-black rounded px-2 flex items-center" title="Auto Generate"><i class="fa fa-bolt"></i></button>
165
+ </div>
166
+ <div>
167
+ <textarea id="ssnGenResult" class="w-full h-16 rounded px-2 py-1 border" readonly></textarea>
168
+ </div>
169
+ </div>
170
+ <!-- SSN Validator -->
171
+ <div class="bg-blue-100 border rounded shadow-card p-3 flex-1">
172
+ <div class="flex items-center mb-2 text-blue-900 font-semibold"><i class="fa fa-spell-check mr-2"></i>SSN Validator (Basic)</div>
173
+ <div class="flex mb-2 items-center gap-2">
174
+ <input id="ssnValidateInput" type="text" class="rounded border px-2 py-1 w-48" placeholder="SSN (XXX-XX-XXXX)">
175
+ <button onclick="validateSSN()" class="bg-blue-700 hover:bg-blue-900 text-white rounded px-3 flex items-center font-bold"><i class="fa fa-check mr-2"></i>Validate</button>
176
+ <button onclick="autoValidateSSN()" class="bg-blue-300 hover:bg-blue-400 text-black rounded px-2 flex items-center" title="Auto Validate"><i class="fa fa-bolt"></i></button>
177
+ </div>
178
+ <div id="ssnValidateResult" class="mt-2 font-bold"></div>
179
+ </div>
180
+ </div>
181
+ <div class="text-xs text-gray-600 ml-1"><i class="fa fa-info-circle text-blue-400"></i> Generated SSNs are <b>fake</b>, follow known allocation patterns, but are not real. Validation is basic format/rule check only.</div>
182
+ </section>
183
+ </main>
184
+ <!-- ADMIN PANEL MODAL -->
185
+ <div id="adminModal" class="hidden fixed inset-0 z-50 bg-black bg-opacity-40 flex justify-center items-center print-hide">
186
+ <div class="bg-white rounded-lg p-6 w-full max-w-2xl shadow-card relative">
187
+ <button onclick="showAdmin(false)" class="absolute top-3 right-4 text-gray-700 hover:text-red-800 text-lg font-bold"><i class="fa fa-times"></i></button>
188
+ <div class="text-2xl font-bold mb-1 text-green-800 flex items-center"><i class="fa fa-user-cog mr-2"></i>Admin Panel</div>
189
+ <div class="admin-divider mb-4"></div>
190
+ <div class="grid md:grid-cols-2 gap-6 mb-6">
191
+ <div>
192
+ <label class="block mb-1 font-bold">Live Card Generation Stats</label>
193
+ <ul class="text-green-800 font-semibold space-y-2 text-sm list-inside list-disc">
194
+ <li><span>Cards generated (this session): </span><span id="adminCardsCount">0</span></li>
195
+ <li><span>Unique BINs imported: </span><span id="adminBinsCount">0</span></li>
196
+ <li><span>Saved emails: </span><span id="adminEmailsCount">0</span></li>
197
+ <li><span>Uploaded IPs: </span><span id="adminIPsCount">0</span></li>
198
+ </ul>
199
+ </div>
200
+ <div>
201
+ <label class="block mb-1 font-bold">Recently Imported Data</label>
202
+ <div class="bg-gray-100 rounded px-3 py-2 h-32 overflow-y-auto text-xs" id="adminImports"></div>
203
+ </div>
204
+ </div>
205
+ <div>
206
+ <label class="block mb-1 font-bold">Card Generation Timeline</label>
207
+ <canvas id="cardGenChart" height="60"></canvas>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ <!-- FOOTER -->
212
+ <footer class="w-full text-xs text-center py-5 mt-10 text-gray-500 print-hide">
213
+ © 2025 Fake ID Data Tools+ &mdash; Data is for testing and educational purposes only.<br>
214
+ Design optimized for browser PDF export - use browser’s print-to-PDF for full content.
215
+ </footer>
216
+ <script>
217
+ // DATA STORAGE
218
+ let cards = JSON.parse(localStorage.getItem("cards")||"[]");
219
+ let importedBins = JSON.parse(localStorage.getItem("importedBins")||"[]");
220
+ let importedEmails = JSON.parse(localStorage.getItem("importedEmails")||"[]");
221
+ let importedIPs = JSON.parse(localStorage.getItem("importedIPs")||"[]");
222
+ let genTimeline = JSON.parse(localStorage.getItem("genTimeline")||"[]"); // [{ts,count}]
223
+ // BIN SOURCES (you can expand with your own or imported)
224
+ const staticBins = [
225
+ '4539 1488', '4556 7375', '4916 7336', '4485 2847',
226
+ '5490 7559', '4621 0428', '4024 0071', '6011 9400'
227
+ ];
228
+
229
+ // DOM HELPERS
230
+ function el(id) { return document.getElementById(id);}
231
+ function showAdmin(show) {
232
+ el('adminModal').style.display = show ? 'flex' : 'none';
233
+ if(show) renderAdmin();
234
+ }
235
+ // RENDER CARDS HISTORY
236
+ function renderHistory() {
237
+ let tbody = el('cardHistory');
238
+ let noCardMsg = el('noCardMsg');
239
+ tbody.innerHTML = "";
240
+ let filtered = cards;
241
+ let kw = el('historySearch').value.trim().toLowerCase();
242
+ if(kw.length) {
243
+ filtered = cards.filter(card =>
244
+ (card.name||"").toLowerCase().includes(kw) ||
245
+ (card.number||"").toLowerCase().includes(kw) ||
246
+ (card.bank||"").toLowerCase().includes(kw) ||
247
+ (card.email||"").toLowerCase().includes(kw) ||
248
+ (card.ip||"").toLowerCase().includes(kw)
249
+ );
250
+ }
251
+ if(!filtered.length) {
252
+ noCardMsg.style.display = '';
253
+ return;
254
+ }
255
+ noCardMsg.style.display = 'none';
256
+ filtered.forEach((card, i) => {
257
+ let tr = document.createElement("tr");
258
+ tr.innerHTML = `
259
+ <td>${i+1}</td>
260
+ <td>${escapeHTML(card.name)}</td>
261
+ <td><span class="font-mono">${card.number}</span></td>
262
+ <td>${escapeHTML(card.mm)}/${escapeHTML(card.yy)}</td>
263
+ <td>${escapeHTML(card.cvv)}</td>
264
+ <td>${escapeHTML(card.bank||'-')}</td>
265
+ <td>${escapeHTML(card.bin||'-')}</td>
266
+ <td>${escapeHTML(card.email||'-')}</td>
267
+ <td>${escapeHTML(card.ip||'-')}</td>
268
+ <td>${escapeHTML(card.status||'-')}</td>
269
+ `;
270
+ tbody.appendChild(tr);
271
+ });
272
+ }
273
+ // CARD GENERATION LOGIC
274
+ el('generateBtn').onclick = function() {
275
+ let n = parseInt(el('qtyInput').value);
276
+ if(n < 1 || n > 5000 || isNaN(n)) { alert("Number to generate: Enter between 1 and 5000."); return;}
277
+ let inputBin = el('binInput').value.replace(/\s+/g, '');
278
+ let useRandomBin = el('randBinCheck').checked || !inputBin;
279
+ let bins = useRandomBin ? (staticBins.concat(importedBins)) : [inputBin];
280
+ let bankName = el('bankInput').value || '-';
281
+ let emails = [];
282
+ let ips = [];
283
+ let cardsBatch = [];
284
+ for(let i=0;i<n;i++) {
285
+ let bin = bins[Math.floor(Math.random() * bins.length)] || "400000";
286
+ let cardNumber = genCardNumberFromBin(bin.replace(/\s+/g,""));
287
+ let mm = String(Math.floor(Math.random()*12+1)).padStart(2,'0');
288
+ let y = (new Date().getFullYear()+Math.floor(Math.random()*7)+1).toString().substr(2,2);
289
+ let cvv = String(Math.floor(Math.random()*900)+100);
290
+ let name = el('holderInput').value || genRndName();
291
+ let phone = el('phoneInput').value || ("+1"+randStr(10,"0123456789"));
292
+ let email = el('emailInput').value || (genRndMail(name));
293
+ let addr = el('addressInput').value || genRndAddr();
294
+ let ip = randIP();
295
+ let status = "LIVE";
296
+ let obj = {number: cardNumber, mm, yy:y, cvv, bank:bankName, bin, name, phone, email, address:addr, ip, status};
297
+ cards.push(obj);
298
+ cardsBatch.push(obj);
299
+ emails.push(email);
300
+ ips.push(ip);
301
+ }
302
+ // Track imported
303
+ importedEmails = importedEmails.concat(emails.filter(x=>!importedEmails.includes(x)));
304
+ importedIPs = importedIPs.concat(ips.filter(x=>!importedIPs.includes(x)));
305
+ // Timeline chart
306
+ const now = new Date();
307
+ genTimeline.push({ts: now.getTime(), count: n});
308
+ localStorage.setItem("cards",JSON.stringify(cards));
309
+ localStorage.setItem("importedEmails",JSON.stringify(importedEmails));
310
+ localStorage.setItem("importedIPs",JSON.stringify(importedIPs));
311
+ localStorage.setItem("genTimeline",JSON.stringify(genTimeline));
312
+ renderHistory();
313
+ alert(`Generated ${n} cards.`);
314
+ }
315
+ function filterHistory() {renderHistory();}
316
+ function clearHistory() {
317
+ if(confirm("Clear all generated cards?")) {
318
+ cards = [];
319
+ localStorage.setItem("cards","[]");
320
+ renderHistory();
321
+ }
322
+ }
323
+ // Helper: Generate Card Number According to Luhn algorithm
324
+ function genCardNumberFromBin(bin) {
325
+ let res = bin;
326
+ for(let i=bin.length;i<15;i++) res += Math.floor(Math.random()*10);
327
+ // Luhn digit
328
+ let arr = res.split('').map(Number);
329
+ for(let i=arr.length-1;i>=0;i-=2)arr[i]*=2;
330
+ let sum = arr.join('').split('').reduce((a,c)=>a+parseInt(c),0);
331
+ let last = (10 - sum%10)%10;
332
+ return res + last;
333
+ }
334
+ function randIP() {
335
+ return [rand4(),rand4(),rand4(),rand4()].join('.');
336
+ function rand4(){ let n=Math.floor(Math.random()*254)+1; return n==127?128:n;}
337
+ }
338
+ function randStr(n, chars) {
339
+ let out = ""; let C = chars || "abcdefghijklmnopqrstuvwxyz";
340
+ for(let i=0;i<n;i++) out += C[Math.floor(Math.random()*C.length)];
341
+ return out;
342
+ }
343
+ function genRndName() {
344
+ const first = ["Alex","Dana","Taylor","Jordan","Robin","Morgan","Sam","Jesse"];
345
+ const last = ["Smith","Johnson","Lee","Walker","Cooper","Scott","Lewis","Allen"];
346
+ return first[Math.floor(Math.random()*first.length)]+" "+last[Math.floor(Math.random()*last.length)];
347
+ }
348
+ function genRndAddr() {
349
+ const streets = ["Park Ave","Maple St.","Elm Dr.","Sunset Blvd.","Oak St.","First Ave."];
350
+ return `${Math.floor(Math.random()*9999)+101} ${streets[Math.floor(Math.random()*streets.length)]}`;
351
+ }
352
+ function genRndMail(name) {
353
+ const d = ["mail.com","yahoo.com","testmail.io","example.org"];
354
+ let nick = name.replace(/\s+/g,'').toLowerCase() + randStr(2,"1234567890");
355
+ return nick+"@"+d[Math.floor(Math.random()*d.length)];
356
+ }
357
+ // CARD EXPORT
358
+ function exportHistory(fmt) {
359
+ let sep, ext, mimetype, content="";
360
+ if(!cards.length) {alert("No cards to export."); return;}
361
+ if(fmt=='csv'){
362
+ let keys = ["Card Holder","Number","MM/YY","CVV","Bank","BIN","Email","IP","Status"];
363
+ content = keys.join(",")+'\n';
364
+ cards.forEach(card=>{
365
+ content += `"${card.name}","${card.number}","${card.mm}/${card.yy}","${card.cvv}","${card.bank}","${card.bin}","${card.email}","${card.ip}","${card.status}"\n`;
366
+ });
367
+ downloadText(content,"cards.csv");
368
+ } else if(fmt=='json'){
369
+ downloadText(JSON.stringify(cards,null,2),"cards.json");
370
+ } else if(fmt=='txt'){
371
+ cards.forEach(c=>{
372
+ content+=`${c.number}|${c.mm}|${c.yy}|${c.cvv}|${c.name}|${c.email}|${c.bank}|${c.bin}|${c.ip}|${c.status}\n`;
373
+ });
374
+ downloadText(content,"cards.txt");
375
+ }
376
+ }
377
+ function downloadText(text, filename){
378
+ let a = document.createElement('a');
379
+ a.href = 'data:text/plain;charset=utf-8,'+encodeURIComponent(text);
380
+ a.download = filename; a.click();
381
+ }
382
+ function copyAllCards() {
383
+ let out = "";
384
+ cards.forEach(c=>{
385
+ out+=`${c.number}|${c.mm}|${c.yy}|${c.cvv}|${c.name}|${c.email}|${c.bank}|${c.bin}|${c.ip}|${c.status}\n`;
386
+ });
387
+ navigator.clipboard.writeText(out).then(()=>alert("All cards copied."));
388
+ }
389
+ // CARD VALIDATOR (Simulated)
390
+ function validateCard() {
391
+ let num = (el('validateCardNumber').value || "").replace(/\s+/g,'');
392
+ if(!num.match(/^\d{13,19}$/)) { showStatus("Enter a valid card number.",false); return;}
393
+ let isLuhn = luhnChk(num);
394
+ let randomStatus = Math.random()>.3 ? "LIVE" : "DEAD";
395
+ let score = Math.floor(Math.random()*60)+40;
396
+ showStatus(`${isLuhn?"Valid, ":"INVALID "} ${randomStatus}. Score:${score}/100`,isLuhn);
397
+ }
398
+ function luhnChk(num) {
399
+ let arr = num.split('').reverse().map(x=>parseInt(x));
400
+ let sum = 0;
401
+ for(let i=0;i<arr.length;i++) {
402
+ let v = arr[i];
403
+ if(i%2==1) v = v*2;
404
+ if(v>9) v-=9;
405
+ sum+=v;
406
+ }
407
+ return sum%10===0;
408
+ }
409
+ function autoValidateCard() {
410
+ if(cards.length){el('validateCardNumber').value = cards[0].number; validateCard();}
411
+ else {alert("Generate at least one card first.");}
412
+ }
413
+ function showStatus(txt, good) {
414
+ el('validateStatus').innerHTML = txt;
415
+ el('validateStatus').style.color = good?"green":"#d97706";
416
+ }
417
+ // FRAUD SCORE SIMULATOR (random)
418
+ function analyzeFraudScore() {
419
+ let num = (el('fraudCardInput').value || "").replace(/\s+/g,'');
420
+ if(!num.match(/^\d{13,19}$/)){el('fraudScoreResult').textContent = "Enter a valid card number.";return;}
421
+ let fs = Math.floor(Math.random()*40)+50;
422
+ el('fraudScoreResult').innerHTML = `Fraud Score: <span class="font-bold">${fs}</span>/100 [${fs < 65 ? "LOW" : "HIGH"}]`;
423
+ el('fraudScoreResult').style.color = fs<65?"#18590d":"#b91c1c";
424
+ }
425
+ function autoAnalyzeFraud() {
426
+ if(cards.length){el('fraudCardInput').value = cards[0].number; analyzeFraudScore();}
427
+ else {alert("Generate at least one card first.");}
428
+ }
429
+ // IP ANALYZER
430
+ function analyzeIP() {
431
+ let ip = el('ipInput').value;
432
+ if(!ip.match(/^(\d{1,3}\.){3}\d{1,3}$/)) { el('ipResult').textContent = "Enter valid IPv4"; el('ipResult').style.color='red'; return; }
433
+ let tag = Math.random() > 0.5 ? "Residential" : "Datacenter";
434
+ el('ipResult').innerHTML = `<b class="text-blue-900">${ip}</b> - Type: <span class="font-bold text-gray-700">${tag}</span>`;
435
+ el('ipResult').style.color = "#1e293b";
436
+ }
437
+ function autoAnalyzeIP() {
438
+ if(cards.length){el('ipInput').value = cards[0].ip; analyzeIP();}
439
+ else {alert("Generate at least one card first.");}
440
+ }
441
+ // EMAIL ANALYZER
442
+ function analyzeEmail() {
443
+ let e = el('emailAnalyzerInput').value || "";
444
+ let hasAt = e.includes("@") && e.length>5;
445
+ el('emailResult').textContent = !hasAt ? "Invalid email address." : (["Valid","Suspicious"][Math.round(Math.random())]);
446
+ el('emailResult').style.color = hasAt ? (el('emailResult').textContent==='Valid'?'#18590d':'#E6B800') : "red";
447
+ }
448
+ function autoAnalyzeEmail() {
449
+ if(cards.length){el('emailAnalyzerInput').value = cards[0].email; analyzeEmail();}
450
+ else {alert("Generate at least one card first.");}
451
+ }
452
+ // SSN GENERATOR
453
+ function generateSSNs() {
454
+ let n = parseInt(el('ssnQtyInput').value)||1;
455
+ let out = "";
456
+ for(let i=0;i<Math.min(5000,n);i++) {
457
+ let a = String(Math.floor(Math.random()*900)+100);
458
+ let g = String(Math.floor(Math.random()*90)+10);
459
+ let s = String(Math.floor(Math.random()*9000)+1000);
460
+ out += `${a}-${g}-${s}\n`;
461
+ }
462
+ el('ssnGenResult').value=out.trim();
463
+ }
464
+ function autoGenerateSSN() {
465
+ el('ssnQtyInput').value=5; generateSSNs();
466
+ }
467
+ // SSN VALIDATOR (format check)
468
+ function validateSSN() {
469
+ let ssn = (el('ssnValidateInput').value||"").replace(/-/g,'');
470
+ let valid = ssn.match(/^(?!000|666|9\d{2})\d{3}(?!00)\d{2}(?!0000)\d{4}$/);
471
+ el('ssnValidateResult').textContent = valid ? "Valid (format)." : "INVALID";
472
+ el('ssnValidateResult').style.color = valid?"#18590d":"#b91c1c";
473
+ }
474
+ function autoValidateSSN() {
475
+ if(el('ssnGenResult').value){
476
+ let v = el('ssnGenResult').value.split('\n')[0];
477
+ el('ssnValidateInput').value = v;
478
+ validateSSN();
479
+ } else {
480
+ autoGenerateSSN(); setTimeout(autoValidateSSN,100);
481
+ }
482
+ }
483
+ // DATA IMPORT HANDLING
484
+ el('importFile').addEventListener('change', function(evt){
485
+ let file = evt.target.files[0]; if(!file) return;
486
+ let ext = file.name.split(".").pop().toLowerCase();
487
+ let reader = new FileReader();
488
+ reader.onload = function(e) {
489
+ let text = e.target.result;
490
+ if(ext==="json") {
491
+ let data = JSON.parse(text);
492
+ if(Array.isArray(data)){
493
+ data.forEach(x=>{
494
+ if(x.bin) importedBins.push(x.bin);
495
+ if(x.email) importedEmails.push(x.email);
496
+ if(x.ip) importedIPs.push(x.ip);
497
+ });
498
+ alert("Imported data from JSON.");
499
+ }
500
+ } else if(ext==="csv" || ext==="txt"){
501
+ let rows = text.split('\n');
502
+ rows.forEach(r=>{
503
+ let vals = r.split(/[,|;]/);
504
+ vals.forEach(v=>{
505
+ v = v.trim();
506
+ if(v.match(/^\d{6,8}$/)) importedBins.push(v);
507
+ if(v.includes('@')) importedEmails.push(v);
508
+ if(v.match(/^(\d{1,3}\.){3}\d{1,3}$/)) importedIPs.push(v);
509
+ });
510
+ });
511
+ alert("Imported plaintext/csv data.");
512
+ } else if(ext==="pdf"){
513
+ alert("PDF import requires server parsing: Cannot auto-detect BINs/emails/ips here.");
514
+ }
515
+ importedBins = Array.from(new Set(importedBins));
516
+ importedEmails = Array.from(new Set(importedEmails));
517
+ importedIPs = Array.from(new Set(importedIPs));
518
+ localStorage.setItem("importedBins",JSON.stringify(importedBins));
519
+ localStorage.setItem("importedEmails",JSON.stringify(importedEmails));
520
+ localStorage.setItem("importedIPs",JSON.stringify(importedIPs));
521
+ };
522
+ if(file) reader.readAsText(file);
523
+ });
524
+ // ADMIN PANEL RENDER
525
+ function renderAdmin() {
526
+ el('adminCardsCount').innerText = cards.length;
527
+ el('adminBinsCount').innerText = importedBins.length;
528
+ el('adminEmailsCount').innerText = importedEmails.length;
529
+ el('adminIPsCount').innerText = importedIPs.length;
530
+ let imports = "";
531
+ if(importedBins.length) imports += "<b>Bins:</b><br>"+importedBins.slice(-6).join(", ")+"<br>";
532
+ if(importedEmails.length) imports += "<b>Emails:</b><br>"+importedEmails.slice(-6).join(", ")+"<br>";
533
+ if(importedIPs.length) imports += "<b>IPs:</b><br>"+importedIPs.slice(-6).join(", ")+"<br>";
534
+ el('adminImports').innerHTML = imports||"No data imported yet.";
535
+ renderChart();
536
+ }
537
+ // Card timeline chart
538
+ function renderChart() {
539
+ let data = {};
540
+ genTimeline.forEach(x=>{
541
+ let dt = new Date(x.ts);
542
+ let ds = `${dt.getFullYear()}-${(dt.getMonth()+1).toString().padStart(2,'0')}-${dt.getDate().toString().padStart(2,'0')}`;
543
+ data[ds]= (data[ds]||0)+x.count;
544
+ });
545
+ let labels = Object.keys(data);
546
+ let counts = Object.values(data);
547
+ if(window.cardGenChart) window.cardGenChart.destroy();
548
+ let ctx = document.getElementById('cardGenChart').getContext('2d');
549
+ window.cardGenChart = new Chart(ctx, {
550
+ type: 'bar',
551
+ data: {labels, datasets:[{label:'Cards Generated',data:counts, backgroundColor:"#059669"}]},
552
+ options: {plugins:{legend:{display:false}},scales:{y:{beginAtZero:true}}}
553
+ });
554
+ }
555
+ // UTILS
556
+ function escapeHTML(txt){return String(txt||'').replace(/[&<>"']/g,c=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));}
557
+ // INIT
558
+ renderHistory();
559
+ // END
560
+ </script>
561
+ </body>
562
+ </html>
index.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface User {
2
+ fullname: string;
3
+ avatarUrl: string;
4
+ name: string;
5
+ isLocalUse?: boolean;
6
+ isPro: boolean;
7
+ id: string;
8
+ token?: string;
9
+ }
10
+
11
+ export interface HtmlHistory {
12
+ pages: Page[];
13
+ createdAt: Date;
14
+ prompt: string;
15
+ }
16
+
17
+ export interface Project {
18
+ title: string;
19
+ html: string;
20
+ prompts: string[];
21
+ user_id: string;
22
+ space_id: string;
23
+ _id?: string;
24
+ _updatedAt?: Date;
25
+ _createdAt?: Date;
26
+ }
27
+
28
+ export interface Page {
29
+ path: string;
30
+ html: string;
31
+ }
index.tsx (1).txt ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useRef, useState } from "react";
4
+ import Image from "next/image";
5
+ import Link from "next/link";
6
+ import { useMount, useUnmount } from "react-use";
7
+ import classNames from "classnames";
8
+
9
+ import { Button } from "@/components/ui/button";
10
+ import Logo from "@/assets/logo.svg";
11
+ import { useUser } from "@/hooks/useUser";
12
+ import { UserMenu } from "@/components/user-menu";
13
+
14
+ const navigationLinks = [
15
+ {
16
+ name: "Create Website",
17
+ href: "/projects/new",
18
+ },
19
+ {
20
+ name: "Features",
21
+ href: "#features",
22
+ },
23
+ {
24
+ name: "Community",
25
+ href: "#community",
26
+ },
27
+ {
28
+ name: "Deploy",
29
+ href: "#deploy",
30
+ },
31
+ ];
32
+
33
+ export default function Navigation() {
34
+ const { openLoginWindow, user } = useUser();
35
+ const [hash, setHash] = useState("");
36
+
37
+ const selectorRef = useRef<HTMLDivElement>(null);
38
+ const linksRef = useRef<HTMLLIElement[]>(
39
+ new Array(navigationLinks.length).fill(null)
40
+ );
41
+ const [isScrolled, setIsScrolled] = useState(false);
42
+
43
+ useMount(() => {
44
+ const handleScroll = () => {
45
+ const scrollTop = window.scrollY;
46
+ setIsScrolled(scrollTop > 100);
47
+ };
48
+
49
+ const initialHash = window.location.hash;
50
+ if (initialHash) {
51
+ setHash(initialHash);
52
+ calculateSelectorPosition(initialHash);
53
+ }
54
+
55
+ window.addEventListener("scroll", handleScroll);
56
+ });
57
+
58
+ useUnmount(() => {
59
+ window.removeEventListener("scroll", () => {});
60
+ });
61
+
62
+ const handleClick = (href: string) => {
63
+ setHash(href);
64
+ calculateSelectorPosition(href);
65
+ };
66
+
67
+ const calculateSelectorPosition = (href: string) => {
68
+ if (selectorRef.current && linksRef.current) {
69
+ const index = navigationLinks.findIndex((l) => l.href === href);
70
+ const targetLink = linksRef.current[index];
71
+ if (targetLink) {
72
+ const targetRect = targetLink.getBoundingClientRect();
73
+ selectorRef.current.style.left = targetRect.left + "px";
74
+ selectorRef.current.style.width = targetRect.width + "px";
75
+ }
76
+ }
77
+ };
78
+
79
+ return (
80
+ <div
81
+ className={classNames(
82
+ "sticky top-0 z-10 transition-all duration-200 backdrop-blur-md",
83
+ {
84
+ "bg-black/30": isScrolled,
85
+ }
86
+ )}
87
+ >
88
+ <nav className="grid grid-cols-2 p-4 container mx-auto">
89
+ <Link href="/" className="flex items-center gap-1">
90
+ <Image
91
+ src={Logo}
92
+ className="w-9 mr-1"
93
+ alt="DeepSite Logo"
94
+ width={64}
95
+ height={64}
96
+ />
97
+ <p className="font-sans text-white text-xl font-bold">DeepSite</p>
98
+ </Link>
99
+ <ul className="items-center justify-center gap-6 hidden">
100
+ {navigationLinks.map((link) => (
101
+ <li
102
+ key={link.name}
103
+ ref={(el) => {
104
+ const index = navigationLinks.findIndex(
105
+ (l) => l.href === link.href
106
+ );
107
+ if (el && linksRef.current[index] !== el) {
108
+ linksRef.current[index] = el;
109
+ }
110
+ }}
111
+ className="inline-block font-sans text-sm"
112
+ >
113
+ <Link
114
+ href={link.href}
115
+ className={classNames(
116
+ "text-neutral-500 hover:text-primary transition-colors",
117
+ {
118
+ "text-primary": hash === link.href,
119
+ }
120
+ )}
121
+ onClick={() => {
122
+ handleClick(link.href);
123
+ }}
124
+ >
125
+ {link.name}
126
+ </Link>
127
+ </li>
128
+ ))}
129
+ <div
130
+ ref={selectorRef}
131
+ className={classNames(
132
+ "h-1 absolute bottom-4 transition-all duration-200 flex items-center justify-center",
133
+ {
134
+ "opacity-0": !hash,
135
+ }
136
+ )}
137
+ >
138
+ <div className="size-1 bg-white rounded-full" />
139
+ </div>
140
+ </ul>
141
+ <div className="flex items-center justify-end gap-2">
142
+ {user ? (
143
+ <UserMenu className="!pl-3 !pr-4 !py-2 !h-auto !rounded-lg" />
144
+ ) : (
145
+ <>
146
+ <Button variant="link" size={"sm"} onClick={openLoginWindow}>
147
+ Log In
148
+ </Button>
149
+ <Button size={"sm"}>Sign Up</Button>
150
+ </>
151
+ )}
152
+ </div>
153
+ </nav>
154
+ </div>
155
+ );
156
+ }
index.tsx (10).txt ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { useUpdateEffect } from "react-use";
3
+ import { useMemo, useState } from "react";
4
+ import classNames from "classnames";
5
+ import { toast } from "sonner";
6
+ import { useThrottleFn } from "react-use";
7
+
8
+ import { cn } from "@/lib/utils";
9
+ import { GridPattern } from "@/components/magic-ui/grid-pattern";
10
+ import { htmlTagToText } from "@/lib/html-tag-to-text";
11
+ import { Page } from "@/types";
12
+
13
+ export const Preview = ({
14
+ html,
15
+ isResizing,
16
+ isAiWorking,
17
+ ref,
18
+ device,
19
+ currentTab,
20
+ iframeRef,
21
+ pages,
22
+ setCurrentPage,
23
+ isEditableModeEnabled,
24
+ onClickElement,
25
+ }: {
26
+ html: string;
27
+ isResizing: boolean;
28
+ isAiWorking: boolean;
29
+ pages: Page[];
30
+ setCurrentPage: React.Dispatch<React.SetStateAction<string>>;
31
+ ref: React.RefObject<HTMLDivElement | null>;
32
+ iframeRef?: React.RefObject<HTMLIFrameElement | null>;
33
+ device: "desktop" | "mobile";
34
+ currentTab: string;
35
+ isEditableModeEnabled?: boolean;
36
+ onClickElement?: (element: HTMLElement) => void;
37
+ }) => {
38
+ const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(
39
+ null
40
+ );
41
+
42
+ const handleMouseOver = (event: MouseEvent) => {
43
+ if (iframeRef?.current) {
44
+ const iframeDocument = iframeRef.current.contentDocument;
45
+ if (iframeDocument) {
46
+ const targetElement = event.target as HTMLElement;
47
+ if (
48
+ hoveredElement !== targetElement &&
49
+ targetElement !== iframeDocument.body
50
+ ) {
51
+ setHoveredElement(targetElement);
52
+ targetElement.classList.add("hovered-element");
53
+ } else {
54
+ return setHoveredElement(null);
55
+ }
56
+ }
57
+ }
58
+ };
59
+ const handleMouseOut = () => {
60
+ setHoveredElement(null);
61
+ };
62
+ const handleClick = (event: MouseEvent) => {
63
+ if (iframeRef?.current) {
64
+ const iframeDocument = iframeRef.current.contentDocument;
65
+ if (iframeDocument) {
66
+ const targetElement = event.target as HTMLElement;
67
+ if (targetElement !== iframeDocument.body) {
68
+ onClickElement?.(targetElement);
69
+ }
70
+ }
71
+ }
72
+ };
73
+ const handleCustomNavigation = (event: MouseEvent) => {
74
+ if (iframeRef?.current) {
75
+ const iframeDocument = iframeRef.current.contentDocument;
76
+ if (iframeDocument) {
77
+ const findClosestAnchor = (
78
+ element: HTMLElement
79
+ ): HTMLAnchorElement | null => {
80
+ let current = element;
81
+ while (current && current !== iframeDocument.body) {
82
+ if (current.tagName === "A") {
83
+ return current as HTMLAnchorElement;
84
+ }
85
+ current = current.parentElement as HTMLElement;
86
+ }
87
+ return null;
88
+ };
89
+
90
+ const anchorElement = findClosestAnchor(event.target as HTMLElement);
91
+ if (anchorElement) {
92
+ let href = anchorElement.getAttribute("href");
93
+ if (href) {
94
+ event.stopPropagation();
95
+ event.preventDefault();
96
+
97
+ if (href.includes("#") && !href.includes(".html")) {
98
+ const targetElement = iframeDocument.querySelector(href);
99
+ if (targetElement) {
100
+ targetElement.scrollIntoView({ behavior: "smooth" });
101
+ }
102
+ return;
103
+ }
104
+
105
+ href = href.split(".html")[0] + ".html";
106
+ const isPageExist = pages.some((page) => page.path === href);
107
+ if (isPageExist) {
108
+ setCurrentPage(href);
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ };
115
+
116
+ useUpdateEffect(() => {
117
+ const cleanupListeners = () => {
118
+ if (iframeRef?.current?.contentDocument) {
119
+ const iframeDocument = iframeRef.current.contentDocument;
120
+ iframeDocument.removeEventListener("mouseover", handleMouseOver);
121
+ iframeDocument.removeEventListener("mouseout", handleMouseOut);
122
+ iframeDocument.removeEventListener("click", handleClick);
123
+ }
124
+ };
125
+
126
+ if (iframeRef?.current) {
127
+ const iframeDocument = iframeRef.current.contentDocument;
128
+ if (iframeDocument) {
129
+ cleanupListeners();
130
+
131
+ if (isEditableModeEnabled) {
132
+ iframeDocument.addEventListener("mouseover", handleMouseOver);
133
+ iframeDocument.addEventListener("mouseout", handleMouseOut);
134
+ iframeDocument.addEventListener("click", handleClick);
135
+ }
136
+ }
137
+ }
138
+
139
+ return cleanupListeners;
140
+ }, [iframeRef, isEditableModeEnabled]);
141
+
142
+ const selectedElement = useMemo(() => {
143
+ if (!isEditableModeEnabled) return null;
144
+ if (!hoveredElement) return null;
145
+ return hoveredElement;
146
+ }, [hoveredElement, isEditableModeEnabled]);
147
+
148
+ const throttledHtml = useThrottleFn((html) => html, 1000, [html]);
149
+
150
+ return (
151
+ <div
152
+ ref={ref}
153
+ className={classNames(
154
+ "w-full border-l border-gray-900 h-full relative z-0 flex items-center justify-center",
155
+ {
156
+ "lg:p-4": currentTab !== "preview",
157
+ "max-lg:h-0": currentTab === "chat",
158
+ "max-lg:h-full": currentTab === "preview",
159
+ }
160
+ )}
161
+ onClick={(e) => {
162
+ if (isAiWorking) {
163
+ e.preventDefault();
164
+ e.stopPropagation();
165
+ toast.warning("Please wait for the AI to finish working.");
166
+ }
167
+ }}
168
+ >
169
+ <GridPattern
170
+ x={-1}
171
+ y={-1}
172
+ strokeDasharray={"4 2"}
173
+ className={cn(
174
+ "[mask-image:radial-gradient(900px_circle_at_center,white,transparent)]"
175
+ )}
176
+ />
177
+ {!isAiWorking && hoveredElement && selectedElement && (
178
+ <div
179
+ className="cursor-pointer absolute bg-sky-500/10 border-[2px] border-dashed border-sky-500 rounded-r-lg rounded-b-lg p-3 z-10 pointer-events-none"
180
+ style={{
181
+ top:
182
+ selectedElement.getBoundingClientRect().top +
183
+ (currentTab === "preview" ? 0 : 24),
184
+ left:
185
+ selectedElement.getBoundingClientRect().left +
186
+ (currentTab === "preview" ? 0 : 24),
187
+ width: selectedElement.getBoundingClientRect().width,
188
+ height: selectedElement.getBoundingClientRect().height,
189
+ }}
190
+ >
191
+ <span className="bg-sky-500 rounded-t-md text-sm text-neutral-100 px-2 py-0.5 -translate-y-7 absolute top-0 left-0">
192
+ {htmlTagToText(selectedElement.tagName.toLowerCase())}
193
+ </span>
194
+ </div>
195
+ )}
196
+ <iframe
197
+ id="preview-iframe"
198
+ ref={iframeRef}
199
+ title="output"
200
+ className={classNames(
201
+ "w-full select-none transition-all duration-200 bg-black h-full",
202
+ {
203
+ "pointer-events-none": isResizing || isAiWorking,
204
+ "lg:max-w-md lg:mx-auto lg:!rounded-[42px] lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:h-[80dvh] lg:max-h-[996px]":
205
+ device === "mobile",
206
+ "lg:border-[8px] lg:border-neutral-700 lg:shadow-2xl lg:rounded-[24px]":
207
+ currentTab !== "preview" && device === "desktop",
208
+ }
209
+ )}
210
+ srcDoc={isAiWorking ? (throttledHtml as string) : html}
211
+ onLoad={() => {
212
+ if (iframeRef?.current?.contentWindow?.document?.body) {
213
+ iframeRef.current.contentWindow.document.body.scrollIntoView({
214
+ block: isAiWorking ? "end" : "start",
215
+ inline: "nearest",
216
+ behavior: isAiWorking ? "instant" : "smooth",
217
+ });
218
+ }
219
+ // add event listener to all links in the iframe to handle navigation
220
+ if (iframeRef?.current?.contentWindow?.document) {
221
+ const links =
222
+ iframeRef.current.contentWindow.document.querySelectorAll("a");
223
+ links.forEach((link) => {
224
+ link.addEventListener("click", handleCustomNavigation);
225
+ });
226
+ }
227
+ }}
228
+ />
229
+ </div>
230
+ );
231
+ };
index.tsx (11).txt ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Page } from "@/types";
2
+ import { ListPagesItem } from "./page";
3
+
4
+ export function ListPages({
5
+ pages,
6
+ currentPage,
7
+ onSelectPage,
8
+ onDeletePage,
9
+ }: {
10
+ pages: Array<Page>;
11
+ currentPage: string;
12
+ onSelectPage: (path: string, newPath?: string) => void;
13
+ onNewPage: () => void;
14
+ onDeletePage: (path: string) => void;
15
+ }) {
16
+ return (
17
+ <div className="w-full flex items-center justify-start bg-neutral-950 overflow-auto flex-nowrap min-h-[44px]">
18
+ {pages.map((page, i) => (
19
+ <ListPagesItem
20
+ key={i}
21
+ page={page}
22
+ currentPage={currentPage}
23
+ onSelectPage={onSelectPage}
24
+ onDeletePage={onDeletePage}
25
+ index={i}
26
+ />
27
+ ))}
28
+ </div>
29
+ );
30
+ }
index.tsx (12).txt ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { History as HistoryIcon } from "lucide-react";
2
+ import { HtmlHistory, Page } from "@/types";
3
+ import {
4
+ Popover,
5
+ PopoverContent,
6
+ PopoverTrigger,
7
+ } from "@/components/ui/popover";
8
+ import { Button } from "@/components/ui/button";
9
+
10
+ export function History({
11
+ history,
12
+ setPages,
13
+ }: {
14
+ history: HtmlHistory[];
15
+ setPages: (pages: Page[]) => void;
16
+ }) {
17
+ return (
18
+ <Popover>
19
+ <PopoverTrigger asChild>
20
+ <Button variant="ghost" size="sm" className="max-lg:hidden">
21
+ <HistoryIcon className="size-4 text-neutral-300" />
22
+ {history?.length} edit{history.length !== 1 ? "s" : ""}
23
+ </Button>
24
+ </PopoverTrigger>
25
+ <PopoverContent
26
+ className="!rounded-2xl !p-0 overflow-hidden !bg-neutral-900"
27
+ align="start"
28
+ >
29
+ <header className="text-sm px-4 py-3 border-b gap-2 bg-neutral-950 border-neutral-800 font-semibold text-neutral-200">
30
+ History
31
+ </header>
32
+ <main className="px-4 space-y-3">
33
+ <ul className="max-h-[250px] overflow-y-auto">
34
+ {history?.map((item, index) => (
35
+ <li
36
+ key={index}
37
+ className="text-gray-300 text-xs py-2 border-b border-gray-800 last:border-0 flex items-center justify-between gap-2"
38
+ >
39
+ <div className="">
40
+ <span className="line-clamp-1">{item.prompt}</span>
41
+ <span className="text-gray-500 text-[10px]">
42
+ {new Date(item.createdAt).toLocaleDateString("en-US", {
43
+ month: "2-digit",
44
+ day: "2-digit",
45
+ year: "2-digit",
46
+ }) +
47
+ " " +
48
+ new Date(item.createdAt).toLocaleTimeString("en-US", {
49
+ hour: "2-digit",
50
+ minute: "2-digit",
51
+ second: "2-digit",
52
+ hour12: false,
53
+ })}
54
+ </span>
55
+ </div>
56
+ <Button
57
+ variant="sky"
58
+ size="xs"
59
+ onClick={() => {
60
+ console.log(item);
61
+ setPages(item.pages);
62
+ }}
63
+ >
64
+ Select
65
+ </Button>
66
+ </li>
67
+ ))}
68
+ </ul>
69
+ </main>
70
+ </PopoverContent>
71
+ </Popover>
72
+ );
73
+ }
index.tsx (13).txt ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReactNode } from "react";
2
+ import { Eye, MessageCircleCode } from "lucide-react";
3
+
4
+ import Logo from "@/assets/logo.svg";
5
+
6
+ import { Button } from "@/components/ui/button";
7
+ import classNames from "classnames";
8
+ import Image from "next/image";
9
+
10
+ const TABS = [
11
+ {
12
+ value: "chat",
13
+ label: "Chat",
14
+ icon: MessageCircleCode,
15
+ },
16
+ {
17
+ value: "preview",
18
+ label: "Preview",
19
+ icon: Eye,
20
+ },
21
+ ];
22
+
23
+ export function Header({
24
+ tab,
25
+ onNewTab,
26
+ children,
27
+ }: {
28
+ tab: string;
29
+ onNewTab: (tab: string) => void;
30
+ children?: ReactNode;
31
+ }) {
32
+ return (
33
+ <header className="border-b bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 lg:px-6 py-2 flex items-center max-lg:gap-3 justify-between lg:grid lg:grid-cols-3 z-20">
34
+ <div className="flex items-center justify-start gap-3">
35
+ <h1 className="text-neutral-900 dark:text-white text-lg lg:text-xl font-bold flex items-center justify-start">
36
+ <Image
37
+ src={Logo}
38
+ alt="DeepSite Logo"
39
+ className="size-6 lg:size-8 mr-2 invert-100 dark:invert-0"
40
+ />
41
+ <p className="max-md:hidden flex items-center justify-start">
42
+ DeepSite
43
+ <span className="font-mono bg-gradient-to-br from-sky-500 to-emerald-500 text-neutral-950 rounded-full text-xs ml-2 px-1.5 py-0.5">
44
+ {" "}
45
+ v2
46
+ </span>
47
+ </p>
48
+ </h1>
49
+ </div>
50
+ <div className="flex items-center justify-start lg:justify-center gap-1 max-lg:pl-3 flex-1 max-lg:border-l max-lg:border-l-neutral-800">
51
+ {TABS.map((item) => (
52
+ <Button
53
+ key={item.value}
54
+ variant={tab === item.value ? "secondary" : "ghost"}
55
+ className={classNames("", {
56
+ "opacity-60": tab !== item.value,
57
+ })}
58
+ size="sm"
59
+ onClick={() => onNewTab(item.value)}
60
+ >
61
+ <item.icon className="size-4" />
62
+ <span className="hidden md:inline">{item.label}</span>
63
+ </Button>
64
+ ))}
65
+ </div>
66
+ <div className="flex items-center justify-end gap-3">{children}</div>
67
+ </header>
68
+ );
69
+ }
index.tsx (14).txt ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+ import { FaMobileAlt } from "react-icons/fa";
3
+ import { HelpCircle, LogIn, RefreshCcw, SparkleIcon } from "lucide-react";
4
+ import { FaLaptopCode } from "react-icons/fa6";
5
+ import { HtmlHistory, Page } from "@/types";
6
+ import { Button } from "@/components/ui/button";
7
+ import { MdAdd } from "react-icons/md";
8
+ import { History } from "@/components/editor/history";
9
+ import { UserMenu } from "@/components/user-menu";
10
+ import { useUser } from "@/hooks/useUser";
11
+ import Link from "next/link";
12
+ import { useLocalStorage } from "react-use";
13
+ import { isTheSameHtml } from "@/lib/compare-html-diff";
14
+
15
+ const DEVICES = [
16
+ {
17
+ name: "desktop",
18
+ icon: FaLaptopCode,
19
+ },
20
+ {
21
+ name: "mobile",
22
+ icon: FaMobileAlt,
23
+ },
24
+ ];
25
+
26
+ export function Footer({
27
+ pages,
28
+ isNew = false,
29
+ htmlHistory,
30
+ setPages,
31
+ device,
32
+ setDevice,
33
+ iframeRef,
34
+ }: {
35
+ pages: Page[];
36
+ isNew?: boolean;
37
+ htmlHistory?: HtmlHistory[];
38
+ device: "desktop" | "mobile";
39
+ setPages: (pages: Page[]) => void;
40
+ iframeRef?: React.RefObject<HTMLIFrameElement | null>;
41
+ setDevice: React.Dispatch<React.SetStateAction<"desktop" | "mobile">>;
42
+ }) {
43
+ const { user, openLoginWindow } = useUser();
44
+
45
+ const handleRefreshIframe = () => {
46
+ if (iframeRef?.current) {
47
+ const iframe = iframeRef.current;
48
+ const content = iframe.srcdoc;
49
+ iframe.srcdoc = "";
50
+ setTimeout(() => {
51
+ iframe.srcdoc = content;
52
+ }, 10);
53
+ }
54
+ };
55
+
56
+ const [, setStorage] = useLocalStorage("pages");
57
+ const handleClick = async () => {
58
+ if (pages && !isTheSameHtml(pages[0].html)) {
59
+ setStorage(pages);
60
+ }
61
+ openLoginWindow();
62
+ };
63
+
64
+ return (
65
+ <footer className="border-t bg-slate-200 border-slate-300 dark:bg-neutral-950 dark:border-neutral-800 px-3 py-2 flex items-center justify-between sticky bottom-0 z-20">
66
+ <div className="flex items-center gap-2">
67
+ {user ? (
68
+ user?.isLocalUse ? (
69
+ <>
70
+ <div className="max-w-max bg-amber-500/10 rounded-full px-3 py-1 text-amber-500 border border-amber-500/20 text-sm font-semibold">
71
+ Local Usage
72
+ </div>
73
+ </>
74
+ ) : (
75
+ <UserMenu className="!p-1 !pr-3 !h-auto" />
76
+ )
77
+ ) : (
78
+ <Button size="sm" variant="default" onClick={handleClick}>
79
+ <LogIn className="text-sm" />
80
+ Log In
81
+ </Button>
82
+ )}
83
+ {user && !isNew && <p className="text-neutral-700">|</p>}
84
+ {!isNew && (
85
+ <Link href="/projects/new">
86
+ <Button size="sm" variant="secondary">
87
+ <MdAdd className="text-sm" />
88
+ New <span className="max-lg:hidden">Project</span>
89
+ </Button>
90
+ </Link>
91
+ )}
92
+ {htmlHistory && htmlHistory.length > 0 && (
93
+ <>
94
+ <p className="text-neutral-700">|</p>
95
+ <History history={htmlHistory} setPages={setPages} />
96
+ </>
97
+ )}
98
+ </div>
99
+ <div className="flex justify-end items-center gap-2.5">
100
+ <a
101
+ href="https://huggingface.co/spaces/victor/deepsite-gallery"
102
+ target="_blank"
103
+ >
104
+ <Button size="sm" variant="ghost">
105
+ <SparkleIcon className="size-3.5" />
106
+ <span className="max-lg:hidden">DeepSite Gallery</span>
107
+ </Button>
108
+ </a>
109
+ <a
110
+ target="_blank"
111
+ href="https://huggingface.co/spaces/enzostvs/deepsite/discussions/157"
112
+ >
113
+ <Button size="sm" variant="outline">
114
+ <HelpCircle className="size-3.5" />
115
+ <span className="max-lg:hidden">Help</span>
116
+ </Button>
117
+ </a>
118
+ <Button size="sm" variant="outline" onClick={handleRefreshIframe}>
119
+ <RefreshCcw className="size-3.5" />
120
+ <span className="max-lg:hidden">Refresh Preview</span>
121
+ </Button>
122
+ <div className="flex items-center rounded-full p-0.5 bg-neutral-700/70 relative overflow-hidden z-0 max-lg:hidden gap-0.5">
123
+ <div
124
+ className={classNames(
125
+ "absolute left-0.5 top-0.5 rounded-full bg-white size-7 -z-[1] transition-all duration-200",
126
+ {
127
+ "translate-x-[calc(100%+2px)]": device === "mobile",
128
+ }
129
+ )}
130
+ />
131
+ {DEVICES.map((deviceItem) => (
132
+ <button
133
+ key={deviceItem.name}
134
+ className={classNames(
135
+ "rounded-full text-neutral-300 size-7 flex items-center justify-center cursor-pointer",
136
+ {
137
+ "!text-black": device === deviceItem.name,
138
+ "hover:bg-neutral-800": device !== deviceItem.name,
139
+ }
140
+ )}
141
+ onClick={() => setDevice(deviceItem.name as "desktop" | "mobile")}
142
+ >
143
+ <deviceItem.icon className="text-sm" />
144
+ </button>
145
+ ))}
146
+ </div>
147
+ </div>
148
+ </footer>
149
+ );
150
+ }
index.tsx (15).txt ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { useState } from "react";
3
+ import { MdSave } from "react-icons/md";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import {
7
+ Popover,
8
+ PopoverContent,
9
+ PopoverTrigger,
10
+ } from "@/components/ui/popover";
11
+ import { LoginModal } from "@/components/login-modal";
12
+ import { useUser } from "@/hooks/useUser";
13
+ import { Page } from "@/types";
14
+ import { DeployButtonContent } from "./content";
15
+
16
+ export function DeployButton({
17
+ pages,
18
+ prompts,
19
+ }: {
20
+ pages: Page[];
21
+ prompts: string[];
22
+ }) {
23
+ const { user } = useUser();
24
+ const [open, setOpen] = useState(false);
25
+
26
+ return (
27
+ <div className="flex items-center justify-end gap-5">
28
+ <div className="relative flex items-center justify-end">
29
+ {user?.id ? (
30
+ <Popover>
31
+ <PopoverTrigger asChild>
32
+ <div>
33
+ <Button variant="default" className="max-lg:hidden !px-4">
34
+ <MdSave className="size-4" />
35
+ Publish your Project
36
+ </Button>
37
+ <Button variant="default" size="sm" className="lg:hidden">
38
+ Publish
39
+ </Button>
40
+ </div>
41
+ </PopoverTrigger>
42
+ <PopoverContent
43
+ className="!rounded-2xl !p-0 !bg-white !border-neutral-200 min-w-xs text-center overflow-hidden"
44
+ align="end"
45
+ >
46
+ <DeployButtonContent pages={pages} prompts={prompts} />
47
+ </PopoverContent>
48
+ </Popover>
49
+ ) : (
50
+ <>
51
+ <Button
52
+ variant="default"
53
+ className="max-lg:hidden !px-4"
54
+ onClick={() => setOpen(true)}
55
+ >
56
+ <MdSave className="size-4" />
57
+ Publish your Project
58
+ </Button>
59
+ <Button
60
+ variant="default"
61
+ size="sm"
62
+ className="lg:hidden"
63
+ onClick={() => setOpen(true)}
64
+ >
65
+ Publish
66
+ </Button>
67
+ </>
68
+ )}
69
+ <LoginModal
70
+ open={open}
71
+ onClose={() => setOpen(false)}
72
+ pages={pages}
73
+ title="Log In to publish your Project"
74
+ description="Log In through your Hugging Face account to publish your project and increase your monthly free limit."
75
+ />
76
+ </div>
77
+ </div>
78
+ );
79
+ }
index.tsx (16).txt ADDED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ import { useState, useMemo, useRef } from "react";
4
+ import classNames from "classnames";
5
+ import { toast } from "sonner";
6
+ import { useLocalStorage, useUpdateEffect } from "react-use";
7
+ import { ArrowUp, ChevronDown, Crosshair } from "lucide-react";
8
+ import { FaStopCircle } from "react-icons/fa";
9
+
10
+ import ProModal from "@/components/pro-modal";
11
+ import { Button } from "@/components/ui/button";
12
+ import { MODELS } from "@/lib/providers";
13
+ import { HtmlHistory, Page, Project } from "@/types";
14
+ // import { InviteFriends } from "@/components/invite-friends";
15
+ import { Settings } from "@/components/editor/ask-ai/settings";
16
+ import { LoginModal } from "@/components/login-modal";
17
+ import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
18
+ import Loading from "@/components/loading";
19
+ import { Checkbox } from "@/components/ui/checkbox";
20
+ import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
21
+ import { TooltipContent } from "@radix-ui/react-tooltip";
22
+ import { SelectedHtmlElement } from "./selected-html-element";
23
+ import { FollowUpTooltip } from "./follow-up-tooltip";
24
+ import { isTheSameHtml } from "@/lib/compare-html-diff";
25
+ import { useCallAi } from "@/hooks/useCallAi";
26
+ import { SelectedFiles } from "./selected-files";
27
+ import { Uploader } from "./uploader";
28
+
29
+ export function AskAI({
30
+ isNew,
31
+ project,
32
+ images,
33
+ currentPage,
34
+ previousPrompts,
35
+ onScrollToBottom,
36
+ isAiWorking,
37
+ setisAiWorking,
38
+ isEditableModeEnabled = false,
39
+ pages,
40
+ htmlHistory,
41
+ selectedElement,
42
+ setSelectedElement,
43
+ selectedFiles,
44
+ setSelectedFiles,
45
+ setIsEditableModeEnabled,
46
+ onNewPrompt,
47
+ onSuccess,
48
+ setPages,
49
+ setCurrentPage,
50
+ }: {
51
+ project?: Project | null;
52
+ currentPage: Page;
53
+ images?: string[];
54
+ pages: Page[];
55
+ onScrollToBottom: () => void;
56
+ previousPrompts: string[];
57
+ isAiWorking: boolean;
58
+ onNewPrompt: (prompt: string) => void;
59
+ htmlHistory?: HtmlHistory[];
60
+ setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
61
+ isNew?: boolean;
62
+ onSuccess: (page: Page[], p: string, n?: number[][]) => void;
63
+ isEditableModeEnabled: boolean;
64
+ setIsEditableModeEnabled: React.Dispatch<React.SetStateAction<boolean>>;
65
+ selectedElement?: HTMLElement | null;
66
+ setSelectedElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
67
+ selectedFiles: string[];
68
+ setSelectedFiles: React.Dispatch<React.SetStateAction<string[]>>;
69
+ setPages: React.Dispatch<React.SetStateAction<Page[]>>;
70
+ setCurrentPage: React.Dispatch<React.SetStateAction<string>>;
71
+ }) {
72
+ const refThink = useRef<HTMLDivElement | null>(null);
73
+
74
+ const [open, setOpen] = useState(false);
75
+ const [prompt, setPrompt] = useState("");
76
+ const [provider, setProvider] = useLocalStorage("provider", "auto");
77
+ const [model, setModel] = useLocalStorage("model", MODELS[0].value);
78
+ const [openProvider, setOpenProvider] = useState(false);
79
+ const [providerError, setProviderError] = useState("");
80
+ const [openProModal, setOpenProModal] = useState(false);
81
+ const [openThink, setOpenThink] = useState(false);
82
+ const [isThinking, setIsThinking] = useState(true);
83
+ const [think, setThink] = useState("");
84
+ const [isFollowUp, setIsFollowUp] = useState(true);
85
+ const [isUploading, setIsUploading] = useState(false);
86
+ const [files, setFiles] = useState<string[]>(images ?? []);
87
+
88
+ const {
89
+ callAiNewProject,
90
+ callAiFollowUp,
91
+ callAiNewPage,
92
+ stopController,
93
+ audio: hookAudio,
94
+ } = useCallAi({
95
+ onNewPrompt,
96
+ onSuccess,
97
+ onScrollToBottom,
98
+ setPages,
99
+ setCurrentPage,
100
+ currentPage,
101
+ pages,
102
+ isAiWorking,
103
+ setisAiWorking,
104
+ });
105
+
106
+ const selectedModel = useMemo(() => {
107
+ return MODELS.find((m: { value: string }) => m.value === model);
108
+ }, [model]);
109
+
110
+ const callAi = async (redesignMarkdown?: string) => {
111
+ if (isAiWorking) return;
112
+ if (!redesignMarkdown && !prompt.trim()) return;
113
+
114
+ if (isFollowUp && !redesignMarkdown && !isSameHtml) {
115
+ // Use follow-up function for existing projects
116
+ const selectedElementHtml = selectedElement
117
+ ? selectedElement.outerHTML
118
+ : "";
119
+
120
+ const result = await callAiFollowUp(
121
+ prompt,
122
+ model,
123
+ provider,
124
+ previousPrompts,
125
+ selectedElementHtml,
126
+ selectedFiles
127
+ );
128
+
129
+ if (result?.error) {
130
+ handleError(result.error, result.message);
131
+ return;
132
+ }
133
+
134
+ if (result?.success) {
135
+ setPrompt("");
136
+ }
137
+ } else if (isFollowUp && pages.length > 1 && isSameHtml) {
138
+ const result = await callAiNewPage(
139
+ prompt,
140
+ model,
141
+ provider,
142
+ currentPage.path,
143
+ [
144
+ ...(previousPrompts ?? []),
145
+ ...(htmlHistory?.map((h) => h.prompt) ?? []),
146
+ ]
147
+ );
148
+ if (result?.error) {
149
+ handleError(result.error, result.message);
150
+ return;
151
+ }
152
+
153
+ if (result?.success) {
154
+ setPrompt("");
155
+ }
156
+ } else {
157
+ const result = await callAiNewProject(
158
+ prompt,
159
+ model,
160
+ provider,
161
+ redesignMarkdown,
162
+ handleThink,
163
+ () => {
164
+ setIsThinking(false);
165
+ }
166
+ );
167
+
168
+ if (result?.error) {
169
+ handleError(result.error, result.message);
170
+ return;
171
+ }
172
+
173
+ if (result?.success) {
174
+ setPrompt("");
175
+ if (selectedModel?.isThinker) {
176
+ setModel(MODELS[0].value);
177
+ }
178
+ }
179
+ }
180
+ };
181
+
182
+ const handleThink = (think: string) => {
183
+ setThink(think);
184
+ setIsThinking(true);
185
+ setOpenThink(true);
186
+ };
187
+
188
+ const handleError = (error: string, message?: string) => {
189
+ switch (error) {
190
+ case "login_required":
191
+ setOpen(true);
192
+ break;
193
+ case "provider_required":
194
+ setOpenProvider(true);
195
+ setProviderError(message || "");
196
+ break;
197
+ case "pro_required":
198
+ setOpenProModal(true);
199
+ break;
200
+ case "api_error":
201
+ toast.error(message || "An error occurred");
202
+ break;
203
+ case "network_error":
204
+ toast.error(message || "Network error occurred");
205
+ break;
206
+ default:
207
+ toast.error("An unexpected error occurred");
208
+ }
209
+ };
210
+
211
+ useUpdateEffect(() => {
212
+ if (refThink.current) {
213
+ refThink.current.scrollTop = refThink.current.scrollHeight;
214
+ }
215
+ }, [think]);
216
+
217
+ useUpdateEffect(() => {
218
+ if (!isThinking) {
219
+ setOpenThink(false);
220
+ }
221
+ }, [isThinking]);
222
+
223
+ const isSameHtml = useMemo(() => {
224
+ return isTheSameHtml(currentPage.html);
225
+ }, [currentPage.html]);
226
+
227
+ return (
228
+ <div className="px-3">
229
+ <div className="relative bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent z-10 w-full group">
230
+ {think && (
231
+ <div className="w-full border-b border-neutral-700 relative overflow-hidden">
232
+ <header
233
+ className="flex items-center justify-between px-5 py-2.5 group hover:bg-neutral-600/20 transition-colors duration-200 cursor-pointer"
234
+ onClick={() => {
235
+ setOpenThink(!openThink);
236
+ }}
237
+ >
238
+ <p className="text-sm font-medium text-neutral-300 group-hover:text-neutral-200 transition-colors duration-200">
239
+ {isThinking ? "DeepSite is thinking..." : "DeepSite's plan"}
240
+ </p>
241
+ <ChevronDown
242
+ className={classNames(
243
+ "size-4 text-neutral-400 group-hover:text-neutral-300 transition-all duration-200",
244
+ {
245
+ "rotate-180": openThink,
246
+ }
247
+ )}
248
+ />
249
+ </header>
250
+ <main
251
+ ref={refThink}
252
+ className={classNames(
253
+ "overflow-y-auto transition-all duration-200 ease-in-out",
254
+ {
255
+ "max-h-[0px]": !openThink,
256
+ "min-h-[250px] max-h-[250px] border-t border-neutral-700":
257
+ openThink,
258
+ }
259
+ )}
260
+ >
261
+ <p className="text-[13px] text-neutral-400 whitespace-pre-line px-5 pb-4 pt-3">
262
+ {think}
263
+ </p>
264
+ </main>
265
+ </div>
266
+ )}
267
+ <SelectedFiles
268
+ files={selectedFiles}
269
+ isAiWorking={isAiWorking}
270
+ onDelete={(file) =>
271
+ setSelectedFiles((prev) => prev.filter((f) => f !== file))
272
+ }
273
+ />
274
+ {selectedElement && (
275
+ <div className="px-4 pt-3">
276
+ <SelectedHtmlElement
277
+ element={selectedElement}
278
+ isAiWorking={isAiWorking}
279
+ onDelete={() => setSelectedElement(null)}
280
+ />
281
+ </div>
282
+ )}
283
+ <div className="w-full relative flex items-center justify-between">
284
+ {(isAiWorking || isUploading) && (
285
+ <div className="absolute bg-neutral-800 top-0 left-4 w-[calc(100%-30px)] h-full z-1 flex items-start pt-3.5 justify-between max-lg:text-sm">
286
+ <div className="flex items-center justify-start gap-2">
287
+ <Loading overlay={false} className="!size-4 opacity-50" />
288
+ <p className="text-neutral-400 text-sm">
289
+ {isUploading ? (
290
+ "Uploading images..."
291
+ ) : isAiWorking && !isSameHtml ? (
292
+ "AI is working..."
293
+ ) : (
294
+ <span className="inline-flex">
295
+ {[
296
+ "D",
297
+ "e",
298
+ "e",
299
+ "p",
300
+ "S",
301
+ "i",
302
+ "t",
303
+ "e",
304
+ " ",
305
+ "i",
306
+ "s",
307
+ " ",
308
+ "T",
309
+ "h",
310
+ "i",
311
+ "n",
312
+ "k",
313
+ "i",
314
+ "n",
315
+ "g",
316
+ ".",
317
+ ".",
318
+ ".",
319
+ " ",
320
+ "W",
321
+ "a",
322
+ "i",
323
+ "t",
324
+ " ",
325
+ "a",
326
+ " ",
327
+ "m",
328
+ "o",
329
+ "m",
330
+ "e",
331
+ "n",
332
+ "t",
333
+ ".",
334
+ ".",
335
+ ".",
336
+ ].map((char, index) => (
337
+ <span
338
+ key={index}
339
+ className="bg-gradient-to-r from-neutral-100 to-neutral-300 bg-clip-text text-transparent animate-pulse"
340
+ style={{
341
+ animationDelay: `${index * 0.1}s`,
342
+ animationDuration: "1.3s",
343
+ animationIterationCount: "infinite",
344
+ }}
345
+ >
346
+ {char === " " ? "\u00A0" : char}
347
+ </span>
348
+ ))}
349
+ </span>
350
+ )}
351
+ </p>
352
+ </div>
353
+ {isAiWorking && (
354
+ <div
355
+ className="text-xs text-neutral-400 px-1 py-0.5 rounded-md border border-neutral-600 flex items-center justify-center gap-1.5 bg-neutral-800 hover:brightness-110 transition-all duration-200 cursor-pointer"
356
+ onClick={stopController}
357
+ >
358
+ <FaStopCircle />
359
+ Stop generation
360
+ </div>
361
+ )}
362
+ </div>
363
+ )}
364
+ <textarea
365
+ disabled={isAiWorking}
366
+ className={classNames(
367
+ "w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4 resize-none",
368
+ {
369
+ "!pt-2.5": selectedElement && !isAiWorking,
370
+ }
371
+ )}
372
+ placeholder={
373
+ selectedElement
374
+ ? `Ask DeepSite about ${selectedElement.tagName.toLowerCase()}...`
375
+ : isFollowUp && (!isSameHtml || pages?.length > 1)
376
+ ? "Ask DeepSite for edits"
377
+ : "Ask DeepSite anything..."
378
+ }
379
+ value={prompt}
380
+ onChange={(e) => setPrompt(e.target.value)}
381
+ onKeyDown={(e) => {
382
+ if (e.key === "Enter" && !e.shiftKey) {
383
+ callAi();
384
+ }
385
+ }}
386
+ />
387
+ </div>
388
+ <div className="flex items-center justify-between gap-2 px-4 pb-3 mt-2">
389
+ <div className="flex-1 flex items-center justify-start gap-1.5">
390
+ <Uploader
391
+ pages={pages}
392
+ onLoading={setIsUploading}
393
+ isLoading={isUploading}
394
+ onFiles={setFiles}
395
+ onSelectFile={(file) => {
396
+ if (selectedFiles.includes(file)) {
397
+ setSelectedFiles((prev) => prev.filter((f) => f !== file));
398
+ } else {
399
+ setSelectedFiles((prev) => [...prev, file]);
400
+ }
401
+ }}
402
+ files={files}
403
+ selectedFiles={selectedFiles}
404
+ project={project}
405
+ />
406
+ {isNew && <ReImagine onRedesign={(md) => callAi(md)} />}
407
+ {!isSameHtml && (
408
+ <Tooltip>
409
+ <TooltipTrigger asChild>
410
+ <Button
411
+ size="xs"
412
+ variant={isEditableModeEnabled ? "default" : "outline"}
413
+ onClick={() => {
414
+ setIsEditableModeEnabled?.(!isEditableModeEnabled);
415
+ }}
416
+ className={classNames("h-[28px]", {
417
+ "!text-neutral-400 hover:!text-neutral-200 !border-neutral-600 !hover:!border-neutral-500":
418
+ !isEditableModeEnabled,
419
+ })}
420
+ >
421
+ <Crosshair className="size-4" />
422
+ Edit
423
+ </Button>
424
+ </TooltipTrigger>
425
+ <TooltipContent
426
+ align="start"
427
+ className="bg-neutral-950 text-xs text-neutral-200 py-1 px-2 rounded-md -translate-y-0.5"
428
+ >
429
+ Select an element on the page to ask DeepSite edit it
430
+ directly.
431
+ </TooltipContent>
432
+ </Tooltip>
433
+ )}
434
+ {/* <InviteFriends /> */}
435
+ </div>
436
+ <div className="flex items-center justify-end gap-2">
437
+ <Settings
438
+ provider={provider as string}
439
+ model={model as string}
440
+ onChange={setProvider}
441
+ onModelChange={setModel}
442
+ open={openProvider}
443
+ error={providerError}
444
+ isFollowUp={!isSameHtml && isFollowUp}
445
+ onClose={setOpenProvider}
446
+ />
447
+ <Button
448
+ size="iconXs"
449
+ disabled={isAiWorking || !prompt.trim()}
450
+ onClick={() => callAi()}
451
+ >
452
+ <ArrowUp className="size-4" />
453
+ </Button>
454
+ </div>
455
+ </div>
456
+ <LoginModal open={open} onClose={() => setOpen(false)} pages={pages} />
457
+ <ProModal
458
+ pages={pages}
459
+ open={openProModal}
460
+ onClose={() => setOpenProModal(false)}
461
+ />
462
+ {pages.length === 1 && (
463
+ <div className="border border-sky-500/20 bg-sky-500/40 hover:bg-sky-600 transition-all duration-200 text-sky-500 pl-2 pr-4 py-1.5 text-xs rounded-full absolute top-0 -translate-y-[calc(100%+8px)] left-0 max-w-max flex items-center justify-start gap-2">
464
+ <span className="rounded-full text-[10px] font-semibold bg-white text-neutral-900 px-1.5 py-0.5">
465
+ NEW
466
+ </span>
467
+ <p className="text-sm text-neutral-100">
468
+ DeepSite can now create multiple pages at once. Try it!
469
+ </p>
470
+ </div>
471
+ )}
472
+ {!isSameHtml && (
473
+ <div className="absolute top-0 right-0 -translate-y-[calc(100%+8px)] select-none text-xs text-neutral-400 flex items-center justify-center gap-2 bg-neutral-800 border border-neutral-700 rounded-md p-1 pr-2.5">
474
+ <label
475
+ htmlFor="diff-patch-checkbox"
476
+ className="flex items-center gap-1.5 cursor-pointer"
477
+ >
478
+ <Checkbox
479
+ id="diff-patch-checkbox"
480
+ checked={isFollowUp}
481
+ onCheckedChange={(e) => {
482
+ if (e === true && !isSameHtml && selectedModel?.isThinker) {
483
+ setModel(MODELS[0].value);
484
+ }
485
+ setIsFollowUp(e === true);
486
+ }}
487
+ />
488
+ Diff-Patch Update
489
+ </label>
490
+ <FollowUpTooltip />
491
+ </div>
492
+ )}
493
+ </div>
494
+ <audio ref={hookAudio} id="audio" className="hidden">
495
+ <source src="/success.mp3" type="audio/mpeg" />
496
+ Your browser does not support the audio element.
497
+ </audio>
498
+ </div>
499
+ );
500
+ }
index.tsx (2).txt ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { ArrowUp } from "lucide-react";
4
+ import { PiGearSixFill } from "react-icons/pi";
5
+ import { TiUserAdd } from "react-icons/ti";
6
+
7
+ import { Button } from "@/components/ui/button";
8
+
9
+ export const AskAi = () => {
10
+ return (
11
+ <>
12
+ <div className="bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent group">
13
+ <textarea
14
+ rows={3}
15
+ className="w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4 resize-none mb-1"
16
+ placeholder="Ask DeepSite anything..."
17
+ onChange={() => {}}
18
+ onKeyDown={() => {}}
19
+ />
20
+ <div className="flex items-center justify-between gap-2 px-4 pb-3">
21
+ <div className="flex-1 flex justify-start">
22
+ <Button
23
+ size="iconXs"
24
+ variant="outline"
25
+ className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
26
+ >
27
+ <TiUserAdd className="size-4" />
28
+ </Button>
29
+ </div>
30
+ <div className="flex items-center justify-end gap-2">
31
+ <Button variant="black" size="sm">
32
+ <PiGearSixFill className="size-4" />
33
+ Settings
34
+ </Button>
35
+ <Button size="iconXs">
36
+ <ArrowUp className="size-4" />
37
+ </Button>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </>
42
+ );
43
+ };
index.tsx (3).txt ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useLocalStorage } from "react-use";
2
+ import { Button } from "@/components/ui/button";
3
+ import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
4
+ import { CheckCheck } from "lucide-react";
5
+ import { isTheSameHtml } from "@/lib/compare-html-diff";
6
+ import { Page } from "@/types";
7
+
8
+ export const ProModal = ({
9
+ open,
10
+ pages,
11
+ onClose,
12
+ }: {
13
+ open: boolean;
14
+ pages: Page[];
15
+ onClose: React.Dispatch<React.SetStateAction<boolean>>;
16
+ }) => {
17
+ const [, setStorage] = useLocalStorage("pages");
18
+ const handleProClick = () => {
19
+ if (pages && !isTheSameHtml(pages?.[0].html)) {
20
+ setStorage(pages);
21
+ }
22
+ window.open("https://huggingface.co/subscribe/pro?from=DeepSite", "_blank");
23
+ onClose(false);
24
+ };
25
+ return (
26
+ <Dialog open={open} onOpenChange={onClose}>
27
+ <DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
28
+ <DialogTitle className="hidden" />
29
+ <main className="flex flex-col items-start text-left relative pt-2">
30
+ <div className="flex items-center justify-start -space-x-4 mb-5">
31
+ <div className="size-14 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
32
+ 🚀
33
+ </div>
34
+ <div className="size-16 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-4xl z-2">
35
+ 🤩
36
+ </div>
37
+ <div className="size-14 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
38
+ 🥳
39
+ </div>
40
+ </div>
41
+ <h2 className="text-2xl font-bold text-neutral-950">
42
+ Only $9 to enhance your possibilities
43
+ </h2>
44
+ <p className="text-neutral-500 text-base mt-2 max-w-sm">
45
+ It seems like you have reached the monthly free limit of DeepSite.
46
+ </p>
47
+ <hr className="bg-neutral-200 w-full max-w-[150px] my-6" />
48
+ <p className="text-lg mt-3 text-neutral-900 font-semibold">
49
+ Upgrade to a <ProTag className="mx-1" /> Account, and unlock your
50
+ DeepSite high quota access ⚡
51
+ </p>
52
+ <ul className="mt-3 space-y-1 text-neutral-500">
53
+ <li className="text-sm text-neutral-500 space-x-2 flex items-center justify-start gap-2 mb-3">
54
+ You&apos;ll also unlock some Hugging Face PRO features, like:
55
+ </li>
56
+ <li className="text-sm space-x-2 flex items-center justify-start gap-2">
57
+ <CheckCheck className="text-emerald-500 size-4" />
58
+ Get acces to thousands of AI app (ZeroGPU) with high quota
59
+ </li>
60
+ <li className="text-sm space-x-2 flex items-center justify-start gap-2">
61
+ <CheckCheck className="text-emerald-500 size-4" />
62
+ Get exclusive early access to new features and updates
63
+ </li>
64
+ <li className="text-sm space-x-2 flex items-center justify-start gap-2">
65
+ <CheckCheck className="text-emerald-500 size-4" />
66
+ Get free credits across all Inference Providers
67
+ </li>
68
+ <li className="text-sm text-neutral-500 space-x-2 flex items-center justify-start gap-2 mt-3">
69
+ ... and lots more!
70
+ </li>
71
+ </ul>
72
+ <Button
73
+ variant="black"
74
+ size="lg"
75
+ className="w-full !text-base !h-11 mt-8"
76
+ onClick={handleProClick}
77
+ >
78
+ Subscribe to PRO ($9/month)
79
+ </Button>
80
+ </main>
81
+ </DialogContent>
82
+ </Dialog>
83
+ );
84
+ };
85
+
86
+ const ProTag = ({ className }: { className?: string }) => (
87
+ <span
88
+ className={`${className} bg-linear-to-br shadow-green-500/10 dark:shadow-green-500/20 inline-block -skew-x-12 border border-gray-200 from-pink-300 via-green-200 to-yellow-200 text-xs font-bold text-black shadow-lg rounded-md px-2.5 py-0.5`}
89
+ >
90
+ PRO
91
+ </span>
92
+ );
93
+ export default ProModal;
index.tsx (4).txt ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { Plus } from "lucide-react";
3
+ import Link from "next/link";
4
+ import { useState } from "react";
5
+
6
+ import { useUser } from "@/hooks/useUser";
7
+ import { Project } from "@/types";
8
+ import { redirect } from "next/navigation";
9
+ import { ProjectCard } from "./project-card";
10
+ import { LoadProject } from "./load-project";
11
+
12
+ export function MyProjects({
13
+ projects: initialProjects,
14
+ }: {
15
+ projects: Project[];
16
+ }) {
17
+ const { user } = useUser();
18
+ if (!user) {
19
+ redirect("/");
20
+ }
21
+ const [projects, setProjects] = useState<Project[]>(initialProjects || []);
22
+ return (
23
+ <>
24
+ <section className="max-w-[86rem] py-12 px-4 mx-auto">
25
+ <header className="flex items-center justify-between max-lg:flex-col gap-4">
26
+ <div className="text-left">
27
+ <h1 className="text-3xl font-bold text-white">
28
+ <span className="capitalize">{user.fullname}</span>&apos;s
29
+ DeepSite Projects
30
+ </h1>
31
+ <p className="text-muted-foreground text-base mt-1 max-w-xl">
32
+ Create, manage, and explore your DeepSite projects.
33
+ </p>
34
+ </div>
35
+ <LoadProject
36
+ fullXsBtn
37
+ onSuccess={(project: Project) => {
38
+ setProjects((prev) => [...prev, project]);
39
+ }}
40
+ />
41
+ </header>
42
+ <div className="mt-8 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
43
+ <Link
44
+ href="/projects/new"
45
+ className="bg-neutral-900 rounded-xl h-44 flex items-center justify-center text-neutral-300 border border-neutral-800 hover:brightness-110 transition-all duration-200"
46
+ >
47
+ <Plus className="size-5 mr-1.5" />
48
+ Create Project
49
+ </Link>
50
+ {projects.map((project: Project) => (
51
+ <ProjectCard key={project._id} project={project} />
52
+ ))}
53
+ </div>
54
+ </section>
55
+ </>
56
+ );
57
+ }
index.tsx (5).txt ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useLocalStorage } from "react-use";
2
+ import { Button } from "@/components/ui/button";
3
+ import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
4
+ import { useUser } from "@/hooks/useUser";
5
+ import { isTheSameHtml } from "@/lib/compare-html-diff";
6
+ import { Page } from "@/types";
7
+
8
+ export const LoginModal = ({
9
+ open,
10
+ pages,
11
+ onClose,
12
+ title = "Log In to use DeepSite for free",
13
+ description = "Log In through your Hugging Face account to continue using DeepSite and increase your monthly free limit.",
14
+ }: {
15
+ open: boolean;
16
+ pages?: Page[];
17
+ onClose: React.Dispatch<React.SetStateAction<boolean>>;
18
+ title?: string;
19
+ description?: string;
20
+ }) => {
21
+ const { openLoginWindow } = useUser();
22
+ const [, setStorage] = useLocalStorage("pages");
23
+ const handleClick = async () => {
24
+ if (pages && !isTheSameHtml(pages[0].html)) {
25
+ setStorage(pages);
26
+ }
27
+ openLoginWindow();
28
+ onClose(false);
29
+ };
30
+ return (
31
+ <Dialog open={open} onOpenChange={onClose}>
32
+ <DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
33
+ <DialogTitle className="hidden" />
34
+ <main className="flex flex-col items-start text-left relative pt-2">
35
+ <div className="flex items-center justify-start -space-x-4 mb-5">
36
+ <div className="size-14 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
37
+ 💪
38
+ </div>
39
+ <div className="size-16 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-4xl z-2">
40
+ 😎
41
+ </div>
42
+ <div className="size-14 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-3xl opacity-50">
43
+ 🙌
44
+ </div>
45
+ </div>
46
+ <p className="text-2xl font-bold text-neutral-950">{title}</p>
47
+ <p className="text-neutral-500 text-base mt-2 max-w-sm">
48
+ {description}
49
+ </p>
50
+ <Button
51
+ variant="black"
52
+ size="lg"
53
+ className="w-full !text-base !h-11 mt-8"
54
+ onClick={handleClick}
55
+ >
56
+ Log In to Continue
57
+ </Button>
58
+ </main>
59
+ </DialogContent>
60
+ </Dialog>
61
+ );
62
+ };
index.tsx (6).txt ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import classNames from "classnames";
2
+
3
+ function Loading({
4
+ overlay = true,
5
+ className,
6
+ }: {
7
+ overlay?: boolean;
8
+ className?: string;
9
+ }) {
10
+ return (
11
+ <div
12
+ className={classNames("", {
13
+ "absolute left-0 top-0 h-full w-full flex items-center justify-center z-20 bg-black/50 rounded-full":
14
+ overlay,
15
+ })}
16
+ >
17
+ <svg
18
+ className={`size-5 animate-spin text-white ${className}`}
19
+ xmlns="http://www.w3.org/2000/svg"
20
+ fill="none"
21
+ viewBox="0 0 24 24"
22
+ >
23
+ <circle
24
+ className="opacity-25"
25
+ cx="12"
26
+ cy="12"
27
+ r="10"
28
+ stroke="currentColor"
29
+ strokeWidth="4"
30
+ ></circle>
31
+ <path
32
+ className="opacity-75"
33
+ fill="currentColor"
34
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
35
+ ></path>
36
+ </svg>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ export default Loading;
index.tsx (7).txt ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { TiUserAdd } from "react-icons/ti";
2
+ import { Link } from "lucide-react";
3
+ import { FaXTwitter } from "react-icons/fa6";
4
+ import { useCopyToClipboard } from "react-use";
5
+ import { toast } from "sonner";
6
+
7
+ import { Button } from "@/components/ui/button";
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogTitle,
12
+ DialogTrigger,
13
+ } from "@/components/ui/dialog";
14
+
15
+ export function InviteFriends() {
16
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
17
+ const [_, copyToClipboard] = useCopyToClipboard();
18
+
19
+ return (
20
+ <Dialog>
21
+ <form>
22
+ <DialogTrigger asChild>
23
+ <Button
24
+ size="iconXs"
25
+ variant="outline"
26
+ className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
27
+ >
28
+ <TiUserAdd className="size-4" />
29
+ </Button>
30
+ </DialogTrigger>
31
+ <DialogContent className="sm:max-w-lg lg:!p-8 !rounded-3xl !bg-white !border-neutral-100">
32
+ <DialogTitle className="hidden" />
33
+ <main>
34
+ <div className="flex items-center justify-start -space-x-4 mb-5">
35
+ <div className="size-11 rounded-full bg-pink-300 shadow-2xs flex items-center justify-center text-2xl">
36
+ 😎
37
+ </div>
38
+ <div className="size-11 rounded-full bg-amber-300 shadow-2xs flex items-center justify-center text-2xl z-2">
39
+ 😇
40
+ </div>
41
+ <div className="size-11 rounded-full bg-sky-300 shadow-2xs flex items-center justify-center text-2xl">
42
+ 😜
43
+ </div>
44
+ </div>
45
+ <p className="text-xl font-semibold text-neutral-950 max-w-[200px]">
46
+ Invite your friends to join us!
47
+ </p>
48
+ <p className="text-sm text-neutral-500 mt-2 max-w-sm">
49
+ Support us and share the love and let them know about our awesome
50
+ platform.
51
+ </p>
52
+ <div className="mt-4 space-x-3.5">
53
+ <a
54
+ href="https://x.com/intent/post?url=https://enzostvs-deepsite.hf.space/&text=Checkout%20this%20awesome%20Ai%20Tool!%20Vibe%20coding%20has%20never%20been%20so%20easy✨"
55
+ target="_blank"
56
+ rel="noopener noreferrer"
57
+ >
58
+ <Button
59
+ variant="lightGray"
60
+ size="sm"
61
+ className="!text-neutral-700"
62
+ >
63
+ <FaXTwitter className="size-4" />
64
+ Share on
65
+ </Button>
66
+ </a>
67
+ <Button
68
+ variant="lightGray"
69
+ size="sm"
70
+ className="!text-neutral-700"
71
+ onClick={() => {
72
+ copyToClipboard("https://enzostvs-deepsite.hf.space/");
73
+ toast.success("Invite link copied to clipboard!");
74
+ }}
75
+ >
76
+ <Link className="size-4" />
77
+ Copy Invite Link
78
+ </Button>
79
+ </div>
80
+ </main>
81
+ </DialogContent>
82
+ </form>
83
+ </Dialog>
84
+ );
85
+ }
index.tsx (8).txt ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+ import { useMemo, useRef, useState } from "react";
3
+ import { toast } from "sonner";
4
+ import { editor } from "monaco-editor";
5
+ import Editor from "@monaco-editor/react";
6
+ import { CopyIcon } from "lucide-react";
7
+ import {
8
+ useCopyToClipboard,
9
+ useEvent,
10
+ useLocalStorage,
11
+ useMount,
12
+ useUnmount,
13
+ useUpdateEffect,
14
+ } from "react-use";
15
+ import classNames from "classnames";
16
+ import { useRouter, useSearchParams } from "next/navigation";
17
+
18
+ import { Header } from "@/components/editor/header";
19
+ import { Footer } from "@/components/editor/footer";
20
+ import { defaultHTML } from "@/lib/consts";
21
+ import { Preview } from "@/components/editor/preview";
22
+ import { useEditor } from "@/hooks/useEditor";
23
+ import { AskAI } from "@/components/editor/ask-ai";
24
+ import { DeployButton } from "./deploy-button";
25
+ import { Page, Project } from "@/types";
26
+ import { SaveButton } from "./save-button";
27
+ import { LoadProject } from "../my-projects/load-project";
28
+ import { isTheSameHtml } from "@/lib/compare-html-diff";
29
+ import { ListPages } from "./pages";
30
+
31
+ export const AppEditor = ({
32
+ project,
33
+ pages: initialPages,
34
+ images,
35
+ isNew,
36
+ }: {
37
+ project?: Project | null;
38
+ pages?: Page[];
39
+ images?: string[];
40
+ isNew?: boolean;
41
+ }) => {
42
+ const [htmlStorage, , removeHtmlStorage] = useLocalStorage("pages");
43
+ const [, copyToClipboard] = useCopyToClipboard();
44
+ const { htmlHistory, setHtmlHistory, prompts, setPrompts, pages, setPages } =
45
+ useEditor(
46
+ initialPages,
47
+ project?.prompts ?? [],
48
+ typeof htmlStorage === "string" ? htmlStorage : undefined
49
+ );
50
+
51
+ const searchParams = useSearchParams();
52
+ const router = useRouter();
53
+ const deploy = searchParams.get("deploy") === "true";
54
+
55
+ const iframeRef = useRef<HTMLIFrameElement | null>(null);
56
+ const preview = useRef<HTMLDivElement>(null);
57
+ const editor = useRef<HTMLDivElement>(null);
58
+ const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
59
+ const resizer = useRef<HTMLDivElement>(null);
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ const monacoRef = useRef<any>(null);
62
+
63
+ const [currentTab, setCurrentTab] = useState("chat");
64
+ const [currentPage, setCurrentPage] = useState("index.html");
65
+ const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
66
+ const [isResizing, setIsResizing] = useState(false);
67
+ const [isAiWorking, setIsAiWorking] = useState(false);
68
+ const [isEditableModeEnabled, setIsEditableModeEnabled] = useState(false);
69
+ const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(
70
+ null
71
+ );
72
+ const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
73
+
74
+ const resetLayout = () => {
75
+ if (!editor.current || !preview.current) return;
76
+
77
+ // lg breakpoint is 1024px based on useBreakpoint definition and Tailwind defaults
78
+ if (window.innerWidth >= 1024) {
79
+ // Set initial 1/3 - 2/3 sizes for large screens, accounting for resizer width
80
+ const resizerWidth = resizer.current?.offsetWidth ?? 8; // w-2 = 0.5rem = 8px
81
+ const availableWidth = window.innerWidth - resizerWidth;
82
+ const initialEditorWidth = availableWidth / 3; // Editor takes 1/3 of space
83
+ const initialPreviewWidth = availableWidth - initialEditorWidth; // Preview takes 2/3
84
+ editor.current.style.width = `${initialEditorWidth}px`;
85
+ preview.current.style.width = `${initialPreviewWidth}px`;
86
+ } else {
87
+ // Remove inline styles for smaller screens, let CSS flex-col handle it
88
+ editor.current.style.width = "";
89
+ preview.current.style.width = "";
90
+ }
91
+ };
92
+
93
+ const handleResize = (e: MouseEvent) => {
94
+ if (!editor.current || !preview.current || !resizer.current) return;
95
+
96
+ const resizerWidth = resizer.current.offsetWidth;
97
+ const minWidth = 100; // Minimum width for editor/preview
98
+ const maxWidth = window.innerWidth - resizerWidth - minWidth;
99
+
100
+ const editorWidth = e.clientX;
101
+ const clampedEditorWidth = Math.max(
102
+ minWidth,
103
+ Math.min(editorWidth, maxWidth)
104
+ );
105
+ const calculatedPreviewWidth =
106
+ window.innerWidth - clampedEditorWidth - resizerWidth;
107
+
108
+ editor.current.style.width = `${clampedEditorWidth}px`;
109
+ preview.current.style.width = `${calculatedPreviewWidth}px`;
110
+ };
111
+
112
+ const handleMouseDown = () => {
113
+ setIsResizing(true);
114
+ document.addEventListener("mousemove", handleResize);
115
+ document.addEventListener("mouseup", handleMouseUp);
116
+ };
117
+
118
+ const handleMouseUp = () => {
119
+ setIsResizing(false);
120
+ document.removeEventListener("mousemove", handleResize);
121
+ document.removeEventListener("mouseup", handleMouseUp);
122
+ };
123
+
124
+ useMount(() => {
125
+ if (deploy && project?._id) {
126
+ toast.success("Your project is deployed! 🎉", {
127
+ action: {
128
+ label: "See Project",
129
+ onClick: () => {
130
+ window.open(
131
+ `https://huggingface.co/spaces/${project?.space_id}`,
132
+ "_blank"
133
+ );
134
+ },
135
+ },
136
+ });
137
+ router.replace(`/projects/${project?.space_id}`);
138
+ }
139
+ if (htmlStorage) {
140
+ removeHtmlStorage();
141
+ toast.warning("Previous HTML content restored from local storage.");
142
+ }
143
+
144
+ resetLayout();
145
+ if (!resizer.current) return;
146
+ resizer.current.addEventListener("mousedown", handleMouseDown);
147
+ window.addEventListener("resize", resetLayout);
148
+ });
149
+ useUnmount(() => {
150
+ document.removeEventListener("mousemove", handleResize);
151
+ document.removeEventListener("mouseup", handleMouseUp);
152
+ if (resizer.current) {
153
+ resizer.current.removeEventListener("mousedown", handleMouseDown);
154
+ }
155
+ window.removeEventListener("resize", resetLayout);
156
+ });
157
+
158
+ // Prevent accidental navigation away when AI is working or content has changed
159
+ useEvent("beforeunload", (e) => {
160
+ if (isAiWorking || !isTheSameHtml(currentPageData?.html)) {
161
+ e.preventDefault();
162
+ return "";
163
+ }
164
+ });
165
+
166
+ useUpdateEffect(() => {
167
+ if (currentTab === "chat") {
168
+ // Reset editor width when switching to reasoning tab
169
+ resetLayout();
170
+ // re-add the event listener for resizing
171
+ if (resizer.current) {
172
+ resizer.current.addEventListener("mousedown", handleMouseDown);
173
+ }
174
+ } else {
175
+ if (preview.current) {
176
+ // Reset preview width when switching to preview tab
177
+ preview.current.style.width = "100%";
178
+ }
179
+ }
180
+ }, [currentTab]);
181
+
182
+ const handleEditorValidation = (markers: editor.IMarker[]) => {
183
+ console.log("Editor validation markers:", markers);
184
+ };
185
+
186
+ const currentPageData = useMemo(() => {
187
+ return (
188
+ pages.find((page) => page.path === currentPage) ?? {
189
+ path: "index.html",
190
+ html: defaultHTML,
191
+ }
192
+ );
193
+ }, [pages, currentPage]);
194
+
195
+ return (
196
+ <section className="h-[100dvh] bg-neutral-950 flex flex-col">
197
+ <Header tab={currentTab} onNewTab={setCurrentTab}>
198
+ <LoadProject
199
+ onSuccess={(project: Project) => {
200
+ router.push(`/projects/${project.space_id}`);
201
+ }}
202
+ />
203
+ {/* for these buttons pass the whole pages */}
204
+ {project?._id ? (
205
+ <SaveButton pages={pages} prompts={prompts} />
206
+ ) : (
207
+ <DeployButton pages={pages} prompts={prompts} />
208
+ )}
209
+ </Header>
210
+ <main className="bg-neutral-950 flex-1 max-lg:flex-col flex w-full max-lg:h-[calc(100%-82px)] relative">
211
+ {currentTab === "chat" && (
212
+ <>
213
+ <div
214
+ ref={editor}
215
+ className="bg-neutral-900 relative flex-1 overflow-hidden h-full flex flex-col gap-2 pb-3"
216
+ >
217
+ <ListPages
218
+ pages={pages}
219
+ currentPage={currentPage}
220
+ onSelectPage={(path, newPath) => {
221
+ if (newPath) {
222
+ setPages((prev) =>
223
+ prev.map((page) =>
224
+ page.path === path ? { ...page, path: newPath } : page
225
+ )
226
+ );
227
+ setCurrentPage(newPath);
228
+ } else {
229
+ setCurrentPage(path);
230
+ }
231
+ }}
232
+ onDeletePage={(path) => {
233
+ const newPages = pages.filter((page) => page.path !== path);
234
+ setPages(newPages);
235
+ if (currentPage === path) {
236
+ setCurrentPage(newPages[0]?.path ?? "index.html");
237
+ }
238
+ }}
239
+ onNewPage={() => {
240
+ setPages((prev) => [
241
+ ...prev,
242
+ {
243
+ path: `page-${prev.length + 1}.html`,
244
+ html: defaultHTML,
245
+ },
246
+ ]);
247
+ setCurrentPage(`page-${pages.length + 1}.html`);
248
+ }}
249
+ />
250
+ <CopyIcon
251
+ className="size-4 absolute top-14 right-5 text-neutral-500 hover:text-neutral-300 z-2 cursor-pointer"
252
+ onClick={() => {
253
+ copyToClipboard(currentPageData.html);
254
+ toast.success("HTML copied to clipboard!");
255
+ }}
256
+ />
257
+ <Editor
258
+ defaultLanguage="html"
259
+ theme="vs-dark"
260
+ className={classNames(
261
+ "h-full bg-neutral-900 transition-all duration-200 absolute left-0 top-0",
262
+ {
263
+ "pointer-events-none": isAiWorking,
264
+ }
265
+ )}
266
+ options={{
267
+ colorDecorators: true,
268
+ fontLigatures: true,
269
+ theme: "vs-dark",
270
+ minimap: { enabled: false },
271
+ scrollbar: {
272
+ horizontal: "hidden",
273
+ },
274
+ wordWrap: "on",
275
+ }}
276
+ value={currentPageData.html}
277
+ onChange={(value) => {
278
+ const newValue = value ?? "";
279
+ // setHtml(newValue);
280
+ setPages((prev) =>
281
+ prev.map((page) =>
282
+ page.path === currentPageData.path
283
+ ? { ...page, html: newValue }
284
+ : page
285
+ )
286
+ );
287
+ }}
288
+ onMount={(editor, monaco) => {
289
+ editorRef.current = editor;
290
+ monacoRef.current = monaco;
291
+ }}
292
+ onValidate={handleEditorValidation}
293
+ />
294
+ <AskAI
295
+ project={project}
296
+ images={images}
297
+ currentPage={currentPageData}
298
+ htmlHistory={htmlHistory}
299
+ previousPrompts={prompts}
300
+ onSuccess={(newPages, p: string) => {
301
+ const currentHistory = [...htmlHistory];
302
+ currentHistory.unshift({
303
+ pages: newPages,
304
+ createdAt: new Date(),
305
+ prompt: p,
306
+ });
307
+ setHtmlHistory(currentHistory);
308
+ setSelectedElement(null);
309
+ setSelectedFiles([]);
310
+ // if xs or sm
311
+ if (window.innerWidth <= 1024) {
312
+ setCurrentTab("preview");
313
+ }
314
+ // if (updatedLines && updatedLines?.length > 0) {
315
+ // const decorations = updatedLines.map((line) => ({
316
+ // range: new monacoRef.current.Range(
317
+ // line[0],
318
+ // 1,
319
+ // line[1],
320
+ // 1
321
+ // ),
322
+ // options: {
323
+ // inlineClassName: "matched-line",
324
+ // },
325
+ // }));
326
+ // setTimeout(() => {
327
+ // editorRef?.current
328
+ // ?.getModel()
329
+ // ?.deltaDecorations([], decorations);
330
+
331
+ // editorRef.current?.revealLine(updatedLines[0][0]);
332
+ // }, 100);
333
+ // }
334
+ }}
335
+ setPages={setPages}
336
+ pages={pages}
337
+ setCurrentPage={setCurrentPage}
338
+ isAiWorking={isAiWorking}
339
+ setisAiWorking={setIsAiWorking}
340
+ onNewPrompt={(prompt: string) => {
341
+ setPrompts((prev) => [...prev, prompt]);
342
+ }}
343
+ onScrollToBottom={() => {
344
+ editorRef.current?.revealLine(
345
+ editorRef.current?.getModel()?.getLineCount() ?? 0
346
+ );
347
+ }}
348
+ isNew={isNew}
349
+ isEditableModeEnabled={isEditableModeEnabled}
350
+ setIsEditableModeEnabled={setIsEditableModeEnabled}
351
+ selectedElement={selectedElement}
352
+ setSelectedElement={setSelectedElement}
353
+ setSelectedFiles={setSelectedFiles}
354
+ selectedFiles={selectedFiles}
355
+ />
356
+ </div>
357
+ <div
358
+ ref={resizer}
359
+ className="bg-neutral-800 hover:bg-sky-500 active:bg-sky-500 w-1.5 cursor-col-resize h-full max-lg:hidden"
360
+ />
361
+ </>
362
+ )}
363
+ <Preview
364
+ html={currentPageData?.html}
365
+ isResizing={isResizing}
366
+ isAiWorking={isAiWorking}
367
+ ref={preview}
368
+ device={device}
369
+ pages={pages}
370
+ setCurrentPage={setCurrentPage}
371
+ currentTab={currentTab}
372
+ isEditableModeEnabled={isEditableModeEnabled}
373
+ iframeRef={iframeRef}
374
+ onClickElement={(element) => {
375
+ setIsEditableModeEnabled(false);
376
+ setSelectedElement(element);
377
+ setCurrentTab("chat");
378
+ }}
379
+ />
380
+ </main>
381
+ <Footer
382
+ pages={pages}
383
+ htmlHistory={htmlHistory}
384
+ setPages={setPages}
385
+ iframeRef={iframeRef}
386
+ device={device}
387
+ isNew={isNew}
388
+ setDevice={setDevice}
389
+ />
390
+ </section>
391
+ );
392
+ };
index.tsx (9).txt ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { useState } from "react";
3
+ import { toast } from "sonner";
4
+ import { MdSave } from "react-icons/md";
5
+ import { useParams } from "next/navigation";
6
+
7
+ import Loading from "@/components/loading";
8
+ import { Button } from "@/components/ui/button";
9
+ import { api } from "@/lib/api";
10
+ import { Page } from "@/types";
11
+
12
+ export function SaveButton({
13
+ pages,
14
+ prompts,
15
+ }: {
16
+ pages: Page[];
17
+ prompts: string[];
18
+ }) {
19
+ // get params from URL
20
+ const { namespace, repoId } = useParams<{
21
+ namespace: string;
22
+ repoId: string;
23
+ }>();
24
+ const [loading, setLoading] = useState(false);
25
+
26
+ const updateSpace = async () => {
27
+ setLoading(true);
28
+
29
+ try {
30
+ const res = await api.put(`/me/projects/${namespace}/${repoId}`, {
31
+ pages,
32
+ prompts,
33
+ });
34
+ if (res.data.ok) {
35
+ toast.success("Your space is updated! 🎉", {
36
+ action: {
37
+ label: "See Space",
38
+ onClick: () => {
39
+ window.open(
40
+ `https://huggingface.co/spaces/${namespace}/${repoId}`,
41
+ "_blank"
42
+ );
43
+ },
44
+ },
45
+ });
46
+ } else {
47
+ toast.error(res?.data?.error || "Failed to update space");
48
+ }
49
+ } catch (err: any) {
50
+ toast.error(err.response?.data?.error || err.message);
51
+ } finally {
52
+ setLoading(false);
53
+ }
54
+ };
55
+ return (
56
+ <>
57
+ <Button
58
+ variant="default"
59
+ className="max-lg:hidden !px-4 relative"
60
+ onClick={updateSpace}
61
+ >
62
+ <MdSave className="size-4" />
63
+ Publish your Project{" "}
64
+ {loading && <Loading className="ml-2 size-4 animate-spin" />}
65
+ </Button>
66
+ <Button
67
+ variant="default"
68
+ size="sm"
69
+ className="lg:hidden relative"
70
+ onClick={updateSpace}
71
+ >
72
+ Publish {loading && <Loading className="ml-2 size-4 animate-spin" />}
73
+ </Button>
74
+ </>
75
+ );
76
+ }