Perplexity helper

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

Versão de: 22/05/2024. Veja: a última versão.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name        Perplexity helper
// @namespace   Tiartyos
// @match       https://www.perplexity.ai/*
// @grant       none
// @version     2.6
// @author      Tiartyos, monnef
// @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
// @require     https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/lodash-fp/0.10.4/lodash-fp.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.min.js
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/index.unpkg.umd.js
// @require     https://cdn.jsdelivr.net/npm/[email protected]/dist/showdown.min.js
// @homepageURL https://www.perplexity.ai/
// @license     GPL-3.0-or-later
// ==/UserScript==

const jq = $.noConflict();
const $c = (cls, parent) => jq(`.${cls}`, parent);
const $i = (id, parent) => jq(`#${id}`, parent);
const takeStr = n => str => str.slice(0, n);
const dropStr = n => str => str.slice(n);
const nl = '\n';
const markdownConverter = new showdown.Converter();

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

const logPrefix = '[Perplexity helper]';

const debugLog = (...args) => {
  if (debugMode) {
    console.debug(logPrefix, ...args);
  }
}

let debugTags = false;
const debugLogTags = (...args) => {
  if (debugTags) {
    console.debug(logPrefix, '[tags]', ...args);
  }
}

($ => {
  $.fn.nthParent = function (n) {
    let $p = $(this);
    if (!(n > -0)) { return $() }
    let p = 1 + n;
    while (p--) { $p = $p.parent(); }
    return $p;
  };
})(jq);

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

const upperButton = (id, icoName, title) => `
<div title="${title}" id="${id}" class="border rounded-full px-sm py-xs flex items-center gap-x-sm  border-borderMain/60 dark:border-borderMainDark/60 divide-borderMain dark:divide-borderMainDark ring-borderMain dark:ring-borderMainDark  bg-transparent cursor-pointer"><div class="border-borderMain/60 dark:border-borderMainDark/60 divide-borderMain dark:divide-borderMainDark ring-borderMain dark:ring-borderMainDark  bg-transparent"><div class="flex items-center gap-x-xs transition duration-300 select-none hover:text-superAlt light font-sans text-sm font-medium text-textOff dark:text-textOffDark selection:bg-super selection:text-white dark:selection:bg-opacity-50 selection:bg-opacity-70"><div class="">${icoName}<path fill="currentColor" d="M64 288L39.8 263.8C14.3 238.3 0 203.8 0 167.8C0 92.8 60.8 32 135.8 32c36 0 70.5 14.3 96 39.8L256 96l24.2-24.2c25.5-25.5 60-39.8 96-39.8C451.2 32 512 92.8 512 167.8c0 36-14.3 70.5-39.8 96L448 288 256 480 64 288z"></path></svg></div><div></div></div></div></div>
`

const textButton = (id, text, title) => `
<button title="${title}" id="${id}" type="button" 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-md cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-sm px-sm font-medium h-8">
<div class="flex items-center leading-none justify-center gap-xs"><span class="flex items-center relative ">${text}</span></div></button>
`
const icoColor = '#1F1F1F';
const robotIco = `<svg style="width: 23px; fill: ${icoColor};" 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: ${icoColor};"  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;"/>
    <g transform="matrix(14.135,0,0,14.135,329.029,-28.2701)">
        <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;"/>
    </g></svg>`;

const cogIco = `<svg style="width: 23px; fill: rgb(141, 145, 145);" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"viewBox="0 0 38.297 38.297"
\t xml:space="preserve">
<g>
\t<path d="M25.311,18.136l2.039-2.041l-2.492-2.492l-2.039,2.041c-1.355-0.98-2.941-1.654-4.664-1.934v-2.882H14.63v2.883
\t\tc-1.722,0.278-3.308,0.953-4.662,1.934l-2.041-2.041l-2.492,2.492l2.041,2.041c-0.98,1.354-1.656,2.941-1.937,4.662H2.658v3.523
\t\tH5.54c0.279,1.723,0.955,3.309,1.937,4.664l-2.041,2.039l2.492,2.492l2.041-2.039c1.354,0.979,2.94,1.653,4.662,1.936v2.883h3.524
\t\tv-2.883c1.723-0.279,3.309-0.955,4.664-1.936l2.039,2.039l2.492-2.492l-2.039-2.039c0.98-1.355,1.654-2.941,1.934-4.664h2.885
\t\tv-3.524h-2.885C26.967,21.078,26.293,19.492,25.311,18.136z M16.393,30.869c-3.479,0-6.309-2.83-6.309-6.307
\t\tc0-3.479,2.83-6.308,6.309-6.308c3.479,0,6.307,2.828,6.307,6.308C22.699,28.039,19.871,30.869,16.393,30.869z M35.639,8.113v-2.35
\t\th-0.965c-0.16-0.809-0.474-1.561-0.918-2.221l0.682-0.683l-1.664-1.66l-0.68,0.683c-0.658-0.445-1.41-0.76-2.217-0.918V0h-2.351
\t\tv0.965c-0.81,0.158-1.562,0.473-2.219,0.918L24.625,1.2l-1.662,1.66l0.683,0.683c-0.445,0.66-0.761,1.412-0.918,2.221h-0.966v2.35
\t\th0.966c0.157,0.807,0.473,1.559,0.918,2.217l-0.681,0.68l1.658,1.664l0.685-0.682c0.657,0.443,1.409,0.758,2.219,0.916v0.967h2.351
\t\tv-0.968c0.807-0.158,1.559-0.473,2.217-0.916l0.682,0.68l1.662-1.66l-0.682-0.682c0.444-0.658,0.758-1.41,0.918-2.217H35.639
\t\tL35.639,8.113z M28.701,10.677c-2.062,0-3.74-1.678-3.74-3.74c0-2.064,1.679-3.742,3.74-3.742c2.064,0,3.742,1.678,3.742,3.742
\t\tC32.443,9,30.766,10.677,28.701,10.677z"/>
</g>
</svg>`;


const perplexityHelperModalId = 'perplexityHelperModal';
const getPerplexityHelperModal = () => $i(perplexityHelperModalId);

const modalHTML = `
<div id="${perplexityHelperModalId}" class="modal">
  <div class="modal-content">
    <span class="close">&times;</span>
    <h1>Perplexity Helper settings</h1>
  </div>
</div>
`;

const genCssName = x => `perplexity-helper--${x}`;

const tagsContainerCls = genCssName('tags-container');
const threadTagContainerCls = genCssName('thread-tag-container');
const newTagContainerCls = genCssName('new-tag-container');
const tagCls = genCssName('tag');
const tagDarkTextCls = genCssName('tag-dark-text');
const tagPaletteCls = genCssName('tag-palette');
const tagPaletteItemCls = genCssName('tag-palette-item');
const tagsPreviewCls = genCssName('tags-preview');
const tagsPreviewNewCls = genCssName('tags-preview-new');
const tagsPreviewThreadCls = genCssName('tags-preview-thread');
const helpTextCls = genCssName('help-text');
const queryBoxCls = genCssName('query-box');
const controlsAreaCls = genCssName('controls-area');
const textAreaCls = genCssName('text-area');
const standardButtonCls = genCssName('standard-button');
const roundedMD = genCssName('rounded-md');

const topSettingsButtonId = genCssName('settings-button-top');
const leftSettingsButtonId = genCssName('settings-button-left');
const leftSettingsButtonWrapperId = genCssName('settings-button-left-wrapper');

const styles = `
.checkbox_label {
  color: white;
}

.textarea_wrapper {
  display: flex;
  flex-direction: column;
}

.textarea_wrapper > textarea {
  width: 100%;
  background-color: rgba(0, 0, 0, 0.8);
  padding: 0 5px;
}

.textarea_label {
  margin-right: auto;
}

.${helpTextCls} {
  max-width: 580px;
  background-color: #225;
  padding: 0.3em 0.7em;
  border-radius: 0.5em;
  margin: 1em 0;
}
.${helpTextCls} {
  cursor: text;
}
.${helpTextCls} code {
  font-size: 80%;
  background-color: rgba(255, 255, 255, 0.1);
  border-radius: 0.3em;
  padding: 0.1em;
}
.${helpTextCls} pre > code {
  background: none;
}
.${helpTextCls} pre {
  font-size: 80%;
  overflow: auto;
  background-color: rgba(255, 255, 255, 0.1);
  border-radius: 0.3em;
  padding: 0.1em 1em;
}
.${helpTextCls} li {
  list-style: circle;
  margin-left: 1em;
}
.${helpTextCls} hr {
  margin: 1em 0 0.5em 0;
  border-color: rgba(255, 255, 255, 0.1);
}

.btn-helper {
margin-left: 20px
}

.modal {
  display: none;
  position: fixed;
  z-index: 1000;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: auto;
  background-color: rgba(0, 0, 0, 0.8)
}

.modal-content {
  display: flex;
  margin: 1em auto;
  width: calc(100vw - 2em);
  padding: 20px;
  border: 1px solid #888;
  background-color: #202025;
  border-radius: 6px;
  color: rgb(206, 206, 210);
  flex-direction: column;
  position: relative;
  row-gap: 10px;
  overflow-y: auto;
  cursor: default;
}

.modal-content label {
  padding-right: 10px;
}

.modal-content h1 {
  margin-bottom: 0.5em;
  border-bottom: 1px solid #888;
}

.close {
  color: rgb(206, 206, 210);
  float: right;
  font-size: 28px;
  font-weight: bold;
  position: absolute;
  right: 10px;
  top: 5px;
}

.close:hover,
.close:focus {
  color: white;
  text-decoration: none;
  cursor: pointer;
}

#copied-modal,#copied-modal-2 {
  padding: 5px 5px;
  background:gray;
  position:absolute;
  display: none;
  color: white;
  font-size: 15px;
}

label > div.select-none {
  user-select: text;
  cursor: initial;
}

.${tagsContainerCls} {
  display: flex;
  gap: 5px;
  margin: 5px 0;
  flex-wrap: wrap;
}
.${tagsContainerCls}.${threadTagContainerCls} {
  margin-left: 0.5em;
  margin-right: 0.5em;
  margin-bottom: 2px;
}

.${tagCls} {
  border: 1px solid #3b3b3b;
  background-color: #282828;
  /*color: rgba(255, 255, 255, 0.482);*/ /* equivalent of #909090; when on #282828 background */
  padding: 0px 8px 0 8px;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s;
  display: inline-block;
  color: #D0D0D0;
}
.${tagCls}.${tagDarkTextCls} {
  color: #333333;
  text-shadow: 1px 0 0.5px white, -1px 0 0.5px white, 0 1px 0.5px white, 0 -1px 0.5px white;
}
.${tagCls} span {
  position: relative;
  top: 1.5px;
  text-shadow: 1px 0 0.5px black, -1px 0 0.5px black, 0 1px 0.5px black, 0 -1px 0.5px black;
}
.${tagCls}:hover {
  background-color: #333;
  color: #fff;
}
.${tagCls}.${tagDarkTextCls}:hover {
  /* color: #171717; */
  color: #2f2f2f;
}

.${tagPaletteCls} {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
}
.${tagPaletteCls} .${tagPaletteItemCls} {
  text-shadow: 0 0 4px black;
  width: 35px;
  height: 20px;
  display: inline-block;
  text-align: center;
  border-radius: 0.2em;
  padding: 0 2px;
  transition: color 0.2s;
}

.${tagPaletteItemCls}:hover {
  cursor: pointer;
  color: white;
}

.${tagsPreviewCls} {
  background-color: #191a1a;
  padding: 0.5em 1em;
  border-radius: 1em;
}

.${tagsPreviewNewCls}:before {
  content: 'Target New: ';
}

.${tagsPreviewThreadCls}:before {
  content: 'Target Thread: ';
}

.${queryBoxCls} {
  flex-wrap: wrap;
}

.${controlsAreaCls} {
  grid-template-columns: repeat(4,minmax(0,1fr))
}

.${textAreaCls} {
  grid-column-end: 5;
}

.${standardButtonCls} {
  grid-column-start: 4;
}

.${roundedMD} {
  border-radius: 0.375rem!important;
}

#${leftSettingsButtonId} svg {
  transition: fill 0.2s;
}
#${leftSettingsButtonId}:hover svg {
  fill: #fff !important;
}

.w-collapsedSideBarWidth #${leftSettingsButtonId} span {
  display: none;
}

.w-collapsedSideBarWidth #${leftSettingsButtonId} {
  width: 100%;
  border-radius: 0.25rem;
  height: 40px;
}

#${leftSettingsButtonWrapperId} {
  display: flex;
  padding: 0.1em 0.4em;
  justify-content: flex-end;
}

.w-collapsedSideBarWidth #${leftSettingsButtonWrapperId} {
  justify-content: center;
}
`;

const TAG_POSITION = {
  BEFORE: 'before',
  AFTER: 'after',
  CARET: 'caret',
};

const TAG_CONTAINER_TYPE = {
  NEW: 'new',
  THREAD: 'thread',
  ALL: 'all',
}

const tagsHelpText = `
Each line is one tag.
Non-field text is what will be inserted into prompt.
Field is denoted by \`<\` and \`>\`, field name is before \`:\`, field value after \`:\`.

Supported fields:
- \`label\`: tag label shown on tag "box" (new items around prompt input area)
- \`position\`: where the tag text will be inserted, default is \`before\`; valid values are \`before\`/\`after\` (existing text) or \`caret\` (at cursor position)
- \`color\`: tag color; CSS colors supported, you can use colors from a pre-generated palette via \`%\` syntax, e.g. \`<color:%5>\`. See palette bellow.
- \`tooltip\`: shown on hover (aka title); (default) tooltip can be disabled when this field is set to empty string - \`<tooltip:>\`
- \`target\`: where the tag will be inserted, default is \`new\`; valid values are \`new\` (on home page or when clicking on "New Thread" button) / \`thread\` (on thread page) / \`all\` (everywhere)

---

Examples:
\`\`\`
Vintage Story -
stable diffusion web ui - <label:SDWU>
, prefer concise modern syntax and style, <position:caret><label:concise modern>
FFXIV: <color:%15><label:FFXIV>
tell me a joke<label:Joke><tooltip:>
\`\`\`
`.trim();

const defaultTagColor = '#282828';

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 cyanButtonPerplexityColor = '#1fb8cd';

const TAGS_PALETTE_COLORS_NUM = 16;
const TAGS_PALETTE = Object.freeze((() => {
  const step = 360 / TAGS_PALETTE_COLORS_NUM;
  const [startH, startS, startL, startA] = color2k.parseToHsla(cyanButtonPerplexityColor);
  return _.flow(
    _.map(x => startH + x * step, _),
    _.map(h => color2k.hsla(h, startS, startL, startA), _),
    _.sortBy(x => color2k.parseToHsla(x)[0], _)
  )(_.range(0, TAGS_PALETTE_COLORS_NUM));
})());

debugLogTags('TAGS_PALETTE', TAGS_PALETTE);

const convertColorInPaletteFormat = value => TAGS_PALETTE[parseInt(dropStr(1)(value), 10)] ?? defaultTagColor

const processTagField = name => value => {
  if (name === 'color' && value.startsWith('%')) return convertColorInPaletteFormat(value);
  return value;
}

const tagLineRegex = /<(label|position|color|tooltip|target):([^<>]*)>/g;
const parseOneTagLine = (line) =>
  Array.from(line.matchAll(tagLineRegex)).reduce(
    (acc, match) => {
      const [fullMatch, field, value] = match;
      const processedValue = processTagField(field)(value);
      return {
        ...acc,
        [field]: processedValue,
        text: acc.text.replace(fullMatch, '').replace(/\\n/g, '\n'),
      };
    },
    {text: line, color: defaultTagColor, target: TAG_CONTAINER_TYPE.NEW}
  );

const parseTagsText = text => {
  const lines = text.split('\n').filter(tag => tag.trim().length > 0);
  return lines.map(parseOneTagLine);
}

const getTagsContainer = () => $c(tagsContainerCls);

const promptAreaOfNewThreadSelector = 'textarea[placeholder="Ask anything..."]';
const getPromptAreaOfNewThread = () => jq(promptAreaOfNewThreadSelector);
const getPromptAreaWrapperOfNewThread = () => getPromptAreaOfNewThread().parent().parent().parent().parent();

const promptAreaOnThreadSelector = 'textarea[placeholder="Ask follow-up"]';
const getPromptAreaOnThread = () => jq(promptAreaOnThreadSelector);
const getPromptAreaWrapperOnThread = () => getPromptAreaOnThread().parent().parent().parent().parent();

const anyPromptAreaSelector = `${promptAreaOfNewThreadSelector},${promptAreaOnThreadSelector}`;

const posFromTag = tag => Object.values(TAG_POSITION).includes(tag.position) ? tag.position : TAG_POSITION.BEFORE;

const applyTagToString = (tag, val, caretPos) => {
  const {text} = tag;
  switch (posFromTag(tag)) {
    case TAG_POSITION.BEFORE:
      return `${text}${val}`;
    case TAG_POSITION.AFTER:
      return `${val}${text}`;
    case TAG_POSITION.CARET:
      return `${takeStr(caretPos)(val)}${text}${dropStr(caretPos)(val)}`;
    default:
      throw new Error(`Invalid position: ${tag.position}`);
  }
};

const getPromptAreaFromTagsContainer = tagsContainerEl => tagsContainerEl.parent().find(anyPromptAreaSelector);

const createTag = containerEl => isPreview => tag => {
  const labelString = tag.label ?? tag.text;
  const isTagLight = color2k.getLuminance(tag.color) > 0.35;
  const colorMod = isTagLight ? color2k.darken : color2k.lighten;
  const hoverBgColor = color2k.toRgba(colorMod(tag.color, 0.1));
  const borderColor = color2k.toRgba(colorMod(tag.color, 0.1));
  const clickHandler = evt => {
    debugLog('clicked', tag, evt);
    const el = jq(evt.currentTarget);
    const promptArea = getPromptAreaFromTagsContainer(el.parent());
    if (!promptArea.length) {
      debugLogTags('no prompt area found', promptArea);
      return;
    }
    const promptAreaRaw = promptArea[0];
    const newText = applyTagToString(tag, promptArea.val(), promptAreaRaw.selectionStart);
    changeValueUsingEvent(promptAreaRaw, newText);
    promptAreaRaw.focus();
  };
  const tagEl =
    jq(`<div/>`)
      .addClass(tagCls)
      .prop('title', tag.tooltip ?? `${logPrefix} Insert \`${tag.text}\` at position \`${posFromTag(tag)}\``)
      .attr('data-tag', JSON.stringify(tag))
      .click(isPreview ? null : clickHandler)
      .css({
        backgroundColor: tag.color,
        borderColor,
      })
      .attr('data-color', color2k.toHex(tag.color))
      .attr('data-hoverBgColor', color2k.toHex(hoverBgColor))
      .on('mouseenter', (event) => {
        jq(event.currentTarget).css('background-color', hoverBgColor);
      })
      .on('mouseleave', (event) => {
        jq(event.currentTarget).css('background-color', tag.color);
      })
  ;
  if (isTagLight) {
    tagEl.addClass(tagDarkTextCls);
  }
  const textEl = jq('<span/>')
    .text(labelString)
    .css({
      // TODO: either move tag text shadows to options or remove styles and this override
      textShadow: 'none'
    })
  ;

  tagEl.append(textEl);
  containerEl.append(tagEl);
  return tagEl;
};

const genDebugFakeTags = () =>
  _.times(TAGS_PALETTE_COLORS_NUM, x => `Fake ${x} ${_.times(x / 3).map(() => 'x').join('')}<color:%${x % TAGS_PALETTE_COLORS_NUM}>`)
    .join('\n');

const getTagContainerType = containerEl => {
  if (containerEl.hasClass(threadTagContainerCls) || containerEl.hasClass(tagsPreviewThreadCls)) return TAG_CONTAINER_TYPE.THREAD;
  if (containerEl.hasClass(newTagContainerCls) || containerEl.hasClass(tagsPreviewNewCls)) return TAG_CONTAINER_TYPE.NEW;
  return null;
}

const isTagRelevantForContainer = containerType => tag => containerType === tag.target || tag.target === TAG_CONTAINER_TYPE.ALL

const refreshTags = () => {
  const promptWrapper = getPromptAreaWrapperOfNewThread().add(getPromptAreaWrapperOnThread());
  if (!promptWrapper.length) {
    debugLogTags('no prompt area found');
  }
  const allTags = _.flow(
    x => x + (unsafeWindow.phFakeTags ? `${nl}${genDebugFakeTags()}${nl}` : ''),
    parseTagsText,
  )(loadConfig().tagsText ?? defaultConfig.tagsText);
  debugLogTags('refreshing allTags', allTags);

  const createContainer = (promptWrapper) => {
    const el = jq(`<div/>`).addClass(tagsContainerCls);
    if (promptWrapper.find(promptAreaOnThreadSelector).length) {
      el.addClass(threadTagContainerCls);
    }
    if (promptWrapper.find(promptAreaOfNewThreadSelector).length) {
      el.addClass(newTagContainerCls);
    }
    return el;
  }
  promptWrapper.each((_, rEl) => {
    const el = jq(rEl);
    if (el.parent().find(`.${tagsContainerCls}`).length) {
      el.parent().addClass(queryBoxCls);
      return;
    }
    el.before(createContainer(el));
  });

  const containerEls = getTagsContainer();
  containerEls.each((_i, rEl) => {
    const containerEl = jq(rEl);
    const isPreview = Boolean(containerEl.attr('data-preview'));
    const currentTags = containerEl.find(`.${tagCls}`).map((i, el) => JSON.parse(el.dataset.tag)).toArray();
    const tagContainerType = getTagContainerType(containerEl);
    const tagsForThisContainer = allTags.filter(isTagRelevantForContainer(tagContainerType));
    debugLogTags('tagContainerType', tagContainerType, 'current tags', currentTags, 'tagsForThisContainer', tagsForThisContainer);
    if (_.isEqual(currentTags, tagsForThisContainer)) {
      debugLogTags('no tags changed');
      return;
    }
    containerEl.empty();
    tagsForThisContainer.forEach(createTag(containerEl)(isPreview));
  });
}

const setupTags = () => {
  debugLog('setting up tags');
  setInterval(refreshTags, 500);
}

const defaultConfig = Object.freeze({
  showCopilot: true,
  showCopilotNewThread: true,
  showCopilotRepeatLast: true,
  showCopilotCopyPlaceholder: true,
  tagsText: '',
  debugMode: false,
});

// TODO: if still using local storage, at least it should be prefixed with user script name
const storageKey = 'checkBoxStates';

const loadConfig = () => {
  try {
    // TODO: use storage from GM API
    const val = JSON.parse(localStorage.getItem(storageKey));
    // debugLog('loaded config', val);
    return val;
  } catch (e) {
    console.error('Failed to load config, using default', e);
    return defaultConfig;
  }
}

const loadConfigOrDefault = () => loadConfig() ?? defaultConfig

const saveConfig = cfg => {
  debugLog('saving config', cfg);
  localStorage.setItem(storageKey, JSON.stringify(cfg));
};

const createCheckbox = (id, labelText, onChange) => {
  debugLog("createCheckbox", id);
  const checkbox = jq(`<input type="checkbox" id=${id}>`);
  const label = jq(`<label class="checkbox_label" for="${id}">${labelText}</label>`);
  const checkboxWithLabel = jq('<div class="checkbox_wrapper"></div>').append(label).append(checkbox);
  debugLog('checkboxwithlabel', checkboxWithLabel);

  getSettingsModalContent().append(checkboxWithLabel);
  checkbox.on('change', onChange);
  return checkbox;
};

const createTextArea = (id, labelText, onChange, helpText) => {
  debugLog("createTextArea", id);
  const textarea = jq(`<textarea id=${id}></textarea>`);
  const label = jq(`<label class="textarea_label">${labelText}${helpText ? ' 📖' : ''}</label>`);
  const textareaWithLabel = jq('<div class="textarea_wrapper"></div>').append(label);
  if (helpText) {
    const help = jq(`<div/>`).addClass(helpTextCls).html(markdownConverter.makeHtml(helpText)).append(jq('<br/>'));
    help.append(jq('<button/>').text('[Close help]').on('click', () => help.hide()));
    textareaWithLabel.append(help);
    label
      .css({cursor: 'pointer'})
      .on('click', () => help.toggle())
      .prop('title', 'Click to toggle help')
    ;
    help.hide();
  }
  textareaWithLabel.append(textarea);
  debugLog('textareaWithLabel', textareaWithLabel);

  getSettingsModalContent().append(textareaWithLabel);
  textarea.on('change', onChange);
  return textarea;
}

const createPaletteLegend = () => {
  const wrapper = jq('<div/>')
    .addClass(tagPaletteCls)
    .append(jq('<span>').html('Palette of color codes:&nbsp;'))
  ;
  TAGS_PALETTE.forEach((color, i) => {
    const colorCode = `%${i}`;
    const colorPart = genColorPart(colorCode);
    jq('<span/>')
      .text(colorCode)
      .addClass(tagPaletteItemCls)
      .css({
        'background-color': color,
      })
      .prop('title', `Copy ${colorPart} to clipboard`)
      .click(() => {
        copyTextToClipboard(colorPart);
      })
      .appendTo(wrapper);
  });
  getSettingsModalContent().append(wrapper);
}

const createColorInput = (id, labelText, onChange) => {
  debugLog("createColorInput", id);
  const input = jq(`<input type="color" id=${id}>`);
  const label = jq(`<label class="color_label">${labelText}</label>`);
  const inputWithLabel = jq('<div class="color_wrapper"></div>').append(label).append(input);
  debugLog('inputWithLabel', inputWithLabel);

  getSettingsModalContent().append(inputWithLabel);
  input.on('change', onChange);
  return input;
}

const createTagsPreview = () => {
  const wrapper = jq('<div/>')
    .addClass(tagsPreviewCls)
    .append(jq('<div>').html('Preview'))
    .append(jq('<div>').addClass(tagsPreviewNewCls).addClass(tagsContainerCls).attr('data-preview', 'true'))
    .append(jq('<div>').addClass(tagsPreviewThreadCls).addClass(tagsContainerCls).attr('data-preview', 'true'))
  ;
  getSettingsModalContent().append(wrapper);
}

const coPilotNewThreadAutoSubmitCheckboxId = 'coPilotNewThreadAutoSubmit';
const getCoPilotNewThreadAutoSubmitCheckbox = () => $i(coPilotNewThreadAutoSubmitCheckboxId);

const coPilotRepeatLastAutoSubmitCheckboxId = 'coPilotRepeatLastAutoSubmit';
const getCoPilotRepeatLastAutoSubmitCheckbox = () => $i(coPilotRepeatLastAutoSubmitCheckboxId);

const hideSideMenuCheckboxId = 'hideSideMenu';
const getHideSideMenuCheckbox = () => $i(hideSideMenuCheckboxId);

const tagsTextAreaId = 'tagsText';
const getTagsTextArea = () => $i(tagsTextAreaId);

const tagColorPickerId = genCssName('tagColorPicker');
const getTagColorPicker = () => $i(tagColorPickerId);

const enableDebugCheckboxId = genCssName('enableDebug');
const getEnableDebugCheckbox = () => $i(enableDebugCheckboxId);

const copyTextToClipboard = async text => {
  try {
    await navigator.clipboard.writeText(text);
    console.log('Text copied to clipboard', {text});
  } catch (err) {
    console.error('Failed to copy text: ', err);
  }
};

const genColorPart = color => `<color:${color}>`;

function handleSettingsInit() {
  const modalExists = getPerplexityHelperModal().length > 0;
  const firstCheckboxExists = getCoPilotNewThreadAutoSubmitCheckbox().length > 0;

  if (!modalExists || firstCheckboxExists) { return; }

  const insertSeparator = () => getSettingsModalContent().append('<hr/>');

  createCheckbox(coPilotNewThreadAutoSubmitCheckboxId, 'Auto Submit New Thread With CoPilot', saveConfigFromForm);
  createCheckbox(coPilotRepeatLastAutoSubmitCheckboxId, 'Auto Submit Repeat With CoPilot', saveConfigFromForm);
  createCheckbox(hideSideMenuCheckboxId, 'Hide Side Menu', saveConfigFromForm);

  insertSeparator();

  createTextArea(tagsTextAreaId, 'Tags', saveConfigFromForm, tagsHelpText).prop('rows', 5).css('min-width', '700px').prop('wrap', 'off');
  createPaletteLegend();
  createColorInput(tagColorPickerId, 'Custom color - copy field for tag to clipboard:', () => {
    const color = getTagColorPicker().val();
    debugLog('color', color);
    copyTextToClipboard(genColorPart(color));
  });
  createTagsPreview();

  insertSeparator();

  createCheckbox(enableDebugCheckboxId, 'Enable Debug', saveConfigFromForm);

  const savedStates = JSON.parse(localStorage.getItem(storageKey));
  if (savedStates === null) { return; }

  getCoPilotNewThreadAutoSubmitCheckbox().prop('checked', savedStates.coPilotNewThreadAutoSubmit);
  getCoPilotRepeatLastAutoSubmitCheckbox().prop('checked', savedStates.coPilotRepeatLastAutoSubmit);
  getHideSideMenuCheckbox().prop('checked', savedStates.hideSideMenu);
  getTagsTextArea().val(savedStates.tagsText);
  getEnableDebugCheckbox().prop('checked', savedStates.enableDebug);
}

debugLog(jq.fn.jquery);
const getSavedStates = () => JSON.parse(localStorage.getItem(storageKey));

const getModal = () => jq("[data-testid='quick-search-modal'] > div");
const getCopilotToggleButton = textarea => textarea.parent().parent().find('[data-testid="copilot-toggle"]');
const upperControls = () => jq('svg[data-icon="lock"] ~ div:contains("Share")').nthParent(5).closest('.flex.justify-between:not(.grid-cols-3)');

const getControlsArea = () => jq('textarea[placeholder="Ask follow-up"]').parent().parent().children().last();

const getCopilotNewThreadButton = () => jq('#copilot_new_thread');
const getCopilotRepeatLastButton = () => jq('#copilot_repeat_last');
const getSelectAllButton = () => jq('#perplexity_helper_select_all');
const getSelectAllAndSubmitButton = () => jq('#perplexity_helper_select_all_and_submit');
const getCopyPlaceholder = () => jq('#perplexity_helper_copy_placeholder');
const getCopyAndFillInPlaceholder = () => jq('#perplexity_helper_copy_placeholder_and_fill_in');
const getTopSettingsButtonEl = () => $i(topSettingsButtonId);
const getLeftSettingsButtonEl = () => $i(leftSettingsButtonId);
const getSideMenu = () => jq('.min-h-\\[100vh\\]').children().first();
const getSettingsModalContent = () => getPerplexityHelperModal().find('.modal-content');

const getSubmitBtn0 = () => jq('svg[data-icon="arrow-up"]').last().parent().parent();
const getSubmitBtn1 = () => jq('svg[data-icon="arrow-right"]').last().parent().parent();
const getSubmitBtn2 = () => jq('svg[data-icon="code-fork"]').last().parent().parent();

const isStandardControlsAreaFc = () => !getControlsArea().hasClass('bottom-0');
const getCurrentControlsArea = () => isStandardControlsAreaFc() ? getControlsArea() : getControlsArea().find('.bottom-0');

const getDashedCheckboxButton = () => jq('svg[data-icon="square-dashed"]').parent().parent();
const getStarSVG = () => jq('svg[data-icon="star-christmas"]');
const getSpecifyQuestionBox = () => jq('svg[data-icon="star-christmas"]').parent().parent().parent().last();


const getNumberOfDashedSVGs = () => getSpecifyQuestionBox().find('svg[data-icon="square-dashed"]').length;
const getSpecifyQuestionControlsWrapper = () => getSpecifyQuestionBox().find('button:contains("Continue")').parent()
const getCopiedModal = () => jq('#copied-modal');
const getCopiedModal2 = () => jq('#copied-modal-2');
const getCopyPlaceholderInput = () => getSpecifyQuestionBox().find('textarea');


const getSubmitButton0or2 = () => getSubmitBtn0().length < 1 ? getSubmitBtn2() : getSubmitBtn0();


const questionBoxWithPlaceholderExists = () => getSpecifyQuestionBox().find('textarea')?.attr('placeholder')?.length > 0 ?? false;

const selectAllCheckboxes = () => {
  const currentCheckboxes = getDashedCheckboxButton();
  debugLog('checkboxes', currentCheckboxes);

  const removeLastObject = (arr) => {
    if (!_.isEmpty(arr)) {
      debugLog('arr', arr);
      const newArr = _.dropRight(arr, 1);
      debugLog("newArr", newArr);
      getDashedCheckboxButton().last().click();

      return setTimeout(() => {
        removeLastObject(newArr)
      }, 1)

    }
  };

  removeLastObject(currentCheckboxes);
}

const isCopilotOn = (el) => el.hasClass('text-super')

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

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

  debugLog('btnDotInner', btnDotInner);

  if (!btnDotInner.hasClass('bg-super') && value === true) {
    btnDot.click();
  }
}

const checkForCopilotToggleState = (timer, checkCondition, submitWhenTrue, submitButtonVersion) => {
  debugLog("checkForCopilotToggleState run", timer, checkCondition(), submitWhenTrue, submitButtonVersion);
  if (checkCondition()) {
    clearInterval(timer);
    debugLog("checkForCopilotToggleState condition met, interval cleared");
    const submitBtn = submitButtonVersion === 0 ? getSubmitButton0or2() : getSubmitBtn1();

    debugLog('submitBtn', submitBtn);
    if (submitWhenTrue) {
      submitBtn.click();
    }
  }
}

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

  const newThreadText = jq(".sticky div").filter(function () {
    return /^New Thread$/i.test(jq(this).text());
  });
  if (!newThreadText.length) {
    debugLog('newThreadText.length should be 1', newThreadText.length);
    return;
  }
  debugLog('newThreadText', newThreadText);

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

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

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

        const copilotButton = getCopilotToggleButton(newTextArea);

        toggleBtnDot(copilotButton, true);
        const isCopilotOnBtn = () => isCopilotOn(copilotButton);

        const coPilotNewThreadAutoSubmit =
          getSavedStates()
            ? getSavedStates().coPilotNewThreadAutoSubmit
            : getCoPilotNewThreadAutoSubmitCheckbox().prop('checked');

        const copilotCheck = () => {
          const ctx = {timer: null};
          ctx.timer = setInterval(() => checkForCopilotToggleState(ctx.timer, isCopilotOnBtn, coPilotNewThreadAutoSubmit, 1), 500);
        }

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

const getLastQuery = () => {
  // wrapper around prompt + response
  const lastQueryBox = jq('svg[data-icon="repeat"]').last().nthParent(7);
  if (lastQueryBox.length === 0) {
    debugLog('lastQueryBox not found');
  }

  const wasCopilotUsed = lastQueryBox.find('svg[data-icon="star-christmas"]').length > 0;
  const lastQueryBoxText = lastQueryBox.find('.whitespace-pre-line').text();

  debugLog('[getLastQuery]', {lastQueryBox, wasCopilotUsed, lastQueryBoxText});
  return lastQueryBoxText ?? null;
}

const saveConfigFromForm = () => {
  const checkBoxStates = {
    coPilotNewThreadAutoSubmit: getCoPilotNewThreadAutoSubmitCheckbox().prop('checked'),
    coPilotRepeatLastAutoSubmit: getCoPilotRepeatLastAutoSubmitCheckbox().prop('checked'),
    hideSideMenu: getHideSideMenuCheckbox().prop('checked'),
    tagsText: getTagsTextArea().val(),
    enableDebug: getEnableDebugCheckbox().prop('checked'),
  };
  saveConfig(checkBoxStates);
};

const showPerplexityHelperModal = () => {
  getPerplexityHelperModal().show().css('display', 'flex');
}

const hidePerplexityHelperModal = () => {
  getPerplexityHelperModal().hide();
}

const handleTopSettingsButtonInsertion = () => {
  const copilotHelperSettings = getTopSettingsButtonEl();
  debugLog('upperControls().length > 0', upperControls().length, 'copilotHelperSettings.length', copilotHelperSettings.length, 'upperControls().children().length', upperControls().children().length);
  if (upperControls().length > 0 && copilotHelperSettings.length < 1 && upperControls().children().length >= 1) {
    debugLog('inserting settings button');
    upperControls().children().eq(0).children().eq(0).append(upperButton(topSettingsButtonId, cogIco, 'Perplexity Helper Settings'));
  }
};

const handleTopSettingsButtonSetup = () => {
  const settingsButtonEl = getTopSettingsButtonEl();

  if (settingsButtonEl.length === 1 && !settingsButtonEl.attr('data-has-custom-click-event')) {
    debugLog('handleTopSettingsButtonSetup: setting up the button');
    if (settingsButtonEl.length === 0) {
      debugLog('handleTopSettingsButtonSetup: settingsButtonEl.length === 0');
    }

    settingsButtonEl.on("click", () => {
      debugLog('perplexity_helper_settings open click');
      showPerplexityHelperModal();
    });

    settingsButtonEl.attr('data-has-custom-click-event', true);
  }
};

const applySideMenuHiding = () => {
  const sideMenu = getSideMenu();
  if (getSavedStates()) getSavedStates().hideSideMenu || getHideSideMenuCheckbox().prop('checked') ? sideMenu.hide() : sideMenu.show();
};

const handleModalCreation = () => {
  if (getPerplexityHelperModal().length > 0) return;
  debugLog('handleModalCreation: creating modal');
  jq("body").append(modalHTML);

  getPerplexityHelperModal().find('.close').on('click', () => {
    debugLog('perplexity_helper_settings close  click');
    hidePerplexityHelperModal();
  });
};

const getLeftPanel = () => jq('.fixed svg[data-icon="rectangle-vertical-history"]').nthParent(6 + 2);

const handleLeftSettingsButtonSetup = () => {
  const existingLeftSettingsButton = getLeftSettingsButtonEl();
  if (existingLeftSettingsButton.length === 1) {
    const wrapper = existingLeftSettingsButton.parent();
    if (!wrapper.is(':last-child')) {
      wrapper.appendTo(wrapper.parent());
    }
    return;
  }

  const leftPanel = getLeftPanel();

  if (leftPanel.length === 0) {
    debugLog('handleLeftSettingsButtonSetup: leftPanel not found');
  }

  const wrapperEl = jq('<div>').attr('id', leftSettingsButtonWrapperId);
  const iconEl = jq(cogIco);

  const btnEl = jq('<button>')
    .attr('id', leftSettingsButtonId)
    .attr('title', 'Perplexity Helper Settings')
    .addClass('text-textOff dark:text-textOffDark font-sans text-xs flex items-center gap-x-sm')
    .addClass('md:hover:bg-offsetPlus text-textOff dark:text-textOffDark md:hover:text-textMain dark:md:hover:bg-offsetPlusDark  dark:md:hover:text-textMainDark font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-in-out font-sans  select-none items-center relative group  justify-center text-center items-center rounded-full cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-sm px-sm font-medium h-8')
    .append(jq('<span>').text('Perplexity Helper'))
    .append(iconEl)
  ;
  btnEl.on('click', () => {
    debugLog('left settings button clicked');
    showPerplexityHelperModal();
  });
  wrapperEl.append(btnEl);
  leftPanel.append(wrapperEl);
}

const work = () => {
  handleModalCreation();
  handleTopSettingsButtonInsertion();
  handleTopSettingsButtonSetup();
  handleSettingsInit();
  handleLeftSettingsButtonSetup();

  applySideMenuHiding();

  const regex = /^https:\/\/www\.perplexity\.ai\/search\/?.*/;
  const currentUrl = jq(location).attr('href');
  const matchedCurrentUrlAsSearchPage = regex.test(currentUrl);

  // debugLog("currentUrl", currentUrl);
  // debugLog("matchedCurrentUrlAsSearchPage", matchedCurrentUrlAsSearchPage);

  if (matchedCurrentUrlAsSearchPage) {
    const controlsArea = getCurrentControlsArea();
    controlsArea.addClass(controlsAreaCls);
    controlsArea.parent().find('textarea').first().addClass(textAreaCls);
    controlsArea.addClass(roundedMD);
    controlsArea.parent().addClass(roundedMD);


    if (controlsArea.length === 0) {
      debugLog('controlsArea not found', {
        controlsArea,
        currentControlsArea: getCurrentControlsArea(),
        isStandardControlsAreaFc: isStandardControlsAreaFc()
      });
    }

    const lastQueryBoxText = getLastQuery();

    const mainTextArea = isStandardControlsAreaFc() ? controlsArea.prev().prev() : controlsArea.parent().prev();

    if (mainTextArea.length === 0) {
      debugLog('mainTextArea not found', mainTextArea);
    }


    debugLog('lastQueryBoxText', {lastQueryBoxText});
    if (lastQueryBoxText) {
      const copilotNewThread = getCopilotNewThreadButton();
      const copilotRepeatLast = getCopilotRepeatLastButton();

      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", standardButtonCls));
      }

      // Due to updates in Perplexity, this is unnecessary for now
      // 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 controlsArea = getCurrentControlsArea();
          const textAreaElement = controlsArea.parent().find('textarea')[0];

          const coPilotRepeatLastAutoSubmit =
            getSavedStates()
              ? getSavedStates().coPilotRepeatLastAutoSubmit
              : getCoPilotRepeatLastAutoSubmitCheckbox().prop('checked');

          debugLog('coPilotRepeatLastAutoSubmit', coPilotRepeatLastAutoSubmit);
          changeValueUsingEvent(textAreaElement, getLastQuery());
          const copilotToggleButton = getCopilotToggleButton(mainTextArea)
          debugLog('mainTextArea', mainTextArea);
          debugLog('copilotToggleButton', copilotToggleButton);

          toggleBtnDot(copilotToggleButton, true);
          const isCopilotOnBtn = () => isCopilotOn(copilotToggleButton);

          const copilotCheck = () => {
            const ctx = {timer: null};
            ctx.timer = setInterval(() => checkForCopilotToggleState(ctx.timer, isCopilotOnBtn, coPilotRepeatLastAutoSubmit, 0), 500);
          }

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

    if (getNumberOfDashedSVGs() > 0 && getNumberOfDashedSVGs() === getDashedCheckboxButton().length
      && getSelectAllButton().length < 1 && getSelectAllAndSubmitButton().length < 1) {
      debugLog('getNumberOfDashedSVGs() === getNumberOfDashedSVGs()', getNumberOfDashedSVGs());
      debugLog('getSpecifyQuestionBox', getSpecifyQuestionBox());

      const specifyQuestionControlsWrapper = getSpecifyQuestionControlsWrapper();
      debugLog('specifyQuestionControlsWrapper', specifyQuestionControlsWrapper);
      const selectAllButton = textButton('perplexity_helper_select_all', 'Select all', 'Selects all options');
      const selectAllAndSubmitButton = textButton('perplexity_helper_select_all_and_submit', 'Select all & submit', 'Selects all options and submits');

      specifyQuestionControlsWrapper.append(selectAllButton);
      specifyQuestionControlsWrapper.append(selectAllAndSubmitButton);

      getSelectAllButton().on("click", function () {
        selectAllCheckboxes();
      })

      getSelectAllAndSubmitButton().on("click", function () {
        selectAllCheckboxes();
        setTimeout(() => {
          getSpecifyQuestionControlsWrapper().find('button:contains("Continue")').click();
        }, 200);
      })
    }

    const constructClipBoard = (buttonId, buttonGetter, modalGetter, copiedModalId, elementGetter) => {
      const placeholderValue = getSpecifyQuestionBox().find('textarea').attr('placeholder')

      const clipboardInstance = new ClipboardJS(`#${buttonId}`, {
        text: () => placeholderValue
      });

      const copiedModal = `<span id="${copiedModalId}">Copied!</span>`;
      debugLog('copiedModalId', copiedModalId);
      debugLog('copiedModal', copiedModal);

      jq('main').append(copiedModal);

      clipboardInstance.on('success', _ => {
        var buttonPosition = buttonGetter().position();
        jq(`#${copiedModalId}`).css({
          top: buttonPosition.top - 30,
          left: buttonPosition.left + 50
        }).show();

        if (elementGetter !== undefined) {
          changeValueUsingEvent(elementGetter()[0], placeholderValue);
        }

        setTimeout(() => {
          modalGetter().hide();
        }, 5000);
      });
    }

    if (questionBoxWithPlaceholderExists() && getCopyPlaceholder().length < 1) {
      const copyPlaceholder = textButton('perplexity_helper_copy_placeholder', 'Copy placeholder', 'Copies placeholder value');
      const copyPlaceholderAndFillIn = textButton('perplexity_helper_copy_placeholder_and_fill_in', 'Copy placeholder and fill in',
        'Copies placeholder value and fills in input');

      const specifyQuestionControlsWrapper = getSpecifyQuestionControlsWrapper();

      specifyQuestionControlsWrapper.append(copyPlaceholder);
      specifyQuestionControlsWrapper.append(copyPlaceholderAndFillIn);

      constructClipBoard('perplexity_helper_copy_placeholder', getCopyPlaceholder, getCopiedModal, 'copied-modal')
      constructClipBoard('perplexity_helper_copy_placeholder_and_fill_in', getCopyAndFillInPlaceholder, getCopiedModal2, 'copied-modal-2', getCopyPlaceholderInput)
    }


  }
};

(function () {
  if (loadConfigOrDefault()?.enableDebug) {
    enableDebugMode();
  }

  'use strict';
  jq("head").append(`<style>${styles}</style>`);


  setupTags();

  const mainInterval = setInterval(work, 1000);
  window.ph = {
    stopWork: () => { clearInterval(mainInterval); },
    work,
    jq,
    showPerplexityHelperModal,
    enableTagsDebugging: () => { debugTags = true; },
    disableTagsDebugging: () => { debugTags = false; },
  }
}());