Spaces:
Sleeping
Sleeping
| // server/index.ts | |
| import express from "express"; | |
| import cors from "cors"; | |
| import dotenv from "dotenv"; | |
| import multer from "multer"; | |
| import { execFile } from "child_process"; | |
| import fs from "fs"; | |
| import path from "path"; | |
| import { fileURLToPath } from "url"; | |
| dotenv.config(); | |
| var __filename = fileURLToPath(import.meta.url); | |
| var __dirname = path.dirname(__filename); | |
| var app = express(); | |
| var PORT = process.env.PORT || 3001; | |
| var isBundled = fs.existsSync(path.join(__dirname, "dist")); | |
| var distPath = isBundled ? path.join(__dirname, "dist") : path.join(__dirname, "../dist"); | |
| var difyApiKey = ""; | |
| try { | |
| const configPath = isBundled ? path.join(__dirname, "config.json") : path.join(__dirname, "../config.json"); | |
| if (fs.existsSync(configPath)) { | |
| const configData = JSON.parse(fs.readFileSync(configPath, "utf8")); | |
| if (configData.dify_api_key) { | |
| difyApiKey = configData.dify_api_key; | |
| } | |
| } | |
| } catch (e) { | |
| console.error("Failed to read config.json:", e); | |
| } | |
| var DIFY_API_KEY = process.env.DIFY_API_KEY || difyApiKey; | |
| if (!DIFY_API_KEY) { | |
| console.warn("\n\u26A0\uFE0F [WARNING]: DIFY_API_KEY is not configured! Please configure it in config.json or environment variables.\n"); | |
| } | |
| var upload = multer({ storage: multer.memoryStorage() }); | |
| app.use(cors()); | |
| app.use(express.json()); | |
| app.get("/api/health", (req, res) => { | |
| res.json({ status: "ok", message: "Backend is running!" }); | |
| }); | |
| app.get("/api/proxy-image", async (req, res) => { | |
| const imageUrl = req.query.url; | |
| if (!imageUrl) return res.status(400).send("No URL provided"); | |
| try { | |
| const fetchRes = await fetch(imageUrl); | |
| const buffer = await fetchRes.arrayBuffer(); | |
| res.set("Content-Type", fetchRes.headers.get("content-type") || "image/jpeg"); | |
| res.send(Buffer.from(buffer)); | |
| } catch (e) { | |
| res.status(500).send("Error proxying image"); | |
| } | |
| }); | |
| app.post("/api/analyze", upload.array("files", 5), async (req, res) => { | |
| try { | |
| if (!DIFY_API_KEY) { | |
| return res.status(500).json({ | |
| success: false, | |
| error: "Dify API Key is not configured on the server. Please configure it in config.json or environment variables." | |
| }); | |
| } | |
| const query = req.body.query || ""; | |
| const files = req.files; | |
| const difyFileObjects = []; | |
| if (files && files.length > 0) { | |
| for (const file of files) { | |
| const formData = new FormData(); | |
| const blob = new Blob([file.buffer], { type: file.mimetype }); | |
| formData.append("file", blob, file.originalname); | |
| formData.append("user", "web-user"); | |
| const uploadRes = await fetch("https://api.dify.ai/v1/files/upload", { | |
| method: "POST", | |
| headers: { | |
| "Authorization": `Bearer ${DIFY_API_KEY}` | |
| }, | |
| body: formData | |
| }); | |
| if (!uploadRes.ok) { | |
| const err = await uploadRes.text(); | |
| console.error("File upload failed:", err); | |
| throw new Error(`Failed to upload file to Dify: ${err}`); | |
| } | |
| const uploadData = await uploadRes.json(); | |
| let type = "document"; | |
| if (file.mimetype.startsWith("image/")) type = "image"; | |
| else if (file.mimetype.startsWith("audio/")) type = "audio"; | |
| else if (file.mimetype.startsWith("video/")) type = "video"; | |
| difyFileObjects.push({ | |
| type, | |
| transfer_method: "local_file", | |
| upload_file_id: uploadData.id | |
| }); | |
| } | |
| } | |
| const isElderlyModeStr = req.body.isElderlyMode === "true" ? "true" : "false"; | |
| const workflowPayload = { | |
| inputs: { | |
| upload_files: difyFileObjects, | |
| user_text: query, | |
| isElderlyMode: isElderlyModeStr | |
| }, | |
| response_mode: "streaming", | |
| user: "web-user" | |
| }; | |
| const runRes = await fetch("https://api.dify.ai/v1/workflows/run", { | |
| method: "POST", | |
| headers: { | |
| "Authorization": `Bearer ${DIFY_API_KEY}`, | |
| "Content-Type": "application/json" | |
| }, | |
| body: JSON.stringify(workflowPayload) | |
| }); | |
| if (!runRes.ok) { | |
| const errText = await runRes.text(); | |
| console.error("Workflow run failed:", errText); | |
| try { | |
| const errJson = JSON.parse(errText); | |
| return res.status(runRes.status).json({ success: false, error: errJson.message || errJson.code || "Workflow failed to start", details: errJson }); | |
| } catch (e) { | |
| return res.status(runRes.status).json({ success: false, error: errText }); | |
| } | |
| } | |
| res.setHeader("Content-Type", "text/event-stream"); | |
| res.setHeader("Cache-Control", "no-cache"); | |
| res.setHeader("Connection", "keep-alive"); | |
| if (runRes.body) { | |
| const reader = runRes.body.getReader(); | |
| const decoder = new TextDecoder("utf-8"); | |
| let buffer = ""; | |
| const pump = async () => { | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) { | |
| res.end(); | |
| break; | |
| } | |
| res.write(value); | |
| buffer += decoder.decode(value, { stream: true }); | |
| let lines = buffer.split("\n"); | |
| buffer = lines.pop() || ""; | |
| for (let line of lines) { | |
| line = line.trim(); | |
| if (line.startsWith("data: ")) { | |
| try { | |
| const data = JSON.parse(line.substring(6)); | |
| if (data.event === "node_finished" && data.data) { | |
| const title = data.data.title || data.data.node_type || "Unknown Node"; | |
| const outputText = data.data.outputs?.text || data.data.outputs?.answer || data.data.outputs?.string; | |
| const outputAll = data.data.outputs; | |
| console.log(` | |
| ======================================================`); | |
| console.log(`\u{1F7E2} [NODE FINISHED]: ${title}`); | |
| if (outputText) { | |
| console.log(`[TEXT OUTPUT]: | |
| ${outputText}`); | |
| } else { | |
| console.log(`[OUTPUT DATA]:`, JSON.stringify(outputAll, null, 2)); | |
| } | |
| console.log(`====================================================== | |
| `); | |
| } | |
| } catch (e) { | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| pump().catch((err) => { | |
| console.error("Stream error:", err); | |
| res.end(); | |
| }); | |
| } else { | |
| res.end(); | |
| } | |
| } catch (error) { | |
| console.error("Error in analysis:", error); | |
| res.status(500).json({ success: false, error: "Internal Server Error", details: error.message || String(error) }); | |
| } | |
| }); | |
| app.post("/api/tts", async (req, res) => { | |
| try { | |
| const { text, voice, rate } = req.body; | |
| if (!text) { | |
| return res.status(400).json({ error: "No text provided" }); | |
| } | |
| const voiceName = voice || "zh-CN-XiaoyiNeural"; | |
| const speechRate = rate || "-12%"; | |
| const tempFileName = `tts_${Date.now()}_${Math.floor(Math.random() * 1e3)}.mp3`; | |
| const tempFilePath = path.join(process.cwd(), tempFileName); | |
| execFile("edge-tts", [ | |
| "--voice", | |
| voiceName, | |
| "--text", | |
| text, | |
| `--rate=${speechRate}`, | |
| "--write-media", | |
| tempFilePath | |
| ], (error, stdout, stderr) => { | |
| if (error) { | |
| console.error("edge-tts execution failed:", error, stderr); | |
| return res.status(500).json({ error: "TTS generation failed", details: error.message }); | |
| } | |
| res.sendFile(tempFilePath, (err) => { | |
| fs.unlink(tempFilePath, (unlinkErr) => { | |
| if (unlinkErr) console.error("Failed to unlink temporary TTS file:", unlinkErr); | |
| }); | |
| if (err) { | |
| console.error("Error sending TTS file:", err); | |
| } | |
| }); | |
| }); | |
| } catch (err) { | |
| console.error("TTS endpoint error:", err); | |
| res.status(500).json({ error: "Internal Server Error in TTS endpoint", details: err.message }); | |
| } | |
| }); | |
| app.use(express.static(distPath)); | |
| app.get("*", (req, res) => { | |
| const indexPage = path.join(distPath, "index.html"); | |
| if (fs.existsSync(indexPage)) { | |
| res.sendFile(indexPage); | |
| } else { | |
| res.status(404).send('Frontend not built. Run "npm run build" first.'); | |
| } | |
| }); | |
| app.listen(PORT, () => { | |
| console.log(`Server is running on http://localhost:${PORT}`); | |
| }); | |