From fd8b6f3aaf046cf6e07a1e17df3b54293df9b800 Mon Sep 17 00:00:00 2001 From: geyun <1556375442@qq.com> Date: Sun, 25 Jan 2026 14:58:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=BE=93=E5=87=BA=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server2.js | 727 +++++++++++++++++++++++------------------------------ 1 file changed, 317 insertions(+), 410 deletions(-) diff --git a/server2.js b/server2.js index 76771dc..95b3d6d 100755 --- a/server2.js +++ b/server2.js @@ -1370,454 +1370,361 @@ app.post("/refreshAiChatNum", async (req, res) => { // }); app.post("/analyze", upload.single("file"), async (req, res) => { - const { userId } = req.body; - try { - const exists = await hasQuota(userId); - if (!exists) { - const [userRemainChatNum] = await pool.query( - "SELECT ai_chat_num from sys_user where user_id= ?", - [userId] - ); - if (userRemainChatNum.length > 0) { - const quota = userRemainChatNum[0].ai_chat_num; - if (quota <= 0 && quota !== -1) { - return res.status(400).json({ error: "对话次数已用完,请完成账号注册" }); - } - await setQuota(userId, quota); - } - } else { - const quota = await getQuota(userId); - if (quota <= 0 && quota !== -1) { - return res.status(400).json({ error: "对话次数已用完,请完成账号注册" }); - } - } + const { userId } = req.body; + try { + const exists = await hasQuota(userId); + if (!exists) { + const [userRemainChatNum] = await pool.query( + "SELECT ai_chat_num from sys_user where user_id= ?", + [userId] + ); + if (userRemainChatNum.length > 0) { + const quota = userRemainChatNum[0].ai_chat_num; + if (quota <= 0 && quota !== -1) { + return res.status(400).json({ error: "对话次数已用完,请完成账号注册" }); + } + await setQuota(userId, quota); + } + } else { + const quota = await getQuota(userId); + if (quota <= 0 && quota !== -1) { + return res.status(400).json({ error: "对话次数已用完,请完成账号注册" }); + } + } - const { requestId } = req.body; + const { requestId } = req.body; - res.setHeader("Content-Type", "text/plain; charset=utf-8"); - res.setHeader("Transfer-Encoding", "chunked"); - res.setHeader("Cache-Control", "no-cache"); - res.setHeader("Connection", "keep-alive"); - let aborted = false; - req.on("close", () => { - console.log("🔴 前端断开连接,停止整个分析流程"); - aborted = true; - }); - - - console.time("analyze"); - const logBody = { ...req.body }; - if (req.body?.imageBase64) { - delete logBody.imageBase64; - } - console.log("analyze: ", logBody); - - // console.log("analyze: ",req.body) - if (!req.body?.userId || !req.body?.sessionId) { - console.log("缺少必要参数"); - return res.status(400).json({ error: "缺少必要参数" }); - } - const { userPrompt, imageBase64, sessionId } = req.body; - const [rows] = await pool.query( - ` - SELECT EXISTS( - SELECT 1 - FROM ai_chat_messages - WHERE session_id = ? - AND user_id = ? - AND title IS NOT NULL - AND title != '' - ) AS hasTitle - `, - [sessionId, userId] - ); - const existingTitle = rows[0].hasTitle === 1; // true / false - console.log(existingTitle, rows?.[0], 9999, rows) - - // 如果前端没有传 sessionId,生成一个 - const sessId = sessionId || Date.now().toString(); - - // ------------------ 获取历史对话(上下文) ------------------ - const [historyRows] = await pool.query( - "SELECT role, content, image_base64 FROM ai_chat_messages WHERE session_id = ? AND user_id = ? ORDER BY created_at ASC", - [sessId, userId || null] - ); - - // 构建历史消息数组 - const historyMessages = historyRows.map(row => { - const message = { - role: row.role, - content: [] - }; - - // 添加文本内容 - if (row.content) { - message.content.push({ type: "text", text: row.content }); - } - - // 添加图片内容 - if (row.image_base64) { - message.content.push({ - type: "image_url", - image_url: { - url: row.image_base64.startsWith("data:") - ? row.image_base64 - : `data:image/png;base64,${row.image_base64}`, - } - }); - } - - return message; - }); - - - // ------------------ 保存用户提问 ------------------ - await pool.query( - "INSERT INTO ai_chat_messages (session_id, user_id, role, content, image_base64) VALUES (?, ?, 'user', ?, ?)", - [sessId, userId || null, userPrompt || "", imageBase64 || null] - ); - - // console.log("userPrompt: ",userPrompt); - // console.log("imageBase64: ",imageBase64); - // console.log("userId: ",userId); - // console.log("sessionId: ",sessionId); - - // ------------------ 图像分析 ------------------ - // 构建包含上下文的消息数组 - const imageAnalysisMessages = [ - { - role: "system", - content: "你是一名图像分析专家,需要结合历史对话内容分析当前图像。" - }, - ...historyMessages, // 添加历史对话作为上下文 - { - role: "user", - content: [ - { type: "text", text: userPrompt || "请描述图片内容,并给出分析思考过程" }, - imageBase64 - ? { - type: "image_url", - image_url: { - url: imageBase64.startsWith("data:") - ? imageBase64 - : `data:image/png;base64,${imageBase64}`, - }, - } - : null, - ].filter(Boolean), - }, - ]; - - const completion = await client.chat.completions.create({ - model: "gpt-4.1", - stream: true, - messages: imageAnalysisMessages - }); - - console.log("====== 开始图像分析... ======") - let sceneText = ""; - - /* TLS 加密缓冲,小块数据可能先缓存在服务器端,不立即发送,会导致图像分析模型的结果不能立即返回 */ res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", + "Content-Type": "text/event-stream; charset=utf-8", + "Cache-Control": "no-cache, no-transform", "Connection": "keep-alive", - }); - res.socket.setNoDelay(true); + "X-Accel-Buffering": "no", + }); + res.flushHeaders?.(); + let aborted = false; + req.on("close", () => { + console.log("🔴 前端断开连接,停止整个分析流程"); + aborted = true; + }); - for await (const chunk of completion) { - if (stopFlags.get(requestId)) { - console.log("⛔ 图像分析被前端中断"); - res.end(); - stopFlags.delete(requestId); // 用完清理 - return; - } - const delta = chunk.choices[0]?.delta?.content || ""; - sceneText += delta; + console.time("analyze"); + const logBody = { ...req.body }; + if (req.body?.imageBase64) { + delete logBody.imageBase64; + } + console.log("analyze: ", logBody); - if (delta) { - res.write( - `data: ${JSON.stringify({ type: "scene", content: delta })}\n\n` - ); - } + if (!req.body?.userId || !req.body?.sessionId) { + console.log("缺少必要参数"); + return res.status(400).json({ error: "缺少必要参数" }); + } + const { userPrompt, imageBase64, sessionId } = req.body; + const [rows] = await pool.query( + ` + SELECT EXISTS( + SELECT 1 + FROM ai_chat_messages + WHERE session_id = ? + AND user_id = ? + AND title IS NOT NULL + AND title != '' + ) AS hasTitle + `, + [sessionId, userId] + ); + const existingTitle = rows[0].hasTitle === 1; // true / false + console.log(existingTitle, rows?.[0], 9999, rows); - // if (delta) - // { - // for (const ch of delta) - // { - // res.write(`data: ${JSON.stringify({ type: "scene", content: ch })}\n\n`); - // await new Promise(r => setTimeout(r, 0)); // 控制渲染节奏 - // } - // } - } - console.log(sceneText); - console.log("====== 图像分析结束 ======") - // ------------------ ① 思路阶段(模拟 ChatGPT 思考) ------------------ - const thinkingMessages = [ - { - role: "system", - content: ` - 你是一名新能源项目AI分析师。 - 请输出【思路概览】,模拟人类分析问题的过程,但不要暴露内部推理细节。 + const sessId = sessionId || Date.now().toString(); - 要求: - - 使用项目符号 - - 只输出思路,不给结论 - ` - }, - ...historyMessages, - { - role: "user", - content: userPrompt || "请分析图片。" - }, - { - role: "assistant", - content: sceneText - } - ]; + // ------------------ 获取历史对话(上下文) ------------------ + const [historyRows] = await pool.query( + "SELECT role, content, image_base64 FROM ai_chat_messages WHERE session_id = ? AND user_id = ? ORDER BY created_at ASC", + [sessId, userId || null] + ); - const thinkingStream = await client1.chat.completions.create({ - model: "gpt-5-mini", - stream: true, - messages: thinkingMessages - }); + // 构建历史消息数组 + const historyMessages = historyRows.map(row => { + const message = { + role: row.role, + content: [] + }; - res.write( - `data: ${JSON.stringify({ type: "phase", content: "thinking" })}\n\n` - ); + // 添加文本内容 + if (row.content) { + message.content.push({ type: "text", text: row.content }); + } - let thinkingText = ""; + // 添加图片内容 + if (row.image_base64) { + message.content.push({ + type: "image_url", + image_url: { + url: row.image_base64.startsWith("data:") + ? row.image_base64 + : `data:image/png;base64,${row.image_base64}`, + } + }); + } - console.log("====== 开始思路输出 ======"); - for await (const chunk of thinkingStream) { - if (stopFlags.get(requestId)) { - console.log("⛔ 思路阶段被中断"); - res.end(); - stopFlags.delete(requestId); - return; - } + return message; + }); - const delta = chunk.choices[0]?.delta?.content || ""; - thinkingText += delta; + // ------------------ 保存用户提问 ------------------ + await pool.query( + "INSERT INTO ai_chat_messages (session_id, user_id, role, content, image_base64) VALUES (?, ?, 'user', ?, ?)", + [sessId, userId || null, userPrompt || "", imageBase64 || null] + ); - if (delta) { - res.write( - `data: ${JSON.stringify({ type: "thinking", content: delta })}\n\n` - ); - } - } - console.log(thinkingText); - console.log("====== 思路输出结束 ======"); + // ------------------ 图像分析 ------------------ + let sceneText = ""; + // If an image is uploaded, start with image analysis + if (imageBase64) { + const imageAnalysisMessages = [ + { + role: "system", + content: "你是一名图像分析专家,需要结合历史对话内容分析当前图像。" + }, + ...historyMessages, // 添加历史对话作为上下文 + { + role: "user", + content: [ + { type: "text", text: userPrompt || `请描述图片内容, 请输出【思路概览】,模拟人类分析问题的过程,但不要暴露内部推理细节。 - // 保存图像分析结果到数据库 - // await pool.query( - // "INSERT INTO ai_chat_messages (session_id, user_id, role, content) VALUES (?, ?, 'assistant', ?)", - // [sessId, userId || null, sceneText] - // ); + 要求: + - 使用项目符号 + - 只输出思路,不给结论 + ` }, + imageBase64 + ? { + type: "image_url", + image_url: { + url: imageBase64.startsWith("data:") + ? imageBase64 + : `data:image/png;base64,${imageBase64}`, + }, + } + : null, + ].filter(Boolean), + }, + ]; - // ------------------ 3️⃣ LLM 分析 ------------------ - // console.log("LLM 分析 sceneText: ", sceneText) - // const preText = "详细分析这个光伏场站,根据图片给出详细的运维方案" - // + "如果这个光伏场站需要割草怎么做,给出一份详细的计划" - // + "如果这个光伏场站需要用无人机清洗怎么做,给出一份详细的计划" - // + "如果这个光伏场站需要巡检怎么做,给出一份详细的计划" + const completion = await client.chat.completions.create({ + model: "gpt-4.1", + stream: true, + messages: imageAnalysisMessages + }); - // 构建包含完整上下文的LLM消息数组 - const llmMessages = [ - { - role: "system", - content: "你是一名新能源项目的图像分析AI分析师,需要结合历史对话提供专业建议。" - }, - ...historyMessages, // 添加历史对话作为上下文 - { - role: "user", - content: userPrompt || "请基于图片分析提供建议。" - }, - { - role: "assistant", - content: sceneText // 添加当前图像分析结果 - } - ]; + console.log("====== 开始图像分析... ======"); + /* TLS 加密缓冲 */ + - // const llmStream = await client1.chat.completions.create({ - // model: "gpt-5-mini", - // stream: true, - // messages: llmMessages - // }); + for await (const chunk of completion) { + if (stopFlags.get(requestId)) { + console.log("⛔ 图像分析被前端中断"); + res.end(); + stopFlags.delete(requestId); // 清理 + return; + } + const delta = chunk.choices[0]?.delta?.content || ""; + sceneText += delta; - // ------------------ ② 结论阶段(最终分析) ------------------ - const answerMessages = [ - { - role: "system", - content: ` - 你是一名新能源项目图像分析AI。 - 请基于以下信息,输出【最终结论】: + if (delta) { + res.write( + `data: ${JSON.stringify({ type: "scene", content: delta })}\n\n` + ); + } + } + console.log(sceneText); + console.log("====== 图像分析结束 ======"); + } - 要求: - - 内容完整、专业、可执行 - - 不重复思路概览 - - 可以分段、分点说明 - ` - }, - ...historyMessages, - { - role: "user", - content: userPrompt || "请给出最终分析建议。" - }, - { - role: "assistant", - content: sceneText + thinkingText - } - ]; + // ------------------ ① 思路阶段(模拟 ChatGPT 思考) ------------------ + const thinkingMessages = [ + { + role: "system", + content: ` + 你是一名新能源项目AI分析师。 + 请输出【思路概览】,模拟人类分析问题的过程,但不要暴露内部推理细节。 - const answerStream = await client1.chat.completions.create({ - model: "gpt-5-mini", - stream: true, - messages: answerMessages - }); + 要求: + - 使用项目符号 + - 只输出思路,不给结论 + ` + }, + ...historyMessages, + { + role: "user", + content: userPrompt || "请分析图片。" + }, + { + role: "assistant", + content: sceneText + } + ]; - res.write( - `data: ${JSON.stringify({ type: "phase", content: "answer" })}\n\n` - ); + const thinkingStream = await client1.chat.completions.create({ + model: "gpt-5-mini", + stream: true, + messages: thinkingMessages + }); - let llmText = ""; + res.write( + `data: ${JSON.stringify({ type: "phase", content: "thinking" })}\n\n` + ); - console.log("====== 开始最终分析 ======"); - for await (const chunk of answerStream) { - if (stopFlags.get(requestId)) { - console.log("⛔ 最终分析被中断"); - res.end(); - stopFlags.delete(requestId); - return; - } + let thinkingText = ""; - const delta = chunk.choices[0]?.delta?.content || ""; - llmText += delta; + console.log("====== 开始思路输出 ======"); + for await (const chunk of thinkingStream) { + if (stopFlags.get(requestId)) { + console.log("⛔ 思路阶段被中断"); + res.end(); + stopFlags.delete(requestId); + return; + } - if (delta) { - res.write( - `data: ${JSON.stringify({ type: "answer", content: delta })}\n\n` - ); - } - } - console.log(llmText); - console.log("====== 最终分析结束 ======"); + const delta = chunk.choices[0]?.delta?.content || ""; + thinkingText += delta; + if (delta) { + res.write( + `data: ${JSON.stringify({ type: "thinking", content: delta })}\n\n` + ); + } + } + console.log(thinkingText); + console.log("====== 思路输出结束 ======"); - // console.log("====== 开始深度思考... ======") - // let llmText = ""; - // for await (const chunk of llmStream) { - // if (stopFlags.get(requestId)) { - // console.log("⛔ 深度分析被前端中断"); - // res.end(); - // stopFlags.delete(requestId); - // return; - // } - // const delta = chunk.choices[0]?.delta?.content || ""; - // llmText += delta; + // ------------------ ② 结论阶段(最终分析) ------------------ + const answerMessages = [ + { + role: "system", + content: ` + 你是一名新能源项目图像分析AI。 + 请基于以下信息,输出【最终结论】: - // if (delta) - // res.write( - // `data: ${JSON.stringify({ type: "llm", content: delta })}\n\n` - // ); + 要求: + - 内容完整、专业、可执行 + - 不重复思路概览 + - 可以分段、分点说明 + ` + }, + ...historyMessages, + { + role: "user", + content: userPrompt || "请给出最终分析建议。" + }, + { + role: "assistant", + content: sceneText + thinkingText + } + ]; + const answerStream = await client1.chat.completions.create({ + model: "gpt-5-mini", + stream: true, + messages: answerMessages + }); - // /* delta 很长,这会 人为拉长处理时间。 */ - // // if (delta) - // // { - // // for (const ch of delta) - // // { - // // res.write(`data: ${JSON.stringify({ type: "llm", content: ch })}\n\n`); - // // await new Promise(r => setTimeout(r, 0)); // 控制渲染节奏 - // // } - // // } - // } + res.write( + `data: ${JSON.stringify({ type: "phase", content: "answer" })}\n\n` + ); + let llmText = ""; - // 保存 LLM 输出到数据库 - /* 加标题需要异步,且在会话开始时就进行 */ - if (existingTitle) { - console.log("uidh") - await pool.query( - "INSERT INTO ai_chat_messages (session_id, user_id, role, content) VALUES (?, ?, 'assistant', ?)", - [sessId, userId || null, llmText] - ); + console.log("====== 开始最终分析 ======"); + for await (const chunk of answerStream) { + if (stopFlags.get(requestId)) { + console.log("⛔ 最终分析被中断"); + res.end(); + stopFlags.delete(requestId); + return; + } + + const delta = chunk.choices[0]?.delta?.content || ""; + llmText += delta; + + if (delta) { + res.write( + `data: ${JSON.stringify({ type: "answer", content: delta })}\n\n` + ); + } + } + console.log(llmText); + console.log("====== 最终分析结束 ======"); + + // ------------------ Save Results to Database ------------------ + if (existingTitle) { + await pool.query( + "INSERT INTO ai_chat_messages (session_id, user_id, role, content) VALUES (?, ?, 'assistant', ?)", + [sessId, userId || null, llmText] + ); + } else { + try { + const titlePrompt = `请根据以下对话内容生成一个简短的标题(不超过15字):\n用户问题: ${userPrompt}\nAI回答: ${llmText}`; + const titleResp = await client1.chat.completions.create({ + model: "gpt-5-mini", + messages: [ + { role: "system", content: "你是标题生成助手,只返回标题,不要解释。" }, + { role: "user", content: titlePrompt } + ] + }); + + const title = titleResp.choices[0]?.message?.content?.trim() || "未命名会话"; + res.write( + `data: ${JSON.stringify({ type: "title", content: title })}\n\n` + ); + console.log("生成的标题:", title); + + await pool.query( + "INSERT INTO ai_chat_messages (session_id, user_id, role, content, title) VALUES (?, ?, 'assistant', ?, ?)", + [sessId, userId || null, llmText, title] + ); + } catch (err) { + console.error("生成标题失败:", err.message); + } + } + + res.write("data: [DONE]\n\n"); + res.end(); + + // 删除上传文件 + if (req.file && req.file.path) { + fs.unlinkSync(req.file.path); + } + console.log("====== 分析完成 ======"); + console.timeEnd("analyze"); + + } catch (err) { + console.error("❌ 调用失败:", err.message); + + if (!res.headersSent) { + res.status(500).json({ error: err.message }); } else { - console.log("uidh1111") - - try { - const titlePrompt = `请根据以下对话内容生成一个简短的标题(不超过15字): - 用户问题: ${userPrompt} - AI回答: ${llmText}`; - - const titleResp = await client1.chat.completions.create({ - model: "gpt-5-mini", - messages: [ - { role: "system", content: "你是标题生成助手,只返回标题,不要解释。" }, - { role: "user", content: titlePrompt } - ] - }); - - const title = titleResp.choices[0]?.message?.content?.trim() || "未命名会话"; - res.write( - `data: ${JSON.stringify({ type: "title", content: title })}\n\n` - ); - - console.log("生成的标题:", title); - - // 保存到数据库(建议你给 ai_chat_messages 表新增一列 title,或者单独建一张会话表) - await pool.query( - "INSERT INTO ai_chat_messages (session_id, user_id, role, content,title) VALUES (?, ?, 'assistant', ?,?)", - [sessId, userId || null, llmText, title] - ); - } - catch (err) { - console.error("生成标题失败:", err.message); - } + res.write(`data: ${JSON.stringify({ + type: "error", + message: err.message + })}\n\n`); + res.end(); } - - - - - res.write("data: [DONE]\n\n"); - res.end(); - - // 删除上传文件 - if (req.file && req.file.path) { - fs.unlinkSync(req.file.path); - } - console.log("====== 分析完成 ======"); - console.timeEnd("analyze"); - - // // 更新用户剩余对话次数 - // let userChatNum = await getQuota(userId); - // if(userChatNum!==-1){ - // userChatNum = Math.max(0, userChatNum - 1); // 防止变成负数 - // await setQuota(userId,userChatNum); - // console.log(`用户剩余AI 对话次数: ${userChatNum}`); - // } - } - catch (err) { - console.error("❌ 调用失败:", err.response?.data || err.message); - res.status(500).json({ code: 1, error: err.response?.data || err.message }); - } - finally { - let userChatNum = await getQuota(userId); - if (userChatNum !== -1 && userChatNum !== undefined) { - userChatNum = Math.max(0, userChatNum - 1); // 防止变成负数 - await setQuota(userId, userChatNum); - // 更新数据库 - await pool.query( - "update sys_user set ai_chat_num = ? where user_id= ?", - [userChatNum, userId] - ); - console.log("更新用户对话次数:", userId, userChatNum); - } - - } + } finally { + let userChatNum = await getQuota(userId); + if (userChatNum !== -1 && userChatNum !== undefined) { + userChatNum = Math.max(0, userChatNum - 1); // 防止变成负数 + await setQuota(userId, userChatNum); + // 更新数据库 + await pool.query( + "update sys_user set ai_chat_num = ? where user_id= ?", + [userChatNum, userId] + ); + console.log("更新用户对话次数:", userId, userChatNum); + } + } }); + app.post("/stop", express.json(), (req, res) => { const { requestId } = req.body; console.log("🔴 收到停止信号:", requestId);