dsh-whale-widget 0.3.1 → 0.3.2
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/README.md +2 -2
- package/assets/whale-widget.js +134 -106
- package/lib/index.js +22 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ DeepSeek Harness(DSH)Web 界面右下角的常驻挂件:小鲸鱼气泡图
|
|
|
10
10
|
|
|
11
11
|
- 🐋 **常驻自启**:随 DSH Web 界面每次打开自动出现(标准 DSH bundle 插件)
|
|
12
12
|
- 💰 **余额**:60 秒自动刷新 + 点击鲸鱼手动刷新;余额变化时数字**滚动动画**;瞬时网络抖动自动沿用最近余额不报错
|
|
13
|
-
- 📊 **今日已用(小鲸鱼记账)**:不需要任何令牌。余额**下降**按观测累计为消费,余额**上升**(充值 / 赠金)单独记录、不会冲掉已有消费;检测到余额增加会提示「待核对余额调整」,可在「小鲸鱼记账 →
|
|
13
|
+
- 📊 **今日已用(小鲸鱼记账)**:不需要任何令牌。余额**下降**按观测累计为消费,余额**上升**(充值 / 赠金)单独记录、不会冲掉已有消费;检测到余额增加会提示「待核对余额调整」,可在「小鲸鱼记账 → **DeepSeek(内置)→ 设置 → 余额校正**」按实际累计到账金额与非调用扣减校正 —— **「已观测消费」与「余额校正」都是 DeepSeek 账户口径**(由该 API key 的余额观测得出,不含其它厂商;「本机模型费用」才是所有模型的本地估算)(逐轮明细保留 90 天或最多 2 万条、逐日归档保留 365 天,超期数据归档到 `.dshw-usage-archive.json`);金额按 8 位小数记账、显示保留两位
|
|
14
14
|
- 💬 **每轮对话消耗**:监听 DSH 本机会话事件,按模型**真实 usage**(input / cache / output / reasoning tokens)换算金额,每轮结束弹出消耗泡泡;可开关、自动关闭秒数可设(0 = 不自动关闭),**泡泡内容可自定义**(模块化,金额用 `{cost}` 引用),入口:菜单 → 每轮消耗提示 → 「自定义提示」
|
|
15
15
|
- ⛰️ **峰谷定价**:工作日高峰 9:00–12:00、14:00–18:00(北京时间),其余空闲;2026-08-23 起**周末全天谷价**。峰谷模块支持状态文字、倒计时、"梁文峰谷 / !?强强?! / 简洁"等多种样式
|
|
16
16
|
- 📒 **用量记录窗口**:今日模型消费、近 7 天、全部记录;模型占比条;按日期展开逐条明细(含时间与金额);支持按日期或**模型名搜索**;模型名带友好标注(`deepseek-flash` → `DeepSeek-V4.1-Flash`,旧名标注"同 V4.1 Flash")
|
|
@@ -341,7 +341,7 @@ curl http://127.0.0.1:3080/dsh-whale/audio.json
|
|
|
341
341
|
- **图片/音效不显示、没声音**:确认插件包内 `assets/` 完整(`DSniang1.png`、`*.mp3`、`minecraft-exp-orb.wav` 等);缺失时相关功能静默降级。
|
|
342
342
|
- **余额报「未配置 DEEPSEEK_API_KEY」**:去 DSH 凭据里配置。
|
|
343
343
|
- **今日已用显示 `--`**:先等一次成功的余额观测;统计从该观测时刻开始,起点之前的消费不在此区间内。
|
|
344
|
-
- **今日已用与官网有差异**:先核对同一账户、同一币种与同一统计区间;若有充值或其它余额调整,用「小鲸鱼记账 →
|
|
344
|
+
- **今日已用与官网有差异**:先核对同一账户、同一币种与同一统计区间;若有充值或其它余额调整,用「小鲸鱼记账 → DeepSeek(内置)→ 设置 → 余额校正」填写实际到账金额。余额接口只返回余额快照、不提供充值流水,充值与消费发生在同一次刷新间隔时需要实际到账金额才能校正。
|
|
345
345
|
- **充值后消费数字没变**:这是预期行为——充值不会增加消费;数字上方会出现「待核对余额调整」,提示你补充本统计区间的累计到账金额。
|
|
346
346
|
- **每轮消耗不显示**:确认菜单里「每轮对话后自动显示消耗金额」已勾选;一轮对话要完整结束(`turn/end`)才结算。余额变化泡泡与消耗泡泡抢层时,提醒会退化为居中卡片。
|
|
347
347
|
- **每轮消耗泡泡的内容**:菜单 → 每轮消耗提示 → 「自定义提示」里编辑(模块化,金额用 `{cost}`);这里同时能设自动关闭秒数与任务结束音效。
|
package/assets/whale-widget.js
CHANGED
|
@@ -293,7 +293,7 @@ var css = [
|
|
|
293
293
|
'.dshwv-usagepanel{position:fixed;z-index:26020;background:#fff;border:1px solid rgba(32,49,112,.35);border-radius:10px;box-shadow:0 8px 22px rgba(15,23,42,.22);padding:10px 12px;color-scheme:light;max-height:70vh;overflow-y:auto}',
|
|
294
294
|
// 用量作为主菜单内的子界面
|
|
295
295
|
'.dshwv-menuview{display:block}',
|
|
296
|
-
'.dshwv-usage-sub{display:none;max-height:min(70vh,560px);overflow-y:auto;padding-right:2px;width:100%;box-sizing:border-box}',
|
|
296
|
+
'.dshwv-usage-sub{display:none;max-height:min(70vh,560px);overflow-y:auto;padding-right:2px;padding-bottom:12px;width:100%;box-sizing:border-box}',
|
|
297
297
|
'.dshwv-usage-back{border:none;background:none;color:#203170;font-size:12px;font-weight:600;cursor:pointer;padding:0 0 2px;text-align:left;width:100%}',
|
|
298
298
|
'.dshwv-usage-back:hover{color:#2f4488;text-decoration:underline}',
|
|
299
299
|
'.dshwv-usagebody{display:flex;flex-direction:column;gap:2px;color:#203170;font-size:12px;min-width:0;overflow-x:hidden}',
|
|
@@ -1870,6 +1870,13 @@ function apiModelById(id) {
|
|
|
1870
1870
|
for (var i = 0; i < apiModels.length; i++) if (apiModels[i] && apiModels[i].id === id) return apiModels[i]
|
|
1871
1871
|
return null
|
|
1872
1872
|
}
|
|
1873
|
+
// 「充值 / 余额校正」只属于固定的 DeepSeek(内置):宿主在模型条目里下发 canAdjustBalance,
|
|
1874
|
+
// 前端据此决定要不要在设置菜单里给出入口 —— 手动新增的同名模型、Kimi 等其它厂商都不会有,
|
|
1875
|
+
// 新增模板也不会自动继承(宿主还会在路由层再校验一次,见 balance-adjustments.json)。
|
|
1876
|
+
function apiCanAdjustBalance(model) {
|
|
1877
|
+
return !!(model && model.id === 'deepseek' && model.builtin === true &&
|
|
1878
|
+
model.provider === 'deepseek' && model.canAdjustBalance === true)
|
|
1879
|
+
}
|
|
1873
1880
|
// 今日已用金额自带的币种(host 下发的 todayUsageCurrency):
|
|
1874
1881
|
// 会话事件金额在 host 已按自定义单价折算成人民币,余额差则是厂商币种 → 显示必须按各自的币种,
|
|
1875
1882
|
// 否则 USD 单价的模型会把人民币数值渲染成 $。
|
|
@@ -2800,7 +2807,7 @@ function openApiModelMenu(modelId) {
|
|
|
2800
2807
|
else st.textContent = '余额 ' + apiFmtMoney(m.balance, m.currency) + ' · 今日已用 ' + apiFmtMoney(m.todayUsage, apiTodayCur(m))
|
|
2801
2808
|
card.appendChild(st)
|
|
2802
2809
|
var ms = (usageSet && usageSet.models && usageSet.models[modelId]) || {}
|
|
2803
|
-
function rowOf(label, stateFn, onEdit) {
|
|
2810
|
+
function rowOf(label, stateFn, onEdit, buttonLabel) {
|
|
2804
2811
|
var r = document.createElement('div')
|
|
2805
2812
|
r.className = 'dshwv-audiorow'
|
|
2806
2813
|
var l = document.createElement('span')
|
|
@@ -2817,8 +2824,9 @@ function openApiModelMenu(modelId) {
|
|
|
2817
2824
|
info.style.textOverflow = 'ellipsis'
|
|
2818
2825
|
info.textContent = stateFn()
|
|
2819
2826
|
r.appendChild(info)
|
|
2820
|
-
r.appendChild(apiBtn('编辑', 'dshwv-roleimport', onEdit))
|
|
2827
|
+
r.appendChild(apiBtn(buttonLabel || '编辑', 'dshwv-roleimport', onEdit))
|
|
2821
2828
|
card.appendChild(r)
|
|
2829
|
+
return r
|
|
2822
2830
|
}
|
|
2823
2831
|
rowOf('余额预警', function () {
|
|
2824
2832
|
var a = ms.alert
|
|
@@ -2875,13 +2883,15 @@ function openApiModelMenu(modelId) {
|
|
|
2875
2883
|
card.appendChild(pr)
|
|
2876
2884
|
}
|
|
2877
2885
|
// 只读行(复用现有 .dshwv-audiorow / .dshwv-usage-hint,不引入新颜色与字体)
|
|
2878
|
-
function readonlyRow(label, text) {
|
|
2886
|
+
function readonlyRow(label, text, hintHtml) {
|
|
2879
2887
|
var r = document.createElement('div')
|
|
2880
2888
|
r.className = 'dshwv-audiorow'
|
|
2881
2889
|
var l = document.createElement('span')
|
|
2882
2890
|
l.textContent = label
|
|
2883
2891
|
l.style.flex = '0 0 auto'
|
|
2884
2892
|
r.appendChild(l)
|
|
2893
|
+
// 需要「?」说明时,说明收进圆圈(悬停显示、点击钉住),与挂件其它说明圈同一套组件
|
|
2894
|
+
if (hintHtml) { try { r.appendChild(dshwvAskDot(hintHtml)) } catch (err) {} }
|
|
2885
2895
|
var v = document.createElement('span')
|
|
2886
2896
|
v.className = 'dshwv-usage-hint'
|
|
2887
2897
|
v.style.flex = '1'
|
|
@@ -2914,6 +2924,32 @@ function openApiModelMenu(modelId) {
|
|
|
2914
2924
|
pTxt = '未设置(沿用内置价目表)→ 在「密钥 / 接口」里填写'
|
|
2915
2925
|
}
|
|
2916
2926
|
readonlyRow('单价', pTxt)
|
|
2927
|
+
// 「已观测消费」= DeepSeek 账户口径(余额观测),只在内置项的设置里显示,与下面的「余额校正」配套。
|
|
2928
|
+
if (apiCanAdjustBalance(m)) {
|
|
2929
|
+
var acc = m.accounting || null
|
|
2930
|
+
var accAmt = (acc && typeof acc.amount === 'number') ? acc.amount : m.todayUsage
|
|
2931
|
+
var accInfo = (acc && acc.firstObservedAt)
|
|
2932
|
+
? '统计起点(北京):' + accountingTime(acc.firstObservedAt) + '。起点前的消费未计入;该账户的观测包含同一个 key 在别处的消费。'
|
|
2933
|
+
: '尚无余额观测:先配置 DeepSeek API key 并成功刷新一次余额。'
|
|
2934
|
+
readonlyRow('已观测消费', ((acc && acc.label) || m.usageLabel || '已观测消费') + ' ' +
|
|
2935
|
+
(isFinite(Number(accAmt)) ? apiFmtMoney(accAmt, apiTodayCur(m)) : '--'), accInfo)
|
|
2936
|
+
// 需要用户动手的提示仍然直接显示(不藏进「?」里)
|
|
2937
|
+
if (acc && acc.needsReview) {
|
|
2938
|
+
var accWarn = document.createElement('div')
|
|
2939
|
+
accWarn.className = 'dshwv-usage-hint'
|
|
2940
|
+
accWarn.style.cssText = 'line-height:1.65;white-space:normal;margin:2px 0 4px'
|
|
2941
|
+
accWarn.textContent = '检测到余额增加,请用下面的「余额校正」核对本区间累计到账。'
|
|
2942
|
+
card.appendChild(accWarn)
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
// 「充值 / 余额校正」入口:只给固定的 DeepSeek(内置),放在「单价」下方、沿用同一行布局与按钮样式。
|
|
2946
|
+
// 其它厂商、手动新增的 DeepSeek、仅改名为「DeepSeek(内置)」的模型都不会走到这里。
|
|
2947
|
+
if (apiCanAdjustBalance(m)) {
|
|
2948
|
+
var adjustmentRow = rowOf('余额校正', function () {
|
|
2949
|
+
return (m.accounting && m.accounting.label) || '充值与余额调整'
|
|
2950
|
+
}, function () { openBalanceAdjustment(modelId) }, '校正')
|
|
2951
|
+
adjustmentRow.lastElementChild.setAttribute('data-action', 'balance-adjustment')
|
|
2952
|
+
}
|
|
2917
2953
|
// 币种不一致提示:今日已用按「自带币种」显示(会话事件为 CNY)。若与模型币种不同且没填汇率,
|
|
2918
2954
|
// 今日预算提醒会被跳过(见 A 方案),这里给出可见的补救提示。
|
|
2919
2955
|
var tuc = String((m && m.todayUsageCurrency) || '').toUpperCase()
|
|
@@ -3009,94 +3045,92 @@ function accountingTime(at) {
|
|
|
3009
3045
|
} catch (err) { return String(at || '') }
|
|
3010
3046
|
}
|
|
3011
3047
|
var accountingMask = null
|
|
3012
|
-
function openBalanceAdjustment() {
|
|
3013
|
-
|
|
3014
|
-
|
|
3048
|
+
function openBalanceAdjustment(modelId) {
|
|
3049
|
+
// 只有固定的 DeepSeek(内置)能打开:入口由宿主下发的 canAdjustBalance 控制,这里再挡一次
|
|
3050
|
+
if (!apiCanAdjustBalance(apiModelById(modelId)) || accountingMask) return
|
|
3051
|
+
var adjustmentUrl = '/dsh-whale/balance-adjustments.json?modelId=' + encodeURIComponent(modelId)
|
|
3052
|
+
closeApiModelPanel()
|
|
3053
|
+
// 与「额度」「余额预警 / 今日预算」等窗口共用同一套骨架与样式:
|
|
3054
|
+
// dshwv-usage-mask + dshwv-usage-card + dshwv-bubtitle/bubhint + apiPanelRow/apiTextInput + dshwv-bubbtns
|
|
3015
3055
|
var mask = document.createElement('div')
|
|
3016
3056
|
mask.className = 'dshwv-usage-mask'
|
|
3017
|
-
mask.style.
|
|
3018
|
-
mask.style.zIndex = '2147483647'
|
|
3057
|
+
mask.style.zIndex = '30000'
|
|
3019
3058
|
var card = document.createElement('form')
|
|
3020
3059
|
card.className = 'dshwv-usage-card'
|
|
3021
|
-
card.style.
|
|
3060
|
+
card.style.width = 'min(430px,94vw)'
|
|
3061
|
+
card.style.padding = '14px 16px'
|
|
3062
|
+
card.style.boxSizing = 'border-box'
|
|
3063
|
+
card.style.textAlign = 'left'
|
|
3022
3064
|
card.setAttribute('role', 'dialog')
|
|
3023
3065
|
card.setAttribute('aria-modal', 'true')
|
|
3024
|
-
card.setAttribute('aria-label', '
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3066
|
+
card.setAttribute('aria-label', 'DeepSeek(内置)余额校正')
|
|
3067
|
+
// 内容可能比 82vh 高:中间这段自己滚,按钮行固定在底部(与 .dshwv-reswrap 同一做法)
|
|
3068
|
+
var body = document.createElement('div')
|
|
3069
|
+
body.style.cssText = 'min-height:0;overflow-y:auto;flex:1 1 auto'
|
|
3070
|
+
var title = document.createElement('div')
|
|
3071
|
+
title.className = 'dshwv-bubtitle'
|
|
3072
|
+
title.textContent = '余额校正 · DeepSeek(内置)'
|
|
3073
|
+
body.appendChild(title)
|
|
3074
|
+
var introduction = document.createElement('div')
|
|
3075
|
+
introduction.className = 'dshwv-bubhint'
|
|
3076
|
+
introduction.style.cssText = 'margin:0 0 8px;white-space:normal;line-height:1.6;text-align:left'
|
|
3030
3077
|
introduction.textContent = '核对统计区间内的全部到账后,重新计算本地消费。此操作不会充值,也不会改变 DeepSeek 账户余额。'
|
|
3031
|
-
introduction
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
var label = document.createElement('label')
|
|
3035
|
-
label.style.cssText = 'display:block;font-size:13px;margin:12px 0'
|
|
3036
|
-
var text = document.createElement('span')
|
|
3037
|
-
text.textContent = labelText
|
|
3038
|
-
label.appendChild(text)
|
|
3039
|
-
input.style.cssText = 'display:block;width:100%;box-sizing:border-box;min-height:40px;margin-top:5px;border:1px solid #b9c5e7;border-radius:8px;padding:8px;background:#fff;color:#203170;font:inherit'
|
|
3040
|
-
label.appendChild(input)
|
|
3041
|
-
card.appendChild(label)
|
|
3042
|
-
return text
|
|
3043
|
-
}
|
|
3044
|
-
var dates = document.createElement('select')
|
|
3045
|
-
field('记账日期(北京时间)', dates)
|
|
3078
|
+
body.appendChild(introduction)
|
|
3079
|
+
var dates = apiSelectEl([], '')
|
|
3080
|
+
body.appendChild(apiPanelRow('日期', dates))
|
|
3046
3081
|
var interval = document.createElement('div')
|
|
3047
|
-
interval.
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
credits
|
|
3082
|
+
interval.className = 'dshwv-bubhint'
|
|
3083
|
+
interval.style.cssText = 'margin:0 0 8px;white-space:pre-line;line-height:1.6;text-align:left'
|
|
3084
|
+
body.appendChild(interval)
|
|
3085
|
+
var credits = apiTextInput('', '未到账请填 0')
|
|
3051
3086
|
credits.inputMode = 'decimal'
|
|
3052
3087
|
credits.required = true
|
|
3053
3088
|
credits.autocomplete = 'off'
|
|
3054
|
-
|
|
3055
|
-
var
|
|
3056
|
-
|
|
3089
|
+
body.appendChild(apiPanelRow('累计到账', credits))
|
|
3090
|
+
var creditsHint = document.createElement('div')
|
|
3091
|
+
creditsHint.className = 'dshwv-bubhint'
|
|
3092
|
+
creditsHint.style.cssText = 'margin:0 0 8px;white-space:normal;line-height:1.6;text-align:left'
|
|
3093
|
+
body.appendChild(creditsHint)
|
|
3094
|
+
var debits = apiTextInput('0', '没有请填 0')
|
|
3057
3095
|
debits.inputMode = 'decimal'
|
|
3058
3096
|
debits.autocomplete = 'off'
|
|
3059
|
-
|
|
3060
|
-
var
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3097
|
+
body.appendChild(apiPanelRow('非调用扣减', debits))
|
|
3098
|
+
var debitsHint = document.createElement('div')
|
|
3099
|
+
debitsHint.className = 'dshwv-bubhint'
|
|
3100
|
+
debitsHint.style.cssText = 'margin:0 0 8px;white-space:normal;line-height:1.6;text-align:left'
|
|
3101
|
+
body.appendChild(debitsHint)
|
|
3064
3102
|
var preview = document.createElement('div')
|
|
3065
|
-
preview.
|
|
3103
|
+
preview.className = 'dshwv-bubhint'
|
|
3104
|
+
preview.style.cssText = 'margin:0 0 8px;white-space:normal;line-height:1.6;text-align:left;font-weight:700;color:#203170'
|
|
3066
3105
|
preview.setAttribute('aria-live', 'polite')
|
|
3067
|
-
|
|
3106
|
+
body.appendChild(preview)
|
|
3068
3107
|
var confirmRow = document.createElement('label')
|
|
3069
|
-
confirmRow.style.cssText = 'display:flex;gap:
|
|
3108
|
+
confirmRow.style.cssText = 'display:flex;gap:6px;align-items:flex-start;flex:1;min-width:0;font-size:12px;color:#203170;cursor:pointer;line-height:1.5'
|
|
3070
3109
|
var confirm = document.createElement('input')
|
|
3071
3110
|
confirm.type = 'checkbox'
|
|
3072
|
-
confirm.style.marginTop = '
|
|
3111
|
+
confirm.style.marginTop = '1px'
|
|
3073
3112
|
confirmRow.appendChild(confirm)
|
|
3074
3113
|
confirmRow.appendChild(document.createTextNode('我已核对本统计区间的全部到账和非调用扣减'))
|
|
3075
|
-
|
|
3114
|
+
body.appendChild(apiPanelRow('确认', confirmRow))
|
|
3076
3115
|
var status = document.createElement('div')
|
|
3116
|
+
status.className = 'dshwv-bubhint'
|
|
3077
3117
|
status.setAttribute('role', 'status')
|
|
3078
|
-
status.style.cssText = '
|
|
3118
|
+
status.style.cssText = 'margin:0 0 6px;white-space:normal;line-height:1.6;text-align:left;color:#b33333'
|
|
3079
3119
|
status.textContent = '正在刷新余额…'
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
actions.style.cssText = 'display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end;margin-top:16px'
|
|
3083
|
-
function button(text, callback) {
|
|
3084
|
-
var btn = document.createElement('button')
|
|
3085
|
-
btn.type = 'button'
|
|
3086
|
-
btn.textContent = text
|
|
3087
|
-
btn.className = 'dshwv-bubbtn dshwv-bubbtn-no'
|
|
3088
|
-
btn.style.cssText = 'min-height:38px;padding:8px 12px;cursor:pointer'
|
|
3089
|
-
if (callback) btn.addEventListener('click', callback)
|
|
3090
|
-
actions.appendChild(btn)
|
|
3091
|
-
return btn
|
|
3092
|
-
}
|
|
3120
|
+
body.appendChild(status)
|
|
3121
|
+
card.appendChild(body)
|
|
3093
3122
|
var busy = false
|
|
3094
3123
|
function close() {
|
|
3095
3124
|
if (busy) return
|
|
3096
3125
|
document.removeEventListener('keydown', keyHandler)
|
|
3097
3126
|
mask.remove()
|
|
3098
3127
|
accountingMask = null
|
|
3099
|
-
|
|
3128
|
+
// 关掉校正窗口后回到同一个设置菜单,并把焦点放回「校正」按钮
|
|
3129
|
+
openApiModelMenu(modelId)
|
|
3130
|
+
try {
|
|
3131
|
+
var returnButton = apiModelMaskEl && apiModelMaskEl.querySelector('[data-action="balance-adjustment"]')
|
|
3132
|
+
if (returnButton) returnButton.focus()
|
|
3133
|
+
} catch (err) {}
|
|
3100
3134
|
}
|
|
3101
3135
|
function keyHandler(e) {
|
|
3102
3136
|
if (e.key === 'Escape') { e.preventDefault(); close() }
|
|
@@ -3109,25 +3143,17 @@ function openBalanceAdjustment() {
|
|
|
3109
3143
|
else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus() }
|
|
3110
3144
|
}
|
|
3111
3145
|
}
|
|
3112
|
-
|
|
3113
|
-
var
|
|
3114
|
-
|
|
3115
|
-
var
|
|
3116
|
-
|
|
3117
|
-
save
|
|
3118
|
-
|
|
3119
|
-
|
|
3146
|
+
// 按钮行与其它窗口完全一致:.dshwv-bubbtns + apiBtn 的标准按钮样式(不再自绘尺寸/配色)
|
|
3147
|
+
var actions = document.createElement('div')
|
|
3148
|
+
actions.className = 'dshwv-bubbtns'
|
|
3149
|
+
var reload = apiBtn('重新读取', 'dshwv-bubbtn dshwv-bubbtn-no', function () { load() })
|
|
3150
|
+
var reset = apiBtn('撤销该日校正', 'dshwv-bubbtn dshwv-bubbtn-no', function () { submit('reset') })
|
|
3151
|
+
var save = apiBtn('保存校正', 'dshwv-bubbtn dshwv-bubbtn-ok', function () { submit('save') })
|
|
3152
|
+
actions.appendChild(reload)
|
|
3153
|
+
actions.appendChild(reset)
|
|
3154
|
+
actions.appendChild(apiBtn('取消', 'dshwv-bubbtn dshwv-bubbtn-no', function () { close() }))
|
|
3155
|
+
actions.appendChild(save)
|
|
3120
3156
|
card.appendChild(actions)
|
|
3121
|
-
var scrollBody = document.createElement('div')
|
|
3122
|
-
scrollBody.style.cssText = 'min-height:0;overflow:auto;flex:1 1 auto;padding-right:3px'
|
|
3123
|
-
while (card.firstChild && card.firstChild !== actions) scrollBody.appendChild(card.firstChild)
|
|
3124
|
-
card.insertBefore(scrollBody, actions)
|
|
3125
|
-
card.style.display = 'flex'
|
|
3126
|
-
card.style.flexDirection = 'column'
|
|
3127
|
-
card.style.overflow = 'hidden'
|
|
3128
|
-
actions.style.flex = '0 0 auto'
|
|
3129
|
-
actions.style.paddingTop = '10px'
|
|
3130
|
-
actions.style.borderTop = '1px solid #e2e8f5'
|
|
3131
3157
|
mask.appendChild(card)
|
|
3132
3158
|
mask.addEventListener('click', function (e) { if (e.target === mask) close() })
|
|
3133
3159
|
document.body.appendChild(mask)
|
|
@@ -3141,6 +3167,8 @@ function openBalanceAdjustment() {
|
|
|
3141
3167
|
reset.disabled = !selected || !selected.correctedAt || busy
|
|
3142
3168
|
if (!selected) {
|
|
3143
3169
|
interval.textContent = '尚无可校正的余额观测。请先配置 DeepSeek API key 并成功刷新余额。'
|
|
3170
|
+
creditsHint.textContent = ''
|
|
3171
|
+
debitsHint.textContent = ''
|
|
3144
3172
|
return
|
|
3145
3173
|
}
|
|
3146
3174
|
interval.textContent = '统计起点:' + accountingTime(selected.firstObservedAt) +
|
|
@@ -3148,8 +3176,8 @@ function openBalanceAdjustment() {
|
|
|
3148
3176
|
'\n起点余额 ' + usageMoney(selected.openingBalance, selected.currency) +
|
|
3149
3177
|
' → 当前余额 ' + usageMoney(selected.currentBalance, selected.currency) +
|
|
3150
3178
|
'\n当前:' + selected.label + ' ' + usageMoney(selected.amount, selected.currency)
|
|
3151
|
-
|
|
3152
|
-
|
|
3179
|
+
creditsHint.textContent = '本统计区间累计到账金额(' + selected.currency + ',未到账请填 0):包括充值、赠金等;多次到账请填合计,不要只填最后一笔。'
|
|
3180
|
+
debitsHint.textContent = '非调用造成的余额减少(' + selected.currency + ',没有请填 0):到期赠金、余额退回等。仅填写统计起点之后的金额;保存会替换之前的校正值。'
|
|
3153
3181
|
credits.value = selected.credits == null ? '' : String(selected.credits)
|
|
3154
3182
|
debits.value = selected.otherDebits == null ? '0' : String(selected.otherDebits)
|
|
3155
3183
|
confirm.checked = false
|
|
@@ -3173,7 +3201,7 @@ function openBalanceAdjustment() {
|
|
|
3173
3201
|
reset.disabled = true
|
|
3174
3202
|
reload.disabled = true
|
|
3175
3203
|
status.textContent = '正在刷新余额…'
|
|
3176
|
-
fetch(
|
|
3204
|
+
fetch(adjustmentUrl, { cache: 'no-store' })
|
|
3177
3205
|
.then(function (r) { return r.json() })
|
|
3178
3206
|
.then(function (data) {
|
|
3179
3207
|
if (!data.ok) throw new Error(data.error || '读取失败')
|
|
@@ -3198,16 +3226,29 @@ function openBalanceAdjustment() {
|
|
|
3198
3226
|
reset.disabled = true
|
|
3199
3227
|
reload.disabled = true
|
|
3200
3228
|
status.textContent = '正在保存…'
|
|
3201
|
-
fetch(
|
|
3229
|
+
fetch(adjustmentUrl, {
|
|
3202
3230
|
method: 'PUT', headers: { 'Content-Type': 'application/json' },
|
|
3203
3231
|
body: JSON.stringify({
|
|
3204
|
-
day: selected.day, revision: selected.revision, action: action,
|
|
3232
|
+
modelId: modelId, day: selected.day, revision: selected.revision, action: action,
|
|
3205
3233
|
credits: credits.value.trim(), otherDebits: debits.value.trim() || '0', confirmed: confirm.checked
|
|
3206
3234
|
})
|
|
3207
3235
|
})
|
|
3208
3236
|
.then(function (r) { return r.json() })
|
|
3209
3237
|
.then(function (data) {
|
|
3210
3238
|
if (!data.ok) throw new Error(data.error || '保存失败')
|
|
3239
|
+
// 保存后立刻用返回的摘要刷新该模型条目:关窗回到设置菜单时显示的就是新金额
|
|
3240
|
+
var model = apiModelById(modelId)
|
|
3241
|
+
var currentDay = new Date(Date.now() + 8 * 3600000).toISOString().slice(0, 10)
|
|
3242
|
+
if (apiCanAdjustBalance(model) && data.summary && data.summary.day === currentDay) {
|
|
3243
|
+
model.balance = data.summary.currentBalance
|
|
3244
|
+
model.currency = data.summary.currency
|
|
3245
|
+
model.todayUsage = data.summary.amount
|
|
3246
|
+
model.todayUsageCurrency = data.summary.currency
|
|
3247
|
+
model.usageSource = data.summary.source
|
|
3248
|
+
model.usageLabel = data.summary.label
|
|
3249
|
+
model.accounting = data.summary
|
|
3250
|
+
model.error = null
|
|
3251
|
+
}
|
|
3211
3252
|
busy = false
|
|
3212
3253
|
close()
|
|
3213
3254
|
refresh(true)
|
|
@@ -3235,25 +3276,12 @@ function fillUsagePanel(d) {
|
|
|
3235
3276
|
var today = d.today || {}
|
|
3236
3277
|
var todayModels = today.models || []
|
|
3237
3278
|
var hasEvToday = todayModels.length > 0
|
|
3238
|
-
// ①
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
coverage.style.cssText = 'line-height:1.65;white-space:normal;margin:4px 0'
|
|
3245
|
-
coverage.textContent = today.firstObservedAt
|
|
3246
|
-
? '统计起点(北京):' + accountingTime(today.firstObservedAt) + '。起点前的消费未计入。'
|
|
3247
|
-
: '尚无余额观测;此处仅显示本机会话估算或旧版记录。'
|
|
3248
|
-
if (today.needsReview) coverage.textContent += ' 检测到余额增加,请核对本区间累计到账。'
|
|
3249
|
-
wrap.appendChild(coverage)
|
|
3250
|
-
var adjustButton = document.createElement('button')
|
|
3251
|
-
adjustButton.type = 'button'
|
|
3252
|
-
adjustButton.className = 'dshwv-usage-more'
|
|
3253
|
-
adjustButton.textContent = '充值 / 余额校正'
|
|
3254
|
-
adjustButton.addEventListener('click', function (e) { e.stopPropagation(); openBalanceAdjustment() })
|
|
3255
|
-
wrap.appendChild(adjustButton)
|
|
3256
|
-
var modelTitle = uSectionTitle('本机模型费用 · 估算', usageMoney(today.modelTotal, 'CNY'))
|
|
3279
|
+
// ① 本机模型费用(所有模型的本地会话估算)
|
|
3280
|
+
// 「已观测消费」是 DeepSeek 账户口径(余额观测),已移到 小鲸鱼记账 → DeepSeek(内置)→ 设置 里,
|
|
3281
|
+
// 与「余额校正」放在一起,避免在这里被误读成"全模型合计"。
|
|
3282
|
+
var modelTitle = uSectionTitle('本机模型费用', usageMoney(today.modelTotal, 'CNY'))
|
|
3283
|
+
// 标题与下方模型列表之间不再画分隔线(列表本身有边框,够了)
|
|
3284
|
+
modelTitle.style.borderBottom = 'none'
|
|
3257
3285
|
modelTitle.title = '按本机 DSH 会话计算,不按账户余额比例缩放;两者覆盖范围不同。'
|
|
3258
3286
|
wrap.appendChild(modelTitle)
|
|
3259
3287
|
var todayBox = document.createElement('div')
|
|
@@ -8232,7 +8260,7 @@ var bubbleColorOpenMenu = null // 当前展开的颜色下拉(纯色/跑马灯,
|
|
|
8232
8260
|
// —— 弹层/菜单层级统一助手(所有下拉与弹窗据此取高于当前可见层,避免互相压制) ——
|
|
8233
8261
|
function visibleTopZ() {
|
|
8234
8262
|
var top = 20500
|
|
8235
|
-
var cand = [bubbleMask, bubbleItemMask, moduleMask, usageMoreMask, qeditEl, window.__dshwRemindMask]
|
|
8263
|
+
var cand = [bubbleMask, bubbleItemMask, moduleMask, usageMoreMask, qeditEl, window.__dshwRemindMask, apiModelMaskEl, accountingMask]
|
|
8236
8264
|
function eff(el) {
|
|
8237
8265
|
try {
|
|
8238
8266
|
if (!el) return 0
|
package/lib/index.js
CHANGED
|
@@ -75,6 +75,11 @@ const API_FILE_CANDIDATES = [
|
|
|
75
75
|
path.join(DSH_HOME, 'profiles', 'web', '.dshw-api.json'),
|
|
76
76
|
]
|
|
77
77
|
const API_BUILTIN_ID = 'deepseek'
|
|
78
|
+
// 「充值 / 余额校正」只属于固定的 DeepSeek(内置):模型条目下发 canAdjustBalance,路由层再校验一次。
|
|
79
|
+
// 新增厂商模板 / 手动新增的同名模型 / Kimi 等其它厂商都不会继承这个能力。
|
|
80
|
+
function canAdjustBuiltinBalance(model) {
|
|
81
|
+
return !!(model && model.id === API_BUILTIN_ID && model.builtin === true && model.provider === 'deepseek')
|
|
82
|
+
}
|
|
78
83
|
// 内置厂商模板:balance 描述「怎么取余额」。json 路径支持 a.b[0].c;scale 为取值后的乘数。
|
|
79
84
|
// 余额接口已按官方文档 / 社区参考核对(DeepSeek、OpenRouter、Kimi、阶跃、Novita、OpenAI 兼容中转站);
|
|
80
85
|
// 硅基流动的余额接口已官方下线(410),故改为「无余额接口 + /v1/models 探活」;
|
|
@@ -916,7 +921,7 @@ export default {
|
|
|
916
921
|
const days = new Set([today, ...Object.keys(led.history || {}), ...accountingDays(led)])
|
|
917
922
|
for (const e of events) if (/^\d{4}-\d{2}-\d{2}$/.test(e.day)) days.add(e.day)
|
|
918
923
|
return {
|
|
919
|
-
ok: true, version: '0.3.
|
|
924
|
+
ok: true, version: '0.3.2', today: todayData, days7,
|
|
920
925
|
total7: total7ByCurrency[todayData.currency] || 0, total7Currency: todayData.currency, total7ByCurrency,
|
|
921
926
|
all: {
|
|
922
927
|
days: Array.from(days).filter(d => /^\d{4}-\d{2}-\d{2}$/.test(d)).sort().reverse().map(forDay),
|
|
@@ -1824,6 +1829,7 @@ export default {
|
|
|
1824
1829
|
let entry = {
|
|
1825
1830
|
id: m.id, name: m.name, provider: m.provider, currency: m.currency,
|
|
1826
1831
|
keyRef: m.keyRef, builtin: !!m.builtin, baseUrl: m.baseUrl || '',
|
|
1832
|
+
canAdjustBalance: canAdjustBuiltinBalance(m),
|
|
1827
1833
|
matchIds: m.matchIds || [], settings: settings.models && settings.models[m.id] ? settings.models[m.id] : null,
|
|
1828
1834
|
price: m.price || null,
|
|
1829
1835
|
// 手动额度(订阅/资源包):总量、单位、已用、重置周期;前端据此显示额度模块
|
|
@@ -1959,7 +1965,7 @@ export default {
|
|
|
1959
1965
|
const led = readUsageLedger()
|
|
1960
1966
|
const summary = daySummary(led, todayKey())
|
|
1961
1967
|
return {
|
|
1962
|
-
...visible, version: '0.3.
|
|
1968
|
+
...visible, version: '0.3.2', isPeak: isPeakTime(Math.floor(Date.now() / 1000)),
|
|
1963
1969
|
todayUsage: summary.amount, todayUsageCurrency: summary.currency,
|
|
1964
1970
|
usageSource: summary.source, usageLabel: summary.label, usageMode: 'ledger',
|
|
1965
1971
|
accounting: balanceSummary(led, todayKey()),
|
|
@@ -2498,9 +2504,23 @@ export default {
|
|
|
2498
2504
|
path: '/dsh-whale/balance-adjustments.json',
|
|
2499
2505
|
handler: async (req, res) => {
|
|
2500
2506
|
try {
|
|
2507
|
+
// 先核对身份再碰余额或账本:必须是唯一且明确的 DeepSeek(内置)。
|
|
2508
|
+
// 新增厂商模板不会自动继承这个能力,也拒绝其它模型读写校正。
|
|
2509
|
+
const targetIds = new URL(req.url || '/', 'http://localhost').searchParams.getAll('modelId')
|
|
2510
|
+
const targetModelId = targetIds.length === 1 ? targetIds[0] : null
|
|
2511
|
+
if (targetModelId !== API_BUILTIN_ID || !canAdjustBuiltinBalance(apiModelById(targetModelId))) {
|
|
2512
|
+
const error = new Error('余额校正仅支持 DeepSeek(内置),请从该模型的设置菜单进入')
|
|
2513
|
+
error.status = 403
|
|
2514
|
+
throw error
|
|
2515
|
+
}
|
|
2501
2516
|
if (req.method === 'PUT') {
|
|
2502
2517
|
const input = JSON.parse(await readBodyMax(req, 8192))
|
|
2503
2518
|
if (!input || typeof input !== 'object') throw new Error('校正内容无效')
|
|
2519
|
+
if (input.modelId !== targetModelId) {
|
|
2520
|
+
const error = new Error('校正请求的模型不匹配,仅支持 DeepSeek(内置)')
|
|
2521
|
+
error.status = 403
|
|
2522
|
+
throw error
|
|
2523
|
+
}
|
|
2504
2524
|
if (input.day === todayKey()) {
|
|
2505
2525
|
const fresh = await getBalance(true)
|
|
2506
2526
|
if (!fresh.ok || fresh.stale) {
|
package/package.json
CHANGED