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 +0 -0
- package/README.md +1 -1
- package/cordis.patch.yml +0 -0
- package/lib/client.js +28 -20
- package/lib/index.js +21 -3
- package/package.json +1 -1
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
|
-
| 🖥️ **设置页完整面板** |
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
+
"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",
|