// ==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 // @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) => { 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

,

, etc. text = text.replace(/^(######\s)(.*)$/gm, '

$2
'); // For h6 text = text.replace(/^(#####\s)(.*)$/gm, '
$2
'); // For h5 text = text.replace(/^(####\s)(.*)$/gm, '

$2

'); // For h4 text = text.replace(/^(###\s)(.*)$/gm, '

$2

'); // For h3 text = text.replace(/^(##\s)(.*)$/gm, '

$2

'); // For h2 text = text.replace(/^(#\s)(.*)$/gm, '

$2

'); // For h1 // Process bold italic before bold or italic to avoid nesting conflicts text = text.replace(/\*\*\*([^*]+)\*\*\*/g, '$1'); // Replace text wrapped in double asterisks with tags for bold text = text.replace(/\*\*([^*]+)\*\*/g, '$1'); // Finally, replace text wrapped in single asterisks with tags for italics text = text.replace(/\*([^*]+)\*/g, '$1'); 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 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(); })();