aborboa/A6Life_HTMLer
1
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});56 57app.get( "/test", (req, res) =>{58 res.send( "Hola" );59});60 61app.get("/auth/login", async (req, res) => {62 const { code } = req.query;63 64 if (!code) {65 return res.redirect(302, "/");66 }67 const Authorization = `Basic ${Buffer.from(68 `${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}`69 ).toString("base64")}`;70 71 const request_auth = await fetch("https://huggingface.co/oauth/token", {72 method: "POST",73 headers: {74 "Content-Type": "application/x-www-form-urlencoded",75 Authorization,76 },77 body: new URLSearchParams({78 grant_type: "authorization_code",79 code: code,80 redirect_uri: REDIRECT_URI,81 }),82 });83 84 const response = await request_auth.json();85 86 if (!response.access_token) {87 return res.redirect(302, "/");88 }89 90 res.cookie("hf_token", response.access_token, {91 httpOnly: false,92 secure: true,93 sameSite: "none",94 maxAge: 30 * 24 * 60 * 60 * 1000,95 });96 97 return res.redirect(302, "/");98});99app.get("/auth/logout", (req, res) => {100 res.clearCookie("hf_token", {101 httpOnly: false,102 secure: true,103 sameSite: "none",104 });105 return res.redirect(302, "/");106});107 108app.get("/api/@me", checkUser, async (req, res) => {109 const { hf_token } = req.cookies;110 try {111 const request_user = await fetch("https://huggingface.co/oauth/userinfo", {112 headers: {113 Authorization: `Bearer ${hf_token}`,114 },115 });116 117 const user = await request_user.json();118 res.send(user);119 } catch (err) {120 res.clearCookie("hf_token", {121 httpOnly: false,122 secure: true,123 sameSite: "none",124 });125 res.status(401).send({126 ok: false,127 message: err.message,128 });129 }130});131 132app.post("/api/deploy", checkUser, async (req, res) => {133 const { html, title, path } = req.body;134 if (!html || (!path && !title)) {135 return res.status(400).send({136 ok: false,137 message: "Missing required fields",138 });139 }140 141 const { hf_token } = req.cookies;142 try {143 const repo = {144 type: "space",145 name: path ?? "",146 };147 148 let readme;149 let newHtml = html;150 151 if (!path || path === "") {152 const { name: username } = await whoAmI({ accessToken: hf_token });153 const newTitle = title154 .toLowerCase()155 .replace(/[^a-z0-9]+/g, "-")156 .split("-")157 .filter(Boolean)158 .join("-")159 .slice(0, 96);160 161 const repoId = `${username}/${newTitle}`;162 repo.name = repoId;163 164 await createRepo({165 repo,166 accessToken: hf_token,167 });168 const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)];169 const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)];170 readme = `---171title: ${newTitle}172emoji: 🐳173colorFrom: ${colorFrom}174colorTo: ${colorTo}175sdk: static176pinned: false177tags:178 - deepsite179---180 181Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`;182 }183 184 newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}</body>`);185 const file = new Blob([newHtml], { type: "text/html" });186 file.name = "index.html"; // Add name property to the Blob187 188 const files = [file];189 if (readme) {190 const readmeFile = new Blob([readme], { type: "text/markdown" });191 readmeFile.name = "README.md"; // Add name property to the Blob192 files.push(readmeFile);193 }194 await uploadFiles({195 repo,196 files,197 accessToken: hf_token,198 });199 return res.status(200).send({ ok: true, path: repo.name });200 } catch (err) {201 return res.status(500).send({202 ok: false,203 message: err.message,204 });205 }206});207 208app.post("/api/ask-ai", async (req, res) => {209 const { prompt, html, previousPrompt, provider } = req.body;210 if (!prompt) {211 return res.status(400).send({212 ok: false,213 message: "Missing required fields",214 });215 }216 217 const { hf_token } = req.cookies;218 let token = hf_token;219 const ip =220 req.headers["x-forwarded-for"]?.split(",")[0].trim() ||221 req.headers["x-real-ip"] ||222 req.socket.remoteAddress ||223 req.ip ||224 "0.0.0.0";225 226 if (!hf_token) {227 ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1);228 if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) {229 return res.status(429).send({230 ok: false,231 openLogin: true,232 message: "Log In to continue using the service",233 });234 }235 236 token = process.env.DEFAULT_HF_TOKEN;237 }238 239 // Set up response headers for streaming240 res.setHeader("Content-Type", "text/plain");241 res.setHeader("Cache-Control", "no-cache");242 res.setHeader("Connection", "keep-alive");243 244 const client = new InferenceClient(token);245 let completeResponse = "";246 247 let TOKENS_USED = prompt?.length;248 if (previousPrompt) TOKENS_USED += previousPrompt.length;249 if (html) TOKENS_USED += html.length;250 251 const DEFAULT_PROVIDER = PROVIDERS.novita;252 // const selectedProvider =253 // provider === "auto"254 // ? TOKENS_USED < PROVIDERS.sambanova.max_tokens255 // ? PROVIDERS.sambanova256 // : DEFAULT_PROVIDER257 // : PROVIDERS[provider] ?? DEFAULT_PROVIDER;258 const selectedProvider =259 provider === "auto"260 ? DEFAULT_PROVIDER261 : PROVIDERS[provider] ?? DEFAULT_PROVIDER;262 263 if (provider !== "auto" && TOKENS_USED >= selectedProvider.max_tokens) {264 return res.status(400).send({265 ok: false,266 openSelectProvider: true,267 message: `Context is too long. ${selectedProvider.name} allow ${selectedProvider.max_tokens} max tokens.`,268 });269 }270 271 try {272 const chatCompletion = client.chatCompletionStream({273 model: MODEL_ID,274 provider: selectedProvider.id,275 messages: [276 {277 role: "system",278 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`,279 },280 ...(previousPrompt281 ? [282 {283 role: "user",284 content: previousPrompt,285 },286 ]287 : []),288 ...(html289 ? [290 {291 role: "assistant",292 content: `The current code is: ${html}.`,293 },294 ]295 : []),296 {297 role: "user",298 content: prompt,299 },300 ],301 ...(selectedProvider.id !== "sambanova"302 ? {303 max_tokens: selectedProvider.max_tokens,304 }305 : {}),306 });307 308 while (true) {309 const { done, value } = await chatCompletion.next();310 if (done) {311 break;312 }313 const chunk = value.choices[0]?.delta?.content;314 if (chunk) {315 if (provider !== "sambanova") {316 res.write(chunk);317 completeResponse += chunk;318 319 if (completeResponse.includes("</html>")) {320 break;321 }322 } else {323 let newChunk = chunk;324 if (chunk.includes("</html>")) {325 // Replace everything after the last </html> tag with an empty string326 newChunk = newChunk.replace(/<\/html>[\s\S]*/, "</html>");327 }328 completeResponse += newChunk;329 res.write(newChunk);330 console.log( newChunk );331 if (newChunk.includes("</html>")) {332 break;333 }334 }335 }336 }337 // End the response stream338 res.end();339 } catch (error) {340 if (error.message.includes("exceeded your monthly included credits")) {341 return res.status(402).send({342 ok: false,343 openProModal: true,344 message: error.message,345 });346 }347 if (!res.headersSent) {348 res.status(500).send({349 ok: false,350 message:351 error.message || "An error occurred while processing your request.",352 });353 } else {354 // Otherwise end the stream355 res.end();356 }357 }358});359 360app.get("/api/remix/:username/:repo", async (req, res) => {361 const { username, repo } = req.params;362 const { hf_token } = req.cookies;363 364 const token = hf_token || process.env.DEFAULT_HF_TOKEN;365 366 const repoId = `${username}/${repo}`;367 368 const url = `https://huggingface.co/spaces/${repoId}/raw/main/index.html`;369 try {370 const space = await spaceInfo({371 name: repoId,372 accessToken: token,373 additionalFields: ["author"],374 });375 376 if (!space || space.sdk !== "static" || space.private) {377 return res.status(404).send({378 ok: false,379 message: "Space not found",380 });381 }382 383 const response = await fetch(url);384 if (!response.ok) {385 return res.status(404).send({386 ok: false,387 message: "Space not found",388 });389 }390 let html = await response.text();391 // remove the last p tag including this url https://enzostvs-deepsite.hf.space392 html = html.replace(getPTag(repoId), "");393 394 let user = null;395 396 if (token) {397 const request_user = await fetch(398 "https://huggingface.co/oauth/userinfo",399 {400 headers: {401 Authorization: `Bearer ${hf_token}`,402 },403 }404 )405 .then((res) => res.json())406 .catch(() => null);407 408 user = request_user;409 }410 411 res.status(200).send({412 ok: true,413 html,414 isOwner: space.author === user?.preferred_username,415 path: repoId,416 });417 } catch (error) {418 return res.status(500).send({419 ok: false,420 message: error.message,421 });422 }423});424 425app.get("*", (_req, res) => {426 res.sendFile(path.join(__dirname, "dist", "index.html"));427});428 429app.listen(PORT, () => {430 console.log(`Server is running on port ${PORT}`);431});432 