ChatGPT: Message Records

Remind you how many quota you left

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        ChatGPT: Message Records
// @namespace   UserScripts
// @match       https://chatgpt.com/*
// @match       https://chat.openai.com/*
// @grant       GM.getValue
// @grant       GM.setValue
// @grant       GM.deleteValue
// @grant       GM_addValueChangeListener
// @grant       unsafeWindow
// @version     1.2.2
// @author      CY Fung
// @license     MIT
// @description Remind you how many quota you left
// @run-at      document-start
// @inject-into page
// ==/UserScript==

const __errorCode21167__ = (() => {

  try {
    Promise.resolve('\u{1F4D9}', ((async () => { })()).constructor);
  } catch (e) {
    console.log('%cUnsupported Browser', 'background-color: #FAD02E; color: #333; padding: 4px 8px; font-weight: bold; border-radius: 4px;');
    return 0x3041;
  }

  if (typeof GM_addValueChangeListener !== 'function' || typeof GM !== 'object' || typeof (GM || 0).setValue !== 'function') {
    console.log('%cUnsupported UserScript Manager', 'background-color: #FAD02E; color: #333; padding: 4px 8px; font-weight: bold; border-radius: 4px;');
    return 0x3042;
  }

  return 0;
})();

__errorCode21167__ || (() => {

  /** @type {Window} */
  const uWin = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
  /** @type {{ globalThis.PromiseConstructor}} */
  const Promise = ((async () => { })()).constructor;

  let __recordId_new = 1;
  let abortCounter = 0;

  let userOpenAction = null;

  const kPattern = (num) => {
    const letters = 'abcdefghijklmnopqrstuvwxyz';
    const k = num % 9;
    const j = Math.floor(num / 9);
    const letter = letters[j];
    return `${letter}${k + 1}`;
  }

  const kHash = (n) => {
    if (n < 0 || n > 54755) {
      throw new Error('Number out of range');
    }

    const nValue = 9 * 26; // precompute this value since it's constant

    // Simplified equation, combined terms
    let hashBase = (n * 9173) % 54756;
    // ((54756 - n) * 9173 + (n) * 7919 + (n) * 5119) % 54756;`

    let hash = '';
    for (let i = 0; i < 2; i++) {
      const t = hashBase % nValue;
      hash = kPattern(t) + hash;
      hashBase = Math.floor(hashBase / nValue);
    }

    return hash;
  }

  const cleanContext = async (win, gmWindow) => {
    /** @param {Window} fc */
    const sanitize = (fc) => {
      const { setTimeout, clearTimeout, setInterval, clearInterval, requestAnimationFrame, cancelAnimationFrame } = fc;
      const res = { setTimeout, clearTimeout, setInterval, clearInterval, requestAnimationFrame, cancelAnimationFrame };
      for (let k in res) res[k] = res[k].bind(win); // necessary
      return res;
    }
    if (gmWindow && typeof gmWindow === 'object' && gmWindow.GM_info && gmWindow.GM) {
      let isIsolatedContext = (
        (gmWindow.requestAnimationFrame !== win.requestAnimationFrame) &&
        (gmWindow.cancelAnimationFrame !== win.cancelAnimationFrame) &&
        (gmWindow.setTimeout !== win.setTimeout) &&
        (gmWindow.setInterval !== win.setInterval) &&
        (gmWindow.clearTimeout !== win.clearTimeout) &&
        (gmWindow.clearInterval !== win.clearInterval)
      );
      if (isIsolatedContext) {
        return sanitize(gmWindow);
      }
    }
    const waitFn = requestAnimationFrame; // shall have been binded to window
    try {
      let mx = 16; // MAX TRIAL
      const frameId = 'vanillajs-iframe-v1'
      let frame = document.getElementById(frameId);
      let removeIframeFn = null;
      if (!frame) {
        frame = document.createElement('iframe');
        frame.id = frameId;
        const blobURL = typeof webkitCancelAnimationFrame === 'function' && typeof kagi === 'undefined' ? (frame.src = URL.createObjectURL(new Blob([], { type: 'text/html' }))) : null; // avoid Brave Crash
        frame.sandbox = 'allow-same-origin'; // script cannot be run inside iframe but API can be obtained from iframe
        let n = document.createElement('noscript'); // wrap into NOSCRPIT to avoid reflow (layouting)
        n.appendChild(frame);
        while (!document.documentElement && mx-- > 0) await new Promise(waitFn); // requestAnimationFrame here could get modified by YouTube engine
        const root = document.documentElement;
        root.appendChild(n); // throw error if root is null due to exceeding MAX TRIAL
        if (blobURL) Promise.resolve().then(() => URL.revokeObjectURL(blobURL));

        removeIframeFn = (setTimeout) => {
          const removeIframeOnDocumentReady = (e) => {
            e && win.removeEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
            e = n;
            n = win = removeIframeFn = 0;
            setTimeout ? setTimeout(() => e.remove(), 200) : e.remove();
          }
          if (!setTimeout || document.readyState !== 'loading') {
            removeIframeOnDocumentReady();
          } else {
            win.addEventListener("DOMContentLoaded", removeIframeOnDocumentReady, false);
          }
        }
      }
      while (!frame.contentWindow && mx-- > 0) await new Promise(waitFn);
      const fc = frame.contentWindow;
      if (!fc) throw "window is not found."; // throw error if root is null due to exceeding MAX TRIAL
      try {
        const res = sanitize(fc);
        if (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn);
        return res;
      } catch (e) {
        if (removeIframeFn) removeIframeFn();
        return null;
      }
    } catch (e) {
      console.warn(e);
      return null;
    }
  };

  const observablePromise = (proc, timeoutPromise) => {
    let promise = null;
    return {
      obtain() {
        if (!promise) {
          promise = new Promise(resolve => {
            let mo = null;
            const f = () => {
              let t = proc();
              if (t) {
                mo.disconnect();
                mo.takeRecords();
                mo = null;
                resolve(t);
              }
            }
            mo = new MutationObserver(f);
            mo.observe(document, { subtree: true, childList: true })
            f();
            timeoutPromise && timeoutPromise.then(() => {
              resolve(null)
            });
          });
        }
        return promise
      }
    }
  }

  cleanContext(uWin, window).then((__CONTEXT__) => {

    const { setTimeout, clearTimeout, setInterval, clearInterval, requestAnimationFrame, cancelAnimationFrame } = __CONTEXT__;
    
    const console = Object.assign({}, window.console);
    /** @type {JSON.parse} */
    const jParse = window.JSON.parse.bind(window.JSON);
    const jParseCatched = (val) => {
      let res = null;
      try {
        res = jParse(val);
      } catch (e) { }
      return res;
    }
    const jStringify = window.JSON.stringify.bind(window.JSON);

    const GM_RECORD_KEY = 'TOTAL_MESSAGE_RECORDS';

    let __foregroundActivityMeasure = 0;
    let __totalActivityMeasure = 0;
    const foregroundActivityMeasureInterval = 500;
    const amiUL = foregroundActivityMeasureInterval * 1.1;
    const amiLL = foregroundActivityMeasureInterval * 0.9;
    const activityMeasure = {
      get foreground() {
        return __foregroundActivityMeasure;
      },

      get background() {
        return activityMeasure.total - activityMeasure.foreground;
      },
      get total() {
        return Math.round(__totalActivityMeasure)
      }
    }


    let __uid = 0;

    let message_cap = null;

    let message_cap_window = null;
    let categories = null;
    let models = null;
    let currentAccount = null;
    let currentUser = null;
    const getUserId = () => currentAccount && currentUser ? `${currentAccount}.${currentUser}` : '';

    const dummyObject = {};
    for (const [key, value] of Object.entries(console)) {
      if (typeof value === 'function' && typeof dummyObject[key] !== 'function') {
        console[key] = value.bind(window.console);
      }
    }

    const messageRecords = [];
    let messageRecordsOnCurrentAccount = null;



    let rafPromise = null;
    const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {
      requestAnimationFrame(hRes => {
        rafPromise = null;
        resolve(hRes);
      });
    }));
  

    const foregroundWrap = (cb) => {

      let wait = false;

      return () => {
        if (wait) return;
        wait = true;
        requestAnimationFrame(function () {
          wait = false;
          cb();
        });
      }

    }

    const findRecordIndexByRId = (rid) => {

      if (!rid) return null;
      for (let i = 0; i < messageRecords.length; i++) {
        const record = messageRecords[i];
        if (record.$recordId && rid === record.$recordId) {
          return i;
        }
      }
      return -1;

    }

    const cssStyleText = () => `

      :root {
        --mr-background-color: #2a5c47;
        --mr-font-stack: "Ubuntu-Italic", "Lucida Sans", helvetica, sans;

        --mr-target-width: 30px;
        --mr-target-height: 30px;
        --mr-target-bottom: 90px;
        --mr-target-right: 50px;

        /* 8-0.5*x+0.25*x*x */
        --mr-tb-radius: calc(2.42 * var(--mr-border-width));
        /* 20 -> 8px.  14 -> 6px   4px. 10px */
        --mr-message-bubble-width: 200px;
        --mr-message-bubble-margin: 0;
        --mr-border-width: 2px;
        --mr-triangle-border-width: var(--mr-tb-radius);

        --mr-message-bubble-opacity: 0;
        --mr-message-bubble-scale: 0.5;
        --mr-message-bubble-transform-origin: bottom right;
        --mr-message-bubble-transition: opacity 0.3s, transform 0.3s, visibility 0s 0.3s;
        --mr-border-color: #666;

        --mr-tb-btm: calc(var(--mr-tb-radius) * 1.72);
      }

      html {
          --mr-message-bubble-bg-color: #ecf3e7;
          --mr-message-bubble-text-color: #414351;
          --progress-color: #807e1e;
      }

      html.dark{
          --mr-message-bubble-bg-color: #40414f;
          --mr-message-bubble-text-color: #ececf1;
      }

      html[mr-request-model="gpt-4"]{
          --progress-color: #ac68ff;
      }

      html[mr-request-model="gpt-3"] {
      --progress-color: #19c37d;
      }

      html[mr-request-state="request"] {
        --progress-percent: 25%;
        --progress-rr: 9px;
      }

      html[mr-request-state="response"] {
        --progress-percent: 75%;
        --progress-rr: 9px;
      }


      html[mr-request-state=""] {
        --progress-percent: 100%;
        --progress-rr: 20px;
      }


      html[mr-request-state=""] .mr-progress-bar::before {
        --mr-animate-background-image: none;
      }



      .mr-progress-bar.mr-progress-bar-show {

        visibility:visible;

      }

      .mr-progress-bar {
        display: inline-block;
        width: 200px;
        --progress-height: 16px;
        --progress-padding: 4px;
        /* --progress-percent: 50%; */
        /* --progress-color: #2bc253; */
        --progress-stripe-color: rgba(255, 255, 255, 0.2);
        --progress-shadow1: rgba(255, 255, 255, 0.3);
        --progress-shadow2: rgba(0, 0, 0, 0.4);
        --progress-rl: 20px;
        /* --progress-rr: 9px; */

        width: 100%;
        /* --progress-percent: 100%;
        --progress-rr: 20px;*/
        visibility: collapse;
      }


      @keyframes mr-progress-bar-move {
        0% {
          background-position: 0 0;
        }

        100% {
          background-position: 50px 50px;
        }
      }

      .mr-progress-bar {
        box-sizing: border-box;
        height: var(--progress-height);
        position: relative;
        background: #555;
        border-radius: 25px;
        box-shadow: inset 0 -1px 1px var(--progress-shadow1);
        display: inline-block;
      }

      .mr-progress-bar::before {
        box-sizing: border-box;
        content: "";
        display: block;
        margin: var(--progress-padding);
        border-top-right-radius: var(--progress-rr);
        border-bottom-right-radius: var(--progress-rr);
        border-top-left-radius: var(--progress-rl);
        border-bottom-left-radius: var(--progress-rl);
        background-color: var(--progress-color);
        box-shadow: inset 0 2px 9px var(--progress-shadow1), inset 0 -2px 6px var(--progress-shadow2);
        position: absolute;
        top: 0;
        left: 0;
        right: calc(100% - var(--progress-percent));
        transition: right 300ms, background-color 300ms;
        bottom: 0;



        --mr-animate-background-image: linear-gradient(-45deg, var(--progress-stripe-color) 25%, transparent 25%, transparent 50%, var(--progress-stripe-color) 50%, var(--progress-stripe-color) 75%, transparent 75%, transparent);

        background-image: var(--mr-animate-background-image);

        background-size: 50px 50px;
        animation: mr-progress-bar-move 2s linear infinite;

      }

      .mr-nostripes::before {
      --mr-animate-background-image: none;
      }


      #mr-msg-l {

          text-align: center;
          font-size: .875rem;
          color: var(--tw-prose-code);
          font-size: .875em;
          font-weight: 600;
      }
      #mr-msg-p {
          text-align: center;
          font-size: 1rem;
      }

      #mr-msg-p1{
          display: block;
      }
      #mr-msg-p2{
          display: block;
          font-size: .75rem;
      }




      .mr-message-bubble {
        margin: 0;
        display: inline-block;
        position: absolute;
        width: var(--mr-message-bubble-width);
        height: auto;
        background-color: var(--mr-message-bubble-bg-color);
        opacity: var(--mr-message-bubble-opacity);
        transform: scale(var(--mr-message-bubble-scale));
        transform-origin: var(--mr-message-bubble-transform-origin);
        transition: var(--mr-message-bubble-transition);
        visibility: hidden;
        margin-bottom: var(--mr-tb-btm);
        bottom:0;
        right:0;
        color: var(--mr-message-bubble-text-color);
        --mr-user-select: auto-user-select;
      }

      .mr-border {
        border: var(--mr-border-width) solid var(--mr-border-color);
      }

      .mr-round {
        border-radius: var(--mr-tb-radius);
      }

      .mr-tri-right.mr-border.mr-btm-right:before {
        content: ' ';
        position: absolute;
        width: 0;
        height: 0;
        left: auto;
        right: calc(var(--mr-border-width) * -1);
        bottom: calc(var(--mr-tb-radius) * -2);
        border: calc(var(--mr-border-width) * 4) solid;
        border-color: transparent var(--mr-border-color) transparent transparent;
      }

      .mr-tri-right.mr-btm-right:after {
        content: ' ';
        position: absolute;
        width: 0;
        height: 0;
        left: auto;
        right: 0px;
        bottom: calc(var(--mr-tb-radius) * -1);
        border: var(--mr-triangle-border-width) solid;
        border-color: var(--mr-message-bubble-bg-color) var(--mr-message-bubble-bg-color) transparent transparent;
      }

      .mr-msg-text {
        padding: 1em;
        text-align: left;
        line-height: 1.5em;
      }

      .mr-message-bubble.mr-open {
        opacity: 1;
        transform: scale(1);
        visibility: visible;
        transition-delay: 0s;
      }

      .mr-msg-text p {
        margin: 0;
      }

      .mr-a33 {
        position: absolute;
        top: auto;
        left: auto;
        bottom: 0px;
        right: 0px;
      }

      .mr-k33 {
        position: absolute;
        contain: size layout style;
        width: 100%;
        height: 100%;
        transform: translate(-50%, -100%);
      }


      .mr-button-container[class] button[class] {
        opacity: 0.8;

      }
      .mr-button-container[class] button[class]:hover {
        opacity: 1;
      }

      .mr-button-container.mr-clicked[class] button[class],
      .mr-button-container.mr-clicked[class] button[class]:hover {
        background-color: #616a8a;
        opacity:1;
      }

      .mr-button-container[class], .mr-button-container[class] button[class] {

        --mr-user-select: none;
        user-select: var(--mr-user-select);

      }

      .mr-offset-book-btn {
        margin-right: 28px;
      }

      .flex.w-full.flex-col[class*="rounded-"][class*="bg-"]:hover {
        z-index: 999;
      }


    `;

    const addCssText = () => {
      if (document.querySelector('#mr-style811')) return;
      const style = document.createElement('style');
      style.id = 'mr-style811';
      style.textContent = cssStyleText();
      document.head.appendChild(style);

    }


    const updateGMRecord = () => {

      Promise.resolve().then(() => {
        GM.setValue(GM_RECORD_KEY, jStringify({
          version: 1,
          records: messageRecords
        }));
      });

    }

    const updateMessageRecordsOnCurrentAccount = () => {
      messageRecordsOnCurrentAccount = messageRecords.filter(entry => entry.$account_uid === `${currentAccount}.${currentUser}`);
    }

    const fixOverRecords = () => {
      messageRecords.splice(0, Math.floor(messageRecords.length / 2));
      let rid = 1;
      for (const record of messageRecords) {
        record.$recordId = rid;
        messageRecords.push(record);
        rid++;
      }
      __recordId_new = rid;
      keep.length = 0;
      keep = null;

      updateMessageRecordsOnCurrentAccount();
      updateGMRecord();
    }

    const addRecord = (record) => {
      if (!currentAccount || !currentUser || !record || record.$account_uid) {
        console.log('addRecord aborted');
        return;
      }

      record.$account_uid = getUserId();

      const recordId = __recordId_new;
      record.$recordId = recordId;

      record.$recorded_at = Date.now(); // Local Time
      messageRecords.push(record);
      __recordId_new++;
      messageRecordsOnCurrentAccount.push(record);


      if (messageRecords.length > 3600 || __recordId_new > 3600) {
        // around 4MB
        Promise.resolve().then(fixOverRecords);
      }


      return recordId;

    }



    Object.assign(uWin, {
      $$mr$$getMessageRecords() {
        return messageRecords;
      },
      $$mr$$getMessageRecordsFromGM() {
        return GM.getValue(GM_RECORD_KEY);
      },
      $$mr$$clearMessageRecords() {
        return GM.deleteValue(GM_RECORD_KEY);
      },
      $$mr$$getUserId() {
        const r = getUserId();
        if (!r) console.log(`!! ${currentAccount}.${currentUser} !!`)
        return r;
      },
      $$mr$$activityMeasure() {
        return Object.assign({}, activityMeasure)
      }
    });



    const setRecordsByJSONString = (newValue, initial) => {

      let tObj = jParseCatched(newValue || '{}');
      if (!tObj || !tObj.version || !tObj.records) tObj = { version: 1, records: [] };

      if (tObj.version !== 1) {
        if (initial) {
          GM.deleteValue(GM_RECORD_KEY);
          // and wait change confirmed by listener
        } else {
          console.warn('record version is incorrect. please reload the page.');
        }
        return;
      }

      if (messageRecords.length > 0) messageRecords.length = 0;
      __recordId_new = 1;
      let rid = 1;
      for (const record of tObj.records) {
        if (record.$recordId >= rid) rid = record.$recordId + 1;
        messageRecords.push(record);
        __recordId_new++;
      }
      __recordId_new = rid;
      updateMessageRecordsOnCurrentAccount();



    }

    const onAccountDetectedOrChanged = () => {

      updateMessageRecordsOnCurrentAccount();


    }

    let last_GM_RECORD_KEY_newValue = null;

    const setRecordsByJSONStringX = () => {
      setRecordsByJSONString(last_GM_RECORD_KEY_newValue);
    }
    const setRecordsByJSONStringForegroundWrapped = foregroundWrap(setRecordsByJSONStringX);


    let gmValueListenerId = GM_addValueChangeListener(GM_RECORD_KEY, (key, oldValue, newValue, remote) => {
      last_GM_RECORD_KEY_newValue = newValue;
      setRecordsByJSONStringForegroundWrapped();
    });

    Promise.resolve().then(() => GM.getValue(GM_RECORD_KEY)).then(result => {
      //

      result = result || '{}';

      if (typeof result !== 'string') {
        console.log('GM.getValue aborted')
        return;
      }

      GM.setValue()
      setRecordsByJSONString(result, true)


    })

    const arrayTypeFix = (a) => {
      return a === null || a === undefined ? [] : a;
    }

    const getRequestQuotaString = () => {

      let num = null;

      if (document.documentElement.getAttribute('mr-request-model') === 'gpt-4') {


        num = messageRecordsOnCurrentAccount.filter(entry => {
          return typeof entry.model === 'string' && entry.model.startsWith('gpt-4') && (entry.$recorded_at || entry.$record_time_ms || 0) > (Date.now() - (6 * 1000 * 60) - message_cap_window * 60 * 1000)

        }).length;

        + ' out of ' + message_cap;

        let p1 = document.querySelector('#mr-msg-p1')
        let p2 = document.querySelector('#mr-msg-p2')

        if (p1 && p2) {
          p1.textContent = `${num}`
          p2.textContent = ' out of ' + message_cap;
        }


      } else if (document.documentElement.getAttribute('mr-request-model') === 'gpt-3') {



        num = messageRecordsOnCurrentAccount.filter(entry => {
          return typeof entry.model === 'string' && entry.model.startsWith('text-davinci-002-render-sha') && (entry.$recorded_at || entry.$record_time_ms || 0) > (Date.now() - (6 * 1000 * 60) - 24 * 60 * 60 * 1000)

        }).length;

        let p1 = document.querySelector('#mr-msg-p1')
        let p2 = document.querySelector('#mr-msg-p2')

        if (p1 && p2) {
          p1.textContent = `${num}`
          p2.textContent = ` in past 24 hours`;
        }




      } else {

        let p1 = document.querySelector('#mr-msg-p1')
        let p2 = document.querySelector('#mr-msg-p2')

        if (p1 && p2) {
          p1.textContent = '';
          p2.textContent = '';
        }

        // return '';
      }


    }

    function onRequest(_body) {

      const body = _body;

      const bodyObject = jParseCatched(body);
      if (!bodyObject) {
        console.log('invalid JSON object');
        return;
      }

      if (!('messages' in bodyObject)) {
        console.log('invalid format of JSON body')
        return;
      }

      const model = typeof bodyObject.model === 'string' ? bodyObject.model : null;
      const messages = arrayTypeFix(bodyObject.messages);

      if (!model || !messages || typeof (messages || 0).length !== 'number') {
        console.log('invalid format of JSON body')
        return;
      }

      if (!currentAccount) {
        console.log('No account information is found. Message Record aborted.')
        return;
      }

      let conversation_id = bodyObject.conversation_id;
      if (!conversation_id) conversation_id = "***"

      let recordIds = null;


      let request_model = '';
      if (typeof model === 'string' && (model === 'text-davinci-002-render-sha' || model.startsWith('text-davinci-002-render-sha'))) {


        request_model = 'gpt-3';
      } else if (typeof model === 'string' && (model === 'gpt-4' || model.startsWith('gpt-4'))) {

        request_model = 'gpt-4';

      }

      if (request_model) {


        try {
          document.documentElement.setAttribute('mr-request-model', request_model);
          getRequestQuotaString();
          document.documentElement.setAttribute('mr-request-state', 'request');
          document.querySelector('.mr-progress-bar').classList.add('mr-progress-bar-show');

        } catch (e) { }

        if (userOpenAction === null && attachedGroup && attachedGroup.isConnected === true && isChatBubbleOpened() === false) {
          const myGroup = attachedGroup;
          myGroupClicked(myGroup);
        }



      } else {


        try {
          document.documentElement.setAttribute('mr-request-model', '')
          getRequestQuotaString();
          document.querySelector('.mr-progress-bar').classList.remove('mr-progress-bar-show');

        } catch (e) { }



      }




      const onAbort = (evt, signal, newChatId) => {

        if (typeof newChatId === 'string' && newChatId) {

          const cd002 = !!recordIds && recordIds.length === 1 && recordIds[0] > 0;
          console.log('condition 002', cd002);

          if (cd002) {
            const rid = recordIds[0];
            const idx = findRecordIndexByRId(rid);

            if (idx === null || idx < 0 || !messageRecords[idx] || messageRecords[idx].conversation_id !== '***') {
              console.warn('error found in onAbort');
            } else {
              messageRecords[idx].conversation_id = newChatId

              console.log(`record#${rid} is updated with conversation_id = "${newChatId}"`)
            }

          }

        }

        if (recordIds && recordIds.length >= 1) {

          const completionTime = evt.__aborted_at__ > 0 ? evt.__aborted_at__ : 0;

          if (completionTime) {
            for (const rid of recordIds) {
              const idx = findRecordIndexByRId(rid);
              if (idx === null || idx < 0 || !messageRecords[idx]) {
                console.warn('completionTime found in onAbort');
              } else if (messageRecords[idx].conversation_id === '***') {
                // TBC
              } else {
                messageRecords[idx].$completed_at = completionTime;
              }
            }
          }

        }




        updateGMRecord();


        if (document.documentElement.getAttribute('mr-request-state') === 'response') document.documentElement.setAttribute('mr-request-state', '')


        console.log('messageHandler: onAbort', evt, signal, newChatId);
      };

      const uid = ++__uid;

      const onResponse = (response, info) => {

        const { requestTime, responseTime } = info;

        // response.lockedBodyStream.then((body) => {

        //   // console.log(13, body)

        // })

        if (!currentAccount) {
          console.log('No account information is found. Message Record aborted.')
          return;
        }

        if (recordIds !== null) {
          console.warn('recordIds !== null');
        }
        recordIds = [];
        for (const message of messages) {

          const rid = addRecord({
            model,
            conversation_id,
            message,
            $requested_at: requestTime,
            $responsed_at: responseTime
          });
          recordIds.push(rid);

        }

        updateGMRecord();

        if (document.documentElement.getAttribute('mr-request-state') === 'request') document.documentElement.setAttribute('mr-request-state', 'response')

        try {

          getRequestQuotaString();
        } catch (e) { }

        // console.log(bodyObject)
        // console.log(response, info)
        // console.log({
        //   message_cap, message_cap_window,
        //   categories,
        //   models
        // })

      }

      return {
        uid,
        model,
        conversation_id,
        message_cap, message_cap_window,
        categories,
        bodyObject,

        messages,
        onAbort,
        onResponse,

      }


    }


    uWin.__fetch247__ = uWin.fetch;

    let onceRgStr = false;

    let __newChatIdResolveFn__ = null;

    const authJsonPn = function () {

      const target = this['$a039$'] || this;
      return new Promise((resolve, reject) => {
        // console.log(112)

        target.json().then((result) => {


          const __jsonRes__ = result;


          if (typeof (__jsonRes__ || 0).user === 'object' && (__jsonRes__.user || 0).id) {
            currentUser = `${(__jsonRes__.user || 0).id}`;
            // console.log('user??', currentUser)
            // __NEXT_DATA__.props.pageProps.user.id // document.cookie.__puid
          }


          // console.log(566, result)
          resolve(result)
        }).catch(reject)

      })
    }

    const jsonPnForGetRequest = function () {

      const target = this['$a039$'] || this;
      return new Promise((resolve, reject) => {

        target.json().then((result) => {


          const __jsonRes__ = result;



          if (typeof (__jsonRes__ || 0).accounts === 'object') {

            const tmpSet = new Set();
            if (((__jsonRes__ || 0).accounts || 0).length > 0) {

              for (const account of __jsonRes__.accounts) {
                tmpSet.add(`${account.account_id}.${account.account_user_id}`);
              }

            } else {

              for (let [key, account] of Object.entries(__jsonRes__.accounts)) {
                account = account.account || account;
                tmpSet.add(`${account.account_id}.${account.account_user_id}`);

              }

            }
            if (tmpSet.size !== 1) {
              console.log('account detection failed')
            } else {
              let acc = [...tmpSet.keys()][0];
              if (acc !== currentAccount) {

                currentAccount = acc;
                onAccountDetectedOrChanged();
              }
            }



          }
          else if (((__jsonRes__ || 0).categories || 0).length >= 1 && ((__jsonRes__ || 0).models || 0).length >= 1) {

            const jsonRes = __jsonRes__;


            try {

              categories = [...jsonRes.categories];
            } catch (e) { }
            try {
              models = [...jsonRes.models];

            } catch (e) { }

            // console.log(233, categories, models)

          }




          // console.log(544, result)
          resolve(result)
        }).catch(reject)

      })
    };

    const message_limit_jsonPn = function () {

      const target = this['$a039$'] || this;
      return new Promise((resolve, reject) => {


        // console.log(114)
        target.clone().text().then(r => {

          // console.log(r)
          let jr = jParseCatched(r);
          if (jr) {

            if (jr.message_cap > 0 && jr.message_cap_window > 0) {
              message_cap = +jr.message_cap;
              message_cap_window = +jr.message_cap_window;
            }

          }

        })

        target.json().then((result) => {

          // console.log(result)
          resolve(result)
        }).catch(reject)

      })
    };

    uWin.fetch = function (a) {
      const args = arguments;
      return new Promise((resolve, reject) => {
        let doCatch = false;
        let body = null;

        let _onAbort = null;

        if (typeof a === 'string' && a.endsWith('/backend-api/conversation')) {
          const b = args[1] || 0;
          if (b.method === "POST" && typeof b.body === 'string' && ((b.headers || 0)['Content-Type'] || '').includes('application/json')) {
            doCatch = true;
            body = b.body;

          }
          if (b && b.signal) {

            const signal = b.signal;
            const tid = ++abortCounter;
            signal.addEventListener('abort', (evt) => {
              evt.__aborted_at__ = Date.now();
              const aid = abortCounter;
              ++abortCounter;

              console.log('onabort', aid, tid, evt, signal)

              if (aid === tid && _onAbort) {
                _onAbort(evt, signal);
              }



            });
          }
        } else if (typeof a === 'string' && a.startsWith('https://events.statsigapi.net/v1/rgstr')) {
          if (onceRgStr) {
            resolve = null;
            reject = null;
            return; // no resolve or reject for subsequent requests
          }
          onceRgStr = true; // no resolve or reject for next request
        } else if (__newChatIdResolveFn__ && typeof a === 'string' && a.startsWith('https://chat.openai.com/backend-api/conversation/gen_title/')) {

          let m = /gen_title\/([-0-9a-z]+)(\/|$)/.exec(a);
          if (m && m[1]) {
            __newChatIdResolveFn__(m[1]);
          }
        }

        const unprocessedFetch = () => {

          const actualRequest = uWin.__fetch247__.apply(this, args);

          // console.log(269, false, args[0], Object.assign({}, args[1] || {}))




          let rType = 0;


          if (typeof args[0] === 'string' && args[0].includes('/') && args[1] && args[1].method === 'GET') {
            rType = 1;
          } else if (args[0].includes('/api/auth/session')) {
            rType = 2;
          }


          actualRequest.then((result) => {

            if (rType > 0) {
              result = new Proxy(result, {
                get(target, key, receiver) {
                  if (key === '$a039$') return target;
                  const r = target[key];
                  if (key === 'json' && key in target) {


                    if (typeof r === 'function') {

                      if (rType === 1) {

                        if (typeof args[0] === 'string' && args[0].includes('/conversation_limit') && args[1] && args[1].method === 'GET') {
                          return message_limit_jsonPn;
                        } else {

                          return jsonPnForGetRequest;
                        }
                      }
                      else if (rType === 2) {


                        return authJsonPn;


                      }


                    }
                  }
                  if (typeof r === 'function') {

                    return (receiver['$b031$' + key] || (receiver['$b031$' + key] = ((r) => (function () { return r.apply(this['$a039$'] || this, arguments) }))(r)));

                  }
                  return r;

                }
              });
            }
            //   console.log(result)
            resolve(result);

          }).catch((error) => {
            reject(error);
          });

        }

        const messageHandler = doCatch ? onRequest(body) : false;
        if (!messageHandler) {
          unprocessedFetch();
          return;
        }
        const requireNewChatId = (messageHandler.conversation_id === '***');

        _onAbort = (evt, signal) => {

          __newChatIdResolveFn__ = null;

          if (requireNewChatId) {
            let resolveFn = null;
            let promise = new Promise(resolve => {
              resolveFn = resolve;
            })
            __newChatIdResolveFn__ = (x) => {
              resolveFn && resolveFn(x);
              resolveFn = null;
            };
            setTimeout(() => {
              resolveFn && resolveFn();
              resolveFn = null;
            }, 16);

            // 777ms -> 781ms => 16ms shall be sufficient
            promise.then((newChatId) => {
              if (__newChatIdResolveFn__ === null) {
                console.warn('unexpected error');
                return;
              }
              __newChatIdResolveFn__ = null;

              newChatId = newChatId || null;
              console.log(`newChatId: ${newChatId}`);
              messageHandler.onAbort(evt, signal, newChatId);
            })
          } else {

            messageHandler.onAbort(evt, signal, false);
          }

        }


        const requestTime1 = Date.now();
        const actualRequest = uWin.__fetch247__.apply(this, args);
        const requestTime2 = Date.now();
        const requestTime = Math.round((requestTime1 + requestTime2) / 2);

        // console.log(269, true, args[0], Object.assign({}, args[1] || {}))






        actualRequest.then((result) => {
          const responseTime = Date.now();

          let mBodyResolve = null;
          const mBody = new Promise(r => {
            mBodyResolve = r;
          });

          const pRes = new Proxy(result, {
            get(target, property, receiver) {
              if (property === '$a039$') return target;
              const r = target[property];
              /**
               *
               * property's get order
               *
               * then
               * status
               * then
               *
               * ----
               *
               * type
               * status
               * clone
               * headers
               * headers
               * ok
               * body
               *
               *
               */
              if (property === 'body') {
                mBodyResolve && mBodyResolve(r);
                // console.log(667, r);
              } else if (typeof r === 'function') {


                return (receiver['$b031$' + property] || (receiver['$b031$' + property] = ((r) => (function () { return r.apply(this['$a039$'] || this, arguments) }))(r)));

              }
              return r;
            }
          });

          const mResult = {
            headers: result.headers, ok: result.ok, redirected: result.redirected, status: result.status,
            statusText: result.statusText, type: result.type, url: result.url, get lockedBodyStream() { return mBody },

          };

          resolve(pRes);
          Promise.resolve().then(() => {
            messageHandler.onResponse(mResult, { requestTime, responseTime });
          }).catch(console.warn);

        }).catch((error) => {
          reject(error);
        })

      });
    }

    const findButtonByExpression = (()=>{

      const xpathExpressionV1 = '//button[normalize-space(text())="?"][contains(@class, "h-") and contains(@class, "w-")]';
      const xpathExpressionV0 = '//div[normalize-space(text())="?"][contains(@class, "h-") and contains(@class, "w-")]';

      return (contextNode) => {
        let w = document.evaluate(xpathExpressionV1, contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        if (!w || !w.singleNodeValue) {
          w = document.evaluate(xpathExpressionV0, contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
          if (!w || !w.singleNodeValue) {
            w = null;
          }
        }
        return w;
      }

    })(); 

    let observer = null;
    let mct = 0;
    let wType = 0;

    let attachedGroup = null;


    function openChatBubble() {
      const chatBubble = document.querySelector(".mr-message-bubble");
      if (!chatBubble) return;
      chatBubble.classList.add('mr-open');
    }

    function isChatBubbleOpened() {
      const chatBubble = document.querySelector(".mr-message-bubble");
      if (!chatBubble) return null;
      return chatBubble.classList.contains('mr-open');
    }

    function closeChatBubble() {
      const chatBubble = document.querySelector(".mr-message-bubble");
      if (!chatBubble) return;
      chatBubble.classList.remove('mr-open');
    }


    const myGroupClicked = (myGroup) => {



      const chatBubble = document.querySelector(".mr-message-bubble");
      if (!chatBubble) return null;

      const msgP = document.querySelector("#mr-msg-p1");
      if (!msgP || msgP.firstChild === null) return null;

      if (!chatBubble.classList.contains('mr-open')) {
        myGroup.classList.add('mr-clicked');
        openChatBubble()

        return true;

      } else {

        myGroup.classList.remove('mr-clicked');
        closeChatBubble();

        return false;
      }
      return null;

    }

    /** @param {HTMLElement} myGroup */
    const setupMyGroup = (myGroup) => {

      addCssText();

      const buttonText = findButtonByExpression(myGroup).singleNodeValue;
      buttonText.textContent = '\u{1F4D9}';



      const placeholder = document.createElement('div');

      placeholder.classList.add('mr-k33');
      placeholder.innerHTML = `
        <div class="mr-message-bubble mr-tri-right mr-round mr-border mr-btm-right">
          <div class="mr-msg-text">
              <p id="mr-msg-l">count(messages)</p>
              <p id="mr-msg-p"><span id="mr-msg-p1"></span><span id="mr-msg-p2"></span></p>
            <p class="mr-progress-bar"></p>
          </div>
        </div>
      `;
      myGroup.classList.add('mr-button-container');

      myGroup.insertBefore(placeholder, myGroup.firstChild);



      myGroup.addEventListener('click', function (evt) {



        if (!evt || !evt.target) return;
        if (evt.target.closest('.mr-k33')) return;

        const myGroup = this;


        const chatBubble = document.querySelector(".mr-message-bubble");
        if (!chatBubble) return;


        let clickedResult = myGroupClicked(myGroup);
        if (typeof clickedResult === 'boolean') {

          if (evt.isTrusted === true) {

            if (clickedResult) {
              userOpenAction = true;

            } else {

              userOpenAction = false;
            }
          }
        }



      })


    }

    const onElementFound = (matchedElement) => {

      console.log('onElementFound', matchedElement)



      let group = matchedElement.closest('.group');
      if (!group) {
        console.log('The group parent of Question Mark Button cannot be found.')
        return;
      }
      let groupParent = group;
      let level = 0;
      while (groupParent && groupParent.nextSibling === null && groupParent.previousSibling === null && (groupParent.parentNode instanceof HTMLElement)) {

        groupParent = groupParent.parentNode;

        if (++level === 1) {
          groupParent.style.columnGap = '6px';
        }


        groupParent.classList.remove('flex-col');
        groupParent.classList.add('flex-row');

        groupParent.style.display = 'inline-flex'


      }


      if (!attachedGroup) {


        let myGroup = group.cloneNode(true);

        // if (/\bright-\d+\b/.test(myGroup.className)) {
          myGroup.classList.add('mr-offset-book-btn');
        // }
        group.parentNode.insertBefore(myGroup, group);
        setupMyGroup(myGroup);

        attachedGroup = myGroup;
      } else {
        group.parentNode.insertBefore(attachedGroup, group);

      }

    }

    const setupMRAM = () => {

      const mram = document.createElement('mr-activity-measure');
      mram.setAttribute('m', '');
      document.head.appendChild(document.createElement('style')).textContent = `
        mr-activity-measure[m] {
          visibility: collapse !important;
          width: 1px !important;
          height: 1px !important;

          display: block !important;
          z-index: -1 !important;
          contain: strict !important;
          box-sizing: border-box !important;

          position: fixed !important;
          top: -1000px !important;
          left: -1000px !important;
          animation: ${foregroundActivityMeasureInterval}ms ease-in 500ms infinite alternate forwards running mrActivityMeasure !important;
        }
        @keyframes mrActivityMeasure{
          0%{
            order: 0;
          }
          100%{
            order: 1;
          }
        }
      `;
      let lastEt = 0;
      mram.onanimationiteration = function (evt) {
        const et = evt.elapsedTime * 1000;
        if (__totalActivityMeasure > et) {
          this.onanimationiteration = null;
          return;
        }
        const wt = lastEt;
        lastEt = et;
        const dt = et - wt;
        if (dt < amiUL && dt > amiLL) __foregroundActivityMeasure += foregroundActivityMeasureInterval;

        __totalActivityMeasure = et;
        // console.log(evt)
      }
      document.documentElement.appendChild(mram);
    }

    const findAndHandleElement = () => {
      if (!observer) return;

      if (wType !== 0) {

        if (!attachedGroup) return;
        if (attachedGroup.isConnected) return;
      }


      const result = findButtonByExpression( document);
      if(!result) return;
      const matchedElement = result.singleNodeValue;

      if (!matchedElement) return;
      console.log('findAndHandleElement OK');
      const cb = wType === 0 ? setupMRAM : () => { };
      if (wType === 0) {
        wType = 1;
      }
      Promise.resolve(matchedElement).then(onElementFound).then(cb).catch(console.warn);
    }


    const findAndHandleElementForegroundWrapped = foregroundWrap(findAndHandleElement);

    observer = new MutationObserver(function (mutationsList) {
      if (!observer) return;
      findAndHandleElementForegroundWrapped();
    });

    observer.observe(document, { childList: true, subtree: true });

    (async function (){

      if (document.readyState === 'loading') {
        await new Promise(resolve=>window.addEventListener('load', resolve, false));
      }

      if (!observer) return;
      if (wType > 0) return;

      const main = await observablePromise(() => document.querySelector('main')).obtain();
      if (!main) return;
      await getRafPromise();

      setTimeout(function () {
        if (!observer) return;
        if (wType > 0) return;
        console.log('The Question Mark Button cannot be found.')
        observer.disconnect();
        observer.takeRecords();
        observer = null;
      }, 1200);

    })();



    



    console.log('script loaded')



  })


})();


/**
 *


$record_time_ms:  1692831419486
$requested_at:    1692831418865
$responsed_at:    1692831419485
create_time:      1692831782.061773


server time is now() + 6 minutes

 *
 *
 */