Spaces:
Sleeping
Sleeping
File size: 4,400 Bytes
ec675f2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | import "dotenv/config";
import express from "express";
import { chromium, Browser } from "playwright";
import { crawlPage } from "./lib/crawler";
import { chunkData } from "./lib/chunker";
import { formatAnalysisForPrompt, buildWebsitePrompt } from "./lib/ai-prompt";
import { generateWebsite } from "./lib/llm";
import { crawlQueue } from "./lib/queue";
import { parseGeneratedFiles } from "./lib/file-parser";
import { writeProjectFiles } from "./lib/project-writer";
import { validateAndSave } from "./lib/validator";
const API_KEY = process.env.CRAWL_API_KEY || "d3v-crawl-server-key-2026";
const PORT = Number(process.env.PORT) || 7860;
const HOST = "0.0.0.0";
let browser: Browser | undefined;
async function launchBrowser(): Promise<Browser> {
console.log("Launching Chromium in-process...");
return chromium.launch({
headless: true,
args: ["--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu"],
});
}
async function ensureBrowser(): Promise<Browser> {
if (!browser || !browser.isConnected()) {
browser = await launchBrowser();
}
return browser;
}
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
return Promise.race([
promise,
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
),
]);
}
const app = express();
app.use(express.json());
app.use((req, res, next) => {
if (req.path === "/health") return next();
const auth = req.headers.authorization;
if (auth !== `Bearer ${API_KEY}`) return res.status(401).json({ error: "unauthorized" });
next();
});
app.get("/crawl", async (req, res) => {
const url = req.query.url as string;
if (!url) return res.status(400).json({ error: "url query param required" });
try {
const result = await crawlQueue.run(async () => {
const b = await ensureBrowser();
return crawlPage(b, url);
});
const out = req.query.chunked === "true" ? chunkData(result) : result;
res.json(out);
} catch (err: any) {
res.status(500).json({ error: err.message });
}
});
app.post("/build", async (req, res) => {
const { prompt, reference, validate } = req.body || {};
if (!prompt) return res.status(400).json({ error: "prompt is required" });
try {
let analysisText: string | undefined;
if (reference) {
console.log("[build] starting crawl of", reference);
try {
const crawled = await withTimeout(
crawlQueue.run(async () => {
const b = await ensureBrowser();
return crawlPage(b, reference);
}),
70000,
"crawl"
);
console.log("[build] crawl finished");
analysisText = formatAnalysisForPrompt(crawled);
} catch (err: any) {
console.log("[build] crawl failed/timed out, continuing without reference:", err.message);
}
}
const fullPrompt = buildWebsitePrompt(prompt, analysisText);
console.log("[build] calling LLM...");
const rawResult = await generateWebsite(fullPrompt);
console.log("[build] LLM call finished, length:", rawResult.length);
const files = parseGeneratedFiles(rawResult);
if (files.length === 0) {
res.json({ prompt, reference: reference || null, result: rawResult, files: [] });
return;
}
const slug = prompt.toLowerCase().replace(/[^a-z0-9]+/g, "-");
const { projectDir, written } = writeProjectFiles(files, slug);
let validation = null;
if (validate) {
console.log("[build] validating (npm install + npm run build)...");
validation = await validateAndSave(projectDir);
console.log("[build] validation finished, success:", validation.success);
}
res.json({ prompt, reference: reference || null, projectDir, files: written, validation });
} catch (err: any) {
res.status(500).json({ error: err.message });
}
});
app.get("/health", (_, res) => {
res.json({ status: "ok", browserConnected: browser ? browser.isConnected() : false });
});
async function start() {
await ensureBrowser();
app.listen(PORT, HOST, () => console.log(`API server alive on http://${HOST}:${PORT}`));
}
async function shutdown() {
console.log("Shutting down API server and closing browser...");
if (browser) await browser.close();
process.exit(0);
}
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
start();
|