Perplexity helper

Simple script that adds buttons to Perplexity website for repeating request using Copilot.

Versión del día 2/7/2023. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name        Perplexity helper
// @namespace   Tiartyos
// @match       https://www.perplexity.ai/search/*
// @match       https://www.perplexity.ai/search
// @grant       none
// @version     1.0
// @author      Tiartyos
// @description Simple script that adds buttons to Perplexity website for repeating request using Copilot.
// @require     https://code.jquery.com/jquery-3.6.0.min.js
// @homepageURL https://www.perplexity.ai/
// @license GPL-3.0-or-later
// ==/UserScript==

var jq = $.noConflict();

let debugMode = false;
const enableDebugMode = () => {
  debugMode = true;
};

const debugLog = (...args) => {
  if (debugMode) {
    console.log('[DEBUG]', ...args);
  }
}

const button = (id, icoName, title) => `<button title="${title}" type="button" id="${id}" class="bg-super text-white hover:opacity-80 font-sans focus:outline-none outline-none transition duration-300 ease-in-out font-sans  select-none items-center relative group justify-center rounded-full cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-base aspect-square h-10">
<div class="flex items-center leading-none justify-center gap-xs">
    ${icoName}
</div></button>`;

const robotIco = '<svg style="width: 23px; fill: white;" viewBox="0 0 640 512" xmlns="http://www.w3.org/2000/svg"><path d="m32 224h32v192h-32a31.96166 31.96166 0 0 1 -32-32v-128a31.96166 31.96166 0 0 1 32-32zm512-48v272a64.06328 64.06328 0 0 1 -64 64h-320a64.06328 64.06328 0 0 1 -64-64v-272a79.974 79.974 0 0 1 80-80h112v-64a32 32 0 0 1 64 0v64h112a79.974 79.974 0 0 1 80 80zm-280 80a40 40 0 1 0 -40 40 39.997 39.997 0 0 0 40-40zm-8 128h-64v32h64zm96 0h-64v32h64zm104-128a40 40 0 1 0 -40 40 39.997 39.997 0 0 0 40-40zm-8 128h-64v32h64zm192-128v128a31.96166 31.96166 0 0 1 -32 32h-32v-192h32a31.96166 31.96166 0 0 1 32 32z"/></svg>';
const robotRepeatIco = '<svg style="width: 23px; fill: white;"  viewBox="0 0 640 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/">' +
  '    <path d="M442.179,325.051L442.179,459.979C442.151,488.506 418.685,511.972 390.158,512L130.053,512C101.525,511.972 78.06,488.506 78.032,459.979L78.032,238.868C78.032,203.208 107.376,173.863 143.037,173.863L234.095,173.863L234.095,121.842C234.095,107.573 245.836,95.832 260.105,95.832C274.374,95.832 286.116,107.573 286.116,121.842L286.116,173.863L309.247,173.863C321.515,245.71 373.724,304.005 442.179,325.051ZM26.011,277.905L52.021,277.905L52.021,433.968L25.979,433.968C11.727,433.968 -0,422.241 -0,407.989L-0,303.885C-0,289.633 11.727,277.905 25.979,277.905L26.011,277.905ZM468.19,331.092C478.118,332.676 488.289,333.497 498.65,333.497C505.935,333.497 513.126,333.091 520.211,332.299L520.211,407.989C520.211,422.241 508.483,433.968 494.231,433.968L468.19,433.968L468.19,331.092ZM208.084,407.958L156.063,407.958L156.063,433.968L208.084,433.968L208.084,407.958ZM286.116,407.958L234.095,407.958L234.095,433.968L286.116,433.968L286.116,407.958ZM364.147,407.958L312.126,407.958L312.126,433.968L364.147,433.968L364.147,407.958ZM214.587,303.916C214.587,286.08 199.91,271.403 182.074,271.403C164.238,271.403 149.561,286.08 149.561,303.916C149.561,321.752 164.238,336.429 182.074,336.429C182.075,336.429 182.075,336.429 182.076,336.429C199.911,336.429 214.587,321.753 214.587,303.918C214.587,303.917 214.587,303.917 214.587,303.916ZM370.65,303.916C370.65,286.08 355.973,271.403 338.137,271.403C320.301,271.403 305.624,286.08 305.624,303.916C305.624,321.752 320.301,336.429 338.137,336.429C338.138,336.429 338.139,336.429 338.139,336.429C355.974,336.429 370.65,321.753 370.65,303.918C370.65,303.917 370.65,303.917 370.65,303.916Z" style="fill-rule:nonzero;"/>\n' +
  '    <g transform="matrix(14.135,0,0,14.135,329.029,-28.2701)">\n' +
  '        <path d="M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2ZM17.19,15.94C17.15,16.03 17.1,16.11 17.03,16.18L15.34,17.87C15.19,18.02 15,18.09 14.81,18.09C14.62,18.09 14.43,18.02 14.28,17.87C13.99,17.58 13.99,17.1 14.28,16.81L14.69,16.4L9.1,16.4C7.8,16.4 6.75,15.34 6.75,14.05L6.75,12.28C6.75,11.87 7.09,11.53 7.5,11.53C7.91,11.53 8.25,11.87 8.25,12.28L8.25,14.05C8.25,14.52 8.63,14.9 9.1,14.9L14.69,14.9L14.28,14.49C13.99,14.2 13.99,13.72 14.28,13.43C14.57,13.14 15.05,13.14 15.34,13.43L17.03,15.12C17.1,15.19 17.15,15.27 17.19,15.36C17.27,15.55 17.27,15.76 17.19,15.94ZM17.25,11.72C17.25,12.13 16.91,12.47 16.5,12.47C16.09,12.47 15.75,12.13 15.75,11.72L15.75,9.95C15.75,9.48 15.37,9.1 14.9,9.1L9.31,9.1L9.72,9.5C10.01,9.79 10.01,10.27 9.72,10.56C9.57,10.71 9.38,10.78 9.19,10.78C9,10.78 8.81,10.71 8.66,10.56L6.97,8.87C6.9,8.8 6.85,8.72 6.81,8.63C6.73,8.45 6.73,8.24 6.81,8.06C6.85,7.97 6.9,7.88 6.97,7.81L8.66,6.12C8.95,5.83 9.43,5.83 9.72,6.12C10.01,6.41 10.01,6.89 9.72,7.18L9.31,7.59L14.9,7.59C16.2,7.59 17.25,8.65 17.25,9.94L17.25,11.72Z" style="fill-rule:nonzero;"/>\n' +
  '    </g>' +
  '</svg>';


(function () {
  'use strict';
  enableDebugMode();
  debugLog(jq.fn.jquery);

  const getModal = () => jq(".bg-black\\/80").next();
  const getBtnDotFromTextArea = (textArea) => textArea.parent().find('button .rounded-full').first();

  const isCopilotOn = (el) => {
    return el.hasClass('text-super');
  }

  const toggleBtnDot = (btnDot, value) => {
    debugLog('btnDot', btnDot);

    const btnDotInner = btnDot.find('.rounded-full');

    if (!btnDotInner.hasClass('bg-super') && value === true) {
      clickAndHold(btnDot, () => {
      }, 500);
      clickAndHold(btnDot, () => {
      }, 500);
    }
  }

  const checkForCopilotToggleState = (timer, checkCondition, submitWhenTrue, submitButtonSelector) => {
    debugLog("checkForCopilotToggleState run", timer, checkCondition(), submitWhenTrue, submitButtonSelector);
    if (checkCondition()) {
      clearInterval(timer);
      debugLog("checkForCopilotToggleState condition met, interval cleared");
      const submitBtn = submitButtonSelector
      if (submitWhenTrue) {
        clickAndHold(submitBtn, () => {
        }, 500);
      }
    }
  }

  const clickAndHold = (selector, callback, holdTime = 500) => {
    setTimeout(() => {
      let holdTimer;

      selector.mousedown(() => {
        holdTimer = setTimeout(() => {
          callback();
        }, holdTime);
      }).mouseup(() => {
        clearTimeout(holdTimer);
      });

      selector.click();
    }, 500)
  };

  const changeValueUsingEvent = (selector, value) => {
    debugLog('changeValueUsingEvent', value, selector);

    const nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
    nativeTextareaValueSetter.call(selector, value);
    const inputEvent = new Event('input', {bubbles: true});
    selector.dispatchEvent(inputEvent);
  }

  const openNewThreadModal = (lastQuery) => {
    debugLog('openNewThreadModal', lastQuery)

    const newThreadText = jq(".sticky div").filter(function () {
      return /^New Thread$/i.test(jq(this).text());
    });

    newThreadText.click();
    setTimeout(() => {
        debugLog('newThreadText.click()');
        const modal = getModal();

        if (modal.length > 0) {
          const textArea = modal.find('textarea');
          debugLog('textArea.length should be 1', textArea.length);

          const newTextArea = textArea.last();
          const textareaElement = newTextArea[0];
          debugLog('textareaElement', textareaElement);
          changeValueUsingEvent(textareaElement, lastQuery);

          const btnDot = getBtnDotFromTextArea(newTextArea);
          const btn = btnDot.parent().parent().parent();

          toggleBtnDot(btnDot, true);
          const isCopilotOnBtn = () => isCopilotOn(btn);

          const copilotCheck = () => {
            const ctx = {timer: null};
            ctx.timer = setInterval(() => checkForCopilotToggleState(ctx.timer, isCopilotOnBtn, true, btn.next()), 500);
          }

          copilotCheck()
        } else {
          debugLog('modal.length > 0', textArea)
        }
      },
      2000);
  }

  const getLastQuery = () => {
    const lastQueryBoxU = jq('svg[data-icon="arrow-down-right"]').last().parent().parent().parent().parent().parent();
    const wasCopilotUsed = lastQueryBoxU.prev().find('svg[data-icon="star-christmas"]').length > 0;

    return wasCopilotUsed ? lastQueryBoxU.prev().prev().text() : lastQueryBoxU.prev().text();
  }

  setInterval(() => {
    const controlsArea = jq('#ppl-message-scroll-target ~ div textarea ~ div');
    const lastQueryBoxText = getLastQuery();


    const mainTextArea = controlsArea.prev()

    if (lastQueryBoxText) {
      const copilotNewThread = jq('#copilot_new_thread');
      const copilotRepeatLast = jq('#copilot_repeat_last');

      if (controlsArea.length > 0 && copilotNewThread.length < 1) {
        controlsArea.append(button('copilot_new_thread', robotIco, "Starts new thread for with last query text and Copilot ON"));
      }

      if (controlsArea.length > 0 && copilotRepeatLast.length < 1)
        controlsArea.append(button('copilot_repeat_last', robotRepeatIco, "Repeats last query with Copilot ON"));

      if (!copilotNewThread.attr('data-has-custom-click-event')) {
        copilotNewThread.on("click", function () {
          debugLog('copilotNewThread Button clicked!');
          openNewThreadModal(getLastQuery());
        })
        copilotNewThread.attr('data-has-custom-click-event', true);
      }

      if (!copilotRepeatLast.attr('data-has-custom-click-event')) {
        copilotRepeatLast.on("click", function () {
          const textAreaElement = mainTextArea[0];
          debugLog('mainTextArea', mainTextArea);

          const btnDot = getBtnDotFromTextArea(mainTextArea)

          changeValueUsingEvent(textAreaElement, getLastQuery());

          toggleBtnDot(btnDot, true);
          const btn = btnDot.parent().parent().parent();
          const isCopilotOnBtn = () => isCopilotOn(btn);

          const copilotCheck = () => {
            const ctx = {timer: null};
            ctx.timer = setInterval(() => checkForCopilotToggleState(ctx.timer, isCopilotOnBtn, true, btn.next()), 500);
          }

          copilotCheck();
          debugLog('copilot_repeat_last Button clicked!');
        })
        copilotRepeatLast.attr('data-has-custom-click-event', true);
      }
    }
  }, 1000)
}());