ChatGPT用量统计

优雅的 ChatGPT 模型调用量实时统计,界面简洁清爽(中文版),支持导入导出、一周分析报告、快捷键切换最小化(Ctrl/Cmd+I)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         ChatGPT用量统计
// @namespace    https://github.com/tizee/tampermonkey-chatgpt-model-usage-monitor
// @version      4.0.0
// @description  优雅的 ChatGPT 模型调用量实时统计,界面简洁清爽(中文版),支持导入导出、一周分析报告、快捷键切换最小化(Ctrl/Cmd+I)
// @author       tizee (original), schweigen (modified)
// @match        https://chatgpt.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @license      MIT
// @run-at       document-start
// ==/UserScript==


(() => {
  // src/config.js
  var COLORS = {
    primary: "#5E9EFF",
    background: "#1A1B1E",
    surface: "#2A2B2E",
    border: "#363636",
    text: "#E5E7EB",
    secondaryText: "#9CA3AF",
    success: "#10B981",
    warning: "#F59E0B",
    danger: "#EF4444",
    disabled: "#4B5563",
    white: "oklch(.928 .006 264.531)",
    gray: "oklch(.92 .004 286.32)",
    yellow: "oklch(.905 .182 98.111)",
    green: "oklch(.845 .143 164.978)",
    progressLow: "#EF4444",
    progressMed: "#F59E0B",
    progressHigh: "#10B981",
    progressExceed: "#4B5563",
    hourModel: "#61DAFB",
    dailyModel: "#9F7AEA",
    weeklyModel: "#10B981",
    monthlyModel: "#F472B6"
  };
  var STYLE = {
    borderRadius: "12px",
    boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.2), 0 2px 4px -1px rgba(0, 0, 0, 0.1)",
    spacing: {
      xs: "4px",
      sm: "8px",
      md: "16px",
      lg: "24px"
    },
    textSize: {
      xs: "0.75rem",
      sm: "0.875rem",
      md: "1rem"
    },
    lineHeight: {
      xs: "calc(1/.75)",
      sm: "calc(1.25/.875)",
      md: "1.5"
    }
  };
  var TIME_WINDOWS = {
    hour3: 3 * 60 * 60 * 1e3,
    hour5: 5 * 60 * 60 * 1e3,
    daily: 24 * 60 * 60 * 1e3,
    weekly: 7 * 24 * 60 * 60 * 1e3,
    monthly: 30 * 24 * 60 * 60 * 1e3
  };
  var defaultUsageData = {
    position: { x: null, y: null },
    size: { width: 400, height: 500 },
    minimized: false,
    silentMode: false,
    progressType: "bar",
    planType: "team",
    showWindowResetTime: false,
    sharedQuotaGroups: {},
    models: {
      "gpt-5-2-pro": { requests: [], quota: 15, windowType: "monthly" },
      "gpt-5-1-pro": { requests: [], quota: 15, windowType: "monthly" },
      "gpt-5-pro": { requests: [], quota: 15, windowType: "monthly" },
      "o3-pro": { requests: [], quota: 0, windowType: "monthly" },
      "gpt-4-5": { requests: [], quota: 0, windowType: "daily" },
      "gpt-5-2-thinking": { requests: [], quota: 3e3, windowType: "weekly" },
      "gpt-5-1-thinking": { requests: [], quota: 3e3, windowType: "weekly" },
      "gpt-5-thinking": { requests: [], quota: 3e3, windowType: "weekly" },
      o3: { requests: [], quota: 100, windowType: "weekly" },
      "gpt-5-2-instant": { requests: [], quota: 1e4, windowType: "hour3" },
      "gpt-5-1": { requests: [], quota: 1e4, windowType: "hour3" },
      "gpt-5": { requests: [], quota: 1e4, windowType: "hour3" },
      "gpt-5-t-mini": { requests: [], quota: 1e4, windowType: "hour3" },
      "o4-mini": { requests: [], quota: 300, windowType: "daily" },
      "gpt-4o": { requests: [], quota: 80, windowType: "hour3" },
      "gpt-4-1": { requests: [], quota: 500, windowType: "hour3" },
      "gpt-5-mini": { requests: [], quota: 1e4, windowType: "hour3" }
    }
  };
  var SHARED_GROUP_COLORS = {
    "pro-premium-shared": "#facc15",
    "pro-thinking-shared": "#34d399",
    "pro-instant-shared": "#60a5fa",
    "team-premium-shared": "#facc15",
    "edu-premium-shared": "#facc15",
    "enterprise-premium-shared": "#facc15",
    "go-thinking-shared": "#34d399",
    "k12-thinking-shared": "#34d399",
    "plus-thinking-shared": "#34d399",
    "team-thinking-shared": "#34d399",
    "edu-thinking-shared": "#34d399",
    "enterprise-thinking-shared": "#34d399",
    "free-instant-shared": "#60a5fa",
    "go-instant-shared": "#60a5fa",
    "k12-instant-shared": "#60a5fa",
    "plus-instant-shared": "#60a5fa",
    "team-instant-shared": "#60a5fa",
    "edu-instant-shared": "#60a5fa",
    "enterprise-instant-shared": "#60a5fa"
  };
  var MODEL_DISPLAY_ORDER = [
    "gpt-5-2-pro",
    "gpt-5-1-pro",
    "gpt-5-pro",
    "o3-pro",
    "gpt-4-5",
    "gpt-5-2-thinking",
    "gpt-5-1-thinking",
    "gpt-5-thinking",
    "o3",
    "gpt-5-2-instant",
    "gpt-5-1",
    "gpt-5-t-mini",
    "o4-mini",
    "gpt-4o",
    "gpt-4-1",
    "gpt-5-mini",
    "gpt-5",
    "alpha"
  ];
  var MODEL_DISPLAY_NAME_OVERRIDES = {
    "gpt-5": "gpt-5-instant",
    "gpt-5-1": "gpt-5-1-instant"
  };
  function displayModelName(modelKey) {
    return MODEL_DISPLAY_NAME_OVERRIDES[modelKey] || modelKey;
  }
  var PLAN_DISPLAY_ORDER = [
    "free",
    "go",
    "k12_teacher",
    "plus",
    "team",
    "edu",
    "enterprise",
    "pro"
  ];
  var PLAN_CONFIGS = {
    free: {
      name: "Free",
      sharedQuotaGroups: {
        "free-instant-shared": {
          quota: 10,
          windowType: "hour5",
          displayName: "Free即时共用池"
        }
      },
      models: {
        "gpt-5-2-pro": { quota: 0, windowType: "monthly" },
        "gpt-5-1-pro": { quota: 0, windowType: "monthly" },
        "gpt-5-pro": { quota: 0, windowType: "monthly" },
        "o3-pro": { quota: 0, windowType: "monthly" },
        "gpt-4-5": { quota: 0, windowType: "daily" },
        "gpt-5-2-thinking": { quota: 1, windowType: "hour5" },
        "gpt-5-1-thinking": { quota: 1, windowType: "hour5" },
        "gpt-5-thinking": { quota: 1, windowType: "hour5" },
        o3: { quota: 0, windowType: "weekly" },
        "gpt-5-2-instant": { sharedGroup: "free-instant-shared" },
        "gpt-5-1": { sharedGroup: "free-instant-shared" },
        "gpt-5": { sharedGroup: "free-instant-shared" },
        "gpt-5-t-mini": { quota: 10, windowType: "daily" },
        "o4-mini": { quota: 0, windowType: "daily" },
        "gpt-4o": { quota: 0, windowType: "hour3" },
        "gpt-4-1": { quota: 0, windowType: "hour3" },
        "gpt-5-mini": { quota: 1e4, windowType: "hour3" }
      }
    },
    go: {
      name: "Go",
      sharedQuotaGroups: {
        "go-thinking-shared": {
          quota: 10,
          windowType: "hour5",
          displayName: "Go思考共用池"
        },
        "go-instant-shared": {
          quota: 100,
          windowType: "hour5",
          displayName: "Go即时共用池"
        }
      },
      models: {
        "gpt-5-2-pro": { quota: 0, windowType: "monthly" },
        "gpt-5-1-pro": { quota: 0, windowType: "monthly" },
        "gpt-5-pro": { quota: 0, windowType: "monthly" },
        "o3-pro": { quota: 0, windowType: "monthly" },
        "gpt-4-5": { quota: 0, windowType: "daily" },
        "gpt-5-2-thinking": { sharedGroup: "go-thinking-shared" },
        "gpt-5-1-thinking": { sharedGroup: "go-thinking-shared" },
        "gpt-5-thinking": { sharedGroup: "go-thinking-shared" },
        o3: { quota: 0, windowType: "weekly" },
        "gpt-5-2-instant": { sharedGroup: "go-instant-shared" },
        "gpt-5-1": { sharedGroup: "go-instant-shared" },
        "gpt-5": { sharedGroup: "go-instant-shared" },
        "gpt-5-t-mini": { quota: 100, windowType: "daily" },
        "o4-mini": { quota: 0, windowType: "daily" },
        "gpt-4o": { quota: 0, windowType: "hour3" },
        "gpt-4-1": { quota: 0, windowType: "hour3" },
        "gpt-5-mini": { quota: 1e4, windowType: "hour3" }
      }
    },
    k12_teacher: {
      name: "K12 Teacher",
      sharedQuotaGroups: {
        "k12-thinking-shared": {
          quota: 160,
          windowType: "hour3",
          displayName: "K12思考共用池"
        },
        "k12-instant-shared": {
          quota: 1e4,
          windowType: "hour3",
          displayName: "K12即时共用池"
        }
      },
      models: {
        "gpt-5-2-pro": { quota: 0, windowType: "monthly" },
        "gpt-5-1-pro": { quota: 0, windowType: "monthly" },
        "gpt-5-pro": { quota: 0, windowType: "monthly" },
        "o3-pro": { quota: 0, windowType: "monthly" },
        "gpt-4-5": { quota: 0, windowType: "daily" },
        "gpt-5-2-thinking": { sharedGroup: "k12-thinking-shared" },
        "gpt-5-1-thinking": { sharedGroup: "k12-thinking-shared" },
        "gpt-5-thinking": { sharedGroup: "k12-thinking-shared" },
        o3: { quota: 0, windowType: "weekly" },
        "gpt-5-2-instant": { sharedGroup: "k12-instant-shared" },
        "gpt-5-1": { sharedGroup: "k12-instant-shared" },
        "gpt-5": { sharedGroup: "k12-instant-shared" },
        "gpt-5-t-mini": { quota: 0, windowType: "daily" },
        "o4-mini": { quota: 0, windowType: "daily" },
        "gpt-4o": { quota: 0, windowType: "hour3" },
        "gpt-4-1": { quota: 0, windowType: "hour3" },
        "gpt-5-mini": { quota: 1e4, windowType: "hour3" }
      }
    },
    plus: {
      name: "Plus",
      sharedQuotaGroups: {
        "plus-thinking-shared": {
          quota: 160,
          windowType: "hour3",
          displayName: "Plus思考共用池"
        },
        "plus-instant-shared": {
          quota: 1e4,
          windowType: "hour3",
          displayName: "Plus即时共用池"
        }
      },
      models: {
        "gpt-5-2-pro": { quota: 0, windowType: "monthly" },
        "gpt-5-1-pro": { quota: 0, windowType: "monthly" },
        "gpt-5-pro": { quota: 0, windowType: "monthly" },
        "o3-pro": { quota: 0, windowType: "monthly" },
        "gpt-4-5": { quota: 0, windowType: "daily" },
        "gpt-5-2-thinking": { sharedGroup: "plus-thinking-shared" },
        "gpt-5-1-thinking": { sharedGroup: "plus-thinking-shared" },
        "gpt-5-thinking": { sharedGroup: "plus-thinking-shared" },
        o3: { quota: 100, windowType: "weekly" },
        "gpt-5-2-instant": { sharedGroup: "plus-instant-shared" },
        "gpt-5-1": { sharedGroup: "plus-instant-shared" },
        "gpt-5": { sharedGroup: "plus-instant-shared" },
        "gpt-5-t-mini": { quota: 1e4, windowType: "hour3" },
        "o4-mini": { quota: 300, windowType: "daily" },
        "gpt-4o": { quota: 80, windowType: "hour3" },
        "gpt-4-1": { quota: 80, windowType: "hour3" },
        "gpt-5-mini": { quota: 1e4, windowType: "hour3" }
      }
    },
    team: {
      name: "Team",
      sharedQuotaGroups: {
        "team-premium-shared": {
          quota: 15,
          windowType: "monthly",
          displayName: "Team高级共用池"
        },
        "team-thinking-shared": {
          quota: 3e3,
          windowType: "weekly",
          displayName: "Team思考共用池"
        },
        "team-instant-shared": {
          quota: 1e4,
          windowType: "hour3",
          displayName: "Team即时共用池"
        }
      },
      models: {
        "gpt-5-2-pro": { sharedGroup: "team-premium-shared" },
        "gpt-5-1-pro": { sharedGroup: "team-premium-shared" },
        "gpt-5-pro": { sharedGroup: "team-premium-shared" },
        "o3-pro": { quota: 0, windowType: "monthly" },
        "gpt-4-5": { quota: 0, windowType: "daily" },
        "gpt-5-2-thinking": { sharedGroup: "team-thinking-shared" },
        "gpt-5-1-thinking": { sharedGroup: "team-thinking-shared" },
        "gpt-5-thinking": { sharedGroup: "team-thinking-shared" },
        o3: { quota: 100, windowType: "weekly" },
        "gpt-5-2-instant": { sharedGroup: "team-instant-shared" },
        "gpt-5-1": { sharedGroup: "team-instant-shared" },
        "gpt-5": { sharedGroup: "team-instant-shared" },
        "gpt-5-t-mini": { quota: 1e4, windowType: "hour3" },
        "o4-mini": { quota: 300, windowType: "daily" },
        "gpt-4o": { quota: 80, windowType: "hour3" },
        "gpt-4-1": { quota: 500, windowType: "hour3" },
        "gpt-5-mini": { quota: 1e4, windowType: "hour3" }
      }
    },
    edu: {
      name: "Edu",
      sharedQuotaGroups: {
        "edu-premium-shared": {
          quota: 15,
          windowType: "monthly",
          displayName: "Edu高级共用池"
        },
        "edu-thinking-shared": {
          quota: 3e3,
          windowType: "weekly",
          displayName: "Edu思考共用池"
        },
        "edu-instant-shared": {
          quota: 1e4,
          windowType: "hour3",
          displayName: "Edu即时共用池"
        }
      },
      models: {
        "gpt-5-2-pro": { sharedGroup: "edu-premium-shared" },
        "gpt-5-1-pro": { sharedGroup: "edu-premium-shared" },
        "gpt-5-pro": { sharedGroup: "edu-premium-shared" },
        "o3-pro": { quota: 0, windowType: "monthly" },
        "gpt-4-5": { quota: 0, windowType: "daily" },
        "gpt-5-2-thinking": { sharedGroup: "edu-thinking-shared" },
        "gpt-5-1-thinking": { sharedGroup: "edu-thinking-shared" },
        "gpt-5-thinking": { sharedGroup: "edu-thinking-shared" },
        o3: { quota: 100, windowType: "weekly" },
        "gpt-5-2-instant": { sharedGroup: "edu-instant-shared" },
        "gpt-5-1": { sharedGroup: "edu-instant-shared" },
        "gpt-5": { sharedGroup: "edu-instant-shared" },
        "gpt-5-t-mini": { quota: 1e4, windowType: "hour3" },
        "o4-mini": { quota: 300, windowType: "daily" },
        "gpt-4o": { quota: 80, windowType: "hour3" },
        "gpt-4-1": { quota: 500, windowType: "hour3" },
        "gpt-5-mini": { quota: 1e4, windowType: "hour3" }
      }
    },
    enterprise: {
      name: "Enterprise",
      sharedQuotaGroups: {
        "enterprise-premium-shared": {
          quota: 15,
          windowType: "monthly",
          displayName: "Enterprise高级共用池"
        },
        "enterprise-thinking-shared": {
          quota: 3e3,
          windowType: "weekly",
          displayName: "Enterprise思考共用池"
        },
        "enterprise-instant-shared": {
          quota: 1e4,
          windowType: "hour3",
          displayName: "Enterprise即时共用池"
        }
      },
      models: {
        "gpt-5-2-pro": { sharedGroup: "enterprise-premium-shared" },
        "gpt-5-1-pro": { sharedGroup: "enterprise-premium-shared" },
        "gpt-5-pro": { sharedGroup: "enterprise-premium-shared" },
        "o3-pro": { quota: 0, windowType: "monthly" },
        "gpt-4-5": { quota: 0, windowType: "daily" },
        "gpt-5-2-thinking": { sharedGroup: "enterprise-thinking-shared" },
        "gpt-5-1-thinking": { sharedGroup: "enterprise-thinking-shared" },
        "gpt-5-thinking": { sharedGroup: "enterprise-thinking-shared" },
        o3: { quota: 100, windowType: "weekly" },
        "gpt-5-2-instant": { sharedGroup: "enterprise-instant-shared" },
        "gpt-5-1": { sharedGroup: "enterprise-instant-shared" },
        "gpt-5": { sharedGroup: "enterprise-instant-shared" },
        "gpt-5-t-mini": { quota: 1e4, windowType: "hour3" },
        "o4-mini": { quota: 300, windowType: "daily" },
        "gpt-4o": { quota: 80, windowType: "hour3" },
        "gpt-4-1": { quota: 500, windowType: "hour3" },
        "gpt-5-mini": { quota: 1e4, windowType: "hour3" }
      }
    },
    pro: {
      name: "Pro",
      sharedQuotaGroups: {
        "pro-premium-shared": {
          quota: 100,
          windowType: "daily",
          displayName: "Pro高级共用池"
        },
        "pro-thinking-shared": {
          quota: 1e4,
          windowType: "hour3",
          displayName: "Pro思考共用池"
        },
        "pro-instant-shared": {
          quota: 1e4,
          windowType: "hour3",
          displayName: "Pro即时共用池"
        }
      },
      models: {
        "gpt-5-2-pro": { sharedGroup: "pro-premium-shared" },
        "gpt-5-1-pro": { sharedGroup: "pro-premium-shared" },
        "gpt-5-pro": { sharedGroup: "pro-premium-shared" },
        "o3-pro": { sharedGroup: "pro-premium-shared" },
        "gpt-4-5": { quota: 100, windowType: "daily" },
        "gpt-5-2-thinking": { sharedGroup: "pro-thinking-shared" },
        "gpt-5-1-thinking": { sharedGroup: "pro-thinking-shared" },
        "gpt-5-thinking": { sharedGroup: "pro-thinking-shared" },
        o3: { quota: 1e4, windowType: "hour3" },
        "gpt-5-2-instant": { sharedGroup: "pro-instant-shared" },
        "gpt-5-1": { sharedGroup: "pro-instant-shared" },
        "gpt-5": { sharedGroup: "pro-instant-shared" },
        "gpt-5-t-mini": { quota: 1e4, windowType: "hour3" },
        "o4-mini": { quota: 1e4, windowType: "hour3" },
        "gpt-4o": { quota: 1e4, windowType: "hour3" },
        "gpt-4-1": { quota: 1e4, windowType: "hour3" },
        "gpt-5-mini": { quota: 1e4, windowType: "hour3" }
      }
    }
  };

  // src/events.js
  var EVENT_DATA_CHANGED = "chatgpt-usage-monitor:data-changed";
  function emitDataChanged() {
    try {
      window.dispatchEvent(new CustomEvent(EVENT_DATA_CHANGED));
    } catch {
    }
  }
  function onDataChanged(handler) {
    window.addEventListener(EVENT_DATA_CHANGED, handler);
    return () => window.removeEventListener(EVENT_DATA_CHANGED, handler);
  }

  // src/state.js
  var usageData = null;
  function setUsageData(next) {
    usageData = next;
  }

  // src/userConfig.js
  var SILENT_MODE = false;

  // src/utils.js
  function formatTimeAgo(timestamp) {
    const now = Date.now();
    const seconds = Math.floor((now - timestamp) / 1e3);
    if (seconds < 60) return `${seconds}s ago`;
    const minutes = Math.floor(seconds / 60);
    if (minutes < 60) return `${minutes}m ago`;
    const hours = Math.floor(minutes / 60);
    if (hours < 24) return `${hours}h ago`;
    const days = Math.floor(hours / 24);
    return `${days}d ago`;
  }
  function formatTimestampForFilename(date = /* @__PURE__ */ new Date()) {
    const pad = (n) => String(n).padStart(2, "0");
    const y = date.getFullYear();
    const m = pad(date.getMonth() + 1);
    const d = pad(date.getDate());
    const hh = pad(date.getHours());
    const mm = pad(date.getMinutes());
    const ss = pad(date.getSeconds());
    return `${y}-${m}-${d}_${hh}-${mm}-${ss}`;
  }
  function tsOf(req) {
    if (typeof req === "number") return req;
    if (req && typeof req.t === "number") return req.t;
    if (req && typeof req.timestamp === "number") return req.timestamp;
    return NaN;
  }
  function formatTimeLeft(windowEnd) {
    const now = Date.now();
    const timeLeft = windowEnd - now;
    if (timeLeft <= 0) return "0h 0m";
    const hours = Math.floor(timeLeft / (60 * 60 * 1e3));
    const minutes = Math.floor(timeLeft % (60 * 60 * 1e3) / (60 * 1e3));
    return `${hours}h ${minutes}m`;
  }
  function getWindowEnd(timestamp, windowType) {
    return timestamp + TIME_WINDOWS[windowType];
  }

  // src/storage.js
  function deepClone(value) {
    return JSON.parse(JSON.stringify(value));
  }
  var Storage = {
    key: "usageData",
    get() {
      let data = GM_getValue(this.key);
      if (!data || typeof data !== "object") {
        data = deepClone(defaultUsageData);
      }
      if (!data.models || typeof data.models !== "object") {
        data.models = {};
      }
      if (!data.position) {
        data.position = { x: null, y: null };
      }
      if (!data.size) {
        data.size = { width: 400, height: 500 };
      }
      if (data.minimized === void 0) {
        data.minimized = false;
      }
      if (data.silentMode === void 0) {
        data.silentMode = false;
      }
      if (typeof SILENT_MODE === "boolean") {
        data.silentMode = SILENT_MODE;
      }
      if (!data.progressType) {
        data.progressType = "bar";
      }
      if (!data.planType) {
        data.planType = "team";
      }
      if (!PLAN_CONFIGS[data.planType]) {
        data.planType = "team";
      }
      if (!data.sharedQuotaGroups) {
        data.sharedQuotaGroups = {};
      }
      if (data.showWindowResetTime === void 0) {
        data.showWindowResetTime = false;
      }
      if (data.models["gpt-4-1-mini"]) delete data.models["gpt-4-1-mini"];
      if (data.models["o4-mini-high"]) delete data.models["o4-mini-high"];
      const gpt5ProAllowedPlans = ["team", "edu", "enterprise", "pro"];
      const isGpt5ProAllowed = gpt5ProAllowedPlans.includes(data.planType);
      if (!isGpt5ProAllowed) {
        ["gpt-5-2-pro", "gpt-5-1-pro"].forEach((key) => {
          if (data.models[key]) delete data.models[key];
        });
      }
      if (data.deepResearch) delete data.deepResearch;
      const newModels = [
        "gpt-5",
        "gpt-5-thinking",
        "gpt-5-2-instant",
        "gpt-5-2-thinking",
        "gpt-5-2-pro",
        "gpt-5-1",
        "gpt-5-1-thinking",
        "gpt-5-pro",
        "gpt-5-1-pro",
        "o3",
        "o3-pro",
        "gpt-4-5",
        "o4-mini",
        "gpt-4o",
        "gpt-4-1",
        "gpt-5-t-mini",
        "gpt-5-mini"
      ];
      if (!isGpt5ProAllowed) {
        ["gpt-5-2-pro", "gpt-5-1-pro"].forEach((m) => {
          const idx = newModels.indexOf(m);
          if (idx !== -1) newModels.splice(idx, 1);
        });
      }
      newModels.forEach((modelId) => {
        if (data.models[modelId]) return;
        if (modelId === "gpt-5") {
          data.models[modelId] = { requests: [], quota: 1e4, windowType: "hour3" };
        } else if (modelId === "gpt-5-thinking") {
          data.models[modelId] = { requests: [], quota: 3e3, windowType: "weekly" };
        } else if (modelId === "gpt-5-2-instant") {
          data.models[modelId] = { requests: [], quota: 1e4, windowType: "hour3" };
        } else if (modelId === "gpt-5-2-thinking") {
          data.models[modelId] = { requests: [], quota: 3e3, windowType: "weekly" };
        } else if (modelId === "gpt-5-1") {
          data.models[modelId] = { requests: [], quota: 1e4, windowType: "hour3" };
        } else if (modelId === "gpt-5-1-thinking") {
          data.models[modelId] = { requests: [], quota: 3e3, windowType: "weekly" };
        } else if (modelId === "gpt-5-pro") {
          data.models[modelId] = { requests: [], quota: 15, windowType: "monthly" };
        } else if (modelId === "gpt-5-2-pro") {
          data.models[modelId] = { requests: [], quota: 15, windowType: "monthly" };
        } else if (modelId === "gpt-5-1-pro") {
          data.models[modelId] = { requests: [], quota: 15, windowType: "monthly" };
        } else if (modelId === "o3") {
          data.models[modelId] = { requests: [], quota: 100, windowType: "weekly" };
        } else if (modelId === "o3-pro") {
          data.models[modelId] = { requests: [], quota: 0, windowType: "monthly" };
        } else if (modelId === "gpt-4-5") {
          data.models[modelId] = { requests: [], quota: 0, windowType: "daily" };
        } else if (modelId === "o4-mini") {
          data.models[modelId] = { requests: [], quota: 300, windowType: "daily" };
        } else if (modelId === "gpt-4o") {
          data.models[modelId] = { requests: [], quota: 80, windowType: "hour3" };
        } else if (modelId === "gpt-4-1") {
          data.models[modelId] = { requests: [], quota: 500, windowType: "hour3" };
        } else if (modelId === "gpt-5-t-mini") {
          data.models[modelId] = { requests: [], quota: 1e4, windowType: "hour3" };
        } else if (modelId === "gpt-5-mini") {
          data.models[modelId] = { requests: [], quota: 1e4, windowType: "hour3" };
        }
      });
      if (data.models["gpt-4"]) delete data.models["gpt-4"];
      Object.entries(data.models).forEach(([key, model]) => {
        if (!model || typeof model !== "object") {
          data.models[key] = { requests: [], quota: 50, windowType: "daily" };
          return;
        }
        if (!Array.isArray(model.requests)) {
          model.requests = [];
          if (typeof model.count === "number" && model.count > 0) {
            const now = Date.now();
            for (let i = 0; i < model.count; i++) {
              model.requests.push(now - i * 6e4);
            }
          }
          delete model.count;
          delete model.lastUpdate;
        }
        if (model.dailyLimit !== void 0 && model.quota === void 0) {
          model.quota = model.dailyLimit;
          delete model.dailyLimit;
        }
        if (model.resetFrequency !== void 0 && model.windowType === void 0) {
          model.windowType = model.resetFrequency;
          delete model.resetFrequency;
        }
        if (!["hour3", "hour5", "daily", "weekly", "monthly"].includes(model.windowType)) {
          model.windowType = "daily";
        }
        if (Array.isArray(model.requests)) {
          model.requests = model.requests.map((r) => tsOf(r)).filter((ts) => typeof ts === "number" && !Number.isNaN(ts));
        }
      });
      if (data.sharedQuotaGroups && typeof data.sharedQuotaGroups === "object") {
        Object.values(data.sharedQuotaGroups).forEach((group) => {
          if (group && Array.isArray(group.requests)) {
            group.requests = group.requests.map((r) => {
              if (typeof r === "number") return { t: r };
              if (r && typeof r === "object") {
                const t = tsOf(r);
                if (typeof r.modelId === "string") return { t, modelId: r.modelId };
                const copy = { ...r };
                if (t && typeof copy.t !== "number") copy.t = t;
                return copy;
              }
              return r;
            });
            group.requests = [];
          }
        });
      }
      delete data.lastDailyReset;
      delete data.lastWeeklyReset;
      delete data.lastReset;
      this.set(data);
      return data;
    },
    set(newData) {
      GM_setValue(this.key, newData);
    },
    update(callback) {
      const data = this.get();
      callback(data);
      this.set(data);
    }
  };
  function refreshUsageData() {
    const data = Storage.get();
    setUsageData(data);
    return usageData;
  }
  function updateUsageData(mutator) {
    Storage.update((data) => {
      mutator(data);
    });
    return refreshUsageData();
  }

  // src/ui/toast.js
  function showToast(message, type = "success") {
    const container = document.getElementById("chatUsageMonitor");
    if (!container) return;
    const existingToast = container.querySelector(".toast");
    if (existingToast) existingToast.remove();
    const toast = document.createElement("div");
    toast.className = "toast";
    toast.textContent = message;
    if (type === "error") {
      toast.style.color = COLORS.danger;
      toast.style.borderColor = COLORS.danger;
    } else if (type === "warning") {
      toast.style.color = COLORS.warning;
      toast.style.borderColor = COLORS.warning;
    }
    container.appendChild(toast);
    setTimeout(() => {
      toast.classList.add("show");
    }, 10);
    setTimeout(() => {
      toast.classList.remove("show");
      setTimeout(() => toast.remove(), 300);
    }, 3e3);
  }

  // src/features/importExport.js
  function exportUsageData() {
    const data = Storage.get();
    const exportData = { ...data };
    const jsonData = JSON.stringify(exportData, null, 2);
    const blob = new Blob([jsonData], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `chatgpt-usage-${formatTimestampForFilename()}.json`;
    document.body.appendChild(a);
    a.click();
    setTimeout(() => {
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      showToast("用量统计数据已导出");
    }, 100);
  }
  function importUsageData() {
    if (!confirm("导入将合并现有记录与导入文件中的记录。继续吗?")) return;
    const input = document.createElement("input");
    input.type = "file";
    input.accept = "application/json";
    input.style.display = "none";
    input.onchange = function(e) {
      const file = e.target.files?.[0];
      if (!file) return;
      const reader = new FileReader();
      reader.onload = function(event) {
        try {
          const importedData = JSON.parse(event.target.result);
          if (!validateImportedData(importedData)) {
            showToast("导入失败:数据格式不正确", "error");
            return;
          }
          const importSummary = generateImportSummary(importedData);
          if (!confirm(`导入记录摘要:\\n${importSummary}\\n\\n确认导入这些数据吗?`)) return;
          const currentData = Storage.get();
          const mergedData = mergeUsageData(currentData, importedData);
          Storage.set(mergedData);
          refreshUsageData();
          emitDataChanged();
          showToast("用量记录已成功导入", "success");
        } catch (error) {
          console.error("[monitor] Import error:", error);
          showToast("导入失败:" + error.message, "error");
        } finally {
          document.body.removeChild(input);
        }
      };
      reader.readAsText(file);
    };
    document.body.appendChild(input);
    input.click();
  }
  function validateImportedData(data) {
    if (!data || typeof data !== "object") return false;
    if (!("models" in data) || typeof data.models !== "object") return false;
    for (const modelKey in data.models) {
      const model = data.models[modelKey];
      if (!model || typeof model !== "object") return false;
      if (!Array.isArray(model.requests)) return false;
      if (typeof model.quota !== "number" && typeof model.sharedGroup !== "string") return false;
      if (model.windowType && !["hour3", "hour5", "daily", "weekly", "monthly"].includes(model.windowType))
        return false;
    }
    return true;
  }
  function generateImportSummary(importedData) {
    let summary = "";
    const modelCount = Object.keys(importedData.models || {}).length;
    let totalRequests = 0;
    const modelDetails = [];
    Object.entries(importedData.models || {}).forEach(([key, model]) => {
      const count = (model.requests || []).length;
      totalRequests += count;
      if (count > 0) modelDetails.push(`${key}: ${count}条记录`);
    });
    summary += `共 ${modelCount} 个模型,${totalRequests} 条请求记录\\n`;
    if (modelDetails.length <= 5) {
      summary += `\\n模型详情:\\n${modelDetails.join("\\n")}`;
    }
    if (importedData.legacyMiniCount !== void 0 && importedData.legacyMiniCount > 0) {
      summary += `\\n\\n遗留特殊模型计数: ${importedData.legacyMiniCount} (已不再使用)`;
    }
    return summary;
  }
  function mergeUsageData(currentData, importedData) {
    const result = JSON.parse(JSON.stringify(currentData));
    Object.entries(importedData.models || {}).forEach(([modelKey, importedModel]) => {
      if (!result.models[modelKey]) {
        result.models[modelKey] = {
          requests: [],
          quota: importedModel.quota || 50,
          windowType: importedModel.windowType || "daily"
        };
        if (importedModel.sharedGroup) result.models[modelKey].sharedGroup = importedModel.sharedGroup;
      }
      const currentRequests = result.models[modelKey].requests || [];
      const now = Date.now();
      const windowType = result.models[modelKey].windowType || "daily";
      const windowDuration = TIME_WINDOWS[windowType] || TIME_WINDOWS.daily;
      const oldestRelevantTime = now - windowDuration;
      const relevantImportedRequests = (importedModel.requests || []).map((req) => tsOf(req)).filter((ts) => ts > oldestRelevantTime);
      const existingTimeMap = /* @__PURE__ */ new Map();
      currentRequests.forEach((req) => {
        const roundedTime = Math.floor(tsOf(req) / 1e3) * 1e3;
        existingTimeMap.set(roundedTime, true);
      });
      const newRequests = relevantImportedRequests.filter((ts) => {
        const roundedTime = Math.floor(ts / 1e3) * 1e3;
        return !existingTimeMap.has(roundedTime);
      });
      result.models[modelKey].requests = [...currentRequests.map(tsOf), ...newRequests].filter((ts) => typeof ts === "number" && !Number.isNaN(ts)).sort((a, b) => b - a);
    });
    return result;
  }

  // src/styles.js
  function injectStyles() {
    GM_addStyle(`
  #chatUsageMonitor {
  position: fixed;
  bottom: 100px;  /* 往下移动一点点 */
  left: ${STYLE.spacing.lg};  /* 改为左侧 */
  width: 400px;
  height: 500px;
  max-height: 80vh;
  overflow: auto;
  background: ${COLORS.background};
  color: ${COLORS.text};
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  border-radius: ${STYLE.borderRadius};
  box-shadow: ${STYLE.boxShadow};
  z-index: 9999;
  border: 1px solid ${COLORS.border};
  user-select: none;
  resize: both;
  transition: all 0.3s ease;
  transform-origin: top left;  /* 改为左侧 */
  }
  
  #chatUsageMonitor.hidden {
  display: none !important;
  }
  
  #chatUsageMonitor::after {
  content: "";
  position: absolute;
  bottom: 0;
  right: 0;
  width: 15px;
  height: 15px;
  background: transparent;
  border-bottom: 2px solid ${COLORS.yellow};
  border-right: 2px solid ${COLORS.yellow};
  opacity: 0.5;
  pointer-events: none;
  }
  
  #chatUsageMonitor:hover::after {
  opacity: 1;
  }
  
  #chatUsageMonitor.minimized {
  width: 30px !important;
  height: 30px !important;
  border-radius: 50%;
  overflow: hidden;
  resize: none;
  opacity: 0.8;
  cursor: pointer;
  background-color: ${COLORS.primary};
  bottom: auto;
  top: 100px;  /* 往下移动一点点 */
  left: ${STYLE.spacing.lg};  /* 改为左侧 */
  z-index: 9999;
  }
  
  #chatUsageMonitor.minimized:hover {
  opacity: 1;
  }
  
  #chatUsageMonitor.minimized > * {
  display: none !important;
  }
  
  #chatUsageMonitor.minimized::before {
  content: "次";
  color: white;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  font-weight: bold;
  }
  
  #chatUsageMonitor header {
  padding: 0 ${STYLE.spacing.md};
  display: flex;
  border-radius: ${STYLE.borderRadius} ${STYLE.borderRadius} 0 0;
  background: ${COLORS.background};
  flex-direction: row;
  position: relative;
  align-items: center;
  height: 36px;
  cursor: move; /* 指示整个头部可拖动 */
  }
  
  #chatUsageMonitor .minimize-btn {
  position: absolute;
  left: 8px;
  top: 0;
  height: 36px;
  width: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: ${COLORS.secondaryText};
  cursor: pointer;
  font-size: 18px;
  transition: color 0.2s ease;
  z-index: 10;
  }
  
  #chatUsageMonitor .minimize-btn:hover {
  color: ${COLORS.yellow};
  }
  
  #chatUsageMonitor header button {
  border: none;
  background: none;
  color: ${COLORS.secondaryText};
  cursor: pointer;
  font-weight: 500;
  transition: color 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-left: 30px; /* Move buttons to the right to avoid overlap with minimize button */
  padding-top: ${STYLE.spacing.sm};
  }
  
  #chatUsageMonitor header button.active {
  color: ${COLORS.yellow};
  }
  
  #chatUsageMonitor .content {
  padding: ${STYLE.spacing.xs} ${STYLE.spacing.md};
  overflow-y: auto;
  }
  
  #chatUsageMonitor .reset-info {
  font-size: ${STYLE.textSize.xs};
  color: ${COLORS.secondaryText};
  margin: ${STYLE.spacing.xs} 0;
  }
  
  #chatUsageMonitor input {
  width: 80px;
  padding: ${STYLE.spacing.xs} ${STYLE.spacing.sm};
  margin: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  color: ${COLORS.secondaryText};
  font-family: monospace;
  font-size: ${STYLE.textSize.xs};
  line-height: ${STYLE.lineHeight.xs};
  transition: color 0.2s ease;
  }
  
  #chatUsageMonitor input:focus {
  outline: none;
  color: ${COLORS.yellow};
  background: transparent;
  }
  
  #chatUsageMonitor input:hover {
  color: ${COLORS.yellow};
  }
  
  #chatUsageMonitor .btn {
  padding: ${STYLE.spacing.sm} ${STYLE.spacing.md};
  border: none;
  cursor: pointer;
  color: ${COLORS.white};
  font-weight: 500;
  font-size: ${STYLE.textSize.sm};
  transition: all 0.2s ease;
  text-decoration: underline;
  }
  
  #chatUsageMonitor .btn:hover {
  color: ${COLORS.yellow};
  }
  
  #chatUsageMonitor .delete-btn {
  padding: ${STYLE.spacing.xs} ${STYLE.spacing.sm};
  margin-left: ${STYLE.spacing.sm};
  }
  
  #chatUsageMonitor .delete-btn.btn:hover {
  color: ${COLORS.danger};
  }
  
  #chatUsageMonitor::-webkit-scrollbar {
  width: 8px;
  }
  
  #chatUsageMonitor::-webkit-scrollbar-track {
  background: ${COLORS.surface};
  border-radius: 4px;
  }
  
  #chatUsageMonitor::-webkit-scrollbar-thumb {
  background: ${COLORS.border};
  border-radius: 4px;
  }
  
  #chatUsageMonitor::-webkit-scrollbar-thumb:hover {
  background: ${COLORS.secondaryText};
  }
  
  #chatUsageMonitor .progress-container {
  width: 100%;
  background: ${COLORS.surface};
  margin-top: ${STYLE.spacing.xs};
  border-radius: 6px;
  overflow: hidden;
  height: 8px;
  position: relative;
  }
  
  #chatUsageMonitor .progress-bar {
  height: 100%;
  transition: width 0.3s ease;
  border-radius: 6px;
  background: linear-gradient(
  90deg,
  ${COLORS.progressLow} 0%,
  ${COLORS.progressMed} 50%,
  ${COLORS.progressHigh} 100%
  );
  background-size: 200% 100%;
  animation: gradientShift 2s linear infinite;
  }
  
  #chatUsageMonitor .progress-bar.low-usage {
  animation: pulse 1.5s ease-in-out infinite;
  }
  
  #chatUsageMonitor .progress-bar.exceeded {
  background: ${COLORS.progressExceed};
  animation: none;
  }
  
  #chatUsageMonitor .window-badge {
  display: inline-block;
  font-size: 10px;
  padding: 2px 4px;
  border-radius: 4px;
  margin-left: 4px;
  color: ${COLORS.background};
  font-weight: bold;
  }
  
  #chatUsageMonitor .window-badge.hour3 {
  background-color: ${COLORS.hourModel};
  }
  
  #chatUsageMonitor .window-badge.hour5 {
  background-color: ${COLORS.hourModel};
  }
  
  #chatUsageMonitor .window-badge.daily {
  background-color: ${COLORS.dailyModel};
  }
  
  #chatUsageMonitor .window-badge.weekly {
  background-color: ${COLORS.weeklyModel};
  }
  
  #chatUsageMonitor .window-badge.monthly {
  background-color: ${COLORS.monthlyModel};
  }
  
  #chatUsageMonitor .request-time {
  color: ${COLORS.secondaryText};
  font-size: ${STYLE.textSize.xs};
  }
  
  #chatUsageMonitor .window-info {
  color: ${COLORS.secondaryText};
  font-size: ${STYLE.textSize.xs};
  margin-top: 2px;
  }
  
  #chatUsageMonitor .active-window {
  font-weight: bold;
  }
  
  #chatUsageMonitor .unknown-quota {
  color: ${COLORS.warning};
  font-style: italic;
  }
  
  /* 为特殊模型添加样式 */
  #chatUsageMonitor .special-model-row {
  border-top: 1px dashed ${COLORS.border};
  margin-top: 8px;
  padding-top: 8px;
  opacity: 0.8;
  }
  
  #chatUsageMonitor .special-model-name {
  color: ${COLORS.disabled};
  font-style: italic;
  }
  
  /* 周分析报告样式 */
  #chatUsageMonitor .weekly-report {
  background: ${COLORS.surface};
  border-radius: 8px;
  padding: ${STYLE.spacing.md};
  margin-top: ${STYLE.spacing.md};
  }
  
  #chatUsageMonitor .weekly-report h3 {
  color: ${COLORS.yellow};
  margin-bottom: ${STYLE.spacing.sm};
  font-size: ${STYLE.textSize.md};
  }
  
  #chatUsageMonitor .weekly-report .stat-row {
  display: flex;
  justify-content: space-between;
  padding: ${STYLE.spacing.xs} 0;
  font-size: ${STYLE.textSize.sm};
  border-bottom: 1px solid ${COLORS.border};
  }
  
  #chatUsageMonitor .weekly-report .stat-row:last-child {
  border-bottom: none;
  }
  
  #chatUsageMonitor .weekly-report .stat-label {
  color: ${COLORS.secondaryText};
  }
  
  #chatUsageMonitor .weekly-report .stat-value {
  color: ${COLORS.text};
  font-weight: 500;
  }
  
  #chatUsageMonitor .weekly-report .model-breakdown {
  margin-top: ${STYLE.spacing.sm};
  }
  
  #chatUsageMonitor .weekly-report .model-item {
  display: flex;
  justify-content: space-between;
  padding: ${STYLE.spacing.xs} ${STYLE.spacing.sm};
  font-size: ${STYLE.textSize.xs};
  background: ${COLORS.background};
  border-radius: 4px;
  margin: 2px 0;
  }
  
  @keyframes gradientShift {
  0% { background-position: 100% 0; }
  100% { background-position: -100% 0; }
  }
  
  @keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
  70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
  100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
  }
  
  /* Dot-based progression system */
  #chatUsageMonitor .dot-progress {
  display: flex;
  gap: 4px;
  align-items: center;
  height: 8px;
  }
  
  #chatUsageMonitor .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  transition: all 0.3s ease;
  }
  
  #chatUsageMonitor .dot-empty {
  background: rgba(239, 68, 68, 0.3);
  border: 1px solid ${COLORS.progressLow};
  }
  
  #chatUsageMonitor .dot-partial {
  background: ${COLORS.progressMed};
  }
  
  #chatUsageMonitor .dot-full {
  background: ${COLORS.progressHigh};
  }
  
  #chatUsageMonitor .dot-exceeded {
  background: ${COLORS.progressExceed};
  position: relative;
  }
  
  #chatUsageMonitor .dot-exceeded::before {
  content: '';
  position: absolute;
  top: 50%;
  left: -2px;
  right: -2px;
  height: 2px;
  background: ${COLORS.surface};
  transform: rotate(45deg);
  }
  
  #chatUsageMonitor .table-header {
  font-family: monospace;
  color: ${COLORS.white};
  font-size:  ${STYLE.textSize.xs};
  line-height: ${STYLE.lineHeight.xs};
  display : grid;
  align-items: center;
  grid-template-columns: 2fr 1.5fr 1.5fr 2fr;
  }
  
  #chatUsageMonitor .model-row {
  font-family: monospace;
  color: ${COLORS.secondaryText};
  transition: color 0.2s ease;
  font-size:  ${STYLE.textSize.xs};
  line-height: ${STYLE.lineHeight.xs};
  display : grid;
  grid-template-columns: 2fr 1.5fr 1.5fr 2fr;
  align-items: center;
  }
  
  #chatUsageMonitor .model-row:hover {
  color: ${COLORS.yellow};
  text-decoration-line: underline;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  }
  
  /* DeepResearch 分割线与区域样式 */
  #chatUsageMonitor .section-divider {
  margin: 8px 0 10px 0;
  border-top: 1px dashed ${COLORS.border};
  opacity: 0.8;
  }
  #chatUsageMonitor .deepresearch-title {
  font-family: monospace;
  font-weight: bold;
  color: ${COLORS.white};
  font-size: ${STYLE.textSize.xs};
  margin: 6px 0;
  }
  
  /* Container to help position the arrow (pseudo-element) */
  #chatUsageMonitor .custom-select {
  position: relative;
  display: inline-block;
  margin-right: 8px;
  }
  
  /* Hide the native select arrow and style the dropdown */
  #chatUsageMonitor .custom-select select {
  -webkit-appearance: none; /* Safari and Chrome */
  -moz-appearance: none;    /* Firefox */
  appearance: none;         /* Standard modern browsers */
  background-color: transparent;
  color: #ffffff;
  border: none;
  cursor: pointer;
  color: ${COLORS.white};
  font-size: ${STYLE.textSize.sm};
  line-height:  ${STYLE.lineHeight.sm};
  padding: 2px 5px;
  }
  
  /* Style the list of options (when the dropdown is open) */
  .custom-select select option {
  background: ${COLORS.background};
  color: ${COLORS.white};
  }
  
  /* Optional: highlight the hovered option in some browsers */
  .custom-select select option:hover {
  background: ${COLORS.background};
  color: ${COLORS.yellow};
  text-decoration-line: underline;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  }
  
  #chatUsageMonitor input {
  width: 90%;
  padding: ${STYLE.spacing.xs} ${STYLE.spacing.sm};
  margin: 0;
  border: 1px solid ${COLORS.border};
  border-radius: 4px;
  background: ${COLORS.surface};
  color: ${COLORS.secondaryText};
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: ${STYLE.textSize.xs};
  line-height: ${STYLE.lineHeight.xs};
  transition: all 0.2s ease;
  }
  
  #chatUsageMonitor input:focus {
  outline: none;
  border-color: ${COLORS.yellow};
  color: ${COLORS.yellow};
  background: rgba(245, 158, 11, 0.1);
  }
  
  #chatUsageMonitor input:hover {
  border-color: ${COLORS.yellow};
  color: ${COLORS.yellow};
  }
  
  /* Toast notification for feedback */
  #chatUsageMonitor .toast {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  background: ${COLORS.background};
  color: ${COLORS.success};
  padding: ${STYLE.spacing.sm} ${STYLE.spacing.md};
  border-radius: ${STYLE.borderRadius};
  border: 1px solid ${COLORS.success};
  opacity: 0;
  transition: opacity 0.3s ease;
  z-index: 10000;
  }
  
  #chatUsageMonitor .toast.show {
  opacity: 1;
  }
  `);
  }

  // src/tracking/fetchInterceptor.js
  function installFetchInterceptor(modelRouting) {
    const targetWindow = typeof unsafeWindow === "undefined" ? window : unsafeWindow;
    const originalFetch = targetWindow.fetch;
    if (originalFetch?.__chatgptUsagePatched) return;
    const wrapped = new Proxy(originalFetch, {
      apply: async function(target, thisArg, args) {
        let autoRequestId = null;
        try {
          const [requestInfo, requestInit] = args;
          const fetchUrl = typeof requestInfo === "string" ? requestInfo : requestInfo?.href || requestInfo?.url || "";
          const requestMethod = typeof requestInfo === "object" && requestInfo?.method ? requestInfo.method : requestInit?.method || "GET";
          if (requestMethod === "PATCH" && fetchUrl?.includes("/backend-api/settings/user_last_used_model_config")) {
            modelRouting.updateLastSelectedModelConfigFromUrl(fetchUrl);
          }
          if (requestMethod === "POST" && /\/conversation(?:\?|$)/.test(fetchUrl || "")) {
            const bodyText = requestInit?.body;
            if (typeof bodyText === "string") {
              const bodyObj = JSON.parse(bodyText);
              if (bodyObj?.model) {
                autoRequestId = modelRouting.handleConversationRequest(bodyObj.model);
              }
            }
          }
        } catch {
        }
        const response = await target.apply(thisArg, args);
        if (autoRequestId) {
          modelRouting.attachAutoSseParser(autoRequestId, response);
        }
        return response;
      }
    });
    wrapped.__chatgptUsagePatched = true;
    targetWindow.fetch = wrapped;
  }

  // src/usage.js
  function cleanupExpiredRequests() {
    const now = Date.now();
    const maxWindow = TIME_WINDOWS.monthly;
    Object.values(usageData.models || {}).forEach((model) => {
      if (!Array.isArray(model.requests)) return;
      model.requests = model.requests.map((req) => tsOf(req)).filter((ts) => now - ts < maxWindow);
    });
    if (usageData.sharedQuotaGroups && typeof usageData.sharedQuotaGroups === "object") {
      Object.values(usageData.sharedQuotaGroups).forEach((group) => {
        if (!Array.isArray(group.requests)) return;
        group.requests = group.requests.filter((req) => now - tsOf(req) < maxWindow);
      });
    }
  }
  function recordModelUsageByModelId(modelId) {
    refreshUsageData();
    cleanupExpiredRequests();
    if (!usageData.models[modelId]) {
      usageData.models[modelId] = {
        requests: [],
        quota: 50,
        windowType: "daily"
      };
    }
    usageData.models[modelId].requests.push(Date.now());
    Storage.set(usageData);
    refreshUsageData();
    emitDataChanged();
  }
  function applyPlanConfig(planType) {
    const planConfig = PLAN_CONFIGS[planType];
    if (!planConfig) return;
    updateUsageData((data) => {
      const existingUsageByModel = {};
      Object.entries(data.models || {}).forEach(([modelKey, model]) => {
        if (model?.requests?.length) existingUsageByModel[modelKey] = [...model.requests];
      });
      data.sharedQuotaGroups = {};
      if (planConfig.sharedQuotaGroups) {
        Object.entries(planConfig.sharedQuotaGroups).forEach(([groupId, groupConfig]) => {
          data.sharedQuotaGroups[groupId] = {
            quota: groupConfig.quota,
            windowType: groupConfig.windowType,
            models: groupConfig.models,
            displayName: groupConfig.displayName,
            requests: []
          };
        });
      }
      const nextModels = {};
      Object.entries(planConfig.models).forEach(([modelKey, cfg]) => {
        nextModels[modelKey] = {
          requests: existingUsageByModel[modelKey] ? [...existingUsageByModel[modelKey]] : []
        };
        if (cfg.sharedGroup) {
          nextModels[modelKey].sharedGroup = cfg.sharedGroup;
        } else {
          nextModels[modelKey].quota = cfg.quota;
          nextModels[modelKey].windowType = cfg.windowType;
        }
      });
      data.models = nextModels;
    });
    emitDataChanged();
  }
  function collectSharedGroupUsage(groupId, now = Date.now()) {
    const group = usageData.sharedQuotaGroups?.[groupId];
    if (!group) return null;
    const windowType = group.windowType || "daily";
    const windowDuration = TIME_WINDOWS[windowType];
    const activeRequests = [];
    Object.entries(usageData.models || {}).forEach(([key, model]) => {
      if (model.sharedGroup !== groupId) return;
      if (!Array.isArray(model.requests)) return;
      model.requests.map((req) => tsOf(req)).filter((ts) => typeof ts === "number" && !Number.isNaN(ts) && now - ts < windowDuration).forEach((ts) => activeRequests.push({ ts, modelKey: key }));
    });
    activeRequests.sort((a, b) => a.ts - b.ts);
    return {
      group,
      windowType,
      windowDuration,
      activeRequests,
      windowEnd: activeRequests.length > 0 ? getWindowEnd(activeRequests[0].ts, windowType) : null
    };
  }

  // src/tracking/modelRouting.js
  function resolveRedirectedModelId(originalModelId) {
    if (originalModelId === "chatgpt_alpha_model_external_access_reserved_gate_13") {
      return "alpha";
    }
    if (originalModelId === "auto") {
      return "gpt-5-2";
    }
    try {
      const plan = usageData && usageData.planType || "team";
      if (originalModelId === "gpt-4-5" && plan !== "pro") return "gpt-5-2-instant";
      if (originalModelId === "o3-pro" && plan !== "pro") return "gpt-5-2-instant";
    } catch {
    }
    return originalModelId;
  }
  function createModelRouting() {
    const pendingAutoRequests = /* @__PURE__ */ new Map();
    const seenAssistantMessageIds = /* @__PURE__ */ new Set();
    let assistantObserverStarted = false;
    let lastSelectedModelConfig = {
      modelSlug: null,
      thinkingEffort: null,
      updatedAt: 0
    };
    function parseUrl(urlLike) {
      try {
        return new URL(urlLike, location.origin);
      } catch {
        return null;
      }
    }
    function updateLastSelectedModelConfigFromUrl(urlLike) {
      const url = parseUrl(urlLike);
      if (!url) return;
      const modelSlug = url.searchParams.get("model_slug") || url.searchParams.get("model");
      const thinkingEffort = url.searchParams.get("thinking_effort") || url.searchParams.get("effort");
      if (!modelSlug && !thinkingEffort) return;
      lastSelectedModelConfig = {
        modelSlug: modelSlug ?? lastSelectedModelConfig.modelSlug,
        thinkingEffort: thinkingEffort ?? lastSelectedModelConfig.thinkingEffort,
        updatedAt: Date.now()
      };
    }
    function getOldestUnresolvedAutoRequest() {
      let oldest = null;
      for (const [id, req] of pendingAutoRequests.entries()) {
        if (req.resolved) continue;
        if (!oldest || req.startedAt < oldest.startedAt) oldest = { id, ...req };
      }
      return oldest;
    }
    function mapRoutedSlugToModelKey(baseModelKey, routedModelSlug, didAutoSwitchToReasoning) {
      const slug = (routedModelSlug || "").toLowerCase();
      if (baseModelKey === "gpt-5-2") {
        if (slug.includes("pro")) return "gpt-5-2-pro";
        const looksReasoning = didAutoSwitchToReasoning === true || slug.includes("thinking") || slug.includes("reasoning");
        return looksReasoning ? "gpt-5-2-thinking" : "gpt-5-2-instant";
      }
      return routedModelSlug || baseModelKey;
    }
    function resolveAutoRequest(requestId, routed) {
      const req = pendingAutoRequests.get(requestId);
      if (!req || req.resolved) return;
      const modelKey = mapRoutedSlugToModelKey(
        req.baseModelKey,
        routed?.modelSlug,
        routed?.didAutoSwitchToReasoning
      );
      req.resolved = true;
      req.resolvedAt = Date.now();
      req.routed = routed;
      recordModelUsageByModelId(modelKey);
    }
    async function parseSseFromResponse(response, onJson) {
      const body = response?.body;
      if (!body || typeof body.getReader !== "function") return;
      const reader = body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        buffer = buffer.replace(/\r\n/g, "\n");
        let sepIdx;
        while ((sepIdx = buffer.indexOf("\n\n")) !== -1) {
          const rawEvent = buffer.slice(0, sepIdx);
          buffer = buffer.slice(sepIdx + 2);
          const dataLines = rawEvent.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
          if (!dataLines.length) continue;
          const data = dataLines.join("\n");
          if (!data || data === "[DONE]") continue;
          try {
            onJson(JSON.parse(data));
          } catch {
          }
        }
      }
    }
    function extractRoutingInfo(json) {
      if (!json || typeof json !== "object") return null;
      const ste = json.server_ste_metadata && typeof json.server_ste_metadata === "object" ? json.server_ste_metadata : json;
      if (ste && (ste.type === "server_ste_metadata" || json.type === "server_ste_metadata")) {
        const modelSlug2 = ste.model_slug || ste.model || ste.modelSlug;
        const didAutoSwitchToReasoning = ste.did_auto_switch_to_reasoning ?? ste.didAutoSwitchToReasoning;
        const thinkingEffort2 = ste.thinking_effort ?? ste.thinkingEffort ?? ste.effort;
        if (modelSlug2 || didAutoSwitchToReasoning !== void 0 || thinkingEffort2) {
          return { source: "sse", modelSlug: modelSlug2, didAutoSwitchToReasoning, thinkingEffort: thinkingEffort2 };
        }
      }
      const message = json.message && typeof json.message === "object" ? json.message : null;
      const metadata = message?.metadata && typeof message.metadata === "object" ? message.metadata : null;
      const modelSlug = metadata?.model_slug || metadata?.modelSlug;
      const thinkingEffort = metadata?.thinking_effort || metadata?.thinkingEffort;
      if (modelSlug || thinkingEffort) {
        return { source: "sse-message", modelSlug, thinkingEffort };
      }
      return null;
    }
    function attachAutoSseParser(requestId, response) {
      try {
        const clone = response.clone();
        parseSseFromResponse(clone, (json) => {
          const info = extractRoutingInfo(json);
          if (info) resolveAutoRequest(requestId, info);
        }).catch(() => {
        });
      } catch {
      }
    }
    function startAssistantMessageObserver() {
      if (assistantObserverStarted) return;
      assistantObserverStarted = true;
      const start = () => {
        if (!document?.body) {
          setTimeout(start, 300);
          return;
        }
        const observer = new MutationObserver((mutations) => {
          for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
              if (!(node instanceof Element)) continue;
              const candidates = [];
              if (node.matches?.('[data-message-author-role="assistant"]')) candidates.push(node);
              node.querySelectorAll?.('[data-message-author-role="assistant"]').forEach((el) => candidates.push(el));
              for (const el of candidates) {
                const msgId = el.getAttribute("data-message-id") || el.id || null;
                if (msgId && seenAssistantMessageIds.has(msgId)) continue;
                if (msgId) seenAssistantMessageIds.add(msgId);
                const modelSlug = el.getAttribute("data-message-model-slug");
                if (!modelSlug) continue;
                const oldest = getOldestUnresolvedAutoRequest();
                if (!oldest) continue;
                const ageMs = Date.now() - oldest.startedAt;
                if (ageMs < 0 || ageMs > 2 * 60 * 1e3) continue;
                resolveAutoRequest(oldest.id, { source: "dom", modelSlug });
              }
            }
          }
        });
        observer.observe(document.body, { childList: true, subtree: true });
      };
      start();
    }
    function beginAutoRequest(requestedModelId, baseModelKey = "gpt-5-2") {
      startAssistantMessageObserver();
      const requestId = crypto?.randomUUID && crypto.randomUUID() || `auto_${Date.now()}_${Math.random().toString(16).slice(2)}`;
      pendingAutoRequests.set(requestId, {
        baseModelKey,
        requestedModel: requestedModelId,
        startedAt: Date.now(),
        resolved: false,
        lastSelectedModelConfig: { ...lastSelectedModelConfig }
      });
      setTimeout(() => {
        const req = pendingAutoRequests.get(requestId);
        if (!req || req.resolved) return;
        resolveAutoRequest(requestId, {
          source: "timeout",
          modelSlug: req.baseModelKey,
          didAutoSwitchToReasoning: false
        });
      }, 60 * 1e3);
      return requestId;
    }
    function handleConversationRequest(modelId) {
      const effectiveModelId = resolveRedirectedModelId(modelId);
      if (effectiveModelId === "gpt-5-instant") {
        recordModelUsageByModelId("gpt-5");
        return null;
      }
      if (effectiveModelId === "gpt-5-1-instant") {
        recordModelUsageByModelId("gpt-5-1");
        return null;
      }
      if (effectiveModelId === "gpt-5-2") {
        return beginAutoRequest(modelId, "gpt-5-2");
      }
      recordModelUsageByModelId(resolveRedirectedModelId(effectiveModelId));
      return null;
    }
    return {
      handleConversationRequest,
      attachAutoSseParser,
      updateLastSelectedModelConfigFromUrl
    };
  }

  // src/textScrambler.js
  function installTextScrambler() {
    (() => {
      var TextScrambler = (() => {
        var l = Object.defineProperty;
        var c = Object.getOwnPropertyDescriptor;
        var u = Object.getOwnPropertyNames;
        var m = Object.prototype.hasOwnProperty;
        var d = (n, t) => {
          for (var e in t) l(n, e, { get: t[e], enumerable: true });
        }, f = (n, t, e, s) => {
          if (t && typeof t == "object" || typeof t == "function")
            for (let i of u(t))
              !m.call(n, i) && i !== e && l(n, i, {
                get: () => t[i],
                enumerable: !(s = c(t, i)) || s.enumerable
              });
          return n;
        };
        var g = (n) => f(l({}, "__esModule", { value: true }), n);
        var T = {};
        d(T, { default: () => r });
        function _(n) {
          let t = document.createTreeWalker(n, NodeFilter.SHOW_TEXT, {
            acceptNode: (s) => s.nodeValue.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
          }), e = [];
          for (; t.nextNode(); ) t.currentNode.nodeValue = t.currentNode.nodeValue.replace(/(\n|\r|\t)/gm, ""), e.push(t.currentNode);
          return e;
        }
        function p(n, t, e) {
          return t < 0 || t >= n.length ? n : n.substring(0, t) + e + n.substring(t + 1);
        }
        function M(n, t) {
          return n ? "x" : t[Math.floor(Math.random() * t.length)];
        }
        var r = class {
          constructor(t, e = {}) {
            this.el = t;
            let s = {
              duration: 1e3,
              delay: 0,
              reverse: false,
              absolute: false,
              pointerEvents: true,
              scrambleSymbols: "—~±§|[].+$^@*()•x%!?#",
              randomThreshold: null
            };
            this.config = Object.assign({}, s, e), this.config.randomThreshold === null && (this.config.randomThreshold = this.config.reverse ? 0.1 : 0.8), this.textNodes = _(this.el), this.nodeLengths = this.textNodes.map((i) => i.nodeValue.length), this.originalText = this.textNodes.map((i) => i.nodeValue).join(""), this.mask = this.originalText.split(" ").map((i) => " ".repeat(i.length)).join(" "), this.currentMask = this.mask, this.totalChars = this.originalText.length, this.scrambleRange = Math.floor(this.totalChars * (this.config.reverse ? 0.25 : 1.5)), this.direction = this.config.reverse ? -1 : 1, this.config.absolute && (this.el.style.position = "absolute", this.el.style.top = "0"), this.config.pointerEvents || (this.el.style.pointerEvents = "none"), this._animationFrame = null, this._startTime = null, this._running = false;
          }
          initialize() {
            return this.currentMask = this.mask, this;
          }
          _getEased(t) {
            let e = -(Math.cos(Math.PI * t) - 1) / 2;
            return e = Math.pow(e, 2), this.config.reverse ? 1 - e : e;
          }
          _updateScramble(t, e, s) {
            if (Math.random() < 0.5 && t > 0 && t < 1)
              for (let i = 0; i < 20; i++) {
                let o = i / 20, a;
                if (this.config.reverse) a = e - Math.floor((1 - Math.random()) * this.scrambleRange * o);
                else a = e + Math.floor((1 - Math.random()) * this.scrambleRange * o);
                if (!(a < 0 || a >= this.totalChars) && this.currentMask[a] !== " ") {
                  let h = Math.random() > this.config.randomThreshold ? this.originalText[a] : M(this.config.reverse, this.config.scrambleSymbols);
                  this.currentMask = p(this.currentMask, a, h);
                }
              }
          }
          _composeOutput(t, e, s) {
            let i = "";
            if (this.config.reverse) {
              let o = Math.max(e - s, 0);
              i = this.mask.slice(0, o) + this.currentMask.slice(o, e) + this.originalText.slice(e);
            } else i = this.originalText.slice(0, e) + this.currentMask.slice(e, e + s) + this.mask.slice(e + s);
            return i;
          }
          _updateTextNodes(t) {
            let e = 0;
            for (let s = 0; s < this.textNodes.length; s++) {
              let i = this.nodeLengths[s];
              this.textNodes[s].nodeValue = t.slice(e, e + i), e += i;
            }
          }
          _tick = (t) => {
            this._startTime || (this._startTime = t);
            let e = t - this._startTime, s = Math.min(e / this.config.duration, 1), i = this._getEased(s), o = Math.floor(this.totalChars * s), a = Math.floor(2 * (0.5 - Math.abs(s - 0.5)) * this.scrambleRange);
            this._updateScramble(s, o, a);
            let h = this._composeOutput(s, o, a);
            this._updateTextNodes(h), s < 1 ? this._animationFrame = requestAnimationFrame(this._tick) : this._running = false;
          };
          start() {
            this._running = true, this._startTime = null, this.config.delay ? setTimeout(() => {
              this._animationFrame = requestAnimationFrame(this._tick);
            }, this.config.delay) : this._animationFrame = requestAnimationFrame(this._tick);
          }
          stop() {
            this._animationFrame && (cancelAnimationFrame(this._animationFrame), this._animationFrame = null), this._running = false;
          }
        };
        return g(T);
      })();
      window.TextScrambler = TextScrambler.default || TextScrambler;
    })();
  }

  // src/features/reports.js
  function generateWeeklyReport() {
    const now = /* @__PURE__ */ new Date();
    const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
    const sevenDaysAgoStart = todayStart - 6 * TIME_WINDOWS.daily;
    const report = {
      totalRequests: 0,
      modelBreakdown: {},
      dailyData: [],
      // 最近7天的数据
      peakDay: "",
      averageDaily: 0,
      generatedAt: (/* @__PURE__ */ new Date()).toISOString()
    };
    for (let i = 0; i < 7; i++) {
      const dayStart = todayStart - (6 - i) * TIME_WINDOWS.daily;
      const date = new Date(dayStart);
      report.dailyData.push({
        date: date.toLocaleDateString("zh-CN"),
        dayOfWeek: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"][date.getDay()],
        models: {},
        total: 0,
        dayStart,
        dayEnd: dayStart + TIME_WINDOWS.daily - 1
      });
    }
    const currentPlanForWeekly = usageData && usageData.planType || "team";
    const sortedModelEntries = MODEL_DISPLAY_ORDER.filter((modelKey) => usageData.models[modelKey]).filter((modelKey) => !(modelKey === "o3-pro" && currentPlanForWeekly !== "pro")).map((modelKey) => [modelKey, usageData.models[modelKey]]);
    Object.entries(usageData.models).forEach(([modelKey, model]) => {
      if (!MODEL_DISPLAY_ORDER.includes(modelKey)) {
        if (modelKey === "o3-pro" && currentPlanForWeekly !== "pro") return;
        sortedModelEntries.push([modelKey, model]);
      }
    });
    sortedModelEntries.forEach(([modelKey, model]) => {
      const validRequests = model.requests.map((req) => tsOf(req)).filter((ts) => ts >= sevenDaysAgoStart && ts < todayStart + TIME_WINDOWS.daily);
      if (validRequests.length > 0) {
        if (!report.modelBreakdown[modelKey]) {
          report.modelBreakdown[modelKey] = 0;
        }
        validRequests.forEach((ts) => {
          const dayData = report.dailyData.find((day) => ts >= day.dayStart && ts <= day.dayEnd);
          if (dayData) {
            dayData.total++;
            dayData.models[modelKey] = (dayData.models[modelKey] || 0) + 1;
            report.modelBreakdown[modelKey]++;
            report.totalRequests++;
          }
        });
      }
    });
    const activeDays = report.dailyData.filter((d) => d.total > 0).length || 1;
    report.averageDaily = Math.round(report.totalRequests / activeDays);
    const maxDayUsage = Math.max(...report.dailyData.map((d) => d.total), 0);
    const peakDayData = report.dailyData.find((d) => d.total === maxDayUsage);
    if (peakDayData) {
      report.peakDay = `${peakDayData.date} ${peakDayData.dayOfWeek}`;
    }
    return report;
  }
  function generateMonthlyReport() {
    const now = /* @__PURE__ */ new Date();
    const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
    const thirtyDaysAgoStart = todayStart - 29 * TIME_WINDOWS.daily;
    const report = {
      totalRequests: 0,
      modelBreakdown: {},
      dailyData: [],
      // 最近30天的数据
      peakDay: "",
      averageDaily: 0,
      generatedAt: (/* @__PURE__ */ new Date()).toISOString()
    };
    for (let i = 0; i < 30; i++) {
      const dayStart = todayStart - (29 - i) * TIME_WINDOWS.daily;
      const date = new Date(dayStart);
      report.dailyData.push({
        date: date.toLocaleDateString("zh-CN"),
        dayOfWeek: ["周日", "周一", "周二", "周三", "周四", "周五", "周六"][date.getDay()],
        models: {},
        total: 0,
        dayStart,
        dayEnd: dayStart + TIME_WINDOWS.daily - 1
      });
    }
    const currentPlanForMonthly = usageData && usageData.planType || "team";
    const sortedModelEntries = MODEL_DISPLAY_ORDER.filter((modelKey) => usageData.models[modelKey]).filter((modelKey) => !(modelKey === "o3-pro" && currentPlanForMonthly !== "pro")).map((modelKey) => [modelKey, usageData.models[modelKey]]);
    Object.entries(usageData.models).forEach(([modelKey, model]) => {
      if (!MODEL_DISPLAY_ORDER.includes(modelKey)) {
        if (modelKey === "o3-pro" && currentPlanForMonthly !== "pro") return;
        sortedModelEntries.push([modelKey, model]);
      }
    });
    sortedModelEntries.forEach(([modelKey, model]) => {
      const validRequests = model.requests.map((req) => tsOf(req)).filter((ts) => ts >= thirtyDaysAgoStart && ts < todayStart + TIME_WINDOWS.daily);
      if (validRequests.length > 0) {
        if (!report.modelBreakdown[modelKey]) {
          report.modelBreakdown[modelKey] = 0;
        }
        validRequests.forEach((ts) => {
          const dayData = report.dailyData.find((day) => ts >= day.dayStart && ts <= day.dayEnd);
          if (dayData) {
            dayData.total++;
            dayData.models[modelKey] = (dayData.models[modelKey] || 0) + 1;
            report.modelBreakdown[modelKey]++;
            report.totalRequests++;
          }
        });
      }
    });
    const activeDays = report.dailyData.filter((d) => d.total > 0).length || 1;
    report.averageDaily = Math.round(report.totalRequests / activeDays);
    const maxDayUsage = Math.max(...report.dailyData.map((d) => d.total), 0);
    const peakDayData = report.dailyData.find((d) => d.total === maxDayUsage);
    if (peakDayData) {
      report.peakDay = `${peakDayData.date} ${peakDayData.dayOfWeek}`;
    }
    return report;
  }
  function mergeUnknownModelsForHtml(report) {
    try {
      const KNOWN = /* @__PURE__ */ new Set([
        // 采用固定显示顺序中的模型
        ...MODEL_DISPLAY_ORDER,
        // 再补充兼容显示顺序外但“已知”的模型键
        "gpt-5",
        "gpt-5-thinking",
        "gpt-5-2-instant",
        "gpt-5-2-thinking",
        "gpt-5-2-pro",
        "gpt-5-1",
        "gpt-5-1-thinking",
        "gpt-5-1-instant",
        "alpha"
      ]);
      const targetKey = "gpt-5-2-instant";
      if (!report.modelBreakdown[targetKey]) report.modelBreakdown[targetKey] = 0;
      const unknownKeys = Object.keys(report.modelBreakdown).filter((k) => !KNOWN.has(k));
      if (unknownKeys.length === 0) return report;
      for (const key of unknownKeys) {
        report.modelBreakdown[targetKey] += report.modelBreakdown[key] || 0;
        delete report.modelBreakdown[key];
      }
      for (const day of report.dailyData) {
        let add = 0;
        for (const key of unknownKeys) {
          if (day.models[key]) {
            add += day.models[key];
            delete day.models[key];
          }
        }
        if (add > 0) {
          day.models[targetKey] = (day.models[targetKey] || 0) + add;
        }
      }
      return report;
    } catch (e) {
      console.warn("[monitor] Failed to merge unknown models for HTML:", e);
      return report;
    }
  }
  function exportWeeklyAnalysis() {
    const report = mergeUnknownModelsForHtml(generateWeeklyReport());
    const sortedModelKeys = MODEL_DISPLAY_ORDER.filter((modelKey) => report.modelBreakdown[modelKey]).concat(Object.keys(report.modelBreakdown).filter((key) => !MODEL_DISPLAY_ORDER.includes(key)));
    const htmlContent = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatGPT 一周用量分析报告 - ${(/* @__PURE__ */ new Date()).toLocaleDateString("zh-CN")}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"><\/script>
<style>
    body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        background-color: #1a1b1e;
        color: #e5e7eb;
        padding: 20px;
        margin: 0;
    }
    .container {
        max-width: 1200px;
        margin: 0 auto;
    }
    h1, h2 {
        color: #f59e0b;
    }
    .summary-cards {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
        gap: 20px;
        margin-bottom: 40px;
    }
    .card {
        background: #2a2b2e;
        padding: 20px;
        border-radius: 12px;
        border: 1px solid #363636;
    }
    .card h3 {
        margin-top: 0;
        color: #9ca3af;
        font-size: 14px;
    }
    .card .value {
        font-size: 28px;
        font-weight: bold;
        color: #f59e0b;
    }
    .card .subtext {
        font-size: 12px;
        color: #9ca3af;
        margin-top: 4px;
    }
    .chart-container {
        background: #2a2b2e;
        padding: 20px;
        border-radius: 12px;
        border: 1px solid #363636;
        margin-bottom: 20px;
        position: relative;
    }
    .chart-container.daily {
        height: 400px;
    }
    .chart-container.pie {
        height: 350px;
    }
    .table-container {
        background: #2a2b2e;
        padding: 20px;
        border-radius: 12px;
        border: 1px solid #363636;
        overflow-x: auto;
    }
    table {
        width: 100%;
        border-collapse: collapse;
    }
    th, td {
        padding: 12px;
        text-align: left;
        border-bottom: 1px solid #363636;
    }
    th {
        background: #1a1b1e;
        color: #f59e0b;
        font-weight: 600;
    }
    .highlight {
        color: #f59e0b;
        font-weight: bold;
    }
    .footer {
        text-align: center;
        margin-top: 40px;
        color: #9ca3af;
        font-size: 12px;
    }
    .info-text {
        color: #9ca3af;
        font-size: 14px;
        margin: 10px 0;
    }
</style>
</head>
<body>
<div class="container">
    <h1>ChatGPT 一周用量分析报告</h1>
    <p class="info-text">分析时间段: ${report.dailyData[0].date} 至 ${report.dailyData[6].date}</p>
    <p class="info-text">生成时间: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}</p>

    <div class="summary-cards">
        <div class="card">
            <h3>总请求数</h3>
            <div class="value">${report.totalRequests}</div>
            <div class="subtext">最近7天</div>
        </div>
        <div class="card">
            <h3>日均使用</h3>
            <div class="value">${report.averageDaily}</div>
            <div class="subtext">活跃天数平均</div>
        </div>
        <div class="card">
            <h3>使用高峰日</h3>
            <div class="value" style="font-size: 20px;">${report.peakDay || "N/A"}</div>
        </div>
        <div class="card">
            <h3>活跃模型数</h3>
            <div class="value">${sortedModelKeys.length}</div>
            <div class="subtext">有使用记录</div>
        </div>
    </div>

    <h2>每日使用趋势</h2>
    <div class="chart-container daily">
        <canvas id="dailyChart"></canvas>
    </div>

    <h2>模型使用分布</h2>
    <div class="chart-container pie">
        <canvas id="modelChart"></canvas>
    </div>

    <h2>详细数据表</h2>
    <div class="table-container">
        <table>
            <thead>
                <tr>
                    <th>日期</th>
                    <th>星期</th>
                    <th>总请求数</th>
                    ${sortedModelKeys.map((model) => `<th>${model}</th>`).join("")}
                </tr>
            </thead>
            <tbody>
                ${report.dailyData.map((day, index) => `
                    <tr ${index === 6 ? 'style="background: rgba(245, 158, 11, 0.1);"' : ""}>
                        <td>${day.date} ${index === 6 ? '<span style="color: #f59e0b;">(今天)</span>' : ""}</td>
                        <td>${day.dayOfWeek}</td>
                        <td class="highlight">${day.total}</td>
                        ${sortedModelKeys.map(
      (model) => `<td>${day.models[model] || 0}</td>`
    ).join("")}
                    </tr>
                `).join("")}
            </tbody>
            <tfoot>
                <tr style="background: #1a1b1e; font-weight: bold;">
                    <td colspan="2">总计</td>
                    <td class="highlight">${report.totalRequests}</td>
                    ${sortedModelKeys.map(
      (model) => `<td>${report.modelBreakdown[model] || 0}</td>`
    ).join("")}
                </tr>
            </tfoot>
        </table>
    </div>

    <div class="footer">
        <p>此报告由 ChatGPT 用量统计脚本自动生成</p>
    </div>
</div>

<script>
    // 配置图表默认选项
    Chart.defaults.color = '#9ca3af';
    Chart.defaults.borderColor = '#363636';

    // 每日使用趋势图
    const dailyCtx = document.getElementById('dailyChart').getContext('2d');
    new Chart(dailyCtx, {
        type: 'line',
        data: {
            labels: ${JSON.stringify(report.dailyData.map(
      (d, i) => i === 6 ? d.date + " (今天)" : d.date
    ))},
            datasets: [{
                label: '每日请求数',
                data: ${JSON.stringify(report.dailyData.map((d) => d.total))},
                borderColor: '#f59e0b',
                backgroundColor: 'rgba(245, 158, 11, 0.1)',
                tension: 0.3,
                fill: true,
                pointRadius: 5,
                pointHoverRadius: 8,
                pointBackgroundColor: '#f59e0b',
                pointBorderColor: '#fff',
                pointBorderWidth: 2
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                legend: {
                    display: false
                },
                tooltip: {
                    callbacks: {
                        afterLabel: function(context) {
                            const index = context.dataIndex;
                            const dayData = ${JSON.stringify(report.dailyData.map((d) => d.dayOfWeek))};
                            return dayData[index];
                        }
                    }
                }
            },
            scales: {
                y: {
                    beginAtZero: true,
                    grid: {
                        color: '#363636'
                    },
                    ticks: {
                        stepSize: 1
                    }
                },
                x: {
                    grid: {
                        color: '#363636'
                    }
                }
            }
        }
    });

    // 模型使用分布饼图
    const modelCtx = document.getElementById('modelChart').getContext('2d');
    new Chart(modelCtx, {
        type: 'doughnut',
        data: {
            labels: ${JSON.stringify(sortedModelKeys)},
            datasets: [{
                data: ${JSON.stringify(sortedModelKeys.map((key) => report.modelBreakdown[key] || 0))},
                backgroundColor: [
                    '#f59e0b', '#10b981', '#ef4444', '#3b82f6',
                    '#9333ea', '#ec4899', '#14b8a6', '#f97316',
                    '#06b6d4', '#84cc16', '#f43f5e', '#8b5cf6'
                ],
                borderWidth: 2,
                borderColor: '#1a1b1e'
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                legend: {
                    position: 'right',
                    labels: {
                        padding: 15,
                        font: {
                            size: 12
                        }
                    }
                },
                tooltip: {
                    callbacks: {
                        label: function(context) {
                            const label = context.label || '';
                            const value = context.parsed || 0;
                            const total = context.dataset.data.reduce((a, b) => a + b, 0);
                            const percentage = ((value / total) * 100).toFixed(2);
                            return label + ': ' + value + ' (' + percentage + '%)';
                        }
                    }
                }
            }
        }
    });
<\/script>
</body>
</html>
    `;
    const blob = new Blob([htmlContent], { type: "text/html;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `chatgpt-weekly-analysis-${formatTimestampForFilename()}.html`;
    document.body.appendChild(a);
    a.click();
    setTimeout(() => {
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      showToast("一周用量分析报告已导出", "success");
    }, 100);
  }
  function exportMonthlyAnalysis() {
    const report = mergeUnknownModelsForHtml(generateMonthlyReport());
    const sortedModelKeys = MODEL_DISPLAY_ORDER.filter((modelKey) => report.modelBreakdown[modelKey]).concat(Object.keys(report.modelBreakdown).filter((key) => !MODEL_DISPLAY_ORDER.includes(key)));
    const htmlContent = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatGPT 一个月用量分析报告 - ${(/* @__PURE__ */ new Date()).toLocaleDateString("zh-CN")}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"><\/script>
<style>
    body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        background-color: #1a1b1e;
        color: #e5e7eb;
        padding: 20px;
        margin: 0;
    }
    .container {
        max-width: 1200px;
        margin: 0 auto;
    }
    h1, h2 {
        color: #f59e0b;
    }
    .summary-cards {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
        gap: 20px;
        margin-bottom: 40px;
    }
    .card {
        background: #2a2b2e;
        padding: 20px;
        border-radius: 12px;
        border: 1px solid #363636;
    }
    .card h3 {
        margin-top: 0;
        color: #9ca3af;
        font-size: 14px;
    }
    .card .value {
        font-size: 28px;
        font-weight: bold;
        color: #f59e0b;
    }
    .card .subtext {
        font-size: 12px;
        color: #9ca3af;
        margin-top: 4px;
    }
    .chart-container {
        background: #2a2b2e;
        padding: 20px;
        border-radius: 12px;
        border: 1px solid #363636;
        margin-bottom: 20px;
        position: relative;
    }
    .chart-container.daily {
        height: 500px;
    }
    .chart-container.pie {
        height: 350px;
    }
    .table-container {
        background: #2a2b2e;
        padding: 20px;
        border-radius: 12px;
        border: 1px solid #363636;
        overflow-x: auto;
        max-height: 600px;
        overflow-y: auto;
    }
    table {
        width: 100%;
        border-collapse: collapse;
    }
    th, td {
        padding: 8px 12px;
        text-align: left;
        border-bottom: 1px solid #363636;
        font-size: 12px;
    }
    th {
        background: #1a1b1e;
        color: #f59e0b;
        font-weight: 600;
        position: sticky;
        top: 0;
        z-index: 1;
    }
    .highlight {
        color: #f59e0b;
        font-weight: bold;
    }
    .footer {
        text-align: center;
        margin-top: 40px;
        color: #9ca3af;
        font-size: 12px;
    }
    .info-text {
        color: #9ca3af;
        font-size: 14px;
        margin: 10px 0;
    }
    .week-separator {
        border-top: 2px solid #f59e0b;
        background: rgba(245, 158, 11, 0.1);
    }
</style>
</head>
<body>
<div class="container">
    <h1>ChatGPT 一个月用量分析报告</h1>
    <p class="info-text">分析时间段: ${report.dailyData[0].date} 至 ${report.dailyData[29].date}</p>
    <p class="info-text">生成时间: ${(/* @__PURE__ */ new Date()).toLocaleString("zh-CN")}</p>

    <div class="summary-cards">
        <div class="card">
            <h3>总请求数</h3>
            <div class="value">${report.totalRequests}</div>
            <div class="subtext">最近30天</div>
        </div>
        <div class="card">
            <h3>日均使用</h3>
            <div class="value">${report.averageDaily}</div>
            <div class="subtext">活跃天数平均</div>
        </div>
        <div class="card">
            <h3>使用高峰日</h3>
            <div class="value" style="font-size: 20px;">${report.peakDay || "N/A"}</div>
        </div>
        <div class="card">
            <h3>活跃模型数</h3>
            <div class="value">${sortedModelKeys.length}</div>
            <div class="subtext">有使用记录</div>
        </div>
    </div>

    <h2>每日使用趋势</h2>
    <div class="chart-container daily">
        <canvas id="dailyChart"></canvas>
    </div>

    <h2>模型使用分布</h2>
    <div class="chart-container pie">
        <canvas id="modelChart"></canvas>
    </div>

    <h2>详细数据表</h2>
    <div class="table-container">
        <table>
            <thead>
                <tr>
                    <th>日期</th>
                    <th>星期</th>
                    <th>总请求数</th>
                    ${sortedModelKeys.map((model) => `<th>${model}</th>`).join("")}
                </tr>
            </thead>
            <tbody>
                ${report.dailyData.map((day, index) => {
      const isToday = index === 29;
      const isWeekStart = new Date(day.dayStart).getDay() === 1;
      return `
                    <tr ${isToday ? 'style="background: rgba(245, 158, 11, 0.1);"' : ""} ${isWeekStart && !isToday ? 'class="week-separator"' : ""}>
                        <td>${day.date} ${isToday ? '<span style="color: #f59e0b;">(今天)</span>' : ""}</td>
                        <td>${day.dayOfWeek}</td>
                        <td class="highlight">${day.total}</td>
                        ${sortedModelKeys.map(
        (model) => `<td>${day.models[model] || 0}</td>`
      ).join("")}
                    </tr>
                `;
    }).join("")}
            </tbody>
            <tfoot>
                <tr style="background: #1a1b1e; font-weight: bold;">
                    <td colspan="2">总计</td>
                    <td class="highlight">${report.totalRequests}</td>
                    ${sortedModelKeys.map(
      (model) => `<td>${report.modelBreakdown[model] || 0}</td>`
    ).join("")}
                </tr>
            </tfoot>
        </table>
    </div>

    <div class="footer">
        <p>此报告由 ChatGPT 用量统计脚本自动生成</p>
    </div>
</div>

<script>
    // 配置图表默认选项
    Chart.defaults.color = '#9ca3af';
    Chart.defaults.borderColor = '#363636';

    // 每日使用趋势图
    const dailyCtx = document.getElementById('dailyChart').getContext('2d');
    new Chart(dailyCtx, {
        type: 'line',
        data: {
            labels: ${JSON.stringify(report.dailyData.map(
      (d, i) => i === 29 ? d.date + " (今天)" : d.date
    ))},
            datasets: [{
                label: '每日请求数',
                data: ${JSON.stringify(report.dailyData.map((d) => d.total))},
                borderColor: '#f59e0b',
                backgroundColor: 'rgba(245, 158, 11, 0.1)',
                tension: 0.3,
                fill: true,
                pointRadius: 3,
                pointHoverRadius: 6,
                pointBackgroundColor: '#f59e0b',
                pointBorderColor: '#fff',
                pointBorderWidth: 2
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                legend: {
                    display: false
                },
                tooltip: {
                    callbacks: {
                        afterLabel: function(context) {
                            const index = context.dataIndex;
                            const dayData = ${JSON.stringify(report.dailyData.map((d) => d.dayOfWeek))};
                            return dayData[index];
                        }
                    }
                }
            },
            scales: {
                y: {
                    beginAtZero: true,
                    grid: {
                        color: '#363636'
                    },
                    ticks: {
                        stepSize: 1
                    }
                },
                x: {
                    grid: {
                        color: '#363636'
                    },
                    ticks: {
                        maxTicksLimit: 15,
                        callback: function(value, index) {
                            // 只显示部分日期标签,避免过于拥挤
                            const date = new Date(${JSON.stringify(report.dailyData.map((d) => d.dayStart))}[index]);
                            if (index === 0 || index === 14 || index === 29 || date.getDate() === 1 || date.getDay() === 1) {
                                return this.getLabelForValue(value);
                            }
                            return '';
                        }
                    }
                }
            }
        }
    });

    // 模型使用分布饼图
    const modelCtx = document.getElementById('modelChart').getContext('2d');
    new Chart(modelCtx, {
        type: 'doughnut',
        data: {
            labels: ${JSON.stringify(sortedModelKeys)},
            datasets: [{
                data: ${JSON.stringify(sortedModelKeys.map((key) => report.modelBreakdown[key] || 0))},
                backgroundColor: [
                    '#f59e0b', '#10b981', '#ef4444', '#3b82f6',
                    '#9333ea', '#ec4899', '#14b8a6', '#f97316',
                    '#06b6d4', '#84cc16', '#f43f5e', '#8b5cf6'
                ],
                borderWidth: 2,
                borderColor: '#1a1b1e'
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            plugins: {
                legend: {
                    position: 'right',
                    labels: {
                        padding: 15,
                        font: {
                            size: 12
                        }
                    }
                },
                tooltip: {
                    callbacks: {
                        label: function(context) {
                            const label = context.label || '';
                            const value = context.parsed || 0;
                            const total = context.dataset.data.reduce((a, b) => a + b, 0);
                            const percentage = ((value / total) * 100).toFixed(2);
                            return label + ': ' + value + ' (' + percentage + '%)';
                        }
                    }
                }
            }
        }
    });
<\/script>
</body>
</html>
    `;
    const blob = new Blob([htmlContent], { type: "text/html;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `chatgpt-monthly-analysis-${formatTimestampForFilename()}.html`;
    document.body.appendChild(a);
    a.click();
    setTimeout(() => {
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
      showToast("一个月用量分析报告已导出", "success");
    }, 100);
  }

  // src/ui/monitor.js
  function animateText(el, config) {
    try {
      const Scrambler = typeof window !== "undefined" && (window.TextScrambler || window["TextScrambler"]) || null;
      if (!Scrambler) return;
      const animator = new Scrambler(el, { ...config });
      if (typeof animator.initialize === "function") animator.initialize();
      if (typeof animator.start === "function") animator.start();
    } catch (e) {
      console.debug("[monitor] Text animation skipped:", e?.message || e);
    }
  }
  function updateUI() {
    const usageContent = document.getElementById("usageContent");
    const settingsContent = document.getElementById("settingsContent");
    if (usageContent) {
      updateUsageContent(usageContent);
      animateText(usageContent, {
        duration: 500,
        delay: 0,
        reverse: false,
        absolute: false,
        pointerEvents: true
      });
    }
    if (settingsContent) {
      updateSettingsContent(settingsContent);
      animateText(settingsContent, {
        duration: 500,
        delay: 0,
        reverse: false,
        absolute: false,
        pointerEvents: true
      });
    }
  }
  function createSettingsModelRow(model, modelKey) {
    const row = document.createElement("div");
    row.className = "model-row";
    const keyLabel = document.createElement("div");
    keyLabel.textContent = displayModelName(modelKey);
    row.appendChild(keyLabel);
    const quotaInput = document.createElement("input");
    quotaInput.type = "number";
    if (model.sharedGroup && usageData.sharedQuotaGroups?.[model.sharedGroup]) {
      quotaInput.value = usageData.sharedQuotaGroups[model.sharedGroup].quota ?? "";
      quotaInput.disabled = true;
      quotaInput.title = `由共享组(${usageData.sharedQuotaGroups[model.sharedGroup].displayName || model.sharedGroup})控制`;
    } else {
      quotaInput.value = typeof model.quota === "number" ? model.quota : "";
    }
    quotaInput.placeholder = "配额";
    quotaInput.dataset.modelKey = modelKey;
    quotaInput.dataset.field = "quota";
    row.appendChild(quotaInput);
    const windowSelect = document.createElement("select");
    windowSelect.dataset.modelKey = modelKey;
    windowSelect.dataset.field = "windowType";
    windowSelect.innerHTML = `
    <option value="hour3">3小时窗口</option>
    <option value="hour5">5小时窗口</option>
    <option value="daily">24小时窗口</option>
    <option value="weekly">7天窗口</option>
    <option value="monthly">30天窗口</option>
  `;
    if (model.sharedGroup && usageData.sharedQuotaGroups?.[model.sharedGroup]) {
      windowSelect.value = usageData.sharedQuotaGroups[model.sharedGroup].windowType || "daily";
      windowSelect.disabled = true;
      windowSelect.title = `由共享组(${usageData.sharedQuotaGroups[model.sharedGroup].displayName || model.sharedGroup})控制`;
    } else {
      windowSelect.value = model.windowType || "daily";
    }
    const controlsContainer = document.createElement("div");
    controlsContainer.style.display = "flex";
    controlsContainer.style.alignItems = "center";
    controlsContainer.style.gap = "4px";
    controlsContainer.appendChild(windowSelect);
    const delBtn = document.createElement("button");
    delBtn.className = "btn delete-btn";
    delBtn.textContent = "删除";
    delBtn.addEventListener("click", () => handleDeleteModel(modelKey));
    controlsContainer.appendChild(delBtn);
    row.appendChild(controlsContainer);
    return row;
  }
  function createUsageModelRow(model, modelKey) {
    const now = Date.now();
    let count = 0;
    let quota = 0;
    let windowType = "daily";
    let lastRequestTime = "never";
    let windowEndInfo = "";
    if (model.sharedGroup) {
      const sharedUsage = collectSharedGroupUsage(model.sharedGroup, now);
      if (sharedUsage) {
        quota = sharedUsage.group.quota;
        windowType = sharedUsage.windowType;
        count = sharedUsage.activeRequests.length;
        const modelRequests = sharedUsage.activeRequests.filter((req) => req.modelKey === modelKey);
        if (modelRequests.length > 0) {
          lastRequestTime = formatTimeAgo(Math.max(...modelRequests.map((req) => req.ts)));
        }
        if (count > 0 && usageData.showWindowResetTime) {
          const oldestActiveTimestamp = Math.min(...sharedUsage.activeRequests.map((req) => req.ts));
          const windowEnd = getWindowEnd(oldestActiveTimestamp, windowType);
          if (windowEnd > now) {
            windowEndInfo = `Window resets in: ${formatTimeLeft(windowEnd)}`;
          }
        }
      }
    } else {
      quota = model.quota;
      windowType = model.windowType;
      const windowDuration = TIME_WINDOWS[windowType] || TIME_WINDOWS.daily;
      const activeRequests = (model.requests || []).map((req) => tsOf(req)).filter((ts) => now - ts < windowDuration);
      count = activeRequests.length;
      if (count > 0) {
        lastRequestTime = formatTimeAgo(Math.max(...activeRequests));
      }
      if (count > 0 && usageData.showWindowResetTime) {
        const oldestActiveTimestamp = Math.min(...activeRequests);
        const windowEnd = getWindowEnd(oldestActiveTimestamp, windowType);
        if (windowEnd > now) {
          windowEndInfo = `Window resets in: ${formatTimeLeft(windowEnd)}`;
        }
      }
    }
    const row = document.createElement("div");
    row.className = "model-row";
    const modelNameContainer = document.createElement("div");
    modelNameContainer.style.display = "flex";
    modelNameContainer.style.alignItems = "center";
    const modelName = document.createElement("span");
    modelName.textContent = displayModelName(modelKey);
    let sharedColor = null;
    if (model.sharedGroup) {
      sharedColor = SHARED_GROUP_COLORS[model.sharedGroup] || COLORS.warning;
      modelName.style.color = sharedColor;
      modelName.title = `共享组:${usageData.sharedQuotaGroups?.[model.sharedGroup]?.displayName || model.sharedGroup}`;
    }
    modelNameContainer.appendChild(modelName);
    const windowBadge = document.createElement("span");
    windowBadge.className = `window-badge ${windowType}`;
    windowBadge.textContent = windowType === "hour3" ? "3h" : windowType === "hour5" ? "5h" : windowType === "daily" ? "24h" : windowType === "weekly" ? "7d" : "30d";
    modelNameContainer.appendChild(windowBadge);
    row.appendChild(modelNameContainer);
    const lastUpdateValue = document.createElement("div");
    lastUpdateValue.className = "request-time";
    lastUpdateValue.textContent = lastRequestTime;
    row.appendChild(lastUpdateValue);
    const usageValue = document.createElement("div");
    if (sharedColor) usageValue.style.color = sharedColor;
    const currentPlan = usageData.planType || "team";
    const quotaDisplay = quota === 0 ? currentPlan === "pro" ? "∞" : "不可用" : String(quota ?? "不可用");
    usageValue.textContent = `${count} / ${quotaDisplay}`;
    if (windowEndInfo && usageData.showWindowResetTime) {
      const windowInfoEl = document.createElement("div");
      windowInfoEl.className = "window-info";
      windowInfoEl.textContent = windowEndInfo;
      usageValue.appendChild(windowInfoEl);
    }
    row.appendChild(usageValue);
    const progressCell = document.createElement("div");
    if (quota === 0) {
      if (currentPlan === "pro") {
        progressCell.textContent = "无限制";
        progressCell.style.color = COLORS.success;
        progressCell.style.fontStyle = "italic";
      } else {
        progressCell.textContent = "不可用";
        progressCell.style.color = COLORS.disabled;
        progressCell.style.fontStyle = "italic";
      }
    } else {
      const usagePercent = count / quota;
      if (usageData.progressType === "dots") {
        const dotContainer = document.createElement("div");
        dotContainer.className = "dot-progress";
        const totalDots = 8;
        for (let i = 0; i < totalDots; i++) {
          const dot = document.createElement("div");
          dot.className = "dot";
          const dotThreshold = (i + 1) / totalDots;
          if (usagePercent >= 1) {
            dot.classList.add("dot-exceeded");
          } else if (usagePercent >= dotThreshold) {
            dot.classList.add("dot-full");
          } else if (usagePercent >= dotThreshold - 0.1) {
            dot.classList.add("dot-partial");
          } else {
            dot.classList.add("dot-empty");
          }
          dotContainer.appendChild(dot);
        }
        progressCell.appendChild(dotContainer);
      } else {
        const progressContainer = document.createElement("div");
        progressContainer.className = "progress-container";
        const progressBar = document.createElement("div");
        progressBar.className = "progress-bar";
        if (usagePercent > 1) progressBar.classList.add("exceeded");
        else if (usagePercent < 0.3) progressBar.classList.add("low-usage");
        progressBar.style.width = `${Math.min(usagePercent * 100, 100)}%`;
        progressContainer.appendChild(progressBar);
        progressCell.appendChild(progressContainer);
      }
    }
    row.appendChild(progressCell);
    return row;
  }
  function updateUsageContent(container) {
    container.innerHTML = "";
    const infoSection = document.createElement("div");
    infoSection.className = "reset-info";
    infoSection.innerHTML = `<b>滑动窗口跟踪:</b>`;
    const windowTypes = document.createElement("div");
    windowTypes.style.display = "flex";
    windowTypes.style.justifyContent = "space-between";
    windowTypes.style.marginTop = "4px";
    windowTypes.innerHTML = `
    <span><span class="window-badge hour3">3h</span> 3小时窗口</span>
    <span><span class="window-badge hour5">5h</span> 5小时窗口</span>
    <span><span class="window-badge daily">24h</span> 24小时窗口</span>
    <span><span class="window-badge weekly">7d</span> 7天窗口</span>
    <span><span class="window-badge monthly">30d</span> 30天窗口</span>
  `;
    infoSection.appendChild(windowTypes);
    container.appendChild(infoSection);
    const tableHeader = document.createElement("div");
    tableHeader.className = "table-header";
    tableHeader.innerHTML = `
    <div>模型名称</div>
    <div>最后使用</div>
    <div>使用量</div>
    <div>进度</div>
  `;
    container.appendChild(tableHeader);
    const now = Date.now();
    const planType = usageData.planType || "team";
    const modelCounts = Object.entries(usageData.models || {}).map(([key, model]) => {
      let activeCount = 0;
      let hasBeenUsed = false;
      let isAvailable = false;
      if (model.sharedGroup) {
        const sharedUsage = collectSharedGroupUsage(model.sharedGroup, now);
        if (sharedUsage) {
          activeCount = sharedUsage.activeRequests.length;
          hasBeenUsed = activeCount > 0;
          isAvailable = sharedUsage.group.quota > 0 || sharedUsage.group.quota === 0 && planType === "pro";
        }
      } else {
        const windowDuration = TIME_WINDOWS[model.windowType] || TIME_WINDOWS.daily;
        activeCount = (model.requests || []).map(tsOf).filter((ts) => now - ts < windowDuration).length;
        hasBeenUsed = (model.requests || []).length > 0;
        isAvailable = model.quota > 0 || model.quota === 0 && planType === "pro";
      }
      return { key, model, hasBeenUsed, isAvailable };
    });
    const sortedModels = MODEL_DISPLAY_ORDER.filter((modelKey) => {
      const modelData = modelCounts.find(({ key }) => key === modelKey);
      if (!modelData) return false;
      if (modelKey === "o3-pro" && planType !== "pro") return false;
      return modelData.hasBeenUsed || modelData.isAvailable;
    }).map((modelKey) => modelCounts.find(({ key }) => key === modelKey)).filter(Boolean);
    const extraModels = modelCounts.filter(({ key }) => !MODEL_DISPLAY_ORDER.includes(key)).filter(({ key }) => !(key === "o3-pro" && planType !== "pro")).filter(({ hasBeenUsed, isAvailable }) => hasBeenUsed || isAvailable).sort((a, b) => a.key.localeCompare(b.key));
    [...sortedModels, ...extraModels].forEach(({ key, model }) => {
      container.appendChild(createUsageModelRow(model, key));
    });
    if (sortedModels.length === 0 && extraModels.length === 0) {
      const emptyState = document.createElement("div");
      emptyState.style.textAlign = "center";
      emptyState.style.color = COLORS.secondaryText;
      emptyState.style.padding = STYLE.spacing.lg;
      emptyState.textContent = Object.keys(usageData.models || {}).length > 0 ? "使用模型后才会显示用量统计。" : "未配置任何模型,请在设置中添加。";
      container.appendChild(emptyState);
    }
  }
  function updateSettingsContent(container) {
    container.innerHTML = "";
    const info = document.createElement("p");
    info.innerHTML = `配置模型映射与配额:<br>
    <span style="color:${COLORS.secondaryText}; font-size:${STYLE.textSize.xs};">
    使用像OpenAI一样的滑动时间窗口(统计最近N小时的使用量)
    </span>`;
    info.style.fontSize = STYLE.textSize.md;
    info.style.lineHeight = STYLE.lineHeight.md;
    info.style.color = COLORS.text;
    container.appendChild(info);
    const tableHeader = document.createElement("div");
    tableHeader.className = "table-header";
    tableHeader.style.gridTemplateColumns = "2fr 1fr 2fr";
    tableHeader.innerHTML = `<div>模型ID</div><div>配额</div><div>窗口/操作</div>`;
    container.appendChild(tableHeader);
    if (!document.querySelector('style[data-monitor-settings-style="true"]')) {
      const css = `
      #settingsContent .table-header,
      #settingsContent .model-row { grid-template-columns: 2fr 1fr 2fr; }
    `;
      const styleEl = document.createElement("style");
      styleEl.setAttribute("data-monitor-settings-style", "true");
      styleEl.textContent = css;
      document.head.appendChild(styleEl);
    }
    const sortedModelKeys = MODEL_DISPLAY_ORDER.filter((modelKey) => usageData.models?.[modelKey]);
    const extraModelKeys = Object.keys(usageData.models || {}).filter((k) => !MODEL_DISPLAY_ORDER.includes(k));
    [...sortedModelKeys, ...extraModelKeys].forEach((modelKey) => {
      container.appendChild(createSettingsModelRow(usageData.models[modelKey], modelKey));
    });
    const addBtn = document.createElement("button");
    addBtn.className = "btn";
    addBtn.textContent = "添加模型映射";
    addBtn.style.marginTop = "20px";
    addBtn.addEventListener("click", () => {
      const rawId = prompt('输入新模型的内部ID(例如:"o3-mini")');
      const newModelID = rawId ? rawId.trim() : "";
      if (!newModelID) return;
      let added = false;
      updateUsageData((data) => {
        if (data.models[newModelID]) return;
        data.models[newModelID] = { requests: [], quota: 50, windowType: "daily" };
        added = true;
      });
      if (!added) {
        alert("模型映射已存在。");
        return;
      }
      updateUI();
      showToast(`模型 ${newModelID} 已添加。`, "success");
    });
    container.appendChild(addBtn);
    const saveBtn = document.createElement("button");
    saveBtn.className = "btn";
    saveBtn.textContent = "保存设置";
    saveBtn.style.marginLeft = STYLE.spacing.sm;
    saveBtn.addEventListener("click", () => {
      const inputs = container.querySelectorAll("input, select");
      let hasChanges = false;
      updateUsageData((data) => {
        inputs.forEach((input) => {
          if (input.disabled) return;
          const modelKey = input.dataset.modelKey;
          const field = input.dataset.field;
          if (!modelKey || !data.models[modelKey]) return;
          if (field === "quota") {
            const newQuota = parseInt(input.value, 10);
            if (!isNaN(newQuota) && newQuota !== data.models[modelKey].quota) {
              data.models[modelKey].quota = newQuota;
              hasChanges = true;
            }
          } else if (field === "windowType") {
            const newWindowType = input.value;
            if (newWindowType && newWindowType !== data.models[modelKey].windowType) {
              data.models[modelKey].windowType = newWindowType;
              hasChanges = true;
            }
          }
        });
      });
      if (hasChanges) {
        updateUI();
        showToast("设置保存成功。");
      } else {
        showToast("未检测到更改。", "warning");
      }
    });
    container.appendChild(saveBtn);
    const clearBtn = document.createElement("button");
    clearBtn.className = "btn";
    clearBtn.textContent = "清除历史";
    clearBtn.style.marginLeft = STYLE.spacing.sm;
    clearBtn.addEventListener("click", () => {
      if (!confirm("确定要清除所有模型的使用历史吗?")) return;
      updateUsageData((data) => {
        Object.values(data.models).forEach((model) => {
          if (Array.isArray(model.requests)) model.requests = [];
        });
        if (data.sharedQuotaGroups && typeof data.sharedQuotaGroups === "object") {
          Object.values(data.sharedQuotaGroups).forEach((group) => {
            if (Array.isArray(group.requests)) group.requests = [];
          });
        }
      });
      updateUI();
      showToast("所有模型的使用历史已清除。");
    });
    container.appendChild(clearBtn);
    const resetQuotaBtn = document.createElement("button");
    resetQuotaBtn.className = "btn";
    resetQuotaBtn.textContent = "恢复默认配额";
    resetQuotaBtn.style.marginLeft = STYLE.spacing.sm;
    resetQuotaBtn.style.color = COLORS.warning;
    resetQuotaBtn.addEventListener("click", () => {
      if (!confirm("确定要恢复当前套餐的默认配额设置吗?\n\n这将重置所有模型的配额和时间窗口,但保留使用历史。")) return;
      const currentPlan = refreshUsageData().planType || "team";
      applyPlanConfig(currentPlan);
      updateUI();
      showToast(`已恢复 ${PLAN_CONFIGS[currentPlan].name} 套餐的默认配额设置`, "success");
    });
    container.appendChild(resetQuotaBtn);
    const resetAllBtn = document.createElement("button");
    resetAllBtn.className = "btn";
    resetAllBtn.textContent = "重置所有";
    resetAllBtn.style.marginLeft = STYLE.spacing.sm;
    resetAllBtn.style.color = COLORS.danger;
    resetAllBtn.addEventListener("click", () => {
      if (!confirm("警告:这将重置所有内容为默认值,包括所有模型配置。确定继续吗?")) return;
      const freshDefaults = JSON.parse(JSON.stringify(defaultUsageData));
      Storage.set(freshDefaults);
      refreshUsageData();
      const planToApply = usageData.planType || "team";
      applyPlanConfig(planToApply);
      updateUI();
      showToast("所有内容已重置为默认值。", "warning");
    });
    container.appendChild(resetAllBtn);
    const weeklyAnalysisBtn = document.createElement("button");
    weeklyAnalysisBtn.className = "btn";
    weeklyAnalysisBtn.textContent = "导出一周分析";
    weeklyAnalysisBtn.style.marginTop = "20px";
    weeklyAnalysisBtn.style.display = "block";
    weeklyAnalysisBtn.style.width = "100%";
    weeklyAnalysisBtn.style.backgroundColor = COLORS.surface;
    weeklyAnalysisBtn.style.border = `1px solid ${COLORS.yellow}`;
    weeklyAnalysisBtn.addEventListener("click", () => exportWeeklyAnalysis());
    container.appendChild(weeklyAnalysisBtn);
    const monthlyAnalysisBtn = document.createElement("button");
    monthlyAnalysisBtn.className = "btn";
    monthlyAnalysisBtn.textContent = "导出一个月分析";
    monthlyAnalysisBtn.style.marginTop = "10px";
    monthlyAnalysisBtn.style.display = "block";
    monthlyAnalysisBtn.style.width = "100%";
    monthlyAnalysisBtn.style.backgroundColor = COLORS.surface;
    monthlyAnalysisBtn.style.border = `1px solid ${COLORS.green}`;
    monthlyAnalysisBtn.addEventListener("click", () => exportMonthlyAnalysis());
    container.appendChild(monthlyAnalysisBtn);
    const dataOperationsContainer = document.createElement("div");
    dataOperationsContainer.style.marginTop = "20px";
    dataOperationsContainer.style.display = "flex";
    dataOperationsContainer.style.gap = "8px";
    dataOperationsContainer.style.justifyContent = "center";
    const exportBtn = document.createElement("button");
    exportBtn.className = "btn";
    exportBtn.textContent = "导出数据";
    exportBtn.style.backgroundColor = COLORS.background;
    exportBtn.style.border = `1px solid ${COLORS.border}`;
    exportBtn.style.borderRadius = "4px";
    exportBtn.style.padding = "8px 12px";
    exportBtn.addEventListener("click", exportUsageData);
    const importBtn = document.createElement("button");
    importBtn.className = "btn";
    importBtn.textContent = "导入数据";
    importBtn.style.backgroundColor = COLORS.background;
    importBtn.style.border = `1px solid ${COLORS.border}`;
    importBtn.style.borderRadius = "4px";
    importBtn.style.padding = "8px 12px";
    importBtn.addEventListener("click", importUsageData);
    dataOperationsContainer.appendChild(exportBtn);
    dataOperationsContainer.appendChild(importBtn);
    container.appendChild(dataOperationsContainer);
    const dataOperationsInfo = document.createElement("div");
    dataOperationsInfo.style.textAlign = "center";
    dataOperationsInfo.style.marginTop = "8px";
    dataOperationsInfo.style.color = COLORS.secondaryText;
    dataOperationsInfo.style.fontSize = STYLE.textSize.xs;
    dataOperationsInfo.textContent = "导入/导出功能可在不同浏览器间同步用量统计数据";
    container.appendChild(dataOperationsInfo);
    const planSelectorContainer = document.createElement("div");
    planSelectorContainer.style.marginTop = STYLE.spacing.md;
    planSelectorContainer.style.display = "flex";
    planSelectorContainer.style.flexDirection = "column";
    planSelectorContainer.style.gap = "12px";
    planSelectorContainer.style.padding = "10px";
    planSelectorContainer.style.border = `1px solid ${COLORS.border}`;
    planSelectorContainer.style.borderRadius = "8px";
    planSelectorContainer.style.backgroundColor = COLORS.surface;
    const planTitle = document.createElement("div");
    planTitle.textContent = "套餐设置";
    planTitle.style.fontWeight = "bold";
    planTitle.style.marginBottom = "8px";
    planTitle.style.color = COLORS.white;
    planSelectorContainer.appendChild(planTitle);
    const planSelectContainer = document.createElement("div");
    planSelectContainer.style.display = "flex";
    planSelectContainer.style.alignItems = "center";
    planSelectContainer.style.justifyContent = "space-between";
    planSelectContainer.style.width = "100%";
    const planTypeLabel = document.createElement("span");
    planTypeLabel.textContent = "当前套餐:";
    planTypeLabel.style.color = COLORS.secondaryText;
    planSelectContainer.appendChild(planTypeLabel);
    const planTypeSelect = document.createElement("select");
    planTypeSelect.style.width = "140px";
    planTypeSelect.style.backgroundColor = COLORS.background;
    planTypeSelect.style.color = COLORS.white;
    planTypeSelect.style.border = `1px solid ${COLORS.border}`;
    planTypeSelect.style.borderRadius = "4px";
    planTypeSelect.style.padding = "4px 8px";
    PLAN_DISPLAY_ORDER.filter((k) => PLAN_CONFIGS[k]).concat(Object.keys(PLAN_CONFIGS).filter((k) => !PLAN_DISPLAY_ORDER.includes(k))).forEach((key) => {
      const config = PLAN_CONFIGS[key];
      const option = document.createElement("option");
      option.value = key;
      option.textContent = config.name;
      planTypeSelect.appendChild(option);
    });
    planTypeSelect.value = usageData.planType || "team";
    planTypeSelect.addEventListener("change", () => {
      const newPlan = planTypeSelect.value;
      const currentPlan = refreshUsageData().planType || "team";
      if (!confirm(`确定要切换到 ${PLAN_CONFIGS[newPlan].name} 套餐吗?

这将更新所有模型的配额和时间窗口设置。`)) {
        planTypeSelect.value = currentPlan;
        return;
      }
      updateUsageData((data) => {
        data.planType = newPlan;
      });
      applyPlanConfig(newPlan);
      updateUI();
      showToast(`已切换到 ${PLAN_CONFIGS[newPlan].name} 套餐`, "success");
    });
    planSelectContainer.appendChild(planTypeSelect);
    planSelectorContainer.appendChild(planSelectContainer);
    const planInfo = document.createElement("div");
    planInfo.style.fontSize = STYLE.textSize.xs;
    planInfo.style.color = COLORS.secondaryText;
    planInfo.style.marginTop = "4px";
    planInfo.textContent = "切换套餐将根据官方限制自动调整所有模型的配额和时间窗口";
    planSelectorContainer.appendChild(planInfo);
    const currentPlanConfig = PLAN_CONFIGS[usageData.planType || "team"];
    const planDetailsContainer = document.createElement("div");
    planDetailsContainer.style.marginTop = "8px";
    planDetailsContainer.style.padding = "8px";
    planDetailsContainer.style.backgroundColor = COLORS.background;
    planDetailsContainer.style.borderRadius = "4px";
    planDetailsContainer.style.border = `1px solid ${COLORS.border}`;
    const planDetailsTitle = document.createElement("div");
    planDetailsTitle.textContent = `${currentPlanConfig.name} 套餐配置:`;
    planDetailsTitle.style.fontWeight = "bold";
    planDetailsTitle.style.marginBottom = "6px";
    planDetailsTitle.style.fontSize = STYLE.textSize.xs;
    planDetailsTitle.style.color = COLORS.yellow;
    planDetailsContainer.appendChild(planDetailsTitle);
    const planDetailsList = document.createElement("div");
    planDetailsList.style.fontSize = STYLE.textSize.xs;
    planDetailsList.style.color = COLORS.secondaryText;
    planDetailsList.style.lineHeight = "1.4";
    const windowTextOf = (windowType) => windowType === "hour3" ? "3小时" : windowType === "hour5" ? "5小时" : windowType === "daily" ? "24小时" : windowType === "weekly" ? "7天" : windowType === "monthly" ? "30天" : "";
    const visibleModels = Object.entries(currentPlanConfig.models).filter(([_, cfg]) => {
      if (cfg.sharedGroup) {
        const group = currentPlanConfig.sharedQuotaGroups?.[cfg.sharedGroup];
        if (!group) return false;
        if (group.quota === 0 && (usageData.planType || "team") !== "pro") return false;
        return true;
      }
      return !(cfg.quota === 0 && (usageData.planType || "team") !== "pro");
    });
    const detailsText = visibleModels.map(([model, cfg]) => {
      if (cfg.sharedGroup) {
        const group = currentPlanConfig.sharedQuotaGroups?.[cfg.sharedGroup];
        if (!group) return `• ${displayModelName(model)}: 未知配置`;
        const quotaText2 = group.quota === 0 ? "无限制" : `${group.quota}次`;
        return `• ${displayModelName(model)}: ${quotaText2}/${windowTextOf(group.windowType)} (共享)`;
      }
      const quotaText = cfg.quota === 0 ? "无限制" : `${cfg.quota}次`;
      return `• ${displayModelName(model)}: ${quotaText}/${windowTextOf(cfg.windowType)}`;
    }).join("\n") || "当前套餐未包含可用模型";
    planDetailsList.textContent = detailsText;
    planDetailsList.style.whiteSpace = "pre-line";
    planDetailsContainer.appendChild(planDetailsList);
    planSelectorContainer.appendChild(planDetailsContainer);
    container.appendChild(planSelectorContainer);
    const optionsContainer = document.createElement("div");
    optionsContainer.style.marginTop = STYLE.spacing.md;
    optionsContainer.style.display = "flex";
    optionsContainer.style.flexDirection = "column";
    optionsContainer.style.gap = "12px";
    optionsContainer.style.padding = "10px";
    optionsContainer.style.border = `1px solid ${COLORS.border}`;
    optionsContainer.style.borderRadius = "8px";
    optionsContainer.style.backgroundColor = COLORS.surface;
    const optionsTitle = document.createElement("div");
    optionsTitle.textContent = "显示选项";
    optionsTitle.style.fontWeight = "bold";
    optionsTitle.style.marginBottom = "8px";
    optionsTitle.style.color = COLORS.white;
    optionsContainer.appendChild(optionsTitle);
    const progressSelectContainer = document.createElement("div");
    progressSelectContainer.style.display = "flex";
    progressSelectContainer.style.alignItems = "center";
    progressSelectContainer.style.justifyContent = "space-between";
    progressSelectContainer.style.width = "100%";
    const progressTypeLabel = document.createElement("span");
    progressTypeLabel.textContent = "进度条样式:";
    progressTypeLabel.style.color = COLORS.secondaryText;
    progressSelectContainer.appendChild(progressTypeLabel);
    const progressTypeSelect = document.createElement("select");
    progressTypeSelect.style.width = "100px";
    progressTypeSelect.style.backgroundColor = COLORS.background;
    progressTypeSelect.style.color = COLORS.white;
    progressTypeSelect.style.border = `1px solid ${COLORS.border}`;
    progressTypeSelect.style.borderRadius = "4px";
    progressTypeSelect.style.padding = "3px 6px";
    progressTypeSelect.innerHTML = `<option value="dots">点状进度</option><option value="bar">条状进度</option>`;
    progressTypeSelect.value = usageData.progressType || "bar";
    progressTypeSelect.addEventListener("change", () => {
      updateUsageData((data) => {
        data.progressType = progressTypeSelect.value;
      });
      updateUI();
    });
    progressSelectContainer.appendChild(progressTypeSelect);
    optionsContainer.appendChild(progressSelectContainer);
    const showResetTimeContainer = document.createElement("div");
    showResetTimeContainer.style.display = "flex";
    showResetTimeContainer.style.alignItems = "center";
    showResetTimeContainer.style.justifyContent = "space-between";
    showResetTimeContainer.style.width = "100%";
    const showResetTimeLabel = document.createElement("label");
    showResetTimeLabel.textContent = "显示窗口重置时间";
    showResetTimeLabel.style.color = COLORS.secondaryText;
    showResetTimeLabel.style.cursor = "pointer";
    const checkboxWrapper = document.createElement("div");
    checkboxWrapper.style.position = "relative";
    checkboxWrapper.style.width = "40px";
    checkboxWrapper.style.height = "20px";
    checkboxWrapper.style.backgroundColor = usageData.showWindowResetTime ? COLORS.success : COLORS.disabled;
    checkboxWrapper.style.borderRadius = "10px";
    checkboxWrapper.style.transition = "all 0.3s ease";
    checkboxWrapper.style.cursor = "pointer";
    const slider = document.createElement("div");
    slider.style.position = "absolute";
    slider.style.top = "2px";
    slider.style.left = usageData.showWindowResetTime ? "22px" : "2px";
    slider.style.width = "16px";
    slider.style.height = "16px";
    slider.style.borderRadius = "50%";
    slider.style.backgroundColor = COLORS.white;
    slider.style.transition = "all 0.3s ease";
    checkboxWrapper.appendChild(slider);
    checkboxWrapper.addEventListener("click", () => {
      const checked = !usageData.showWindowResetTime;
      updateUsageData((data) => {
        data.showWindowResetTime = checked;
      });
      checkboxWrapper.style.backgroundColor = checked ? COLORS.success : COLORS.disabled;
      slider.style.left = checked ? "22px" : "2px";
      updateUI();
    });
    showResetTimeLabel.addEventListener("click", () => checkboxWrapper.click());
    showResetTimeContainer.appendChild(showResetTimeLabel);
    showResetTimeContainer.appendChild(checkboxWrapper);
    optionsContainer.appendChild(showResetTimeContainer);
    container.appendChild(optionsContainer);
  }
  function handleDeleteModel(modelKey) {
    if (!confirm(`确定要删除模型 "${modelKey}" 的配置吗?`)) return;
    let removed = false;
    updateUsageData((data) => {
      if (data.models[modelKey]) {
        delete data.models[modelKey];
        removed = true;
      }
    });
    if (removed) {
      updateUI();
      showToast(`模型 "${modelKey}" 已删除。`);
    } else {
      showToast(`未找到模型 "${modelKey}"。`, "warning");
    }
  }
  function setupDraggable(element) {
    let isDragging = false;
    let startX;
    let startY;
    let origLeft;
    let origTop;
    const handle = element.querySelector("header");
    if (handle) handle.addEventListener("mousedown", startDrag);
    element.addEventListener("mousedown", (e) => {
      if (element.classList.contains("minimized")) startDrag(e);
    });
    function startDrag(e) {
      if (e.target.classList.contains("minimize-btn") || e.target.tagName === "BUTTON" || e.target.tagName === "INPUT" || e.target.tagName === "SELECT") {
        return;
      }
      isDragging = false;
      startX = e.clientX;
      startY = e.clientY;
      const rect = element.getBoundingClientRect();
      origLeft = rect.left;
      origTop = rect.top;
      document.addEventListener("mousemove", handleDrag);
      document.addEventListener("mouseup", stopDrag);
      e.preventDefault();
    }
    function handleDrag(e) {
      const deltaX = e.clientX - startX;
      const deltaY = e.clientY - startY;
      if (!isDragging && (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5)) {
        isDragging = true;
      }
      if (isDragging) {
        const rect = element.getBoundingClientRect();
        const maxX = window.innerWidth - rect.width;
        const maxY = window.innerHeight - rect.height;
        const newLeft = Math.min(Math.max(0, origLeft + deltaX), maxX);
        const newTop = Math.min(Math.max(0, origTop + deltaY), maxY);
        element.style.setProperty("left", `${newLeft}px`, "important");
        element.style.setProperty("top", `${newTop}px`, "important");
        element.style.setProperty("right", "auto", "important");
        element.style.setProperty("bottom", "auto", "important");
        e.preventDefault();
      }
    }
    function stopDrag(e) {
      document.removeEventListener("mousemove", handleDrag);
      document.removeEventListener("mouseup", stopDrag);
      if (isDragging) {
        const newLeft = parseInt(element.style.left, 10);
        const newTop = parseInt(element.style.top, 10);
        Storage.update((data) => {
          data.position = { x: newLeft, y: newTop };
        });
        setTimeout(() => {
          isDragging = false;
        }, 200);
        e.preventDefault();
        e.stopPropagation();
      }
    }
  }
  var _keyboardShortcutsInstalled = false;
  function setupKeyboardShortcuts() {
    if (_keyboardShortcutsInstalled) return;
    _keyboardShortcutsInstalled = true;
    const handleShortcut = (e) => {
      if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "i") {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        const monitor = document.getElementById("chatUsageMonitor");
        if (!monitor) return false;
        if (monitor.classList.contains("minimized")) {
          monitor.classList.remove("minimized");
          const currentData = Storage.get();
          if (currentData.size?.width && currentData.size?.height) {
            monitor.style.width = `${currentData.size.width}px`;
            monitor.style.height = `${currentData.size.height}px`;
          }
          Storage.update((data) => {
            data.minimized = false;
          });
        } else {
          monitor.classList.add("minimized");
          Storage.update((data) => {
            data.minimized = true;
          });
        }
        return false;
      }
    };
    document.addEventListener("keydown", handleShortcut, true);
  }
  var _uiUpdateIntervalId = null;
  function createMonitorUI() {
    if (usageData?.silentMode) return;
    if (document.getElementById("chatUsageMonitor")) return;
    const container = document.createElement("div");
    container.id = "chatUsageMonitor";
    if (usageData.minimized) container.classList.add("minimized");
    if (usageData.size?.width && usageData.size?.height && !usageData.minimized) {
      container.style.width = `${usageData.size.width}px`;
      container.style.height = `${usageData.size.height}px`;
    }
    if (usageData.position?.x !== null && usageData.position?.y !== null) {
      const maxX = window.innerWidth - 400;
      const maxY = window.innerHeight - 500;
      const x = Math.min(Math.max(0, usageData.position.x), maxX);
      const y = Math.min(Math.max(0, usageData.position.y), maxY);
      container.style.setProperty("left", `${x}px`, "important");
      container.style.setProperty("top", `${y}px`, "important");
      container.style.setProperty("right", "auto", "important");
      container.style.setProperty("bottom", "auto", "important");
    } else {
      container.style.setProperty("left", STYLE.spacing.lg, "important");
      container.style.setProperty("bottom", "100px", "important");
      container.style.setProperty("right", "auto", "important");
      container.style.setProperty("top", "auto", "important");
    }
    const header = document.createElement("header");
    const minimizeBtn = document.createElement("div");
    minimizeBtn.className = "minimize-btn";
    minimizeBtn.innerHTML = "−";
    minimizeBtn.title = "最小化监视器";
    minimizeBtn.addEventListener("click", (e) => {
      e.stopPropagation();
      container.classList.add("minimized");
      Storage.update((data) => {
        data.minimized = true;
      });
    });
    header.appendChild(minimizeBtn);
    const usageTabBtn = document.createElement("button");
    usageTabBtn.innerHTML = `<span>用量</span>`;
    usageTabBtn.classList.add("active");
    const settingsTabBtn = document.createElement("button");
    settingsTabBtn.innerHTML = `<span>设置</span>`;
    header.appendChild(usageTabBtn);
    header.appendChild(settingsTabBtn);
    container.appendChild(header);
    const usageContent = document.createElement("div");
    usageContent.className = "content";
    usageContent.id = "usageContent";
    container.appendChild(usageContent);
    const settingsContent = document.createElement("div");
    settingsContent.className = "content";
    settingsContent.id = "settingsContent";
    settingsContent.style.display = "none";
    container.appendChild(settingsContent);
    usageTabBtn.addEventListener("click", () => {
      usageTabBtn.classList.add("active");
      settingsTabBtn.classList.remove("active");
      usageContent.style.display = "";
      settingsContent.style.display = "none";
    });
    settingsTabBtn.addEventListener("click", () => {
      settingsTabBtn.classList.add("active");
      usageTabBtn.classList.remove("active");
      settingsContent.style.display = "";
      usageContent.style.display = "none";
    });
    container.addEventListener("click", (e) => {
      if (!container.classList.contains("minimized")) return;
      container.classList.remove("minimized");
      if (usageData.size?.width && usageData.size?.height) {
        container.style.width = `${usageData.size.width}px`;
        container.style.height = `${usageData.size.height}px`;
      }
      Storage.update((data) => {
        data.minimized = false;
      });
      e.stopPropagation();
    });
    document.body.appendChild(container);
    setupDraggable(container);
    updateUI();
    if (typeof ResizeObserver !== "undefined") {
      const resizeObserver = new ResizeObserver(() => {
        if (container.classList.contains("minimized")) return;
        const width = container.offsetWidth;
        const height = container.offsetHeight;
        if (width > 50 && height > 50) {
          Storage.update((data) => {
            data.size = { width, height };
          });
        }
      });
      resizeObserver.observe(container);
    }
    if (_uiUpdateIntervalId) {
      clearInterval(_uiUpdateIntervalId);
      _uiUpdateIntervalId = null;
    }
    _uiUpdateIntervalId = setInterval(updateUI, 6e4);
  }

  // src/main.js
  function main() {
    installTextScrambler();
    injectStyles();
    refreshUsageData();
    const modelRouting = createModelRouting();
    installFetchInterceptor(modelRouting);
    onDataChanged(() => {
      if (usageData?.silentMode) return;
      updateUI();
    });
    let _pendingInitTimerId = null;
    function scheduleInitialize(delay = 300) {
      if (_pendingInitTimerId) return;
      _pendingInitTimerId = setTimeout(() => {
        _pendingInitTimerId = null;
        initialize();
      }, delay);
    }
    function initialize() {
      if (!document?.body) {
        setTimeout(initialize, 300);
        return;
      }
      refreshUsageData();
      const currentPlan = usageData.planType || "team";
      const planConfig = PLAN_CONFIGS[currentPlan];
      if (planConfig) {
        const firstModelKey = Object.keys(planConfig.models)[0];
        const firstModelCfg = planConfig.models[firstModelKey];
        const shouldReapply = !usageData.models[firstModelKey] || usageData.models[firstModelKey].quota !== firstModelCfg.quota;
        if (shouldReapply) {
          applyPlanConfig(currentPlan);
        } else {
          let addedModels = 0;
          updateUsageData((data) => {
            Object.entries(planConfig.models).forEach(([modelKey, cfg]) => {
              if (data.models[modelKey]) return;
              data.models[modelKey] = {
                requests: [],
                quota: cfg.quota,
                windowType: cfg.windowType
              };
              if (cfg.sharedGroup) delete data.models[modelKey].quota;
              if (cfg.sharedGroup) delete data.models[modelKey].windowType;
              if (cfg.sharedGroup) data.models[modelKey].sharedGroup = cfg.sharedGroup;
              addedModels++;
            });
          });
          if (addedModels > 0) {
            console.log(`[monitor] Added ${addedModels} missing models for ${planConfig.name} plan during init`);
          }
        }
      }
      cleanupExpiredRequests();
      if (usageData.silentMode) {
        const existingMonitor = document.getElementById("chatUsageMonitor");
        if (existingMonitor) existingMonitor.remove();
        return;
      }
      createMonitorUI();
      setupKeyboardShortcuts();
    }
    GM_registerMenuCommand("重置监视器位置", () => {
      Storage.update((data) => {
        data.position = { x: null, y: null };
        data.minimized = false;
      });
      const existingMonitor = document.getElementById("chatUsageMonitor");
      if (existingMonitor) existingMonitor.remove();
      scheduleInitialize(100);
      setTimeout(() => {
        const monitor = document.getElementById("chatUsageMonitor");
        if (monitor) {
          monitor.style.setProperty("left", STYLE.spacing.lg, "important");
          monitor.style.setProperty("bottom", "100px", "important");
          monitor.style.setProperty("right", "auto", "important");
          monitor.style.setProperty("top", "auto", "important");
          showToast("监视器已重置并重新加载", "success");
        } else {
          alert("监视器重置完成。如果没有看到监视器,请刷新页面。");
        }
      }, 500);
    });
    GM_registerMenuCommand("导出用量统计数据", exportUsageData);
    GM_registerMenuCommand("导入用量统计数据", importUsageData);
    if (document.readyState === "loading") {
      window.addEventListener("DOMContentLoaded", () => scheduleInitialize(0));
    } else {
      scheduleInitialize(0);
    }
    const observer = new MutationObserver(() => {
      if (usageData?.silentMode) return;
      if (!document.getElementById("chatUsageMonitor")) scheduleInitialize(300);
    });
    observer.observe(document.documentElement || document.body, { childList: true, subtree: true });
    window.addEventListener("popstate", () => scheduleInitialize(300));
    scheduleInitialize(300);
    console.log("🚀 ChatGPT Usage Monitor loaded");
  }

  // src/index.js
  main();
})();