From aa3ffbf15ffd36a3243cc167844ad6ac0b43f04d Mon Sep 17 00:00:00 2001 From: kicer Date: Wed, 22 Jul 2026 13:21:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=8A=9F=E8=83=BD=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/db.js | 2 + server/public/index.html | 42 +++++++-- server/server.js | 196 +++++++++++++-------------------------- 3 files changed, 100 insertions(+), 140 deletions(-) diff --git a/server/db.js b/server/db.js index 7a43a1b..63ca632 100644 --- a/server/db.js +++ b/server/db.js @@ -83,7 +83,9 @@ const migrations = [ `ALTER TABLE purchases ADD COLUMN invoice_type TEXT DEFAULT '无票'`, `ALTER TABLE purchases ADD COLUMN payment_method TEXT DEFAULT '未指定'`, `ALTER TABLE purchases ADD COLUMN remarks TEXT DEFAULT ''`, + `ALTER TABLE rooms ADD COLUMN white_list TEXT DEFAULT ''`, ]; + for (const sql of migrations) { try { db.exec(sql); diff --git a/server/public/index.html b/server/public/index.html index 437c29f..7f576ce 100644 --- a/server/public/index.html +++ b/server/public/index.html @@ -61,7 +61,7 @@ .msg-bubble code { background: #f0f0f0; padding: 2px 4px; border-radius: 3px; font-size: 14px; } .input-area { padding: 8px 12px; border-top: 1px solid #eee; display: flex; align-items: center; background: #fff; gap: 8px; } - .input-area textarea { flex: 1; border: 1px solid #ddd; border-radius: 12px; padding: 10px 12px; font-size: 16px; outline: none; resize: none; height: 40px; line-height: 1.4; } + .input-area textarea { flex: 1; border: 1px solid #ddd; border-radius: 12px; padding: 10px 12px; font-size: 16px; outline: none; resize: none; min-height: 40px; max-height: 120px; overflow-y: hidden; line-height: 1.4; } .file-upload-btn { background: none; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 4px; } .file-upload-btn svg { width: 24px; height: 24px; stroke: #666; fill: none; stroke-width: 2; } .send-btn { background: #3b82f6; border: none; color: #fff; width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; } @@ -124,7 +124,7 @@

-
v2.4
+
v2.5
@@ -235,6 +235,7 @@ let pendingUploads = []; const messagesContainer = document.getElementById('messages-container'); let messageCache = []; + let tempBubbleTimer = null; function getToken() { return localStorage.getItem('token'); } function saveAuth(data) { @@ -248,9 +249,7 @@ localStorage.removeItem('isAdmin'); } - if (typeof marked !== 'undefined') { - marked.setOptions({ breaks: true, gfm: true, sanitize: false }); - } + if (typeof marked !== 'undefined') marked.setOptions({ breaks: true, gfm: true, sanitize: false }); function compressImage(file) { return new Promise((resolve, reject) => { @@ -317,6 +316,7 @@ messagesContainer.insertAdjacentHTML('beforeend', renderMessageHTML(msg)); scrollToBottom(); bindMessageEvents(); + clearTempBubble(); // 收到新消息时移除气泡 } function replaceMessage(msgId, newMsg) { @@ -367,6 +367,24 @@ document.body.appendChild(overlay); } + // 临时“输入中...”气泡 + function insertTempBubble() { + clearTempBubble(); + const tempId = 'temp_' + Date.now(); + const tempMsg = { id: tempId, user: '小财', text: '输入中...', attachments: [], timestamp: '', isTemp: true }; + messageCache.push(tempMsg); + messagesContainer.insertAdjacentHTML('beforeend', renderMessageHTML(tempMsg)); + scrollToBottom(); + tempBubbleTimer = setTimeout(() => { clearTempBubble(); }, 8000); + } + + function clearTempBubble() { + if (tempBubbleTimer) { clearTimeout(tempBubbleTimer); tempBubbleTimer = null; } + messageCache = messageCache.filter(m => !m.isTemp); + const tempEls = messagesContainer.querySelectorAll('[data-msg-id^="temp_"]'); + tempEls.forEach(el => el.remove()); + } + // WebSocket 重连 let wsReconnectTimer = null; let reconnectAttempts = 0; @@ -386,7 +404,9 @@ const data = JSON.parse(e.data); if (data.type === 'new_message') { const msg = data.message; - if (currentRoom === msg.room_id) appendMessage(msg); + if (currentRoom === msg.room_id) { + appendMessage(msg); + } if (data.room_preview) { const room = rooms.find(r => r.id === data.room_preview.room_id); if (room) { room.last_message = data.room_preview.last_message; room.last_time = data.room_preview.last_time; renderChatList(); } @@ -565,6 +585,7 @@ async function openChat(roomId, autoOpenPurchase = false) { currentRoom = roomId; + clearTempBubble(); document.getElementById('list-page').classList.add('hidden'); document.getElementById('chat-page').classList.remove('hidden'); const room = rooms.find(r => r.id === roomId); @@ -579,6 +600,7 @@ } function showList() { + clearTempBubble(); document.getElementById('chat-page').classList.add('hidden'); document.getElementById('list-page').classList.remove('hidden'); currentRoom = null; @@ -602,6 +624,7 @@ appendMessage(newMsg); input.value = ''; pendingUploads = []; + insertTempBubble(); // 显示“输入中...” } catch(e) { alert('发送失败,请重试'); } } @@ -778,6 +801,13 @@ } else { const err = await res.json(); alert(err.error || '删除失败'); } } + document.addEventListener('input', function(e) { + if (e.target.id === 'msg-input') { + e.target.style.height = 'auto'; + e.target.style.height = e.target.scrollHeight + 'px'; + } + }); + document.addEventListener('keydown', function(e) { if (e.target.id === 'msg-input' && e.key === 'Enter' && !e.ctrlKey) { e.preventDefault(); sendMessage(); } }); diff --git a/server/server.js b/server/server.js index abe7e74..0890e47 100644 --- a/server/server.js +++ b/server/server.js @@ -27,9 +27,7 @@ const TIMEZONE = 'Asia/Shanghai'; function timestamp() { return new Date().toLocaleString('zh-CN', { timeZone: TIMEZONE, hour12: false }); } -function currentTimeStr() { - return timestamp(); -} +function currentTimeStr() { return timestamp(); } // 初始化用户 const insertUser = db.prepare('INSERT OR IGNORE INTO users (username, password, is_admin) VALUES (?, ?, ?)'); @@ -94,8 +92,16 @@ app.get('/api/user', auth, (req, res) => { res.json({ username: req.user.username, isAdmin: req.user.isAdmin }); }); +// 群聊列表(白名单过滤) app.get('/api/rooms', auth, (req, res) => { - const rooms = db.prepare('SELECT * FROM rooms').all(); + const username = req.user.username; + const isAdmin = req.user.isAdmin; + let rooms; + if (isAdmin) { + rooms = db.prepare('SELECT * FROM rooms').all(); + } else { + rooms = db.prepare('SELECT * FROM rooms WHERE white_list = ? OR white_list LIKE ?').all('', `%,${username},%`); + } const result = rooms.map(room => { const lastMsg = db.prepare('SELECT text, timestamp FROM messages WHERE room_id = ? ORDER BY timestamp DESC LIMIT 1').get(room.id); return { @@ -151,22 +157,13 @@ app.post('/api/rooms/:roomId/messages', auth, async (req, res) => { const result = db.prepare('INSERT INTO messages (room_id, user, text, attachments, timestamp) VALUES (?,?,?,?,?)').run( roomId, username, text || '', attachments ? JSON.stringify(attachments) : null, now ); - const msg = { - id: result.lastInsertRowid, - room_id: roomId, - user: username, - text: text || '', - attachments: attachments || [], - timestamp: now - }; + const msg = { id: result.lastInsertRowid, room_id: roomId, user: username, text: text || '', attachments: attachments || [], timestamp: now }; const lastMsg = db.prepare('SELECT text, timestamp FROM messages WHERE room_id = ? ORDER BY timestamp DESC LIMIT 1').get(roomId); broadcastToRoomExcludeSelf(roomId, username, { - type: 'new_message', - message: msg, + type: 'new_message', message: msg, room_preview: { room_id: roomId, last_message: lastMsg ? lastMsg.text : '', last_time: lastMsg ? lastMsg.timestamp : '' } }); - addToHistory(roomId, 'user', `${username}: ${text || '图片'}`); res.json(msg); @@ -182,9 +179,7 @@ app.post('/api/rooms/:roomId/messages', auth, async (req, res) => { if (aiResponse && aiResponse.action !== 'ignore') { handleAIResult(aiResponse, roomId, username, text || '', attachments || []); } - } catch (e) { - console.error('AI 分析失败:', e); - } + } catch (e) { console.error('AI 分析失败:', e); } }); app.post('/api/upload', auth, upload.single('file'), (req, res) => { @@ -206,7 +201,15 @@ app.get('/api/purchases/:id', auth, (req, res) => { }); app.get('/api/summary', auth, (req, res) => { - const rooms = db.prepare('SELECT id, name FROM rooms').all(); + const username = req.user.username; + const isAdmin = req.user.isAdmin; + let rooms; + if (isAdmin) { + rooms = db.prepare('SELECT id, name FROM rooms').all(); + } else { + // 使用与群聊列表完全相同的白名单过滤方式 + rooms = db.prepare('SELECT id, name FROM rooms WHERE white_list = ? OR white_list LIKE ?').all('', `%,${username},%`); + } const summary = rooms.map(room => { const stats = db.prepare(`SELECT COUNT(*) as count, SUM(amount) as total FROM purchases WHERE room_id = ?`).get(room.id); return { @@ -230,76 +233,48 @@ app.get('/api/rooms/:roomId/purchases/export', auth, (req, res) => { res.send('\uFEFF' + csv); }); -// WebSocket 管理 +// WebSocket const clients = new Map(); wss.on('connection', (ws, req) => { console.log('✅ WebSocket 客户端已连接'); const url = new URL(req.url, 'http://localhost'); const token = url.searchParams.get('token'); if (!token) return ws.close(); - let username; - try { - const decoded = jwt.verify(token, JWT_SECRET); - username = decoded.username; - } catch (e) { - return ws.close(); - } - + try { const decoded = jwt.verify(token, JWT_SECRET); username = decoded.username; } + catch (e) { return ws.close(); } ws.username = username; ws.isAdmin = ADMINS.includes(username); clients.set(ws, { username, roomId: null, isAdmin: ws.isAdmin }); - ws.on('message', (data) => { try { const msg = JSON.parse(data); - if (msg.type === 'join') { - ws.roomId = msg.roomId; - clients.set(ws, { username, roomId: msg.roomId, isAdmin: ws.isAdmin }); - } + if (msg.type === 'join') { ws.roomId = msg.roomId; clients.set(ws, { username, roomId: msg.roomId, isAdmin: ws.isAdmin }); } } catch (e) {} }); - - ws.on('close', () => { - clients.delete(ws); - }); + ws.on('close', () => clients.delete(ws)); }); function broadcastToRoomExcludeSelf(roomId, excludeUsername, data) { const message = JSON.stringify(data); clients.forEach((info, ws) => { - if (info.roomId === roomId && info.username !== excludeUsername && ws.readyState === 1) { - ws.send(message); - } + if (info.roomId === roomId && info.username !== excludeUsername && ws.readyState === 1) ws.send(message); }); } - function broadcastToRoom(roomId, data) { const message = JSON.stringify(data); - clients.forEach((info, ws) => { - if (info.roomId === roomId && ws.readyState === 1) { - ws.send(message); - } - }); + clients.forEach((info, ws) => { if (info.roomId === roomId && ws.readyState === 1) ws.send(message); }); } - function broadcast(data) { const message = JSON.stringify(data); - clients.forEach((ws) => { - if (ws.readyState === 1) ws.send(message); - }); + clients.forEach((ws) => { if (ws.readyState === 1) ws.send(message); }); } - function storeAndBroadcastText(roomId, user, text) { const now = timestamp(); const result = db.prepare('INSERT INTO messages (room_id, user, text, timestamp) VALUES (?,?,?,?)').run(roomId, user, text, now); const msg = { id: result.lastInsertRowid, room_id: roomId, user, text, attachments: [], timestamp: now }; const lastMsg = db.prepare('SELECT text, timestamp FROM messages WHERE room_id = ? ORDER BY timestamp DESC LIMIT 1').get(roomId); - broadcastToRoom(roomId, { - type: 'new_message', - message: msg, - room_preview: { room_id: roomId, last_message: lastMsg ? lastMsg.text : '', last_time: lastMsg ? lastMsg.timestamp : '' } - }); + broadcastToRoom(roomId, { type: 'new_message', message: msg, room_preview: { room_id: roomId, last_message: lastMsg ? lastMsg.text : '', last_time: lastMsg ? lastMsg.timestamp : '' } }); return msg; } @@ -316,24 +291,22 @@ function executePendingAction(roomId, username) { try { if (action.type === 'delete') { const { itemName, purchaseIds } = action.data; - const deleteAttachments = db.prepare('DELETE FROM purchase_attachments WHERE purchase_id = ?'); - const deleteHistory = db.prepare('DELETE FROM purchase_history WHERE purchase_id = ?'); - const deletePurchase = db.prepare('DELETE FROM purchases WHERE id = ?'); - for (const id of purchaseIds) { deleteAttachments.run(id); deleteHistory.run(id); deletePurchase.run(id); } + const delA = db.prepare('DELETE FROM purchase_attachments WHERE purchase_id = ?'); + const delH = db.prepare('DELETE FROM purchase_history WHERE purchase_id = ?'); + const delP = db.prepare('DELETE FROM purchases WHERE id = ?'); + for (const id of purchaseIds) { delA.run(id); delH.run(id); delP.run(id); } const reply = `✅ 已删除采购记录:${itemName}(共 ${purchaseIds.length} 条)`; - storeAndBroadcastText(roomId, '小财', reply); - addToHistory(roomId, 'assistant', reply); + storeAndBroadcastText(roomId, '小财', reply); addToHistory(roomId, 'assistant', reply); } else if (action.type === 'clear') { db.prepare('DELETE FROM purchase_attachments WHERE purchase_id IN (SELECT id FROM purchases WHERE room_id = ?)').run(roomId); db.prepare('DELETE FROM purchase_history WHERE purchase_id IN (SELECT id FROM purchases WHERE room_id = ?)').run(roomId); db.prepare('DELETE FROM purchases WHERE room_id = ?').run(roomId); const reply = '✅ 已清空当前房间的所有采购数据。'; - storeAndBroadcastText(roomId, '小财', reply); - addToHistory(roomId, 'assistant', reply); + storeAndBroadcastText(roomId, '小财', reply); addToHistory(roomId, 'assistant', reply); } broadcastToRoom(roomId, { type: 'purchase_updated' }); return true; - } catch (e) { console.error('执行待处理操作失败:', e); storeAndBroadcastText(roomId, '小财', '❌ 操作执行失败,请重试。'); return false; } + } catch (e) { storeAndBroadcastText(roomId, '小财', '❌ 操作执行失败,请重试。'); return false; } } // 对话历史 @@ -341,25 +314,24 @@ const conversationHistory = new Map(); function getHistory(roomId) { if (!conversationHistory.has(roomId)) conversationHistory.set(roomId, []); return conversationHistory.get(roomId); } function addToHistory(roomId, role, content) { const history = getHistory(roomId); history.push({ role, content }); if (history.length > 60) conversationHistory.set(roomId, history.slice(-40)); } -// AI 分析 +// AI 分析(明确权限不足时返回 chat 而非 ignore) async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin) { const systemPrompt = `你是智能财务助手“小财”。当前服务器时间:${currentTimeStr()}。结合对话历史理解用户意图,只返回JSON。 意图分类: -- 采购/付款/发票相关:action="purchase",提取以下字段(所有字段可选,缺失则不返回): - purchase_item (物品名称), quantity (数量,默认1), unit_price (单价,默认0), amount (总金额,默认0), - freight (邮费,默认0), payment_method (付款方式:支付宝/微信/对公转账/现金等,淘宝购买默认支付宝), - invoice_type (发票类型:增票/普票/无票,默认无票), status (待采购/已采购/发票已收), applicant (申请人), - remarks (备注,重要信息如“下次换商家”“缺少配件”等), created_time (如果用户提到具体时间则按yyyy/MM/dd HH:mm:ss格式输出) +- 采购/付款/发票相关:action="purchase",提取字段:purchase_item, quantity(默认1), unit_price(默认0), amount(默认0), freight(默认0), payment_method(淘宝默认支付宝), invoice_type(默认无票), status, applicant, remarks, created_time - 查询汇总:action="query" - 聊天:action="chat",reply简短回复 - 删除指定物品:action="delete",delete_item为物品名 - 清空所有采购数据:action="clear_all" -- 忽略:action="ignore" +- 忽略(仅当完全无关):action="ignore" -权限规则:删除和清空仅限管理员。当前用户${username},管理员状态:${isAdmin}。 -采购规则:用户补充信息时更新已有记录。更新时请返回完整字段值,包括未变化的字段。 -如果用户要求修改时间,请生成正确的created_time字段。`; +⚠️ 重要权限规则: +- 如果用户要求删除或清空,但当前用户${username}不是管理员(管理员状态:${isAdmin}),你必须返回 action="chat",reply 说明需要管理员权限。绝对不要返回 action="ignore" 或 action="delete"/"clear_all"。 +- 如果是管理员,正常返回对应 action。 + +采购规则:用户补充信息时更新已有记录。更新时请返回完整字段值,包括未变化的字段。如果用户要求修改时间,请生成正确的created_time字段。 +⚠️ 总金额(amount)由系统自动按“数量×单价+邮费”计算,你无需提供该字段。`; const messages = [ { role: 'system', content: systemPrompt }, @@ -381,61 +353,35 @@ async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin) { function handleAIResult(aiResult, roomId, username, originalText, attachments) { const now = timestamp(); - - if (aiResult.action === 'ask') { - storeAndBroadcastText(roomId, '小财', aiResult.question); - addToHistory(roomId, 'assistant', aiResult.question); - return; - } - + if (aiResult.action === 'ask') { storeAndBroadcastText(roomId, '小财', aiResult.question); addToHistory(roomId, 'assistant', aiResult.question); return; } if (aiResult.action === 'delete') { const itemName = aiResult.delete_item; const purchases = db.prepare('SELECT id, item, amount, payment_method, status, applicant, created_at FROM purchases WHERE room_id = ? AND item LIKE ?').all(roomId, `%${itemName}%`); - if (purchases.length === 0) { - storeAndBroadcastText(roomId, '小财', `没有找到与“${itemName}”相关的采购记录。`); - addToHistory(roomId, 'assistant', `没有找到与“${itemName}”相关的采购记录。`); - return; - } + if (purchases.length === 0) { storeAndBroadcastText(roomId, '小财', `没有找到与“${itemName}”相关的采购记录。`); return; } let confirmText = `⚠️ 即将删除以下 ${purchases.length} 条采购记录,请回复“确认”继续:\n\n`; - purchases.forEach(p => { - confirmText += `• ${p.item} | ¥${p.amount} | ${p.status} | ${p.applicant} | ${p.created_at}\n`; - }); + purchases.forEach(p => confirmText += `• ${p.item} | ¥${p.amount} | ${p.status} | ${p.applicant} | ${p.created_at}\n`); confirmText += `\n如果不删除,请忽略此消息。`; storeAndBroadcastText(roomId, '小财', confirmText); addToHistory(roomId, 'assistant', confirmText); setPendingAction(roomId, username, { type: 'delete', data: { itemName, purchaseIds: purchases.map(p => p.id) } }); return; } - if (aiResult.action === 'clear_all') { const count = db.prepare('SELECT COUNT(*) as count FROM purchases WHERE room_id = ?').get(roomId).count; - if (count === 0) { - storeAndBroadcastText(roomId, '小财', '当前房间没有采购记录,无需清空。'); - addToHistory(roomId, 'assistant', '当前房间没有采购记录,无需清空。'); - return; - } - const confirmText = `⚠️ 即将清空当前房间的 ${count} 条采购数据,请回复“确认”继续,否则忽略。`; - storeAndBroadcastText(roomId, '小财', confirmText); - addToHistory(roomId, 'assistant', confirmText); + if (count === 0) { storeAndBroadcastText(roomId, '小财', '当前房间没有采购记录,无需清空。'); return; } + storeAndBroadcastText(roomId, '小财', `⚠️ 即将清空当前房间的 ${count} 条采购数据,请回复“确认”继续,否则忽略。`); + addToHistory(roomId, 'assistant', `请求清空${count}条记录`); setPendingAction(roomId, username, { type: 'clear', data: {} }); return; } - if (aiResult.action === 'purchase') { const createdTime = aiResult.created_time || now; let purchase = db.prepare('SELECT * FROM purchases WHERE room_id = ? AND item LIKE ? AND status != ?').get(roomId, `%${aiResult.purchase_item}%`, '已完成'); let replyText = ''; - - // 计算总金额:优先使用 AI 提供的 amount,否则根据单价*数量+邮费计算 const quantity = aiResult.quantity || 1; const unitPrice = aiResult.unit_price || 0; const freight = aiResult.freight || 0; - let amount; - if (aiResult.amount !== undefined && aiResult.amount !== null) { - amount = aiResult.amount; - } else { - amount = unitPrice * quantity + freight; - } + const amount = quantity * unitPrice + freight; if (!purchase) { const id = uuidv4(); @@ -445,9 +391,7 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { const status = aiResult.status || '待处理'; const applicant = aiResult.applicant || username; const remarks = aiResult.remarks || ''; - - db.prepare(`INSERT INTO purchases (id, room_id, item, quantity, unit_price, amount, freight, payment_method, invoice_type, status, applicant, remarks, created_at) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`).run( + db.prepare(`INSERT INTO purchases (id, room_id, item, quantity, unit_price, amount, freight, payment_method, invoice_type, status, applicant, remarks, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`).run( id, roomId, item, quantity, unitPrice, amount, freight, paymentMethod, invoiceType, status, applicant, remarks, createdTime ); db.prepare('INSERT INTO purchase_history (purchase_id, action, user, timestamp) VALUES (?,?,?,?)').run(id, `创建采购:${item},数量${quantity},金额¥${amount},状态${status}`, '小财', now); @@ -455,7 +399,6 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { purchase = { id }; } else { const old = purchase; - // 合并新值 const newVals = { quantity: aiResult.quantity ?? old.quantity, unit_price: aiResult.unit_price ?? old.unit_price, @@ -467,9 +410,10 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { remarks: aiResult.remarks !== undefined ? aiResult.remarks : old.remarks, created_at: aiResult.created_time || old.created_at }; - // 重新计算金额,除非 AI 明确给了 amount if (aiResult.amount !== undefined && aiResult.amount !== null) { - newVals.amount = aiResult.amount; + // 始终根据数量、单价、邮费重新计算总金额,确保准确 + // newVals.amount = aiResult.amount; + newVals.amount = newVals.quantity * newVals.unit_price + newVals.freight; } else { newVals.amount = newVals.unit_price * newVals.quantity + newVals.freight; } @@ -483,7 +427,6 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { db.prepare(`UPDATE purchases SET ${field} = ? WHERE id = ?`).run(newVal, purchase.id); } } - if (changed) { db.prepare('UPDATE purchases SET updated_at = ? WHERE id = ?').run(now, purchase.id); db.prepare('INSERT INTO purchase_history (purchase_id, action, user, timestamp) VALUES (?,?,?,?)').run(purchase.id, `更新:${historyChanges.join(';')}`, username, now); @@ -492,35 +435,22 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { replyText = `ℹ️ 采购「${aiResult.purchase_item}」的信息没有发生变化。`; } } - if (attachments?.length) { const insertAttach = db.prepare('INSERT INTO purchase_attachments (purchase_id, file_path, uploaded_by, timestamp) VALUES (?,?,?,?)'); attachments.forEach(fp => insertAttach.run(purchase.id, fp, username, now)); } - storeAndBroadcastText(roomId, '小财', replyText); addToHistory(roomId, 'assistant', replyText); broadcastToRoom(roomId, { type: 'purchase_updated' }); return; } - if (aiResult.action === 'query') { - const purchaseData = db.prepare('SELECT item, amount, payment_method, status FROM purchases WHERE room_id = ? ORDER BY created_at DESC').all(roomId) - .map(p => `${p.item} ¥${p.amount} ${p.payment_method||''} ${p.status}`).join('\n'); + const purchaseData = db.prepare('SELECT item, amount, payment_method, status FROM purchases WHERE room_id = ? ORDER BY created_at DESC').all(roomId).map(p => `${p.item} ¥${p.amount} ${p.payment_method||''} ${p.status}`).join('\n'); const summaryPrompt = `根据以下采购记录,用自然语言回答用户查询“${originalText}”。采购记录:\n${purchaseData || '暂无记录'}`; - callDeepSeekForSummary(summaryPrompt).then(reply => { - storeAndBroadcastText(roomId, '小财', reply); - addToHistory(roomId, 'assistant', reply); - }); - return; - } - - if (aiResult.action === 'chat') { - const reply = aiResult.reply || '好的,有什么需要帮助的吗?'; - storeAndBroadcastText(roomId, '小财', reply); - addToHistory(roomId, 'assistant', reply); + callDeepSeekForSummary(summaryPrompt).then(reply => { storeAndBroadcastText(roomId, '小财', reply); addToHistory(roomId, 'assistant', reply); }); return; } + if (aiResult.action === 'chat') { storeAndBroadcastText(roomId, '小财', aiResult.reply || '好的。'); addToHistory(roomId, 'assistant', aiResult.reply); return; } } async function callDeepSeekForSummary(prompt) { @@ -540,6 +470,4 @@ async function callDeepSeekForSummary(prompt) { return data.choices[0].message.content; } -server.listen(process.env.PORT || 3000, () => { - console.log(`Server running on port ${process.env.PORT || 3000}`); -}); +server.listen(process.env.PORT || 3000, () => console.log(`Server running on port ${process.env.PORT || 3000}`));