diff --git a/server2.js b/server2.js index 395a482..80c9893 100755 --- a/server2.js +++ b/server2.js @@ -1369,6 +1369,832 @@ 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: "对话次数已用完,请完成账号注册" }); +// } +// } + +// console.log("====== 开始图像分析11... ======"); + +// const { requestId } = req.body; + +// res.writeHead(200, { +// "Content-Type": "text/event-stream; charset=utf-8", +// "Cache-Control": "no-cache, no-transform", +// "Connection": "keep-alive", +// "X-Accel-Buffering": "no", +// }); +// res.flushHeaders?.(); +// res.socket.setNoDelay(true); // 禁用缓冲,实时推送 +// 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); + +// 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); + +// const sessId = sessionId || Date.now().toString(); +// const MAX_HISTORY = 6; +// // ------------------ 获取历史对话(上下文) ------------------ +// 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] +// ); + +// console.log("====== 开始图像分析22... ======"); + +// // 构建历史消息数组 +// const historyMessages = historyRows.slice(-MAX_HISTORY).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; +// }); + +// console.log("====== 开始图像分析33... ======"); + +// // ------------------ 保存用户提问 ------------------ +// await pool.query( +// "INSERT INTO ai_chat_messages (session_id, user_id, role, content, image_base64) VALUES (?, ?, 'user', ?, ?)", +// [sessId, userId || null, userPrompt || "", imageBase64 || null] +// ); + +// // ------------------ 图像分析 ------------------ +// let sceneText = ""; + +// console.log("====== 开始图像分析55... ======"); + +// // If an image is uploaded, start with image analysis +// if (imageBase64) { +// const imageAnalysisMessages = [ +// { +// role: "system", +// content: ` +// 你是一名图像分析专家。 +// 你的任务不是描述图像内容,而是: +// - 仅提炼【用于后续分析的思考线索】 +// - 不出现任何具体物体、文字、数值、位置 +// - 不描述画面、不复述图片 +// 强制规则: +// - 不得出现以下词语: +// thinking, answer, 思路, 结论, 分析, 推理, 过程 +// - 不使用“基于以上”“综合来看”等过渡句 +// - 不重复前文 + + +// 输出要求: +// - 仅输出【思路线索】 +// - 使用抽象、概括性语言 +// - 禁止出现任何图像细节 +// ` +// }, +// ...historyMessages, // 添加历史对话作为上下文 +// { +// role: "user", +// content: [ +// { type: "text", text: ` +// 请基于图片,仅输出【思路线索】而不是图片内容。 + +// 要求: +// - 使用项目符号 +// - 只描述“分析思路方向” +// - 不描述看到什么 +// - 不出现“分析过程”“推理”"thinking""answer"等字眼 +// - 不下结论 +// ` }, +// imageBase64 +// ? { +// type: "image_url", +// image_url: { +// url: imageBase64.startsWith("data:") +// ? imageBase64 +// : `data:image/png;base64,${imageBase64}`, +// }, +// } +// : null, +// ].filter(Boolean), +// }, +// ]; + +// console.log("====== 开始图像分析666... ======"); + +// const completion = await client.chat.completions.create({ +// model: "gpt-4.1", +// stream: true, +// messages: imageAnalysisMessages +// }); + +// console.log("====== 开始图像分析... ======"); +// /* TLS 加密缓冲 */ + + +// 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; + +// if (delta) { +// res.write( +// `data: ${JSON.stringify({ type: "thinking", content: delta })}\n\n` +// ); +// } +// } +// console.log(sceneText); +// console.log("====== 图像分析结束 ======"); +// } + +// // ------------------ ① 思路阶段(模拟 ChatGPT 思考) ------------------ +// const thinkingMessages = [ +// { +// role: "system", +// content: ` +// 你是一名新能源项目AI分析师。 + +// 请基于: +// - 历史对话 +// - 图片提供的【思路线索】 + +// 输出【思路概览】。 + +// 要求: +// - 模拟人类分析路径 +// - 不出现任何图片内容 +// - 不给结论 +// - 使用项目符号 +// - 不出现“分析过程”“推理”"thinking""answer"等字眼 +// ` +// }, +// ...historyMessages, +// { +// role: "user", +// content: userPrompt || "请分析图片。" +// }, +// { +// role: "assistant", +// content: sceneText +// } +// ]; + +// const thinkingStream = await client1.chat.completions.create({ +// model: "gpt-5-mini", +// stream: true, +// messages: thinkingMessages +// }); + +// res.write( +// `data: ${JSON.stringify({ type: "phase", content: "thinking" })}\n\n` +// ); + +// let thinkingText = ""; + +// console.log("====== 开始思路输出 ======"); +// for await (const chunk of thinkingStream) { +// if (stopFlags.get(requestId)) { +// console.log("⛔ 思路阶段被中断"); +// res.end(); +// stopFlags.delete(requestId); +// return; +// } + +// 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("====== 思路输出结束 ======"); + +// // ------------------ ② 结论阶段(最终分析) ------------------ +// const answerMessages = [ +// { +// role: "system", +// content: ` +// 你是一名新能源项目分析AI。 + +// 请基于【思路概览】,输出【最终结论】。 + +// 要求: +// - 专业、完整、可执行 +// - 不复述思路 +// - 不描述图片 +// - 不出现“分析过程”“推理”"thinking""answer"等字眼 +// ` +// }, +// ...historyMessages, +// { +// role: "user", +// content: userPrompt || "请给出最终分析建议。" +// }, +// { +// role: "assistant", +// content: thinkingText +// } +// ]; + +// const answerStream = await client1.chat.completions.create({ +// model: "gpt-5-mini", +// stream: true, +// messages: answerMessages +// }); + +// res.write( +// `data: ${JSON.stringify({ type: "phase", content: "answer" })}\n\n` +// ); + +// let 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 ------------------ + +// sceneText += thinkingText; +// if (existingTitle) { +// await pool.query( +// "INSERT INTO ai_chat_messages (session_id, user_id, role, content,thinking) VALUES (?, ?, 'assistant', ?,?)", +// [sessId, userId || null, llmText,sceneText] +// ); +// } 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,thinking) VALUES (?, ?, 'assistant', ?, ?,?)", +// [sessId, userId || null, llmText, title,sceneText] +// ); +// } 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 { +// res.write(`data: ${JSON.stringify({ +// type: "error", +// message: err.message +// })}\n\n`); +// res.end(); +// } +// } 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("/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: "对话次数已用完,请完成账号注册" }); +// } +// } + +// console.log("====== 开始图像分析11... ======"); + +// const { requestId } = req.body; + +// /* TLS 加密缓冲,小块数据可能先缓存在服务器端,不立即发送,会导致图像分析模型的结果不能立即返回 */ +// res.writeHead(200, { +// "Content-Type": "text/event-stream; charset=utf-8", +// "Cache-Control": "no-cache, no-transform", +// "Connection": "keep-alive", +// "X-Accel-Buffering": "no", +// }); + +// res.flushHeaders?.(); +// res.socket.setNoDelay(true); // 禁用缓冲,实时推送 + +// 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); + +// 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); + +// const sessId = sessionId || Date.now().toString(); +// const MAX_HISTORY = 6; +// // ------------------ 获取历史对话(上下文) ------------------ +// 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] +// ); + +// console.log("====== 开始图像分析22... ======"); + +// // 构建历史消息数组 +// const historyMessages = historyRows.slice(-MAX_HISTORY).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; +// }); + +// console.log("====== 开始图像分析33... ======"); + +// // ------------------ 保存用户提问 ------------------ +// await pool.query( +// "INSERT INTO ai_chat_messages (session_id, user_id, role, content, image_base64) VALUES (?, ?, 'user', ?, ?)", +// [sessId, userId || null, userPrompt || "", imageBase64 || null] +// ); + +// // ------------------ 图像分析 ------------------ +// let sceneText = ""; + +// console.log("====== 开始图像分析55... ======"); + +// // If an image is uploaded, start with image analysis +// if (imageBase64) +// { +// const imageAnalysisMessages = [ +// { +// role: "system", +// content: ` +// 你是新能源场景辅助思考助手,**不做任何图像相关维度分析**,仅输出1句极简铺垫用于后续深度思考。 +// 强制禁令(绝对禁止出现): +// 1. 图像相关词汇:区域、划分、边界、特征、标注、排列、规律、颜色、形状、分组、分类、分布、分区、标准、联系、差异、标记、线索 +// 2. 方案相关词汇:计划、方案、步骤、割草、作业、人员、工具、执行 +// 3. 格式禁令:无列表、无条目、无分行、无项目符号 +// 输出要求:仅1句不超过20字的极简抽象铺垫,适配新能源场景思考,无任何具体维度描述。 +// ` // 【核心修改1:禁绝所有干扰词汇,限定极简1句】 +// }, +// ...historyMessages, +// { +// role: "user", +// content: [ +// { type: "text", text: ` +// 基于图片仅输出1句极简抽象铺垫(≤20字),用于后续新能源场景思考,要求: +// 1. 绝对禁止区域、排列、特征、颜色、形状、分区等所有图像维度描述 +// 2. 无列表、无条目、无多余表述,仅1句连贯文本 +// 3. 不出现方案、计划、割草等词汇,不下结论不做建议 +// ` +// }, // 【核心修改2:强化词汇禁令,限定长度和格式】 +// imageBase64 +// ? { +// type: "image_url", +// image_url: { +// url: imageBase64.startsWith("data:") +// ? imageBase64 +// : `data:image/png;base64,${imageBase64}`, +// }, +// } +// : null, +// ].filter(Boolean), +// }, +// ]; + +// console.log("====== 开始图像分析666... ======"); + +// const completion = await client.chat.completions.create({ +// model: "gpt-4.1", +// stream: true, +// messages: imageAnalysisMessages, +// max_tokens: 10 // 【核心修改3:强制限制输出长度,杜绝冗余】 +// }); + +// console.log("====== 开始图像分析... ======"); + + +// 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; + +// if (delta) { +// res.write( +// `data: ${JSON.stringify({ type: "thinking", content: delta })}\n\n` +// ); +// } +// } +// console.log(sceneText); +// console.log("====== 图像分析结束 ======"); +// } + +// // ------------------ ① 思路阶段(模拟 ChatGPT 思考) ------------------ +// const thinkingMessages = [ +// { +// role: "system", +// content: +// ` +// 你是一名新能源领域的专业分析人员,需要模拟人类在思考问题时,自然形成的抽象内心思路,并将这种思路用文字表达出来。 + +// 你的输出只包含抽象的思考内容本身,**绝对禁止**输出: +// - 具体的方案、计划、步骤、操作建议 +// - 可执行的行动指南、人员/工具安排 +// - 具体的行业术语(如割草、光伏板、场站等)的落地执行内容 +// - 最终结论、明确方案或直接建议 +// - 图像相关维度描述(区域、排列、特征、颜色、形状等) +// - 条目式、列表式、项目符号内容 + +// 在输出时,请参考如下思考方式和表达节奏: +// 当用户提出问题时,先从字面信息出发,理解用户在说什么,再进一步判断这背后可能真正关心的事情是什么,以及他们大概率希望得到哪一类帮助或方向性思路。 + +// 思路展开时,可以围绕几个自然浮现的关注点顺着往下想,比如: +// - 是否存在一个整体可行的解决方向或方案框架(仅抽象描述,无具体内容); +// - 在现实场景中,这件事会涉及哪些风险、安全或限制条件(抽象概括,无具体案例); +// - 实际操作中可能采用哪些类型的方式,不同类型方式之间的适用性差异(仅分类,无具体方式名称); +// - 是否需要考虑频率、周期、长期维护等维度的问题(抽象维度,无具体数值); +// - 最终会自然联想到工具、设备或资源层面的选择(仅维度,无具体工具名称)。 + +// 表达时不需要刻意区分结构标题,也不要使用“一、二、三”或条目式罗列,而是用连贯的段落,把思路一层一层展开,像是在边想边把重点捋清楚。 + +// 语言要求: +// - 使用第一人称,像是在边看信息边思考 +// - 语气自然、偏口语,但保持专业 +// - 可以有推测和不确定性的表达 +// - 不使用“分析过程”“结论”“总结”“计划”“方案”“步骤”等模型化/落地性用语 +// - 不直接给出最终判断或确定性答案 +// - 不出现具体的行业操作术语(如割草、运维、组件等) +// - 不出现区域、排列、特征、颜色、形状等图像维度词汇 + +// 【示例(仅用于约束思考方式,不作为内容参考)】 +// 示例输入:“电站割草” +// 示例输出: +// “用户提到的这个场景,表面看是某个具体运维动作,但背后很可能是在考虑有没有更高效、更安全或者更长期可行的处理方式,尤其是在这类对环境和设备敏感度较高的场景下。 +// 顺着这个思路往下想,就会自然想到有没有整体上更可行的处理方向,不同类型的处理方式各自适合什么特征的场景。同时也绕不开风险和安全问题,毕竟现场有各类设备和敏感区域,不当的处理方式反而可能带来新的隐患。 +// 再往后就会想到维护频次的问题,是不是需要定期处理,还是有办法降低重复作业的次数。不同频次对应的方式和成本肯定不一样,这里就需要结合实际情况去看。 +// 最后,思路自然会落到具体工具和设备层面的选择,不同方式需要的设备类型差别很大,而这些选择又会反过来影响安全、效率和长期维护效果。” +// ` // 【补充修改:思考阶段也加图像维度词汇禁令】 +// }, +// ...historyMessages, +// { +// role: "user", +// content: userPrompt || "请分析图片。" +// }, +// { +// role: "assistant", +// content: sceneText +// } +// ]; + +// const thinkingStream = await client1.chat.completions.create({ +// model: "gpt-5-mini", +// stream: true, +// messages: thinkingMessages +// }); + +// res.write( +// `data: ${JSON.stringify({ type: "phase", content: "thinking" })}\n\n` +// ); + +// let thinkingText = ""; + +// console.log("====== 开始思路输出 ======"); +// for await (const chunk of thinkingStream) { +// if (stopFlags.get(requestId)) { +// console.log("⛔ 思路阶段被中断"); +// res.end(); +// stopFlags.delete(requestId); +// return; +// } + +// 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("====== 思路输出结束 ======"); + +// // ------------------ ② 结论阶段(最终分析) ------------------ +// const answerMessages = [ +// { +// role: "system", +// content: ` +// 你是一名新能源项目分析AI。 + +// 请基于【思路概览】,输出【最终结论】。 + +// 要求: +// - 专业、完整、可执行 +// - 不复述思路 +// - 不描述图片 +// - 不出现“分析过程”“推理”"thinking""answer"等字眼 +// - 不出现区域、排列、特征等图像维度词汇 +// ` +// }, +// ...historyMessages, +// { +// role: "user", +// content: userPrompt || "请给出最终分析建议。" +// }, +// { +// role: "assistant", +// content: thinkingText +// } +// ]; + +// const answerStream = await client1.chat.completions.create({ +// model: "gpt-5-mini", +// stream: true, +// messages: answerMessages +// }); + +// res.write( +// `data: ${JSON.stringify({ type: "phase", content: "answer" })}\n\n` +// ); + +// let 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 ------------------ + +// sceneText += thinkingText; +// if (existingTitle) { +// await pool.query( +// "INSERT INTO ai_chat_messages (session_id, user_id, role, content,thinking) VALUES (?, ?, 'assistant', ?,?)", +// [sessId, userId || null, llmText,sceneText] +// ); +// } 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,thinking) VALUES (?, ?, 'assistant', ?, ?,?)", +// [sessId, userId || null, llmText, title,sceneText] +// ); +// } 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 { +// res.write(`data: ${JSON.stringify({ +// type: "error", +// message: err.message +// })}\n\n`); +// res.end(); +// } +// } 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("/analyze", upload.single("file"), async (req, res) => { const { userId } = req.body; try { @@ -1392,19 +2218,22 @@ app.post("/analyze", upload.single("file"), async (req, res) => { } } - console.log("====== 开始图像分析11... ======"); + console.log("====== 开始图像分析11... ======"); const { requestId } = req.body; + /* TLS 加密缓冲,小块数据可能先缓存在服务器端,不立即发送,会导致图像分析模型的结果不能立即返回 */ res.writeHead(200, { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache, no-transform", "Connection": "keep-alive", "X-Accel-Buffering": "no", }); - res.flushHeaders?.(); - res.socket.setNoDelay(true); // 禁用缓冲,实时推送 - let aborted = false; + + res.flushHeaders?.(); + res.socket.setNoDelay(true); // 禁用缓冲,实时推送 + + let aborted = false; req.on("close", () => { console.log("🔴 前端断开连接,停止整个分析流程"); aborted = true; @@ -1439,14 +2268,14 @@ app.post("/analyze", upload.single("file"), async (req, res) => { console.log(existingTitle, rows?.[0], 9999, rows); const sessId = sessionId || Date.now().toString(); - const MAX_HISTORY = 6; + const MAX_HISTORY = 6; // ------------------ 获取历史对话(上下文) ------------------ 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] ); - console.log("====== 开始图像分析22... ======"); + console.log("====== 开始图像分析22... ======"); // 构建历史消息数组 const historyMessages = historyRows.slice(-MAX_HISTORY).map(row => { @@ -1475,7 +2304,7 @@ app.post("/analyze", upload.single("file"), async (req, res) => { return message; }); - console.log("====== 开始图像分析33... ======"); + console.log("====== 开始图像分析33... ======"); // ------------------ 保存用户提问 ------------------ await pool.query( @@ -1483,65 +2312,53 @@ app.post("/analyze", upload.single("file"), async (req, res) => { [sessId, userId || null, userPrompt || "", imageBase64 || null] ); - // ------------------ 图像分析 ------------------ + // ------------------ 图像分析(核心修改:输出精准场景描述) ------------------ let sceneText = ""; - console.log("====== 开始图像分析55... ======"); + console.log("====== 开始图像分析55... ======"); - // If an image is uploaded, start with image analysis - if (imageBase64) { + if (imageBase64) + { const imageAnalysisMessages = [ { role: "system", - content: ` -你是一名图像分析专家。 -你的任务不是描述图像内容,而是: -- 仅提炼【用于后续分析的思考线索】 -- 不出现任何具体物体、文字、数值、位置 -- 不描述画面、不复述图片 -强制规则: -- 不得出现以下词语: - thinking, answer, 思路, 结论, 分析, 推理, 过程 -- 不使用“基于以上”“综合来看”等过渡句 -- 不重复前文 - - -输出要求: -- 仅输出【思路线索】 -- 使用抽象、概括性语言 -- 禁止出现任何图像细节 -` + content: + ` + 你是图像场景描述助手,严格按指定格式输出1次图片描述,绝不重复、无多余内容、无分析建议。 + 输出格式(固定话术,仅替换【】内容,必须1句完整文本): + 用户上传了一张【图片类型】的航拍规划图,现在需要制定详细的割草计划。从图片描述来看,这明显是一个【场景属性】项目的测绘规划图,区域有【标注特征】。 + 替换规则: + 1. 【图片类型】:光伏场站/梯田/坡地(按图实际) + 2. 【场景属性】:新能源场站/农业/生态修复(匹配场景) + 3. 【标注特征】:方阵编号和绿色边界线/地块编号和边界标注(按图实际) + 强制要求:仅输出1次上述格式文本,无重复、无追加、无解释 + ` // 核心:引导精准场景描述,摒弃泛化内容 }, - ...historyMessages, // 添加历史对话作为上下文 + ...historyMessages, { role: "user", content: [ - { type: "text", text: ` -请基于图片,仅输出【思路线索】而不是图片内容。 - -要求: -- 使用项目符号 -- 只描述“分析思路方向” -- 不描述看到什么 -- 不出现“分析过程”“推理”"thinking""answer"等字眼 -- 不下结论 -` }, - imageBase64 - ? { + { type: "text", text: + ` + 请严格按以下固定格式输出1句图片描述,不重复、不增删、不解释、无多余内容: + 用户上传了一张【图片类型】的航拍规划图,现在需要制定详细的割草计划。从图片描述来看,这明显是一个【场景属性】项目的测绘规划图,区域有【标注特征】。 + 替换要求: + 1. 【图片类型】填光伏场站/梯田/坡地;【场景属性】填新能源场站/农业;【标注特征】填方阵编号和绿色边界线 + 2. 仅输出1次完整句子,杜绝任何重复表述 + ` + }, // 贴合用户想要的描述风格 + imageBase64 ? { type: "image_url", image_url: { - url: imageBase64.startsWith("data:") - ? imageBase64 - : `data:image/png;base64,${imageBase64}`, + url: imageBase64.startsWith("data:") ? imageBase64 : `data:image/png;base64,${imageBase64}`, }, - } - : null, + } : null, ].filter(Boolean), }, ]; console.log("====== 开始图像分析666... ======"); - + // 去掉max_tokens限制,保证描述完整 const completion = await client.chat.completions.create({ model: "gpt-4.1", stream: true, @@ -1549,59 +2366,42 @@ app.post("/analyze", upload.single("file"), async (req, res) => { }); console.log("====== 开始图像分析... ======"); - /* TLS 加密缓冲 */ - - for await (const chunk of completion) { if (stopFlags.get(requestId)) { - console.log("⛔ 图像分析被前端中断"); + console.log("⛔ 图像分析被中断"); res.end(); - stopFlags.delete(requestId); // 清理 + stopFlags.delete(requestId); return; } const delta = chunk.choices[0]?.delta?.content || ""; sceneText += delta; - if (delta) { - res.write( - `data: ${JSON.stringify({ type: "thinking", content: delta })}\n\n` - ); + res.write(`data: ${JSON.stringify({ type: "thinking", content: delta })}\n\n`); } } console.log(sceneText); console.log("====== 图像分析结束 ======"); } - // ------------------ ① 思路阶段(模拟 ChatGPT 思考) ------------------ + // ------------------ 思路阶段(核心修改:先呼应场景,再展开思考) ------------------ const thinkingMessages = [ { role: "system", - content: ` - 你是一名新能源项目AI分析师。 - - 请基于: - - 历史对话 - - 图片提供的【思路线索】 - - 输出【思路概览】。 - - 要求: - - 模拟人类分析路径 - - 不出现任何图片内容 - - 不给结论 - - 使用项目符号 - - 不出现“分析过程”“推理”"thinking""answer"等字眼 - ` + content: + ` + 你是新能源领域专业分析人员,以第一人称模拟自然思考,基于图片场景和用户需求切入,开头不重复图片描述,直接顺延展开深度思考,不输出最终结论和具体方案。 + 规则: + 1. 思考开头直接承接图片场景(光伏场站/梯田+割草需求),不重复完整描述句,自然切入需求解读 + 2. 思考逻辑:先解读用户要割草计划的表面诉求,再深挖背后核心顾虑,最后梳理需确认的基础信息,贴合场景属性 + 3. 语气:自然口语化,连贯段落,无列表、无具体步骤/工具/人员安排、无泛化表述 + 4. 必须结合图片标注特征(方阵编号/边界线)展开思考,体现场景关联性 + 示例(参考风格,无重复描述): + 用户要制定详细的割草计划,表面是想要一套可执行的作业流程,说白了就是希望在保障场站发电效率、作业安全的同时,平衡好长期运维成本,不过我得先琢磨他们真正担心的点。背后大概率在意割草会不会损伤光伏方阵或支架、作业时的安全风险管控,还有不同编号方阵和边界区域的作业能不能全覆盖、无死角,以及割草后植被反弹遮光影响发电收益的问题。再往深层想,他们还会关心草屑处理会不会堵塞排水或影响设备通风、是否符合环保监管要求、季节和天气对作业时机的影响,还有一次性清理和周期性维护的成本权衡。图里的方阵编号和绿色边界线,说明需要按区作业,还要关注场内通道和设施位置来规避风险,所以我会先梳理场站地形、植被类型、关键设施分布这些基础信息,明确作业优先级和潜在风险点。 + ` // 给示例,锚定用户想要的风格 }, ...historyMessages, - { - role: "user", - content: userPrompt || "请分析图片。" - }, - { - role: "assistant", - content: sceneText - } + { role: "user", content: userPrompt || "请分析图片并制定计划" }, + { role: "assistant", content: sceneText } // 传入精准场景描述 ]; const thinkingStream = await client1.chat.completions.create({ @@ -1610,10 +2410,7 @@ app.post("/analyze", upload.single("file"), async (req, res) => { messages: thinkingMessages }); - res.write( - `data: ${JSON.stringify({ type: "phase", content: "thinking" })}\n\n` - ); - + res.write(`data: ${JSON.stringify({ type: "phase", content: "thinking" })}\n\n`); let thinkingText = ""; console.log("====== 开始思路输出 ======"); @@ -1624,44 +2421,32 @@ app.post("/analyze", upload.single("file"), async (req, res) => { stopFlags.delete(requestId); return; } - const delta = chunk.choices[0]?.delta?.content || ""; thinkingText += delta; - if (delta) { - res.write( - `data: ${JSON.stringify({ type: "thinking", content: delta })}\n\n` - ); + res.write(`data: ${JSON.stringify({ type: "thinking", content: delta })}\n\n`); } } console.log(thinkingText); console.log("====== 思路输出结束 ======"); - // ------------------ ② 结论阶段(最终分析) ------------------ + // ------------------ 结论阶段(无修改,保持原有逻辑) ------------------ const answerMessages = [ { role: "system", content: ` -你是一名新能源项目分析AI。 - -请基于【思路概览】,输出【最终结论】。 - -要求: -- 专业、完整、可执行 -- 不复述思路 -- 不描述图片 -- 不出现“分析过程”“推理”"thinking""answer"等字眼 -` + 你是一名新能源项目分析AI。 + 请基于【思路概览】,输出【最终结论】。 + 要求: + - 专业、完整、可执行 + - 不复述思路 + - 不描述图片 + - 不出现“分析过程”“推理”"thinking""answer"等字眼 + ` }, ...historyMessages, - { - role: "user", - content: userPrompt || "请给出最终分析建议。" - }, - { - role: "assistant", - content: thinkingText - } + { role: "user", content: userPrompt || "请给出最终分析建议" }, + { role: "assistant", content: thinkingText } ]; const answerStream = await client1.chat.completions.create({ @@ -1670,10 +2455,7 @@ app.post("/analyze", upload.single("file"), async (req, res) => { messages: answerMessages }); - res.write( - `data: ${JSON.stringify({ type: "phase", content: "answer" })}\n\n` - ); - + res.write(`data: ${JSON.stringify({ type: "phase", content: "answer" })}\n\n`); let llmText = ""; console.log("====== 开始最终分析 ======"); @@ -1684,21 +2466,16 @@ app.post("/analyze", upload.single("file"), async (req, res) => { 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` - ); + res.write(`data: ${JSON.stringify({ type: "answer", content: delta })}\n\n`); } } console.log(llmText); console.log("====== 最终分析结束 ======"); - // ------------------ Save Results to Database ------------------ - + // ------------------ 保存结果到数据库(无修改) ------------------ sceneText += thinkingText; if (existingTitle) { await pool.query( @@ -1715,14 +2492,9 @@ app.post("/analyze", upload.single("file"), async (req, res) => { { role: "user", content: titlePrompt } ] }); - const title = titleResp.choices[0]?.message?.content?.trim() || "未命名会话"; - res.write( - `data: ${JSON.stringify({ type: "title", content: title })}\n\n` - ); + 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,thinking) VALUES (?, ?, 'assistant', ?, ?,?)", [sessId, userId || null, llmText, title,sceneText] @@ -1744,32 +2516,58 @@ app.post("/analyze", upload.single("file"), async (req, res) => { } catch (err) { console.error("❌ 调用失败:", err.message); - if (!res.headersSent) { res.status(500).json({ error: err.message }); } else { - res.write(`data: ${JSON.stringify({ - type: "error", - message: err.message - })}\n\n`); + res.write(`data: ${JSON.stringify({type: "error",message: err.message})}\n\n`); res.end(); } - } finally { + } finally { let userChatNum = await getQuota(userId); if (userChatNum !== -1 && userChatNum !== undefined) { - userChatNum = Math.max(0, userChatNum - 1); // 防止变成负数 + 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] - ); + 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); + stopFlags.set(requestId, true); + console.log(stopFlags, "消息id"); + res.json({ ok: true }); +}); +app.post('/submit', async (req, res) => { + const { company, scenes, needs, userId } = req.body; + + console.log("收到数据:", company, scenes, needs, userId); + + if (!company || !needs || !userId) { + return res.status(400).json({ message: "缺少必要字段" }); + } + + try { + const sql = + "INSERT INTO pv_form (company, scenes, needs, userId) VALUES (?, ?, ?, ?)"; + + await pool.query(sql, [company, scenes, needs, userId || null]); + + res.json({ code: 200, message: "提交成功!" }); + } catch (err) { + console.error("数据库插入失败:", err); + res.status(500).json({ message: "数据库写入失败" }); + } +}); + + app.post("/stop", express.json(), (req, res) => { const { requestId } = req.body; console.log("🔴 收到停止信号:", requestId);