Single-page interactive translator that converts plain language into LinkedIn-style corporate posts and decodes them back to human speak. - Two translation modes: to LinkedIn and to Human - Cringe intensity slider (1-5) - 3 variation cards per translation with copy buttons - Dual engine support: direct API key or local proxy server - i18n toggle for Portuguese and English (UI + AI prompts) - Dark theme with LinkedIn blue accent, fully responsive - Local proxy server (server.js) bridges to the LLM CLI
96 lines
3.2 KiB
JavaScript
96 lines
3.2 KiB
JavaScript
const http = require("http");
|
|
const { spawn } = require("child_process");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const PORT = 3000;
|
|
|
|
const server = http.createServer((req, res) => {
|
|
// CORS
|
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
|
|
if (req.method === "OPTIONS") {
|
|
res.writeHead(204);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
// Serve index.html
|
|
if ((req.method === "GET" || req.method === "HEAD") && (req.url === "/" || req.url === "/index.html")) {
|
|
const filePath = path.join(__dirname, "index.html");
|
|
fs.readFile(filePath, (err, data) => {
|
|
if (err) {
|
|
res.writeHead(500);
|
|
res.end("Erro ao carregar index.html");
|
|
return;
|
|
}
|
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
res.end(data);
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Translation endpoint
|
|
if (req.method === "POST" && req.url === "/translate") {
|
|
let body = "";
|
|
req.on("data", (chunk) => { body += chunk; });
|
|
req.on("end", () => {
|
|
let parsed;
|
|
try {
|
|
parsed = JSON.parse(body);
|
|
} catch (e) {
|
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
res.end(JSON.stringify({ error: "JSON inválido" }));
|
|
return;
|
|
}
|
|
|
|
const { prompt } = parsed;
|
|
if (!prompt || typeof prompt !== "string") {
|
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
res.end(JSON.stringify({ error: "Campo 'prompt' é obrigatório" }));
|
|
return;
|
|
}
|
|
|
|
console.log(`[Jargonify] Traduzindo (${prompt.length} chars)...`);
|
|
|
|
const claudePath = "/opt/homebrew/bin/claude";
|
|
const childEnv = { ...process.env, PATH: process.env.PATH + ":/opt/homebrew/bin", LANG: "en_US.UTF-8", LC_ALL: "en_US.UTF-8" };
|
|
delete childEnv.CLAUDECODE;
|
|
delete childEnv.CLAUDE_CODE_ENTRYPOINT;
|
|
|
|
const child = spawn(claudePath, ["-p", "-"], { encoding: "utf-8", env: childEnv, timeout: 60000 });
|
|
let stdout = "";
|
|
let stderr = "";
|
|
child.stdout.on("data", (data) => { stdout += data; });
|
|
child.stderr.on("data", (data) => { stderr += data; });
|
|
child.on("close", (code) => {
|
|
if (code !== 0) {
|
|
console.error("[Jargonify] Erro (code " + code + "):", stderr);
|
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
res.end(JSON.stringify({ error: `Erro ao executar Claude CLI: ${stderr || "exit code " + code}` }));
|
|
return;
|
|
}
|
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
res.end(JSON.stringify({ result: stdout.trim() }));
|
|
});
|
|
child.on("error", (err) => {
|
|
console.error("[Jargonify] Spawn erro:", err.message);
|
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
res.end(JSON.stringify({ error: `Erro ao iniciar Claude CLI: ${err.message}` }));
|
|
});
|
|
child.stdin.write(prompt);
|
|
child.stdin.end();
|
|
});
|
|
return;
|
|
}
|
|
|
|
res.writeHead(404);
|
|
res.end("Not found");
|
|
});
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`\n ✨ Jargonify Server rodando em http://localhost:${PORT}\n`);
|
|
console.log(` Abra no browser para usar com traduções via Claude.\n`);
|
|
});
|