Files
AI/server2.js

1055 lines
33 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 xml2js = require('xml2js');
const xmlparser = require('express-xml-bodyparser'); // 引入XML解析中间件
const bodyParser = require("body-parser");
const axios = require('axios');
const crypto = require('crypto');
require('dotenv').config();
const app = express();
const port =30009;
const appid = process.env.WECHAT_APPID;
const secret = process.env.WECHAT_SECRET;
const accessTokenUrl = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
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 }));
app.use(xmlparser({
explicitArray: false, // 禁用默认的数组格式,和你后续xml2js配置一致
trim: 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(cors({
// origin: ["https://ai.satabot.com"], // 允许的域
// methods: "GET,POST,PUT,DELETE,OPTIONS",
// allowedHeaders: "Content-Type,Authorization",
// credentials: true
// }));
// app.options('*', 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
});
// 获取 access_token
// getAccessToken();
async function getAccessToken() {
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
const res = await axios.get(url);
if (res.data.access_token) {
console.log('✅ 获取 access_token 成功',res.data);
return res.data.access_token;
} else {
throw new Error('❌ 获取 access_token 失败: ' + JSON.stringify(res.data));
}
}
const WECHAT_OAUTH_TOKEN_URL = 'https://api.weixin.qq.com/sns/oauth2/access_token';
app.get('/wechat/oauth/access-token', async (req, res) => {
try {
console.log(req.query)
// 1. 接收并校验前端传递的 code 参数
const { code } = req.query; // 从URL查询参数中获取 code
if (!code) {
return res.status(400).json({
code: -1,
message: '参数错误:缺少 code(微信授权临时票据)'
});
}
// 3. 调用微信 OAuth2 接口,传递必要参数
const response = await axios.get(WECHAT_OAUTH_TOKEN_URL, {
params: {
appid: "wx364b8ebf17fe45fe", // 你的微信appid
secret: "9b7476020991a2c71f1416da7f011d31", // 你的微信secret
code: code, // 前端传递的授权code
grant_type: 'authorization_code' // 固定值
}
});
// 4. 处理微信返回的结果
const wechatData = response.data;
// 微信接口失败(返回 errcode 字段)
if (wechatData.errcode) {
throw new Error(`微信接口错误:${wechatData.errmsg}(错误码:${wechatData.errcode})`);
}
// 5. 成功响应:返回微信返回的所有信息(包含 access_token、openid 等)
res.status(200).json({
code: 0,
message: '获取 OAuth2 access_token 成功',
data: wechatData // 包含 access_token、expires_in、refresh_token、openid 等
});
} catch (error) {
// 异常处理:捕获网络错误、微信接口错误等
console.error('获取 OAuth2 access_token 失败:', error.message);
res.status(500).json({
code: -3,
message: '获取 access_token 失败:' + error.message
});
}
});
// 创建菜单的接口
app.post('/create-menu', async (req, res) => {
const accessToken = await getAccessToken();
const menuData = getMenuForPlatform(req); // 根据请求判断平台并获取菜单
const createMenuUrl = `https://api.weixin.qq.com/cgi-bin/menu/create?access_token=${accessToken}`;
try {
const response = await axios.post(createMenuUrl, menuData);
res.send(`菜单创建成功: ${JSON.stringify(response.data)}`);
} catch (error) {
res.status(500).send(`菜单创建失败: ${error}`);
}
});
app.post('/wechat', async (req, res) => {
try {
console.log("收到请求:", req.body);
const xml = req.body;
const message = xml.xml;
// 修复语法错误:移除msgtype前的空格
const msgType = message.msgtype;
const fromUser = message.fromusername; // openid
const toUser = message.tousername; // 公众号原始ID
console.log("消息类型:", msgType);
console.log("用户OpenID:", fromUser);
// 菜单点击事件
if (msgType === 'event' && message.event === 'CLICK') {
const key = message.eventkey;
console.log("菜单点击Key:", key);
if (key === 'AI_INTELLIGENT') {
try {
const timestamp = Date.now();
// 方案一:简单文本回复(推荐,避免超时)
const reply = `
<xml>
<ToUserName><![CDATA[${fromUser}]]></ToUserName>
<FromUserName><![CDATA[${toUser}]]></FromUserName>
<CreateTime>${Date.now()}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[欢迎使用AI智能体!正在为您准备服务...<a href="https://ai.satabot.com?userId=${fromUser}&from=weixin&ts=${timestamp}" target="_blank">立即进入AI智能体</a>]]></Content>
</xml>
`;
res.send(reply);
return;
// 启动异步任务处理耗时操作
setTimeout(async () => {
try {
const token = await getAccessToken();
console.log("获取到Token:", token);
// 获取用户信息
const userRes = await axios.get('https://api.weixin.qq.com/sns/userinfo', {
params: {
access_token:token,
fromUser,
lang: 'zh_CN'
}
});
console.log(userRes, 'user000000000000');
// res.redirect('https://ai.satabot.com');
const { nickname, errcode: userErrCode } = userRes.data;
console.log(nickname, 'nickname');
return;
if (userErrCode) return res.status(500).send(`获取用户信息失败:${userErrCode}`);
const userInfoRes = await axios.get(
`https://api.weixin.qq.com/cgi-bin/user/info?access_token=${token}&openid=${fromUser}&lang=zh_CN`
);
const user = userInfoRes.data;
console.log("用户信息:", user);
// 使用客服消息接口发送详细回复
if (user && user.nickname) {
await sendCustomerServiceMessage(fromUser, `你好,${user.nickname}!欢迎使用AI智能体`);
}
} catch (err) {
console.error("异步处理错误:", err);
}
}, 0);
return;
// 方案二:直接回复(如果能确保5秒内完成)
/*
const token = await getAccessToken();
console.log("Token:", token);
const userInfoRes = await axios.get(
`https://api.weixin.qq.com/cgi-bin/user/info?access_token=${token}&openid=${fromUser}&lang=zh_CN`
);
const user = userInfoRes.data;
console.log("用户信息:", user);
const reply = `
<xml>
<ToUserName><![CDATA[${fromUser}]]></ToUserName>
<FromUserName><![CDATA[${toUser}]]></FromUserName>
<CreateTime>${Date.now()}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好,${user.nickname || '用户'}!欢迎使用AI智能体]]></Content>
</xml>
`;
res.send(reply);
return;
*/
} catch (err) {
console.error("处理AI_INTELLIGENT错误:", err);
// 出错时返回标准回复
const errorReply = `
<xml>
<ToUserName><![CDATA[${fromUser}]]></ToUserName>
<FromUserName><![CDATA[${toUser}]]></FromUserName>
<CreateTime>${Date.now()}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[服务暂时不可用,请稍后再试]]></Content>
</xml>
`;
res.send(errorReply);
return;
}
}
}
// 默认回复空串(表示不处理)
res.send('');
} catch (err) {
console.error("服务器错误:", err);
// 异常情况下回复空串
res.send('');
}
});
// 新增:发送客服消息的函数
async function sendCustomerServiceMessage(openid, content) {
try {
const token = await getAccessToken();
const url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${token}`;
const data = {
touser: openid,
msgtype: 'text',
text: {
content: content
}
};
await axios.post(url, data, {
headers: {
'Content-Type': 'application/json'
}
});
console.log("客服消息发送成功");
} catch (err) {
console.error("发送客服消息失败:", err);
}
}
// app.post('/wechat', async (req, res) => {
// try {
// console.log(req,"收到恢复")
// const xml = req.body;
// console.log(xml);
// const message = xml.xml;
// const msgType = message.msgtype;
// const fromUser = message.fromusername; // openid
// // 菜单点击事件
// if (msgType === 'event' && message.event === 'CLICK') {
// const key = message.eventkey;
// if (key === 'AI_INTELLIGENT') {
// const redirectUri = encodeURIComponent(`https://ai.satabot.com/api/wechat/oauth/callback`);
// const state = fromUser; // 可用 openid 做状态标识
// console.log(state,"state")
// // const oauthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
// // console.log("oauthUrl",oauthUrl)
// // // 回复用户点击菜单提示,引导跳转
// // const reply = `
// // <xml>
// // <ToUserName><![CDATA[${fromUser}]]></ToUserName>
// // <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
// // <CreateTime>${Date.now()}</CreateTime>
// // <MsgType><![CDATA[text]]></MsgType>
// // <Content><![CDATA[请点击以下链接授权获取昵称:${oauthUrl}]]></Content>
// // </xml>
// // `;
// // res.send(reply);
// // return;
// const token = await getAccessToken();
// console.log(token,"token")
// // 获取用户信息
// // https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=o6_bmjrPTlm6_2sgVt7hMZOPfL2M&lang=zh_CN
// const userInfoRes = await axios.get(`https://api.weixin.qq.com/cgi-bin/user/info?access_token=${token}&openid=${fromUser}&lang=zh_CN`);
// // https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=ENCODED_URL&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
// const user = userInfoRes.data;
// console.log(userInfoRes,'用户信息')
// // 回复文本消息
// const reply = `
// <xml>
// <ToUserName><![CDATA[${fromUser}]]></ToUserName>
// <FromUserName><![CDATA[${message.fromUser}]]></FromUserName>
// <CreateTime>${Date.now()}</CreateTime>
// <MsgType><![CDATA[text]]></MsgType>
// <Content><![CDATA[你好,${user.nickname}!欢迎使用 AI智能体]]></Content>
// </xml>
// `;
// res.send(xml);
// return;
// }
// }
// // 默认回复空
// res.send('success');
// } catch (err) {
// console.error(err);
// res.send('success');
// }
// });
// OAuth2 回调
app.get('/wechat/oauth/callback', async (req, res) => {
try {
const { code, state } = req.query;
console.log(code,"code")
// 用 code 换取 access_token 和 openid
const tokenResp = await axios.get(
`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`
);
const { access_token, openid } = tokenResp.data;
// 拉取用户信息
const userInfoResp = await axios.get(
`https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=zh_CN`
);
const user = userInfoResp.data;
console.log(user, "获取到用户信息");
// 你可以直接跳转到前端页面并带上用户信息
res.redirect(`https://ai.satabot.com?nickname=${encodeURIComponent(user.nickname)}&openid=${openid}`);
} catch (err) {
console.error(err);
res.send("获取用户信息失败");
}
});
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("/wechat/userinfo", async (req, res) => {
const { code } = req.query;
if (!code) return res.status(400).send("缺少 code");
try {
// 1. 用 code 换取 access_token 和 openid
const tokenResp = await axios.get(
`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`
);
const { access_token, openid } = tokenResp.data;
// 2. 拉取用户信息
const userRes = await axios.get(
`https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&lang=zh_CN`
);
const userInfo = userRes.data;
// 3. 返回给前端
res.json({ code: 0, data: userInfo });
} catch (err) {
console.error(err);
res.status(500).send("获取用户信息失败");
}
});
app.post("/user", async (req, res) => {
try {
const { userId } = req.body; // 从请求体里拿到 user_id
if (!userId&&userId!="NaN") {
return res.status(400).send("缺少 user_id");
}
// 查询 user_name
const [rows] = await pool.query(
"SELECT user_name FROM sys_user WHERE user_id = ?",
[userId]
);
if (rows.length === 0) {
res.json({
code: 300,
error: "http://ri.satabot.com/#/login?goRemote=1",
msg:"未找到该用户"
});
return;
}
res.json({ code:0,userId, userName: rows[0].user_name });
} catch (err) {
console.error(err);
res.status(500).send("服务器错误");
}
});
app.post("/getuserByopenid", async (req, res) => {
try {
const { userId } = req.body; // 从请求体里拿到 user_id
if (!userId) {
return res.status(400).send("缺少 user_id");
}
// 查询 user_name
const [rows] = await pool.query(
"SELECT * FROM sys_user WHERE open_id = ?",
[userId]
);
if (rows.length > 0) {
// 用户存在,直接返回
res.json({ code: 0, msg: "找到该用户",userId:rows[0].user_id });
return
} else {
// 用户不存在,插入新用户
const [result] = await pool.query(
"INSERT INTO sys_user (open_id,user_name,nick_name) VALUES (?,?,?)",
[userId,userId,userId]
);
console.log(result,"udgwehvdh")
res.json({code:0,msg:"新增该用户",userId:result.insertId});
return ;
}
} catch (err) {
console.error(err);
res.status(500).send("服务器错误");
}
});
const TOKEN = 'my_token_123'; // 与公众号后台配置一致
app.get('/wechat', (req, res) => {
const { signature, timestamp, nonce, echostr } = req.query;
console.log(req, "req");
const arr = [TOKEN, timestamp, nonce].sort();
const str = arr.join('');
const hash = crypto.createHash('sha1').update(str).digest('hex');
if (hash === signature) res.send(echostr);
else res.send('验证失败');
});
// 查询用户所有会话列表
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:'https://gsgg.com'
// error: "http://ri.satabot.com/#/dhhdghgbshwadhg"
error: "http://ri.satabot.com/#/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,title,MAX(created_at) AS last_time
// FROM ai_chat_messages
// WHERE user_id = ?
// GROUP BY session_id
// ORDER BY last_time DESC
// `,
// [userId]
// );
const [rows] = await pool.query(`
SELECT m.session_id, m.title
FROM ai_chat_messages m
INNER JOIN (
SELECT session_id, MAX(created_at) AS last_time
FROM ai_chat_messages
WHERE user_id = ?
GROUP BY session_id
) t ON m.session_id = t.session_id AND m.created_at = t.last_time
WHERE m.user_id = ?
ORDER BY t.last_time ASC
`, [userId, userId]);
res.json({
code: 0,
userId,
sessions: rows.map((r) => ({ sessId: r.session_id, title: r.title })), // 只返回 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,title 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 });
}
});
const stopFlags = new Map();
// ====================== API ======================
app.post("/analyze", upload.single("file"), async (req, res) => {
try
{
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;
});
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, 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)
{
console.log(stopFlags.get(requestId),"停止了嘛")
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`
// );
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 输出到数据库
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("====== 分析完成 ======")
}
catch (err)
{
console.error("❌ 调用失败:", err.response?.data || err.message);
res.status(500).json({ code: 1, error: err.response?.data || err.message });
}
});
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.get("/callback", async (req, res) => {
// const code = req.query.code;
// try {
// // 用 code 换 openid
// const resp = await axios.get("https://api.weixin.qq.com/sns/oauth2/access_token", {
// params: {
// appid: APPID,
// secret: APPSECRET,
// code: code,
// grant_type: "authorization_code",
// },
// });
// const data = resp.data;
// console.log("微信授权返回:", data);
// if (data.openid) {
// res.json({
// code: 0,
// openid:data.openid
// })
// // 判断数据库里是否已经有这个 openid
// // 没有 → 注册;有 → 登录
// // res.send(`授权成功,openid: ${data.openid}`);
// } else {
// res.json({
// code: 500,
// msg:"微信授权失败"+ JSON.stringify(data)
// })
// // res.send("授权失败: " + JSON.stringify(data));
// }
// } catch (err) {
// console.error(err);
// res.send("服务器错误");
// }
// });
// 静态文件服务
app.use("/uploads", express.static("uploads"));
// 启动服务
app.listen(port, () => {
console.log(`✅ 服务器运行:http://localhost:${port}`);
});