| const express = require("express"); |
| const cors = require("cors"); |
| const morgan = require("morgan"); |
| const helmet = require("helmet"); |
| const chalk = require("chalk"); |
|
|
| const app = express(); |
|
|
| |
| app.use(helmet()); |
| app.use(cors()); |
| app.use(express.json({ limit: "20mb" })); |
|
|
| |
| morgan.token("local-time", () => chalk.gray(`[${new Date().toLocaleTimeString()}]`)); |
| app.use(morgan(":local-time :method :url :status :res[content-length] - :response-time ms")); |
|
|
| |
| app.get("/", (req, res) => { |
| res.json({ status: "ok", service: "Gemini REST Bridge", live: true }); |
| }); |
|
|
| |
| app.post("/", async (req, res) => { |
| const { apiKey, model = "gemini-2.5-flash-preview-tts", prompt, generationConfig } = req.body; |
|
|
| if (!apiKey) { |
| console.log(chalk.yellow(` -> [Warning] Request rejected: Missing apiKey`)); |
| return res.status(400).json({ error: "Missing apiKey" }); |
| } |
| if (!prompt) { |
| console.log(chalk.yellow(` -> [Warning] Request rejected: Missing prompt`)); |
| return res.status(400).json({ error: "Missing prompt" }); |
| } |
|
|
| const logPrefix = chalk.cyan(`[Gemini Request]`); |
| console.log(`${logPrefix} Processing prompt (${prompt.length} chars) using model: ${chalk.magenta(model)}`); |
|
|
| try { |
| const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`; |
| |
| const defaultConfig = { |
| responseModalities: ["AUDIO"], |
| speechConfig: { |
| voiceConfig: { |
| prebuiltVoiceConfig: { |
| voiceName: "Kore" |
| } |
| } |
| } |
| }; |
|
|
| const response = await fetch(url, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ |
| contents: [{ parts: [{ text: prompt }] }], |
| generationConfig: generationConfig || defaultConfig |
| }) |
| }); |
|
|
| const data = await response.json(); |
|
|
| if (!response.ok) { |
| console.log(chalk.red(` -> [Gemini API Error] Status: ${response.status}`)); |
| |
| |
| console.log(chalk.red("π [DEBUG GOOGLE API ERROR]:"), JSON.stringify(data, null, 2)); |
| |
| return res.status(response.status).json({ |
| error: "Gemini API Error", |
| details: data |
| }); |
| } |
|
|
| const parts = data?.candidates?.[0]?.content?.parts || []; |
| const textPart = parts.find(p => p.text); |
| const audioPart = parts.find(p => p.inlineData); |
| const usage = data.usageMetadata || {}; |
|
|
| |
| console.log(chalk.green(` -> [Success] Model responded successfully.`)); |
| console.log(` | Audio Generated: ${audioPart ? chalk.green("Yes") : chalk.yellow("No")}`); |
| if (audioPart) { |
| console.log(` | Audio MimeType: ${chalk.blue(audioPart.inlineData.mimeType)} (${audioPart.inlineData.data.length} base64 chars)`); |
| } |
| if (usage.totalTokenCount) { |
| console.log(` | Token Usage -> Input: ${usage.promptTokenCount} | Output: ${usage.candidatesTokenCount} | Total: ${chalk.bold(usage.totalTokenCount)}`); |
| } |
|
|
| |
| const finalPayload = { |
| text: textPart?.text || null, |
| audio: audioPart ? { |
| mimeType: audioPart.inlineData.mimeType, |
| data: audioPart.inlineData.data.substring(0, 100) + "... [Truncated for Logs]" |
| } : null, |
| usage: usage || null |
| }; |
| |
| console.log(chalk.magenta.bold("\nπ [DEBUG RESPONSE STRUCTURE SENT TO FRONTEND]:")); |
| console.log(JSON.stringify(finalPayload, null, 2), "\n"); |
| |
|
|
| |
| res.json({ |
| text: textPart?.text || null, |
| audio: audioPart ? { |
| mimeType: audioPart.inlineData.mimeType, |
| data: audioPart.inlineData.data |
| } : null, |
| usage: usage || null |
| }); |
|
|
| } catch (err) { |
| console.error(chalk.red(` -> [Server Fatal Error]:`), err); |
| res.status(500).json({ error: err.message || "Internal Server Error" }); |
| } |
| }); |
|
|
| |
| const PORT = process.env.PORT || 7860; |
| app.listen(PORT, "0.0.0.0", () => { |
| console.log(chalk.green.bold(`\nπ Gemini REST Bridge is live on http://0.0.0.0:${PORT}\n`)); |
| }); |
|
|