dsh-whale-widget 0.2.5 → 0.2.6
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 +21 -0
- package/README.md +4 -0
- package/lib/index.js +93 -20
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MeteorNOX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
package/lib/index.js
CHANGED
|
@@ -336,6 +336,8 @@ gifEl.src = GIF_URL
|
|
|
336
336
|
gifEl.alt = ''
|
|
337
337
|
gifEl.draggable = false
|
|
338
338
|
bubbleBox.appendChild(gifEl)
|
|
339
|
+
var gifFailed = false
|
|
340
|
+
gifEl.onerror = function () { gifFailed = true }
|
|
339
341
|
bubbleBox.appendChild(textBox)
|
|
340
342
|
bubbleBox.addEventListener('click', function (e) {
|
|
341
343
|
e.stopPropagation()
|
|
@@ -436,13 +438,18 @@ function pickRandomLines() {
|
|
|
436
438
|
function applyBubbleLines(lines) {
|
|
437
439
|
if (lines && lines.gif) {
|
|
438
440
|
// gif 台词组:只显示 gif,隐藏三行文字(display 必须显式覆盖 CSS 的 none)
|
|
439
|
-
if (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
441
|
+
if (gifFailed) {
|
|
442
|
+
// gif 加载失败/路由缺失:降级为文字台词,避免空白白色气泡
|
|
443
|
+
lines = singleCenter('A', pickOne(['gif 加载失败了...', '今天没有动图给你看~', '呜呜 动图不见了...']), '', true)
|
|
444
|
+
} else {
|
|
445
|
+
if (gifFadeTimer) { clearTimeout(gifFadeTimer); gifFadeTimer = null }
|
|
446
|
+
gifEl.style.display = 'block'
|
|
447
|
+
gifEl.style.opacity = ''
|
|
448
|
+
labelEl.style.display = 'none'
|
|
449
|
+
amountEl.style.display = 'none'
|
|
450
|
+
hintEl.style.display = 'none'
|
|
451
|
+
return
|
|
452
|
+
}
|
|
446
453
|
}
|
|
447
454
|
if (gifFadeTimer) { clearTimeout(gifFadeTimer); gifFadeTimer = null }
|
|
448
455
|
gifEl.style.display = 'none'
|
|
@@ -605,6 +612,11 @@ function viewport() {
|
|
|
605
612
|
h: window.innerHeight || document.documentElement.clientHeight || 800
|
|
606
613
|
}
|
|
607
614
|
}
|
|
615
|
+
function rightGap() {
|
|
616
|
+
var sw = window.innerWidth - document.documentElement.clientWidth
|
|
617
|
+
if (sw < 8) sw = 10 // 悬浮滚动条测量为0时兜底
|
|
618
|
+
return sw + 2 // 滚动条宽度 + 2px
|
|
619
|
+
}
|
|
608
620
|
function fmt(balance, currency) {
|
|
609
621
|
var num = Number(balance)
|
|
610
622
|
var fixed = isFinite(num) ? num.toFixed(2) : '--'
|
|
@@ -671,17 +683,17 @@ function settle() {
|
|
|
671
683
|
var h = root.offsetHeight || root.getBoundingClientRect().height || 0
|
|
672
684
|
if (drag && drag.active) {
|
|
673
685
|
// mid-drag resize: keep the pointer-follow position, just clamp into view
|
|
674
|
-
state.left = clamp(state.left, 0, Math.max(0, vp.w - w))
|
|
686
|
+
state.left = clamp(state.left, 0, Math.max(0, vp.w - w - rightGap()))
|
|
675
687
|
state.top = clamp(state.top, 0, Math.max(0, vp.h - h))
|
|
676
688
|
express()
|
|
677
689
|
return
|
|
678
690
|
}
|
|
679
691
|
if (state.h === 'right') {
|
|
680
|
-
state.left = Math.max(0, vp.w - w - state.hOff)
|
|
692
|
+
state.left = Math.max(0, vp.w - w - state.hOff - rightGap())
|
|
681
693
|
} else if (state.h === 'left') {
|
|
682
694
|
state.left = state.hOff
|
|
683
695
|
} else {
|
|
684
|
-
state.left = clamp(state.left, 0, Math.max(0, vp.w - w))
|
|
696
|
+
state.left = clamp(state.left, 0, Math.max(0, vp.w - w - rightGap()))
|
|
685
697
|
}
|
|
686
698
|
if (state.v === 'bottom') {
|
|
687
699
|
state.top = Math.max(0, vp.h - h - state.vOff)
|
|
@@ -765,11 +777,23 @@ var peakMode = 'default'
|
|
|
765
777
|
var bubbleOn = true
|
|
766
778
|
var turnCostOn = true
|
|
767
779
|
var turnCostCloseMs = 5000
|
|
768
|
-
var lastCostSeq = 0
|
|
769
780
|
var costBubbleActive = false
|
|
770
781
|
function saveConfig() {
|
|
771
782
|
try {
|
|
772
783
|
fetch(SIZE_URL, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scale: state.scale, sound: soundOn, vol: soundVol, soundSet: soundSet, usageMode: usageMode, peakMode: peakMode, bubbleOn: bubbleOn, turnCostOn: turnCostOn, turnCostCloseMs: turnCostCloseMs }) })
|
|
784
|
+
var vp = viewport()
|
|
785
|
+
var w = root.offsetWidth || root.getBoundingClientRect().width || 0
|
|
786
|
+
var h = root.offsetHeight || root.getBoundingClientRect().height || 0
|
|
787
|
+
var leftDist = state.left
|
|
788
|
+
var rightDist = vp.w - state.left - w
|
|
789
|
+
var topDist = state.top
|
|
790
|
+
var bottomDist = vp.h - state.top - h
|
|
791
|
+
localStorage.setItem('dshw-pos', JSON.stringify({
|
|
792
|
+
hAnchor: leftDist <= rightDist ? 'left' : 'right',
|
|
793
|
+
hDist: Math.round(Math.min(leftDist, rightDist)),
|
|
794
|
+
vAnchor: topDist <= bottomDist ? 'top' : 'bottom',
|
|
795
|
+
vDist: Math.round(Math.min(topDist, bottomDist))
|
|
796
|
+
}))
|
|
773
797
|
} catch (err) {}
|
|
774
798
|
}
|
|
775
799
|
function setUsageMode(v) {
|
|
@@ -787,7 +811,8 @@ function setBubbleOn(v) {
|
|
|
787
811
|
bubbleOn = !!v
|
|
788
812
|
bubbleToggle.checked = bubbleOn
|
|
789
813
|
saveConfig()
|
|
790
|
-
|
|
814
|
+
// 必须走 hideCostBubble:残留的 costBubbleActive 会让 render()/showBubble() 永久早退
|
|
815
|
+
if (!bubbleOn) hideCostBubble()
|
|
791
816
|
}
|
|
792
817
|
function setTurnCostOn(v) {
|
|
793
818
|
turnCostOn = !!v
|
|
@@ -955,7 +980,7 @@ function snapCheck() {
|
|
|
955
980
|
} else if (centerX > vp.w * 3 / 4) {
|
|
956
981
|
state.h = 'right'
|
|
957
982
|
state.hOff = 0
|
|
958
|
-
left = vp.w - w
|
|
983
|
+
left = vp.w - w - rightGap()
|
|
959
984
|
moved = true
|
|
960
985
|
} else {
|
|
961
986
|
state.h = null
|
|
@@ -1009,7 +1034,9 @@ function setupHitTest() {
|
|
|
1009
1034
|
var probe = new Image()
|
|
1010
1035
|
probe.onload = function () {
|
|
1011
1036
|
try {
|
|
1012
|
-
|
|
1037
|
+
// 拉伸到 610×610 与 isWhaleHit 的坐标映射对齐;不指定尺寸会按原图大小绘制,
|
|
1038
|
+
// 回退到非 610×610 素材(如 DSniang02.png)时命中区域会错位
|
|
1039
|
+
hitCanvas.getContext('2d').drawImage(probe, 0, 0, 610, 610)
|
|
1013
1040
|
hitReady = true
|
|
1014
1041
|
} catch (err) {}
|
|
1015
1042
|
}
|
|
@@ -1136,7 +1163,28 @@ function endDrag(e, clickAllowed) {
|
|
|
1136
1163
|
state.top = top
|
|
1137
1164
|
settle()
|
|
1138
1165
|
}
|
|
1166
|
+
// 窗口尺寸变化时:自由位置的鲸鱼按相对边框锚点重算(保持离边距离,窗口恢复原状即回原位);
|
|
1167
|
+
// 贴边吸附的鲸鱼走 settle()(保持贴边)
|
|
1168
|
+
function applyAnchorPos() {
|
|
1169
|
+
try {
|
|
1170
|
+
var a = JSON.parse(localStorage.getItem('dshw-pos') || 'null')
|
|
1171
|
+
if (!a || (a.hAnchor !== 'left' && a.hAnchor !== 'right') || typeof a.hDist !== 'number' ||
|
|
1172
|
+
(a.vAnchor !== 'top' && a.vAnchor !== 'bottom') || typeof a.vDist !== 'number') return false
|
|
1173
|
+
var vp = viewport()
|
|
1174
|
+
var w = root.offsetWidth || root.getBoundingClientRect().width || 0
|
|
1175
|
+
var h = root.offsetHeight || root.getBoundingClientRect().height || 0
|
|
1176
|
+
var l = a.hAnchor === 'left' ? a.hDist : vp.w - a.hDist - w
|
|
1177
|
+
var t = a.vAnchor === 'top' ? a.vDist : vp.h - a.vDist - h
|
|
1178
|
+
state.left = clamp(l, 0, Math.max(0, vp.w - w))
|
|
1179
|
+
state.top = clamp(t, 0, Math.max(0, vp.h - h))
|
|
1180
|
+
state.h = null
|
|
1181
|
+
state.v = null
|
|
1182
|
+
express()
|
|
1183
|
+
return true
|
|
1184
|
+
} catch (err) { return false }
|
|
1185
|
+
}
|
|
1139
1186
|
window.addEventListener('resize', function () {
|
|
1187
|
+
if (state.h === null && state.v === null && applyAnchorPos()) return
|
|
1140
1188
|
settle()
|
|
1141
1189
|
})
|
|
1142
1190
|
|
|
@@ -1192,6 +1240,23 @@ fetch(SIZE_URL, { cache: 'no-store' })
|
|
|
1192
1240
|
turnCostCloseMs = d.turnCostCloseMs > 0 ? d.turnCostCloseMs : 0
|
|
1193
1241
|
turnCostCloseInput.value = String(Math.round(turnCostCloseMs / 1000))
|
|
1194
1242
|
}
|
|
1243
|
+
// 相对边框恢复(localStorage 锚点):窗口变化后保持离边距离
|
|
1244
|
+
try {
|
|
1245
|
+
var a = JSON.parse(localStorage.getItem('dshw-pos') || 'null')
|
|
1246
|
+
if (a && (a.hAnchor === 'left' || a.hAnchor === 'right') && typeof a.hDist === 'number' &&
|
|
1247
|
+
(a.vAnchor === 'top' || a.vAnchor === 'bottom') && typeof a.vDist === 'number') {
|
|
1248
|
+
var vpA = viewport()
|
|
1249
|
+
var wA = root.offsetWidth || root.getBoundingClientRect().width || 0
|
|
1250
|
+
var hA = root.offsetHeight || root.getBoundingClientRect().height || 0
|
|
1251
|
+
var lA = a.hAnchor === 'left' ? a.hDist : vpA.w - a.hDist - wA
|
|
1252
|
+
var tA = a.vAnchor === 'top' ? a.vDist : vpA.h - a.vDist - hA
|
|
1253
|
+
state.left = clamp(lA, 0, Math.max(0, vpA.w - wA))
|
|
1254
|
+
state.top = clamp(tA, 0, Math.max(0, vpA.h - hA))
|
|
1255
|
+
state.h = null // 恢复为自由位置,避免 settle() 重新吸附覆盖
|
|
1256
|
+
state.v = null
|
|
1257
|
+
settle()
|
|
1258
|
+
}
|
|
1259
|
+
} catch (err) {}
|
|
1195
1260
|
refresh(false)
|
|
1196
1261
|
})
|
|
1197
1262
|
.catch(function () { refresh(false) })
|
|
@@ -1287,10 +1352,6 @@ function apply(ctx) {
|
|
|
1287
1352
|
|
|
1288
1353
|
// 监听所有会话的追加事件;turn/end 时结算本轮
|
|
1289
1354
|
disposers.push(ctx.on('session/event', (session, event) => {
|
|
1290
|
-
// [debug] 确认事件是否到达(下个版本移除)
|
|
1291
|
-
if (event && (event.type === 'turn/end' || event.type === 'assistant/message')) {
|
|
1292
|
-
try { console.error('[whale-balance:event]', event.type, 'turn=' + (event.data && event.data.turn), 'usage=' + (event.data && event.data.usage ? 'yes' : 'no')) } catch (err) {}
|
|
1293
|
-
}
|
|
1294
1355
|
handleSessionEvent(event)
|
|
1295
1356
|
}))
|
|
1296
1357
|
|
|
@@ -1322,6 +1383,17 @@ function apply(ctx) {
|
|
|
1322
1383
|
throw new Error('whale image not found')
|
|
1323
1384
|
}
|
|
1324
1385
|
|
|
1386
|
+
function pickBalanceInfo(infos) {
|
|
1387
|
+
if (!Array.isArray(infos) || infos.length === 0) return null
|
|
1388
|
+
const num = (x) => (x && x.total_balance !== undefined ? Number(x.total_balance) : NaN)
|
|
1389
|
+
return (
|
|
1390
|
+
infos.find((x) => x && x.currency === 'CNY' && num(x) > 0) ||
|
|
1391
|
+
infos.find((x) => num(x) > 0) ||
|
|
1392
|
+
infos.find((x) => x && x.currency === 'CNY') ||
|
|
1393
|
+
infos[0]
|
|
1394
|
+
)
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1325
1397
|
async function fetchBalance() {
|
|
1326
1398
|
let cred
|
|
1327
1399
|
try {
|
|
@@ -1357,7 +1429,7 @@ function apply(ctx) {
|
|
|
1357
1429
|
} catch (err) {
|
|
1358
1430
|
return { ok: false, code: 'PARSE', error: '余额接口返回不是合法 JSON' }
|
|
1359
1431
|
}
|
|
1360
|
-
const info = data &&
|
|
1432
|
+
const info = pickBalanceInfo(data && data.balance_infos)
|
|
1361
1433
|
if (!info || info.total_balance === undefined) {
|
|
1362
1434
|
return { ok: false, code: 'SHAPE', error: '余额接口返回结构异常' }
|
|
1363
1435
|
}
|
|
@@ -1580,7 +1652,8 @@ function apply(ctx) {
|
|
|
1580
1652
|
const pm = peakMode === 'liangwen' || peakMode === 'qiangqiang' ? peakMode : 'default'
|
|
1581
1653
|
const bo = bubbleOn !== false
|
|
1582
1654
|
const tco = turnCostOn !== false
|
|
1583
|
-
|
|
1655
|
+
// 0 是合法值(不自动关闭),只有缺失/非法时才回落默认 5 秒
|
|
1656
|
+
const tcc = typeof turnCostCloseMs === 'number' && turnCostCloseMs >= 0 ? turnCostCloseMs : 5000
|
|
1584
1657
|
const body = JSON.stringify({
|
|
1585
1658
|
scale: scale,
|
|
1586
1659
|
sound: sound !== false,
|