DeepSeek AI Avatar Uploader

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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();
      };
  })();