// server.js import express from "express"; import path from "path"; import { fileURLToPath } from "url"; import dotenv from "dotenv"; import cookieParser from "cookie-parser"; import bodyParser from "body-parser"; import fs from "fs"; import { createRepo, uploadFiles, whoAmI, spaceInfo, fileExists, } from "@huggingface/hub"; import { InferenceClient } from "@huggingface/inference"; import OpenAI from "openai"; import { TextServiceClient } from "@google-ai/generativelanguage"; import { Readable } from "stream"; import { pipeline } from "@xenova/transformers"; import checkUser from "./middlewares/checkUser.js"; import { PROVIDERS } from "./utils/providers.js"; import { COLORS } from "./utils/colors.js"; // Carrega .env dotenv.config(); const app = express(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PORT = process.env.APP_PORT || 5173; const REDIRECT_URI = process.env.REDIRECT_URI || `http://localhost:${PORT}/auth/login`; const MODEL_ID = "deepseek-ai/DeepSeek-V3-0324"; const MAX_REQUESTS_PER_IP = 2; // Cache para armazenar chaves da API inválidas const invalidApiKeys = new Set(); // Constantes para modelos locais const LOCAL_MODELS = { CHAT: "Xenova/distilgpt2", // Modelo básico para conversas CODE: "Xenova/codegen-350M-mono", // Modelo para código INSTRUCTION: "Xenova/gpt2-imdb", // Modelo para seguir instruções QA: "Xenova/distilbert-base-uncased-distilled-squad", // Perguntas e respostas SUMMARIZATION: "Xenova/distilbart-cnn-6-6", // Resumo de textos HTML: "Xenova/codegen-350M-mono", // Especializado em geração de HTML PORTUGUESE: "Xenova/gpt2-imdb", // Substitua por modelo em português se disponível }; // Função para selecionar modelo baseado no prompt function selectModelForPrompt(prompt) { const promptLower = prompt.toLowerCase(); // Detecção de idioma português const portugueseWords = ['como', 'está', 'você', 'olá', 'por', 'que', 'para', 'quero', 'preciso', 'site', 'código', 'crie']; const isPortuguese = portugueseWords.some(word => promptLower.includes(word)); if (isPortuguese && promptLower.includes("html")) { return LOCAL_MODELS.HTML; } // Detecção de tipo de tarefa if (promptLower.includes("código") || promptLower.includes("code") || promptLower.includes("programa") || promptLower.includes("function") || promptLower.includes("javascript") || promptLower.includes("css")) { return LOCAL_MODELS.CODE; } if (promptLower.includes("html") || promptLower.includes("página") || promptLower.includes("site") || promptLower.includes("website") || promptLower.includes("web")) { return LOCAL_MODELS.HTML; } if (promptLower.includes("resumo") || promptLower.includes("resume") || promptLower.includes("sumarize")) { return LOCAL_MODELS.SUMMARIZATION; } if (promptLower.includes("?") || promptLower.startsWith("o que") || promptLower.startsWith("como") || promptLower.startsWith("por que") || promptLower.startsWith("when") || promptLower.startsWith("what") || promptLower.startsWith("how")) { return LOCAL_MODELS.QA; } // Detecta instruções específicas if (promptLower.includes("crie") || promptLower.includes("faça") || promptLower.includes("implemente") || promptLower.includes("desenvolva") || promptLower.includes("gere")) { return LOCAL_MODELS.INSTRUCTION; } // Modelo padrão para conversas return isPortuguese ? LOCAL_MODELS.PORTUGUESE : LOCAL_MODELS.CHAT; } // Cache para os geradores de modelos const modelCache = {}; // Função para obter configurações ideais para cada modelo function getGenerationConfig(modelType, prompt) { // Configurações básicas const config = { max_new_tokens: 512, temperature: 0.7, do_sample: true, top_k: 50, top_p: 0.95, }; // Ajustes específicos por tipo de modelo switch (modelType) { case LOCAL_MODELS.CODE: case LOCAL_MODELS.HTML: // Geração de código precisa ser menos aleatória e mais estruturada return { ...config, max_new_tokens: 1024, // Código geralmente precisa de mais tokens temperature: 0.4, // Menos aleatoriedade para código top_p: 0.85, // Mais foco em tokens prováveis repetition_penalty: 1.2, // Evitar repetições }; case LOCAL_MODELS.QA: // Perguntas precisam de respostas mais precisas e concisas return { ...config, max_new_tokens: 300, // Respostas mais curtas temperature: 0.3, // Menos aleatoriedade para maior precisão top_k: 20, // Limita ainda mais o vocabulário }; case LOCAL_MODELS.SUMMARIZATION: // Resumos precisam ser concisos mas informativos return { ...config, max_new_tokens: 400, temperature: 0.6, no_repeat_ngram_size: 3, // Evita repetir frases de 3 palavras }; case LOCAL_MODELS.INSTRUCTION: // Para seguir instruções com criatividade return { ...config, max_new_tokens: 800, temperature: 0.8, // Mais criatividade top_p: 0.92, }; default: // Para chat e outros modelos, configuração padrão return config; } } // Função para obter ou criar pipeline para um modelo async function getModelPipeline(modelName) { if (!modelCache[modelName]) { console.log(`Carregando modelo local: ${modelName}`); modelCache[modelName] = await pipeline('text-generation', modelName); } return modelCache[modelName]; } app.use(cookieParser()); app.use(bodyParser.json()); app.use(express.static(path.join(__dirname, "dist"))); const ipAddresses = new Map(); const getPTag = (repoId) => `
`; /** Helpers **/ async function pipeStream(stream, res) { // AsyncIterable? if (stream[Symbol.asyncIterator]) { for await (const chunk of stream) { res.write(typeof chunk === "string" ? chunk : chunk.toString()); } } // EventEmitter? else if (typeof stream.on === "function") { await new Promise((resolve, reject) => { stream.on("data", (c) => res.write(c)); stream.on("end", resolve); stream.on("error", reject); }); } else { throw new Error("Stream type not supported"); } } /** AUTH & USER **/ app.get("/api/login", (_req, res) => { 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`; res.send({ ok: true, redirectUrl }); }); app.get("/auth/login", async (req, res) => { const { code } = req.query; if (!code) return res.redirect(302, "/"); const Authorization = `Basic ${Buffer.from( `${process.env.OAUTH_CLIENT_ID}:${process.env.OAUTH_CLIENT_SECRET}` ).toString("base64")}`; const tokenRes = await fetch("https://huggingface.co/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Authorization }, body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: REDIRECT_URI, }), }); const { access_token } = await tokenRes.json(); if (!access_token) return res.redirect(302, "/"); res.cookie("hf_token", access_token, { httpOnly: false, secure: true, sameSite: "none", maxAge: 30 * 24 * 60 * 60 * 1000, }); res.redirect(302, "/"); }); app.get("/auth/logout", (req, res) => { res.clearCookie("hf_token", { httpOnly: false, secure: true, sameSite: "none" }); res.redirect(302, "/"); }); app.get("/api/@me", checkUser, async (req, res) => { let { hf_token } = req.cookies; if (process.env.HF_TOKEN) { return res.send({ preferred_username: "local-use", isLocalUse: true }); } try { const userRes = await fetch("https://huggingface.co/oauth/userinfo", { headers: { Authorization: `Bearer ${hf_token}` }, }); const user = await userRes.json(); res.send(user); } catch (err) { res.clearCookie("hf_token", { httpOnly: false, secure: true, sameSite: "none" }); res.status(401).send({ ok: false, message: err.message }); } }); /** DEPLOY **/ app.post("/api/deploy", checkUser, async (req, res) => { const { html, title, path: repoPath, prompts } = req.body; if (!html || (!repoPath && !title)) { return res.status(400).send({ ok: false, message: "Missing required fields" }); } let hf_token = req.cookies.hf_token; if (process.env.HF_TOKEN) hf_token = process.env.HF_TOKEN; try { const repo = { type: "space", name: repoPath || "" }; let readme, newHtml = html; if (!repoPath) { const { name: username } = await whoAmI({ accessToken: hf_token }); const slug = title .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .split("-") .filter(Boolean) .join("-") .slice(0, 96); repo.name = `${username}/${slug}`; await createRepo({ repo, accessToken: hf_token }); const [cFrom, cTo] = [ COLORS[Math.floor(Math.random() * COLORS.length)], COLORS[Math.floor(Math.random() * COLORS.length)], ]; readme = `--- title: ${slug} emoji: 🐳 colorFrom: ${cFrom} colorTo: ${cTo} sdk: static pinned: false tags: - deepsite --- See https://huggingface.co/docs/hub/spaces-config-reference`; } newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}