diff --git a/.env.example b/.env.example
deleted file mode 100644
index d7419a983b3e8d6606db990addea8d78dd660692..0000000000000000000000000000000000000000
--- a/.env.example
+++ /dev/null
@@ -1,4 +0,0 @@
-AUTH_HUGGINGFACE_ID=
-AUTH_HUGGINGFACE_SECRET=
-NEXTAUTH_URL=http://localhost:3001
-AUTH_SECRET=
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 0c77e42ea5d9b5dbd9e33ea96413786910d09085..0000000000000000000000000000000000000000
--- a/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-public/banner.png filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
index e72b4d6a488ccacb6296f0db7c609e15ff3bac14..5ef6a520780202a1d6addd833d800ccb1ecac0bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,7 +31,7 @@ yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
-.env
+.env*
# vercel
.vercel
diff --git a/Dockerfile b/Dockerfile
index a2b0759c0a612c3be02d8c1eb3021252cdd4a7f8..cbe0188aaee92186937765d2c85d76f7b212c537 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,22 +1,19 @@
FROM node:20-alpine
USER root
-# Install pnpm
-RUN corepack enable && corepack prepare pnpm@latest --activate
-
USER 1000
WORKDIR /usr/src/app
-# Copy package.json and pnpm-lock.yaml to the container
-COPY --chown=1000 package.json pnpm-lock.yaml ./
+# Copy package.json and package-lock.json to the container
+COPY --chown=1000 package.json package-lock.json ./
# Copy the rest of the application files to the container
COPY --chown=1000 . .
-RUN pnpm install
-RUN pnpm run build
+RUN npm install
+RUN npm run build
# Expose the application port (assuming your app runs on port 3000)
-EXPOSE 3001
+EXPOSE 3000
# Start the application
-CMD ["pnpm", "start"]
\ No newline at end of file
+CMD ["npm", "start"]
\ No newline at end of file
diff --git a/README.md b/README.md
index 6b13fd8b95aeb699f979cbc2cbb8d7495aeacdf5..5ab2231fc7dc96070548f1d03ab1d0f73a799600 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,22 @@
---
-title: DeepSite v4
+title: DeepSite v2
emoji: 🐳
colorFrom: blue
colorTo: blue
sdk: docker
pinned: true
-app_port: 3001
+app_port: 3000
license: mit
-failure_strategy: rollback
-short_description: Generate any application by Vibe Coding it
+short_description: Generate any application with DeepSeek
models:
- deepseek-ai/DeepSeek-V3-0324
- - deepseek-ai/DeepSeek-V3.2
- - Qwen/Qwen3-Coder-30B-A3B-Instruct
- - moonshotai/Kimi-K2-Instruct-0905
- - zai-org/GLM-4.7
- - MiniMaxAI/MiniMax-M2.1
+ - deepseek-ai/DeepSeek-R1-0528
---
# 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.
+DeepSite is a coding platform powered by DeepSeek AI, designed to make coding smarter and more efficient. Tailored for developers, data scientists, and AI engineers, it integrates generative AI into your coding projects to enhance creativity and productivity.
+
+## How to use it locally
+
+Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
diff --git a/actions/mentions.ts b/actions/mentions.ts
deleted file mode 100644
index c6b7dabfbaa6f0efe0d492dfe5470353b0139bae..0000000000000000000000000000000000000000
--- a/actions/mentions.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-"use client";
-
-import { File } from "@/lib/type";
-
-export const searchMentions = async (query: string) => {
- const promises = [searchModels(query), searchDatasets(query)];
- const results = await Promise.all(promises);
- return { models: results[0], datasets: results[1] };
-};
-
-const searchModels = async (query: string) => {
- const response = await fetch(
- `https://huggingface.co/api/quicksearch?q=${query}&type=model&limit=3`
- );
- const data = await response.json();
- return data?.models ?? [];
-};
-
-const searchDatasets = async (query: string) => {
- const response = await fetch(
- `https://huggingface.co/api/quicksearch?q=${query}&type=dataset&limit=3`
- );
- const data = await response.json();
- return data?.datasets ?? [];
-};
-
-export const searchFilesMentions = async (query: string, files: File[]) => {
- if (!query) return files;
- const lowerQuery = query.toLowerCase();
- return files.filter((file) => file.path.toLowerCase().includes(lowerQuery));
-};
diff --git a/actions/projects.ts b/actions/projects.ts
deleted file mode 100644
index d4d52e0b1e5fd19ec412237cb5044e70bcd73bbb..0000000000000000000000000000000000000000
--- a/actions/projects.ts
+++ /dev/null
@@ -1,175 +0,0 @@
-"use server";
-import {
- downloadFile,
- listCommits,
- listFiles,
- listSpaces,
- RepoDesignation,
- SpaceEntry,
- spaceInfo,
-} from "@huggingface/hub";
-
-import { auth } from "@/lib/auth";
-import { Commit, File } from "@/lib/type";
-
-export interface ProjectWithCommits extends SpaceEntry {
- commits?: Commit[];
- medias?: string[];
-}
-
-const IGNORED_PATHS = ["README.md", ".gitignore", ".gitattributes"];
-const IGNORED_FORMATS = [
- ".png",
- ".jpg",
- ".jpeg",
- ".gif",
- ".svg",
- ".webp",
- ".mp4",
- ".mp3",
- ".wav",
-];
-
-export const getProjects = async () => {
- const projects: SpaceEntry[] = [];
- const session = await auth();
- if (!session?.user) {
- return projects;
- }
- const token = session.accessToken;
- for await (const space of listSpaces({
- accessToken: token,
- additionalFields: ["author", "cardData"],
- search: {
- owner: "enzostvs",
- },
- })) {
- if (
- space.sdk === "static" &&
- Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
- (space.cardData as { tags?: string[] })?.tags?.some((tag) =>
- tag.includes("deepsite")
- )
- ) {
- projects.push(space);
- }
- }
- return projects;
-};
-export const getProject = async (id: string, commitId?: string) => {
- const session = await auth();
- if (!session?.user) {
- return null;
- }
- const token = session.accessToken;
- try {
- const project: ProjectWithCommits | null = await spaceInfo({
- name: id,
- accessToken: token,
- additionalFields: ["author", "cardData"],
- });
- const repo: RepoDesignation = {
- type: "space",
- name: id,
- };
- const files: File[] = [];
- const medias: string[] = [];
- const params = { repo, accessToken: token };
- if (commitId) {
- Object.assign(params, { revision: commitId });
- }
- for await (const fileInfo of listFiles(params)) {
- if (IGNORED_PATHS.includes(fileInfo.path)) continue;
- if (IGNORED_FORMATS.some((format) => fileInfo.path.endsWith(format))) {
- medias.push(
- `https://huggingface.co/spaces/${id}/resolve/main/${fileInfo.path}`
- );
- continue;
- }
-
- if (fileInfo.type === "directory") {
- for await (const subFile of listFiles({
- repo,
- accessToken: token,
- path: fileInfo.path,
- })) {
- if (IGNORED_FORMATS.some((format) => subFile.path.endsWith(format))) {
- medias.push(
- `https://huggingface.co/spaces/${id}/resolve/main/${subFile.path}`
- );
- }
- const blob = await downloadFile({
- repo,
- accessToken: token,
- path: subFile.path,
- raw: true,
- ...(commitId ? { revision: commitId } : {}),
- }).catch((_) => {
- return null;
- });
- if (!blob) {
- continue;
- }
- const html = await blob?.text();
- if (!html) {
- continue;
- }
- files[subFile.path === "index.html" ? "unshift" : "push"]({
- path: subFile.path,
- content: html,
- });
- }
- } else {
- const blob = await downloadFile({
- repo,
- accessToken: token,
- path: fileInfo.path,
- raw: true,
- ...(commitId ? { revision: commitId } : {}),
- }).catch((_) => {
- return null;
- });
- if (!blob) {
- continue;
- }
- const html = await blob?.text();
- if (!html) {
- continue;
- }
- files[fileInfo.path === "index.html" ? "unshift" : "push"]({
- path: fileInfo.path,
- content: html,
- });
- }
- }
- const commits: Commit[] = [];
- const commitIterator = listCommits({ repo, accessToken: token });
- for await (const commit of commitIterator) {
- if (
- commit.title?.toLowerCase() === "initial commit" ||
- commit.title
- ?.toLowerCase()
- ?.includes("upload media files through deepsite")
- )
- continue;
- commits.push({
- title: commit.title,
- oid: commit.oid,
- date: commit.date,
- });
- if (commits.length >= 20) {
- break;
- }
- }
-
- project.commits = commits;
- project.medias = medias;
-
- return { project, files };
- } catch (error) {
- return {
- project: null,
- files: [],
- };
- }
-};
diff --git a/app/(public)/layout.tsx b/app/(public)/layout.tsx
index 0eb2c8fbb85b745d5be01c9e79fa0ebc93a71d00..4a4ec57d2609c783602beb6c06c8dca6a1e6192d 100644
--- a/app/(public)/layout.tsx
+++ b/app/(public)/layout.tsx
@@ -1,12 +1,13 @@
-import { Navigation } from "@/components/public/navigation";
+import Navigation from "@/components/public/navigation";
-export default function PublicLayout({
+export default async function PublicLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
-
+
diff --git a/app/(public)/page.tsx b/app/(public)/page.tsx
index cba36b46276880eb96f5bb70a85ffe3a46518f8d..c0849e72cf29027524ec9ebc3818e80a8aee5ef3 100644
--- a/app/(public)/page.tsx
+++ b/app/(public)/page.tsx
@@ -1,21 +1,44 @@
-import { AnimatedDotsBackground } from "@/components/public/animated-dots-background";
-import { HeroHeader } from "@/components/public/hero-header";
-import { UserProjects } from "@/components/projects/user-projects";
-import { AskAiLanding } from "@/components/ask-ai/ask-ai-landing";
-
-export default async function Homepage() {
+import { AskAi } from "@/components/space/ask-ai";
+import { redirect } from "next/navigation";
+export default function Home() {
+ redirect("/projects/new");
return (
<>
-
-
-
-
+
+
+ ✨ DeepSite Public Beta
-
-
+
+ Code your website with AI in seconds
+
+
+ Vibe Coding has never been so easy.
+
+
-
-
+
+
+
+
+
+ Deploy your website in seconds
+
+
+
+
+ Features that make you smile
+
+
>
);
}
diff --git a/app/(public)/projects/page.tsx b/app/(public)/projects/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..374dc6b1194256c5b142a62168ce0f414f6098be
--- /dev/null
+++ b/app/(public)/projects/page.tsx
@@ -0,0 +1,13 @@
+import { redirect } from "next/navigation";
+
+import { MyProjects } from "@/components/my-projects";
+import { getProjects } from "@/app/actions/projects";
+
+export default async function ProjectsPage() {
+ const { ok, projects } = await getProjects();
+ if (!ok) {
+ redirect("/");
+ }
+
+ return
;
+}
diff --git a/app/[owner]/[repoId]/page.tsx b/app/[owner]/[repoId]/page.tsx
deleted file mode 100644
index ad0eaf89e1496b264117a91ef58d09746536a61a..0000000000000000000000000000000000000000
--- a/app/[owner]/[repoId]/page.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { getProject } from "@/actions/projects";
-import { AppEditor } from "@/components/editor";
-import { auth } from "@/lib/auth";
-import { notFound, redirect } from "next/navigation";
-
-export default async function ProjectPage({
- params,
- searchParams,
-}: {
- params: Promise<{ owner: string; repoId: string }>;
- searchParams: Promise<{ commit?: string }>;
-}) {
- const session = await auth();
-
- if (!session) {
- redirect("/api/auth/signin");
- }
-
- const { owner, repoId } = await params;
- const { commit } = await searchParams;
- const datas = await getProject(`${owner}/${repoId}`, commit);
- if (!datas?.project) {
- return notFound();
- }
- return (
-
- );
-}
diff --git a/app/actions/auth.ts b/app/actions/auth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a343e65e6726b35b32f022c117d3f3b5187d78e6
--- /dev/null
+++ b/app/actions/auth.ts
@@ -0,0 +1,18 @@
+"use server";
+
+import { headers } from "next/headers";
+
+export async function getAuth() {
+ const authList = await headers();
+ const host = authList.get("host") ?? "localhost:3000";
+ const url = host.includes("/spaces/enzostvs")
+ ? "enzostvs-deepsite.hf.space"
+ : host;
+ const redirect_uri =
+ `${host.includes("localhost") ? "http://" : "https://"}` +
+ url +
+ "/auth/callback";
+
+ const loginRedirectUrl = `https://huggingface.co/oauth/authorize?client_id=${process.env.OAUTH_CLIENT_ID}&redirect_uri=${redirect_uri}&response_type=code&scope=openid%20profile%20write-repos%20manage-repos%20inference-api&prompt=consent&state=1234567890`;
+ return loginRedirectUrl;
+}
diff --git a/app/actions/projects.ts b/app/actions/projects.ts
new file mode 100644
index 0000000000000000000000000000000000000000..209b16d9d9960eeafb9e0b02d7b1b3eda638338d
--- /dev/null
+++ b/app/actions/projects.ts
@@ -0,0 +1,63 @@
+"use server";
+
+import { isAuthenticated } from "@/lib/auth";
+import { NextResponse } from "next/server";
+import dbConnect from "@/lib/mongodb";
+import Project from "@/models/Project";
+import { Project as ProjectType } from "@/types";
+
+export async function getProjects(): Promise<{
+ ok: boolean;
+ projects: ProjectType[];
+}> {
+ const user = await isAuthenticated();
+
+ if (user instanceof NextResponse || !user) {
+ return {
+ ok: false,
+ projects: [],
+ };
+ }
+
+ await dbConnect();
+ const projects = await Project.find({
+ user_id: user?.id,
+ })
+ .sort({ _createdAt: -1 })
+ .limit(100)
+ .lean();
+ if (!projects) {
+ return {
+ ok: false,
+ projects: [],
+ };
+ }
+ return {
+ ok: true,
+ projects: JSON.parse(JSON.stringify(projects)) as ProjectType[],
+ };
+}
+
+export async function getProject(
+ namespace: string,
+ repoId: string
+): Promise
{
+ const user = await isAuthenticated();
+
+ if (user instanceof NextResponse || !user) {
+ return null;
+ }
+
+ await dbConnect();
+ const project = await Project.findOne({
+ user_id: user.id,
+ namespace,
+ repoId,
+ }).lean();
+
+ if (!project) {
+ return null;
+ }
+
+ return JSON.parse(JSON.stringify(project)) as ProjectType;
+}
diff --git a/app/api/ask-ai/route.ts b/app/api/ask-ai/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac076fe255c99ff9e33417e5cde007e291ece123
--- /dev/null
+++ b/app/api/ask-ai/route.ts
@@ -0,0 +1,419 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import type { NextRequest } from "next/server";
+import { NextResponse } from "next/server";
+import { headers } from "next/headers";
+import { InferenceClient } from "@huggingface/inference";
+
+import { MODELS, PROVIDERS } from "@/lib/providers";
+import {
+ DIVIDER,
+ FOLLOW_UP_SYSTEM_PROMPT,
+ INITIAL_SYSTEM_PROMPT,
+ MAX_REQUESTS_PER_IP,
+ REPLACE_END,
+ SEARCH_START,
+} from "@/lib/prompts";
+import MY_TOKEN_KEY from "@/lib/get-cookie-name";
+
+const ipAddresses = new Map();
+
+export async function POST(request: NextRequest) {
+ const authHeaders = await headers();
+ const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
+
+ const body = await request.json();
+ const { prompt, provider, model, redesignMarkdown, html } = body;
+
+ if (!model || (!prompt && !redesignMarkdown)) {
+ return NextResponse.json(
+ { ok: false, error: "Missing required fields" },
+ { status: 400 }
+ );
+ }
+
+ const selectedModel = MODELS.find(
+ (m) => m.value === model || m.label === model
+ );
+ if (!selectedModel) {
+ return NextResponse.json(
+ { ok: false, error: "Invalid model selected" },
+ { status: 400 }
+ );
+ }
+
+ if (!selectedModel.providers.includes(provider) && provider !== "auto") {
+ return NextResponse.json(
+ {
+ ok: false,
+ error: `The selected model does not support the ${provider} provider.`,
+ openSelectProvider: true,
+ },
+ { status: 400 }
+ );
+ }
+
+ let token = userToken;
+ let billTo: string | null = null;
+
+ /**
+ * Handle local usage token, this bypass the need for a user token
+ * and allows local testing without authentication.
+ * This is useful for development and testing purposes.
+ */
+ if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
+ token = process.env.HF_TOKEN;
+ }
+
+ const ip = authHeaders.get("x-forwarded-for")?.includes(",")
+ ? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
+ : authHeaders.get("x-forwarded-for");
+
+ if (!token) {
+ ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
+ if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
+ return NextResponse.json(
+ {
+ ok: false,
+ openLogin: true,
+ message: "Log In to continue using the service",
+ },
+ { status: 429 }
+ );
+ }
+
+ token = process.env.DEFAULT_HF_TOKEN as string;
+ billTo = "huggingface";
+ }
+
+ const DEFAULT_PROVIDER = PROVIDERS.novita;
+ const selectedProvider =
+ provider === "auto"
+ ? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS]
+ : PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
+
+ try {
+ // Create a stream response
+ const encoder = new TextEncoder();
+ const stream = new TransformStream();
+ const writer = stream.writable.getWriter();
+
+ // Start the response
+ const response = new NextResponse(stream.readable, {
+ headers: {
+ "Content-Type": "text/plain; charset=utf-8",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ },
+ });
+
+ (async () => {
+ let completeResponse = "";
+ try {
+ const client = new InferenceClient(token);
+ const chatCompletion = client.chatCompletionStream(
+ {
+ model: selectedModel.value,
+ provider: selectedProvider.id as any,
+ messages: [
+ {
+ role: "system",
+ content: INITIAL_SYSTEM_PROMPT,
+ },
+ {
+ role: "user",
+ content: redesignMarkdown
+ ? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.`
+ : html
+ ? `Here is my current HTML code:\n\n\`\`\`html\n${html}\n\`\`\`\n\nNow, please create a new design based on this HTML.`
+ : prompt,
+ },
+ ],
+ max_tokens: selectedProvider.max_tokens,
+ },
+ billTo ? { billTo } : {}
+ );
+
+ while (true) {
+ const { done, value } = await chatCompletion.next();
+ if (done) {
+ break;
+ }
+
+ const chunk = value.choices[0]?.delta?.content;
+ if (chunk) {
+ let newChunk = chunk;
+ if (!selectedModel?.isThinker) {
+ if (provider !== "sambanova") {
+ await writer.write(encoder.encode(chunk));
+ completeResponse += chunk;
+
+ if (completeResponse.includes("