Spaces:
				
			
			
	
			
			
					
		Running
		
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
	Load existing project
Browse files
    	
        app/api/me/projects/[namespace]/[repoId]/route.ts
    CHANGED
    
    | @@ -41,11 +41,11 @@ export async function GET( | |
| 41 | 
             
                  additionalFields: ["author"],
         | 
| 42 | 
             
                });
         | 
| 43 |  | 
| 44 | 
            -
                if (!space || space.sdk !== "static" | 
| 45 | 
             
                  return NextResponse.json(
         | 
| 46 | 
             
                    {
         | 
| 47 | 
             
                      ok: false,
         | 
| 48 | 
            -
                      error: "Space is not a static space | 
| 49 | 
             
                    },
         | 
| 50 | 
             
                    { status: 404 }
         | 
| 51 | 
             
                  );
         | 
| @@ -160,3 +160,76 @@ export async function PUT( | |
| 160 | 
             
              );
         | 
| 161 | 
             
              return NextResponse.json({ ok: true }, { status: 200 });
         | 
| 162 | 
             
            }
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 41 | 
             
                  additionalFields: ["author"],
         | 
| 42 | 
             
                });
         | 
| 43 |  | 
| 44 | 
            +
                if (!space || space.sdk !== "static") {
         | 
| 45 | 
             
                  return NextResponse.json(
         | 
| 46 | 
             
                    {
         | 
| 47 | 
             
                      ok: false,
         | 
| 48 | 
            +
                      error: "Space is not a static space",
         | 
| 49 | 
             
                    },
         | 
| 50 | 
             
                    { status: 404 }
         | 
| 51 | 
             
                  );
         | 
|  | |
| 160 | 
             
              );
         | 
| 161 | 
             
              return NextResponse.json({ ok: true }, { status: 200 });
         | 
| 162 | 
             
            }
         | 
| 163 | 
            +
             | 
| 164 | 
            +
            export async function POST(
         | 
| 165 | 
            +
              req: NextRequest,
         | 
| 166 | 
            +
              { params }: { params: Promise<{ namespace: string; repoId: string }> }
         | 
| 167 | 
            +
            ) {
         | 
| 168 | 
            +
              const user = await isAuthenticated();
         | 
| 169 | 
            +
             | 
| 170 | 
            +
              if (user instanceof NextResponse || !user) {
         | 
| 171 | 
            +
                return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
         | 
| 172 | 
            +
              }
         | 
| 173 | 
            +
             | 
| 174 | 
            +
              await dbConnect();
         | 
| 175 | 
            +
              const param = await params;
         | 
| 176 | 
            +
              const { namespace, repoId } = param;
         | 
| 177 | 
            +
             | 
| 178 | 
            +
              const space = await spaceInfo({
         | 
| 179 | 
            +
                name: namespace + "/" + repoId,
         | 
| 180 | 
            +
                accessToken: user.token as string,
         | 
| 181 | 
            +
                additionalFields: ["author"],
         | 
| 182 | 
            +
              });
         | 
| 183 | 
            +
             | 
| 184 | 
            +
              if (!space || space.sdk !== "static") {
         | 
| 185 | 
            +
                return NextResponse.json(
         | 
| 186 | 
            +
                  {
         | 
| 187 | 
            +
                    ok: false,
         | 
| 188 | 
            +
                    error: "Space is not a static space",
         | 
| 189 | 
            +
                  },
         | 
| 190 | 
            +
                  { status: 404 }
         | 
| 191 | 
            +
                );
         | 
| 192 | 
            +
              }
         | 
| 193 | 
            +
              if (space.author !== user.name) {
         | 
| 194 | 
            +
                return NextResponse.json(
         | 
| 195 | 
            +
                  {
         | 
| 196 | 
            +
                    ok: false,
         | 
| 197 | 
            +
                    error: "Space does not belong to the authenticated user",
         | 
| 198 | 
            +
                  },
         | 
| 199 | 
            +
                  { status: 403 }
         | 
| 200 | 
            +
                );
         | 
| 201 | 
            +
              }
         | 
| 202 | 
            +
             | 
| 203 | 
            +
              const project = await Project.findOne({
         | 
| 204 | 
            +
                user_id: user.id,
         | 
| 205 | 
            +
                space_id: `${namespace}/${repoId}`,
         | 
| 206 | 
            +
              }).lean();
         | 
| 207 | 
            +
              if (project) {
         | 
| 208 | 
            +
                return NextResponse.json(
         | 
| 209 | 
            +
                  {
         | 
| 210 | 
            +
                    ok: false,
         | 
| 211 | 
            +
                    error: "Project already exists",
         | 
| 212 | 
            +
                  },
         | 
| 213 | 
            +
                  { status: 400 }
         | 
| 214 | 
            +
                );
         | 
| 215 | 
            +
              }
         | 
| 216 | 
            +
             | 
| 217 | 
            +
              const newProject = new Project({
         | 
| 218 | 
            +
                user_id: user.id,
         | 
| 219 | 
            +
                space_id: `${namespace}/${repoId}`,
         | 
| 220 | 
            +
                prompts: [],
         | 
| 221 | 
            +
              });
         | 
| 222 | 
            +
             | 
| 223 | 
            +
              await newProject.save();
         | 
| 224 | 
            +
              return NextResponse.json(
         | 
| 225 | 
            +
                {
         | 
| 226 | 
            +
                  ok: true,
         | 
| 227 | 
            +
                  project: {
         | 
| 228 | 
            +
                    id: newProject._id,
         | 
| 229 | 
            +
                    space_id: newProject.space_id,
         | 
| 230 | 
            +
                    prompts: newProject.prompts,
         | 
| 231 | 
            +
                  },
         | 
| 232 | 
            +
                },
         | 
| 233 | 
            +
                { status: 201 }
         | 
| 234 | 
            +
              );
         | 
| 235 | 
            +
            }
         | 
    	
        components/editor/index.tsx
    CHANGED
    
    | @@ -3,7 +3,7 @@ import { useRef, useState } from "react"; | |
| 3 | 
             
            import { toast } from "sonner";
         | 
| 4 | 
             
            import { editor } from "monaco-editor";
         | 
| 5 | 
             
            import Editor from "@monaco-editor/react";
         | 
| 6 | 
            -
            import { CopyIcon } from "lucide-react";
         | 
| 7 | 
             
            import {
         | 
| 8 | 
             
              useCopyToClipboard,
         | 
| 9 | 
             
              useEvent,
         | 
| @@ -24,10 +24,13 @@ import { AskAI } from "@/components/editor/ask-ai"; | |
| 24 | 
             
            import { DeployButton } from "./deploy-button";
         | 
| 25 | 
             
            import { Project } from "@/types";
         | 
| 26 | 
             
            import { SaveButton } from "./save-button";
         | 
|  | |
|  | |
| 27 |  | 
| 28 | 
             
            export const AppEditor = ({ project }: { project?: Project | null }) => {
         | 
| 29 | 
             
              const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
         | 
| 30 | 
             
              const [, copyToClipboard] = useCopyToClipboard();
         | 
|  | |
| 31 | 
             
              const { html, setHtml, htmlHistory, setHtmlHistory, prompts, setPrompts } =
         | 
| 32 | 
             
                useEditor(project?.html ?? (htmlStorage as string) ?? defaultHTML);
         | 
| 33 | 
             
              // get query params from URL
         | 
| @@ -173,6 +176,16 @@ export const AppEditor = ({ project }: { project?: Project | null }) => { | |
| 173 | 
             
              return (
         | 
| 174 | 
             
                <section className="h-[100dvh] bg-neutral-950 flex flex-col">
         | 
| 175 | 
             
                  <Header tab={currentTab} onNewTab={setCurrentTab}>
         | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 176 | 
             
                    {project?._id ? (
         | 
| 177 | 
             
                      <SaveButton html={html} prompts={prompts} />
         | 
| 178 | 
             
                    ) : (
         | 
|  | |
| 3 | 
             
            import { toast } from "sonner";
         | 
| 4 | 
             
            import { editor } from "monaco-editor";
         | 
| 5 | 
             
            import Editor from "@monaco-editor/react";
         | 
| 6 | 
            +
            import { CopyIcon, Import } from "lucide-react";
         | 
| 7 | 
             
            import {
         | 
| 8 | 
             
              useCopyToClipboard,
         | 
| 9 | 
             
              useEvent,
         | 
|  | |
| 24 | 
             
            import { DeployButton } from "./deploy-button";
         | 
| 25 | 
             
            import { Project } from "@/types";
         | 
| 26 | 
             
            import { SaveButton } from "./save-button";
         | 
| 27 | 
            +
            import { Button } from "@/components/ui/button";
         | 
| 28 | 
            +
            import { useUser } from "@/hooks/useUser";
         | 
| 29 |  | 
| 30 | 
             
            export const AppEditor = ({ project }: { project?: Project | null }) => {
         | 
| 31 | 
             
              const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content");
         | 
| 32 | 
             
              const [, copyToClipboard] = useCopyToClipboard();
         | 
| 33 | 
            +
              const { user, openLoginWindow } = useUser();
         | 
| 34 | 
             
              const { html, setHtml, htmlHistory, setHtmlHistory, prompts, setPrompts } =
         | 
| 35 | 
             
                useEditor(project?.html ?? (htmlStorage as string) ?? defaultHTML);
         | 
| 36 | 
             
              // get query params from URL
         | 
|  | |
| 176 | 
             
              return (
         | 
| 177 | 
             
                <section className="h-[100dvh] bg-neutral-950 flex flex-col">
         | 
| 178 | 
             
                  <Header tab={currentTab} onNewTab={setCurrentTab}>
         | 
| 179 | 
            +
                    <Button
         | 
| 180 | 
            +
                      variant="outline"
         | 
| 181 | 
            +
                      onClick={() => {
         | 
| 182 | 
            +
                        if (user?.id) router.push("/projects");
         | 
| 183 | 
            +
                        else openLoginWindow();
         | 
| 184 | 
            +
                      }}
         | 
| 185 | 
            +
                    >
         | 
| 186 | 
            +
                      <Import className="size-4 mr-1.5" />
         | 
| 187 | 
            +
                      Load Project
         | 
| 188 | 
            +
                    </Button>
         | 
| 189 | 
             
                    {project?._id ? (
         | 
| 190 | 
             
                      <SaveButton html={html} prompts={prompts} />
         | 
| 191 | 
             
                    ) : (
         | 
    	
        components/my-projects/index.tsx
    CHANGED
    
    | @@ -1,28 +1,43 @@ | |
| 1 | 
             
            "use client";
         | 
|  | |
|  | |
|  | |
|  | |
| 2 | 
             
            import { useUser } from "@/hooks/useUser";
         | 
| 3 | 
             
            import { Project } from "@/types";
         | 
| 4 | 
             
            import { redirect } from "next/navigation";
         | 
| 5 | 
             
            import { ProjectCard } from "./project-card";
         | 
| 6 | 
            -
            import {  | 
| 7 | 
            -
            import Link from "next/link";
         | 
| 8 |  | 
| 9 | 
            -
            export function MyProjects({ | 
|  | |
|  | |
|  | |
|  | |
| 10 | 
             
              const { user } = useUser();
         | 
| 11 | 
             
              if (!user) {
         | 
| 12 | 
             
                redirect("/");
         | 
| 13 | 
             
              }
         | 
|  | |
| 14 | 
             
              return (
         | 
| 15 | 
             
                <>
         | 
| 16 | 
             
                  <section className="max-w-[86rem] py-12 px-4 mx-auto">
         | 
| 17 | 
            -
                    < | 
| 18 | 
            -
                      < | 
| 19 | 
            -
                        < | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
                         | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 26 | 
             
                    <div className="mt-8 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
         | 
| 27 | 
             
                      <Link
         | 
| 28 | 
             
                        href="/projects/new"
         | 
|  | |
| 1 | 
             
            "use client";
         | 
| 2 | 
            +
            import { Plus } from "lucide-react";
         | 
| 3 | 
            +
            import Link from "next/link";
         | 
| 4 | 
            +
            import { useState } from "react";
         | 
| 5 | 
            +
             | 
| 6 | 
             
            import { useUser } from "@/hooks/useUser";
         | 
| 7 | 
             
            import { Project } from "@/types";
         | 
| 8 | 
             
            import { redirect } from "next/navigation";
         | 
| 9 | 
             
            import { ProjectCard } from "./project-card";
         | 
| 10 | 
            +
            import { LoadProject } from "./load-project";
         | 
|  | |
| 11 |  | 
| 12 | 
            +
            export function MyProjects({
         | 
| 13 | 
            +
              projects: initialProjects,
         | 
| 14 | 
            +
            }: {
         | 
| 15 | 
            +
              projects: Project[];
         | 
| 16 | 
            +
            }) {
         | 
| 17 | 
             
              const { user } = useUser();
         | 
| 18 | 
             
              if (!user) {
         | 
| 19 | 
             
                redirect("/");
         | 
| 20 | 
             
              }
         | 
| 21 | 
            +
              const [projects, setProjects] = useState<Project[]>(initialProjects || []);
         | 
| 22 | 
             
              return (
         | 
| 23 | 
             
                <>
         | 
| 24 | 
             
                  <section className="max-w-[86rem] py-12 px-4 mx-auto">
         | 
| 25 | 
            +
                    <header className="flex items-center justify-between">
         | 
| 26 | 
            +
                      <div className="text-left">
         | 
| 27 | 
            +
                        <h1 className="text-3xl font-bold text-white">
         | 
| 28 | 
            +
                          <span className="capitalize">{user.fullname}</span>'s
         | 
| 29 | 
            +
                          DeepSite Projects
         | 
| 30 | 
            +
                        </h1>
         | 
| 31 | 
            +
                        <p className="text-muted-foreground text-base mt-1 max-w-xl">
         | 
| 32 | 
            +
                          Create, manage, and explore your DeepSite projects.
         | 
| 33 | 
            +
                        </p>
         | 
| 34 | 
            +
                      </div>
         | 
| 35 | 
            +
                      <LoadProject
         | 
| 36 | 
            +
                        addProject={(project: Project) => {
         | 
| 37 | 
            +
                          setProjects((prev) => [...prev, project]);
         | 
| 38 | 
            +
                        }}
         | 
| 39 | 
            +
                      />
         | 
| 40 | 
            +
                    </header>
         | 
| 41 | 
             
                    <div className="mt-8 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
         | 
| 42 | 
             
                      <Link
         | 
| 43 | 
             
                        href="/projects/new"
         | 
    	
        components/my-projects/load-project.tsx
    ADDED
    
    | @@ -0,0 +1,150 @@ | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | 
|  | |
| 1 | 
            +
            import { useState } from "react";
         | 
| 2 | 
            +
            import { Import } from "lucide-react";
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            import { Project } from "@/types";
         | 
| 5 | 
            +
            import { Button } from "@/components/ui/button";
         | 
| 6 | 
            +
            import {
         | 
| 7 | 
            +
              Dialog,
         | 
| 8 | 
            +
              DialogContent,
         | 
| 9 | 
            +
              DialogTitle,
         | 
| 10 | 
            +
              DialogTrigger,
         | 
| 11 | 
            +
            } from "@/components/ui/dialog";
         | 
| 12 | 
            +
            import Loading from "@/components/loading";
         | 
| 13 | 
            +
            import { Input } from "../ui/input";
         | 
| 14 | 
            +
            import { toast } from "sonner";
         | 
| 15 | 
            +
            import { api } from "@/lib/api";
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            export const LoadProject = ({
         | 
| 18 | 
            +
              addProject,
         | 
| 19 | 
            +
            }: {
         | 
| 20 | 
            +
              addProject: (project: Project) => void;
         | 
| 21 | 
            +
            }) => {
         | 
| 22 | 
            +
              const [open, setOpen] = useState(false);
         | 
| 23 | 
            +
              const [url, setUrl] = useState<string>("");
         | 
| 24 | 
            +
              const [isLoading, setIsLoading] = useState(false);
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              const checkIfUrlIsValid = (url: string) => {
         | 
| 27 | 
            +
                // should match a hugging face spaces URL like: https://huggingface.co/spaces/username/project or https://hf.co/spaces/username/project
         | 
| 28 | 
            +
                const urlPattern = new RegExp(
         | 
| 29 | 
            +
                  /^(https?:\/\/)?(huggingface\.co|hf\.co)\/spaces\/([\w-]+)\/([\w-]+)$/,
         | 
| 30 | 
            +
                  "i"
         | 
| 31 | 
            +
                );
         | 
| 32 | 
            +
                return urlPattern.test(url);
         | 
| 33 | 
            +
              };
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              const handleClick = async () => {
         | 
| 36 | 
            +
                if (isLoading) return; // Prevent multiple clicks while loading
         | 
| 37 | 
            +
                if (!url) {
         | 
| 38 | 
            +
                  toast.error("Please enter a URL.");
         | 
| 39 | 
            +
                  return;
         | 
| 40 | 
            +
                }
         | 
| 41 | 
            +
                if (!checkIfUrlIsValid(url)) {
         | 
| 42 | 
            +
                  toast.error("Please enter a valid Hugging Face Spaces URL.");
         | 
| 43 | 
            +
                  return;
         | 
| 44 | 
            +
                }
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                const [username, namespace] = url
         | 
| 47 | 
            +
                  .replace("https://huggingface.co/spaces/", "")
         | 
| 48 | 
            +
                  .replace("https://hf.co/spaces/", "")
         | 
| 49 | 
            +
                  .split("/");
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                setIsLoading(true);
         | 
| 52 | 
            +
                try {
         | 
| 53 | 
            +
                  const response = await api.post(`/me/projects/${username}/${namespace}`);
         | 
| 54 | 
            +
                  console.log("response", response);
         | 
| 55 | 
            +
                  toast.success("Project imported successfully!");
         | 
| 56 | 
            +
                  setOpen(false);
         | 
| 57 | 
            +
                  setUrl("");
         | 
| 58 | 
            +
                  addProject(response.data.project);
         | 
| 59 | 
            +
                  // eslint-disable-next-line @typescript-eslint/no-explicit-any
         | 
| 60 | 
            +
                } catch (error: any) {
         | 
| 61 | 
            +
                  toast.error(
         | 
| 62 | 
            +
                    error?.response?.data?.error ?? "Failed to import the project."
         | 
| 63 | 
            +
                  );
         | 
| 64 | 
            +
                } finally {
         | 
| 65 | 
            +
                  setIsLoading(false);
         | 
| 66 | 
            +
                }
         | 
| 67 | 
            +
              };
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              return (
         | 
| 70 | 
            +
                <Dialog open={open} onOpenChange={setOpen}>
         | 
| 71 | 
            +
                  <DialogTrigger asChild>
         | 
| 72 | 
            +
                    <Button variant="outline">
         | 
| 73 | 
            +
                      <Import className="size-4 mr-1.5" />
         | 
| 74 | 
            +
                      Load existing Project
         | 
| 75 | 
            +
                    </Button>
         | 
| 76 | 
            +
                  </DialogTrigger>
         | 
| 77 | 
            +
                  <DialogContent className="sm:max-w-md !p-0 !rounded-3xl !bg-white !border-neutral-100 overflow-hidden text-center">
         | 
| 78 | 
            +
                    <DialogTitle className="hidden" />
         | 
| 79 | 
            +
                    <header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
         | 
| 80 | 
            +
                      <div className="flex items-center justify-center -space-x-4 mb-3">
         | 
| 81 | 
            +
                        <div className="size-11 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-2xl opacity-50">
         | 
| 82 | 
            +
                          🎨
         | 
| 83 | 
            +
                        </div>
         | 
| 84 | 
            +
                        <div className="size-13 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-3xl z-2">
         | 
| 85 | 
            +
                          🥳
         | 
| 86 | 
            +
                        </div>
         | 
| 87 | 
            +
                        <div className="size-11 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-2xl opacity-50">
         | 
| 88 | 
            +
                          💎
         | 
| 89 | 
            +
                        </div>
         | 
| 90 | 
            +
                      </div>
         | 
| 91 | 
            +
                      <p className="text-2xl font-semibold text-neutral-950">
         | 
| 92 | 
            +
                        Import a Project
         | 
| 93 | 
            +
                      </p>
         | 
| 94 | 
            +
                      <p className="text-base text-neutral-500 mt-1.5">
         | 
| 95 | 
            +
                        Enter the URL of your Hugging Face Space to import an existing
         | 
| 96 | 
            +
                        project.
         | 
| 97 | 
            +
                      </p>
         | 
| 98 | 
            +
                    </header>
         | 
| 99 | 
            +
                    <main className="space-y-4 px-9 pb-9 pt-2">
         | 
| 100 | 
            +
                      <div>
         | 
| 101 | 
            +
                        <p className="text-sm text-neutral-700 mb-2">
         | 
| 102 | 
            +
                          Enter your Hugging Face Space
         | 
| 103 | 
            +
                        </p>
         | 
| 104 | 
            +
                        <Input
         | 
| 105 | 
            +
                          type="text"
         | 
| 106 | 
            +
                          placeholder="https://huggingface.com/spaces/username/project"
         | 
| 107 | 
            +
                          value={url}
         | 
| 108 | 
            +
                          onChange={(e) => setUrl(e.target.value)}
         | 
| 109 | 
            +
                          onBlur={(e) => {
         | 
| 110 | 
            +
                            const inputUrl = e.target.value.trim();
         | 
| 111 | 
            +
                            if (!inputUrl) {
         | 
| 112 | 
            +
                              setUrl("");
         | 
| 113 | 
            +
                              return;
         | 
| 114 | 
            +
                            }
         | 
| 115 | 
            +
                            if (!checkIfUrlIsValid(inputUrl)) {
         | 
| 116 | 
            +
                              toast.error("Please enter a valid URL.");
         | 
| 117 | 
            +
                              return;
         | 
| 118 | 
            +
                            }
         | 
| 119 | 
            +
                            setUrl(inputUrl);
         | 
| 120 | 
            +
                          }}
         | 
| 121 | 
            +
                          className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
         | 
| 122 | 
            +
                        />
         | 
| 123 | 
            +
                      </div>
         | 
| 124 | 
            +
                      <div>
         | 
| 125 | 
            +
                        <p className="text-sm text-neutral-700 mb-2">
         | 
| 126 | 
            +
                          Then, let's import it!
         | 
| 127 | 
            +
                        </p>
         | 
| 128 | 
            +
                        <Button
         | 
| 129 | 
            +
                          variant="black"
         | 
| 130 | 
            +
                          onClick={handleClick}
         | 
| 131 | 
            +
                          className="relative w-full"
         | 
| 132 | 
            +
                        >
         | 
| 133 | 
            +
                          {isLoading ? (
         | 
| 134 | 
            +
                            <>
         | 
| 135 | 
            +
                              <Loading
         | 
| 136 | 
            +
                                overlay={false}
         | 
| 137 | 
            +
                                className="ml-2 size-4 animate-spin"
         | 
| 138 | 
            +
                              />
         | 
| 139 | 
            +
                              Fetching your Space...
         | 
| 140 | 
            +
                            </>
         | 
| 141 | 
            +
                          ) : (
         | 
| 142 | 
            +
                            <>Import your Space</>
         | 
| 143 | 
            +
                          )}
         | 
| 144 | 
            +
                        </Button>
         | 
| 145 | 
            +
                      </div>
         | 
| 146 | 
            +
                    </main>
         | 
| 147 | 
            +
                  </DialogContent>
         | 
| 148 | 
            +
                </Dialog>
         | 
| 149 | 
            +
              );
         | 
| 150 | 
            +
            };
         | 
