// ==UserScript==
// @name Perplexity Model Selection
// @namespace https://greasyfork.org/en/users/688917
// @version 0.3
// @description Adds model selection buttons to Perplexity AI using jQuery
// @author dpgc, lyh16, mall-fluffy-bongo
// @match https://www.perplexity.ai/*
// @license MIT
// @run-at document-end
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// Check if jQuery is loaded on the page
if (typeof jQuery === 'undefined') {
var script = document.createElement('script');
script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js';
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);
script.onload = function() {
setup();
};
} else {
setup();
}
function createModelSelectorElement(buttonText) {
var $button = $('', {
type: 'button',
class: 'model-selector 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/button justify-center text-center items-center rounded-full cursor-point active:scale-95 origin-center whitespace-nowrap inline-flex text-sm px-2 font-medium h-8'
});
const $svg = $(`
`)
var $textDiv = $(`
${buttonText}
`);
var $buttonContentDiv = $('', {
class: 'flex items-center leading-none justify-center gap-1',
}).append($svg).append($textDiv);
$button.append($buttonContentDiv);
var $wrapperDiv = $('').append($('').append($button));
return {
$element: $wrapperDiv,
setModelName: (modelName) => {
// $textDiv.text(`${buttonText} (${modelName})`);
$textDiv.text(`${modelName} `);
}
}
}
function createSelectionPopover(sourceElement) {
const createSelectionElement = (input) => {
const {name, onClick} = input;
const $element = $(`
`);
$element.click(onClick);
return $element;
}
const popoverHTML = ``;
const $popover = $(popoverHTML);
const $popper = $popover.find('[data-tag="popper"]');
const $menuContaienr = $popover.find('[data-tag="menu"]');
if (sourceElement) {
const {top, left, width, height} = sourceElement.getBoundingClientRect();
const offset = 10;
const popperWidth = $popper.outerWidth();
$popper.css('transform', `translate(${left + (width + popperWidth * 2)}px, ${top + height + offset}px)`);
}
return {
$element: $popover,
addSelection: (input) => {
const $selection = createSelectionElement(input);
$menuContaienr.append($selection);
}
}
}
async function fetchSettings() {
const url = 'https://www.perplexity.ai/p/api/v1/user/settings';
const response = await fetch(url);
if (!response.ok) throw new Error('Failed to fetch settings');
return await response.json();
}
function setupSelection() {
let selector = '';
// const currentURL = window.location.href;
// if (currentURL === 'https://www.perplexity.ai/') {
// selector = '.flex.bg-background.dark\\:bg-offsetDark.rounded-l-lg.col-start-1.row-start-2.-ml-2';
// } else if (currentURL.startsWith('https://www.perplexity.ai/search/')) {
// selector = '.pointer-events-none.fixed.z-10.grid-cols-12.gap-xl.px-sm.py-sm.md\\:bottom-lg.md\\:grid.md\\:px-0.bottom-\\[64px\\].border-borderMain\\/50.ring-borderMain\\/50.divide-borderMain\\/50.dark\\:divide-borderMainDark\\/50.dark\\:ring-borderMainDark\\/50.dark\\:border-borderMainDark\\/50.bg-transparent';
// } else {
// return;
// }
selector = '.flex.bg-background.dark\\:bg-offsetDark.rounded-l-lg.col-start-1.row-start-2.-ml-2';
const $focusElement = $('div:contains("Focus")').closest(selector);
if (!$focusElement.length) return;
if ($focusElement.data('state') === 'injected') return;
$focusElement.data('state', 'injected');
const aiModels = [
{
"name": "Default",
"code": "turbo"
},
{
"name": "Experimental",
"code": "experimental"
},
{
"name": "GPT-4 Turbo",
"code": "gpt4"
},
{
"name": "Claude 3 Opus",
"code": "claude3opus"
},
{
"name": "Claude 3 Sonnet",
"code": "claude2"
},
{
"name": "Mistral Large",
"code": "mistral"
}
];
const imageModels = [
{
"name": "Playground v.2.5",
"code": "default"
},
{
"name": "DALL-E 3",
"code": "dall-e-3"
},
{
"name": "Stable Diffusion XL",
"code": "sdxl"
}
];
const aiModelSelector = createModelSelectorElement("Chat Model");
const imageModelSelector = createModelSelectorElement("Image Model");
let latestSettings = undefined;
const getCurrentModel = () => {
return latestSettings?.["default_model"];
}
const getCurrentImageModel = () => {
return latestSettings?.["default_image_generation_model"];
}
const updateFromSettings = () => {
fetchSettings().then((settings) => {
latestSettings = settings;
const aiModelCode = getCurrentModel();
const aiModelName = aiModels.find(m => m.code === aiModelCode)?.name;
if (aiModelName) aiModelSelector.setModelName(aiModelName);
const imageModelCode = getCurrentImageModel();
const imageModelName = imageModels.find(m => m.code === imageModelCode)?.name;
if (imageModelName) imageModelSelector.setModelName(imageModelName);
});
};
updateFromSettings();
const setModel = async (model, isImageModel) => {
const el = $focusElement[0];
const fiberKey = Object.keys(el).find(k => k.startsWith('__reactFiber'));
if (!fiberKey) throw new Error('Failed to find key of React Fiber');
const fiber = el[fiberKey];
const settingsKey = isImageModel ? 'default_image_generation_model' : 'default_model';
return await fiber.child.sibling.memoizedProps.socket.emitWithAck('save_user_settings', {
[settingsKey]: model,
source: "default",
version: "2.5"
})
}
aiModelSelector.$element.click(async () => {
const {$element: $popover, addSelection} = createSelectionPopover(aiModelSelector.$element[0]);
$('main').append($popover);
const closePopover = () => {
$popover.remove();
$(document).off('click', closePopover);
}
for (const model of aiModels) {
addSelection({
name: model.name,
onClick: async () => {
await setModel(model.code, false);
updateFromSettings();
closePopover();
}
});
}
setTimeout(() => {
$(document).on('click', closePopover);
$popover.on('click', (e) => e.stopPropagation());
}, 500);
});
imageModelSelector.$element.click(async () => {
const {$element: $popover, addSelection} = createSelectionPopover(imageModelSelector.$element[0]);
$('main').append($popover);
const closePopover = () => {
$popover.remove();
$(document).off('click', closePopover);
}
for (const model of imageModels) {
addSelection({
name: model.name,
onClick: async () => {
await setModel(model.code, true);
updateFromSettings();
closePopover();
}
});
}
setTimeout(() => {
$(document).on('click', closePopover);
$popover.on('click', (e) => e.stopPropagation());
}, 500);
});
$focusElement.append(aiModelSelector.$element);
$focusElement.append(imageModelSelector.$element);
// Add CSS styles for responsive layout
$('