Greasy Fork

Greasy Fork is available in English.

c.ai Background Image Library (Improved)

Customize the chat interface with an improved image library for backgrounds, with multiple rows of thumbnails and click-outside popup closure.

安装此脚本?
作者推荐脚本

您可能也喜欢CAI CharPic replacer

安装此脚本
// ==UserScript==
// @name        c.ai Background Image Library (Improved)
// @author      LuxTallis
// @namespace   c.ai Background Image Library Improved
// @match       https://character.ai/*
// @grant       none
// @license     MIT
// @version     1.1
// @description Customize the chat interface with an improved image library for backgrounds, with multiple rows of thumbnails and click-outside popup closure.
// @icon        https://i.imgur.com/ynjBqKW.png
// ==/UserScript==

(function () {
  function saveLibrary(library) {
    localStorage.setItem('background_image_library', JSON.stringify(library));
  }

  function getLibrary() {
    return JSON.parse(localStorage.getItem('background_image_library') || '[]');
  }

  function addToLibrary(url) {
    const library = getLibrary();
    if (!url || library.includes(url)) return;
    library.push(url);
    saveLibrary(library);
  }

  function removeFromLibrary(url) {
    const library = getLibrary().filter((image) => image !== url);
    saveLibrary(library);
  }

  // Function to get the current chat ID
  function getChatID() {
    const path = window.location.pathname;
    const match = path.match(/\/chat\/([^/]+)/);  // Matches chat ID in URL
    return match ? match[1] : null;  // Return the chat ID if found
  }

  // Apply background image for the specific chat
  function applyBackgroundImage(url) {
    const chatID = getChatID();
    if (!chatID) return; // If no chat ID found, return

    const chatKey = `background_image_${chatID}`;
    localStorage.setItem(chatKey, url);  // Save the background URL for this chat

    const css = `
      body {
        background-image: url('${url}');
        background-size: cover;
        background-position: center;
        background-repeat: no-repeat;
      }
    `;
    let styleElement = document.getElementById('customBackgroundStyle');
    if (!styleElement) {
      styleElement = document.createElement('style');
      styleElement.id = 'customBackgroundStyle';
      document.head.appendChild(styleElement);
    }
    styleElement.innerHTML = css;
  }

  function createCustomizationPanel() {
    const panel = document.createElement('div');
    panel.id = 'customizationPanel';
    panel.style.position = 'fixed';
    panel.style.top = '50%';
    panel.style.left = '50%';
    panel.style.transform = 'translate(-50%, -50%)';
    panel.style.backgroundColor = '#1e1e1e';
    panel.style.color = 'white';
    panel.style.borderRadius = '5px';
    panel.style.padding = '20px';
    panel.style.zIndex = '9999';
    panel.style.fontFamily = 'Montserrat, sans-serif';
    panel.style.maxWidth = '1250px'; // 2.5x wider
    panel.style.minWidth = '875px';  // 2.5x wider
    panel.style.width = 'auto'; // Ensure auto width based on content

    const label = document.createElement('label');
    label.textContent = 'Add Image URL to Library:';
    label.style.display = 'block';
    label.style.marginBottom = '5px';

    const input = document.createElement('input');
    input.type = 'text';
    input.placeholder = 'Enter image URL';
    input.style.width = '100%';
    input.style.marginBottom = '10px';

    const addButton = document.createElement('button');
    addButton.textContent = 'Add';
    addButton.style.marginTop = '10px';
    addButton.style.padding = '5px 10px';
    addButton.style.border = 'none';
    addButton.style.borderRadius = '3px';
    addButton.style.backgroundColor = '#444';
    addButton.style.color = 'white';
    addButton.style.fontFamily = 'Montserrat, sans-serif';
    addButton.addEventListener('click', () => {
      const url = input.value.trim();
      if (url) {
        addToLibrary(url);
        input.value = '';
        renderLibrary();
      }
    });

    const libraryContainer = document.createElement('div');
    libraryContainer.id = 'libraryContainer';
    libraryContainer.style.marginTop = '10px';
    libraryContainer.style.overflowX = 'auto';
    libraryContainer.style.display = 'flex';
    libraryContainer.style.flexWrap = 'wrap'; // Allow wrapping into multiple lines
    libraryContainer.style.gap = '15px'; // Increased gap between thumbnails
    libraryContainer.style.paddingBottom = '10px';
    libraryContainer.style.borderTop = '1px solid #555';
    libraryContainer.style.paddingTop = '10px';
    libraryContainer.style.whiteSpace = 'nowrap'; // Prevent wrapping of thumbnails
    libraryContainer.style.maxHeight = '380px';  // Ensure 3 rows of thumbnails
    libraryContainer.style.height = 'auto';

    function renderLibrary() {
      libraryContainer.innerHTML = '';
      const library = getLibrary();
      library.forEach((url) => {
        const imgContainer = document.createElement('div');
        imgContainer.style.position = 'relative';
        imgContainer.style.flex = '0 0 auto'; // Ensures the thumbnail stays at its natural width

        const img = document.createElement('img');
        img.src = url;
        img.alt = 'Preview';
        img.style.width = '99px';  // Half the size
        img.style.height = '64px'; // Half the size
        img.style.objectFit = 'cover';
        img.style.border = '1px solid #fff';
        img.style.borderRadius = '3px';
        img.style.cursor = 'pointer';
        img.title = url;

        img.addEventListener('click', () => {
          applyBackgroundImage(url);
        });

        const removeButton = document.createElement('button');
        removeButton.textContent = '×';
        removeButton.style.position = 'absolute';
        removeButton.style.top = '5px';
        removeButton.style.right = '5px';
        removeButton.style.backgroundColor = 'red';
        removeButton.style.color = 'white';
        removeButton.style.border = 'none';
        removeButton.style.borderRadius = '50%';
        removeButton.style.cursor = 'pointer';
        removeButton.style.width = '20px';
        removeButton.style.height = '20px';
        removeButton.style.textAlign = 'center';
        removeButton.style.fontSize = '12px';
        removeButton.addEventListener('click', (e) => {
          e.stopPropagation();
          removeFromLibrary(url);
          renderLibrary();
        });

        imgContainer.appendChild(img);
        imgContainer.appendChild(removeButton);
        libraryContainer.appendChild(imgContainer);
      });
    }

    panel.appendChild(label);
    panel.appendChild(input);
    panel.appendChild(addButton);
    panel.appendChild(libraryContainer);
    document.body.appendChild(panel);

    renderLibrary();

    // Close the panel when clicking outside of it
    document.addEventListener('click', function closeOnOutsideClick(event) {
      if (!panel.contains(event.target) && !mainButton.contains(event.target)) {
        panel.remove();
        document.removeEventListener('click', closeOnOutsideClick); // Remove the event listener
      }
    });
  }

  function createButton(symbol, onClick) {
    const button = document.createElement('button');
    button.innerHTML = symbol;
    button.style.position = 'fixed';
    button.style.top = '82px';
    button.style.right = '5px';
    button.style.width = '22px';
    button.style.height = '22px';
    button.style.backgroundColor = '#444';
    button.style.color = 'white';
    button.style.border = 'none';
    button.style.borderRadius = '3px';
    button.style.cursor = 'pointer';
    button.style.fontFamily = 'Montserrat, sans-serif';
    button.addEventListener('click', onClick);
    return button;
  }

  const mainButton = createButton('🖼️', () => {
    const panelExists = document.getElementById('customizationPanel');
    if (!panelExists) {
      createCustomizationPanel();
    }
  });

  document.body.appendChild(mainButton);

  // Function to update background when URL changes
  function updateBackgroundForCurrentChat() {
    const chatID = getChatID();
    const currentImageUrl = chatID ? localStorage.getItem(`background_image_${chatID}`) : '';
    applyBackgroundImage(currentImageUrl || '');
  }

  // Initial background update
  updateBackgroundForCurrentChat();

  // Update background whenever the URL changes
  window.addEventListener('popstate', updateBackgroundForCurrentChat);
  window.addEventListener('pushstate', updateBackgroundForCurrentChat);
  window.addEventListener('replacestate', updateBackgroundForCurrentChat);
})();