您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds character/token count indicator to Perplexity conversations
当前为
- // ==UserScript==
- // @name Perplexity Length Indicator
- // @namespace https://lugia19.com
- // @version 0.6.1
- // @description Adds character/token count indicator to Perplexity conversations
- // @author lugia19
- // @license MIT
- // @match https://www.perplexity.ai/*
- // @grant none
- // ==/UserScript==
- (function () {
- 'use strict';
- const CHECK_INTERVAL = 30000; // Check every 15 seconds
- const RETRY_INTERVAL = 1000; // Retry every 1 second
- const MAX_RETRY_TIME = 30000; // Retry for up to 30 seconds
- let lengthIndicator = null;
- let injectionAttempts = 0;
- let injectionStartTime = 0;
- let injectionRetryTimer = null;
- const originalFetch = window.fetch;
- function isConversationPage() {
- return window.location.href.match(/https:\/\/www\.perplexity\.ai\/search\/.*-.*$/);
- }
- function getConversationId() {
- const match = window.location.href.match(/https:\/\/www\.perplexity\.ai\/search\/(.*)/);
- return match ? match[1] : null;
- }
- function isLengthIndicatorPresent() {
- return document.querySelector('.perplexity-length-indicator') !== null;
- }
- function injectLengthIndicator() {
- // Find the bottom right container with the help button
- const bottomRightContainer = document.querySelector('.bottom-md.right-md.m-sm.fixed.hidden .gap-sm.flex.items-center');
- if (!bottomRightContainer) return false;
- // Create our indicator
- lengthIndicator = document.createElement('span');
- lengthIndicator.className = 'perplexity-length-indicator';
- // Start hidden if not on a conversation page
- if (!isConversationPage()) {
- lengthIndicator.style.display = 'none';
- }
- // Create token counter with styled text
- const counter = document.createElement('div');
- counter.className = 'bg-offsetPlus dark:bg-offsetPlusDark text-textMain dark:text-textMainDark md:hover:text-textOff md:dark:hover:text-textOffDark !bg-background dark:border-borderMain/25 dark:!bg-offset border shadow-subtle border-borderMain/50 font-sans focus:outline-none outline-none outline-transparent transition duration-300 ease-out font-sans select-none items-center relative group/button justify-center text-center items-center rounded-full cursor-pointer origin-center whitespace-nowrap inline-flex text-sm h-8 px-3';
- counter.style.display = 'flex';
- counter.style.alignItems = 'center';
- counter.style.justifyContent = 'center';
- // Create the text span with blue color
- const textSpan = document.createElement('span');
- textSpan.className = 'font-medium';
- textSpan.style.color = '#3b82f6'; // Keep the blue color
- textSpan.textContent = '0 tokens';
- counter.appendChild(textSpan);
- // Add components to the indicator
- lengthIndicator.appendChild(counter);
- // Add the indicator at the beginning of the container (before the help button)
- bottomRightContainer.insertBefore(lengthIndicator, bottomRightContainer.firstChild);
- // Reset injection retry counters
- clearTimeout(injectionRetryTimer);
- injectionAttempts = 0;
- injectionStartTime = 0;
- return true;
- }
- async function updateLengthIndicator() {
- // If not on a conversation page, hide the indicator
- if (!isConversationPage()) {
- if (lengthIndicator) {
- lengthIndicator.style.display = 'none';
- }
- return;
- }
- // On conversation page, show the indicator
- if (lengthIndicator) {
- lengthIndicator.style.display = '';
- }
- const conversationId = getConversationId();
- if (!conversationId) return;
- try {
- const response = await fetch(`https://www.perplexity.ai/rest/thread/${conversationId}?with_schematized_response=true&limit=9999`);
- const data = await response.json();
- let charCount = 0;
- if (data.entries && Array.isArray(data.entries)) {
- data.entries.forEach(entry => {
- // Add query string length
- if (entry.query_str) {
- charCount += entry.query_str.length;
- }
- // Add response text length
- if (entry.blocks && Array.isArray(entry.blocks)) {
- entry.blocks.forEach(block => {
- if (block.intended_usage === "ask_text" &&
- block.markdown_block &&
- block.markdown_block.answer) {
- charCount += block.markdown_block.answer.length;
- }
- });
- }
- });
- }
- // Estimate tokens (char count / 4)
- const tokenCount = Math.round(charCount / 4);
- // Update the indicator
- if (lengthIndicator) {
- const counterSpan = lengthIndicator.querySelector('span.font-medium');
- if (counterSpan) {
- counterSpan.textContent = `${tokenCount} tokens`;
- }
- }
- } catch (error) {
- console.error('Error fetching conversation data:', error);
- }
- }
- function startInjectionRetry() {
- // Start tracking injection attempts
- if (injectionStartTime === 0) {
- injectionStartTime = Date.now();
- }
- // Try to inject the indicator
- const injected = injectLengthIndicator();
- // If successful, update the indicator and stop retrying
- if (injected) {
- updateLengthIndicator();
- return;
- }
- // Check if we've reached the maximum retry time
- injectionAttempts++;
- const elapsedTime = Date.now() - injectionStartTime;
- if (elapsedTime < MAX_RETRY_TIME) {
- // Continue retrying
- injectionRetryTimer = setTimeout(startInjectionRetry, RETRY_INTERVAL);
- } else {
- // Reset counters after max retry time
- injectionAttempts = 0;
- injectionStartTime = 0;
- console.log('Failed to inject length indicator after maximum retry time');
- }
- }
- function checkAndUpdate() {
- if (!isLengthIndicatorPresent()) {
- // Start the retry process for injection
- startInjectionRetry();
- } else {
- updateLengthIndicator();
- }
- }
- // Setup fetch interception using the provided pattern
- window.fetch = async (...args) => {
- const [input, config] = args;
- let url;
- if (input instanceof URL) {
- url = input.href;
- } else if (typeof input === 'string') {
- url = input;
- } else if (input instanceof Request) {
- url = input.url;
- }
- const method = config?.method || (input instanceof Request ? input.method : 'GET');
- // Proceed with the original fetch
- const response = await originalFetch(...args);
- // Check if this is a request to the perplexity_ask endpoint
- if (url && url.includes('perplexity.ai/rest/sse/perplexity_ask') && method === 'POST') {
- // Wait a bit for the response to be processed and update
- console.log("UPDATING, GOT RESPONSE!")
- setTimeout(checkAndUpdate, 10000);
- }
- return response;
- };
- // Initial check
- setTimeout(checkAndUpdate, 1000);
- // Set up interval for regular checks
- setInterval(checkAndUpdate, CHECK_INTERVAL);
- // Listen for URL changes (for single-page apps)
- let lastUrl = window.location.href;
- new MutationObserver(() => {
- if (lastUrl !== window.location.href) {
- lastUrl = window.location.href;
- // Update the visibility based on new URL
- if (lengthIndicator) {
- lengthIndicator.style.display = isConversationPage() ? '' : 'none';
- }
- setTimeout(checkAndUpdate, 1000); // Check after URL change
- }
- }).observe(document, { subtree: true, childList: true });
- })();