// ==UserScript==
// @name Telegram AI 聊天摘要笔记助手 (v2.2.0-fix16b - 高 Tokens 完整)
// @namespace http://tampermonkey.net/
// @version 2.2.0-aisync-fix16b-full // 设置 max_tokens=30000 (完整代码)
// @description AI结合现有笔记和新消息,仅提取指定类别核心信息,使用消息精确时间戳,替换旧摘要块。设置高 max_tokens。
// @author 萧遥
// @match https://web.telegram.org/k/*
// @match https://web.telegram.org/a/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_info
// @grant GM_xmlhttpRequest
// @connect api.ohmygpt.com
// ==/UserScript==
(function() {
'use strict';
// --- 配置 ---
const NOTES_AREA_ID = 'userscript-tg-chat-notes-ai-summary';
const STORAGE_KEY_PREFIX = 'tg_notes_';
const DEBOUNCE_DELAY = 750;
const OHMYGPT_API_KEY = "sk-RK1MU6Cg6a48fBecBBADT3BlbKFJ4C209a954d3b4428b54b"; // 使用你原来的 Key
const OHMYGPT_API_ENDPOINT = "https://api.ohmygpt.com/v1/chat/completions";
// ★★★ 仍然使用你指定的模型,如果 API 报错请更换 ★★★
const OHMYGPT_MODEL = "gemini-2.5-flash-preview-04-17-thinking-disabled";
const SUMMARY_HEADER_MARKER = "--- AI 摘要关键点"; // 用于查找和替换旧摘要的标记
// --- 图标 ---
const NOTEBOOK_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-journal-text" viewBox="0 0 16 16" style="vertical-align: -1px; margin-right: 4px;"><path d="M5 10.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/><path d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"/><path d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"/></svg>`;
const SUMMARY_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-body-text" viewBox="0 0 16 16" style="vertical-align: -2px; margin-right: 3px;"><path fill-rule="evenodd" d="M0 .5A.5.5 0 0 1 .5 0h4a.5.5 0 0 1 0 1h-4A.5.5 0 0 1 0 .5m0 2A.5.5 0 0 1 .5 2h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 0 2.5m0 2A.5.5 0 0 1 .5 4h11a.5.5 0 0 1 0 1h-11A.5.5 0 0 1 0 4.5m0 2A.5.5 0 0 1 .5 6h11a.5.5 0 0 1 0 1h-11A.5.5 0 0 1 0 6.5m0 2A.5.5 0 0 1 .5 8h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 0 8.5m0 2A.5.5 0 0 1 .5 10h4a.5.5 0 0 1 0 1h-4A.5.5 0 0 1 0 10.5m0 2A.5.5 0 0 1 .5 12h11a.5.5 0 0 1 0 1h-11A.5.5 0 0 1 0 12.5m0 2A.5.5 0 0 1 .5 14h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 0 14.5"/></svg>`;
// --- 样式 ---
GM_addStyle(`
.chat-view, #column-center, .MiddleColumn { position: relative !important; }
#${NOTES_AREA_ID} {
position: absolute; top: 50px; right: 20px;
width: 250px; max-width: 40%;
z-index: 105; display: none; flex-direction: column;
padding: 8px 10px; border: 1px solid var(--border-color, #ccc);
border-radius: 8px; background-color: var(--background-color-secondary, rgba(248, 248, 248, 0.94));
box-shadow: 0 1px 4px rgba(0,0,0,0.18); opacity: 0.85;
transition: opacity 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
overflow: hidden; max-height: calc(100vh - 100px);
}
#${NOTES_AREA_ID}.visible { display: flex; }
#${NOTES_AREA_ID}:hover, #${NOTES_AREA_ID}:focus-within { opacity: 1; box-shadow: 0 2px 6px rgba(0,0,0,0.25); }
#${NOTES_AREA_ID} .notes-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; padding-bottom: 3px; border-bottom: 1px dashed var(--border-color, #ccc); flex-shrink: 0; }
#${NOTES_AREA_ID} .notes-label { font-size: 0.75em; font-weight: bold; color: var(--secondary-text-color, #444); display: flex; align-items: center; user-select: none; cursor: default; }
#${NOTES_AREA_ID} .ai-summary-button { font-size: 0.75em; padding: 2px 5px; cursor: pointer; border: 1px solid var(--accent-color-secondary, #aaa); border-radius: 5px; background-color: var(--button-secondary-background, #eee); color: var(--secondary-text-color, #333); transition: background-color 0.2s; display: inline-flex; align-items: center; line-height: 1.1; }
#${NOTES_AREA_ID} .ai-summary-button:hover { background-color: var(--button-secondary-background-hover, #ddd); }
#${NOTES_AREA_ID} .ai-summary-button:disabled { opacity: 0.6; cursor: not-allowed; }
#${NOTES_AREA_ID} .ai-summary-button.loading { cursor: wait; background-color: #f0ad4e; color: white; border-color: #eea236; animation: pulse 1.5s infinite ease-in-out; }
#${NOTES_AREA_ID} .ai-summary-button.error { background-color: #d9534f; color: white; border-color: #d43f3a; }
@keyframes pulse { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
#${NOTES_AREA_ID} textarea { width: 100%; min-height: 60px; height: auto; resize: vertical; border: none; outline: none; background-color: transparent; color: var(--text-color, #000); font-size: 0.9em; line-height: 1.3; font-family: inherit; flex-grow: 1; margin-top: 3px; scrollbar-width: thin; scrollbar-color: var(--accent-color, #888) transparent; }
#${NOTES_AREA_ID} textarea::-webkit-scrollbar { width: 6px; }
#${NOTES_AREA_ID} textarea::-webkit-scrollbar-track { background: transparent; }
#${NOTES_AREA_ID} textarea::-webkit-scrollbar-thumb { background-color: var(--accent-color, #888); border-radius: 3px; }
`);
let saveTimeout = null;
let currentChatId = null;
let isAISummarizing = false;
// --- 辅助函数 ---
function getChatIdFromHash() { const hash = window.location.hash; if (hash && hash.length > 1) { const match = hash.match(/^#\/(?:k|a)\/([\w-]+)/); if (match) return match[1]; const mainId = hash.substring(1).split('?')[0]; return mainId; } return null; }
function getStorageKey(chatId) { if (!chatId) return null; return `${STORAGE_KEY_PREFIX}${chatId}`; }
function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; }
function formatUnixTimestamp(unixTimestamp) { if (!unixTimestamp) return null; try { const date = new Date(parseInt(unixTimestamp, 10) * 1000); const hours = date.getHours().toString().padStart(2, '0'); const minutes = date.getMinutes().toString().padStart(2, '0'); return `${hours}:${minutes}`; } catch (e) { console.error("Error formatting timestamp:", e); return null; } }
// --- 核心逻辑 ---
function saveNotes(notesValue) { if (currentChatId) { const storageKey = getStorageKey(currentChatId); if (storageKey) { GM_setValue(storageKey, notesValue); } } }
const debouncedSaveNotes = debounce(saveNotes, DEBOUNCE_DELAY);
function findChatViewContainer() { let container = document.querySelector('.chat-view'); if (container) return container; container = document.querySelector('#column-center'); if (container) return container; container = document.querySelector('.MessageList'); if (container && container.parentElement) { return container.parentElement; } container = document.querySelector('.MiddleColumn'); if (container) { console.log("Using .MiddleColumn as container fallback"); return container; } console.warn("[TG Notes Sum v2.2.0-aisync-fix16b-full] Could not find suitable chat view container element using v2.2.0 logic + fallback."); return null; }
// --- 文本提取逻辑 ---
function extractTextFromBubble(elementToExtractFrom) {
// console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full Extract Debug] Trying to extract text from element:", elementToExtractFrom);
if (!elementToExtractFrom) return '';
let text = '';
const translationWrapper = elementToExtractFrom.querySelector('font.immersive-translate-target-translation-block-wrapper');
if (translationWrapper) {
const clone = translationWrapper.cloneNode(true);
clone.querySelectorAll('.gpt-controls-container, .gpt-controls-container-wa, .persona-selector, .gpt-api-button, .userscript-tg-chat-notes-ai, .ai-summary-button, button, svg, img, span.time, .message-sign').forEach(el => el.remove());
text = clone.textContent?.trim() || '';
if (text) { return text; }
}
const originalTextSpan = elementToExtractFrom.querySelector('span.translatable-message');
if (originalTextSpan) {
const clone = originalTextSpan.cloneNode(true);
clone.querySelectorAll('font.notranslate, span.time, span.message-reactions, div.message-reactions, span.hidden-copy-text, button, svg, img, .message-sign').forEach(el => el.remove());
text = clone.textContent?.trim() || '';
if (text) { return text; }
}
const clone = elementToExtractFrom.cloneNode(true);
clone.querySelectorAll('span.time, span.message-reactions, div.message-reactions, button, svg, img, .bubble-tail, .bubble-actions, .message-sign, .message-views, .message-forwarded, .reply-markup, .reactions-container, font, .hidden-copy-text').forEach(el => el.remove());
text = clone.textContent?.trim().replace(/\s+/g, ' ') || '';
if (text) { return text; }
else { console.warn("[TG Notes Sum v2.2.0-aisync-fix16b-full Extract Debug] Failed: All extraction methods failed for element:", elementToExtractFrom.innerHTML.substring(0, 200) + "..."); }
return '';
}
// --- 提取时间戳逻辑 ---
function extractTimestampFromMessageItem(item) {
if (!item) return null;
const dataTimestamp = item.dataset.timestamp;
if (dataTimestamp) {
const formattedTime = formatUnixTimestamp(dataTimestamp);
if(formattedTime) return formattedTime;
}
const timeElement = item.querySelector('.bubble-content .message-sign .time, .bubble-content > .time');
if (timeElement) { return timeElement.textContent?.trim() || null; }
const timeElementFallback = item.querySelector('.time');
if (timeElementFallback) { return timeElementFallback.textContent?.trim() || null; }
return null;
}
// --- 获取最新传入消息文本 ---
function getLastIncomingMessagesText() {
console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 开始执行 getLastIncomingMessagesText...");
const messageItemSelector = 'div[data-mid]'; const incomingClass = 'is-in'; const contentSelector = '.bubble-content';
const chatAreaSelector = '#column-center, .MiddleColumn'; let searchContext = document; const chatArea = document.querySelector(chatAreaSelector);
if (chatArea) { searchContext = chatArea; } else { console.warn(`[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 未找到聊天区域,在整个文档查找 "${messageItemSelector}"。`); }
const allMessageItems = searchContext.querySelectorAll(messageItemSelector); console.log(`[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 找到 ${allMessageItems.length} 个消息项。`);
if (allMessageItems.length === 0) { console.error(`[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 未找到任何消息项 ("${messageItemSelector}")。`); return null; }
const messages = []; let lastMessageTimestamp = null; let incomingCount = 0; let checkedCount = 0; console.log(`[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 从 ${allMessageItems.length} 项中倒序查找所有对方消息...`);
for (let i = allMessageItems.length - 1; i >= 0; i--) {
checkedCount++; const item = allMessageItems[i]; const isIncoming = item.classList.contains(incomingClass);
if (isIncoming) {
const contentElement = item.querySelector(contentSelector);
if (contentElement) {
const text = extractTextFromBubble(contentElement);
if (text) {
messages.unshift(text);
if (incomingCount === 0) { lastMessageTimestamp = extractTimestampFromMessageItem(item); }
incomingCount++;
} else { console.warn(`[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 警告: Item ${i} 被识别为接收,找到了 Content (${contentSelector}),但未能提取文本。`); }
} else { console.warn(`[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 警告: Item ${i} 被识别为接收消息,但未找到其内部的 "${contentSelector}" 元素。`); }
}
}
console.log(`[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 循环结束。总共检查了 ${checkedCount} 条消息项。`);
if (messages.length === 0) { console.error(`[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 错误: 遍历了 ${checkedCount} 条消息,但未能提取到文本。`); return null; }
console.log(`[TG Notes Sum v2.2.0-aisync-fix16b-full Debug] 成功提取 ${messages.length} 条接收消息。最后一条消息时间戳: ${lastMessageTimestamp}`);
return { text: messages.join('\n\n'), lastTimestamp: lastMessageTimestamp };
}
// --- 构造 AI Prompt ---
function createSummaryPrompt(existingNotes, newMessagesText) {
let previousSummaryPoints = "";
const lastSummaryStartIndex = existingNotes.lastIndexOf(SUMMARY_HEADER_MARKER);
if (lastSummaryStartIndex !== -1) {
const previousBlock = existingNotes.substring(lastSummaryStartIndex);
const lines = previousBlock.split('\n');
const points = lines.filter(line => line.trim().startsWith('*') || line.trim().startsWith('-')).map(line => line.trim()).join('\n');
if (points) {
previousSummaryPoints = points;
console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full Prompt Debug] 找到了之前的摘要要点用于上下文:\n", previousSummaryPoints);
} else {
console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full Prompt Debug] 找到旧摘要标记,但未能提取有效要点。");
}
} else {
console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full Prompt Debug] 未找到之前的摘要标记,将首次生成摘要。");
}
let prompt = `你是一个极其精准和简洁的信息提取与整合助手。请回顾【历史关键信息点】(如果为空则忽略),并分析【最新客户消息】。
你的任务是生成一份【更新后的完整关键信息列表】,**仅包含且必须包含**以下类别的信息点,如果信息存在的话:
* **个人信息**: **核心特征**,如客户年龄(若提及)、主要职业身份、常驻城市/国家。**忽略**临时状态如“刚到家/机场/正在吃饭”。
* **家人信息**: 提及的配偶、子女、父母、兄弟姐妹等及其**关键情况**(例如:儿子在XX大学读书,女儿计划一起晚餐)。仅记录关系和核心信息。
* **财务信息**: 提及的**具体**投资领域(如:股票、贵金属)、未投资领域(如:比特币)、市场看法、收入来源、重要财务决策或问题。
* **个人爱好**: 明确表示的**长期兴趣或习惯**(如:喜欢高尔夫、阅读、烹饪特定菜肴、有晨间冥想习惯)。**忽略**一次性的活动或普通的饮食(如“吃了三明治”)。
* **宗教信仰**: 明确提及的宗教归属、实践或相关价值观。
* **痛点**: 明确表达的**持续性或重要的**不满、担忧、困难或挑战(例如:工作繁忙影响规划、朋友重病需照顾)。**忽略**短暂的不便。
* **刚需点**: 明确提出的**强烈愿望、长期目标、梦想或必须解决的问题**(例如:退休后的生活目标、重要的业务需求)。**忽略**日常计划或普通意愿(如“需要休息”、“要开会”)。
* **感情史**: 提及的恋爱、婚姻状况。
* **历史/背景**: 提及的**带有时间标记(如年份)的重大事件、重要的职业履历或人生转折点**。**忽略**近期的、不具里程碑意义的行程或活动(如“最近在旅行”)。
**请严格遵守以下规则**:
1. **保留历史**: **必须完整保留**【历史关键信息点】中的所有信息。**禁止删除**任何旧的信息点,除非新消息明确更新或否定了它。
2. **精确提取新增**: 从【最新客户消息】中**只提取**严格符合上述**类别定义和筛选标准**的、并且在【历史关键信息点】中**没有记录过的新的核心事实**,或者对旧信息的**明确更新**。
3. **合并输出**: 生成一份**包含所有历史信息点和所有新增/更新信息点**的【更新后的完整关键信息列表】。
4. **极度简洁与合并**: 每个信息点必须是**核心事实短语**。**同一类别**的信息点如果内容相关,应**尽可能合并到一行内**,用逗号或分号分隔,**避免每个点单独占一行**。
5. **格式**: 使用清晰的要点列表('* '),按类别标题组织(例如 "**个人爱好**:")。类别标题使用 **粗体**。
6. **无新信息处理**: 如果【最新客户消息】中**没有**任何符合类别定义的**新信息或更新**,请在【更新后的完整关键信息列表】的末尾明确添加一行:“* (近期无指定类别新信息)*”。但仍然需要输出包含所有历史信息点的列表。
---
【历史关键信息点】:
${previousSummaryPoints || "(无)"}
---
【最新客户消息】:
${newMessagesText}
---
【更新后的完整关键信息列表】 (请严格遵守规则,保留所有历史信息,仅增加新的核心事实短语,合并同一类别信息点):`;
return prompt;
}
// --- 调用 OhMyGPT API ---
function callOhMyGptForSummary(prompt, callback) {
if (!OHMYGPT_API_KEY || !OHMYGPT_API_KEY.startsWith("sk-")) { console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] OhMyGPT API 密钥丢失或无效。"); callback(null, "API 密钥丢失或无效"); return; }
if (!prompt) { console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] API 调用收到空 Prompt。"); callback(null, "没有可摘要的消息"); return; }
console.log(`[TG Notes Sum v2.2.0-aisync-fix16b-full] 正在调用 OhMyGPT API (模型: ${OHMYGPT_MODEL}, max_tokens: 30000)...`);
GM_xmlhttpRequest({
method: "POST", url: OHMYGPT_API_ENDPOINT, headers: { "Content-Type": "application/json", "Authorization": `Bearer ${OHMYGPT_API_KEY}` },
data: JSON.stringify({
model: OHMYGPT_MODEL,
messages: [{"role": "user", "content": prompt }],
temperature: 0.1,
max_tokens: 30000 // 设置为 30000
}),
timeout: 120000, // 增加超时时间到 120 秒
onload: function(response) {
try {
if (response.status >= 200 && response.status < 300) { const data = JSON.parse(response.responseText); const reply = data.choices?.[0]?.message?.content?.trim(); if (reply) callback(reply); else { console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] API 响应错误:", data); callback(null, data.error?.message || "无效响应结构"); } }
else { console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] API 请求失败,状态码:", response.status, response.responseText); let errorMsg = `HTTP 错误 ${response.status}`; try { const errorData = JSON.parse(response.responseText); errorMsg = errorData.error?.message || errorMsg; } catch (e) {} callback(null, errorMsg); }
} catch (e) { console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] 解析 API 响应出错:", e); callback(null, "解析 API 响应失败"); }
},
onerror: function(response) { console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] API 请求 onerror:", response); callback(null, `网络错误: ${response.statusText || '未知'}`); },
ontimeout: function() { console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] API 请求超时。"); callback(null, "API 请求超时 (120s)"); }
});
}
// --- AI 摘要按钮点击处理 ---
function handleAiSummaryClick(event) {
const button = event.currentTarget; const notesContainer = document.getElementById(NOTES_AREA_ID); const notesTextarea = notesContainer?.querySelector('textarea');
if (isAISummarizing || !notesTextarea || button.disabled) return;
console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full] AI 摘要按钮点击。"); const messageData = getLastIncomingMessagesText();
if (!messageData || !messageData.text) { alert("未能找到足够的可供分析的对方消息。\n请确保聊天记录中有对方的消息 (可向上滚动),并检查控制台日志。"); console.warn("[TG Notes Sum v2.2.0-aisync-fix16b-full] 未找到用于摘要的对方消息文本。"); return; }
const existingNotes = notesTextarea.value; console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full] 获取到现有笔记内容用于上下文。");
isAISummarizing = true; const originalButtonHTML = button.innerHTML; button.innerHTML = `${SUMMARY_ICON_SVG} 更新中...`; button.classList.add('loading'); button.classList.remove('error'); button.disabled = true; button.title = "正在结合上下文更新摘要...";
const prompt = createSummaryPrompt(existingNotes, messageData.text); callOhMyGptForSummary(prompt, (result, error) => {
isAISummarizing = false; const currentButton = notesContainer?.querySelector('.ai-summary-button'); if (!currentButton) return;
currentButton.classList.remove('loading'); currentButton.disabled = false; currentButton.innerHTML = originalButtonHTML;
if (result) {
console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full] 收到更新后的摘要结果:", result);
const generationTimestamp = new Date().toLocaleString('zh-CN', { hour12: false });
const timeInfo = messageData.lastTimestamp ? `截至 ${messageData.lastTimestamp} 的消息` : '近期消息';
const newSummaryBlock = `${SUMMARY_HEADER_MARKER} (更新于 ${generationTimestamp}, 基于${timeInfo}) ---\n${result.trim()}`;
let newNotesContent = "";
const lastSummaryIndex = existingNotes.lastIndexOf(SUMMARY_HEADER_MARKER);
if (lastSummaryIndex !== -1) {
newNotesContent = existingNotes.substring(0, lastSummaryIndex).trimEnd();
console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full] 找到旧摘要标记,将替换其后的内容。");
} else {
newNotesContent = existingNotes.trimEnd();
console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full] 未找到旧摘要标记,将在末尾追加新摘要。");
}
const separator = newNotesContent === '' ? '' : '\n\n';
notesTextarea.value = newNotesContent + separator + newSummaryBlock;
debouncedSaveNotes(notesTextarea.value);
notesTextarea.scrollTop = notesTextarea.scrollHeight;
currentButton.title = `摘要已更新,包含指定类别信息`;
} else {
console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] 摘要失败:", error);
currentButton.classList.add('error'); currentButton.title = `AI 摘要错误: ${error || '未知错误'}. 点击重试。`;
setTimeout(() => { currentButton.classList.remove('error'); currentButton.title = `结合现有笔记和新消息,更新摘要`; }, 5000);
}
});
}
// --- UI 注入与更新 ---
function injectOrUpdateNotesUI() {
const newChatId = getChatIdFromHash(); let notesContainer = document.getElementById(NOTES_AREA_ID);
if (!newChatId) { if (notesContainer) notesContainer.classList.remove('visible'); currentChatId = null; return; }
if (newChatId === currentChatId && notesContainer) { if (!notesContainer.classList.contains('visible')) notesContainer.classList.add('visible'); return; }
currentChatId = newChatId; const chatViewContainer = findChatViewContainer();
if (!chatViewContainer) { if (notesContainer) notesContainer.classList.remove('visible'); console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] Chat View Container not found by v2.2.0 logic."); return; }
let notesTextarea; let aiSummaryButton;
if (!notesContainer) {
console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full] Creating notes UI elements...");
notesContainer = document.createElement('div'); notesContainer.id = NOTES_AREA_ID;
const headerDiv = document.createElement('div'); headerDiv.className = 'notes-header';
const labelDiv = document.createElement('div'); labelDiv.className = 'notes-label'; labelDiv.innerHTML = `${NOTEBOOK_ICON_SVG} 聊天笔记:`; headerDiv.appendChild(labelDiv);
aiSummaryButton = document.createElement('button'); aiSummaryButton.type = 'button'; aiSummaryButton.className = 'ai-summary-button'; aiSummaryButton.innerHTML = `${SUMMARY_ICON_SVG} 更新摘要`; aiSummaryButton.title = `结合现有笔记和新消息,更新指定类别摘要`;
aiSummaryButton.addEventListener('click', handleAiSummaryClick); headerDiv.appendChild(aiSummaryButton);
notesContainer.appendChild(headerDiv);
notesTextarea = document.createElement('textarea'); notesTextarea.placeholder = '输入笔记... 点击 "更新摘要" 更新指定信息。'; notesContainer.appendChild(notesTextarea);
notesTextarea.addEventListener('input', (event) => debouncedSaveNotes(event.target.value));
notesTextarea.addEventListener('keydown', (event) => event.stopPropagation());
chatViewContainer.appendChild(notesContainer); console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full] Notes UI appended.");
} else {
if (notesContainer.parentElement !== chatViewContainer) { console.log("[TG Notes Sum v2.2.0-aisync-fix16b-full] Moving notes UI to new container."); chatViewContainer.appendChild(notesContainer); }
notesTextarea = notesContainer.querySelector('textarea'); aiSummaryButton = notesContainer.querySelector('.ai-summary-button');
aiSummaryButton.title = `结合现有笔记和新消息,更新指定类别摘要`; aiSummaryButton.innerHTML = `${SUMMARY_ICON_SVG} 更新摘要`;
}
notesContainer.classList.add('visible');
if (notesTextarea) {
const storageKey = getStorageKey(currentChatId); if (storageKey) { const savedNotes = GM_getValue(storageKey, ''); if (notesTextarea.value !== savedNotes) notesTextarea.value = savedNotes; } else { notesTextarea.value = ''; }
if(aiSummaryButton){ isAISummarizing = false; aiSummaryButton.disabled = false; aiSummaryButton.classList.remove('loading', 'error'); aiSummaryButton.innerHTML = `${SUMMARY_ICON_SVG} 更新摘要`; aiSummaryButton.title = `结合现有笔记和新消息,更新指定类别摘要`; }
} else { console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] Textarea not found after UI update/create."); }
}
// --- 初始化 ---
function initializeScript() {
try { injectOrUpdateNotesUI(); } catch (e) { console.error("[TG Notes Sum v2.2.0-aisync-fix16b-full] Initial UI injection error:", e); }
window.addEventListener('hashchange', injectOrUpdateNotesUI);
const observeTargetNode = document.body;
if (observeTargetNode) {
const observerDebouncedCheck = debounce(injectOrUpdateNotesUI, 250);
const observer = new MutationObserver((mutations) => { if (mutations.some(m => m.addedNodes.length > 0 || m.removedNodes.length > 0)) { observerDebouncedCheck(); } });
observer.observe(observeTargetNode, { childList: true, subtree: true });
}
console.log(`[TG Notes Summarizer] Script v${GM_info.script.version} (aisync-fix16b-full) initialized using v2.2.0 UI logic.`);
}
// --- 启动 ---
const initialLoadDelay = 3000;
setTimeout(initializeScript, initialLoadDelay);
})();