Skip to content

dsh-plugin-ecustcic-quota

Verified

dsh-plugin-ecustcic-quota · v0.2.3 · MIT · Web UI

在 DSH 侧边栏显示 ecustcic(ai.ecustcic.com)的周期配额:每个受限维度一行进度条,数字与重置倒计时放在悬停提示里;密钥自动复用已指向该站点的模型供应商,无需额外配置。

Install

dsh plugin add dsh-plugin-ecustcic-quota

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Published to npm without a public repository. Inspect the package contents before installing.

Tags

Creators

Readme


description: 在 DSH 侧边栏显示 ecustcic(ai.ecustcic.com)的周期配额剩余量 kind: plugin

dsh-plugin-ecustcic-quota

在 DSH 侧边栏里常驻一张小卡片,显示 ecustcic(ModelGate)账户的周期配额剩余量:每个受限维度一行(「每小时 · 请求」+ 进度条),数字与倒计时放在悬停提示里。

不需要配置 API Key——直接复用 DSH 里已指向 ai.ecustcic.com 的模型供应商的密钥。

安装

本插件同时发布到 npm 官方源和 CNB 制品库,两个源内容一致。装进任意 DSH 应用有两种方式。

方式一:插件市场(推荐)

  1. 打开 DSH,在侧边栏的面板列表里点「插件」(和「会话」「文件」并列的那一行)。

  2. 点「添加插件」→「安装第三方插件」。

  3. 「包名或地址」填:

    dsh-plugin-ecustcic-quota
    
  4. 「安装源」:

    • 默认安装源即可(包已发布到 npm 官方源);

    • 国内网络慢或连不上时,改选「自定义地址」,填:

      https://npm.cnb.cool/ecustcic/dsh-ecustcic-quota/-/packages/
      
  5. 点「安装」,装完按提示重启 DSH。

两点说明:

  • 两个源都公开只读,不需要登录、不需要令牌。
  • 安装界面只填包名和源地址;不要在这里填 sk-gw-... 之类的密钥——密钥由插件自动复用(见下方「配置凭据」)。

装不上时先核对包名和源地址是否和上面完全一致。若你的 DSH 版本面板列表里没有 「插件」,用下面的手动方式。

方式二:手动装进 profile

插件必须挂在 DSH profile 的 bundle 列表里。以 desktop profile 为例,编辑 ~/.dsh/profiles/desktop/package.json:

{
  "dsh": {
    "profile": {
      "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "dsh-plugin-ecustcic-quota"]
    }
  },
  "dependencies": {
    "dsh-plugin-ecustcic-quota": "^0.2.3"
  }
}

用 npm 官方源不需要额外配置;想走 CNB 源,在 profile 目录加一行 .npmrc:

registry=https://npm.cnb.cool/ecustcic/dsh-ecustcic-quota/-/packages/

然后在该目录执行 pnpm install,并重启 DSH(宿主半是 Node 侧代码,改完要重启,光刷新页面不够)。

本地开发时依赖写成 "file:D:\\path\\to\\dshplugin-ecustcic-quota" 或 link: 更顺手;改客户端代码只需重新 pnpm run build 再刷新页面,改宿主代码仍要重启。

如果重启后 DSH 起不来:把 dsh-plugin-ecustcic-quota 从 dsh.profile.bundles 里删掉即可恢复。DSH 对启动期的 unhandled rejection 是 fail-loud 的(直接 process.exit(1)),所以插件有启动缺陷会表现为「应用崩溃」 而不是「插件不显示」。本仓库的 pnpm test 里包含 boot-check,会用真实的 cordis Context 和真实补丁完整走一遍启动路径,正是为了防止这种情况。

功能

  • 一维一行:卡面按上游返回的周期档逐条展示,只列出真的设了上限的维度;不限的维度不占位置。
  • 悬停看数字:每行只有标签和进度条,已用/上限、剩余量、重置倒计时、百分比都在 tooltip 里。
  • 最紧张维度高亮:卡片标题和加粗的那一行都指向「最接近耗尽」的那个维度(同比例时取更短的窗口),而不是上游的计费档。
  • 失败不白屏:一次刷新失败会保留上一次的数据并标注「(旧数据)」,错误卡片会直接告诉你该改什么(哪台主机、实际发现了哪些 provider)。
  • API Key 不进浏览器:凭据只在宿主侧读取,浏览器访问的是同源路由。

配置凭据

插件不保存 API Key,也不需要你额外配置:它直接复用 DSH 里已经指向 baseUrl 的那个模型供应商的密钥。

DSH 的 profile 里本来就有一条通往 ModelGate 的配置:

- id: llm-pi-ai
  config:
    providers:
      cic:
        apiKeyEnv: CIC_API_KEY          # 凭据名字,不是密钥本身
        baseURL: https://ai.ecustcic.com/api/v1

宿主侧启动时向 cordis loader 读合并后的 provider 表(bundle 层 + 你写的 patch 层都已合并),按主机名 ai.ecustcic.com 找到 cic 这一条,取它的 apiKeyEnv(CIC_API_KEY),再从 DSH 凭据库现取密钥。

所以只要 DSH 里能正常用 cic 模型,卡片就能读配额,不需要任何额外步骤。轮换 Key 同样不需要改配置、不需要重启。

密钥在 ~/.dsh/.credentials.yaml 的 refs 段里:

refs:
  CIC_API_KEY: sk-gw-your-key-here

匹配只按主机名,不比对路径:provider 指向 …/api/v1(模型接口),而配额接口在 …/api/user/quota,两者路径不同是设计如此。

找不到主机匹配的 provider 时,卡片会明说找不到,并列出它实际看到的 provider,例如「bestzyq (newapi.bestzyq.cn), cic (ai.ecustcic.com)」,而不是丢一句「未配置」。

配置项

写在 cordis.patch.yml(或任意 patch 层)里,也可以被 Settings 表单覆盖:

配置项 默认值 说明
baseUrl https://ai.ecustcic.com 上游地址,路径会被忽略(只取 origin)
apiKeyEnv 空(自动发现) 覆盖用。留空即复用指向 baseUrl 的 provider 的凭据;填了就只用它
refreshIntervalMs 60000 刷新间隔,范围 5000–3600000
requestTimeoutMs 15000 单次上游超时,范围 1000–120000
showInSidebar true 是否显示在侧边栏
startCollapsed false 是否默认收起卡面(只显示标题行)

apiKeyEnv 留空或填合法的标识符(/^[A-Za-z_][A-Za-z0-9_]*$/)——直接粘 sk-gw-... 进来会被 schema 拒绝,因为那是密钥而不是名字。

工作原理

侧边栏卡片 ──GET /api/ecustcic-quota──▶ 宿主插件 ──GET /api/user/quota──▶ ai.ecustcic.com
   (浏览器)                              (Node 侧)      Authorization: Bearer sk-gw-…
  1. 宿主侧(lib/index.js)按间隔拉 /api/user/quota,把上游的 snake_case 字段整理成浏览器直接能画的形状(periods[].requests.ratio 之类),并把结果缓存起来。
  2. 同源路由 /api/ecustcic-quota 返回这份快照。它受 connection.requestRejection 把守,只有受信任的、已登录的浏览器会话能读,所以它不会变成一个谁都能用的转发口。
  3. 浏览器侧(lib/client.js)只认这个路由,拿不到也存不了 Key。

并发轮询会合并成一次上游请求;POST(或 ?refresh=1)可以强制绕过缓存立即刷新。

上游几处反直觉的地方都做了处理:

  • quota_* 为 null 表示不限量,不是 0。这类维度整行不渲染,不会被误读成「剩余 0」。
  • rate 里的 -1 同样表示不限;三项全不限时速率块整个不出现。
  • 上游没给 period_label 时按 period_seconds 补一个(3600 → 每小时,86400 → 每日,604800 → 每周,2592000 → 每月)。
  • total 常常只有 used_* 没有 quota_*,没有任何上限可算比例,因此不做展示。

数据从哪来

主数据源是 GET /api/user/quota,它一次返回账户级总量(total)、各周期档(periods)和速率限制(rate)。卡片用其中的 periods(每个受限维度一行)和 rate(有上限时才显示);total 只有已用量、没有上限可算比例,因此不展示。

/api/user/model-quotas(按模型拆分)和 /api/user/redeem(兑换码)没有被使用——周期配额卡片用不上它们。

开发

pnpm run build      # 编译宿主半 + 打包浏览器半
pnpm test           # 单元测试 + 渲染冒烟测试 + 加载器契约校验
pnpm run smoke      # 只跑渲染冒烟测试
pnpm run verify     # 只跑加载器契约校验
pnpm run preview    # 把卡片渲染成 preview.html,改样式时先看这里

打包与转译全部零依赖,不需要 esbuild / babel / typescript:

  • scripts/build-host.mjs:宿主半是普通 ESM,直接复制并把相对导入补成显式 .js。
  • scripts/build.mjs:浏览器半打成 window.__ModuleLoader__.load({id, factory}) 单文件,外部依赖只允许用 shell 的共享模块表(react、@deepseek-ai/dsh-client-*)。写盘前会先解析一遍产物,所以模板字符串写坏之类的问题当场构建失败,而不是等重启才发现。
  • scripts/jsx.mjs:一个就够用的 JSX → jsx() 转译器,认字符串、模板串、注释和嵌套标签。模板字符串里的 JSX 不支持,需要的话先赋给变量。
  • scripts/smoke.mjs:假造一个 shell 装载 bundle,用 React 的 server renderer 把每种状态都画一遍,断言关键文案在、且没有 undefined / NaN。

测试全部离线跑,不依赖线上 ModelGate。

发布新版本(两个源都发,保持版本一致):

npm publish --registry=https://registry.npmjs.org/                                   # npm 官方源
npm publish --registry=https://npm.cnb.cool/ecustcic/dsh-ecustcic-quota/-/packages/  # CNB 制品库

publishConfig 里没有锁 registry,所以发哪个源由命令行的 --registry 决定。

已知限制

  • 只读,不提供额度充值或兑换。
  • 不做按模型的细分配额。
  • 倒计时精度是 30 秒一跳,够看但不做秒级。

License

MIT