diff --git a/www/app.js b/www/app.js index c304c7f..4ec0663 100644 --- a/www/app.js +++ b/www/app.js @@ -4790,6 +4790,66 @@ function renderMessages() { // ==================== TTS 队列播放 ==================== +// 清理 TTS 文本(过滤Markdown特殊字符和表情) +function cleanTTSText(text) { + if (!text) return ''; + + let cleaned = text; + + // 移除代码块(```code```) + cleaned = cleaned.replace(/```[\s\S]*?```/g, ''); + + // 移除行内代码(`code`) + cleaned = cleaned.replace(/`[^`]+`/g, ''); + + // 移除标题符号(#、##、###等) + cleaned = cleaned.replace(/^#{1,6}\s*/gm, ''); + + // 移除列表符号(-、*、+) + cleaned = cleaned.replace(/^[\-\*\+]\s+/gm, ''); + + // 移除数字列表(1.、2.等) + cleaned = cleaned.replace(/^\d+\.\s+/gm, ''); + + // 处理链接 [text](url) -> 只保留text + cleaned = cleaned.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1'); + + // 移除粗体/斜体符号(**text**、*text*、__text__、_text_) + cleaned = cleaned.replace(/\*\*([^*]+)\*\*/g, '$1'); + cleaned = cleaned.replace(/\*([^*]+)\*/g, '$1'); + cleaned = cleaned.replace(/__([^_]+)__/g, '$1'); + cleaned = cleaned.replace(/_([^_]+)_/g, '$1'); + + // 移除引用符号(>) + cleaned = cleaned.replace(/^>\s*/gm, ''); + + // 移除分割线(---、***) + cleaned = cleaned.replace(/^[\-\*]{3,}$/gm, ''); + + // 移除表情符号(常见emoji范围) + cleaned = cleaned.replace(/[\u{1F600}-\u{1F64F}]/gu, ''); // 表情 + cleaned = cleaned.replace(/[\u{1F300}-\u{1F5FF}]/gu, ''); // 符号和图形 + cleaned = cleaned.replace(/[\u{1F680}-\u{1F6FF}]/gu, ''); // 交通和地图 + cleaned = cleaned.replace(/[\u{1F700}-\u{1F77F}]/gu, ''); // 占星术 + cleaned = cleaned.replace(/[\u{1F780}-\u{1F7FF}]/gu, ''); // 几何图形 + cleaned = cleaned.replace(/[\u{1F800}-\u{1F8FF}]/gu, ''); // 补充箭头 + cleaned = cleaned.replace(/[\u{1F900}-\u{1F9FF}]/gu, ''); // 补充符号 + cleaned = cleaned.replace(/[\u{1FA00}-\u{1FA6F}]/gu, ''); // 游戏符号 + cleaned = cleaned.replace(/[\u{1FA70}-\u{1FAFF}]/gu, ''); // 补充符号B + cleaned = cleaned.replace(/[\u{2600}-\u{26FF}]/gu, ''); // 杂项符号 + cleaned = cleaned.replace(/[\u{2700}-\u{27BF}]/gu, ''); // 装饰符号 + cleaned = cleaned.replace(/[\u{FE00}-\u{FE0F}]/gu, ''); // 变体选择符 + cleaned = cleaned.replace(/[\u{1F1E0}-\u{1F1FF}]/gu, ''); // 旗帜 + + // 移除HTML实体 + cleaned = cleaned.replace(/&[a-zA-Z]+;/g, ''); + + // 清理多余空白 + cleaned = cleaned.replace(/\s+/g, ' ').trim(); + + return cleaned; +} + // 添加文本到 TTS 队列 async function addToTTSQueue(text) { if (!text || !enableTTS) return; @@ -4812,7 +4872,10 @@ async function playTTSQueue() { isTTSPlaying = true; while (ttsQueue.length > 0 && enableTTS) { - const text = ttsQueue.shift(); + const rawText = ttsQueue.shift(); + const text = cleanTTSText(rawText); // 清理文本 + + if (!text) continue; // 跳过空文本 try { const response = await fetch('/api/tts', { @@ -4866,7 +4929,13 @@ async function playTTS(index) { const msg = currentConversation.messages[index]; if (!msg || msg.role !== 'assistant') return; - const text = msg.content; + const rawText = msg.content; + if (!rawText) { + showToast('没有可播放的内容'); + return; + } + + const text = cleanTTSText(rawText); // 清理文本 if (!text) { showToast('没有可播放的内容'); return;