diff --git a/server/ai/prompt.js b/server/ai/prompt.js
index f13b17f..e7146e3 100644
--- a/server/ai/prompt.js
+++ b/server/ai/prompt.js
@@ -37,7 +37,11 @@ function buildSystemPrompt(username, isAdmin) {
const purchaseRules = `
⚠️ 采购规则:
-- 系统会在 Prompt 末尾提供"当前房间采购清单",你必须根据清单判断用户是要更新已有记录还是新建。
+- 系统会在 Prompt 末尾提供“当前房间采购清单”,你必须根据清单和对话历史判断用户是要更新已有记录还是新建。
+- **新建采购**(用户首次购买、重复购买、明确说“再买/又/新/另一批/这次”等)→ 必须返回 "is_new": true。
+- **更新已有记录**(用户要求修改、更正、调整,如“改一下/更新/修改/更正/搞错了/不是XX是YY”)→ 返回 "is_new": false。
+- **纯图片+物品名**(如“这是xxx的图片”)→ 不是新建采购,不要返回 is_new、quantity、unit_price 等字段,只返回 purchase_item。
+- 如果无法明确判断新建/更新,优先按新建处理 → 返回 "is_new": true。
- 采购状态(status)只能从以下 4 个值中选择,绝不允许自创:
* "待付款" — 采购已记录,等待付款
* "已付款" — 已完成支付,等待收货
@@ -49,13 +53,8 @@ function buildSystemPrompt(username, isAdmin) {
* "发票到/全搞定/完结" → "已完成"
* 新建不指定 → "待付款"
- 只有明确消费意图(购买/采购/下单/付款/买了/花了)才返回 action="purchase"
-- 纯陈述(如"前天deep")→ action="ignore"
+- 纯陈述 → action="ignore"
- 信息不完整 → action="ask" 追问
-- 新建 vs 更新:
- * "昨天/今天/又/再/新/另一批"等重复购买 → is_new: true(强制新建)
- * "改一下/更新/修改/调整/更正/搞错了/不对/错了/不是XX是YY"等纠正意图 → 不提供 is_new(匹配已有更新),**购买清单中的名称是你的匹配线索**
- * "纯图片+物品名"不是新建,不要返回 is_new
- * 无法确定 → 默认 is_new: true
- amount 由系统自动计算(quantity × unit_price + freight),AI 无需提供
- 无法确定物品名 → action="ask" 追问`;
diff --git a/server/public/css/style.css b/server/public/css/style.css
index 9170492..ce235b7 100644
--- a/server/public/css/style.css
+++ b/server/public/css/style.css
@@ -56,8 +56,6 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
.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; }
.send-btn svg { width: 18px; height: 18px; fill: #fff; }
-.purchase-panel-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: #fff; z-index: 10; display: flex; flex-direction: column; }
-.purchase-panel-header { background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); color: #fff; padding: 0 16px; display: flex; justify-content: space-between; align-items: center; min-height: 48px; }
.purchase-panel-body { flex: 1; overflow-y: auto; padding: 12px; }
.purchase-month-group { margin-bottom: 20px; }
.purchase-month-header { background: #f5f5f5; font-size: 16px; font-weight: 600; padding: 10px 8px; border-radius: 4px; display: flex; justify-content: space-between; }
diff --git a/server/public/index.html b/server/public/index.html
index ce1801f..75ff7f8 100644
--- a/server/public/index.html
+++ b/server/public/index.html
@@ -54,17 +54,28 @@
-
-
-
diff --git a/server/public/js/purchases.js b/server/public/js/purchases.js
index 6b41f74..d6f1109 100644
--- a/server/public/js/purchases.js
+++ b/server/public/js/purchases.js
@@ -46,14 +46,6 @@ function formatMonth(dateStr) {
return dateStr.substring(0,7);
}
-function openPurchasePanel() {
- document.getElementById('purchase-panel-body').innerHTML = '
加载中...
';
- document.getElementById('purchase-panel-overlay').classList.remove('hidden');
- loadPurchases();
-}
-
-function closePurchasePanel() { document.getElementById('purchase-panel-overlay').classList.add('hidden'); }
-
async function openDetail(pid) {
const token = Store.getToken();
const res = await fetch('/api/purchases/' + pid, { headers: { 'Authorization': `Bearer ${token}` } });
@@ -89,7 +81,7 @@ async function openSummary() {
if (summary.length === 0) html = '
暂无采购记录
';
else {
summary.forEach(s => {
- html += `
${escapeHtml(s.room_name)}
${s.purchase_count} 条,合计 ¥${s.total_amount}
`;
+ html += `
${escapeHtml(s.room_name)}
${s.purchase_count} 条,合计 ¥${s.total_amount}
`;
});
}
document.getElementById('detail-title').textContent = '采购汇总';
@@ -151,32 +143,14 @@ async function deleteRoom() {
// 滑动手势
function initSwipeGestures() {
const chatPage = document.getElementById('chat-page');
- const purchaseOverlay = document.getElementById('purchase-panel-overlay');
- let startX = 0, startY = 0;
+ let startX = 0;
- function handleTouchStart(e) { startX = e.touches[0].clientX; startY = e.touches[0].clientY; }
-
- function handleTouchEnd(e, target) {
+ chatPage.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; }, { passive: true });
+ chatPage.addEventListener('touchend', (e) => {
if (!startX) return;
const endX = e.changedTouches[0].clientX;
- const endY = e.changedTouches[0].clientY;
const diffX = endX - startX;
- const diffY = endY - startY;
- if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
- if (target === chatPage && !purchaseOverlay.classList.contains('hidden')) {
- if (Math.abs(diffX) > 30) closePurchasePanel();
- } else if (target === chatPage) {
- if (diffX > 30) showList();
- else if (diffX < -30) openPurchasePanel();
- } else if (target === purchaseOverlay) {
- if (Math.abs(diffX) > 30) closePurchasePanel();
- }
- }
- startX = 0; startY = 0;
- }
-
- chatPage.addEventListener('touchstart', handleTouchStart, { passive: true });
- chatPage.addEventListener('touchend', (e) => handleTouchEnd(e, chatPage), { passive: true });
- purchaseOverlay.addEventListener('touchstart', handleTouchStart, { passive: true });
- purchaseOverlay.addEventListener('touchend', (e) => handleTouchEnd(e, purchaseOverlay), { passive: true });
+ if (diffX > 50) backToPurchase();
+ startX = 0;
+ }, { passive: true });
}
diff --git a/server/public/js/ws.js b/server/public/js/ws.js
index c1b1448..0abedab 100644
--- a/server/public/js/ws.js
+++ b/server/public/js/ws.js
@@ -80,13 +80,13 @@ function renderChatList() {
}
return `
-
${escapeHtml(room.name.charAt(0))}
-
+
${escapeHtml(room.name.charAt(0))}
+
${escapeHtml(room.name)}
${escapeHtml(lastMsg)}
-
${lastTime}
+
${lastTime}
${isAdmin ? `
${whiteListHtml}