Compare commits
9
Commits
762c7e24f6
...
074e907cae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
074e907cae | ||
|
|
0e17abd653 | ||
|
|
c90718c6de | ||
|
|
467d879dc3 | ||
|
|
416b173183 | ||
|
|
e08c0cdff0 | ||
|
|
1f6bc4789b | ||
|
|
300930cebd | ||
|
|
56cc1f6e2e |
+48
-11
@@ -1,18 +1,55 @@
|
|||||||
// DeepSeek API 客户端
|
// DeepSeek API 客户端
|
||||||
const DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY || '';
|
const DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY || '';
|
||||||
|
|
||||||
async function callDeepSeek(messages, temperature = 0.1) {
|
function sanitizeUserId(username) {
|
||||||
console.log('🤖 调用 DeepSeek...');
|
return username.replace(/[^a-zA-Z0-9\-_]/g, '_').substring(0, 64) || 'anon';
|
||||||
const res = await fetch('https://api.deepseek.com/chat/completions', {
|
}
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${DEEPSEEK_API_KEY}` },
|
function getErrorMsg(status, data) {
|
||||||
body: JSON.stringify({ model: 'deepseek-v4-flash', messages, temperature, stream: false })
|
var msg = data && data.error && data.error.message ? data.error.message : '';
|
||||||
});
|
switch (status) {
|
||||||
const data = await res.json();
|
case 400: return 'AI 请求格式错误,请联系管理员(400)' + (msg ? ':' + msg : '');
|
||||||
if (!data.choices || !data.choices[0]) {
|
case 401: return 'AI 认证失败,请检查 API Key(401)' + (msg ? ':' + msg : '');
|
||||||
console.error('🤖 DeepSeek 返回异常:', JSON.stringify(data));
|
case 402: return 'AI 账户余额不足,请联系管理员充值(402)';
|
||||||
throw new Error('DeepSeek API 返回异常: ' + (data.error?.message || JSON.stringify(data)));
|
case 422: return 'AI 参数错误(422)' + (msg ? ':' + msg : '');
|
||||||
|
case 429: return 'AI 请求太频繁,请稍后重试(429)';
|
||||||
|
case 500: return 'AI 服务器故障,请稍后重试(500)';
|
||||||
|
case 503: return 'AI 服务器繁忙,请稍后重试(503)';
|
||||||
|
default: return 'AI 响应异常(' + status + ')' + (msg ? ':' + msg : '');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function callDeepSeek(messages, userId, temperature = 0.1, jsonMode = true) {
|
||||||
|
console.log('调用 DeepSeek...');
|
||||||
|
var body = { model: 'deepseek-v4-flash', messages: messages, temperature: temperature, stream: false, thinking: { type: 'disabled' } };
|
||||||
|
if (jsonMode) body.response_format = { type: 'json_object' };
|
||||||
|
if (userId) body.user_id = sanitizeUserId(userId);
|
||||||
|
var res = await fetch('https://api.deepseek.com/chat/completions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + DEEPSEEK_API_KEY },
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
var data = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
var errMsg = getErrorMsg(res.status, data);
|
||||||
|
console.error('DeepSeek API 错误:', errMsg);
|
||||||
|
throw new Error(errMsg);
|
||||||
|
}
|
||||||
|
if (!data.choices || !data.choices[0]) {
|
||||||
|
console.error('DeepSeek 返回异常:', JSON.stringify(data));
|
||||||
|
throw new Error('AI 返回数据异常');
|
||||||
|
}
|
||||||
|
// 缓存命中日志
|
||||||
|
if (data.usage) {
|
||||||
|
var hit = data.usage.prompt_cache_hit_tokens || 0;
|
||||||
|
var miss = data.usage.prompt_cache_miss_tokens || 0;
|
||||||
|
var total = data.usage.prompt_tokens || 0;
|
||||||
|
var rate = total > 0 ? (hit / total * 100).toFixed(1) : 0;
|
||||||
|
console.log('缓存: hit=' + hit + ' miss=' + miss + ' total=' + total + ' 命中率=' + rate + '%');
|
||||||
|
}
|
||||||
|
// 输入消息大小日志
|
||||||
|
var inputChars = JSON.stringify(messages).length;
|
||||||
|
console.log('输入大小: ' + inputChars + ' 字符, ' + messages.length + ' 条消息');
|
||||||
return data.choices[0].message.content;
|
return data.choices[0].message.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
-5
@@ -2,7 +2,8 @@
|
|||||||
function buildSystemPrompt(username, isAdmin) {
|
function buildSystemPrompt(username, isAdmin) {
|
||||||
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false });
|
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false });
|
||||||
|
|
||||||
const role = '你是智能财务助手"小财"。当前服务器时间:' + now + '。结合对话历史理解用户意图,只返回JSON。';
|
// 稳定前缀放前面(提升缓存命中率)
|
||||||
|
const role = '你是智能财务助手"小财"。结合对话历史理解用户意图,只返回JSON。';
|
||||||
|
|
||||||
const intent = [
|
const intent = [
|
||||||
'意图分类:',
|
'意图分类:',
|
||||||
@@ -57,9 +58,9 @@ function buildSystemPrompt(username, isAdmin) {
|
|||||||
' * "已收货" — 已收到货物',
|
' * "已收货" — 已收到货物',
|
||||||
' * "已完成" — 发票已收,全部结束',
|
' * "已完成" — 发票已收,全部结束',
|
||||||
'- 状态映射:',
|
'- 状态映射:',
|
||||||
' * "付了/已付/付款了" → "已付款"',
|
' * "付了/已付/付款了/买了/花了"等明确已付款完成意图的 → "已付款"',
|
||||||
' * "到了/收到货了" → "已收货"',
|
' * "到了/收到货了"等明确已收到货意图的 → "已收货"',
|
||||||
' * "发票到/全搞定/完结" → "已完成"',
|
' * "发票到/全搞定/完结"等明确有发票或结束意图的 → "已完成"',
|
||||||
' * 新建不指定 → "待付款"',
|
' * 新建不指定 → "待付款"',
|
||||||
'- 只有明确消费意图(购买/采购/下单/付款/买了/花了)才返回 action="purchase"',
|
'- 只有明确消费意图(购买/采购/下单/付款/买了/花了)才返回 action="purchase"',
|
||||||
'- 纯陈述 → action="ignore"',
|
'- 纯陈述 → action="ignore"',
|
||||||
@@ -68,7 +69,10 @@ function buildSystemPrompt(username, isAdmin) {
|
|||||||
'- 无法确定物品名 → action="ask" 追问'
|
'- 无法确定物品名 → action="ask" 追问'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
return [role, intent, permission, deleteRules, attachmentRules, prohibitionRules, modifyGuide, purchaseRules].join('\n');
|
// 变动的放最后
|
||||||
|
const timeInfo = '当前服务器时间:' + now;
|
||||||
|
|
||||||
|
return [role, intent, permission, deleteRules, attachmentRules, prohibitionRules, modifyGuide, purchaseRules, timeInfo].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { buildSystemPrompt };
|
module.exports = { buildSystemPrompt };
|
||||||
|
|||||||
+4
-4
@@ -170,7 +170,7 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
|||||||
if (aiResult.action === 'query') {
|
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 = '\u6839\u636e\u4ee5\u4e0b\u91c7\u8d2d\u8bb0\u5f55\uff0c\u7528\u81ea\u7136\u8bed\u8a00\u56de\u7b54\u7528\u6237\u67e5\u8be2"' + originalText + '"' + '\u3002\u91c7\u8d2d\u8bb0\u5f55\uff1a\n' + (purchaseData || '\u6682\u65e0\u8bb0\u5f55');
|
const summaryPrompt = '\u6839\u636e\u4ee5\u4e0b\u91c7\u8d2d\u8bb0\u5f55\uff0c\u7528\u81ea\u7136\u8bed\u8a00\u56de\u7b54\u7528\u6237\u67e5\u8be2"' + originalText + '"' + '\u3002\u91c7\u8d2d\u8bb0\u5f55\uff1a\n' + (purchaseData || '\u6682\u65e0\u8bb0\u5f55');
|
||||||
callDeepSeekForSummary(summaryPrompt).then(reply => { storeAndBroadcastText(roomId, '\u5c0f\u8d22', reply); addToHistory(roomId, 'assistant', reply); });
|
callDeepSeekForSummary(summaryPrompt, username).then(reply => { storeAndBroadcastText(roomId, '小财', reply); addToHistory(roomId, 'assistant', reply); });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,13 +181,13 @@ function handleAIResult(aiResult, roomId, username, originalText, attachments) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function callDeepSeekForSummary(prompt) {
|
async function callDeepSeekForSummary(prompt, userId) {
|
||||||
const { callDeepSeek } = require('./ai/client');
|
const { callDeepSeek } = require('./ai/client');
|
||||||
const messages = [
|
const messages = [
|
||||||
{ role: 'system', content: '\u4f60\u662f\u4e00\u4e2a\u8d22\u52a1\u52a9\u624b\uff0c\u8bf7\u6839\u636e\u91c7\u8d2d\u8bb0\u5f55\u751f\u6210\u7b80\u6d01\u56de\u590d\u3002' },
|
{ role: 'system', content: '你是一个财务助手,请根据采购记录生成简洁回复。' },
|
||||||
{ role: 'user', content: prompt }
|
{ role: 'user', content: prompt }
|
||||||
];
|
];
|
||||||
return callDeepSeek(messages, 0.3);
|
return callDeepSeek(messages, userId, 0.3, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; height: 100vh; display: flex; justify-content: center; align-items: center; }
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; height: 100vh; display: flex; justify-content: center; align-items: center; }
|
||||||
.app { width: 100%; max-width: 420px; height: 100vh; background: #fff; display: flex; flex-direction: column; box-shadow: 0 0 20px rgba(0,0,0,0.1); position: relative; }
|
.app { width: 100%; height: 100vh; background: #fff; display: flex; flex-direction: column; box-shadow: 0 0 20px rgba(0,0,0,0.1); position: relative; }
|
||||||
.hidden { display: none !important; }
|
.hidden { display: none !important; }
|
||||||
|
|
||||||
.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; }
|
.header { background: #2a5298; color: #fff; padding: 0 16px; display: flex; justify-content: space-between; align-items: center; min-height: 48px; }
|
||||||
.header-left { display: flex; align-items: center; gap: 8px; cursor: pointer; }
|
.header-left { display: flex; align-items: center; gap: 8px; cursor: pointer; }
|
||||||
.header-left h2 { font-size: 18px; font-weight: 500; }
|
.header-left h2 { font-size: 18px; font-weight: 500; }
|
||||||
.icon-btn { background: none; border: none; color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 4px; border-radius: 50%; }
|
.icon-btn { background: none; border: none; color: #fff; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 4px; border-radius: 50%; }
|
||||||
.icon-btn svg { width: 22px; height: 22px; stroke: #fff; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
|
.icon-btn svg { width: 22px; height: 22px; stroke: #fff; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
|
||||||
|
.chat-item:nth-child(even) {background-color: #e8f0fe;}
|
||||||
|
|
||||||
|
|
||||||
.chat-list { flex: 1; overflow-y: auto; padding-bottom: 8px; }
|
.chat-list { flex: 1; overflow-y: auto; padding-bottom: 8px; }
|
||||||
.chat-item { padding: 14px 16px; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; cursor: pointer; }
|
.chat-item { padding: 14px 16px; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; cursor: pointer; }
|
||||||
.chat-item:active { background: #f9f9f9; }
|
.chat-item:active { background: #f9f9f9; }
|
||||||
.avatar { width: 44px; height: 44px; border-radius: 50%; background: #e0e0e0; margin-right: 12px; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; color: #555; flex-shrink: 0; }
|
.avatar { width: 44px; height: 44px; border-radius: 50%; background: #2a5298; margin-right: 12px; display: flex; align-items: center; justify-content: center; font-size: 18px; font-weight: 600; color: #fff; flex-shrink: 0; }
|
||||||
.chat-info { flex: 1; min-width: 0; }
|
.chat-info { flex: 1; min-width: 0; }
|
||||||
.chat-name { font-size: 16px; font-weight: 500; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.chat-name { font-size: 16px; font-weight: 500; margin-bottom: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
.last-msg { font-size: 14px; color: #888; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.last-msg { font-size: 14px; color: #888; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
@@ -23,13 +21,11 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
|
|||||||
.room-actions { display: flex; gap: 4px; margin-top: 4px; }
|
.room-actions { display: flex; gap: 4px; margin-top: 4px; }
|
||||||
.edit-room-btn { background: none; border: none; cursor: pointer; display: flex; align-items: center; padding: 2px; }
|
.edit-room-btn { background: none; border: none; cursor: pointer; display: flex; align-items: center; padding: 2px; }
|
||||||
.edit-room-btn svg { width: 16px; height: 16px; stroke: #888; fill: none; stroke-width: 2; }
|
.edit-room-btn svg { width: 16px; height: 16px; stroke: #888; fill: none; stroke-width: 2; }
|
||||||
|
|
||||||
.summary-card { background: linear-gradient(135deg, #e8f0fe 0%, #d4e4fc 100%); margin: 8px; border-radius: 12px; padding: 14px 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; border: 1px solid #b8d4f8; }
|
.summary-card { background: linear-gradient(135deg, #e8f0fe 0%, #d4e4fc 100%); margin: 8px; border-radius: 12px; padding: 14px 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; border: 1px solid #b8d4f8; }
|
||||||
.summary-card h4 { font-size: 16px; font-weight: 500; }
|
.summary-card h4 { font-size: 16px; font-weight: 500; }
|
||||||
.summary-card .summary-preview { font-size: 14px; color: #555; }
|
.summary-card .summary-preview { font-size: 14px; color: #555; }
|
||||||
|
|
||||||
.chat-window { display: flex; flex-direction: column; height: 100%; }
|
.chat-window { display: flex; flex-direction: column; height: 100%; }
|
||||||
.chat-header { background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); color: #fff; padding: 0 16px; display: flex; align-items: center; min-height: 48px; }
|
.chat-header { background: #2a5298; color: #fff; padding: 0 16px; display: flex; align-items: center; min-height: 48px; }
|
||||||
.header .back-btn { margin-right: 12px; }
|
.header .back-btn { margin-right: 12px; }
|
||||||
.chat-header .back-btn { margin-right: 12px; }
|
.chat-header .back-btn { margin-right: 12px; }
|
||||||
.chat-title { font-size: 17px; font-weight: 500; flex: 1; }
|
.chat-title { font-size: 17px; font-weight: 500; flex: 1; }
|
||||||
@@ -48,7 +44,6 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
|
|||||||
.msg-bubble th { background: #f0f0f0; }
|
.msg-bubble th { background: #f0f0f0; }
|
||||||
.msg-bubble pre { background: #f0f0f0; padding: 8px; border-radius: 4px; overflow-x: auto; }
|
.msg-bubble pre { background: #f0f0f0; padding: 8px; border-radius: 4px; overflow-x: auto; }
|
||||||
.msg-bubble code { background: #f0f0f0; padding: 2px 4px; border-radius: 3px; font-size: 14px; }
|
.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 { 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; min-height: 40px; max-height: 120px; overflow-y: hidden; 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 { background: none; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 4px; }
|
||||||
@@ -120,7 +115,7 @@ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-
|
|||||||
/* 可编辑详情弹窗 */
|
/* 可编辑详情弹窗 */
|
||||||
.detail-modal { max-height: 90vh; overflow-y: auto; padding: 0; }
|
.detail-modal { max-height: 90vh; overflow-y: auto; padding: 0; }
|
||||||
.detail-modal input, .detail-modal select, .detail-modal textarea { margin: 0; }
|
.detail-modal input, .detail-modal select, .detail-modal textarea { margin: 0; }
|
||||||
.detail-header { background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); color: #fff; padding: 8px 12px 8px 16px; display: flex; align-items: center; gap: 8px; position: sticky; top: 0; z-index: 1; }
|
.detail-header { background: #2a5298; color: #fff; padding: 8px 12px 8px 16px; display: flex; align-items: center; gap: 8px; position: sticky; top: 0; z-index: 1; }
|
||||||
.detail-header .icon-btn { padding:0; margin-right:0; flex-shrink: 0; margin-left: auto; }
|
.detail-header .icon-btn { padding:0; margin-right:0; flex-shrink: 0; margin-left: auto; }
|
||||||
.detail-header .icon-btn svg { stroke: #fff; }
|
.detail-header .icon-btn svg { stroke: #fff; }
|
||||||
.modal .detail-title-input { flex: 1; font-size: 18px; font-weight: 600; color: #fff; background: transparent; border: none; outline: none; padding: 4px 0; min-width: 0; }
|
.modal .detail-title-input { flex: 1; font-size: 18px; font-weight: 600; color: #fff; background: transparent; border: none; outline: none; padding: 4px 0; min-width: 0; }
|
||||||
|
|||||||
@@ -237,7 +237,7 @@
|
|||||||
<script>
|
<script>
|
||||||
// 版本号
|
// 版本号
|
||||||
(function() {
|
(function() {
|
||||||
document.getElementById('version-text').textContent = 'v2.8-260728';
|
document.getElementById('version-text').textContent = 'v2.8.2-260729';
|
||||||
})();
|
})();
|
||||||
document.addEventListener('input', function(e) {
|
document.addEventListener('input', function(e) {
|
||||||
if (e.target.id === 'msg-input' || e.target.id === 'purchase-msg-input') {
|
if (e.target.id === 'msg-input' || e.target.id === 'purchase-msg-input') {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ function onImageClick(e) {
|
|||||||
initPinchZoom(overlay.querySelector('img'));
|
initPinchZoom(overlay.querySelector('img'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function onImgThumbDblClick(img) {
|
function onImgThumbClick(img) {
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
overlay.className = 'fullscreen-overlay';
|
overlay.className = 'fullscreen-overlay';
|
||||||
overlay.innerHTML = '<img src="' + img.src + '" style="max-width:100%;max-height:100%;object-fit:contain;transition: transform 0.1s; transform-origin: 0 0;" alt="">';
|
overlay.innerHTML = '<img src="' + img.src + '" style="max-width:100%;max-height:100%;object-fit:contain;transition: transform 0.1s; transform-origin: 0 0;" alt="">';
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function renderEditAttachments(attachments) {
|
|||||||
}
|
}
|
||||||
container.innerHTML = attachments.map(function(a) {
|
container.innerHTML = attachments.map(function(a) {
|
||||||
return '<div class="attach-thumb-wrap">' +
|
return '<div class="attach-thumb-wrap">' +
|
||||||
'<img class="attach-thumb" src="' + a.file_path + '" ondblclick="onImgThumbDblClick(this)">' +
|
'<img class="attach-thumb" src="' + a.file_path + '" onclick="onImgThumbClick(this)">' +
|
||||||
'<button class="attach-del-btn" onclick="deleteAttachment(event, \'' + encodeURIComponent(a.file_path) + '\')">' +
|
'<button class="attach-del-btn" onclick="deleteAttachment(event, \'' + encodeURIComponent(a.file_path) + '\')">' +
|
||||||
'<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>' +
|
'<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>' +
|
||||||
'</button>' +
|
'</button>' +
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// 消息路由 + AI 分析
|
// 消息路由 + AI 分析
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { authMiddleware } = require('../middleware/auth');
|
const { authMiddleware } = require('../middleware/auth');
|
||||||
const { broadcastToRoomExcludeSelf } = require('../ws');
|
const { broadcastToRoomExcludeSelf, broadcastToRoom } = require('../ws');
|
||||||
const { buildSystemPrompt } = require('../ai/prompt');
|
const { buildSystemPrompt } = require('../ai/prompt');
|
||||||
const { validate, normalize } = require('../ai/validator');
|
const { validate, normalize } = require('../ai/validator');
|
||||||
const { callDeepSeek } = require('../ai/client');
|
const { callDeepSeek } = require('../ai/client');
|
||||||
@@ -48,7 +48,13 @@ router.post('/:roomId/messages', authMiddleware, async (req, res) => {
|
|||||||
if (aiResponse && aiResponse.action !== 'ignore') {
|
if (aiResponse && aiResponse.action !== 'ignore') {
|
||||||
handleAIResult(aiResponse, roomId, username, text || '', attachments || []);
|
handleAIResult(aiResponse, roomId, username, text || '', attachments || []);
|
||||||
}
|
}
|
||||||
} catch (e) { console.error('AI 分析失败:', e); }
|
} catch (e) {
|
||||||
|
console.error('AI 分析失败:', e.message);
|
||||||
|
var errText = '❌ ' + (e.message || 'AI 处理失败,请稍后重试');
|
||||||
|
var result = db.prepare('INSERT INTO messages (room_id, user, text, timestamp) VALUES (?,?,?,?)').run(roomId, '小财', errText, timestamp());
|
||||||
|
var msg = { id: result.lastInsertRowid, room_id: roomId, user: '小财', text: errText, attachments: [], timestamp: timestamp() };
|
||||||
|
broadcastToRoom(roomId, { type: 'new_message', message: msg });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin, roomId) {
|
async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin, roomId) {
|
||||||
@@ -63,7 +69,7 @@ async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin, roo
|
|||||||
...historyMessages.slice(-30),
|
...historyMessages.slice(-30),
|
||||||
{ role: 'user', content: `${username}: ${text}` }
|
{ role: 'user', content: `${username}: ${text}` }
|
||||||
];
|
];
|
||||||
const content = await callDeepSeek(messages, 0.1);
|
const content = await callDeepSeek(messages, username);
|
||||||
console.log('🤖 AI 返回:', content);
|
console.log('🤖 AI 返回:', content);
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(content.replace(/```json|```/g, '').trim());
|
const parsed = JSON.parse(content.replace(/```json|```/g, '').trim());
|
||||||
|
|||||||
Reference in New Issue
Block a user