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 d79e0a395535132a784bb36d84283114493fba1f..7a8243be5368140f97260c71b293740cb94b0044 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,13 @@ --- -title: DeepSite v3 -emoji: 🐳 -colorFrom: blue -colorTo: blue +title: Space Generator +emoji: 🚀 +colorFrom: green +colorTo: purple 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 - - Qwen/Qwen3-Coder-480B-A35B-Instruct - - moonshotai/Kimi-K2-Instruct - - moonshotai/Kimi-K2-Instruct-0905 - - deepseek-ai/DeepSeek-V3.1 - - deepseek-ai/DeepSeek-V3.1-Terminus - - deepseek-ai/DeepSeek-V3.2-Exp +short_description: Develop and deploy your static space in few-seconds --- -# 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) \ No newline at end of file +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 4c640fb83ccfced8842764d029c87a2d85718d65..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 9d505589926b285353023b2e3dd1682f7e0788ee..0000000000000000000000000000000000000000 --- a/app/(public)/page.tsx +++ /dev/null @@ -1,193 +0,0 @@ -// import { AskAi } from "@/components/space/ask-ai"; -import { redirect } from "next/navigation"; -import { AnimatedText } from "@/components/animated-text"; -import { AnimatedBlobs } from "@/components/animated-blobs"; - -export default function Home() { - redirect("/projects"); - return ( -
-
-
- ✨ DeepSite v3 is out! -
-

- Code your website with AI in seconds -

- -
{/* */}
- -
- -
-
-
-
- 🚀 Powerful Features -
-

- Everything you need -

-

- Build, deploy, and scale your websites with cutting-edge features -

-
- - {/* Bento Grid */} -
- {/* Multi Pages */} -
-
-
📄
-

- Multi Pages -

-

- Create complex websites with multiple interconnected pages. - Build everything from simple landing pages to full-featured web - applications with dynamic routing and navigation. -

-
- - Dynamic Routing - - - Navigation - - - SEO Ready - -
-
-
-
- - {/* Auto Deploy */} -
-
-
-

- Auto Deploy -

-

- Push your changes and watch them go live instantly. No complex - CI/CD setup required. -

-
-
-
- - {/* Free Hosting */} -
-
-
🌐
-

- Free Hosting -

-

- Host your websites for free with global CDN and lightning-fast - performance. -

-
-
-
- - {/* Open Source Models */} -
-
-
🔓
-

- Open Source Models -

-

- Powered by cutting-edge open source AI models. Transparent, - customizable, and community-driven development. -

-
- - Llama - - - Mistral - - - CodeLlama - -
-
-
-
- - {/* UX Focus */} -
-
-
-

- Perfect UX -

-

- Intuitive interface designed for developers and non-developers - alike. -

-
-
-
- - {/* Hugging Face Integration */} -
-
-
🤗
-

- Hugging Face -

-

- Seamless integration with Hugging Face models and datasets for - cutting-edge AI capabilities. -

-
-
-
- - {/* Performance */} -
-
-
🚀
-

- Blazing Fast -

-

- Optimized performance with edge computing and smart caching. -

-
-
-
-
-
- - {/* Background Effects */} -
-
-
-
-
-
- ); -} diff --git a/app/(public)/projects/page.tsx b/app/(public)/projects/page.tsx deleted file mode 100644 index c7a8f5e93ea46537673bf02496221f971554771a..0000000000000000000000000000000000000000 --- a/app/(public)/projects/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { getProjects } from "@/app/actions/projects"; -import { MyProjects } from "@/components/my-projects"; -import { NotLogged } from "@/components/not-logged/not-logged"; - -export default async function ProjectsPage() { - // const { ok, projects } = await getProjects(); - - return ; -} diff --git a/app/actions/auth.ts b/app/actions/auth.ts deleted file mode 100644 index a343e65e6726b35b32f022c117d3f3b5187d78e6..0000000000000000000000000000000000000000 --- a/app/actions/auth.ts +++ /dev/null @@ -1,18 +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 url = host.includes("/spaces/enzostvs") - ? "enzostvs-deepsite.hf.space" - : host; - const redirect_uri = - `${host.includes("localhost") ? "http://" : "https://"}` + - url + - "/auth/callback"; - - const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`; - return loginRedirectUrl; -} diff --git a/app/actions/projects.ts b/app/actions/projects.ts deleted file mode 100644 index 5f99d85273352e21e22efb85b03bced1cf210897..0000000000000000000000000000000000000000 --- a/app/actions/projects.ts +++ /dev/null @@ -1,47 +0,0 @@ -"use server"; - -import { isAuthenticated } from "@/lib/auth"; -import { NextResponse } from "next/server"; -import { listSpaces } from "@huggingface/hub"; -import { ProjectType } from "@/types"; - -export async function getProjects(): Promise<{ - ok: boolean; - projects: ProjectType[]; - isEmpty?: boolean; -}> { - const user = await isAuthenticated(); - - if (user instanceof NextResponse || !user) { - return { - ok: false, - projects: [], - }; - } - - const projects = []; - for await (const space of listSpaces({ - accessToken: user.token as string, - additionalFields: ["author", "cardData"], - search: { - owner: user.name, - } - })) { - if ( - !space.private && - space.sdk === "static" && - Array.isArray((space.cardData as { tags?: string[] })?.tags) && - ( - ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite-v3")) || - ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite")) - ) - ) { - projects.push(space); - } - } - - return { - ok: true, - projects, - }; -} diff --git a/app/api/ask/route.ts b/app/api/ask/route.ts deleted file mode 100644 index 41f2b0bdf7118eaa9326540c1b5cde6723d30ee8..0000000000000000000000000000000000000000 --- a/app/api/ask/route.ts +++ /dev/null @@ -1,628 +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 } from "@/lib/providers"; -import { - DIVIDER, - FOLLOW_UP_SYSTEM_PROMPT, - INITIAL_SYSTEM_PROMPT, - MAX_REQUESTS_PER_IP, - NEW_PAGE_END, - NEW_PAGE_START, - REPLACE_END, - SEARCH_START, - UPDATE_PAGE_START, - UPDATE_PAGE_END, - PROMPT_FOR_PROJECT_NAME, -} from "@/lib/prompts"; -import { calculateMaxTokens, estimateInputTokens, getProviderSpecificConfig } from "@/lib/max-tokens"; -import MY_TOKEN_KEY from "@/lib/get-cookie-name"; -import { Page } from "@/types"; -import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub"; -import { isAuthenticated } from "@/lib/auth"; -import { getBestProvider } from "@/lib/best-provider"; -// import { rewritePrompt } from "@/lib/rewrite-prompt"; -import { COLORS } from "@/lib/utils"; -import { templates } from "@/lib/templates"; - -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, enhancedSettings, pages } = 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: string | null = null; - if (userToken) 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 selectedProvider = await getBestProvider(selectedModel.value, provider) - - let rewrittenPrompt = redesignMarkdown ? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown. Use the images in the markdown.` : prompt; - - if (enhancedSettings.isActive) { - // rewrittenPrompt = await rewritePrompt(rewrittenPrompt, enhancedSettings, { token, billTo }, selectedModel.value, selectedProvider.provider); - } - - try { - const encoder = new TextEncoder(); - const stream = new TransformStream(); - const writer = stream.writable.getWriter(); - - 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); - - // Calculate dynamic max_tokens based on provider and input size - const systemPrompt = INITIAL_SYSTEM_PROMPT + (enhancedSettings.isActive ? ` -Here are some examples of designs that you can inspire from: -${templates.map((template) => `- ${template}`).join("\n")} -IMPORTANT: Use the templates as inspiration, but do not copy them exactly. -Try to create a unique design, based on the templates, but not exactly like them, mostly depending on the user's prompt. These are just examples, do not copy them exactly. -` : ""); - - const userPrompt = rewrittenPrompt; - const estimatedInputTokens = estimateInputTokens(systemPrompt, userPrompt); - const dynamicMaxTokens = calculateMaxTokens(selectedProvider, estimatedInputTokens, true); - const providerConfig = getProviderSpecificConfig(selectedProvider, dynamicMaxTokens); - - const chatCompletion = client.chatCompletionStream( - { - model: selectedModel.value, - provider: selectedProvider.provider, - messages: [ - { - role: "system", - content: systemPrompt, - }, - { - role: "user", - content: userPrompt + (enhancedSettings.isActive ? `1. I want to use the following primary color: ${enhancedSettings.primaryColor} (eg: bg-${enhancedSettings.primaryColor}-500). -2. I want to use the following secondary color: ${enhancedSettings.secondaryColor} (eg: bg-${enhancedSettings.secondaryColor}-500). -3. I want to use the following theme: ${enhancedSettings.theme} mode.` : "") - }, - ], - ...providerConfig, - }, - billTo ? { billTo } : {} - ); - - while (true) { - const { done, value } = await chatCompletion.next() - if (done) { - break; - } - - const chunk = value.choices[0]?.delta?.content; - if (chunk) { - await writer.write(encoder.encode(chunk)); - } - } - - // Explicitly close the writer after successful completion - await writer.close(); - } 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 if (error?.message?.includes("inference provider information")) { - await writer.write( - encoder.encode( - JSON.stringify({ - ok: false, - openSelectProvider: true, - message: error.message, - }) - ) - ); - } - else { - await writer.write( - encoder.encode( - JSON.stringify({ - ok: false, - message: - error.message || - "An error occurred while processing your request.", - }) - ) - ); - } - } finally { - // Ensure the writer is always closed, even if already closed - try { - await writer?.close(); - } catch { - // Ignore errors when closing the writer as it might already be closed - } - } - })(); - - 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 user = await isAuthenticated(); - if (user instanceof NextResponse || !user) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - const authHeaders = await headers(); - - const body = await request.json(); - const { prompt, previousPrompts, provider, selectedElementHtml, model, pages, files, repoId: repoIdFromBody, isNew, enhancedSettings } = - body; - - let repoId = repoIdFromBody; - - if (!prompt || pages.length === 0) { - 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 } - ); - } - - let token = user.token as string; - 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); - - // Helper function to escape regex special characters - const escapeRegExp = (string: string) => { - return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - }; - - // Helper function to create flexible HTML regex that handles varying spaces - const createFlexibleHtmlRegex = (searchBlock: string) => { - let searchRegex = escapeRegExp(searchBlock) - .replace(/\s+/g, '\\s*') // Allow any amount of whitespace where there are spaces - .replace(/>\s*\\s*<') // Allow spaces between HTML tags - .replace(/\s*>/g, '\\s*>'); // Allow spaces before closing > - - return new RegExp(searchRegex, 'g'); - }; - - const selectedProvider = await getBestProvider(selectedModel.value, provider) - - try { - // Calculate dynamic max_tokens for PUT request - const systemPrompt = FOLLOW_UP_SYSTEM_PROMPT + (isNew ? PROMPT_FOR_PROJECT_NAME : ""); - const userContext = previousPrompts - ? `Also here are the previous prompts:\n\n${previousPrompts.map((p: string) => `- ${p}`).join("\n")}` - : "You are modifying the HTML file based on the user's request."; - const assistantContext = `${ - selectedElementHtml - ? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\` Could be in multiple pages, if so, update all the pages.` - : "" - }. Current pages: ${pages?.map((p: Page) => `- ${p.path} \n${p.html}`).join("\n")}. ${files?.length > 0 ? `Current images: ${files?.map((f: string) => `- ${f}`).join("\n")}.` : ""}`; - - const estimatedInputTokens = estimateInputTokens(systemPrompt, prompt, userContext + assistantContext); - const dynamicMaxTokens = calculateMaxTokens(selectedProvider, estimatedInputTokens, false); - const providerConfig = getProviderSpecificConfig(selectedProvider, dynamicMaxTokens); - - const response = await client.chatCompletion( - { - model: selectedModel.value, - provider: selectedProvider.provider, - messages: [ - { - role: "system", - content: systemPrompt, - }, - { - role: "user", - content: userContext, - }, - { - role: "assistant", - content: assistantContext, - }, - { - role: "user", - content: prompt, - }, - ], - ...providerConfig, - }, - 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 = ""; - const updatedPages = [...(pages || [])]; - - const updatePageRegex = new RegExp(`${UPDATE_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^\\s]+)\\s*${UPDATE_PAGE_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([\\s\\S]*?)(?=${UPDATE_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|${NEW_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|$)`, 'g'); - let updatePageMatch; - - while ((updatePageMatch = updatePageRegex.exec(chunk)) !== null) { - const [, pagePath, pageContent] = updatePageMatch; - - const pageIndex = updatedPages.findIndex(p => p.path === pagePath); - if (pageIndex !== -1) { - let pageHtml = updatedPages[pageIndex].html; - - let processedContent = pageContent; - const htmlMatch = pageContent.match(/```html\s*([\s\S]*?)\s*```/); - if (htmlMatch) { - processedContent = htmlMatch[1]; - } - let position = 0; - let moreBlocks = true; - - while (moreBlocks) { - const searchStartIndex = processedContent.indexOf(SEARCH_START, position); - if (searchStartIndex === -1) { - moreBlocks = false; - continue; - } - - const dividerIndex = processedContent.indexOf(DIVIDER, searchStartIndex); - if (dividerIndex === -1) { - moreBlocks = false; - continue; - } - - const replaceEndIndex = processedContent.indexOf(REPLACE_END, dividerIndex); - if (replaceEndIndex === -1) { - moreBlocks = false; - continue; - } - - const searchBlock = processedContent.substring( - searchStartIndex + SEARCH_START.length, - dividerIndex - ); - const replaceBlock = processedContent.substring( - dividerIndex + DIVIDER.length, - replaceEndIndex - ); - - if (searchBlock.trim() === "") { - pageHtml = `${replaceBlock}\n${pageHtml}`; - updatedLines.push([1, replaceBlock.split("\n").length]); - } else { - const regex = createFlexibleHtmlRegex(searchBlock); - const match = regex.exec(pageHtml); - - if (match) { - const matchedText = match[0]; - const beforeText = pageHtml.substring(0, match.index); - const startLineNumber = beforeText.split("\n").length; - const replaceLines = replaceBlock.split("\n").length; - const endLineNumber = startLineNumber + replaceLines - 1; - - updatedLines.push([startLineNumber, endLineNumber]); - pageHtml = pageHtml.replace(matchedText, replaceBlock); - } - } - - position = replaceEndIndex + REPLACE_END.length; - } - - updatedPages[pageIndex].html = pageHtml; - - if (pagePath === '/' || pagePath === '/index' || pagePath === 'index') { - newHtml = pageHtml; - } - } - } - - const newPageRegex = new RegExp(`${NEW_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^\\s]+)\\s*${NEW_PAGE_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([\\s\\S]*?)(?=${UPDATE_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|${NEW_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|$)`, 'g'); - let newPageMatch; - - while ((newPageMatch = newPageRegex.exec(chunk)) !== null) { - const [, pagePath, pageContent] = newPageMatch; - - let pageHtml = pageContent; - const htmlMatch = pageContent.match(/```html\s*([\s\S]*?)\s*```/); - if (htmlMatch) { - pageHtml = htmlMatch[1]; - } - - const existingPageIndex = updatedPages.findIndex(p => p.path === pagePath); - - if (existingPageIndex !== -1) { - updatedPages[existingPageIndex] = { - path: pagePath, - html: pageHtml.trim() - }; - } else { - updatedPages.push({ - path: pagePath, - html: pageHtml.trim() - }); - } - } - - if (updatedPages.length === pages?.length && !chunk.includes(UPDATE_PAGE_START)) { - 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 regex = createFlexibleHtmlRegex(searchBlock); - const match = regex.exec(newHtml); - - if (match) { - const matchedText = match[0]; - const beforeText = newHtml.substring(0, match.index); - const startLineNumber = beforeText.split("\n").length; - const replaceLines = replaceBlock.split("\n").length; - const endLineNumber = startLineNumber + replaceLines - 1; - - updatedLines.push([startLineNumber, endLineNumber]); - newHtml = newHtml.replace(matchedText, replaceBlock); - } - } - - position = replaceEndIndex + REPLACE_END.length; - } - - // Update the main HTML if it's the index page - const mainPageIndex = updatedPages.findIndex(p => p.path === '/' || p.path === '/index' || p.path === 'index'); - if (mainPageIndex !== -1) { - updatedPages[mainPageIndex].html = newHtml; - } - } - - const files: File[] = []; - updatedPages.forEach((page: Page) => { - const file = new File([page.html], page.path, { type: "text/html" }); - files.push(file); - }); - - if (isNew) { - const projectName = chunk.match(/<<<<<<< PROJECT_NAME_START ([\s\S]*?) >>>>>>> PROJECT_NAME_END/)?.[1]?.trim(); - const formattedTitle = projectName?.toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .split("-") - .filter(Boolean) - .join("-") - .slice(0, 96); - const repo: RepoDesignation = { - type: "space", - name: `${user.name}/${formattedTitle}`, - }; - const { repoUrl} = await createRepo({ - repo, - accessToken: user.token as string, - }); - repoId = repoUrl.split("/").slice(-2).join("/"); - const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)]; - const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)]; - const README = `--- -title: ${projectName} -colorFrom: ${colorFrom} -colorTo: ${colorTo} -emoji: 🐳 -sdk: static -pinned: false -tags: - - deepsite-v3 ---- - -# Welcome to your new DeepSite project! -This project was created with [DeepSite](https://deepsite.hf.co). - `; - files.push(new File([README], "README.md", { type: "text/markdown" })); - } - - const response = await uploadFiles({ - repo: { - type: "space", - name: repoId, - }, - files, - commitTitle: prompt, - accessToken: user.token as string, - }); - - return NextResponse.json({ - ok: true, - updatedLines, - pages: updatedPages, - repoId, - commit: { - ...response.commit, - title: prompt, - } - }); - } 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/login-url/route.ts b/app/api/auth/login-url/route.ts deleted file mode 100644 index ec05f2f8a66a5d85e9f7f615300871bebe9f8171..0000000000000000000000000000000000000000 --- a/app/api/auth/login-url/route.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -export async function GET(req: NextRequest) { - const host = req.headers.get("host") ?? "localhost:3000"; - - let url: string; - if (host.includes("localhost")) { - url = host; - } else if (host.includes("hf.space") || host.includes("/spaces/enzostvs")) { - url = "enzostvs-deepsite.hf.space"; - } else { - url = "deepsite.hf.co"; - } - - const redirect_uri = - `${host.includes("localhost") ? "http://" : "https://"}` + - url + - "/auth/callback"; - - const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`; - - return NextResponse.json({ loginUrl: loginRedirectUrl }); -} diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts deleted file mode 100644 index 01feb588119d8472e23d61fffbc6f43d8f5a508c..0000000000000000000000000000000000000000 --- a/app/api/auth/logout/route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { NextResponse } from "next/server"; -import MY_TOKEN_KEY from "@/lib/get-cookie-name"; - -export async function POST() { - const cookieName = MY_TOKEN_KEY(); - const isProduction = process.env.NODE_ENV === "production"; - - const response = NextResponse.json( - { message: "Logged out successfully" }, - { status: 200 } - ); - - // Clear the HTTP-only cookie - const cookieOptions = [ - `${cookieName}=`, - "Max-Age=0", - "Path=/", - "HttpOnly", - ...(isProduction ? ["Secure", "SameSite=None"] : ["SameSite=Lax"]) - ].join("; "); - - response.headers.set("Set-Cookie", cookieOptions); - - return response; -} diff --git a/app/api/auth/route.ts b/app/api/auth/route.ts deleted file mode 100644 index a57a0447aeea3d9f9f526762aaf7d4ddab16b663..0000000000000000000000000000000000000000 --- a/app/api/auth/route.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import MY_TOKEN_KEY from "@/lib/get-cookie-name"; - -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 url = host.includes("/spaces/enzostvs") - ? "enzostvs-deepsite.hf.space" - : host; - const redirect_uri = - `${host.includes("localhost") ? "http://" : "https://"}` + - url + - "/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(); - - const cookieName = MY_TOKEN_KEY(); - const isProduction = process.env.NODE_ENV === "production"; - - // Create response with user data - const nextResponse = NextResponse.json( - { - access_token: response.access_token, - expires_in: response.expires_in, - user, - // Include fallback flag for iframe contexts - useLocalStorageFallback: true, - }, - { - status: 200, - headers: { - "Content-Type": "application/json", - }, - } - ); - - // Set HTTP-only cookie with proper attributes for iframe support - const cookieOptions = [ - `${cookieName}=${response.access_token}`, - `Max-Age=${response.expires_in || 3600}`, // Default 1 hour if not provided - "Path=/", - "HttpOnly", - ...(isProduction ? ["Secure", "SameSite=None"] : ["SameSite=Lax"]) - ].join("; "); - - nextResponse.headers.set("Set-Cookie", cookieOptions); - - return nextResponse; -} diff --git a/app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/promote/route.ts b/app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/promote/route.ts deleted file mode 100644 index 1e070e50ad1654ddd200786d4feac6d6919c8502..0000000000000000000000000000000000000000 --- a/app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/promote/route.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { RepoDesignation, listFiles, spaceInfo, uploadFiles, deleteFiles } from "@huggingface/hub"; - -import { isAuthenticated } from "@/lib/auth"; -import { Page } from "@/types"; - -export async function POST( - req: NextRequest, - { params }: { - params: Promise<{ - namespace: string; - repoId: string; - commitId: string; - }> - } -) { - const user = await isAuthenticated(); - - if (user instanceof NextResponse || !user) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - const param = await params; - const { namespace, repoId, commitId } = param; - - try { - const repo: RepoDesignation = { - type: "space", - name: `${namespace}/${repoId}`, - }; - - 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 } - ); - } - - // Fetch files from the specific commit - const files: File[] = []; - const pages: Page[] = []; - const allowedExtensions = ["html", "md", "css", "js", "json", "txt"]; - const commitFilePaths: Set = new Set(); - - // Get all files from the specific commit - for await (const fileInfo of listFiles({ - repo, - accessToken: user.token as string, - revision: commitId, - })) { - const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase(); - - if (allowedExtensions.includes(fileExtension || "")) { - commitFilePaths.add(fileInfo.path); - - // Fetch the file content from the specific commit - const response = await fetch( - `https://huggingface.co/spaces/${namespace}/${repoId}/raw/${commitId}/${fileInfo.path}` - ); - - if (response.ok) { - const content = await response.text(); - let mimeType = "text/plain"; - - switch (fileExtension) { - case "html": - mimeType = "text/html"; - // Add HTML files to pages array for client-side setPages - pages.push({ - path: fileInfo.path, - html: content, - }); - break; - case "css": - mimeType = "text/css"; - break; - case "js": - mimeType = "application/javascript"; - break; - case "json": - mimeType = "application/json"; - break; - case "md": - mimeType = "text/markdown"; - break; - } - - const file = new File([content], fileInfo.path, { type: mimeType }); - files.push(file); - } - } - } - - // Get files currently in main branch to identify files to delete - const mainBranchFilePaths: Set = new Set(); - for await (const fileInfo of listFiles({ - repo, - accessToken: user.token as string, - revision: "main", - })) { - const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase(); - - if (allowedExtensions.includes(fileExtension || "")) { - mainBranchFilePaths.add(fileInfo.path); - } - } - - // Identify files to delete (exist in main but not in commit) - const filesToDelete: string[] = []; - for (const mainFilePath of mainBranchFilePaths) { - if (!commitFilePaths.has(mainFilePath)) { - filesToDelete.push(mainFilePath); - } - } - - if (files.length === 0 && filesToDelete.length === 0) { - return NextResponse.json( - { ok: false, error: "No files found in the specified commit and no files to delete" }, - { status: 404 } - ); - } - - // Delete files that exist in main but not in the commit being promoted - if (filesToDelete.length > 0) { - await deleteFiles({ - repo, - paths: filesToDelete, - accessToken: user.token as string, - commitTitle: `Removed files from promoting ${commitId.slice(0, 7)}`, - commitDescription: `Removed files that don't exist in commit ${commitId}:\n${filesToDelete.map(path => `- ${path}`).join('\n')}`, - }); - } - - // Upload the files to the main branch with a promotion commit message - if (files.length > 0) { - await uploadFiles({ - repo, - files, - accessToken: user.token as string, - commitTitle: `Promote version ${commitId.slice(0, 7)} to main`, - commitDescription: `Promoted commit ${commitId} to main branch`, - }); - } - - return NextResponse.json( - { - ok: true, - message: "Version promoted successfully", - promotedCommit: commitId, - pages: pages, - }, - { status: 200 } - ); - - } catch (error: any) { - - // Handle specific HuggingFace API errors - if (error.statusCode === 404) { - return NextResponse.json( - { ok: false, error: "Commit not found" }, - { status: 404 } - ); - } - - if (error.statusCode === 403) { - return NextResponse.json( - { ok: false, error: "Access denied to repository" }, - { status: 403 } - ); - } - - return NextResponse.json( - { ok: false, error: error.message || "Failed to promote version" }, - { status: 500 } - ); - } -} diff --git a/app/api/me/projects/[namespace]/[repoId]/images/route.ts b/app/api/me/projects/[namespace]/[repoId]/images/route.ts deleted file mode 100644 index c6ec4e96591667e0e33bb47743037c1a2aa11d17..0000000000000000000000000000000000000000 --- a/app/api/me/projects/[namespace]/[repoId]/images/route.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { RepoDesignation, spaceInfo, uploadFiles } from "@huggingface/hub"; - -import { isAuthenticated } from "@/lib/auth"; -import Project from "@/models/Project"; -import dbConnect from "@/lib/mongodb"; - -export async function POST( - req: NextRequest, - { params }: { params: Promise<{ namespace: string; repoId: string }> } -) { - try { - const user = await isAuthenticated(); - - if (user instanceof NextResponse || !user) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - 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 } - ); - } - - // Parse the FormData to get the images - const formData = await req.formData(); - const imageFiles = formData.getAll("images") as File[]; - - if (!imageFiles || imageFiles.length === 0) { - return NextResponse.json( - { - ok: false, - error: "At least one image file is required under the 'images' key", - }, - { status: 400 } - ); - } - - const files: File[] = []; - for (const file of imageFiles) { - if (!(file instanceof File)) { - return NextResponse.json( - { - ok: false, - error: "Invalid file format - all items under 'images' key must be files", - }, - { status: 400 } - ); - } - - if (!file.type.startsWith('image/')) { - return NextResponse.json( - { - ok: false, - error: `File ${file.name} is not an image`, - }, - { status: 400 } - ); - } - - // Create File object with images/ folder prefix - const fileName = `images/${file.name}`; - const processedFile = new File([file], fileName, { type: file.type }); - files.push(processedFile); - } - - // Upload files to HuggingFace space - const repo: RepoDesignation = { - type: "space", - name: `${namespace}/${repoId}`, - }; - - await uploadFiles({ - repo, - files, - accessToken: user.token as string, - commitTitle: `Upload ${files.length} image(s)`, - }); - - return NextResponse.json({ - ok: true, - message: `Successfully uploaded ${files.length} image(s) to ${namespace}/${repoId}/images/`, - uploadedFiles: files.map((file) => `https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${file.name}`), - }, { status: 200 }); - - } catch (error) { - console.error('Error uploading images:', error); - return NextResponse.json( - { - ok: false, - error: "Failed to upload images", - }, - { status: 500 } - ); - } -} 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 f19049c9925fe4a1f0fd44fe8fc5bac299f4b12e..0000000000000000000000000000000000000000 --- a/app/api/me/projects/[namespace]/[repoId]/route.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { RepoDesignation, spaceInfo, listFiles, deleteRepo, listCommits, downloadFile } from "@huggingface/hub"; - -import { isAuthenticated } from "@/lib/auth"; -import { Commit, Page } from "@/types"; - -export async function DELETE( - 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 }); - } - - const param = await params; - const { namespace, repoId } = param; - - 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 repo: RepoDesignation = { - type: "space", - name: `${namespace}/${repoId}`, - }; - - await deleteRepo({ - repo, - accessToken: user.token as string, - }); - - - return NextResponse.json({ ok: true }, { status: 200 }); - } catch (error: any) { - return NextResponse.json( - { ok: false, error: error.message }, - { status: 500 } - ); - } -} - -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 }); - } - - const param = await params; - const { namespace, repoId } = param; - - 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 repo: RepoDesignation = { - type: "space", - name: `${namespace}/${repoId}`, - }; - - const htmlFiles: Page[] = []; - const files: string[] = []; - - const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif"]; - - for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) { - if (fileInfo.path.endsWith(".html")) { - const blob = await downloadFile({ repo, accessToken: user.token as string, path: fileInfo.path, raw: true }); - const html = await blob?.text(); - if (!html) { - continue; - } - if (fileInfo.path === "index.html") { - htmlFiles.unshift({ - path: fileInfo.path, - html, - }); - } else { - htmlFiles.push({ - path: fileInfo.path, - html, - }); - } - } - if (fileInfo.type === "directory" && fileInfo.path === "images") { - for await (const imageInfo of listFiles({repo, accessToken: user.token as string, path: fileInfo.path})) { - if (allowedFilesExtensions.includes(imageInfo.path.split(".").pop() || "")) { - files.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${imageInfo.path}`); - } - } - } - } - const commits: Commit[] = []; - for await (const commit of listCommits({ repo, accessToken: user.token as string })) { - if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Removed files from promoting")) { - continue; - } - commits.push({ - title: commit.title, - oid: commit.oid, - date: commit.date, - }); - } - - if (htmlFiles.length === 0) { - return NextResponse.json( - { - ok: false, - error: "No HTML files found", - }, - { status: 404 } - ); - } - return NextResponse.json( - { - project: { - id: space.id, - space_id: space.name, - private: space.private, - _updatedAt: space.updatedAt, - }, - pages: htmlFiles, - files, - commits, - ok: true, - }, - { status: 200 } - ); - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - if (error.statusCode === 404) { - return NextResponse.json( - { error: "Space not found", ok: false }, - { status: 404 } - ); - } - return NextResponse.json( - { error: error.message, ok: false }, - { status: 500 } - ); - } -} diff --git a/app/api/me/projects/[namespace]/[repoId]/save/route.ts b/app/api/me/projects/[namespace]/[repoId]/save/route.ts deleted file mode 100644 index c129dae5508f9a5e50650f7872ae4c9cd8c062a5..0000000000000000000000000000000000000000 --- a/app/api/me/projects/[namespace]/[repoId]/save/route.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { uploadFiles } from "@huggingface/hub"; - -import { isAuthenticated } from "@/lib/auth"; -import { Page } from "@/types"; - -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 }); - } - - const param = await params; - const { namespace, repoId } = param; - const { pages, commitTitle = "Manual changes saved" } = await req.json(); - - if (!pages || !Array.isArray(pages) || pages.length === 0) { - return NextResponse.json( - { ok: false, error: "Pages are required" }, - { status: 400 } - ); - } - - try { - // Prepare files for upload - const files: File[] = []; - pages.forEach((page: Page) => { - const file = new File([page.html], page.path, { type: "text/html" }); - files.push(file); - }); - - // Upload files to HuggingFace Hub - const response = await uploadFiles({ - repo: { - type: "space", - name: `${namespace}/${repoId}`, - }, - files, - commitTitle, - accessToken: user.token as string, - }); - - return NextResponse.json({ - ok: true, - pages, - commit: { - ...response.commit, - title: commitTitle, - } - }); - } catch (error: any) { - console.error("Error saving manual changes:", error); - return NextResponse.json( - { - ok: false, - error: error.message || "Failed to save changes", - }, - { status: 500 } - ); - } -} diff --git a/app/api/me/projects/route.ts b/app/api/me/projects/route.ts deleted file mode 100644 index a37afbc9dd93320d2bd808ca9f3981eca3936555..0000000000000000000000000000000000000000 --- a/app/api/me/projects/route.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { RepoDesignation, createRepo, listCommits, spaceInfo, uploadFiles } from "@huggingface/hub"; - -import { isAuthenticated } from "@/lib/auth"; -import { Commit, Page } from "@/types"; -import { COLORS } from "@/lib/utils"; - -export async function POST( - req: NextRequest, -) { - const user = await isAuthenticated(); - if (user instanceof NextResponse || !user) { - return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); - } - - const { title: titleFromRequest, pages, prompt } = await req.json(); - - const title = titleFromRequest ?? "DeepSite Project"; - - const formattedTitle = title - .toLowerCase() - .replace(/[^a-z0-9]+/g, "-") - .split("-") - .filter(Boolean) - .join("-") - .slice(0, 96); - - const repo: RepoDesignation = { - type: "space", - name: `${user.name}/${formattedTitle}`, - }; - const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)]; - const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)]; - const README = `--- -title: ${title} -colorFrom: ${colorFrom} -colorTo: ${colorTo} -emoji: 🐳 -sdk: static -pinned: false -tags: - - deepsite-v3 ---- - -# Welcome to your new DeepSite project! -This project was created with [DeepSite](https://deepsite.hf.co). -`; - - const files: File[] = []; - const readmeFile = new File([README], "README.md", { type: "text/markdown" }); - files.push(readmeFile); - pages.forEach((page: Page) => { - const file = new File([page.html], page.path, { type: "text/html" }); - files.push(file); - }); - - try { - const { repoUrl} = await createRepo({ - repo, - accessToken: user.token as string, - }); - const commitTitle = !prompt || prompt.trim() === "" ? "Redesign my website" : prompt; - await uploadFiles({ - repo, - files, - accessToken: user.token as string, - commitTitle - }); - - const path = repoUrl.split("/").slice(-2).join("/"); - - const commits: Commit[] = []; - for await (const commit of listCommits({ repo, accessToken: user.token as string })) { - if (commit.title.includes("initial commit") || commit.title.includes("image(s)") || commit.title.includes("Promote version")) { - continue; - } - commits.push({ - title: commit.title, - oid: commit.oid, - date: commit.date, - }); - } - - const space = await spaceInfo({ - name: repo.name, - accessToken: user.token as string, - }); - - let newProject = { - files, - pages, - commits, - project: { - id: space.id, - space_id: space.name, - _updatedAt: space.updatedAt, - } - } - - return NextResponse.json({ space: newProject, path, ok: true }, { status: 201 }); - } catch (err: any) { - return NextResponse.json( - { error: err.message, ok: false }, - { status: 500 } - ); - } -} \ No newline at end of file diff --git a/app/api/me/route.ts b/app/api/me/route.ts deleted file mode 100644 index 25bc6c81aa826d65cac703e43c5d4647ae5cd141..0000000000000000000000000000000000000000 --- a/app/api/me/route.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { listSpaces } from "@huggingface/hub"; -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(); - const projects = []; - for await (const space of listSpaces({ - accessToken: token.replace("Bearer ", "") as string, - additionalFields: ["author", "cardData"], - search: { - owner: user.name, - } - })) { - if ( - space.sdk === "static" && - Array.isArray((space.cardData as { tags?: string[] })?.tags) && - ( - ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite-v3")) || - ((space.cardData as { tags?: string[] })?.tags?.includes("deepsite")) - ) - ) { - projects.push(space); - } - } - - return NextResponse.json({ user, projects, 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 6cf420cea009bbe322d10d7fc9a9d7badee467f6..0000000000000000000000000000000000000000 --- a/app/auth/callback/page.tsx +++ /dev/null @@ -1,97 +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"; -import { AnimatedBlobs } from "@/components/animated-blobs"; -import { useBroadcastChannel } from "@/lib/useBroadcastChannel"; -export default function AuthCallback({ - searchParams, -}: { - searchParams: Promise<{ code: string }>; -}) { - const [showButton, setShowButton] = useState(false); - const [isPopupAuth, setIsPopupAuth] = useState(false); - const { code } = use(searchParams); - const { loginFromCode } = useUser(); - const { postMessage } = useBroadcastChannel("auth", () => {}); - - useMount(async () => { - if (code) { - const isPopup = window.opener || window.parent !== window; - setIsPopupAuth(isPopup); - - if (isPopup) { - postMessage({ - type: "user-oauth", - code: code, - }); - - setTimeout(() => { - if (window.opener) { - window.close(); - } - }, 1000); - } else { - await loginFromCode(code); - } - } - }); - - useTimeoutFn(() => setShowButton(true), 7000); - - return ( -
-
-
-
-
-
-
- 🚀 -
-
- 👋 -
-
- 🙌 -
-
-

- {isPopupAuth - ? "Authentication Complete!" - : "Login In Progress..."} -

-

- {isPopupAuth - ? "You can now close this tab and return to the previous page." - : "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 e7f517c1ffe027c788a598959aac26f0838e3a6e..0000000000000000000000000000000000000000 --- a/app/layout.tsx +++ /dev/null @@ -1,116 +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 Script from "next/script"; - -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 IframeDetector from "@/components/iframe-detector"; -import AppContext from "@/components/contexts/app-context"; -import TanstackContext from "@/components/contexts/tanstack-query-context"; -import { LoginProvider } from "@/components/contexts/login-context"; -import { ProProvider } from "@/components/contexts/pro-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, projects: [], errCode: null }; - try { - const res = await apiServer.get("/me", { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - return { user: res.data.user, projects: res.data.projects, errCode: null }; - } catch (err: any) { - return { user: null, projects: [], 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 66d78b3ec8bf617eb018764cb76bff1913779ae2..0000000000000000000000000000000000000000 --- a/app/projects/[namespace]/[repoId]/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { AppEditor } from "@/components/editor"; - -export default async function ProjectNamespacePage({ - params, -}: { - params: Promise<{ namespace: string; repoId: string }>; -}) { - const { namespace, repoId } = await params; - return ; -} diff --git a/app/projects/new/page.tsx b/app/projects/new/page.tsx deleted file mode 100644 index 591377b92589fa8c7077817d12dc231ff030a776..0000000000000000000000000000000000000000 --- a/app/projects/new/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { AppEditor } from "@/components/editor"; - -export default function NewProjectPage() { - return ; -} diff --git a/assets/deepseek.svg b/assets/deepseek.svg deleted file mode 100644 index dc224e43a4d68070ca6eed494476c8ddd900bf80..0000000000000000000000000000000000000000 --- a/assets/deepseek.svg +++ /dev/null @@ -1 +0,0 @@ -DeepSeek \ No newline at end of file diff --git a/assets/globals.css b/assets/globals.css deleted file mode 100644 index 9b6618f9d91f8066c44b1ce2608d9af665f7d42f..0000000000000000000000000000000000000000 --- a/assets/globals.css +++ /dev/null @@ -1,371 +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); -} - -body { - @apply scroll-smooth -} - -@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; -} - -/* Fast liquid deformation animations */ -@keyframes liquidBlob1 { - 0%, 100% { - border-radius: 40% 60% 50% 50%; - transform: scaleX(1) scaleY(1) rotate(0deg); - } - 12.5% { - border-radius: 20% 80% 70% 30%; - transform: scaleX(1.6) scaleY(0.4) rotate(25deg); - } - 25% { - border-radius: 80% 20% 30% 70%; - transform: scaleX(0.5) scaleY(2.1) rotate(-15deg); - } - 37.5% { - border-radius: 30% 70% 80% 20%; - transform: scaleX(1.8) scaleY(0.6) rotate(40deg); - } - 50% { - border-radius: 70% 30% 20% 80%; - transform: scaleX(0.4) scaleY(1.9) rotate(-30deg); - } - 62.5% { - border-radius: 25% 75% 60% 40%; - transform: scaleX(1.5) scaleY(0.7) rotate(55deg); - } - 75% { - border-radius: 75% 25% 40% 60%; - transform: scaleX(0.6) scaleY(1.7) rotate(-10deg); - } - 87.5% { - border-radius: 50% 50% 75% 25%; - transform: scaleX(1.3) scaleY(0.8) rotate(35deg); - } -} - -@keyframes liquidBlob2 { - 0%, 100% { - border-radius: 60% 40% 50% 50%; - transform: scaleX(1) scaleY(1) rotate(12deg); - } - 16% { - border-radius: 15% 85% 60% 40%; - transform: scaleX(0.3) scaleY(2.3) rotate(50deg); - } - 32% { - border-radius: 85% 15% 25% 75%; - transform: scaleX(2.0) scaleY(0.5) rotate(-20deg); - } - 48% { - border-radius: 30% 70% 85% 15%; - transform: scaleX(0.4) scaleY(1.8) rotate(70deg); - } - 64% { - border-radius: 70% 30% 15% 85%; - transform: scaleX(1.9) scaleY(0.6) rotate(-35deg); - } - 80% { - border-radius: 40% 60% 70% 30%; - transform: scaleX(0.7) scaleY(1.6) rotate(45deg); - } -} - -@keyframes liquidBlob3 { - 0%, 100% { - border-radius: 50% 50% 40% 60%; - transform: scaleX(1) scaleY(1) rotate(0deg); - } - 20% { - border-radius: 10% 90% 75% 25%; - transform: scaleX(2.2) scaleY(0.3) rotate(-45deg); - } - 40% { - border-radius: 90% 10% 20% 80%; - transform: scaleX(0.4) scaleY(2.5) rotate(60deg); - } - 60% { - border-radius: 25% 75% 90% 10%; - transform: scaleX(1.7) scaleY(0.5) rotate(-25deg); - } - 80% { - border-radius: 75% 25% 10% 90%; - transform: scaleX(0.6) scaleY(2.0) rotate(80deg); - } -} - -@keyframes liquidBlob4 { - 0%, 100% { - border-radius: 45% 55% 50% 50%; - transform: scaleX(1) scaleY(1) rotate(-15deg); - } - 14% { - border-radius: 90% 10% 65% 35%; - transform: scaleX(0.2) scaleY(2.8) rotate(35deg); - } - 28% { - border-radius: 10% 90% 20% 80%; - transform: scaleX(2.4) scaleY(0.4) rotate(-50deg); - } - 42% { - border-radius: 35% 65% 90% 10%; - transform: scaleX(0.3) scaleY(2.1) rotate(70deg); - } - 56% { - border-radius: 80% 20% 10% 90%; - transform: scaleX(2.0) scaleY(0.5) rotate(-40deg); - } - 70% { - border-radius: 20% 80% 55% 45%; - transform: scaleX(0.5) scaleY(1.9) rotate(55deg); - } - 84% { - border-radius: 65% 35% 80% 20%; - transform: scaleX(1.6) scaleY(0.6) rotate(-25deg); - } -} - -/* Fast flowing movement animations */ -@keyframes liquidFlow1 { - 0%, 100% { transform: translate(0, 0); } - 16% { transform: translate(60px, -40px); } - 32% { transform: translate(-45px, -70px); } - 48% { transform: translate(80px, 25px); } - 64% { transform: translate(-30px, 60px); } - 80% { transform: translate(50px, -20px); } -} - -@keyframes liquidFlow2 { - 0%, 100% { transform: translate(0, 0); } - 20% { transform: translate(-70px, 50px); } - 40% { transform: translate(90px, -30px); } - 60% { transform: translate(-40px, -55px); } - 80% { transform: translate(65px, 35px); } -} - -@keyframes liquidFlow3 { - 0%, 100% { transform: translate(0, 0); } - 12% { transform: translate(-50px, -60px); } - 24% { transform: translate(40px, -20px); } - 36% { transform: translate(-30px, 70px); } - 48% { transform: translate(70px, 20px); } - 60% { transform: translate(-60px, -35px); } - 72% { transform: translate(35px, 55px); } - 84% { transform: translate(-25px, -45px); } -} - -@keyframes liquidFlow4 { - 0%, 100% { transform: translate(0, 0); } - 14% { transform: translate(50px, 60px); } - 28% { transform: translate(-80px, -40px); } - 42% { transform: translate(30px, -90px); } - 56% { transform: translate(-55px, 45px); } - 70% { transform: translate(75px, -25px); } - 84% { transform: translate(-35px, 65px); } -} - -/* Light sweep animation for buttons */ -@keyframes lightSweep { - 0% { - transform: translateX(-150%); - opacity: 0; - } - 8% { - opacity: 0.3; - } - 25% { - opacity: 0.8; - } - 42% { - opacity: 0.3; - } - 50% { - transform: translateX(150%); - opacity: 0; - } - 58% { - opacity: 0.3; - } - 75% { - opacity: 0.8; - } - 92% { - opacity: 0.3; - } - 100% { - transform: translateX(-150%); - opacity: 0; - } -} - -.light-sweep { - position: relative; - overflow: hidden; -} - -.light-sweep::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 300%; - background: linear-gradient( - 90deg, - transparent 0%, - transparent 20%, - rgba(56, 189, 248, 0.1) 35%, - rgba(56, 189, 248, 0.2) 45%, - rgba(255, 255, 255, 0.2) 50%, - rgba(168, 85, 247, 0.2) 55%, - rgba(168, 85, 247, 0.1) 65%, - transparent 80%, - transparent 100% - ); - animation: lightSweep 7s cubic-bezier(0.4, 0, 0.2, 1) infinite; - pointer-events: none; - z-index: 1; - filter: blur(1px); -} diff --git a/assets/kimi.svg b/assets/kimi.svg deleted file mode 100644 index 4355c522a2dece99e187d9e5c898a66313f4a374..0000000000000000000000000000000000000000 --- a/assets/kimi.svg +++ /dev/null @@ -1 +0,0 @@ -Kimi \ No newline at end of file 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/assets/qwen.svg b/assets/qwen.svg deleted file mode 100644 index a4bb382a6359b82c581fd3e7fb7169fe8fba1657..0000000000000000000000000000000000000000 --- a/assets/qwen.svg +++ /dev/null @@ -1 +0,0 @@ -Qwen \ No newline at end of file diff --git a/components.json b/components.json deleted file mode 100644 index 8854f1e2cb22a6949612c72de37b6d9a57489b85..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": "assets/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/animated-blobs/index.tsx b/components/animated-blobs/index.tsx deleted file mode 100644 index 516c36cf8ac5dd62a5f293b405bf3b2c480cb78f..0000000000000000000000000000000000000000 --- a/components/animated-blobs/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -export function AnimatedBlobs() { - return ( -
-
-
-
-
-
- ); -} diff --git a/components/animated-text/index.tsx b/components/animated-text/index.tsx deleted file mode 100644 index 1bfef666235566c3f8fec1872b30ff5bb55b3168..0000000000000000000000000000000000000000 --- a/components/animated-text/index.tsx +++ /dev/null @@ -1,123 +0,0 @@ -"use client"; - -import { useState, useEffect } from "react"; - -interface AnimatedTextProps { - className?: string; -} - -export function AnimatedText({ className = "" }: AnimatedTextProps) { - const [displayText, setDisplayText] = useState(""); - const [currentSuggestionIndex, setCurrentSuggestionIndex] = useState(0); - const [isTyping, setIsTyping] = useState(true); - const [showCursor, setShowCursor] = useState(true); - const [lastTypedIndex, setLastTypedIndex] = useState(-1); - const [animationComplete, setAnimationComplete] = useState(false); - - // Randomize suggestions on each component mount - const [suggestions] = useState(() => { - const baseSuggestions = [ - "create a stunning portfolio!", - "build a tic tac toe game!", - "design a website for my restaurant!", - "make a sleek landing page!", - "build an e-commerce store!", - "create a personal blog!", - "develop a modern dashboard!", - "design a company website!", - "build a todo app!", - "create an online gallery!", - "make a contact form!", - "build a weather app!", - ]; - - // Fisher-Yates shuffle algorithm - const shuffled = [...baseSuggestions]; - for (let i = shuffled.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; - } - - return shuffled; - }); - - useEffect(() => { - if (animationComplete) return; - - let timeout: NodeJS.Timeout; - - const typeText = () => { - const currentSuggestion = suggestions[currentSuggestionIndex]; - - if (isTyping) { - if (displayText.length < currentSuggestion.length) { - setDisplayText(currentSuggestion.slice(0, displayText.length + 1)); - setLastTypedIndex(displayText.length); - timeout = setTimeout(typeText, 80); - } else { - // Finished typing, wait then start erasing - setLastTypedIndex(-1); - timeout = setTimeout(() => { - setIsTyping(false); - }, 2000); - } - } - }; - - timeout = setTimeout(typeText, 100); - return () => clearTimeout(timeout); - }, [ - displayText, - currentSuggestionIndex, - isTyping, - suggestions, - animationComplete, - ]); - - // Cursor blinking effect - useEffect(() => { - if (animationComplete) { - setShowCursor(false); - return; - } - - const cursorInterval = setInterval(() => { - setShowCursor((prev) => !prev); - }, 600); - - return () => clearInterval(cursorInterval); - }, [animationComplete]); - - useEffect(() => { - if (lastTypedIndex >= 0) { - const timeout = setTimeout(() => { - setLastTypedIndex(-1); - }, 400); - - return () => clearTimeout(timeout); - } - }, [lastTypedIndex]); - - return ( -

- Hey DeepSite,  - {displayText.split("").map((char, index) => ( - - {char} - - ))} - - | - -

- ); -} diff --git a/components/contexts/app-context.tsx b/components/contexts/app-context.tsx deleted file mode 100644 index 361ae64bdad3243019f9a711a2bf31843e9675e2..0000000000000000000000000000000000000000 --- a/components/contexts/app-context.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -"use client"; -import { useMount } from "react-use"; -import { toast } from "sonner"; -import { usePathname, useRouter } from "next/navigation"; - -import { useUser } from "@/hooks/useUser"; -import { ProjectType, User } from "@/types"; -import { useBroadcastChannel } from "@/lib/useBroadcastChannel"; - -export default function AppContext({ - children, - me: initialData, -}: { - children: React.ReactNode; - me?: { - user: User | null; - projects: ProjectType[]; - 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/login-context.tsx b/components/contexts/login-context.tsx deleted file mode 100644 index 2aa4842b55a7f878bc75aa39a71d7b0d68e83945..0000000000000000000000000000000000000000 --- a/components/contexts/login-context.tsx +++ /dev/null @@ -1,62 +0,0 @@ -"use client"; - -import React, { createContext, useContext, useState, ReactNode } from "react"; -import { LoginModal } from "@/components/login-modal"; -import { Page } from "@/types"; - -interface LoginContextType { - isOpen: boolean; - openLoginModal: (options?: LoginModalOptions) => void; - closeLoginModal: () => void; -} - -interface LoginModalOptions { - pages?: Page[]; - title?: string; - prompt?: string; - description?: string; -} - -const LoginContext = createContext(undefined); - -export function LoginProvider({ children }: { children: ReactNode }) { - const [isOpen, setIsOpen] = useState(false); - const [modalOptions, setModalOptions] = useState({}); - - const openLoginModal = (options: LoginModalOptions = {}) => { - setModalOptions(options); - setIsOpen(true); - }; - - const closeLoginModal = () => { - setIsOpen(false); - setModalOptions({}); - }; - - const value = { - isOpen, - openLoginModal, - closeLoginModal, - }; - - return ( - - {children} - - - ); -} - -export function useLoginModal() { - const context = useContext(LoginContext); - if (context === undefined) { - throw new Error("useLoginModal must be used within a LoginProvider"); - } - return context; -} diff --git a/components/contexts/pro-context.tsx b/components/contexts/pro-context.tsx deleted file mode 100644 index ec7c0fc0443594232efe097d1c4e7dec455eeb0d..0000000000000000000000000000000000000000 --- a/components/contexts/pro-context.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client"; - -import React, { createContext, useContext, useState, ReactNode } from "react"; -import { ProModal } from "@/components/pro-modal"; -import { Page } from "@/types"; -import { useEditor } from "@/hooks/useEditor"; - -interface ProContextType { - isOpen: boolean; - openProModal: (pages: Page[]) => void; - closeProModal: () => void; -} - -const ProContext = createContext(undefined); - -export function ProProvider({ children }: { children: ReactNode }) { - const [isOpen, setIsOpen] = useState(false); - const { pages } = useEditor(); - - const openProModal = () => { - setIsOpen(true); - }; - - const closeProModal = () => { - setIsOpen(false); - }; - - const value = { - isOpen, - openProModal, - closeProModal, - }; - - return ( - - {children} - - - ); -} - -export function useProModal() { - const context = useContext(ProContext); - if (context === undefined) { - throw new Error("useProModal must be used within a ProProvider"); - } - return context; -} diff --git a/components/contexts/tanstack-query-context.tsx b/components/contexts/tanstack-query-context.tsx deleted file mode 100644 index d0c214de91a6ce2a9711eb70e96e79152ddee4bb..0000000000000000000000000000000000000000 --- a/components/contexts/tanstack-query-context.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -import { useState } from "react"; - -export default function TanstackContext({ - children, -}: { - children: React.ReactNode; -}) { - // Create QueryClient instance only once using useState with a function - const [queryClient] = useState( - () => - new QueryClient({ - defaultOptions: { - queries: { - staleTime: 60 * 1000, // 1 minute - refetchOnWindowFocus: false, - }, - }, - }) - ); - - 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/fake-ask.tsx b/components/editor/ask-ai/fake-ask.tsx deleted file mode 100644 index c65e73ba95c1c49fdbfa35b031d361b2f6de77c3..0000000000000000000000000000000000000000 --- a/components/editor/ask-ai/fake-ask.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { useState } from "react"; -import { useLocalStorage } from "react-use"; -import { ArrowUp, Dice6 } from "lucide-react"; -import { useRouter } from "next/navigation"; - -import { Button } from "@/components/ui/button"; -import { PromptBuilder } from "./prompt-builder"; -import { EnhancedSettings } from "@/types"; -import { Settings } from "./settings"; -import classNames from "classnames"; -import { PROMPTS_FOR_AI } from "@/lib/prompts"; - -export const FakeAskAi = () => { - const router = useRouter(); - const [prompt, setPrompt] = useState(""); - const [openProvider, setOpenProvider] = useState(false); - const [enhancedSettings, setEnhancedSettings, removeEnhancedSettings] = - useLocalStorage("deepsite-enhancedSettings", { - isActive: true, - primaryColor: undefined, - secondaryColor: undefined, - theme: undefined, - }); - const [, setPromptStorage] = useLocalStorage("prompt", ""); - const [randomPromptLoading, setRandomPromptLoading] = useState(false); - - const callAi = async () => { - setPromptStorage(prompt); - router.push("/projects/new"); - }; - - const randomPrompt = () => { - setRandomPromptLoading(true); - setTimeout(() => { - setPrompt( - PROMPTS_FOR_AI[Math.floor(Math.random() * PROMPTS_FOR_AI.length)] - ); - setRandomPromptLoading(false); - }, 400); - }; - - return ( -
-
-
-