import express from "express"; import path from "path"; import { fileURLToPath } from "url"; import dotenv from "dotenv"; import cookieParser from "cookie-parser"; import fs from "fs"; // 使用 ESM 语法导入 fs 模块 import { createRepo, uploadFiles, whoAmI, spaceInfo, fileExists, } from "@huggingface/hub"; import { InferenceClient } from "@huggingface/inference"; import bodyParser from "body-parser"; import checkUser from "./middlewares/checkUser.js"; import { PROVIDERS, DEFAULT_SYSTEM_PROMPT, convertToOpenAIFormat, convertToHFFormat } from "./utils/providers.js"; import { COLORS } from "./utils/colors.js"; // Load environment variables from .env file dotenv.config(); // 设置环境变量如果不存在 process.env.APP_PORT = 7860; // 打印环境变量(排除敏感信息) console.log('===== 环境变量 ====='); console.log('NODE_ENV:', process.env.NODE_ENV); console.log('APP_PORT:', process.env.APP_PORT); console.log('DEFAULT_MODEL_ID:', process.env.DEFAULT_MODEL_ID); console.log('REDIRECT_URI:', process.env.REDIRECT_URI); console.log('OPENAI_COMPATIBLE_ENDPOINT:', process.env.OPENAI_COMPATIBLE_ENDPOINT ? '已设置' : '未设置'); console.log('OPENAI_COMPATIBLE_MODEL_ID:', process.env.OPENAI_COMPATIBLE_MODEL_ID); console.log('==================='); const app = express(); const ipAddresses = new Map(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Hugging Face Spaces 使用 7860 端口 const PORT = 7860; const REDIRECT_URI = process.env.REDIRECT_URI || `http://localhost:${PORT}/auth/login`; const MODEL_ID = process.env.DEFAULT_MODEL_ID || "deepseek-ai/DeepSeek-V3-0324"; const MAX_REQUESTS_PER_IP = 4; app.use((req, res, next) => { console.log(`收到请求: ${req.method} ${req.url}`); next(); }); app.use(cookieParser()); app.use(express.static(path.join(__dirname, "dist"), { index: false, setHeaders: (res, filePath) => { console.log(`提供静态文件: ${filePath}`); // 移除可能导致问题的安全头部 res.removeHeader('Feature-Policy'); res.removeHeader('Permissions-Policy'); // 设置适当的内容安全策略 res.setHeader('Content-Security-Policy', "default-src 'self' https: data: 'unsafe-inline' 'unsafe-eval';"); } })); app.use(bodyParser.json()); const getPTag = (repoId) => { return `

Made with DeepSite LogoDeepSite - 🧬 Remix

`; }; app.get("/api/login", (_req, res) => { res.redirect( 302, `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` ); }); 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 request_auth = 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: code, redirect_uri: REDIRECT_URI, }), }); const response = await request_auth.json(); if (!response.access_token) { return res.redirect(302, "/"); } res.cookie("hf_token", response.access_token, { httpOnly: false, secure: true, sameSite: "none", maxAge: 30 * 24 * 60 * 60 * 1000, }); return res.redirect(302, "/"); }); app.get("/auth/logout", (req, res) => { res.clearCookie("hf_token", { httpOnly: false, secure: true, sameSite: "none", }); return res.redirect(302, "/"); }); app.get("/api/@me", checkUser, async (req, res) => { const { hf_token } = req.cookies; try { const request_user = await fetch("https://huggingface.co/oauth/userinfo", { headers: { Authorization: `Bearer ${hf_token}`, }, }); const user = await request_user.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, }); } }); app.post("/api/deploy", checkUser, async (req, res) => { const { html, title, path } = req.body; if (!html || !title) { return res.status(400).send({ ok: false, message: "Missing required fields", }); } const { hf_token } = req.cookies; try { const repo = { type: "space", name: path ?? "", }; let readme; let newHtml = html; if (!path || path === "") { const { name: username } = await whoAmI({ accessToken: hf_token }); const newTitle = title .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .split("-") .filter(Boolean) .join("-") .slice(0, 96); const repoId = `${username}/${newTitle}`; repo.name = repoId; await createRepo({ repo, accessToken: hf_token, }); const colorFrom = COLORS[Math.floor(Math.random() * COLORS.length)]; const colorTo = COLORS[Math.floor(Math.random() * COLORS.length)]; readme = `--- title: ${newTitle} emoji: 🐳 colorFrom: ${colorFrom} colorTo: ${colorTo} sdk: static pinned: false tags: - deepsite --- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference`; } newHtml = html.replace(/<\/body>/, `${getPTag(repo.name)}`); const file = new Blob([newHtml], { type: "text/html" }); file.name = "index.html"; // Add name property to the Blob const files = [file]; if (readme) { const readmeFile = new Blob([readme], { type: "text/markdown" }); readmeFile.name = "README.md"; // Add name property to the Blob files.push(readmeFile); } await uploadFiles({ repo, files, accessToken: hf_token, }); return res.status(200).send({ ok: true, path: repo.name }); } catch (err) { return res.status(500).send({ ok: false, message: err.message, }); } }); // 添加OpenAI兼容的流式响应处理函数 const handleOpenAIStream = async (response, res) => { const reader = response.body.getReader(); const decoder = new TextDecoder(); let completeResponse = ""; try { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.trim() === '') continue; if (line.trim() === 'data: [DONE]') break; if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); const content = data.choices[0]?.delta?.content; if (content) { res.write(content); completeResponse += content; if (completeResponse.includes("")) { return; } } } catch (e) { console.error('Error parsing SSE message:', e); } } } } } catch (error) { console.error('Stream reading error:', error); throw error; } }; app.post("/api/ask-ai", async (req, res) => { const { prompt, html, previousPrompt, provider, customConfig } = req.body; if (!prompt) { return res.status(400).send({ ok: false, message: "Missing required fields", }); } const { hf_token } = req.cookies; let token = hf_token; const ip = req.headers["x-forwarded-for"]?.split(",")[0].trim() || req.headers["x-real-ip"] || req.socket.remoteAddress || req.ip || "0.0.0.0"; if (!hf_token) { ipAddresses.set(ip, (ipAddresses.get(ip) || 0) + 1); if (ipAddresses.get(ip) > MAX_REQUESTS_PER_IP) { return res.status(429).send({ ok: false, openLogin: true, message: "Log In to continue using the service", }); } token = process.env.DEFAULT_HF_TOKEN; } // Set up response headers for streaming res.setHeader("Content-Type", "text/plain"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); let client; let selectedProvider; try { if (provider === "custom" && customConfig) { selectedProvider = { ...PROVIDERS.custom, ...customConfig, }; const messages = [ { role: "system", content: selectedProvider.systemPrompt || DEFAULT_SYSTEM_PROMPT, }, ...(previousPrompt ? [ { role: "user", content: previousPrompt, }, ] : []), ...(html ? [ { role: "assistant", content: `The current code is: ${html}.`, }, ] : []), { role: "user", content: prompt, }, ]; if (selectedProvider.apiType === 'openai') { // 使用OpenAI兼容的API格式 const response = await fetch(`${selectedProvider.endpoint}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${selectedProvider.apiKey}`, }, body: JSON.stringify(convertToOpenAIFormat(messages)), }); if (!response.ok) { throw new Error(`API request failed with status ${response.status}`); } await handleOpenAIStream(response, res); } else { // 使用HuggingFace格式 client = new InferenceClient({ endpoint: selectedProvider.endpoint, token: selectedProvider.apiKey || token, }); const chatCompletion = client.chatCompletionStream(convertToHFFormat(messages)); let completeResponse = ""; while (true) { const { done, value } = await chatCompletion.next(); if (done) break; const chunk = value.choices[0]?.delta?.content; if (chunk) { res.write(chunk); completeResponse += chunk; if (completeResponse.includes("")) { break; } } } } } else { // 使用默认配置 client = new InferenceClient(token); selectedProvider = provider === "auto" ? PROVIDERS.novita : PROVIDERS[provider] ?? PROVIDERS.novita; let TOKENS_USED = prompt?.length; if (previousPrompt) TOKENS_USED += previousPrompt.length; if (html) TOKENS_USED += html.length; if (provider !== "auto" && TOKENS_USED >= selectedProvider.max_tokens) { return res.status(400).send({ ok: false, openSelectProvider: true, message: `Context is too long. ${selectedProvider.name} allow ${selectedProvider.max_tokens} max tokens.`, }); } const chatCompletion = client.chatCompletionStream({ model: selectedProvider.model_id || MODEL_ID, provider: selectedProvider.id, messages: [ { role: "system", content: selectedProvider.systemPrompt || DEFAULT_SYSTEM_PROMPT, }, ...(previousPrompt ? [ { role: "user", content: previousPrompt, }, ] : []), ...(html ? [ { role: "assistant", content: `The current code is: ${html}.`, }, ] : []), { role: "user", content: prompt, }, ], ...(selectedProvider.id !== "sambanova" && selectedProvider.max_tokens ? { max_tokens: selectedProvider.max_tokens, } : {}), }); let completeResponse = ""; while (true) { const { done, value } = await chatCompletion.next(); if (done) { break; } const chunk = value.choices[0]?.delta?.content; if (chunk) { if (provider !== "sambanova") { res.write(chunk); completeResponse += chunk; if (completeResponse.includes("")) { break; } } else { let newChunk = chunk; if (chunk.includes("")) { newChunk = newChunk.replace(/<\/html>[\s\S]*/, ""); } completeResponse += newChunk; res.write(newChunk); if (newChunk.includes("")) { break; } } } } } res.end(); } catch (error) { if (!res.headersSent) { res.status(500).send({ ok: false, message: error.message || "An error occurred while processing your request.", }); } else { res.end(); } } }); app.post("/api/custom-model", checkUser, async (req, res) => { const { endpoint, apiKey, model_id, max_tokens, name, systemPrompt, apiType = 'hf' // 默认使用HuggingFace格式 } = req.body; if (!endpoint || !model_id) { return res.status(400).send({ ok: false, message: "Missing required fields: endpoint and model_id", }); } try { // 验证端点可用性 if (apiType === 'openai') { // 验证OpenAI兼容的API端点 const response = await fetch(`${endpoint}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, }, body: JSON.stringify({ model: model_id, messages: [{ role: "system", content: "Hello" }], max_tokens: 1, }), }); if (!response.ok) { throw new Error(`API endpoint validation failed: ${response.statusText}`); } } else { // 验证HuggingFace格式的端点 const client = new InferenceClient({ endpoint, token: apiKey, }); await client.ping(); } // 更新自定义提供商配置 PROVIDERS.custom = { ...PROVIDERS.custom, endpoint, model_id, apiKey, apiType, max_tokens: max_tokens || 0, name: name || "自定义模型", systemPrompt: systemPrompt || DEFAULT_SYSTEM_PROMPT, }; res.status(200).send({ ok: true, message: "Custom model configured successfully", provider: PROVIDERS.custom, }); } catch (error) { res.status(500).send({ ok: false, message: error.message || "Failed to configure custom model", }); } }); app.get("/api/remix/:username/:repo", async (req, res) => { const { username, repo } = req.params; const { hf_token } = req.cookies; const token = hf_token || process.env.DEFAULT_HF_TOKEN; const repoId = `${username}/${repo}`; const space = await spaceInfo({ name: repoId, }); console.log(space); if (!space || space.sdk !== "static" || space.private) { return res.status(404).send({ ok: false, message: "Space not found", }); } const url = `https://huggingface.co/spaces/${repoId}/raw/main/index.html`; const response = await fetch(url); if (!response.ok) { return res.status(404).send({ ok: false, message: "Space not found", }); } let html = await response.text(); // remove the last p tag including this url https://enzostvs-deepsite.hf.space html = html.replace(getPTag(repoId), ""); res.status(200).send({ ok: true, html, }); }); // 添加根路由的专门处理 app.get("/", (req, res) => { console.log("接收到根路径请求"); console.log("请求头:", req.headers); // 直接提供简单的页面而不是依赖构建的应用 res.send(` DeepSite - AI驱动的前端助手

DeepSite

一个强大的 AI 驱动的前端开发助手,帮助你快速创建和优化网页。现在支持自定义模型和 OpenAI 兼容 API。

🎨 智能UI生成

快速生成美观的用户界面

🔧 代码优化

智能优化和改进代码

🚀 实时预览

即时查看设计效果

🔄 自定义模型

支持接入自定义AI模型

登录使用 查看源码
`); }); // 将 /debug 路由放到开头部分(在其他路由之前) // 添加调试路由 app.get("/debug", (req, res) => { console.log("处理 /debug 请求"); const info = { env: process.env.NODE_ENV, port: PORT, time: new Date().toISOString(), static_dir: path.join(__dirname, "dist"), files: fs.readdirSync(path.join(__dirname, "dist")), headers: req.headers, cookies: req.cookies }; // 禁用缓存 res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); res.setHeader('Surrogate-Control', 'no-store'); // 设置内容类型为 JSON res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify(info, null, 2)); }); // 修改通配符路由,排除 /debug 路径 app.get("*", (req, res) => { // 跳过 /debug 请求 if (req.url === '/debug') { return; } console.log(`通配符路由请求: ${req.url}`); const indexPath = path.join(__dirname, "dist", "index.html"); if (fs.existsSync(indexPath)) { console.log(`通配符路由提供 index.html: ${indexPath}`); res.sendFile(indexPath); } else { console.error(`通配符路由错误: index.html 文件不存在: ${indexPath}`); res.status(404).send("找不到页面文件"); } }); // 在 app.listen 之前添加 // 确保 dist 目录和 index.html 存在 const distDir = path.join(__dirname, "dist"); const indexPath = path.join(distDir, "index.html"); if (!fs.existsSync(distDir)) { console.log("创建 dist 目录"); fs.mkdirSync(distDir, { recursive: true }); } // 修改 createBasicHtml 函数,创建一个更基础的 HTML 页面 const createBasicHtml = () => { return ` DeepSite

欢迎使用 DeepSite

一个强大的 AI 驱动的前端开发助手

正在加载中...

刷新页面

`; }; // 在检查 index.html 不存在时使用新的函数 if (!fs.existsSync(indexPath)) { console.log("创建基本的 index.html 文件"); fs.writeFileSync(indexPath, createBasicHtml()); } app.listen(PORT, () => { console.log(`===== 应用启动信息 =====`); console.log(`启动时间: ${new Date().toISOString()}`); console.log(`运行环境: ${process.env.NODE_ENV || 'development'}`); console.log(`服务器启动在端口: ${PORT}`); console.log(`默认模型: ${MODEL_ID}`); console.log(`静态文件目录: ${path.join(__dirname, "dist")}`); // 检查静态文件是否存在 const indexPath = path.join(__dirname, "dist", "index.html"); if (fs.existsSync(indexPath)) { console.log(`index.html 文件存在: ${indexPath}`); } else { console.error(`错误: index.html 文件不存在: ${indexPath}`); console.log(`目录内容: ${fs.readdirSync(path.join(__dirname, "dist")).join(', ') || '空目录'}`); } console.log(`=======================`); });