AI内容捕获模块

在页面上添加一个可拖动、可折叠、带标签切换和新控件的紫色主题交互式面板。

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @match *
// @name         AI内容捕获模块
// @version      3.0
// @license MIT
// @namespace http://tampermonkey.net/  // Optional: Change to your namespace (e.g., your website or GitHub)
// @description 在页面上添加一个可拖动、可折叠、带标签切换和新控件的紫色主题交互式面板。
// ==/UserScript==

var ContentCaptureModule = (function() {
    'use strict';

    const SCRIPT_NAME = 'AI内容捕获模块 v1.2';
    let DEBUG = false; // 可由 init 配置

    // --- 默认配置 ---
    const DEFAULT_CONFIG = {
        debug: false,
        chatContainerSelector: '.mx-auto.pt-24.w-full.max-w-\\[720px\\].px-4.relative',
        aiMessageSelector: '#ai-chat-answer',
        aiReplyBodySelector: '.markdown-body',
        integralTextPattern: /消耗\s*\d+\s*积分/,
        sectionsToExtract: [
            { key: 'thinking', selector: 'div.thinking-area', description: '思考区', extractMethod: 'innerHTML' },
            { key: 'textBody', selector: 'div.text-body', description: '回复主体', extractMethod: 'innerHTML' },
            { key: 'statusBar', selector: 'div.status-bar', description: '状态栏', extractMethod: 'innerHTML' },
            { key: 'memoryArea', selector: 'div.memory-area', description: '记忆区', extractMethod: 'innerHTML' },
            { key: 'optionsArea', selector: 'div.options-area ol', description: '选项区列表', extractMethod: 'innerHTML' } // 直接取 ol 的内容
        ],
         optionItemSelector: 'div.options-area ol li', // 用于提取 optionTexts
         optionTextRegex: /\[(.*?)\]/, // 用于提取选项文本
        maxObserverRetries: 5,
        observerRetryDelayBase: 2000
    };

    // --- 模块状态变量 ---
    let mainObserver = null;
    let currentContentObserver = null;
    let watchingAiNode = null;
    let latestFullyLoadedAiNode = null;
    let observerRetryCount = 0;
    let currentConfig = { ...DEFAULT_CONFIG };
    let updateCallback = null; // 由 init 传入
    let isRunning = false;

    // --- 工具函数 ---
    const log = (...args) => currentConfig.debug && console.log(`[${SCRIPT_NAME}]`, ...args);
    const logInfo = (...args) => console.log(`[${SCRIPT_NAME}]`, ...args); // 关键信息
    const logError = (...args) => console.error(`[${SCRIPT_NAME}] [ERROR]`, ...args);

    // --- 核心功能:提取内容 (修改自 extractAndLogContent) ---
    function extractContent(aiMessageNode) {
        if (!aiMessageNode) { /* ... 错误处理 ... */ return null; }
        log(`Starting content extraction from node: ${aiMessageNode.id}`);
        const contentBody = aiMessageNode.querySelector(currentConfig.aiReplyBodySelector);
        if (!contentBody) { /* ... 错误处理 ... */ return null; }

        const extractedData = {
            timestamp: new Date().toISOString(),
            messageId: aiMessageNode.id || 'N/A',
            // rawHtmlBody: contentBody.innerHTML, // 可以移除或保留用于调试
            sections: {}
        };

        currentConfig.sectionsToExtract.forEach(config => {
            const sectionElement = contentBody.querySelector(config.selector);
            if (sectionElement) {
                if (config.extractMethod === 'innerHTML') {
                    extractedData.sections[config.key] = sectionElement.innerHTML.trim();
                } else { // 默认或指定 innerText
                    extractedData.sections[config.key] = sectionElement.innerText.trim();
                }
                log(`   -> Extracted [${config.key}] using ${config.extractMethod}`);
            } else {
                extractedData.sections[config.key] = null; // 用 null 表示未找到,而不是字符串
                log(`   -> Section [${config.key}] not found using selector: ${config.selector}`);
            }
        });

         // 单独提取选项文本 (使用配置)
        const optionItems = contentBody.querySelectorAll(currentConfig.optionItemSelector);
         if (optionItems.length > 0) {
             extractedData.sections.optionTexts = Array.from(optionItems).map(li => {
                 const textContent = li.textContent?.trim();
                 const match = currentConfig.optionTextRegex ? textContent.match(currentConfig.optionTextRegex) : null;
                 // 如果正则匹配到,取捕获组1;否则取整个文本;如果正则为null,也取整个文本
                 return match ? match[1].trim() : textContent;
             }).filter(text => text);
             log(`   -> Extracted specific option texts:`, extractedData.sections.optionTexts);
         } else {
             extractedData.sections.optionTexts = []; // 未找到选项 li 或列表容器
             log(`   -> No option items found using selector: ${currentConfig.optionItemSelector}`);
         }

        log("Content extraction complete:", extractedData);
        return extractedData; // 返回数据,不在此处 log
    }

    // --- 清理内容观察器 ---
    function disconnectContentObserver() { /* ... 代码基本不变 ... */ }

    // --- 内部内容观察器的回调 ---
    const contentObserverCallback = function(mutationsList, observer) {
        if (!watchingAiNode || !updateCallback) return; // 增加 updateCallback 检查
        let integralFound = currentConfig.integralTextPattern.test(watchingAiNode.textContent);

        if (integralFound) {
            log(`Completion pattern found in node: ${watchingAiNode.id}. Finalizing capture.`);
            latestFullyLoadedAiNode = watchingAiNode;
            disconnectContentObserver();
            const data = extractContent(latestFullyLoadedAiNode); // 获取数据
            if (data) {
                log("Invoking updateCallback with extracted data.");
                updateCallback(data); // **调用回调函数**
            } else {
                logError("Extraction failed after completion detected.");
            }
        }
    };

    // --- 主 MutationObserver 回调 ---
    const mainObserverCallback = function(mutationsList, observer) {
        if (!updateCallback) return; // 增加 updateCallback 检查
        let newAiMessageNode = null;
        // ... (查找 newAiMessageNode 的逻辑不变, 使用 currentConfig.aiMessageSelector) ...

        if (newAiMessageNode) {
            log(`New AI message node detected: ${newAiMessageNode.id || '(no id)'}`);
            disconnectContentObserver();
            watchingAiNode = newAiMessageNode;

            if (currentConfig.integralTextPattern.test(watchingAiNode.textContent)) {
                log(`Completion pattern found immediately in new node: ${watchingAiNode.id}.`);
                latestFullyLoadedAiNode = watchingAiNode;
                const data = extractContent(latestFullyLoadedAiNode);
                if (data) {
                     log("Invoking updateCallback for immediately complete node.");
                     updateCallback(data); // **调用回调函数**
                } else {
                     logError("Extraction failed for immediately complete node.");
                }
                watchingAiNode = null; // 处理完毕
            } else {
                log(`Setting up content observer for node: ${watchingAiNode.id}`);
                // ... (设置 currentContentObserver 的逻辑不变) ...
            }
        }
    };

    // --- 查找并设置主观察器 ---
    function setupObserver() {
        const targetNode = document.querySelector(currentConfig.chatContainerSelector);
        if (targetNode) {
            log(`Starting Main Observer on ${currentConfig.chatContainerSelector}.`);
            // ... (设置 mainObserver 的逻辑不变, 使用 mainObserverCallback) ...
            mainObserver.observe(targetNode, { childList: true, subtree: false });
            observerRetryCount = 0;
            isRunning = true; // 标记为运行中
            log('Main Observer started.');

            // 初始加载检查 (逻辑不变, 但提取后调用回调)
            const existingAiMessages = targetNode.querySelectorAll(currentConfig.aiMessageSelector);
            if (existingAiMessages.length > 0) {
               const lastExistingMessage = existingAiMessages[existingAiMessages.length - 1];
               if (currentConfig.integralTextPattern.test(lastExistingMessage.textContent)) {
                    latestFullyLoadedAiNode = lastExistingMessage;
                    const data = extractContent(latestFullyLoadedAiNode);
                    if (data && updateCallback) { // 检查回调是否存在
                        log("Invoking updateCallback for existing complete message on load.");
                        updateCallback(data); // **调用回调函数**
                    } else if (!data) {
                         logError("Extraction failed for existing complete message on load.");
                    }
               } else {
                    // ... (设置 currentContentObserver 观察现有未完成消息的逻辑不变) ...
               }
            }

        } else {
            observerRetryCount++;
            logError(`Chat container not found. Retry ${observerRetryCount}/${currentConfig.maxObserverRetries}...`);
            if (observerRetryCount < currentConfig.maxObserverRetries) {
                setTimeout(setupObserver, currentConfig.observerRetryDelayBase * observerRetryCount);
            } else {
                logError(`Failed to find chat container. Observer not started.`);
                isRunning = false; // 标记为未运行
            }
        }
    }

    // --- 公共接口方法 ---
    function init(config = {}, callback) {
         currentConfig = { ...DEFAULT_CONFIG, ...config }; // 合并配置
         DEBUG = currentConfig.debug; // 更新 DEBUG 标志
         if (typeof callback !== 'function') {
             logError("Initialization failed: updateCallback must be a function.");
             return false;
         }
         updateCallback = callback;
         logInfo("ContentCaptureModule initialized. Config:", currentConfig);
         return true;
    }

    function start() {
        if (isRunning) {
            log("Observer already running.");
            return;
        }
        if (!updateCallback) {
             logError("Cannot start: Module not initialized with a callback function.");
             return;
        }
        logInfo("Starting content observation...");
        observerRetryCount = 0; // 重置重试计数器
        setupObserver(); // 开始尝试设置观察器
    }

    function stop() {
        logInfo("Stopping content observation...");
        disconnectContentObserver();
        if (mainObserver) {
            mainObserver.disconnect();
            mainObserver = null;
            log('Main Observer disconnected.');
        }
        isRunning = false; // 标记为停止
        watchingAiNode = null;
        // latestFullyLoadedAiNode 保留,refresh 可能会用到
    }

    function refresh() {
        logInfo("Manual refresh triggered.");
        if (!updateCallback) {
             logError("Cannot refresh: Module not initialized.");
             return Promise.reject("Module not initialized.");
        }

        const aiMessages = document.querySelectorAll(currentConfig.aiMessageSelector);
        if (aiMessages.length > 0) {
             const latestMessage = aiMessages[aiMessages.length - 1];
             log(`Found latest AI message (ID: ${latestMessage.id}) for refresh.`);
             const data = extractContent(latestMessage); // 直接提取最新的
             if (data) {
                 log("Invoking updateCallback with refreshed data.");
                 updateCallback(data);
                 return Promise.resolve(data); // 返回提取到的数据
             } else {
                 logError("Extraction failed during manual refresh.");
                 return Promise.reject("Extraction failed during refresh.");
             }
        } else {
             logInfo("No AI messages found on page for refresh.");
             // 可以选择调用回调传递一个空状态,或者什么都不做
             // updateCallback({ timestamp: new Date().toISOString(), sections: {}, isEmpty: true });
             return Promise.resolve(null); // 表示没有内容可刷新
        }
    }

    // 清理事件监听器
    window.addEventListener('beforeunload', stop);

    // --- 返回模块的公共接口 ---
    return {
        init: init,
        start: start,
        stop: stop,
        refresh: refresh,
        isRun: () => { // 添加一个状态检查方法
            return isRunning;
        }
    };

})();