dsh-commandcode-usage 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/client.js +265 -132
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,7 +10,9 @@ A CommandCode usage & balance panel for DeepSeek Harness — shows API usage, cr
10
10
 
11
11
  - **CommandCode 用量**:请求次数、成功率、Token 消耗、总花费
12
12
  - **信用额度**:月额度、5小时窗口、每周窗口,进度条可视化
13
+ - **⏱ 重置时间显示**:每个周期下方标明截止/重置时刻(「重置 今天/明天/N天后 HH:mm」),一眼知道额度何时刷新
13
14
  - **余额与订阅**:套餐信息、订阅状态、计费周期
15
+ - **精致卡片 UI**:渐变 header + 套餐徽章、统计三卡、图标渐变动画进度条、彩色百分比胶囊(≥85% 红 / ≥60% 黄 / 其余绿)
14
16
  - **主题自适应**:跟随 DSH 的浅色/深色主题自动切换
15
17
  - **侧边栏入口**:在 codex-ui 侧边栏底部提供"⚡ 用量"按钮,点击弹出面板
16
18
 
package/client.js CHANGED
@@ -1,103 +1,124 @@
1
1
  /**
2
2
  * usage-panel 客户端半:在 codex-ui 侧边栏底部注入"⚡ 用量"按钮,
3
- * 点击弹出 CommandCode 用量面板(精致版 UI)。
3
+ * 点击弹出 CommandCode 用量面板(精致卡片 UI)。
4
4
  *
5
5
  * 主题自适应:复用 codex-ui 的 body[data-ds-dark-theme] 机制 —— 面板自带
6
6
  * 主题变量,浅色/深色自动切换。
7
7
  *
8
- * 数据获取:ctx.connection.rpc.call("/dsh-usage", "report", {})
8
+ * 数据获取:/dsh-usage/report RPC(同源 HTTP POST,client-request 协议)
9
9
  */
10
10
  window.__ModuleLoader__.load({
11
- id: "dsh-commandcode-usage",
11
+ id: "@deepseek-ai/usage-panel",
12
12
  factory: function (require) {
13
13
  var module = { exports: {} };
14
14
  var exports = module.exports;
15
15
 
16
- var POLL_MS = 5 * 60 * 1000; // 数据缓存 5 分钟
16
+ var POLL_MS = 5 * 60 * 1000;
17
17
  var PANEL_CLS = "dsh-usage-panel";
18
18
  var BTN_CLS = "dsh-usage-btn";
19
19
 
20
- // ---------- 注入主题样式(精致版) ----------
20
+ // ================= 主题样式 =================
21
21
  var STYLE_ID = "dsh-usage-panel-style";
22
22
  function injectStyles() {
23
23
  if (document.getElementById(STYLE_ID)) return;
24
24
  var css = [
25
- // 主题变量
25
+ // ---------- 主题变量 ----------
26
26
  "." + PANEL_CLS + "{",
27
- " --up-bg:#ffffff;--up-fg:#1a1d1c;--up-fg2:#5c6360;--up-fg3:#9aa09d;",
28
- " --up-border:rgba(20,60,50,.10);--up-hover:#f0f6f4;",
29
- " --up-shadow:0 12px 40px rgba(20,60,50,.14);--up-bar-bg:#eef2f1;",
30
- " --up-accent:#10b981;--up-accent-soft:rgba(16,185,129,.12);",
31
- " --up-card:#f8faf9;",
27
+ " --up-bg:#ffffff;--up-fg:#1d2421;--up-fg2:#5b6662;--up-fg3:#8fa09a;",
28
+ " --up-border:rgba(25,65,50,.10);--up-border2:rgba(25,65,50,.18);",
29
+ " --up-hover:#eef6f3;--up-card:#f6faf8;--up-card2:#ffffff;",
30
+ " --up-shadow:0 1px 2px rgba(16,50,40,.05),0 14px 36px rgba(16,50,40,.16);",
31
+ " --up-grad1:#0ea97a;--up-grad2:#14b8a6;--up-grad3:#0d9488;",
32
+ " --up-accent:#0d9f77;--up-accent-soft:rgba(13,159,119,.10);",
33
+ " --up-bar-bg:#e9f1ee;--up-warn:#eab308;--up-danger:#e5484d;",
32
34
  "}",
33
35
  "body[data-ds-dark-theme] ." + PANEL_CLS + "{",
34
- " --up-bg:#1e2422;--up-fg:#e8ecea;--up-fg2:#a0a8a4;--up-fg3:#6b7470;",
35
- " --up-border:rgba(255,255,255,.08);--up-hover:#262e2b;",
36
- " --up-shadow:0 12px 40px rgba(0,0,0,.4);--up-bar-bg:#2a3230;",
37
- " --up-accent:#34d399;--up-accent-soft:rgba(52,211,153,.14);",
38
- " --up-card:#232a28;",
36
+ " --up-bg:#202724;--up-fg:#e9efec;--up-fg2:#aab6b1;--up-fg3:#72807b;",
37
+ " --up-border:rgba(255,255,255,.08);--up-border2:rgba(255,255,255,.16);",
38
+ " --up-hover:#2a332f;--up-card:#252e2a;--up-card2:#29332f;",
39
+ " --up-shadow:0 1px 2px rgba(0,0,0,.25),0 16px 44px rgba(0,0,0,.52);",
40
+ " --up-grad1:#34d399;--up-grad2:#2dd4bf;--up-grad3:#14b8a6;",
41
+ " --up-accent:#34d399;--up-accent-soft:rgba(52,211,153,.15);",
42
+ " --up-bar-bg:#2f3a35;--up-warn:#fbbf24;--up-danger:#f2555a;",
39
43
  "}",
40
- // 面板容器
44
+ // ---------- 面板外壳 ----------
41
45
  "." + PANEL_CLS + "{",
42
- " position:fixed;z-index:9999;right:12px;top:49px;width:300px;",
43
- " max-height:78vh;overflow-y:auto;overflow-x:hidden;",
44
- " background:var(--up-bg);",
45
- " border:1px solid var(--up-border);",
46
- " border-radius:16px;",
47
- " box-shadow:var(--up-shadow);",
48
- " padding:0;font-family:var(--dcu-font,Inter,system-ui);",
49
- " color:var(--up-fg);",
50
- " display:none;",
51
- " scrollbar-width:thin;scrollbar-color:var(--up-fg3) transparent;",
46
+ " position:fixed;z-index:9999;right:12px;top:49px;width:332px;",
47
+ " max-height:calc(100vh - 62px);overflow-y:auto;overflow-x:hidden;",
48
+ " background:var(--up-bg);border:1px solid var(--up-border);",
49
+ " border-radius:18px;box-shadow:var(--up-shadow);",
50
+ " padding:0;font-family:var(--dcu-font,Inter,system-ui);color:var(--up-fg);",
51
+ " display:none;scrollbar-width:thin;scrollbar-color:var(--up-fg3) transparent;",
52
52
  "}",
53
53
  "." + PANEL_CLS + "::-webkit-scrollbar{width:6px}",
54
54
  "." + PANEL_CLS + "::-webkit-scrollbar-thumb{background:var(--up-fg3);border-radius:3px}",
55
- // 头部
56
- "." + PANEL_CLS + " .up-head{display:flex;align-items:center;gap:8px;padding:14px 16px 12px;border-bottom:1px solid var(--up-border);background:linear-gradient(180deg,var(--up-accent-soft),transparent)}",
57
- "." + PANEL_CLS + " .up-head-dot{width:8px;height:8px;border-radius:50%;background:var(--up-accent);box-shadow:0 0 0 3px var(--up-accent-soft);flex:none}",
58
- "." + PANEL_CLS + " .up-head-title{font-size:13px;font-weight:700;letter-spacing:.2px;flex:1}",
59
- "." + PANEL_CLS + " .up-head-sub{font-size:11px;color:var(--up-fg3);font-weight:400;margin-top:1px}",
60
- "." + PANEL_CLS + " .up-icon-btn{appearance:none;border:0;background:transparent;cursor:pointer;width:26px;height:26px;border-radius:7px;display:grid;place-items:center;color:var(--up-fg3);font-size:13px;transition:background .15s,color .15s}",
61
- "." + PANEL_CLS + " .up-icon-btn:hover{background:var(--up-hover);color:var(--up-fg)}",
62
- // 概览卡片
63
- "." + PANEL_CLS + " .up-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;padding:12px 16px}",
64
- "." + PANEL_CLS + " .up-stat{background:var(--up-card);border:1px solid var(--up-border);border-radius:10px;padding:8px 6px;text-align:center}",
65
- "." + PANEL_CLS + " .up-stat-num{font-size:16px;font-weight:700;color:var(--up-fg);line-height:1.2}",
66
- "." + PANEL_CLS + " .up-stat-lbl{font-size:10px;color:var(--up-fg3);margin-top:2px;letter-spacing:.3px}",
67
- // 分区
68
- "." + PANEL_CLS + " .up-section{padding:4px 16px 8px}",
69
- "." + PANEL_CLS + " .up-section-title{font-size:10px;font-weight:700;color:var(--up-fg3);text-transform:uppercase;letter-spacing:1px;margin:8px 0 4px}",
55
+ "@keyframes up-in{from{opacity:0;transform:translateY(-10px) scale(.97)}to{opacity:1;transform:none}}",
56
+ "." + PANEL_CLS + ".up-open{display:block;animation:up-in .24s cubic-bezier(.2,.9,.3,1.15)}",
57
+ // ---------- Header 渐变横幅 ----------
58
+ "." + PANEL_CLS + " .up-head{position:relative;padding:16px 16px 13px;overflow:hidden}",
59
+ "." + PANEL_CLS + " .up-head-bg{position:absolute;inset:0;background:linear-gradient(118deg,var(--up-grad1),var(--up-grad3) 62%,var(--up-grad2))}",
60
+ "." + PANEL_CLS + " .up-head-bg::after{content:\"\";position:absolute;inset:0;background:radial-gradient(130% 100% at 100% 0,rgba(255,255,255,.28),transparent 55%)}",
61
+ "." + PANEL_CLS + " .up-head-in{position:relative;display:flex;align-items:flex-start;gap:10px}",
62
+ "." + PANEL_CLS + " .up-head-logo{width:34px;height:34px;border-radius:11px;background:rgba(255,255,255,.22);border:1px solid rgba(255,255,255,.4);display:grid;place-items:center;font-size:17px;box-shadow:0 2px 10px rgba(0,0,0,.16);flex:none}",
63
+ "." + PANEL_CLS + " .up-head-title{font-size:15px;font-weight:800;letter-spacing:.2px;flex:1;min-width:0;color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.15)}",
64
+ "." + PANEL_CLS + " .up-head-sub{font-size:10.5px;color:rgba(255,255,255,.82);font-weight:500;margin-top:2px;text-shadow:0 1px 2px rgba(0,0,0,.12)}",
65
+ "." + PANEL_CLS + " .up-icon-btn{appearance:none;border:0;background:rgba(255,255,255,.16);cursor:pointer;width:28px;height:28px;border-radius:9px;display:grid;place-items:center;color:#fff;font-size:15px;transition:background .18s,transform .25s}",
66
+ "." + PANEL_CLS + " .up-icon-btn:hover{background:rgba(255,255,255,.3);transform:rotate(140deg)}",
67
+ "." + PANEL_CLS + " .up-plan-chip{position:relative;display:inline-flex;align-items:center;gap:6px;margin-top:10px;padding:3px 10px;border-radius:99px;background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.34);font-size:10px;font-weight:700;letter-spacing:.5px;color:#fff;text-transform:uppercase;max-width:100%;box-sizing:border-box}",
68
+ "." + PANEL_CLS + " .up-plan-chip .up-dot{width:5px;height:5px;border-radius:50%;background:#c9f7e4;box-shadow:0 0 0 2px rgba(201,247,228,.25);animation:up-pulse 2.2s ease-in-out infinite;flex:none}",
69
+ "@keyframes up-pulse{0%,100%{opacity:1}50%{opacity:.4}}",
70
+ // ---------- 主体 ----------
71
+ "." + PANEL_CLS + " .up-body{padding:13px 13px 11px}",
72
+ // 统计三卡
73
+ "." + PANEL_CLS + " .up-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:12px}",
74
+ "." + PANEL_CLS + " .up-stat{position:relative;background:var(--up-card);border:1px solid var(--up-border);border-radius:12px;padding:10px 4px 8px;text-align:center;transition:border-color .18s,transform .18s,box-shadow .18s;overflow:hidden}",
75
+ "." + PANEL_CLS + " .up-stat::before{content:\"\";position:absolute;top:0;left:16%;right:16%;height:2px;border-radius:2px;background:linear-gradient(90deg,transparent,var(--up-accent),transparent);opacity:.75}",
76
+ "." + PANEL_CLS + " .up-stat:hover{border-color:var(--up-border2);transform:translateY(-1px);box-shadow:0 3px 10px rgba(16,50,40,.06)}",
77
+ "." + PANEL_CLS + " .up-stat-num{font-size:16.5px;font-weight:800;color:var(--up-fg);line-height:1.15;font-variant-numeric:tabular-nums;letter-spacing:-.3px}",
78
+ "." + PANEL_CLS + " .up-stat-lbl{font-size:9px;color:var(--up-fg3);margin-top:4px;letter-spacing:.6px;font-weight:700;text-transform:uppercase}",
79
+ // 分区卡片
80
+ "." + PANEL_CLS + " .up-section{background:var(--up-card);border:1px solid var(--up-border);border-radius:14px;padding:11px 12px 10px;margin-bottom:9px;transition:border-color .18s}",
81
+ "." + PANEL_CLS + " .up-section:hover{border-color:var(--up-border2)}",
82
+ "." + PANEL_CLS + " .up-section-title{display:flex;align-items:center;gap:7px;font-size:9.5px;font-weight:800;color:var(--up-fg3);text-transform:uppercase;letter-spacing:1.2px;margin-bottom:7px}",
83
+ "." + PANEL_CLS + " .up-section-title::before{content:\"\";width:3px;height:11px;border-radius:2px;background:linear-gradient(180deg,var(--up-grad1),var(--up-grad3))}",
70
84
  // 进度行
71
- "." + PANEL_CLS + " .up-progress{margin:6px 0}",
72
- "." + PANEL_CLS + " .up-progress-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}",
73
- "." + PANEL_CLS + " .up-progress-label{font-size:12px;color:var(--up-fg2);font-weight:500}",
74
- "." + PANEL_CLS + " .up-progress-val{font-size:11px;color:var(--up-fg3);font-variant-numeric:tabular-nums}",
75
- "." + PANEL_CLS + " .up-progress-track{height:8px;border-radius:4px;background:var(--up-bar-bg);overflow:hidden;position:relative}",
76
- "." + PANEL_CLS + " .up-progress-fill{height:100%;border-radius:4px;transition:width .5s cubic-bezier(.4,0,.2,1);position:relative}",
77
- "." + PANEL_CLS + " .up-progress-fill::after{content:\"\";position:absolute;inset:0;background:linear-gradient(90deg,rgba(255,255,255,.25),transparent 60%);border-radius:4px}",
85
+ "." + PANEL_CLS + " .up-progress{margin:8px 0}",
86
+ "." + PANEL_CLS + " .up-progress-top{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:5px;gap:8px}",
87
+ "." + PANEL_CLS + " .up-progress-label{font-size:12px;color:var(--up-fg);font-weight:650;display:flex;align-items:center;gap:5px}",
88
+ "." + PANEL_CLS + " .up-progress-label .up-lbl-ic{font-size:12px;width:16px;text-align:center;color:var(--up-accent);flex:none}",
89
+ "." + PANEL_CLS + " .up-progress-val{font-size:10.5px;color:var(--up-fg2);font-variant-numeric:tabular-nums;font-weight:500}",
90
+ "." + PANEL_CLS + " .up-progress-val b{color:var(--up-fg);font-weight:700}",
91
+ "." + PANEL_CLS + " .up-progress-track{height:9px;border-radius:99px;background:var(--up-bar-bg);overflow:hidden;box-shadow:inset 0 1px 2px rgba(0,0,0,.06)}",
92
+ "." + PANEL_CLS + " .up-progress-fill{height:100%;border-radius:99px;transition:width .8s cubic-bezier(.3,.7,.3,1);position:relative;box-shadow:0 1px 3px rgba(0,0,0,.14)}",
93
+ "." + PANEL_CLS + " .up-progress-fill::after{content:\"\";position:absolute;inset:0;border-radius:99px;background:linear-gradient(90deg,rgba(255,255,255,.32),transparent 42%)}",
94
+ "." + PANEL_CLS + " .up-progress-bottom{display:flex;align-items:center;justify-content:space-between;margin-top:5px;gap:8px;min-height:15px}",
95
+ "." + PANEL_CLS + " .up-progress-deadline{font-size:10px;color:var(--up-fg3);font-weight:500;display:inline-flex;align-items:center;gap:3px;letter-spacing:.2px;font-variant-numeric:tabular-nums}",
96
+ "." + PANEL_CLS + " .up-progress-deadline .up-cal{font-size:9.5px;opacity:.9}",
97
+ "." + PANEL_CLS + " .up-pct-chip{font-size:9.5px;font-weight:800;padding:2px 8px;border-radius:99px;letter-spacing:.3px;font-variant-numeric:tabular-nums}",
78
98
  // 页脚
79
- "." + PANEL_CLS + " .up-foot{padding:10px 16px 12px;border-top:1px solid var(--up-border);display:flex;align-items:center;justify-content:space-between}",
80
- "." + PANEL_CLS + " .up-foot-left{font-size:10px;color:var(--up-fg3)}",
81
- "." + PANEL_CLS + " .up-foot-badge{font-size:10px;font-weight:600;color:var(--up-accent);background:var(--up-accent-soft);padding:2px 8px;border-radius:99px}",
82
- // 加载/错误
83
- "." + PANEL_CLS + " .up-loading{padding:24px 16px;text-align:center;font-size:12px;color:var(--up-fg3)}",
84
- "." + PANEL_CLS + " .up-spinner{width:22px;height:22px;border:2px solid var(--up-bar-bg);border-top-color:var(--up-accent);border-radius:50%;margin:0 auto 8px;animation:up-spin .8s linear infinite}",
99
+ "." + PANEL_CLS + " .up-foot{padding:10px 16px 12px;border-top:1px solid var(--up-border);display:flex;align-items:center;justify-content:space-between;gap:8px;background:linear-gradient(0deg,var(--up-accent-soft),transparent 85%)}",
100
+ "." + PANEL_CLS + " .up-foot-left{font-size:10px;color:var(--up-fg3);display:flex;align-items:center;gap:6px;font-variant-numeric:tabular-nums}",
101
+ "." + PANEL_CLS + " .up-foot-user{font-weight:700;color:var(--up-fg2);max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
102
+ // 加载 / 错误
103
+ "." + PANEL_CLS + " .up-loading{padding:36px 16px;text-align:center;font-size:12px;color:var(--up-fg3)}",
104
+ "." + PANEL_CLS + " .up-spinner{width:26px;height:26px;border:3px solid var(--up-bar-bg);border-top-color:var(--up-accent);border-radius:50%;margin:0 auto 10px;animation:up-spin .7s linear infinite}",
85
105
  "@keyframes up-spin{to{transform:rotate(360deg)}}",
86
- "." + PANEL_CLS + " .up-error{padding:24px 16px;text-align:center;font-size:12px;color:#e5484d}",
87
- "." + PANEL_CLS + " .up-retry{margin-top:10px;padding:5px 14px;border-radius:8px;border:1px solid var(--up-border);background:var(--up-card);cursor:pointer;font-size:12px;color:var(--up-fg)}",
88
- // 按钮
106
+ "." + PANEL_CLS + " .up-error{padding:32px 16px;text-align:center;font-size:12px;color:var(--up-danger)}",
107
+ "." + PANEL_CLS + " .up-retry{margin-top:12px;padding:6px 18px;border-radius:99px;border:0;background:var(--up-accent);cursor:pointer;font-size:12px;font-weight:600;color:#fff}",
108
+ "." + PANEL_CLS + " .up-retry:hover{filter:brightness(1.1)}",
109
+ // ---------- 侧栏按钮 ----------
89
110
  "." + BTN_CLS + "{",
90
111
  " --up-fg2:#4e5253;--up-hover:#dfe8e5;",
91
112
  " display:flex;align-items:center;gap:8px;width:100%;min-height:36px;",
92
113
  " padding:0 4px;border-radius:8px;background:transparent;border:0;",
93
- " cursor:pointer;color:var(--up-fg2);",
94
- " font-size:14px;text-align:left;font-family:var(--dcu-font,Inter,system-ui);",
114
+ " cursor:pointer;color:var(--up-fg2);font-size:14px;text-align:left;",
115
+ " font-family:var(--dcu-font,Inter,system-ui);transition:background .15s;",
95
116
  "}",
96
- "body[data-ds-dark-theme] ." + BTN_CLS + "{",
97
- " --up-fg2:#b9bab9;--up-hover:#303432;",
98
- "}",
99
- "." + BTN_CLS + ":hover{background:var(--up-hover);}",
100
- "." + BTN_CLS + " .up-btn-icon{width:20px;height:20px;display:grid;place-items:center;font-size:14px;}",
117
+ "body[data-ds-dark-theme] ." + BTN_CLS + "{--up-fg2:#b9bab9;--up-hover:#303432}",
118
+ "." + BTN_CLS + ":hover{background:var(--up-hover)}",
119
+ "." + BTN_CLS + " .up-btn-icon{width:20px;height:20px;display:grid;place-items:center;font-size:14px}",
120
+ "." + BTN_CLS + ".up-active{background:var(--up-accent-soft,#e6f4ef);color:var(--up-accent,#0d9f77);font-weight:600}",
121
+ "body[data-ds-dark-theme] ." + BTN_CLS + ".up-active{color:#34d399;background:rgba(52,211,153,.12)}",
101
122
  ].join("\n");
102
123
  var style = document.createElement("style");
103
124
  style.id = STYLE_ID;
@@ -107,64 +128,82 @@ window.__ModuleLoader__.load({
107
128
 
108
129
  // ---------- 格式化 ----------
109
130
  function fmtMoney(v) { return "$" + (typeof v === "number" ? v.toFixed(2) : "—"); }
110
- function fmtNum(v) { return typeof v === "number" ? v.toLocaleString() : "—"; }
111
131
  function fmtCompact(v) {
112
132
  if (typeof v !== "number") return "—";
113
- if (v >= 1e9) return (v / 1e9).toFixed(1) + "B";
114
- if (v >= 1e6) return (v / 1e6).toFixed(1) + "M";
133
+ if (v >= 1e6) return (v / 1e6).toFixed(2) + "M";
115
134
  if (v >= 1e3) return (v / 1e3).toFixed(1) + "K";
116
- return String(v);
135
+ return String(Math.round(v));
117
136
  }
118
137
  function pct(used, cap) { return cap > 0 ? Math.min(100, Math.round(used / cap * 100)) : 0; }
119
- function barColor(p) { return p >= 80 ? "#e5484d" : p >= 50 ? "#f5a623" : "var(--up-accent)"; }
138
+ function pad2(n) { return (n < 10 ? "0" : "") + n; }
139
+ // 周期截止 → {text:"M/D HH:mm", days} 或 ""
140
+ function fmtDeadline(t) {
141
+ if (t === undefined || t === null || t === "") return "";
142
+ var d = new Date(t);
143
+ if (isNaN(d.getTime())) return "";
144
+ var now = new Date();
145
+ var days = Math.ceil((d - now) / 86400000);
146
+ var text = (d.getMonth() + 1) + "/" + d.getDate() + " " + pad2(d.getHours()) + ":" + pad2(d.getMinutes());
147
+ return { text: text, days: days };
148
+ }
149
+ function fmtCountdown(dl) {
150
+ if (!dl) return "";
151
+ var hm = dl.text.split(" ")[1];
152
+ if (dl.days <= 0) return "今日 " + hm;
153
+ if (dl.days === 1) return "明天 " + hm;
154
+ return dl.days + "天后 " + hm;
155
+ }
156
+ function barColor(p) { return p >= 85 ? "var(--up-danger)" : p >= 60 ? "var(--up-warn)" : "linear-gradient(90deg,var(--up-grad1),var(--up-grad2))"; }
120
157
 
121
- // ---------- RPC 调 host ----------
122
- function callUsage(ctx) {
158
+ // ---------- RPC ----------
159
+ function callUsage() {
123
160
  return new Promise(function (resolve) {
124
161
  try {
125
- var rpc = ctx && ctx.connection && ctx.connection.rpc;
126
- if (rpc && typeof rpc.call === "function") {
127
- rpc.call("/dsh-usage", "report", {}).then(function (res) {
128
- if (res && res.ok && res.value) resolve(res.value);
129
- else resolve({ error: (res && (res.error || (res.value && res.value.error))) || "RPC 返回异常" });
130
- }).catch(function (e) { resolve({ error: String(e && e.message || e) }); });
131
- return;
132
- }
133
- resolve({ error: "RPC 通道不可用" });
162
+ fetch("/dsh-usage/report", {
163
+ method: "POST",
164
+ headers: { "Content-Type": "application/json" },
165
+ body: JSON.stringify({ type: "client-request", rpcId: "usage-" + Date.now(), method: "report", payload: {} })
166
+ }).then(function (res) { return res.ok ? res.json() : Promise.reject(new Error("HTTP " + res.status)); })
167
+ .then(function (msg) {
168
+ if (msg && msg.type === "server-response" && msg.result) {
169
+ var r = msg.result;
170
+ if (r.ok && r.value) resolve(r.value);
171
+ else resolve({ error: (r.error && (r.error.message || JSON.stringify(r.error))) || "RPC 返回异常" });
172
+ } else resolve({ error: "RPC 响应格式异常" });
173
+ })
174
+ .catch(function (e) { resolve({ error: String(e && e.message || e) }); });
134
175
  } catch (e) { resolve({ error: String(e && e.message || e) }); }
135
176
  });
136
177
  }
137
178
 
138
- // ---------- 精致面板 ----------
139
- function buildPanel(ctx) {
179
+ // ================= 面板 =================
180
+ function buildPanel() {
140
181
  var wrap = document.createElement("div");
141
182
  wrap.className = PANEL_CLS;
142
183
  var state = { data: null, error: null, loading: false };
143
184
 
144
- // 头部(固定结构,不随 render 重建)
185
+ // ---- 头部 ----
145
186
  var head = document.createElement("div");
146
187
  head.className = "up-head";
147
- var dot = document.createElement("span");
148
- dot.className = "up-head-dot";
149
- var headTitles = document.createElement("div");
150
- headTitles.style.cssText = "flex:1;min-width:0";
151
- var headTitle = document.createElement("div");
152
- headTitle.className = "up-head-title";
153
- headTitle.textContent = "CommandCode";
154
- var headSub = document.createElement("div");
155
- headSub.className = "up-head-sub";
156
- headSub.textContent = "用量 · 余额";
157
- headTitles.appendChild(headTitle); headTitles.appendChild(headSub);
158
- var refreshBtn = document.createElement("button");
159
- refreshBtn.className = "up-icon-btn";
160
- refreshBtn.textContent = "↻";
188
+ head.appendChild(el("div", "up-head-bg"));
189
+ var headIn = el("div", "up-head-in");
190
+ var logo = el("div", "up-head-logo", "⚡");
191
+ var titles = el("div");
192
+ titles.style.cssText = "flex:1;min-width:0";
193
+ var headTitle = el("div", "up-head-title", "CommandCode");
194
+ var headSub = el("div", "up-head-sub", "用量 · 余额");
195
+ titles.appendChild(headTitle); titles.appendChild(headSub);
196
+ var refreshBtn = el("button", "up-icon-btn", "↻");
161
197
  refreshBtn.title = "刷新";
162
- refreshBtn.onclick = load;
163
- head.appendChild(dot); head.appendChild(headTitles); head.appendChild(refreshBtn);
198
+ refreshBtn.onclick = function () { load(); };
199
+ headIn.appendChild(logo); headIn.appendChild(titles); headIn.appendChild(refreshBtn);
200
+ head.appendChild(headIn);
201
+ var planChip = el("div", "up-plan-chip"); planChip.style.display = "none";
202
+ head.appendChild(planChip);
164
203
  wrap.appendChild(head);
165
204
 
166
- // 主体(render 时重建)
167
- var body = document.createElement("div");
205
+ // ---- 主体 ----
206
+ var body = el("div", "up-body");
168
207
  wrap.appendChild(body);
169
208
 
170
209
  function el(tag, cls, text) {
@@ -179,19 +218,45 @@ window.__ModuleLoader__.load({
179
218
  s.appendChild(el("div", "up-stat-lbl", label));
180
219
  return s;
181
220
  }
182
- function progressRow(label, used, cap) {
221
+ function progressRow(label, icon, used, cap, deadlineT) {
183
222
  var p = pct(used, cap);
184
223
  var pr = el("div", "up-progress");
224
+ // 顶行
185
225
  var top = el("div", "up-progress-top");
186
- top.appendChild(el("span", "up-progress-label", label));
187
- top.appendChild(el("span", "up-progress-val", fmtMoney(used) + " / " + fmtMoney(cap) + " · " + p + "%"));
226
+ var lbl = el("span", "up-progress-label");
227
+ if (icon) lbl.appendChild(el("span", "up-lbl-ic", icon));
228
+ lbl.appendChild(el("span", "", label));
229
+ top.appendChild(lbl);
230
+ var val = el("span", "up-progress-val");
231
+ var usedB = document.createElement("b");
232
+ usedB.textContent = fmtMoney(used);
233
+ val.appendChild(usedB);
234
+ val.appendChild(document.createTextNode(" / " + fmtMoney(cap)));
235
+ top.appendChild(val);
188
236
  pr.appendChild(top);
237
+ // 进度条(动画)
189
238
  var track = el("div", "up-progress-track");
190
239
  var fill = el("div", "up-progress-fill");
191
- fill.style.width = p + "%";
240
+ fill.style.width = "0%";
192
241
  fill.style.background = barColor(p);
193
242
  track.appendChild(fill);
194
243
  pr.appendChild(track);
244
+ setTimeout(function () { fill.style.width = p + "%"; }, 40);
245
+ // 底行:重置倒计时 + 百分比胶囊
246
+ var bottom = el("div", "up-progress-bottom");
247
+ if (deadlineT && deadlineT.text) {
248
+ var dl = el("span", "up-progress-deadline");
249
+ dl.appendChild(el("span", "up-cal", "🕒"));
250
+ dl.appendChild(document.createTextNode("重置 " + fmtCountdown(deadlineT) + " · " + deadlineT.text));
251
+ bottom.appendChild(dl);
252
+ } else {
253
+ bottom.appendChild(el("span", "", ""));
254
+ }
255
+ var chip = el("span", "up-pct-chip", p + "%");
256
+ chip.style.color = p >= 85 ? "var(--up-danger)" : p >= 60 ? "var(--up-warn)" : "var(--up-accent)";
257
+ chip.style.background = p >= 85 ? "rgba(229,72,77,.12)" : p >= 60 ? "rgba(234,179,8,.13)" : "var(--up-accent-soft)";
258
+ bottom.appendChild(chip);
259
+ pr.appendChild(bottom);
195
260
  return pr;
196
261
  }
197
262
 
@@ -221,45 +286,75 @@ window.__ModuleLoader__.load({
221
286
  var win = d.credits && d.credits.windowLimits || {};
222
287
  var sub = d.subscription && d.subscription.data || {};
223
288
 
224
- // 概览卡片:请求 / 成功 / 花费
289
+ // 头部:账号 + 套餐 chip
290
+ if (account.userName) headSub.textContent = account.userName + (sub.planId ? " · " + sub.planId : "");
291
+ planChip.textContent = ""; planChip.style.display = sub.planId ? "inline-flex" : "none";
292
+ if (sub.planId) {
293
+ planChip.appendChild(el("span", "up-dot"));
294
+ planChip.appendChild(document.createTextNode(sub.status === "active" ? "Active · " + sub.planId : sub.planId));
295
+ }
296
+
297
+ // 统计三卡
225
298
  if (usage.totalCount !== undefined) {
226
299
  var stats = el("div", "up-stats");
227
300
  stats.appendChild(statRow(fmtCompact(usage.totalCount), "请求"));
228
- stats.appendChild(statRow((usage.successRate !== undefined ? usage.successRate + "%" : "—"), "成功率"));
229
- stats.appendChild(statRow(fmtMoney(usage.totalCost), "花费"));
301
+ stats.appendChild(statRow(usage.successRate !== undefined ? usage.successRate + "%" : "—", "成功率"));
302
+ stats.appendChild(statRow(fmtMoney(usage.totalCost), "总花费"));
230
303
  body.appendChild(stats);
231
304
  }
232
305
 
233
- // 窗口用量分区
306
+ // 窗口用量
234
307
  var secWin = el("div", "up-section");
235
308
  secWin.appendChild(el("div", "up-section-title", "窗口用量"));
236
- if (win.fiveHour) secWin.appendChild(progressRow("5小时", win.fiveHour.used, win.fiveHour.cap));
237
- if (win.weekly) secWin.appendChild(progressRow("每周", win.weekly.used, win.weekly.cap));
309
+ if (win.fiveHour) secWin.appendChild(progressRow("5小时滚动窗口", "⏱", win.fiveHour.used, win.fiveHour.cap, fmtDeadline(win.fiveHour.resetAt)));
310
+ if (win.weekly) secWin.appendChild(progressRow("每周用量", "📅", win.weekly.used, win.weekly.cap, fmtDeadline(win.weekly.resetAt)));
311
+ if (win.limited === false && !win.fiveHour && !win.weekly) secWin.appendChild(el("div", "up-progress-deadline", "无限额度 · 无窗口限制"));
238
312
  body.appendChild(secWin);
239
313
 
240
- // 信用分区
314
+ // 信用 / 月额度
241
315
  if (credits.monthlyCredits !== undefined) {
242
316
  var secCred = el("div", "up-section");
243
317
  secCred.appendChild(el("div", "up-section-title", "月额度"));
244
- var usedMonthly = win.weekly ? win.weekly.used : (usage.totalCredits || 0);
318
+ var usedMonthly = (usage.totalMonthlyCredits !== undefined ? usage.totalMonthlyCredits : 0);
245
319
  var capMonthly = usedMonthly + credits.monthlyCredits;
246
- secCred.appendChild(progressRow("剩余", credits.monthlyCredits, capMonthly));
320
+ secCred.appendChild(progressRow("当月已用", "💳", usedMonthly, capMonthly, fmtDeadline(sub.currentPeriodEnd)));
321
+ // 明细
322
+ var det = el("div", "up-progress-bottom");
323
+ det.style.cssText = "justify-content:flex-start;gap:14px;margin-top:6px;padding-top:7px;border-top:1px dashed var(--up-border);min-height:0";
324
+ var detItem = function (k, v) {
325
+ var it = el("span", "up-progress-deadline");
326
+ it.appendChild(document.createTextNode(k + " "));
327
+ var vv = document.createElement("b");
328
+ vv.textContent = v;
329
+ vv.style.cssText = "color:var(--up-fg2);font-weight:700";
330
+ it.appendChild(vv);
331
+ return it;
332
+ };
333
+ det.appendChild(detItem("剩余额度", fmtMoney(credits.monthlyCredits)));
334
+ det.appendChild(detItem("已购买", fmtMoney(credits.purchasedCredits)));
335
+ if (credits.freeCredits !== undefined) det.appendChild(detItem("免费", fmtMoney(credits.freeCredits)));
336
+ secCred.appendChild(det);
247
337
  body.appendChild(secCred);
248
338
  }
249
339
 
250
- // 底部:套餐 + 更新时间
340
+ // 页脚
251
341
  var foot = el("div", "up-foot");
252
342
  var footL = el("div", "up-foot-left");
253
- footL.textContent = (account.userName || "") + " · " + (d.fetchedAt ? new Date(d.fetchedAt).toLocaleTimeString() : "");
343
+ var fu = el("span", "up-foot-user", account.userName || "");
344
+ footL.appendChild(fu);
345
+ footL.appendChild(document.createTextNode("·"));
346
+ footL.appendChild(document.createTextNode(d.fetchedAt ? new Date(d.fetchedAt).toLocaleTimeString() : ""));
254
347
  foot.appendChild(footL);
255
- if (sub.planId) foot.appendChild(el("span", "up-foot-badge", sub.planId));
348
+ var footR = el("span", "up-foot-left");
349
+ footR.textContent = "5分钟自动刷新";
350
+ foot.appendChild(footR);
256
351
  body.appendChild(foot);
257
352
  }
258
353
 
259
354
  function load() {
260
355
  state.loading = true; state.error = null;
261
356
  render();
262
- callUsage(ctx).then(function (res) {
357
+ callUsage().then(function (res) {
263
358
  state.loading = false;
264
359
  if (res && res.error) state.error = res.error;
265
360
  else {
@@ -270,21 +365,47 @@ window.__ModuleLoader__.load({
270
365
  });
271
366
  }
272
367
 
273
- // 点击面板外部关闭
274
- function onDocClick(ev) {
275
- if (!wrap.contains(ev.target) && !btn.contains(ev.target)) hide();
368
+ var open = false; var panelBtn = null;
369
+ function onDocClick(ev) { if (!wrap.contains(ev.target) && !(panelBtn && panelBtn.contains(ev.target))) hide(); }
370
+ function onKey(ev) { if (ev.key === "Escape") hide(); }
371
+ function show() {
372
+ open = true;
373
+ load();
374
+ wrap.style.display = "block";
375
+ wrap.classList.add("up-open");
376
+ if (panelBtn) panelBtn.classList.add("up-active");
377
+ document.addEventListener("click", onDocClick, true);
378
+ document.addEventListener("keydown", onKey, true);
379
+ }
380
+ function hide() {
381
+ open = false;
382
+ wrap.style.display = "none";
383
+ wrap.classList.remove("up-open");
384
+ if (panelBtn) panelBtn.classList.remove("up-active");
385
+ document.removeEventListener("click", onDocClick, true);
386
+ document.removeEventListener("keydown", onKey, true);
276
387
  }
277
- function show() { load(); wrap.style.display = "block"; document.addEventListener("click", onDocClick); }
278
- function hide() { wrap.style.display = "none"; document.removeEventListener("click", onDocClick); }
279
- function toggle() { if (wrap.style.display === "none") show(); else hide(); }
388
+ function toggle() { if (open) hide(); else show(); }
280
389
 
281
- // 定时刷新
282
- var timer = setInterval(function () { if (wrap.style.display !== "none") load(); }, POLL_MS);
390
+ var timer = setInterval(function () { if (open) load(); }, POLL_MS);
283
391
 
284
- return { wrap: wrap, toggle: toggle };
392
+ var api = {
393
+ wrap: wrap,
394
+ setButton: function (b) { panelBtn = b; },
395
+ toggle: toggle,
396
+ hide: hide,
397
+ dispose: function () {
398
+ clearInterval(timer);
399
+ document.removeEventListener("click", onDocClick, true);
400
+ document.removeEventListener("keydown", onKey, true);
401
+ if (wrap.parentNode) wrap.parentNode.removeChild(wrap);
402
+ }
403
+ };
404
+ wrap.__dshDispose = api.dispose;
405
+ return api;
285
406
  }
286
407
 
287
- // ---------- 按钮 ----------
408
+ // ---------- 侧栏按钮 ----------
288
409
  function buildButton(panel) {
289
410
  var btn = document.createElement("button");
290
411
  btn.type = "button";
@@ -300,16 +421,28 @@ window.__ModuleLoader__.load({
300
421
  }
301
422
 
302
423
  // ---------- apply ----------
424
+ var _instance = null;
303
425
  exports.apply = function apply(ctx) {
304
426
  if (typeof document === "undefined") return;
305
- if (document.documentElement.dataset.dshUsagePanel === "on") return;
306
- document.documentElement.dataset.dshUsagePanel = "on";
427
+
428
+ if (_instance) {
429
+ try { if (typeof _instance.dispose === "function") _instance.dispose(); } catch (e) {}
430
+ _instance = null;
431
+ }
432
+ var oldWrap = document.querySelector("." + PANEL_CLS);
433
+ if (oldWrap && oldWrap.__dshDispose) { try { oldWrap.__dshDispose(); } catch (e) {} }
434
+ var oldBtns = document.querySelectorAll("." + BTN_CLS);
435
+ for (var i = 0; i < oldBtns.length; i++) {
436
+ if (oldBtns[i].parentNode) oldBtns[i].parentNode.removeChild(oldBtns[i]);
437
+ }
307
438
 
308
439
  injectStyles();
309
440
 
310
- var panel = buildPanel(ctx);
441
+ var panel = buildPanel();
311
442
  var btn = buildButton(panel);
443
+ if (panel && typeof panel.setButton === 'function') panel.setButton(btn);
312
444
  document.body.appendChild(panel.wrap);
445
+ _instance = panel;
313
446
 
314
447
  function tryAttach() {
315
448
  var foot = document.querySelector(".dcu-footer-actions");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-commandcode-usage",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "CommandCode 用量与余额面板 — 在 codex-ui 侧边栏底部显示 CommandCode API 用量、信用额度、5小时/每周窗口。 · CommandCode usage & balance panel for DeepSeek Harness: shows API usage, credit balance, 5-hour/weekly windows from the sidebar footer.",
5
5
  "main": "./index.js",
6
6
  "exports": {