// ==UserScript== // @name c.ai X Character Creation Helper // @namespace c.ai X Character Creation Helper // @version 2.2 // @license MIT // @description Gives visual feedback for the definition // @author Vishanka // @match https://character.ai/* // @grant none // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Function to check for element's presence and execute a callback when found const checkElementPresence = (selector, callback, maxAttempts = 10, slowIntervalTime = 5000) => { let attempts = 0; // Initial fast check interval (e.g., every 1000 milliseconds) const fastInterval = 1000; const interval = setInterval(() => { const element = document.querySelector(selector); if (element) { clearInterval(interval); callback(element); } else if (++attempts >= maxAttempts) { // Once the fast attempts limit is reached, clear the fast interval clearInterval(interval); // Log the switch to slower checking console.warn(`Switching to slower check for ${selector} after ${maxAttempts} attempts.`); // Start a new, slower interval check const slowInterval = setInterval(() => { const element = document.querySelector(selector); if (element) { clearInterval(slowInterval); callback(element); } // Optionally, you can add a new condition to stop the slow checks after a certain number of attempts // This prevents indefinite checking if the element never appears }, slowIntervalTime); } }, fastInterval); }; // 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'; 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) { // Handle headers; replace Markdown headers (# Header) with