«

智谱 GLM Coding Plan 用量查询 - iOS小组件

myluzh 发布于 阅读:131 VibeCdoing


前言

有GLM订阅的朋友们都知道,智谱官方手册给了一个GLM用量查询的插件,但是每次需要在命令行调用,所以我就寻思做一个苹果小组件,这样直接就可以看到。

GLM 用量统计 Widget

关于

将智谱官方的 GLM 用量查询功能做成 iOS 主屏幕小组件,无需手动查询,实时掌握配额使用情况。

小组件如下图:

主要功能

自动刷新:每分钟自动更新用量数据

多尺寸支持:适配 2×2 和 2×4 小组件

智能配色:
根据用量百分比动态变色

显示内容 :

使用方法

1、在AppStore下载scriptable

2、在 Scriptable 中导入脚本,配置你的 API Key,添加到主屏幕即可使用
记得把apikey换成你自己的

// GLM 用量 Widget - Scriptable
var CONFIG = {
  baseURL: "https://api.z.ai",
  apiKey: "xxxxxxxxxxxxxxxx"
};

// 传统 pad 函数,替代 padStart
function padNum(n) {
  if (n < 10) {
    return "0" + n;
  }
  return String(n);
}

// 格式化数字
function fmtNum(n) {
  if (n >= 1000000) {
    return (n / 1000000).toFixed(1) + "M";
  } else if (n >= 1000) {
    return (n / 1000).toFixed(1) + "K";
  }
  return String(n);
}

// 获取状态颜色
function getStatusColor(tokenUsage) {
  if (tokenUsage < 30) return new Color("#34C759");
  if (tokenUsage < 60) return new Color("#32D74B");
  if (tokenUsage < 80) return new Color("#FFD60A");
  return new Color("#FF453A");
}

async function createWidget(widgetFamily) {
  var tokenUsage = 0, tokenUsed = 0, tokenTotal = 0;
  var mcpUsage = 0, mcpUsed = 0, mcpTotal = 0;
  var resetTime = "未知";
  var resetDate = null;

  try {
    var quotaReq = new Request(CONFIG.baseURL + "/api/monitor/usage/quota/limit");
    quotaReq.headers = {
      "Authorization": CONFIG.apiKey,
      "Content-Type": "application/json"
    };
    var quotaResp = await quotaReq.loadJSON();

    var limits = (quotaResp.data && quotaResp.data.limits) ? quotaResp.data.limits : [];
    for (var i = 0; i < limits.length; i++) {
      var limit = limits[i];
      if (limit.type === "TOKENS_LIMIT") {
        tokenUsage = limit.percentage || 0;
        tokenUsed = limit.currentValue || 0;
        tokenTotal = limit.usage || 0;
        resetDate = new Date(limit.nextResetTime);
        resetTime = padNum(resetDate.getMonth() + 1) + "/" + padNum(resetDate.getDate()) + " " + padNum(resetDate.getHours()) + ":" + padNum(resetDate.getMinutes());
      } else if (limit.type === "TIME_LIMIT") {
        mcpUsage = limit.percentage || 0;
        mcpUsed = limit.currentValue || 0;
        mcpTotal = limit.usage || 0;
      }
    }
  } catch (error) {
    console.log("错误: " + error);
  }

  var widget = new ListWidget();
  widget.backgroundColor = new Color("#000000");

  var statusColor = getStatusColor(tokenUsage);
  var showRemaining = widgetFamily !== "small";
  var isWide = widgetFamily !== "small";

  // 顶部内边距
  widget.addSpacer(0);

  // 标题栏
  var headerStack = widget.addStack();
  headerStack.layoutHorizontally();

  var titleText = isWide ? "GLM 用量统计(GLM Coding Plan)" : "GLM 用量统计";
  var title = headerStack.addText(titleText);
  title.font = Font.boldSystemFont(14);
  title.textColor = Color.white();

  headerStack.addSpacer();

  // 圆形图标
  var dot = headerStack.addText("●");
  dot.font = Font.systemFont(20);
  dot.textColor = statusColor;

  widget.addSpacer(10);

  // 主内容区:左侧百分比 + 右侧详情
  var mainStack = widget.addStack();
  mainStack.layoutHorizontally();

  // 左侧大百分比
  var percentText = mainStack.addText(Math.floor(tokenUsage) + "%");
  percentText.font = Font.boldSystemFont(32);
  percentText.textColor = statusColor;

  mainStack.addSpacer(6);

  // 右侧详情:根据尺寸决定布局
  if (isWide) {
    // 2x4 或更大:Token 和 MCP 各占一行(标签和数值在同一行)
    var rightStack = mainStack.addStack();
    rightStack.layoutVertically();

    // Token 行
    var tokenRow = rightStack.addStack();
    tokenRow.layoutHorizontally();

    var tokenLabel = tokenRow.addText("Token");
    tokenLabel.font = Font.systemFont(12);
    tokenLabel.textColor = new Color("#8E8E93");

    tokenRow.addSpacer(4);

    var tokenValue = tokenRow.addText(fmtNum(tokenUsed) + "/" + fmtNum(tokenTotal));
    tokenValue.font = Font.systemFont(12);
    tokenValue.textColor = Color.white();

    rightStack.addSpacer(6);

    // MCP 行
    var mcpRow = rightStack.addStack();
    mcpRow.layoutHorizontally();

    var mcpLabel = mcpRow.addText("MCP");
    mcpLabel.font = Font.systemFont(12);
    mcpLabel.textColor = new Color("#8E8E93");

    mcpRow.addSpacer(4);

    var mcpValue = mcpRow.addText(mcpUsed + "/" + mcpTotal);
    mcpValue.font = Font.systemFont(12);
    mcpValue.textColor = Color.white();
  } else {
    // 2x2:4 行布局
    var rightStack = mainStack.addStack();
    rightStack.layoutVertically();

    // Token 标签
    var tokenLabel = rightStack.addText("Token");
    tokenLabel.font = Font.systemFont(8);
    tokenLabel.textColor = new Color("#8E8E93");

    // Token 数值
    var tokenValue = rightStack.addText(fmtNum(tokenUsed) + "/" + fmtNum(tokenTotal));
    tokenValue.font = Font.systemFont(9);
    tokenValue.textColor = Color.white();

    rightStack.addSpacer(4);

    // MCP 标签
    var mcpLabel = rightStack.addText("MCP");
    mcpLabel.font = Font.systemFont(8);
    mcpLabel.textColor = new Color("#8E8E93");

    // MCP 数值
    var mcpValue = rightStack.addText(mcpUsed + "/" + mcpTotal);
    mcpValue.font = Font.systemFont(9);
    mcpValue.textColor = Color.white();
  }

  widget.addSpacer(10);

  // 分隔线(简化版)
  widget.addSpacer(8);

  // 重置时间
  var resetTextStr = "重置:" + resetTime;
  if (showRemaining && resetDate) {
    var now = new Date();
    var diffMs = resetDate - now;
    if (diffMs > 0) {
      var diffHours = Math.floor(diffMs / (1000 * 60 * 60));
      var diffMins = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
      if (diffHours > 0) {
        var minsText = diffMins > 0 ? diffMins + "m" : "";
        resetTextStr += " (剩" + diffHours + "h" + minsText + ")";
      } else {
        resetTextStr += " (剩" + diffMins + "m)";
      }
    }
  }

  var resetText = widget.addText(resetTextStr);
  resetText.font = Font.systemFont(9);
  resetText.textColor = new Color("#8E8E93");

  widget.addSpacer(4);

  // 刷新时间
  var now = new Date();
  var updateTime = "刷新:" + padNum(now.getMonth() + 1) + "/" + padNum(now.getDate()) + " " + padNum(now.getHours()) + ":" + padNum(now.getMinutes());
  var updateText = widget.addText(updateTime);
  updateText.font = Font.systemFont(9);
  updateText.textColor = new Color("#3C3C43");

  // 底部内边距
  widget.addSpacer(4);

  // 设置每分钟刷新
  var nextRefresh = new Date(Date.now() + 60 * 1000);
  widget.refreshAfterDate = nextRefresh;

  return widget;
}

async function run() {
  var widgetFamily = (typeof config !== 'undefined' && config.widgetFamily) ? config.widgetFamily : "small";
  var widget = await createWidget(widgetFamily);
  if (typeof config !== 'undefined' && config.runsInWidget) {
    Script.setWidget(widget);
  } else {
    widget.presentSmall();
  }
  Script.complete();
}

run();

glm 智谱 widget


正文到此结束
版权声明:若无特殊注明,本文皆为 Myluzh Blog 原创,转载请保留文章出处。
文章内容:https://itho.cn/vibecoding/572.html
文章标题:《智谱 GLM Coding Plan 用量查询 - iOS小组件