diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..d7419a983b3e8d6606db990addea8d78dd660692 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +AUTH_HUGGINGFACE_ID= +AUTH_HUGGINGFACE_SECRET= +NEXTAUTH_URL=http://localhost:3001 +AUTH_SECRET= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e72b4d6a488ccacb6296f0db7c609e15ff3bac14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# 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 +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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..a2b0759c0a612c3be02d8c1eb3021252cdd4a7f8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +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 the rest of the application files to the container +COPY --chown=1000 . . + +RUN pnpm install +RUN pnpm run build + +# Expose the application port (assuming your app runs on port 3000) +EXPOSE 3001 + +# Start the application +CMD ["pnpm", "start"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c55ddca7d407fc08447fbfb7c2c98d3b3a9e9af3 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +--- +title: DeepSite v4 +emoji: 🐳 +colorFrom: blue +colorTo: blue +sdk: docker +pinned: false +app_port: 3001 +license: mit +failure_strategy: rollback +short_description: Generate any application by Vibe Coding it +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 +--- + +# 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. diff --git a/actions/mentions.ts b/actions/mentions.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6b7dabfbaa6f0efe0d492dfe5470353b0139bae --- /dev/null +++ b/actions/mentions.ts @@ -0,0 +1,31 @@ +"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 new file mode 100644 index 0000000000000000000000000000000000000000..dfc399a2e448dbac7261d332189239ba418fc279 --- /dev/null +++ b/actions/projects.ts @@ -0,0 +1,156 @@ +"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[]; +} + +const IGNORED_PATHS = ["README.md", ".gitignore", ".gitattributes"]; + +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 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 ( + fileInfo.path.endsWith(".html") || + fileInfo.path.endsWith(".css") || + fileInfo.path.endsWith(".js") || + fileInfo.path.endsWith(".json") + ) { + 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, + }); + } + if (fileInfo.type === "directory") { + for await (const subFile of listFiles({ + repo, + accessToken: token, + path: fileInfo.path, + })) { + if ( + subFile.path.endsWith(".html") || + subFile.path.endsWith(".css") || + subFile.path.endsWith(".js") || + subFile.path.endsWith(".json") + ) { + 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, + }); + } + } + } + } + const commits: Commit[] = []; + const commitIterator = listCommits({ repo, accessToken: token }); + for await (const commit of commitIterator) { + if (commit.title?.toLowerCase() === "initial commit") continue; + commits.push({ + title: commit.title, + oid: commit.oid, + date: commit.date, + }); + if (commits.length >= 20) { + break; + } + } + + project.commits = commits; + + return { project, files }; + } catch (error) { + return { + project: null, + files: [], + }; + } +}; diff --git a/app/(public)/layout.tsx b/app/(public)/layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0eb2c8fbb85b745d5be01c9e79fa0ebc93a71d00 --- /dev/null +++ b/app/(public)/layout.tsx @@ -0,0 +1,14 @@ +import { Navigation } from "@/components/public/navigation"; + +export default function PublicLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+ + {children} +
+ ); +} diff --git a/app/(public)/page.tsx b/app/(public)/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cba36b46276880eb96f5bb70a85ffe3a46518f8d --- /dev/null +++ b/app/(public)/page.tsx @@ -0,0 +1,21 @@ +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() { + return ( + <> +
+ +
+ +
+
+ +
+
+ + + ); +} diff --git a/app/[owner]/[repoId]/page.tsx b/app/[owner]/[repoId]/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4bb68e064370e3afc1b3720cc1100077c2790a0c --- /dev/null +++ b/app/[owner]/[repoId]/page.tsx @@ -0,0 +1,25 @@ +import { getProject } from "@/actions/projects"; +import { AppEditor } from "@/components/editor"; +import { notFound } from "next/navigation"; + +export default async function ProjectPage({ + params, + searchParams, +}: { + params: Promise<{ owner: string; repoId: string }>; + searchParams: Promise<{ commit?: string }>; +}) { + 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/api/ask/route.ts b/app/api/ask/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e458964d50270da7d6a61f3149181bcb581a7fd --- /dev/null +++ b/app/api/ask/route.ts @@ -0,0 +1,144 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { NextResponse } from "next/server"; +import { InferenceClient } from "@huggingface/inference"; + +import { FOLLOW_UP_SYSTEM_PROMPT, INITIAL_SYSTEM_PROMPT } from "@/lib/prompts"; +import { auth } from "@/lib/auth"; +import { File, Message } from "@/lib/type"; +import { MODELS } from "@/lib/providers"; + +export async function POST(request: Request) { + const session = await auth(); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const token = session.accessToken; + + const body = await request.json(); + const { + prompt, + previousMessages = [], + files = [], + provider: initialProvider, + mentions = [], + model, + redesignMd, + } = body; + const provider = initialProvider ?? "auto"; + + if (!prompt) { + return NextResponse.json({ error: "Prompt is required" }, { status: 400 }); + } + if (!model || !MODELS.find((m: (typeof MODELS)[0]) => m.value === model)) { + return NextResponse.json({ error: "Model is required" }, { status: 400 }); + } + + const client = new InferenceClient(token); + + 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 chatCompletion = client.chatCompletionStream({ + model: model + (provider !== "auto" ? `:${provider}` : ""), + messages: [ + { + role: "system", + content: + files.length > 0 + ? FOLLOW_UP_SYSTEM_PROMPT + : INITIAL_SYSTEM_PROMPT, + }, + ...previousMessages.map((message: Message) => ({ + role: message.role, + content: message.content, + })), + ...(files?.length > 0 + ? [ + { + role: "user", + content: `Here are the files that the user has provider:${files + .map( + (file: File) => + `File: ${file.path}\nContent: ${file.content}` + ) + .join("\n")}\n\n${prompt}`, + }, + ] + : []), + { + role: "user", + content: ` +${prompt} +${ + mentions?.length > 0 + ? `\n\nHere are the informations about the model or dataset that the user has mentioned to use: ${mentions + .map( + (mention: any) => + `Library: ${mention.library_name}\nPipeline: ${mention.pipeline_tag}\nModel: ${mention.model_id}\nReadme for more information: \n${mention.readme}` + ) + .join("\n")}` + : "" +} + `, + }, + ], + stream: true, + max_tokens: 16_000, + }); + 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) { + console.error(error); + try { + const errorMessage = + error instanceof Error + ? error.message + : "An error occurred while processing your request"; + const errorPayload = JSON.stringify({ + messageError: errorMessage, + isError: true, + }); + await writer.write(encoder.encode(`\n\n__ERROR__:${errorPayload}`)); + await writer.close(); + } catch (closeError) { + console.error("Failed to send error message:", closeError); + try { + await writer.abort(error); + } catch (abortError) { + console.error("Failed to abort writer:", abortError); + } + } + } + })(); + + return response; + } catch (error) { + console.error(error); + return NextResponse.json( + { error: "Internal server error" }, + { status: 500 } + ); + } +} diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ce43504c71cdf14baa13c95ca81c99dbda682fe --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,4 @@ +import { handlers } from "@/lib/auth"; + +export const { GET, POST } = handlers; + diff --git a/app/api/projects/[repoId]/[commitId]/route.ts b/app/api/projects/[repoId]/[commitId]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..0362d6c43ec226bf97837f2f88196ad43eb056f2 --- /dev/null +++ b/app/api/projects/[repoId]/[commitId]/route.ts @@ -0,0 +1,49 @@ +import { auth } from "@/lib/auth"; +import { createBranch, RepoDesignation } from "@huggingface/hub"; +import { format } from "date-fns"; +import { NextResponse } from "next/server"; + +export async function POST( + request: Request, + { params }: { params: Promise<{ repoId: string; commitId: string }> } +) { + const { repoId, commitId }: { repoId: string; commitId: string } = + await params; + const session = await auth(); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const token = session.accessToken; + + const repo: RepoDesignation = { + type: "space", + name: session.user?.username + "/" + repoId, + }; + + const commitTitle = `🔖 ${format(new Date(), "dd/MM")} - ${format( + new Date(), + "HH:mm" + )} - Set commit ${commitId} as default.`; + + await fetch( + `https://huggingface.co/api/spaces/${session.user?.username}/${repoId}/branch/main`, + { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + startingPoint: commitId, + overwrite: true, + }), + } + ).catch((error) => { + return NextResponse.json( + { error: error ?? "Failed to create branch" }, + { status: 500 } + ); + }); + + return NextResponse.json({ success: true }, { status: 200 }); +} diff --git a/app/api/projects/[repoId]/route.ts b/app/api/projects/[repoId]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..3e600936254e01c2f858082c191e827610656a17 --- /dev/null +++ b/app/api/projects/[repoId]/route.ts @@ -0,0 +1,97 @@ +import { auth } from "@/lib/auth"; +import { RepoDesignation, deleteRepo, uploadFiles } from "@huggingface/hub"; +import { format } from "date-fns"; +import { NextResponse } from "next/server"; + +export async function PUT( + request: Request, + { params }: { params: Promise<{ repoId: string }> } +) { + const { repoId }: { repoId: string } = await params; + const session = await auth(); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const token = session.accessToken; + + const body = await request.json(); + const { files, prompt, isManualChanges } = body; + + if (!files) { + return NextResponse.json({ error: "Files are required" }, { status: 400 }); + } + + if (!prompt) { + return NextResponse.json({ error: "Prompt is required" }, { status: 400 }); + } + + const repo: RepoDesignation = { + type: "space", + name: session.user?.username + "/" + repoId, + }; + + const filesToUpload: File[] = []; + for (const file of files) { + let mimeType = "text/x-python"; + if (file.path.endsWith(".txt")) { + mimeType = "text/plain"; + } else if (file.path.endsWith(".md")) { + mimeType = "text/markdown"; + } else if (file.path.endsWith(".json")) { + mimeType = "application/json"; + } + filesToUpload.push(new File([file.content], file.path, { type: mimeType })); + } + const baseTitle = isManualChanges + ? "" + : `🐳 ${format(new Date(), "dd/MM")} - ${format(new Date(), "HH:mm")} - `; + const commitTitle = baseTitle + (prompt ?? "Follow-up DeepSite commit"); + const response = await uploadFiles({ + repo, + files: filesToUpload, + accessToken: token, + commitTitle, + }); + + return NextResponse.json( + { + success: true, + commit: { + oid: response.commit, + title: commitTitle, + date: new Date(), + }, + }, + { status: 200 } + ); +} + +export async function DELETE( + request: Request, + { params }: { params: Promise<{ repoId: string }> } +) { + const { repoId }: { repoId: string } = await params; + const session = await auth(); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const token = session.accessToken; + + const repo: RepoDesignation = { + type: "space", + name: session.user?.username + "/" + repoId, + }; + + try { + await deleteRepo({ + repo, + accessToken: token as string, + }); + + return NextResponse.json({ success: true }, { status: 200 }); + } catch (error) { + const errMsg = + error instanceof Error ? error.message : "Failed to delete project"; + return NextResponse.json({ error: errMsg }, { status: 500 }); + } +} diff --git a/app/api/projects/route.ts b/app/api/projects/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..501b27349617a9faf507bbd37925621278770d04 --- /dev/null +++ b/app/api/projects/route.ts @@ -0,0 +1,97 @@ +import { NextResponse } from "next/server"; +import { RepoDesignation, createRepo, uploadFiles } from "@huggingface/hub"; + +import { auth } from "@/lib/auth"; +import { COLORS, injectDeepSiteBadge, isIndexPage } from "@/lib/utils"; + +export async function POST(request: Request) { + const session = await auth(); + if (!session) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + const token = session.accessToken; + + const body = await request.json(); + const { projectTitle, files, prompt } = body; + + if (!files) { + return NextResponse.json( + { error: "Project title and files are required" }, + { status: 400 } + ); + } + + const title = + projectTitle || projectTitle !== "" ? projectTitle : "DeepSite Project"; + + const formattedTitle = title + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .split("-") + .filter(Boolean) + .join("-") + .slice(0, 96); + + const repo: RepoDesignation = { + type: "space", + name: session.user?.username + "/" + formattedTitle, + }; + + const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)]; + const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)]; + const README = `--- +title: ${projectTitle} +colorFrom: ${colorFrom} +colorTo: ${colorTo} +sdk: static +tags: + - deepsite-v4 +--- + +# ${title} + +This project has been created with [DeepSite](https://huggingface.co/deepsite) AI Vibe Coding. +`; + + const filesToUpload: File[] = [ + new File([README], "README.md", { type: "text/markdown" }), + ]; + for (const file of files) { + let mimeType = "text/html"; + if (file.path.endsWith(".css")) { + mimeType = "text/css"; + } else if (file.path.endsWith(".js")) { + mimeType = "text/javascript"; + } + const content = + mimeType === "text/html" && isIndexPage(file.path) + ? injectDeepSiteBadge(file.content) + : file.content; + + filesToUpload.push(new File([content], file.path, { type: mimeType })); + } + + try { + const { repoUrl } = await createRepo({ + accessToken: token as string, + repo: repo, + sdk: "static", + }); + + const commitTitle = prompt ?? "Initial DeepSite commit"; + await uploadFiles({ + repo, + files: filesToUpload, + accessToken: token as string, + commitTitle, + }); + + const path = repoUrl.split("/").slice(-2).join("/"); + + return NextResponse.json({ repoUrl: path }, { status: 200 }); + } catch (error) { + const errMsg = + error instanceof Error ? error.message : "Failed to upload files"; + return NextResponse.json({ error: errMsg }, { status: 500 }); + } +} diff --git a/app/api/redesign/route.ts b/app/api/redesign/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b898d6fd364c5f3ef267706b62c37ee559e19cd --- /dev/null +++ b/app/api/redesign/route.ts @@ -0,0 +1,73 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { NextRequest, NextResponse } from "next/server"; + +const FETCH_TIMEOUT = 30_000; +export const maxDuration = 60; + +export async function PUT(request: NextRequest) { + const body = await request.json(); + const { url } = body; + + if (!url) { + return NextResponse.json({ error: "URL is required" }, { status: 400 }); + } + + try { + const 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; + } + } 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/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c Binary files /dev/null and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000000000000000000000000000000000000..a64b15d9b0025a6d85a71dd47c9dda2cf4337b6d --- /dev/null +++ b/app/globals.css @@ -0,0 +1,161 @@ +@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-geist-sans); + --font-mono: var(--font-geist-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.65rem; + --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); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} + +.monaco-editor .margin { + @apply bg-background!; +} +.monaco-editor .monaco-editor-background { + @apply bg-background!; +} +.monaco-editor .decorationsOverviewRuler { + @apply opacity-0!; +} +.monaco-editor .view-line { + /* @apply bg-primary/50!; */ +} +.monaco-editor .scroll-decoration { + @apply opacity-0!; +} +.monaco-editor .cursors-layer .cursor { + @apply bg-primary!; +} + +.content-placeholder::before { + content: attr(data-placeholder); + position: absolute; + pointer-events: none; + opacity: 0.5; + @apply top-5 left-6; +} + +.sp-layout .sp-file-explorer .sp-file-explorer-list .sp-explorer[data-active="true"] { + @apply text-indigo-500!; +} + +.sp-layout .sp-stack .sp-tabs .sp-tab-container[aria-selected="true"] .sp-tab-button { + @apply text-indigo-500!; +} +.sp-layout .sp-stack .sp-tabs .sp-tab-container:has(button:focus) { + @apply outline-none! border-none!; +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..47b5b6ffe71b10da4c9b98058b80e4c28eebb463 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,60 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; +import { ThemeProvider } from "@/components/providers/theme"; +import { AuthProvider } from "@/components/providers/session"; +import { Toaster } from "@/components/ui/sonner"; +import { ReactQueryProvider } from "@/components/providers/react-query"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Build your next application | DeepSite", + description: + "Build your next application with ease and speed by using AI Vibe Coding.", + icons: { + icon: "/logo.svg", + shortcut: "/logo.svg", + apple: "/logo.svg", + other: { + rel: "icon", + url: "/logo.svg", + }, + }, +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + + + + {children} + + + + + + ); +} diff --git a/app/new/page.tsx b/app/new/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b6c05f51448fcfce40547d63e04040d6c87198d3 --- /dev/null +++ b/app/new/page.tsx @@ -0,0 +1,10 @@ +import { AppEditor } from "@/components/editor"; + +export default async function NewProjectPage({ + searchParams, +}: { + searchParams: Promise<{ prompt: string }>; +}) { + const { prompt } = await searchParams; + return ; +} diff --git a/app/not-found.tsx b/app/not-found.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ae102854e6245bcc2f0d55780d9419b2679d6f38 --- /dev/null +++ b/app/not-found.tsx @@ -0,0 +1,17 @@ +import { NotFoundButtons } from "@/components/not-found/buttons"; +import { Navigation } from "@/components/public/navigation"; + +export default function NotFound() { + return ( +
+ +
+

Oh no! Page not found.

+

+ The page you are looking for does not exist. +

+ +
+
+ ); +} diff --git a/assets/deepseek.svg b/assets/deepseek.svg new file mode 100644 index 0000000000000000000000000000000000000000..dc224e43a4d68070ca6eed494476c8ddd900bf80 --- /dev/null +++ b/assets/deepseek.svg @@ -0,0 +1 @@ +DeepSeek \ No newline at end of file diff --git a/assets/kimi.svg b/assets/kimi.svg new file mode 100644 index 0000000000000000000000000000000000000000..4355c522a2dece99e187d9e5c898a66313f4a374 --- /dev/null +++ b/assets/kimi.svg @@ -0,0 +1 @@ +Kimi \ No newline at end of file diff --git a/assets/minimax.svg b/assets/minimax.svg new file mode 100644 index 0000000000000000000000000000000000000000..1d32449ab8fb0fe9a6c50006a41e67ef49c8dd1c --- /dev/null +++ b/assets/minimax.svg @@ -0,0 +1 @@ +Minimax \ No newline at end of file diff --git a/assets/qwen.svg b/assets/qwen.svg new file mode 100644 index 0000000000000000000000000000000000000000..a4bb382a6359b82c581fd3e7fb7169fe8fba1657 --- /dev/null +++ b/assets/qwen.svg @@ -0,0 +1 @@ +Qwen \ No newline at end of file diff --git a/assets/space.svg b/assets/space.svg new file mode 100644 index 0000000000000000000000000000000000000000..f133cf120bb1f4fe43c949d099965ae9a84db240 --- /dev/null +++ b/assets/space.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/zai.svg b/assets/zai.svg new file mode 100644 index 0000000000000000000000000000000000000000..2adcac387aaca06eb362a177e45c28ae3429b0d0 --- /dev/null +++ b/assets/zai.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/components.json b/components.json new file mode 100644 index 0000000000000000000000000000000000000000..d5005f0974a11b0ea57843b9f52f82f995743963 --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/components/ask-ai/ask-ai-landing.tsx b/components/ask-ai/ask-ai-landing.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f945c84726d2680ab9b2f3129be9472af4234144 --- /dev/null +++ b/components/ask-ai/ask-ai-landing.tsx @@ -0,0 +1,66 @@ +"use client"; +import { ArrowUp } from "lucide-react"; +import { useState } from "react"; +import { useInterval, useLocalStorage } from "react-use"; +import { useRouter } from "next/navigation"; + +import { Button } from "@/components/ui/button"; +import { ProviderType } from "@/lib/type"; +import { Models } from "./models"; +import { MODELS } from "@/lib/providers"; +import { cn } from "@/lib/utils"; +import { AiLoading } from "./loading"; +import { EXAMPLES_OF_PROJECT_SUGGESTIONS } from "@/lib/prompts"; + +export function AskAiLanding({ className }: { className?: string }) { + const [model, setModel] = useState(MODELS[0].value); + const [provider, setProvider] = useLocalStorage( + "provider", + "auto" as ProviderType + ); + const router = useRouter(); + const [prompt, setPrompt] = useState(""); + return ( +
+