dsh-pet-live2d 3.1.0 → 3.1.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/CHANGELOG.md +52 -0
- package/README.md +8 -0
- package/lib/client.js +83 -12
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 3.1.2(2026-09-30)
|
|
4
|
+
|
|
5
|
+
### 修:官方桌面端(`dsh-app://` 外壳)里"贴图 404 / 加载失败"(issue #1 的真身)
|
|
6
|
+
|
|
7
|
+
现象(用户在 DeepSeek Harness 官方桌面端里实测):
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
[Loader.load] Failed to load dsh-app://api/live2d-pet/asset/ds-whale-girl/textures/texture_01.png
|
|
11
|
+
Error: [WorkerManager.loadImageBitmap] Failed to fetch … 404 Not Found
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
注意地址里 **`api` 占了 host 的位置** ✗。根因有两条叠在一起:
|
|
15
|
+
|
|
16
|
+
1. 官方桌面端的页面 origin 是 **`dsh-app://app`**(自定义协议,不是 http);
|
|
17
|
+
2. Live2D 引擎(Pixi)是在 **blob Worker** 里 `fetch` 贴图的,而 **blob Worker 里解析根相对
|
|
18
|
+
地址 `/api/...` 会把 host 丢掉** ⇒ 请求变成 `dsh-app://api/...` ⇒ 服务端收到的是
|
|
19
|
+
`/live2d-pet/asset/...`(少了 `/api`)⇒ 404 ⇒ 贴图全挂、模型加载失败。
|
|
20
|
+
|
|
21
|
+
修法:凡是交给引擎的地址(模型 `modelUrl`、Cubism Core、vendor bundle)都先拼成
|
|
22
|
+
**保住 host 的绝对地址** —— `<协议>//<host>/api/...`。http(s) 外壳下与相对地址等价
|
|
23
|
+
(驱动全绿),`dsh-app://app` 下这正是页面自己那些相对请求能通的形式。
|
|
24
|
+
|
|
25
|
+
(上一版 3.1.1 修的 `localStorage` 兜底是另一个真实问题:自定义协议 / opaque origin 下
|
|
26
|
+
**光访问 `localStorage` 就会抛 `SecurityError`**,抛在组件第一帧 ⇒ 宠物与设置页一起白掉。
|
|
27
|
+
两处都要有,才算真正"适配得了各种桌面外壳"。)
|
|
28
|
+
|
|
29
|
+
## 3.1.1(2026-09-30)
|
|
30
|
+
|
|
31
|
+
### 修:在某些桌面外壳里"宠物整个不出现"(`localStorage` 一访问就抛)
|
|
32
|
+
|
|
33
|
+
有人反馈在 DeepSeek Harness 的**官方桌面端**里用不了(issue #1)。查下来最要命的一处是:
|
|
34
|
+
客户端里有 20 多处**直接读写 `window.localStorage`**,而在**自定义协议 / opaque origin**
|
|
35
|
+
的外壳里,**光是访问 `localStorage` 就会抛 `SecurityError`**(有些壳还会因为隐私设置直接
|
|
36
|
+
禁用 storage)。它抛在组件的第一帧里 ⇒ **宠物和设置页一起白掉**,现象看起来就像"这个插件
|
|
37
|
+
不支持我的桌面端"。
|
|
38
|
+
|
|
39
|
+
现在所有读写都走一层带兜底的封装:
|
|
40
|
+
|
|
41
|
+
- 先试着取一次原生引用(**连这一步都在 `try` 里** —— `typeof window.localStorage` 本身也会抛);
|
|
42
|
+
- 拿不到、或读写中途抛(配额/隐私模式),就退回**内存实现**:本次会话内位置 / 大小 / 装扮
|
|
43
|
+
照常工作,只是关掉窗口不留存。
|
|
44
|
+
|
|
45
|
+
顺带确认了一条:前端**只走相对路径**(`/api/live2d-pet/*`),插件加载走官方机制
|
|
46
|
+
(`dsh.bundle` / `dsh.client` / `slots`)—— 所以换成 `http://127.0.0.1:<端口>` 之外的外壳
|
|
47
|
+
地址也不该受影响。
|
|
48
|
+
|
|
49
|
+
### 改:macOS(arm64)平台子包进入主包声明
|
|
50
|
+
|
|
51
|
+
`dsh-pet-live2d-desktop-darwin-arm64` 已在上一版发上 npm,这一版把主包的
|
|
52
|
+
`optionalDependencies` 补上它 —— mac 用户装插件时会自动带上桌面端(在这之前只能从
|
|
53
|
+
Release 下 zip)。
|
|
54
|
+
|
|
3
55
|
## 3.1.0(2026-09-29)
|
|
4
56
|
|
|
5
57
|
这一轮的主线是**桌面端**:托盘的"没有她"查到底了 —— 根因是本项目自己给产物贴错了
|
package/README.md
CHANGED
|
@@ -39,6 +39,14 @@ git clone https://github.com/A8Chann/dsh-pet-live2d
|
|
|
39
39
|
dsh plugin --profile web add "link:./dsh-pet-live2d/dsh-live2d-pet"
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
> **装到哪个 profile?** 取决于你在哪里用它:
|
|
43
|
+
>
|
|
44
|
+
> - 浏览器里跑的 `dsh web` ⇒ **`--profile web`**(上面几条都是这个);
|
|
45
|
+
> - **DeepSeek Harness 官方桌面客户端** ⇒ 它用的是 **`--profile desktop`**,例如
|
|
46
|
+
> `dsh plugin --profile desktop add dsh-pet-live2d`。
|
|
47
|
+
>
|
|
48
|
+
> 装完**重启一次**(插件在启动时加载,客户端 bundle 也不做热重载)。
|
|
49
|
+
|
|
42
50
|
### Cubism Core:不用手动装
|
|
43
51
|
|
|
44
52
|
`live2dcubismcore.min.js` 是 Live2D 株式会社的**专有运行时**,不能随插件分发 —— 但**你也不
|
package/lib/client.js
CHANGED
|
@@ -31,6 +31,57 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
31
31
|
|
|
32
32
|
const API = "/api/live2d-pet";
|
|
33
33
|
const STORAGE_KEY = "dsh-live2d-pet.state.v1";
|
|
34
|
+
|
|
35
|
+
// ---- 存储:`localStorage` 要裹起来用 ----
|
|
36
|
+
//
|
|
37
|
+
// 为什么不能直接 `window.localStorage.getItem(...)`:**有些桌面外壳里访问它就抛异常**
|
|
38
|
+
// (自定义协议 / opaque origin 下是 `SecurityError: The operation is insecure`,
|
|
39
|
+
// 有的壳还会因为隐私设置直接禁用 storage)。一抛就是**在组件第一帧里**,整个宠物
|
|
40
|
+
// 连带设置页一起白掉 —— 而这种外壳(官方 desktop / 各家 Tauri、Electron 打包版)
|
|
41
|
+
// 恰恰是本插件最常见的运行环境之一。
|
|
42
|
+
//
|
|
43
|
+
// 拿不到就退回内存实现:这一次会话内照样能存(位置、大小、装扮),只是关掉窗口不保留。
|
|
44
|
+
// 注意**不能**用 `typeof window.localStorage` 判断 —— 那个 getter 本身就会抛,
|
|
45
|
+
// 所以连"取一次引用"都要裹在 try 里。
|
|
46
|
+
const nativeStorage = (() => {
|
|
47
|
+
try {
|
|
48
|
+
return window.localStorage;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
const memoryStorage = new Map();
|
|
54
|
+
const storage = {
|
|
55
|
+
getItem(key) {
|
|
56
|
+
if (nativeStorage !== null) {
|
|
57
|
+
try {
|
|
58
|
+
return nativeStorage.getItem(key);
|
|
59
|
+
} catch {
|
|
60
|
+
/* 读也抛(配额/隐私模式)⇒ 退回内存那份 */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return memoryStorage.has(key) ? memoryStorage.get(key) : null;
|
|
64
|
+
},
|
|
65
|
+
setItem(key, value) {
|
|
66
|
+
memoryStorage.set(key, String(value));
|
|
67
|
+
if (nativeStorage === null) return;
|
|
68
|
+
try {
|
|
69
|
+
nativeStorage.setItem(key, value);
|
|
70
|
+
} catch {
|
|
71
|
+
/* 写不进去也别抛:内存那份已经存了 */
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
removeItem(key) {
|
|
75
|
+
memoryStorage.delete(key);
|
|
76
|
+
if (nativeStorage === null) return;
|
|
77
|
+
try {
|
|
78
|
+
nativeStorage.removeItem(key);
|
|
79
|
+
} catch {
|
|
80
|
+
/* 同上 */
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
34
85
|
const ROOT_ATTR = "data-dsh-live2d-pet-root";
|
|
35
86
|
const PET_ATTR = "data-dsh-live2d-pet";
|
|
36
87
|
const LEGACY_ATTR = "data-dsh-live2d-pet-container";
|
|
@@ -40,6 +91,26 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
40
91
|
/** 显示层轮询间隔:桌面端接管/让位要在一秒内被看见。 */
|
|
41
92
|
const LAYER_POLL_MS = 1000;
|
|
42
93
|
|
|
94
|
+
// ---- 交给引擎的地址必须是"保住 host 的绝对地址" ----
|
|
95
|
+
//
|
|
96
|
+
// 症状(2026-09-30 官方桌面端实测):贴图全挂在
|
|
97
|
+
// `dsh-app://api/live2d-pet/asset/ds-whale-girl/textures/texture_01.png` 404
|
|
98
|
+
// —— 注意 host 位置被 `api` 占了:官方桌面端的页面 origin 是 `dsh-app://app`,
|
|
99
|
+
// 而 Pixi 是在 **blob Worker** 里 `fetch` 贴图的(`WorkerManager.loadImageBitmap`),
|
|
100
|
+
// blob Worker 里解析 `/api/...` 这种根相对地址会把 host 丢掉。
|
|
101
|
+
//
|
|
102
|
+
// 所以凡是交给引擎(`Live2DModel.from` / 脚本注入)的地址,都先拼成
|
|
103
|
+
// `<协议>//<host>/api/...`。http(s) 外壳下结果与相对地址等价(只是更长),
|
|
104
|
+
// 自定义协议外壳下这正是能通的那一种写法。
|
|
105
|
+
const URL_BASE = (() => {
|
|
106
|
+
const { protocol, host } = window.location;
|
|
107
|
+
if (host) return protocol + "//" + host;
|
|
108
|
+
const { origin } = window.location;
|
|
109
|
+
return typeof origin === "string" && origin !== "null" ? origin : "";
|
|
110
|
+
})();
|
|
111
|
+
const absolutize = (url) =>
|
|
112
|
+
typeof url === "string" && url.startsWith("/") && URL_BASE !== "" ? URL_BASE + url : url;
|
|
113
|
+
|
|
43
114
|
/**
|
|
44
115
|
* 桌面端注册进来的"跟随处理器"。
|
|
45
116
|
*
|
|
@@ -3010,7 +3081,7 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
3010
3081
|
|
|
3011
3082
|
function loadStored() {
|
|
3012
3083
|
try {
|
|
3013
|
-
const raw =
|
|
3084
|
+
const raw = storage.getItem(STORAGE_KEY);
|
|
3014
3085
|
if (raw === null) return {};
|
|
3015
3086
|
const parsed = JSON.parse(raw);
|
|
3016
3087
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
@@ -3021,7 +3092,7 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
3021
3092
|
|
|
3022
3093
|
function saveStored(patch) {
|
|
3023
3094
|
try {
|
|
3024
|
-
|
|
3095
|
+
storage.setItem(STORAGE_KEY, JSON.stringify(Object.assign(loadStored(), patch)));
|
|
3025
3096
|
} catch {
|
|
3026
3097
|
/* storage is best-effort */
|
|
3027
3098
|
}
|
|
@@ -3637,7 +3708,7 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
3637
3708
|
|
|
3638
3709
|
const readLocal = (key) => {
|
|
3639
3710
|
try {
|
|
3640
|
-
const raw =
|
|
3711
|
+
const raw = storage.getItem(key);
|
|
3641
3712
|
return raw === null ? null : JSON.parse(raw);
|
|
3642
3713
|
} catch {
|
|
3643
3714
|
return null;
|
|
@@ -3645,8 +3716,8 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
3645
3716
|
};
|
|
3646
3717
|
const writeLocal = (key, value) => {
|
|
3647
3718
|
try {
|
|
3648
|
-
if (value === null || value === undefined)
|
|
3649
|
-
else
|
|
3719
|
+
if (value === null || value === undefined) storage.removeItem(key);
|
|
3720
|
+
else storage.setItem(key, JSON.stringify(value));
|
|
3650
3721
|
} catch {
|
|
3651
3722
|
/* 无痕模式之类:这次生效,下次不记得 */
|
|
3652
3723
|
}
|
|
@@ -3912,7 +3983,7 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
3912
3983
|
FLAGS[key] = value === true;
|
|
3913
3984
|
if (key === "outfitArchive" && FLAGS[key] === false) {
|
|
3914
3985
|
try {
|
|
3915
|
-
|
|
3986
|
+
storage.removeItem(OUTFIT_KEY);
|
|
3916
3987
|
} catch {
|
|
3917
3988
|
/* 无痕模式:本来也没存下 */
|
|
3918
3989
|
}
|
|
@@ -3952,7 +4023,7 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
3952
4023
|
let saved = given === undefined ? null : given;
|
|
3953
4024
|
if (saved === undefined || saved === null) {
|
|
3954
4025
|
try {
|
|
3955
|
-
saved = JSON.parse(
|
|
4026
|
+
saved = JSON.parse(storage.getItem(OVERRIDE_KEY) ?? "null");
|
|
3956
4027
|
} catch {
|
|
3957
4028
|
saved = null;
|
|
3958
4029
|
}
|
|
@@ -4304,7 +4375,7 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
4304
4375
|
let saved = given === undefined ? null : given;
|
|
4305
4376
|
if (saved === undefined || saved === null) {
|
|
4306
4377
|
try {
|
|
4307
|
-
saved = JSON.parse(
|
|
4378
|
+
saved = JSON.parse(storage.getItem(TUNING_KEY) ?? "null");
|
|
4308
4379
|
} catch {
|
|
4309
4380
|
saved = null;
|
|
4310
4381
|
}
|
|
@@ -5339,13 +5410,13 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
5339
5410
|
layoutRef.current = layout;
|
|
5340
5411
|
|
|
5341
5412
|
const boot = async () => {
|
|
5342
|
-
if (!await ensureCore(catalog.coreUrl)) {
|
|
5413
|
+
if (!await ensureCore(absolutize(catalog.coreUrl))) {
|
|
5343
5414
|
if (!disposed) setCoreMissing(true);
|
|
5344
5415
|
return;
|
|
5345
5416
|
}
|
|
5346
5417
|
if (disposed) return;
|
|
5347
5418
|
setCoreMissing(false);
|
|
5348
|
-
const vendor = await ensureVendor(catalog.vendorUrl);
|
|
5419
|
+
const vendor = await ensureVendor(absolutize(catalog.vendorUrl));
|
|
5349
5420
|
if (disposed) return;
|
|
5350
5421
|
if (vendor === undefined) throw new Error("vendor bundle unavailable");
|
|
5351
5422
|
configureVendor(vendor);
|
|
@@ -5381,7 +5452,7 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
5381
5452
|
app.canvas.style.height = "100%";
|
|
5382
5453
|
stage.appendChild(app.canvas);
|
|
5383
5454
|
|
|
5384
|
-
const loaded = await vendor.Live2DModel.from(pet.modelUrl, {
|
|
5455
|
+
const loaded = await vendor.Live2DModel.from(absolutize(pet.modelUrl), {
|
|
5385
5456
|
autoUpdate: false,
|
|
5386
5457
|
autoHitTest: true,
|
|
5387
5458
|
autoFocus: false,
|
|
@@ -5985,7 +6056,7 @@ window.__ModuleLoader__.load({ id: "dsh-pet-live2d", factory: (require) => {
|
|
|
5985
6056
|
const readOutfit = () => {
|
|
5986
6057
|
if (!FLAGS.outfitArchive) return null;
|
|
5987
6058
|
try {
|
|
5988
|
-
const parsed = JSON.parse(
|
|
6059
|
+
const parsed = JSON.parse(storage.getItem(OUTFIT_KEY) ?? "null");
|
|
5989
6060
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
5990
6061
|
} catch {
|
|
5991
6062
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-pet-live2d",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.2",
|
|
4
4
|
"description": "Live2D 桌宠插件:为 DSH Web GUI 挂一只可拖动、会跟随鼠标、能换动作与表情的 Live2D 宠物。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"optionalDependencies": {
|
|
52
|
-
"dsh-pet-live2d-desktop-
|
|
52
|
+
"dsh-pet-live2d-desktop-darwin-arm64": "3.1.2",
|
|
53
|
+
"dsh-pet-live2d-desktop-win32-x64": "3.1.2"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"esbuild": "^0.25.10",
|