GPT Auto task

根据缓存中的数据自动在网页上与chat gpt对话

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @namespace         https://greasyfork.org/zh-CN/users/1106595-ikkem-lin
// @name              GPT Auto task
// @author            Mark
// @description       根据缓存中的数据自动在网页上与chat gpt对话
// @homepageURL       https://github.com/IKKEM-Lin/gpt-auto-task
// @version           0.2.0
// @match             *chat.openai.com/*
// @run-at            document-idle
// @require           https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js
// @require           https://cdn.jsdelivr.net/npm/idb-keyval@6/dist/umd.js
// ==/UserScript==
(function () {
  "use strict";

  const tableName = "data";

  const dbTable = {
    tasks: idbKeyval.createStore("tasks", tableName),
    config: idbKeyval.createStore("config", tableName),
    skipSnippet: idbKeyval.createStore("skipSnippet", tableName),
    response1: idbKeyval.createStore("response1", tableName),
    response2: idbKeyval.createStore("response2", tableName),
    responseProcessed: idbKeyval.createStore("responseProcessed", tableName),
  };

  const locationReload = () => {
    location.href = "https://chat.openai.com/?model=gpt-4";
  };

  const downloadFile = (data, fileName) => {
    const a = document.createElement("a");
    document.body.appendChild(a);
    a.style = "display: none";
    const blob = new Blob([data], {
      type: "application/octet-stream",
    });
    const url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = fileName;
    a.click();
    window.URL.revokeObjectURL(url);
  };

  const yaml2object = (yamlStr) => {
    try {
      return jsyaml.load(yamlStr);
    } catch (error) {
      return null;
    }
  };

  function hashFnv32a(str, asString = true, seed = undefined) {
    /*jshint bitwise:false */
    var i,
      l,
      hval = seed === undefined ? 0x811c9dc5 : seed;

    for (i = 0, l = str.length; i < l; i++) {
      hval ^= str.charCodeAt(i);
      hval +=
        (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
    }
    if (asString) {
      // Convert to 8 digit hex string
      return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
    }
    return hval >>> 0;
  }

  function reactionObjHandler(input) {
    let result = [];
    const validateKeys = ["reactants", "products", "condition", "catalysts"];
    function test(reaction) {
      // if (!reaction) {
      //     return
      // }
      if (reaction instanceof Array) {
        return reaction.map((item) => {
          return test(item);
        });
      } else {
        try {
          var keys = Object.keys(reaction);
        } catch (error) {
          //   debugger;
          console.error(error);
          throw new Error();
        }
        if (validateKeys.some((key) => keys.includes(key))) {
          result.push(reaction);
          return;
        }
        keys.forEach((key) => {
          if (reaction[key] && typeof reaction[key] === "object") {
            test(reaction[key]);
          }
        });
      }
    }
    test(input);
    return result;
  }

  function readFile(accept = "", multiple = false) {
    const inputEl = document.createElement("input");
    inputEl.setAttribute("type", "file");
    inputEl.setAttribute("accept", accept);
    inputEl.setAttribute("multiple", !!multiple);
    return new Promise((resolve, reject) => {
      inputEl.addEventListener("change", (e) => {
        resolve(multiple ? inputEl.files : inputEl.files[0]);
        window.removeEventListener("click", onWindowClick, true);
      });
      inputEl.click();

      const onWindowClick = () => {
        if (!inputEl.value) {
          reject(new Error("用户取消选择"));
        }
        window.removeEventListener("click", onWindowClick, true);
      };
      setTimeout(() => {
        window.addEventListener("click", onWindowClick, true);
      }, 100);
    });
  }

  class GPT_ASK_LOOP {
    queue = [];
    abstract = [];
    responds = [];
    checkInterval = 20000;
    account = "";
    downloadBtn = null;
    retrying = false;
    lastSaveTime = 0;
    prompt1 = "";
    prompt2 = "";
    modelNum = 1;

    INPUT_SELECTOR = "#prompt-textarea";
    SUBMIT_BTN_SELECTOR = 'button[data-testid="send-button"]';
    RESPOND_SELECTOR = 'main div[data-message-author-role="assistant"]';
    NEW_CHART_BTN_SELECTOR = "nav div.flex-col a[href='/']";
    NORMAL_RESPOND_BTN_SELECTOR = "form div button.btn-neutral";
    ERROR_RESPOND_BTN_SELECTOR = "form div button.btn-primary";

    sleep(duration) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(true);
        }, duration);
      });
    }

    constructor(account) {
      this.initData().then(() => {
        this.account = account || Math.ceil(Math.random() * 1e10).toString(32);
        const btnWrap = document.createElement("div");
        btnWrap.innerHTML = `<button style="padding: 4px 8px;position: fixed;bottom: 20%;right: 8px;border-radius: 4px;background-color: #224466;color: #fff;">下载已生成结果(queue: ${this.queue.length}, res: ${this.responds.length})</button>`;
        this.downloadBtn = btnWrap.querySelector("button");
        this.downloadBtn.onclick = this.handleDownload.bind(this);
        document.body.appendChild(btnWrap);

        const btnWrapBackup = document.createElement("div");
        btnWrapBackup.innerHTML = `<button style="padding: 4px 8px;position: fixed;bottom: 30%;right: 8px;border-radius: 4px;background-color: #224466;color: #fff;">备份</button>`;
        const backupBtn = btnWrapBackup.querySelector("button");
        backupBtn.onclick = this.backUp.bind(this);
        document.body.appendChild(btnWrapBackup);

        const btnWrapImport = document.createElement("div");
        btnWrapImport.innerHTML = `<button style="padding: 4px 8px;position: fixed;bottom: 40%;right: 8px;border-radius: 4px;background-color: #224466;color: #fff;">导入</button>`;
        const importBtn = btnWrapImport.querySelector("button");
        importBtn.onclick = async () => {
          if (
            !window.confirm(
              "The data in browser will be clear up. Please make sure you have to do this !!!"
            )
          ) {
            return;
          }
          const file = await readFile(".json");
          const reader = new FileReader();

          reader.onload = (event) => {
            const json = JSON.parse(event.target.result);
            // console.log({json}, 'json')
            this.importFromBackUp.bind(this)(json);
          };

          reader.readAsText(file);
        };
        document.body.appendChild(btnWrapImport);
        this.main();
      });
    }

    async initData() {
      await this.legacyTaskInit();
      const skipSnippetKeys = await idbKeyval.keys(dbTable.skipSnippet);
      const responseKeys = await idbKeyval.keys(dbTable.responseProcessed);
      const responseValues = await idbKeyval.values(dbTable.responseProcessed);
      this.responds = responseValues.map((item) => ({
        articleId: item.articleId,
        snippetId: item.snippetId,
        createdTime: item.createdTime,
      }));
      const indexDBConfig = (await idbKeyval.entries(dbTable.config)) || [];
      if (indexDBConfig.length) {
        this.prompt1 = indexDBConfig.find((item) => item[0] === "prompt1");
        this.prompt2 = indexDBConfig.find((item) => item[0] === "prompt2");
        this.modelNum = indexDBConfig.find((item) => item[0] === "modelNum");
        this.prompt1 = (this.prompt1 && this.prompt1[1]) || "";
        this.prompt2 = (this.prompt2 && this.prompt2[1]) || "";
        this.modelNum = (this.modelNum && this.modelNum[1]) || 1;
      }
      const snippetSourceData = (await idbKeyval.values(dbTable.tasks)) || [];
      this.abstract = snippetSourceData.filter(
        (item) => item.type == "abstract"
      );
      const paragraphs = snippetSourceData.filter(
        (item) => item.type != "abstract"
      );
      this.queue = paragraphs.filter(
        (item) =>
          !(responseKeys || []).includes(`${item.article_id}-${item.id}`) &&
          !(skipSnippetKeys || []).includes(`${item.article_id}-${item.id}`)
      );
      if (this.queue.length !== 0) {
        return;
      }
      this.queue = paragraphs.filter((item) =>
        (skipSnippetKeys || []).includes(`${item.article_id}-${item.id}`)
      );
      const skipSnippetEntries = await idbKeyval.entries(dbTable.skipSnippet);
      this.queue.sort((a,b) => {
        const aItem = skipSnippetEntries.find(item => item[0] === `${a.article_id}-${a.id}`)
        const bItem = skipSnippetEntries.find(item => item[0] === `${b.article_id}-${b.id}`)
        return aItem[1] < bItem[1] ? -1 : 1
      })
    }

    async legacyTaskInit() {
      const indexDBTasks = (await idbKeyval.entries(dbTable.tasks)) || [];
      const indexDBConfig = (await idbKeyval.entries(dbTable.config)) || [];
      if (indexDBConfig.length === 0) {
        const prompt1 = localStorage.getItem("mock_prompt1");
        const prompt2 = localStorage.getItem("mock_prompt2");
        const modelNum = +localStorage.getItem("model_number") || 1;
        if (!prompt1 || !prompt2) {
          return;
        }
        await idbKeyval.setMany(
          [
            ["prompt1", prompt1],
            ["prompt2", prompt2],
            ["modelNum", modelNum],
          ],
          dbTable.config
        );
      }
      if (indexDBTasks.length === 0) {
        const snippetSourceData = JSON.parse(
          localStorage.getItem("snippetSourceData") || "[]"
        );
        if (!snippetSourceData.length) {
          return;
        }
        const snippetSourceDataEntries = snippetSourceData.map((item) => [
          `${item.article_id}-${item.id}`,
          item,
        ]);
        await idbKeyval.setMany(snippetSourceDataEntries, dbTable.tasks);
      }
    }

    async importFromBackUp(data) {
      const {
        response1,
        response2,
        responseProcessed,
        skipSnippet,
        config,
        tasks,
      } = data;
      if (
        !(
          response1 &&
          response2 &&
          responseProcessed &&
          skipSnippet &&
          config &&
          tasks
        )
      ) {
        alert(
          `[ "response1", "response2", "responseProcessed", "skipSnippet", "config", "tasks" ], all of them are required`
        );
        return;
      }
      await idbKeyval.clear(dbTable.response1);
      await idbKeyval.clear(dbTable.response2);
      await idbKeyval.clear(dbTable.responseProcessed);
      await idbKeyval.clear(dbTable.skipSnippet);
      await idbKeyval.clear(dbTable.config);
      await idbKeyval.clear(dbTable.tasks);
      await idbKeyval.setMany(response1, dbTable.response1);
      await idbKeyval.setMany(response2, dbTable.response2);
      await idbKeyval.setMany(responseProcessed, dbTable.responseProcessed);
      await idbKeyval.setMany(skipSnippet, dbTable.skipSnippet);
      await idbKeyval.setMany(config, dbTable.config);
      await idbKeyval.setMany(tasks, dbTable.tasks);
      locationReload();
    }

    async backUp() {
      const response1 = (await idbKeyval.entries(dbTable.response1)) || [];
      const response2 = (await idbKeyval.entries(dbTable.response2)) || [];
      const responseProcessed =
        (await idbKeyval.entries(dbTable.responseProcessed)) || [];
      const skipSnippet = (await idbKeyval.entries(dbTable.skipSnippet)) || [];
      const config = (await idbKeyval.entries(dbTable.config)) || [];
      const tasks = (await idbKeyval.entries(dbTable.tasks)) || [];

      const paragraphs = tasks.filter((item) => item[1].type != "abstract");

      const articleIds = tasks.map((item) => item[1].article_id).sort();

      const now = new Date();
      const current = `${now.getFullYear()}-${
        now.getMonth() + 1
      }-${now.getDate()}-${now.getHours()}${now.getMinutes()}${now.getSeconds()}`;
      downloadFile(
        JSON.stringify({
          response1,
          response2,
          responseProcessed,
          skipSnippet,
          config,
          tasks,
        }),
        `Article_${articleIds[0]}_${
          articleIds[articleIds.length - 1]
        }-progress_${paragraphs.length}_${
          responseProcessed.length
        }-${current}.backup.json`
      );
    }

    async handleDownload() {
      const reactionGroups = await idbKeyval.values(dbTable.responseProcessed);
      if (!reactionGroups.length) {
        return;
      }
      const reactions = [];
      reactionGroups.forEach((item) => {
        const { articleId, snippetId, reaction } = item;
        const uniqReaction = Array.from(
          new Set(reaction.map((v) => JSON.stringify(v)))
        ).map((v) => JSON.parse(v));
        uniqReaction.forEach((data) => {
          const name = hashFnv32a(JSON.stringify(data));
          reactions.push({ articleId, snippetId, data, name });
        });
      });

      const now = new Date();
      downloadFile(
        JSON.stringify(reactions),
        `${this.account}-${now.getFullYear()}-${
          now.getMonth() + 1
        }-${now.getDate()}-${now.getHours()}${now.getMinutes()}${now.getSeconds()}-${
          reactions.length
        }.json`
      );
    }

    async report(tip = "") {
      await fetch("http://localhost:3000", {
        method: "POST",
        body: JSON.stringify({
          account: this.account,
          reaction_count: this.responds.length,
          queue_count: this.queue.length,
          tip: tip,
        }),
      }).catch((err) => {
        // console.error({ err });
      });
    }

    genPrompt(content, step = 1) {
      return step === 1
        ? `${this.prompt1}

              ''' ${content} ''' `
        : this.prompt2;
    }

    async _updateDownloadBtnText() {
      if (this.downloadBtn) {
        const snippetSourceData = (await idbKeyval.values(dbTable.tasks)) || [];
        const paragraphs = snippetSourceData.filter(
          (item) => item.type != "abstract"
        );
        this.downloadBtn.innerText = `下载已生成结果(queue: ${
          this.queue.length
        }, res: ${this.responds.length}, skip: ${
          paragraphs.length - this.queue.length - this.responds.length
        })`;
      }
    }

    _getLastRespondTime() {
      return Math.max.apply(
        null,
        this.responds
          .map((item) => item.createdTime)
          .filter((item) => item)
          .concat([0])
      );
    }

    getTask() {
      const task = this.queue[0];
      const maxTime = this._getLastRespondTime();
      this.report(
        (task &&
          `Working on articleId: ${task.article_id}, snippetId: ${
            task.id
          }, last-update-time: ${new Date(maxTime).toLocaleString()}`) ||
          ""
      );
      if (!task) {
        console.log("任务队列为空");
        return async () => null;
      }
      return async () => {
        const { article_id, id, content } = task;
        const relatedAbstract =
          this.abstract.find((item) => item.article_id === article_id)
            ?.content || "";
        console.log(
          `开始触发 ${article_id}-${id}, ${new Date().toTimeString()}`
        );
        const promptContent = `
                  ${relatedAbstract}

                  ${content}
                  `;
        const prompt1 = this.genPrompt(promptContent, 1);
        const prompt2 = this.genPrompt(promptContent, 2);
        const result1 = await this.trigger(prompt1).catch((err) => {
          return null;
        });
        if (!result1) {
          return null;
        }
        await idbKeyval.set(
          `${article_id}-${id}`,
          {
            articleId: article_id,
            snippetId: id,
            reaction: result1,
            createdTime: new Date().valueOf(),
          },
          dbTable.response1
        );
        await this.sleep(3 * 1000);
        const result2 = await this.trigger(prompt2).catch((err) => {
          return null;
        });
        if (!result2) {
          return { articleId: article_id, snippetId: id, reaction: result1 };
        }
        await idbKeyval.set(
          `${article_id}-${id}`,
          {
            articleId: article_id,
            snippetId: id,
            reaction: result2,
            createdTime: new Date().valueOf(),
          },
          dbTable.response2
        );
        return { articleId: article_id, snippetId: id, reaction: result2 };
      };
    }

    async rawReactionProcess(rawReactionHTML) {
      const ele = document.createElement("div");
      ele.innerHTML = rawReactionHTML;
      const res = Array.from(ele.querySelectorAll("code"))
        .map((el) => el.innerText)
        .map((yml) => yaml2object(yml));

      if (res && res.length > 0 && res.every((s) => s !== null)) {
        const result = reactionObjHandler(res);
        return result.length > 0 ? result : null;
      }
      return null;
    }

    async skipSnippetHandler(articleId, snippetId) {
      const oldVal = await idbKeyval.get(
        `${articleId}-${snippetId}`,
        dbTable.skipSnippet
      );
      await idbKeyval.set(
        `${articleId}-${snippetId}`,
        (oldVal || 0) + 1,
        dbTable.skipSnippet
      );
      this.queue = this.queue.filter((item) => item.id !== snippetId);
    }

    async saveRespond(respond) {
      const { articleId, snippetId } = respond;
      const currentTimeStamp = new Date().valueOf();
      const reactionProcessed = await this.rawReactionProcess(respond.reaction);
      if (!reactionProcessed) {
        console.warn(`${articleId}-${snippetId} 无法解析出 reaction, 即将跳过`);
        await this.skipSnippetHandler(articleId, snippetId);
        return;
      }
      this.responds.push({
        articleId,
        snippetId,
        createdTime: currentTimeStamp,
      });
      this.queue = this.queue.filter((item) => item.id !== snippetId);

      await idbKeyval.set(
        `${articleId}-${snippetId}`,
        {
          ...respond,
          reaction: reactionProcessed,
          createdTime: new Date().valueOf(),
        },
        dbTable.responseProcessed
      );
      try {
        await idbKeyval.del(`${articleId}-${snippetId}`, dbTable.skipSnippet);
      } catch (err) {}
      if (this.responds.length && this.responds.length % 50 === 0) {
        this.handleDownload.bind(this)();
      }
    }

    trigger(prompt, checkInterval = this.checkInterval) {
      return new Promise((resolve, reject) => {
        const textEl = document.querySelector(this.INPUT_SELECTOR);
        const submitEl = document.querySelector(this.SUBMIT_BTN_SELECTOR);
        textEl.value = prompt;
        textEl.dispatchEvent(new Event("input", { bubbles: true }));
        setTimeout(() => {
          submitEl.click();

          let resCache = null;
          let checkOutputCount = 0;
          (async () => {
            while (true) {
              await this.sleep(checkInterval);
              const result = Array.from(
                document.querySelectorAll(this.RESPOND_SELECTOR)
              );
              const temp = result[result.length - 1];
              if (!temp) {
                if (checkOutputCount > 0) {
                  console.log("检查结果超时");
                  reject(null);
                  break;
                }
                checkOutputCount++;
                continue;
              }
              if (resCache === temp.innerHTML) {
                // console.log("匹配,resCache:", resCache);
                const validateResult = await this.validate(resCache).catch(
                  (err) => {
                    reject(null);
                    return;
                  }
                );
                if (validateResult === true) {
                  resolve(resCache);
                  break;
                } else if (validateResult === false) {
                  continue;
                }
                reject(null);
                break;
              }
              resCache = temp.innerHTML;
              console.log(`${checkInterval / 1000}s后再次检查结果`);
            }
          })();
        }, 4000);
      });
    }

    async validate(innerHTML) {
      const buttons = document.querySelectorAll(
        this.NORMAL_RESPOND_BTN_SELECTOR
      );
      const errorBtn = document.querySelectorAll(
        this.ERROR_RESPOND_BTN_SELECTOR
      );
      const feedbackBtns = document.querySelectorAll('main .final-completion button[class*="final-completion"]')
      const regenerateBtn = feedbackBtns[feedbackBtns.length - 1];
      // 如果触发gpt-4 3小时25次限制
      if (!regenerateBtn && !errorBtn[0] && innerHTML.includes("usage cap")) {
        console.error("触发gpt-4 3小时25次限制,等待10min后重试");
        await this.sleep(10 * 60 * 1000);
        throw new Error("触发gpt-4 3小时25次限制");
      }
      // 如果openAI服务器报错未返回结果
      if (errorBtn[0]) {
        // && innerHTML.includes("wrong")) {
        if (this.retrying) {
          this.retrying = false;
          return true;
        }
        errorBtn[0].click();
        this.retrying = true;
        return false;
      }
      // 如果输出结果未包含code标签
      if (!innerHTML.includes("</code>")) {
        if (this.retrying) {
          this.retrying = false;
          console.error("第二次还是未输出yaml结构");
          throw new Error("未返回yaml结构");
        }
        console.error("未输出yaml结构,重试一次");
        regenerateBtn.click();
        this.retrying = true;
        return false;
      }
      this.retrying = false;
      // 如果还未完全输出
      if (
        buttons.length > 1 &&
        !buttons[buttons.length - 1].innerText.includes("Regenerate")
      ) {
        buttons[buttons.length - 1].click();
        return false;
      }
      return true;
    }

    async main(sleepTime = 5000) {
      let emptyCount = 0;
      while (true) {
        // {0: gpt-3.5, 1: gpt-4}
        const modelNum = this.modelNum;
        // const gpt4btn = document.querySelectorAll(
        //   "ul > li > button.cursor-pointer"
        // )[modelNum];

        // if (gpt4btn) {
        //   console.log(`当前模型为:${gpt4btn.innerText}`);
        //   gpt4btn.firstChild.click();
        // } else {
        //   console.warn(`无法选择模型,2分钟后刷新`);
        //   await this.sleep(2 * 60 * 1000);
        //   locationReload();
        // }
        const currentModel = document.querySelector('main [aria-haspopup="menu"]')?.innerText;
        const isGPT4 = currentModel.trim() === "ChatGPT 4"
        await this.sleep(sleepTime / 2);
        if (
          modelNum === 1 && !isGPT4
        ) {
          console.log("未切换到gpt-4模式, 5分钟后重试");
          const maxTime = this._getLastRespondTime();
          const diff = new Date().valueOf() - maxTime;
          if (maxTime && diff > 1.5 * 60 * 60 * 1000) {
            console.warn("超时未刷新, 5分钟后刷新页面");
            await this.sleep(5 * 60 * 1000);
            locationReload();
            break;
          }
          this.report(
            `触发gpt-4 3小时25次限制,上次运行时间:${new Date(
              maxTime
            ).toLocaleString()}`
          );
          await this.sleep(5 * 60 * 1000);
          const newChatBtn = document.querySelector(
            this.NEW_CHART_BTN_SELECTOR
          );
          newChatBtn.click();
          continue;
        }
        const task = this.getTask();
        if (!task) {
          if (emptyCount > 0) {
            console.warn("连续两次未获取到任务,2分钟后刷新");
            await this.sleep(2 * 60 * 1000);
            locationReload();
            break;
          }
          emptyCount++;
          await this.sleep(5 * 60 * 1000);
          continue;
        }

        const result = await task();
        if (result) {
          this.saveRespond(result);
          emptyCount = 0;
        } else {
          if (emptyCount > 0) {
            const task = this.queue[0];
            const { article_id, id } = task;
            console.warn(
              `${article_id}-${id}连续两次未获取到任务值,2分钟后刷新`
            );
            await this.skipSnippetHandler(article_id, id);
            await this.sleep(2 * 60 * 1000);
            locationReload();
            break;
          }
          emptyCount += 1;
        }
        console.log(`${sleepTime / 1000}s后将再次触发`);
        const newChatBtn = document.querySelector(this.NEW_CHART_BTN_SELECTOR);
        newChatBtn.click();
        await this.sleep(sleepTime / 2);
        this._updateDownloadBtnText();
      }
    }
  }

  function secondInterval() {
    console.log("start secondInterval...");
    const sleep = (duration) => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(true);
        }, duration);
      });
    };
    setInterval(async () => {
      const responds = await idbKeyval.values(dbTable.responseProcessed);
      const maxTime = Math.max.apply(
        null,
        responds
          .map((item) => item.createdTime)
          .filter((item) => item)
          .concat([0])
      );
      const diff = new Date().valueOf() - maxTime;

      console.log(`last updated at: ${maxTime}, diff is ${diff}`);
      if (maxTime && diff > 30 * 60 * 1000) {
        console.warn("超时未刷新, 2分钟后刷新页面");
        await sleep(2 * 60 * 1000);
        locationReload();
      }
    }, 10 * 60 * 1000);
  }

  function start() {
    const ACCOUNT_NAME_SELECTOR = "nav > div:last-child > div:last-child";
    const nameEl = document.querySelector(ACCOUNT_NAME_SELECTOR);
    const name = nameEl && nameEl.innerText;
    if (name) {
      new GPT_ASK_LOOP(name);
      secondInterval();
    } else {
      setTimeout(() => {
        start();
      }, 5000);
    }
  }
  start();
})();