加入思考过程

This commit is contained in:
2026-01-24 16:46:45 +08:00
parent 19cd48a495
commit 60b1bcf807

View File

@@ -1037,338 +1037,608 @@ 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 { 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",
// "Connection": "keep-alive",
// });
// res.socket.setNoDelay(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;
// if (delta) {
// res.write(
// `data: ${JSON.stringify({ type: "scene", content: delta })}\n\n`
// );
// }
// // 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("====== 图像分析结束 ======")
// // 保存图像分析结果到数据库
// // await pool.query(
// // "INSERT INTO ai_chat_messages (session_id, user_id, role, content) VALUES (?, ?, 'assistant', ?)",
// // [sessId, userId || null, sceneText]
// // );
// // ------------------ 3️⃣ LLM 分析 ------------------
// // console.log("LLM 分析 sceneText: ", sceneText)
// // const preText = "详细分析这个光伏场站,根据图片给出详细的运维方案"
// // + "如果这个光伏场站需要割草怎么做,给出一份详细的计划"
// // + "如果这个光伏场站需要用无人机清洗怎么做,给出一份详细的计划"
// // + "如果这个光伏场站需要巡检怎么做,给出一份详细的计划"
// // 构建包含完整上下文的LLM消息数组
// const llmMessages = [
// {
// role: "system",
// content: "你是一名新能源项目的图像分析AI分析师,需要结合历史对话提供专业建议。"
// },
// ...historyMessages, // 添加历史对话作为上下文
// {
// role: "user",
// content: userPrompt || "请基于图片分析提供建议。"
// },
// {
// role: "assistant",
// content: sceneText // 添加当前图像分析结果
// }
// ];
// const llmStream = await client1.chat.completions.create({
// model: "gpt-5-mini",
// stream: true,
// messages: llmMessages
// });
// 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;
// if (delta)
// res.write(
// `data: ${JSON.stringify({ type: "llm", content: delta })}\n\n`
// );
// /* 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)); // 控制渲染节奏
// // }
// // }
// }
// // 保存 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]
// );
// } 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: [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);
// }
// }
// });
// const app = express();
// const upload = require('multer')({ dest: 'uploads/' });
// const fs = require('fs');
// 替换为你的工具模块
// const { pool, client, client1, hasQuota, getQuota, setQuota, stopFlags } = require('./your-utils');
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;
});
// 统一 SSE 响应头
res.writeHead(200, {
"Content-Type": "text/event-stream; charset=utf-8",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Transfer-Encoding": "chunked"
});
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);
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)
if (!req.body?.userId || !req.body?.sessionId) {
console.log("缺少必要参数");
return res.write(`data: ${JSON.stringify({ type: "error", content: "缺少必要参数" })}\n\ndata: [DONE]\n\n`), res.end();
}
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;
console.log(existingTitle, rows?.[0], 9999, rows);
// 如果前端没有传 sessionId,生成一个
const sessId = sessionId || Date.now().toString();
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 [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 => ({
role: row.role,
content: [
row.content ? { type: "text", text: row.content } : null,
row.image_base64 ? {
type: "image_url",
image_url: { url: row.image_base64.startsWith("data:") ? row.image_base64 : `data:image/png;base64,${row.image_base64}` }
} : null
].filter(Boolean)
}));
// 构建历史消息数组
const historyMessages = historyRows.map(row => {
const message = {
role: row.role,
content: []
};
// ------------------ 保存用户提问 ------------------
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 (row.content) {
message.content.push({ type: "text", text: row.content });
}
// ------------------ 1. 图像分析(GPT 直接返回思考过程 + 结果) ------------------
console.log("====== 开始图像分析... ======");
const imageAnalysisMessages = [
{
role: "system",
content: `你是专注于「新能源运维+智能设备」领域的图像分析专家,同时具备行业信息解答能力,严格按以下规则输出,流式返回且格式不可错乱:
1. 思考过程:第一人称推导不需要明确显示是第一人称,先判断用户需求类型(是图像分析还是信息咨询/设备相关问题),再拆解核心诉求,结合行业经验梳理推理逻辑——若为信息咨询,需关联场景(如光伏运维)补充背景;若为图像问题,需按「元素识别→状态判断→需求匹配」推导,全程贴合用户提问重点,不冗余无关信息。
2. 分隔符:必须用「---分析结果---」清晰分割思考过程与最终内容,分隔符单独成行,前后无多余文字。
3. 最终分析结果:结构简洁,先明确核心结论,再补充关键细节(如企业业务、产品特性、场景适配性),结尾预留延伸空间(引导用户补充信息以获取更精准内容),语言专业且落地,符合光伏运维等实际场景需求。
// 添加图片内容
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}`,
}
});
}
要求:思考过程体现「需求判断→逻辑拆解→信息整合」的完整链路,必须按照思考过程-分析结果-最终分析结果的格式排版。最终结果聚焦用户核心诉求,不偏离主题,适配后续LLM深度分析的衔接。`
},
...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)
}
];
return message;
});
const sceneCompletion = await client.chat.completions.create({
model: "gpt-4.1",
stream: true,
messages: imageAnalysisMessages,
// temperature: 0.3 // 降低随机性,保证格式稳定
});
// 流式解析:拆分思考过程和分析结果
let sceneState = "thinking"; // thinking: 思考过程阶段, result: 分析结果阶段
let sceneThinkingText = "", sceneText = "";
const SCENE_SPLITTER = "---分析结果---";
// ------------------ 保存用户提问 ------------------
await pool.query(
"INSERT INTO ai_chat_messages (session_id, user_id, role, content, image_base64) VALUES (?, ?, 'user', ?, ?)",
[sessId, userId || null, userPrompt || "", imageBase64 || null]
);
for await (const chunk of sceneCompletion) {
if (stopFlags.get(requestId) || aborted) {
console.log("⛔ 图像分析被中断");
return res.end(), stopFlags.delete(requestId);
}
const delta = chunk.choices[0]?.delta?.content || "";
if (!delta) continue;
// console.log("userPrompt: ",userPrompt);
// console.log("imageBase64: ",imageBase64);
// console.log("userId: ",userId);
// console.log("sessionId: ",sessionId);
// 状态切换:遇到分隔符则切换到结果阶段
if (sceneState === "thinking" && delta.includes(SCENE_SPLITTER)) {
const [thinkingPart, resultPart] = delta.split(SCENE_SPLITTER);
sceneThinkingText += thinkingPart;
// 推送思考过程
res.write(`data: ${JSON.stringify({ type: "scene_thinking", content: thinkingPart })}\n\n`);
sceneState = "result";
// 推送分隔符后的结果片段
if (resultPart) {
sceneText += resultPart;
res.write(`data: ${JSON.stringify({ type: "scene", content: resultPart })}\n\n`);
}
} else if (sceneState === "thinking") {
// 持续推送思考过程
sceneThinkingText += delta;
res.write(`data: ${JSON.stringify({ type: "scene_thinking", content: delta })}\n\n`);
} else {
// 持续推送分析结果
sceneText += delta;
res.write(`data: ${JSON.stringify({ type: "scene", content: delta })}\n\n`);
}
}
console.log("====== 图像分析思考过程 ======\n", sceneThinkingText);
console.log("====== 图像分析结果 ======\n", sceneText);
console.log("====== 图像分析结束 ======");
// ------------------ 图像分析 ------------------
// 构建包含上下文的消息数组
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),
},
];
// ------------------ 2. LLM 深度分析(GPT 直接返回思考过程 + 结果) ------------------
console.log("====== 开始深度思考... ======");
const llmMessages = [
{
role: "system",
content: `你是新能源(光伏/风电)场站运维专家,擅长结合智能设备(如割草机器人)给出可落地的标准化方案,严格按以下规则输出,流式返回且格式不可错乱:
1. 思考过程:第一人称推导,先拆解用户核心需求(如设备选型、部署落地、运维优化),再搭建分析框架(按「目标确认→资料收集→现场勘察→试点验证→落地推广」逻辑),明确核心评估维度(安全兼容性、自动化可靠性、运维便利性、经济性),说明方案优先级原则(如「可操作性优先、风险可控」),全程紧扣用户提及的设备品牌(如飒踏智能),推导逻辑与后续方案强关联。
2. 分隔符:必须用「---方案结果---」清晰分割思考过程与最终内容,分隔符单独成行,前后无多余文字。
3. 最终运维方案:按「时间节点明确、步骤可落地、指标可量化」原则,拆分各阶段任务(含时间周期、核心动作、输出物),补充标准化SOP、KPI设定、风险防控要点,兼顾安全性(对光伏设备无损伤)、实用性(适配场站场景)、经济性(成本可控),方案结构清晰,可直接用于场站作业指导或采购决策。
const completion = await client.chat.completions.create({
model: "gpt-4.1",
stream: true,
messages: imageAnalysisMessages
});
要求:思考过程不跳跃,每个分析维度对应方案中的具体环节;最终方案避免空泛,补充可量化指标、资料清单、勘察要点等细节,同时预留灵活调整空间(适配不同场站规模、设备型号)。`
},
...historyMessages,
{ role: "user", content: userPrompt || "请基于图片分析提供建议" },
{ role: "assistant", content: sceneText }
];
console.log("====== 开始图像分析... ======")
let sceneText = "";
const llmStream = await client1.chat.completions.create({
model: "gpt-5-mini",
stream: true,
messages: llmMessages,
// temperature: 0.4
});
/* TLS 加密缓冲,小块数据可能先缓存在服务器端,不立即发送,会导致图像分析模型的结果不能立即返回 */
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
});
res.socket.setNoDelay(true);
// 流式解析:拆分LLM思考过程和方案结果
let llmState = "thinking";
let llmThinkingText = "", llmText = "";
const LLM_SPLITTER = "---方案结果---";
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;
for await (const chunk of llmStream) {
if (stopFlags.get(requestId) || aborted) {
console.log("⛔ 深度分析被中断");
return res.end(), stopFlags.delete(requestId);
}
const delta = chunk.choices[0]?.delta?.content || "";
if (!delta) continue;
if (delta) {
res.write(
`data: ${JSON.stringify({ type: "scene", content: delta })}\n\n`
);
}
if (llmState === "thinking" && delta.includes(LLM_SPLITTER)) {
const [thinkingPart, resultPart] = delta.split(LLM_SPLITTER);
llmThinkingText += thinkingPart;
res.write(`data: ${JSON.stringify({ type: "llm_thinking", content: thinkingPart })}\n\n`);
llmState = "result";
if (resultPart) {
llmText += resultPart;
res.write(`data: ${JSON.stringify({ type: "llm", content: resultPart })}\n\n`);
}
} else if (llmState === "thinking") {
llmThinkingText += delta;
res.write(`data: ${JSON.stringify({ type: "llm_thinking", content: delta })}\n\n`);
} else {
llmText += delta;
res.write(`data: ${JSON.stringify({ type: "llm", content: delta })}\n\n`);
}
}
console.log("====== LLM思考过程 ======\n", llmThinkingText);
console.log("====== LLM方案结果 ======\n", llmText);
console.log("====== 深度思考结束 ======");
// 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("====== 图像分析结束 ======")
// ------------------ 保存结果 & 生成标题 ------------------
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 titleResp = await client1.chat.completions.create({
model: "gpt-5-mini",
messages: [
{ role: "system", content: "只返回15字以内的标题,无解释" },
{ role: "user", content: `用户问题: ${userPrompt}\nAI回答: ${llmText}` }
]
});
const title = titleResp.choices[0]?.message?.content?.trim() || "未命名会话";
res.write(`data: ${JSON.stringify({ type: "title", content: title })}\n\n`);
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);
}
}
// 保存图像分析结果到数据库
// await pool.query(
// "INSERT INTO ai_chat_messages (session_id, user_id, role, content) VALUES (?, ?, 'assistant', ?)",
// [sessId, userId || null, sceneText]
// );
// ------------------ 结束流程 ------------------
res.write("data: [DONE]\n\n");
res.end();
if (req.file?.path) fs.unlinkSync(req.file.path);
console.log("====== 分析完成 ======");
console.timeEnd("analyze");
// ------------------ 3️⃣ LLM 分析 ------------------
// console.log("LLM 分析 sceneText: ", sceneText)
// const preText = "详细分析这个光伏场站,根据图片给出详细的运维方案"
// + "如果这个光伏场站需要割草怎么做,给出一份详细的计划"
// + "如果这个光伏场站需要用无人机清洗怎么做,给出一份详细的计划"
// + "如果这个光伏场站需要巡检怎么做,给出一份详细的计划"
// 构建包含完整上下文的LLM消息数组
const llmMessages = [
{
role: "system",
content: "你是一名新能源项目的图像分析AI分析师,需要结合历史对话提供专业建议。"
},
...historyMessages, // 添加历史对话作为上下文
{
role: "user",
content: userPrompt || "请基于图片分析提供建议。"
},
{
role: "assistant",
content: sceneText // 添加当前图像分析结果
}
];
const llmStream = await client1.chat.completions.create({
model: "gpt-5-mini",
stream: true,
messages: llmMessages
});
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;
if (delta)
res.write(
`data: ${JSON.stringify({ type: "llm", content: delta })}\n\n`
);
/* 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)); // 控制渲染节奏
// }
// }
}
// 保存 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]
);
} 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: [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);
}
}
} catch (err) {
console.error("❌ 调用失败:", err.response?.data || err.message);
res.write(`data: ${JSON.stringify({ type: "error", content: err.response?.data || err.message })}\n\n`);
res.write("data: [DONE]\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);
}
}
});
module.exports = app;
app.post("/stop", express.json(), (req, res) => {
const { requestId } = req.body;
console.log("🔴 收到停止信号:", requestId);