feat: 完善SSH客户端功能 - 移除窗口菜单栏 - 修复终端布局灰色区域 - AI Agent添加API Key配置(支持OpenAI/DeepSeek等) - 监控面板新增进程监控(支持按CPU/内存排序) - 修复.gitignore并移除node_modules跟踪

This commit is contained in:
Your Name
2026-07-28 22:10:19 +08:00
parent edf4d743cb
commit 57df653738
472 changed files with 7932 additions and 15699 deletions
+435 -307
View File
@@ -1,7 +1,9 @@
const { app, BrowserWindow, ipcMain } = require('electron');
const { app, BrowserWindow, ipcMain, Menu } = require('electron');
const path = require('path');
const { Client } = require('ssh2');
const Store = require('electron-store');
const store = new Store();
let mainWindow;
let sshConnections = {};
let sftpConnections = {};
@@ -26,11 +28,10 @@ function createWindow() {
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../public/index.html'));
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
}
mainWindow.on('closed', () => {
// 关闭所有SSH连接
Object.values(sshConnections).forEach(conn => {
try { conn.end(); } catch (e) {}
});
@@ -38,7 +39,10 @@ function createWindow() {
});
}
app.whenReady().then(createWindow);
app.whenReady().then(() => {
Menu.setApplicationMenu(null);
createWindow();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
@@ -52,30 +56,119 @@ app.on('activate', () => {
}
});
// ========== 连接配置持久化 ==========
ipcMain.handle('get-connections', () => {
return store.get('connections', []);
});
ipcMain.handle('save-connection', (event, config) => {
const connections = store.get('connections', []);
const idx = connections.findIndex(c => c.id === config.id);
if (idx >= 0) {
connections[idx] = config;
} else {
connections.push(config);
}
store.set('connections', connections);
return { success: true };
});
ipcMain.handle('delete-connection', (event, id) => {
let connections = store.get('connections', []);
connections = connections.filter(c => c.id !== id);
store.set('connections', connections);
return { success: true };
});
// ========== SSH 连接管理 ==========
// SSH连接
ipcMain.on('ssh-connect', (event, { id, host, port, username, password, privateKey }) => {
const conn = new Client();
conn.on('ready', () => {
sshConnections[id] = conn;
// 通知前端连接成功
if (mainWindow) {
mainWindow.webContents.send('ssh-connected', { id });
ipcMain.handle('ssh-connect', (event, { id, host, port, username, password, privateKey }) => {
return new Promise((resolve, reject) => {
const conn = new Client();
let settled = false;
const timeout = setTimeout(() => {
if (!settled) {
settled = true;
try { conn.end(); } catch (e) {}
reject(new Error('连接超时'));
}
}, 15000);
conn.on('ready', () => {
if (settled) return;
settled = true;
clearTimeout(timeout);
sshConnections[id] = conn;
resolve({ success: true });
});
conn.on('error', (err) => {
if (!settled) {
settled = true;
clearTimeout(timeout);
reject(new Error(err.message));
} else {
if (mainWindow) {
mainWindow.webContents.send('ssh-error', { id, error: err.message });
}
}
});
conn.on('close', () => {
delete sshConnections[id];
delete sftpConnections[id];
delete shellStreams[id];
if (mainWindow) {
mainWindow.webContents.send('ssh-closed', { id });
}
});
const connectConfig = {
host,
port: port || 22,
username,
readyTimeout: 15000
};
if (privateKey && privateKey.trim()) {
connectConfig.privateKey = privateKey;
} else {
connectConfig.password = password;
}
// 创建交互式Shell
conn.connect(connectConfig);
});
});
ipcMain.handle('ssh-disconnect', (event, { id }) => {
if (sshConnections[id]) {
try { sshConnections[id].end(); } catch (e) {}
delete sshConnections[id];
delete sftpConnections[id];
delete shellStreams[id];
}
return { success: true };
});
// ========== Shell 会话 ==========
ipcMain.handle('ssh-shell', (event, { id }) => {
return new Promise((resolve, reject) => {
const conn = sshConnections[id];
if (!conn) {
reject(new Error('SSH连接不存在'));
return;
}
conn.shell({
term: 'xterm-256color',
cols: 120,
rows: 30
}, (err, stream) => {
if (err) {
if (mainWindow) {
mainWindow.webContents.send('ssh-error', { id, error: err.message });
}
reject(new Error(err.message));
return;
}
@@ -98,57 +191,10 @@ ipcMain.on('ssh-connect', (event, { id, host, port, username, password, privateK
mainWindow.webContents.send('ssh-closed', { id });
}
});
});
// 初始化SFTP
conn.sftp((err, sftp) => {
if (!err) {
sftpConnections[id] = sftp;
}
resolve({ success: true });
});
});
conn.on('error', (err) => {
if (mainWindow) {
mainWindow.webContents.send('ssh-error', { id, error: err.message });
}
});
conn.on('close', () => {
delete sshConnections[id];
delete sftpConnections[id];
delete shellStreams[id];
if (mainWindow) {
mainWindow.webContents.send('ssh-closed', { id });
}
});
const connectConfig = {
host,
port: port || 22,
username,
readyTimeout: 15000
};
if (privateKey && privateKey.trim()) {
connectConfig.privateKey = privateKey;
} else {
connectConfig.password = password;
}
conn.connect(connectConfig);
});
// SSH断开连接
ipcMain.on('ssh-disconnect', (event, { id }) => {
if (sshConnections[id]) {
try {
sshConnections[id].end();
} catch (e) {}
delete sshConnections[id];
delete sftpConnections[id];
delete shellStreams[id];
}
});
// 终端输入
@@ -169,294 +215,376 @@ ipcMain.on('ssh-resize', (event, { id, cols, rows }) => {
// ========== SFTP 文件管理 ==========
// 列出目录内容
ipcMain.on('sftp-list', (event, { id, path }) => {
const sftp = sftpConnections[id];
if (!sftp) {
if (mainWindow) {
mainWindow.webContents.send('sftp-list', { id, path, files: [], error: 'SFTP连接不存在' });
ipcMain.handle('sftp-init', (event, { id }) => {
return new Promise((resolve, reject) => {
const conn = sshConnections[id];
if (!conn) {
reject(new Error('SSH连接不存在'));
return;
}
return;
}
sftp.readdir(path || '/', (err, list) => {
if (err) {
if (mainWindow) {
mainWindow.webContents.send('sftp-list', { id, path, files: [], error: err.message });
conn.sftp((err, sftp) => {
if (err) {
reject(new Error(err.message));
return;
}
sftpConnections[id] = sftp;
resolve({ success: true });
});
});
});
ipcMain.handle('sftp-readdir', (event, { id, path }) => {
return new Promise((resolve, reject) => {
const sftp = sftpConnections[id];
if (!sftp) {
reject(new Error('SFTP连接不存在'));
return;
}
const files = list.map(item => ({
filename: item.filename,
longname: item.longname,
size: item.attrs.size,
mtime: item.attrs.mtime,
atime: item.attrs.atime,
permissions: item.attrs.mode,
isDirectory: (item.attrs.mode & 0o40000) === 0o40000
}));
sftp.readdir(path || '/', (err, list) => {
if (err) {
reject(new Error(err.message));
return;
}
if (mainWindow) {
mainWindow.webContents.send('sftp-list', { id, path, files });
}
const files = list.map(item => ({
filename: item.filename,
longname: item.longname,
attrs: {
size: item.attrs.size,
mtime: item.attrs.mtime,
atime: item.attrs.atime,
permissions: item.attrs.mode,
isDirectory: (item.attrs.mode & 0o40000) === 0o40000
}
}));
resolve({ success: true, files });
});
});
});
// 读取文件内容
ipcMain.on('sftp-readfile', (event, { id, path }) => {
const sftp = sftpConnections[id];
if (!sftp) return;
let content = '';
const stream = sftp.createReadStream(path, { encoding: 'utf-8' });
stream.on('data', (chunk) => {
content += chunk;
});
stream.on('end', () => {
if (mainWindow) {
mainWindow.webContents.send('sftp-file-content', { id, path, content });
ipcMain.handle('sftp-readfile', (event, { id, path }) => {
return new Promise((resolve, reject) => {
const sftp = sftpConnections[id];
if (!sftp) {
reject(new Error('SFTP连接不存在'));
return;
}
});
stream.on('error', (err) => {
if (mainWindow) {
mainWindow.webContents.send('sftp-file-content', { id, path, error: err.message });
}
let content = '';
const stream = sftp.createReadStream(path, { encoding: 'utf-8' });
stream.on('data', (chunk) => {
content += chunk;
});
stream.on('end', () => {
resolve({ success: true, content });
});
stream.on('error', (err) => {
reject(new Error(err.message));
});
});
});
// 写入文件内容
ipcMain.on('sftp-writefile', (event, { id, path, content }) => {
const sftp = sftpConnections[id];
if (!sftp) return;
const stream = sftp.createWriteStream(path);
stream.on('close', () => {
if (mainWindow) {
mainWindow.webContents.send('sftp-write-result', { id, path, success: true });
ipcMain.handle('sftp-writefile', (event, { id, path, content }) => {
return new Promise((resolve, reject) => {
const sftp = sftpConnections[id];
if (!sftp) {
reject(new Error('SFTP连接不存在'));
return;
}
});
stream.on('error', (err) => {
if (mainWindow) {
mainWindow.webContents.send('sftp-write-result', { id, path, success: false, error: err.message });
}
});
const stream = sftp.createWriteStream(path);
stream.end(content);
});
stream.on('close', () => {
resolve({ success: true });
});
// 创建目录
ipcMain.on('sftp-mkdir', (event, { id, path }) => {
const sftp = sftpConnections[id];
if (!sftp) return;
stream.on('error', (err) => {
reject(new Error(err.message));
});
sftp.mkdir(path, (err) => {
if (mainWindow) {
mainWindow.webContents.send('sftp-mkdir-result', { id, path, success: !err, error: err?.message });
}
stream.end(content);
});
});
// 删除文件/目录
ipcMain.on('sftp-rm', (event, { id, path, isDirectory }) => {
const sftp = sftpConnections[id];
if (!sftp) return;
const callback = (err) => {
if (mainWindow) {
mainWindow.webContents.send('sftp-rm-result', { id, path, success: !err, error: err?.message });
ipcMain.handle('sftp-mkdir', (event, { id, path }) => {
return new Promise((resolve, reject) => {
const sftp = sftpConnections[id];
if (!sftp) {
reject(new Error('SFTP连接不存在'));
return;
}
};
if (isDirectory) {
sftp.rmdir(path, callback);
} else {
sftp.unlink(path, callback);
}
sftp.mkdir(path, (err) => {
if (err) {
reject(new Error(err.message));
} else {
resolve({ success: true });
}
});
});
});
// 重命名/移动
ipcMain.on('sftp-rename', (event, { id, oldPath, newPath }) => {
const sftp = sftpConnections[id];
if (!sftp) return;
ipcMain.handle('sftp-rm', (event, { id, path, isDirectory }) => {
return new Promise((resolve, reject) => {
const sftp = sftpConnections[id];
if (!sftp) {
reject(new Error('SFTP连接不存在'));
return;
}
sftp.rename(oldPath, newPath, (err) => {
if (mainWindow) {
mainWindow.webContents.send('sftp-rename-result', { id, oldPath, newPath, success: !err, error: err?.message });
const callback = (err) => {
if (err) {
reject(new Error(err.message));
} else {
resolve({ success: true });
}
};
if (isDirectory) {
sftp.rmdir(path, callback);
} else {
sftp.unlink(path, callback);
}
});
});
// ========== 系统监控 ==========
ipcMain.on('monitor-request', (event, { id }) => {
function execRemoteCommand(conn, cmd) {
return new Promise((resolve) => {
conn.exec(cmd, (err, stream) => {
if (err) { resolve(''); return; }
let output = '';
stream.on('data', (data) => { output += data.toString(); });
stream.stderr.on('data', (data) => { output += data.toString(); });
stream.on('close', () => { resolve(output); });
});
});
}
ipcMain.handle('system-monitor', async (event, { id }) => {
const conn = sshConnections[id];
if (!conn) return;
if (!conn) {
return { success: false, error: 'SSH连接不存在' };
}
const monitorData = {
id,
cpu: { usage: 0, cores: '-', load: '-', model: '-' },
memory: { percent: 0, total: '-', used: '-', free: '-' },
disks: [],
system: { hostname: '-', os: '-', uptime: '-', kernel: '-' },
processes: []
};
try {
// 并行执行所有命令
const [cpuOutput, memOutput, diskOutput, sysOutput, processOutput, uptimeOutput] = await Promise.all([
execRemoteCommand(conn, "top -bn1 | grep 'Cpu(s)' | head -1"),
execRemoteCommand(conn, "free -m | grep Mem"),
execRemoteCommand(conn, "df -h | grep -E '^/dev/'"),
execRemoteCommand(conn, "cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'\"' -f2; echo '---'; uname -r; echo '---'; hostname"),
execRemoteCommand(conn, "ps aux | wc -l"),
execRemoteCommand(conn, "cat /proc/loadavg; echo '---'; uptime -p 2>/dev/null || uptime")
]);
let completed = 0;
const total = 5;
const checkDone = () => {
completed++;
if (completed >= total && mainWindow) {
mainWindow.webContents.send('monitor-data', monitorData);
// 解析CPU使用率
let cpuUsage = 0;
const cpuMatch = cpuOutput.match(/([\d.]+)\s*id/);
if (cpuMatch) {
cpuUsage = 100 - parseFloat(cpuMatch[1]);
}
};
// CPU信息
conn.exec("top -bn1 | head -5; echo '---'; nproc; echo '---'; cat /proc/loadavg; echo '---'; lscpu | grep 'Model name' | sed 's/Model name:[ ]*//'", (err, stream) => {
if (err) { checkDone(); return; }
let output = '';
stream.on('data', (data) => { output += data.toString(); });
stream.on('close', () => {
const lines = output.split('\n');
// 解析CPU使用率
const cpuLine = lines.find(l => l.includes('Cpu(s)'));
if (cpuLine) {
const match = cpuLine.match(/([\d.]+)\s*(?:%|id)/i);
if (match) {
monitorData.cpu.usage = 100 - parseFloat(match[1]);
}
// 解析内存 (单位MB)
let memoryStr = '0,0,0,0';
const memParts = memOutput.trim().split(/\s+/);
if (memParts.length >= 7) {
const total = parseInt(memParts[1]) || 0;
const used = parseInt(memParts[2]) || 0;
const free = parseInt(memParts[3]) || 0;
const available = parseInt(memParts[6]) || 0;
memoryStr = `${total},${used},${free},${available}`;
}
// 解析磁盘
const diskLines = diskOutput.trim().split('\n').filter(Boolean).map(line => {
const parts = line.split(/\s+/);
return `${parts[0]},${parts[1]},${parts[2]},${parts[3]},${parseInt(parts[4]) || 0},${parts[5]}`;
}).join('\n');
// 解析系统信息
const sysParts = sysOutput.split('---');
const systemInfo = [
sysParts[0]?.trim(),
sysParts[1]?.trim() ? `内核: ${sysParts[1].trim()}` : '',
sysParts[2]?.trim() ? `主机: ${sysParts[2].trim()}` : ''
].filter(Boolean).join('\n');
// 解析负载
const loadParts = uptimeOutput.split('---');
let loadStr = '0,0,0';
let uptimeStr = '-';
if (loadParts[0]) {
const loads = loadParts[0].trim().split(/\s+/);
loadStr = loads.slice(0, 3).join(',');
}
if (loadParts[1]) {
uptimeStr = loadParts[1].trim().replace('up ', '');
}
// 进程数
const processCount = parseInt(processOutput.trim()) || 0;
return {
success: true,
data: {
cpu: cpuUsage.toFixed(1),
memory: memoryStr,
disk: diskLines,
load: loadStr,
processes: processCount,
uptime: uptimeStr,
system: systemInfo
}
// 解析核心数
const parts = output.split('---');
if (parts[1]) monitorData.cpu.cores = parts[1].trim();
if (parts[2]) {
const loads = parts[2].trim().split(/\s+/);
monitorData.cpu.load = loads.slice(0, 3).join(', ');
}
if (parts[3]) monitorData.cpu.model = parts[3].trim().substring(0, 40);
checkDone();
});
});
// 内存信息
conn.exec("free -h | grep Mem", (err, stream) => {
if (err) { checkDone(); return; }
let output = '';
stream.on('data', (data) => { output += data.toString(); });
stream.on('close', () => {
const parts = output.trim().split(/\s+/);
if (parts.length >= 4) {
monitorData.memory.total = parts[1];
monitorData.memory.used = parts[2];
monitorData.memory.free = parts[3];
// 计算百分比
const totalNum = parseFloat(parts[1]);
const usedNum = parseFloat(parts[2]);
if (totalNum > 0) {
monitorData.memory.percent = (usedNum / totalNum) * 100;
}
}
checkDone();
});
});
// 磁盘信息
conn.exec("df -h | grep -E '^/dev/'", (err, stream) => {
if (err) { checkDone(); return; }
let output = '';
stream.on('data', (data) => { output += data.toString(); });
stream.on('close', () => {
const lines = output.trim().split('\n');
monitorData.disks = lines.map(line => {
const parts = line.split(/\s+/);
return {
device: parts[0],
total: parts[1],
used: parts[2],
available: parts[3],
percent: parseInt(parts[4]) || 0,
mount: parts[5]
};
});
checkDone();
});
});
// 系统信息
conn.exec("hostname; echo '---'; cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'\"' -f2; echo '---'; uptime -p 2>/dev/null || uptime; echo '---'; uname -r", (err, stream) => {
if (err) { checkDone(); return; }
let output = '';
stream.on('data', (data) => { output += data.toString(); });
stream.on('close', () => {
const parts = output.split('---');
if (parts[0]) monitorData.system.hostname = parts[0].trim();
if (parts[1]) monitorData.system.os = parts[1].trim();
if (parts[2]) monitorData.system.uptime = parts[2].trim().replace('up ', '');
if (parts[3]) monitorData.system.kernel = parts[3].trim();
checkDone();
});
});
// 进程列表
conn.exec("ps aux --sort=-%mem | head -16 | tail -15", (err, stream) => {
if (err) { checkDone(); return; }
let output = '';
stream.on('data', (data) => { output += data.toString(); });
stream.on('close', () => {
const lines = output.trim().split('\n');
monitorData.processes = lines.map(line => {
const parts = line.split(/\s+/);
return {
user: parts[0],
pid: parts[1],
cpu: parts[2],
mem: parts[3],
cmd: parts.slice(10).join(' ').substring(0, 50)
};
});
checkDone();
});
});
};
} catch (error) {
return { success: false, error: error.message };
}
});
// ========== AI Agent 命令执行 ==========
ipcMain.on('exec-command', (event, { id, cmd, context }) => {
const conn = sshConnections[id];
if (!conn) {
if (mainWindow) {
mainWindow.webContents.send('cmd-result', { id, output: '错误: SSH连接不存在', context });
}
return;
}
const TASK_COMMANDS = {
system_info: "cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'\"' -f2; uname -a",
cpu_info: "lscpu 2>/dev/null | head -20 || cat /proc/cpuinfo | head -30",
memory_usage: "free -h",
disk_usage: "df -h",
top_processes: "ps aux --sort=-%mem | head -16",
uptime_check: "uptime",
network_info: "ip addr 2>/dev/null || ifconfig",
port_listen: "ss -tulnp 2>/dev/null || netstat -tulnp",
nginx_status: "systemctl status nginx 2>/dev/null || service nginx status 2>/dev/null || echo 'Nginx未安装'",
mysql_status: "systemctl status mysql 2>/dev/null || systemctl status mysqld 2>/dev/null || echo 'MySQL未安装'",
docker: "docker ps 2>/dev/null || echo 'Docker未安装或未运行'",
logged_users: "who",
last_logins: "last -10 2>/dev/null || echo '无法获取登录记录'",
firewall: "iptables -L -n 2>/dev/null || echo '需要root权限'",
ssh_config: "cat /etc/ssh/sshd_config | grep -v '^#' | grep -v '^$'",
sudo_users: "cat /etc/sudoers 2>/dev/null | grep -v '^#' | grep -v '^$' || echo '需要root权限'",
services: "systemctl list-units --type=service --state=running 2>/dev/null | head -20 || service --status-all 2>/dev/null",
cron_jobs: "crontab -l 2>/dev/null || echo '无定时任务'; ls /etc/cron.d/ 2>/dev/null",
env_vars: "env | sort | head -30",
package_update: "apt list --upgradable 2>/dev/null || yum check-update 2>/dev/null || echo '无法检查更新'",
disk_io: "iostat -x 1 1 2>/dev/null || echo '需要安装sysstat'"
};
conn.exec(cmd, (err, stream) => {
if (err) {
if (mainWindow) {
mainWindow.webContents.send('cmd-result', { id, output: '执行错误: ' + err.message, context });
}
ipcMain.handle('ai-execute', (event, { id, task }) => {
return new Promise((resolve) => {
const conn = sshConnections[id];
if (!conn) {
resolve({ success: false, output: '错误: SSH连接不存在' });
return;
}
let output = '';
stream.on('data', (data) => {
output += data.toString();
});
// 如果是预定义任务,使用对应命令;否则直接作为命令执行
const cmd = TASK_COMMANDS[task] || task;
stream.stderr.on('data', (data) => {
output += data.toString();
});
stream.on('close', (code) => {
if (mainWindow) {
mainWindow.webContents.send('cmd-result', { id, output, code, context });
conn.exec(cmd, (err, stream) => {
if (err) {
resolve({ success: false, output: '执行错误: ' + err.message });
return;
}
let output = '';
stream.on('data', (data) => { output += data.toString(); });
stream.stderr.on('data', (data) => { output += data.toString(); });
stream.on('close', (code) => {
resolve({ success: code === 0, output: output || '(无输出)' });
});
});
});
});
// ==================== 进程监控 ====================
ipcMain.handle('get-processes', async (event, { id, sortBy = 'cpu', limit = 20 }) => {
const conn = sshConnections[id];
if (!conn) {
return { success: false, error: 'SSH连接不存在' };
}
try {
const sortFlag = sortBy === 'memory' ? '--sort=-%mem' : '--sort=-%cpu';
const cmd = `ps aux ${sortFlag} | head -${limit + 1}`;
const output = await execRemoteCommand(conn, cmd);
const lines = output.trim().split('\n');
// 跳过表头
const processes = lines.slice(1).filter(Boolean).map(line => {
const parts = line.split(/\s+/);
return {
user: parts[0],
pid: parts[1],
cpu: parts[2],
mem: parts[3],
vsz: parts[4],
rss: parts[5],
stat: parts[7],
start: parts[8],
time: parts[9],
command: parts.slice(10).join(' ')
};
});
return { success: true, data: processes };
} catch (error) {
return { success: false, error: error.message };
}
});
// ==================== AI 设置 ====================
ipcMain.handle('get-ai-settings', () => {
return store.get('aiSettings', { apiKey: '', baseUrl: 'https://api.openai.com/v1', model: 'gpt-3.5-turbo' });
});
ipcMain.handle('save-ai-settings', (event, settings) => {
store.set('aiSettings', settings);
return { success: true };
});
// AI 智能命令解析
ipcMain.handle('ai-parse-command', async (event, { input, apiKey, baseUrl, model }) => {
try {
const url = `${baseUrl.replace(/\/$/, '')}/chat/completions`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: model || 'gpt-3.5-turbo',
messages: [
{
role: 'system',
content: '你是一个Linux服务器管理专家。用户会用自然语言描述他们想要执行的服务器操作。你需要返回对应的Linux命令。只返回命令本身,不要任何解释、不要markdown格式、不要代码块标记。如果用户的请求不安全或无法理解,返回 #ERROR: 加上原因。'
},
{ role: 'user', content: input }
],
temperature: 0.1,
max_tokens: 200
})
});
if (!response.ok) {
const errText = await response.text();
return { success: false, error: `API请求失败 (${response.status}): ${errText}` };
}
const data = await response.json();
const command = data.choices[0].message.content.trim();
if (command.startsWith('#ERROR:')) {
return { success: false, error: command.replace('#ERROR:', '').trim() };
}
return { success: true, command };
} catch (error) {
return { success: false, error: `AI请求失败: ${error.message}` };
}
});