DeepSeek AI Avatar Uploader

Dynamically adds/removes a custom floating AI avatar image on DeepSeek chat subpages with optimized performance

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
  // @name         DeepSeek AI Avatar Uploader
  // @namespace    http://tampermonkey.net/
  // @version      1.0
  // @description  Dynamically adds/removes a custom floating AI avatar image on DeepSeek chat subpages with optimized performance
  // @author       LuxTallis and Grok
  // @match        https://chat.deepseek.com/*
  // @grant        GM_setValue
  // @grant        GM_getValue
  // @license      MIT
  // ==/UserScript==

  (function() {
      'use strict';

      let uploadButton = null;
      let observer = null;
      let lastUpdate = 0;
      const DEBOUNCE_DELAY = 500; // Milliseconds
      let cachedContainer = null;

      // Check if the current page is a chat subpage
      function isChatSubpage() {
          return window.location.pathname.startsWith('/a/chat/');
      }

      // Function to create the upload button
      function createUploadButton() {
          if (uploadButton) return;
          uploadButton = document.createElement('button');
          uploadButton.textContent = 'Upload AI Avatar';
          uploadButton.style.position = 'fixed';
          uploadButton.style.top = '10px';
          uploadButton.style.right = '10px';
          uploadButton.style.zIndex = '1000';
          uploadButton.style.padding = '8px 12px';
          uploadButton.style.backgroundColor = '#4CAF50';
          uploadButton.style.color = 'white';
          uploadButton.style.border = 'none';
          uploadButton.style.borderRadius = '4px';
          uploadButton.style.cursor = 'pointer';
          uploadButton.style.fontSize = '14px';
          uploadButton.addEventListener('click', openFileInput);
          document.body.appendChild(uploadButton);
      }

      // Function to remove the upload button
      function removeUploadButton() {
          if (uploadButton) {
              uploadButton.remove();
              uploadButton = null;
          }
      }

      // Function to create a hidden file input
      function openFileInput() {
          const input = document.createElement('input');
          input.type = 'file';
          input.accept = 'image/*';
          input.style.display = 'none';
          input.addEventListener('change', handleFileSelect);
          document.body.appendChild(input);
          input.click();
      }

      // Function to handle file selection and store the image
      function handleFileSelect(event) {
          const file = event.target.files[0];
          if (file && file.type.startsWith('image/')) {
              const reader = new FileReader();
              reader.onload = function(e) {
                  const imageData = e.target.result;
                  GM_setValue('customAIAvatar', imageData);
                  applyAvatar(imageData);
              };
              reader.readAsDataURL(file);
          }
      }

      // Function to check if an element is scrollable
      function isScrollable(element) {
          const style = window.getComputedStyle(element);
          return style.overflow === 'auto' || style.overflowY === 'auto' ||
                 style.overflow === 'scroll' || style.overflowY === 'scroll';
      }

      // Function to find the chat window container
      function findChatContainer() {
          if (cachedContainer) {
              if (document.contains(cachedContainer.container)) {
                  return cachedContainer;
              } else {
                  cachedContainer = null; // Clear cache if container is gone
              }
          }

          const selectors = [
              'div._3919b83',
              'div.dad65929',
              'div.ds-theme div[class*="chat"]',
              'div.ds-theme div[class*="scrollable"]',
              'div.ds-theme',
              'div#root'
          ];

          for (const selector of selectors) {
              const container = document.querySelector(selector);
              if (container) {
                  const scrollable = isScrollable(container);
                  const isSidebar = container.className.includes('sidebar') ||
                                   container.parentElement?.className.includes('sidebar') ||
                                   container.querySelector('nav') !== null;
                  if (!isSidebar && (scrollable || selector === 'div._3919b83' || selector === 'div.dad65929' || selector.includes('chat'))) {
                      cachedContainer = { container, selector };
                      return cachedContainer;
                  }
              }
          }

          for (const selector of selectors) {
              const container = document.querySelector(selector);
              if (container) {
                  const isSidebar = container.className.includes('sidebar') ||
                                   container.parentElement?.className.includes('sidebar') ||
                                   container.querySelector('nav') !== null;
                  if (!isSidebar) {
                      cachedContainer = { container, selector };
                      return cachedContainer;
                  }
              }
          }

          console.error('No suitable chat container found. Tried selectors:', selectors);
          return null;
      }

      // Function to apply the custom avatar
      function applyAvatar(imageData) {
          const existingAvatar = document.getElementById('custom-ai-avatar');
          if (existingAvatar) {
              existingAvatar.remove();
          }

          const result = findChatContainer();
          if (!result) {
              console.error('Cannot apply avatar: No valid chat container found.');
              return;
          }

          const { container, selector } = result;
          console.log(`Applying avatar to container: ${selector}`);

          const avatarImg = document.createElement('img');
          avatarImg.id = 'custom-ai-avatar';
          avatarImg.src = imageData;
          avatarImg.style.position = isScrollable(container) ? 'sticky' : 'absolute';
          avatarImg.style.top = '180px';
          avatarImg.style.left = '80px';
          avatarImg.style.width = '400px';
          avatarImg.style.height = '400px';
          avatarImg.style.borderRadius = '20%';
          avatarImg.style.objectFit = 'cover';
          avatarImg.style.zIndex = '100';
          avatarImg.style.marginRight = '10px';

          container.style.position = 'relative';
          container.prepend(avatarImg);
      }

      // Function to remove the custom avatar
      function removeAvatar() {
          const existingAvatar = document.getElementById('custom-ai-avatar');
          if (existingAvatar) {
              existingAvatar.remove();
          }
      }

      // Function to load and apply the stored avatar
      function loadStoredAvatar() {
          const storedAvatar = GM_getValue('customAIAvatar', null);
          if (storedAvatar) {
              applyAvatar(storedAvatar);
          }
      }

      // Function to observe DOM changes in chat container
      function observeDOM() {
          if (observer) {
              observer.disconnect();
          }

          const result = findChatContainer();
          if (!result) return;

          const { container } = result;
          observer = new MutationObserver(() => {
              const now = Date.now();
              if (now - lastUpdate < DEBOUNCE_DELAY) return;
              lastUpdate = now;

              if (isChatSubpage()) {
                  const storedAvatar = GM_getValue('customAIAvatar', null);
                  if (storedAvatar && !document.getElementById('custom-ai-avatar')) {
                      console.log('DOM changed, reapplying avatar');
                      applyAvatar(storedAvatar);
                  }
              }
          });
          observer.observe(container, { childList: true, subtree: true });
      }

      // Debounce function to limit update frequency
      function debounce(func, delay) {
          let timeout;
          return function(...args) {
              clearTimeout(timeout);
              timeout = setTimeout(() => func.apply(this, args), delay);
          };
      }

      // Function to update script state based on URL
      const updateScriptState = debounce(() => {
          if (isChatSubpage()) {
              console.log('On chat subpage, activating script');
              createUploadButton();
              loadStoredAvatar();
              observeDOM();
          } else {
              console.log('Not on chat subpage, deactivating script');
              removeUploadButton();
              removeAvatar();
              if (observer) {
                  observer.disconnect();
                  observer = null;
              }
              cachedContainer = null; // Clear cached container
          }
      }, DEBOUNCE_DELAY);

      // Initial check
      updateScriptState();

      // Override pushState to detect SPA navigation
      const originalPushState = history.pushState;
      history.pushState = function() {
          originalPushState.apply(this, arguments);
          updateScriptState();
      };
  })();