c.ai X Character Creation Helper

Gives visual feedback for the definition

当前为 2024-04-02 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 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     1.1
// @license     MIT
// @description Gives visual feedback for the definition
// @author      Vishanka
// @match       https://character.ai/*
// @grant       none
// ==/UserScript==
 
 
(function() {
    'use strict';
 
    // Function to check for element's presence and execute a callback when found
    const checkElementPresence = (selector, callback, maxAttempts = 10) => {
        let attempts = 0;
        const interval = setInterval(() => {
            const element = document.querySelector(selector);
            if (element) {
                clearInterval(interval);
                callback(element);
            } else if (++attempts >= maxAttempts) {
                clearInterval(interval);
                console.warn(`Element ${selector} not found after ${maxAttempts} attempts.`);
            }
        }, 1000);
    };
 
    // Function to monitor elements on the page
    function monitorElements() {
        const initialElementIds = [
//'div.flex-auto:nth-child(1) > div:nth-child(2) > div:nth-child(1)',
            '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
        ];
 
        initialElementIds.forEach(selector => {
            checkElementPresence(selector, (element) => {
                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')) {
                updatePanel(textarea); // Initial panel setup
 
                // 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';
 
    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
 
 
    // Determine line color based on content and dialogue continuation logic
let consecutiveCharCount = 0;
let lastCharColor = '';
let lastNamedCharacterColor = '';
//let isDialogueContinuation = false;
 
function determineLineColor(line, prevLine) {
    const dialogueStarterRegex = /^\{\{(?:char|user|random_user_[^\}]*)\}\}:|^[A-Za-z]+:/;
    const isDialogueStarter = dialogueStarterRegex.test(line);
    const continuesDialogue = prevLine && prevLine.trim().endsWith(':') && (line.startsWith(' ') || !dialogueStarterRegex.test(line));
 
    if (isDialogueStarter) {
        isDialogueContinuation = true;
        if (line.startsWith('{{char}}:')) {
            consecutiveCharCount++;
            lastColor = consecutiveCharCount % 2 === 0 ? '#26272B' : '#292A2E';
            lastCharColor = lastColor;
        } else if (line.match(/^[A-Za-z]+:/)) {
            lastNamedCharacterColor = lastNamedCharacterColor === '#474747' ? '#4C4C4D' : '#474747';
            lastColor = lastNamedCharacterColor;
        }
          else if (line.match(/^\{\{random_user_[^\}]*\}\}:|^\{\{random_user_\}\}:|^{{random_user_}}/)) {
            lastNamedCharacterColor = lastNamedCharacterColor === '#474747' ? '#4C4C4D' : '#474747';
            lastColor = lastNamedCharacterColor;
        } else {
            consecutiveCharCount = 0;
            lastColor = line.startsWith('{{user}}:') ? '#37362F' : '#3B3A32';
        }
    } 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) {
const dialogueStarterRegex = /^(\{\{char\}\}:|\{\{user\}\}:|\{\{random_user_[^\}]*\}\}:|[A-Za-z]+:)\s*/;
 
    const trimmed = line.match(dialogueStarterRegex);
    if (trimmed) {
        trimmedParts.push(trimmed[0]); // Store the trimmed part
    }
    return line.replace(dialogueStarterRegex, '');
}
 
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;
 
        // Create a container div that could potentially use flexbox
        const containerDiv = document.createElement('div');
        containerDiv.style.width = '100%';
 
        groupDiv.style.backgroundColor = color;
        groupDiv.style.padding = '12px';
        groupDiv.style.borderRadius = '16px';
        groupDiv.style.display = 'inline-block';
        groupDiv.style.maxWidth = '100%'; // You might adjust this as needed
 
        // Only apply flexbox styling if the color condition is met
        if (color === '#37362F' || color === '#3B3A32') {
            containerDiv.style.display = 'flex';
            containerDiv.style.justifyContent = 'flex-end'; // Aligns the child div to the right
        }
 
        consecutiveLines.forEach(({ lineDiv }) => {
            groupDiv.appendChild(lineDiv);
        });
 
        // 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) {
    // Handle headers; replace Markdown headers (# Header) with <h1>, <h2>, etc.
    text = text.replace(/^(######\s)(.*)$/gm, '<h6>$2</h6>'); // For h6
    text = text.replace(/^(#####\s)(.*)$/gm, '<h5>$2</h5>'); // For h5
    text = text.replace(/^(####\s)(.*)$/gm, '<h4>$2</h4>'); // For h4
    text = text.replace(/^(###\s)(.*)$/gm, '<h3>$2</h3>'); // For h3
    text = text.replace(/^(##\s)(.*)$/gm, '<h2>$2</h2>'); // For h2
    text = text.replace(/^(#\s)(.*)$/gm, '<h1>$2</h1>'); // For h1
 
    // 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>');
    // Finally, 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');
            const headerText = trimmedParts.shift();
            const formattedHeaderText = headerText.replace(/:/g, '');
            headerDiv.textContent = formattedHeaderText;
            if (formattedHeaderText.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();
})();