Files
AI/server2.js

449 lines
13 KiB
JavaScript
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// server.js
const express = require("express");
const multer = require("multer");
const cors = require("cors");
const fs = require("fs");
const path = require("path");
const OpenAI = require("openai");
const bodyParser = require("body-parser");
const app = express();
const port = 30009;
const mysql = require("mysql2/promise");
// 初始化数据库连接池
const pool = mysql.createPool({
host: "1.95.137.212",
user: "fastbee",
password: "maibu520",
database: "fastbee",
waitForConnections: true,
connectionLimit: 10,
});
// 允许大文件请求体
app.use(bodyParser.json({ limit: "500mb" }));
app.use(bodyParser.urlencoded({ limit: "500mb", extended: true }));
// Multer 上传目录
const upload = multer({
dest: "uploads/",
limits: { fileSize: 500 * 1024 * 1024 }, // 500MB
});
// Node <20 兼容 File
if (typeof File === "undefined") {
globalThis.File = require("node:buffer").File;
}
// 启用 CORS
app.use(cors());
app.use(express.json({ limit: "200mb" })); // 支持大 JSON
app.use(express.urlencoded({ extended: true })); // 支持 form-data 字段
// 初始化混元客户端
// const client = new OpenAI({
// apiKey: "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",
// baseURL: "https://openrouter.ai/api/v1",
// timeout: 120000,
// });
// const client = new OpenAI({
// baseURL:"https://openrouter.ai/api/v1",
// apiKey:"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",
// timeout: 120000
// });
// const client = new OpenAI({
// baseURL:"https://satabot.openai.azure.com/",
// apiKey:"DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
// timeout: 120000
// });
//https://satabot.openai.azure.com/
// const client = new OpenAI({
// apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp", // 推荐放环境变量
// baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-4.1/chat/completions?api-version=2025-01-01-preview",
// defaultQuery: { "api-version": "2024-02-15-preview" },
// defaultHeaders: { "api-key": process.env.AZURE_OPENAI_KEY },
// timeout: 120000
// });
// https://satabot.openai.azure.com/openai/deployments/gpt-4.1/
// https://satabot.openai.azure.com/openai/deployments/gpt-5-mini/
// 初始化客户端
const client = new OpenAI({
apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-4.1",
defaultQuery: { "api-version": "2024-04-01-preview" },
timeout: 120000
});
const client1 = new OpenAI({
apiKey: "DxvRiQWROzLTapiSHcK0FXKAAXDBlhci1XaqTTpr50sgUsAkmxfUJQQJ99BIACYeBjFXJ3w3AAABACOGZdHp",
baseURL: "https://satabot.openai.azure.com/openai/deployments/gpt-5-mini/",
defaultQuery: { "api-version": "2024-04-01-preview" },
timeout: 120000
});
app.get("/test-db", async (req, res) => {
try {
await pool.query(
"INSERT INTO ai_chat_messages (session_id, role, content) VALUES (?, 'user', ?)",
["sess1", "hello"]
);
res.send("OK");
} catch (err) {
console.error(err);
res.send("FAIL");
}
});
// 查询用户所有会话列表
app.get("/sessions", async (req, res) => {
console.log("收到的 userId:", req.query.userId);
try {
const userId = req.query.userId;
if (!userId || userId == "NaN" || isNaN(Number(userId)))
{
console.log("userId:", req.query.userId);
res.json({
code: 300,
error: "http://1.95.137.212/#/login?goRemote=1"
});
return;
// return res.status(500).json({ code: 505, error: "http://1.95.137.212/#/login?redirect=%2Findex&goRemote=1" });
// return res.redirect('http://1.95.137.212/#/login?redirect=%2Findex&goRemote=1');
}
// return;
// 查该用户所有的会话,按最后活跃时间倒序
const [rows] = await pool.query(
`
SELECT session_id, MAX(created_at) AS last_time
FROM ai_chat_messages
WHERE user_id = ?
GROUP BY session_id
ORDER BY last_time DESC
`,
[userId]
);
res.json({
code: 0,
userId,
sessions: rows.map((r) => r.session_id), // 只返回 session_id 列表
});
}
catch (err)
{
console.error("查询会话列表失败:", err);
res.status(500).json({ code: 1, error: err.message });
}
});
// 查询会话历史
app.get("/history", async (req, res) => {
try
{
if(!req.query?.sessionId || req.query?.sessionId == "NaN")
{
return res.status(400).json({ code: 1, error: "sessionId 必填" });
}
const { sessionId } = req.query;
// 查询数据库
const [rows] = await pool.query(
"SELECT user_id, role, content, image_base64, created_at FROM ai_chat_messages WHERE session_id = ? ORDER BY id ASC",
[sessionId]
);
res.json({
code: 0,
sessionId,
messages: rows,
});
} catch (err) {
console.error("查询会话历史失败:", err);
res.status(500).json({ code: 1, error: err.message });
}
});
// 删除指定会话及其消息
app.delete("/session", async (req, res) => {
try
{
console.log("会话删除:", req.query);
// if (!sessionId || !userId)
if (req.query?.userId == "NaN" || !req.query?.userId || !req.query?.sessionId || req.query?.sessionId == "NaN")
{
return res.status(400).json({ code: 1, error: "sessionId 和 userId 必填" });
}
const { sessionId, userId } = req.query;
// 确保只能删除自己的会话
const [rows] = await pool.query(
"SELECT COUNT(*) as cnt FROM ai_chat_messages WHERE session_id = ? AND user_id = ?",
[sessionId, userId]
);
if (rows[0].cnt === 0) {
return res.status(404).json({ code: 1, error: "会话不存在或无权限" });
}
// 删除该 session 的所有消息
await pool.query(
"DELETE FROM ai_chat_messages WHERE session_id = ? AND user_id = ?",
[sessionId, userId]
);
res.json({
code: 0,
message: `会话 ${sessionId} 已删除`,
});
}
catch (err)
{
console.error("删除会话失败:", err);
res.status(500).json({ code: 1, error: err.message });
}
});
// 新建会话(实际上就是往消息表里插入一条记录)
app.post("/session", async (req, res) => {
try
{
console.log("新建会话: ",req.body)
if (req.body?.userId == "NaN" || !req.body?.userId || !req.body?.sessionId || req.body?.sessionId == "NaN")
{
console.log("=== userId: ",req.body?.userId)
return res.status(400).json({ code: 1, error: "userId 和 sessionId 必填" });
}
const { userId, sessionId } = req.body;
// 插入一条“新会话”的首条消息,可以是空内容
await pool.query(
"INSERT INTO ai_chat_messages (user_id, session_id, role, content) VALUES (?, ?, 'system', '')",
[userId, sessionId]
);
res.json({
code: 0,
message: `会话 ${sessionId} 已创建`,
});
}
catch (err)
{
console.error("创建会话失败:", err);
res.status(500).json({ code: 1, error: err.message });
}
});
// ====================== API ======================
app.post("/analyze", upload.single("file"), async (req, res) => {
try
{
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");
const logBody = { ...req.body };
if(req.body?.imageBase64)
{
delete logBody.imageBase64;
}
console.log("analyze: ", logBody);
// console.log("analyze: ",req.body)
if (!req.body?.userPrompt ||
!req.body?.userId || !req.body?.sessionId)
{
console.log("缺少必要参数");
return res.status(400).json({ error: "缺少必要参数" });
}
const { userPrompt, imageBase64, userId, sessionId } = req.body;
// 如果前端没有传 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 = "";
for await (const chunk of completion)
{
const delta = chunk.choices[0]?.delta?.content || "";
sceneText += delta;
if (delta)
res.write(
`data: ${JSON.stringify({ type: "scene", content: delta })}\n\n`
);
}
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)
{
const delta = chunk.choices[0]?.delta?.content || "";
llmText += delta;
if (delta)
res.write(
`data: ${JSON.stringify({ type: "llm", content: delta})}\n\n`
);
}
// 保存 LLM 输出到数据库
await pool.query(
"INSERT INTO ai_chat_messages (session_id, user_id, role, content) VALUES (?, ?, 'assistant', ?)",
[sessId, userId || null, llmText]
);
res.write("data: [DONE]\n\n");
res.end();
// 删除上传文件
if (req.file && req.file.path)
{
fs.unlinkSync(req.file.path);
}
console.log("====== 分析完成 ======")
}
catch (err)
{
console.error("❌ 调用失败:", err.response?.data || err.message);
res.status(500).json({ code: 1, error: err.response?.data || err.message });
}
});
// 静态文件服务
app.use("/uploads", express.static("uploads"));
// 启动服务
app.listen(port, () => {
console.log(`✅ 服务器运行:http://localhost:${port}`);
});