换 ai apikey
This commit is contained in:
525
server2.js
525
server2.js
@@ -121,9 +121,13 @@ app.use(express.urlencoded({ extended: true })); // 支持 form-data 字段
|
||||
// 初始化客户端
|
||||
|
||||
const client = new OpenAI({
|
||||
//apiKey: "4pJOKPLhkW7qJQOeQBe9ACXyBrcu7A8bAjTgo2auV5lSUZbmcn1rJQQJ99CEACHYHv6XJ3w3AAAAACOGk1hV",
|
||||
//baseURL: "https://tech-mp6cvddb-eastus2.cognitiveservices.azure.com/",
|
||||
//apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
|
||||
apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
|
||||
baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-4.1",
|
||||
defaultQuery: { "api-version": "2024-04-01-preview" },
|
||||
baseURL: "https://satabot.services.ai.azure.com/openai/v1/",
|
||||
//baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-4.1",
|
||||
//defaultQuery: { "api-version": "2024-04-01-preview" },
|
||||
timeout: 120000,
|
||||
fetch: fetchIPv4, // 关键点:把自定义 fetch 注入 SDK
|
||||
// 确保底层也传 agent(可选,但保险)
|
||||
@@ -132,9 +136,14 @@ const client = new OpenAI({
|
||||
}
|
||||
});
|
||||
const client1 = new OpenAI({
|
||||
//apiKey: "4pJOKPLhkW7qJQOeQBe9ACXyBrcu7A8bAjTgo2auV5lSUZbmcn1rJQQJ99CEACHYHv6XJ3w3AAAAACOGk1hV",
|
||||
//baseURL: "https://tech-mp6cvddb-eastus2.cognitiveservices.azure.com/",
|
||||
//apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
|
||||
apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
|
||||
baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-5-mini/",
|
||||
defaultQuery: { "api-version": "2024-04-01-preview" },
|
||||
baseURL: "https://satabot.services.ai.azure.com/openai/v1/",
|
||||
|
||||
//baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-5-mini/",
|
||||
//defaultQuery: { "api-version": "2024-04-01-preview" },
|
||||
timeout: 120000,
|
||||
fetch: fetchIPv4, // 关键点:把自定义 fetch 注入 SDK
|
||||
// 确保底层也传 agent(可选,但保险)
|
||||
@@ -160,8 +169,8 @@ async function directCall() {
|
||||
console.timeEnd("directCall");
|
||||
}
|
||||
|
||||
test1();
|
||||
directCall();
|
||||
//test1();
|
||||
//directCall();
|
||||
|
||||
// 获取 access_token
|
||||
// getAccessToken();
|
||||
@@ -1391,7 +1400,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
// return res.status(400).json({ error: "对话次数已用完,请完成账号注册" });
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// console.log("====== 开始图像分析11... ======");
|
||||
|
||||
// const { requestId } = req.body;
|
||||
@@ -1445,7 +1454,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
// "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... ======");
|
||||
|
||||
// // 构建历史消息数组
|
||||
@@ -1474,7 +1483,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
|
||||
// return message;
|
||||
// });
|
||||
|
||||
|
||||
// console.log("====== 开始图像分析33... ======");
|
||||
|
||||
// // ------------------ 保存用户提问 ------------------
|
||||
@@ -1485,7 +1494,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
|
||||
// // ------------------ 图像分析 ------------------
|
||||
// let sceneText = "";
|
||||
|
||||
|
||||
// console.log("====== 开始图像分析55... ======");
|
||||
|
||||
// // If an image is uploaded, start with image analysis
|
||||
@@ -1539,7 +1548,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
// ].filter(Boolean),
|
||||
// },
|
||||
// ];
|
||||
|
||||
|
||||
// console.log("====== 开始图像分析666... ======");
|
||||
|
||||
// const completion = await client.chat.completions.create({
|
||||
@@ -1550,7 +1559,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
|
||||
// console.log("====== 开始图像分析... ======");
|
||||
// /* TLS 加密缓冲 */
|
||||
|
||||
|
||||
|
||||
// for await (const chunk of completion) {
|
||||
// if (stopFlags.get(requestId)) {
|
||||
@@ -1722,7 +1731,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
// );
|
||||
// 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]
|
||||
@@ -1799,7 +1808,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
// return res.status(400).json({ error: "对话次数已用完,请完成账号注册" });
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// console.log("====== 开始图像分析11... ======");
|
||||
|
||||
// const { requestId } = req.body;
|
||||
@@ -1814,7 +1823,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
|
||||
// res.flushHeaders?.();
|
||||
// res.socket.setNoDelay(true); // 禁用缓冲,实时推送
|
||||
|
||||
|
||||
// let aborted = false;
|
||||
// req.on("close", () => {
|
||||
// console.log("🔴 前端断开连接,停止整个分析流程");
|
||||
@@ -1856,7 +1865,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
// "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... ======");
|
||||
|
||||
// // 构建历史消息数组
|
||||
@@ -1885,7 +1894,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
|
||||
// return message;
|
||||
// });
|
||||
|
||||
|
||||
// console.log("====== 开始图像分析33... ======");
|
||||
|
||||
// // ------------------ 保存用户提问 ------------------
|
||||
@@ -1896,7 +1905,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
|
||||
// // ------------------ 图像分析 ------------------
|
||||
// let sceneText = "";
|
||||
|
||||
|
||||
// console.log("====== 开始图像分析55... ======");
|
||||
|
||||
// // If an image is uploaded, start with image analysis
|
||||
@@ -1938,7 +1947,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
// ].filter(Boolean),
|
||||
// },
|
||||
// ];
|
||||
|
||||
|
||||
// console.log("====== 开始图像分析666... ======");
|
||||
|
||||
// const completion = await client.chat.completions.create({
|
||||
@@ -1949,7 +1958,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
// });
|
||||
|
||||
// console.log("====== 开始图像分析... ======");
|
||||
|
||||
|
||||
|
||||
// for await (const chunk of completion) {
|
||||
// if (stopFlags.get(requestId)) {
|
||||
@@ -2147,7 +2156,7 @@ app.post("/refreshAiChatNum", async (req, res) => {
|
||||
// );
|
||||
// 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]
|
||||
@@ -2196,31 +2205,31 @@ 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 { 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;
|
||||
console.log("====== 开始图像分析11... ======");
|
||||
|
||||
const { requestId } = req.body;
|
||||
|
||||
/* TLS 加密缓冲,小块数据可能先缓存在服务器端,不立即发送,会导致图像分析模型的结果不能立即返回 */
|
||||
res.writeHead(200, {
|
||||
@@ -2228,31 +2237,31 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
"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;
|
||||
});
|
||||
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);
|
||||
|
||||
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(
|
||||
`
|
||||
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
|
||||
@@ -2262,67 +2271,66 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
AND title != ''
|
||||
) AS hasTitle
|
||||
`,
|
||||
[sessionId, userId]
|
||||
);
|
||||
const existingTitle = rows[0].hasTitle === 1; // true / false
|
||||
console.log(existingTitle, rows?.[0], 9999, rows);
|
||||
[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 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]
|
||||
);
|
||||
|
||||
// 构建历史消息数组
|
||||
const historyMessages = historyRows.slice(-MAX_HISTORY).map(row => {
|
||||
const message = {
|
||||
role: row.role,
|
||||
content: []
|
||||
};
|
||||
console.log("====== 开始图像分析22... ======");
|
||||
|
||||
// 添加文本内容
|
||||
if (row.content) {
|
||||
message.content.push({ type: "text", text: row.content });
|
||||
}
|
||||
// 构建历史消息数组
|
||||
const historyMessages = historyRows.slice(-MAX_HISTORY).map(row => {
|
||||
const message = {
|
||||
role: row.role,
|
||||
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}`,
|
||||
}
|
||||
});
|
||||
}
|
||||
// 添加文本内容
|
||||
if (row.content) {
|
||||
message.content.push({ type: "text", text: row.content });
|
||||
}
|
||||
|
||||
return message;
|
||||
});
|
||||
|
||||
console.log("====== 开始图像分析33... ======");
|
||||
// 添加图片内容
|
||||
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}`,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------ 保存用户提问 ------------------
|
||||
await pool.query(
|
||||
"INSERT INTO ai_chat_messages (session_id, user_id, role, content, image_base64) VALUES (?, ?, 'user', ?, ?)",
|
||||
[sessId, userId || null, userPrompt || "", imageBase64 || null]
|
||||
);
|
||||
return message;
|
||||
});
|
||||
|
||||
// ------------------ 图像分析(核心修改:输出精准场景描述) ------------------
|
||||
let sceneText = "";
|
||||
|
||||
console.log("====== 开始图像分析55... ======");
|
||||
console.log("====== 开始图像分析33... ======");
|
||||
|
||||
if (imageBase64)
|
||||
{
|
||||
const imageAnalysisMessages = [
|
||||
{
|
||||
role: "system",
|
||||
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]
|
||||
);
|
||||
|
||||
// ------------------ 图像分析(核心修改:输出精准场景描述) ------------------
|
||||
let sceneText = "";
|
||||
|
||||
console.log("====== 开始图像分析55... ======");
|
||||
|
||||
if (imageBase64) {
|
||||
const imageAnalysisMessages = [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
// `
|
||||
// 你是图像场景描述助手,严格按指定格式输出1次图片描述,绝不重复、无多余内容、无分析建议。
|
||||
// 输出格式(固定话术,仅替换【】内容,必须1句完整文本):
|
||||
@@ -2333,7 +2341,7 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
// 3. 【标注特征】:方阵编号和绿色边界线/地块编号和边界标注(按图实际)
|
||||
// 强制要求:仅输出1次上述格式文本,无重复、无追加、无解释
|
||||
// ` // 核心:引导精准场景描述,摒弃泛化内容
|
||||
`
|
||||
`
|
||||
你是图像场景描述助手,任务是根据用户上传的图片内容,输出一段自然且详细的描述。根据图片内容判断场景类型和任务需求,并结合图片的特征进行分析和推理。确保输出的描述自然流畅,符合图片中的实际情况。
|
||||
输出格式(固定话术,仅替换【】内容,必须1句完整文本):
|
||||
用户上传了一张【图片类型】的图片,从图片描述来看,这明显是一个【场景属性】的图片,有【标注特征】的特点。
|
||||
@@ -2345,13 +2353,14 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
|
||||
强制要求:输出一次自然流畅、无死板格式的描述,并根据图片分析作业计划的可行性。要避免固定模板输出,更多地展现图片内容与作业任务的关联,并基于推理得出任务可能的实施方式。
|
||||
`
|
||||
},
|
||||
...historyMessages,
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text:
|
||||
`
|
||||
},
|
||||
...historyMessages,
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text", text:
|
||||
`
|
||||
输出格式要求(固定话术,仅替换【】内容,必须1句完整文本):
|
||||
用户上传了一张【图片类型】的图片,从图片描述来看,这明显是一个【场景属性】的图片,有【标注特征】的特点。
|
||||
1. 【图片类型】:根据图片内容,判断为如光伏场站、农业梯田、坡地、森林、生态保护区、矿区等(根据实际场景推断)。
|
||||
@@ -2363,57 +2372,57 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
强制要求:输出一次自然流畅、无死板格式的描述,并根据图片分析作业计划的可行性。要避免固定模板输出,更多地展现图片内容与作业任务的关联,并基于推理得出任务可能的实施方式。
|
||||
`
|
||||
}, // 贴合用户想要的描述风格
|
||||
imageBase64 ? {
|
||||
type: "image_url",
|
||||
image_url: {
|
||||
url: imageBase64.startsWith("data:") ? imageBase64 : `data:image/png;base64,${imageBase64}`,
|
||||
},
|
||||
} : null,
|
||||
].filter(Boolean),
|
||||
},
|
||||
];
|
||||
|
||||
// 去掉max_tokens限制,保证描述完整
|
||||
const completion = await client.chat.completions.create({
|
||||
model: "gpt-4.1",
|
||||
stream: true,
|
||||
messages: imageAnalysisMessages
|
||||
});
|
||||
imageBase64 ? {
|
||||
type: "image_url",
|
||||
image_url: {
|
||||
url: imageBase64.startsWith("data:") ? imageBase64 : `data:image/png;base64,${imageBase64}`,
|
||||
},
|
||||
} : null,
|
||||
].filter(Boolean),
|
||||
},
|
||||
];
|
||||
|
||||
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("====== 图像分析结束 ======");
|
||||
}
|
||||
// 去掉max_tokens限制,保证描述完整
|
||||
const completion = await client.chat.completions.create({
|
||||
model: "gpt-5.6-luna",
|
||||
stream: true,
|
||||
messages: imageAnalysisMessages
|
||||
});
|
||||
|
||||
// ------------------ 思路阶段(核心修改:先呼应场景,再展开思考) ------------------
|
||||
const thinkingMessages = [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
// `
|
||||
// 你是新能源领域专业分析人员,以第一人称模拟自然思考,基于图片场景和用户需求切入,开头不重复图片描述,直接顺延展开深度思考,不输出最终结论和具体方案。
|
||||
// 规则:
|
||||
// 1. 思考开头直接承接图片场景(光伏场站/梯田+割草需求),不重复完整描述句,自然切入需求解读
|
||||
// 2. 思考逻辑:先解读用户要割草计划的表面诉求,再深挖背后核心顾虑,最后梳理需确认的基础信息,贴合场景属性
|
||||
// 3. 语气:自然口语化,连贯段落,无列表、无具体步骤/工具/人员安排、无泛化表述
|
||||
// 4. 必须结合图片标注特征(方阵编号/边界线)展开思考,体现场景关联性
|
||||
// 示例(参考风格,无重复描述):
|
||||
// 用户要制定详细的割草计划,表面是想要一套可执行的作业流程,说白了就是希望在保障场站发电效率、作业安全的同时,平衡好长期运维成本,不过我得先琢磨他们真正担心的点。背后大概率在意割草会不会损伤光伏方阵或支架、作业时的安全风险管控,还有不同编号方阵和边界区域的作业能不能全覆盖、无死角,以及割草后植被反弹遮光影响发电收益的问题。再往深层想,他们还会关心草屑处理会不会堵塞排水或影响设备通风、是否符合环保监管要求、季节和天气对作业时机的影响,还有一次性清理和周期性维护的成本权衡。图里的方阵编号和绿色边界线,说明需要按区作业,还要关注场内通道和设施位置来规避风险,所以我会先梳理场站地形、植被类型、关键设施分布这些基础信息,明确作业优先级和潜在风险点。
|
||||
// ` // 给示例,锚定用户想要的风格
|
||||
`
|
||||
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("====== 图像分析结束 ======");
|
||||
}
|
||||
|
||||
// ------------------ 思路阶段(核心修改:先呼应场景,再展开思考) ------------------
|
||||
const thinkingMessages = [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
// `
|
||||
// 你是新能源领域专业分析人员,以第一人称模拟自然思考,基于图片场景和用户需求切入,开头不重复图片描述,直接顺延展开深度思考,不输出最终结论和具体方案。
|
||||
// 规则:
|
||||
// 1. 思考开头直接承接图片场景(光伏场站/梯田+割草需求),不重复完整描述句,自然切入需求解读
|
||||
// 2. 思考逻辑:先解读用户要割草计划的表面诉求,再深挖背后核心顾虑,最后梳理需确认的基础信息,贴合场景属性
|
||||
// 3. 语气:自然口语化,连贯段落,无列表、无具体步骤/工具/人员安排、无泛化表述
|
||||
// 4. 必须结合图片标注特征(方阵编号/边界线)展开思考,体现场景关联性
|
||||
// 示例(参考风格,无重复描述):
|
||||
// 用户要制定详细的割草计划,表面是想要一套可执行的作业流程,说白了就是希望在保障场站发电效率、作业安全的同时,平衡好长期运维成本,不过我得先琢磨他们真正担心的点。背后大概率在意割草会不会损伤光伏方阵或支架、作业时的安全风险管控,还有不同编号方阵和边界区域的作业能不能全覆盖、无死角,以及割草后植被反弹遮光影响发电收益的问题。再往深层想,他们还会关心草屑处理会不会堵塞排水或影响设备通风、是否符合环保监管要求、季节和天气对作业时机的影响,还有一次性清理和周期性维护的成本权衡。图里的方阵编号和绿色边界线,说明需要按区作业,还要关注场内通道和设施位置来规避风险,所以我会先梳理场站地形、植被类型、关键设施分布这些基础信息,明确作业优先级和潜在风险点。
|
||||
// ` // 给示例,锚定用户想要的风格
|
||||
`
|
||||
你是新能源领域的专业分析人员,以第一人称模拟自然思考。基于用户提供的图片场景,直奔用户需求,避免重复场景描述,直接通过场景细节展开深度思考,不输出最终结论和具体方案。
|
||||
规则:
|
||||
1. 思考开始时直接切入用户的实际需求,结合图片中的内容,避免不必要的描述,准确把握用户核心诉求。
|
||||
@@ -2424,23 +2433,23 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
示例:
|
||||
用户希望制定一份割草计划,表面上看,是为了确保割草作业高效、安全,并控制运维成本。但从我对场站的理解来看,他们更深层次的顾虑是割草时是否会影响到光伏方阵的正常运作,或者是否会对设备的支架产生影响。再者,割草过程中需要特别注意的是,不同区域、不同编号方阵的处理,可能需要差异化的作业策略,确保每个区域都能得到充分覆盖,且不留死角。我还需要思考割草后的草屑是否会影响场站的排水系统、设备的通风散热,甚至环保合规问题。基于这些疑虑,我需要进一步确认场站的地形特点、植被情况、方阵分布,以及重要设施的位置,以便更好地评估潜在的作业风险与优先级。
|
||||
`
|
||||
},
|
||||
...historyMessages,
|
||||
{ role: "user", content: userPrompt || "请分析图片并制定计划" },
|
||||
{ role: "assistant", content: sceneText } // 传入精准场景描述
|
||||
];
|
||||
const thinkingStream = await client.chat.completions.create({
|
||||
model: "gpt-4",
|
||||
stream: true,
|
||||
messages: thinkingMessages,
|
||||
max_completion_tokens: 1500, // 根据需要调整
|
||||
});
|
||||
},
|
||||
...historyMessages,
|
||||
{ role: "user", content: userPrompt || "请分析图片并制定计划" },
|
||||
{ role: "assistant", content: sceneText } // 传入精准场景描述
|
||||
];
|
||||
const thinkingStream = await client.chat.completions.create({
|
||||
model: "gpt-5.6-luna",
|
||||
stream: true,
|
||||
messages: thinkingMessages,
|
||||
max_completion_tokens: 1500, // 根据需要调整
|
||||
});
|
||||
|
||||
res.write(`data: ${JSON.stringify({ type: "phase", content: "thinking" })}\n\n`);
|
||||
let thinkingText = "";
|
||||
res.write(`data: ${JSON.stringify({ type: "phase", content: "thinking" })}\n\n`);
|
||||
let thinkingText = "";
|
||||
|
||||
console.log("====== 开始思路输出 ======");
|
||||
for await (const chunk of thinkingStream) {
|
||||
console.log("====== 开始思路输出 ======");
|
||||
for await (const chunk of thinkingStream) {
|
||||
if (stopFlags.get(requestId)) {
|
||||
console.log("⛔ 思路阶段被中断");
|
||||
res.end();
|
||||
@@ -2463,14 +2472,14 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(thinkingText);
|
||||
console.log("====== 思路输出结束 ======");
|
||||
console.log(thinkingText);
|
||||
console.log("====== 思路输出结束 ======");
|
||||
|
||||
// ------------------ 结论阶段(无修改,保持原有逻辑) ------------------
|
||||
const answerMessages = [
|
||||
{
|
||||
role: "system",
|
||||
content: `
|
||||
// ------------------ 结论阶段(无修改,保持原有逻辑) ------------------
|
||||
const answerMessages = [
|
||||
{
|
||||
role: "system",
|
||||
content: `
|
||||
你是一名新能源项目分析AI。
|
||||
请基于【思路概览】,输出【最终结论】。
|
||||
要求:
|
||||
@@ -2479,23 +2488,23 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
- 不描述图片
|
||||
- 不出现“分析过程”“推理”"thinking""answer"等字眼
|
||||
`
|
||||
},
|
||||
...historyMessages,
|
||||
{ role: "user", content: userPrompt || "请给出最终分析建议" },
|
||||
{ role: "assistant", content: thinkingText }
|
||||
];
|
||||
},
|
||||
...historyMessages,
|
||||
{ role: "user", content: userPrompt || "请给出最终分析建议" },
|
||||
{ role: "assistant", content: thinkingText }
|
||||
];
|
||||
|
||||
const answerStream = await client.chat.completions.create({
|
||||
model: "gpt-4",
|
||||
stream: true,
|
||||
messages: answerMessages
|
||||
});
|
||||
const answerStream = await client.chat.completions.create({
|
||||
model: "gpt-5.6-luna",
|
||||
stream: true,
|
||||
messages: answerMessages
|
||||
});
|
||||
|
||||
res.write(`data: ${JSON.stringify({ type: "phase", content: "answer" })}\n\n`);
|
||||
let llmText = "";
|
||||
res.write(`data: ${JSON.stringify({ type: "phase", content: "answer" })}\n\n`);
|
||||
let llmText = "";
|
||||
|
||||
console.log("====== 开始最终分析 ======");
|
||||
for await (const chunk of answerStream) {
|
||||
console.log("====== 开始最终分析 ======");
|
||||
for await (const chunk of answerStream) {
|
||||
if (stopFlags.get(requestId)) {
|
||||
console.log("⛔ 最终分析被中断");
|
||||
res.end();
|
||||
@@ -2504,7 +2513,7 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
}
|
||||
|
||||
const delta = chunk.choices[0]?.delta?.content || "";
|
||||
|
||||
|
||||
// 如果 delta 为空,不处理
|
||||
if (delta) {
|
||||
// 逐字输出
|
||||
@@ -2520,64 +2529,64 @@ app.post("/analyze", upload.single("file"), async (req, res) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(llmText);
|
||||
console.log("====== 最终分析结束 ======");
|
||||
console.log(llmText);
|
||||
console.log("====== 最终分析结束 ======");
|
||||
|
||||
// ------------------ 保存结果到数据库(无修改) ------------------
|
||||
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);
|
||||
}
|
||||
}
|
||||
// ------------------ 保存结果到数据库(无修改) ------------------
|
||||
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.6-luna",
|
||||
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();
|
||||
res.write("data: [DONE]\n\n");
|
||||
res.end();
|
||||
|
||||
// 删除上传文件
|
||||
if (req.file && req.file.path) {
|
||||
fs.unlinkSync(req.file.path);
|
||||
}
|
||||
console.log("====== 分析完成 ======");
|
||||
// 删除上传文件
|
||||
if (req.file && req.file.path) {
|
||||
fs.unlinkSync(req.file.path);
|
||||
}
|
||||
console.log("====== 分析完成 ======");
|
||||
|
||||
} catch (err) {
|
||||
} 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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user