facessss/deepsite
0
1import express from "express";2import path from "path";3import { fileURLToPath } from "url";4import dotenv from "dotenv";5import cookieParser from "cookie-parser";6import {7 createRepo,8 uploadFiles,9 whoAmI,10 spaceInfo,11 fileExists,12} from "@huggingface/hub";13import { InferenceClient } from "@huggingface/inference";14import bodyParser from "body-parser";15 16import checkUser from "./middlewares/checkUser.js";17import { PROVIDERS } from "./utils/providers.js";18import { COLORS } from "./utils/colors.js";19 20// Load environment variables from .env file21dotenv.config();22 23const app = express();24 25const ipAddresses = new Map();26 27const __filename = fileURLToPath(import.meta.url);28const __dirname = path.dirname(__filename);29 30const PORT = process.env.APP_PORT || 3000;31const REDIRECT_URI =32 process.env.REDIRECT_URI || `http://localhost:${PORT}/auth/login`;33const MODEL_ID = "deepseek-ai/DeepSeek-V3-0324";34const MAX_REQUESTS_PER_IP = 2;35 36app.use(cookieParser());37app.use(bodyParser.json());38app.use(express.static(path.join(__dirname, "dist")));39 40const getPTag = (repoId) => {41 return `<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=${repoId}" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p>`;42};43 44app.get("/api/login", (_req, res) => {45 // res.redirect(46 // 302,47 // `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`48 // );49 // redirect in new tab50 const redirectUrl = `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`;51 res.status(200).send({52 ok: true,53 redirectUrl,54 });55});56app.get("/auth/login", async (req, res) => {57 const { code } = req.query;58 59 if (!code) {60 return res.redirect(302, "/");61 }62 const Authorization = `Basic ${Buffer.from(63 `${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`64 ).toString("base64")}`;65 66 const request_auth = await fetch("https://huggingface.co/oauth/token", {67 method: "POST",68 headers: {69 "Content-Type": "application/x-www-form-urlencoded",70 Authorization,71 },72 body: new URLSearchParams({73 grant_type: "authorization_code",74 code: code,75 redirect_uri: REDIRECT_URI,76 }),77 });78 79 const response = await request_auth.json();80 81 if (!response.access_token) {82 return res.redirect(302, "/");83 }84 85 res.cookie("hf_token", response.access_token, {86 httpOnly: false,87 secure: true,88 sameSite: "none",89 maxAge: 30 * 24 * 60 * 60 * 1000,90 });91 92 return res.redirect(302, "/");93});94app.get("/auth/logout", (req, res) => {95 res.clearCookie("hf_token", {96 httpOnly: false,97 secure: true,98 sameSite: "none",99 });100 return res.redirect(302, "/");101});102 103app.get("/api/@me", checkUser, async (req, res) => {104 const { hf_token } = req.cookies;105 try {106 const request_user = await fetch("https://huggingface.co/oauth/userinfo", {107 headers: {108 Authorization: `Bearer ${hf_token}`,109 },110 });111 112 const user = await request_user.json();113 res.send(user);114 } catch (err) {115 res.clearCookie("hf_token", {116 httpOnly: false,117 secure: true,118 sameSite: "none",119 });120 res.status(401).send({121 ok: false,122 message: err.message,123 });124 }125});126 127app.post("/api/deploy", checkUser, async (req, res) => {128 const { html, title, path } = req.body;129 if (!html || (!path && !title)) {130 return res.status(400).send({131 ok: false,132 message: "Missing required fields",133 });134 }135 136 const { hf_token } = req.cookies;137 try {138 const repo = {139 type: "space",140 name: path ?? "",141 };142 143 let readme;144 let newHtml = html;145 146 if (!path || path === "") {147 const { name: username } = await whoAmI({ accessToken: hf_token });148 const newTitle = title149 .toLowerCase()150 .replace(/[^a-z0-9]+/g, "-")151 .split("-")152 .filter(Boolean)153 .join("-")154 .slice(0, 96);155 156 const repoId = `${username}/${newTitle}`;157 repo.name = repoId;158 159 await createRepo({160 repo,161 accessToken: hf_token,162 });163 const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];164 const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];165 readme = `---166title: ${newTitle}167emoji: 🐳168colorFrom: ${colorFrom}169colorTo: ${colorTo}170sdk: static171pinned: false172tags:173 - deepsite174---175 176Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`;177 }178 179 newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);180 const file = new Blob([newHtml], { type: "text/html" });181 file.name = "index.html"; // Add name property to the Blob182 183 const files = [file];184 if (readme) {185 const readmeFile = new Blob([readme], { type: "text/markdown" });186 readmeFile.name = "README.md"; // Add name property to the Blob187 files.push(readmeFile);188 }189 await uploadFiles({190 repo,191 files,192 accessToken: hf_token,193 });194 return res.status(200).send({ ok: true, path: repo.name });195 } catch (err) {196 return res.status(500).send({197 ok: false,198 message: err.message,199 });200 }201});202 203app.post("/api/ask-ai", async (req, res) => {204 const { prompt, html, previousPrompt, provider } = req.body;205 if (!prompt) {206 return res.status(400).send({207 ok: false,208 message: "Missing required fields",209 });210 }211 212 const { hf_token } = req.cookies;213 let token = hf_token;214 const ip =215 req.headers["x-forwarded-for"]?.split(",")[0].trim() ||216 req.headers["x-real-ip"] ||217 req.socket.remoteAddress ||218 req.ip ||219 "0.0.0.0";220 221 if (!hf_token) {222 ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);223 if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {224 return res.status(429).send({225 ok: false,226 openLogin: true,227 message: "Log In to continue using the service",228 });229 }230 231 token = process.env.DEFAULT_HF_TOKEN;232 }233 234 // Set up response headers for streaming235 res.setHeader("Content-Type", "text/plain");236 res.setHeader("Cache-Control", "no-cache");237 res.setHeader("Connection", "keep-alive");238 239 const client = new InferenceClient(token);240 let completeResponse = "";241 242 let TOKENS_USED = prompt?.length;243 if (previousPrompt) TOKENS_USED += previousPrompt.length;244 if (html) TOKENS_USED += html.length;245 246 const DEFAULT_PROVIDER = PROVIDERS.novita;247 // const selectedProvider =248 // provider === "auto"249 // ? TOKENS_USED < PROVIDERS.sambanova.max_tokens250 // ? PROVIDERS.sambanova251 // : DEFAULT_PROVIDER252 // : PROVIDERS[provider] ?? DEFAULT_PROVIDER;253 const selectedProvider =254 provider === "auto"255 ? DEFAULT_PROVIDER256 : PROVIDERS[provider] ?? DEFAULT_PROVIDER;257 258 if (provider !== "auto" && TOKENS_USED >= selectedProvider.max_tokens) {259 return res.status(400).send({260 ok: false,261 openSelectProvider: true,262 message: `Context is too long. ${selectedProvider.name} allow ${selectedProvider.max_tokens} max tokens.`,263 });264 }265 266 try {267 const chatCompletion = client.chatCompletionStream({268 model: MODEL_ID,269 provider: selectedProvider.id,270 messages: [271 {272 role: "system",273 content: `ONLY USE HTML, CSS AND JAVASCRIPT. If you want to use ICON make sure to import the library first. Try to create the best UI possible by using only HTML, CSS and JAVASCRIPT. Use as much as you can TailwindCSS for the CSS, if you can't do something with TailwindCSS, then use custom CSS (make sure to import <script src="https://cdn.tailwindcss.com"></script> in the head). Also, try to ellaborate as much as you can, to create something unique. ALWAYS GIVE THE RESPONSE INTO A SINGLE HTML FILE`,274 },275 ...(previousPrompt276 ? [277 {278 role: "user",279 content: previousPrompt,280 },281 ]282 : []),283 ...(html284 ? [285 {286 role: "assistant",287 content: `The current code is: ${html}.`,288 },289 ]290 : []),291 {292 role: "user",293 content: prompt,294 },295 ],296 ...(selectedProvider.id !== "sambanova"297 ? {298 max_tokens: selectedProvider.max_tokens,299 }300 : {}),301 });302 303 while (true) {304 const { done, value } = await chatCompletion.next();305 if (done) {306 break;307 }308 const chunk = value.choices[0]?.delta?.content;309 if (chunk) {310 if (provider !== "sambanova") {311 res.write(chunk);312 completeResponse += chunk;313 314 if (completeResponse.includes("</html>")) {315 break;316 }317 } else {318 let newChunk = chunk;319 if (chunk.includes("</html>")) {320 // Replace everything after the last </html> tag with an empty string321 newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");322 }323 completeResponse += newChunk;324 res.write(newChunk);325 if (newChunk.includes("</html>")) {326 break;327 }328 }329 }330 }331 // End the response stream332 res.end();333 } catch (error) {334 if (error.message.includes("exceeded your monthly included credits")) {335 return res.status(402).send({336 ok: false,337 openProModal: true,338 message: error.message,339 });340 }341 if (!res.headersSent) {342 res.status(500).send({343 ok: false,344 message:345 error.message || "An error occurred while processing your request.",346 });347 } else {348 // Otherwise end the stream349 res.end();350 }351 }352});353 354app.get("/api/remix/:username/:repo", async (req, res) => {355 const { username, repo } = req.params;356 const { hf_token } = req.cookies;357 358 const token = hf_token || process.env.DEFAULT_HF_TOKEN;359 360 const repoId = `${username}/${repo}`;361 362 const url = `https://huggingface.co/spaces/${repoId}/raw/main/index.html`;363 try {364 const space = await spaceInfo({365 name: repoId,366 accessToken: token,367 additionalFields: ["author"],368 });369 370 if (!space || space.sdk !== "static" || space.private) {371 return res.status(404).send({372 ok: false,373 message: "Space not found",374 });375 }376 377 const response = await fetch(url);378 if (!response.ok) {379 return res.status(404).send({380 ok: false,381 message: "Space not found",382 });383 }384 let html = await response.text();385 // remove the last p tag including this url https://enzostvs-deepsite.hf.space386 html = html.replace(getPTag(repoId), "");387 388 let user = null;389 390 if (token) {391 const request_user = await fetch(392 "https://huggingface.co/oauth/userinfo",393 {394 headers: {395 Authorization: `Bearer ${hf_token}`,396 },397 }398 )399 .then((res) => res.json())400 .catch(() => null);401 402 user = request_user;403 }404 405 res.status(200).send({406 ok: true,407 html,408 isOwner: space.author === user?.preferred_username,409 path: repoId,410 });411 } catch (error) {412 return res.status(500).send({413 ok: false,414 message: error.message,415 });416 }417});418app.get("*", (_req, res) => {419 res.sendFile(path.join(__dirname, "dist", "index.html"));420});421 422app.listen(PORT, () => {423 console.log(`Server is running on port ${PORT}`);424});425 