Greasy Fork

Greasy Fork is available in English.

c.ai X Character Creation Helper

Gives visual feedback for the definition

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        c.ai X Character Creation Helper
// @namespace   c.ai X Character Creation Helper
// @version     2.6
// @license     MIT
// @description Gives visual feedback for the definition
// @author      Vishanka
// @match       https://character.ai/*
// @icon        https://i.imgur.com/iH2r80g.png
// @grant       none
// ==/UserScript==


(function() {
    'use strict';

    // Define the new CSS rule
    var newCss = ".custom-char-color { color: #ff4500 !important; }";

    // Create a new <style> tag
    var style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = newCss;

    // Append the <style> tag to the <head> of the document
    document.head.appendChild(style);

const checkElementPresence = (selector, callback, fastIntervalTime = 1000, slowIntervalTime = 5000) => {
    let elementFoundPreviously = false;
    let fastCheck = true;
    let attempts = 0;
    let maxAttempts = 10;

    const checkElement = () => {
        const element = document.querySelector(selector);
        // When element is found for the first time or reappears after being absent
        if (element) {
            if (!elementFoundPreviously) {
                callback(element);
                console.log(`Element ${selector} found and callback applied.`);
                elementFoundPreviously = true;
                // Reset fastCheck to quickly detect if it disappears again
                fastCheck = true;
                attempts = 0;
            }
        } else {
            if (elementFoundPreviously) {
                // Element was found before but now is missing, might have been removed
                console.warn(`Element ${selector} disappeared.`);
                elementFoundPreviously = false;
                // Speed up the checks temporarily to catch the re-appearance
                fastCheck = true;
                attempts = 0;
            }
        }

        // Adjust checking interval based on state
        if (fastCheck && ++attempts >= maxAttempts) {
            // Switch to slower checking after a number of fast attempts
            console.warn(`Switching to slower check for ${selector}.`);
            fastCheck = false;
        }

        // Continuously adjust timeout interval for checking based on fastCheck flag
        setTimeout(checkElement, fastCheck ? fastIntervalTime : slowIntervalTime);
    };

    // Start checking
    checkElement();
};

// Usage example
checkElementPresence('#definitionSelector', (element) => {
    // Apply your action here
    console.log('Action applied to:', element);
});



    // Function to monitor elements on the page
function monitorElements() {
    const containerSelectors = [
        'div.flex-auto:nth-child(1) > div:nth-child(2)', // Container potentially holding the input
        'div.relative:nth-child(5) > div:nth-child(1) > div:nth-child(1)', // Greeting
        'div.relative:nth-child(4) > div:nth-child(1) > div:nth-child(1)'  // Description
    ];

    function updateInputCurrentName(newValue) {
        // Trim leading and trailing spaces and replace internal spaces with hyphens
        const processedValue = newValue.trim().replace(/\s+/g, '-');
        sessionStorage.setItem('inputCurrentName', processedValue);
        console.log(`Updated session storage with new input value: ${processedValue}`);

        // Dispatch a custom event to indicate that the input value has changed
        window.dispatchEvent(new CustomEvent('inputCurrentNameChanged', { detail: { newValue: processedValue } }));
    }

    containerSelectors.forEach(selector => {
        checkElementPresence(selector, (element) => {
            const inputElement = element.querySelector('input');
            if (inputElement) {
                inputElement.addEventListener('input', () => {
                    updateInputCurrentName(inputElement.value);
                });

                // Store initial value in session storage
                updateInputCurrentName(inputElement.value);
            } else {
                console.log(`Content of ${selector}:`, element.textContent);
            }
        });
    });




// Selector for the definition
const definitionSelector = '.transition > div:nth-child(1) > div:nth-child(1) > div:nth-child(1)';
checkElementPresence(definitionSelector, (element) => {
    const textarea = element.querySelector('textarea');
    if (textarea && !document.querySelector('.custom-definition-panel')) {
        // Initial panel setup
        updatePanel(textarea);

        // Listen for the custom event to update the panel
        window.addEventListener('inputCurrentNameChanged', () => {
            updatePanel(textarea);
        });

        // Observer to detect changes in the textarea content
        const observer = new MutationObserver(() => {
            updatePanel(textarea);
        });

        observer.observe(textarea, { attributes: true, childList: true, subtree: true, characterData: true });
    }
});
}




// Function to update or create the DefinitionFeedbackPanel based on textarea content
function updatePanel(textarea) {
    let DefinitionFeedbackPanel = document.querySelector('.custom-definition-panel');
    if (!DefinitionFeedbackPanel) {
        DefinitionFeedbackPanel = document.createElement('div');
        DefinitionFeedbackPanel.classList.add('custom-definition-panel');
        textarea.parentNode.insertBefore(DefinitionFeedbackPanel, textarea);
    }
    DefinitionFeedbackPanel.innerHTML = ''; // Clear existing content
    DefinitionFeedbackPanel.style.border = '0px solid #ccc';
    DefinitionFeedbackPanel.style.padding = '10px';
    DefinitionFeedbackPanel.style.marginBottom = '10px';
    DefinitionFeedbackPanel.style.marginTop = '5px';
    DefinitionFeedbackPanel.style.maxHeight = '500px'; // Adjust the max-height as needed
    DefinitionFeedbackPanel.style.overflowY = 'auto';


    var plaintextColor = localStorage.getItem('plaintext_color');
    var defaultColor = '#D1D5DB';
    var color = plaintextColor || defaultColor;
    DefinitionFeedbackPanel.style.color = color;

    const cleanedContent = textarea.value.trim();
    console.log(`Content of Definition:`, cleanedContent);
    const textLines = cleanedContent.split('\n');

let lastColor = '#222326';
let isDialogueContinuation = false; // Track if the current line continues a dialogue
let prevColor = null; // Track the previous line's color for detecting color changes

let consecutiveCharCount = 0;
let consecutiveUserCount = 0; // Track the number of consecutive {{user}} lines
let lastCharColor = '';
let lastNamedCharacterColor = '';

function determineLineColor(line, prevLine) {
    // Extract the part of the line before the first colon
    const indexFirstColon = line.indexOf(':');
    const firstPartOfLine = indexFirstColon !== -1 ? line.substring(0, indexFirstColon + 1) : line;
    // Define the dialogue starter regex with updated conditions
    const dialogueStarterRegex = /^\{\{(?:char|user|random_user_[^\}]*)\}\}:|^{{[\S\s]+}}:|^[^\s:]+:/;
    const isDialogueStarter = dialogueStarterRegex.test(firstPartOfLine);
    const continuesDialogue = prevLine && prevLine.trim().endsWith(':') && (line.startsWith(' ') || !dialogueStarterRegex.test(firstPartOfLine));

    const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';

    if (isDialogueStarter) {
        isDialogueContinuation = true;
          if (line.startsWith('{{char}}:')) {
            consecutiveCharCount++;
            if (currentTheme === 'dark') {
              lastColor = consecutiveCharCount % 2 === 0 ? '#26272B' : '#292A2E';
              lastCharColor = lastColor;
            } else {
              lastColor = consecutiveCharCount % 2 === 0 ? '#E4E4E7' : '#E3E3E6';
              lastCharColor = lastColor;
            }
        } else if (line.startsWith('{{user}}:')) {
            consecutiveUserCount++;
    if (currentTheme === 'dark') {
        lastColor = consecutiveUserCount % 2 === 0 ? '#363630' : '#383832';
    } else {
        lastColor = consecutiveUserCount % 2 === 0 ? '#D9D9DF' : '#D5D5DB'; // Light theme color
    }
            consecutiveCharCount = 0; // Reset this if you need to ensure it only affects consecutive {{char}} dialogues

        } else if (line.match(/^\{\{(?:char|user|random_user_[^\}]*)\}\}:|^{{[\S\s]+}}:|^[\S]+:/)) {
            if (currentTheme === 'dark') {
            lastNamedCharacterColor = lastNamedCharacterColor === '#474747' ? '#4C4C4D' : '#474747';
            } else {
            lastNamedCharacterColor = lastNamedCharacterColor === '#CFDCE8' ? '#CCDAE6' : '#CFDCE8';
            }
            lastColor = lastNamedCharacterColor;
        }
          else if (line.match(/^\{\{random_user_[^\}]*\}\}:|^\{\{random_user_\}\}:|^{{random_user_}}/)) {
            if (currentTheme === 'dark') {
            lastNamedCharacterColor = lastNamedCharacterColor === '#474747' ? '#4C4C4D' : '#474747';
            } else {
            lastNamedCharacterColor = lastNamedCharacterColor === '#CFDCE8' ? '#CCDAE6' : '#CFDCE8';
            }
            lastColor = lastNamedCharacterColor;
        } else {
            consecutiveCharCount = 0;
            if (currentTheme === 'dark') {
            lastColor = '#474747' ? '#4C4C4D' : '#474747'; // Default case for non-{{char}} dialogues; adjust as needed
            } else {
            lastColor = '#CFDCE8' ? '#CCDAE6' : '#CFDCE8';
            }
        }
    } else if (line.startsWith('END_OF_DIALOG')) {
        isDialogueContinuation = false;
        lastColor = 'rgba(65, 65, 66, 0)';
    } else if (isDialogueContinuation && continuesDialogue) {
        // Do nothing, continuation of dialogue
    } else if (isDialogueContinuation && !isDialogueStarter) {
        // Do nothing, continuation of dialogue
    } else {
        isDialogueContinuation = false;
        lastColor = 'rgba(65, 65, 66, 0)';
    }
    return lastColor;
}


// Function to remove dialogue starters from the start of a line
let trimmedParts = []; // Array to store trimmed parts
let consecutiveLines = []; // Array to store consecutive lines with the same color
//let prevColor = null;

function trimDialogueStarters(line) {
    // Find the index of the first colon
    const indexFirstColon = line.indexOf(':');
    // If there's no colon, return the line as is
    if (indexFirstColon === -1) return line;

    // Extract the part of the line before the first colon to check against the regex
    const firstPartOfLine = line.substring(0, indexFirstColon + 1);

    // Define the dialogue starter regex
    const dialogueStarterRegex = /^(\{\{char\}\}:|\{\{user\}\}:|\{\{random_user_[^\}]*\}\}:|^{{[\S\s]+}}:|^[^\s:]+:)\s*/;

    // Check if the first part of the line matches the dialogue starter regex
    const trimmed = firstPartOfLine.match(dialogueStarterRegex);
    if (trimmed) {
        // Store the trimmed part
        trimmedParts.push(trimmed[0]);
        // Return the line without the dialogue starter and any leading whitespace that follows it
        return line.substring(indexFirstColon + 1).trim();
    }

    // If the first part doesn't match, return the original line
    return line;
}

function groupConsecutiveLines(color, lineDiv) {
    // Check if there are consecutive lines with the same color
    if (consecutiveLines.length > 0 && consecutiveLines[0].color === color) {
        consecutiveLines.push({ color, lineDiv });
    } else {
        // If not, append the previous group of consecutive lines and start a new group
        appendConsecutiveLines();
        consecutiveLines.push({ color, lineDiv });
    }
}



function appendConsecutiveLines() {
    if (consecutiveLines.length > 0) {
        const groupDiv = document.createElement('div');
        const color = consecutiveLines[0].color;

        const containerDiv = document.createElement('div');
        containerDiv.style.width = '100%';

        groupDiv.style.backgroundColor = color;
        groupDiv.style.padding = '12px';
        groupDiv.style.paddingBottom = '15px'; // Increased bottom padding to provide space
        groupDiv.style.borderRadius = '16px';
        groupDiv.style.display = 'inline-block';
        groupDiv.style.maxWidth = '90%';
        groupDiv.style.position = 'relative'; // Set position as relative for the absolute positioning of countDiv

        if (color === '#363630' || color === '#383832' || color === '#D9D9DF' || color === '#D5D5DB') {
            containerDiv.style.display = 'flex';
            containerDiv.style.justifyContent = 'flex-end';
        }

        // Calculate total number of characters across all lines
        const totalSymbolCount = consecutiveLines.reduce((acc, { lineDiv }) => acc + lineDiv.textContent.length, 0);

        consecutiveLines.forEach(({ lineDiv }) => {
            const lineContainer = document.createElement('div');

            lineContainer.style.display = 'flex';
            lineContainer.style.justifyContent = 'space-between';
            lineContainer.style.alignItems = 'flex-end'; // Ensure items align to the bottom
            lineContainer.style.width = '100%'; // Ensure container takes full width

            lineDiv.style.flexGrow = '1'; // Allow lineDiv to grow and fill space
            // Append the lineDiv to the container
            lineContainer.appendChild(lineDiv);

            // Append the container to the groupDiv
            groupDiv.appendChild(lineContainer);
        });

        const countDiv = document.createElement('div');
        countDiv.textContent = `${totalSymbolCount}`;
        countDiv.style.position = 'absolute'; // Use absolute positioning
        countDiv.style.bottom = '3px'; // Position at the bottom
        countDiv.style.right = '12px'; // Position on the right
        countDiv.style.fontSize = '11px';
// darkmode user
        if (color === '#363630' || color === '#383832'){
            countDiv.style.color = '#5C5C52';
//lightmode user
        } else if (color === '#D9D9DF' || color === '#D5D5DB') {
            countDiv.style.color = '#B3B3B8';
//darkmode char
        } else if (color === '#26272B' || color === '#292A2E') {
          countDiv.style.color = '#44464D';
//lightmode char
        } else if (color === '#E4E4E7' || color === '#E3E3E6') {
          countDiv.style.color = '#C4C4C7';
//darkmode random
        } else if (color === '#474747' || color === '#4C4C4D') {
          countDiv.style.color = '#6E6E6E';
//lightmode random
        } else if (color === '#CFDCE8' || color === '#CCDAE6') {
          countDiv.style.color = '#B4BFC9';
        } else {
          countDiv.style.color = 'rgba(65, 65, 66, 0)';
        }

        // Append the countDiv to the groupDiv
        groupDiv.appendChild(countDiv);

        // Add the groupDiv to the containerDiv (flex or not based on color)
        containerDiv.appendChild(groupDiv);

        // Append the containerDiv to the DefinitionFeedbackPanel
        DefinitionFeedbackPanel.appendChild(containerDiv);
        consecutiveLines = []; // Clear the array
    }
}




function formatText(text) {
    // Retrieve the value of 'inputCurrentName' from session storage
const inputCurrentName = sessionStorage.getItem('inputCurrentName') || '';

text = text.replace(/(?:{{)+char(?:}})+|{{((?!{{char}}|{{user}}|random_user_)[^}]+)}}/g, function(match, p1) {
    if (match.includes('char')) {
        return match.replace(/{{char}}/g, inputCurrentName);
    } else if (p1 === 'user') {
        return match;
    } else {
        return p1.replace(/ /g, '-');
    }
});


    // Process bold italic before bold or italic to avoid nesting conflicts
    text = text.replace(/\*\*\*([^*]+)\*\*\*/g, '<em><strong>$1</strong></em>');
    // Replace text wrapped in double asterisks with <strong> tags for bold
    text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
    // Replace text wrapped in single asterisks with <em> tags for italics
    text = text.replace(/\*([^*]+)\*/g, '<em>$1</em>');

    return text;
}


textLines.forEach((line, index) => {
    const prevLine = index > 0 ? textLines[index - 1] : null;
    const currentColor = determineLineColor(line, prevLine);
    const trimmedLine = trimDialogueStarters(line);

    if (prevColor && currentColor !== prevColor) {
        appendConsecutiveLines(); // Append previous group of consecutive lines

        const spacingDiv = document.createElement('div');
        spacingDiv.style.marginBottom = '20px';
        DefinitionFeedbackPanel.appendChild(spacingDiv);
    }

    const lineDiv = document.createElement('div');
    lineDiv.style.wordWrap = 'break-word'; // Allow text wrapping

    if (trimmedLine.startsWith("END_OF_DIALOG")) {
        appendConsecutiveLines(); // Make sure to append any pending groups before adding the divider
        const separatorLine = document.createElement('hr');
        DefinitionFeedbackPanel.appendChild(separatorLine); // This ensures the divider is on a new line
    } else {
        if (trimmedParts.length > 0) {
            const headerDiv = document.createElement('div');
            let headerText = trimmedParts.shift();

            // Apply replacements for '{{char}}' and other placeholders within double curly brackets
            headerText = headerText.replace(/{{char}}/g, sessionStorage.getItem('inputCurrentName') || '');

            // Apply logic to handle text within curly brackets, excluding '{{user}}'
headerText = headerText.replace(/{{((?!random_user_)[^}]+)}}/g, function(match, p1) {
    if (p1 === 'user') {
        return match;
    }
    return p1.replace(/ /g, '-');
});


            headerText = headerText.replace(/:/g, ''); // Remove colons
            headerDiv.textContent = headerText;

            const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
            headerDiv.style.color = currentTheme === 'dark' ? '#A2A2AC' : '#26272B';
            if (headerText.includes('{{user}}')) {
                headerDiv.style.textAlign = 'right';
            }
            DefinitionFeedbackPanel.appendChild(headerDiv);
        }

        if (trimmedLine.trim() === '') {
            lineDiv.appendChild(document.createElement('br'));
        } else {
            const paragraph = document.createElement('p');
            // Call formatTextForItalics to wrap text in asterisks with <em> tags
            paragraph.innerHTML = formatText(trimmedLine);
            lineDiv.appendChild(paragraph);
        }

        groupConsecutiveLines(currentColor, lineDiv);
    }

    prevColor = currentColor;
});

appendConsecutiveLines();



}


    // Monitor for URL changes to re-initialize element monitoring
    let currentUrl = window.location.href;
    setInterval(() => {
        if (window.location.href !== currentUrl) {
            console.log("URL changed. Re-initializing element monitoring.");
            currentUrl = window.location.href;
            monitorElements();
        }
    }, 1000);

    monitorElements();
})();