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/MCP-SERVER.md b/MCP-SERVER.md
deleted file mode 100644
index 675acdbb77beeb3bdc3e0b7b49a2e01d39df1b58..0000000000000000000000000000000000000000
--- a/MCP-SERVER.md
+++ /dev/null
@@ -1,428 +0,0 @@
-# DeepSite MCP Server
-
-DeepSite is now available as an MCP (Model Context Protocol) server, enabling AI assistants like Claude to create websites directly using natural language.
-
-## Two Ways to Use DeepSite MCP
-
-**Quick Comparison:**
-
-| Feature | Option 1: HTTP Server | Option 2: Local Server |
-|---------|----------------------|------------------------|
-| **Setup Difficulty** | β
Easy (just config) | β οΈ Requires installation |
-| **Authentication** | HF Token in config header | HF Token or session cookie in env |
-| **Best For** | Most users | Developers, custom modifications |
-| **Maintenance** | β
Always up-to-date | Need to rebuild for updates |
-
-**Recommendation:** Use Option 1 (HTTP Server) unless you need to modify the MCP server code.
-
----
-
-### π Option 1: HTTP Server (Recommended)
-
-**No installation required!** Use DeepSite's hosted MCP server.
-
-#### Setup for Claude Desktop
-
-Add to your Claude Desktop configuration file:
-
-**MacOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
-**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
-
-```json
-{
- "mcpServers": {
- "deepsite": {
- "url": "https://huggingface.co/deepsite/api/mcp",
- "transport": {
- "type": "sse"
- },
- "headers": {
- "Authorization": "Bearer hf_your_token_here"
- }
- }
- }
-}
-```
-
-**Getting Your Hugging Face Token:**
-
-1. Go to https://huggingface.co/settings/tokens
-2. Create a new token with `write` access
-3. Copy the token
-4. Add it to the `Authorization` header in your config (recommended for security)
-5. Alternatively, you can pass it as the `hf_token` parameter when using the tool
-
-**β οΈ Security Recommendation:** Use the `Authorization` header in your config instead of passing the token in chat. This keeps your token secure and out of conversation history.
-
-#### Example Usage with Claude
-
-> "Create a portfolio website using DeepSite. Include a hero section, about section, and contact form."
-
-Claude will automatically:
-1. Use the `create_project` tool
-2. Authenticate using the token from your config
-3. Create the website on Hugging Face Spaces
-4. Return the URLs to access your new site
-
----
-
-### π» Option 2: Local Server
-
-Run the MCP server locally for more control or offline use.
-
-> **Note:** Most users should use Option 1 (HTTP Server) instead. Option 2 is only needed if you want to run the MCP server locally or modify its behavior.
-
-#### Installation
-
-```bash
-cd mcp-server
-npm install
-npm run build
-```
-
-#### Setup for Claude Desktop
-
-**Method A: Using HF Token (Recommended)**
-
-```json
-{
- "mcpServers": {
- "deepsite-local": {
- "command": "node",
- "args": ["/absolute/path/to/deepsite-v3/mcp-server/dist/index.js"],
- "env": {
- "HF_TOKEN": "hf_your_token_here",
- "DEEPSITE_API_URL": "https://huggingface.co/deepsite"
- }
- }
- }
-}
-```
-
-**Method B: Using Session Cookie (Alternative)**
-
-```json
-{
- "mcpServers": {
- "deepsite-local": {
- "command": "node",
- "args": ["/absolute/path/to/deepsite-v3/mcp-server/dist/index.js"],
- "env": {
- "DEEPSITE_AUTH_COOKIE": "your-session-cookie",
- "DEEPSITE_API_URL": "https://huggingface.co/deepsite"
- }
- }
- }
-}
-```
-
-**Getting Your Session Cookie (Method B only):**
-
-1. Log in to https://huggingface.co/deepsite
-2. Open Developer Tools (F12)
-3. Go to Application β Cookies
-4. Copy the session cookie value
-5. Set as `DEEPSITE_AUTH_COOKIE` in the config
-
----
-
-## Available Tools
-
-### `create_project`
-
-Creates a new DeepSite project with HTML/CSS/JS files.
-
-**Parameters:**
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `title` | string | No | Project title (defaults to "DeepSite Project") |
-| `pages` | array | Yes | Array of file objects with `path` and `html` |
-| `prompt` | string | No | Commit message/description |
-| `hf_token` | string | No* | Hugging Face API token (*optional if provided via Authorization header in config) |
-
-**Page Object:**
-```typescript
-{
- path: string; // e.g., "index.html", "styles.css", "script.js"
- html: string; // File content
-}
-```
-
-**Returns:**
-```json
-{
- "success": true,
- "message": "Project created successfully!",
- "projectUrl": "https://huggingface.co/deepsite/username/project-name",
- "spaceUrl": "https://huggingface.co/spaces/username/project-name",
- "liveUrl": "https://username-project-name.hf.space",
- "spaceId": "username/project-name",
- "projectId": "space-id",
- "files": ["index.html", "styles.css"]
-}
-```
-
----
-
-## Example Prompts for Claude
-
-### Simple Landing Page
-> "Create a modern landing page for my SaaS product using DeepSite. Include a hero section with CTA, features grid, and footer. Use gradient background."
-
-### Portfolio Website
-> "Build a portfolio website with DeepSite. I need:
-> - Hero section with my name and photo
-> - Projects gallery with 3 sample projects
-> - Skills section with tech stack
-> - Contact form
-> Use dark mode with accent colors."
-
-### Blog Homepage
-> "Create a blog homepage using DeepSite. Include:
-> - Header with navigation
-> - Featured post section
-> - Grid of recent posts (3 cards)
-> - Sidebar with categories
-> - Footer with social links
-> Clean, minimal design."
-
-### Interactive Dashboard
-> "Make an analytics dashboard with DeepSite:
-> - Sidebar navigation
-> - 4 metric cards at top
-> - 2 chart placeholders
-> - Data table
-> - Modern, professional UI with charts.css"
-
----
-
-## Direct API Usage
-
-You can also call the HTTP endpoint directly:
-
-### Using Authorization Header (Recommended)
-
-```bash
-curl -X POST https://huggingface.co/deepsite/api/mcp \
- -H "Content-Type: application/json" \
- -H "Authorization: Bearer hf_your_token_here" \
- -d '{
- "jsonrpc": "2.0",
- "id": 1,
- "method": "tools/call",
- "params": {
- "name": "create_project",
- "arguments": {
- "title": "My Website",
- "pages": [
- {
- "path": "index.html",
- "html": "
Hello Hello World! "
- }
- ]
- }
- }
- }'
-```
-
-### Using Token Parameter (Fallback)
-
-```bash
-curl -X POST https://huggingface.co/deepsite/api/mcp \
- -H "Content-Type: application/json" \
- -d '{
- "jsonrpc": "2.0",
- "id": 1,
- "method": "tools/call",
- "params": {
- "name": "create_project",
- "arguments": {
- "title": "My Website",
- "pages": [
- {
- "path": "index.html",
- "html": "Hello Hello World! "
- }
- ],
- "hf_token": "hf_xxxxx"
- }
- }
- }'
-```
-
-### List Available Tools
-
-```bash
-curl -X POST https://huggingface.co/deepsite/api/mcp \
- -H "Content-Type: application/json" \
- -d '{
- "jsonrpc": "2.0",
- "id": 1,
- "method": "tools/list",
- "params": {}
- }'
-```
-
----
-
-## Testing
-
-### Test Local Server
-
-```bash
-cd mcp-server
-./test.sh
-```
-
-### Test HTTP Server
-
-```bash
-curl -X POST https://huggingface.co/deepsite/api/mcp \
- -H "Content-Type: application/json" \
- -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
-```
-
----
-
-## Migration Guide: From Parameter to Header Auth
-
-If you're currently passing the token as a parameter in your prompts, here's how to migrate to the more secure header-based authentication:
-
-### Step 1: Update Your Config
-
-Edit your Claude Desktop config file and add the `headers` section:
-
-```json
-{
- "mcpServers": {
- "deepsite": {
- "url": "https://huggingface.co/deepsite/api/mcp",
- "transport": {
- "type": "sse"
- },
- "headers": {
- "Authorization": "Bearer hf_your_actual_token_here"
- }
- }
- }
-}
-```
-
-### Step 2: Restart Claude Desktop
-
-Completely quit and restart Claude Desktop for the changes to take effect.
-
-### Step 3: Use Simpler Prompts
-
-Now you can simply say:
-> "Create a portfolio website with DeepSite"
-
-Instead of:
-> "Create a portfolio website with DeepSite using token `hf_xxxxx`"
-
-Your token is automatically included in all requests via the header!
-
----
-
-## Security Notes
-
-### HTTP Server (Option 1)
-- **β
Recommended:** Store your HF token in the `Authorization` header in your Claude Desktop config
-- The token is stored locally on your machine and never exposed in chat
-- The token is sent with each request but only used to authenticate with Hugging Face API
-- DeepSite does not store your token
-- Use tokens with minimal required permissions (write access to spaces)
-- You can revoke tokens anytime at https://huggingface.co/settings/tokens
-- **β οΈ Fallback:** You can still pass the token as a parameter, but this is less secure as it appears in conversation history
-
-### Local Server (Option 2)
-- Use `HF_TOKEN` environment variable (same security as Option 1)
-- Or use `DEEPSITE_AUTH_COOKIE` if you prefer session-based auth
-- All authentication data stays on your local machine
-- Better for development and testing
-- No need for both HTTP Server and Local Server - choose one!
-
----
-
-## Troubleshooting
-
-### "Invalid Hugging Face token"
-- Verify your token at https://huggingface.co/settings/tokens
-- Ensure the token has write permissions
-- Check that you copied the full token (starts with `hf_`)
-
-### "At least one page is required"
-- Make sure you're providing the `pages` array
-- Each page must have both `path` and `html` properties
-
-### "Failed to create project"
-- Check your token permissions
-- Ensure the project title doesn't conflict with existing spaces
-- Verify your Hugging Face account is in good standing
-
-### Claude doesn't see the tool
-- Restart Claude Desktop after modifying the config
-- Check that the JSON config is valid (no trailing commas)
-- For HTTP: verify the URL is correct
-- For local: check the absolute path to index.js
-
----
-
-## Architecture
-
-### HTTP Server Flow
-```
-Claude Desktop
- β
- (HTTP Request)
- β
-huggingface.co/deepsite/api/mcp
- β
-Hugging Face API (with user's token)
- β
-New Space Created
- β
-URLs returned to Claude
-```
-
-### Local Server Flow
-```
-Claude Desktop
- β
- (stdio transport)
- β
-Local MCP Server
- β
- (HTTP to DeepSite API)
- β
-huggingface.co/deepsite/api/me/projects
- β
-New Space Created
-```
-
----
-
-## Contributing
-
-The MCP server implementation lives in:
-- HTTP Server: `/app/api/mcp/route.ts`
-- Local Server: `/mcp-server/index.ts`
-
-Both use the same core DeepSite logic for creating projects - no duplication!
-
----
-
-## License
-
-MIT
-
----
-
-## Resources
-
-- [Model Context Protocol Spec](https://modelcontextprotocol.io/)
-- [DeepSite Documentation](https://huggingface.co/deepsite)
-- [Hugging Face Spaces](https://huggingface.co/docs/hub/spaces)
-- [Claude Desktop](https://claude.ai/desktop)
-
diff --git a/README.md b/README.md
index 9154f9026a5ccedb6d9aa26c2b34462372d7741b..5d5239cffd688fecefc35757a3f74ff155a1b47b 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,13 @@
---
-title: DeepSite v3
+title: DeepSite
emoji: π³
colorFrom: blue
colorTo: blue
sdk: docker
pinned: true
-app_port: 3000
+app_port: 5173
license: mit
-failure_strategy: rollback
-short_description: Generate any application by Vibe Coding
-models:
- - deepseek-ai/DeepSeek-V3-0324
- - deepseek-ai/DeepSeek-R1-0528
- - deepseek-ai/DeepSeek-V3.1
- - deepseek-ai/DeepSeek-V3.1-Terminus
- - deepseek-ai/DeepSeek-V3.2-Exp
- - Qwen/Qwen3-Coder-480B-A35B-Instruct
- - moonshotai/Kimi-K2-Instruct
- - moonshotai/Kimi-K2-Instruct-0905
- - zai-org/GLM-4.6
- - MiniMaxAI/MiniMax-M2
- - moonshotai/Kimi-K2-Thinking
+short_description: Imagine and Share in 1-Click
---
-# DeepSite π³
-
-DeepSite is a Vibe Coding Platform 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.
+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 (
-
- );
-}
diff --git a/app/(public)/page.tsx b/app/(public)/page.tsx
deleted file mode 100644
index e2d5c88a4977801d6acabd904535b00ad622a267..0000000000000000000000000000000000000000
--- a/app/(public)/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import { MyProjects } from "@/components/my-projects";
-
-export default async function HomePage() {
- return ;
-}
diff --git a/app/[namespace]/[repoId]/page.tsx b/app/[namespace]/[repoId]/page.tsx
deleted file mode 100644
index 19a09d72477dd437fa8a786601cbc584676609ee..0000000000000000000000000000000000000000
--- a/app/[namespace]/[repoId]/page.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { AppEditor } from "@/components/editor";
-import { generateSEO } from "@/lib/seo";
-import { Metadata } from "next";
-
-export async function generateMetadata({
- params,
-}: {
- params: Promise<{ namespace: string; repoId: string }>;
-}): Promise {
- const { namespace, repoId } = await params;
-
- return generateSEO({
- title: `${namespace}/${repoId} - DeepSite Editor`,
- description: `Edit and build ${namespace}/${repoId} with AI-powered tools on DeepSite. Create stunning websites with no code required.`,
- path: `/${namespace}/${repoId}`,
- // Prevent indexing of individual project editor pages if they contain sensitive content
- noIndex: false, // Set to true if you want to keep project pages private
- });
-}
-
-export default async function ProjectNamespacePage({
- params,
-}: {
- params: Promise<{ namespace: string; repoId: string }>;
-}) {
- const { namespace, repoId } = await params;
- return ;
-}
diff --git a/app/actions/auth.ts b/app/actions/auth.ts
deleted file mode 100644
index b0028105705dde5ace29f4484398edbd0e9c9dc0..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 +
- "/deepsite/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 d03e1a136f6a5b98ff976d1861d0e1f5aeae6000..0000000000000000000000000000000000000000
--- a/app/api/ask/route.ts
+++ /dev/null
@@ -1,394 +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 {
- FOLLOW_UP_SYSTEM_PROMPT,
- FOLLOW_UP_SYSTEM_PROMPT_LIGHT,
- INITIAL_SYSTEM_PROMPT,
- INITIAL_SYSTEM_PROMPT_LIGHT,
- MAX_REQUESTS_PER_IP,
- PROMPT_FOR_PROJECT_NAME,
-} from "@/lib/prompts";
-import MY_TOKEN_KEY from "@/lib/get-cookie-name";
-import { Page } from "@/types";
-import { isAuthenticated } from "@/lib/auth";
-import { getBestProvider } from "@/lib/best-provider";
-
-const ipAddresses = new Map();
-
-export async function POST(request: NextRequest) {
- const authHeaders = await headers();
- const tokenInHeaders = authHeaders.get("Authorization");
- const userToken = tokenInHeaders ? tokenInHeaders.replace("Bearer ", "") : 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 }
- );
- }
-
- 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 ||Β token === "null" || 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";
- }
-
- 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 () => {
- try {
- const client = new InferenceClient(token);
-
- const systemPrompt = selectedModel.value.includes('MiniMax')
- ? INITIAL_SYSTEM_PROMPT_LIGHT
- : INITIAL_SYSTEM_PROMPT;
-
- const userPrompt = prompt;
-
- const chatCompletion = client.chatCompletionStream(
- {
- model: selectedModel.value + (provider !== "auto" ? `:${provider}` : ""),
- messages: [
- {
- role: "system",
- content: systemPrompt,
- },
- ...(redesignMarkdown ? [{
- role: "assistant",
- content: `User will ask you to redesign the site based on this markdown. Use the same images as the site, but you can improve the content and the design. Here is the markdown: ${redesignMarkdown}`
- }] : []),
- {
- 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.` : "")
- },
- ],
- ...(selectedModel.top_k ? { top_k: selectedModel.top_k } : {}),
- ...(selectedModel.temperature ? { temperature: selectedModel.temperature } : {}),
- ...(selectedModel.top_p ? { top_p: selectedModel.top_p } : {}),
- max_tokens: 16_384,
- },
- 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));
- }
- }
-
- await writer.close();
- } catch (error: any) {
- console.error(error);
- 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 {
- try {
- await writer?.close();
- } catch {
- }
- }
- })();
-
- 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, provider, selectedElementHtml, model, pages, files, repoId, isNew } =
- body;
-
- 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 ||Β token === "null" || 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";
- }
-
- 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 () => {
- try {
- const client = new InferenceClient(token);
-
- const basePrompt = selectedModel.value.includes('MiniMax')
- ? FOLLOW_UP_SYSTEM_PROMPT_LIGHT
- : FOLLOW_UP_SYSTEM_PROMPT;
- const systemPrompt = basePrompt + (isNew ? PROMPT_FOR_PROJECT_NAME : "");
- // const userContext = "You are modifying the HTML file based on the user's request.";
-
- const allPages = pages || [];
- const pagesContext = allPages
- .map((p: Page) => `- ${p.path}\n${p.html}`)
- .join("\n\n");
-
- 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 (${allPages.length} total): ${pagesContext}. ${files?.length > 0 ? `Available images: ${files?.map((f: string) => f).join(', ')}.` : ""}`;
-
- const chatCompletion = client.chatCompletionStream(
- {
- model: selectedModel.value + (provider !== "auto" ? `:${provider}` : ""),
- messages: [
- {
- role: "system",
- content: systemPrompt + assistantContext
- },
- {
- role: "user",
- content: prompt,
- },
- ],
- ...(selectedModel.top_k ? { top_k: selectedModel.top_k } : {}),
- ...(selectedModel.temperature ? { temperature: selectedModel.temperature } : {}),
- ...(selectedModel.top_p ? { top_p: selectedModel.top_p } : {}),
- max_tokens: 16_384,
- },
- billTo ? { billTo } : {}
- );
-
- // Stream the response chunks to the client
- 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));
- }
- }
-
- await writer.write(encoder.encode(`\n___METADATA_START___\n${JSON.stringify({
- repoId,
- isNew,
- userName: user.name,
- })}\n___METADATA_END___\n`));
-
- 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 {
- try {
- await writer?.close();
- } catch {
- // ignore
- }
- }
- })();
-
- return response;
- } catch (error: any) {
- 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 e46df350e48e088ad278dec248f55651cdefd027..0000000000000000000000000000000000000000
--- a/app/api/auth/login-url/route.ts
+++ /dev/null
@@ -1,21 +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 {
- url = "huggingface.co";
- }
-
- const redirect_uri =
- `${host.includes("localhost") ? "http://" : "https://"}` +
- url +
- "/deepsite/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 f826af35fa35c5767a318d1e146e77fb6fc07592..0000000000000000000000000000000000000000
--- a/app/api/auth/route.ts
+++ /dev/null
@@ -1,86 +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 url = host.includes("/spaces/enzostvs")
- ? "huggingface.co/deepsite"
- : host;
- const redirect_uri =
- `${host.includes("localhost") ? "http://" : "https://"}` +
- url +
- "/deepsite/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/mcp/route.ts b/app/api/mcp/route.ts
deleted file mode 100644
index be92c97380e2ad66379edede9fe07b96f697ff49..0000000000000000000000000000000000000000
--- a/app/api/mcp/route.ts
+++ /dev/null
@@ -1,435 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-import { RepoDesignation, createRepo, uploadFiles, spaceInfo, listCommits } from "@huggingface/hub";
-import { COLORS } from "@/lib/utils";
-import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
-import { Commit, Page } from "@/types";
-
-// Timeout configuration (in milliseconds)
-const OPERATION_TIMEOUT = 120000; // 2 minutes for HF operations
-
-// Extend the maximum execution time for this route
-export const maxDuration = 180; // 3 minutes
-
-// Utility function to wrap promises with timeout
-async function withTimeout(
- promise: Promise,
- timeoutMs: number,
- errorMessage: string = "Operation timed out"
-): Promise {
- let timeoutId: NodeJS.Timeout;
-
- const timeoutPromise = new Promise((_, reject) => {
- timeoutId = setTimeout(() => {
- reject(new Error(errorMessage));
- }, timeoutMs);
- });
-
- try {
- const result = await Promise.race([promise, timeoutPromise]);
- clearTimeout(timeoutId!);
- return result;
- } catch (error) {
- clearTimeout(timeoutId!);
- throw error;
- }
-}
-
-interface MCPRequest {
- jsonrpc: "2.0";
- id: number | string;
- method: string;
- params?: any;
-}
-
-interface MCPResponse {
- jsonrpc: "2.0";
- id: number | string;
- result?: any;
- error?: {
- code: number;
- message: string;
- data?: any;
- };
-}
-
-interface CreateProjectParams {
- title?: string;
- pages: Page[];
- prompt?: string;
- hf_token?: string; // Optional - can come from header instead
-}
-
-// MCP Server over HTTP
-export async function POST(req: NextRequest) {
- try {
- const body: MCPRequest = await req.json();
- const { jsonrpc, id, method, params } = body;
-
- // Validate JSON-RPC 2.0 format
- if (jsonrpc !== "2.0") {
- return NextResponse.json({
- jsonrpc: "2.0",
- id: id || null,
- error: {
- code: -32600,
- message: "Invalid Request: jsonrpc must be '2.0'",
- },
- });
- }
-
- let response: MCPResponse;
-
- switch (method) {
- case "initialize":
- response = {
- jsonrpc: "2.0",
- id,
- result: {
- protocolVersion: "2024-11-05",
- capabilities: {
- tools: {},
- },
- serverInfo: {
- name: "deepsite-mcp-server",
- version: "1.0.0",
- },
- },
- };
- break;
-
- case "tools/list":
- response = {
- jsonrpc: "2.0",
- id,
- result: {
- tools: [
- {
- name: "create_project",
- description: `Create a new DeepSite project. This will create a new Hugging Face Space with your HTML/CSS/JS files.
-
-Example usage:
-- Create a simple website with HTML, CSS, and JavaScript files
-- Each page needs a 'path' (filename like "index.html", "styles.css", "script.js") and 'html' (the actual content)
-- The title will be formatted to a valid repository name
-- Returns the project URL and metadata`,
- inputSchema: {
- type: "object",
- properties: {
- title: {
- type: "string",
- description: "Project title (optional, defaults to 'DeepSite Project'). Will be formatted to a valid repo name.",
- },
- pages: {
- type: "array",
- description: "Array of files to include in the project",
- items: {
- type: "object",
- properties: {
- path: {
- type: "string",
- description: "File path (e.g., 'index.html', 'styles.css', 'script.js')",
- },
- html: {
- type: "string",
- description: "File content",
- },
- },
- required: ["path", "html"],
- },
- },
- prompt: {
- type: "string",
- description: "Optional prompt/description for the commit message",
- },
- hf_token: {
- type: "string",
- description: "Hugging Face API token (optional if provided via Authorization header)",
- },
- },
- required: ["pages"],
- },
- },
- ],
- },
- };
- break;
-
- case "tools/call":
- const { name, arguments: toolArgs } = params;
-
- if (name === "create_project") {
- try {
- // Extract token from Authorization header if present
- const authHeader = req.headers.get("authorization");
- let hf_token = toolArgs.hf_token;
-
- if (authHeader && authHeader.startsWith("Bearer ")) {
- hf_token = authHeader.substring(7); // Remove "Bearer " prefix
- }
-
- const result = await handleCreateProject({
- ...toolArgs,
- hf_token,
- } as CreateProjectParams);
- response = {
- jsonrpc: "2.0",
- id,
- result,
- };
- } catch (error: any) {
- response = {
- jsonrpc: "2.0",
- id,
- error: {
- code: -32000,
- message: error.message || "Failed to create project",
- data: error.data,
- },
- };
- }
- } else {
- response = {
- jsonrpc: "2.0",
- id,
- error: {
- code: -32601,
- message: `Unknown tool: ${name}`,
- },
- };
- }
- break;
-
- default:
- response = {
- jsonrpc: "2.0",
- id,
- error: {
- code: -32601,
- message: `Method not found: ${method}`,
- },
- };
- }
-
- return NextResponse.json(response);
- } catch (error: any) {
- return NextResponse.json({
- jsonrpc: "2.0",
- id: null,
- error: {
- code: -32700,
- message: "Parse error",
- data: error.message,
- },
- });
- }
-}
-
-// Handle OPTIONS for CORS
-export async function OPTIONS() {
- return new NextResponse(null, {
- status: 200,
- headers: {
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Methods": "POST, OPTIONS",
- "Access-Control-Allow-Headers": "Content-Type",
- },
- });
-}
-
-async function handleCreateProject(params: CreateProjectParams) {
- const { title: titleFromRequest, pages, prompt, hf_token } = params;
-
- // Validate required parameters
- if (!hf_token || typeof hf_token !== "string") {
- throw new Error("hf_token is required and must be a string");
- }
-
- if (!pages || !Array.isArray(pages) || pages.length === 0) {
- throw new Error("At least one page is required");
- }
-
- // Validate that each page has required fields
- for (const page of pages) {
- if (!page.path || !page.html) {
- throw new Error("Each page must have 'path' and 'html' properties");
- }
- }
-
- // Get user info from HF token
- let username: string;
- try {
- const userResponse = await withTimeout(
- fetch("https://huggingface.co/api/whoami-v2", {
- headers: {
- Authorization: `Bearer ${hf_token}`,
- },
- }),
- 30000, // 30 seconds for authentication
- "Authentication timeout: Unable to verify Hugging Face token"
- );
-
- if (!userResponse.ok) {
- throw new Error("Invalid Hugging Face token");
- }
-
- const userData = await userResponse.json();
- username = userData.name;
- } catch (error: any) {
- if (error.message?.includes('timeout')) {
- throw new Error(`Authentication timeout: ${error.message}`);
- }
- throw new Error(`Authentication failed: ${error.message}`);
- }
-
- 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: `${username}/${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://huggingface.co/deepsite).
-`;
-
- const files: File[] = [];
- const readmeFile = new File([README], "README.md", { type: "text/markdown" });
- files.push(readmeFile);
-
- pages.forEach((page: Page) => {
- // Determine MIME type based on file extension
- let mimeType = "text/html";
- if (page.path.endsWith(".css")) {
- mimeType = "text/css";
- } else if (page.path.endsWith(".js")) {
- mimeType = "text/javascript";
- } else if (page.path.endsWith(".json")) {
- mimeType = "application/json";
- }
-
- // Inject the DeepSite badge script into index pages only
- const content = mimeType === "text/html" && isIndexPage(page.path)
- ? injectDeepSiteBadge(page.html)
- : page.html;
- const file = new File([content], page.path, { type: mimeType });
- files.push(file);
- });
-
- try {
- const { repoUrl } = await withTimeout(
- createRepo({
- repo,
- accessToken: hf_token,
- }),
- 60000, // 1 minute for repo creation
- "Timeout creating repository. Please try again."
- );
-
- const commitTitle = !prompt || prompt.trim() === "" ? "Initial project creation via MCP" : prompt;
-
- await withTimeout(
- uploadFiles({
- repo,
- files,
- accessToken: hf_token,
- commitTitle,
- }),
- OPERATION_TIMEOUT,
- "Timeout uploading files. The repository was created but files may not have been uploaded."
- );
-
- const path = repoUrl.split("/").slice(-2).join("/");
-
- const commits: Commit[] = [];
- const commitIterator = listCommits({ repo, accessToken: hf_token });
-
- // Wrap the commit listing with a timeout
- const commitTimeout = new Promise((_, reject) => {
- setTimeout(() => reject(new Error("Timeout listing commits")), 30000);
- });
-
- try {
- await Promise.race([
- (async () => {
- for await (const commit of commitIterator) {
- 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,
- });
- }
- })(),
- commitTimeout
- ]);
- } catch (error: any) {
- // If listing commits times out, continue with empty commits array
- console.error("Failed to list commits:", error.message);
- }
-
- const space = await withTimeout(
- spaceInfo({
- name: repo.name,
- accessToken: hf_token,
- }),
- 30000, // 30 seconds for space info
- "Timeout fetching space information"
- );
-
- const projectUrl = `https://huggingface.co/deepsite/${path}`;
- const spaceUrl = `https://huggingface.co/spaces/${path}`;
- const liveUrl = `https://${username}-${formattedTitle}.hf.space`;
-
- return {
- content: [
- {
- type: "text",
- text: JSON.stringify(
- {
- success: true,
- message: "Project created successfully!",
- projectUrl,
- spaceUrl,
- liveUrl,
- spaceId: space.name,
- projectId: space.id,
- files: pages.map((p) => p.path),
- updatedAt: space.updatedAt,
- },
- null,
- 2
- ),
- },
- ],
- };
- } catch (err: any) {
- if (err.message?.includes('timeout') || err.message?.includes('Timeout')) {
- throw new Error(err.message || "Operation timed out. Please try again.");
- }
- throw new Error(err.message || "Failed to create project");
- }
-}
-
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 d62b38711819e9b9888fb186c825f334ff5c8f2c..0000000000000000000000000000000000000000
--- a/app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/promote/route.ts
+++ /dev/null
@@ -1,240 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-import { RepoDesignation, listFiles, spaceInfo, uploadFiles, deleteFiles, downloadFile } 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 }
- );
- }
-
- const files: File[] = [];
- const pages: Page[] = [];
- const mediaFiles: string[] = [];
- const allowedExtensions = ["html", "md", "css", "js", "json", "txt"];
- const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif", "mp4", "webm", "ogg", "avi", "mov", "mp3", "wav", "ogg", "aac", "m4a"];
- const commitFilePaths: Set = new Set();
-
- for await (const fileInfo of listFiles({
- repo,
- accessToken: user.token as string,
- revision: commitId,
- })) {
- const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase();
-
- if (fileInfo.path.endsWith(".html") || fileInfo.path.endsWith(".css") || fileInfo.path.endsWith(".js") || fileInfo.path.endsWith(".json")) {
- commitFilePaths.add(fileInfo.path);
-
- const blob = await downloadFile({
- repo,
- accessToken: user.token as string,
- path: fileInfo.path,
- revision: commitId,
- raw: true
- }).catch((error) => {
- return null;
- });
- if (!blob) {
- continue;
- }
- const content = await blob?.text();
-
- if (content) {
- let mimeType = "text/plain";
-
- switch (fileExtension) {
- case "html":
- mimeType = "text/html";
- break;
- case "css":
- mimeType = "text/css";
- break;
- case "js":
- mimeType = "application/javascript";
- break;
- case "json":
- mimeType = "application/json";
- break;
- }
-
- if (fileInfo.path === "index.html") {
- pages.unshift({
- path: fileInfo.path,
- html: content,
- });
- } else {
- pages.push({
- path: fileInfo.path,
- html: content,
- });
- }
-
- const file = new File([content], fileInfo.path, { type: mimeType });
- files.push(file);
- }
- }
- else if (fileInfo.type === "directory" && (["videos", "images", "audio"].includes(fileInfo.path) || fileInfo.path === "components")) {
- for await (const subFileInfo of listFiles({
- repo,
- accessToken: user.token as string,
- revision: commitId,
- path: fileInfo.path,
- })) {
- if (subFileInfo.path.includes("components")) {
- commitFilePaths.add(subFileInfo.path);
- const blob = await downloadFile({
- repo,
- accessToken: user.token as string,
- path: subFileInfo.path,
- revision: commitId,
- raw: true
- }).catch((error) => {
- return null;
- });
- if (!blob) {
- continue;
- }
- const content = await blob?.text();
-
- if (content) {
- pages.push({
- path: subFileInfo.path,
- html: content,
- });
-
- const file = new File([content], subFileInfo.path, { type: "text/html" });
- files.push(file);
- }
- } else if (allowedFilesExtensions.includes(subFileInfo.path.split(".").pop() || "")) {
- commitFilePaths.add(subFileInfo.path);
- mediaFiles.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${subFileInfo.path}`);
- }
- }
- }
- else if (allowedExtensions.includes(fileExtension || "")) {
- commitFilePaths.add(fileInfo.path);
- }
- }
-
- 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);
- }
- }
-
- 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 }
- );
- }
-
- 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')}`,
- });
- }
-
- 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,
- files: mediaFiles,
- },
- { 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]/commits/[commitId]/route.ts b/app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/route.ts
deleted file mode 100644
index 993215f7558d36bcb38f566650b0469f40876686..0000000000000000000000000000000000000000
--- a/app/api/me/projects/[namespace]/[repoId]/commits/[commitId]/route.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-import { RepoDesignation, listFiles, spaceInfo, downloadFile } from "@huggingface/hub";
-
-import { isAuthenticated } from "@/lib/auth";
-import { Page } from "@/types";
-
-export async function GET(
- 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 }
- );
- }
-
- const pages: Page[] = [];
-
- for await (const fileInfo of listFiles({
- repo,
- accessToken: user.token as string,
- revision: commitId,
- })) {
- const fileExtension = fileInfo.path.split('.').pop()?.toLowerCase();
-
- if (fileInfo.path.endsWith(".html") || fileInfo.path.endsWith(".css") || fileInfo.path.endsWith(".js") || fileInfo.path.endsWith(".json")) {
- const blob = await downloadFile({
- repo,
- accessToken: user.token as string,
- path: fileInfo.path,
- revision: commitId,
- raw: true
- }).catch((error) => {
- return null;
- });
- if (!blob) {
- continue;
- }
- const content = await blob?.text();
-
- if (content) {
- if (fileInfo.path === "index.html") {
- pages.unshift({
- path: fileInfo.path,
- html: content,
- });
- } else {
- pages.push({
- path: fileInfo.path,
- html: content,
- });
- }
- }
- }
- }
-
- return NextResponse.json({
- ok: true,
- pages,
- });
- } catch (error: any) {
- console.error("Error fetching commit pages:", error);
- return NextResponse.json(
- {
- ok: false,
- error: error.message || "Failed to fetch commit pages",
- },
- { status: 500 }
- );
- }
-}
-
diff --git a/app/api/me/projects/[namespace]/[repoId]/download/route.ts b/app/api/me/projects/[namespace]/[repoId]/download/route.ts
deleted file mode 100644
index 2c731029dfa09b56a6e4245a83c2518842e7f7a6..0000000000000000000000000000000000000000
--- a/app/api/me/projects/[namespace]/[repoId]/download/route.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-import { RepoDesignation, listFiles, spaceInfo, downloadFile } from "@huggingface/hub";
-import JSZip from "jszip";
-
-import { isAuthenticated } from "@/lib/auth";
-
-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 zip = new JSZip();
-
- for await (const fileInfo of listFiles({
- repo,
- accessToken: user.token as string,
- recursive: true,
- })) {
- if (fileInfo.type === "directory" || fileInfo.path.startsWith(".")) {
- continue;
- }
-
- try {
- const blob = await downloadFile({
- repo,
- accessToken: user.token as string,
- path: fileInfo.path,
- raw: true
- }).catch((error) => {
- return null;
- });
- if (!blob) {
- continue;
- }
-
- if (blob) {
- const arrayBuffer = await blob.arrayBuffer();
- zip.file(fileInfo.path, arrayBuffer);
- }
- } catch (error) {
- console.error(`Error downloading file ${fileInfo.path}:`, error);
- }
- }
-
- const zipBlob = await zip.generateAsync({
- type: "blob",
- compression: "DEFLATE",
- compressionOptions: {
- level: 6
- }
- });
-
- const projectName = `${namespace}-${repoId}`.replace(/[^a-zA-Z0-9-_]/g, '_');
- const filename = `${projectName}.zip`;
-
- return new NextResponse(zipBlob, {
- headers: {
- "Content-Type": "application/zip",
- "Content-Disposition": `attachment; filename="${filename}"`,
- "Content-Length": zipBlob.size.toString(),
- },
- });
- } catch (error: any) {
- return NextResponse.json(
- { ok: false, error: error.message || "Failed to create ZIP file" },
- { 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 d91aa3c8a33b9cc27c675289d902f168df79cdc4..0000000000000000000000000000000000000000
--- a/app/api/me/projects/[namespace]/[repoId]/images/route.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-import { RepoDesignation, spaceInfo, uploadFiles } from "@huggingface/hub";
-
-import { isAuthenticated } from "@/lib/auth";
-
-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 media files
- const formData = await req.formData();
- const mediaFiles = formData.getAll("images") as File[];
-
- if (!mediaFiles || mediaFiles.length === 0) {
- return NextResponse.json(
- {
- ok: false,
- error: "At least one media file is required under the 'images' key",
- },
- { status: 400 }
- );
- }
-
- const files: File[] = [];
- for (const file of mediaFiles) {
- if (!(file instanceof File)) {
- return NextResponse.json(
- {
- ok: false,
- error: "Invalid file format - all items under 'images' key must be files",
- },
- { status: 400 }
- );
- }
-
- // Check if file is a supported media type
- const isImage = file.type.startsWith('image/');
- const isVideo = file.type.startsWith('video/');
- const isAudio = file.type.startsWith('audio/');
-
- if (!isImage && !isVideo && !isAudio) {
- return NextResponse.json(
- {
- ok: false,
- error: `File ${file.name} is not a supported media type (image, video, or audio)`,
- },
- { status: 400 }
- );
- }
-
- // Create File object with appropriate folder prefix
- let folderPrefix = 'images/';
- if (isVideo) {
- folderPrefix = 'videos/';
- } else if (isAudio) {
- folderPrefix = 'audio/';
- }
-
- const fileName = `${folderPrefix}${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} media file(s)`,
- });
-
- return NextResponse.json({
- ok: true,
- message: `Successfully uploaded ${files.length} media file(s) to ${namespace}/${repoId}/`,
- uploadedFiles: files.map((file) => `https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${file.name}`),
- }, { status: 200 });
-
- } catch (error) {
- console.error('Error uploading media files:', error);
- return NextResponse.json(
- {
- ok: false,
- error: "Failed to upload media files",
- },
- { 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 a6034cb3f27016817e14b55682c92a3ed9134223..0000000000000000000000000000000000000000
--- a/app/api/me/projects/[namespace]/[repoId]/route.ts
+++ /dev/null
@@ -1,207 +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", "mp4", "webm", "ogg", "avi", "mov", "mp3", "wav", "ogg", "aac", "m4a"];
-
- for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) {
- if (fileInfo.path.endsWith(".html") || fileInfo.path.endsWith(".css") || fileInfo.path.endsWith(".js") || fileInfo.path.endsWith(".json")) {
- const blob = await downloadFile({ repo, accessToken: user.token as string, path: fileInfo.path, raw: true }).catch((error) => {
- return null;
- });
- if (!blob) {
- continue;
- }
- 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") {
- for await (const subFileInfo of listFiles({repo, accessToken: user.token as string, path: fileInfo.path})) {
- if (allowedFilesExtensions.includes(subFileInfo.path.split(".").pop() || "")) {
- files.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${subFileInfo.path}`);
- } else {
- const blob = await downloadFile({ repo, accessToken: user.token as string, path: subFileInfo.path, raw: true }).catch((error) => {
- return null;
- });
- if (!blob) {
- continue;
- }
- const html = await blob?.text();
- if (!html) {
- continue;
- }
- htmlFiles.push({
- path: subFileInfo.path,
- html,
- });
- }
- }
- }
- }
- 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 d0a99d0c14b299872f4f9c8c874769bd198894ee..0000000000000000000000000000000000000000
--- a/app/api/me/projects/[namespace]/[repoId]/save/route.ts
+++ /dev/null
@@ -1,72 +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) => {
- // Determine MIME type based on file extension
- let mimeType = "text/html";
- if (page.path.endsWith(".css")) {
- mimeType = "text/css";
- } else if (page.path.endsWith(".js")) {
- mimeType = "text/javascript";
- } else if (page.path.endsWith(".json")) {
- mimeType = "application/json";
- }
- const file = new File([page.html], page.path, { type: mimeType });
- files.push(file);
- });
-
- 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/[namespace]/[repoId]/update/route.ts b/app/api/me/projects/[namespace]/[repoId]/update/route.ts
deleted file mode 100644
index dad31b4e988a5ce815ba90ce76186a0ec4f00b1c..0000000000000000000000000000000000000000
--- a/app/api/me/projects/[namespace]/[repoId]/update/route.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub";
-
-import { isAuthenticated } from "@/lib/auth";
-import { Page } from "@/types";
-import { COLORS } from "@/lib/utils";
-import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
-import { pagesToFiles } from "@/lib/format-ai-response";
-
-/**
- * UPDATE route - for updating existing projects or creating new ones after AI streaming
- * This route handles the HuggingFace upload after client-side AI response processing
- */
-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;
- let { namespace, repoId } = param;
- const { pages, commitTitle = "AI-generated changes", isNew, projectName } = await req.json();
-
- if (!pages || !Array.isArray(pages) || pages.length === 0) {
- return NextResponse.json(
- { ok: false, error: "Pages are required" },
- { status: 400 }
- );
- }
-
- try {
- let files: File[];
-
- if (isNew) {
- // Creating a new project
- const title = projectName || "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}`,
- };
-
- try {
- const { repoUrl } = await createRepo({
- repo,
- accessToken: user.token as string,
- });
- namespace = user.name;
- repoId = repoUrl.split("/").slice(-2).join("/").split("/")[1];
- } catch (createRepoError: any) {
- return NextResponse.json(
- {
- ok: false,
- error: `Failed to create repository: ${createRepoError.message || 'Unknown error'}`,
- },
- { status: 500 }
- );
- }
-
- // Prepare files with badge injection for new projects
- files = [];
- pages.forEach((page: Page) => {
- let mimeType = "text/html";
- if (page.path.endsWith(".css")) {
- mimeType = "text/css";
- } else if (page.path.endsWith(".js")) {
- mimeType = "text/javascript";
- } else if (page.path.endsWith(".json")) {
- mimeType = "application/json";
- }
- const content = (mimeType === "text/html" && isIndexPage(page.path))
- ? injectDeepSiteBadge(page.html)
- : page.html;
- const file = new File([content], page.path, { type: mimeType });
- files.push(file);
- });
-
- // Add README.md for new projects
- 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://huggingface.co/deepsite).
-`;
- files.push(new File([README], "README.md", { type: "text/markdown" }));
- } else {
- // Updating existing project - no badge injection
- files = pagesToFiles(pages);
- }
-
- const response = await uploadFiles({
- repo: {
- type: "space",
- name: `${namespace}/${repoId}`,
- },
- files,
- commitTitle,
- accessToken: user.token as string,
- });
-
- return NextResponse.json({
- ok: true,
- pages,
- repoId: `${namespace}/${repoId}`,
- commit: {
- ...response.commit,
- title: commitTitle,
- }
- });
- } catch (error: any) {
- console.error("Error updating project:", error);
- return NextResponse.json(
- {
- ok: false,
- error: error.message || "Failed to update project",
- },
- { status: 500 }
- );
- }
-}
-
diff --git a/app/api/me/projects/route.ts b/app/api/me/projects/route.ts
deleted file mode 100644
index dec5b655708390a0779bb2917defee6ab1bb02b2..0000000000000000000000000000000000000000
--- a/app/api/me/projects/route.ts
+++ /dev/null
@@ -1,121 +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";
-import { injectDeepSiteBadge, isIndexPage } from "@/lib/inject-badge";
-
-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://huggingface.co/deepsite).
-`;
-
- const files: File[] = [];
- const readmeFile = new File([README], "README.md", { type: "text/markdown" });
- files.push(readmeFile);
- pages.forEach((page: Page) => {
- // Determine MIME type based on file extension
- let mimeType = "text/html";
- if (page.path.endsWith(".css")) {
- mimeType = "text/css";
- } else if (page.path.endsWith(".js")) {
- mimeType = "text/javascript";
- } else if (page.path.endsWith(".json")) {
- mimeType = "application/json";
- }
- // Inject the DeepSite badge script into index pages only (not components or other HTML files)
- const content = (mimeType === "text/html" && isIndexPage(page.path))
- ? injectDeepSiteBadge(page.html)
- : page.html;
- const file = new File([content], page.path, { type: mimeType });
- 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 aeff09fbc04f8b398a312b8db6f66f4a74113d2b..0000000000000000000000000000000000000000
--- a/app/api/re-design/route.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-
-// Timeout configuration (in milliseconds)
-const FETCH_TIMEOUT = 30000; // 30 seconds for external fetch
-
-// Extend the maximum execution time for this route
-export const maxDuration = 60; // 1 minute
-
-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 {
- // Create an AbortController for timeout
- const controller = new AbortController();
- const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
-
- try {
- const response = await fetch(
- `https://r.jina.ai/${encodeURIComponent(url)}`,
- {
- method: "POST",
- signal: controller.signal,
- }
- );
-
- clearTimeout(timeoutId);
-
- 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 }
- );
- } catch (fetchError: any) {
- clearTimeout(timeoutId);
-
- if (fetchError.name === 'AbortError') {
- return NextResponse.json(
- { error: "Request timeout: The external service took too long to respond. Please try again." },
- { status: 504 }
- );
- }
- throw fetchError;
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } catch (error: any) {
- if (error.name === 'AbortError' || error.message?.includes('timeout')) {
- return NextResponse.json(
- { error: "Request timeout: The external service took too long to respond. Please try again." },
- { status: 504 }
- );
- }
- 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 ? (
-
-
- Go to Home
-
-
- ) : (
-
- 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 a18c0c579f4f66e487a6e86c2bd4eb05e9fab87a..0000000000000000000000000000000000000000
--- a/app/layout.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import type { Metadata, Viewport } from "next";
-import { Inter, PT_Sans } from "next/font/google";
-import Script from "next/script";
-import { headers } from "next/headers";
-import { redirect } from "next/navigation";
-
-import "@/assets/globals.css";
-import { Toaster } from "@/components/ui/sonner";
-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";
-import { generateSEO, generateStructuredData } from "@/lib/seo";
-
-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 = {
- ...generateSEO({
- 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.",
- path: "/",
- }),
- appleWebApp: {
- capable: true,
- title: "DeepSite",
- statusBarStyle: "black-translucent",
- },
- icons: {
- icon: "/logo.svg",
- shortcut: "/logo.svg",
- apple: "/logo.svg",
- },
- verification: {
- google: process.env.GOOGLE_SITE_VERIFICATION,
- },
-};
-
-export const viewport: Viewport = {
- initialScale: 1,
- maximumScale: 1,
- themeColor: "#000000",
-};
-
-// async function getMe() {
-// const cookieStore = await cookies();
-// const cookieName = MY_TOKEN_KEY();
-// const token = cookieStore.get(cookieName)?.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;
-}>) {
- // Domain redirect check
- const headersList = await headers();
- const forwardedHost = headersList.get("x-forwarded-host");
- const host = headersList.get("host");
- const hostname = (forwardedHost || host || "").split(":")[0];
-
- const isLocalDev =
- hostname === "localhost" ||
- hostname === "127.0.0.1" ||
- hostname.startsWith("192.168.");
- const isHuggingFace =
- hostname === "huggingface.co" || hostname.endsWith(".huggingface.co");
-
- if (!isHuggingFace && !isLocalDev) {
- const pathname = headersList.get("x-invoke-path") || "/deepsite";
- redirect(`https://huggingface.co${pathname}`);
- }
-
- // const data = await getMe();
-
- // Generate structured data
- const structuredData = generateStructuredData("WebApplication", {
- name: "DeepSite",
- description: "Build websites with AI, no code required",
- url: "https://huggingface.co/deepsite",
- });
-
- const organizationData = generateStructuredData("Organization", {
- name: "DeepSite",
- url: "https://huggingface.co/deepsite",
- });
-
- return (
-
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
- );
-}
diff --git a/app/new/page.tsx b/app/new/page.tsx
deleted file mode 100644
index 6f9a3e815dd4df433d10c4b0f850ade9d6fb38fb..0000000000000000000000000000000000000000
--- a/app/new/page.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { AppEditor } from "@/components/editor";
-import { Metadata } from "next";
-import { generateSEO } from "@/lib/seo";
-
-export const metadata: Metadata = generateSEO({
- title: "Create New Project - DeepSite",
- description:
- "Start building your next website with AI. Create a new project on DeepSite and experience the power of AI-driven web development.",
- path: "/new",
-});
-
-export default function NewProjectPage() {
- return ;
-}
diff --git a/app/sitemap.ts b/app/sitemap.ts
deleted file mode 100644
index 6c00dd749547a9187b999fc25953f78f31d1216d..0000000000000000000000000000000000000000
--- a/app/sitemap.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { MetadataRoute } from 'next';
-
-export default function sitemap(): MetadataRoute.Sitemap {
- const baseUrl = 'https://huggingface.co/deepsite';
-
- return [
- {
- url: baseUrl,
- lastModified: new Date(),
- changeFrequency: 'daily',
- priority: 1,
- },
- {
- url: `${baseUrl}/new`,
- lastModified: new Date(),
- changeFrequency: 'weekly',
- priority: 0.8,
- },
- {
- url: `${baseUrl}/auth`,
- lastModified: new Date(),
- changeFrequency: 'monthly',
- priority: 0.5,
- },
- // Note: Dynamic project routes will be handled by Next.js automatically
- // but you can add specific high-priority project pages here if needed
- ];
-}
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 299014cc6db1db5b73dc03a97958b93fc2a9e811..0000000000000000000000000000000000000000
--- a/assets/globals.css
+++ /dev/null
@@ -1,380 +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("/deepsite/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);
-}
-
-.transparent-scroll {
- scrollbar-width: none; /* Firefox */
- -ms-overflow-style: none; /* IE and Edge */
-}
-
-.transparent-scroll::-webkit-scrollbar {
- display: none; /* Chrome, Safari, Opera */
-}
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/minimax.svg b/assets/minimax.svg
deleted file mode 100644
index 1d32449ab8fb0fe9a6c50006a41e67ef49c8dd1c..0000000000000000000000000000000000000000
--- a/assets/minimax.svg
+++ /dev/null
@@ -1 +0,0 @@
-Minimax
\ No newline at end of file
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/assets/zai.svg b/assets/zai.svg
deleted file mode 100644
index 2adcac387aaca06eb362a177e45c28ae3429b0d0..0000000000000000000000000000000000000000
--- a/assets/zai.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
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 b5fdbca49aaf1b2a2ac866efa13014eb667f3fb7..0000000000000000000000000000000000000000
--- a/components/contexts/app-context.tsx
+++ /dev/null
@@ -1,52 +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();
- 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/discord-promo-modal/index.tsx b/components/discord-promo-modal/index.tsx
deleted file mode 100644
index 83f15c11859473dfc50ceb34e76a58da7384c22b..0000000000000000000000000000000000000000
--- a/components/discord-promo-modal/index.tsx
+++ /dev/null
@@ -1,225 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import { useLocalStorage } from "react-use";
-import Image from "next/image";
-import { Button } from "@/components/ui/button";
-import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
-import { DiscordIcon } from "@/components/icons/discord";
-import Logo from "@/assets/logo.svg";
-
-const DISCORD_PROMO_KEY = "discord-promo-dismissed";
-const DISCORD_URL = "https://discord.gg/KpanwM3vXa";
-
-const Sparkle = ({
- size = "w-3 h-3",
- delay = "0s",
- top = "20%",
- left = "20%",
-}: {
- size?: string;
- delay?: string;
- top?: string;
- left?: string;
-}) => (
-
-);
-
-export const DiscordPromoModal = () => {
- const [open, setOpen] = useState(false);
- const [dismissed, setDismissed] = useLocalStorage(
- DISCORD_PROMO_KEY,
- false
- );
-
- useEffect(() => {
- const cookieDismissed = document.cookie
- .split("; ")
- .find((row) => row.startsWith(`${DISCORD_PROMO_KEY}=`))
- ?.split("=")[1];
-
- if (dismissed || cookieDismissed === "true") {
- return;
- }
-
- const timer = setTimeout(() => {
- setOpen(true);
- }, 60000);
-
- return () => clearTimeout(timer);
- }, [dismissed]);
-
- const handleClose = () => {
- setOpen(false);
- setDismissed(true);
-
- const expiryDate = new Date();
- expiryDate.setDate(expiryDate.getDate() + 5);
- document.cookie = `${DISCORD_PROMO_KEY}=true; expires=${expiryDate.toUTCString()}; path=/; SameSite=Lax`;
- };
-
- const handleJoinDiscord = () => {
- window.open(DISCORD_URL, "_blank");
- handleClose();
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Ready to level up your DeepSite experience?
-
-
- Get help, share your projects and ask for suggestions!
-
-
-
-
- {[
- "Get exclusive preview to new features",
- "Share your projects and get feedback",
- "Priority support from the team",
- "Enjoy real-time updates",
- ].map((benefit, index) => (
-
- ))}
-
-
- {/* CTA Button */}
-
-
-
- Join Discord Community
-
-
-
- Free to join. Connect instantly.
-
-
-
-
-
-
-
-
- );
-};
diff --git a/components/editor/ask-ai/context.tsx b/components/editor/ask-ai/context.tsx
deleted file mode 100644
index 3f852d6240cc4cbe0eedc9c647ed8b1ada118de3..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/context.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { useState, useMemo } from "react";
-import { FileCode, FileText, Braces, AtSign } from "lucide-react";
-
-import { Button } from "@/components/ui/button";
-import { useEditor } from "@/hooks/useEditor";
-import { useAi } from "@/hooks/useAi";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import classNames from "classnames";
-
-export const Context = () => {
- const { pages, currentPage, globalEditorLoading } = useEditor();
- const { contextFile, setContextFile, globalAiLoading } = useAi();
- const [open, setOpen] = useState(false);
-
- const selectedFile = contextFile || null;
-
- const getFileIcon = (filePath: string, size = "size-3.5") => {
- if (filePath.endsWith(".css")) {
- return ;
- } else if (filePath.endsWith(".js")) {
- return ;
- } else if (filePath.endsWith(".json")) {
- return ;
- } else {
- return ;
- }
- };
-
- const buttonContent = useMemo(() => {
- if (selectedFile) {
- return (
- <>
- {selectedFile}
- >
- );
- }
- return <>Add Context>;
- }, [selectedFile]);
-
- return (
-
-
-
-
-
- {buttonContent}
-
-
-
-
- Select a file to send as context
-
-
-
- {pages.length === 0 ? (
-
- No files available
-
- ) : (
- <>
-
{
- setContextFile(null);
- setOpen(false);
- }}
- className={`cursor-pointer w-full px-2 py-1.5 text-xs text-left rounded-md hover:bg-neutral-800 transition-colors ${
- !selectedFile
- ? "bg-neutral-800 text-neutral-200 font-medium"
- : "text-neutral-400 hover:text-neutral-200"
- }`}
- >
- All files (default)
-
- {pages.map((page) => (
-
{
- setContextFile(page.path);
- setOpen(false);
- }}
- className={`cursor-pointer w-full px-2 py-1.5 text-xs text-left rounded-md hover:bg-neutral-800 transition-colors flex items-center gap-1.5 ${
- selectedFile === page.path
- ? "bg-neutral-800 text-neutral-200 font-medium"
- : "text-neutral-400 hover:text-neutral-200"
- }`}
- >
-
- {getFileIcon(page.path, "size-3")}
-
- {page.path}
- {page.path === currentPage && (
-
- (current)
-
- )}
-
- ))}
- >
- )}
-
-
-
-
- );
-};
diff --git a/components/editor/ask-ai/fake-ask.tsx b/components/editor/ask-ai/fake-ask.tsx
deleted file mode 100644
index 41962ca7644fbd674870ed9adb16553bffdfa5a3..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("/new");
- };
-
- const randomPrompt = () => {
- setRandomPromptLoading(true);
- setTimeout(() => {
- setPrompt(
- PROMPTS_FOR_AI[Math.floor(Math.random() * PROMPTS_FOR_AI.length)]
- );
- setRandomPromptLoading(false);
- }, 400);
- };
-
- return (
-
- );
-};
diff --git a/components/editor/ask-ai/index.tsx b/components/editor/ask-ai/index.tsx
deleted file mode 100644
index e5fcc9095419569a545be3d9577d7dba1802f03c..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/index.tsx
+++ /dev/null
@@ -1,353 +0,0 @@
-import { useRef, useState } from "react";
-import classNames from "classnames";
-import { ArrowUp, ChevronDown, CircleStop, Dice6 } from "lucide-react";
-import { useLocalStorage, useUpdateEffect, useMount } from "react-use";
-import { toast } from "sonner";
-
-import { useAi } from "@/hooks/useAi";
-import { useEditor } from "@/hooks/useEditor";
-import { EnhancedSettings, Project } from "@/types";
-import { SelectedFiles } from "@/components/editor/ask-ai/selected-files";
-import { SelectedHtmlElement } from "@/components/editor/ask-ai/selected-html-element";
-import { AiLoading } from "@/components/editor/ask-ai/loading";
-import { Button } from "@/components/ui/button";
-import { Uploader } from "@/components/editor/ask-ai/uploader";
-import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
-import { Selector } from "@/components/editor/ask-ai/selector";
-import { PromptBuilder } from "@/components/editor/ask-ai/prompt-builder";
-import { Context } from "@/components/editor/ask-ai/context";
-import { useUser } from "@/hooks/useUser";
-import { useLoginModal } from "@/components/contexts/login-context";
-import { Settings } from "./settings";
-import { useProModal } from "@/components/contexts/pro-context";
-import { MAX_FREE_PROJECTS } from "@/lib/utils";
-import { PROMPTS_FOR_AI } from "@/lib/prompts";
-import { SelectedRedesignUrl } from "./selected-redesign-url";
-
-export const AskAi = ({
- project,
- isNew,
- onScrollToBottom,
-}: {
- project?: Project;
- files?: string[];
- isNew?: boolean;
- onScrollToBottom?: () => void;
-}) => {
- const { user, projects } = useUser();
- const { isSameHtml, isUploading, pages, isLoadingProject } = useEditor();
- const {
- isAiWorking,
- isThinking,
- thinkingContent,
- selectedFiles,
- setSelectedFiles,
- selectedElement,
- setSelectedElement,
- callAiNewProject,
- callAiFollowUp,
- audio: hookAudio,
- cancelRequest,
- } = useAi(onScrollToBottom);
- const { openLoginModal } = useLoginModal();
- const { openProModal } = useProModal();
- const [openProvider, setOpenProvider] = useState(false);
- const [providerError, setProviderError] = useState("");
- const [redesignData, setRedesignData] = useState<
- undefined | { markdown: string; url: string }
- >(undefined);
- const refThink = useRef(null);
-
- const [enhancedSettings, setEnhancedSettings, removeEnhancedSettings] =
- useLocalStorage("deepsite-enhancedSettings", {
- isActive: false,
- primaryColor: undefined,
- secondaryColor: undefined,
- theme: undefined,
- });
- const [promptStorage, , removePromptStorage] = useLocalStorage("prompt", "");
-
- const [isFollowUp, setIsFollowUp] = useState(true);
- const [prompt, setPrompt] = useState(
- promptStorage && promptStorage.trim() !== "" ? promptStorage : ""
- );
- const [openThink, setOpenThink] = useState(false);
- const [randomPromptLoading, setRandomPromptLoading] = useState(false);
-
- useMount(() => {
- if (promptStorage && promptStorage.trim() !== "") {
- callAi();
- }
- });
-
- const callAi = async (redesignMarkdown?: string | undefined) => {
- removePromptStorage();
- if (user && !user.isPro && projects.length >= MAX_FREE_PROJECTS)
- return openProModal([]);
- if (isAiWorking) return;
- if (!redesignMarkdown && !prompt.trim()) return;
-
- if (isFollowUp && !redesignMarkdown && !isSameHtml) {
- if (!user) return openLoginModal({ prompt });
- const result = await callAiFollowUp(prompt, enhancedSettings, isNew);
-
- if (result?.error) {
- handleError(result.error, result.message);
- return;
- }
-
- if (result?.success) {
- setPrompt("");
- }
- } else {
- const result = await callAiNewProject(
- prompt,
- enhancedSettings,
- redesignMarkdown,
- !!user,
- user?.name
- );
-
- if (result?.error) {
- handleError(result.error, result.message);
- return;
- }
-
- if (result?.success) {
- setPrompt("");
- }
- }
- };
-
- const handleError = (error: string, message?: string) => {
- switch (error) {
- case "login_required":
- openLoginModal();
- break;
- case "provider_required":
- setOpenProvider(true);
- setProviderError(message || "");
- break;
- case "pro_required":
- openProModal([]);
- break;
- case "api_error":
- toast.error(message || "An error occurred");
- break;
- case "network_error":
- toast.error(message || "Network error occurred");
- break;
- default:
- toast.error("An unexpected error occurred");
- }
- };
-
- useUpdateEffect(() => {
- if (refThink.current) {
- refThink.current.scrollTop = refThink.current.scrollHeight;
- }
- // Auto-open dropdown when thinking content appears
- if (thinkingContent && isThinking && !openThink) {
- setOpenThink(true);
- }
- // Auto-collapse when thinking is complete
- if (thinkingContent && !isThinking && openThink) {
- setOpenThink(false);
- }
- }, [thinkingContent, isThinking]);
-
- const randomPrompt = () => {
- setRandomPromptLoading(true);
- setTimeout(() => {
- setPrompt(
- PROMPTS_FOR_AI[Math.floor(Math.random() * PROMPTS_FOR_AI.length)]
- );
- setRandomPromptLoading(false);
- }, 400);
- };
-
- return (
-
-
- {thinkingContent && (
-
-
-
-
- {thinkingContent}
-
-
-
- )}
-
- setSelectedFiles(selectedFiles.filter((f) => f !== file))
- }
- />
- {selectedElement && (
-
- setSelectedElement(null)}
- />
-
- )}
- {redesignData && (
-
- setRedesignData(undefined)}
- />
-
- )}
-
- {(isAiWorking || isUploading || isThinking || isLoadingProject) && (
-
-
- {isAiWorking && (
-
-
-
- )}
-
- )}
-
-
-
- {isNew ? (
-
- ) : (
-
- )}
-
- {!isNew &&
}
- {isNew && (
-
- setRedesignData({ markdown: md, url: url })
- }
- />
- )}
- {!isNew && !isSameHtml && }
-
-
-
callAi(redesignData?.markdown)}
- >
-
-
-
-
-
-
-
- Your browser does not support the audio element.
-
-
- );
-};
diff --git a/components/editor/ask-ai/loading.tsx b/components/editor/ask-ai/loading.tsx
deleted file mode 100644
index c22740e34cadceb16604dce377d823bf69ecc6ac..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/loading.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-"use client";
-import Loading from "@/components/loading";
-import { useState, useEffect } from "react";
-import { useInterval } from "react-use";
-
-const TEXTS = [
- "Teaching pixels to dance with style...",
- "AI is having a creative breakthrough...",
- "Channeling digital vibes into pure code...",
- "Summoning the website spirits...",
- "Brewing some algorithmic magic...",
- "Composing a symphony of divs and spans...",
- "Riding the wave of computational creativity...",
- "Aligning the stars for perfect design...",
- "Training circus animals to write CSS...",
- "Launching ideas into the digital stratosphere...",
-];
-
-export const AiLoading = ({
- text,
- className,
-}: {
- text?: string;
- className?: string;
-}) => {
- const [selectedText, setSelectedText] = useState(
- text ?? TEXTS[0] // Start with first text to avoid hydration issues
- );
-
- // Set random text on client-side only to avoid hydration mismatch
- useEffect(() => {
- if (!text) {
- setSelectedText(TEXTS[Math.floor(Math.random() * TEXTS.length)]);
- }
- }, [text]);
-
- useInterval(() => {
- if (!text) {
- if (selectedText === TEXTS[TEXTS.length - 1]) {
- setSelectedText(TEXTS[0]);
- } else {
- setSelectedText(TEXTS[TEXTS.indexOf(selectedText) + 1]);
- }
- }
- }, 12000);
- return (
-
-
-
-
- {selectedText.split("").map((char, index) => (
-
- {char === " " ? "\u00A0" : char}
-
- ))}
-
-
-
- );
-};
diff --git a/components/editor/ask-ai/prompt-builder/content-modal.tsx b/components/editor/ask-ai/prompt-builder/content-modal.tsx
deleted file mode 100644
index 19f4340bc7f743a09d4b1ee96a5e9514d068ba87..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/prompt-builder/content-modal.tsx
+++ /dev/null
@@ -1,196 +0,0 @@
-import classNames from "classnames";
-import { ChevronRight, RefreshCcw } from "lucide-react";
-import { useState } from "react";
-import { TailwindColors } from "./tailwind-colors";
-import { Switch } from "@/components/ui/switch";
-import { Button } from "@/components/ui/button";
-import { Themes } from "./themes";
-import { EnhancedSettings } from "@/types";
-
-export const ContentModal = ({
- enhancedSettings,
- setEnhancedSettings,
-}: {
- enhancedSettings: EnhancedSettings;
- setEnhancedSettings: (settings: EnhancedSettings) => void;
-}) => {
- const [collapsed, setCollapsed] = useState(["colors", "theme"]);
- return (
-
-
-
-
- Allow DeepSite to enhance your prompt
-
-
- setEnhancedSettings({
- ...enhancedSettings,
- isActive: !enhancedSettings.isActive,
- })
- }
- />
-
-
- While using DeepSite enhanced prompt, you'll get better results. We'll
- add more details and features to your request.
-
-
-
- You can also use the custom properties below to set specific
- information.
-
-
-
-
-
- setCollapsed((prev) => {
- if (prev.includes("colors")) {
- return prev.filter((item) => item !== "colors");
- }
- return [...prev, "colors"];
- })
- }
- >
-
-
Colors
-
- {collapsed.includes("colors") && (
-
-
-
-
- Primary Color
-
-
- setEnhancedSettings({
- ...enhancedSettings,
- primaryColor: undefined,
- })
- }
- >
-
- Reset
-
-
-
-
- setEnhancedSettings({
- ...enhancedSettings,
- primaryColor: value,
- })
- }
- />
-
-
-
-
-
- Secondary Color
-
-
- setEnhancedSettings({
- ...enhancedSettings,
- secondaryColor: undefined,
- })
- }
- >
-
- Reset
-
-
-
-
- setEnhancedSettings({
- ...enhancedSettings,
- secondaryColor: value,
- })
- }
- />
-
-
-
- )}
-
-
-
- setCollapsed((prev) => {
- if (prev.includes("theme")) {
- return prev.filter((item) => item !== "theme");
- }
- return [...prev, "theme"];
- })
- }
- >
-
-
Theme
-
- {collapsed.includes("theme") && (
-
-
-
- Theme
-
-
- setEnhancedSettings({
- ...enhancedSettings,
- theme: undefined,
- })
- }
- >
-
- Reset
-
-
-
-
- setEnhancedSettings({
- ...enhancedSettings,
- theme: value,
- })
- }
- />
-
-
- )}
-
-
- );
-};
diff --git a/components/editor/ask-ai/prompt-builder/index.tsx b/components/editor/ask-ai/prompt-builder/index.tsx
deleted file mode 100644
index f59e8f8a95f47bba52520923f7f86e6e425a39f9..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/prompt-builder/index.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { useState } from "react";
-import { WandSparkles } from "lucide-react";
-
-import { Button } from "@/components/ui/button";
-import { useEditor } from "@/hooks/useEditor";
-import { useAi } from "@/hooks/useAi";
-import {
- Dialog,
- DialogContent,
- DialogFooter,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { ContentModal } from "./content-modal";
-import { EnhancedSettings } from "@/types";
-
-export const PromptBuilder = ({
- enhancedSettings,
- setEnhancedSettings,
-}: {
- enhancedSettings: EnhancedSettings;
- setEnhancedSettings: (settings: EnhancedSettings) => void;
-}) => {
- const { globalAiLoading } = useAi();
- const { globalEditorLoading } = useEditor();
-
- const [open, setOpen] = useState(false);
- return (
- <>
- {
- setOpen(true);
- }}
- >
-
-
- Enhance
-
-
- setOpen(false)}>
-
-
-
-
-
-
- setOpen(false)}
- >
- Close
-
-
-
-
- >
- );
-};
diff --git a/components/editor/ask-ai/prompt-builder/tailwind-colors.tsx b/components/editor/ask-ai/prompt-builder/tailwind-colors.tsx
deleted file mode 100644
index 70a2ab847cdeabb50887a29a03b831eb3cc65114..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/prompt-builder/tailwind-colors.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import classNames from "classnames";
-import { useRef } from "react";
-
-import { TAILWIND_COLORS } from "@/lib/prompt-builder";
-import { useMount } from "react-use";
-
-export const TailwindColors = ({
- value,
- onChange,
-}: {
- value: string | undefined;
- onChange: (value: string) => void;
-}) => {
- const ref = useRef(null);
-
- useMount(() => {
- if (ref.current) {
- if (value) {
- const color = ref.current.querySelector(`[data-color="${value}"]`);
- if (color) {
- color.scrollIntoView({ inline: "center" });
- }
- }
- }
- });
- return (
-
- {TAILWIND_COLORS.map((color) => (
-
onChange(color)}
- >
-
-
- {color}
-
-
- ))}
-
- );
-};
diff --git a/components/editor/ask-ai/prompt-builder/themes.tsx b/components/editor/ask-ai/prompt-builder/themes.tsx
deleted file mode 100644
index b419e8618153c8fd467dd3018fd521ac8930d487..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/prompt-builder/themes.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { Theme } from "@/types";
-import classNames from "classnames";
-import { Moon, Sun } from "lucide-react";
-import { useRef } from "react";
-
-export const Themes = ({
- value,
- onChange,
-}: {
- value: Theme;
- onChange: (value: Theme) => void;
-}) => {
- const ref = useRef(null);
-
- return (
-
-
onChange("light")}
- >
-
-
Light
-
-
onChange("dark")}
- >
-
-
Dark
-
-
- );
-};
diff --git a/components/editor/ask-ai/re-imagine.tsx b/components/editor/ask-ai/re-imagine.tsx
deleted file mode 100644
index 9531db25a30e4eabb3e17f1b399eab7b32f64d67..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/re-imagine.tsx
+++ /dev/null
@@ -1,158 +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";
-import { useAi } from "@/hooks/useAi";
-import { useEditor } from "@/hooks/useEditor";
-import classNames from "classnames";
-
-export function ReImagine({
- onRedesign,
-}: {
- onRedesign: (md: string, url: string) => void;
-}) {
- const [url, setUrl] = useState("");
- const [open, setOpen] = useState(false);
- const [isLoading, setIsLoading] = useState(false);
- const { globalAiLoading } = useAi();
- const { globalEditorLoading } = useEditor();
-
- 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);
- onRedesign(response.data.markdown, url.trim());
- setUrl("");
- toast.success("DeepSite is redesigning your site! Let him cook... π₯");
- } else {
- toast.error(response?.data?.error || "Failed to redesign the site.");
- }
- setIsLoading(false);
- };
-
- return (
-
-
-
- );
-}
diff --git a/components/editor/ask-ai/selected-files.tsx b/components/editor/ask-ai/selected-files.tsx
deleted file mode 100644
index 7dd4acbccc7a3d2bc88c95277155acb4ea045522..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/selected-files.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import Image from "next/image";
-
-import { Button } from "@/components/ui/button";
-import { Minus, Video, Music } from "lucide-react";
-import { getFileType } from "./uploader";
-
-export const SelectedFiles = ({
- files,
- isAiWorking,
- onDelete,
-}: {
- files: string[];
- isAiWorking: boolean;
- onDelete: (file: string) => void;
-}) => {
- if (files.length === 0) return null;
- return (
-
-
- {files.map((file) => (
-
- {getFileType(file) === "image" ? (
-
- ) : getFileType(file) === "video" ? (
-
- ) : getFileType(file) === "audio" ? (
-
- ) : null}
- onDelete(file)}
- >
-
-
-
- ))}
-
-
- );
-};
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/selected-redesign-url.tsx b/components/editor/ask-ai/selected-redesign-url.tsx
deleted file mode 100644
index c5ba812c3d540d5095a99ce42795c51a78ff4454..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/selected-redesign-url.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import classNames from "classnames";
-import { Paintbrush, XCircle } from "lucide-react";
-
-export const SelectedRedesignUrl = ({
- url,
- isAiWorking = false,
- onDelete,
-}: {
- url: string;
- isAiWorking: boolean;
- onDelete?: () => void;
-}) => {
- return (
- {
- if (!isAiWorking && onDelete) {
- onDelete();
- }
- }}
- >
-
-
- );
-};
diff --git a/components/editor/ask-ai/selector.tsx b/components/editor/ask-ai/selector.tsx
deleted file mode 100644
index 0ce4a6bfd0a5d4a02bb20ad82b33ca3947662084..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/selector.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import classNames from "classnames";
-import { Crosshair } from "lucide-react";
-
-import { Button } from "@/components/ui/button";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { useAi } from "@/hooks/useAi";
-import { useEditor } from "@/hooks/useEditor";
-
-export const Selector = () => {
- const { globalEditorLoading } = useEditor();
- const { isEditableModeEnabled, setIsEditableModeEnabled, globalAiLoading } =
- useAi();
- return (
-
-
- {
- setIsEditableModeEnabled?.(!isEditableModeEnabled);
- }}
- disabled={globalAiLoading || globalEditorLoading}
- className="!rounded-md"
- >
-
- Edit
-
-
-
- Select an element on the page to ask DeepSite edit it directly.
-
-
- );
-};
diff --git a/components/editor/ask-ai/settings.tsx b/components/editor/ask-ai/settings.tsx
deleted file mode 100644
index 95633aadb07cbebb3b039702a7ec5d7897b09abc..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/settings.tsx
+++ /dev/null
@@ -1,335 +0,0 @@
-"use client";
-import classNames from "classnames";
-
-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, useState, useEffect } from "react";
-import { useUpdateEffect } from "react-use";
-import Image from "next/image";
-import {
- BrainIcon,
- CheckCheck,
- ChevronDown,
- Sparkles,
- Zap,
- DollarSign,
-} from "lucide-react";
-import { useAi } from "@/hooks/useAi";
-import { getProviders } from "@/lib/get-providers";
-import Loading from "@/components/loading";
-
-export function Settings({
- open,
- onClose,
- error,
- isFollowUp = false,
-}: {
- open: boolean;
- error?: string;
- isFollowUp?: boolean;
- onClose: React.Dispatch>;
-}) {
- const {
- model,
- provider,
- setProvider,
- setModel,
- selectedModel,
- globalAiLoading,
- } = useAi();
- const [isMounted, setIsMounted] = useState(false);
- const [loadingProviders, setLoadingProviders] = useState(false);
-
- useEffect(() => {
- setIsMounted(true);
- }, []);
-
- // 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 (
- !["auto", "fastest", "cheapest"].includes(provider as string) &&
- !providers.includes(provider as string)
- ) {
- setProvider("auto");
- }
- }, [model, provider]);
-
- const formattedModels = useMemo(() => {
- const lists: ((typeof MODELS)[0] | { isCategory: true; name: string })[] =
- [];
- const keys = new Set();
- MODELS.forEach((model) => {
- if (!keys.has(model.companyName)) {
- lists.push({
- isCategory: true,
- name: model.companyName,
- logo: model.logo,
- });
- keys.add(model.companyName);
- }
- lists.push(model);
- });
- return lists;
- }, [MODELS]);
-
- const [providers, setProviders] = useState([]);
- const [failedImages, setFailedImages] = useState>(new Set());
-
- useEffect(() => {
- const loadProviders = async () => {
- setLoadingProviders(true);
- if (!model) {
- setProviders([]);
- return;
- }
- try {
- const result = await getProviders(model);
- setProviders(result);
- } catch (error) {
- console.error("Failed to load providers:", error);
- setProviders([]);
- } finally {
- setLoadingProviders(false);
- }
- };
-
- loadProviders();
- }, [model]);
-
- const handleImageError = (providerId: string) => {
- setFailedImages((prev) => new Set([...prev, providerId]));
- };
-
- return (
-
-
-
- {/* */}
- {selectedModel?.logo && (
-
- )}
-
- {isMounted
- ? selectedModel?.label?.split(" ").join("-").toLowerCase()
- : "..."}
-
-
-
-
-
-
-
- {error !== "" && (
-
- {error}
-
- )}
-
- Choose a model
-
-
-
-
-
-
- {formattedModels.map((item: any) => {
- if ("isCategory" in item) {
- return (
-
- {item.name}
-
- );
- }
- const {
- value,
- label,
- isNew = false,
- isThinker = false,
- } = item;
- return (
-
- {label}
- {isNew && (
-
- New
-
- )}
-
- );
- })}
-
-
-
-
- {/* {isFollowUp && (
-
- Note: You can't use a Thinker model for follow-up requests.
- We automatically switch to the default model for you.
-
- )} */}
-
-
-
Provider Mode
-
- Choose how we select providers:{" "}
-
- Auto
- {" "}
- (smart),{" "}
-
- Fastest
- {" "}
- (speed), or{" "}
-
- Cheapest
- {" "}
- (cost).
-
-
- setProvider("auto")}
- >
-
- Auto
-
- setProvider("fastest")}
- >
-
- Fastest
-
- setProvider("cheapest")}
- >
-
- Cheapest
-
-
-
-
-
- Or choose a specific provider
-
-
- {loadingProviders ? (
-
- ) : (
- providers.map((id: string) => (
- {
- setProvider(id);
- }}
- >
- {failedImages.has(id) ? (
-
- ) : (
- handleImageError(id)}
- />
- )}
- {PROVIDERS?.[id as keyof typeof PROVIDERS]?.name || id}
- {id === provider && (
-
- )}
-
- ))
- )}
-
-
-
-
-
-
- );
-}
diff --git a/components/editor/ask-ai/uploader.tsx b/components/editor/ask-ai/uploader.tsx
deleted file mode 100644
index 4aaf6a0a55464c0bb86b378c4e9975895ac77351..0000000000000000000000000000000000000000
--- a/components/editor/ask-ai/uploader.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import { useRef, useState } from "react";
-import {
- CheckCircle,
- ImageIcon,
- Paperclip,
- Upload,
- Video,
- Music,
- FileVideo,
- Lock,
-} from "lucide-react";
-import Image from "next/image";
-
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { Button } from "@/components/ui/button";
-import { Project } from "@/types";
-import Loading from "@/components/loading";
-import { useUser } from "@/hooks/useUser";
-import { useEditor } from "@/hooks/useEditor";
-import { useAi } from "@/hooks/useAi";
-import { useLoginModal } from "@/components/contexts/login-context";
-import Link from "next/link";
-
-export const getFileType = (url: string) => {
- if (typeof url !== "string") {
- return "unknown";
- }
- const extension = url.split(".").pop()?.toLowerCase();
- if (["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(extension || "")) {
- return "image";
- } else if (["mp4", "webm", "ogg", "avi", "mov"].includes(extension || "")) {
- return "video";
- } else if (["mp3", "wav", "ogg", "aac", "m4a"].includes(extension || "")) {
- return "audio";
- }
- return "unknown";
-};
-
-export const Uploader = ({ project }: { project: Project | undefined }) => {
- const { user } = useUser();
- const { openLoginModal } = useLoginModal();
- const {
- uploadFiles,
- isUploading,
- files,
- globalEditorLoading,
- project: editorProject,
- } = useEditor();
- const { selectedFiles, setSelectedFiles, globalAiLoading } = useAi();
-
- const [open, setOpen] = useState(false);
- const fileInputRef = useRef(null);
-
- const getFileIcon = (url: string) => {
- const fileType = getFileType(url);
- switch (fileType) {
- case "image":
- return ;
- case "video":
- return ;
- case "audio":
- return ;
- default:
- return ;
- }
- };
-
- if (!user)
- return (
- openLoginModal()}
- >
-
- Attach
-
- );
-
- return (
-
-
-
- );
-};
diff --git a/components/editor/file-browser/index.tsx b/components/editor/file-browser/index.tsx
deleted file mode 100644
index f9876a1a058a53778ab785974979774b30f47b9b..0000000000000000000000000000000000000000
--- a/components/editor/file-browser/index.tsx
+++ /dev/null
@@ -1,458 +0,0 @@
-"use client";
-
-import { useState, useMemo } from "react";
-import {
- FolderOpen,
- FileCode2,
- Folder,
- ChevronRight,
- ChevronDown,
- FileJson,
-} from "lucide-react";
-import classNames from "classnames";
-
-import { Page } from "@/types";
-import { useEditor } from "@/hooks/useEditor";
-import { Button } from "@/components/ui/button";
-import {
- Sheet,
- SheetContent,
- SheetHeader,
- SheetTitle,
- SheetTrigger,
-} from "@/components/ui/sheet";
-import {
- Tooltip,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-
-interface FileNode {
- name: string;
- path: string;
- type: "file" | "folder";
- children?: FileNode[];
- page?: Page;
-}
-
-export function FileBrowser() {
- const {
- pages,
- currentPage,
- setCurrentPage,
- setPreviewPage,
- globalEditorLoading,
- project,
- } = useEditor();
- const [open, setOpen] = useState(false);
- const [expandedFolders, setExpandedFolders] = useState>(
- new Set(["/"])
- );
-
- const toggleFolder = (path: string) => {
- setExpandedFolders((prev) => {
- const next = new Set(prev);
- if (next.has(path)) {
- next.delete(path);
- } else {
- next.add(path);
- }
- return next;
- });
- };
-
- const fileTree = useMemo(() => {
- const root: FileNode = {
- name: "root",
- path: "/",
- type: "folder",
- children: [],
- };
-
- pages.forEach((page) => {
- const parts = page.path.split("/").filter(Boolean);
- let currentNode = root;
-
- parts.forEach((part, index) => {
- const isFile = index === parts.length - 1;
- const currentPath = "/" + parts.slice(0, index + 1).join("/");
-
- if (!currentNode.children) {
- currentNode.children = [];
- }
-
- let existingNode = currentNode.children.find((n) => n.name === part);
-
- if (!existingNode) {
- existingNode = {
- name: part,
- path: currentPath,
- type: isFile ? "file" : "folder",
- children: isFile ? undefined : [],
- page: isFile ? page : undefined,
- };
- currentNode.children.push(existingNode);
- }
-
- if (!isFile) {
- currentNode = existingNode;
- }
- });
- });
-
- // Sort: folders first, then files, both alphabetically
- const sortNodes = (nodes: FileNode[] = []): FileNode[] => {
- return nodes
- .sort((a, b) => {
- if (a.type !== b.type) {
- return a.type === "folder" ? -1 : 1;
- }
- return a.name.localeCompare(b.name);
- })
- .map((node) => ({
- ...node,
- children: node.children ? sortNodes(node.children) : undefined,
- }));
- };
-
- root.children = sortNodes(root.children);
- return root;
- }, [pages]);
-
- const getFileIcon = (path: string) => {
- const extension = path.split(".").pop()?.toLowerCase();
-
- switch (extension) {
- case "html":
- return (
-
-
-
-
-
-
-
-
- );
- case "css":
- return (
-
-
-
-
-
-
-
-
- );
- case "js":
- case "jsx":
- return (
-
-
-
-
-
- );
- case "json":
- return ;
- default:
- return ;
- }
- };
-
- const getFileExtension = (path: string) => {
- return path.split(".").pop()?.toLowerCase() || "";
- };
-
- const getLanguageTag = (path: string) => {
- const extension = path.split(".").pop()?.toLowerCase();
-
- switch (extension) {
- case "html":
- return {
- name: "HTML",
- color: "bg-orange-500/20 border-orange-500/30 text-orange-400",
- };
- case "css":
- return {
- name: "CSS",
- color: "bg-blue-500/20 border-blue-500/30 text-blue-400",
- };
- case "js":
- return {
- name: "JS",
- color: "bg-yellow-500/20 border-yellow-500/30 text-yellow-400",
- };
- case "json":
- return {
- name: "JSON",
- color: "bg-amber-500/20 border-amber-500/30 text-amber-400",
- };
- default:
- return {
- name: extension?.toUpperCase() || "FILE",
- color: "bg-neutral-500/20 border-neutral-500/30 text-neutral-400",
- };
- }
- };
-
- const currentPageData = pages.find((p) => p.path === currentPage);
-
- const renderFileTree = (nodes: FileNode[], depth = 0) => {
- return nodes.map((node) => {
- if (node.type === "folder") {
- const isExpanded = expandedFolders.has(node.path);
- return (
-
-
toggleFolder(node.path)}
- >
- {isExpanded ? (
-
- ) : (
-
- )}
-
-
- {node.name}
-
-
- {node.children?.length || 0}
-
-
- {isExpanded &&
- node.children &&
- renderFileTree(node.children, depth + 1)}
-
- );
- } else {
- const isActive = currentPage === node.page?.path;
- return (
- {
- if (node.page) {
- setCurrentPage(node.page.path);
- if (
- node.page.path.endsWith(".html") &&
- !node.page.path.includes("components")
- ) {
- setPreviewPage(node.page.path);
- }
- setOpen(false);
- }
- }}
- >
-
- {getFileIcon(node.name)}
-
-
-
{node.name}
-
-
- {getFileExtension(node.name)}
-
-
- {isActive && (
-
- )}
-
- );
- }
- });
- };
-
- return (
-
- {/* VS Code-style Tab Bar */}
-
-
- {currentPageData && (
-
-
- {getFileIcon(currentPageData.path)}
-
- {currentPageData.path}
-
-
- {getLanguageTag(currentPageData.path).name}
-
-
-
- )}
-
-
- {/* Open Explorer Button */}
-
-
-
-
-
-
-
- Files
-
- {pages.length}
-
-
-
-
-
-
- Open File Explorer ({pages.length}{" "}
- {pages.length === 1 ? "file" : "files"})
-
-
-
-
-
-
-
-
- Explorer
-
-
-
-
-
-
-
-
-
-
-
- {project?.space_id || "No space selected"}
-
-
- {pages.length || 0}
-
-
-
-
-
- {fileTree.children && renderFileTree(fileTree.children)}
-
-
-
-
-
-
-
- HTML:{" "}
- {pages.filter((p) => p.path.endsWith(".html")).length}
-
-
-
-
-
- CSS:{" "}
- {pages.filter((p) => p.path.endsWith(".css")).length}
-
-
-
-
-
- JS: {pages.filter((p) => p.path.endsWith(".js")).length}
-
-
-
-
-
- JSON:{" "}
- {pages.filter((p) => p.path.endsWith(".json")).length}
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/components/editor/header/index.tsx b/components/editor/header/index.tsx
deleted file mode 100644
index d52679de93464384b8741a1d90f1601383cab962..0000000000000000000000000000000000000000
--- a/components/editor/header/index.tsx
+++ /dev/null
@@ -1,161 +0,0 @@
-import {
- ArrowRight,
- HelpCircle,
- RefreshCcw,
- Lock,
- Eye,
- Sparkles,
-} from "lucide-react";
-import Image from "next/image";
-import Link from "next/link";
-
-import Logo from "@/assets/logo.svg";
-import { Button } from "@/components/ui/button";
-import { useUser } from "@/hooks/useUser";
-import { ProTag } from "@/components/pro-modal";
-import { UserMenu } from "@/components/user-menu";
-import { SwitchDevice } from "@/components/editor/switch-devide";
-import { SwitchTab } from "./switch-tab";
-import { History } from "@/components/editor/history";
-import { useEditor } from "@/hooks/useEditor";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "@/components/ui/tooltip";
-import { DiscordIcon } from "@/components/icons/discord";
-
-export function Header({ isNew }: { isNew: boolean }) {
- const { project } = useEditor();
- const { user, openLoginWindow } = useUser();
- return (
-
- );
-}
diff --git a/components/editor/header/switch-tab.tsx b/components/editor/header/switch-tab.tsx
deleted file mode 100644
index 30b0324a7c5ee14460b1f6119fc884a393ec3e4d..0000000000000000000000000000000000000000
--- a/components/editor/header/switch-tab.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import {
- PanelLeftClose,
- PanelLeftOpen,
- Eye,
- MessageCircleCode,
-} from "lucide-react";
-import classNames from "classnames";
-
-import { Button } from "@/components/ui/button";
-import { useEditor } from "@/hooks/useEditor";
-
-const TABS = [
- {
- value: "chat",
- label: "Chat",
- icon: MessageCircleCode,
- },
- {
- value: "preview",
- label: "Preview",
- icon: Eye,
- },
-];
-
-export const SwitchTab = ({ isMobile = false }: { isMobile?: boolean }) => {
- const { currentTab, setCurrentTab } = useEditor();
-
- if (isMobile) {
- return (
-
- {TABS.map((item) => (
- setCurrentTab(item.value)}
- >
-
- {item.label}
-
- ))}
-
- );
- }
- return (
- setCurrentTab(currentTab === "chat" ? "preview" : "chat")}
- >
- {currentTab === "chat" ? : }
-
- );
-};
diff --git a/components/editor/history-notification/index.tsx b/components/editor/history-notification/index.tsx
deleted file mode 100644
index 12d71fc351a5209f71f2954aaefe832bb2a85bdc..0000000000000000000000000000000000000000
--- a/components/editor/history-notification/index.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import classNames from "classnames";
-import { Button } from "@/components/ui/button";
-import Loading from "@/components/loading";
-import {
- History,
- ChevronUp,
- ChevronDown,
- MousePointerClick,
-} from "lucide-react";
-
-interface HistoryNotificationProps {
- /** Whether the historical version notification should be visible */
- isVisible: boolean;
- /** Whether the version promotion is in progress */
- isPromotingVersion: boolean;
- /** Function to promote the current historical version */
- onPromoteVersion: () => void;
- /** Function to go back to the current version */
- onGoBackToCurrent: () => void;
- /** Additional CSS classes */
- className?: string;
-}
-
-export const HistoryNotification = ({
- isVisible,
- isPromotingVersion,
- onPromoteVersion,
- onGoBackToCurrent,
- className,
-}: HistoryNotificationProps) => {
- const [isCollapsed, setIsCollapsed] = useState(false);
-
- if (!isVisible) {
- return null;
- }
-
- return (
-
- {isCollapsed ? (
- // Collapsed state
-
-
-
- Historical Version
-
- setIsCollapsed(false)}
- >
-
-
-
- ) : (
- // Expanded state
-
-
-
-
-
-
-
- Historical Version
-
-
-
setIsCollapsed(true)}
- >
-
-
-
-
- You're viewing a previous version of this project. Promote this
- version to make it current and deploy it live.
-
-
-
- {isPromotingVersion ? (
-
- ) : (
-
- )}
- Promote Version
-
-
- Go back to current
-
-
-
-
-
- )}
-
- );
-};
diff --git a/components/editor/history/index.tsx b/components/editor/history/index.tsx
deleted file mode 100644
index 179f47a5d7268f1e3403ba6aec16c4f541607643..0000000000000000000000000000000000000000
--- a/components/editor/history/index.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import { History as HistoryIcon } from "lucide-react";
-import { useState } from "react";
-
-import { Commit } from "@/types";
-import {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover";
-import { Button } from "@/components/ui/button";
-import { useEditor } from "@/hooks/useEditor";
-import classNames from "classnames";
-
-export function History() {
- const { commits, currentCommit, setCurrentCommit, project } = useEditor();
- const [open, setOpen] = useState(false);
-
- if (commits.length === 0) return null;
-
- return (
-
-
-
-
- {commits?.length} edit{commits?.length !== 1 ? "s" : ""}
-
-
-
-
-
- {project?.private && (
-
-
- As this project is private, you can't see the history of
- changes.
-
-
- )}
-
- {commits?.map((item: Commit, index: number) => (
-
- {item.title}
-
-
- {new Date(item.date).toLocaleDateString("en-US", {
- month: "2-digit",
- day: "2-digit",
- year: "2-digit",
- }) +
- " " +
- new Date(item.date).toLocaleTimeString("en-US", {
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- hour12: false,
- })}
-
- {currentCommit === item.oid ||
- (index === 0 && currentCommit === null) ? (
-
- Current version
-
- ) : (
- !project?.private && (
-
{
- if (index === 0) {
- setCurrentCommit(null);
- } else {
- setCurrentCommit(item.oid);
- }
- }}
- >
- See version
-
- )
- )}
-
-
- ))}
-
-
-
-
- );
-}
diff --git a/components/editor/index.tsx b/components/editor/index.tsx
deleted file mode 100644
index 20e75cf0743ea4b4b06cc96ad2dcf49cf93ad0c0..0000000000000000000000000000000000000000
--- a/components/editor/index.tsx
+++ /dev/null
@@ -1,174 +0,0 @@
-"use client";
-import { useMemo, useRef, useState, useEffect } from "react";
-import { useCopyToClipboard, useLocalStorage, useMount } from "react-use";
-import { CopyIcon } from "lucide-react";
-import { toast } from "sonner";
-import classNames from "classnames";
-import { editor } from "monaco-editor";
-import Editor from "@monaco-editor/react";
-
-import { useEditor } from "@/hooks/useEditor";
-import { Header } from "@/components/editor/header";
-import { useAi } from "@/hooks/useAi";
-
-import { FileBrowser } from "./file-browser";
-import { AskAi } from "./ask-ai";
-import { Preview } from "./preview";
-import { SaveChangesPopup } from "./save-changes-popup";
-import { DiscordPromoModal } from "@/components/discord-promo-modal";
-import Loading from "../loading";
-import { Page } from "@/types";
-
-export const AppEditor = ({
- namespace,
- repoId,
- isNew = false,
-}: {
- namespace?: string;
- repoId?: string;
- isNew?: boolean;
-}) => {
- const {
- project,
- setPages,
- files,
- currentPageData,
- currentTab,
- currentCommit,
- hasUnsavedChanges,
- saveChanges,
- globalEditorLoading,
- pages,
- } = useEditor(namespace, repoId);
- const { isAiWorking } = useAi();
- const [, copyToClipboard] = useCopyToClipboard();
- const [showSavePopup, setShowSavePopup] = useState(false);
- const [pagesStorage, , removePagesStorage] = useLocalStorage("pages");
-
- const monacoRef = useRef(null);
- const editor = useRef(null);
- const editorRef = useRef(null);
-
- useMount(() => {
- if (isNew && pagesStorage) {
- setPages(pagesStorage);
- removePagesStorage();
- }
- });
-
- useEffect(() => {
- if (hasUnsavedChanges && !isAiWorking && project?.space_id) {
- setShowSavePopup(true);
- } else {
- setShowSavePopup(false);
- }
- }, [hasUnsavedChanges, isAiWorking]);
-
- // Determine the language based on file extension
- const editorLanguage = useMemo(() => {
- const path = currentPageData.path;
- if (path.endsWith(".css")) return "css";
- if (path.endsWith(".js")) return "javascript";
- if (path.endsWith(".json")) return "json";
- return "html";
- }, [currentPageData.path]);
-
- // Determine the copy message based on file type
- const copyMessage = useMemo(() => {
- if (editorLanguage === "css") return "CSS copied to clipboard!";
- if (editorLanguage === "javascript")
- return "JavaScript copied to clipboard!";
- if (editorLanguage === "json") return "JSON copied to clipboard!";
- return "HTML copied to clipboard!";
- }, [editorLanguage]);
-
- return (
-
-
-
-
-
-
{
- copyToClipboard(currentPageData.html);
- toast.success(copyMessage);
- }}
- />
- }
- className="h-full absolute left-0 top-0 lg:min-w-[600px]"
- options={{
- colorDecorators: true,
- fontLigatures: true,
- theme: "vs-dark",
- minimap: { enabled: false },
- scrollbar: {
- horizontal: "hidden",
- },
- wordWrap: "on",
- readOnly: !!isAiWorking || !!currentCommit || globalEditorLoading,
- readOnlyMessage: {
- value: globalEditorLoading
- ? "Wait for DeepSite loading your project..."
- : currentCommit
- ? "You can't edit the code, as this is an old version of the project."
- : "Wait for DeepSite to finish working...",
- isTrusted: true,
- },
- cursorBlinking: "smooth",
- }}
- value={currentPageData.html}
- onChange={(value) => {
- const newValue = value ?? "";
- setPages((prev) =>
- prev.map((page) =>
- page.path === currentPageData.path
- ? { ...page, html: newValue }
- : page
- )
- );
- }}
- onMount={(editor, monaco) => {
- editorRef.current = editor;
- monacoRef.current = monaco;
- }}
- />
- {
- editorRef.current?.revealLine(
- editorRef.current?.getModel()?.getLineCount() ?? 0
- );
- }}
- />
-
-
-
-
- setShowSavePopup(false)}
- onSave={saveChanges}
- hasUnsavedChanges={hasUnsavedChanges}
- pages={pages}
- project={project}
- />
-
-
-
- );
-};
diff --git a/components/editor/pages/index.tsx b/components/editor/pages/index.tsx
deleted file mode 100644
index 20451664558f97770d9f58f67d3abc6d9653e2fc..0000000000000000000000000000000000000000
--- a/components/editor/pages/index.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Page } from "@/types";
-import { ListPagesItem } from "./page";
-import { useEditor } from "@/hooks/useEditor";
-
-export function ListPages() {
- const { pages, setPages, currentPage, setCurrentPage } = useEditor();
- return (
-
- {pages.map((page: Page, i: number) => (
- {
- setPages(pages.filter((page) => page.path !== path));
- setCurrentPage("index.html");
- }}
- index={i}
- />
- ))}
-
- );
-}
diff --git a/components/editor/pages/page.tsx b/components/editor/pages/page.tsx
deleted file mode 100644
index ba3008313f787966774aed441951497128a398de..0000000000000000000000000000000000000000
--- a/components/editor/pages/page.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import classNames from "classnames";
-import { FileCode, XIcon } from "lucide-react";
-
-import { Button } from "@/components/ui/button";
-import { Page } from "@/types";
-
-export function ListPagesItem({
- page,
- currentPage,
- onSelectPage,
- onDeletePage,
- index,
-}: {
- page: Page;
- currentPage: string;
- onSelectPage: (path: string, newPath?: string) => void;
- onDeletePage: (path: string) => void;
- index: number;
-}) {
- return (
- onSelectPage(page.path)}
- title={page.path}
- >
-
- {page.path}
- {index > 0 && (
- {
- e.stopPropagation();
- if (
- window.confirm(
- "Are you sure you want to delete this page? This action cannot be undone."
- )
- ) {
- onDeletePage(page.path);
- }
- }}
- >
-
-
- )}
-
- );
-}
diff --git a/components/editor/preview/index.tsx b/components/editor/preview/index.tsx
deleted file mode 100644
index 5fb8b23898f053402097125cf0f29b38f104559b..0000000000000000000000000000000000000000
--- a/components/editor/preview/index.tsx
+++ /dev/null
@@ -1,871 +0,0 @@
-"use client";
-
-import { useRef, useState, useEffect, useCallback, useMemo } from "react";
-import { useUpdateEffect } from "react-use";
-import classNames from "classnames";
-
-import { cn } from "@/lib/utils";
-import { GridPattern } from "@/components/magic-ui/grid-pattern";
-import { useEditor } from "@/hooks/useEditor";
-import { useAi } from "@/hooks/useAi";
-import { htmlTagToText } from "@/lib/html-tag-to-text";
-import { AnimatedBlobs } from "@/components/animated-blobs";
-import { AiLoading } from "../ask-ai/loading";
-import { defaultHTML } from "@/lib/consts";
-import { HistoryNotification } from "../history-notification";
-import { api } from "@/lib/api";
-import { toast } from "sonner";
-import { RefreshCcw, TriangleAlert } from "lucide-react";
-import { Page } from "@/types";
-
-export const Preview = ({
- isNew,
- namespace,
- repoId,
-}: {
- isNew: boolean;
- namespace?: string;
- repoId?: string;
-}) => {
- const {
- project,
- device,
- isLoadingProject,
- currentTab,
- currentCommit,
- setCurrentCommit,
- currentPageData,
- pages,
- setPages,
- setCurrentPage,
- previewPage,
- setPreviewPage,
- setLastSavedPages,
- hasUnsavedChanges,
- } = useEditor();
- const {
- isEditableModeEnabled,
- setSelectedElement,
- isAiWorking,
- globalAiLoading,
- } = useAi();
-
- const iframeRef = useRef(null);
-
- const [hoveredElement, setHoveredElement] = useState<{
- tagName: string;
- rect: { top: number; left: number; width: number; height: number };
- } | null>(null);
- const [isPromotingVersion, setIsPromotingVersion] = useState(false);
- const [stableHtml, setStableHtml] = useState("");
- const [throttledHtml, setThrottledHtml] = useState("");
- const lastUpdateTimeRef = useRef(0);
- const [iframeKey, setIframeKey] = useState(0);
- const [commitPages, setCommitPages] = useState([]);
- const [isLoadingCommitPages, setIsLoadingCommitPages] = useState(false);
- const prevCommitRef = useRef(null);
-
- useEffect(() => {
- if (!previewPage && pages.length > 0) {
- const indexPage = pages.find(
- (p) => p.path === "index.html" || p.path === "index" || p.path === "/"
- );
- const firstHtmlPage = pages.find((p) => p.path.endsWith(".html"));
- setPreviewPage(indexPage?.path || firstHtmlPage?.path || "index.html");
- }
- }, [pages, previewPage]);
-
- const pagesToUse = currentCommit ? commitPages : pages;
-
- const previewPageData = useMemo(() => {
- const found = pagesToUse.find((p) => {
- const normalizedPagePath = p.path.replace(/^\.?\//, "");
- const normalizedPreviewPage = previewPage.replace(/^\.?\//, "");
- return normalizedPagePath === normalizedPreviewPage;
- });
- return found || (pagesToUse.length > 0 ? pagesToUse[0] : currentPageData);
- }, [pagesToUse, previewPage, currentPageData]);
-
- // Fetch commit pages when currentCommit changes
- useEffect(() => {
- if (currentCommit && namespace && repoId) {
- setIsLoadingCommitPages(true);
- api
- .get(`/me/projects/${namespace}/${repoId}/commits/${currentCommit}`)
- .then((res) => {
- if (res.data.ok) {
- setCommitPages(res.data.pages);
- // Set preview page to index.html if available
- const indexPage = res.data.pages.find(
- (p: Page) =>
- p.path === "index.html" || p.path === "index" || p.path === "/"
- );
- if (indexPage) {
- setPreviewPage(indexPage.path);
- }
- // Refresh iframe to show commit version
- setIframeKey((prev) => prev + 1);
- }
- })
- .catch((err) => {
- toast.error(
- err.response?.data?.error || "Failed to fetch commit pages"
- );
- })
- .finally(() => {
- setIsLoadingCommitPages(false);
- });
- } else if (!currentCommit && prevCommitRef.current !== null) {
- // Only clear commitPages when transitioning from a commit to no commit
- setCommitPages([]);
- }
- prevCommitRef.current = currentCommit;
- }, [currentCommit, namespace, repoId]);
-
- // Create navigation interception script
- const createNavigationScript = useCallback((availablePages: Page[]) => {
- const pagePaths = availablePages.map((p) => p.path.replace(/^\.?\//, ""));
- return `
- (function() {
- const availablePages = ${JSON.stringify(pagePaths)};
-
- function normalizePath(path) {
- let normalized = path.replace(/^\.?\//, "");
- if (normalized === "" || normalized === "/") {
- normalized = "index.html";
- }
- const hashIndex = normalized.indexOf("#");
- if (hashIndex !== -1) {
- normalized = normalized.substring(0, hashIndex);
- }
- if (!normalized.includes(".")) {
- normalized = normalized + ".html";
- }
- return normalized;
- }
-
- function handleNavigation(url) {
- if (!url) return;
-
- // Handle hash-only navigation
- if (url.startsWith("#")) {
- const targetElement = document.querySelector(url);
- if (targetElement) {
- targetElement.scrollIntoView({ behavior: "smooth" });
- }
- // Search in shadow DOM
- const searchInShadows = (root) => {
- const elements = root.querySelectorAll("*");
- for (const el of elements) {
- if (el.shadowRoot) {
- const found = el.shadowRoot.querySelector(url);
- if (found) {
- found.scrollIntoView({ behavior: "smooth" });
- return;
- }
- searchInShadows(el.shadowRoot);
- }
- }
- };
- searchInShadows(document);
- return;
- }
-
- // Handle external URLs
- if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
- window.open(url, "_blank");
- return;
- }
-
- const normalizedPath = normalizePath(url);
- if (availablePages.includes(normalizedPath)) {
- // Dispatch custom event to notify parent
- window.parent.postMessage({ type: 'navigate', path: normalizedPath }, '*');
- } else {
- console.warn('Page not found:', normalizedPath);
- }
- }
-
- // Intercept window.location methods
- const originalAssign = window.location.assign;
- const originalReplace = window.location.replace;
-
- window.location.assign = function(url) {
- handleNavigation(url);
- };
-
- window.location.replace = function(url) {
- handleNavigation(url);
- };
-
- // Intercept window.location.href setter
- try {
- let currentHref = window.location.href;
- Object.defineProperty(window.location, 'href', {
- get: function() {
- return currentHref;
- },
- set: function(url) {
- handleNavigation(url);
- },
- configurable: true
- });
- } catch (e) {
- // Fallback: use proxy if defineProperty fails
- console.warn('Could not intercept location.href:', e);
- }
-
- // Intercept link clicks
- document.addEventListener('click', function(e) {
- const anchor = e.target.closest('a');
- if (anchor && anchor.href) {
- const href = anchor.getAttribute('href');
- if (href && !href.startsWith('http://') && !href.startsWith('https://') && !href.startsWith('//') && !href.startsWith('mailto:') && !href.startsWith('tel:')) {
- e.preventDefault();
- handleNavigation(href);
- }
- }
- }, true);
-
- // Intercept form submissions
- document.addEventListener('submit', function(e) {
- const form = e.target;
- if (form.action && !form.action.startsWith('http://') && !form.action.startsWith('https://') && !form.action.startsWith('//')) {
- e.preventDefault();
- handleNavigation(form.action);
- }
- }, true);
- })();
- `;
- }, []);
-
- const injectAssetsIntoHtml = useCallback(
- (html: string, pagesToUse: Page[] = pages): string => {
- if (!html) return html;
-
- const cssFiles = pagesToUse.filter(
- (p) => p.path.endsWith(".css") && p.path !== previewPageData?.path
- );
- const jsFiles = pagesToUse.filter(
- (p) => p.path.endsWith(".js") && p.path !== previewPageData?.path
- );
- const jsonFiles = pagesToUse.filter(
- (p) => p.path.endsWith(".json") && p.path !== previewPageData?.path
- );
-
- let modifiedHtml = html;
-
- // Inject navigation script for srcDoc
- const navigationScript = createNavigationScript(pagesToUse);
-
- // Inject all CSS files
- if (cssFiles.length > 0) {
- const allCssContent = cssFiles
- .map(
- (file) =>
- ``
- )
- .join("\n");
-
- if (modifiedHtml.includes("")) {
- modifiedHtml = modifiedHtml.replace(
- "",
- `${allCssContent}\n`
- );
- } else if (modifiedHtml.includes("")) {
- modifiedHtml = modifiedHtml.replace(
- "",
- `\n${allCssContent}`
- );
- } else {
- modifiedHtml = allCssContent + "\n" + modifiedHtml;
- }
-
- cssFiles.forEach((file) => {
- const escapedPath = file.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- modifiedHtml = modifiedHtml.replace(
- new RegExp(
- ` ]*href=["'][\\.\/]*${escapedPath}["'][^>]*>`,
- "gi"
- ),
- ""
- );
- });
- }
-
- if (jsFiles.length > 0) {
- const allJsContent = jsFiles
- .map(
- (file) =>
- ``
- )
- .join("\n");
-
- if (modifiedHtml.includes("")) {
- modifiedHtml = modifiedHtml.replace(
- "",
- `${allJsContent}\n`
- );
- } else if (modifiedHtml.includes("")) {
- modifiedHtml = modifiedHtml + allJsContent;
- } else {
- modifiedHtml = modifiedHtml + "\n" + allJsContent;
- }
-
- jsFiles.forEach((file) => {
- const escapedPath = file.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- modifiedHtml = modifiedHtml.replace(
- new RegExp(
- ``
- )
- .join("\n");
-
- if (modifiedHtml.includes("")) {
- modifiedHtml = modifiedHtml.replace(
- " ")) {
- modifiedHtml = modifiedHtml + allJsonContent;
- } else {
- modifiedHtml = modifiedHtml + "\n" + allJsonContent;
- }
-
- jsonFiles.forEach((file) => {
- const escapedPath = file.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- modifiedHtml = modifiedHtml.replace(
- new RegExp(
- ``
- );
- } else if (modifiedHtml.includes("")) {
- modifiedHtml = modifiedHtml.replace(
- "",
- `\n`
- );
- } else if (modifiedHtml.includes("")) {
- modifiedHtml = modifiedHtml.replace(
- "",
- `\n`
- );
- } else {
- modifiedHtml =
- `\n` + modifiedHtml;
- }
- }
-
- return modifiedHtml;
- },
- [pages, previewPageData?.path, createNavigationScript]
- );
-
- useEffect(() => {
- if (isNew && previewPageData?.html) {
- const now = Date.now();
- const timeSinceLastUpdate = now - lastUpdateTimeRef.current;
-
- if (lastUpdateTimeRef.current === 0 || timeSinceLastUpdate >= 3000) {
- const processedHtml = injectAssetsIntoHtml(
- previewPageData.html,
- pagesToUse
- );
- setThrottledHtml(processedHtml);
- lastUpdateTimeRef.current = now;
- } else {
- const timeUntilNextUpdate = 3000 - timeSinceLastUpdate;
- const timer = setTimeout(() => {
- const processedHtml = injectAssetsIntoHtml(
- previewPageData.html,
- pagesToUse
- );
- setThrottledHtml(processedHtml);
- lastUpdateTimeRef.current = Date.now();
- }, timeUntilNextUpdate);
- return () => clearTimeout(timer);
- }
- }
- }, [isNew, previewPageData?.html, injectAssetsIntoHtml, pagesToUse]);
-
- useEffect(() => {
- if (!isAiWorking && !globalAiLoading && previewPageData?.html) {
- const processedHtml = injectAssetsIntoHtml(
- previewPageData.html,
- pagesToUse
- );
- setStableHtml(processedHtml);
- }
- }, [
- isAiWorking,
- globalAiLoading,
- previewPageData?.html,
- injectAssetsIntoHtml,
- previewPage,
- pagesToUse,
- ]);
-
- useEffect(() => {
- if (
- previewPageData?.html &&
- !stableHtml &&
- !isAiWorking &&
- !globalAiLoading
- ) {
- const processedHtml = injectAssetsIntoHtml(
- previewPageData.html,
- pagesToUse
- );
- setStableHtml(processedHtml);
- }
- }, [
- previewPageData?.html,
- stableHtml,
- isAiWorking,
- globalAiLoading,
- injectAssetsIntoHtml,
- pagesToUse,
- ]);
-
- const setupIframeListeners = () => {
- if (iframeRef?.current?.contentDocument) {
- const iframeDocument = iframeRef.current.contentDocument;
-
- iframeDocument.addEventListener(
- "click",
- handleCustomNavigation as any,
- true
- );
-
- if (isEditableModeEnabled) {
- iframeDocument.addEventListener("mouseover", handleMouseOver);
- iframeDocument.addEventListener("mouseout", handleMouseOut);
- iframeDocument.addEventListener("click", handleClick);
- }
- }
- };
-
- // Listen for navigation messages from iframe
- useEffect(() => {
- const handleMessage = (event: MessageEvent) => {
- if (event.data?.type === "navigate" && event.data?.path) {
- setPreviewPage(event.data.path);
- }
- };
- window.addEventListener("message", handleMessage);
- return () => window.removeEventListener("message", handleMessage);
- }, [setPreviewPage]);
-
- useEffect(() => {
- const cleanupListeners = () => {
- if (iframeRef?.current?.contentDocument) {
- const iframeDocument = iframeRef.current.contentDocument;
- iframeDocument.removeEventListener(
- "click",
- handleCustomNavigation as any,
- true
- );
- iframeDocument.removeEventListener("mouseover", handleMouseOver);
- iframeDocument.removeEventListener("mouseout", handleMouseOut);
- iframeDocument.removeEventListener("click", handleClick);
- }
- };
-
- const timer = setTimeout(() => {
- if (iframeRef?.current?.contentDocument) {
- cleanupListeners();
- setupIframeListeners();
- }
- }, 50);
-
- return () => {
- clearTimeout(timer);
- cleanupListeners();
- };
- }, [isEditableModeEnabled, stableHtml, throttledHtml, previewPage]);
-
- const refreshIframe = () => {
- setIframeKey((prev) => prev + 1);
- };
-
- const promoteVersion = async () => {
- setIsPromotingVersion(true);
- await api
- .post(
- `/me/projects/${project?.space_id}/commits/${currentCommit}/promote`
- )
- .then((res) => {
- if (res.data.ok) {
- setCurrentCommit(null);
- setPages(res.data.pages);
- setCurrentPage(res.data.pages[0].path);
- setLastSavedPages(res.data.pages);
- setPreviewPage(res.data.pages[0].path);
- toast.success("Version promoted successfully");
- }
- })
- .catch((err) => {
- toast.error(err.response.data.error);
- });
- setIsPromotingVersion(false);
- };
-
- const handleMouseOver = (event: MouseEvent) => {
- if (iframeRef?.current) {
- const iframeDocument = iframeRef.current.contentDocument;
- if (iframeDocument) {
- const targetElement = event.target as HTMLElement;
- if (
- hoveredElement?.tagName !== targetElement.tagName ||
- hoveredElement?.rect.top !==
- targetElement.getBoundingClientRect().top ||
- hoveredElement?.rect.left !==
- targetElement.getBoundingClientRect().left ||
- hoveredElement?.rect.width !==
- targetElement.getBoundingClientRect().width ||
- hoveredElement?.rect.height !==
- targetElement.getBoundingClientRect().height
- ) {
- if (targetElement !== iframeDocument.body) {
- const rect = targetElement.getBoundingClientRect();
- setHoveredElement({
- tagName: targetElement.tagName,
- rect: {
- top: rect.top,
- left: rect.left,
- width: rect.width,
- height: rect.height,
- },
- });
- 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 path = event.composedPath();
- const targetElement = path[0] as HTMLElement;
-
- const findClosestAnchor = (
- element: HTMLElement
- ): HTMLAnchorElement | null => {
- let current: HTMLElement | null = element;
- while (current) {
- if (current.tagName?.toUpperCase() === "A") {
- return current as HTMLAnchorElement;
- }
- if (current === iframeDocument.body) {
- break;
- }
- const parent: Node | null = current.parentNode;
- if (parent && parent.nodeType === 11) {
- current = (parent as ShadowRoot).host as HTMLElement;
- } else if (parent && parent.nodeType === 1) {
- current = parent as HTMLElement;
- } else {
- break;
- }
- }
- return null;
- };
-
- const anchorElement = findClosestAnchor(targetElement);
-
- if (anchorElement) {
- return;
- }
-
- if (targetElement !== iframeDocument.body) {
- setSelectedElement(targetElement);
- }
- }
- }
- };
-
- const handleCustomNavigation = (event: MouseEvent) => {
- if (iframeRef?.current) {
- const iframeDocument = iframeRef.current.contentDocument;
- if (iframeDocument) {
- const path = event.composedPath();
- const actualTarget = path[0] as HTMLElement;
-
- const findClosestAnchor = (
- element: HTMLElement
- ): HTMLAnchorElement | null => {
- let current: HTMLElement | null = element;
- while (current) {
- if (current.tagName?.toUpperCase() === "A") {
- return current as HTMLAnchorElement;
- }
- if (current === iframeDocument.body) {
- break;
- }
- const parent: Node | null = current.parentNode;
- if (parent && parent.nodeType === 11) {
- current = (parent as ShadowRoot).host as HTMLElement;
- } else if (parent && parent.nodeType === 1) {
- current = parent as HTMLElement;
- } else {
- break;
- }
- }
- return null;
- };
-
- const anchorElement = findClosestAnchor(actualTarget);
- if (anchorElement) {
- let href = anchorElement.getAttribute("href");
- if (href) {
- event.stopPropagation();
- event.preventDefault();
-
- if (href.startsWith("#")) {
- let targetElement = iframeDocument.querySelector(href);
-
- if (!targetElement) {
- const searchInShadows = (
- root: Document | ShadowRoot
- ): Element | null => {
- const elements = root.querySelectorAll("*");
- for (const el of elements) {
- if (el.shadowRoot) {
- const found = el.shadowRoot.querySelector(href);
- if (found) return found;
- const nested = searchInShadows(el.shadowRoot);
- if (nested) return nested;
- }
- }
- return null;
- };
- targetElement = searchInShadows(iframeDocument);
- }
-
- if (targetElement) {
- targetElement.scrollIntoView({ behavior: "smooth" });
- }
- return;
- }
-
- let normalizedHref = href.replace(/^\.?\//, "");
-
- if (normalizedHref === "" || normalizedHref === "/") {
- normalizedHref = "index.html";
- }
-
- const hashIndex = normalizedHref.indexOf("#");
- if (hashIndex !== -1) {
- normalizedHref = normalizedHref.substring(0, hashIndex);
- }
-
- if (!normalizedHref.includes(".")) {
- normalizedHref = normalizedHref + ".html";
- }
-
- const isPageExist = pagesToUse.some((page) => {
- const pagePath = page.path.replace(/^\.?\//, "");
- return pagePath === normalizedHref;
- });
-
- if (isPageExist) {
- setPreviewPage(normalizedHref);
- }
- }
- }
- }
- }
- };
-
- return (
-
-
- {/* Preview page indicator */}
- {!isAiWorking && hoveredElement && isEditableModeEnabled && (
-
-
- {htmlTagToText(hoveredElement.tagName.toLowerCase())}
-
-
- )}
- {isLoadingProject ? (
-
- ) : (
- <>
- {isLoadingCommitPages && (
-
- )}
- {!isNew && !currentCommit && (
-
-
-
-
- Preview version of the project. Try refreshing the preview if
- you experience any issues.
-
-
-
-
- Refresh
-
-
- )}
-
- );
-};
diff --git a/components/editor/save-changes-popup/index.tsx b/components/editor/save-changes-popup/index.tsx
deleted file mode 100644
index 76c9a9153835ce859ccfd64320e4e725ad9e1b1b..0000000000000000000000000000000000000000
--- a/components/editor/save-changes-popup/index.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-"use client";
-import { useState } from "react";
-import { toast } from "sonner";
-import { Save, X, ChevronUp, ChevronDown } from "lucide-react";
-import { motion, AnimatePresence } from "framer-motion";
-import classNames from "classnames";
-
-import { Page } from "@/types";
-import { api } from "@/lib/api";
-import { Button } from "@/components/ui/button";
-import Loading from "@/components/loading";
-
-interface SaveChangesPopupProps {
- isOpen: boolean;
- onClose: () => void;
- onSave: () => Promise;
- hasUnsavedChanges: boolean;
- pages: Page[];
- project?: any;
-}
-
-export const SaveChangesPopup = ({
- isOpen,
- onClose,
- onSave,
- hasUnsavedChanges,
- pages,
- project,
-}: SaveChangesPopupProps) => {
- const [isSaving, setIsSaving] = useState(false);
- const [isCollapsed, setIsCollapsed] = useState(false);
-
- const handleSave = async () => {
- if (!project || !hasUnsavedChanges) return;
-
- setIsSaving(true);
- try {
- await onSave();
- toast.success("Changes saved successfully!");
- onClose();
- } catch (error: any) {
- toast.error(error.message || "Failed to save changes");
- } finally {
- setIsSaving(false);
- }
- };
-
- if (!hasUnsavedChanges || !isOpen) return null;
-
- return (
-
-
- {isCollapsed ? (
- // Collapsed state
-
-
-
- Unsaved Changes
-
- setIsCollapsed(false)}
- >
-
-
-
- ) : (
- // Expanded state
-
-
-
-
-
-
-
setIsCollapsed(true)}
- >
-
-
-
-
- You have unsaved changes in your project. Save them to
- preserve your work.
-
-
-
- {isSaving ? (
-
- ) : (
-
- )}
- Save Changes
-
-
- Later
-
-
-
-
-
- )}
-
-
- );
-};
diff --git a/components/editor/switch-devide/index.tsx b/components/editor/switch-devide/index.tsx
deleted file mode 100644
index 934e75e0a84674a485c06386ae984818b1401bba..0000000000000000000000000000000000000000
--- a/components/editor/switch-devide/index.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import classNames from "classnames";
-import { Laptop, Smartphone } from "lucide-react";
-
-import { useEditor } from "@/hooks/useEditor";
-
-const DEVICES = [
- {
- name: "desktop",
- icon: Laptop,
- },
- {
- name: "mobile",
- icon: Smartphone,
- },
-];
-
-export const SwitchDevice = () => {
- const { device, setDevice } = useEditor();
- return (
-
- {DEVICES.map((dvc) => (
- setDevice(dvc.name)}
- >
-
- {dvc.name}
-
- ))}
-
- );
-};
diff --git a/components/icons/discord.tsx b/components/icons/discord.tsx
deleted file mode 100644
index ac42b39459807e623639cf3f74e12a78b0e63778..0000000000000000000000000000000000000000
--- a/components/icons/discord.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-export const DiscordIcon = ({ className }: { className?: string }) => {
- return (
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/components/iframe-detector/index.tsx b/components/iframe-detector/index.tsx
deleted file mode 100644
index f3c7e7ed0adb3874aec45df5e52a0572daa85326..0000000000000000000000000000000000000000
--- a/components/iframe-detector/index.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import IframeWarningModal from "./modal";
-
-export default function IframeDetector() {
- const [showWarning, setShowWarning] = useState(false);
-
- useEffect(() => {
- // Helper function to check if a hostname is from allowed domains
- const isAllowedDomain = (hostname: string) => {
- const host = hostname.toLowerCase();
- return (
- host.endsWith(".huggingface.co") ||
- host.endsWith(".hf.co") ||
- host === "huggingface.co" ||
- host === "hf.co" ||
- host === "enzostvs-deepsite.hf.space" ||
- host === "huggingface.co/deepsite"
- );
- };
-
- // Check if the current window is in an iframe
- const isInIframe = () => {
- try {
- return window.self !== window.top;
- } catch {
- // If we can't access window.top due to cross-origin restrictions,
- // we're likely in an iframe
- return true;
- }
- };
-
- // Additional check: compare window location with parent location
- const isEmbedded = () => {
- try {
- return window.location !== window.parent.location;
- } catch {
- // Cross-origin iframe
- return true;
- }
- };
-
- // SEO: Add canonical URL meta tag when in iframe
- const addCanonicalUrl = () => {
- // Remove existing canonical link if present
- const existingCanonical = document.querySelector('link[rel="canonical"]');
- if (existingCanonical) {
- existingCanonical.remove();
- }
-
- // Add canonical URL pointing to the standalone version
- const canonical = document.createElement("link");
- canonical.rel = "canonical";
- canonical.href = window.location.href;
- document.head.appendChild(canonical);
-
- // Add meta tag to indicate this page should not be indexed when in iframe from unauthorized domains
- if (isInIframe() || isEmbedded()) {
- try {
- const parentHostname = document.referrer
- ? new URL(document.referrer).hostname
- : null;
- if (parentHostname && !isAllowedDomain(parentHostname)) {
- // Add noindex meta tag when embedded in unauthorized domains
- const noIndexMeta = document.createElement("meta");
- noIndexMeta.name = "robots";
- noIndexMeta.content = "noindex, nofollow";
- document.head.appendChild(noIndexMeta);
- }
- } catch (error) {
- // Silently handle cross-origin errors
- console.debug(
- "SEO: Could not determine parent domain for iframe indexing rules"
- );
- }
- }
- };
-
- // Check if we're in an iframe from a non-allowed domain
- const shouldShowWarning = () => {
- if (!isInIframe() && !isEmbedded()) {
- return false; // Not in an iframe
- }
-
- try {
- // Try to get the parent's hostname
- const parentHostname = window.parent.location.hostname;
- return !isAllowedDomain(parentHostname);
- } catch {
- // Cross-origin iframe - try to get referrer instead
- try {
- if (document.referrer) {
- const referrerUrl = new URL(document.referrer);
- return !isAllowedDomain(referrerUrl.hostname);
- }
- } catch {
- // If we can't determine the parent domain, assume it's not allowed
- }
- return true;
- }
- };
-
- // Always add canonical URL for SEO, regardless of iframe status
- addCanonicalUrl();
-
- if (shouldShowWarning()) {
- // Show warning modal instead of redirecting immediately
- setShowWarning(true);
- }
- }, []);
-
- return (
-
- );
-}
diff --git a/components/iframe-detector/modal.tsx b/components/iframe-detector/modal.tsx
deleted file mode 100644
index 8c5e3f34bc11bd86ae491707f0000db839d25f2d..0000000000000000000000000000000000000000
--- a/components/iframe-detector/modal.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-"use client";
-
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog";
-import { Button } from "@/components/ui/button";
-import { ExternalLink, AlertTriangle } from "lucide-react";
-
-interface IframeWarningModalProps {
- isOpen: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export default function IframeWarningModal({
- isOpen,
-}: // onOpenChange,
-IframeWarningModalProps) {
- const handleVisitSite = () => {
- window.open("https://huggingface.co/deepsite", "_blank");
- };
-
- return (
- {}}>
-
-
-
-
-
Unauthorized Embedding
-
-
- You're viewing DeepSite through an unauthorized iframe. For the
- best experience and security, please visit the official website
- directly.
-
-
-
-
-
Why visit the official site?
-
- β’ Better performance and security
- β’ Full functionality access
- β’ Latest features and updates
- β’ Proper authentication support
-
-
-
-
-
-
- Visit huggingface.co/deepsite
-
-
-
-
- );
-}
diff --git a/components/loading/full-loading.tsx b/components/loading/full-loading.tsx
deleted file mode 100644
index 2ee9c53f9845b3c8b4e9ea073fbacbe26addc7c6..0000000000000000000000000000000000000000
--- a/components/loading/full-loading.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import Loading from ".";
-import { AnimatedBlobs } from "../animated-blobs";
-
-export const FullLoading = () => {
- return (
-
-
- {/*
Fetching user data...
*/}
-
-
- );
-};
diff --git a/components/login-modal/index.tsx b/components/login-modal/index.tsx
deleted file mode 100644
index aed8e8160b9c31ae8c5a799258ed27bb383d8e18..0000000000000000000000000000000000000000
--- a/components/login-modal/index.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { useLocalStorage } from "react-use";
-import { Button } from "@/components/ui/button";
-import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
-import { useUser } from "@/hooks/useUser";
-import { isTheSameHtml } from "@/lib/compare-html-diff";
-import { Page } from "@/types";
-import { useEditor } from "@/hooks/useEditor";
-
-export const LoginModal = ({
- open,
- onClose,
- title = "Log In to use DeepSite for free",
- description = "Log In through your Hugging Face account to continue using DeepSite and increase your monthly free limit.",
- prompt,
-}: {
- open: boolean;
- onClose: React.Dispatch>;
- title?: string;
- description?: string;
- prompt?: string;
-}) => {
- const { openLoginWindow } = useUser();
- const { pages } = useEditor();
- const [, setStorage] = useLocalStorage("pages");
- const [, setPromptStorage] = useLocalStorage("prompt", "");
- const handleClick = async () => {
- if (prompt) {
- setPromptStorage(prompt);
- }
- if (pages && !isTheSameHtml(pages[0].html)) {
- setStorage(pages);
- }
- openLoginWindow();
- onClose(false);
- };
- return (
-
-
-
-
-
-
- πͺ
-
-
- π
-
-
- π
-
-
- {title}
-
- {description}
-
-
- Log In to Continue
-
-
-
-
- );
-};
diff --git a/components/magic-ui/grid-pattern.tsx b/components/magic-ui/grid-pattern.tsx
deleted file mode 100644
index 0ead903d1de84aba97f328e5686f3c631a33cef6..0000000000000000000000000000000000000000
--- a/components/magic-ui/grid-pattern.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { useId } from "react";
-import { cn } from "@/lib/utils";
-
-interface GridPatternProps extends React.SVGProps {
- width?: number;
- height?: number;
- x?: number;
- y?: number;
- squares?: Array<[x: number, y: number]>;
- strokeDasharray?: string;
- className?: string;
- [key: string]: unknown;
-}
-
-export function GridPattern({
- width = 40,
- height = 40,
- x = -1,
- y = -1,
- strokeDasharray = "0",
- squares,
- className,
- ...props
-}: GridPatternProps) {
- const id = useId();
-
- return (
-
-
-
-
-
-
-
- {squares && (
-
- {squares.map(([x, y]) => (
-
- ))}
-
- )}
-
- );
-}
diff --git a/components/my-projects/index.tsx b/components/my-projects/index.tsx
deleted file mode 100644
index 7d61ecede75ab7557b1dba9da1c9f74b57de52a4..0000000000000000000000000000000000000000
--- a/components/my-projects/index.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-"use client";
-import { Plus } from "lucide-react";
-import Link from "next/link";
-import { useState } from "react";
-import { toast } from "sonner";
-
-import { useUser } from "@/hooks/useUser";
-import { ProjectType } from "@/types";
-import { ProjectCard } from "./project-card";
-import { MAX_FREE_PROJECTS } from "@/lib/utils";
-import { ProTag } from "@/components/pro-modal";
-import { Button } from "@/components/ui/button";
-import { useProModal } from "@/components/contexts/pro-context";
-import { api } from "@/lib/api";
-import { NotLogged } from "../not-logged/not-logged";
-
-export function MyProjects() {
- const { user, projects, setProjects } = useUser();
- const { openProModal } = useProModal();
-
- if (!user) {
- return ;
- }
-
- const onDelete = async (project: ProjectType) => {
- const response = await api.delete(`/me/projects/${project.name}`);
- if (response.data.ok) {
- toast.success("Project deleted successfully!");
- const newProjects = projects.filter((p) => p.id !== project.id);
- setProjects(newProjects);
- } else {
- toast.error(response.data.error);
- }
- };
- return (
- <>
-
-
-
- {projects.length < MAX_FREE_PROJECTS || user?.isPro ? (
-
-
- Create Project
-
- ) : (
-
openProModal([])}
- >
-
- Create Project
-
- )}
- {projects.map((project: ProjectType, index: number) => (
-
onDelete(project)}
- />
- ))}
-
-
- >
- );
-}
diff --git a/components/my-projects/load-project.tsx b/components/my-projects/load-project.tsx
deleted file mode 100644
index a86a6f2d32095b046d614b2730c2b6d50811d48d..0000000000000000000000000000000000000000
--- a/components/my-projects/load-project.tsx
+++ /dev/null
@@ -1,200 +0,0 @@
-"use client";
-import { useState } from "react";
-import { Import } from "lucide-react";
-
-import { Button } from "@/components/ui/button";
-import {
- Dialog,
- DialogContent,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog";
-import Loading from "@/components/loading";
-import { Input } from "../ui/input";
-import { toast } from "sonner";
-import { api } from "@/lib/api";
-import { useUser } from "@/hooks/useUser";
-import { LoginModal } from "@/components/login-modal";
-import { useRouter } from "next/navigation";
-import { SpaceEntry } from "@huggingface/hub";
-
-export const LoadProject = ({
- fullXsBtn = false,
- onSuccess,
-}: {
- fullXsBtn?: boolean;
- onSuccess: (project: SpaceEntry) => void;
-}) => {
- const { user } = useUser();
- const router = useRouter();
-
- const [openLoginModal, setOpenLoginModal] = useState(false);
- const [open, setOpen] = useState(false);
- const [url, setUrl] = useState("");
- const [isLoading, setIsLoading] = useState(false);
-
- const checkIfUrlIsValid = (url: string) => {
- // should match a hugging face spaces URL like: https://huggingface.co/spaces/username/project or https://hf.co/spaces/username/project
- const urlPattern = new RegExp(
- /^(https?:\/\/)?(huggingface\.co|hf\.co)\/spaces\/([\w-]+)\/([\w-]+)$/,
- "i"
- );
- return urlPattern.test(url);
- };
-
- const handleClick = async () => {
- if (isLoading) return; // Prevent multiple clicks while loading
- if (!url) {
- toast.error("Please enter a URL.");
- return;
- }
- if (!checkIfUrlIsValid(url)) {
- toast.error("Please enter a valid Hugging Face Spaces URL.");
- return;
- }
-
- const [username, namespace] = url
- .replace("https://huggingface.co/spaces/", "")
- .replace("https://hf.co/spaces/", "")
- .split("/");
-
- setIsLoading(true);
- try {
- const response = await api.post(`/me/projects/${username}/${namespace}`);
- toast.success("Project imported successfully!");
- setOpen(false);
- setUrl("");
- onSuccess(response.data.project);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } catch (error: any) {
- if (error?.response?.data?.redirect) {
- return router.push(error.response.data.redirect);
- }
- toast.error(
- error?.response?.data?.error ?? "Failed to import the project."
- );
- } finally {
- setIsLoading(false);
- }
- };
-
- return (
- <>
- {!user ? (
- <>
- setOpenLoginModal(true)}
- >
-
- Load existing Project
-
- setOpenLoginModal(true)}
- >
- {fullXsBtn && }
- Load
- {fullXsBtn && " existing Project"}
-
-
- >
- ) : (
-
-
-
-
-
- Load existing Project
-
-
- {fullXsBtn && }
- Load
- {fullXsBtn && " existing Project"}
-
-
-
-
-
-
-
-
-
- Enter your Hugging Face Space
-
-
setUrl(e.target.value)}
- onBlur={(e) => {
- const inputUrl = e.target.value.trim();
- if (!inputUrl) {
- setUrl("");
- return;
- }
- if (!checkIfUrlIsValid(inputUrl)) {
- toast.error("Please enter a valid URL.");
- return;
- }
- setUrl(inputUrl);
- }}
- className="!bg-white !border-neutral-300 !text-neutral-800 !placeholder:text-neutral-400 selection:!bg-blue-100"
- />
-
-
-
- Then, let's import it!
-
-
- {isLoading ? (
- <>
-
- Fetching your Space...
- >
- ) : (
- <>Import your Space>
- )}
-
-
-
-
-
- )}
- >
- );
-};
diff --git a/components/my-projects/project-card.tsx b/components/my-projects/project-card.tsx
deleted file mode 100644
index 40d70d3d2412c5813e35dfba96e755d29a36a2ae..0000000000000000000000000000000000000000
--- a/components/my-projects/project-card.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import Link from "next/link";
-import { formatDistance } from "date-fns";
-import {
- Download,
- EllipsisVertical,
- Lock,
- Settings,
- Trash,
-} from "lucide-react";
-
-import { Button } from "@/components/ui/button";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { ProjectType } from "@/types";
-import { toast } from "sonner";
-import { useUser } from "@/hooks/useUser";
-
-// from-red-500 to-red-500
-// from-yellow-500 to-yellow-500
-// from-green-500 to-green-500
-// from-purple-500 to-purple-500
-// from-blue-500 to-blue-500
-// from-pink-500 to-pink-500
-// from-gray-500 to-gray-500
-// from-indigo-500 to-indigo-500
-
-export function ProjectCard({
- project,
- onDelete,
-}: {
- project: ProjectType;
- onDelete: () => void;
-}) {
- const { token } = useUser();
- const handleDelete = () => {
- if (
- confirm(
- "Are you sure you want to delete this project? This action cannot be undone."
- )
- ) {
- onDelete();
- }
- };
-
- const handleDownload = async () => {
- try {
- toast.info("Preparing download...");
- const response = await fetch(
- `/deepsite/api/me/projects/${project.name}/download`,
- {
- credentials: "include",
- headers: {
- Accept: "application/zip",
- Authorization: `Bearer ${token}`,
- },
- }
- );
-
- if (!response.ok) {
- const error = await response
- .json()
- .catch(() => ({ error: "Download failed" }));
- toast.error(error.error || "Failed to download project");
- return;
- }
-
- const blob = await response.blob();
-
- const url = window.URL.createObjectURL(blob);
- const link = document.createElement("a");
- link.href = url;
- link.download = `${project.name.replace(/\//g, "-")}.zip`;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- window.URL.revokeObjectURL(url);
-
- toast.success("Download started!");
- } catch (error) {
- console.error("Download error:", error);
- toast.error("Failed to download project");
- }
- };
- // from-gray-600 to-gray-600
- // from-blue-600 to-blue-600
- // from-green-600 to-green-600
- // from-yellow-600 to-yellow-600
- // from-purple-600 to-purple-600
- // from-pink-600 to-pink-600
- // from-red-600 to-red-600
- // from-orange-600 to-orange-600
-
- return (
-
-
- {project.private ? (
-
-
-
-
- Private
-
-
-
{project.cardData?.emoji}
-
- ) : (
-
-
-
- )}
-
-
- Open project
-
-
-
-
-
- {project?.cardData?.title ?? project.name}
-
-
- Updated{" "}
- {formatDistance(
- new Date(project.updatedAt || Date.now()),
- new Date(),
- {
- addSuffix: true,
- }
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- Project Settings
-
-
-
-
- Download as ZIP
-
-
-
- Delete Project
-
-
-
-
-
-
- );
-}
diff --git a/components/not-logged/not-logged.tsx b/components/not-logged/not-logged.tsx
deleted file mode 100644
index 330b45cea1064d1e47840eee3f52956441fdf980..0000000000000000000000000000000000000000
--- a/components/not-logged/not-logged.tsx
+++ /dev/null
@@ -1,188 +0,0 @@
-"use client";
-
-import { AnimatedBlobs } from "../animated-blobs";
-import { FakeAskAi } from "../editor/ask-ai/fake-ask";
-
-export const NotLogged = () => {
- return (
-
-
-
- β¨ DeepSite v3 is out!
-
-
- Welcome to DeepSite
-
-
- Code your website with AI in seconds.
- Access the most simple and powerful AI Vibe Code Editor to create your
- next project.
-
-
-
-
-
-
-
-
- π 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.
-
-
-
-
-
-
-
- );
-};
diff --git a/components/pro-modal/index.tsx b/components/pro-modal/index.tsx
deleted file mode 100644
index 8b40e0372131f7c1a148f3f91d33da43321b361e..0000000000000000000000000000000000000000
--- a/components/pro-modal/index.tsx
+++ /dev/null
@@ -1,106 +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";
-import { Page } from "@/types";
-
-export const ProModal = ({
- open,
- pages,
- onClose,
-}: {
- open: boolean;
- pages?: Page[];
- onClose: React.Dispatch>;
-}) => {
- const [, setStorage] = useLocalStorage("pages");
- const handleProClick = () => {
- if (pages && !isTheSameHtml(pages?.[0].html)) {
- setStorage(pages);
- }
- window.open("https://huggingface.co/subscribe/pro?from=DeepSite", "_blank");
- onClose(false);
- };
- return (
-
-
-
-
-
-
- π
-
-
- π€©
-
-
- π₯³
-
-
-
- Only $9 to enhance your possibilities
-
-
- It seems like you have reached the monthly free limit of DeepSite.
-
-
-
- Upgrade to a Account, and unlock your
- DeepSite high quota access β‘
-
-
-
- You'll also unlock some Hugging Face PRO features, like:
-
-
-
- Get acces to thousands of AI app (ZeroGPU) with high quota
-
-
-
- Get exclusive early access to new features and updates
-
-
-
- Get free credits across all Inference Providers
-
-
- ... and lots more!
-
-
-
- Subscribe to PRO ($9/month)
-
-
-
-
- );
-};
-
-export const ProTag = ({
- className,
- ...props
-}: {
- className?: string;
- onClick?: () => void;
-}) => (
-
- PRO
-
-);
-export default ProModal;
diff --git a/components/public/navigation/index.tsx b/components/public/navigation/index.tsx
deleted file mode 100644
index d4f0598ccb8684330b7faa056eea61d4b05a98c6..0000000000000000000000000000000000000000
--- a/components/public/navigation/index.tsx
+++ /dev/null
@@ -1,181 +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 { ArrowRight } from "lucide-react";
-
-import { Button } from "@/components/ui/button";
-import Logo from "@/assets/logo.svg";
-import { useUser } from "@/hooks/useUser";
-import { UserMenu } from "@/components/user-menu";
-import { ProTag } from "@/components/pro-modal";
-import { DiscordIcon } from "@/components/icons/discord";
-
-const navigationLinks = [
- {
- name: "Create Website",
- href: "/new",
- },
- {
- name: "Features",
- href: "#features",
- },
- {
- name: "Community",
- href: "#community",
- },
- {
- name: "Deploy",
- href: "#deploy",
- },
-];
-
-export default function Navigation() {
- const { openLoginWindow, user, loading } = useUser();
- const [hash, setHash] = useState("");
-
- const selectorRef = useRef(null);
- const linksRef = useRef(
- new Array(navigationLinks.length).fill(null)
- );
- const [isScrolled, setIsScrolled] = useState(false);
-
- useMount(() => {
- const handleScroll = () => {
- const scrollTop = window.scrollY;
- setIsScrolled(scrollTop > 100);
- };
-
- const initialHash = window.location.hash;
- if (initialHash) {
- setHash(initialHash);
- calculateSelectorPosition(initialHash);
- }
-
- window.addEventListener("scroll", handleScroll);
- });
-
- useUnmount(() => {
- window.removeEventListener("scroll", () => {});
- });
-
- const handleClick = (href: string) => {
- setHash(href);
- calculateSelectorPosition(href);
- };
-
- const calculateSelectorPosition = (href: string) => {
- if (selectorRef.current && linksRef.current) {
- const index = navigationLinks.findIndex((l) => l.href === href);
- const targetLink = linksRef.current[index];
- if (targetLink) {
- const targetRect = targetLink.getBoundingClientRect();
- selectorRef.current.style.left = targetRect.left + "px";
- selectorRef.current.style.width = targetRect.width + "px";
- }
- }
- };
-
- return (
-
-
-
-
-
- DeepSite
-
- {user?.isPro && }
-
-
- {navigationLinks.map((link) => (
- {
- const index = navigationLinks.findIndex(
- (l) => l.href === link.href
- );
- if (el && linksRef.current[index] !== el) {
- linksRef.current[index] = el;
- }
- }}
- className="inline-block font-sans text-sm"
- >
- {
- handleClick(link.href);
- }}
- >
- {link.name}
-
-
- ))}
-
-
-
-
-
-
- Discord Community
- Discord
-
-
- {loading ? (
-
-
-
-
- ) : user ? (
-
- ) : (
- <>
-
- Start Vibe Coding
-
-
- >
- )}
-
-
-
- );
-}
diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx
deleted file mode 100644
index 71e428b4ca6154811e8f569d5fdd971ead095996..0000000000000000000000000000000000000000
--- a/components/ui/avatar.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as AvatarPrimitive from "@radix-ui/react-avatar"
-
-import { cn } from "@/lib/utils"
-
-function Avatar({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function AvatarImage({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function AvatarFallback({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export { Avatar, AvatarImage, AvatarFallback }
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
deleted file mode 100644
index a13ae96063c5e4b0eadcc5db34fb96ae37f3001a..0000000000000000000000000000000000000000
--- a/components/ui/button.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const buttonVariants = cva(
- "inline-flex items-center cursor-pointer justify-center gap-2 whitespace-nowrap rounded-full text-sm font-sans font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
- {
- variants: {
- variant: {
- default:
- "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 border border-primary",
- destructive:
- "bg-red-500 text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 [&_svg]:!text-white",
- outline:
- "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
- bordered:
- "border border-neutral-700/70 text-neutral-200 hover:brightness-120 !rounded-md bg-neutral-900",
- secondary:
- "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
- ghost:
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
- lightGray: "bg-neutral-200/60 hover:bg-neutral-200",
- gray: "bg-neutral-800 !rounded-md text-neutral-300 border border-neutral-700/40 hover:brightness-120",
- link: "text-primary underline-offset-4 hover:underline",
- ghostDarker:
- "text-white shadow-xs focus-visible:ring-black/40 bg-black/40 hover:bg-black/70",
- black: "bg-neutral-950 text-neutral-300 hover:brightness-110",
- sky: "bg-sky-500 text-white hover:brightness-110",
- },
- size: {
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
- sm: "h-8 rounded-full text-[13px] gap-1.5 px-3",
- lg: "h-10 rounded-full px-6 has-[>svg]:px-4",
- xl: "h-12 rounded-full px-8 has-[>svg]:px-5",
- icon: "size-9",
- iconXs: "size-7",
- iconXss: "size-6",
- iconXsss: "size-5",
- xs: "h-6 text-xs rounded-full pl-2 pr-2 gap-1",
- xss: "h-5 text-[10px] rounded-full pl-1.5 pr-1.5 gap-1",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- }
-);
-
-function Button({
- className,
- variant,
- size,
- asChild = false,
- ...props
-}: React.ComponentProps<"button"> &
- VariantProps & {
- asChild?: boolean;
- }) {
- const Comp = asChild ? Slot : "button";
-
- return (
-
- );
-}
-
-export { Button, buttonVariants };
diff --git a/components/ui/checkbox.tsx b/components/ui/checkbox.tsx
deleted file mode 100644
index bd4167ec38b98869c4ad17d22cd6514ea4d85e3c..0000000000000000000000000000000000000000
--- a/components/ui/checkbox.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
-import { CheckIcon } from "lucide-react";
-
-import { cn } from "@/lib/utils";
-
-function Checkbox({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
-
- );
-}
-
-export { Checkbox };
diff --git a/components/ui/collapsible.tsx b/components/ui/collapsible.tsx
deleted file mode 100644
index ae9fad04a3716b5d6f6c957b75841737eb8ed7a8..0000000000000000000000000000000000000000
--- a/components/ui/collapsible.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-"use client"
-
-import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
-
-function Collapsible({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function CollapsibleTrigger({
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function CollapsibleContent({
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx
deleted file mode 100644
index a6f410cbff8a4524949b2cec5f3b05bcb459b6b7..0000000000000000000000000000000000000000
--- a/components/ui/dialog.tsx
+++ /dev/null
@@ -1,143 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as DialogPrimitive from "@radix-ui/react-dialog";
-import { XIcon } from "lucide-react";
-
-import { cn } from "@/lib/utils";
-
-function Dialog({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DialogTrigger({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DialogPortal({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DialogClose({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DialogOverlay({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function DialogContent({
- className,
- children,
- showCloseButton = true,
- ...props
-}: React.ComponentProps & {
- showCloseButton?: boolean;
-}) {
- return (
-
-
-
- {children}
- {showCloseButton && (
-
-
- Close
-
- )}
-
-
- );
-}
-
-function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function DialogTitle({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function DialogDescription({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-export {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogOverlay,
- DialogPortal,
- DialogTitle,
- DialogTrigger,
-};
diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx
deleted file mode 100644
index 6bbd969389aa30c911b6388de5f22191eca62a32..0000000000000000000000000000000000000000
--- a/components/ui/dropdown-menu.tsx
+++ /dev/null
@@ -1,257 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
-import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
-
-import { cn } from "@/lib/utils";
-
-function DropdownMenu({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DropdownMenuPortal({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function DropdownMenuTrigger({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function DropdownMenuContent({
- className,
- sideOffset = 4,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- );
-}
-
-function DropdownMenuGroup({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function DropdownMenuItem({
- className,
- inset,
- variant = "default",
- ...props
-}: React.ComponentProps & {
- inset?: boolean;
- variant?: "default" | "destructive";
-}) {
- return (
-
- );
-}
-
-function DropdownMenuCheckboxItem({
- className,
- children,
- checked,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
-
-
- {children}
-
- );
-}
-
-function DropdownMenuRadioGroup({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function DropdownMenuRadioItem({
- className,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
-
-
- {children}
-
- );
-}
-
-function DropdownMenuLabel({
- className,
- inset,
- ...props
-}: React.ComponentProps & {
- inset?: boolean;
-}) {
- return (
-
- );
-}
-
-function DropdownMenuSeparator({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function DropdownMenuShortcut({
- className,
- ...props
-}: React.ComponentProps<"span">) {
- return (
-
- );
-}
-
-function DropdownMenuSub({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function DropdownMenuSubTrigger({
- className,
- inset,
- children,
- ...props
-}: React.ComponentProps & {
- inset?: boolean;
-}) {
- return (
-
- {children}
-
-
- );
-}
-
-function DropdownMenuSubContent({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-export {
- DropdownMenu,
- DropdownMenuPortal,
- DropdownMenuTrigger,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuLabel,
- DropdownMenuItem,
- DropdownMenuCheckboxItem,
- DropdownMenuRadioGroup,
- DropdownMenuRadioItem,
- DropdownMenuSeparator,
- DropdownMenuShortcut,
- DropdownMenuSub,
- DropdownMenuSubTrigger,
- DropdownMenuSubContent,
-};
diff --git a/components/ui/input.tsx b/components/ui/input.tsx
deleted file mode 100644
index 03295ca6ac617de95b78b09e5e3a6de897a204f0..0000000000000000000000000000000000000000
--- a/components/ui/input.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import * as React from "react"
-
-import { cn } from "@/lib/utils"
-
-function Input({ className, type, ...props }: React.ComponentProps<"input">) {
- return (
-
- )
-}
-
-export { Input }
diff --git a/components/ui/popover.tsx b/components/ui/popover.tsx
deleted file mode 100644
index 27576b28e331f9c4f42f91569c963bd99d97c598..0000000000000000000000000000000000000000
--- a/components/ui/popover.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as PopoverPrimitive from "@radix-ui/react-popover";
-
-import { cn } from "@/lib/utils";
-
-function Popover({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function PopoverTrigger({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function PopoverContent({
- className,
- align = "center",
- sideOffset = 4,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- );
-}
-
-function PopoverAnchor({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
diff --git a/components/ui/select.tsx b/components/ui/select.tsx
deleted file mode 100644
index dcbbc0ca0c781dfa6d2fe4ee6f1c9c2cad905a9b..0000000000000000000000000000000000000000
--- a/components/ui/select.tsx
+++ /dev/null
@@ -1,185 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as SelectPrimitive from "@radix-ui/react-select"
-import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
-
-import { cn } from "@/lib/utils"
-
-function Select({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function SelectGroup({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function SelectValue({
- ...props
-}: React.ComponentProps) {
- return
-}
-
-function SelectTrigger({
- className,
- size = "default",
- children,
- ...props
-}: React.ComponentProps & {
- size?: "sm" | "default"
-}) {
- return (
-
- {children}
-
-
-
-
- )
-}
-
-function SelectContent({
- className,
- children,
- position = "popper",
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
- {children}
-
-
-
-
- )
-}
-
-function SelectLabel({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function SelectItem({
- className,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
-
-
- {children}
-
- )
-}
-
-function SelectSeparator({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function SelectScrollUpButton({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- )
-}
-
-function SelectScrollDownButton({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- )
-}
-
-export {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectScrollDownButton,
- SelectScrollUpButton,
- SelectSeparator,
- SelectTrigger,
- SelectValue,
-}
diff --git a/components/ui/sheet.tsx b/components/ui/sheet.tsx
deleted file mode 100644
index e8f898a0f1935fcfad88d0c62129781e9931eaab..0000000000000000000000000000000000000000
--- a/components/ui/sheet.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as SheetPrimitive from "@radix-ui/react-dialog";
-import { cva, type VariantProps } from "class-variance-authority";
-import { X } from "lucide-react";
-
-import { cn } from "@/lib/utils";
-
-const Sheet = SheetPrimitive.Root;
-
-const SheetTrigger = SheetPrimitive.Trigger;
-
-const SheetClose = SheetPrimitive.Close;
-
-const SheetPortal = SheetPrimitive.Portal;
-
-const SheetOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
-
-const sheetVariants = cva(
- "fixed z-50 gap-4 bg-neutral-900 p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
- {
- variants: {
- side: {
- top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
- bottom:
- "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
- left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
- right:
- "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
- },
- },
- defaultVariants: {
- side: "right",
- },
- }
-);
-
-interface SheetContentProps
- extends React.ComponentPropsWithoutRef,
- VariantProps {}
-
-const SheetContent = React.forwardRef<
- React.ElementRef,
- SheetContentProps
->(({ side = "right", className, children, ...props }, ref) => (
-
-
-
- {children}
-
-
- Close
-
-
-
-));
-SheetContent.displayName = SheetPrimitive.Content.displayName;
-
-const SheetHeader = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-);
-SheetHeader.displayName = "SheetHeader";
-
-const SheetFooter = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-);
-SheetFooter.displayName = "SheetFooter";
-
-const SheetTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SheetTitle.displayName = SheetPrimitive.Title.displayName;
-
-const SheetDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SheetDescription.displayName = SheetPrimitive.Description.displayName;
-
-export {
- Sheet,
- SheetPortal,
- SheetOverlay,
- SheetTrigger,
- SheetClose,
- SheetContent,
- SheetHeader,
- SheetFooter,
- SheetTitle,
- SheetDescription,
-};
diff --git a/components/ui/sonner.tsx b/components/ui/sonner.tsx
deleted file mode 100644
index 2922154241f1415b767c55ca77bbfa8a1569cefd..0000000000000000000000000000000000000000
--- a/components/ui/sonner.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-"use client";
-
-import { Toaster as Sonner, ToasterProps } from "sonner";
-
-const Toaster = ({ ...props }: ToasterProps) => {
- return (
-
- );
-};
-
-export { Toaster };
diff --git a/components/ui/switch.tsx b/components/ui/switch.tsx
deleted file mode 100644
index 4864d4e8cfa54dc9ae2e29f9dfb636d8f541f5ce..0000000000000000000000000000000000000000
--- a/components/ui/switch.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as SwitchPrimitive from "@radix-ui/react-switch";
-
-import { cn } from "@/lib/utils";
-
-function Switch({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- );
-}
-
-export { Switch };
diff --git a/components/ui/tabs.tsx b/components/ui/tabs.tsx
deleted file mode 100644
index 497ba5ea34247f6843e0c58ccd7da61b7c8edb46..0000000000000000000000000000000000000000
--- a/components/ui/tabs.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as TabsPrimitive from "@radix-ui/react-tabs"
-
-import { cn } from "@/lib/utils"
-
-function Tabs({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function TabsList({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function TabsTrigger({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-function TabsContent({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- )
-}
-
-export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/components/ui/toggle-group.tsx b/components/ui/toggle-group.tsx
deleted file mode 100644
index 5eed401b6c9c19f7b6f88e90d3cbe38783ef198b..0000000000000000000000000000000000000000
--- a/components/ui/toggle-group.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
-import { type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-import { toggleVariants } from "@/components/ui/toggle"
-
-const ToggleGroupContext = React.createContext<
- VariantProps
->({
- size: "default",
- variant: "default",
-})
-
-function ToggleGroup({
- className,
- variant,
- size,
- children,
- ...props
-}: React.ComponentProps &
- VariantProps) {
- return (
-
-
- {children}
-
-
- )
-}
-
-function ToggleGroupItem({
- className,
- children,
- variant,
- size,
- ...props
-}: React.ComponentProps &
- VariantProps) {
- const context = React.useContext(ToggleGroupContext)
-
- return (
-
- {children}
-
- )
-}
-
-export { ToggleGroup, ToggleGroupItem }
diff --git a/components/ui/toggle.tsx b/components/ui/toggle.tsx
deleted file mode 100644
index 94ec8f589b345a8c33b165463dfda393c7255967..0000000000000000000000000000000000000000
--- a/components/ui/toggle.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-"use client"
-
-import * as React from "react"
-import * as TogglePrimitive from "@radix-ui/react-toggle"
-import { cva, type VariantProps } from "class-variance-authority"
-
-import { cn } from "@/lib/utils"
-
-const toggleVariants = cva(
- "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
- {
- variants: {
- variant: {
- default: "bg-transparent",
- outline:
- "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
- },
- size: {
- default: "h-9 px-2 min-w-9",
- sm: "h-8 px-1.5 min-w-8",
- lg: "h-10 px-2.5 min-w-10",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- }
-)
-
-function Toggle({
- className,
- variant,
- size,
- ...props
-}: React.ComponentProps &
- VariantProps) {
- return (
-
- )
-}
-
-export { Toggle, toggleVariants }
diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx
deleted file mode 100644
index 9a8f2af7839f0d4c3910431419695a70855ace37..0000000000000000000000000000000000000000
--- a/components/ui/tooltip.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
-
-import { cn } from "@/lib/utils";
-
-function TooltipProvider({
- delayDuration = 0,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function Tooltip({
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
- );
-}
-
-function TooltipTrigger({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function TooltipContent({
- className,
- sideOffset = 0,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
-
- {children}
-
-
- );
-}
-
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/components/user-menu/index.tsx b/components/user-menu/index.tsx
deleted file mode 100644
index 5d729788d4115763fbcb8d917283b1682f71463b..0000000000000000000000000000000000000000
--- a/components/user-menu/index.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import {
- ChartSpline,
- CirclePlus,
- FolderCode,
- Import,
- LogOut,
-} from "lucide-react";
-import Link from "next/link";
-
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
-import { Button } from "@/components/ui/button";
-import { useUser } from "@/hooks/useUser";
-
-export const UserMenu = ({ className }: { className?: string }) => {
- const { logout, user } = useUser();
- return (
-
-
-
-
-
-
- {user?.fullname?.charAt(0).toUpperCase() ?? "E"}
-
-
- {user?.fullname}
-
- {user?.fullname?.slice(0, 10)}
- {(user?.fullname?.length ?? 0) > 10 ? "..." : ""}
-
-
-
-
-
- My Account
-
-
-
- (window.location.href = "/deepsite/new")}
- >
-
- New Project
-
-
-
-
- View Projects
-
-
-
-
-
- Usage Quota
-
-
-
-
- {
- if (confirm("Are you sure you want to log out?")) {
- logout();
- }
- }}
- >
-
-
- Log out
-
-
-
-
- );
-};
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..092408a9f09eae19150818b4f0db5d1b70744828
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,28 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ { ignores: ['dist'] },
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['**/*.{ts,tsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+)
diff --git a/hooks/useAi.ts b/hooks/useAi.ts
deleted file mode 100644
index cb02b23a7f897bbd3b0bb580f307e2168892a027..0000000000000000000000000000000000000000
--- a/hooks/useAi.ts
+++ /dev/null
@@ -1,720 +0,0 @@
-import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
-import { useMemo, useRef, useState } from "react";
-import { toast } from "sonner";
-import { useLocalStorage } from "react-use";
-
-import { MODELS } from "@/lib/providers";
-import { useEditor } from "./useEditor";
-import { Page, EnhancedSettings } from "@/types";
-import { api } from "@/lib/api";
-import { usePathname, useRouter } from "next/navigation";
-import { useUser } from "./useUser";
-import { isTheSameHtml } from "@/lib/compare-html-diff";
-
-export const useAi = (onScrollToBottom?: () => void) => {
- const client = useQueryClient();
- const audio = useRef(null);
- const { setPages, setCurrentPage, setPreviewPage, setPrompts, prompts, pages, project, setProject, commits, setCommits, setLastSavedPages, isSameHtml } = useEditor();
- const [controller, setController] = useState(null);
- const [storageProvider, setStorageProvider] = useLocalStorage("provider", "auto");
- const [storageModel, setStorageModel] = useLocalStorage("model", MODELS[0].value);
- const router = useRouter();
- const { token } = useUser();
- const pathname = usePathname();
- const namespace = pathname.split("/")[1];
- const repoId = pathname.split("/")[2];
- const streamingPagesRef = useRef>(new Set());
-
- const { data: isAiWorking = false } = useQuery({
- queryKey: ["ai.isAiWorking"],
- queryFn: async () => false,
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- refetchOnMount: false,
- });
- const setIsAiWorking = (newIsAiWorking: boolean) => {
- client.setQueryData(["ai.isAiWorking"], newIsAiWorking);
- };
-
- const { data: isThinking = false } = useQuery({
- queryKey: ["ai.isThinking"],
- queryFn: async () => false,
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- refetchOnMount: false,
- });
- const setIsThinking = (newIsThinking: boolean) => {
- client.setQueryData(["ai.isThinking"], newIsThinking);
- };
-
- const { data: thinkingContent } = useQuery({
- queryKey: ["ai.thinkingContent"],
- queryFn: async () => "",
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- refetchOnMount: false,
- initialData: ""
- });
- const setThinkingContent = (newThinkingContent: string) => {
- client.setQueryData(["ai.thinkingContent"], newThinkingContent);
- };
-
- const { data: selectedElement } = useQuery({
- queryKey: ["ai.selectedElement"],
- queryFn: async () => null,
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- refetchOnMount: false,
- initialData: null
- });
- const setSelectedElement = (newSelectedElement: HTMLElement | null) => {
- client.setQueryData(["ai.selectedElement"], newSelectedElement);
- setIsEditableModeEnabled(false);
- };
-
- const { data: isEditableModeEnabled = false } = useQuery({
- queryKey: ["ai.isEditableModeEnabled"],
- queryFn: async () => false,
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- refetchOnMount: false,
- });
- const setIsEditableModeEnabled = (newIsEditableModeEnabled: boolean) => {
- client.setQueryData(["ai.isEditableModeEnabled"], newIsEditableModeEnabled);
- };
-
- const { data: selectedFiles } = useQuery({
- queryKey: ["ai.selectedFiles"],
- queryFn: async () => [],
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- refetchOnMount: false,
- initialData: []
- });
- const setSelectedFiles = (newFiles: string[]) => {
- client.setQueryData(["ai.selectedFiles"], newFiles)
- };
-
- const { data: contextFile } = useQuery({
- queryKey: ["ai.contextFile"],
- queryFn: async () => null,
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- refetchOnMount: false,
- initialData: null
- });
- const setContextFile = (newContextFile: string | null) => {
- client.setQueryData(["ai.contextFile"], newContextFile)
- };
-
- const { data: provider } = useQuery({
- queryKey: ["ai.provider"],
- queryFn: async () => storageProvider ?? "auto",
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- refetchOnMount: false,
- initialData: storageProvider ?? "auto"
- });
- const setProvider = (newProvider: string) => {
- setStorageProvider(newProvider);
- client.setQueryData(["ai.provider"], newProvider);
- };
-
- const { data: model } = useQuery({
- queryKey: ["ai.model"],
- queryFn: async () => {
- // check if the model exist in the MODELS array
- const selectedModel = MODELS.find(m => m.value === storageModel || m.label === storageModel);
- if (selectedModel) {
- return selectedModel.value;
- }
- return MODELS[0].value;
- },
- refetchOnWindowFocus: false,
- refetchOnReconnect: false,
- refetchOnMount: false,
- initialData: undefined,
- });
- const setModel = (newModel: string) => {
- setStorageModel(newModel);
- client.setQueryData(["ai.model"], newModel);
- };
-
- const createNewProject = async (prompt: string, htmlPages: Page[], projectName: string | undefined, isLoggedIn?: boolean, userName?: string) => {
- if (isLoggedIn && userName) {
- try {
- const uploadRequest = await fetch(`/deepsite/api/me/projects/${userName}/new/update`, {
- method: "PUT",
- body: JSON.stringify({
- pages: htmlPages,
- commitTitle: prompt,
- isNew: true,
- projectName,
- }),
- headers: {
- "Content-Type": "application/json",
- "Authorization": `Bearer ${token}`,
- },
- });
-
- const uploadRes = await uploadRequest.json();
-
- if (!uploadRequest.ok || !uploadRes.ok) {
- throw new Error(uploadRes.error || "Failed to create project");
- }
-
- setIsAiWorking(false);
- router.replace(`/${uploadRes.repoId}`);
- toast.success("AI responded successfully");
- if (audio.current) audio.current.play();
- } catch (error: any) {
- setIsAiWorking(false);
- toast.error(error?.message || "Failed to create project");
- }
- } else {
- setIsAiWorking(false);
- toast.success("AI responded successfully");
- if (audio.current) audio.current.play();
- }
- }
-
- const callAiNewProject = async (prompt: string, enhancedSettings?: EnhancedSettings, redesignMarkdown?: string, isLoggedIn?: boolean, userName?: string) => {
- if (isAiWorking) return;
- if (!redesignMarkdown && !prompt.trim()) return;
-
- setIsAiWorking(true);
- setThinkingContent(""); // Reset thinking content
- streamingPagesRef.current.clear(); // Reset tracking for new generation
-
- const abortController = new AbortController();
- setController(abortController);
-
- try {
- const request = await fetch("/deepsite/api/ask", {
- method: "POST",
- body: JSON.stringify({
- prompt,
- provider,
- model,
- redesignMarkdown,
- enhancedSettings,
- }),
- headers: {
- "Content-Type": "application/json",
- "x-forwarded-for": window.location.hostname,
- "Authorization": `Bearer ${token}`,
- },
- signal: abortController.signal,
- });
-
- if (request && request.body) {
- const reader = request.body.getReader();
- const decoder = new TextDecoder("utf-8");
- let contentResponse = "";
-
- const read = async (): Promise => {
- const { done, value } = await reader.read();
-
- if (done) {
- // Final processing - extract and remove thinking content
- const thinkMatch = contentResponse.match(/([\s\S]*?)<\/think>/);
- if (thinkMatch) {
- setThinkingContent(thinkMatch[1].trim());
- setIsThinking(false);
- contentResponse = contentResponse.replace(/[\s\S]*?<\/think>/, '').trim();
- }
-
- const trimmedResponse = contentResponse.trim();
- if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
- try {
- const jsonResponse = JSON.parse(trimmedResponse);
- if (jsonResponse && !jsonResponse.ok) {
- setIsAiWorking(false);
- if (jsonResponse.openLogin) {
- return { error: "login_required" };
- } else if (jsonResponse.openSelectProvider) {
- return { error: "provider_required", message: jsonResponse.message };
- } else if (jsonResponse.openProModal) {
- return { error: "pro_required" };
- } else {
- toast.error(jsonResponse.message);
- return { error: "api_error", message: jsonResponse.message };
- }
- }
- } catch (e) {
- }
- }
-
- const newPages = formatPages(contentResponse, false);
- let projectName = contentResponse.match(/<<<<<<< PROJECT_NAME_START\s*([\s\S]*?)\s*>>>>>>> PROJECT_NAME_END/)?.[1]?.trim();
- if (!projectName) {
- projectName = prompt.substring(0, 20).replace(/[^a-zA-Z0-9]/g, "-") + "-" + Math.random().toString(36).substring(2, 9);
- }
- setPages(newPages);
- setLastSavedPages([...newPages]);
- if (newPages.length > 0 && !isTheSameHtml(newPages[0].html)) {
- createNewProject(prompt, newPages, projectName, isLoggedIn, userName);
- }
- setPrompts([...prompts, prompt]);
-
- return { success: true, pages: newPages };
- }
-
- const chunk = decoder.decode(value, { stream: true });
- contentResponse += chunk;
-
- // Extract thinking content while streaming
- if (contentResponse.includes(' ')) {
- // Thinking is complete, extract final content and stop thinking
- const thinkMatch = contentResponse.match(/([\s\S]*?)<\/think>/);
- if (thinkMatch) {
- setThinkingContent(thinkMatch[1].trim());
- setIsThinking(false);
- }
- } else if (contentResponse.includes('')) {
- // Still thinking, update content
- const thinkMatch = contentResponse.match(/([\s\S]*)$/);
- if (thinkMatch) {
- const thinkingText = thinkMatch[1].trim();
- if (thinkingText) {
- setIsThinking(true);
- setThinkingContent(thinkingText);
- }
- }
- }
-
- const trimmedResponse = contentResponse.trim();
- if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
- try {
- const jsonResponse = JSON.parse(trimmedResponse);
- if (jsonResponse && !jsonResponse.ok) {
- setIsAiWorking(false);
- if (jsonResponse.openLogin) {
- return { error: "login_required" };
- } else if (jsonResponse.openSelectProvider) {
- return { error: "provider_required", message: jsonResponse.message };
- } else if (jsonResponse.openProModal) {
- return { error: "pro_required" };
- } else {
- toast.error(jsonResponse.message);
- return { error: "api_error", message: jsonResponse.message };
- }
- }
- } catch (e) {
- }
- }
-
- formatPages(contentResponse, true);
-
- return read();
- };
-
- return await read();
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } catch (error: any) {
- setIsAiWorking(false);
- setIsThinking(false);
- setThinkingContent("");
- setController(null);
-
- if (!abortController.signal.aborted) {
- toast.error(error.message || "Network error occurred");
- }
-
- if (error.openLogin) {
- return { error: "login_required" };
- }
- return { error: "network_error", message: error.message };
- }
- };
-
- const callAiFollowUp = async (prompt: string, enhancedSettings?: EnhancedSettings, isNew?: boolean) => {
- if (isAiWorking) return;
- if (!prompt.trim()) return;
-
-
- setIsAiWorking(true);
- setThinkingContent(""); // Reset thinking content
-
- const abortController = new AbortController();
- setController(abortController);
-
- try {
- const pagesToSend = contextFile
- ? pages.filter(page => page.path === contextFile)
- : pages;
-
- const request = await fetch("/deepsite/api/ask", {
- method: "PUT",
- body: JSON.stringify({
- prompt,
- provider,
- previousPrompts: prompts,
- model,
- pages: pagesToSend,
- selectedElementHtml: selectedElement?.outerHTML,
- files: selectedFiles,
- repoId: project?.space_id,
- isNew,
- enhancedSettings,
- }),
- headers: {
- "Content-Type": "application/json",
- "x-forwarded-for": window.location.hostname,
- "Authorization": `Bearer ${token}`,
- },
- signal: abortController.signal,
- });
-
- if (request && request.body) {
- const reader = request.body.getReader();
- const decoder = new TextDecoder("utf-8");
- let contentResponse = "";
- let metadata: any = null;
-
- const read = async (): Promise => {
- const { done, value } = await reader.read();
-
- if (done) {
- // Extract and remove thinking content
- const thinkMatch = contentResponse.match(/([\s\S]*?)<\/think>/);
- if (thinkMatch) {
- setThinkingContent(thinkMatch[1].trim());
- setIsThinking(false);
- contentResponse = contentResponse.replace(/[\s\S]*?<\/think>/, '').trim();
- }
-
- // const metadataMatch = contentResponse.match(/___METADATA_START___([\s\S]*?)___METADATA_END___/);
- // if (metadataMatch) {
- // try {
- // metadata = JSON.parse(metadataMatch[1]);
- // contentResponse = contentResponse.replace(/___METADATA_START___[\s\S]*?___METADATA_END___/, '').trim();
- // } catch (e) {
- // console.error("Failed to parse metadata", e);
- // }
- // }
-
- const trimmedResponse = contentResponse.trim();
- if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
- try {
- const jsonResponse = JSON.parse(trimmedResponse);
- if (jsonResponse && !jsonResponse.ok) {
- setIsAiWorking(false);
- if (jsonResponse.openLogin) {
- return { error: "login_required" };
- } else if (jsonResponse.openSelectProvider) {
- return { error: "provider_required", message: jsonResponse.message };
- } else if (jsonResponse.openProModal) {
- return { error: "pro_required" };
- } else {
- toast.error(jsonResponse.message);
- return { error: "api_error", message: jsonResponse.message };
- }
- }
- } catch (e) {
- // Not JSON, continue with normal processing
- }
- }
-
- const { processAiResponse, extractProjectName } = await import("@/lib/format-ai-response");
- const { updatedPages, updatedLines } = processAiResponse(contentResponse, pagesToSend);
-
- const updatedPagesMap = new Map(updatedPages.map((p: Page) => [p.path, p]));
- const mergedPages: Page[] = pages.map(page =>
- updatedPagesMap.has(page.path) ? updatedPagesMap.get(page.path)! : page
- );
- updatedPages.forEach((page: Page) => {
- if (!pages.find(p => p.path === page.path)) {
- mergedPages.push(page);
- }
- });
-
- let projectName = null;
- if (isNew) {
- projectName = extractProjectName(contentResponse);
- if (!projectName) {
- projectName = prompt.substring(0, 40).replace(/[^a-zA-Z0-9]/g, "-").slice(0, 40) + "-" + Math.random().toString(36).substring(2, 15);
- }
- }
-
- try {
- const uploadRequest = await fetch(`/deepsite/api/me/projects/${namespace ?? 'unknown'}/${repoId ?? 'unknown'}/update`, {
- method: "PUT",
- body: JSON.stringify({
- pages: mergedPages,
- commitTitle: prompt,
- isNew,
- projectName,
- }),
- headers: {
- "Content-Type": "application/json",
- "Authorization": `Bearer ${token}`,
- },
- });
-
- const uploadRes = await uploadRequest.json();
-
- if (!uploadRequest.ok || !uploadRes.ok) {
- throw new Error(uploadRes.error || "Failed to upload to HuggingFace");
- }
-
- toast.success("AI responded successfully");
- const iframe = document.getElementById("preview-iframe") as HTMLIFrameElement;
-
- if (isNew && uploadRes.repoId) {
- router.push(`/${uploadRes.repoId}`);
- setIsAiWorking(false);
- } else {
- setPages(mergedPages);
- setLastSavedPages([...mergedPages]);
- setCommits([uploadRes.commit, ...commits]);
- setPrompts([...prompts, prompt]);
- setSelectedElement(null);
- setSelectedFiles([]);
- setIsEditableModeEnabled(false);
- setIsAiWorking(false);
- }
-
- if (audio.current) audio.current.play();
- if (iframe) {
- setTimeout(() => {
- iframe.src = iframe.src;
- }, 500);
- }
-
- return { success: true, updatedLines };
- } catch (uploadError: any) {
- setIsAiWorking(false);
- toast.error(uploadError.message || "Failed to save changes");
- return { error: "upload_error", message: uploadError.message };
- }
- }
-
- const chunk = decoder.decode(value, { stream: true });
- contentResponse += chunk;
-
- // Extract thinking content while streaming
- if (contentResponse.includes(' ')) {
- // Thinking is complete, extract final content and stop thinking
- const thinkMatch = contentResponse.match(/([\s\S]*?)<\/think>/);
- if (thinkMatch) {
- setThinkingContent(thinkMatch[1].trim());
- setIsThinking(false);
- }
- } else if (contentResponse.includes('')) {
- // Still thinking, update content
- const thinkMatch = contentResponse.match(/([\s\S]*)$/);
- if (thinkMatch) {
- const thinkingText = thinkMatch[1].trim();
- if (thinkingText) {
- setIsThinking(true);
- setThinkingContent(thinkingText);
- }
- }
- }
-
- // Check for error responses during streaming
- const trimmedResponse = contentResponse.trim();
- if (trimmedResponse.startsWith("{") && trimmedResponse.endsWith("}")) {
- try {
- const jsonResponse = JSON.parse(trimmedResponse);
- if (jsonResponse && !jsonResponse.ok) {
- setIsAiWorking(false);
- if (jsonResponse.openLogin) {
- return { error: "login_required" };
- } else if (jsonResponse.openSelectProvider) {
- return { error: "provider_required", message: jsonResponse.message };
- } else if (jsonResponse.openProModal) {
- return { error: "pro_required" };
- } else {
- toast.error(jsonResponse.message);
- return { error: "api_error", message: jsonResponse.message };
- }
- }
- } catch (e) {
- // Not complete JSON yet, continue
- }
- }
-
- return read();
- };
-
- return await read();
- }
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- } catch (error: any) {
- setIsAiWorking(false);
- setIsThinking(false);
- setThinkingContent("");
- setController(null);
-
- if (!abortController.signal.aborted) {
- toast.error(error.message || "Network error occurred");
- }
-
- if (error.openLogin) {
- return { error: "login_required" };
- }
- return { error: "network_error", message: error.message };
- }
- };
-
- const formatPages = (content: string, isStreaming: boolean = true) => {
- const pages: Page[] = [];
- if (!content.match(/<<<<<<< NEW_FILE_START[\s\S]*?>>>>>>> NEW_FILE_END/)) {
- return pages;
- }
-
- const cleanedContent = content.replace(
- /[\s\S]*?<<<<<<< NEW_FILE_START\s+([\s\S]*?)\s+>>>>>>> NEW_FILE_END/,
- "<<<<<<< NEW_FILE_START $1 >>>>>>> NEW_FILE_END"
- );
- const fileChunks = cleanedContent.split(
- /<<<<<<< NEW_FILE_START\s+([\s\S]*?)\s+>>>>>>> NEW_FILE_END/
- );
- const processedChunks = new Set();
-
- fileChunks.forEach((chunk, index) => {
- if (processedChunks.has(index) || !chunk?.trim()) {
- return;
- }
- const filePath = chunk.trim();
- const fileContent = extractFileContent(fileChunks[index + 1], filePath);
-
- if (fileContent) {
- const page: Page = {
- path: filePath,
- html: fileContent,
- };
- pages.push(page);
-
- if (fileContent.length > 200) {
- onScrollToBottom?.();
- }
-
- processedChunks.add(index);
- processedChunks.add(index + 1);
- }
- });
- if (pages.length > 0) {
- setPages(pages);
- if (isStreaming) {
- const newPages = pages.filter(p =>
- !streamingPagesRef.current.has(p.path)
- );
-
- if (newPages.length > 0) {
- const newPage = newPages[0];
- setCurrentPage(newPage.path);
- streamingPagesRef.current.add(newPage.path);
-
- if (newPage.path.endsWith('.html') && !newPage.path.includes('/components/')) {
- setPreviewPage(newPage.path);
- }
- }
- } else {
- streamingPagesRef.current.clear();
- const indexPage = pages.find(p => p.path === 'index.html' || p.path === 'index' || p.path === '/');
- if (indexPage) {
- setCurrentPage(indexPage.path);
- }
- }
- }
-
- return pages;
- };
-
- const extractFileContent = (chunk: string, filePath: string): string => {
- if (!chunk) return "";
-
- let content = chunk.trim();
-
- if (filePath.endsWith('.css')) {
- const cssMatch = content.match(/```css\s*([\s\S]*?)\s*```/);
- if (cssMatch) {
- content = cssMatch[1];
- } else {
- content = content.replace(/^```css\s*/i, "");
- }
- return content.replace(/```/g, "").trim();
- } else if (filePath.endsWith('.js')) {
- const jsMatch = content.match(/```(?:javascript|js)\s*([\s\S]*?)\s*```/);
- if (jsMatch) {
- content = jsMatch[1];
- } else {
- content = content.replace(/^```(?:javascript|js)\s*/i, "");
- }
- return content.replace(/```/g, "").trim();
- } else {
- const htmlMatch = content.match(/```html\s*([\s\S]*?)\s*```/);
- if (htmlMatch) {
- content = htmlMatch[1];
- } else {
- content = content.replace(/^```html\s*/i, "");
- const doctypeMatch = content.match(/[\s\S]*/);
- if (doctypeMatch) {
- content = doctypeMatch[0];
- }
- }
-
- let htmlContent = content.replace(/```/g, "");
- htmlContent = ensureCompleteHtml(htmlContent);
- return htmlContent;
- }
- };
-
- const ensureCompleteHtml = (html: string): string => {
- let completeHtml = html;
- if (completeHtml.includes("") && !completeHtml.includes("")) {
- completeHtml += "\n";
- }
- if (completeHtml.includes("")) {
- completeHtml += "\n";
- }
- if (!completeHtml.includes("