diff --git a/server/ai/client.js b/server/ai/client.js index 8b44920..c91786a 100644 --- a/server/ai/client.js +++ b/server/ai/client.js @@ -48,8 +48,8 @@ async function callDeepSeek(messages, userId, temperature = 0.1, jsonMode = true console.log('缓存: hit=' + hit + ' miss=' + miss + ' total=' + total + ' 命中率=' + rate + '%'); } // 输入消息大小日志 - var inputChars = JSON.stringify(messages).length; - console.log('输入大小: ' + inputChars + ' 字符, ' + messages.length + ' 条消息'); + //var inputChars = JSON.stringify(messages).length; + //console.log('输入大小: ' + inputChars + ' 字符, ' + messages.length + ' 条消息'); return data.choices[0].message.content; } diff --git a/server/handlers.js b/server/handlers.js index 28101ba..90aa940 100644 --- a/server/handlers.js +++ b/server/handlers.js @@ -36,26 +36,26 @@ function executePendingAction(roomId, username) { 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 = '\u2705 \u5df2\u5220\u9664\u91c7\u8d2d\u8bb0\u5f55\uff1a' + itemName + '\uff08\u5171 ' + purchaseIds.length + ' \u6761\uff09'; - storeAndBroadcastText(roomId, '\u5c0f\u8d22', reply); addToHistory(roomId, 'assistant', reply); + const reply = '✅ 已删除采购记录:' + itemName + '(共 ' + purchaseIds.length + ' 条)'; + 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 = '\u2705 \u5df2\u6e05\u7a7a\u5f53\u524d\u623f\u95f4\u7684\u6240\u6709\u91c7\u8d2d\u6570\u636e\u3002'; - storeAndBroadcastText(roomId, '\u5c0f\u8d22', reply); addToHistory(roomId, 'assistant', reply); + const reply = '✅ 已清空当前房间的所有采购数据。'; + storeAndBroadcastText(roomId, '小财', reply); addToHistory(roomId, 'assistant', reply); } broadcastToRoom(roomId, { type: 'purchase_updated' }); return true; - } catch (e) { storeAndBroadcastText(roomId, '\u5c0f\u8d22', '\u274c \u64cd\u4f5c\u6267\u884c\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5\u3002'); return false; } + } catch (e) { storeAndBroadcastText(roomId, '小财', '❌ 操作执行失败,请重试。'); return false; } } function handleAIResult(aiResult, roomId, username, originalText, attachments) { const now = timestamp(); if (aiResult.action === 'ask') { - const q = aiResult.question || aiResult.reply || '\u8bf7\u63d0\u4f9b\u66f4\u591a\u4fe1\u606f\u3002'; - storeAndBroadcastText(roomId, '\u5c0f\u8d22', q); addToHistory(roomId, 'assistant', q); return; + const q = aiResult.question || aiResult.reply || '请提供更多信息。'; + storeAndBroadcastText(roomId, '小财', q); addToHistory(roomId, 'assistant', q); return; } if (aiResult.action === 'delete') { @@ -73,14 +73,14 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { purchases = db.prepare(query).all(...params); } if (purchases.length === 0) { - const label = itemName === '__AMOUNT_ZERO__' ? '\u91d1\u989d\u4e3a0' : (itemName === '__NULL_NAME__' ? '\u540d\u79f0\u4e3a\u7a7a' : '"' + itemName + '"'); - storeAndBroadcastText(roomId, '\u5c0f\u8d22', '\u6ca1\u6709\u627e\u5230\u4e0e"' + label + '"\u76f8\u5173\u7684\u91c7\u8d2d\u8bb0\u5f55\u3002'); + const label = itemName === '__AMOUNT_ZERO__' ? '金额为0' : (itemName === '__NULL_NAME__' ? '名称为空' : '"' + itemName + '"'); + storeAndBroadcastText(roomId, '小财', '没有找到与"' + label + '"相关的采购记录。'); return; } - let confirmText = '\u26a0\ufe0f \u5373\u5c06\u5220\u9664\u4ee5\u4e0b ' + purchases.length + ' \u6761\u91c7\u8d2d\u8bb0\u5f55\uff0c\u8bf7\u56de\u590d"\u786e\u8ba4"\u7ee7\u7eed\uff1a\n\n'; - purchases.forEach(p => confirmText += '\u2022 ' + p.item + ' | ' + p.amount + ' | ' + p.status + ' | ' + p.applicant + ' | ' + p.created_at + '\n'); - confirmText += '\n\u5982\u679c\u4e0d\u5220\u9664\uff0c\u8bf7\u5ffd\u7565\u6b64\u6d88\u606f\u3002'; - storeAndBroadcastText(roomId, '\u5c0f\u8d22', confirmText); + let confirmText = '⚠️ 即将删除以下 ' + purchases.length + ' 条采购记录,请回复"确认"继续:\n\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; @@ -88,23 +88,22 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { 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, '\u5c0f\u8d22', '\u5f53\u524d\u623f\u95f4\u6ca1\u6709\u91c7\u8d2d\u8bb0\u5f55\uff0c\u65e0\u9700\u6e05\u7a7a\u3002'); return; } - storeAndBroadcastText(roomId, '\u5c0f\u8d22', '\u26a0\ufe0f \u5373\u5c06\u6e05\u7a7a\u5f53\u524d\u623f\u95f4\u7684 ' + count + ' \u6761\u91c7\u8d2d\u6570\u636e\uff0c\u8bf7\u56de\u590d"\u786e\u8ba4"\u7ee7\u7eed\uff0c\u5426\u5219\u5ffd\u7565\u3002'); - addToHistory(roomId, 'assistant', '\u8bf7\u6c42\u6e05\u7a7a' + count + '\u6761\u8bb0\u5f55'); + if (count === 0) { storeAndBroadcastText(roomId, '小财', '当前房间没有采购记录,无需清空。'); return; } + storeAndBroadcastText(roomId, '小财', '⚠️ 即将清空当前房间的 ' + count + ' 条采购数据,请回复"确认"继续,否则忽略。'); + addToHistory(roomId, 'assistant', '请求清空' + count + '条记录'); setPendingAction(roomId, username, { type: 'clear', data: {} }); return; } if (aiResult.action === 'purchase') { - // use_recent_image 但缺 purchase_item if (aiResult.use_recent_image && (!aiResult.purchase_item || aiResult.purchase_item === 'null')) { const lastPurchase = db.prepare("SELECT item FROM purchases WHERE room_id = ? AND item IS NOT NULL AND item != '' ORDER BY created_at DESC LIMIT 1").get(roomId); if (lastPurchase) { aiResult.purchase_item = lastPurchase.item; } } const item = aiResult.purchase_item; if (!item || item === 'null' || item === 'undefined' || item.trim().length < 2) { - storeAndBroadcastText(roomId, '\u5c0f\u8d22', '\u8bf7\u63d0\u4f9b\u5177\u4f53\u7684\u7269\u54c1\u540d\u79f0\u3002'); - addToHistory(roomId, 'assistant', '\u8bf7\u63d0\u4f9b\u5177\u4f53\u7684\u7269\u54c1\u540d\u79f0\u3002'); + storeAndBroadcastText(roomId, '小财', '请提供具体的物品名称。'); + addToHistory(roomId, 'assistant', '请提供具体的物品名称。'); return; } @@ -115,7 +114,7 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { try { const files = JSON.parse(recentMsg.attachments); if (files.length) { attachments = files; usedRecentImage = true; } } catch(e) {} } } - if (!usedRecentImage && !attachments?.length && /(\u56fe\u7247|\u9644\u4ef6|\u52a0\u5230|\u5b58\u5165|\u5173\u8054|\u4f5c\u4e3a\u9644\u4ef6)/.test(originalText)) { + if (!usedRecentImage && !attachments?.length && /(图片|附件|加到|存入|关联|作为附件)/.test(originalText)) { const recentMsg = db.prepare("SELECT attachments FROM messages WHERE room_id = ? AND attachments IS NOT NULL AND attachments != '' AND attachments != '[]' ORDER BY id DESC LIMIT 1").get(roomId); if (recentMsg) { try { const files = JSON.parse(recentMsg.attachments); if (files.length) { attachments = files; usedRecentImage = true; } } catch(e) {} @@ -123,7 +122,6 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { } const createdTime = normalizeTime(aiResult.created_time); - // AI 不负责更新 — 聊天永远新建,修改由用户从采购清单手动完成 let purchase = null; let replyText = ''; @@ -132,13 +130,13 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { const freight = aiResult.freight || 0; const amount = quantity * unitPrice + freight; - if (!purchase && amount === 0 && aiResult.quantity === undefined && aiResult.unit_price === undefined) { - storeAndBroadcastText(roomId, '\u5c0f\u8d22', '\u65e0\u6cd5\u521b\u5efa\u300c' + aiResult.purchase_item + '\u300d\uff1a\u7f3a\u5c11\u6570\u91cf\u548c\u4ef7\u683c\u4fe1\u606f\uff0c\u8bf7\u8865\u5145\u3002'); - addToHistory(roomId, 'assistant', '\u7f3a\u5c11\u6570\u91cf\u548c\u4ef7\u683c'); + // 金额为 0 → AI 判断错误,拒绝创建 + if (!purchase && amount === 0) { + storeAndBroadcastText(roomId, '小财', '无法创建「' + aiResult.purchase_item + '」:金额为 0,请补充价格信息。'); + addToHistory(roomId, 'assistant', '金额为0,拒绝'); return; } - // 永远新建 const id = uuidv4(); const paymentMethod = aiResult.payment_method || (aiResult.method === '淘宝' ? '支付宝' : (aiResult.method || '未指定')); const invoiceType = aiResult.invoice_type || '无票'; @@ -148,20 +146,20 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { 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); - replyText = '✅ 已记录采购:' + item + ',数量 ' + quantity + ',金额 ¥' + amount + ',状态 ' + status; + db.prepare('INSERT INTO purchase_history (purchase_id, action, user, timestamp) VALUES (?,?,?,?)').run(id, '创建采购:' + item + ',数量' + quantity + ',金额¥' + amount + ',状态' + status, '小财', now); + replyText = '✅ 已记录采购:' + item + ',数量 ' + quantity + ',金额 ¥' + amount + ',状态 ' + status; purchase = { id }; 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)); - db.prepare('INSERT INTO purchase_history (purchase_id, action, user, timestamp) VALUES (?,?,?,?)').run(purchase.id, '\u6dfb\u52a0\u9644\u4ef6\uff1a' + attachments.length + ' \u4e2a', username, now); - if (replyText.includes('\u6ca1\u6709\u53d1\u751f') || usedRecentImage) replyText = '\ud83d\udcce \u5df2\u4e3a\u300c' + aiResult.purchase_item + '\u300d\u6dfb\u52a0 ' + attachments.length + ' \u4e2a\u9644\u4ef6'; + db.prepare('INSERT INTO purchase_history (purchase_id, action, user, timestamp) VALUES (?,?,?,?)').run(purchase.id, '添加附件:' + attachments.length + ' 个', username, now); + if (usedRecentImage) replyText = '📎 已为「' + aiResult.purchase_item + '」添加 ' + attachments.length + ' 个附件'; } else if (usedRecentImage) { - replyText = '\u274c \u672a\u627e\u5230\u6700\u8fd1\u7684\u56fe\u7247\u6d88\u606f\uff0c\u8bf7\u5148\u53d1\u9001\u56fe\u7247\u518d\u8bd5\u3002'; + replyText = '❌ 未找到最近的图片消息,请先发送图片再试。'; } - storeAndBroadcastText(roomId, '\u5c0f\u8d22', replyText); + storeAndBroadcastText(roomId, '小财', replyText); addToHistory(roomId, 'assistant', replyText); broadcastToRoom(roomId, { type: 'purchase_updated' }); return; @@ -169,10 +167,10 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { if (aiResult.action === 'query') { broadcastToRoom(roomId, { type: 'query_pending', text: '🔍 正在查询采购数据,请稍候...' }); - const purchaseData = db.prepare('SELECT item, amount, payment_method, status, created_at FROM purchases WHERE room_id = ? ORDER BY created_at DESC').all(roomId).map(p => p.created_at + ' ' + p.item + ' ¥' + p.amount + ' ' + (p.payment_method||'') + ' ' + p.status).join('\n'); + const purchaseData = db.prepare('SELECT item, amount, payment_method, status, created_at FROM purchases WHERE room_id = ? ORDER BY created_at DESC').all(roomId).map(p => p.created_at + ' ' + p.item + ' ¥' + p.amount + ' ' + (p.payment_method||'') + ' ' + p.status).join('\n'); const summaryPrompt = '根据以下采购记录,用自然语言回答用户查询"' + originalText + '"。采购记录:\n' + (purchaseData || '暂无记录'); callDeepSeekForSummary(summaryPrompt, username).then(function(reply) { - console.log('🤖 Query汇总回复:', reply.substring(0, 200)); + console.log('Query汇总回复:', reply.substring(0, 200)); storeAndBroadcastText(roomId, '小财', reply); addToHistory(roomId, 'assistant', reply); }); @@ -180,7 +178,7 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) { } if (aiResult.action === 'chat') { - storeAndBroadcastText(roomId, '\u5c0f\u8d22', aiResult.reply || '\u597d\u7684\u3002'); + storeAndBroadcastText(roomId, '小财', aiResult.reply || '好的。'); addToHistory(roomId, 'assistant', aiResult.reply); return; }