Spaces:
Running
Running
add Carrefour Logo
#421
by
mistercharlou
- opened
This view is limited to 50 files because it contains too many changes.
See the raw diff here.
- .env.example +0 -4
- .github/workflows/deploy-prod.yml +0 -77
- .gitignore +1 -7
- Dockerfile +6 -9
- README.md +9 -10
- actions/mentions.ts +0 -31
- actions/projects.ts +0 -175
- app/(public)/layout.tsx +4 -3
- app/(public)/page.tsx +37 -18
- app/(public)/projects/page.tsx +13 -0
- app/(public)/signin/page.tsx +0 -21
- app/[owner]/[repoId]/page.tsx +0 -35
- app/actions/auth.ts +18 -0
- app/actions/projects.ts +63 -0
- app/actions/rewrite-prompt.ts +35 -0
- app/api/ask-ai/route.ts +510 -0
- app/api/ask/route.ts +0 -183
- app/api/auth/[...nextauth]/route.ts +0 -6
- app/api/auth/route.ts +86 -0
- app/api/healthcheck/route.ts +0 -5
- app/api/me/projects/[namespace]/[repoId]/images/route.ts +111 -0
- app/api/me/projects/[namespace]/[repoId]/route.ts +276 -0
- app/api/me/projects/route.ts +127 -0
- app/api/me/route.ts +25 -0
- app/api/projects/[repoId]/[commitId]/route.ts +0 -49
- app/api/projects/[repoId]/download/route.ts +0 -76
- app/api/projects/[repoId]/medias/route.ts +0 -87
- app/api/projects/[repoId]/rename/route.ts +0 -92
- app/api/projects/[repoId]/route.ts +0 -104
- app/api/projects/route.ts +0 -145
- app/api/re-design/route.ts +39 -0
- app/api/redesign/route.ts +0 -73
- app/auth/callback/page.tsx +72 -0
- app/auth/page.tsx +28 -0
- app/layout.tsx +69 -65
- app/new/page.tsx +0 -18
- app/not-found.tsx +0 -17
- app/projects/[namespace]/[repoId]/page.tsx +42 -0
- app/projects/new/page.tsx +5 -0
- assets/deepseek.svg +0 -1
- {app → assets}/globals.css +19 -41
- assets/hf-logo.svg +0 -7
- assets/kimi.svg +0 -1
- assets/logo.svg +316 -0
- assets/minimax.svg +0 -1
- assets/pro.svg +0 -10
- assets/qwen.svg +0 -1
- assets/zai.svg +0 -13
- chart/Chart.yaml +0 -5
- chart/env/prod.yaml +0 -59
.env.example
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
AUTH_HUGGINGFACE_ID=
|
| 2 |
-
AUTH_HUGGINGFACE_SECRET=
|
| 3 |
-
NEXTAUTH_URL=http://localhost:3001
|
| 4 |
-
AUTH_SECRET=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.github/workflows/deploy-prod.yml
DELETED
|
@@ -1,77 +0,0 @@
|
|
| 1 |
-
name: Deploy to k8s
|
| 2 |
-
on:
|
| 3 |
-
# run this workflow manually from the Actions tab
|
| 4 |
-
workflow_dispatch:
|
| 5 |
-
|
| 6 |
-
jobs:
|
| 7 |
-
build-and-publish:
|
| 8 |
-
runs-on:
|
| 9 |
-
group: cpu-high
|
| 10 |
-
steps:
|
| 11 |
-
- name: Checkout
|
| 12 |
-
uses: actions/checkout@v4
|
| 13 |
-
|
| 14 |
-
- name: Login to Registry
|
| 15 |
-
uses: docker/login-action@v3
|
| 16 |
-
with:
|
| 17 |
-
registry: registry.internal.huggingface.tech
|
| 18 |
-
username: ${{ secrets.DOCKER_INTERNAL_USERNAME }}
|
| 19 |
-
password: ${{ secrets.DOCKER_INTERNAL_PASSWORD }}
|
| 20 |
-
|
| 21 |
-
- name: Docker metadata
|
| 22 |
-
id: meta
|
| 23 |
-
uses: docker/metadata-action@v5
|
| 24 |
-
with:
|
| 25 |
-
images: |
|
| 26 |
-
registry.internal.huggingface.tech/deepsite/deepsite
|
| 27 |
-
tags: |
|
| 28 |
-
type=raw,value=latest,enable={{is_default_branch}}
|
| 29 |
-
type=sha,enable=true,prefix=sha-,format=short,sha-len=8
|
| 30 |
-
|
| 31 |
-
- name: Set up Docker Buildx
|
| 32 |
-
uses: docker/setup-buildx-action@v3
|
| 33 |
-
|
| 34 |
-
- name: Inject slug/short variables
|
| 35 |
-
uses: rlespinasse/github-slug-action@v4
|
| 36 |
-
|
| 37 |
-
- name: Build and Publish image
|
| 38 |
-
uses: docker/build-push-action@v5
|
| 39 |
-
with:
|
| 40 |
-
context: .
|
| 41 |
-
file: Dockerfile
|
| 42 |
-
push: ${{ github.event_name != 'pull_request' }}
|
| 43 |
-
tags: ${{ steps.meta.outputs.tags }}
|
| 44 |
-
labels: ${{ steps.meta.outputs.labels }}
|
| 45 |
-
platforms: linux/amd64
|
| 46 |
-
cache-to: type=gha,mode=max,scope=amd64
|
| 47 |
-
cache-from: type=gha,scope=amd64
|
| 48 |
-
provenance: false
|
| 49 |
-
|
| 50 |
-
deploy:
|
| 51 |
-
name: Deploy on prod
|
| 52 |
-
runs-on: ubuntu-latest
|
| 53 |
-
needs: ["build-and-publish"]
|
| 54 |
-
steps:
|
| 55 |
-
- name: Inject slug/short variables
|
| 56 |
-
uses: rlespinasse/github-slug-action@v4
|
| 57 |
-
|
| 58 |
-
- name: Gen values
|
| 59 |
-
run: |
|
| 60 |
-
VALUES=$(cat <<-END
|
| 61 |
-
image:
|
| 62 |
-
tag: "sha-${{ env.GITHUB_SHA_SHORT }}"
|
| 63 |
-
END
|
| 64 |
-
)
|
| 65 |
-
echo "VALUES=$(echo "$VALUES" | yq -o=json | jq tostring)" >> $GITHUB_ENV
|
| 66 |
-
|
| 67 |
-
- name: Deploy on infra-deployments
|
| 68 |
-
uses: the-actions-org/workflow-dispatch@v2
|
| 69 |
-
with:
|
| 70 |
-
workflow: Update application single value
|
| 71 |
-
repo: huggingface/infra-deployments
|
| 72 |
-
wait-for-completion: true
|
| 73 |
-
wait-for-completion-interval: 10s
|
| 74 |
-
display-workflow-run-url-interval: 10s
|
| 75 |
-
ref: refs/heads/main
|
| 76 |
-
token: ${{ secrets.GIT_TOKEN_INFRA_DEPLOYMENT }}
|
| 77 |
-
inputs: '{"path": "hub/deepsite/deepsite.yaml", "value": ${{ env.VALUES }}, "url": "${{ github.event.head_commit.url }}"}'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
CHANGED
|
@@ -31,7 +31,7 @@ yarn-error.log*
|
|
| 31 |
.pnpm-debug.log*
|
| 32 |
|
| 33 |
# env files (can opt-in for committing if needed)
|
| 34 |
-
.env
|
| 35 |
|
| 36 |
# vercel
|
| 37 |
.vercel
|
|
@@ -39,9 +39,3 @@ yarn-error.log*
|
|
| 39 |
# typescript
|
| 40 |
*.tsbuildinfo
|
| 41 |
next-env.d.ts
|
| 42 |
-
|
| 43 |
-
.idea
|
| 44 |
-
|
| 45 |
-
# binary assets (hosted on CDN)
|
| 46 |
-
assets/assistant.jpg
|
| 47 |
-
.gitattributes
|
|
|
|
| 31 |
.pnpm-debug.log*
|
| 32 |
|
| 33 |
# env files (can opt-in for committing if needed)
|
| 34 |
+
.env*
|
| 35 |
|
| 36 |
# vercel
|
| 37 |
.vercel
|
|
|
|
| 39 |
# typescript
|
| 40 |
*.tsbuildinfo
|
| 41 |
next-env.d.ts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Dockerfile
CHANGED
|
@@ -1,22 +1,19 @@
|
|
| 1 |
FROM node:20-alpine
|
| 2 |
USER root
|
| 3 |
|
| 4 |
-
# Install pnpm
|
| 5 |
-
RUN corepack enable && corepack prepare pnpm@latest --activate
|
| 6 |
-
|
| 7 |
USER 1000
|
| 8 |
WORKDIR /usr/src/app
|
| 9 |
-
# Copy package.json and
|
| 10 |
-
COPY --chown=1000 package.json
|
| 11 |
|
| 12 |
# Copy the rest of the application files to the container
|
| 13 |
COPY --chown=1000 . .
|
| 14 |
|
| 15 |
-
RUN
|
| 16 |
-
RUN
|
| 17 |
|
| 18 |
# Expose the application port (assuming your app runs on port 3000)
|
| 19 |
-
EXPOSE
|
| 20 |
|
| 21 |
# Start the application
|
| 22 |
-
CMD ["
|
|
|
|
| 1 |
FROM node:20-alpine
|
| 2 |
USER root
|
| 3 |
|
|
|
|
|
|
|
|
|
|
| 4 |
USER 1000
|
| 5 |
WORKDIR /usr/src/app
|
| 6 |
+
# Copy package.json and package-lock.json to the container
|
| 7 |
+
COPY --chown=1000 package.json package-lock.json ./
|
| 8 |
|
| 9 |
# Copy the rest of the application files to the container
|
| 10 |
COPY --chown=1000 . .
|
| 11 |
|
| 12 |
+
RUN npm install
|
| 13 |
+
RUN npm run build
|
| 14 |
|
| 15 |
# Expose the application port (assuming your app runs on port 3000)
|
| 16 |
+
EXPOSE 3000
|
| 17 |
|
| 18 |
# Start the application
|
| 19 |
+
CMD ["npm", "start"]
|
README.md
CHANGED
|
@@ -1,23 +1,22 @@
|
|
| 1 |
---
|
| 2 |
-
title: DeepSite
|
| 3 |
emoji: 🐳
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: true
|
| 8 |
-
app_port:
|
| 9 |
license: mit
|
| 10 |
-
|
| 11 |
-
short_description: Generate any application by Vibe Coding it
|
| 12 |
models:
|
| 13 |
- deepseek-ai/DeepSeek-V3-0324
|
| 14 |
-
- deepseek-ai/DeepSeek-
|
| 15 |
-
- Qwen/Qwen3-Coder-30B-A3B-Instruct
|
| 16 |
-
- moonshotai/Kimi-K2-Instruct-0905
|
| 17 |
-
- zai-org/GLM-4.7
|
| 18 |
-
- MiniMaxAI/MiniMax-M2.1
|
| 19 |
---
|
| 20 |
|
| 21 |
# DeepSite 🐳
|
| 22 |
|
| 23 |
-
DeepSite is a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: DeepSite v2
|
| 3 |
emoji: 🐳
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: true
|
| 8 |
+
app_port: 3000
|
| 9 |
license: mit
|
| 10 |
+
short_description: Generate any application with DeepSeek
|
|
|
|
| 11 |
models:
|
| 12 |
- deepseek-ai/DeepSeek-V3-0324
|
| 13 |
+
- deepseek-ai/DeepSeek-R1-0528
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
---
|
| 15 |
|
| 16 |
# DeepSite 🐳
|
| 17 |
|
| 18 |
+
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.
|
| 19 |
+
|
| 20 |
+
## How to use it locally
|
| 21 |
+
|
| 22 |
+
Follow [this discussion](https://huggingface.co/spaces/enzostvs/deepsite/discussions/74)
|
actions/mentions.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
| 1 |
-
"use client";
|
| 2 |
-
|
| 3 |
-
import { File } from "@/lib/type";
|
| 4 |
-
|
| 5 |
-
export const searchMentions = async (query: string) => {
|
| 6 |
-
const promises = [searchModels(query), searchDatasets(query)];
|
| 7 |
-
const results = await Promise.all(promises);
|
| 8 |
-
return { models: results[0], datasets: results[1] };
|
| 9 |
-
};
|
| 10 |
-
|
| 11 |
-
const searchModels = async (query: string) => {
|
| 12 |
-
const response = await fetch(
|
| 13 |
-
`https://huggingface.co/api/quicksearch?q=${query}&type=model&limit=3`
|
| 14 |
-
);
|
| 15 |
-
const data = await response.json();
|
| 16 |
-
return data?.models ?? [];
|
| 17 |
-
};
|
| 18 |
-
|
| 19 |
-
const searchDatasets = async (query: string) => {
|
| 20 |
-
const response = await fetch(
|
| 21 |
-
`https://huggingface.co/api/quicksearch?q=${query}&type=dataset&limit=3`
|
| 22 |
-
);
|
| 23 |
-
const data = await response.json();
|
| 24 |
-
return data?.datasets ?? [];
|
| 25 |
-
};
|
| 26 |
-
|
| 27 |
-
export const searchFilesMentions = async (query: string, files: File[]) => {
|
| 28 |
-
if (!query) return files;
|
| 29 |
-
const lowerQuery = query.toLowerCase();
|
| 30 |
-
return files.filter((file) => file.path.toLowerCase().includes(lowerQuery));
|
| 31 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
actions/projects.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
| 1 |
-
"use server";
|
| 2 |
-
import {
|
| 3 |
-
downloadFile,
|
| 4 |
-
listCommits,
|
| 5 |
-
listFiles,
|
| 6 |
-
listSpaces,
|
| 7 |
-
RepoDesignation,
|
| 8 |
-
SpaceEntry,
|
| 9 |
-
spaceInfo,
|
| 10 |
-
} from "@huggingface/hub";
|
| 11 |
-
|
| 12 |
-
import { auth } from "@/lib/auth";
|
| 13 |
-
import { Commit, File } from "@/lib/type";
|
| 14 |
-
|
| 15 |
-
export interface ProjectWithCommits extends SpaceEntry {
|
| 16 |
-
commits?: Commit[];
|
| 17 |
-
medias?: string[];
|
| 18 |
-
}
|
| 19 |
-
|
| 20 |
-
const IGNORED_PATHS = ["README.md", ".gitignore", ".gitattributes"];
|
| 21 |
-
const IGNORED_FORMATS = [
|
| 22 |
-
".png",
|
| 23 |
-
".jpg",
|
| 24 |
-
".jpeg",
|
| 25 |
-
".gif",
|
| 26 |
-
".svg",
|
| 27 |
-
".webp",
|
| 28 |
-
".mp4",
|
| 29 |
-
".mp3",
|
| 30 |
-
".wav",
|
| 31 |
-
];
|
| 32 |
-
|
| 33 |
-
export const getProjects = async () => {
|
| 34 |
-
const projects: SpaceEntry[] = [];
|
| 35 |
-
const session = await auth();
|
| 36 |
-
if (!session?.user) {
|
| 37 |
-
return projects;
|
| 38 |
-
}
|
| 39 |
-
const token = session.accessToken;
|
| 40 |
-
for await (const space of listSpaces({
|
| 41 |
-
accessToken: token,
|
| 42 |
-
additionalFields: ["author", "cardData"],
|
| 43 |
-
search: {
|
| 44 |
-
owner: session.user.username,
|
| 45 |
-
},
|
| 46 |
-
})) {
|
| 47 |
-
if (
|
| 48 |
-
space.sdk === "static" &&
|
| 49 |
-
Array.isArray((space.cardData as { tags?: string[] })?.tags) &&
|
| 50 |
-
(space.cardData as { tags?: string[] })?.tags?.some((tag) =>
|
| 51 |
-
tag.includes("deepsite")
|
| 52 |
-
)
|
| 53 |
-
) {
|
| 54 |
-
projects.push(space);
|
| 55 |
-
}
|
| 56 |
-
}
|
| 57 |
-
return projects;
|
| 58 |
-
};
|
| 59 |
-
export const getProject = async (id: string, commitId?: string) => {
|
| 60 |
-
const session = await auth();
|
| 61 |
-
if (!session?.user) {
|
| 62 |
-
return null;
|
| 63 |
-
}
|
| 64 |
-
const token = session.accessToken;
|
| 65 |
-
try {
|
| 66 |
-
const project: ProjectWithCommits | null = await spaceInfo({
|
| 67 |
-
name: id,
|
| 68 |
-
accessToken: token,
|
| 69 |
-
additionalFields: ["author", "cardData"],
|
| 70 |
-
});
|
| 71 |
-
const repo: RepoDesignation = {
|
| 72 |
-
type: "space",
|
| 73 |
-
name: id,
|
| 74 |
-
};
|
| 75 |
-
const files: File[] = [];
|
| 76 |
-
const medias: string[] = [];
|
| 77 |
-
const params = { repo, accessToken: token };
|
| 78 |
-
if (commitId) {
|
| 79 |
-
Object.assign(params, { revision: commitId });
|
| 80 |
-
}
|
| 81 |
-
for await (const fileInfo of listFiles(params)) {
|
| 82 |
-
if (IGNORED_PATHS.includes(fileInfo.path)) continue;
|
| 83 |
-
if (IGNORED_FORMATS.some((format) => fileInfo.path.endsWith(format))) {
|
| 84 |
-
medias.push(
|
| 85 |
-
`https://huggingface.co/spaces/${id}/resolve/main/${fileInfo.path}`
|
| 86 |
-
);
|
| 87 |
-
continue;
|
| 88 |
-
}
|
| 89 |
-
|
| 90 |
-
if (fileInfo.type === "directory") {
|
| 91 |
-
for await (const subFile of listFiles({
|
| 92 |
-
repo,
|
| 93 |
-
accessToken: token,
|
| 94 |
-
path: fileInfo.path,
|
| 95 |
-
})) {
|
| 96 |
-
if (IGNORED_FORMATS.some((format) => subFile.path.endsWith(format))) {
|
| 97 |
-
medias.push(
|
| 98 |
-
`https://huggingface.co/spaces/${id}/resolve/main/${subFile.path}`
|
| 99 |
-
);
|
| 100 |
-
}
|
| 101 |
-
const blob = await downloadFile({
|
| 102 |
-
repo,
|
| 103 |
-
accessToken: token,
|
| 104 |
-
path: subFile.path,
|
| 105 |
-
raw: true,
|
| 106 |
-
...(commitId ? { revision: commitId } : {}),
|
| 107 |
-
}).catch((_) => {
|
| 108 |
-
return null;
|
| 109 |
-
});
|
| 110 |
-
if (!blob) {
|
| 111 |
-
continue;
|
| 112 |
-
}
|
| 113 |
-
const html = await blob?.text();
|
| 114 |
-
if (!html) {
|
| 115 |
-
continue;
|
| 116 |
-
}
|
| 117 |
-
files[subFile.path === "index.html" ? "unshift" : "push"]({
|
| 118 |
-
path: subFile.path,
|
| 119 |
-
content: html,
|
| 120 |
-
});
|
| 121 |
-
}
|
| 122 |
-
} else {
|
| 123 |
-
const blob = await downloadFile({
|
| 124 |
-
repo,
|
| 125 |
-
accessToken: token,
|
| 126 |
-
path: fileInfo.path,
|
| 127 |
-
raw: true,
|
| 128 |
-
...(commitId ? { revision: commitId } : {}),
|
| 129 |
-
}).catch((_) => {
|
| 130 |
-
return null;
|
| 131 |
-
});
|
| 132 |
-
if (!blob) {
|
| 133 |
-
continue;
|
| 134 |
-
}
|
| 135 |
-
const html = await blob?.text();
|
| 136 |
-
if (!html) {
|
| 137 |
-
continue;
|
| 138 |
-
}
|
| 139 |
-
files[fileInfo.path === "index.html" ? "unshift" : "push"]({
|
| 140 |
-
path: fileInfo.path,
|
| 141 |
-
content: html,
|
| 142 |
-
});
|
| 143 |
-
}
|
| 144 |
-
}
|
| 145 |
-
const commits: Commit[] = [];
|
| 146 |
-
const commitIterator = listCommits({ repo, accessToken: token });
|
| 147 |
-
for await (const commit of commitIterator) {
|
| 148 |
-
if (
|
| 149 |
-
commit.title?.toLowerCase() === "initial commit" ||
|
| 150 |
-
commit.title
|
| 151 |
-
?.toLowerCase()
|
| 152 |
-
?.includes("upload media files through deepsite")
|
| 153 |
-
)
|
| 154 |
-
continue;
|
| 155 |
-
commits.push({
|
| 156 |
-
title: commit.title,
|
| 157 |
-
oid: commit.oid,
|
| 158 |
-
date: commit.date,
|
| 159 |
-
});
|
| 160 |
-
if (commits.length >= 20) {
|
| 161 |
-
break;
|
| 162 |
-
}
|
| 163 |
-
}
|
| 164 |
-
|
| 165 |
-
project.commits = commits;
|
| 166 |
-
project.medias = medias;
|
| 167 |
-
|
| 168 |
-
return { project, files };
|
| 169 |
-
} catch (error) {
|
| 170 |
-
return {
|
| 171 |
-
project: null,
|
| 172 |
-
files: [],
|
| 173 |
-
};
|
| 174 |
-
}
|
| 175 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/(public)/layout.tsx
CHANGED
|
@@ -1,12 +1,13 @@
|
|
| 1 |
-
import
|
| 2 |
|
| 3 |
-
export default function PublicLayout({
|
| 4 |
children,
|
| 5 |
}: Readonly<{
|
| 6 |
children: React.ReactNode;
|
| 7 |
}>) {
|
| 8 |
return (
|
| 9 |
-
<div className="min-h-screen
|
|
|
|
| 10 |
<Navigation />
|
| 11 |
{children}
|
| 12 |
</div>
|
|
|
|
| 1 |
+
import Navigation from "@/components/public/navigation";
|
| 2 |
|
| 3 |
+
export default async function PublicLayout({
|
| 4 |
children,
|
| 5 |
}: Readonly<{
|
| 6 |
children: React.ReactNode;
|
| 7 |
}>) {
|
| 8 |
return (
|
| 9 |
+
<div className="min-h-screen bg-black z-1 relative">
|
| 10 |
+
<div className="background__noisy" />
|
| 11 |
<Navigation />
|
| 12 |
{children}
|
| 13 |
</div>
|
app/(public)/page.tsx
CHANGED
|
@@ -1,25 +1,44 @@
|
|
| 1 |
-
import {
|
| 2 |
-
import {
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
import { Bento } from "@/components/public/bento";
|
| 6 |
-
|
| 7 |
-
export const dynamic = "force-dynamic";
|
| 8 |
-
|
| 9 |
-
export default async function Homepage() {
|
| 10 |
return (
|
| 11 |
<>
|
| 12 |
-
<
|
| 13 |
-
<
|
| 14 |
-
|
| 15 |
-
<AnimatedDotsBackground />
|
| 16 |
</div>
|
| 17 |
-
<
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</div>
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
</>
|
| 24 |
);
|
| 25 |
}
|
|
|
|
| 1 |
+
import { AskAi } from "@/components/space/ask-ai";
|
| 2 |
+
import { redirect } from "next/navigation";
|
| 3 |
+
export default function Home() {
|
| 4 |
+
redirect("/projects/new");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
return (
|
| 6 |
<>
|
| 7 |
+
<header className="container mx-auto pt-20 px-6 relative flex flex-col items-center justify-center text-center">
|
| 8 |
+
<div className="rounded-full border border-neutral-100/10 bg-neutral-100/5 text-xs text-neutral-300 px-3 py-1 max-w-max mx-auto mb-2">
|
| 9 |
+
✨ DeepSite Public Beta
|
|
|
|
| 10 |
</div>
|
| 11 |
+
<h1 className="text-8xl font-semibold text-white font-mono max-w-4xl">
|
| 12 |
+
Code your website with AI in seconds
|
| 13 |
+
</h1>
|
| 14 |
+
<p className="text-2xl text-neutral-300/80 mt-4 text-center max-w-2xl">
|
| 15 |
+
Vibe Coding has never been so easy.
|
| 16 |
+
</p>
|
| 17 |
+
<div className="mt-14 max-w-2xl w-full mx-auto">
|
| 18 |
+
<AskAi />
|
| 19 |
</div>
|
| 20 |
+
<div className="absolute inset-0 pointer-events-none -z-[1]">
|
| 21 |
+
<div className="w-full h-full bg-gradient-to-r from-purple-500 to-pink-500 opacity-10 blur-3xl rounded-full" />
|
| 22 |
+
<div className="w-2/3 h-3/4 bg-gradient-to-r from-blue-500 to-teal-500 opacity-24 blur-3xl absolute -top-20 right-10 transform rotate-12" />
|
| 23 |
+
<div className="w-1/2 h-1/2 bg-gradient-to-r from-amber-500 to-rose-500 opacity-20 blur-3xl absolute bottom-0 left-10 rounded-3xl" />
|
| 24 |
+
<div className="w-48 h-48 bg-gradient-to-r from-cyan-500 to-indigo-500 opacity-20 blur-3xl absolute top-1/3 right-1/3 rounded-lg transform -rotate-15" />
|
| 25 |
+
</div>
|
| 26 |
+
</header>
|
| 27 |
+
<div id="community" className="h-screen flex items-center justify-center">
|
| 28 |
+
<h1 className="text-7xl font-extrabold text-white font-mono">
|
| 29 |
+
Community Driven
|
| 30 |
+
</h1>
|
| 31 |
+
</div>
|
| 32 |
+
<div id="deploy" className="h-screen flex items-center justify-center">
|
| 33 |
+
<h1 className="text-7xl font-extrabold text-white font-mono">
|
| 34 |
+
Deploy your website in seconds
|
| 35 |
+
</h1>
|
| 36 |
+
</div>
|
| 37 |
+
<div id="features" className="h-screen flex items-center justify-center">
|
| 38 |
+
<h1 className="text-7xl font-extrabold text-white font-mono">
|
| 39 |
+
Features that make you smile
|
| 40 |
+
</h1>
|
| 41 |
+
</div>
|
| 42 |
</>
|
| 43 |
);
|
| 44 |
}
|
app/(public)/projects/page.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { redirect } from "next/navigation";
|
| 2 |
+
|
| 3 |
+
import { MyProjects } from "@/components/my-projects";
|
| 4 |
+
import { getProjects } from "@/app/actions/projects";
|
| 5 |
+
|
| 6 |
+
export default async function ProjectsPage() {
|
| 7 |
+
const { ok, projects } = await getProjects();
|
| 8 |
+
if (!ok) {
|
| 9 |
+
redirect("/");
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
return <MyProjects projects={projects} />;
|
| 13 |
+
}
|
app/(public)/signin/page.tsx
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
import { LoginButtons } from "@/components/login/login-buttons";
|
| 2 |
-
|
| 3 |
-
export default async function SignInPage({
|
| 4 |
-
searchParams,
|
| 5 |
-
}: {
|
| 6 |
-
searchParams: Promise<{ callbackUrl: string }>;
|
| 7 |
-
}) {
|
| 8 |
-
const { callbackUrl } = await searchParams;
|
| 9 |
-
console.log(callbackUrl);
|
| 10 |
-
return (
|
| 11 |
-
<section className="min-h-screen font-sans">
|
| 12 |
-
<div className="px-6 py-16 max-w-5xl mx-auto text-center">
|
| 13 |
-
<h1 className="text-5xl font-bold mb-5">You shall not pass 🧙</h1>
|
| 14 |
-
<p className="text-lg text-muted-foreground mb-8">
|
| 15 |
-
You can't access this resource without being signed in.
|
| 16 |
-
</p>
|
| 17 |
-
<LoginButtons callbackUrl={callbackUrl ?? "/"} />
|
| 18 |
-
</div>
|
| 19 |
-
</section>
|
| 20 |
-
);
|
| 21 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/[owner]/[repoId]/page.tsx
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
import { getProject } from "@/actions/projects";
|
| 2 |
-
import { AppEditor } from "@/components/editor";
|
| 3 |
-
import { auth } from "@/lib/auth";
|
| 4 |
-
import { notFound, redirect } from "next/navigation";
|
| 5 |
-
|
| 6 |
-
export default async function ProjectPage({
|
| 7 |
-
params,
|
| 8 |
-
searchParams,
|
| 9 |
-
}: {
|
| 10 |
-
params: Promise<{ owner: string; repoId: string }>;
|
| 11 |
-
searchParams: Promise<{ commit?: string }>;
|
| 12 |
-
}) {
|
| 13 |
-
const session = await auth();
|
| 14 |
-
|
| 15 |
-
const { owner, repoId } = await params;
|
| 16 |
-
const { commit } = await searchParams;
|
| 17 |
-
if (!session) {
|
| 18 |
-
redirect(
|
| 19 |
-
`/api/auth/signin?callbackUrl=/${owner}/${repoId}${
|
| 20 |
-
commit ? `?commit=${commit}` : ""
|
| 21 |
-
}`
|
| 22 |
-
);
|
| 23 |
-
}
|
| 24 |
-
const datas = await getProject(`${owner}/${repoId}`, commit);
|
| 25 |
-
if (!datas?.project) {
|
| 26 |
-
return notFound();
|
| 27 |
-
}
|
| 28 |
-
return (
|
| 29 |
-
<AppEditor
|
| 30 |
-
project={datas.project}
|
| 31 |
-
files={datas.files ?? []}
|
| 32 |
-
isHistoryView={!!commit}
|
| 33 |
-
/>
|
| 34 |
-
);
|
| 35 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/actions/auth.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use server";
|
| 2 |
+
|
| 3 |
+
import { headers } from "next/headers";
|
| 4 |
+
|
| 5 |
+
export async function getAuth() {
|
| 6 |
+
const authList = await headers();
|
| 7 |
+
const host = authList.get("host") ?? "localhost:3000";
|
| 8 |
+
const url = host.includes("/spaces/enzostvs")
|
| 9 |
+
? "enzostvs-deepsite.hf.space"
|
| 10 |
+
: host;
|
| 11 |
+
const redirect_uri =
|
| 12 |
+
`${host.includes("localhost") ? "http://" : "https://"}` +
|
| 13 |
+
url +
|
| 14 |
+
"/auth/callback";
|
| 15 |
+
|
| 16 |
+
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`;
|
| 17 |
+
return loginRedirectUrl;
|
| 18 |
+
}
|
app/actions/projects.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use server";
|
| 2 |
+
|
| 3 |
+
import { isAuthenticated } from "@/lib/auth";
|
| 4 |
+
import { NextResponse } from "next/server";
|
| 5 |
+
import dbConnect from "@/lib/mongodb";
|
| 6 |
+
import Project from "@/models/Project";
|
| 7 |
+
import { Project as ProjectType } from "@/types";
|
| 8 |
+
|
| 9 |
+
export async function getProjects(): Promise<{
|
| 10 |
+
ok: boolean;
|
| 11 |
+
projects: ProjectType[];
|
| 12 |
+
}> {
|
| 13 |
+
const user = await isAuthenticated();
|
| 14 |
+
|
| 15 |
+
if (user instanceof NextResponse || !user) {
|
| 16 |
+
return {
|
| 17 |
+
ok: false,
|
| 18 |
+
projects: [],
|
| 19 |
+
};
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
await dbConnect();
|
| 23 |
+
const projects = await Project.find({
|
| 24 |
+
user_id: user?.id,
|
| 25 |
+
})
|
| 26 |
+
.sort({ _createdAt: -1 })
|
| 27 |
+
.limit(100)
|
| 28 |
+
.lean();
|
| 29 |
+
if (!projects) {
|
| 30 |
+
return {
|
| 31 |
+
ok: false,
|
| 32 |
+
projects: [],
|
| 33 |
+
};
|
| 34 |
+
}
|
| 35 |
+
return {
|
| 36 |
+
ok: true,
|
| 37 |
+
projects: JSON.parse(JSON.stringify(projects)) as ProjectType[],
|
| 38 |
+
};
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
export async function getProject(
|
| 42 |
+
namespace: string,
|
| 43 |
+
repoId: string
|
| 44 |
+
): Promise<ProjectType | null> {
|
| 45 |
+
const user = await isAuthenticated();
|
| 46 |
+
|
| 47 |
+
if (user instanceof NextResponse || !user) {
|
| 48 |
+
return null;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
await dbConnect();
|
| 52 |
+
const project = await Project.findOne({
|
| 53 |
+
user_id: user.id,
|
| 54 |
+
namespace,
|
| 55 |
+
repoId,
|
| 56 |
+
}).lean();
|
| 57 |
+
|
| 58 |
+
if (!project) {
|
| 59 |
+
return null;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
return JSON.parse(JSON.stringify(project)) as ProjectType;
|
| 63 |
+
}
|
app/actions/rewrite-prompt.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { InferenceClient } from "@huggingface/inference";
|
| 2 |
+
|
| 3 |
+
const START_REWRITE_PROMPT = ">>>>>>> START PROMPT >>>>>>";
|
| 4 |
+
const END_REWRITE_PROMPT = ">>>>>>> END PROMPT >>>>>>";
|
| 5 |
+
|
| 6 |
+
export const callAiRewritePrompt = async (prompt: string, { token, billTo }: { token: string, billTo?: string | null }) => {
|
| 7 |
+
const client = new InferenceClient(token);
|
| 8 |
+
const response = await client.chatCompletion(
|
| 9 |
+
{
|
| 10 |
+
model: "deepseek-ai/DeepSeek-V3.1",
|
| 11 |
+
provider: "novita",
|
| 12 |
+
messages: [{
|
| 13 |
+
role: "system",
|
| 14 |
+
content: `You are a helpful assistant that rewrites prompts to make them better. All the prompts will be about creating a website or app.
|
| 15 |
+
Try to make the prompt more detailed and specific to create a good UI/UX Design and good code.
|
| 16 |
+
Format the result by following this format:
|
| 17 |
+
${START_REWRITE_PROMPT}
|
| 18 |
+
new prompt here
|
| 19 |
+
${END_REWRITE_PROMPT}
|
| 20 |
+
If you don't rewrite the prompt, return the original prompt.
|
| 21 |
+
Make sure to return the prompt in the same language as the prompt you are given. Also IMPORTANT: Make sure to keep the original intent of the prompt. Improve it it needed, but don't change the original intent.
|
| 22 |
+
`
|
| 23 |
+
},{ role: "user", content: prompt }],
|
| 24 |
+
},
|
| 25 |
+
billTo ? { billTo } : {}
|
| 26 |
+
);
|
| 27 |
+
|
| 28 |
+
const responseContent = response.choices[0]?.message?.content;
|
| 29 |
+
if (!responseContent) {
|
| 30 |
+
return prompt;
|
| 31 |
+
}
|
| 32 |
+
const startIndex = responseContent.indexOf(START_REWRITE_PROMPT);
|
| 33 |
+
const endIndex = responseContent.indexOf(END_REWRITE_PROMPT);
|
| 34 |
+
return responseContent.substring(startIndex + START_REWRITE_PROMPT.length, endIndex);
|
| 35 |
+
};
|
app/api/ask-ai/route.ts
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
+
import type { NextRequest } from "next/server";
|
| 3 |
+
import { NextResponse } from "next/server";
|
| 4 |
+
import { headers } from "next/headers";
|
| 5 |
+
import { InferenceClient } from "@huggingface/inference";
|
| 6 |
+
|
| 7 |
+
import { MODELS, PROVIDERS } from "@/lib/providers";
|
| 8 |
+
import {
|
| 9 |
+
DIVIDER,
|
| 10 |
+
FOLLOW_UP_SYSTEM_PROMPT,
|
| 11 |
+
INITIAL_SYSTEM_PROMPT,
|
| 12 |
+
MAX_REQUESTS_PER_IP,
|
| 13 |
+
NEW_PAGE_END,
|
| 14 |
+
NEW_PAGE_START,
|
| 15 |
+
REPLACE_END,
|
| 16 |
+
SEARCH_START,
|
| 17 |
+
UPDATE_PAGE_START,
|
| 18 |
+
UPDATE_PAGE_END,
|
| 19 |
+
} from "@/lib/prompts";
|
| 20 |
+
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 21 |
+
import { Page } from "@/types";
|
| 22 |
+
|
| 23 |
+
const ipAddresses = new Map();
|
| 24 |
+
|
| 25 |
+
export async function POST(request: NextRequest) {
|
| 26 |
+
const authHeaders = await headers();
|
| 27 |
+
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
| 28 |
+
|
| 29 |
+
const body = await request.json();
|
| 30 |
+
const { prompt, provider, model, redesignMarkdown, previousPrompts, pages } = body;
|
| 31 |
+
|
| 32 |
+
if (!model || (!prompt && !redesignMarkdown)) {
|
| 33 |
+
return NextResponse.json(
|
| 34 |
+
{ ok: false, error: "Missing required fields" },
|
| 35 |
+
{ status: 400 }
|
| 36 |
+
);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
const selectedModel = MODELS.find(
|
| 40 |
+
(m) => m.value === model || m.label === model
|
| 41 |
+
);
|
| 42 |
+
|
| 43 |
+
if (!selectedModel) {
|
| 44 |
+
return NextResponse.json(
|
| 45 |
+
{ ok: false, error: "Invalid model selected" },
|
| 46 |
+
{ status: 400 }
|
| 47 |
+
);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
if (!selectedModel.providers.includes(provider) && provider !== "auto") {
|
| 51 |
+
return NextResponse.json(
|
| 52 |
+
{
|
| 53 |
+
ok: false,
|
| 54 |
+
error: `The selected model does not support the ${provider} provider.`,
|
| 55 |
+
openSelectProvider: true,
|
| 56 |
+
},
|
| 57 |
+
{ status: 400 }
|
| 58 |
+
);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
let token = userToken;
|
| 62 |
+
let billTo: string | null = null;
|
| 63 |
+
|
| 64 |
+
/**
|
| 65 |
+
* Handle local usage token, this bypass the need for a user token
|
| 66 |
+
* and allows local testing without authentication.
|
| 67 |
+
* This is useful for development and testing purposes.
|
| 68 |
+
*/
|
| 69 |
+
if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
|
| 70 |
+
token = process.env.HF_TOKEN;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
const ip = authHeaders.get("x-forwarded-for")?.includes(",")
|
| 74 |
+
? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
|
| 75 |
+
: authHeaders.get("x-forwarded-for");
|
| 76 |
+
|
| 77 |
+
if (!token) {
|
| 78 |
+
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 79 |
+
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 80 |
+
return NextResponse.json(
|
| 81 |
+
{
|
| 82 |
+
ok: false,
|
| 83 |
+
openLogin: true,
|
| 84 |
+
message: "Log In to continue using the service",
|
| 85 |
+
},
|
| 86 |
+
{ status: 429 }
|
| 87 |
+
);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
token = process.env.DEFAULT_HF_TOKEN as string;
|
| 91 |
+
billTo = "huggingface";
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
const DEFAULT_PROVIDER = PROVIDERS.novita;
|
| 95 |
+
const selectedProvider =
|
| 96 |
+
provider === "auto"
|
| 97 |
+
? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS]
|
| 98 |
+
: PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
|
| 99 |
+
|
| 100 |
+
const rewrittenPrompt = prompt;
|
| 101 |
+
|
| 102 |
+
// if (prompt?.length < 240) {
|
| 103 |
+
|
| 104 |
+
//rewrittenPrompt = await callAiRewritePrompt(prompt, { token, billTo });
|
| 105 |
+
// }
|
| 106 |
+
|
| 107 |
+
try {
|
| 108 |
+
const encoder = new TextEncoder();
|
| 109 |
+
const stream = new TransformStream();
|
| 110 |
+
const writer = stream.writable.getWriter();
|
| 111 |
+
|
| 112 |
+
const response = new NextResponse(stream.readable, {
|
| 113 |
+
headers: {
|
| 114 |
+
"Content-Type": "text/plain; charset=utf-8",
|
| 115 |
+
"Cache-Control": "no-cache",
|
| 116 |
+
Connection: "keep-alive",
|
| 117 |
+
},
|
| 118 |
+
});
|
| 119 |
+
|
| 120 |
+
(async () => {
|
| 121 |
+
// let completeResponse = "";
|
| 122 |
+
try {
|
| 123 |
+
const client = new InferenceClient(token);
|
| 124 |
+
const chatCompletion = client.chatCompletionStream(
|
| 125 |
+
{
|
| 126 |
+
model: selectedModel.value,
|
| 127 |
+
provider: selectedProvider.id as any,
|
| 128 |
+
messages: [
|
| 129 |
+
{
|
| 130 |
+
role: "system",
|
| 131 |
+
content: INITIAL_SYSTEM_PROMPT,
|
| 132 |
+
},
|
| 133 |
+
...(pages?.length > 1 ? [{
|
| 134 |
+
role: "assistant",
|
| 135 |
+
content: `Here are the current pages:\n\n${pages.map((p: Page) => `- ${p.path} \n${p.html}`).join("\n")}\n\nNow, please create a new page based on this code. Also here are the previous prompts:\n\n${previousPrompts.map((p: string) => `- ${p}`).join("\n")}`
|
| 136 |
+
}] : []),
|
| 137 |
+
{
|
| 138 |
+
role: "user",
|
| 139 |
+
content: redesignMarkdown
|
| 140 |
+
? `Here is my current design as a markdown:\n\n${redesignMarkdown}\n\nNow, please create a new design based on this markdown.`
|
| 141 |
+
: rewrittenPrompt,
|
| 142 |
+
},
|
| 143 |
+
],
|
| 144 |
+
max_tokens: selectedProvider.max_tokens,
|
| 145 |
+
},
|
| 146 |
+
billTo ? { billTo } : {}
|
| 147 |
+
);
|
| 148 |
+
|
| 149 |
+
while (true) {
|
| 150 |
+
const { done, value } = await chatCompletion.next();
|
| 151 |
+
if (done) {
|
| 152 |
+
break;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
const chunk = value.choices[0]?.delta?.content;
|
| 156 |
+
if (chunk) {
|
| 157 |
+
await writer.write(encoder.encode(chunk));
|
| 158 |
+
}
|
| 159 |
+
}
|
| 160 |
+
} catch (error: any) {
|
| 161 |
+
if (error.message?.includes("exceeded your monthly included credits")) {
|
| 162 |
+
await writer.write(
|
| 163 |
+
encoder.encode(
|
| 164 |
+
JSON.stringify({
|
| 165 |
+
ok: false,
|
| 166 |
+
openProModal: true,
|
| 167 |
+
message: error.message,
|
| 168 |
+
})
|
| 169 |
+
)
|
| 170 |
+
);
|
| 171 |
+
} else {
|
| 172 |
+
await writer.write(
|
| 173 |
+
encoder.encode(
|
| 174 |
+
JSON.stringify({
|
| 175 |
+
ok: false,
|
| 176 |
+
message:
|
| 177 |
+
error.message ||
|
| 178 |
+
"An error occurred while processing your request.",
|
| 179 |
+
})
|
| 180 |
+
)
|
| 181 |
+
);
|
| 182 |
+
}
|
| 183 |
+
} finally {
|
| 184 |
+
await writer?.close();
|
| 185 |
+
}
|
| 186 |
+
})();
|
| 187 |
+
|
| 188 |
+
return response;
|
| 189 |
+
} catch (error: any) {
|
| 190 |
+
return NextResponse.json(
|
| 191 |
+
{
|
| 192 |
+
ok: false,
|
| 193 |
+
openSelectProvider: true,
|
| 194 |
+
message:
|
| 195 |
+
error?.message || "An error occurred while processing your request.",
|
| 196 |
+
},
|
| 197 |
+
{ status: 500 }
|
| 198 |
+
);
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
export async function PUT(request: NextRequest) {
|
| 203 |
+
const authHeaders = await headers();
|
| 204 |
+
const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
|
| 205 |
+
|
| 206 |
+
const body = await request.json();
|
| 207 |
+
const { prompt, previousPrompts, provider, selectedElementHtml, model, pages, files, } =
|
| 208 |
+
body;
|
| 209 |
+
|
| 210 |
+
if (!prompt || pages.length === 0) {
|
| 211 |
+
return NextResponse.json(
|
| 212 |
+
{ ok: false, error: "Missing required fields" },
|
| 213 |
+
{ status: 400 }
|
| 214 |
+
);
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
const selectedModel = MODELS.find(
|
| 218 |
+
(m) => m.value === model || m.label === model
|
| 219 |
+
);
|
| 220 |
+
if (!selectedModel) {
|
| 221 |
+
return NextResponse.json(
|
| 222 |
+
{ ok: false, error: "Invalid model selected" },
|
| 223 |
+
{ status: 400 }
|
| 224 |
+
);
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
let token = userToken;
|
| 228 |
+
let billTo: string | null = null;
|
| 229 |
+
|
| 230 |
+
/**
|
| 231 |
+
* Handle local usage token, this bypass the need for a user token
|
| 232 |
+
* and allows local testing without authentication.
|
| 233 |
+
* This is useful for development and testing purposes.
|
| 234 |
+
*/
|
| 235 |
+
if (process.env.HF_TOKEN && process.env.HF_TOKEN.length > 0) {
|
| 236 |
+
token = process.env.HF_TOKEN;
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
const ip = authHeaders.get("x-forwarded-for")?.includes(",")
|
| 240 |
+
? authHeaders.get("x-forwarded-for")?.split(",")[1].trim()
|
| 241 |
+
: authHeaders.get("x-forwarded-for");
|
| 242 |
+
|
| 243 |
+
if (!token) {
|
| 244 |
+
ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);
|
| 245 |
+
if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {
|
| 246 |
+
return NextResponse.json(
|
| 247 |
+
{
|
| 248 |
+
ok: false,
|
| 249 |
+
openLogin: true,
|
| 250 |
+
message: "Log In to continue using the service",
|
| 251 |
+
},
|
| 252 |
+
{ status: 429 }
|
| 253 |
+
);
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
token = process.env.DEFAULT_HF_TOKEN as string;
|
| 257 |
+
billTo = "huggingface";
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
const client = new InferenceClient(token);
|
| 261 |
+
|
| 262 |
+
const DEFAULT_PROVIDER = PROVIDERS.novita;
|
| 263 |
+
const selectedProvider =
|
| 264 |
+
provider === "auto"
|
| 265 |
+
? PROVIDERS[selectedModel.autoProvider as keyof typeof PROVIDERS]
|
| 266 |
+
: PROVIDERS[provider as keyof typeof PROVIDERS] ?? DEFAULT_PROVIDER;
|
| 267 |
+
|
| 268 |
+
try {
|
| 269 |
+
const response = await client.chatCompletion(
|
| 270 |
+
{
|
| 271 |
+
model: selectedModel.value,
|
| 272 |
+
provider: selectedProvider.id as any,
|
| 273 |
+
messages: [
|
| 274 |
+
{
|
| 275 |
+
role: "system",
|
| 276 |
+
content: FOLLOW_UP_SYSTEM_PROMPT,
|
| 277 |
+
},
|
| 278 |
+
{
|
| 279 |
+
role: "user",
|
| 280 |
+
content: previousPrompts
|
| 281 |
+
? `Also here are the previous prompts:\n\n${previousPrompts.map((p: string) => `- ${p}`).join("\n")}`
|
| 282 |
+
: "You are modifying the HTML file based on the user's request.",
|
| 283 |
+
},
|
| 284 |
+
{
|
| 285 |
+
role: "assistant",
|
| 286 |
+
|
| 287 |
+
content: `${
|
| 288 |
+
selectedElementHtml
|
| 289 |
+
? `\n\nYou have to update ONLY the following element, NOTHING ELSE: \n\n\`\`\`html\n${selectedElementHtml}\n\`\`\``
|
| 290 |
+
: ""
|
| 291 |
+
}. Current pages: ${pages?.map((p: Page) => `- ${p.path} \n${p.html}`).join("\n")}. ${files?.length > 0 ? `Current images: ${files?.map((f: string) => `- ${f}`).join("\n")}.` : ""}`,
|
| 292 |
+
},
|
| 293 |
+
{
|
| 294 |
+
role: "user",
|
| 295 |
+
content: prompt,
|
| 296 |
+
},
|
| 297 |
+
],
|
| 298 |
+
...(selectedProvider.id !== "sambanova"
|
| 299 |
+
? {
|
| 300 |
+
max_tokens: selectedProvider.max_tokens,
|
| 301 |
+
}
|
| 302 |
+
: {}),
|
| 303 |
+
},
|
| 304 |
+
billTo ? { billTo } : {}
|
| 305 |
+
);
|
| 306 |
+
|
| 307 |
+
const chunk = response.choices[0]?.message?.content;
|
| 308 |
+
if (!chunk) {
|
| 309 |
+
return NextResponse.json(
|
| 310 |
+
{ ok: false, message: "No content returned from the model" },
|
| 311 |
+
{ status: 400 }
|
| 312 |
+
);
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
if (chunk) {
|
| 316 |
+
const updatedLines: number[][] = [];
|
| 317 |
+
let newHtml = "";
|
| 318 |
+
const updatedPages = [...(pages || [])];
|
| 319 |
+
|
| 320 |
+
const updatePageRegex = new RegExp(`${UPDATE_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^\\s]+)\\s*${UPDATE_PAGE_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([\\s\\S]*?)(?=${UPDATE_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|${NEW_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|$)`, 'g');
|
| 321 |
+
let updatePageMatch;
|
| 322 |
+
|
| 323 |
+
while ((updatePageMatch = updatePageRegex.exec(chunk)) !== null) {
|
| 324 |
+
const [, pagePath, pageContent] = updatePageMatch;
|
| 325 |
+
|
| 326 |
+
const pageIndex = updatedPages.findIndex(p => p.path === pagePath);
|
| 327 |
+
if (pageIndex !== -1) {
|
| 328 |
+
let pageHtml = updatedPages[pageIndex].html;
|
| 329 |
+
|
| 330 |
+
let processedContent = pageContent;
|
| 331 |
+
const htmlMatch = pageContent.match(/```html\s*([\s\S]*?)\s*```/);
|
| 332 |
+
if (htmlMatch) {
|
| 333 |
+
processedContent = htmlMatch[1];
|
| 334 |
+
}
|
| 335 |
+
let position = 0;
|
| 336 |
+
let moreBlocks = true;
|
| 337 |
+
|
| 338 |
+
while (moreBlocks) {
|
| 339 |
+
const searchStartIndex = processedContent.indexOf(SEARCH_START, position);
|
| 340 |
+
if (searchStartIndex === -1) {
|
| 341 |
+
moreBlocks = false;
|
| 342 |
+
continue;
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
const dividerIndex = processedContent.indexOf(DIVIDER, searchStartIndex);
|
| 346 |
+
if (dividerIndex === -1) {
|
| 347 |
+
moreBlocks = false;
|
| 348 |
+
continue;
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
const replaceEndIndex = processedContent.indexOf(REPLACE_END, dividerIndex);
|
| 352 |
+
if (replaceEndIndex === -1) {
|
| 353 |
+
moreBlocks = false;
|
| 354 |
+
continue;
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
const searchBlock = processedContent.substring(
|
| 358 |
+
searchStartIndex + SEARCH_START.length,
|
| 359 |
+
dividerIndex
|
| 360 |
+
);
|
| 361 |
+
const replaceBlock = processedContent.substring(
|
| 362 |
+
dividerIndex + DIVIDER.length,
|
| 363 |
+
replaceEndIndex
|
| 364 |
+
);
|
| 365 |
+
|
| 366 |
+
if (searchBlock.trim() === "") {
|
| 367 |
+
pageHtml = `${replaceBlock}\n${pageHtml}`;
|
| 368 |
+
updatedLines.push([1, replaceBlock.split("\n").length]);
|
| 369 |
+
} else {
|
| 370 |
+
const blockPosition = pageHtml.indexOf(searchBlock);
|
| 371 |
+
if (blockPosition !== -1) {
|
| 372 |
+
const beforeText = pageHtml.substring(0, blockPosition);
|
| 373 |
+
const startLineNumber = beforeText.split("\n").length;
|
| 374 |
+
const replaceLines = replaceBlock.split("\n").length;
|
| 375 |
+
const endLineNumber = startLineNumber + replaceLines - 1;
|
| 376 |
+
|
| 377 |
+
updatedLines.push([startLineNumber, endLineNumber]);
|
| 378 |
+
pageHtml = pageHtml.replace(searchBlock, replaceBlock);
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
position = replaceEndIndex + REPLACE_END.length;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
updatedPages[pageIndex].html = pageHtml;
|
| 386 |
+
|
| 387 |
+
if (pagePath === '/' || pagePath === '/index' || pagePath === 'index') {
|
| 388 |
+
newHtml = pageHtml;
|
| 389 |
+
}
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
const newPageRegex = new RegExp(`${NEW_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^\\s]+)\\s*${NEW_PAGE_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([\\s\\S]*?)(?=${UPDATE_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|${NEW_PAGE_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}|$)`, 'g');
|
| 394 |
+
let newPageMatch;
|
| 395 |
+
|
| 396 |
+
while ((newPageMatch = newPageRegex.exec(chunk)) !== null) {
|
| 397 |
+
const [, pagePath, pageContent] = newPageMatch;
|
| 398 |
+
|
| 399 |
+
let pageHtml = pageContent;
|
| 400 |
+
const htmlMatch = pageContent.match(/```html\s*([\s\S]*?)\s*```/);
|
| 401 |
+
if (htmlMatch) {
|
| 402 |
+
pageHtml = htmlMatch[1];
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
const existingPageIndex = updatedPages.findIndex(p => p.path === pagePath);
|
| 406 |
+
|
| 407 |
+
if (existingPageIndex !== -1) {
|
| 408 |
+
updatedPages[existingPageIndex] = {
|
| 409 |
+
path: pagePath,
|
| 410 |
+
html: pageHtml.trim()
|
| 411 |
+
};
|
| 412 |
+
} else {
|
| 413 |
+
updatedPages.push({
|
| 414 |
+
path: pagePath,
|
| 415 |
+
html: pageHtml.trim()
|
| 416 |
+
});
|
| 417 |
+
}
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
if (updatedPages.length === pages?.length && !chunk.includes(UPDATE_PAGE_START)) {
|
| 421 |
+
let position = 0;
|
| 422 |
+
let moreBlocks = true;
|
| 423 |
+
|
| 424 |
+
while (moreBlocks) {
|
| 425 |
+
const searchStartIndex = chunk.indexOf(SEARCH_START, position);
|
| 426 |
+
if (searchStartIndex === -1) {
|
| 427 |
+
moreBlocks = false;
|
| 428 |
+
continue;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
const dividerIndex = chunk.indexOf(DIVIDER, searchStartIndex);
|
| 432 |
+
if (dividerIndex === -1) {
|
| 433 |
+
moreBlocks = false;
|
| 434 |
+
continue;
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
const replaceEndIndex = chunk.indexOf(REPLACE_END, dividerIndex);
|
| 438 |
+
if (replaceEndIndex === -1) {
|
| 439 |
+
moreBlocks = false;
|
| 440 |
+
continue;
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
const searchBlock = chunk.substring(
|
| 444 |
+
searchStartIndex + SEARCH_START.length,
|
| 445 |
+
dividerIndex
|
| 446 |
+
);
|
| 447 |
+
const replaceBlock = chunk.substring(
|
| 448 |
+
dividerIndex + DIVIDER.length,
|
| 449 |
+
replaceEndIndex
|
| 450 |
+
);
|
| 451 |
+
|
| 452 |
+
if (searchBlock.trim() === "") {
|
| 453 |
+
newHtml = `${replaceBlock}\n${newHtml}`;
|
| 454 |
+
updatedLines.push([1, replaceBlock.split("\n").length]);
|
| 455 |
+
} else {
|
| 456 |
+
const blockPosition = newHtml.indexOf(searchBlock);
|
| 457 |
+
if (blockPosition !== -1) {
|
| 458 |
+
const beforeText = newHtml.substring(0, blockPosition);
|
| 459 |
+
const startLineNumber = beforeText.split("\n").length;
|
| 460 |
+
const replaceLines = replaceBlock.split("\n").length;
|
| 461 |
+
const endLineNumber = startLineNumber + replaceLines - 1;
|
| 462 |
+
|
| 463 |
+
updatedLines.push([startLineNumber, endLineNumber]);
|
| 464 |
+
newHtml = newHtml.replace(searchBlock, replaceBlock);
|
| 465 |
+
}
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
position = replaceEndIndex + REPLACE_END.length;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
// Update the main HTML if it's the index page
|
| 472 |
+
const mainPageIndex = updatedPages.findIndex(p => p.path === '/' || p.path === '/index' || p.path === 'index');
|
| 473 |
+
if (mainPageIndex !== -1) {
|
| 474 |
+
updatedPages[mainPageIndex].html = newHtml;
|
| 475 |
+
}
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
return NextResponse.json({
|
| 479 |
+
ok: true,
|
| 480 |
+
updatedLines,
|
| 481 |
+
pages: updatedPages,
|
| 482 |
+
});
|
| 483 |
+
} else {
|
| 484 |
+
return NextResponse.json(
|
| 485 |
+
{ ok: false, message: "No content returned from the model" },
|
| 486 |
+
{ status: 400 }
|
| 487 |
+
);
|
| 488 |
+
}
|
| 489 |
+
} catch (error: any) {
|
| 490 |
+
if (error.message?.includes("exceeded your monthly included credits")) {
|
| 491 |
+
return NextResponse.json(
|
| 492 |
+
{
|
| 493 |
+
ok: false,
|
| 494 |
+
openProModal: true,
|
| 495 |
+
message: error.message,
|
| 496 |
+
},
|
| 497 |
+
{ status: 402 }
|
| 498 |
+
);
|
| 499 |
+
}
|
| 500 |
+
return NextResponse.json(
|
| 501 |
+
{
|
| 502 |
+
ok: false,
|
| 503 |
+
openSelectProvider: true,
|
| 504 |
+
message:
|
| 505 |
+
error.message || "An error occurred while processing your request.",
|
| 506 |
+
},
|
| 507 |
+
{ status: 500 }
|
| 508 |
+
);
|
| 509 |
+
}
|
| 510 |
+
}
|
app/api/ask/route.ts
DELETED
|
@@ -1,183 +0,0 @@
|
|
| 1 |
-
import { NextResponse } from "next/server";
|
| 2 |
-
import { InferenceClient } from "@huggingface/inference";
|
| 3 |
-
|
| 4 |
-
import { FOLLOW_UP_SYSTEM_PROMPT, INITIAL_SYSTEM_PROMPT } from "@/lib/prompts";
|
| 5 |
-
import { auth } from "@/lib/auth";
|
| 6 |
-
import { File, Message } from "@/lib/type";
|
| 7 |
-
import { DEFAULT_MODEL, MODELS } from "@/lib/providers";
|
| 8 |
-
|
| 9 |
-
export async function POST(request: Request) {
|
| 10 |
-
const session = await auth();
|
| 11 |
-
if (!session) {
|
| 12 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 13 |
-
}
|
| 14 |
-
const token = session.accessToken;
|
| 15 |
-
|
| 16 |
-
const body = await request.json();
|
| 17 |
-
const {
|
| 18 |
-
prompt,
|
| 19 |
-
previousMessages = [],
|
| 20 |
-
files = [],
|
| 21 |
-
provider,
|
| 22 |
-
model,
|
| 23 |
-
redesignMd,
|
| 24 |
-
medias,
|
| 25 |
-
} = body;
|
| 26 |
-
|
| 27 |
-
if (!prompt) {
|
| 28 |
-
return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
|
| 29 |
-
}
|
| 30 |
-
if (!model || !MODELS.find((m: (typeof MODELS)[0]) => m.value === model)) {
|
| 31 |
-
return NextResponse.json({ error: "Model is required" }, { status: 400 });
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
const client = new InferenceClient(token);
|
| 35 |
-
|
| 36 |
-
try {
|
| 37 |
-
const encoder = new TextEncoder();
|
| 38 |
-
const stream = new TransformStream();
|
| 39 |
-
const writer = stream.writable.getWriter();
|
| 40 |
-
|
| 41 |
-
const response = new NextResponse(stream.readable, {
|
| 42 |
-
headers: {
|
| 43 |
-
"Content-Type": "text/plain; charset=utf-8",
|
| 44 |
-
"Cache-Control": "no-cache",
|
| 45 |
-
Connection: "keep-alive",
|
| 46 |
-
},
|
| 47 |
-
});
|
| 48 |
-
(async () => {
|
| 49 |
-
let hasRetried = false;
|
| 50 |
-
let currentModel = model;
|
| 51 |
-
|
| 52 |
-
const tryGeneration = async (): Promise<void> => {
|
| 53 |
-
try {
|
| 54 |
-
const chatCompletion = client.chatCompletionStream({
|
| 55 |
-
model: currentModel + (provider !== "auto" ? `:${provider}` : ""),
|
| 56 |
-
messages: [
|
| 57 |
-
{
|
| 58 |
-
role: "system",
|
| 59 |
-
content:
|
| 60 |
-
files.length > 0
|
| 61 |
-
? FOLLOW_UP_SYSTEM_PROMPT
|
| 62 |
-
: INITIAL_SYSTEM_PROMPT,
|
| 63 |
-
},
|
| 64 |
-
...previousMessages.map((message: Message) => ({
|
| 65 |
-
role: message.role,
|
| 66 |
-
content: message.content,
|
| 67 |
-
})),
|
| 68 |
-
...(files?.length > 0
|
| 69 |
-
? [
|
| 70 |
-
{
|
| 71 |
-
role: "user",
|
| 72 |
-
content: `Here are the files that the user has provider:${files
|
| 73 |
-
.map(
|
| 74 |
-
(file: File) =>
|
| 75 |
-
`File: ${file.path}\nContent: ${file.content}`
|
| 76 |
-
)
|
| 77 |
-
.join("\n")}\n\n${prompt}`,
|
| 78 |
-
},
|
| 79 |
-
]
|
| 80 |
-
: []),
|
| 81 |
-
{
|
| 82 |
-
role: "user",
|
| 83 |
-
content: `${
|
| 84 |
-
redesignMd?.url &&
|
| 85 |
-
`Redesign the following website ${redesignMd.url}, try to use the same images and content, but you can still improve it if needed. Do the best version possibile. Here is the markdown:\n ${redesignMd.md} \n\n`
|
| 86 |
-
}${prompt} ${
|
| 87 |
-
medias && medias.length > 0
|
| 88 |
-
? `\nHere is the list of my media files: ${medias.join(
|
| 89 |
-
", "
|
| 90 |
-
)}\n`
|
| 91 |
-
: ""
|
| 92 |
-
}`,
|
| 93 |
-
}
|
| 94 |
-
],
|
| 95 |
-
stream: true,
|
| 96 |
-
max_tokens: 16_000,
|
| 97 |
-
});
|
| 98 |
-
while (true) {
|
| 99 |
-
const { done, value } = await chatCompletion.next();
|
| 100 |
-
if (done) {
|
| 101 |
-
break;
|
| 102 |
-
}
|
| 103 |
-
|
| 104 |
-
const chunk = value.choices[0]?.delta?.content;
|
| 105 |
-
if (chunk) {
|
| 106 |
-
await writer.write(encoder.encode(chunk));
|
| 107 |
-
}
|
| 108 |
-
}
|
| 109 |
-
|
| 110 |
-
await writer.close();
|
| 111 |
-
} catch (error) {
|
| 112 |
-
const errorMessage =
|
| 113 |
-
error instanceof Error
|
| 114 |
-
? error.message
|
| 115 |
-
: "An error occurred while processing your request";
|
| 116 |
-
|
| 117 |
-
if (
|
| 118 |
-
!hasRetried &&
|
| 119 |
-
errorMessage?.includes(
|
| 120 |
-
"Failed to perform inference: Model not found"
|
| 121 |
-
)
|
| 122 |
-
) {
|
| 123 |
-
hasRetried = true;
|
| 124 |
-
if (model === DEFAULT_MODEL) {
|
| 125 |
-
const availableFallbackModels = MODELS.filter(
|
| 126 |
-
(m) => m.value !== model
|
| 127 |
-
);
|
| 128 |
-
const randomIndex = Math.floor(
|
| 129 |
-
Math.random() * availableFallbackModels.length
|
| 130 |
-
);
|
| 131 |
-
currentModel = availableFallbackModels[randomIndex];
|
| 132 |
-
} else {
|
| 133 |
-
currentModel = DEFAULT_MODEL;
|
| 134 |
-
}
|
| 135 |
-
const switchMessage = `\n\n_Note: The selected model was not available. Switched to \`${currentModel}\`._\n\n`;
|
| 136 |
-
await writer.write(encoder.encode(switchMessage));
|
| 137 |
-
|
| 138 |
-
return tryGeneration();
|
| 139 |
-
}
|
| 140 |
-
|
| 141 |
-
try {
|
| 142 |
-
let errorPayload = "";
|
| 143 |
-
if (
|
| 144 |
-
errorMessage?.includes("exceeded your monthly included credits") ||
|
| 145 |
-
errorMessage?.includes("reached the free monthly usage limit")
|
| 146 |
-
) {
|
| 147 |
-
errorPayload = JSON.stringify({
|
| 148 |
-
messageError: errorMessage,
|
| 149 |
-
showProMessage: true,
|
| 150 |
-
isError: true,
|
| 151 |
-
});
|
| 152 |
-
} else {
|
| 153 |
-
errorPayload = JSON.stringify({
|
| 154 |
-
messageError: errorMessage,
|
| 155 |
-
isError: true,
|
| 156 |
-
});
|
| 157 |
-
}
|
| 158 |
-
await writer.write(encoder.encode(`\n\n__ERROR__:${errorPayload}`));
|
| 159 |
-
await writer.close();
|
| 160 |
-
} catch (closeError) {
|
| 161 |
-
console.error("Failed to send error message:", closeError);
|
| 162 |
-
try {
|
| 163 |
-
await writer.abort(error);
|
| 164 |
-
} catch (abortError) {
|
| 165 |
-
console.error("Failed to abort writer:", abortError);
|
| 166 |
-
}
|
| 167 |
-
}
|
| 168 |
-
}
|
| 169 |
-
};
|
| 170 |
-
|
| 171 |
-
await tryGeneration();
|
| 172 |
-
})();
|
| 173 |
-
|
| 174 |
-
return response;
|
| 175 |
-
} catch (error) {
|
| 176 |
-
return NextResponse.json(
|
| 177 |
-
{
|
| 178 |
-
error: error instanceof Error ? error.message : "Internal Server Error",
|
| 179 |
-
},
|
| 180 |
-
{ status: 500 }
|
| 181 |
-
);
|
| 182 |
-
}
|
| 183 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/auth/[...nextauth]/route.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
import NextAuth from "next-auth";
|
| 2 |
-
import { authOptions } from "@/lib/auth";
|
| 3 |
-
|
| 4 |
-
const handler = NextAuth(authOptions);
|
| 5 |
-
|
| 6 |
-
export { handler as GET, handler as POST };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/auth/route.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
|
| 3 |
+
export async function POST(req: NextRequest) {
|
| 4 |
+
const body = await req.json();
|
| 5 |
+
const { code } = body;
|
| 6 |
+
|
| 7 |
+
if (!code) {
|
| 8 |
+
return NextResponse.json(
|
| 9 |
+
{ error: "Code is required" },
|
| 10 |
+
{
|
| 11 |
+
status: 400,
|
| 12 |
+
headers: {
|
| 13 |
+
"Content-Type": "application/json",
|
| 14 |
+
},
|
| 15 |
+
}
|
| 16 |
+
);
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
const Authorization = `Basic ${Buffer.from(
|
| 20 |
+
`${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`
|
| 21 |
+
).toString("base64")}`;
|
| 22 |
+
|
| 23 |
+
const host =
|
| 24 |
+
req.headers.get("host") ?? req.headers.get("origin") ?? "localhost:3000";
|
| 25 |
+
|
| 26 |
+
const url = host.includes("/spaces/enzostvs")
|
| 27 |
+
? "enzostvs-deepsite.hf.space"
|
| 28 |
+
: host;
|
| 29 |
+
const redirect_uri =
|
| 30 |
+
`${host.includes("localhost") ? "http://" : "https://"}` +
|
| 31 |
+
url +
|
| 32 |
+
"/auth/callback";
|
| 33 |
+
const request_auth = await fetch("https://huggingface.co/oauth/token", {
|
| 34 |
+
method: "POST",
|
| 35 |
+
headers: {
|
| 36 |
+
"Content-Type": "application/x-www-form-urlencoded",
|
| 37 |
+
Authorization,
|
| 38 |
+
},
|
| 39 |
+
body: new URLSearchParams({
|
| 40 |
+
grant_type: "authorization_code",
|
| 41 |
+
code,
|
| 42 |
+
redirect_uri,
|
| 43 |
+
}),
|
| 44 |
+
});
|
| 45 |
+
|
| 46 |
+
const response = await request_auth.json();
|
| 47 |
+
if (!response.access_token) {
|
| 48 |
+
return NextResponse.json(
|
| 49 |
+
{ error: "Failed to retrieve access token" },
|
| 50 |
+
{
|
| 51 |
+
status: 400,
|
| 52 |
+
headers: {
|
| 53 |
+
"Content-Type": "application/json",
|
| 54 |
+
},
|
| 55 |
+
}
|
| 56 |
+
);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 60 |
+
headers: {
|
| 61 |
+
Authorization: `Bearer ${response.access_token}`,
|
| 62 |
+
},
|
| 63 |
+
});
|
| 64 |
+
|
| 65 |
+
if (!userResponse.ok) {
|
| 66 |
+
return NextResponse.json(
|
| 67 |
+
{ user: null, errCode: userResponse.status },
|
| 68 |
+
{ status: userResponse.status }
|
| 69 |
+
);
|
| 70 |
+
}
|
| 71 |
+
const user = await userResponse.json();
|
| 72 |
+
|
| 73 |
+
return NextResponse.json(
|
| 74 |
+
{
|
| 75 |
+
access_token: response.access_token,
|
| 76 |
+
expires_in: response.expires_in,
|
| 77 |
+
user,
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
status: 200,
|
| 81 |
+
headers: {
|
| 82 |
+
"Content-Type": "application/json",
|
| 83 |
+
},
|
| 84 |
+
}
|
| 85 |
+
);
|
| 86 |
+
}
|
app/api/healthcheck/route.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
| 1 |
-
import { NextResponse } from "next/server";
|
| 2 |
-
|
| 3 |
-
export async function GET() {
|
| 4 |
-
return NextResponse.json({ status: "ok" }, { status: 200 });
|
| 5 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/me/projects/[namespace]/[repoId]/images/route.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { RepoDesignation, uploadFiles } from "@huggingface/hub";
|
| 3 |
+
|
| 4 |
+
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
+
import Project from "@/models/Project";
|
| 6 |
+
import dbConnect from "@/lib/mongodb";
|
| 7 |
+
|
| 8 |
+
// No longer need the ImageUpload interface since we're handling FormData with File objects
|
| 9 |
+
|
| 10 |
+
export async function POST(
|
| 11 |
+
req: NextRequest,
|
| 12 |
+
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 13 |
+
) {
|
| 14 |
+
try {
|
| 15 |
+
const user = await isAuthenticated();
|
| 16 |
+
|
| 17 |
+
if (user instanceof NextResponse || !user) {
|
| 18 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
await dbConnect();
|
| 22 |
+
const param = await params;
|
| 23 |
+
const { namespace, repoId } = param;
|
| 24 |
+
|
| 25 |
+
const project = await Project.findOne({
|
| 26 |
+
user_id: user.id,
|
| 27 |
+
space_id: `${namespace}/${repoId}`,
|
| 28 |
+
}).lean();
|
| 29 |
+
|
| 30 |
+
if (!project) {
|
| 31 |
+
return NextResponse.json(
|
| 32 |
+
{
|
| 33 |
+
ok: false,
|
| 34 |
+
error: "Project not found",
|
| 35 |
+
},
|
| 36 |
+
{ status: 404 }
|
| 37 |
+
);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
// Parse the FormData to get the images
|
| 41 |
+
const formData = await req.formData();
|
| 42 |
+
const imageFiles = formData.getAll("images") as File[];
|
| 43 |
+
|
| 44 |
+
if (!imageFiles || imageFiles.length === 0) {
|
| 45 |
+
return NextResponse.json(
|
| 46 |
+
{
|
| 47 |
+
ok: false,
|
| 48 |
+
error: "At least one image file is required under the 'images' key",
|
| 49 |
+
},
|
| 50 |
+
{ status: 400 }
|
| 51 |
+
);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
const files: File[] = [];
|
| 55 |
+
for (const file of imageFiles) {
|
| 56 |
+
if (!(file instanceof File)) {
|
| 57 |
+
return NextResponse.json(
|
| 58 |
+
{
|
| 59 |
+
ok: false,
|
| 60 |
+
error: "Invalid file format - all items under 'images' key must be files",
|
| 61 |
+
},
|
| 62 |
+
{ status: 400 }
|
| 63 |
+
);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
if (!file.type.startsWith('image/')) {
|
| 67 |
+
return NextResponse.json(
|
| 68 |
+
{
|
| 69 |
+
ok: false,
|
| 70 |
+
error: `File ${file.name} is not an image`,
|
| 71 |
+
},
|
| 72 |
+
{ status: 400 }
|
| 73 |
+
);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Create File object with images/ folder prefix
|
| 77 |
+
const fileName = `images/${file.name}`;
|
| 78 |
+
const processedFile = new File([file], fileName, { type: file.type });
|
| 79 |
+
files.push(processedFile);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
// Upload files to HuggingFace space
|
| 83 |
+
const repo: RepoDesignation = {
|
| 84 |
+
type: "space",
|
| 85 |
+
name: `${namespace}/${repoId}`,
|
| 86 |
+
};
|
| 87 |
+
|
| 88 |
+
await uploadFiles({
|
| 89 |
+
repo,
|
| 90 |
+
files,
|
| 91 |
+
accessToken: user.token as string,
|
| 92 |
+
commitTitle: `Upload ${files.length} image(s)`,
|
| 93 |
+
});
|
| 94 |
+
|
| 95 |
+
return NextResponse.json({
|
| 96 |
+
ok: true,
|
| 97 |
+
message: `Successfully uploaded ${files.length} image(s) to ${namespace}/${repoId}/images/`,
|
| 98 |
+
uploadedFiles: files.map((file) => `https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${file.name}`),
|
| 99 |
+
}, { status: 200 });
|
| 100 |
+
|
| 101 |
+
} catch (error) {
|
| 102 |
+
console.error('Error uploading images:', error);
|
| 103 |
+
return NextResponse.json(
|
| 104 |
+
{
|
| 105 |
+
ok: false,
|
| 106 |
+
error: "Failed to upload images",
|
| 107 |
+
},
|
| 108 |
+
{ status: 500 }
|
| 109 |
+
);
|
| 110 |
+
}
|
| 111 |
+
}
|
app/api/me/projects/[namespace]/[repoId]/route.ts
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { RepoDesignation, spaceInfo, uploadFiles, listFiles } from "@huggingface/hub";
|
| 3 |
+
|
| 4 |
+
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
+
import Project from "@/models/Project";
|
| 6 |
+
import dbConnect from "@/lib/mongodb";
|
| 7 |
+
import { Page } from "@/types";
|
| 8 |
+
|
| 9 |
+
export async function GET(
|
| 10 |
+
req: NextRequest,
|
| 11 |
+
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 12 |
+
) {
|
| 13 |
+
const user = await isAuthenticated();
|
| 14 |
+
|
| 15 |
+
if (user instanceof NextResponse || !user) {
|
| 16 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
await dbConnect();
|
| 20 |
+
const param = await params;
|
| 21 |
+
const { namespace, repoId } = param;
|
| 22 |
+
|
| 23 |
+
const project = await Project.findOne({
|
| 24 |
+
user_id: user.id,
|
| 25 |
+
space_id: `${namespace}/${repoId}`,
|
| 26 |
+
}).lean();
|
| 27 |
+
if (!project) {
|
| 28 |
+
return NextResponse.json(
|
| 29 |
+
{
|
| 30 |
+
ok: false,
|
| 31 |
+
error: "Project not found",
|
| 32 |
+
},
|
| 33 |
+
{ status: 404 }
|
| 34 |
+
);
|
| 35 |
+
}
|
| 36 |
+
try {
|
| 37 |
+
const space = await spaceInfo({
|
| 38 |
+
name: namespace + "/" + repoId,
|
| 39 |
+
accessToken: user.token as string,
|
| 40 |
+
additionalFields: ["author"],
|
| 41 |
+
});
|
| 42 |
+
|
| 43 |
+
if (!space || space.sdk !== "static") {
|
| 44 |
+
return NextResponse.json(
|
| 45 |
+
{
|
| 46 |
+
ok: false,
|
| 47 |
+
error: "Space is not a static space",
|
| 48 |
+
},
|
| 49 |
+
{ status: 404 }
|
| 50 |
+
);
|
| 51 |
+
}
|
| 52 |
+
if (space.author !== user.name) {
|
| 53 |
+
return NextResponse.json(
|
| 54 |
+
{
|
| 55 |
+
ok: false,
|
| 56 |
+
error: "Space does not belong to the authenticated user",
|
| 57 |
+
},
|
| 58 |
+
{ status: 403 }
|
| 59 |
+
);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
const repo: RepoDesignation = {
|
| 63 |
+
type: "space",
|
| 64 |
+
name: `${namespace}/${repoId}`,
|
| 65 |
+
};
|
| 66 |
+
|
| 67 |
+
const htmlFiles: Page[] = [];
|
| 68 |
+
const images: string[] = [];
|
| 69 |
+
|
| 70 |
+
const allowedImagesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif"];
|
| 71 |
+
|
| 72 |
+
for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) {
|
| 73 |
+
if (fileInfo.path.endsWith(".html")) {
|
| 74 |
+
const res = await fetch(`https://huggingface.co/spaces/${namespace}/${repoId}/raw/main/${fileInfo.path}`);
|
| 75 |
+
if (res.ok) {
|
| 76 |
+
const html = await res.text();
|
| 77 |
+
if (fileInfo.path === "index.html") {
|
| 78 |
+
htmlFiles.unshift({
|
| 79 |
+
path: fileInfo.path,
|
| 80 |
+
html,
|
| 81 |
+
});
|
| 82 |
+
} else {
|
| 83 |
+
htmlFiles.push({
|
| 84 |
+
path: fileInfo.path,
|
| 85 |
+
html,
|
| 86 |
+
});
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
if (fileInfo.type === "directory" && fileInfo.path === "images") {
|
| 91 |
+
for await (const imageInfo of listFiles({repo, accessToken: user.token as string, path: fileInfo.path})) {
|
| 92 |
+
if (allowedImagesExtensions.includes(imageInfo.path.split(".").pop() || "")) {
|
| 93 |
+
images.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${imageInfo.path}`);
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
if (htmlFiles.length === 0) {
|
| 100 |
+
return NextResponse.json(
|
| 101 |
+
{
|
| 102 |
+
ok: false,
|
| 103 |
+
error: "No HTML files found",
|
| 104 |
+
},
|
| 105 |
+
{ status: 404 }
|
| 106 |
+
);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
return NextResponse.json(
|
| 110 |
+
{
|
| 111 |
+
project: {
|
| 112 |
+
...project,
|
| 113 |
+
pages: htmlFiles,
|
| 114 |
+
images,
|
| 115 |
+
},
|
| 116 |
+
ok: true,
|
| 117 |
+
},
|
| 118 |
+
{ status: 200 }
|
| 119 |
+
);
|
| 120 |
+
|
| 121 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 122 |
+
} catch (error: any) {
|
| 123 |
+
if (error.statusCode === 404) {
|
| 124 |
+
await Project.deleteOne({
|
| 125 |
+
user_id: user.id,
|
| 126 |
+
space_id: `${namespace}/${repoId}`,
|
| 127 |
+
});
|
| 128 |
+
return NextResponse.json(
|
| 129 |
+
{ error: "Space not found", ok: false },
|
| 130 |
+
{ status: 404 }
|
| 131 |
+
);
|
| 132 |
+
}
|
| 133 |
+
return NextResponse.json(
|
| 134 |
+
{ error: error.message, ok: false },
|
| 135 |
+
{ status: 500 }
|
| 136 |
+
);
|
| 137 |
+
}
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
export async function PUT(
|
| 141 |
+
req: NextRequest,
|
| 142 |
+
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 143 |
+
) {
|
| 144 |
+
const user = await isAuthenticated();
|
| 145 |
+
|
| 146 |
+
if (user instanceof NextResponse || !user) {
|
| 147 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
await dbConnect();
|
| 151 |
+
const param = await params;
|
| 152 |
+
const { namespace, repoId } = param;
|
| 153 |
+
const { pages, prompts } = await req.json();
|
| 154 |
+
|
| 155 |
+
const project = await Project.findOne({
|
| 156 |
+
user_id: user.id,
|
| 157 |
+
space_id: `${namespace}/${repoId}`,
|
| 158 |
+
}).lean();
|
| 159 |
+
if (!project) {
|
| 160 |
+
return NextResponse.json(
|
| 161 |
+
{
|
| 162 |
+
ok: false,
|
| 163 |
+
error: "Project not found",
|
| 164 |
+
},
|
| 165 |
+
{ status: 404 }
|
| 166 |
+
);
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
const repo: RepoDesignation = {
|
| 170 |
+
type: "space",
|
| 171 |
+
name: `${namespace}/${repoId}`,
|
| 172 |
+
};
|
| 173 |
+
|
| 174 |
+
const files: File[] = [];
|
| 175 |
+
const promptsFile = new File([prompts.join("\n")], "prompts.txt", {
|
| 176 |
+
type: "text/plain",
|
| 177 |
+
});
|
| 178 |
+
files.push(promptsFile);
|
| 179 |
+
pages.forEach((page: Page) => {
|
| 180 |
+
const file = new File([page.html], page.path, { type: "text/html" });
|
| 181 |
+
files.push(file);
|
| 182 |
+
});
|
| 183 |
+
await uploadFiles({
|
| 184 |
+
repo,
|
| 185 |
+
files,
|
| 186 |
+
accessToken: user.token as string,
|
| 187 |
+
commitTitle: `${prompts[prompts.length - 1]} - Follow Up Deployment`,
|
| 188 |
+
});
|
| 189 |
+
|
| 190 |
+
await Project.updateOne(
|
| 191 |
+
{ user_id: user.id, space_id: `${namespace}/${repoId}` },
|
| 192 |
+
{
|
| 193 |
+
$set: {
|
| 194 |
+
prompts: [
|
| 195 |
+
...prompts,
|
| 196 |
+
],
|
| 197 |
+
},
|
| 198 |
+
}
|
| 199 |
+
);
|
| 200 |
+
return NextResponse.json({ ok: true }, { status: 200 });
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
export async function POST(
|
| 204 |
+
req: NextRequest,
|
| 205 |
+
{ params }: { params: Promise<{ namespace: string; repoId: string }> }
|
| 206 |
+
) {
|
| 207 |
+
const user = await isAuthenticated();
|
| 208 |
+
|
| 209 |
+
if (user instanceof NextResponse || !user) {
|
| 210 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
await dbConnect();
|
| 214 |
+
const param = await params;
|
| 215 |
+
const { namespace, repoId } = param;
|
| 216 |
+
|
| 217 |
+
const space = await spaceInfo({
|
| 218 |
+
name: namespace + "/" + repoId,
|
| 219 |
+
accessToken: user.token as string,
|
| 220 |
+
additionalFields: ["author"],
|
| 221 |
+
});
|
| 222 |
+
|
| 223 |
+
if (!space || space.sdk !== "static") {
|
| 224 |
+
return NextResponse.json(
|
| 225 |
+
{
|
| 226 |
+
ok: false,
|
| 227 |
+
error: "Space is not a static space",
|
| 228 |
+
},
|
| 229 |
+
{ status: 404 }
|
| 230 |
+
);
|
| 231 |
+
}
|
| 232 |
+
if (space.author !== user.name) {
|
| 233 |
+
return NextResponse.json(
|
| 234 |
+
{
|
| 235 |
+
ok: false,
|
| 236 |
+
error: "Space does not belong to the authenticated user",
|
| 237 |
+
},
|
| 238 |
+
{ status: 403 }
|
| 239 |
+
);
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
const project = await Project.findOne({
|
| 243 |
+
user_id: user.id,
|
| 244 |
+
space_id: `${namespace}/${repoId}`,
|
| 245 |
+
}).lean();
|
| 246 |
+
if (project) {
|
| 247 |
+
// redirect to the project page if it already exists
|
| 248 |
+
return NextResponse.json(
|
| 249 |
+
{
|
| 250 |
+
ok: false,
|
| 251 |
+
error: "Project already exists",
|
| 252 |
+
redirect: `/projects/${namespace}/${repoId}`,
|
| 253 |
+
},
|
| 254 |
+
{ status: 400 }
|
| 255 |
+
);
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
const newProject = new Project({
|
| 259 |
+
user_id: user.id,
|
| 260 |
+
space_id: `${namespace}/${repoId}`,
|
| 261 |
+
prompts: [],
|
| 262 |
+
});
|
| 263 |
+
|
| 264 |
+
await newProject.save();
|
| 265 |
+
return NextResponse.json(
|
| 266 |
+
{
|
| 267 |
+
ok: true,
|
| 268 |
+
project: {
|
| 269 |
+
id: newProject._id,
|
| 270 |
+
space_id: newProject.space_id,
|
| 271 |
+
prompts: newProject.prompts,
|
| 272 |
+
},
|
| 273 |
+
},
|
| 274 |
+
{ status: 201 }
|
| 275 |
+
);
|
| 276 |
+
}
|
app/api/me/projects/route.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
import { createRepo, RepoDesignation, uploadFiles } from "@huggingface/hub";
|
| 3 |
+
|
| 4 |
+
import { isAuthenticated } from "@/lib/auth";
|
| 5 |
+
import Project from "@/models/Project";
|
| 6 |
+
import dbConnect from "@/lib/mongodb";
|
| 7 |
+
import { COLORS } from "@/lib/utils";
|
| 8 |
+
import { Page } from "@/types";
|
| 9 |
+
|
| 10 |
+
export async function GET() {
|
| 11 |
+
const user = await isAuthenticated();
|
| 12 |
+
|
| 13 |
+
if (user instanceof NextResponse || !user) {
|
| 14 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
await dbConnect();
|
| 18 |
+
|
| 19 |
+
const projects = await Project.find({
|
| 20 |
+
user_id: user?.id,
|
| 21 |
+
})
|
| 22 |
+
.sort({ _createdAt: -1 })
|
| 23 |
+
.limit(100)
|
| 24 |
+
.lean();
|
| 25 |
+
if (!projects) {
|
| 26 |
+
return NextResponse.json(
|
| 27 |
+
{
|
| 28 |
+
ok: false,
|
| 29 |
+
projects: [],
|
| 30 |
+
},
|
| 31 |
+
{ status: 404 }
|
| 32 |
+
);
|
| 33 |
+
}
|
| 34 |
+
return NextResponse.json(
|
| 35 |
+
{
|
| 36 |
+
ok: true,
|
| 37 |
+
projects,
|
| 38 |
+
},
|
| 39 |
+
{ status: 200 }
|
| 40 |
+
);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
export async function POST(request: NextRequest) {
|
| 44 |
+
const user = await isAuthenticated();
|
| 45 |
+
|
| 46 |
+
if (user instanceof NextResponse || !user) {
|
| 47 |
+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
const { title, pages, prompts } = await request.json();
|
| 51 |
+
|
| 52 |
+
if (!title || !pages || pages.length === 0) {
|
| 53 |
+
return NextResponse.json(
|
| 54 |
+
{ message: "Title and HTML content are required.", ok: false },
|
| 55 |
+
{ status: 400 }
|
| 56 |
+
);
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
await dbConnect();
|
| 60 |
+
|
| 61 |
+
try {
|
| 62 |
+
let readme = "";
|
| 63 |
+
|
| 64 |
+
const newTitle = title
|
| 65 |
+
.toLowerCase()
|
| 66 |
+
.replace(/[^a-z0-9]+/g, "-")
|
| 67 |
+
.split("-")
|
| 68 |
+
.filter(Boolean)
|
| 69 |
+
.join("-")
|
| 70 |
+
.slice(0, 96);
|
| 71 |
+
|
| 72 |
+
const repo: RepoDesignation = {
|
| 73 |
+
type: "space",
|
| 74 |
+
name: `${user.name}/${newTitle}`,
|
| 75 |
+
};
|
| 76 |
+
|
| 77 |
+
const { repoUrl } = await createRepo({
|
| 78 |
+
repo,
|
| 79 |
+
accessToken: user.token as string,
|
| 80 |
+
});
|
| 81 |
+
const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 82 |
+
const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 83 |
+
readme = `---
|
| 84 |
+
title: ${newTitle}
|
| 85 |
+
emoji: 🐳
|
| 86 |
+
colorFrom: ${colorFrom}
|
| 87 |
+
colorTo: ${colorTo}
|
| 88 |
+
sdk: static
|
| 89 |
+
pinned: false
|
| 90 |
+
tags:
|
| 91 |
+
- deepsite
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`;
|
| 95 |
+
|
| 96 |
+
const readmeFile = new File([readme], "README.md", {
|
| 97 |
+
type: "text/markdown",
|
| 98 |
+
});
|
| 99 |
+
const promptsFile = new File([prompts.join("\n")], "prompts.txt", {
|
| 100 |
+
type: "text/plain",
|
| 101 |
+
});
|
| 102 |
+
const files = [readmeFile, promptsFile];
|
| 103 |
+
pages.forEach((page: Page) => {
|
| 104 |
+
const file = new File([page.html], page.path, { type: "text/html" });
|
| 105 |
+
files.push(file);
|
| 106 |
+
});
|
| 107 |
+
await uploadFiles({
|
| 108 |
+
repo,
|
| 109 |
+
files,
|
| 110 |
+
accessToken: user.token as string,
|
| 111 |
+
commitTitle: `${prompts[prompts.length - 1]} - Initial Deployment`,
|
| 112 |
+
});
|
| 113 |
+
const path = repoUrl.split("/").slice(-2).join("/");
|
| 114 |
+
const project = await Project.create({
|
| 115 |
+
user_id: user.id,
|
| 116 |
+
space_id: path,
|
| 117 |
+
prompts,
|
| 118 |
+
});
|
| 119 |
+
return NextResponse.json({ project, path, ok: true }, { status: 201 });
|
| 120 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 121 |
+
} catch (err: any) {
|
| 122 |
+
return NextResponse.json(
|
| 123 |
+
{ error: err.message, ok: false },
|
| 124 |
+
{ status: 500 }
|
| 125 |
+
);
|
| 126 |
+
}
|
| 127 |
+
}
|
app/api/me/route.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { headers } from "next/headers";
|
| 2 |
+
import { NextResponse } from "next/server";
|
| 3 |
+
|
| 4 |
+
export async function GET() {
|
| 5 |
+
const authHeaders = await headers();
|
| 6 |
+
const token = authHeaders.get("Authorization");
|
| 7 |
+
if (!token) {
|
| 8 |
+
return NextResponse.json({ user: null, errCode: 401 }, { status: 401 });
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
const userResponse = await fetch("https://huggingface.co/api/whoami-v2", {
|
| 12 |
+
headers: {
|
| 13 |
+
Authorization: `${token}`,
|
| 14 |
+
},
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
if (!userResponse.ok) {
|
| 18 |
+
return NextResponse.json(
|
| 19 |
+
{ user: null, errCode: userResponse.status },
|
| 20 |
+
{ status: userResponse.status }
|
| 21 |
+
);
|
| 22 |
+
}
|
| 23 |
+
const user = await userResponse.json();
|
| 24 |
+
return NextResponse.json({ user, errCode: null }, { status: 200 });
|
| 25 |
+
}
|
app/api/projects/[repoId]/[commitId]/route.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { createBranch, RepoDesignation } from "@huggingface/hub";
|
| 3 |
-
import { format } from "date-fns";
|
| 4 |
-
import { NextResponse } from "next/server";
|
| 5 |
-
|
| 6 |
-
export async function POST(
|
| 7 |
-
request: Request,
|
| 8 |
-
{ params }: { params: Promise<{ repoId: string; commitId: string }> }
|
| 9 |
-
) {
|
| 10 |
-
const { repoId, commitId }: { repoId: string; commitId: string } =
|
| 11 |
-
await params;
|
| 12 |
-
const session = await auth();
|
| 13 |
-
if (!session) {
|
| 14 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 15 |
-
}
|
| 16 |
-
const token = session.accessToken;
|
| 17 |
-
|
| 18 |
-
const repo: RepoDesignation = {
|
| 19 |
-
type: "space",
|
| 20 |
-
name: session.user?.username + "/" + repoId,
|
| 21 |
-
};
|
| 22 |
-
|
| 23 |
-
const commitTitle = `🔖 ${format(new Date(), "dd/MM")} - ${format(
|
| 24 |
-
new Date(),
|
| 25 |
-
"HH:mm"
|
| 26 |
-
)} - Set commit ${commitId} as default.`;
|
| 27 |
-
|
| 28 |
-
await fetch(
|
| 29 |
-
`https://huggingface.co/api/spaces/${session.user?.username}/${repoId}/branch/main`,
|
| 30 |
-
{
|
| 31 |
-
method: "POST",
|
| 32 |
-
headers: {
|
| 33 |
-
Authorization: `Bearer ${token}`,
|
| 34 |
-
"Content-Type": "application/json",
|
| 35 |
-
},
|
| 36 |
-
body: JSON.stringify({
|
| 37 |
-
startingPoint: commitId,
|
| 38 |
-
overwrite: true,
|
| 39 |
-
}),
|
| 40 |
-
}
|
| 41 |
-
).catch((error) => {
|
| 42 |
-
return NextResponse.json(
|
| 43 |
-
{ error: error ?? "Failed to create branch" },
|
| 44 |
-
{ status: 500 }
|
| 45 |
-
);
|
| 46 |
-
});
|
| 47 |
-
|
| 48 |
-
return NextResponse.json({ success: true }, { status: 200 });
|
| 49 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/[repoId]/download/route.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { downloadFile, listFiles, RepoDesignation } from "@huggingface/hub";
|
| 3 |
-
import { NextResponse } from "next/server";
|
| 4 |
-
import JSZip from "jszip";
|
| 5 |
-
|
| 6 |
-
export async function GET(
|
| 7 |
-
request: Request,
|
| 8 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 9 |
-
) {
|
| 10 |
-
const { repoId }: { repoId: string } = await params;
|
| 11 |
-
const session = await auth();
|
| 12 |
-
if (!session) {
|
| 13 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 14 |
-
}
|
| 15 |
-
const token = session.accessToken;
|
| 16 |
-
const repo: RepoDesignation = {
|
| 17 |
-
type: "space",
|
| 18 |
-
name: session.user?.username + "/" + repoId,
|
| 19 |
-
};
|
| 20 |
-
|
| 21 |
-
try {
|
| 22 |
-
const zip = new JSZip();
|
| 23 |
-
for await (const fileInfo of listFiles({
|
| 24 |
-
repo,
|
| 25 |
-
accessToken: token as string,
|
| 26 |
-
recursive: true,
|
| 27 |
-
})) {
|
| 28 |
-
if (fileInfo.type === "directory" || fileInfo.path.startsWith(".")) {
|
| 29 |
-
continue;
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
try {
|
| 33 |
-
const blob = await downloadFile({
|
| 34 |
-
repo,
|
| 35 |
-
accessToken: token as string,
|
| 36 |
-
path: fileInfo.path,
|
| 37 |
-
raw: true
|
| 38 |
-
}).catch((error) => {
|
| 39 |
-
return null;
|
| 40 |
-
});
|
| 41 |
-
if (!blob) {
|
| 42 |
-
continue;
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
if (blob) {
|
| 46 |
-
const arrayBuffer = await blob.arrayBuffer();
|
| 47 |
-
zip.file(fileInfo.path, arrayBuffer);
|
| 48 |
-
}
|
| 49 |
-
} catch (error) {
|
| 50 |
-
console.error(`Error downloading file ${fileInfo.path}:`, error);
|
| 51 |
-
}
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
const zipBlob = await zip.generateAsync({
|
| 55 |
-
type: "blob",
|
| 56 |
-
compression: "DEFLATE",
|
| 57 |
-
compressionOptions: {
|
| 58 |
-
level: 6
|
| 59 |
-
}
|
| 60 |
-
});
|
| 61 |
-
|
| 62 |
-
const projectName = `${session.user?.username}-${repoId}`.replace(/[^a-zA-Z0-9-_]/g, '_');
|
| 63 |
-
const filename = `${projectName}.zip`;
|
| 64 |
-
|
| 65 |
-
return new NextResponse(zipBlob, {
|
| 66 |
-
headers: {
|
| 67 |
-
"Content-Type": "application/zip",
|
| 68 |
-
"Content-Disposition": `attachment; filename="${filename}"`,
|
| 69 |
-
"Content-Length": zipBlob.size.toString(),
|
| 70 |
-
},
|
| 71 |
-
});
|
| 72 |
-
} catch (error) {
|
| 73 |
-
console.error("Error downloading project:", error);
|
| 74 |
-
return NextResponse.json({ error: "Failed to download project" }, { status: 500 });
|
| 75 |
-
}
|
| 76 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/[repoId]/medias/route.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { RepoDesignation, uploadFiles } from "@huggingface/hub";
|
| 3 |
-
import { NextResponse } from "next/server";
|
| 4 |
-
|
| 5 |
-
export async function POST(
|
| 6 |
-
request: Request,
|
| 7 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 8 |
-
) {
|
| 9 |
-
const { repoId }: { repoId: string } = await params;
|
| 10 |
-
const session = await auth();
|
| 11 |
-
if (!session) {
|
| 12 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 13 |
-
}
|
| 14 |
-
const token = session.accessToken;
|
| 15 |
-
|
| 16 |
-
const repo: RepoDesignation = {
|
| 17 |
-
type: "space",
|
| 18 |
-
name: session.user?.username + "/" + repoId,
|
| 19 |
-
};
|
| 20 |
-
|
| 21 |
-
const formData = await request.formData();
|
| 22 |
-
const newMedias = formData.getAll("images") as File[];
|
| 23 |
-
|
| 24 |
-
const filesToUpload: File[] = [];
|
| 25 |
-
|
| 26 |
-
if (!newMedias || newMedias.length === 0) {
|
| 27 |
-
return NextResponse.json(
|
| 28 |
-
{
|
| 29 |
-
ok: false,
|
| 30 |
-
error: "At least one media file is required under the 'images' key",
|
| 31 |
-
},
|
| 32 |
-
{ status: 400 }
|
| 33 |
-
);
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
try {
|
| 37 |
-
for (const media of newMedias) {
|
| 38 |
-
const isImage = media.type.startsWith("image/");
|
| 39 |
-
const isVideo = media.type.startsWith("video/");
|
| 40 |
-
const isAudio = media.type.startsWith("audio/");
|
| 41 |
-
|
| 42 |
-
const folderPath = isImage
|
| 43 |
-
? "images/"
|
| 44 |
-
: isVideo
|
| 45 |
-
? "videos/"
|
| 46 |
-
: isAudio
|
| 47 |
-
? "audios/"
|
| 48 |
-
: null;
|
| 49 |
-
|
| 50 |
-
if (!folderPath) {
|
| 51 |
-
return NextResponse.json(
|
| 52 |
-
{ ok: false, error: "Unsupported media type: " + media.type },
|
| 53 |
-
{ status: 400 }
|
| 54 |
-
);
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
const mediaName = `${folderPath}${media.name}`;
|
| 58 |
-
const processedFile = new File([media], mediaName, { type: media.type });
|
| 59 |
-
filesToUpload.push(processedFile);
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
await uploadFiles({
|
| 63 |
-
repo,
|
| 64 |
-
files: filesToUpload,
|
| 65 |
-
accessToken: token,
|
| 66 |
-
commitTitle: `📁 Upload media files through DeepSite`,
|
| 67 |
-
});
|
| 68 |
-
|
| 69 |
-
return NextResponse.json(
|
| 70 |
-
{
|
| 71 |
-
success: true,
|
| 72 |
-
medias: filesToUpload.map(
|
| 73 |
-
(file) =>
|
| 74 |
-
`https://huggingface.co/spaces/${session.user?.username}/${repoId}/resolve/main/${file.name}`
|
| 75 |
-
),
|
| 76 |
-
},
|
| 77 |
-
{ status: 200 }
|
| 78 |
-
);
|
| 79 |
-
} catch (error) {
|
| 80 |
-
return NextResponse.json(
|
| 81 |
-
{ ok: false, error: error ?? "Failed to upload media files" },
|
| 82 |
-
{ status: 500 }
|
| 83 |
-
);
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
return NextResponse.json({ success: true }, { status: 200 });
|
| 87 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/[repoId]/rename/route.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { downloadFile, RepoDesignation, uploadFile } from "@huggingface/hub";
|
| 3 |
-
import { format } from "date-fns";
|
| 4 |
-
import { NextResponse } from "next/server";
|
| 5 |
-
|
| 6 |
-
export async function PUT(
|
| 7 |
-
request: Request,
|
| 8 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 9 |
-
) {
|
| 10 |
-
const { repoId }: { repoId: string } = await params;
|
| 11 |
-
const session = await auth();
|
| 12 |
-
if (!session) {
|
| 13 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 14 |
-
}
|
| 15 |
-
const token = session.accessToken;
|
| 16 |
-
|
| 17 |
-
const body = await request.json();
|
| 18 |
-
const { newTitle } = body;
|
| 19 |
-
|
| 20 |
-
if (!newTitle) {
|
| 21 |
-
return NextResponse.json(
|
| 22 |
-
{ error: "newTitle is required" },
|
| 23 |
-
{ status: 400 }
|
| 24 |
-
);
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
const repo: RepoDesignation = {
|
| 28 |
-
type: "space",
|
| 29 |
-
name: session.user?.username + "/" + repoId,
|
| 30 |
-
};
|
| 31 |
-
|
| 32 |
-
const blob = await downloadFile({
|
| 33 |
-
repo,
|
| 34 |
-
accessToken: token,
|
| 35 |
-
path: "README.md",
|
| 36 |
-
raw: true,
|
| 37 |
-
}).catch((_) => {
|
| 38 |
-
return null;
|
| 39 |
-
});
|
| 40 |
-
|
| 41 |
-
if (!blob) {
|
| 42 |
-
return NextResponse.json(
|
| 43 |
-
{ error: "Could not fetch README.md" },
|
| 44 |
-
{ status: 500 }
|
| 45 |
-
);
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
const readmeFile = await blob?.text();
|
| 49 |
-
if (!readmeFile) {
|
| 50 |
-
return NextResponse.json(
|
| 51 |
-
{ error: "Could not read README.md content" },
|
| 52 |
-
{ status: 500 }
|
| 53 |
-
);
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
// Escape YAML values to prevent injection attacks
|
| 57 |
-
const escapeYamlValue = (value: string): string => {
|
| 58 |
-
if (/[:|>]|^[-*#]|^\s|['"]/.test(value) || value.includes("\n")) {
|
| 59 |
-
return `"${value.replace(/"/g, '\\"')}"`;
|
| 60 |
-
}
|
| 61 |
-
return value;
|
| 62 |
-
};
|
| 63 |
-
|
| 64 |
-
// Escape commit message to prevent injection
|
| 65 |
-
const escapeCommitMessage = (message: string): string => {
|
| 66 |
-
return message.replace(/[\r\n]/g, " ").slice(0, 200);
|
| 67 |
-
};
|
| 68 |
-
|
| 69 |
-
const updatedReadmeFile = readmeFile.replace(
|
| 70 |
-
/^title:\s*(.*)$/m,
|
| 71 |
-
`title: ${escapeYamlValue(newTitle)}`
|
| 72 |
-
);
|
| 73 |
-
|
| 74 |
-
await uploadFile({
|
| 75 |
-
repo,
|
| 76 |
-
accessToken: token,
|
| 77 |
-
file: new File([updatedReadmeFile], "README.md", { type: "text/markdown" }),
|
| 78 |
-
commitTitle: escapeCommitMessage(
|
| 79 |
-
`🐳 ${format(new Date(), "dd/MM")} - ${format(
|
| 80 |
-
new Date(),
|
| 81 |
-
"HH:mm"
|
| 82 |
-
)} - Rename project to "${newTitle}"`
|
| 83 |
-
),
|
| 84 |
-
});
|
| 85 |
-
|
| 86 |
-
return NextResponse.json(
|
| 87 |
-
{
|
| 88 |
-
success: true,
|
| 89 |
-
},
|
| 90 |
-
{ status: 200 }
|
| 91 |
-
);
|
| 92 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/[repoId]/route.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
| 1 |
-
import { auth } from "@/lib/auth";
|
| 2 |
-
import { RepoDesignation, deleteRepo, uploadFiles } from "@huggingface/hub";
|
| 3 |
-
import { format } from "date-fns";
|
| 4 |
-
import { NextResponse } from "next/server";
|
| 5 |
-
|
| 6 |
-
export async function PUT(
|
| 7 |
-
request: Request,
|
| 8 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 9 |
-
) {
|
| 10 |
-
const { repoId }: { repoId: string } = await params;
|
| 11 |
-
const session = await auth();
|
| 12 |
-
if (!session) {
|
| 13 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 14 |
-
}
|
| 15 |
-
const token = session.accessToken;
|
| 16 |
-
|
| 17 |
-
const body = await request.json();
|
| 18 |
-
const { files, prompt, isManualChanges } = body;
|
| 19 |
-
|
| 20 |
-
if (!files) {
|
| 21 |
-
return NextResponse.json({ error: "Files are required" }, { status: 400 });
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
-
if (!prompt) {
|
| 25 |
-
return NextResponse.json({ error: "Prompt is required" }, { status: 400 });
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
const repo: RepoDesignation = {
|
| 29 |
-
type: "space",
|
| 30 |
-
name: session.user?.username + "/" + repoId,
|
| 31 |
-
};
|
| 32 |
-
|
| 33 |
-
const filesToUpload: File[] = [];
|
| 34 |
-
for (const file of files) {
|
| 35 |
-
let mimeType = "text/x-python";
|
| 36 |
-
if (file.path.endsWith(".txt")) {
|
| 37 |
-
mimeType = "text/plain";
|
| 38 |
-
} else if (file.path.endsWith(".md")) {
|
| 39 |
-
mimeType = "text/markdown";
|
| 40 |
-
} else if (file.path.endsWith(".json")) {
|
| 41 |
-
mimeType = "application/json";
|
| 42 |
-
}
|
| 43 |
-
filesToUpload.push(new File([file.content], file.path, { type: mimeType }));
|
| 44 |
-
}
|
| 45 |
-
// Escape commit title to prevent injection
|
| 46 |
-
const escapeCommitTitle = (title: string): string => {
|
| 47 |
-
return title.replace(/[\r\n]/g, " ").slice(0, 200);
|
| 48 |
-
};
|
| 49 |
-
|
| 50 |
-
const baseTitle = isManualChanges
|
| 51 |
-
? ""
|
| 52 |
-
: `🐳 ${format(new Date(), "dd/MM")} - ${format(new Date(), "HH:mm")} - `;
|
| 53 |
-
const commitTitle = escapeCommitTitle(
|
| 54 |
-
baseTitle + (prompt ?? "Follow-up DeepSite commit")
|
| 55 |
-
);
|
| 56 |
-
const response = await uploadFiles({
|
| 57 |
-
repo,
|
| 58 |
-
files: filesToUpload,
|
| 59 |
-
accessToken: token,
|
| 60 |
-
commitTitle,
|
| 61 |
-
});
|
| 62 |
-
|
| 63 |
-
return NextResponse.json(
|
| 64 |
-
{
|
| 65 |
-
success: true,
|
| 66 |
-
commit: {
|
| 67 |
-
oid: response.commit,
|
| 68 |
-
title: commitTitle,
|
| 69 |
-
date: new Date(),
|
| 70 |
-
},
|
| 71 |
-
},
|
| 72 |
-
{ status: 200 }
|
| 73 |
-
);
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
export async function DELETE(
|
| 77 |
-
request: Request,
|
| 78 |
-
{ params }: { params: Promise<{ repoId: string }> }
|
| 79 |
-
) {
|
| 80 |
-
const { repoId }: { repoId: string } = await params;
|
| 81 |
-
const session = await auth();
|
| 82 |
-
if (!session) {
|
| 83 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 84 |
-
}
|
| 85 |
-
const token = session.accessToken;
|
| 86 |
-
|
| 87 |
-
const repo: RepoDesignation = {
|
| 88 |
-
type: "space",
|
| 89 |
-
name: session.user?.username + "/" + repoId,
|
| 90 |
-
};
|
| 91 |
-
|
| 92 |
-
try {
|
| 93 |
-
await deleteRepo({
|
| 94 |
-
repo,
|
| 95 |
-
accessToken: token as string,
|
| 96 |
-
});
|
| 97 |
-
|
| 98 |
-
return NextResponse.json({ success: true }, { status: 200 });
|
| 99 |
-
} catch (error) {
|
| 100 |
-
const errMsg =
|
| 101 |
-
error instanceof Error ? error.message : "Failed to delete project";
|
| 102 |
-
return NextResponse.json({ error: errMsg }, { status: 500 });
|
| 103 |
-
}
|
| 104 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/projects/route.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
| 1 |
-
import { NextResponse } from "next/server";
|
| 2 |
-
import { RepoDesignation, createRepo, uploadFiles } from "@huggingface/hub";
|
| 3 |
-
|
| 4 |
-
import { auth } from "@/lib/auth";
|
| 5 |
-
import {
|
| 6 |
-
COLORS,
|
| 7 |
-
EMOJIS_FOR_SPACE,
|
| 8 |
-
injectDeepSiteBadge,
|
| 9 |
-
isIndexPage,
|
| 10 |
-
} from "@/lib/utils";
|
| 11 |
-
|
| 12 |
-
// todo: catch error while publishing project, and return the error to the user
|
| 13 |
-
// if space has been created, but can't push, try again or catch well the error and return the error to the user
|
| 14 |
-
|
| 15 |
-
export async function POST(request: Request) {
|
| 16 |
-
const session = await auth();
|
| 17 |
-
if (!session) {
|
| 18 |
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
| 19 |
-
}
|
| 20 |
-
const token = session.accessToken;
|
| 21 |
-
|
| 22 |
-
const body = await request.json();
|
| 23 |
-
const { projectTitle, files, prompt } = body;
|
| 24 |
-
|
| 25 |
-
if (!files) {
|
| 26 |
-
return NextResponse.json(
|
| 27 |
-
{ error: "Project title and files are required" },
|
| 28 |
-
{ status: 400 }
|
| 29 |
-
);
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
const title =
|
| 33 |
-
projectTitle || projectTitle !== "" ? projectTitle : "DeepSite Project";
|
| 34 |
-
|
| 35 |
-
let formattedTitle = title
|
| 36 |
-
.toLowerCase()
|
| 37 |
-
.replace(/[^a-z0-9]+/g, "-")
|
| 38 |
-
.split("-")
|
| 39 |
-
.filter(Boolean)
|
| 40 |
-
.join("-")
|
| 41 |
-
.slice(0, 75);
|
| 42 |
-
|
| 43 |
-
formattedTitle =
|
| 44 |
-
formattedTitle + "-" + Math.random().toString(36).substring(2, 7);
|
| 45 |
-
|
| 46 |
-
const repo: RepoDesignation = {
|
| 47 |
-
type: "space",
|
| 48 |
-
name: session.user?.username + "/" + formattedTitle,
|
| 49 |
-
};
|
| 50 |
-
|
| 51 |
-
// Escape YAML values to prevent injection attacks
|
| 52 |
-
const escapeYamlValue = (value: string): string => {
|
| 53 |
-
if (/[:|>]|^[-*#]|^\s|['"]/.test(value) || value.includes("\n")) {
|
| 54 |
-
return `"${value.replace(/"/g, '\\"')}"`;
|
| 55 |
-
}
|
| 56 |
-
return value;
|
| 57 |
-
};
|
| 58 |
-
|
| 59 |
-
// Escape markdown headers to prevent injection
|
| 60 |
-
const escapeMarkdownHeader = (value: string): string => {
|
| 61 |
-
return value.replace(/^#+\s*/g, "").replace(/\n/g, " ");
|
| 62 |
-
};
|
| 63 |
-
|
| 64 |
-
const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 65 |
-
const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];
|
| 66 |
-
const emoji =
|
| 67 |
-
EMOJIS_FOR_SPACE[Math.floor(Math.random() * EMOJIS_FOR_SPACE.length)];
|
| 68 |
-
const README = `---
|
| 69 |
-
title: ${escapeYamlValue(projectTitle)}
|
| 70 |
-
colorFrom: ${colorFrom}
|
| 71 |
-
colorTo: ${colorTo}
|
| 72 |
-
sdk: static
|
| 73 |
-
emoji: ${emoji}
|
| 74 |
-
tags:
|
| 75 |
-
- deepsite-v4
|
| 76 |
-
---
|
| 77 |
-
|
| 78 |
-
# ${escapeMarkdownHeader(title)}
|
| 79 |
-
|
| 80 |
-
This project has been created with [DeepSite](https://deepsite.hf.co) AI Vibe Coding.
|
| 81 |
-
`;
|
| 82 |
-
|
| 83 |
-
const filesToUpload: File[] = [
|
| 84 |
-
new File([README], "README.md", { type: "text/markdown" }),
|
| 85 |
-
];
|
| 86 |
-
for (const file of files) {
|
| 87 |
-
let mimeType = "text/html";
|
| 88 |
-
if (file.path.endsWith(".css")) {
|
| 89 |
-
mimeType = "text/css";
|
| 90 |
-
} else if (file.path.endsWith(".js")) {
|
| 91 |
-
mimeType = "text/javascript";
|
| 92 |
-
}
|
| 93 |
-
const content =
|
| 94 |
-
mimeType === "text/html" && isIndexPage(file.path)
|
| 95 |
-
? injectDeepSiteBadge(file.content)
|
| 96 |
-
: file.content;
|
| 97 |
-
|
| 98 |
-
filesToUpload.push(new File([content], file.path, { type: mimeType }));
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
let repoUrl: string | undefined;
|
| 102 |
-
|
| 103 |
-
try {
|
| 104 |
-
// Create the space first
|
| 105 |
-
const createResult = await createRepo({
|
| 106 |
-
accessToken: token as string,
|
| 107 |
-
repo: repo,
|
| 108 |
-
sdk: "static",
|
| 109 |
-
});
|
| 110 |
-
repoUrl = createResult.repoUrl;
|
| 111 |
-
|
| 112 |
-
// Escape commit message to prevent injection
|
| 113 |
-
const escapeCommitMessage = (message: string): string => {
|
| 114 |
-
return message.replace(/[\r\n]/g, " ").slice(0, 200);
|
| 115 |
-
};
|
| 116 |
-
const commitMessage = escapeCommitMessage(prompt ?? "Initial DeepSite commit");
|
| 117 |
-
|
| 118 |
-
// Upload files to the created space
|
| 119 |
-
await uploadFiles({
|
| 120 |
-
repo,
|
| 121 |
-
files: filesToUpload,
|
| 122 |
-
accessToken: token as string,
|
| 123 |
-
commitTitle: commitMessage,
|
| 124 |
-
});
|
| 125 |
-
|
| 126 |
-
const path = repoUrl.split("/").slice(-2).join("/");
|
| 127 |
-
|
| 128 |
-
return NextResponse.json({ repoUrl: path }, { status: 200 });
|
| 129 |
-
} catch (error) {
|
| 130 |
-
const errMsg =
|
| 131 |
-
error instanceof Error ? error.message : "Failed to create or upload to space";
|
| 132 |
-
|
| 133 |
-
// If space was created but upload failed, include the repo URL in the error
|
| 134 |
-
if (repoUrl) {
|
| 135 |
-
const path = repoUrl.split("/").slice(-2).join("/");
|
| 136 |
-
return NextResponse.json({
|
| 137 |
-
error: `${errMsg}. Space was created at ${path} but files could not be uploaded.`,
|
| 138 |
-
repoUrl: path,
|
| 139 |
-
partialSuccess: true
|
| 140 |
-
}, { status: 500 });
|
| 141 |
-
}
|
| 142 |
-
|
| 143 |
-
return NextResponse.json({ error: errMsg }, { status: 500 });
|
| 144 |
-
}
|
| 145 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/api/re-design/route.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { NextRequest, NextResponse } from "next/server";
|
| 2 |
+
|
| 3 |
+
export async function PUT(request: NextRequest) {
|
| 4 |
+
const body = await request.json();
|
| 5 |
+
const { url } = body;
|
| 6 |
+
|
| 7 |
+
if (!url) {
|
| 8 |
+
return NextResponse.json({ error: "URL is required" }, { status: 400 });
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
try {
|
| 12 |
+
const response = await fetch(
|
| 13 |
+
`https://r.jina.ai/${encodeURIComponent(url)}`,
|
| 14 |
+
{
|
| 15 |
+
method: "POST",
|
| 16 |
+
}
|
| 17 |
+
);
|
| 18 |
+
if (!response.ok) {
|
| 19 |
+
return NextResponse.json(
|
| 20 |
+
{ error: "Failed to fetch redesign" },
|
| 21 |
+
{ status: 500 }
|
| 22 |
+
);
|
| 23 |
+
}
|
| 24 |
+
const markdown = await response.text();
|
| 25 |
+
return NextResponse.json(
|
| 26 |
+
{
|
| 27 |
+
ok: true,
|
| 28 |
+
markdown,
|
| 29 |
+
},
|
| 30 |
+
{ status: 200 }
|
| 31 |
+
);
|
| 32 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 33 |
+
} catch (error: any) {
|
| 34 |
+
return NextResponse.json(
|
| 35 |
+
{ error: error.message || "An error occurred" },
|
| 36 |
+
{ status: 500 }
|
| 37 |
+
);
|
| 38 |
+
}
|
| 39 |
+
}
|
app/api/redesign/route.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
| 1 |
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
-
import { NextRequest, NextResponse } from "next/server";
|
| 3 |
-
|
| 4 |
-
const FETCH_TIMEOUT = 30_000;
|
| 5 |
-
export const maxDuration = 60;
|
| 6 |
-
|
| 7 |
-
export async function PUT(request: NextRequest) {
|
| 8 |
-
const body = await request.json();
|
| 9 |
-
const { url } = body;
|
| 10 |
-
|
| 11 |
-
if (!url) {
|
| 12 |
-
return NextResponse.json({ error: "URL is required" }, { status: 400 });
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
try {
|
| 16 |
-
const controller = new AbortController();
|
| 17 |
-
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
| 18 |
-
|
| 19 |
-
try {
|
| 20 |
-
const response = await fetch(
|
| 21 |
-
`https://r.jina.ai/${encodeURIComponent(url)}`,
|
| 22 |
-
{
|
| 23 |
-
method: "POST",
|
| 24 |
-
signal: controller.signal,
|
| 25 |
-
}
|
| 26 |
-
);
|
| 27 |
-
|
| 28 |
-
clearTimeout(timeoutId);
|
| 29 |
-
|
| 30 |
-
if (!response.ok) {
|
| 31 |
-
return NextResponse.json(
|
| 32 |
-
{ error: "Failed to fetch redesign" },
|
| 33 |
-
{ status: 500 }
|
| 34 |
-
);
|
| 35 |
-
}
|
| 36 |
-
const markdown = await response.text();
|
| 37 |
-
return NextResponse.json(
|
| 38 |
-
{
|
| 39 |
-
ok: true,
|
| 40 |
-
markdown,
|
| 41 |
-
},
|
| 42 |
-
{ status: 200 }
|
| 43 |
-
);
|
| 44 |
-
} catch (fetchError: any) {
|
| 45 |
-
clearTimeout(timeoutId);
|
| 46 |
-
|
| 47 |
-
if (fetchError.name === "AbortError") {
|
| 48 |
-
return NextResponse.json(
|
| 49 |
-
{
|
| 50 |
-
error:
|
| 51 |
-
"Request timeout: The external service took too long to respond. Please try again.",
|
| 52 |
-
},
|
| 53 |
-
{ status: 504 }
|
| 54 |
-
);
|
| 55 |
-
}
|
| 56 |
-
throw fetchError;
|
| 57 |
-
}
|
| 58 |
-
} catch (error: any) {
|
| 59 |
-
if (error.name === "AbortError" || error.message?.includes("timeout")) {
|
| 60 |
-
return NextResponse.json(
|
| 61 |
-
{
|
| 62 |
-
error:
|
| 63 |
-
"Request timeout: The external service took too long to respond. Please try again.",
|
| 64 |
-
},
|
| 65 |
-
{ status: 504 }
|
| 66 |
-
);
|
| 67 |
-
}
|
| 68 |
-
return NextResponse.json(
|
| 69 |
-
{ error: error.message || "An error occurred" },
|
| 70 |
-
{ status: 500 }
|
| 71 |
-
);
|
| 72 |
-
}
|
| 73 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/auth/callback/page.tsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import Link from "next/link";
|
| 3 |
+
import { useUser } from "@/hooks/useUser";
|
| 4 |
+
import { use, useState } from "react";
|
| 5 |
+
import { useMount, useTimeoutFn } from "react-use";
|
| 6 |
+
|
| 7 |
+
import { Button } from "@/components/ui/button";
|
| 8 |
+
export default function AuthCallback({
|
| 9 |
+
searchParams,
|
| 10 |
+
}: {
|
| 11 |
+
searchParams: Promise<{ code: string }>;
|
| 12 |
+
}) {
|
| 13 |
+
const [showButton, setShowButton] = useState(false);
|
| 14 |
+
const { code } = use(searchParams);
|
| 15 |
+
const { loginFromCode } = useUser();
|
| 16 |
+
|
| 17 |
+
useMount(async () => {
|
| 18 |
+
if (code) {
|
| 19 |
+
await loginFromCode(code);
|
| 20 |
+
}
|
| 21 |
+
});
|
| 22 |
+
|
| 23 |
+
useTimeoutFn(
|
| 24 |
+
() => setShowButton(true),
|
| 25 |
+
7000 // Show button after 5 seconds
|
| 26 |
+
);
|
| 27 |
+
|
| 28 |
+
return (
|
| 29 |
+
<div className="h-screen flex flex-col justify-center items-center">
|
| 30 |
+
<div className="!rounded-2xl !p-0 !bg-white !border-neutral-100 min-w-xs text-center overflow-hidden ring-[8px] ring-white/20">
|
| 31 |
+
<header className="bg-neutral-50 p-6 border-b border-neutral-200/60">
|
| 32 |
+
<div className="flex items-center justify-center -space-x-4 mb-3">
|
| 33 |
+
<div className="size-9 rounded-full bg-pink-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
| 34 |
+
🚀
|
| 35 |
+
</div>
|
| 36 |
+
<div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
|
| 37 |
+
👋
|
| 38 |
+
</div>
|
| 39 |
+
<div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
|
| 40 |
+
🙌
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
<p className="text-xl font-semibold text-neutral-950">
|
| 44 |
+
Login In Progress...
|
| 45 |
+
</p>
|
| 46 |
+
<p className="text-sm text-neutral-500 mt-1.5">
|
| 47 |
+
Wait a moment while we log you in with your code.
|
| 48 |
+
</p>
|
| 49 |
+
</header>
|
| 50 |
+
<main className="space-y-4 p-6">
|
| 51 |
+
<div>
|
| 52 |
+
<p className="text-sm text-neutral-700 mb-4 max-w-xs">
|
| 53 |
+
If you are not redirected automatically in the next 5 seconds,
|
| 54 |
+
please click the button below
|
| 55 |
+
</p>
|
| 56 |
+
{showButton ? (
|
| 57 |
+
<Link href="/">
|
| 58 |
+
<Button variant="black" className="relative">
|
| 59 |
+
Go to Home
|
| 60 |
+
</Button>
|
| 61 |
+
</Link>
|
| 62 |
+
) : (
|
| 63 |
+
<p className="text-xs text-neutral-500">
|
| 64 |
+
Please wait, we are logging you in...
|
| 65 |
+
</p>
|
| 66 |
+
)}
|
| 67 |
+
</div>
|
| 68 |
+
</main>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
);
|
| 72 |
+
}
|
app/auth/page.tsx
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { redirect } from "next/navigation";
|
| 2 |
+
import { Metadata } from "next";
|
| 3 |
+
|
| 4 |
+
import { getAuth } from "@/app/actions/auth";
|
| 5 |
+
|
| 6 |
+
export const revalidate = 1;
|
| 7 |
+
|
| 8 |
+
export const metadata: Metadata = {
|
| 9 |
+
robots: "noindex, nofollow",
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
export default async function Auth() {
|
| 13 |
+
const loginRedirectUrl = await getAuth();
|
| 14 |
+
if (loginRedirectUrl) {
|
| 15 |
+
redirect(loginRedirectUrl);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
return (
|
| 19 |
+
<div className="p-4">
|
| 20 |
+
<div className="border bg-red-500/10 border-red-500/20 text-red-500 px-5 py-3 rounded-lg">
|
| 21 |
+
<h1 className="text-xl font-bold">Error</h1>
|
| 22 |
+
<p className="text-sm">
|
| 23 |
+
An error occurred while trying to log in. Please try again later.
|
| 24 |
+
</p>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
);
|
| 28 |
+
}
|
app/layout.tsx
CHANGED
|
@@ -1,33 +1,54 @@
|
|
|
|
|
| 1 |
import type { Metadata, Viewport } from "next";
|
| 2 |
-
import {
|
| 3 |
-
import {
|
| 4 |
-
import Script from "next/script";
|
| 5 |
|
| 6 |
-
import "@/
|
| 7 |
-
import
|
| 8 |
-
import { AuthProvider } from "@/components/providers/session";
|
| 9 |
import { Toaster } from "@/components/ui/sonner";
|
| 10 |
-
import
|
| 11 |
-
import {
|
| 12 |
-
import
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
const
|
| 15 |
-
variable: "--font-
|
| 16 |
subsets: ["latin"],
|
| 17 |
});
|
| 18 |
|
| 19 |
-
const
|
| 20 |
-
variable: "--font-
|
| 21 |
subsets: ["latin"],
|
|
|
|
| 22 |
});
|
| 23 |
|
| 24 |
export const metadata: Metadata = {
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
title: "DeepSite | Build with AI ✨",
|
| 27 |
description:
|
| 28 |
"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.",
|
| 29 |
-
|
| 30 |
-
}
|
| 31 |
appleWebApp: {
|
| 32 |
capable: true,
|
| 33 |
title: "DeepSite",
|
|
@@ -38,70 +59,53 @@ export const metadata: Metadata = {
|
|
| 38 |
shortcut: "/logo.svg",
|
| 39 |
apple: "/logo.svg",
|
| 40 |
},
|
| 41 |
-
verification: {
|
| 42 |
-
google: process.env.GOOGLE_SITE_VERIFICATION,
|
| 43 |
-
},
|
| 44 |
};
|
| 45 |
|
| 46 |
export const viewport: Viewport = {
|
| 47 |
initialScale: 1,
|
| 48 |
maximumScale: 1,
|
| 49 |
-
themeColor: "#
|
| 50 |
};
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
export default async function RootLayout({
|
| 53 |
children,
|
| 54 |
}: Readonly<{
|
| 55 |
children: React.ReactNode;
|
| 56 |
}>) {
|
| 57 |
-
const
|
| 58 |
-
name: "DeepSite",
|
| 59 |
-
description: "Build websites with AI, no code required",
|
| 60 |
-
url: "https://deepsite.hf.co",
|
| 61 |
-
});
|
| 62 |
-
const organizationData = generateStructuredData("Organization", {
|
| 63 |
-
name: "DeepSite",
|
| 64 |
-
url: "https://deepsite.hf.co",
|
| 65 |
-
});
|
| 66 |
-
|
| 67 |
return (
|
| 68 |
-
<html lang="en"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
<body
|
| 70 |
-
className={`${
|
| 71 |
>
|
| 72 |
-
<
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
/>
|
| 78 |
-
<script
|
| 79 |
-
type="application/ld+json"
|
| 80 |
-
dangerouslySetInnerHTML={{
|
| 81 |
-
__html: JSON.stringify(organizationData),
|
| 82 |
-
}}
|
| 83 |
-
/>
|
| 84 |
-
<Script
|
| 85 |
-
defer
|
| 86 |
-
data-domain="deepsite.hf.co"
|
| 87 |
-
src="https://plausible.io/js/script.js"
|
| 88 |
-
/>
|
| 89 |
-
<Toaster richColors />
|
| 90 |
-
<AuthProvider>
|
| 91 |
-
<ReactQueryProvider>
|
| 92 |
-
<ThemeProvider
|
| 93 |
-
attribute="class"
|
| 94 |
-
defaultTheme="dark"
|
| 95 |
-
enableSystem
|
| 96 |
-
disableTransitionOnChange
|
| 97 |
-
>
|
| 98 |
-
<NextStepProvider>
|
| 99 |
-
{children}
|
| 100 |
-
<NotAuthorizedDomain />
|
| 101 |
-
</NextStepProvider>
|
| 102 |
-
</ThemeProvider>
|
| 103 |
-
</ReactQueryProvider>
|
| 104 |
-
</AuthProvider>
|
| 105 |
</body>
|
| 106 |
</html>
|
| 107 |
);
|
|
|
|
| 1 |
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
| 2 |
import type { Metadata, Viewport } from "next";
|
| 3 |
+
import { Inter, PT_Sans } from "next/font/google";
|
| 4 |
+
import { cookies } from "next/headers";
|
|
|
|
| 5 |
|
| 6 |
+
import TanstackProvider from "@/components/providers/tanstack-query-provider";
|
| 7 |
+
import "@/assets/globals.css";
|
|
|
|
| 8 |
import { Toaster } from "@/components/ui/sonner";
|
| 9 |
+
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 10 |
+
import { apiServer } from "@/lib/api";
|
| 11 |
+
import AppContext from "@/components/contexts/app-context";
|
| 12 |
+
import Script from "next/script";
|
| 13 |
+
import IframeDetector from "@/components/iframe-detector";
|
| 14 |
|
| 15 |
+
const inter = Inter({
|
| 16 |
+
variable: "--font-inter-sans",
|
| 17 |
subsets: ["latin"],
|
| 18 |
});
|
| 19 |
|
| 20 |
+
const ptSans = PT_Sans({
|
| 21 |
+
variable: "--font-ptSans-mono",
|
| 22 |
subsets: ["latin"],
|
| 23 |
+
weight: ["400", "700"],
|
| 24 |
});
|
| 25 |
|
| 26 |
export const metadata: Metadata = {
|
| 27 |
+
title: "DeepSite | Build with AI ✨",
|
| 28 |
+
description:
|
| 29 |
+
"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.",
|
| 30 |
+
openGraph: {
|
| 31 |
+
title: "DeepSite | Build with AI ✨",
|
| 32 |
+
description:
|
| 33 |
+
"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.",
|
| 34 |
+
url: "https://deepsite.hf.co",
|
| 35 |
+
siteName: "DeepSite",
|
| 36 |
+
images: [
|
| 37 |
+
{
|
| 38 |
+
url: "https://deepsite.hf.co/banner.png",
|
| 39 |
+
width: 1200,
|
| 40 |
+
height: 630,
|
| 41 |
+
alt: "DeepSite Open Graph Image",
|
| 42 |
+
},
|
| 43 |
+
],
|
| 44 |
+
},
|
| 45 |
+
twitter: {
|
| 46 |
+
card: "summary_large_image",
|
| 47 |
title: "DeepSite | Build with AI ✨",
|
| 48 |
description:
|
| 49 |
"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.",
|
| 50 |
+
images: ["https://deepsite.hf.co/banner.png"],
|
| 51 |
+
},
|
| 52 |
appleWebApp: {
|
| 53 |
capable: true,
|
| 54 |
title: "DeepSite",
|
|
|
|
| 59 |
shortcut: "/logo.svg",
|
| 60 |
apple: "/logo.svg",
|
| 61 |
},
|
|
|
|
|
|
|
|
|
|
| 62 |
};
|
| 63 |
|
| 64 |
export const viewport: Viewport = {
|
| 65 |
initialScale: 1,
|
| 66 |
maximumScale: 1,
|
| 67 |
+
themeColor: "#000000",
|
| 68 |
};
|
| 69 |
|
| 70 |
+
async function getMe() {
|
| 71 |
+
const cookieStore = await cookies();
|
| 72 |
+
const token = cookieStore.get(MY_TOKEN_KEY())?.value;
|
| 73 |
+
if (!token) return { user: null, errCode: null };
|
| 74 |
+
try {
|
| 75 |
+
const res = await apiServer.get("/me", {
|
| 76 |
+
headers: {
|
| 77 |
+
Authorization: `Bearer ${token}`,
|
| 78 |
+
},
|
| 79 |
+
});
|
| 80 |
+
return { user: res.data.user, errCode: null };
|
| 81 |
+
} catch (err: any) {
|
| 82 |
+
return { user: null, errCode: err.status };
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
// if domain isn't deepsite.hf.co or enzostvs-deepsite.hf.space redirect to deepsite.hf.co
|
| 87 |
+
|
| 88 |
export default async function RootLayout({
|
| 89 |
children,
|
| 90 |
}: Readonly<{
|
| 91 |
children: React.ReactNode;
|
| 92 |
}>) {
|
| 93 |
+
const data = await getMe();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
return (
|
| 95 |
+
<html lang="en">
|
| 96 |
+
<Script
|
| 97 |
+
defer
|
| 98 |
+
data-domain="deepsite.hf.co"
|
| 99 |
+
src="https://plausible.io/js/script.js"
|
| 100 |
+
></Script>
|
| 101 |
<body
|
| 102 |
+
className={`${inter.variable} ${ptSans.variable} antialiased bg-black dark h-[100dvh] overflow-hidden`}
|
| 103 |
>
|
| 104 |
+
<IframeDetector />
|
| 105 |
+
<Toaster richColors position="bottom-center" />
|
| 106 |
+
<TanstackProvider>
|
| 107 |
+
<AppContext me={data}>{children}</AppContext>
|
| 108 |
+
</TanstackProvider>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
</body>
|
| 110 |
</html>
|
| 111 |
);
|
app/new/page.tsx
DELETED
|
@@ -1,18 +0,0 @@
|
|
| 1 |
-
import { AppEditor } from "@/components/editor";
|
| 2 |
-
import { auth } from "@/lib/auth";
|
| 3 |
-
import { redirect } from "next/navigation";
|
| 4 |
-
|
| 5 |
-
export default async function NewProjectPage({
|
| 6 |
-
searchParams,
|
| 7 |
-
}: {
|
| 8 |
-
searchParams: Promise<{ prompt: string }>;
|
| 9 |
-
}) {
|
| 10 |
-
const session = await auth();
|
| 11 |
-
|
| 12 |
-
if (!session) {
|
| 13 |
-
redirect("/api/auth/signin?callbackUrl=/new");
|
| 14 |
-
}
|
| 15 |
-
|
| 16 |
-
const { prompt } = await searchParams;
|
| 17 |
-
return <AppEditor isNew={true} initialPrompt={prompt} />;
|
| 18 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/not-found.tsx
DELETED
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
import { NotFoundButtons } from "@/components/not-found/buttons";
|
| 2 |
-
import { Navigation } from "@/components/public/navigation";
|
| 3 |
-
|
| 4 |
-
export default function NotFound() {
|
| 5 |
-
return (
|
| 6 |
-
<div className="min-h-screen font-sans">
|
| 7 |
-
<Navigation />
|
| 8 |
-
<div className="px-6 py-16 max-w-5xl mx-auto text-center">
|
| 9 |
-
<h1 className="text-5xl font-bold mb-5">Oh no! Page not found.</h1>
|
| 10 |
-
<p className="text-lg text-muted-foreground mb-8">
|
| 11 |
-
The page you are looking for does not exist.
|
| 12 |
-
</p>
|
| 13 |
-
<NotFoundButtons />
|
| 14 |
-
</div>
|
| 15 |
-
</div>
|
| 16 |
-
);
|
| 17 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app/projects/[namespace]/[repoId]/page.tsx
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { cookies } from "next/headers";
|
| 2 |
+
import { redirect } from "next/navigation";
|
| 3 |
+
|
| 4 |
+
import { apiServer } from "@/lib/api";
|
| 5 |
+
import MY_TOKEN_KEY from "@/lib/get-cookie-name";
|
| 6 |
+
import { AppEditor } from "@/components/editor";
|
| 7 |
+
|
| 8 |
+
async function getProject(namespace: string, repoId: string) {
|
| 9 |
+
// TODO replace with a server action
|
| 10 |
+
const cookieStore = await cookies();
|
| 11 |
+
const token = cookieStore.get(MY_TOKEN_KEY())?.value;
|
| 12 |
+
if (!token) return {};
|
| 13 |
+
try {
|
| 14 |
+
const { data } = await apiServer.get(
|
| 15 |
+
`/me/projects/${namespace}/${repoId}`,
|
| 16 |
+
{
|
| 17 |
+
headers: {
|
| 18 |
+
Authorization: `Bearer ${token}`,
|
| 19 |
+
},
|
| 20 |
+
}
|
| 21 |
+
);
|
| 22 |
+
|
| 23 |
+
return data.project;
|
| 24 |
+
} catch {
|
| 25 |
+
return {};
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
export default async function ProjectNamespacePage({
|
| 30 |
+
params,
|
| 31 |
+
}: {
|
| 32 |
+
params: Promise<{ namespace: string; repoId: string }>;
|
| 33 |
+
}) {
|
| 34 |
+
const { namespace, repoId } = await params;
|
| 35 |
+
const data = await getProject(namespace, repoId);
|
| 36 |
+
if (!data?.pages) {
|
| 37 |
+
redirect("/projects");
|
| 38 |
+
}
|
| 39 |
+
return (
|
| 40 |
+
<AppEditor project={data} pages={data.pages} images={data.images ?? []} />
|
| 41 |
+
);
|
| 42 |
+
}
|
app/projects/new/page.tsx
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { AppEditor } from "@/components/editor";
|
| 2 |
+
|
| 3 |
+
export default function ProjectsNewPage() {
|
| 4 |
+
return <AppEditor isNew />;
|
| 5 |
+
}
|
assets/deepseek.svg
DELETED
{app → assets}/globals.css
RENAMED
|
@@ -6,8 +6,8 @@
|
|
| 6 |
@theme inline {
|
| 7 |
--color-background: var(--background);
|
| 8 |
--color-foreground: var(--foreground);
|
| 9 |
-
--font-sans: var(--font-
|
| 10 |
-
--font-mono: var(--font-
|
| 11 |
--color-sidebar-ring: var(--sidebar-ring);
|
| 12 |
--color-sidebar-border: var(--sidebar-border);
|
| 13 |
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
@@ -44,7 +44,7 @@
|
|
| 44 |
}
|
| 45 |
|
| 46 |
:root {
|
| 47 |
-
--radius: 0.
|
| 48 |
--background: oklch(1 0 0);
|
| 49 |
--foreground: oklch(0.145 0 0);
|
| 50 |
--card: oklch(1 0 0);
|
|
@@ -68,7 +68,6 @@
|
|
| 68 |
--chart-3: oklch(0.398 0.07 227.392);
|
| 69 |
--chart-4: oklch(0.828 0.189 84.429);
|
| 70 |
--chart-5: oklch(0.769 0.188 70.08);
|
| 71 |
-
--radius: 0.625rem;
|
| 72 |
--sidebar: oklch(0.985 0 0);
|
| 73 |
--sidebar-foreground: oklch(0.145 0 0);
|
| 74 |
--sidebar-primary: oklch(0.205 0 0);
|
|
@@ -120,49 +119,28 @@
|
|
| 120 |
body {
|
| 121 |
@apply bg-background text-foreground;
|
| 122 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
}
|
| 124 |
|
| 125 |
.monaco-editor .margin {
|
| 126 |
-
@apply bg-
|
| 127 |
}
|
| 128 |
.monaco-editor .monaco-editor-background {
|
| 129 |
-
@apply bg-
|
| 130 |
-
}
|
| 131 |
-
.monaco-editor .decorationsOverviewRuler {
|
| 132 |
-
@apply opacity-0!;
|
| 133 |
-
}
|
| 134 |
-
.monaco-editor .view-line {
|
| 135 |
-
/* @apply bg-primary/50!; */
|
| 136 |
}
|
| 137 |
-
.monaco-editor .
|
| 138 |
-
@apply
|
| 139 |
-
}
|
| 140 |
-
.monaco-editor .cursors-layer .cursor {
|
| 141 |
-
@apply bg-primary!;
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
.content-placeholder::before {
|
| 145 |
-
content: attr(data-placeholder);
|
| 146 |
-
position: absolute;
|
| 147 |
-
pointer-events: none;
|
| 148 |
-
opacity: 0.5;
|
| 149 |
-
@apply top-5 left-6;
|
| 150 |
}
|
| 151 |
|
| 152 |
-
.
|
| 153 |
-
|
| 154 |
-
.sp-file-explorer-list
|
| 155 |
-
.sp-explorer[data-active="true"] {
|
| 156 |
-
@apply text-indigo-500!;
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
.sp-layout
|
| 160 |
-
.sp-stack
|
| 161 |
-
.sp-tabs
|
| 162 |
-
.sp-tab-container[aria-selected="true"]
|
| 163 |
-
.sp-tab-button {
|
| 164 |
-
@apply text-indigo-500!;
|
| 165 |
-
}
|
| 166 |
-
.sp-layout .sp-stack .sp-tabs .sp-tab-container:has(button:focus) {
|
| 167 |
-
@apply outline-none! border-none!;
|
| 168 |
}
|
|
|
|
| 6 |
@theme inline {
|
| 7 |
--color-background: var(--background);
|
| 8 |
--color-foreground: var(--foreground);
|
| 9 |
+
--font-sans: var(--font-inter-sans);
|
| 10 |
+
--font-mono: var(--font-ptSans-mono);
|
| 11 |
--color-sidebar-ring: var(--sidebar-ring);
|
| 12 |
--color-sidebar-border: var(--sidebar-border);
|
| 13 |
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
|
|
| 44 |
}
|
| 45 |
|
| 46 |
:root {
|
| 47 |
+
--radius: 0.625rem;
|
| 48 |
--background: oklch(1 0 0);
|
| 49 |
--foreground: oklch(0.145 0 0);
|
| 50 |
--card: oklch(1 0 0);
|
|
|
|
| 68 |
--chart-3: oklch(0.398 0.07 227.392);
|
| 69 |
--chart-4: oklch(0.828 0.189 84.429);
|
| 70 |
--chart-5: oklch(0.769 0.188 70.08);
|
|
|
|
| 71 |
--sidebar: oklch(0.985 0 0);
|
| 72 |
--sidebar-foreground: oklch(0.145 0 0);
|
| 73 |
--sidebar-primary: oklch(0.205 0 0);
|
|
|
|
| 119 |
body {
|
| 120 |
@apply bg-background text-foreground;
|
| 121 |
}
|
| 122 |
+
html {
|
| 123 |
+
@apply scroll-smooth;
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
.background__noisy {
|
| 128 |
+
@apply bg-blend-normal pointer-events-none opacity-90;
|
| 129 |
+
background-size: 25ww auto;
|
| 130 |
+
background-image: url("/background_noisy.webp");
|
| 131 |
+
@apply fixed w-screen h-screen -z-1 top-0 left-0;
|
| 132 |
}
|
| 133 |
|
| 134 |
.monaco-editor .margin {
|
| 135 |
+
@apply !bg-neutral-900;
|
| 136 |
}
|
| 137 |
.monaco-editor .monaco-editor-background {
|
| 138 |
+
@apply !bg-neutral-900;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
}
|
| 140 |
+
.monaco-editor .line-numbers {
|
| 141 |
+
@apply !text-neutral-500;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
}
|
| 143 |
|
| 144 |
+
.matched-line {
|
| 145 |
+
@apply bg-sky-500/30;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
}
|
assets/hf-logo.svg
DELETED
assets/kimi.svg
DELETED
assets/logo.svg
ADDED
|
|
assets/minimax.svg
DELETED
assets/pro.svg
DELETED
assets/qwen.svg
DELETED
assets/zai.svg
DELETED
chart/Chart.yaml
DELETED
|
@@ -1,5 +0,0 @@
|
|
| 1 |
-
apiVersion: v2
|
| 2 |
-
name: deepsite
|
| 3 |
-
version: 0.0.0-latest
|
| 4 |
-
type: application
|
| 5 |
-
icon: https://huggingface.co/front/assets/huggingface_logo-noborder.svg
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chart/env/prod.yaml
DELETED
|
@@ -1,59 +0,0 @@
|
|
| 1 |
-
nodeSelector:
|
| 2 |
-
role-deepsite: "true"
|
| 3 |
-
|
| 4 |
-
tolerations:
|
| 5 |
-
- key: "huggingface.co/deepsite"
|
| 6 |
-
operator: "Equal"
|
| 7 |
-
value: "true"
|
| 8 |
-
effect: "NoSchedule"
|
| 9 |
-
|
| 10 |
-
serviceAccount:
|
| 11 |
-
enabled: true
|
| 12 |
-
create: true
|
| 13 |
-
name: deepsite-prod
|
| 14 |
-
|
| 15 |
-
ingress:
|
| 16 |
-
path: "/"
|
| 17 |
-
annotations:
|
| 18 |
-
alb.ingress.kubernetes.io/healthcheck-path: "/api/healthcheck"
|
| 19 |
-
alb.ingress.kubernetes.io/listen-ports: "[{\"HTTP\": 80}, {\"HTTPS\": 443}]"
|
| 20 |
-
alb.ingress.kubernetes.io/load-balancer-name: "hub-utils-prod-cloudfront"
|
| 21 |
-
alb.ingress.kubernetes.io/group.name: "hub-utils-prod-cloudfront"
|
| 22 |
-
alb.ingress.kubernetes.io/scheme: "internal"
|
| 23 |
-
alb.ingress.kubernetes.io/ssl-redirect: "443"
|
| 24 |
-
alb.ingress.kubernetes.io/tags: "Env=prod,Project=hub,Terraform=true"
|
| 25 |
-
alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30
|
| 26 |
-
alb.ingress.kubernetes.io/target-type: "ip"
|
| 27 |
-
alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:us-east-1:707930574880:certificate/5b25b145-75db-4837-b9f3-7f238ba8a9c7,arn:aws:acm:us-east-1:707930574880:certificate/bfdf509c-f44b-400f-b9e1-6f7a861abe91"
|
| 28 |
-
kubernetes.io/ingress.class: "alb"
|
| 29 |
-
|
| 30 |
-
networkPolicy:
|
| 31 |
-
enabled: true
|
| 32 |
-
allowedBlocks:
|
| 33 |
-
- 10.0.0.0/16
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
ingressInternal:
|
| 37 |
-
enabled: false
|
| 38 |
-
|
| 39 |
-
envVars:
|
| 40 |
-
NEXTAUTH_URL: https://deepsite.hf.co/api/auth
|
| 41 |
-
|
| 42 |
-
infisical:
|
| 43 |
-
enabled: true
|
| 44 |
-
env: "prod-us-east-1"
|
| 45 |
-
|
| 46 |
-
autoscaling:
|
| 47 |
-
enabled: true
|
| 48 |
-
minReplicas: 1
|
| 49 |
-
maxReplicas: 10
|
| 50 |
-
targetMemoryUtilizationPercentage: "50"
|
| 51 |
-
targetCPUUtilizationPercentage: "50"
|
| 52 |
-
|
| 53 |
-
resources:
|
| 54 |
-
requests:
|
| 55 |
-
cpu: 2
|
| 56 |
-
memory: 4Gi
|
| 57 |
-
limits:
|
| 58 |
-
cpu: 4
|
| 59 |
-
memory: 8Gi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|