dsh-ollama-usage 0.1.3 → 0.1.4

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.
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -27,7 +27,7 @@ panel, persisted credentials & snapshots, auto-refresh.*
27
27
  | 🎯 **会话用量(每 5 小时)** | Ollama Cloud 的 5 小时会话配额,置顶展示「已用 X% · 剩余 Y%」 |
28
28
  | 📅 **周用量(Weekly)** | 本周配额进度,与会话用量并排展示 |
29
29
  | 🎨 **侧边栏双横条** | 极简透明风格:会话 5h **淡紫** + 周用量**深紫**,各带百分比数字,悬停显示详情,每 60 秒自动更新(窄栏自动隐藏) |
30
- | 🖥️ **设置页完整面板** | 周期起止时间、近 4 周费用、模型请求数排行、最近 5 条会话用量历史 |
30
+ | 🖥️ **设置页完整面板** | 会话/周用量进度条、周用量重置倒计时、模型请求数排行、最近 5 条会话用量历史 |
31
31
  | 💾 **持久化** | API Key 与用量快照写入 `$DSH_HOME/storages/ollama-usage/usage.json`(权限 600,保留 24 条历史),跨对话 / 重启自动恢复 |
32
32
  | 🔄 **自动刷新** | Host 每 10 分钟自动查询(页面关闭也持续);Key 失效(401)自动清除并提示 |
33
33
  | 🔑 **登录引导** | 未登录时一键打开 [ollama.com/settings](https://ollama.com/settings) 登录、到 [settings/keys](https://ollama.com/settings/keys) 创建 API Key;兼容旧版 CLI 的 `~/.ollama/auth.json` |
package/cordis.patch.yml CHANGED
File without changes
package/lib/client.js CHANGED
@@ -2,8 +2,8 @@
2
2
  * dsh-ollama-usage 浏览器端 bundle(经 __ModuleLoader__ 加载)。
3
3
  *
4
4
  * 提供三处界面:
5
- * - settings.section「Ollama 用量」:完整面板(会话 5h / 周用量进度条、费用、
6
- * 模型请求数、历史记录、API Key 配置、清除 Key);
5
+ * - settings.section「Ollama 用量」:完整面板(会话 5h / 周用量进度条、
6
+ * 周用量重置倒计时、模型请求数、历史记录、API Key 配置、清除 Key);
7
7
  * - sidebar.footer.action:极简双横条(会话 5h 淡紫 / 周用量深紫,透明背景);
8
8
  * - tool.view.cordis:Run 卡片内的同款面板。
9
9
  *
@@ -81,9 +81,7 @@ window.__ModuleLoader__.load({
81
81
  if (p < 90) return 'var(--dsw-alias-state-warn-primary,#e0af68)'
82
82
  return 'var(--dsw-alias-state-error-primary,#f7768e)'
83
83
  }
84
- const PERIOD_LABELS = { last_4_weeks: '近 4 周', weekly: '本周', session: '本次会话' }
85
- const fmtTime = (iso) => {
86
- try {
84
+ const fmtTime = (iso) => { try {
87
85
  const d = new Date(iso)
88
86
  const p2 = (n) => (n < 10 ? '0' + n : String(n))
89
87
  return p2(d.getMonth() + 1) + '-' + p2(d.getDate()) + ' ' + p2(d.getHours()) + ':' + p2(d.getMinutes())
@@ -91,6 +89,26 @@ window.__ModuleLoader__.load({
91
89
  return String(iso || '')
92
90
  }
93
91
  }
92
+ /** 距重置时间还有多久,如 "2 小时 33 分钟"、"6 天 15 小时 38 分钟"。 */
93
+ const fmtCountdown = (iso) => {
94
+ try {
95
+ const end = new Date(iso).getTime()
96
+ if (!Number.isFinite(end)) return null
97
+ const ms = end - Date.now()
98
+ if (ms < 60000) return '不到 1 分钟'
99
+ const totalMin = Math.floor(ms / 60000)
100
+ const days = Math.floor(totalMin / 1440)
101
+ const hours = Math.floor((totalMin % 1440) / 60)
102
+ const mins = totalMin % 60
103
+ const parts = []
104
+ if (days > 0) parts.push(days + ' 天')
105
+ if (hours > 0 || days > 0) parts.push(hours + ' 小时')
106
+ parts.push(mins + ' 分钟')
107
+ return parts.join(' ')
108
+ } catch (e) {
109
+ return null
110
+ }
111
+ }
94
112
 
95
113
  function apply(ctx) {
96
114
  injectCss()
@@ -124,7 +142,8 @@ window.__ModuleLoader__.load({
124
142
  ),
125
143
  React.createElement('div', { className: 'olusa-bar' },
126
144
  React.createElement('div', { className: 'olusa-barfill', style: { width: width + '%', background: colorFor(p) } })
127
- )
145
+ ),
146
+ (props.resetText ? React.createElement('div', { className: 'olusa-hint' }, props.resetText) : null)
128
147
  )
129
148
  }
130
149
 
@@ -154,23 +173,12 @@ window.__ModuleLoader__.load({
154
173
 
155
174
  function UsageBody(props) {
156
175
  const d = props.data
157
- const periodLabel = PERIOD_LABELS[d.periodType] || (d.periodType ? String(d.periodType).toUpperCase() : '')
158
- const periodText = periodLabel && d.periodStart && d.periodEnd
159
- ? (periodLabel + ' ' + d.periodStart + ' → ' + d.periodEnd)
160
- : periodLabel
161
- const costNum = d.cost !== null && d.cost !== undefined && d.cost !== '' ? Number(d.cost) : NaN
162
- const costText = Number.isFinite(costNum) ? '$' + costNum.toFixed(2) : null
176
+ const weeklyResetText = d.weeklyResetAt ? fmtCountdown(d.weeklyResetAt) : null
163
177
  const models = Array.isArray(d.models) ? d.models.slice(0, 5) : []
164
178
  const history = Array.isArray(props.history) ? props.history.slice(-5).reverse() : []
165
179
  return React.createElement('div', { className: 'olusa-block' },
166
- (periodText ? React.createElement('div', { className: 'olusa-hint' }, periodText) : null),
167
180
  React.createElement(BarRow, { label: '会话用量(每 5 小时)', percent: d.sessionPercent, em: true }),
168
- React.createElement(BarRow, { label: '本周用量(Weekly)', percent: d.weeklyPercent }),
169
- (costText !== null
170
- ? React.createElement('div', { className: 'olusa-row' },
171
- React.createElement('span', null, '近 4 周费用'),
172
- React.createElement('span', null, costText))
173
- : null),
181
+ React.createElement(BarRow, { label: '本周用量(Weekly)', percent: d.weeklyPercent, resetText: weeklyResetText ? '还有 ' + weeklyResetText + ' 重置' : null }),
174
182
  (models.length > 0
175
183
  ? React.createElement('div', { className: 'olusa-models' },
176
184
  React.createElement('div', { className: 'olusa-hint' }, '模型请求数(本周)'),
@@ -282,7 +290,7 @@ window.__ModuleLoader__.load({
282
290
  React.createElement('button', { className: 'olusa-btn', disabled: busy, onClick: () => doCheck('') }, busy ? '查询中…' : '刷新')
283
291
  ),
284
292
  body,
285
- React.createElement('div', { className: 'olusa-ver' }, 'dsh-ollama-usage · v0.1.0')
293
+ React.createElement('div', { className: 'olusa-ver' }, 'dsh-ollama-usage · v0.1.4')
286
294
  )
287
295
  }
288
296
 
package/lib/index.js CHANGED
@@ -139,6 +139,22 @@ function parseUsage(body, status) {
139
139
  }
140
140
  }
141
141
 
142
+ /* ------------------------------------------------------------------ *
143
+ * 周重置时间推算(API 不提供各限额重置时间,按配额语义推算)
144
+ * - weekly:下周一 00:00 UTC(last_4_weeks 周期锚点为周一)
145
+ * ------------------------------------------------------------------ */
146
+
147
+ function computeResetTimes(nowIso) {
148
+ const now = new Date(nowIso)
149
+ const daysUntilMonday = ((7 - now.getUTCDay()) % 7) + 1
150
+ const weeklyResetAt = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + daysUntilMonday)).toISOString()
151
+ return { weeklyResetAt }
152
+ }
153
+
154
+ function withResetTimes(usage, nowIso) {
155
+ return Object.assign({}, usage, computeResetTimes(nowIso))
156
+ }
157
+
142
158
  /** 用 token 查询并把结果写入持久化文件;返回 RpcResult({ ok, value|error })。 */
143
159
  async function checkWithToken(token) {
144
160
  let res
@@ -166,10 +182,11 @@ async function checkWithToken(token) {
166
182
  const history = Array.isArray(prev && prev.history) ? prev.history.slice() : []
167
183
  history.push({ at: now, sessionPercent: parsed.usage.sessionPercent, weeklyPercent: parsed.usage.weeklyPercent })
168
184
  const trimmed = history.length > HISTORY_CAP ? history.slice(history.length - HISTORY_CAP) : history
169
- const persisted = writeFile({ token, updatedAt: now, usage: parsed.usage, history: trimmed })
185
+ const usage = withResetTimes(parsed.usage, now)
186
+ const persisted = writeFile({ token, updatedAt: now, usage, history: trimmed })
170
187
  return {
171
188
  ok: true,
172
- value: { usage: parsed.usage, updatedAt: now, persisted, history: trimmed.slice(-5) },
189
+ value: { usage, updatedAt: now, persisted, history: trimmed.slice(-5) },
173
190
  }
174
191
  }
175
192
 
@@ -203,7 +220,8 @@ async function dispatch(endpoint, payload) {
203
220
  if (endpoint === 'snapshot') {
204
221
  const rec = readFile()
205
222
  if (!rec || !rec.usage) return { ok: false, error: { code: 'no-data', message: '暂无持久化的用量数据' } }
206
- return { ok: true, value: { tokenPersisted: !!rec.token, updatedAt: rec.updatedAt, history: (rec.history || []).slice(-5), usage: rec.usage } }
223
+ const usage = withResetTimes(rec.usage, new Date().toISOString())
224
+ return { ok: true, value: { tokenPersisted: !!rec.token, updatedAt: rec.updatedAt, history: (rec.history || []).slice(-5), usage } }
207
225
  }
208
226
  if (endpoint === 'forget') {
209
227
  const rec = readFile()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ollama-usage",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "DeepSeek Harness 插件:可视化 Ollama Cloud 账号用量余量(每 5 小时会话用量 + 周用量),支持侧边栏双横条、设置页面板、API Key 与用量快照持久化、自动刷新与登录引导。Ollama Pro usage & remaining-quota visualization for DeepSeek Harness: 5-hour session + weekly quota bars in the sidebar footer, a full settings panel, persisted API key and usage snapshots, auto-refresh and login guidance.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",