Add files via upload

This commit is contained in:
Cola-Echo
2025-12-24 02:40:46 +08:00
committed by GitHub
parent 1e7ea5cc13
commit 475707f104
9 changed files with 144 additions and 24 deletions

24
ai.js
View File

@@ -634,7 +634,29 @@ ${allowStickers ? buildStickerPrompt(settings) : ''}${allowMusicShare ? buildMus
拉黑后: 拉黑后:
- 用户发的消息你收不到,会显示"被拒收" - 用户发的消息你收不到,会显示"被拒收"
- 你发的消息用户也看不到(解除后才能看到)`; - 你发的消息用户也看不到(解除后才能看到)
【红包和转账功能 - 你可以主动发送!】
当你想给用户发红包时,使用格式:[红包:金额:祝福语]
当你想给用户转账时,使用格式:[转账:金额:说明]
示例:
- [红包:88:生日快乐!] ← 发88元红包
- [红包:6.66] ← 发6.66元红包(可省略祝福语)
- [转账:520:想你了] ← 转账520元
- [转账:100] ← 转账100元可省略说明
使用场景建议:
- 用户生日、节日时可以发红包
- 用户说想买东西时可以转账
- 想表达心意、哄用户开心时
- 用户问你要红包/转账时可以发
- 红包金额建议1-200元
- 转账金额不限
【绝对禁止】红包/转账标签必须单独一条!
× 错误:给你买奶茶[转账:20] ← 错误!
√ 正确:给你买奶茶|||[转账:20] ← 用 ||| 分开`;
// Meme 表情包提示词(如果启用) // Meme 表情包提示词(如果启用)
if (allowStickers && settings.memeStickersEnabled) { if (allowStickers && settings.memeStickersEnabled) {

13
chat.js
View File

@@ -116,11 +116,8 @@ export function appendBlockedNotice(contact) {
exclamationDiv.className = 'wechat-blocked-exclamation'; exclamationDiv.className = 'wechat-blocked-exclamation';
exclamationDiv.innerHTML = `<span class="wechat-blocked-exclamation-icon">!</span>`; exclamationDiv.innerHTML = `<span class="wechat-blocked-exclamation-icon">!</span>`;
// 插入到消息内容前面 // 插入到 .wechat-message 的末尾(因为 self 是 row-reverse末尾会显示在左边
const contentDiv = lastUserMsg.querySelector('.wechat-message-content'); lastUserMsg.appendChild(exclamationDiv);
if (contentDiv) {
contentDiv.insertBefore(exclamationDiv, contentDiv.firstChild);
}
// 添加点击事件 // 添加点击事件
exclamationDiv.addEventListener('click', () => { exclamationDiv.addEventListener('click', () => {
@@ -389,16 +386,18 @@ function appendBlockedAIMessage(content, contact, quote = null) {
const hasMeme = processedContent !== content; const hasMeme = processedContent !== content;
const bubbleContent = `<div class="wechat-message-bubble">${hasMeme ? processedContent : escapeHtml(content)}</div>`; const bubbleContent = `<div class="wechat-message-bubble">${hasMeme ? processedContent : escapeHtml(content)}</div>`;
// 红色感叹号 // 红色感叹号(作为独立元素,不在 content 内部)
const exclamationHtml = ` const exclamationHtml = `
<div class="wechat-blocked-ai-exclamation" title="对方在您拉黑期间发送"> <div class="wechat-blocked-ai-exclamation" title="对方在您拉黑期间发送">
<span class="wechat-blocked-exclamation-icon">!</span> <span class="wechat-blocked-exclamation-icon">!</span>
</div> </div>
`; `;
// 感叹号作为 .wechat-message 的直接子元素,在 content 后面flex row 会让它显示在右边)
messageDiv.innerHTML = ` messageDiv.innerHTML = `
<div class="wechat-message-avatar">${avatarContent}</div> <div class="wechat-message-avatar">${avatarContent}</div>
<div class="wechat-message-content">${bubbleContent}${exclamationHtml}</div> <div class="wechat-message-content">${bubbleContent}</div>
${exclamationHtml}
`; `;
messagesContainer.appendChild(messageDiv); messagesContainer.appendChild(messageDiv);

View File

@@ -113,10 +113,17 @@ export const MEME_PROMPT_TEMPLATE = `##【必须使用】表情包功能
【重要】你【必须】经常发送表情包每2-3条回复至少发一个表情包 【重要】你【必须】经常发送表情包每2-3条回复至少发一个表情包
使用规则: 使用规则:
- 表情包【必须】单独一条消息,用 ||| 分隔
- 格式:<meme>文件名</meme> - 格式:<meme>文件名</meme>
- 只能从下面列表选择,不能编造文件名 - 只能从下面列表选择,不能编造文件名
【绝对禁止 - 最重要的规则!】
<meme>标签前后【绝对不能】有任何其他文字!必须用 ||| 分隔!
× 错误:好想你<meme>xxx</meme> ← 绝对禁止!标签和文字混在一起!
× 错误:<meme>xxx</meme>哈哈 ← 绝对禁止!标签后面有文字!
× 错误:我很开心<meme>xxx</meme>你呢 ← 绝对禁止!标签夹在文字中间!
√ 正确:好想你|||<meme>xxx</meme> ← 用|||分开,标签独立!
√ 正确:<meme>xxx</meme>|||哈哈哈 ← 标签独立一条!
可用表情包列表: 可用表情包列表:
[ [
${MEME_STICKERS.join('\n')} ${MEME_STICKERS.join('\n')}
@@ -127,11 +134,7 @@ ${MEME_STICKERS.join('\n')}
哈哈哈笑死|||<meme>小熊跳舞122o4w.gif</meme>|||你太搞笑了 哈哈哈笑死|||<meme>小熊跳舞122o4w.gif</meme>|||你太搞笑了
<meme>喜欢你egvwqb.jpg</meme>|||我真的好喜欢你 <meme>喜欢你egvwqb.jpg</meme>|||我真的好喜欢你
【错误示例 - 绝对禁止】: 记住:表情包让聊天更生动,【必须】经常使用!但<meme>标签必须独立!`;
好想你<meme>xxx</meme> ← 错误!表情包没有用|||分开
<meme>不存在的表情.jpg</meme> ← 错误!编造了不存在的文件名
记住:表情包让聊天更生动,【必须】经常使用!`;
// 一起听功能提示词模板 // 一起听功能提示词模板
export const LISTEN_TOGETHER_PROMPT_TEMPLATE = `##【一起听歌场景】 export const LISTEN_TOGETHER_PROMPT_TEMPLATE = `##【一起听歌场景】

View File

@@ -351,7 +351,7 @@ export function renderToyHistory(contact) {
</div> </div>
<div class="wechat-toy-history-card-actions"> <div class="wechat-toy-history-card-actions">
<span class="wechat-toy-history-card-target">${targetText}</span> <span class="wechat-toy-history-card-target">${targetText}</span>
<button class="wechat-history-delete-btn" data-tab="toy" data-index="${originalIndex}" title="删除">🗑️</button> <button class="wechat-history-delete-btn" data-tab="toy" data-index="${originalIndex}" title="删除">×</button>
</div> </div>
</div> </div>
<div class="wechat-toy-history-card-meta"> <div class="wechat-toy-history-card-meta">

View File

@@ -167,10 +167,10 @@ function renderHistoryContent(contact, tabType) {
html += `<div class="wechat-history-card-header">`; html += `<div class="wechat-history-card-header">`;
html += `<span class="wechat-history-card-time">${escapeHtml(time)}</span>`; html += `<span class="wechat-history-card-time">${escapeHtml(time)}</span>`;
html += `<div class="wechat-history-card-actions">`; html += `<div class="wechat-history-card-actions">`;
html += `<button class="wechat-history-delete-btn" data-tab="${tabType}" data-index="${originalIndex}" title="删除">×</button>`;
if (duration) { if (duration) {
html += `<span class="wechat-history-card-duration">${escapeHtml(duration)}</span>`; html += `<span class="wechat-history-card-duration">${escapeHtml(duration)}</span>`;
} }
html += `<button class="wechat-history-delete-btn" data-tab="${tabType}" data-index="${originalIndex}" title="删除">🗑️</button>`;
html += `</div>`; html += `</div>`;
html += `</div>`; html += `</div>`;

View File

@@ -6888,6 +6888,7 @@
.wechat-moment-images.grid-2 { .wechat-moment-images.grid-2 {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
max-width: 280px;
} }
.wechat-moment-images.grid-3, .wechat-moment-images.grid-3,
@@ -10674,9 +10675,9 @@
justify-content: center; justify-content: center;
width: 20px; width: 20px;
height: 20px; height: 20px;
margin-right: 6px;
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
align-self: center;
} }
.wechat-blocked-exclamation-icon { .wechat-blocked-exclamation-icon {
@@ -10709,8 +10710,8 @@
justify-content: center; justify-content: center;
width: 20px; width: 20px;
height: 20px; height: 20px;
margin-left: 6px;
flex-shrink: 0; flex-shrink: 0;
align-self: center;
} }
/* 手机弹窗样式 */ /* 手机弹窗样式 */
@@ -10898,14 +10899,17 @@
background: none; background: none;
border: none; border: none;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 18px;
padding: 2px 4px; color: #999;
opacity: 0.5; padding: 0 4px;
transition: opacity 0.2s; line-height: 1;
opacity: 0.7;
transition: opacity 0.2s, color 0.2s;
} }
.wechat-history-delete-btn:hover { .wechat-history-delete-btn:hover {
opacity: 1; opacity: 1;
color: #ff4d4f;
} }
.wechat-toy-history-card-actions { .wechat-toy-history-card-actions {

View File

@@ -307,7 +307,7 @@ export function generateSummaryPrompt(allChats, cupNumber) {
// 如果有自定义模板,使用自定义模板 // 如果有自定义模板,使用自定义模板
let prompt; let prompt;
if (settings.customSummaryTemplate && settings.customSummaryTemplate.trim()) { if (settings.customSummaryTemplate && settings.customSummaryTemplate.trim()) {
prompt = settings.customSummaryTemplate.trim() + '\n\n【线上聊天记录】\n'; prompt = settings.customSummaryTemplate.trim() + '\n\n以下是线上聊天记录:\n\n【线上聊天记录】\n';
} else { } else {
// 使用默认模板(纯对话记录模式) // 使用默认模板(纯对话记录模式)
prompt = `你的任务是将这段【线上聊天记录】原样整理成JSON格式。 prompt = `你的任务是将这段【线上聊天记录】原样整理成JSON格式。
@@ -328,6 +328,8 @@ export function generateSummaryPrompt(allChats, cupNumber) {
【JSON示例】 【JSON示例】
{"keys":["公园","约会","周末"],"content":"{{user}}: 今天去哪玩?\\n{{char}}: 去公园吧\\n{{user}}: 好呀\\n{{char}}: 那我们下午2点见","comment":"${getCupName(cupNumber)}"} {"keys":["公园","约会","周末"],"content":"{{user}}: 今天去哪玩?\\n{{char}}: 去公园吧\\n{{user}}: 好呀\\n{{char}}: 那我们下午2点见","comment":"${getCupName(cupNumber)}"}
以下是线上聊天记录:
【线上聊天记录】 【线上聊天记录】
`; `;
} }

View File

@@ -655,6 +655,18 @@ function bindVideoCallEvents() {
} }
}); });
// 移动端键盘收起后重置滚动位置
document.getElementById('wechat-video-call-input')?.addEventListener('blur', () => {
setTimeout(() => {
window.scrollTo(0, 0);
const page = document.getElementById('wechat-video-call-page');
if (page) {
page.style.transform = '';
page.style.top = '0';
}
}, 100);
});
// AI来电界面事件 // AI来电界面事件
document.getElementById('wechat-video-call-incoming-accept')?.addEventListener('click', acceptIncomingCall); document.getElementById('wechat-video-call-incoming-accept')?.addEventListener('click', acceptIncomingCall);
document.getElementById('wechat-video-call-incoming-decline')?.addEventListener('click', declineIncomingCall); document.getElementById('wechat-video-call-incoming-decline')?.addEventListener('click', declineIncomingCall);
@@ -724,6 +736,21 @@ async function triggerAIVideoGreeting() {
if (/^\[(?:表情|照片|分享音乐|音乐)[:]/.test(reply)) continue; if (/^\[(?:表情|照片|分享音乐|音乐)[:]/.test(reply)) continue;
reply = reply.replace(/\[.*?\]/g, '').trim(); reply = reply.replace(/\[.*?\]/g, '').trim();
// 过滤掉泄露的提示词或内部指令
reply = reply.replace(/^-\d+\s*.*/gm, '').trim();
if (/我需要.*(回复|做出|扮演|以.*身份)/.test(reply)) {
const dashMatch = reply.match(/---+\s*(.+)$/);
if (dashMatch) {
reply = dashMatch[1].trim();
} else {
continue;
}
}
if (reply.includes('---')) {
const parts2 = reply.split(/---+/);
reply = parts2[parts2.length - 1].trim();
}
if (reply) { if (reply) {
// 分离场景描述和说话内容 // 分离场景描述和说话内容
// 提取所有括号内的场景描述 // 提取所有括号内的场景描述
@@ -798,6 +825,21 @@ async function triggerCameraToggleReaction() {
if (/^\[(?:表情|照片|分享音乐|音乐)[:]/.test(reply)) continue; if (/^\[(?:表情|照片|分享音乐|音乐)[:]/.test(reply)) continue;
reply = reply.replace(/\[.*?\]/g, '').trim(); reply = reply.replace(/\[.*?\]/g, '').trim();
// 过滤掉泄露的提示词或内部指令
reply = reply.replace(/^-\d+\s*.*/gm, '').trim();
if (/我需要.*(回复|做出|扮演|以.*身份)/.test(reply)) {
const dashMatch = reply.match(/---+\s*(.+)$/);
if (dashMatch) {
reply = dashMatch[1].trim();
} else {
continue;
}
}
if (reply.includes('---')) {
const parts2 = reply.split(/---+/);
reply = parts2[parts2.length - 1].trim();
}
if (reply) { if (reply) {
// 分离场景描述和说话内容 // 分离场景描述和说话内容
const sceneMatches = reply.match(/[^]+/g); const sceneMatches = reply.match(/[^]+/g);
@@ -906,6 +948,21 @@ ${lastMessages}
if (/^\[(?:表情|照片|分享音乐|音乐)[:]/.test(reply)) continue; if (/^\[(?:表情|照片|分享音乐|音乐)[:]/.test(reply)) continue;
reply = reply.replace(/\[.*?\]/g, '').trim(); reply = reply.replace(/\[.*?\]/g, '').trim();
// 过滤掉泄露的提示词或内部指令
reply = reply.replace(/^-\d+\s*.*/gm, '').trim();
if (/我需要.*(回复|做出|扮演|以.*身份)/.test(reply)) {
const dashMatch = reply.match(/---+\s*(.+)$/);
if (dashMatch) {
reply = dashMatch[1].trim();
} else {
continue;
}
}
if (reply.includes('---')) {
const parts2 = reply.split(/---+/);
reply = parts2[parts2.length - 1].trim();
}
if (reply) { if (reply) {
// 保存到聊天历史 // 保存到聊天历史
const now = new Date(); const now = new Date();

View File

@@ -632,6 +632,20 @@ function bindCallEvents() {
sendCallMessage(); sendCallMessage();
} }
}); });
// 移动端键盘收起后重置滚动位置
document.getElementById('wechat-voice-call-input')?.addEventListener('blur', () => {
// 延迟执行以等待键盘完全收起
setTimeout(() => {
window.scrollTo(0, 0);
// 重置可能被移动的元素
const page = document.getElementById('wechat-voice-call-page');
if (page) {
page.style.transform = '';
page.style.top = '0';
}
}, 100);
});
} }
// 接听来电 // 接听来电
@@ -803,7 +817,7 @@ ${lastMessages}
hideTypingIndicator(); hideTypingIndicator();
} }
// 按 ||| 分割,并将特殊标签与文本分离,避免文字+表情包混在同一条 // 按 ||| 分割,并将特殊标签与文本分离,避免"文字+表情包"混在同一条
const parts = splitAIMessages(aiResponse); const parts = splitAIMessages(aiResponse);
for (const part of parts) { for (const part of parts) {
@@ -815,6 +829,25 @@ ${lastMessages}
// 移除可能的特殊标记 // 移除可能的特殊标记
reply = reply.replace(/\[.*?\]/g, '').trim(); reply = reply.replace(/\[.*?\]/g, '').trim();
// 过滤掉泄露的提示词或内部指令
// 1. 以负数开头的行(如 -5000
reply = reply.replace(/^-\d+\s*.*/gm, '').trim();
// 2. 包含"我需要"+"回复/做出/扮演"等指令性内容
if (/我需要.*(回复|做出|扮演|以.*身份)/.test(reply)) {
// 尝试提取 --- 后面的实际内容
const dashMatch = reply.match(/---+\s*(.+)$/);
if (dashMatch) {
reply = dashMatch[1].trim();
} else {
continue; // 跳过这条消息
}
}
// 3. 移除 --- 分隔符前面的内容(如果有的话)
if (reply.includes('---')) {
const parts2 = reply.split(/---+/);
reply = parts2[parts2.length - 1].trim();
}
if (reply) { if (reply) {
// 保存到聊天历史 // 保存到聊天历史
const now = new Date(); const now = new Date();