diff --git a/Dockerfile.txt b/Dockerfile.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cbe0188aaee92186937765d2c85d76f7b212c537
--- /dev/null
+++ b/Dockerfile.txt
@@ -0,0 +1,19 @@
+FROM node:20-alpine
+USER root
+
+USER 1000
+WORKDIR /usr/src/app
+# Copy package.json and package-lock.json to the container
+COPY --chown=1000 package.json package-lock.json ./
+
+# Copy the rest of the application files to the container
+COPY --chown=1000 . .
+
+RUN npm install
+RUN npm run build
+
+# Expose the application port (assuming your app runs on port 3000)
+EXPOSE 3000
+
+# Start the application
+CMD ["npm", "start"]
\ No newline at end of file
diff --git a/Project.ts b/Project.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0f4d2943ef1137fa8d81b8055fdb6d0e105e73fd
--- /dev/null
+++ b/Project.ts
@@ -0,0 +1,27 @@
+import mongoose from "mongoose";
+
+const ProjectSchema = new mongoose.Schema({
+ space_id: {
+ type: String,
+ required: true,
+ },
+ user_id: {
+ type: String,
+ required: true,
+ },
+ prompts: {
+ type: [String],
+ default: [],
+ },
+ _createdAt: {
+ type: Date,
+ default: Date.now,
+ },
+ _updatedAt: {
+ type: Date,
+ default: Date.now,
+ },
+});
+
+export default mongoose.models.Project ||
+ mongoose.model("Project", ProjectSchema);
diff --git a/README (2).md b/README (2).md
new file mode 100644
index 0000000000000000000000000000000000000000..5ab2231fc7dc96070548f1d03ab1d0f73a799600
--- /dev/null
+++ b/README (2).md
@@ -0,0 +1,22 @@
+---
+title: DeepSite v2
+emoji: 🐳
+colorFrom: blue
+colorTo: blue
+sdk: docker
+pinned: true
+app_port: 3000
+license: mit
+short_description: Generate any application with DeepSeek
+models:
+ - deepseek-ai/DeepSeek-V3-0324
+ - deepseek-ai/DeepSeek-R1-0528
+---
+
+# DeepSite 🐳
+
+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.
+
+## How to use it locally
+
+Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
diff --git a/api.ts b/api.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7afcb38097bcebad958410a3788b50e229e76d0e
--- /dev/null
+++ b/api.ts
@@ -0,0 +1,35 @@
+import axios from "axios";
+import MY_TOKEN_KEY from "./get-cookie-name";
+
+export const api = axios.create({
+ baseURL: `/api`,
+ headers: {
+ cache: "no-store",
+ },
+});
+
+export const apiServer = axios.create({
+ baseURL: process.env.NEXT_APP_API_URL as string,
+ headers: {
+ cache: "no-store",
+ },
+});
+
+api.interceptors.request.use(
+ async (config) => {
+ // get the token from cookies
+ const cookie_name = MY_TOKEN_KEY();
+ const token = document.cookie
+ .split("; ")
+ .find((row) => row.startsWith(`${cookie_name}=`))
+ ?.split("=")[1];
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+ },
+ (error) => {
+ // Handle the error
+ return Promise.reject(error);
+ }
+);
diff --git a/app-context.tsx.txt b/app-context.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a97820d9e26fa6197ef583ce88aba66a3bc10082
--- /dev/null
+++ b/app-context.tsx.txt
@@ -0,0 +1,57 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+"use client";
+
+import { useUser } from "@/hooks/useUser";
+import { usePathname, useRouter } from "next/navigation";
+import { useMount } from "react-use";
+import { UserContext } from "@/components/contexts/user-context";
+import { User } from "@/types";
+import { toast } from "sonner";
+import { useBroadcastChannel } from "@/lib/useBroadcastChannel";
+
+export default function AppContext({
+ children,
+ me: initialData,
+}: {
+ children: React.ReactNode;
+ me?: {
+ user: User | null;
+ errCode: number | null;
+ };
+}) {
+ const { loginFromCode, user, logout, loading, errCode } =
+ useUser(initialData);
+ const pathname = usePathname();
+ const router = useRouter();
+
+ useMount(() => {
+ if (!initialData?.user && !user) {
+ if ([401, 403].includes(errCode as number)) {
+ logout();
+ } else if (pathname.includes("/spaces")) {
+ if (errCode) {
+ toast.error("An error occured while trying to log in");
+ }
+ // If we did not manage to log in (probs because api is down), we simply redirect to the home page
+ router.push("/");
+ }
+ }
+ });
+
+ const events: any = {};
+
+ useBroadcastChannel("auth", (message) => {
+ if (pathname.includes("/auth/callback")) return;
+
+ if (!message.code) return;
+ if (message.type === "user-oauth" && message?.code && !events.code) {
+ loginFromCode(message.code);
+ }
+ });
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/arrow.svg b/arrow.svg
new file mode 100644
index 0000000000000000000000000000000000000000..fb915424ce2661999036154e50d12464e673e06a
--- /dev/null
+++ b/arrow.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/auth (1).ts b/auth (1).ts
new file mode 100644
index 0000000000000000000000000000000000000000..a343e65e6726b35b32f022c117d3f3b5187d78e6
--- /dev/null
+++ b/auth (1).ts
@@ -0,0 +1,18 @@
+"use server";
+
+import { headers } from "next/headers";
+
+export async function getAuth() {
+ const authList = await headers();
+ const host = authList.get("host") ?? "localhost:3000";
+ const url = host.includes("/spaces/enzostvs")
+ ? "enzostvs-deepsite.hf.space"
+ : host;
+ const redirect_uri =
+ `${host.includes("localhost") ? "http://" : "https://"}` +
+ url +
+ "/auth/callback";
+
+ 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`;
+ return loginRedirectUrl;
+}
diff --git a/auth.ts b/auth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9e3516693366004961acaa31b74ac3bc9e3cc555
--- /dev/null
+++ b/auth.ts
@@ -0,0 +1,72 @@
+import { User } from "@/types";
+import { NextResponse } from "next/server";
+import { cookies, headers } from "next/headers";
+import MY_TOKEN_KEY from "./get-cookie-name";
+
+// UserResponse = type User & { token: string };
+type UserResponse = User & { token: string };
+
+export const isAuthenticated = async (): // req: NextRequest
+Promise | undefined> => {
+ const authHeaders = await headers();
+ const cookieStore = await cookies();
+ const token = cookieStore.get(MY_TOKEN_KEY())?.value
+ ? `Bearer ${cookieStore.get(MY_TOKEN_KEY())?.value}`
+ : authHeaders.get("Authorization");
+
+ if (!token) {
+ return NextResponse.json(
+ {
+ ok: false,
+ message: "Wrong castle fam :(",
+ },
+ {
+ status: 401,
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ }
+
+ const user = await fetch("https://huggingface.co/api/whoami-v2", {
+ headers: {
+ Authorization: token,
+ },
+ method: "GET",
+ })
+ .then((res) => res.json())
+ .catch(() => {
+ return NextResponse.json(
+ {
+ ok: false,
+ message: "Invalid token",
+ },
+ {
+ status: 401,
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ });
+ if (!user || !user.id) {
+ return NextResponse.json(
+ {
+ ok: false,
+ message: "Invalid token",
+ },
+ {
+ status: 401,
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ );
+ }
+
+ return {
+ ...user,
+ token: token.replace("Bearer ", ""),
+ };
+};
diff --git a/avatar.tsx.txt b/avatar.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..71e428b4ca6154811e8f569d5fdd971ead095996
--- /dev/null
+++ b/avatar.tsx.txt
@@ -0,0 +1,53 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/background_noisy.webp b/background_noisy.webp
new file mode 100644
index 0000000000000000000000000000000000000000..b3dd47eb3ea1192ec4b78f5c6b922d7f852bb492
Binary files /dev/null and b/background_noisy.webp differ
diff --git a/banner.png b/banner.png
new file mode 100644
index 0000000000000000000000000000000000000000..c4ab35880d4a77b6d17c7dd330c427460e16704c
Binary files /dev/null and b/banner.png differ
diff --git a/button.tsx.txt b/button.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9d4fba01222a7008e4aa26d559b79fd7fb736451
--- /dev/null
+++ b/button.tsx.txt
@@ -0,0 +1,68 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "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",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
+ destructive:
+ "bg-red-500 text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 [&_svg]:!text-white",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ lightGray: "bg-neutral-200/60 hover:bg-neutral-200",
+ link: "text-primary underline-offset-4 hover:underline",
+ ghostDarker:
+ "text-white shadow-xs focus-visible:ring-black/40 bg-black/40 hover:bg-black/70",
+ black: "bg-neutral-950 text-neutral-300 hover:brightness-110",
+ sky: "bg-sky-500 text-white hover:brightness-110",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-full text-[13px] gap-1.5 px-3",
+ lg: "h-10 rounded-full px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ iconXs: "size-7",
+ iconXss: "size-6",
+ iconXsss: "size-5",
+ xs: "h-6 text-xs rounded-full pl-2 pr-2 gap-1",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? Slot : "button";
+
+ return (
+
+ );
+}
+
+export { Button, buttonVariants };
diff --git a/checkbox.tsx.txt b/checkbox.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..bd4167ec38b98869c4ad17d22cd6514ea4d85e3c
--- /dev/null
+++ b/checkbox.tsx.txt
@@ -0,0 +1,32 @@
+"use client";
+
+import * as React from "react";
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
+import { CheckIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Checkbox({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+ );
+}
+
+export { Checkbox };
diff --git a/collapsible.tsx.txt b/collapsible.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ae9fad04a3716b5d6f6c957b75841737eb8ed7a8
--- /dev/null
+++ b/collapsible.tsx.txt
@@ -0,0 +1,33 @@
+"use client"
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+function Collapsible({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function CollapsibleTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CollapsibleContent({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/compare-html-diff.ts b/compare-html-diff.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4f6d19cfe3ce1bb420b3020ee42cbce718f4721c
--- /dev/null
+++ b/compare-html-diff.ts
@@ -0,0 +1,11 @@
+import { defaultHTML } from "./consts";
+
+export const isTheSameHtml = (currentHtml: string): boolean => {
+ const normalize = (html: string): string =>
+ html
+ .replace(//g, "")
+ .replace(/\s+/g, " ")
+ .trim();
+
+ return normalize(defaultHTML) === normalize(currentHtml);
+};
diff --git a/consts.ts b/consts.ts
new file mode 100644
index 0000000000000000000000000000000000000000..48db724c14489f1cc93ba9647e0e098e4016c80f
--- /dev/null
+++ b/consts.ts
@@ -0,0 +1,21 @@
+export const defaultHTML = `
+
+
+ My app
+
+
+
+
+
+
+ 🔥 New version dropped!
+
+ I'm ready to work,
+ Ask me anything.
+
+
+
+
+
+
+`;
diff --git a/content.tsx.txt b/content.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ca54b7e09348f31e4f83ba302fb73d8c261e08ba
--- /dev/null
+++ b/content.tsx.txt
@@ -0,0 +1,111 @@
+import { Rocket } from "lucide-react";
+import Image from "next/image";
+
+import Loading from "@/components/loading";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import SpaceIcon from "@/assets/space.svg";
+import { Page } from "@/types";
+import { api } from "@/lib/api";
+import { toast } from "sonner";
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+
+export const DeployButtonContent = ({
+ pages,
+ options,
+ prompts,
+}: {
+ pages: Page[];
+ options?: {
+ title?: string;
+ description?: string;
+ };
+ prompts: string[];
+}) => {
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+
+ const [config, setConfig] = useState({
+ title: "",
+ });
+
+ const createSpace = async () => {
+ if (!config.title) {
+ toast.error("Please enter a title for your space.");
+ return;
+ }
+ setLoading(true);
+
+ try {
+ const res = await api.post("/me/projects", {
+ title: config.title,
+ pages,
+ prompts,
+ });
+ if (res.data.ok) {
+ router.push(`/projects/${res.data.path}?deploy=true`);
+ } else {
+ toast.error(res?.data?.error || "Failed to create space");
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } catch (err: any) {
+ toast.error(err.response?.data?.error || err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+ Choose a title for your space:
+
+
setConfig({ ...config, title: e.target.value })}
+ className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
+ />
+
+
+
+ Then, let's publish it!
+
+
+ Publish Space
+ {loading && }
+
+
+
+ >
+ );
+};
diff --git a/declare.d.ts b/declare.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b49b3ac74b890e2f76f577861869518dfd832312
--- /dev/null
+++ b/declare.d.ts
@@ -0,0 +1,4 @@
+declare module "*.mp3" {
+ const src: string;
+ export default src;
+}
diff --git a/dialog.tsx.txt b/dialog.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d9ccec91d22fab844bd04340c2b07e8677955350
--- /dev/null
+++ b/dialog.tsx.txt
@@ -0,0 +1,143 @@
+"use client"
+
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { XIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/dropdown-menu.tsx.txt b/dropdown-menu.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6bbd969389aa30c911b6388de5f22191eca62a32
--- /dev/null
+++ b/dropdown-menu.tsx.txt
@@ -0,0 +1,257 @@
+"use client";
+
+import * as React from "react";
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function DropdownMenu({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function DropdownMenuGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+ variant?: "default" | "destructive";
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function DropdownMenuSub({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ {children}
+
+
+ );
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+};
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c
Binary files /dev/null and b/favicon.ico differ
diff --git a/fireworks-ai.svg b/fireworks-ai.svg
new file mode 100644
index 0000000000000000000000000000000000000000..934e9a739b720ec176ee55877777cf24191a75bb
--- /dev/null
+++ b/fireworks-ai.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/follow-up-tooltip.tsx.txt b/follow-up-tooltip.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5ebb4a29de5de5cca175eb6795f1d069be6ba02b
--- /dev/null
+++ b/follow-up-tooltip.tsx.txt
@@ -0,0 +1,36 @@
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Info } from "lucide-react";
+
+export const FollowUpTooltip = () => {
+ return (
+
+
+
+
+
+
+
+
+ Using the Diff-Patch system, allow DeepSite to intelligently update
+ your project without rewritting the entire codebase.
+
+
+ This means faster updates, less data usage, and a more efficient
+ development process.
+
+
+
+
+ );
+};
diff --git a/get-cookie-name.ts b/get-cookie-name.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4aba7c78947957e2a996b271f1d100b279bc7d9c
--- /dev/null
+++ b/get-cookie-name.ts
@@ -0,0 +1,3 @@
+export default function MY_TOKEN_KEY() {
+ return "deepsite-auth-token";
+}
diff --git a/gitignore.txt b/gitignore.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5ef6a520780202a1d6addd833d800ccb1ecac0bb
--- /dev/null
+++ b/gitignore.txt
@@ -0,0 +1,41 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/globals.css b/globals.css
new file mode 100644
index 0000000000000000000000000000000000000000..19dd59e7dcc34e453e9850a052ae8f039628e58a
--- /dev/null
+++ b/globals.css
@@ -0,0 +1,146 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --font-sans: var(--font-inter-sans);
+ --font-mono: var(--font-ptSans-mono);
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
+ --color-chart-5: var(--chart-5);
+ --color-chart-4: var(--chart-4);
+ --color-chart-3: var(--chart-3);
+ --color-chart-2: var(--chart-2);
+ --color-chart-1: var(--chart-1);
+ --color-ring: var(--ring);
+ --color-input: var(--input);
+ --color-border: var(--border);
+ --color-destructive: var(--destructive);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-accent: var(--accent);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-muted: var(--muted);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-secondary: var(--secondary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary: var(--primary);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-popover: var(--popover);
+ --color-card-foreground: var(--card-foreground);
+ --color-card: var(--card);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+}
+
+:root {
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+ html {
+ @apply scroll-smooth;
+ }
+}
+
+.background__noisy {
+ @apply bg-blend-normal pointer-events-none opacity-90;
+ background-size: 25ww auto;
+ background-image: url("/background_noisy.webp");
+ @apply fixed w-screen h-screen -z-1 top-0 left-0;
+}
+
+.monaco-editor .margin {
+ @apply !bg-neutral-900;
+}
+.monaco-editor .monaco-editor-background {
+ @apply !bg-neutral-900;
+}
+.monaco-editor .line-numbers {
+ @apply !text-neutral-500;
+}
+
+.matched-line {
+ @apply bg-sky-500/30;
+}
diff --git a/grid-pattern.tsx.txt b/grid-pattern.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0ead903d1de84aba97f328e5686f3c631a33cef6
--- /dev/null
+++ b/grid-pattern.tsx.txt
@@ -0,0 +1,69 @@
+import { useId } from "react";
+import { cn } from "@/lib/utils";
+
+interface GridPatternProps extends React.SVGProps {
+ width?: number;
+ height?: number;
+ x?: number;
+ y?: number;
+ squares?: Array<[x: number, y: number]>;
+ strokeDasharray?: string;
+ className?: string;
+ [key: string]: unknown;
+}
+
+export function GridPattern({
+ width = 40,
+ height = 40,
+ x = -1,
+ y = -1,
+ strokeDasharray = "0",
+ squares,
+ className,
+ ...props
+}: GridPatternProps) {
+ const id = useId();
+
+ return (
+
+
+
+
+
+
+
+ {squares && (
+
+ {squares.map(([x, y]) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/groq.svg b/groq.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2c1f3edb8af82414f88d31090a316a9d54a23668
--- /dev/null
+++ b/groq.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/html-tag-to-text.ts b/html-tag-to-text.ts
new file mode 100644
index 0000000000000000000000000000000000000000..62296f4cbe06f57eabe299126cf85bcf7694b115
--- /dev/null
+++ b/html-tag-to-text.ts
@@ -0,0 +1,96 @@
+export const htmlTagToText = (tagName: string): string => {
+ switch (tagName.toLowerCase()) {
+ case "h1":
+ return "Heading 1";
+ case "h2":
+ return "Heading 2";
+ case "h3":
+ return "Heading 3";
+ case "h4":
+ return "Heading 4";
+ case "h5":
+ return "Heading 5";
+ case "h6":
+ return "Heading 6";
+ case "p":
+ return "Text Paragraph";
+ case "span":
+ return "Inline Text";
+ case "button":
+ return "Button";
+ case "input":
+ return "Input Field";
+ case "select":
+ return "Select Dropdown";
+ case "textarea":
+ return "Text Area";
+ case "form":
+ return "Form";
+ case "table":
+ return "Table";
+ case "thead":
+ return "Table Header";
+ case "tbody":
+ return "Table Body";
+ case "tr":
+ return "Table Row";
+ case "th":
+ return "Table Header Cell";
+ case "td":
+ return "Table Data Cell";
+ case "nav":
+ return "Navigation";
+ case "header":
+ return "Header";
+ case "footer":
+ return "Footer";
+ case "section":
+ return "Section";
+ case "article":
+ return "Article";
+ case "aside":
+ return "Aside";
+ case "div":
+ return "Block";
+ case "main":
+ return "Main Content";
+ case "details":
+ return "Details";
+ case "summary":
+ return "Summary";
+ case "code":
+ return "Code Snippet";
+ case "pre":
+ return "Preformatted Text";
+ case "kbd":
+ return "Keyboard Input";
+ case "label":
+ return "Label";
+ case "canvas":
+ return "Canvas";
+ case "svg":
+ return "SVG Graphic";
+ case "video":
+ return "Video Player";
+ case "audio":
+ return "Audio Player";
+ case "iframe":
+ return "Embedded Frame";
+ case "link":
+ return "Link";
+ case "a":
+ return "Link";
+ case "img":
+ return "Image";
+ case "ul":
+ return "Unordered List";
+ case "ol":
+ return "Ordered List";
+ case "li":
+ return "List Item";
+ case "blockquote":
+ return "Blockquote";
+ default:
+ return tagName.charAt(0).toUpperCase() + tagName.slice(1);
+ }
+};
diff --git a/hyperbolic.svg b/hyperbolic.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7af378deca446054bdfd15074f9ed4b7e3bc4a51
--- /dev/null
+++ b/hyperbolic.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/iframe-detector.tsx.txt b/iframe-detector.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d686b664e443782c288e5b056fa907559b23ec8c
--- /dev/null
+++ b/iframe-detector.tsx.txt
@@ -0,0 +1,75 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import IframeWarningModal from "./iframe-warning-modal";
+
+export default function IframeDetector() {
+ const [showWarning, setShowWarning] = useState(false);
+
+ useEffect(() => {
+ // Helper function to check if a hostname is from allowed domains
+ const isAllowedDomain = (hostname: string) => {
+ const host = hostname.toLowerCase();
+ return (
+ host.endsWith(".huggingface.co") ||
+ host.endsWith(".hf.co") ||
+ host === "huggingface.co" ||
+ host === "hf.co"
+ );
+ };
+
+ // Check if the current window is in an iframe
+ const isInIframe = () => {
+ try {
+ return window.self !== window.top;
+ } catch {
+ // If we can't access window.top due to cross-origin restrictions,
+ // we're likely in an iframe
+ return true;
+ }
+ };
+
+ // Additional check: compare window location with parent location
+ const isEmbedded = () => {
+ try {
+ return window.location !== window.parent.location;
+ } catch {
+ // Cross-origin iframe
+ return true;
+ }
+ };
+
+ // Check if we're in an iframe from a non-allowed domain
+ const shouldShowWarning = () => {
+ if (!isInIframe() && !isEmbedded()) {
+ return false; // Not in an iframe
+ }
+
+ try {
+ // Try to get the parent's hostname
+ const parentHostname = window.parent.location.hostname;
+ return !isAllowedDomain(parentHostname);
+ } catch {
+ // Cross-origin iframe - try to get referrer instead
+ try {
+ if (document.referrer) {
+ const referrerUrl = new URL(document.referrer);
+ return !isAllowedDomain(referrerUrl.hostname);
+ }
+ } catch {
+ // If we can't determine the parent domain, assume it's not allowed
+ }
+ return true;
+ }
+ };
+
+ if (shouldShowWarning()) {
+ // Show warning modal instead of redirecting immediately
+ setShowWarning(true);
+ }
+ }, []);
+
+ return (
+
+ );
+}
diff --git a/iframe-warning-modal.tsx.txt b/iframe-warning-modal.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..acb58b87ee0709e716954cfcd5b8a78c3bcebc67
--- /dev/null
+++ b/iframe-warning-modal.tsx.txt
@@ -0,0 +1,61 @@
+"use client";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { ExternalLink, AlertTriangle } from "lucide-react";
+
+interface IframeWarningModalProps {
+ isOpen: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+export default function IframeWarningModal({
+ isOpen,
+}: // onOpenChange,
+IframeWarningModalProps) {
+ const handleVisitSite = () => {
+ window.open("https://deepsite.hf.co", "_blank");
+ };
+
+ return (
+ {}}>
+
+
+
+
+
Unauthorized Embedding
+
+
+ You're viewing DeepSite through an unauthorized iframe. For the
+ best experience and security, please visit the official website
+ directly.
+
+
+
+
+
Why visit the official site?
+
+ • Better performance and security
+ • Full functionality access
+ • Latest features and updates
+ • Proper authentication support
+
+
+
+
+
+
+ Visit Deepsite.hf.co
+
+
+
+
+ );
+}
diff --git a/index (1).html b/index (1).html
new file mode 100644
index 0000000000000000000000000000000000000000..2394f43fa4736034312baacc9e8cc56b18ac88f4
--- /dev/null
+++ b/index (1).html
@@ -0,0 +1,562 @@
+
+
+
+
+ Enhanced ID Generator Tool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Card Generation
+
+
+
+
+
+ Generate Cards
+ Import Data
+
+ Clear History
+ Export CSV
+ Export JSON
+ Export TXT
+ Copy All
+
+
+
+
+
+ Generated Cards History
+
+
+
+
+
+ #
+ Card Holder
+ Number
+ MM/YY
+ CVV
+ Bank
+ BIN
+ Email
+ IP
+ Status
+
+
+
+
+
No generated cards yet.
+
+
+
+
+
+
+ Simulated Analyzers
+
+
+
+
Fraud Score Simulator
+
+
+ Analyze
+
+
+
+
+
+
+
IP Address Simulator
+
+
+ Analyze
+
+
+
+
+
+
+
Email Analyzer Simulator
+
+
+ Analyze
+
+
+
+
+
+
+
+
+ SSN Tools (Fake Data)
+
+
+
+
Fake SSN Generator
+
+
+ Generate SSNs
+
+
+
+
+
+
+
+
+
SSN Validator (Basic)
+
+
+ Validate
+
+
+
+
+
+ Generated SSNs are fake , follow known allocation patterns, but are not real. Validation is basic format/rule check only.
+
+
+
+
+
+
+
Admin Panel
+
+
+
+
Live Card Generation Stats
+
+ Cards generated (this session): 0
+ Unique BINs imported: 0
+ Saved emails: 0
+ Uploaded IPs: 0
+
+
+
+
Recently Imported Data
+
+
+
+
+ Card Generation Timeline
+
+
+
+
+
+
+ © 2025 Fake ID Data Tools+ — Data is for testing and educational purposes only.
+ Design optimized for browser PDF export - use browser’s print-to-PDF for full content.
+
+
+
+
\ No newline at end of file
diff --git a/index.ts b/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..07d088c3ebf9b2e0f0ab00814c92d07399889480
--- /dev/null
+++ b/index.ts
@@ -0,0 +1,31 @@
+export interface User {
+ fullname: string;
+ avatarUrl: string;
+ name: string;
+ isLocalUse?: boolean;
+ isPro: boolean;
+ id: string;
+ token?: string;
+}
+
+export interface HtmlHistory {
+ pages: Page[];
+ createdAt: Date;
+ prompt: string;
+}
+
+export interface Project {
+ title: string;
+ html: string;
+ prompts: string[];
+ user_id: string;
+ space_id: string;
+ _id?: string;
+ _updatedAt?: Date;
+ _createdAt?: Date;
+}
+
+export interface Page {
+ path: string;
+ html: string;
+}
diff --git a/index.tsx (1).txt b/index.tsx (1).txt
new file mode 100644
index 0000000000000000000000000000000000000000..95c6975c5f3799b2a1db77ae56520297a9e0760e
--- /dev/null
+++ b/index.tsx (1).txt
@@ -0,0 +1,156 @@
+"use client";
+
+import { useRef, useState } from "react";
+import Image from "next/image";
+import Link from "next/link";
+import { useMount, useUnmount } from "react-use";
+import classNames from "classnames";
+
+import { Button } from "@/components/ui/button";
+import Logo from "@/assets/logo.svg";
+import { useUser } from "@/hooks/useUser";
+import { UserMenu } from "@/components/user-menu";
+
+const navigationLinks = [
+ {
+ name: "Create Website",
+ href: "/projects/new",
+ },
+ {
+ name: "Features",
+ href: "#features",
+ },
+ {
+ name: "Community",
+ href: "#community",
+ },
+ {
+ name: "Deploy",
+ href: "#deploy",
+ },
+];
+
+export default function Navigation() {
+ const { openLoginWindow, user } = useUser();
+ const [hash, setHash] = useState("");
+
+ const selectorRef = useRef(null);
+ const linksRef = useRef(
+ new Array(navigationLinks.length).fill(null)
+ );
+ const [isScrolled, setIsScrolled] = useState(false);
+
+ useMount(() => {
+ const handleScroll = () => {
+ const scrollTop = window.scrollY;
+ setIsScrolled(scrollTop > 100);
+ };
+
+ const initialHash = window.location.hash;
+ if (initialHash) {
+ setHash(initialHash);
+ calculateSelectorPosition(initialHash);
+ }
+
+ window.addEventListener("scroll", handleScroll);
+ });
+
+ useUnmount(() => {
+ window.removeEventListener("scroll", () => {});
+ });
+
+ const handleClick = (href: string) => {
+ setHash(href);
+ calculateSelectorPosition(href);
+ };
+
+ const calculateSelectorPosition = (href: string) => {
+ if (selectorRef.current && linksRef.current) {
+ const index = navigationLinks.findIndex((l) => l.href === href);
+ const targetLink = linksRef.current[index];
+ if (targetLink) {
+ const targetRect = targetLink.getBoundingClientRect();
+ selectorRef.current.style.left = targetRect.left + "px";
+ selectorRef.current.style.width = targetRect.width + "px";
+ }
+ }
+ };
+
+ return (
+
+
+
+
+ DeepSite
+
+
+ {navigationLinks.map((link) => (
+ {
+ const index = navigationLinks.findIndex(
+ (l) => l.href === link.href
+ );
+ if (el && linksRef.current[index] !== el) {
+ linksRef.current[index] = el;
+ }
+ }}
+ className="inline-block font-sans text-sm"
+ >
+ {
+ handleClick(link.href);
+ }}
+ >
+ {link.name}
+
+
+ ))}
+
+
+
+ {user ? (
+
+ ) : (
+ <>
+
+ Log In
+
+ Sign Up
+ >
+ )}
+
+
+
+ );
+}
diff --git a/index.tsx (10).txt b/index.tsx (10).txt
new file mode 100644
index 0000000000000000000000000000000000000000..4698327e9c85f19779fda5f379045c202bd75c61
--- /dev/null
+++ b/index.tsx (10).txt
@@ -0,0 +1,231 @@
+"use client";
+import { useUpdateEffect } from "react-use";
+import { useMemo, useState } from "react";
+import classNames from "classnames";
+import { toast } from "sonner";
+import { useThrottleFn } from "react-use";
+
+import { cn } from "@/lib/utils";
+import { GridPattern } from "@/components/magic-ui/grid-pattern";
+import { htmlTagToText } from "@/lib/html-tag-to-text";
+import { Page } from "@/types";
+
+export const Preview = ({
+ html,
+ isResizing,
+ isAiWorking,
+ ref,
+ device,
+ currentTab,
+ iframeRef,
+ pages,
+ setCurrentPage,
+ isEditableModeEnabled,
+ onClickElement,
+}: {
+ html: string;
+ isResizing: boolean;
+ isAiWorking: boolean;
+ pages: Page[];
+ setCurrentPage: React.Dispatch>;
+ ref: React.RefObject;
+ iframeRef?: React.RefObject;
+ device: "desktop" | "mobile";
+ currentTab: string;
+ isEditableModeEnabled?: boolean;
+ onClickElement?: (element: HTMLElement) => void;
+}) => {
+ const [hoveredElement, setHoveredElement] = useState(
+ null
+ );
+
+ const handleMouseOver = (event: MouseEvent) => {
+ if (iframeRef?.current) {
+ const iframeDocument = iframeRef.current.contentDocument;
+ if (iframeDocument) {
+ const targetElement = event.target as HTMLElement;
+ if (
+ hoveredElement !== targetElement &&
+ targetElement !== iframeDocument.body
+ ) {
+ setHoveredElement(targetElement);
+ targetElement.classList.add("hovered-element");
+ } else {
+ return setHoveredElement(null);
+ }
+ }
+ }
+ };
+ const handleMouseOut = () => {
+ setHoveredElement(null);
+ };
+ const handleClick = (event: MouseEvent) => {
+ if (iframeRef?.current) {
+ const iframeDocument = iframeRef.current.contentDocument;
+ if (iframeDocument) {
+ const targetElement = event.target as HTMLElement;
+ if (targetElement !== iframeDocument.body) {
+ onClickElement?.(targetElement);
+ }
+ }
+ }
+ };
+ const handleCustomNavigation = (event: MouseEvent) => {
+ if (iframeRef?.current) {
+ const iframeDocument = iframeRef.current.contentDocument;
+ if (iframeDocument) {
+ const findClosestAnchor = (
+ element: HTMLElement
+ ): HTMLAnchorElement | null => {
+ let current = element;
+ while (current && current !== iframeDocument.body) {
+ if (current.tagName === "A") {
+ return current as HTMLAnchorElement;
+ }
+ current = current.parentElement as HTMLElement;
+ }
+ return null;
+ };
+
+ const anchorElement = findClosestAnchor(event.target as HTMLElement);
+ if (anchorElement) {
+ let href = anchorElement.getAttribute("href");
+ if (href) {
+ event.stopPropagation();
+ event.preventDefault();
+
+ if (href.includes("#") && !href.includes(".html")) {
+ const targetElement = iframeDocument.querySelector(href);
+ if (targetElement) {
+ targetElement.scrollIntoView({ behavior: "smooth" });
+ }
+ return;
+ }
+
+ href = href.split(".html")[0] + ".html";
+ const isPageExist = pages.some((page) => page.path === href);
+ if (isPageExist) {
+ setCurrentPage(href);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ useUpdateEffect(() => {
+ const cleanupListeners = () => {
+ if (iframeRef?.current?.contentDocument) {
+ const iframeDocument = iframeRef.current.contentDocument;
+ iframeDocument.removeEventListener("mouseover", handleMouseOver);
+ iframeDocument.removeEventListener("mouseout", handleMouseOut);
+ iframeDocument.removeEventListener("click", handleClick);
+ }
+ };
+
+ if (iframeRef?.current) {
+ const iframeDocument = iframeRef.current.contentDocument;
+ if (iframeDocument) {
+ cleanupListeners();
+
+ if (isEditableModeEnabled) {
+ iframeDocument.addEventListener("mouseover", handleMouseOver);
+ iframeDocument.addEventListener("mouseout", handleMouseOut);
+ iframeDocument.addEventListener("click", handleClick);
+ }
+ }
+ }
+
+ return cleanupListeners;
+ }, [iframeRef, isEditableModeEnabled]);
+
+ const selectedElement = useMemo(() => {
+ if (!isEditableModeEnabled) return null;
+ if (!hoveredElement) return null;
+ return hoveredElement;
+ }, [hoveredElement, isEditableModeEnabled]);
+
+ const throttledHtml = useThrottleFn((html) => html, 1000, [html]);
+
+ return (
+ {
+ if (isAiWorking) {
+ e.preventDefault();
+ e.stopPropagation();
+ toast.warning("Please wait for the AI to finish working.");
+ }
+ }}
+ >
+
+ {!isAiWorking && hoveredElement && selectedElement && (
+
+
+ {htmlTagToText(selectedElement.tagName.toLowerCase())}
+
+
+ )}
+
+ );
+};
diff --git a/index.tsx (11).txt b/index.tsx (11).txt
new file mode 100644
index 0000000000000000000000000000000000000000..5560a5f014d77cc911a59f56c351b8123925689e
--- /dev/null
+++ b/index.tsx (11).txt
@@ -0,0 +1,30 @@
+import { Page } from "@/types";
+import { ListPagesItem } from "./page";
+
+export function ListPages({
+ pages,
+ currentPage,
+ onSelectPage,
+ onDeletePage,
+}: {
+ pages: Array;
+ currentPage: string;
+ onSelectPage: (path: string, newPath?: string) => void;
+ onNewPage: () => void;
+ onDeletePage: (path: string) => void;
+}) {
+ return (
+
+ {pages.map((page, i) => (
+
+ ))}
+
+ );
+}
diff --git a/index.tsx (12).txt b/index.tsx (12).txt
new file mode 100644
index 0000000000000000000000000000000000000000..86856f89da4c31681388cf4580c3fb1e301601c3
--- /dev/null
+++ b/index.tsx (12).txt
@@ -0,0 +1,73 @@
+import { History as HistoryIcon } from "lucide-react";
+import { HtmlHistory, Page } from "@/types";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Button } from "@/components/ui/button";
+
+export function History({
+ history,
+ setPages,
+}: {
+ history: HtmlHistory[];
+ setPages: (pages: Page[]) => void;
+}) {
+ return (
+
+
+
+
+ {history?.length} edit{history.length !== 1 ? "s" : ""}
+
+
+
+
+
+
+ {history?.map((item, index) => (
+
+
+ {item.prompt}
+
+ {new Date(item.createdAt).toLocaleDateString("en-US", {
+ month: "2-digit",
+ day: "2-digit",
+ year: "2-digit",
+ }) +
+ " " +
+ new Date(item.createdAt).toLocaleTimeString("en-US", {
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: false,
+ })}
+
+
+ {
+ console.log(item);
+ setPages(item.pages);
+ }}
+ >
+ Select
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/index.tsx (13).txt b/index.tsx (13).txt
new file mode 100644
index 0000000000000000000000000000000000000000..184918363d4aa28eb1e866e9d711d8fdb15f2dc8
--- /dev/null
+++ b/index.tsx (13).txt
@@ -0,0 +1,69 @@
+import { ReactNode } from "react";
+import { Eye, MessageCircleCode } from "lucide-react";
+
+import Logo from "@/assets/logo.svg";
+
+import { Button } from "@/components/ui/button";
+import classNames from "classnames";
+import Image from "next/image";
+
+const TABS = [
+ {
+ value: "chat",
+ label: "Chat",
+ icon: MessageCircleCode,
+ },
+ {
+ value: "preview",
+ label: "Preview",
+ icon: Eye,
+ },
+];
+
+export function Header({
+ tab,
+ onNewTab,
+ children,
+}: {
+ tab: string;
+ onNewTab: (tab: string) => void;
+ children?: ReactNode;
+}) {
+ return (
+
+ );
+}
diff --git a/index.tsx (14).txt b/index.tsx (14).txt
new file mode 100644
index 0000000000000000000000000000000000000000..df27a095d2205207ba584b19913576dd650d262e
--- /dev/null
+++ b/index.tsx (14).txt
@@ -0,0 +1,150 @@
+import classNames from "classnames";
+import { FaMobileAlt } from "react-icons/fa";
+import { HelpCircle, LogIn, RefreshCcw, SparkleIcon } from "lucide-react";
+import { FaLaptopCode } from "react-icons/fa6";
+import { HtmlHistory, Page } from "@/types";
+import { Button } from "@/components/ui/button";
+import { MdAdd } from "react-icons/md";
+import { History } from "@/components/editor/history";
+import { UserMenu } from "@/components/user-menu";
+import { useUser } from "@/hooks/useUser";
+import Link from "next/link";
+import { useLocalStorage } from "react-use";
+import { isTheSameHtml } from "@/lib/compare-html-diff";
+
+const DEVICES = [
+ {
+ name: "desktop",
+ icon: FaLaptopCode,
+ },
+ {
+ name: "mobile",
+ icon: FaMobileAlt,
+ },
+];
+
+export function Footer({
+ pages,
+ isNew = false,
+ htmlHistory,
+ setPages,
+ device,
+ setDevice,
+ iframeRef,
+}: {
+ pages: Page[];
+ isNew?: boolean;
+ htmlHistory?: HtmlHistory[];
+ device: "desktop" | "mobile";
+ setPages: (pages: Page[]) => void;
+ iframeRef?: React.RefObject;
+ setDevice: React.Dispatch>;
+}) {
+ const { user, openLoginWindow } = useUser();
+
+ const handleRefreshIframe = () => {
+ if (iframeRef?.current) {
+ const iframe = iframeRef.current;
+ const content = iframe.srcdoc;
+ iframe.srcdoc = "";
+ setTimeout(() => {
+ iframe.srcdoc = content;
+ }, 10);
+ }
+ };
+
+ const [, setStorage] = useLocalStorage("pages");
+ const handleClick = async () => {
+ if (pages && !isTheSameHtml(pages[0].html)) {
+ setStorage(pages);
+ }
+ openLoginWindow();
+ };
+
+ return (
+
+ );
+}
diff --git a/index.tsx (15).txt b/index.tsx (15).txt
new file mode 100644
index 0000000000000000000000000000000000000000..43db70a984d59475ca6019bc610b3c64be6fae94
--- /dev/null
+++ b/index.tsx (15).txt
@@ -0,0 +1,79 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useState } from "react";
+import { MdSave } from "react-icons/md";
+
+import { Button } from "@/components/ui/button";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { LoginModal } from "@/components/login-modal";
+import { useUser } from "@/hooks/useUser";
+import { Page } from "@/types";
+import { DeployButtonContent } from "./content";
+
+export function DeployButton({
+ pages,
+ prompts,
+}: {
+ pages: Page[];
+ prompts: string[];
+}) {
+ const { user } = useUser();
+ const [open, setOpen] = useState(false);
+
+ return (
+
+
+ {user?.id ? (
+
+
+
+
+
+ Publish your Project
+
+
+ Publish
+
+
+
+
+
+
+
+ ) : (
+ <>
+
setOpen(true)}
+ >
+
+ Publish your Project
+
+
setOpen(true)}
+ >
+ Publish
+
+ >
+ )}
+
setOpen(false)}
+ pages={pages}
+ title="Log In to publish your Project"
+ description="Log In through your Hugging Face account to publish your project and increase your monthly free limit."
+ />
+
+
+ );
+}
diff --git a/index.tsx (16).txt b/index.tsx (16).txt
new file mode 100644
index 0000000000000000000000000000000000000000..2076173a327338cdc34a3d8c66c22cc3e72bc408
--- /dev/null
+++ b/index.tsx (16).txt
@@ -0,0 +1,500 @@
+"use client";
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useState, useMemo, useRef } from "react";
+import classNames from "classnames";
+import { toast } from "sonner";
+import { useLocalStorage, useUpdateEffect } from "react-use";
+import { ArrowUp, ChevronDown, Crosshair } from "lucide-react";
+import { FaStopCircle } from "react-icons/fa";
+
+import ProModal from "@/components/pro-modal";
+import { Button } from "@/components/ui/button";
+import { MODELS } from "@/lib/providers";
+import { HtmlHistory, Page, Project } from "@/types";
+// import { InviteFriends } from "@/components/invite-friends";
+import { Settings } from "@/components/editor/ask-ai/settings";
+import { LoginModal } from "@/components/login-modal";
+import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
+import Loading from "@/components/loading";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
+import { TooltipContent } from "@radix-ui/react-tooltip";
+import { SelectedHtmlElement } from "./selected-html-element";
+import { FollowUpTooltip } from "./follow-up-tooltip";
+import { isTheSameHtml } from "@/lib/compare-html-diff";
+import { useCallAi } from "@/hooks/useCallAi";
+import { SelectedFiles } from "./selected-files";
+import { Uploader } from "./uploader";
+
+export function AskAI({
+ isNew,
+ project,
+ images,
+ currentPage,
+ previousPrompts,
+ onScrollToBottom,
+ isAiWorking,
+ setisAiWorking,
+ isEditableModeEnabled = false,
+ pages,
+ htmlHistory,
+ selectedElement,
+ setSelectedElement,
+ selectedFiles,
+ setSelectedFiles,
+ setIsEditableModeEnabled,
+ onNewPrompt,
+ onSuccess,
+ setPages,
+ setCurrentPage,
+}: {
+ project?: Project | null;
+ currentPage: Page;
+ images?: string[];
+ pages: Page[];
+ onScrollToBottom: () => void;
+ previousPrompts: string[];
+ isAiWorking: boolean;
+ onNewPrompt: (prompt: string) => void;
+ htmlHistory?: HtmlHistory[];
+ setisAiWorking: React.Dispatch>;
+ isNew?: boolean;
+ onSuccess: (page: Page[], p: string, n?: number[][]) => void;
+ isEditableModeEnabled: boolean;
+ setIsEditableModeEnabled: React.Dispatch>;
+ selectedElement?: HTMLElement | null;
+ setSelectedElement: React.Dispatch>;
+ selectedFiles: string[];
+ setSelectedFiles: React.Dispatch>;
+ setPages: React.Dispatch>;
+ setCurrentPage: React.Dispatch>;
+}) {
+ const refThink = useRef(null);
+
+ const [open, setOpen] = useState(false);
+ const [prompt, setPrompt] = useState("");
+ const [provider, setProvider] = useLocalStorage("provider", "auto");
+ const [model, setModel] = useLocalStorage("model", MODELS[0].value);
+ const [openProvider, setOpenProvider] = useState(false);
+ const [providerError, setProviderError] = useState("");
+ const [openProModal, setOpenProModal] = useState(false);
+ const [openThink, setOpenThink] = useState(false);
+ const [isThinking, setIsThinking] = useState(true);
+ const [think, setThink] = useState("");
+ const [isFollowUp, setIsFollowUp] = useState(true);
+ const [isUploading, setIsUploading] = useState(false);
+ const [files, setFiles] = useState(images ?? []);
+
+ const {
+ callAiNewProject,
+ callAiFollowUp,
+ callAiNewPage,
+ stopController,
+ audio: hookAudio,
+ } = useCallAi({
+ onNewPrompt,
+ onSuccess,
+ onScrollToBottom,
+ setPages,
+ setCurrentPage,
+ currentPage,
+ pages,
+ isAiWorking,
+ setisAiWorking,
+ });
+
+ const selectedModel = useMemo(() => {
+ return MODELS.find((m: { value: string }) => m.value === model);
+ }, [model]);
+
+ const callAi = async (redesignMarkdown?: string) => {
+ if (isAiWorking) return;
+ if (!redesignMarkdown && !prompt.trim()) return;
+
+ if (isFollowUp && !redesignMarkdown && !isSameHtml) {
+ // Use follow-up function for existing projects
+ const selectedElementHtml = selectedElement
+ ? selectedElement.outerHTML
+ : "";
+
+ const result = await callAiFollowUp(
+ prompt,
+ model,
+ provider,
+ previousPrompts,
+ selectedElementHtml,
+ selectedFiles
+ );
+
+ if (result?.error) {
+ handleError(result.error, result.message);
+ return;
+ }
+
+ if (result?.success) {
+ setPrompt("");
+ }
+ } else if (isFollowUp && pages.length > 1 && isSameHtml) {
+ const result = await callAiNewPage(
+ prompt,
+ model,
+ provider,
+ currentPage.path,
+ [
+ ...(previousPrompts ?? []),
+ ...(htmlHistory?.map((h) => h.prompt) ?? []),
+ ]
+ );
+ if (result?.error) {
+ handleError(result.error, result.message);
+ return;
+ }
+
+ if (result?.success) {
+ setPrompt("");
+ }
+ } else {
+ const result = await callAiNewProject(
+ prompt,
+ model,
+ provider,
+ redesignMarkdown,
+ handleThink,
+ () => {
+ setIsThinking(false);
+ }
+ );
+
+ if (result?.error) {
+ handleError(result.error, result.message);
+ return;
+ }
+
+ if (result?.success) {
+ setPrompt("");
+ if (selectedModel?.isThinker) {
+ setModel(MODELS[0].value);
+ }
+ }
+ }
+ };
+
+ const handleThink = (think: string) => {
+ setThink(think);
+ setIsThinking(true);
+ setOpenThink(true);
+ };
+
+ const handleError = (error: string, message?: string) => {
+ switch (error) {
+ case "login_required":
+ setOpen(true);
+ break;
+ case "provider_required":
+ setOpenProvider(true);
+ setProviderError(message || "");
+ break;
+ case "pro_required":
+ setOpenProModal(true);
+ break;
+ case "api_error":
+ toast.error(message || "An error occurred");
+ break;
+ case "network_error":
+ toast.error(message || "Network error occurred");
+ break;
+ default:
+ toast.error("An unexpected error occurred");
+ }
+ };
+
+ useUpdateEffect(() => {
+ if (refThink.current) {
+ refThink.current.scrollTop = refThink.current.scrollHeight;
+ }
+ }, [think]);
+
+ useUpdateEffect(() => {
+ if (!isThinking) {
+ setOpenThink(false);
+ }
+ }, [isThinking]);
+
+ const isSameHtml = useMemo(() => {
+ return isTheSameHtml(currentPage.html);
+ }, [currentPage.html]);
+
+ return (
+
+
+ {think && (
+
+ )}
+
+ setSelectedFiles((prev) => prev.filter((f) => f !== file))
+ }
+ />
+ {selectedElement && (
+
+ setSelectedElement(null)}
+ />
+
+ )}
+
+ {(isAiWorking || isUploading) && (
+
+
+
+
+ {isUploading ? (
+ "Uploading images..."
+ ) : isAiWorking && !isSameHtml ? (
+ "AI is working..."
+ ) : (
+
+ {[
+ "D",
+ "e",
+ "e",
+ "p",
+ "S",
+ "i",
+ "t",
+ "e",
+ " ",
+ "i",
+ "s",
+ " ",
+ "T",
+ "h",
+ "i",
+ "n",
+ "k",
+ "i",
+ "n",
+ "g",
+ ".",
+ ".",
+ ".",
+ " ",
+ "W",
+ "a",
+ "i",
+ "t",
+ " ",
+ "a",
+ " ",
+ "m",
+ "o",
+ "m",
+ "e",
+ "n",
+ "t",
+ ".",
+ ".",
+ ".",
+ ].map((char, index) => (
+
+ {char === " " ? "\u00A0" : char}
+
+ ))}
+
+ )}
+
+
+ {isAiWorking && (
+
+
+ Stop generation
+
+ )}
+
+ )}
+
+
+
+ {
+ if (selectedFiles.includes(file)) {
+ setSelectedFiles((prev) => prev.filter((f) => f !== file));
+ } else {
+ setSelectedFiles((prev) => [...prev, file]);
+ }
+ }}
+ files={files}
+ selectedFiles={selectedFiles}
+ project={project}
+ />
+ {isNew && callAi(md)} />}
+ {!isSameHtml && (
+
+
+ {
+ setIsEditableModeEnabled?.(!isEditableModeEnabled);
+ }}
+ className={classNames("h-[28px]", {
+ "!text-neutral-400 hover:!text-neutral-200 !border-neutral-600 !hover:!border-neutral-500":
+ !isEditableModeEnabled,
+ })}
+ >
+
+ Edit
+
+
+
+ Select an element on the page to ask DeepSite edit it
+ directly.
+
+
+ )}
+ {/* */}
+
+
+
+ setOpen(false)} pages={pages} />
+ setOpenProModal(false)}
+ />
+ {pages.length === 1 && (
+
+
+ NEW
+
+
+ DeepSite can now create multiple pages at once. Try it!
+
+
+ )}
+ {!isSameHtml && (
+
+
+ {
+ if (e === true && !isSameHtml && selectedModel?.isThinker) {
+ setModel(MODELS[0].value);
+ }
+ setIsFollowUp(e === true);
+ }}
+ />
+ Diff-Patch Update
+
+
+
+ )}
+
+
+
+ Your browser does not support the audio element.
+
+
+ );
+}
diff --git a/index.tsx (2).txt b/index.tsx (2).txt
new file mode 100644
index 0000000000000000000000000000000000000000..46202ffe6f8d82f29eab068db97573985eaf9cca
--- /dev/null
+++ b/index.tsx (2).txt
@@ -0,0 +1,43 @@
+"use client";
+
+import { ArrowUp } from "lucide-react";
+import { PiGearSixFill } from "react-icons/pi";
+import { TiUserAdd } from "react-icons/ti";
+
+import { Button } from "@/components/ui/button";
+
+export const AskAi = () => {
+ return (
+ <>
+
+ >
+ );
+};
diff --git a/index.tsx (3).txt b/index.tsx (3).txt
new file mode 100644
index 0000000000000000000000000000000000000000..db39eb9f0e8b46d023e8901d2633af89f88b48e6
--- /dev/null
+++ b/index.tsx (3).txt
@@ -0,0 +1,93 @@
+import { useLocalStorage } from "react-use";
+import { Button } from "@/components/ui/button";
+import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
+import { CheckCheck } from "lucide-react";
+import { isTheSameHtml } from "@/lib/compare-html-diff";
+import { Page } from "@/types";
+
+export const ProModal = ({
+ open,
+ pages,
+ onClose,
+}: {
+ open: boolean;
+ pages: Page[];
+ onClose: React.Dispatch>;
+}) => {
+ const [, setStorage] = useLocalStorage("pages");
+ const handleProClick = () => {
+ if (pages && !isTheSameHtml(pages?.[0].html)) {
+ setStorage(pages);
+ }
+ window.open("https://huggingface.co/subscribe/pro?from=DeepSite", "_blank");
+ onClose(false);
+ };
+ return (
+
+
+
+
+
+
+ 🚀
+
+
+ 🤩
+
+
+ 🥳
+
+
+
+ Only $9 to enhance your possibilities
+
+
+ It seems like you have reached the monthly free limit of DeepSite.
+
+
+
+ Upgrade to a Account, and unlock your
+ DeepSite high quota access ⚡
+
+
+
+ You'll also unlock some Hugging Face PRO features, like:
+
+
+
+ Get acces to thousands of AI app (ZeroGPU) with high quota
+
+
+
+ Get exclusive early access to new features and updates
+
+
+
+ Get free credits across all Inference Providers
+
+
+ ... and lots more!
+
+
+
+ Subscribe to PRO ($9/month)
+
+
+
+
+ );
+};
+
+const ProTag = ({ className }: { className?: string }) => (
+
+ PRO
+
+);
+export default ProModal;
diff --git a/index.tsx (4).txt b/index.tsx (4).txt
new file mode 100644
index 0000000000000000000000000000000000000000..b58b1af5a3ea13d454cd0dbefc431e8e8cffcd95
--- /dev/null
+++ b/index.tsx (4).txt
@@ -0,0 +1,57 @@
+"use client";
+import { Plus } from "lucide-react";
+import Link from "next/link";
+import { useState } from "react";
+
+import { useUser } from "@/hooks/useUser";
+import { Project } from "@/types";
+import { redirect } from "next/navigation";
+import { ProjectCard } from "./project-card";
+import { LoadProject } from "./load-project";
+
+export function MyProjects({
+ projects: initialProjects,
+}: {
+ projects: Project[];
+}) {
+ const { user } = useUser();
+ if (!user) {
+ redirect("/");
+ }
+ const [projects, setProjects] = useState(initialProjects || []);
+ return (
+ <>
+
+
+
+
+
+ Create Project
+
+ {projects.map((project: Project) => (
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/index.tsx (5).txt b/index.tsx (5).txt
new file mode 100644
index 0000000000000000000000000000000000000000..d7333a13c0cbd9ed2a87d9420e6bfb704c845604
--- /dev/null
+++ b/index.tsx (5).txt
@@ -0,0 +1,62 @@
+import { useLocalStorage } from "react-use";
+import { Button } from "@/components/ui/button";
+import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
+import { useUser } from "@/hooks/useUser";
+import { isTheSameHtml } from "@/lib/compare-html-diff";
+import { Page } from "@/types";
+
+export const LoginModal = ({
+ open,
+ pages,
+ onClose,
+ title = "Log In to use DeepSite for free",
+ description = "Log In through your Hugging Face account to continue using DeepSite and increase your monthly free limit.",
+}: {
+ open: boolean;
+ pages?: Page[];
+ onClose: React.Dispatch>;
+ title?: string;
+ description?: string;
+}) => {
+ const { openLoginWindow } = useUser();
+ const [, setStorage] = useLocalStorage("pages");
+ const handleClick = async () => {
+ if (pages && !isTheSameHtml(pages[0].html)) {
+ setStorage(pages);
+ }
+ openLoginWindow();
+ onClose(false);
+ };
+ return (
+
+
+
+
+
+
+ 💪
+
+
+ 😎
+
+
+ 🙌
+
+
+ {title}
+
+ {description}
+
+
+ Log In to Continue
+
+
+
+
+ );
+};
diff --git a/index.tsx (6).txt b/index.tsx (6).txt
new file mode 100644
index 0000000000000000000000000000000000000000..b55a26b0d0c47033eb70c73960c8e4d028f7f2a0
--- /dev/null
+++ b/index.tsx (6).txt
@@ -0,0 +1,41 @@
+import classNames from "classnames";
+
+function Loading({
+ overlay = true,
+ className,
+}: {
+ overlay?: boolean;
+ className?: string;
+}) {
+ return (
+
+ );
+}
+
+export default Loading;
diff --git a/index.tsx (7).txt b/index.tsx (7).txt
new file mode 100644
index 0000000000000000000000000000000000000000..fb7ee9ed84597daa5bd7a354c2a216ed94909f3f
--- /dev/null
+++ b/index.tsx (7).txt
@@ -0,0 +1,85 @@
+import { TiUserAdd } from "react-icons/ti";
+import { Link } from "lucide-react";
+import { FaXTwitter } from "react-icons/fa6";
+import { useCopyToClipboard } from "react-use";
+import { toast } from "sonner";
+
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+
+export function InviteFriends() {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [_, copyToClipboard] = useCopyToClipboard();
+
+ return (
+
+
+
+ );
+}
diff --git a/index.tsx (8).txt b/index.tsx (8).txt
new file mode 100644
index 0000000000000000000000000000000000000000..0f7c44c537311a507cd4bdf161c2e3f44bb81f9b
--- /dev/null
+++ b/index.tsx (8).txt
@@ -0,0 +1,392 @@
+"use client";
+import { useMemo, useRef, useState } from "react";
+import { toast } from "sonner";
+import { editor } from "monaco-editor";
+import Editor from "@monaco-editor/react";
+import { CopyIcon } from "lucide-react";
+import {
+ useCopyToClipboard,
+ useEvent,
+ useLocalStorage,
+ useMount,
+ useUnmount,
+ useUpdateEffect,
+} from "react-use";
+import classNames from "classnames";
+import { useRouter, useSearchParams } from "next/navigation";
+
+import { Header } from "@/components/editor/header";
+import { Footer } from "@/components/editor/footer";
+import { defaultHTML } from "@/lib/consts";
+import { Preview } from "@/components/editor/preview";
+import { useEditor } from "@/hooks/useEditor";
+import { AskAI } from "@/components/editor/ask-ai";
+import { DeployButton } from "./deploy-button";
+import { Page, Project } from "@/types";
+import { SaveButton } from "./save-button";
+import { LoadProject } from "../my-projects/load-project";
+import { isTheSameHtml } from "@/lib/compare-html-diff";
+import { ListPages } from "./pages";
+
+export const AppEditor = ({
+ project,
+ pages: initialPages,
+ images,
+ isNew,
+}: {
+ project?: Project | null;
+ pages?: Page[];
+ images?: string[];
+ isNew?: boolean;
+}) => {
+ const [htmlStorage, , removeHtmlStorage] = useLocalStorage("pages");
+ const [, copyToClipboard] = useCopyToClipboard();
+ const { htmlHistory, setHtmlHistory, prompts, setPrompts, pages, setPages } =
+ useEditor(
+ initialPages,
+ project?.prompts ?? [],
+ typeof htmlStorage === "string" ? htmlStorage : undefined
+ );
+
+ const searchParams = useSearchParams();
+ const router = useRouter();
+ const deploy = searchParams.get("deploy") === "true";
+
+ const iframeRef = useRef(null);
+ const preview = useRef(null);
+ const editor = useRef(null);
+ const editorRef = useRef(null);
+ const resizer = useRef(null);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const monacoRef = useRef(null);
+
+ const [currentTab, setCurrentTab] = useState("chat");
+ const [currentPage, setCurrentPage] = useState("index.html");
+ const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
+ const [isResizing, setIsResizing] = useState(false);
+ const [isAiWorking, setIsAiWorking] = useState(false);
+ const [isEditableModeEnabled, setIsEditableModeEnabled] = useState(false);
+ const [selectedElement, setSelectedElement] = useState(
+ null
+ );
+ const [selectedFiles, setSelectedFiles] = useState([]);
+
+ const resetLayout = () => {
+ if (!editor.current || !preview.current) return;
+
+ // lg breakpoint is 1024px based on useBreakpoint definition and Tailwind defaults
+ if (window.innerWidth >= 1024) {
+ // Set initial 1/3 - 2/3 sizes for large screens, accounting for resizer width
+ const resizerWidth = resizer.current?.offsetWidth ?? 8; // w-2 = 0.5rem = 8px
+ const availableWidth = window.innerWidth - resizerWidth;
+ const initialEditorWidth = availableWidth / 3; // Editor takes 1/3 of space
+ const initialPreviewWidth = availableWidth - initialEditorWidth; // Preview takes 2/3
+ editor.current.style.width = `${initialEditorWidth}px`;
+ preview.current.style.width = `${initialPreviewWidth}px`;
+ } else {
+ // Remove inline styles for smaller screens, let CSS flex-col handle it
+ editor.current.style.width = "";
+ preview.current.style.width = "";
+ }
+ };
+
+ const handleResize = (e: MouseEvent) => {
+ if (!editor.current || !preview.current || !resizer.current) return;
+
+ const resizerWidth = resizer.current.offsetWidth;
+ const minWidth = 100; // Minimum width for editor/preview
+ const maxWidth = window.innerWidth - resizerWidth - minWidth;
+
+ const editorWidth = e.clientX;
+ const clampedEditorWidth = Math.max(
+ minWidth,
+ Math.min(editorWidth, maxWidth)
+ );
+ const calculatedPreviewWidth =
+ window.innerWidth - clampedEditorWidth - resizerWidth;
+
+ editor.current.style.width = `${clampedEditorWidth}px`;
+ preview.current.style.width = `${calculatedPreviewWidth}px`;
+ };
+
+ const handleMouseDown = () => {
+ setIsResizing(true);
+ document.addEventListener("mousemove", handleResize);
+ document.addEventListener("mouseup", handleMouseUp);
+ };
+
+ const handleMouseUp = () => {
+ setIsResizing(false);
+ document.removeEventListener("mousemove", handleResize);
+ document.removeEventListener("mouseup", handleMouseUp);
+ };
+
+ useMount(() => {
+ if (deploy && project?._id) {
+ toast.success("Your project is deployed! 🎉", {
+ action: {
+ label: "See Project",
+ onClick: () => {
+ window.open(
+ `https://huggingface.co/spaces/${project?.space_id}`,
+ "_blank"
+ );
+ },
+ },
+ });
+ router.replace(`/projects/${project?.space_id}`);
+ }
+ if (htmlStorage) {
+ removeHtmlStorage();
+ toast.warning("Previous HTML content restored from local storage.");
+ }
+
+ resetLayout();
+ if (!resizer.current) return;
+ resizer.current.addEventListener("mousedown", handleMouseDown);
+ window.addEventListener("resize", resetLayout);
+ });
+ useUnmount(() => {
+ document.removeEventListener("mousemove", handleResize);
+ document.removeEventListener("mouseup", handleMouseUp);
+ if (resizer.current) {
+ resizer.current.removeEventListener("mousedown", handleMouseDown);
+ }
+ window.removeEventListener("resize", resetLayout);
+ });
+
+ // Prevent accidental navigation away when AI is working or content has changed
+ useEvent("beforeunload", (e) => {
+ if (isAiWorking || !isTheSameHtml(currentPageData?.html)) {
+ e.preventDefault();
+ return "";
+ }
+ });
+
+ useUpdateEffect(() => {
+ if (currentTab === "chat") {
+ // Reset editor width when switching to reasoning tab
+ resetLayout();
+ // re-add the event listener for resizing
+ if (resizer.current) {
+ resizer.current.addEventListener("mousedown", handleMouseDown);
+ }
+ } else {
+ if (preview.current) {
+ // Reset preview width when switching to preview tab
+ preview.current.style.width = "100%";
+ }
+ }
+ }, [currentTab]);
+
+ const handleEditorValidation = (markers: editor.IMarker[]) => {
+ console.log("Editor validation markers:", markers);
+ };
+
+ const currentPageData = useMemo(() => {
+ return (
+ pages.find((page) => page.path === currentPage) ?? {
+ path: "index.html",
+ html: defaultHTML,
+ }
+ );
+ }, [pages, currentPage]);
+
+ return (
+
+
+ {
+ router.push(`/projects/${project.space_id}`);
+ }}
+ />
+ {/* for these buttons pass the whole pages */}
+ {project?._id ? (
+
+ ) : (
+
+ )}
+
+
+ {currentTab === "chat" && (
+ <>
+
+
{
+ if (newPath) {
+ setPages((prev) =>
+ prev.map((page) =>
+ page.path === path ? { ...page, path: newPath } : page
+ )
+ );
+ setCurrentPage(newPath);
+ } else {
+ setCurrentPage(path);
+ }
+ }}
+ onDeletePage={(path) => {
+ const newPages = pages.filter((page) => page.path !== path);
+ setPages(newPages);
+ if (currentPage === path) {
+ setCurrentPage(newPages[0]?.path ?? "index.html");
+ }
+ }}
+ onNewPage={() => {
+ setPages((prev) => [
+ ...prev,
+ {
+ path: `page-${prev.length + 1}.html`,
+ html: defaultHTML,
+ },
+ ]);
+ setCurrentPage(`page-${pages.length + 1}.html`);
+ }}
+ />
+ {
+ copyToClipboard(currentPageData.html);
+ toast.success("HTML copied to clipboard!");
+ }}
+ />
+ {
+ const newValue = value ?? "";
+ // setHtml(newValue);
+ setPages((prev) =>
+ prev.map((page) =>
+ page.path === currentPageData.path
+ ? { ...page, html: newValue }
+ : page
+ )
+ );
+ }}
+ onMount={(editor, monaco) => {
+ editorRef.current = editor;
+ monacoRef.current = monaco;
+ }}
+ onValidate={handleEditorValidation}
+ />
+ {
+ const currentHistory = [...htmlHistory];
+ currentHistory.unshift({
+ pages: newPages,
+ createdAt: new Date(),
+ prompt: p,
+ });
+ setHtmlHistory(currentHistory);
+ setSelectedElement(null);
+ setSelectedFiles([]);
+ // if xs or sm
+ if (window.innerWidth <= 1024) {
+ setCurrentTab("preview");
+ }
+ // if (updatedLines && updatedLines?.length > 0) {
+ // const decorations = updatedLines.map((line) => ({
+ // range: new monacoRef.current.Range(
+ // line[0],
+ // 1,
+ // line[1],
+ // 1
+ // ),
+ // options: {
+ // inlineClassName: "matched-line",
+ // },
+ // }));
+ // setTimeout(() => {
+ // editorRef?.current
+ // ?.getModel()
+ // ?.deltaDecorations([], decorations);
+
+ // editorRef.current?.revealLine(updatedLines[0][0]);
+ // }, 100);
+ // }
+ }}
+ setPages={setPages}
+ pages={pages}
+ setCurrentPage={setCurrentPage}
+ isAiWorking={isAiWorking}
+ setisAiWorking={setIsAiWorking}
+ onNewPrompt={(prompt: string) => {
+ setPrompts((prev) => [...prev, prompt]);
+ }}
+ onScrollToBottom={() => {
+ editorRef.current?.revealLine(
+ editorRef.current?.getModel()?.getLineCount() ?? 0
+ );
+ }}
+ isNew={isNew}
+ isEditableModeEnabled={isEditableModeEnabled}
+ setIsEditableModeEnabled={setIsEditableModeEnabled}
+ selectedElement={selectedElement}
+ setSelectedElement={setSelectedElement}
+ setSelectedFiles={setSelectedFiles}
+ selectedFiles={selectedFiles}
+ />
+
+
+ >
+ )}
+ {
+ setIsEditableModeEnabled(false);
+ setSelectedElement(element);
+ setCurrentTab("chat");
+ }}
+ />
+
+
+
+ );
+};
diff --git a/index.tsx (9).txt b/index.tsx (9).txt
new file mode 100644
index 0000000000000000000000000000000000000000..92d712035abdb393c908de29cc7fb4a2e2390552
--- /dev/null
+++ b/index.tsx (9).txt
@@ -0,0 +1,76 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { useState } from "react";
+import { toast } from "sonner";
+import { MdSave } from "react-icons/md";
+import { useParams } from "next/navigation";
+
+import Loading from "@/components/loading";
+import { Button } from "@/components/ui/button";
+import { api } from "@/lib/api";
+import { Page } from "@/types";
+
+export function SaveButton({
+ pages,
+ prompts,
+}: {
+ pages: Page[];
+ prompts: string[];
+}) {
+ // get params from URL
+ const { namespace, repoId } = useParams<{
+ namespace: string;
+ repoId: string;
+ }>();
+ const [loading, setLoading] = useState(false);
+
+ const updateSpace = async () => {
+ setLoading(true);
+
+ try {
+ const res = await api.put(`/me/projects/${namespace}/${repoId}`, {
+ pages,
+ prompts,
+ });
+ if (res.data.ok) {
+ toast.success("Your space is updated! 🎉", {
+ action: {
+ label: "See Space",
+ onClick: () => {
+ window.open(
+ `https://huggingface.co/spaces/${namespace}/${repoId}`,
+ "_blank"
+ );
+ },
+ },
+ });
+ } else {
+ toast.error(res?.data?.error || "Failed to update space");
+ }
+ } catch (err: any) {
+ toast.error(err.response?.data?.error || err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+ return (
+ <>
+
+
+ Publish your Project{" "}
+ {loading && }
+
+
+ Publish {loading && }
+
+ >
+ );
+}
diff --git a/index.tsx.txt b/index.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ed39ba92d6801eeddee845eddd3d2a5732197306
--- /dev/null
+++ b/index.tsx.txt
@@ -0,0 +1,89 @@
+import {
+ ChartSpline,
+ CirclePlus,
+ FolderCode,
+ Import,
+ LogOut,
+} from "lucide-react";
+import Link from "next/link";
+
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import { Button } from "@/components/ui/button";
+import { useUser } from "@/hooks/useUser";
+
+export const UserMenu = ({ className }: { className?: string }) => {
+ const { logout, user } = useUser();
+ return (
+
+
+
+
+
+
+ {user?.fullname?.charAt(0).toUpperCase() ?? "E"}
+
+
+ {user?.fullname}
+
+ {user?.fullname.slice(0, 10)}
+ {(user?.fullname?.length ?? 0) > 10 ? "..." : ""}
+
+
+
+
+
+ My Account
+
+
+
+
+
+
+ New Project
+
+
+
+
+
+ Import Project
+
+
+
+
+
+ View Projects
+
+
+
+
+
+ Usage Quota
+
+
+
+
+ {
+ if (confirm("Are you sure you want to log out?")) {
+ logout();
+ }
+ }}
+ >
+
+
+ Log out
+
+
+
+
+ );
+};
diff --git a/input.tsx.txt b/input.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..03295ca6ac617de95b78b09e5e3a6de897a204f0
--- /dev/null
+++ b/input.tsx.txt
@@ -0,0 +1,21 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/layout.tsx (1).txt b/layout.tsx (1).txt
new file mode 100644
index 0000000000000000000000000000000000000000..4a4ec57d2609c783602beb6c06c8dca6a1e6192d
--- /dev/null
+++ b/layout.tsx (1).txt
@@ -0,0 +1,15 @@
+import Navigation from "@/components/public/navigation";
+
+export default async function PublicLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+ );
+}
diff --git a/layout.tsx.txt b/layout.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d960a121272f5b976ae70dbd1c7656e181ca6e6d
--- /dev/null
+++ b/layout.tsx.txt
@@ -0,0 +1,112 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import type { Metadata, Viewport } from "next";
+import { Inter, PT_Sans } from "next/font/google";
+import { cookies } from "next/headers";
+
+import TanstackProvider from "@/components/providers/tanstack-query-provider";
+import "@/assets/globals.css";
+import { Toaster } from "@/components/ui/sonner";
+import MY_TOKEN_KEY from "@/lib/get-cookie-name";
+import { apiServer } from "@/lib/api";
+import AppContext from "@/components/contexts/app-context";
+import Script from "next/script";
+import IframeDetector from "@/components/iframe-detector";
+
+const inter = Inter({
+ variable: "--font-inter-sans",
+ subsets: ["latin"],
+});
+
+const ptSans = PT_Sans({
+ variable: "--font-ptSans-mono",
+ subsets: ["latin"],
+ weight: ["400", "700"],
+});
+
+export const metadata: Metadata = {
+ title: "DeepSite | Build with AI ✨",
+ description:
+ "DeepSite is a web development tool that helps you build websites with AI, no code required. Let's deploy your website with DeepSite and enjoy the magic of AI.",
+ openGraph: {
+ title: "DeepSite | Build with AI ✨",
+ description:
+ "DeepSite is a web development tool that helps you build websites with AI, no code required. Let's deploy your website with DeepSite and enjoy the magic of AI.",
+ url: "https://deepsite.hf.co",
+ siteName: "DeepSite",
+ images: [
+ {
+ url: "https://deepsite.hf.co/banner.png",
+ width: 1200,
+ height: 630,
+ alt: "DeepSite Open Graph Image",
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "DeepSite | Build with AI ✨",
+ description:
+ "DeepSite is a web development tool that helps you build websites with AI, no code required. Let's deploy your website with DeepSite and enjoy the magic of AI.",
+ images: ["https://deepsite.hf.co/banner.png"],
+ },
+ appleWebApp: {
+ capable: true,
+ title: "DeepSite",
+ statusBarStyle: "black-translucent",
+ },
+ icons: {
+ icon: "/logo.svg",
+ shortcut: "/logo.svg",
+ apple: "/logo.svg",
+ },
+};
+
+export const viewport: Viewport = {
+ initialScale: 1,
+ maximumScale: 1,
+ themeColor: "#000000",
+};
+
+async function getMe() {
+ const cookieStore = await cookies();
+ const token = cookieStore.get(MY_TOKEN_KEY())?.value;
+ if (!token) return { user: null, errCode: null };
+ try {
+ const res = await apiServer.get("/me", {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return { user: res.data.user, errCode: null };
+ } catch (err: any) {
+ return { user: null, errCode: err.status };
+ }
+}
+
+// if domain isn't deepsite.hf.co or enzostvs-deepsite.hf.space redirect to deepsite.hf.co
+
+export default async function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ const data = await getMe();
+ return (
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/load-project.tsx.txt b/load-project.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a02c31ae144fe91b06ca47e44521f07cb4cce65c
--- /dev/null
+++ b/load-project.tsx.txt
@@ -0,0 +1,200 @@
+"use client";
+import { useState } from "react";
+import { Import } from "lucide-react";
+
+import { Project } from "@/types";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import Loading from "@/components/loading";
+import { Input } from "../ui/input";
+import { toast } from "sonner";
+import { api } from "@/lib/api";
+import { useUser } from "@/hooks/useUser";
+import { LoginModal } from "../login-modal";
+import { useRouter } from "next/navigation";
+
+export const LoadProject = ({
+ fullXsBtn = false,
+ onSuccess,
+}: {
+ fullXsBtn?: boolean;
+ onSuccess: (project: Project) => void;
+}) => {
+ const { user } = useUser();
+ const router = useRouter();
+
+ const [openLoginModal, setOpenLoginModal] = useState(false);
+ const [open, setOpen] = useState(false);
+ const [url, setUrl] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+
+ const checkIfUrlIsValid = (url: string) => {
+ // should match a hugging face spaces URL like: https://huggingface.co/spaces/username/project or https://hf.co/spaces/username/project
+ const urlPattern = new RegExp(
+ /^(https?:\/\/)?(huggingface\.co|hf\.co)\/spaces\/([\w-]+)\/([\w-]+)$/,
+ "i"
+ );
+ return urlPattern.test(url);
+ };
+
+ const handleClick = async () => {
+ if (isLoading) return; // Prevent multiple clicks while loading
+ if (!url) {
+ toast.error("Please enter a URL.");
+ return;
+ }
+ if (!checkIfUrlIsValid(url)) {
+ toast.error("Please enter a valid Hugging Face Spaces URL.");
+ return;
+ }
+
+ const [username, namespace] = url
+ .replace("https://huggingface.co/spaces/", "")
+ .replace("https://hf.co/spaces/", "")
+ .split("/");
+
+ setIsLoading(true);
+ try {
+ const response = await api.post(`/me/projects/${username}/${namespace}`);
+ toast.success("Project imported successfully!");
+ setOpen(false);
+ setUrl("");
+ onSuccess(response.data.project);
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } catch (error: any) {
+ if (error?.response?.data?.redirect) {
+ return router.push(error.response.data.redirect);
+ }
+ toast.error(
+ error?.response?.data?.error ?? "Failed to import the project."
+ );
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+ <>
+ {!user ? (
+ <>
+ setOpenLoginModal(true)}
+ >
+
+ Load existing Project
+
+ setOpenLoginModal(true)}
+ >
+ {fullXsBtn && }
+ Load
+ {fullXsBtn && " existing Project"}
+
+
+ >
+ ) : (
+
+
+
+
+
+ Load existing Project
+
+
+ {fullXsBtn && }
+ Load
+ {fullXsBtn && " existing Project"}
+
+
+
+
+
+
+
+
+
+ Enter your Hugging Face Space
+
+
setUrl(e.target.value)}
+ onBlur={(e) => {
+ const inputUrl = e.target.value.trim();
+ if (!inputUrl) {
+ setUrl("");
+ return;
+ }
+ if (!checkIfUrlIsValid(inputUrl)) {
+ toast.error("Please enter a valid URL.");
+ return;
+ }
+ setUrl(inputUrl);
+ }}
+ className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
+ />
+
+
+
+ Then, let's import it!
+
+
+ {isLoading ? (
+ <>
+
+ Fetching your Space...
+ >
+ ) : (
+ <>Import your Space>
+ )}
+
+
+
+
+
+ )}
+ >
+ );
+};
diff --git a/logo (1).svg b/logo (1).svg
new file mode 100644
index 0000000000000000000000000000000000000000..e69f057d4d4c256f02881888e781aa0943010c3e
--- /dev/null
+++ b/logo (1).svg
@@ -0,0 +1,316 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/logo.svg b/logo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..565f498f64b434d4b479fe5da0e7cddd5ee78591
--- /dev/null
+++ b/logo.svg
@@ -0,0 +1,330 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4388f0dac6e3d9bbb26b4ae8fb4f5e3be20d4247
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,12 @@
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
+
+export function middleware(request: NextRequest) {
+ const headers = new Headers(request.headers);
+ headers.set("x-current-host", request.nextUrl.host);
+ return NextResponse.next({ headers });
+}
+
+export const config = {
+ matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
+};
diff --git a/mongodb.ts b/mongodb.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8145d079b0623c2ead2454f2d5329e644a69ab0a
--- /dev/null
+++ b/mongodb.ts
@@ -0,0 +1,28 @@
+import mongoose from "mongoose";
+
+const MONGODB_URI = process.env.MONGODB_URI;
+// @ts-expect-error iknown issue with mongoose types
+let cached = global.mongoose;
+
+if (!cached) {
+ // @ts-expect-error iknown issue with mongoose types
+ cached = global.mongoose = { conn: null, promise: null };
+}
+
+async function dbConnect() {
+ if (cached.conn) {
+ return cached.conn;
+ }
+
+ if (!cached.promise) {
+ cached.promise = mongoose
+ .connect(MONGODB_URI as string)
+ .then((mongoose) => {
+ return mongoose;
+ });
+ }
+ cached.conn = await cached.promise;
+ return cached.conn;
+}
+
+export default dbConnect;
diff --git a/nebius.svg b/nebius.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2e4ab69276e518c2cb45f54d209c58030f530e45
--- /dev/null
+++ b/nebius.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/next.config.ts b/next.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2e18ae34bb84c3ae287fc2420cb7a17d340616d1
--- /dev/null
+++ b/next.config.ts
@@ -0,0 +1,32 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ /* config options here */
+ webpack(config, options) {
+ const { isServer } = options;
+ config.module.rules.push({
+ test: /\.(ogg|mp3|wav|mpe?g)$/i,
+ exclude: config.exclude,
+ use: [
+ {
+ loader: require.resolve("url-loader"),
+ options: {
+ limit: config.inlineImageLimit,
+ fallback: require.resolve("file-loader"),
+ publicPath: `${config.assetPrefix}/_next/static/images/`,
+ outputPath: `${isServer ? "../" : ""}static/images/`,
+ name: "[name]-[hash].[ext]",
+ esModule: config.esModule || false,
+ },
+ },
+ ],
+ });
+
+ return config;
+ },
+ images: {
+ remotePatterns: [new URL('https://huggingface.co/**')],
+ },
+};
+
+export default nextConfig;
diff --git a/not-logged.tsx.txt b/not-logged.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f36f399c437ac74e14743462f31b1ff0e777916a
--- /dev/null
+++ b/not-logged.tsx.txt
@@ -0,0 +1,26 @@
+"use client";
+
+import { useUser } from "@/hooks/useUser";
+import { Button } from "@/components/ui/button";
+
+export const NotLogged = () => {
+ const { openLoginWindow } = useUser();
+ return (
+
+
+
+
+ Oops! You must be logged to continue.
+
+
+ Unfortunately you cannot access DeepSite without being logged
+ through your Hugging Face account.
+
+
+
+ Log In to Continue
+
+
+
+ );
+};
diff --git a/novita.svg b/novita.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d14e345951ccb6966f34ddde190b80d6a18b5f98
--- /dev/null
+++ b/novita.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/page.tsx (1).txt b/page.tsx (1).txt
new file mode 100644
index 0000000000000000000000000000000000000000..9dabea0dcf9de7c30b66ff35588740ed59de3f0e
--- /dev/null
+++ b/page.tsx (1).txt
@@ -0,0 +1,5 @@
+import { AppEditor } from "@/components/editor";
+
+export default function ProjectsNewPage() {
+ return ;
+}
diff --git a/page.tsx (2).txt b/page.tsx (2).txt
new file mode 100644
index 0000000000000000000000000000000000000000..e60c46e774de73ea30c6964ae49c5a32daf6c87d
--- /dev/null
+++ b/page.tsx (2).txt
@@ -0,0 +1,42 @@
+import { cookies } from "next/headers";
+import { redirect } from "next/navigation";
+
+import { apiServer } from "@/lib/api";
+import MY_TOKEN_KEY from "@/lib/get-cookie-name";
+import { AppEditor } from "@/components/editor";
+
+async function getProject(namespace: string, repoId: string) {
+ // TODO replace with a server action
+ const cookieStore = await cookies();
+ const token = cookieStore.get(MY_TOKEN_KEY())?.value;
+ if (!token) return {};
+ try {
+ const { data } = await apiServer.get(
+ `/me/projects/${namespace}/${repoId}`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+
+ return data.project;
+ } catch {
+ return {};
+ }
+}
+
+export default async function ProjectNamespacePage({
+ params,
+}: {
+ params: Promise<{ namespace: string; repoId: string }>;
+}) {
+ const { namespace, repoId } = await params;
+ const data = await getProject(namespace, repoId);
+ if (!data?.pages) {
+ redirect("/projects");
+ }
+ return (
+
+ );
+}
diff --git a/page.tsx (3).txt b/page.tsx (3).txt
new file mode 100644
index 0000000000000000000000000000000000000000..a45a6bc6f58907b4ee5efbf0f70a51ee153625c7
--- /dev/null
+++ b/page.tsx (3).txt
@@ -0,0 +1,28 @@
+import { redirect } from "next/navigation";
+import { Metadata } from "next";
+
+import { getAuth } from "@/app/actions/auth";
+
+export const revalidate = 1;
+
+export const metadata: Metadata = {
+ robots: "noindex, nofollow",
+};
+
+export default async function Auth() {
+ const loginRedirectUrl = await getAuth();
+ if (loginRedirectUrl) {
+ redirect(loginRedirectUrl);
+ }
+
+ return (
+
+
+
Error
+
+ An error occurred while trying to log in. Please try again later.
+
+
+
+ );
+}
diff --git a/page.tsx (4).txt b/page.tsx (4).txt
new file mode 100644
index 0000000000000000000000000000000000000000..ac44f365b707908d6003a4266db1269d297a4336
--- /dev/null
+++ b/page.tsx (4).txt
@@ -0,0 +1,72 @@
+"use client";
+import Link from "next/link";
+import { useUser } from "@/hooks/useUser";
+import { use, useState } from "react";
+import { useMount, useTimeoutFn } from "react-use";
+
+import { Button } from "@/components/ui/button";
+export default function AuthCallback({
+ searchParams,
+}: {
+ searchParams: Promise<{ code: string }>;
+}) {
+ const [showButton, setShowButton] = useState(false);
+ const { code } = use(searchParams);
+ const { loginFromCode } = useUser();
+
+ useMount(async () => {
+ if (code) {
+ await loginFromCode(code);
+ }
+ });
+
+ useTimeoutFn(
+ () => setShowButton(true),
+ 7000 // Show button after 5 seconds
+ );
+
+ return (
+
+
+
+
+
+
+ If you are not redirected automatically in the next 5 seconds,
+ please click the button below
+
+ {showButton ? (
+
+
+ Go to Home
+
+
+ ) : (
+
+ Please wait, we are logging you in...
+
+ )}
+
+
+
+
+ );
+}
diff --git a/page.tsx (5).txt b/page.tsx (5).txt
new file mode 100644
index 0000000000000000000000000000000000000000..c0849e72cf29027524ec9ebc3818e80a8aee5ef3
--- /dev/null
+++ b/page.tsx (5).txt
@@ -0,0 +1,44 @@
+import { AskAi } from "@/components/space/ask-ai";
+import { redirect } from "next/navigation";
+export default function Home() {
+ redirect("/projects/new");
+ return (
+ <>
+
+
+
+
+ Deploy your website in seconds
+
+
+
+
+ Features that make you smile
+
+
+ >
+ );
+}
diff --git a/page.tsx (6).txt b/page.tsx (6).txt
new file mode 100644
index 0000000000000000000000000000000000000000..374dc6b1194256c5b142a62168ce0f414f6098be
--- /dev/null
+++ b/page.tsx (6).txt
@@ -0,0 +1,13 @@
+import { redirect } from "next/navigation";
+
+import { MyProjects } from "@/components/my-projects";
+import { getProjects } from "@/app/actions/projects";
+
+export default async function ProjectsPage() {
+ const { ok, projects } = await getProjects();
+ if (!ok) {
+ redirect("/");
+ }
+
+ return ;
+}
diff --git a/page.tsx.txt b/page.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c05b3d7aa56006078326facc3107c450faad80b6
--- /dev/null
+++ b/page.tsx.txt
@@ -0,0 +1,82 @@
+import classNames from "classnames";
+import { XIcon } from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import { Page } from "@/types";
+
+export function ListPagesItem({
+ page,
+ currentPage,
+ onSelectPage,
+ onDeletePage,
+ index,
+}: {
+ page: Page;
+ currentPage: string;
+ onSelectPage: (path: string, newPath?: string) => void;
+ onDeletePage: (path: string) => void;
+ index: number;
+}) {
+ return (
+ onSelectPage(page.path)}
+ title={page.path}
+ >
+ {/* {index > 0 && (
+ {
+ e.stopPropagation();
+ // open the window modal to edit the name page
+ let newName = window.prompt(
+ "Enter new name for the page:",
+ page.path
+ );
+ if (newName && newName.trim() !== "") {
+ newName = newName.toLowerCase();
+ if (!newName.endsWith(".html")) {
+ newName = newName.replace(/\.[^/.]+$/, "");
+ newName = newName.replace(/\s+/g, "-");
+ newName += ".html";
+ }
+ onSelectPage(page.path, newName);
+ } else {
+ window.alert("Page name cannot be empty.");
+ }
+ }}
+ >
+
+
+ )} */}
+ {page.path}
+ {index > 0 && (
+ {
+ e.stopPropagation();
+ if (
+ window.confirm(
+ "Are you sure you want to delete this page? This action cannot be undone."
+ )
+ ) {
+ onDeletePage(page.path);
+ }
+ }}
+ >
+
+
+ )}
+
+ );
+}
diff --git a/popover.tsx.txt b/popover.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..27576b28e331f9c4f42f91569c963bd99d97c598
--- /dev/null
+++ b/popover.tsx.txt
@@ -0,0 +1,48 @@
+"use client";
+
+import * as React from "react";
+import * as PopoverPrimitive from "@radix-ui/react-popover";
+
+import { cn } from "@/lib/utils";
+
+function Popover({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function PopoverTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function PopoverContent({
+ className,
+ align = "center",
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function PopoverAnchor({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
diff --git a/project-card.tsx.txt b/project-card.tsx.txt
new file mode 100644
index 0000000000000000000000000000000000000000..57db464bdcefb247ada72db40e457aa57e445248
--- /dev/null
+++ b/project-card.tsx.txt
@@ -0,0 +1,74 @@
+import Link from "next/link";
+import { formatDistance } from "date-fns";
+import { EllipsisVertical, Settings } from "lucide-react";
+
+import { Project } from "@/types";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+export function ProjectCard({ project }: { project: Project }) {
+ return (
+
+
+
+
+
+ Open project
+
+
+
+
+
+ {project.space_id}
+
+
+ Updated{" "}
+ {formatDistance(
+ new Date(project._updatedAt || Date.now()),
+ new Date(),
+ {
+ addSuffix: true,
+ }
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Project Settings
+
+
+
+
+
+
+
+ );
+}
diff --git a/projects.ts b/projects.ts
new file mode 100644
index 0000000000000000000000000000000000000000..209b16d9d9960eeafb9e0b02d7b1b3eda638338d
--- /dev/null
+++ b/projects.ts
@@ -0,0 +1,63 @@
+"use server";
+
+import { isAuthenticated } from "@/lib/auth";
+import { NextResponse } from "next/server";
+import dbConnect from "@/lib/mongodb";
+import Project from "@/models/Project";
+import { Project as ProjectType } from "@/types";
+
+export async function getProjects(): Promise<{
+ ok: boolean;
+ projects: ProjectType[];
+}> {
+ const user = await isAuthenticated();
+
+ if (user instanceof NextResponse || !user) {
+ return {
+ ok: false,
+ projects: [],
+ };
+ }
+
+ await dbConnect();
+ const projects = await Project.find({
+ user_id: user?.id,
+ })
+ .sort({ _createdAt: -1 })
+ .limit(100)
+ .lean();
+ if (!projects) {
+ return {
+ ok: false,
+ projects: [],
+ };
+ }
+ return {
+ ok: true,
+ projects: JSON.parse(JSON.stringify(projects)) as ProjectType[],
+ };
+}
+
+export async function getProject(
+ namespace: string,
+ repoId: string
+): Promise {
+ const user = await isAuthenticated();
+
+ if (user instanceof NextResponse || !user) {
+ return null;
+ }
+
+ await dbConnect();
+ const project = await Project.findOne({
+ user_id: user.id,
+ namespace,
+ repoId,
+ }).lean();
+
+ if (!project) {
+ return null;
+ }
+
+ return JSON.parse(JSON.stringify(project)) as ProjectType;
+}
diff --git a/prompts.ts b/prompts.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4e8c13accbbf58dd2cfce11187076a33a8ef0866
--- /dev/null
+++ b/prompts.ts
@@ -0,0 +1,141 @@
+export const SEARCH_START = "<<<<<<< SEARCH";
+export const DIVIDER = "=======";
+export const REPLACE_END = ">>>>>>> REPLACE";
+export const MAX_REQUESTS_PER_IP = 2;
+export const TITLE_PAGE_START = "<<<<<<< START_TITLE ";
+export const TITLE_PAGE_END = " >>>>>>> END_TITLE";
+export const NEW_PAGE_START = "<<<<<<< NEW_PAGE_START ";
+export const NEW_PAGE_END = " >>>>>>> NEW_PAGE_END";
+export const UPDATE_PAGE_START = "<<<<<<< UPDATE_PAGE_START ";
+export const UPDATE_PAGE_END = " >>>>>>> UPDATE_PAGE_END";
+
+// TODO REVIEW LINK. MAYBE GO BACK TO SANDPACK.
+// FIX PREVIEW LINK NOT WORKING ONCE THE SITE IS DEPLOYED.
+
+export const PROMPT_FOR_IMAGE_GENERATION = `If you want to use image placeholder, http://Static.photos Usage:Format: http://static.photos/[category]/[dimensions]/[seed] where dimensions must be one of: 200x200, 320x240, 640x360, 1024x576, or 1200x630; seed can be any number (1-999+) for consistent images or omit for random; categories include: nature, office, people, technology, minimal, abstract, aerial, blurred, bokeh, gradient, monochrome, vintage, white, black, blue, red, green, yellow, cityscape, workspace, food, travel, textures, industry, indoor, outdoor, studio, finance, medical, season, holiday, event, sport, science, legal, estate, restaurant, retail, wellness, agriculture, construction, craft, cosmetic, automotive, gaming, or education.
+Examples: http://static.photos/red/320x240/133 (red-themed with seed 133), http://static.photos/640x360 (random category and image), http://static.photos/nature/1200x630/42 (nature-themed with seed 42).`
+
+export const INITIAL_SYSTEM_PROMPT = `You are an expert UI/UX and Front-End Developer.
+You create website in a way a designer would, using ONLY HTML, CSS and Javascript.
+Try to create the best UI possible. Important: Make the website responsive by using TailwindCSS. Use it as much as you can, if you can't use it, use custom css (make sure to import tailwind with in the head).
+Also try to elaborate as much as you can, to create something unique, with a great design.
+If you want to use ICONS import Feather Icons (Make sure to add and in the head., and in the body. Ex : ).
+For scroll animations you can use: AOS.com (Make sure to add and and ).
+For interactive animations you can use: Vanta.js (Make sure to add and in the body.).
+You can create multiple pages website at once (following the format rules below) or a Single Page Application. If the user doesn't ask for a specific version, you have to determine the best version for the user, depending on the request. (Try to avoid the Single Page Application if the user asks for multiple pages.)
+${PROMPT_FOR_IMAGE_GENERATION}
+No need to explain what you did. Just return the expected result. AVOID Chinese characters in the code if not asked by the user.
+Return the results in a \`\`\`html\`\`\` markdown. Format the results like:
+1. Start with ${TITLE_PAGE_START}.
+2. Add the name of the page without special character, such as spaces or punctuation, using the .html format only, right after the start tag.
+3. Close the start tag with the ${TITLE_PAGE_END}.
+4. Start the HTML response with the triple backticks, like \`\`\`html.
+5. Insert the following html there.
+6. Close with the triple backticks, like \`\`\`.
+7. Retry if another pages.
+Example Code:
+${TITLE_PAGE_START}index.html${TITLE_PAGE_END}
+\`\`\`html
+
+
+
+
+
+ Index
+
+
+
+
+
+
+
+
+
+ Hello World
+
+
+
+
+
+\`\`\`
+IMPORTANT: The first file should be always named index.html.`
+
+export const FOLLOW_UP_SYSTEM_PROMPT = `You are an expert UI/UX and Front-End Developer modifying an existing HTML files.
+The user wants to apply changes and probably add new features/pages to the website, based on their request.
+You MUST output ONLY the changes required using the following UPDATE_PAGE_START and SEARCH/REPLACE format. Do NOT output the entire file.
+If it's a new page, you MUST applied the following NEW_PAGE_START and UPDATE_PAGE_END format.
+${PROMPT_FOR_IMAGE_GENERATION}
+Do NOT explain the changes or what you did, just return the expected results.
+Update Format Rules:
+1. Start with ${UPDATE_PAGE_START}
+2. Provide the name of the page you are modifying.
+3. Close the start tag with the ${UPDATE_PAGE_END}.
+4. Start with ${SEARCH_START}
+5. Provide the exact lines from the current code that need to be replaced.
+6. Use ${DIVIDER} to separate the search block from the replacement.
+7. Provide the new lines that should replace the original lines.
+8. End with ${REPLACE_END}
+9. You can use multiple SEARCH/REPLACE blocks if changes are needed in different parts of the file.
+10. To insert code, use an empty SEARCH block (only ${SEARCH_START} and ${DIVIDER} on their lines) if inserting at the very beginning, otherwise provide the line *before* the insertion point in the SEARCH block and include that line plus the new lines in the REPLACE block.
+11. To delete code, provide the lines to delete in the SEARCH block and leave the REPLACE block empty (only ${DIVIDER} and ${REPLACE_END} on their lines).
+12. IMPORTANT: The SEARCH block must *exactly* match the current code, including indentation and whitespace.
+Example Modifying Code:
+\`\`\`
+Some explanation...
+${UPDATE_PAGE_START}index.html${UPDATE_PAGE_END}
+${SEARCH_START}
+ Old Title
+${DIVIDER}
+ New Title
+${REPLACE_END}
+${SEARCH_START}
+
+ Hello World
+
+
+
+
+${DIVIDER}
+
+
+${REPLACE_END}
+\`\`\`
+Example Deleting Code:
+\`\`\`
+Removing the paragraph...
+${TITLE_PAGE_START}index.html${TITLE_PAGE_END}
+${SEARCH_START}
+
This paragraph will be deleted.
+${DIVIDER}
+${REPLACE_END}
+\`\`\`
+The user can also ask to add a new page, in this case you should return the new page in the following format:
+1. Start with ${NEW_PAGE_START}.
+2. Add the name of the page without special character, such as spaces or punctuation, using the .html format only, right after the start tag.
+3. Close the start tag with the ${NEW_PAGE_END}.
+4. Start the HTML response with the triple backticks, like \`\`\`html.
+5. Insert the following html there.
+6. Close with the triple backticks, like \`\`\`.
+7. Retry if another pages.
+Example Code:
+${NEW_PAGE_START}index.html${NEW_PAGE_END}
+\`\`\`html
+
+
+