Auto Scroll Chat Enhancer

Streamline your character.ai chat experience with the Auto Scroll Chat Enhancer! This script features a control panel with "Start" and "Stop" for key press simulations and an "Auto Scroll" toggle. Activate "Auto Scroll" to automatically view the latest messages, keeping you in the conversation flow seamlessly. Perfect for users seeking an efficient chatting experience.

目前為 2024-04-06 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Auto Scroll Chat Enhancer
// @namespace    http://tampermonkey.net/
// @version      2024-04-06-1
// @description  Streamline your character.ai chat experience with the Auto Scroll Chat Enhancer! This script features a control panel with "Start" and "Stop" for key press simulations and an "Auto Scroll" toggle. Activate "Auto Scroll" to automatically view the latest messages, keeping you in the conversation flow seamlessly. Perfect for users seeking an efficient chatting experience.
// @author       anonymous
// @match        https://character.ai/chat/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=character.ai
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 自动滚动状态变量
    let autoScrollEnabled = true;

    // 控制面板设置
    const controlPanel = document.createElement('div');
    controlPanel.style.position = 'fixed';
    controlPanel.style.top = '0';
    controlPanel.style.left = '0';
    controlPanel.style.zIndex = '10000';
    controlPanel.style.display = 'flex';
    controlPanel.style.gap = '10px'; // 按钮间距
    document.body.appendChild(controlPanel);

    const startButton = document.createElement('button');
    startButton.textContent = '开始';
    controlPanel.appendChild(startButton);

    const stopButton = document.createElement('button');
    stopButton.textContent = '停止';
    controlPanel.appendChild(stopButton);


    // 创建自动滚动控制按钮
    const autoScrollButton = document.createElement('button');
    autoScrollButton.textContent = `自动滚动: ${autoScrollEnabled ? 'on' : 'off'}`;
    controlPanel.appendChild(autoScrollButton);

    // 为自动滚动按钮添加点击事件监听器
    autoScrollButton.addEventListener('click', () => {
        autoScrollEnabled = !autoScrollEnabled; // 切换自动滚动状态
        autoScrollButton.textContent = `自动滚动: ${autoScrollEnabled ? 'on' : 'off'}`; // 更新按钮文本

        // 如果自动滚动被启用,立即执行一次滚动
        if (autoScrollEnabled) {
            Array.from(box.children).pop()?.scrollIntoView(false);
        }
    });

    // 添加样式
    const style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = `
        .swiper-no-swiping.box-item {
            background-color: var(--surface-elevation-2);
            margin: 0 5px 15px 8px;
            border-radius: 15px;
            padding: 5px 10px;
            cursor: pointer; /* 添加手形光标表示可点击 */
        }
        div[data-test-id="virtuoso-item-list"] {
            width: 50%;
            float: right;
        }
        .size-full > .items-center {
            align-items: flex-end;
        }
        aside {
            display: none;
        }
        *,
        *::before,
        *::after {
            animation-duration: .01s !important;
            transition-duration: .01s !important;
        }

        .tidot {
            animation: none;
        }

        .group {
            margin-right: 0;
        }

        .swiper-no-swiping.box-item > div:nth-child(3) {
            height: 0px;
            padding: 0;
            margin: 0;
            opacity: 0.2;
            overflow: hidden;
        }
    `;
    document.head.appendChild(style);

    // 模拟按键操作
    function simulateKeyPress(keyCode) {
        let event = document.createEvent('Event');
        event.initEvent('keydown', true, false);
        event = Object.assign(event, {
            ctrlKey: false,
            metaKey: false,
            altKey: false,
            which: keyCode,
            keyCode: keyCode,
            key: keyCode === 37 ? 'ArrowLeft' : 'ArrowRight',
            code: keyCode === 37 ? 'ArrowLeft' : 'ArrowRight'
        });
        document.dispatchEvent(event);
    }

    let intervalId;

    // 点击开始按钮
    startButton.addEventListener('click', () => {
        console.log('开始模拟按键');
        start();
    });

    function start(){
        if(intervalId) clearInterval(intervalId);
        document.querySelector('textarea').focus();
        intervalId = setInterval(() => simulateKeyPress(39), 40);
    }

    // 点击停止按钮
    stopButton.addEventListener('click', () => {
        console.log('停止模拟按键');
        stop();
    });

    function stop(){
        clearInterval(intervalId);
    }

    // 显示内容的box
    const box = document.createElement('div');
    box.style.position = 'fixed';
    box.style.top = '50px';
    box.style.left = '0';
    box.style.width = '50%';
    box.style.height = '94vh';
    box.style.overflow = 'scroll';
    document.body.appendChild(box);

    let cache = '';
    // 更新box中的内容以匹配swiper-wrapper的当前状态
    function updateBoxContent() {
        const swiperWrapper = document.querySelector('.swiper-wrapper');
        if (swiperWrapper) {
            if(swiperWrapper.innerText===cache) return;
            cache = swiperWrapper.innerText;
            if(swiperWrapper.children.length<3){
                start();
            }
            box.innerHTML = ''; // 清空现有内容
            const elements = swiperWrapper.querySelectorAll('.swiper-no-swiping');
            elements.forEach((element, index) => {
                const clone = element.cloneNode(true);
                clone.classList.add('box-item');
                clone.addEventListener('dblclick', () => {
                    stop();
                    const a = Array.from(box.children).indexOf(clone);
                    const b = Array.from(swiperWrapper.children).indexOf(document.querySelector('.swiper-wrapper > .swiper-slide-active'));
                    const difference = a - b;
                    console.error(a,b)
                    if (difference < 0) {
                        for (let i = 0; i < Math.abs(difference); i++) {
                            simulateKeyPress(37); // 按下左箭头
                        }
                    } else {
                        for (let i = 0; i < difference; i++) {
                            simulateKeyPress(39); // 按下右箭头
                        }
                    }
                });
                box.appendChild(clone);
            });
            // 当自动滚动开启时,自动滚动到最新内容
            if (autoScrollEnabled) {
                Array.from(box.children).pop()?.scrollIntoView(false);
            }
        }
    }

    function setupObserver() {
        // 选择更高级别的元素作为观察目标,例如 body 或特定容器
        const container = document.body;

        let check = Date.now();
        const observer = new MutationObserver((mutations, obs) => {
            if(document.querySelector('div[role=dialog')){
                stop();
                return;
            }
            if(Date.now()-check<1000) return;
            // 每次变动时检查.swiper-wrapper是否存在
            const swiperWrapper = document.querySelector('.swiper-wrapper');
            if (swiperWrapper) {
                check = Date.now()
                updateBoxContent();
            }
        });

        // 监视元素的子元素变化
        observer.observe(container, { childList: true, subtree: true });
    }

    setupObserver();
})();