dsh-pet-live2d-viola 1.1.3 → 1.2.0
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 +15 -3
- package/assets/menu/balance.png +0 -0
- package/lib/client.js +63 -28
- package/lib/index.js +38 -8
- package/lib/persist.js +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,6 +102,18 @@ DeepSeek Harness Web GUI 的 **Live2D 桌宠插件**,一台桌宠里可以切
|
|
|
102
102
|
|
|
103
103
|
## 余额警告
|
|
104
104
|
|
|
105
|
+
**右键 → 设置 → 余额警告**,二级界面:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
← 返回 余额警告
|
|
109
|
+
余额 ¥4.50
|
|
110
|
+
──────────────────
|
|
111
|
+
✓ 余额警告语音
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- 第一行是**当前余额**(只读),读不到时显示「读不到(未登录或账户不可用)」,不会显示成 ¥0.00
|
|
115
|
+
- 第二行是**语音开关**:关掉后她不再播提醒,但余额照样读取、照样显示
|
|
116
|
+
|
|
105
117
|
账户余额降到 **5、4、3、2、1 元**时,她会提醒你一次 —— **每跨过一档提醒一次**,同一档位不会重复。
|
|
106
118
|
|
|
107
119
|
提醒内容是**一段完整反应**:气泡 + 动作 + 表情,播完恢复原来的表情。每个角色有三种,**随机播放一种**:
|
|
@@ -117,8 +129,8 @@ DeepSeek Harness Web GUI 的 **Live2D 桌宠插件**,一台桌宠里可以切
|
|
|
117
129
|
- **每 60 秒**读一次账户余额
|
|
118
130
|
- 桌宠启动时余额已经很低 → **不提醒**,因为那不是「刚刚发生的变化」
|
|
119
131
|
- 余额回到 5 元以上再降下来 → 会**重新提醒**
|
|
120
|
-
-
|
|
121
|
-
-
|
|
132
|
+
- 关闭语音后,余额**仍会读取并显示**,只是不再提醒
|
|
133
|
+
- 未登录、或读不到余额时**完全静默**:不会把「读不到」当成 0 元
|
|
122
134
|
|
|
123
135
|
## 功能简介
|
|
124
136
|
|
|
@@ -133,7 +145,7 @@ DeepSeek Harness Web GUI 的 **Live2D 桌宠插件**,一台桌宠里可以切
|
|
|
133
145
|
| 待机动作数 | 本角色的待机池 |
|
|
134
146
|
| 语音条数 | 点击 + 阶段反应 + 待机 + 下线 |
|
|
135
147
|
| 随机模式 | 说明 + **当前是否开启及所选范围** |
|
|
136
|
-
| 余额警告 |
|
|
148
|
+
| 余额警告 | 一句话说明 + **指向「设置 → 余额警告」**(余额和开关都在那里) |
|
|
137
149
|
|
|
138
150
|
## 取景
|
|
139
151
|
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -1117,7 +1117,9 @@ window.__ModuleLoader__.load({
|
|
|
1117
1117
|
/* The three settings toggles: the framing boundary, eye blink, and gaze tracking. */
|
|
1118
1118
|
boundary: "boundary.png",
|
|
1119
1119
|
blink: "blink.png",
|
|
1120
|
-
gaze: "gaze.png"
|
|
1120
|
+
gaze: "gaze.png",
|
|
1121
|
+
// A coin, for the 余额警告 row.
|
|
1122
|
+
balance: "balance.png"
|
|
1121
1123
|
};
|
|
1122
1124
|
|
|
1123
1125
|
/**
|
|
@@ -1914,6 +1916,11 @@ window.__ModuleLoader__.load({
|
|
|
1914
1916
|
"pet.menu.gaze": "视线追随",
|
|
1915
1917
|
"pet.menu.randomMode": "随机模式",
|
|
1916
1918
|
"pet.menu.title.random": "随机模式",
|
|
1919
|
+
"pet.menu.balance": "余额警告",
|
|
1920
|
+
"pet.menu.title.balance": "余额警告",
|
|
1921
|
+
"pet.menu.balanceAmount": "余额",
|
|
1922
|
+
"pet.menu.balanceVoice": "余额警告语音",
|
|
1923
|
+
"pet.menu.balanceUnavailable": "读不到(未登录或账户不可用)",
|
|
1917
1924
|
"pet.menu.randomModeOn": "开启随机模式",
|
|
1918
1925
|
"pet.menu.randomScope": "随机内容",
|
|
1919
1926
|
"pet.menu.randomMotion": "随机动作",
|
|
@@ -1937,11 +1944,7 @@ window.__ModuleLoader__.load({
|
|
|
1937
1944
|
"pet.about.random.text": "在「设置 → 随机模式」里开启。开启后待机时每 12 秒按所选范围重新随机,并且不出声;可选只为动作、只为表情,或两者都随机。",
|
|
1938
1945
|
"pet.about.random.now": "当前:已开启({scope})",
|
|
1939
1946
|
"pet.about.balance": "余额警告",
|
|
1940
|
-
"pet.about.balance.text": "余额降到 5
|
|
1941
|
-
"pet.about.balance.on": "当前:已开启",
|
|
1942
|
-
"pet.about.balance.off": "当前:已关闭",
|
|
1943
|
-
"pet.about.balance.value": ",余额 ¥{value}",
|
|
1944
|
-
"pet.about.balance.unknown": "(读不到账户余额时不会提醒)",
|
|
1947
|
+
"pet.about.balance.text": "余额降到 5 元以下时提醒你,每跨过一元一次。开关和余额都在「设置 → 余额警告」里。",
|
|
1945
1948
|
"pet.about.how": "操作",
|
|
1946
1949
|
"pet.about.how.text": "左键拖动移动、滚轮缩放、点击说话、右键打开菜单。",
|
|
1947
1950
|
"pet.menu.submenuHint": "点击进入",
|
|
@@ -2072,6 +2075,11 @@ window.__ModuleLoader__.load({
|
|
|
2072
2075
|
"pet.menu.gaze": "Eyes follow",
|
|
2073
2076
|
"pet.menu.randomMode": "Random mode",
|
|
2074
2077
|
"pet.menu.title.random": "Random mode",
|
|
2078
|
+
"pet.menu.balance": "Balance warning",
|
|
2079
|
+
"pet.menu.title.balance": "Balance warning",
|
|
2080
|
+
"pet.menu.balanceAmount": "Balance",
|
|
2081
|
+
"pet.menu.balanceVoice": "Balance warning voice",
|
|
2082
|
+
"pet.menu.balanceUnavailable": "Unavailable (signed out or no account service)",
|
|
2075
2083
|
"pet.menu.randomModeOn": "Enable random mode",
|
|
2076
2084
|
"pet.menu.randomScope": "What to randomise",
|
|
2077
2085
|
"pet.menu.randomMotion": "Random motions",
|
|
@@ -2095,11 +2103,7 @@ window.__ModuleLoader__.load({
|
|
|
2095
2103
|
"pet.about.random.text": "Turn it on under Settings → Random mode. While it is on, the idle pose is redrawn every 12 seconds according to the scope you pick, and she stays silent; the scope can be motions only, expressions only, or both.",
|
|
2096
2104
|
"pet.about.random.now": "Currently on ({scope})",
|
|
2097
2105
|
"pet.about.balance": "Balance warning",
|
|
2098
|
-
"pet.about.balance.text": "Warns you as the balance drops
|
|
2099
|
-
"pet.about.balance.on": "Currently on",
|
|
2100
|
-
"pet.about.balance.off": "Currently off",
|
|
2101
|
-
"pet.about.balance.value": ", balance ¥{value}",
|
|
2102
|
-
"pet.about.balance.unknown": " (no warning while the balance cannot be read)",
|
|
2106
|
+
"pet.about.balance.text": "Warns you as the balance drops below 5 yuan, once per yuan crossed. The balance and the switch are in Settings → Balance warning.",
|
|
2103
2107
|
"pet.about.how": "Controls",
|
|
2104
2108
|
"pet.about.how.text": "Drag to move, scroll to zoom, click to talk, right-click for the menu.",
|
|
2105
2109
|
"pet.menu.submenuHint": "open",
|
|
@@ -5247,26 +5251,14 @@ window.__ModuleLoader__.load({
|
|
|
5247
5251
|
});
|
|
5248
5252
|
|
|
5249
5253
|
/*
|
|
5250
|
-
* Balance warnings
|
|
5251
|
-
* reporting what the character has, and it is the one feature tied to something OUTSIDE the pet.
|
|
5254
|
+
* Balance warnings — ONE line, because the numbers and the switch live in 设置 → 余额警告.
|
|
5252
5255
|
*
|
|
5253
|
-
*
|
|
5254
|
-
*
|
|
5255
|
-
*
|
|
5256
|
-
* unreadable account would look like an empty wallet.
|
|
5256
|
+
* This panel describes what the character IS; a balance readout and a toggle belong where the rest of the pet's
|
|
5257
|
+
* switches are. Mentioning the location is the whole job here, so the row says the behaviour and points at the
|
|
5258
|
+
* panel rather than repeating its contents.
|
|
5257
5259
|
*/
|
|
5258
|
-
const balanceInfo = snapshot?.balance;
|
|
5259
5260
|
const scenes = (BALANCE_WARNING_REACTIONS[CHARACTER_GROUPS[id] ?? id] ?? []).length;
|
|
5260
|
-
if (scenes > 0) {
|
|
5261
|
-
const on = balanceInfo?.enabled !== false;
|
|
5262
|
-
const value = typeof balanceInfo?.value === "number" ? balanceInfo.value : undefined;
|
|
5263
|
-
const state = on ? t("pet.about.balance.on") : t("pet.about.balance.off");
|
|
5264
|
-
const shown = value === undefined ? t("pet.about.balance.unknown") : t("pet.about.balance.value", { value: value.toFixed(2) });
|
|
5265
|
-
rows.push({
|
|
5266
|
-
head: t("pet.about.balance"),
|
|
5267
|
-
text: `${t("pet.about.balance.text", { count: scenes })}${state}${shown}`
|
|
5268
|
-
});
|
|
5269
|
-
}
|
|
5261
|
+
if (scenes > 0) rows.push({ head: t("pet.about.balance"), text: t("pet.about.balance.text") });
|
|
5270
5262
|
|
|
5271
5263
|
// Interactions worth knowing about, which are the same for every character.
|
|
5272
5264
|
rows.push({ head: t("pet.about.how"), text: t("pet.about.how.text") });
|
|
@@ -7599,6 +7591,42 @@ window.__ModuleLoader__.load({
|
|
|
7599
7591
|
// as well as mapping the identifier to readable copy.
|
|
7600
7592
|
item(expressionLabel(expression), () => this.stage?.playExpression(expression, { user: true }));
|
|
7601
7593
|
}
|
|
7594
|
+
} else if (view === "balance") {
|
|
7595
|
+
header(t("pet.menu.title.balance"));
|
|
7596
|
+
/*
|
|
7597
|
+
* The balance itself: a read-only line, not a control.
|
|
7598
|
+
*
|
|
7599
|
+
* `value` is null whenever the account is not readable — signed out, no account service, or a failed query.
|
|
7600
|
+
* That is shown as a sentence rather than as ¥0.00, because a zero would read as an empty wallet.
|
|
7601
|
+
*/
|
|
7602
|
+
const balanceValue = this.snapshot?.balance?.value;
|
|
7603
|
+
const readout = document.createElement("div");
|
|
7604
|
+
readout.className = "dsh-l2d-menu-text";
|
|
7605
|
+
readout.textContent = `${t("pet.menu.balanceAmount")} ${
|
|
7606
|
+
typeof balanceValue === "number" ? `\u00a5${balanceValue.toFixed(2)}` : t("pet.menu.balanceUnavailable")
|
|
7607
|
+
}`;
|
|
7608
|
+
menu.appendChild(readout);
|
|
7609
|
+
/*
|
|
7610
|
+
* The voice switch. `balanceWarnings` in the layout record is the single source, so the host's poller stops
|
|
7611
|
+
* reading once it is off; the balance above stays visible either way.
|
|
7612
|
+
*/
|
|
7613
|
+
const voiceOn = this.snapshot?.balance?.enabled !== false;
|
|
7614
|
+
item(`${voiceOn ? "\u2713 " : "\u3000"}${t("pet.menu.balanceVoice")}`, () => {
|
|
7615
|
+
API.layout({ balanceWarnings: !voiceOn }).then((result) => {
|
|
7616
|
+
if (this.destroyed || !result.ok) return;
|
|
7617
|
+
/*
|
|
7618
|
+
* Both copies are updated: `layout` is what the host persisted, and `balance.enabled` is what the
|
|
7619
|
+
* tick and the warning path read. The next poll would carry it too, but the tick would then lag a
|
|
7620
|
+
* poll behind the click — which reads as the switch not working.
|
|
7621
|
+
*/
|
|
7622
|
+
this.snapshot = {
|
|
7623
|
+
...this.snapshot,
|
|
7624
|
+
layout: result.layout,
|
|
7625
|
+
balance: { ...(this.snapshot?.balance ?? {}), enabled: !voiceOn }
|
|
7626
|
+
};
|
|
7627
|
+
this.refreshMenu("balance");
|
|
7628
|
+
}, () => {});
|
|
7629
|
+
});
|
|
7602
7630
|
} else if (view === "random") {
|
|
7603
7631
|
header(t("pet.menu.title.random"));
|
|
7604
7632
|
/** Write one layout key and re-render, so the ticks track the state. */
|
|
@@ -7830,6 +7858,13 @@ window.__ModuleLoader__.load({
|
|
|
7830
7858
|
* what it randomises. A single checkbox could only express the first.
|
|
7831
7859
|
*/
|
|
7832
7860
|
submenu(`${this.snapshot.layout?.randomModeEnabled === true ? "\u2713 " : "\u3000"}${t("pet.menu.randomMode")}`, "random", menuIcon("random"));
|
|
7861
|
+
/*
|
|
7862
|
+
* The balance panel: the current balance and the voice's on/off.
|
|
7863
|
+
*
|
|
7864
|
+
* Its own level rather than a plain toggle, because a toggle alone cannot answer the question someone opens
|
|
7865
|
+
* this for — 「我还剩多少」. The balance is read-only there; only the voice switches.
|
|
7866
|
+
*/
|
|
7867
|
+
submenu(t("pet.menu.balance"), "balance", menuIcon("balance"));
|
|
7833
7868
|
// A read-only description of whoever is on screen. @see characterIntro
|
|
7834
7869
|
submenu(t("pet.menu.about"), "about", menuIcon("about"));
|
|
7835
7870
|
/*
|
package/lib/index.js
CHANGED
|
@@ -284,10 +284,12 @@ export class PetLive2DService extends Service {
|
|
|
284
284
|
* signed-out account, must leave the pet working exactly as before rather than failing to mount. `undefined` from
|
|
285
285
|
* `ctx.get` is the ordinary case for both, so it is not treated as an error anywhere.
|
|
286
286
|
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
287
|
+
* ON/OFF lives in the LAYOUT RECORD, not here, because the switch is a row in the pet's own 设置 menu — that menu writes
|
|
288
|
+
* through `POST /layout`, which normalises into the record. The getter below is the only reader, so the menu's toggle and
|
|
289
|
+
* the poller cannot disagree.
|
|
290
|
+
*
|
|
291
|
+
* The warning copy is per threshold and stays in the config, since it is prose edited by hand rather than a switch.
|
|
289
292
|
*/
|
|
290
|
-
this.balanceWarningsEnabled = config.balanceWarnings !== false;
|
|
291
293
|
this.balanceWarningLines = config.balanceWarningLines ?? {};
|
|
292
294
|
this.balance = new BalanceWatcher();
|
|
293
295
|
this.balanceTimer = null;
|
|
@@ -451,7 +453,8 @@ export class PetLive2DService extends Service {
|
|
|
451
453
|
* `lines` is the copy per threshold, empty by default; the client renders nothing when the text is empty.
|
|
452
454
|
*/
|
|
453
455
|
balance: {
|
|
454
|
-
|
|
456
|
+
// Read through the getter so the menu's toggle and this snapshot can never disagree.
|
|
457
|
+
enabled: this.balanceWarningsOn(),
|
|
455
458
|
value: this.balance.current ?? null,
|
|
456
459
|
band: this.balance.lastBand ?? null,
|
|
457
460
|
lines: this.balanceWarningLines ?? {}
|
|
@@ -459,6 +462,19 @@ export class PetLive2DService extends Service {
|
|
|
459
462
|
};
|
|
460
463
|
}
|
|
461
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Whether balance warnings are switched on, from the persisted layout.
|
|
467
|
+
*
|
|
468
|
+
* The single reader: the poller and the state snapshot both go through here, so a toggle in the 设置 menu takes effect on the
|
|
469
|
+
* next poll rather than needing a restart. `DEFAULT_LAYOUT.balanceWarnings` is the default; `config.balanceWarnings` is only an
|
|
470
|
+
* initial seed (see `applyConfig`), which is why this cannot read the config directly.
|
|
471
|
+
*
|
|
472
|
+
* @returns true when warnings should be read and shown.
|
|
473
|
+
*/
|
|
474
|
+
balanceWarningsOn() {
|
|
475
|
+
return this.record?.layout?.balanceWarnings !== false;
|
|
476
|
+
}
|
|
477
|
+
|
|
462
478
|
/**
|
|
463
479
|
* The whole phase -> animation table, for diagnostics and for the harness that verifies
|
|
464
480
|
* every phase's clips actually play. `view()` only exposes the current phase, which is all
|
|
@@ -494,6 +510,10 @@ export class PetLive2DService extends Service {
|
|
|
494
510
|
? this.setCharacter(patch.character)
|
|
495
511
|
: false;
|
|
496
512
|
this.record = { ...this.record, layout: normalizeLayout(patch, this.record.layout) };
|
|
513
|
+
/*
|
|
514
|
+
* No timer to restart when the warning switch changes: the balance poll runs regardless, because the 设置 panel shows the
|
|
515
|
+
* balance even while the warnings are silenced. Only `enabled` in the next snapshot changes.
|
|
516
|
+
*/
|
|
497
517
|
if (!changed) this.flush();
|
|
498
518
|
return {
|
|
499
519
|
ok: true,
|
|
@@ -581,13 +601,16 @@ export class PetLive2DService extends Service {
|
|
|
581
601
|
* Start or stop the balance poll.
|
|
582
602
|
*
|
|
583
603
|
* Idempotent, like the two `sync*` methods above, so a config change can call it without stacking timers.
|
|
604
|
+
*
|
|
605
|
+
* The poll runs whether or not the warning voice is switched on, because the 设置 panel DISPLAYS the balance — silencing the
|
|
606
|
+
* warnings must not also blank the readout. Only `this.disabled` stops it.
|
|
584
607
|
*/
|
|
585
608
|
syncBalance() {
|
|
586
609
|
if (this.balanceTimer !== null) {
|
|
587
610
|
clearInterval(this.balanceTimer);
|
|
588
611
|
this.balanceTimer = null;
|
|
589
612
|
}
|
|
590
|
-
if (this.disabled
|
|
613
|
+
if (this.disabled) return;
|
|
591
614
|
// Read once immediately: waiting a full interval would leave a freshly started pet with no balance at all, and the
|
|
592
615
|
// first reading is also what establishes the band the balance is ALREADY in.
|
|
593
616
|
void this.readBalance();
|
|
@@ -718,10 +741,17 @@ export const Config = z.object({
|
|
|
718
741
|
]).description("随机模式随机的内容。"),
|
|
719
742
|
voiceVolume: z.number().step(0.1).min(LAYOUT_LIMITS.voiceVolume.min).max(LAYOUT_LIMITS.voiceVolume.max)
|
|
720
743
|
.description("语音音量,0 为静音(也可在桌宠菜单的「设置 → 语音音量」里调整)。"),
|
|
721
|
-
|
|
722
|
-
|
|
744
|
+
/*
|
|
745
|
+
* NO `.default(...)` on this one, deliberately.
|
|
746
|
+
*
|
|
747
|
+
* A default makes the schema always produce a value, which `normalizeLayout` then applies over the stored layout — so the
|
|
748
|
+
* 设置 menu's toggle would be silently reset on every reload. Left unset the field is `undefined`, the stored value survives,
|
|
749
|
+
* and `DEFAULT_LAYOUT.balanceWarnings` supplies the initial true.
|
|
750
|
+
*/
|
|
751
|
+
balanceWarnings: z.boolean()
|
|
752
|
+
.description("余额警告:账户余额降到 5、4、3、2、1 元时让桌宠提醒一次(每跨过一档提醒一次)。也可在桌宠菜单的「设置」里开关。"),
|
|
723
753
|
balanceWarningLines: z.dict(z.string())
|
|
724
|
-
.description("各档位的提醒文案,键为余额档位(\"5\" 到 \"1\"
|
|
754
|
+
.description("各档位的提醒文案,键为余额档位(\"5\" 到 \"1\")。留空时使用桌宠自带的三段反应。"),
|
|
725
755
|
name: z.string().min(1).max(24).description("桌宠显示名。")
|
|
726
756
|
});
|
|
727
757
|
|
package/lib/persist.js
CHANGED
|
@@ -83,6 +83,14 @@ export const DEFAULT_LAYOUT = {
|
|
|
83
83
|
* and later on again should restore the scope that was chosen before, not reset it to a default.
|
|
84
84
|
*/
|
|
85
85
|
randomModeScope: "both",
|
|
86
|
+
/**
|
|
87
|
+
* Warn when the account balance drops past 5, 4, 3, 2 and 1 yuan.
|
|
88
|
+
*
|
|
89
|
+
* A LAYOUT field rather than a plugin-config field, because the switch lives in the pet's own 设置 menu: the menu writes
|
|
90
|
+
* through `POST /api/pet-live2d-viola/layout`, which normalises against this record. Keeping it in `Config` alone would have
|
|
91
|
+
* made the menu's toggle a value nothing read.
|
|
92
|
+
*/
|
|
93
|
+
balanceWarnings: true,
|
|
86
94
|
/**
|
|
87
95
|
* Playback volume for every spoken clip, 0..1.
|
|
88
96
|
*
|
|
@@ -208,6 +216,7 @@ export function normalizeLayout(patch, base = DEFAULT_LAYOUT) {
|
|
|
208
216
|
// An unknown scope degrades to the previous one, matching how `crop` is handled: the value reaches the drift, and an
|
|
209
217
|
// unrecognised string there would silently match no branch and leave the mode doing nothing.
|
|
210
218
|
randomModeScope: RANDOM_MODE_SCOPES.includes(raw.randomModeScope) ? raw.randomModeScope : base.randomModeScope,
|
|
219
|
+
balanceWarnings: typeof raw.balanceWarnings === "boolean" ? raw.balanceWarnings : base.balanceWarnings,
|
|
211
220
|
voiceVolume: clampNumber(raw.voiceVolume, base.voiceVolume, LAYOUT_LIMITS.voiceVolume.min, LAYOUT_LIMITS.voiceVolume.max)
|
|
212
221
|
};
|
|
213
222
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-pet-live2d-viola",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Live2D desktop pet for the DeepSeek Harness web GUI. One pet, three characters: 都子 (miyako_014_casual_spring_01_marukun), 薇欧拉 in casual or school uniform, and 由乃 in casual clothes or her virtual-avatar outfit — each with its own motions, expressions, voice lines, captions, idle drift, click reactions and goodbye.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|