diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..6c6400984ecfa46d366a5308b2264b6fa129be97 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +OAUTH_CLIENT_ID= +OAUTH_CLIENT_SECRET= +APP_PORT=5173 +REDIRECT_URI=http://localhost:5173/auth/login +DEFAULT_HF_TOKEN= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ef6a520780202a1d6addd833d800ccb1ecac0bb..38d4117ba770f7518a4bc651b80446a4f2f218d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,41 +1,26 @@ -# 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 +# Logs +logs +*.log 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 +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.env +.aider* diff --git a/Dockerfile b/Dockerfile index cbe0188aaee92186937765d2c85d76f7b212c537..8003b5cb2da5b411b794263c7d70bae1f6866ae0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ -FROM node:20-alpine +# Dockerfile +# Use an official Node.js runtime as the base image +FROM node:22.1.0 USER root +RUN apt-get update USER 1000 WORKDIR /usr/src/app # Copy package.json and package-lock.json to the container @@ -13,7 +16,7 @@ RUN npm install RUN npm run build # Expose the application port (assuming your app runs on port 3000) -EXPOSE 3000 +EXPOSE 5173 # Start the application CMD ["npm", "start"] \ No newline at end of file diff --git a/README.md b/README.md index 5ab2231fc7dc96070548f1d03ab1d0f73a799600..5d5239cffd688fecefc35757a3f74ff155a1b47b 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,13 @@ --- -title: DeepSite v2 +title: DeepSite emoji: 🐳 colorFrom: blue colorTo: blue sdk: docker pinned: true -app_port: 3000 +app_port: 5173 license: mit -short_description: Generate any application with DeepSeek -models: - - deepseek-ai/DeepSeek-V3-0324 - - deepseek-ai/DeepSeek-R1-0528 +short_description: Imagine and Share in 1-Click --- -# 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) +Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference diff --git a/app/(public)/layout.tsx b/app/(public)/layout.tsx deleted file mode 100644 index 4a4ec57d2609c783602beb6c06c8dca6a1e6192d..0000000000000000000000000000000000000000 --- a/app/(public)/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import Navigation from "@/components/public/navigation"; - -export default async function PublicLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( -
-
- - {children} -
- ); -} diff --git a/app/(public)/page.tsx b/app/(public)/page.tsx deleted file mode 100644 index c0849e72cf29027524ec9ebc3818e80a8aee5ef3..0000000000000000000000000000000000000000 --- a/app/(public)/page.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { AskAi } from "@/components/space/ask-ai"; -import { redirect } from "next/navigation"; -export default function Home() { - redirect("/projects/new"); - return ( - <> -
-
- ✨ DeepSite Public Beta -
-

- Code your website with AI in seconds -

-

- Vibe Coding has never been so easy. -

-
- -
-
-
-
-
-
-
-
-
-

- Community Driven -

-
-
-

- Deploy your website in seconds -

-
-
-

- Features that make you smile -

-
- - ); -} diff --git a/app/(public)/projects/page.tsx b/app/(public)/projects/page.tsx deleted file mode 100644 index 8bf6fc850473236a51ce3366e1820e00882aa454..0000000000000000000000000000000000000000 --- a/app/(public)/projects/page.tsx +++ /dev/null @@ -1,33 +0,0 @@ -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 { MyProjects } from "@/components/my-projects"; - -async function getMyProjects() { - const cookieStore = await cookies(); - const token = cookieStore.get(MY_TOKEN_KEY())?.value; - if (!token) return { redirectUrl: true, projects: [] }; - try { - const { data } = await apiServer.get("/me/projects", { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - return { - projects: data.projects, - }; - } catch { - return { projects: [] }; - } -} -export default async function ProjectsPage() { - const { redirectUrl, projects } = await getMyProjects(); - if (redirectUrl) { - redirect("/"); - } - - return ; -} diff --git a/app/actions/auth.ts b/app/actions/auth.ts deleted file mode 100644 index a914cf71e5f41b268a93d7f20b654079bb3bd75e..0000000000000000000000000000000000000000 --- a/app/actions/auth.ts +++ /dev/null @@ -1,15 +0,0 @@ -"use server"; - -import { headers } from "next/headers"; - -export async function getAuth() { - const authList = await headers(); - const host = authList.get("host") ?? "localhost:3000"; - const redirect_uri = - `${host.includes("localhost") ? "http://" : "https://"}` + - host + - "/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/app/api/ask-ai/route.ts b/app/api/ask-ai/route.ts deleted file mode 100644 index 0cfd357f17e169a95e04a26f3d60bb48cf5a9583..0000000000000000000000000000000000000000 --- a/app/api/ask-ai/route.ts +++ /dev/null @@ -1,411 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; -import { headers } from "next/headers"; -import { InferenceClient } from "@huggingface/inference"; - -import { MODELS, PROVIDERS } from "@/lib/providers"; -import { - DIVIDER, - FOLLOW_UP_SYSTEM_PROMPT, - INITIAL_SYSTEM_PROMPT, - MAX_REQUESTS_PER_IP, - REPLACE_END, - SEARCH_START, -} from "@/lib/prompts"; -import MY_TOKEN_KEY from "@/lib/get-cookie-name"; - -const ipAddresses = new Map(); - -export async function POST(request: NextRequest) { - const authHeaders = await headers(); - const userToken = request.cookies.get(MY_TOKEN_KEY())?.value; - - const body = await request.json(); - const { prompt, provider, model, redesignMarkdown, html } = body; - - if (!model || (!prompt && !redesignMarkdown)) { - return NextResponse.json( - { ok: false, error: "Missing required fields" }, - { status: 400 } - ); - } - - const selectedModel = MODELS.find( - (m) => m.value === model || m.label === model - ); - if (!selectedModel) { - return NextResponse.json( - { ok: false, error: "Invalid model selected" }, - { status: 400 } - ); - } - - if (!selectedModel.providers.includes(provider) && provider !== "auto") { - return NextResponse.json( - { - ok: false, - error: `The selected model does not support the ${provider} provider.`, - openSelectProvider: true, - }, - { status: 400 } - ); - } - - let token = userToken; - let billTo: string | null = null; - - /** - * Handle local usage token, this bypass the need for a user token - * and allows local testing without authentication. - * This is useful for development and testing purposes. - */ - if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) { - token = process.env.HF_TOKEN; - } - - const ip = authHeaders.get("x-forwarded-for")?.includes(",") - ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim() - : authHeaders.get("x-forwarded-for"); - - if (!token) { - ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1); - if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) { - return NextResponse.json( - { - ok: false, - openLogin: true, - message: "Log In to continue using the service", - }, - { status: 429 } - ); - } - - token = process.env.DEFAULT_HF_TOKEN as string; - billTo = "huggingface"; - } - - const DEFAULT_PROVIDER = PROVIDERS.novita; - const selectedProvider = - provider === "auto" - ? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS] - : PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER; - - try { - // Create a stream response - const encoder = new TextEncoder(); - const stream = new TransformStream(); - const writer = stream.writable.getWriter(); - - // Start the response - const response = new NextResponse(stream.readable, { - headers: { - "Content-Type": "text/plain; charset=utf-8", - "Cache-Control": "no-cache", - Connection: "keep-alive", - }, - }); - - (async () => { - let completeResponse = ""; - try { - const client = new InferenceClient(token); - const chatCompletion = client.chatCompletionStream( - { - model: selectedModel.value, - provider: selectedProvider.id as any, - messages: [ - { - role: "system", - content: INITIAL_SYSTEM_PROMPT, - }, - { - role: "user", - content: redesignMarkdown - ? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.` - : html - ? `Here is my current HTML code:\n\n\`\`\`html\n${html}\n\`\`\`\n\nNow, please create a new design based on this HTML.` - : prompt, - }, - ], - max_tokens: selectedProvider.max_tokens, - }, - billTo ? { billTo } : {} - ); - - while (true) { - const { done, value } = await chatCompletion.next(); - if (done) { - break; - } - - const chunk = value.choices[0]?.delta?.content; - if (chunk) { - let newChunk = chunk; - if (!selectedModel?.isThinker) { - if (provider !== "sambanova") { - await writer.write(encoder.encode(chunk)); - completeResponse += chunk; - - if (completeResponse.includes("")) { - break; - } - } else { - if (chunk.includes("")) { - newChunk = newChunk.replace(/<\/html>[\s\S]*/, ""); - } - completeResponse += newChunk; - await writer.write(encoder.encode(newChunk)); - if (newChunk.includes("")) { - break; - } - } - } else { - const lastThinkTagIndex = - completeResponse.lastIndexOf(""); - completeResponse += newChunk; - await writer.write(encoder.encode(newChunk)); - if (lastThinkTagIndex !== -1) { - const afterLastThinkTag = completeResponse.slice( - lastThinkTagIndex + "".length - ); - if (afterLastThinkTag.includes("")) { - break; - } - } - } - } - } - } catch (error: any) { - if (error.message?.includes("exceeded your monthly included credits")) { - await writer.write( - encoder.encode( - JSON.stringify({ - ok: false, - openProModal: true, - message: error.message, - }) - ) - ); - } else { - await writer.write( - encoder.encode( - JSON.stringify({ - ok: false, - openSelectProvider: true, - message: - error.message || - "An error occurred while processing your request.", - }) - ) - ); - } - } finally { - await writer?.close(); - } - })(); - - return response; - } catch (error: any) { - return NextResponse.json( - { - ok: false, - openSelectProvider: true, - message: - error?.message || "An error occurred while processing your request.", - }, - { status: 500 } - ); - } -} - -export async function PUT(request: NextRequest) { - const authHeaders = await headers(); - const userToken = request.cookies.get(MY_TOKEN_KEY())?.value; - - const body = await request.json(); - const { prompt, html, previousPrompt, provider, selectedElementHtml } = body; - - if (!prompt || !html) { - return NextResponse.json( - { ok: false, error: "Missing required fields" }, - { status: 400 } - ); - } - - const selectedModel = MODELS[0]; - - let token = userToken; - let billTo: string | null = null; - - /** - * Handle local usage token, this bypass the need for a user token - * and allows local testing without authentication. - * This is useful for development and testing purposes. - */ - if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) { - token = process.env.HF_TOKEN; - } - - const ip = authHeaders.get("x-forwarded-for")?.includes(",") - ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim() - : authHeaders.get("x-forwarded-for"); - - if (!token) { - ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1); - if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) { - return NextResponse.json( - { - ok: false, - openLogin: true, - message: "Log In to continue using the service", - }, - { status: 429 } - ); - } - - token = process.env.DEFAULT_HF_TOKEN as string; - billTo = "huggingface"; - } - - const client = new InferenceClient(token); - - const DEFAULT_PROVIDER = PROVIDERS.novita; - const selectedProvider = - provider === "auto" - ? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS] - : PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER; - - try { - const response = await client.chatCompletion( - { - model: selectedModel.value, - provider: selectedProvider.id as any, - messages: [ - { - role: "system", - content: FOLLOW_UP_SYSTEM_PROMPT, - }, - { - role: "user", - content: previousPrompt - ? previousPrompt - : "You are modifying the HTML file based on the user's request.", - }, - { - role: "assistant", - - content: `The current code is: \n\`\`\`html\n${html}\n\`\`\` ${ - selectedElementHtml - ? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\`` - : "" - }`, - }, - { - role: "user", - content: prompt, - }, - ], - ...(selectedProvider.id !== "sambanova" - ? { - max_tokens: selectedProvider.max_tokens, - } - : {}), - }, - billTo ? { billTo } : {} - ); - - const chunk = response.choices[0]?.message?.content; - if (!chunk) { - return NextResponse.json( - { ok: false, message: "No content returned from the model" }, - { status: 400 } - ); - } - - if (chunk) { - const updatedLines: number[][] = []; - let newHtml = html; - let position = 0; - let moreBlocks = true; - - while (moreBlocks) { - const searchStartIndex = chunk.indexOf(SEARCH_START, position); - if (searchStartIndex === -1) { - moreBlocks = false; - continue; - } - - const dividerIndex = chunk.indexOf(DIVIDER, searchStartIndex); - if (dividerIndex === -1) { - moreBlocks = false; - continue; - } - - const replaceEndIndex = chunk.indexOf(REPLACE_END, dividerIndex); - if (replaceEndIndex === -1) { - moreBlocks = false; - continue; - } - - const searchBlock = chunk.substring( - searchStartIndex + SEARCH_START.length, - dividerIndex - ); - const replaceBlock = chunk.substring( - dividerIndex + DIVIDER.length, - replaceEndIndex - ); - - if (searchBlock.trim() === "") { - newHtml = `${replaceBlock}\n${newHtml}`; - updatedLines.push([1, replaceBlock.split("\n").length]); - } else { - const blockPosition = newHtml.indexOf(searchBlock); - if (blockPosition !== -1) { - const beforeText = newHtml.substring(0, blockPosition); - const startLineNumber = beforeText.split("\n").length; - const replaceLines = replaceBlock.split("\n").length; - const endLineNumber = startLineNumber + replaceLines - 1; - - updatedLines.push([startLineNumber, endLineNumber]); - newHtml = newHtml.replace(searchBlock, replaceBlock); - } - } - - position = replaceEndIndex + REPLACE_END.length; - } - - return NextResponse.json({ - ok: true, - html: newHtml, - updatedLines, - }); - } else { - return NextResponse.json( - { ok: false, message: "No content returned from the model" }, - { status: 400 } - ); - } - } catch (error: any) { - if (error.message?.includes("exceeded your monthly included credits")) { - return NextResponse.json( - { - ok: false, - openProModal: true, - message: error.message, - }, - { status: 402 } - ); - } - return NextResponse.json( - { - ok: false, - openSelectProvider: true, - message: - error.message || "An error occurred while processing your request.", - }, - { status: 500 } - ); - } -} diff --git a/app/api/auth/route.ts b/app/api/auth/route.ts deleted file mode 100644 index ecb3f79b8ed2dbf692edab51b79a3e7eff1163f3..0000000000000000000000000000000000000000 --- a/app/api/auth/route.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -export async function POST(req: NextRequest) { - const body = await req.json(); - const { code } = body; - - if (!code) { - return NextResponse.json( - { error: "Code is required" }, - { - status: 400, - headers: { - "Content-Type": "application/json", - }, - } - ); - } - - const Authorization = `Basic ${Buffer.from( - `${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}` - ).toString("base64")}`; - - const host = - req.headers.get("host") ?? req.headers.get("origin") ?? "localhost:3000"; - const redirect_uri = - `${host.includes("localhost") ? "http://" : "https://"}` + - host + - "/auth/callback"; - const request_auth = await fetch("https://huggingface.co/oauth/token", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Authorization, - }, - body: new URLSearchParams({ - grant_type: "authorization_code", - code, - redirect_uri, - }), - }); - - const response = await request_auth.json(); - if (!response.access_token) { - return NextResponse.json( - { error: "Failed to retrieve access token" }, - { - status: 400, - headers: { - "Content-Type": "application/json", - }, - } - ); - } - - const userResponse = await fetch("https://huggingface.co/api/whoami-v2", { - headers: { - Authorization: `Bearer ${response.access_token}`, - }, - }); - - if (!userResponse.ok) { - return NextResponse.json( - { user: null, errCode: userResponse.status }, - { status: userResponse.status } - ); - } - const user = await userResponse.json(); - - return NextResponse.json( - { - access_token: response.access_token, - expires_in: response.expires_in, - user, - }, - { - status: 200, - headers: { - "Content-Type": "application/json", - }, - } - ); -} diff --git a/app/api/me/projects/[namespace]/[repoId]/route.ts b/app/api/me/projects/[namespace]/[repoId]/route.ts deleted file mode 100644 index c9785ee45810f2f3c862bf22c6fea49627d41411..0000000000000000000000000000000000000000 --- a/app/api/me/projects/[namespace]/[repoId]/route.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { RepoDesignation, spaceInfo, uploadFile } from "@huggingface/hub"; - -import { isAuthenticated } from "@/lib/auth"; -import Project from "@/models/Project"; -import dbConnect from "@/lib/mongodb"; -import { getPTag } from "@/lib/utils"; - -export async function GET( - req: NextRequest, - { params }: { params: Promise<{ namespace: string; repoId: string }> } -) { - const user = await isAuthenticated(); - - if (user instanceof NextResponse || !user) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - await dbConnect(); - const param = await params; - const { namespace, repoId } = param; - - const project = await Project.findOne({ - user_id: user.id, - space_id: `${namespace}/${repoId}`, - }).lean(); - if (!project) { - return NextResponse.json( - { - ok: false, - error: "Project not found", - }, - { status: 404 } - ); - } - const space_url = `https://huggingface.co/spaces/${namespace}/${repoId}/raw/main/index.html`; - try { - const space = await spaceInfo({ - name: namespace + "/" + repoId, - accessToken: user.token as string, - additionalFields: ["author"], - }); - - if (!space || space.sdk !== "static") { - return NextResponse.json( - { - ok: false, - error: "Space is not a static space", - }, - { status: 404 } - ); - } - if (space.author !== user.name) { - return NextResponse.json( - { - ok: false, - error: "Space does not belong to the authenticated user", - }, - { status: 403 } - ); - } - - const response = await fetch(space_url); - if (!response.ok) { - return NextResponse.json( - { - ok: false, - error: "Failed to fetch space HTML", - }, - { status: 404 } - ); - } - let html = await response.text(); - // remove the last p tag including this url https://enzostvs-deepsite.hf.space - html = html.replace(getPTag(namespace + "/" + repoId), ""); - - return NextResponse.json( - { - project: { - ...project, - html, - }, - ok: true, - }, - { status: 200 } - ); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - if (error.statusCode === 404) { - await Project.deleteOne({ - user_id: user.id, - space_id: `${namespace}/${repoId}`, - }); - return NextResponse.json( - { error: "Space not found", ok: false }, - { status: 404 } - ); - } - return NextResponse.json( - { error: error.message, ok: false }, - { status: 500 } - ); - } -} - -export async function PUT( - req: NextRequest, - { params }: { params: Promise<{ namespace: string; repoId: string }> } -) { - const user = await isAuthenticated(); - - if (user instanceof NextResponse || !user) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - await dbConnect(); - const param = await params; - const { namespace, repoId } = param; - const { html, prompts } = await req.json(); - - const project = await Project.findOne({ - user_id: user.id, - space_id: `${namespace}/${repoId}`, - }).lean(); - if (!project) { - return NextResponse.json( - { - ok: false, - error: "Project not found", - }, - { status: 404 } - ); - } - - const repo: RepoDesignation = { - type: "space", - name: `${namespace}/${repoId}`, - }; - - const newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}`); - const file = new File([newHtml], "index.html", { type: "text/html" }); - await uploadFile({ - repo, - file, - accessToken: user.token as string, - commitTitle: `${prompts[prompts.length - 1]} - Follow Up Deployment`, - }); - - await Project.updateOne( - { user_id: user.id, space_id: `${namespace}/${repoId}` }, - { - $set: { - prompts: [ - ...(project && "prompts" in project ? project.prompts : []), - ...prompts, - ], - }, - } - ); - return NextResponse.json({ ok: true }, { status: 200 }); -} - -export async function POST( - req: NextRequest, - { params }: { params: Promise<{ namespace: string; repoId: string }> } -) { - const user = await isAuthenticated(); - - if (user instanceof NextResponse || !user) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - await dbConnect(); - const param = await params; - const { namespace, repoId } = param; - - const space = await spaceInfo({ - name: namespace + "/" + repoId, - accessToken: user.token as string, - additionalFields: ["author"], - }); - - if (!space || space.sdk !== "static") { - return NextResponse.json( - { - ok: false, - error: "Space is not a static space", - }, - { status: 404 } - ); - } - if (space.author !== user.name) { - return NextResponse.json( - { - ok: false, - error: "Space does not belong to the authenticated user", - }, - { status: 403 } - ); - } - - const project = await Project.findOne({ - user_id: user.id, - space_id: `${namespace}/${repoId}`, - }).lean(); - if (project) { - return NextResponse.json( - { - ok: false, - error: "Project already exists", - }, - { status: 400 } - ); - } - - const newProject = new Project({ - user_id: user.id, - space_id: `${namespace}/${repoId}`, - prompts: [], - }); - - await newProject.save(); - return NextResponse.json( - { - ok: true, - project: { - id: newProject._id, - space_id: newProject.space_id, - prompts: newProject.prompts, - }, - }, - { status: 201 } - ); -} diff --git a/app/api/me/projects/route.ts b/app/api/me/projects/route.ts deleted file mode 100644 index 9899bc2533a40a28b9e35cfe123b9868c318e19d..0000000000000000000000000000000000000000 --- a/app/api/me/projects/route.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub"; - -import { isAuthenticated } from "@/lib/auth"; -import Project from "@/models/Project"; -import dbConnect from "@/lib/mongodb"; -import { COLORS, getPTag } from "@/lib/utils"; -// import type user -export async function GET() { - const user = await isAuthenticated(); - - if (user instanceof NextResponse || !user) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - await dbConnect(); - - const projects = await Project.find({ - user_id: user?.id, - }) - .sort({ _createdAt: -1 }) - .limit(100) - .lean(); - if (!projects) { - return NextResponse.json( - { - ok: false, - projects: [], - }, - { status: 404 } - ); - } - return NextResponse.json( - { - ok: true, - projects, - }, - { status: 200 } - ); -} - -/** - * This API route creates a new project in Hugging Face Spaces. - * It requires an Authorization header with a valid token and a JSON body with the project details. - */ -export async function POST(request: NextRequest) { - const user = await isAuthenticated(); - - if (user instanceof NextResponse || !user) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - const { title, html, prompts } = await request.json(); - - if (!title || !html) { - return NextResponse.json( - { message: "Title and HTML content are required.", ok: false }, - { status: 400 } - ); - } - - await dbConnect(); - - try { - let readme = ""; - let newHtml = html; - - const newTitle = title - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .split("-") - .filter(Boolean) - .join("-") - .slice(0, 96); - - const repo: RepoDesignation = { - type: "space", - name: `${user.name}/${newTitle}`, - }; - - const { repoUrl } = await createRepo({ - repo, - accessToken: user.token as string, - }); - const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)]; - const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)]; - readme = `--- -title: ${newTitle} -emoji: 🐳 -colorFrom: ${colorFrom} -colorTo: ${colorTo} -sdk: static -pinned: false -tags: - - deepsite ---- - -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`; - - newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}`); - const file = new File([newHtml], "index.html", { type: "text/html" }); - const readmeFile = new File([readme], "README.md", { - type: "text/markdown", - }); - const files = [file, readmeFile]; - await uploadFiles({ - repo, - files, - accessToken: user.token as string, - commitTitle: `${prompts[prompts.length - 1]} - Initial Deployment`, - }); - const path = repoUrl.split("/").slice(-2).join("/"); - const project = await Project.create({ - user_id: user.id, - space_id: path, - prompts, - }); - return NextResponse.json({ project, path, ok: true }, { status: 201 }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (err: any) { - return NextResponse.json( - { error: err.message, ok: false }, - { status: 500 } - ); - } -} diff --git a/app/api/me/route.ts b/app/api/me/route.ts deleted file mode 100644 index c4164daba5c58bb2fe7f4f7508de7165f32ca443..0000000000000000000000000000000000000000 --- a/app/api/me/route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { headers } from "next/headers"; -import { NextResponse } from "next/server"; - -export async function GET() { - const authHeaders = await headers(); - const token = authHeaders.get("Authorization"); - if (!token) { - return NextResponse.json({ user: null, errCode: 401 }, { status: 401 }); - } - - const userResponse = await fetch("https://huggingface.co/api/whoami-v2", { - headers: { - Authorization: `${token}`, - }, - }); - - if (!userResponse.ok) { - return NextResponse.json( - { user: null, errCode: userResponse.status }, - { status: userResponse.status } - ); - } - const user = await userResponse.json(); - return NextResponse.json({ user, errCode: null }, { status: 200 }); -} diff --git a/app/api/re-design/route.ts b/app/api/re-design/route.ts deleted file mode 100644 index 777c2cbd18f95592080c0fe1ad2cab23a5264397..0000000000000000000000000000000000000000 --- a/app/api/re-design/route.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -export async function PUT(request: NextRequest) { - const body = await request.json(); - const { url } = body; - - if (!url) { - return NextResponse.json({ error: "URL is required" }, { status: 400 }); - } - - try { - const response = await fetch( - `https://r.jina.ai/${encodeURIComponent(url)}`, - { - method: "POST", - } - ); - if (!response.ok) { - return NextResponse.json( - { error: "Failed to fetch redesign" }, - { status: 500 } - ); - } - const markdown = await response.text(); - return NextResponse.json( - { - ok: true, - markdown, - }, - { status: 200 } - ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - return NextResponse.json( - { error: error.message || "An error occurred" }, - { status: 500 } - ); - } -} diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx deleted file mode 100644 index ac44f365b707908d6003a4266db1269d297a4336..0000000000000000000000000000000000000000 --- a/app/auth/callback/page.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"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 ( -
-
-
-
-
- 🚀 -
-
- 👋 -
-
- 🙌 -
-
-

- Login In Progress... -

-

- Wait a moment while we log you in with your code. -

-
-
-
-

- If you are not redirected automatically in the next 5 seconds, - please click the button below -

- {showButton ? ( - - - - ) : ( -

- Please wait, we are logging you in... -

- )} -
-
-
-
- ); -} diff --git a/app/auth/page.tsx b/app/auth/page.tsx deleted file mode 100644 index a45a6bc6f58907b4ee5efbf0f70a51ee153625c7..0000000000000000000000000000000000000000 --- a/app/auth/page.tsx +++ /dev/null @@ -1,28 +0,0 @@ -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/app/favicon.ico b/app/favicon.ico deleted file mode 100644 index 718d6fea4835ec2d246af9800eddb7ffb276240c..0000000000000000000000000000000000000000 Binary files a/app/favicon.ico and /dev/null differ diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index 0a781bd27eff044053c9c367c359252e439103cc..0000000000000000000000000000000000000000 --- a/app/layout.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* 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"; - -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 }; - } -} - -export default async function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - const data = await getMe(); - return ( - - - - - {children} - - - - ); -} diff --git a/app/projects/[namespace]/[repoId]/page.tsx b/app/projects/[namespace]/[repoId]/page.tsx deleted file mode 100644 index aa7e27c07454d6f38be25e72782634b74e0411d7..0000000000000000000000000000000000000000 --- a/app/projects/[namespace]/[repoId]/page.tsx +++ /dev/null @@ -1,40 +0,0 @@ -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 project = await getProject(namespace, repoId); - if (!project?.html) { - redirect("/projects"); - } - return ; -} diff --git a/app/projects/new/page.tsx b/app/projects/new/page.tsx deleted file mode 100644 index e47a4375504e549ef3e3f167df8e6f25976a3d35..0000000000000000000000000000000000000000 --- a/app/projects/new/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { AppEditor } from "@/components/editor"; - -export default function ProjectsNewPage() { - return ; -} diff --git a/assets/globals.css b/assets/globals.css deleted file mode 100644 index 19dd59e7dcc34e453e9850a052ae8f039628e58a..0000000000000000000000000000000000000000 --- a/assets/globals.css +++ /dev/null @@ -1,146 +0,0 @@ -@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/assets/logo.svg b/assets/logo.svg deleted file mode 100644 index e69f057d4d4c256f02881888e781aa0943010c3e..0000000000000000000000000000000000000000 --- a/assets/logo.svg +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/components.json b/components.json deleted file mode 100644 index 335484f9424bf72b98e3b892275740bc8f014754..0000000000000000000000000000000000000000 --- a/components.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": true, - "tsx": true, - "tailwind": { - "config": "", - "css": "app/globals.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - }, - "iconLibrary": "lucide" -} \ No newline at end of file diff --git a/components/contexts/app-context.tsx b/components/contexts/app-context.tsx deleted file mode 100644 index a97820d9e26fa6197ef583ce88aba66a3bc10082..0000000000000000000000000000000000000000 --- a/components/contexts/app-context.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* 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/components/contexts/user-context.tsx b/components/contexts/user-context.tsx deleted file mode 100644 index 8a3391744618bfcfc979401cdee76051c70fee8f..0000000000000000000000000000000000000000 --- a/components/contexts/user-context.tsx +++ /dev/null @@ -1,8 +0,0 @@ -"use client"; - -import { createContext } from "react"; -import { User } from "@/types"; - -export const UserContext = createContext({ - user: undefined as User | undefined, -}); diff --git a/components/editor/ask-ai/follow-up-tooltip.tsx b/components/editor/ask-ai/follow-up-tooltip.tsx deleted file mode 100644 index 5ebb4a29de5de5cca175eb6795f1d069be6ba02b..0000000000000000000000000000000000000000 --- a/components/editor/ask-ai/follow-up-tooltip.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Info } from "lucide-react"; - -export const FollowUpTooltip = () => { - return ( - - - - - -
-

- ⚡ Faster, Smarter Updates -

-
-
-

- 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/components/editor/ask-ai/index.tsx b/components/editor/ask-ai/index.tsx deleted file mode 100644 index 3321c70971e0ce035d556d1d79003e2f6e162544..0000000000000000000000000000000000000000 --- a/components/editor/ask-ai/index.tsx +++ /dev/null @@ -1,474 +0,0 @@ -"use client"; -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { useState, useRef, useMemo } 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 } 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"; - -export function AskAI({ - html, - setHtml, - onScrollToBottom, - isAiWorking, - setisAiWorking, - isEditableModeEnabled = false, - selectedElement, - setSelectedElement, - setIsEditableModeEnabled, - onNewPrompt, - onSuccess, -}: { - html: string; - setHtml: (html: string) => void; - onScrollToBottom: () => void; - isAiWorking: boolean; - onNewPrompt: (prompt: string) => void; - htmlHistory?: HtmlHistory[]; - setisAiWorking: React.Dispatch>; - onSuccess: (h: string, p: string, n?: number[][]) => void; - isEditableModeEnabled: boolean; - setIsEditableModeEnabled: React.Dispatch>; - selectedElement?: HTMLElement | null; - setSelectedElement: React.Dispatch>; -}) { - const refThink = useRef(null); - const audio = useRef(null); - - const [open, setOpen] = useState(false); - const [prompt, setPrompt] = useState(""); - const [hasAsked, setHasAsked] = useState(false); - const [previousPrompt, setPreviousPrompt] = 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 [think, setThink] = useState(undefined); - const [openThink, setOpenThink] = useState(false); - const [isThinking, setIsThinking] = useState(true); - const [controller, setController] = useState(null); - const [isFollowUp, setIsFollowUp] = useState(true); - - const callAi = async (redesignMarkdown?: string) => { - if (isAiWorking) return; - if (!redesignMarkdown && !prompt.trim()) return; - setisAiWorking(true); - setProviderError(""); - setThink(""); - setOpenThink(false); - setIsThinking(true); - - let contentResponse = ""; - let thinkResponse = ""; - let lastRenderTime = 0; - - const abortController = new AbortController(); - setController(abortController); - try { - onNewPrompt(prompt); - if (isFollowUp && !redesignMarkdown && !isSameHtml) { - const selectedElementHtml = selectedElement - ? selectedElement.outerHTML - : ""; - const request = await fetch("/api/ask-ai", { - method: "PUT", - body: JSON.stringify({ - prompt, - provider, - previousPrompt, - model, - html, - selectedElementHtml, - }), - headers: { - "Content-Type": "application/json", - "x-forwarded-for": window.location.hostname, - }, - signal: abortController.signal, - }); - if (request && request.body) { - const res = await request.json(); - if (!request.ok) { - if (res.openLogin) { - setOpen(true); - } else if (res.openSelectProvider) { - setOpenProvider(true); - setProviderError(res.message); - } else if (res.openProModal) { - setOpenProModal(true); - } else { - toast.error(res.message); - } - setisAiWorking(false); - return; - } - setHtml(res.html); - toast.success("AI responded successfully"); - setPreviousPrompt(prompt); - setPrompt(""); - setisAiWorking(false); - onSuccess(res.html, prompt, res.updatedLines); - if (audio.current) audio.current.play(); - } - } else { - const request = await fetch("/api/ask-ai", { - method: "POST", - body: JSON.stringify({ - prompt, - provider, - model, - html: isSameHtml ? "" : html, - redesignMarkdown, - }), - headers: { - "Content-Type": "application/json", - "x-forwarded-for": window.location.hostname, - }, - signal: abortController.signal, - }); - if (request && request.body) { - // if (!request.ok) { - // const res = await request.json(); - // if (res.openLogin) { - // setOpen(true); - // } else if (res.openSelectProvider) { - // setOpenProvider(true); - // setProviderError(res.message); - // } else if (res.openProModal) { - // setOpenProModal(true); - // } else { - // toast.error(res.message); - // } - // setisAiWorking(false); - // return; - // } - const reader = request.body.getReader(); - const decoder = new TextDecoder("utf-8"); - const selectedModel = MODELS.find( - (m: { value: string }) => m.value === model - ); - let contentThink: string | undefined = undefined; - const read = async () => { - const { done, value } = await reader.read(); - if (done) { - toast.success("AI responded successfully"); - setPreviousPrompt(prompt); - setPrompt(""); - setisAiWorking(false); - setHasAsked(true); - setModel(MODELS[0].value); - if (audio.current) audio.current.play(); - - // Now we have the complete HTML including , so set it to be sure - const finalDoc = contentResponse.match( - /[\s\S]*<\/html>/ - )?.[0]; - if (finalDoc) { - setHtml(finalDoc); - } - onSuccess(finalDoc ?? contentResponse, prompt); - - return; - } - - const chunk = decoder.decode(value, { stream: true }); - try { - const res = JSON.parse(chunk); - if (res.openLogin) { - setOpen(true); - } else if (res.openSelectProvider) { - setOpenProvider(true); - setProviderError(res.message); - } else if (res.openProModal) { - setOpenProModal(true); - } else { - toast.error(res.message); - } - setisAiWorking(false); - return; - } catch { - thinkResponse += chunk; - if (selectedModel?.isThinker) { - const thinkMatch = thinkResponse.match(/[\s\S]*/)?.[0]; - if (thinkMatch && !thinkResponse?.includes("")) { - if ((contentThink?.length ?? 0) < 3) { - setOpenThink(true); - } - setThink(thinkMatch.replace("", "").trim()); - contentThink += chunk; - return read(); - } - } - - contentResponse += chunk; - - const newHtml = contentResponse.match( - /[\s\S]*/ - )?.[0]; - if (newHtml) { - setIsThinking(false); - let partialDoc = newHtml; - if ( - partialDoc.includes("") && - !partialDoc.includes("") - ) { - partialDoc += "\n"; - } - if ( - partialDoc.includes("") - ) { - partialDoc += "\n"; - } - if (!partialDoc.includes("")) { - partialDoc += "\n"; - } - - // Throttle the re-renders to avoid flashing/flicker - const now = Date.now(); - if (now - lastRenderTime > 300) { - setHtml(partialDoc); - lastRenderTime = now; - } - - if (partialDoc.length > 200) { - onScrollToBottom(); - } - } - read(); - } - }; - - read(); - } - } - } catch (error: any) { - setisAiWorking(false); - toast.error(error.message); - if (error.openLogin) { - setOpen(true); - } - } - }; - - const stopController = () => { - if (controller) { - controller.abort(); - setController(null); - setisAiWorking(false); - setThink(""); - setOpenThink(false); - setIsThinking(false); - } - }; - - useUpdateEffect(() => { - if (refThink.current) { - refThink.current.scrollTop = refThink.current.scrollHeight; - } - }, [think]); - - useUpdateEffect(() => { - if (!isThinking) { - setOpenThink(false); - } - }, [isThinking]); - - const isSameHtml = useMemo(() => { - return isTheSameHtml(html); - }, [html]); - - return ( -
-
- {think && ( -
-
{ - setOpenThink(!openThink); - }} - > -

- {isThinking ? "DeepSite is thinking..." : "DeepSite's plan"} -

- -
-
-

- {think} -

-
-
- )} - {selectedElement && ( -
- setSelectedElement(null)} - /> -
- )} -
- {isAiWorking && ( -
-
- -

- AI is {isThinking ? "thinking" : "coding"}...{" "} -

-
-
- - Stop generation -
-
- )} - setPrompt(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter" && !e.shiftKey) { - callAi(); - } - }} - /> -
-
-
- callAi(md)} /> - {!isSameHtml && ( - - - - - - Select an element on the page to ask DeepSite edit it - directly. - - - )} - -
-
- - -
-
- setOpen(false)} html={html} /> - setOpenProModal(false)} - /> - {!isSameHtml && ( -
- - -
- )} -
- -
- ); -} diff --git a/components/editor/ask-ai/re-imagine.tsx b/components/editor/ask-ai/re-imagine.tsx deleted file mode 100644 index 7fd5d170e0f417f4f2daf38f9b77629f64cf1e95..0000000000000000000000000000000000000000 --- a/components/editor/ask-ai/re-imagine.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { useState } from "react"; -import { Paintbrush } from "lucide-react"; -import { toast } from "sonner"; - -import { Button } from "@/components/ui/button"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Input } from "@/components/ui/input"; -import Loading from "@/components/loading"; -import { api } from "@/lib/api"; - -export function ReImagine({ - onRedesign, -}: { - onRedesign: (md: string) => void; -}) { - const [url, setUrl] = useState(""); - const [open, setOpen] = useState(false); - const [isLoading, setIsLoading] = useState(false); - - const checkIfUrlIsValid = (url: string) => { - const urlPattern = new RegExp( - /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\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 URL."); - return; - } - setIsLoading(true); - const response = await api.put("/re-design", { - url: url.trim(), - }); - if (response?.data?.ok) { - setOpen(false); - setUrl(""); - onRedesign(response.data.markdown); - toast.success("DeepSite is redesigning your site! Let him cook... 🔥"); - } else { - toast.error(response?.data?.error || "Failed to redesign the site."); - } - setIsLoading(false); - }; - - return ( - -
- - - - -
-
-
- 🎨 -
-
- 🥳 -
-
- 💎 -
-
-

- Redesign your Site! -

-

- Try our new Redesign feature to give your site a fresh look. -

-
-
-
-

- Enter your website URL to get started: -

- 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 redesign it! -

- -
-
-
-
-
- ); -} diff --git a/components/editor/ask-ai/selected-html-element.tsx b/components/editor/ask-ai/selected-html-element.tsx deleted file mode 100644 index a0a8930db4d067ef7adc46aa4917983bfcfd55f5..0000000000000000000000000000000000000000 --- a/components/editor/ask-ai/selected-html-element.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import classNames from "classnames"; -import { Code, XCircle } from "lucide-react"; - -import { Collapsible, CollapsibleTrigger } from "@/components/ui/collapsible"; -import { htmlTagToText } from "@/lib/html-tag-to-text"; - -export const SelectedHtmlElement = ({ - element, - isAiWorking = false, - onDelete, -}: { - element: HTMLElement | null; - isAiWorking: boolean; - onDelete?: () => void; -}) => { - if (!element) return null; - - const tagName = element.tagName.toLowerCase(); - return ( - { - if (!isAiWorking && onDelete) { - onDelete(); - } - }} - > - -
- -
-

- {element.textContent?.trim().split(/\s+/)[0]} {htmlTagToText(tagName)} -

- -
- {/* -
-

- ID: {element.id || "No ID"} -

-

- Classes:{" "} - {element.className || "No classes"} -

-
-
*/} -
- ); -}; diff --git a/components/editor/ask-ai/settings.tsx b/components/editor/ask-ai/settings.tsx deleted file mode 100644 index 9172ce62fb7a9f53fa5f5442e11d7f0302a000c1..0000000000000000000000000000000000000000 --- a/components/editor/ask-ai/settings.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import classNames from "classnames"; -import { PiGearSixFill } from "react-icons/pi"; -import { RiCheckboxCircleFill } from "react-icons/ri"; - -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { PROVIDERS, MODELS } from "@/lib/providers"; -import { Button } from "@/components/ui/button"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { useMemo } from "react"; -import { useUpdateEffect } from "react-use"; -import Image from "next/image"; - -export function Settings({ - open, - onClose, - provider, - model, - error, - isFollowUp = false, - onChange, - onModelChange, -}: { - open: boolean; - provider: string; - model: string; - error?: string; - isFollowUp?: boolean; - onClose: React.Dispatch>; - onChange: (provider: string) => void; - onModelChange: (model: string) => void; -}) { - const modelAvailableProviders = useMemo(() => { - const availableProviders = MODELS.find( - (m: { value: string }) => m.value === model - )?.providers; - if (!availableProviders) return Object.keys(PROVIDERS); - return Object.keys(PROVIDERS).filter((id) => - availableProviders.includes(id) - ); - }, [model]); - - useUpdateEffect(() => { - if (provider !== "auto" && !modelAvailableProviders.includes(provider)) { - onChange("auto"); - } - }, [model, provider]); - - return ( -
- - - - - -
- Customize Settings -
-
- {/* - How to use it locally? - - */} - {error !== "" && ( -

- {error} -

- )} - - {isFollowUp && ( -
- Note: You can't use a Thinker model for follow-up requests. - We automatically switch to the default model for you. -
- )} -
-
-
-

- Use auto-provider -

-

- We'll automatically select the best provider for you - based on your prompt. -

-
-
{ - const foundModel = MODELS.find( - (m: { value: string }) => m.value === model - ); - if (provider === "auto" && foundModel?.autoProvider) { - onChange(foundModel.autoProvider); - } else { - onChange("auto"); - } - }} - > -
-
-
- -
-
-
-
-
- ); -} diff --git a/components/editor/deploy-button/index.tsx b/components/editor/deploy-button/index.tsx deleted file mode 100644 index 170bb5cd028541aa0d6558c8f7b44162c4472226..0000000000000000000000000000000000000000 --- a/components/editor/deploy-button/index.tsx +++ /dev/null @@ -1,171 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { useState } from "react"; -import { toast } from "sonner"; -import Image from "next/image"; -import { useRouter } from "next/navigation"; -import { MdSave } from "react-icons/md"; -import { Rocket } from "lucide-react"; - -import SpaceIcon from "@/assets/space.svg"; -import Loading from "@/components/loading"; -import { Button } from "@/components/ui/button"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Input } from "@/components/ui/input"; -import { api } from "@/lib/api"; -import { LoginModal } from "@/components/login-modal"; -import { useUser } from "@/hooks/useUser"; - -export function DeployButton({ - html, - prompts, -}: { - html: string; - prompts: string[]; -}) { - const router = useRouter(); - const { user } = useUser(); - const [loading, setLoading] = useState(false); - const [open, setOpen] = 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, - html, - prompts, - }); - if (res.data.ok) { - router.push(`/projects/${res.data.path}?deploy=true`); - } else { - toast.error(res?.data?.error || "Failed to create space"); - } - } catch (err: any) { - toast.error(err.response?.data?.error || err.message); - } finally { - setLoading(false); - } - }; - - return ( -
-
- {user?.id ? ( - - -
- - -
-
- -
-
-
- 🚀 -
-
- Space Icon -
-
- 👻 -
-
-

- Deploy as Space! -

-

- Save and Deploy your project to a Space on the Hub. Spaces are - a way to share your project with the world. -

-
-
-
-

- 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 deploy it! -

- -
-
-
-
- ) : ( - <> - - - - )} - setOpen(false)} - html={html} - title="Log In to save your Project" - description="Log In through your Hugging Face account to save your project and increase your monthly free limit." - /> -
-
- ); -} diff --git a/components/editor/footer/index.tsx b/components/editor/footer/index.tsx deleted file mode 100644 index 3ab4085f29efdf01b91c6cfff1a758f359758b9d..0000000000000000000000000000000000000000 --- a/components/editor/footer/index.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import classNames from "classnames"; -import { FaMobileAlt } from "react-icons/fa"; -import { RefreshCcw, SparkleIcon } from "lucide-react"; -import { FaLaptopCode } from "react-icons/fa6"; -import { HtmlHistory } 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"; - -const DEVICES = [ - { - name: "desktop", - icon: FaLaptopCode, - }, - { - name: "mobile", - icon: FaMobileAlt, - }, -]; - -export function Footer({ - onReset, - htmlHistory, - setHtml, - device, - setDevice, - iframeRef, -}: { - onReset: () => void; - htmlHistory?: HtmlHistory[]; - device: "desktop" | "mobile"; - setHtml: (html: string) => void; - iframeRef?: React.RefObject; - setDevice: React.Dispatch>; -}) { - const { user } = useUser(); - - const handleRefreshIframe = () => { - if (iframeRef?.current) { - const iframe = iframeRef.current; - const content = iframe.srcdoc; - iframe.srcdoc = ""; - setTimeout(() => { - iframe.srcdoc = content; - }, 10); - } - }; - - return ( -
-
- {user && - (user?.isLocalUse ? ( - <> -
- Local Usage -
- - ) : ( - - ))} - {user &&

|

} - - {htmlHistory && htmlHistory.length > 0 && ( - <> -

|

- - - )} -
-
- - - - -
-
- {DEVICES.map((deviceItem) => ( - - ))} -
-
-
- ); -} diff --git a/components/editor/header/index.tsx b/components/editor/header/index.tsx deleted file mode 100644 index 184918363d4aa28eb1e866e9d711d8fdb15f2dc8..0000000000000000000000000000000000000000 --- a/components/editor/header/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -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 ( -
-
-

- DeepSite Logo -

- DeepSite - - {" "} - v2 - -

-

-
-
- {TABS.map((item) => ( - - ))} -
-
{children}
-
- ); -} diff --git a/components/editor/history/index.tsx b/components/editor/history/index.tsx deleted file mode 100644 index 7687479a53244e98dcab72ff12b6154dad2030cf..0000000000000000000000000000000000000000 --- a/components/editor/history/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { History as HistoryIcon } from "lucide-react"; -import { HtmlHistory } from "@/types"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Button } from "@/components/ui/button"; - -export function History({ - history, - setHtml, -}: { - history: HtmlHistory[]; - setHtml: (html: string) => void; -}) { - return ( - - - - - -
- History -
-
-
    - {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, - })} - -
    - -
  • - ))} -
-
-
-
- ); -} diff --git a/components/editor/index.tsx b/components/editor/index.tsx deleted file mode 100644 index d893a1addec01594905f922c47d01a538068072a..0000000000000000000000000000000000000000 --- a/components/editor/index.tsx +++ /dev/null @@ -1,337 +0,0 @@ -"use client"; -import { 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 { Project } from "@/types"; -import { SaveButton } from "./save-button"; -import { LoadProject } from "../my-projects/load-project"; -import { isTheSameHtml } from "@/lib/compare-html-diff"; - -export const AppEditor = ({ project }: { project?: Project | null }) => { - const [htmlStorage, , removeHtmlStorage] = useLocalStorage("html_content"); - const [, copyToClipboard] = useCopyToClipboard(); - const { html, setHtml, htmlHistory, setHtmlHistory, prompts, setPrompts } = - useEditor(project?.html ?? (htmlStorage as string) ?? defaultHTML); - // get query params from URL - 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 [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 - ); - - /** - * Resets the layout based on screen size - * - For desktop: Sets editor to 1/3 width and preview to 2/3 - * - For mobile: Removes inline styles to let CSS handle it - */ - 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 = ""; - } - }; - - /** - * Handles resizing when the user drags the resizer - * Ensures minimum widths are maintained for both panels - */ - 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(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 { - setIsEditableModeEnabled(false); - if (preview.current) { - // Reset preview width when switching to preview tab - preview.current.style.width = "100%"; - } - } - }, [currentTab]); - - return ( -
-
- { - router.push(`/projects/${project.space_id}`); - }} - /> - {project?._id ? ( - - ) : ( - - )} -
-
- {currentTab === "chat" && ( - <> -
- { - copyToClipboard(html); - toast.success("HTML copied to clipboard!"); - }} - /> - { - const newValue = value ?? ""; - setHtml(newValue); - }} - onMount={(editor, monaco) => { - editorRef.current = editor; - monacoRef.current = monaco; - }} - /> - { - setHtml(newHtml); - }} - htmlHistory={htmlHistory} - onSuccess={( - finalHtml: string, - p: string, - updatedLines?: number[][] - ) => { - const currentHistory = [...htmlHistory]; - currentHistory.unshift({ - html: finalHtml, - createdAt: new Date(), - prompt: p, - }); - setHtmlHistory(currentHistory); - setSelectedElement(null); - // 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); - } - }} - isAiWorking={isAiWorking} - setisAiWorking={setIsAiWorking} - onNewPrompt={(prompt: string) => { - setPrompts((prev) => [...prev, prompt]); - }} - onScrollToBottom={() => { - editorRef.current?.revealLine( - editorRef.current?.getModel()?.getLineCount() ?? 0 - ); - }} - isEditableModeEnabled={isEditableModeEnabled} - setIsEditableModeEnabled={setIsEditableModeEnabled} - selectedElement={selectedElement} - setSelectedElement={setSelectedElement} - /> -
-
- - )} - { - setIsEditableModeEnabled(false); - setSelectedElement(element); - }} - /> -
-
{ - if (isAiWorking) { - toast.warning("Please wait for the AI to finish working."); - return; - } - if ( - window.confirm("You're about to reset the editor. Are you sure?") - ) { - setHtml(defaultHTML); - removeHtmlStorage(); - editorRef.current?.revealLine( - editorRef.current?.getModel()?.getLineCount() ?? 0 - ); - } - }} - htmlHistory={htmlHistory} - setHtml={setHtml} - iframeRef={iframeRef} - device={device} - setDevice={setDevice} - /> -
- ); -}; diff --git a/components/editor/preview/index.tsx b/components/editor/preview/index.tsx deleted file mode 100644 index 472ba83537be2608524c0e7552d9d78f28ee0c4c..0000000000000000000000000000000000000000 --- a/components/editor/preview/index.tsx +++ /dev/null @@ -1,172 +0,0 @@ -"use client"; -import { useUpdateEffect } from "react-use"; -import { useMemo, useState } from "react"; -import classNames from "classnames"; -import { toast } from "sonner"; - -import { cn } from "@/lib/utils"; -import { GridPattern } from "@/components/magic-ui/grid-pattern"; -import { htmlTagToText } from "@/lib/html-tag-to-text"; - -export const Preview = ({ - html, - isResizing, - isAiWorking, - ref, - device, - currentTab, - iframeRef, - isEditableModeEnabled, - onClickElement, -}: { - html: string; - isResizing: boolean; - isAiWorking: boolean; - ref: React.RefObject; - iframeRef?: React.RefObject; - device: "desktop" | "mobile"; - currentTab: string; - isEditableModeEnabled?: boolean; - onClickElement?: (element: HTMLElement) => void; -}) => { - const [hoveredElement, setHoveredElement] = useState( - null - ); - - // add event listener to the iframe to track hovered elements - 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); - } - } - } - }; - - 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) { - // Clean up existing listeners first - cleanupListeners(); - - if (isEditableModeEnabled) { - iframeDocument.addEventListener("mouseover", handleMouseOver); - iframeDocument.addEventListener("mouseout", handleMouseOut); - iframeDocument.addEventListener("click", handleClick); - } - } - } - - // Clean up when component unmounts or dependencies change - return cleanupListeners; - }, [iframeRef, isEditableModeEnabled]); - - const selectedElement = useMemo(() => { - if (!isEditableModeEnabled) return null; - if (!hoveredElement) return null; - return hoveredElement; - }, [hoveredElement, isEditableModeEnabled]); - - return ( -
{ - if (isAiWorking) { - e.preventDefault(); - e.stopPropagation(); - toast.warning("Please wait for the AI to finish working."); - } - }} - > - - {!isAiWorking && hoveredElement && selectedElement && ( -
- - {htmlTagToText(selectedElement.tagName.toLowerCase())} - -
- )} - - - - -
-
-

- {project.space_id} -

-

- Updated{" "} - {formatDistance( - new Date(project._updatedAt || Date.now()), - new Date(), - { - addSuffix: true, - } - )} -

-
- - - - - - - - - - Project Settings - - - - - -
-
- ); -} diff --git a/components/pro-modal/index.tsx b/components/pro-modal/index.tsx deleted file mode 100644 index b9bdb4a73bdfa203f9055c9f4b65fe65f7d6258a..0000000000000000000000000000000000000000 --- a/components/pro-modal/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -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"; - -export const ProModal = ({ - open, - html, - onClose, -}: { - open: boolean; - html: string; - onClose: React.Dispatch>; -}) => { - const [, setStorage] = useLocalStorage("html_content"); - const handleProClick = () => { - if (!isTheSameHtml(html)) { - setStorage(html); - } - 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! -
  • -
- -
-
-
- ); -}; - -const ProTag = ({ className }: { className?: string }) => ( - - PRO - -); -export default ProModal; diff --git a/components/providers/tanstack-query-provider.tsx b/components/providers/tanstack-query-provider.tsx deleted file mode 100644 index 618056fcc0e27cb4433d0debe722ac8bb457f840..0000000000000000000000000000000000000000 --- a/components/providers/tanstack-query-provider.tsx +++ /dev/null @@ -1,18 +0,0 @@ -"use client"; - -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; - -export default function TanstackProvider({ - children, -}: { - children: React.ReactNode; -}) { - const queryClient = new QueryClient(); - - return ( - - {children} - {/* */} - - ); -} diff --git a/components/public/navigation/index.tsx b/components/public/navigation/index.tsx deleted file mode 100644 index 95c6975c5f3799b2a1db77ae56520297a9e0760e..0000000000000000000000000000000000000000 --- a/components/public/navigation/index.tsx +++ /dev/null @@ -1,156 +0,0 @@ -"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 ( -
- -
- ); -} diff --git a/components/space/ask-ai/index.tsx b/components/space/ask-ai/index.tsx deleted file mode 100644 index 46202ffe6f8d82f29eab068db97573985eaf9cca..0000000000000000000000000000000000000000 --- a/components/space/ask-ai/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -"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 ( - <> -
-