ChatGPT: Message Records

Remind you how many quota you left

// ==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

 *
 *
 */