ChatGPT reduce react repaint duration

Reduces react repaint duration by limiting initial number of messages

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         ChatGPT reduce react repaint duration
// @namespace    https://chatgpt.com/
// @version      1.0.0
// @description  Reduces react repaint duration by limiting initial number of messages
// @author       SadSalmonTwT
// @license MIT
// @match        https://chatgpt.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  // defines the number of initially loaded messages
  const INIT_NUMBER_OF_MESSAGES = 10;

  // request for chat history regex
  const conversationRegex = /^https:\/\/chatgpt\.com\/backend-api\/conversation\/[a-f0-9\-]{36}$/i;

  const originalFetch = window.fetch;

  // intercept the fetch api
  window.fetch = function (input, init) {
    const url = (typeof input === 'string')
      ? input
      : input.url;

    // process all other requests like normal, but intercept all chat history request responses
    if (!conversationRegex.test(url))
      return originalFetch.apply(this, arguments);
    else
      return originalFetch.apply(this, arguments)
        .then(async (response) => {
          try {
            const data = await response.clone().json();

            const newMapping = [];

            let nextId = data.current_node;
            let numberOfVisibleMessages = 0;

            // traverse tree upwards from the last message leaf towards root
            while (true) {
              const _message = data.mapping[nextId];

              // if root is encountered early, add it and stop loop
              if (_message.id === "client-created-root") {
                newMapping.push(_message);
                break;
              }

              // traverse downwards tree towards child leaves (these messages are hidden)
              const _childIds = [..._message.children];
              while (_childIds.length !== 0) {
                const _childId = _childIds.pop();
                const _child = data.mapping[_childId];

                // if newMapping already contains child, skip it
                if (newMapping.some(_preservedMessage => _preservedMessage.id === _childId))
                  continue;

                // add hidden message
                newMapping.push(_child);

                // add children's children
                _childIds.push(..._child.children);
              }

              // add message itself (if the number was not reached AND its parent exists AND it's not root)
              if (
                numberOfVisibleMessages < INIT_NUMBER_OF_MESSAGES
                && data.mapping[_message.parent]
              )
                newMapping.push(_message);
              else {
                // add "first" message and link it to root
                newMapping.push({
                  ..._message,
                  parent: "client-created-root"
                });

                // add chat root
                newMapping.push({
                  id: "client-created-root",
                  message: null,
                  parent: null,
                  children: [_message.id]
                });

                // stop loop
                break;
              }

              nextId = _message.parent;
              numberOfVisibleMessages++;
            }

            // if new mappings have same length as old ones, return original response
            if (newMapping.length === Object.keys(data.mapping).length) {
              console.log("ChatGPT_reduce_react_repaint_duration: Response was left unchanged");
              return response;
            }

            // update chat history
            data.mapping = Object.fromEntries(
              newMapping.map(_message => ([
                _message.id,
                _message
              ]))
            );

            console.log('ChatGPT_reduce_react_repaint_duration: Response was trimmed', data);

            // return response with modified data
            return new Response(JSON.stringify(data), {
              status: response.status,
              statusText: response.statusText,
              headers: response.headers
            });
          } catch (err) {
            console.error('ChatGPT_reduce_react_repaint_duration: An error has occurred', err);
            return response;
          }
        });
  };
})();