DeepSeek ShortCuts

Keyboard Shortcuts For DeepSeek (Mac & Windows & Linux)

// ==UserScript==
// @name               DeepSeek ShortCuts
// @name:zh-CN         DeepSeek快捷键
// @name:zh-TW         DeepSeek快捷鍵
// @description        Keyboard Shortcuts For DeepSeek (Mac & Windows & Linux)
// @description:zh-CN  为DeepSeek提供快捷键支持(Mac & Windows & Linux)
// @description:zh-TW  為DeepSeek提供快捷鍵支持(Mac & Windows & Linux)
// @version            1.4.0
// @icon               https://raw.githubusercontent.com/MiPoNianYou/UserScripts/refs/heads/main/Icons/DeepSeekShortcutsIcon.svg
// @author             念柚
// @namespace          https://github.com/MiPoNianYou/UserScripts
// @supportURL         https://github.com/MiPoNianYou/UserScripts/issues
// @license            GPL-3.0
// @match              https://chat.deepseek.com/*
// @grant              GM_addStyle
// ==/UserScript==

(function () {
  "use strict";

  const helpPanelStyles = `
    .ShortcutsHelpPanel {
      --panel-bg-color: rgba(44, 44, 46, 0.85);
      --panel-text-color: rgba(255, 255, 255, 0.9);
      --panel-secondary-text-color: rgba(235, 235, 245, 0.6);
      --panel-border-color: rgba(84, 84, 88, 0.65);
      --panel-padding: 24px;
      --panel-radius: 12px;
      --key-bg-color: rgba(118, 118, 128, 0.24);
      --warning-bg-color: rgba(118, 118, 128, 0.24);
      --warning-border-color: rgba(84, 84, 88, 0.65);
      --warning-text-color: rgb(255, 159, 10);
      --font-stack: system-ui, sans-serif;
      --closebtn-color: #FF5F57;
      --closebtn-hover-color: #E0443E;
      --closebtn-symbol-color: #4D0000;

      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -48%) scale(0.95);
      z-index: 9999;
      pointer-events: none;
      visibility: hidden;
      min-width: 280px;
      max-width: 450px;
      padding: var(--panel-padding);
      border: 0.5px solid var(--panel-border-color);
      border-radius: var(--panel-radius);
      background: var(--panel-bg-color);
      box-shadow: 0 12px 28px rgba(0, 0, 0, 0.2), 0 2px 4px rgba(0, 0, 0, 0.1);
      backdrop-filter: blur(20px) saturate(180%);
      color: var(--panel-text-color);
      font-family: var(--font-stack);
      font-size: 14px;
      font-weight: 500;
      line-height: 1.5;
      opacity: 0;
      transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out, visibility 0s linear 0.3s;
      display: flex;
      flex-direction: column;
    }
    .ShortcutsHelpPanel.visible {
      opacity: 1;
      transform: translate(-50%, -50%) scale(1);
      pointer-events: auto;
      visibility: visible;
      transition-delay: 0s;
    }
    .ShortcutsHelpCloseButton {
      position: absolute;
      top: 14px;
      left: 14px;
      width: 12px;
      height: 12px;
      padding: 0;
      border: 0.5px solid rgba(0, 0, 0, 0.2);
      border-radius: 50%;
      background-color: var(--closebtn-color);
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: background-color 0.15s ease;
      appearance: none;
      -webkit-appearance: none;
    }
     .ShortcutsHelpCloseButton::before {
      content: '×';
      display: block;
      color: transparent;
      font-size: 11px;
      font-weight: bold;
      line-height: 12px;
      text-align: center;
      transition: color 0.15s ease;
    }
    .ShortcutsHelpCloseButton:hover {
      background-color: var(--closebtn-hover-color);
    }
    .ShortcutsHelpCloseButton:hover::before {
      color: var(--closebtn-symbol-color);
    }
    .ShortcutsHelpCloseButton:active {
      background-color: var(--closebtn-hover-color);
      filter: brightness(0.9);
    }
    .ShortcutsHelpTitle {
      margin: 0 0 15px 0;
      width: 100%;
      color: var(--panel-text-color);
      font-size: 16px;
      font-weight: 600;
      text-align: center;
      padding-top: 5px;
      flex-shrink: 0;
    }
    .ShortcutsHelpContent {
      flex-grow: 1;
      overflow-y: auto;
      max-height: 65vh;
      margin-right: -10px;
      padding-right: 10px;
      scrollbar-width: thin;
      scrollbar-color: rgba(235, 235, 245, 0.3) transparent;
    }
    .ShortcutsHelpContent::-webkit-scrollbar {
      width: 6px;
    }
    .ShortcutsHelpContent::-webkit-scrollbar-track {
      background: transparent;
      margin: 5px 0;
    }
    .ShortcutsHelpContent::-webkit-scrollbar-thumb {
      background-color: rgba(235, 235, 245, 0.4);
      border-radius: 3px;
    }
    .ShortcutsHelpContent::-webkit-scrollbar-thumb:hover {
      background-color: rgba(235, 235, 245, 0.6);
    }
    .ShortcutsHelpRow {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
      padding: 5px 0;
    }
    .ShortcutsHelpContent > .ShortcutsHelpRow:last-child {
        margin-bottom: 0;
    }
    .ShortcutsHelpKey {
      min-width: 90px;
      padding: 4px 8px;
      margin-left: 16px;
      background: var(--key-bg-color);
      border: 0.5px solid var(--panel-border-color);
      border-radius: 5px;
      box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
      color: var(--panel-text-color);
      font-family: inherit;
      font-size: 13px;
      text-align: center;
      flex-shrink: 0;
    }
    .ShortcutsHelpDesc {
      flex-grow: 1;
      padding-right: 10px;
      color: var(--panel-secondary-text-color);
    }
    .ShortcutsHelpWarning {
      margin-top: 18px;
      padding: 12px;
      background: var(--warning-bg-color);
      border: 0.5px solid var(--warning-border-color);
      border-radius: 8px;
      color: var(--warning-text-color);
      font-size: 12px;
      line-height: 1.5;
      text-align: center;
      flex-shrink: 0;
    }
  `;

  if (typeof GM_addStyle === "function") {
    GM_addStyle(helpPanelStyles);
  } else {
    const styleElement = document.createElement("style");
    styleElement.textContent = helpPanelStyles;
    document.head.appendChild(styleElement);
  }

  const createFinder = (config) => () => {
    const {
      selector,
      filterCriteria,
      position = "first",
      parentSelector,
      parentPosition = "last",
      childPosition = "first",
    } = config;

    if (parentSelector) {
      const parents = Array.from(document.querySelectorAll(parentSelector));
      if (parents.length === 0) return null;
      const parentIndex = parentPosition === "last" ? parents.length - 1 : 0;
      const targetParent = parents[parentIndex];
      if (!targetParent) return null;
      const children = Array.from(targetParent.querySelectorAll(selector));
      if (children.length === 0) return null;
      const childIndex = childPosition === "last" ? children.length - 1 : 0;
      return children[childIndex] || null;
    } else {
      const elements = Array.from(document.querySelectorAll(selector));
      if (elements.length === 0) return null;
      if (filterCriteria) {
        return (
          elements.find(
            (element) =>
              element.textContent?.includes(filterCriteria) ||
              element.querySelector(filterCriteria)
          ) || null
        );
      } else {
        const index = position === "last" ? elements.length - 1 : 0;
        return elements[index] || null;
      }
    }
  };

  const findRegenerate = createFinder({
    selector: ".ds-icon-button",
    filterCriteria: "#重新生成",
  });
  const findContinue = createFinder({
    selector: ".ds-button",
    filterCriteria: "继续生成",
  });
  const findStop = createFinder({
    selector: "._7436101",
    position: "first",
  });
  const findLastCopy = createFinder({
    parentSelector: "div._4f9bf79.d7dc56a8",
    parentPosition: "last",
    selector: "._965abe9 .ds-icon-button",
    childPosition: "first",
  });
  const findLastEdit = createFinder({
    parentSelector: "._9663006",
    parentPosition: "last",
    selector: "._78e0558 .ds-icon-button",
    childPosition: "last",
  });
  const findDeepThink = createFinder({
    selector: ".ds-button span",
    filterCriteria: "深度思考",
  });
  const findSearch = createFinder({
    selector: ".ds-button span",
    filterCriteria: "联网搜索",
  });
  const findUpload = createFinder({
    selector: ".f02f0e25",
    position: "first",
  });
  const findNewChat = createFinder({
    selector: "._217e214",
    position: "first",
  });
  const findToggleSidebar = createFinder({
    selector: ".ds-icon-button",
    filterCriteria: "svg #打开边栏0730, svg #折叠边栏0730",
  });
  const findChatMenu = createFinder({
    parentSelector: "._83421f9.b64fb9ae",
    parentPosition: "last",
    selector: "._2090548",
    childPosition: "first",
  });

  const getModifiers = () => {
    const isMac = /Macintosh|Mac OS X/i.test(navigator.userAgent);
    return {
      Character: isMac ? "Control" : "Alt",
      Property: isMac ? "ctrlKey" : "altKey",
    };
  };

  const modifierKeys = getModifiers();

  const shortcutDefs = [
    [`${modifierKeys.Character} + R`, "重新生成回答"],
    [`${modifierKeys.Character} + C`, "继续生成回答"],
    [`${modifierKeys.Character} + Q`, "中断当前生成"],
    [`${modifierKeys.Character} + K`, "复制末条回答"],
    [`${modifierKeys.Character} + E`, "编辑末次提问"],
    [`${modifierKeys.Character} + D`, "深度思考模式"],
    [`${modifierKeys.Character} + S`, "联网搜索模式"],
    [`${modifierKeys.Character} + U`, "上传本地文件"],
    [`${modifierKeys.Character} + N`, "新建对话窗口"],
    [`${modifierKeys.Character} + T`, "切换开关边栏"],
    [`${modifierKeys.Character} + I`, "当前对话菜单"],
    [`${modifierKeys.Character} + H`, "快捷按键帮助"],
  ];

  let helpPanelElement = null;

  const createHelpPanel = () => {
    const panelElement = document.createElement("div");
    panelElement.classList.add("ShortcutsHelpPanel");

    const closeButton = document.createElement("button");
    closeButton.classList.add("ShortcutsHelpCloseButton");
    closeButton.addEventListener("click", (e) => {
      e.stopPropagation();
      closeHelpPanel();
    });

    const titleElement = document.createElement("h3");
    titleElement.textContent = "快捷按键指北";
    titleElement.classList.add("ShortcutsHelpTitle");

    const contentDiv = document.createElement("div");
    contentDiv.classList.add("ShortcutsHelpContent");

    panelElement.append(closeButton, titleElement);

    shortcutDefs.forEach(([keyShortcut, description]) => {
      const rowElement = document.createElement("div");
      rowElement.classList.add("ShortcutsHelpRow");

      const keyElement = document.createElement("span");
      keyElement.textContent = keyShortcut;
      keyElement.classList.add("ShortcutsHelpKey");

      const descriptionElement = document.createElement("span");
      descriptionElement.textContent = description;
      descriptionElement.classList.add("ShortcutsHelpDesc");

      rowElement.append(descriptionElement, keyElement);
      contentDiv.append(rowElement);
    });

    panelElement.append(contentDiv);

    const warningElement = document.createElement("div");
    warningElement.textContent = "⚠️ 脚本依UA自动适配快捷键 篡改UA或致功能异常";
    warningElement.classList.add("ShortcutsHelpWarning");
    panelElement.append(warningElement);

    document.body.append(panelElement);
    return panelElement;
  };

  const handleOutsideClick = (mouseEvent) => {
    if (
      helpPanelElement &&
      helpPanelElement.classList.contains("visible") &&
      !helpPanelElement.contains(mouseEvent.target) &&
      !mouseEvent.target.classList.contains("ShortcutsHelpCloseButton")
    ) {
      closeHelpPanel();
    }
  };

  const toggleHelpPanel = (panelElement) => {
    if (!panelElement) {
      return;
    }
    const isVisible = panelElement.classList.contains("visible");
    if (isVisible) {
      panelElement.classList.remove("visible");
      window.removeEventListener("click", handleOutsideClick, true);
    } else {
      panelElement.classList.add("visible");
      setTimeout(() => {
        window.addEventListener("click", handleOutsideClick, true);
      }, 0);
    }
  };

  const initHelpPanel = () => {
    if (!helpPanelElement) {
      helpPanelElement = createHelpPanel();
    }
  };

  const closeHelpPanel = () => {
    if (helpPanelElement && helpPanelElement.classList.contains("visible")) {
      helpPanelElement.classList.remove("visible");
      window.removeEventListener("click", handleOutsideClick, true);
    }
  };

  const safeClick = (finderFunction) => {
    const element = finderFunction();
    if (element) {
      element.click();
    }
  };

  const debounce = (func, wait) => {
    let timeout;
    return function executeDebounced(...args) {
      const later = () => {
        clearTimeout(timeout);
        func.apply(this, args);
      };
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    };
  };

  const toggleHelpPanelAction = () => {
    initHelpPanel();
    if (helpPanelElement) {
      toggleHelpPanel(helpPanelElement);
    }
  };

  const debouncedToggleHelpPanel = debounce(toggleHelpPanelAction, 150);

  const keyActionMap = {
    r: () => safeClick(findRegenerate),
    c: () => safeClick(findContinue),
    q: () => safeClick(findStop),
    k: () => safeClick(findLastCopy),
    e: () => safeClick(findLastEdit),
    d: () => safeClick(findDeepThink),
    s: () => safeClick(findSearch),
    u: () => safeClick(findUpload),
    n: () => safeClick(findNewChat),
    t: () => safeClick(findToggleSidebar),
    i: () => safeClick(findChatMenu),
    h: () => {
      debouncedToggleHelpPanel();
      return true;
    },
  };

  const createKeyHandler = () => {
    const isModifierKeyPressed = (keyboardEvent) =>
      keyboardEvent[modifierKeys.Property];

    return (keyboardEvent) => {
      if (keyboardEvent.key === "Escape") {
        if (
          helpPanelElement &&
          helpPanelElement.classList.contains("visible")
        ) {
          closeHelpPanel();
          keyboardEvent.preventDefault();
          keyboardEvent.stopPropagation();
        }
        return;
      }

      if (!isModifierKeyPressed(keyboardEvent)) {
        return;
      }

      const pressedKey = keyboardEvent.key.toLowerCase();
      const actionFunction = keyActionMap[pressedKey];

      if (actionFunction) {
        const actionResult = actionFunction(keyboardEvent);
        if (actionResult !== false) {
          keyboardEvent.preventDefault();
          keyboardEvent.stopPropagation();
        }
      }
    };
  };

  const mainKeyHandler = createKeyHandler();
  window.addEventListener("keydown", mainKeyHandler, true);
})();