dsh-plugin-vrmpet3d 0.1.0 → 0.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/README.md +2 -0
- package/desktop/main.js +47 -4
- package/desktop/preload.js +7 -2
- package/lib/client.core.js +487 -53
- package/lib/client.js +391 -57
- package/lib/client.menu.js +14 -4
- package/lib/client.scene.js +134 -7
- package/lib/config-page.html +82 -45
- package/lib/config-page.js +1271 -252
- package/lib/core.js +487 -53
- package/lib/desktop-page.js +298 -44
- package/lib/desktop.js +18 -1
- package/lib/global-page.html +172 -101
- package/lib/index.js +196 -58
- package/lib/menu.js +14 -4
- package/lib/model-picker.js +139 -49
- package/lib/picker-page.html +34 -8
- package/lib/picker-page.js +192 -11
- package/lib/scene.js +134 -7
- package/lib/setup-page.html +68 -49
- package/lib/setup-page.js +395 -42
- package/lib/vrm-info.js +16 -1
- package/lib/whale.js +191 -0
- package/package.json +1 -1
package/lib/desktop.js
CHANGED
|
@@ -526,7 +526,24 @@ export class DesktopWindow {
|
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
describe() {
|
|
529
|
-
return { ...this.status, running: Boolean(this.child) };
|
|
529
|
+
return { ...this.status, running: Boolean(this.child), electronAvailable: this.hasElectronRuntime() };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Probe the runtime sources without creating links, copying files or starting Electron. */
|
|
533
|
+
hasElectronRuntime() {
|
|
534
|
+
if (this.child) return true;
|
|
535
|
+
try {
|
|
536
|
+
const supplied = this.options.runtime || resolveStandaloneRuntime(envVar('DESKTOP_RUNTIME'));
|
|
537
|
+
const installed = this.options.installRoot || findInstallRoot(dirname(process.execPath)) || findInstallRoot(process.cwd());
|
|
538
|
+
const cached = this.options.runtimeDir || this.status.runtimeDir;
|
|
539
|
+
const candidates = [supplied, cached && resolveStandaloneRuntime(cached),
|
|
540
|
+
installed && { dir: installed, exe: findExecutable(installed) }];
|
|
541
|
+
return candidates.some((runtime) => runtime && runtime.exe && existsSync(runtime.exe)
|
|
542
|
+
&& ['icudtl.dat', 'resources.pak', 'v8_context_snapshot.bin'].every((name) => existsSync(join(runtime.dir, name)))
|
|
543
|
+
&& existsSync(join(runtime.dir, RUNTIME_SUPPORT.locales)));
|
|
544
|
+
} catch (_error) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
530
547
|
}
|
|
531
548
|
}
|
|
532
549
|
|
package/lib/global-page.html
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
|
|
6
|
+
<!--
|
|
7
|
+
The title, the nav and every row below carry `data-i18n*`, and the Chinese text stays INLINE as the no-JS
|
|
8
|
+
fallback: this page's script can fail to load (that is what the watchdog at the bottom reports), and a page
|
|
9
|
+
that failed should still read in one language rather than show nothing. `lib/config-page.js` owns the table
|
|
10
|
+
and `core.applyI18n` writes these places; a key the table lacks leaves the markup alone.
|
|
11
|
+
-->
|
|
12
|
+
<title data-i18n="titleGlobal">vrmpet3d · 通用设置</title>
|
|
7
13
|
<meta name="color-scheme" content="dark light">
|
|
8
14
|
<!--
|
|
9
15
|
The stylesheet both settings pages share. One file rather than a copy in each page: the two pages render
|
|
@@ -26,118 +32,160 @@
|
|
|
26
32
|
-->
|
|
27
33
|
<div class="page-loading" role="status" aria-live="polite">
|
|
28
34
|
<span class="page-loading-spinner" aria-hidden="true"></span>
|
|
29
|
-
<span>正在加载设置…</span>
|
|
35
|
+
<span data-i18n="loading">正在加载设置…</span>
|
|
30
36
|
</div>
|
|
31
37
|
|
|
32
|
-
<header class="page-header"><div class="eyebrow">VRMPET3D · SETTINGS</div><h1>通用设置</h1><div id="status" role="status" aria-live="polite">正在读取…</div></header>
|
|
33
|
-
<nav class="section-nav" aria-label="设置分组"><a href="#settings-0">显示与外观</a><a href="#settings-1">视线</a><a href="#settings-2">鼠标互动与物理</a><a href="#settings-3">状态与动作</a><a href="#settings-4">碎碎念</a><a href="#settings-5">模型导入</a><a href="#settings-6">动作导入</a><a href="#settings-7">其他</a></nav>
|
|
38
|
+
<header class="page-header"><div class="eyebrow">VRMPET3D · SETTINGS</div><h1 data-i18n="headingGlobal">通用设置</h1><div id="status" role="status" aria-live="polite" data-i18n="reading">正在读取…</div></header>
|
|
39
|
+
<nav class="section-nav" aria-label="设置分组" data-i18n-aria="navSections"><a href="#settings-0" data-i18n="sectionDisplay">显示与外观</a><a href="#settings-1" data-i18n="sectionGaze">视线</a><a href="#settings-2" data-i18n="sectionMouse">鼠标互动与物理</a><a href="#settings-3" data-i18n="sectionState">状态与动作</a><a href="#settings-4" data-i18n="sectionSpeech">碎碎念</a><a href="#settings-5" data-i18n="sectionModelImport">模型导入</a><a href="#settings-6" data-i18n="sectionMotionImport">动作导入</a><a href="#settings-7" data-i18n="sectionOther">其他</a></nav>
|
|
34
40
|
<main>
|
|
35
41
|
<section class="settings-section" id="settings-0">
|
|
36
|
-
<h2>显示与外观</h2>
|
|
42
|
+
<h2 data-i18n="sectionDisplay">显示与外观</h2>
|
|
37
43
|
<div class="card">
|
|
38
44
|
|
|
45
|
+
<!--
|
|
46
|
+
界面语言 — the FIRST row, because everything under it is written in whichever answer it holds.
|
|
47
|
+
-->
|
|
48
|
+
<div class="row">
|
|
49
|
+
<label class="k" for="pref-language" data-i18n="language">界面语言</label>
|
|
50
|
+
<select id="pref-language">
|
|
51
|
+
<option value="auto" data-i18n="languageAuto">自动</option>
|
|
52
|
+
<option value="zh" data-i18n="languageZh">中文</option>
|
|
53
|
+
<option value="en" data-i18n="languageEn">English</option>
|
|
54
|
+
</select>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
39
57
|
<div class="row" id="pref-surface-row" hidden>
|
|
40
|
-
<span class="k">桌宠界面</span>
|
|
41
|
-
<label class="inline"><input type="radio" name="pref-surface" value="app">
|
|
42
|
-
<label class="inline"><input type="radio" name="pref-surface" value="auto">
|
|
58
|
+
<span class="k" data-i18n="petSurface">桌宠界面</span>
|
|
59
|
+
<label class="inline"><input type="radio" name="pref-surface" value="app"> <span data-i18n="surfaceApp">内置宠物</span></label>
|
|
60
|
+
<label class="inline"><input type="radio" name="pref-surface" value="auto"> <span data-i18n="auto">自动</span></label>
|
|
43
61
|
</div>
|
|
44
62
|
<div class="row">
|
|
45
|
-
<label class="k" for="pref-size">应用内大小</label>
|
|
63
|
+
<label class="k" for="pref-size" data-i18n="sizeApp">应用内大小</label>
|
|
46
64
|
<input type="range" id="pref-size" min="40" max="260" step="1">
|
|
47
65
|
<span class="value" id="pref-size-value">100%</span>
|
|
48
|
-
<button id="pref-size-reset" class="ghost">复位</button>
|
|
66
|
+
<button id="pref-size-reset" class="ghost" data-i18n="reset">复位</button>
|
|
49
67
|
</div>
|
|
50
68
|
<div class="row">
|
|
51
|
-
<label class="k" for="pref-window-size">悬浮窗大小</label>
|
|
69
|
+
<label class="k" for="pref-window-size" data-i18n="sizeWindow">悬浮窗大小</label>
|
|
52
70
|
<input type="range" id="pref-window-size" min="10" max="400" step="1">
|
|
53
71
|
<span class="value" id="pref-window-size-value">100%</span>
|
|
54
|
-
<button id="pref-window-size-reset" class="ghost">复位</button>
|
|
72
|
+
<button id="pref-window-size-reset" class="ghost" data-i18n="reset">复位</button>
|
|
55
73
|
</div>
|
|
56
74
|
<!--
|
|
57
75
|
Legacy nap control: retained hidden and disabled for compatibility. Idle stays resting.
|
|
58
76
|
-->
|
|
59
77
|
<div class="row" hidden>
|
|
60
|
-
<label class="k" for="pref-sleep">空闲休眠</label>
|
|
78
|
+
<label class="k" for="pref-sleep" data-i18n="idleNap">空闲休眠</label>
|
|
61
79
|
<select id="pref-sleep" disabled>
|
|
62
|
-
<option value="30000">30 秒</option>
|
|
63
|
-
<option value="120000">2 分钟</option>
|
|
64
|
-
<option value="300000">5 分钟</option>
|
|
80
|
+
<option value="30000" data-i18n="sleep30">30 秒</option>
|
|
81
|
+
<option value="120000" data-i18n="sleep120">2 分钟</option>
|
|
82
|
+
<option value="300000" data-i18n="sleep300">5 分钟</option>
|
|
65
83
|
</select>
|
|
66
84
|
</div>
|
|
67
85
|
<div class="row">
|
|
68
|
-
<label class="k" for="pref-hidden">视角</label>
|
|
69
|
-
<button id="pref-view-reset">复位视角</button>
|
|
70
|
-
<span class="k sub">宠物</span>
|
|
71
|
-
<label class="inline"><input type="checkbox" id="pref-hidden">
|
|
86
|
+
<label class="k" for="pref-hidden" data-i18n="view">视角</label>
|
|
87
|
+
<button id="pref-view-reset" data-i18n="viewReset">复位视角</button>
|
|
88
|
+
<span class="k sub" data-i18n="pet">宠物</span>
|
|
89
|
+
<label class="inline"><input type="checkbox" id="pref-hidden"> <span data-i18n="hide">隐藏</span></label>
|
|
72
90
|
</div>
|
|
73
91
|
</div>
|
|
74
92
|
|
|
75
93
|
</section>
|
|
76
94
|
|
|
77
95
|
<section class="settings-section" id="settings-1">
|
|
78
|
-
<h2>视线</h2>
|
|
96
|
+
<h2 data-i18n="sectionGaze">视线</h2>
|
|
79
97
|
<div class="card" id="gaze-body">
|
|
80
|
-
<div class="row"><label class="k" for="pref-look">看向光标</label><label class="inline"><input type="checkbox" id="pref-look">
|
|
98
|
+
<div class="row"><label class="k" for="pref-look" data-i18n="lookAtPointer">看向光标</label><label class="inline"><input type="checkbox" id="pref-look"> <span data-i18n="on">开启</span></label></div>
|
|
81
99
|
|
|
82
100
|
<div class="row">
|
|
83
|
-
<label class="k" for="pref-gaze-
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
101
|
+
<label class="k" for="pref-gaze-mode" data-i18n="gazeCurveMode">曲线模式</label>
|
|
102
|
+
<select id="pref-gaze-mode"><option value="spatial" data-i18n="gazeSpatial">空间角度</option><option value="radial" data-i18n="gazeRadial">二维距离曲线</option></select>
|
|
103
|
+
</div>
|
|
104
|
+
<div id="gaze-curves-radial" hidden>
|
|
105
|
+
<div class="row">
|
|
106
|
+
<label class="k" for="pref-gaze-curve-radial" data-i18n="gazeCurveRadial">二维距离曲率</label>
|
|
107
|
+
<input type="range" id="pref-gaze-curve-radial" min="5" max="100" step="5">
|
|
108
|
+
<span class="value" id="pref-gaze-curve-radial-value">0.10</span>
|
|
109
|
+
<button id="pref-gaze-curve-radial-reset" class="ghost" data-i18n="reset">复位</button>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<p class="hint" data-i18n="gazeCurveHint">数值越小,中心附近越灵敏;1 为线性。二维距离模式按满偏距离归一化后计算方向与强度,可减少远处光标上下穿过视线水平线时的俯仰跳变。各模式的参数分别保存。</p>
|
|
113
|
+
<div id="gaze-curves-spatial">
|
|
114
|
+
<div class="row">
|
|
115
|
+
<label class="k" for="pref-gaze-curve-spatial" data-i18n="gazeCurveSpatial">空间角度曲率</label>
|
|
116
|
+
<input type="range" id="pref-gaze-curve-spatial" min="5" max="100" step="5">
|
|
117
|
+
<span class="value" id="pref-gaze-curve-spatial-value">1.00</span>
|
|
118
|
+
<button id="pref-gaze-curve-spatial-reset" class="ghost" data-i18n="reset">复位</button>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="row">
|
|
121
|
+
<label class="k" for="pref-gaze-distance" data-i18n="gazeDistance">注视距离</label>
|
|
122
|
+
<input type="number" id="pref-gaze-distance" min="0.1" max="5" step="0.1"> ×
|
|
123
|
+
<button id="pref-gaze-distance-reset" class="ghost" data-i18n="reset">复位</button>
|
|
124
|
+
</div>
|
|
125
|
+
<p class="hint" data-i18n="gazeSpatialHint">距离以满偏距离为单位,越大转头越缓。同高时,鼠标越偏向左右,俯仰越小;与双眼同高时俯仰为零。默认曲率 1、注视距离 0.1。</p>
|
|
87
126
|
</div>
|
|
88
127
|
<div class="row">
|
|
89
|
-
<label class="k" for="pref-gaze-span-x">满偏距离</label>
|
|
90
|
-
<input type="number" id="pref-gaze-span-x" step="10" min="0" max="6000"> px
|
|
91
|
-
<input type="number" id="pref-gaze-span-y" step="10" min="0" max="6000"> px
|
|
92
|
-
<button id="pref-gaze-span-reset" class="ghost">自动</button>
|
|
128
|
+
<label class="k" for="pref-gaze-span-x" data-i18n="span">满偏距离</label>
|
|
129
|
+
<input type="number" id="pref-gaze-span-x" step="10" min="0" max="6000"> <span data-i18n="pxHorizontal">px 横向</span>
|
|
130
|
+
<input type="number" id="pref-gaze-span-y" step="10" min="0" max="6000"> <span data-i18n="pxVertical">px 纵向</span>
|
|
131
|
+
<button id="pref-gaze-span-reset" class="ghost" data-i18n="auto">自动</button>
|
|
93
132
|
</div>
|
|
94
133
|
<div class="row">
|
|
95
|
-
<label class="k" for="pref-gaze-idle">静止停看</label>
|
|
96
|
-
<input type="number" id="pref-gaze-idle" step="1" min="0" max="600">
|
|
97
|
-
<button id="pref-gaze-idle-reset" class="ghost">复位</button>
|
|
134
|
+
<label class="k" for="pref-gaze-idle" data-i18n="gazeIdle">静止停看</label>
|
|
135
|
+
<input type="number" id="pref-gaze-idle" step="1" min="0" max="600"> <span data-i18n="secondsUnit">秒</span>
|
|
136
|
+
<button id="pref-gaze-idle-reset" class="ghost" data-i18n="reset">复位</button>
|
|
98
137
|
</div>
|
|
138
|
+
<div class="row">
|
|
139
|
+
<label class="k" for="pref-gaze-pitch-up" data-i18n="gazePitchUp">最大抬头角度</label>
|
|
140
|
+
<input type="number" id="pref-gaze-pitch-up" min="0" max="60" step="0.1"> °
|
|
141
|
+
<label class="k sub" for="pref-gaze-pitch-down" data-i18n="gazePitchDown">最大低头角度</label>
|
|
142
|
+
<input type="number" id="pref-gaze-pitch-down" min="0" max="60" step="0.1"> °
|
|
143
|
+
<button id="pref-gaze-pitch-reset" class="ghost" data-i18n="auto">自动</button>
|
|
144
|
+
</div>
|
|
145
|
+
<p class="hint" data-i18n="gazePitchHint">自动为抬头、低头各 60°。范围 0–60°;0 表示不向该方向转头。这是视线跟踪的附加角度,动作本身的头部姿势仍会保留。</p>
|
|
146
|
+
<p class="hint" data-i18n="gazeSpanHint">满偏距离填 0 为自动:水平和垂直均使用屏幕宽度的一半。</p>
|
|
99
147
|
<p class="hint" id="pref-gaze-span-auto"></p>
|
|
100
|
-
<p class="hint applied" id="pref-gaze-applied">(正在读取悬浮窗…)</p>
|
|
148
|
+
<p class="hint applied" id="pref-gaze-applied" data-i18n="readingWindow">(正在读取悬浮窗…)</p>
|
|
101
149
|
</div>
|
|
102
150
|
|
|
103
151
|
</section>
|
|
104
152
|
|
|
105
153
|
<section class="settings-section" id="settings-2">
|
|
106
|
-
<h2>鼠标互动与物理</h2>
|
|
154
|
+
<h2 data-i18n="sectionMouse">鼠标互动与物理</h2>
|
|
107
155
|
<div class="card">
|
|
108
|
-
<div class="row"><label class="k" for="pref-physics">弹簧骨物理</label><label class="inline"><input type="checkbox" id="pref-physics">
|
|
156
|
+
<div class="row"><label class="k" for="pref-physics" data-i18n="springPhysics">弹簧骨物理</label><label class="inline"><input type="checkbox" id="pref-physics"> <span data-i18n="on">开启</span></label></div>
|
|
109
157
|
|
|
110
158
|
<div class="row">
|
|
111
|
-
<label class="k" for="pref-wind-power">划过强度</label>
|
|
159
|
+
<label class="k" for="pref-wind-power" data-i18n="windPower">划过强度</label>
|
|
112
160
|
<input type="range" id="pref-wind-power" min="0" max="200" step="5">
|
|
113
161
|
<span class="value" id="pref-wind-power-value">1.00</span>
|
|
114
|
-
<button id="pref-wind-power-reset" class="ghost">复位</button>
|
|
162
|
+
<button id="pref-wind-power-reset" class="ghost" data-i18n="reset">复位</button>
|
|
115
163
|
</div>
|
|
116
164
|
<div class="row">
|
|
117
|
-
<label class="k" for="pref-wind-radius">作用范围</label>
|
|
165
|
+
<label class="k" for="pref-wind-radius" data-i18n="windRadius">作用范围</label>
|
|
118
166
|
<input type="range" id="pref-wind-radius" min="0" max="100" step="5">
|
|
119
167
|
<span class="value" id="pref-wind-radius-value">15%</span>
|
|
120
|
-
<button id="pref-wind-radius-reset" class="ghost">复位</button>
|
|
168
|
+
<button id="pref-wind-radius-reset" class="ghost" data-i18n="reset">复位</button>
|
|
121
169
|
</div>
|
|
122
170
|
<div class="row">
|
|
123
|
-
<label class="k" for="pref-wind-decay">余韵</label>
|
|
124
|
-
<input type="number" id="pref-wind-decay" step="0.02" min="0.02" max="2">
|
|
125
|
-
<button id="pref-wind-decay-reset" class="ghost">复位</button>
|
|
171
|
+
<label class="k" for="pref-wind-decay" data-i18n="windDecay">余韵</label>
|
|
172
|
+
<input type="number" id="pref-wind-decay" step="0.02" min="0.02" max="2"> <span data-i18n="secondsUnit">秒</span>
|
|
173
|
+
<button id="pref-wind-decay-reset" class="ghost" data-i18n="reset">复位</button>
|
|
126
174
|
</div>
|
|
127
175
|
<div class="row">
|
|
128
|
-
<label class="k" for="pref-wind-speed">满力速度</label>
|
|
129
|
-
<input type="number" id="pref-wind-speed" step="100" min="100" max="6000"> px
|
|
130
|
-
<button id="pref-wind-speed-reset" class="ghost">复位</button>
|
|
176
|
+
<label class="k" for="pref-wind-speed" data-i18n="windSpeed">满力速度</label>
|
|
177
|
+
<input type="number" id="pref-wind-speed" step="100" min="100" max="6000"> <span data-i18n="pxPerSecond">px/秒</span>
|
|
178
|
+
<button id="pref-wind-speed-reset" class="ghost" data-i18n="reset">复位</button>
|
|
131
179
|
</div>
|
|
132
|
-
<p class="hint applied" id="pref-wind-applied">(正在读取悬浮窗…)</p>
|
|
180
|
+
<p class="hint applied" id="pref-wind-applied" data-i18n="readingWindow">(正在读取悬浮窗…)</p>
|
|
133
181
|
</div>
|
|
134
182
|
|
|
135
183
|
</section>
|
|
136
184
|
|
|
137
185
|
<section class="settings-section" id="settings-3">
|
|
138
|
-
<h2
|
|
186
|
+
<h2><span data-i18n="sectionState">状态与动作</span> <span id="motion-count"></span></h2>
|
|
139
187
|
<div class="card">
|
|
140
|
-
<div class="row"><label class="k" for="pref-motion">播放动画</label><label class="inline"><input type="checkbox" id="pref-motion">
|
|
188
|
+
<div class="row"><label class="k" for="pref-motion" data-i18n="playMotion">播放动画</label><label class="inline"><input type="checkbox" id="pref-motion"> <span data-i18n="on">开启</span></label></div>
|
|
141
189
|
|
|
142
190
|
<div id="motion-list"></div>
|
|
143
191
|
<p class="hint" id="motion-note"></p>
|
|
@@ -146,13 +194,19 @@
|
|
|
146
194
|
</section>
|
|
147
195
|
|
|
148
196
|
<section class="settings-section" id="settings-4">
|
|
149
|
-
<h2>碎碎念</h2>
|
|
197
|
+
<h2 data-i18n="sectionSpeech">碎碎念</h2>
|
|
150
198
|
<!--
|
|
151
199
|
碎碎念的免责说明 (operator, 2026-10-15): 「碎碎念说的话都是玩笑,请勿当真,该插件并不会进行任何实际操作,也不会干扰dsh运行,
|
|
152
200
|
在配置较差的机器上可能轻微影响性能」.
|
|
153
201
|
|
|
154
202
|
设置页这份是**精简版**(操作者同一条消息里也说了「设置页面精简一些」):引导页讲全,因为那是用户第一次决定"让不让它说话"
|
|
155
203
|
的地方;这里只留一句提醒加一句性能提示,压在碎碎念这一段的最上面 —— 改台词来源、调频率的人都会先看到它。
|
|
204
|
+
|
|
205
|
+
This paragraph's TAG is not touched, and that is deliberate: `verify-desktop.mjs` reads it by matching its
|
|
206
|
+
opening tag with the class and id written in that exact order and in that exact spelling (so an extra attribute
|
|
207
|
+
or a wrapping `<span>` would hide the copy from the check), and it then compares the copy's length against the
|
|
208
|
+
guide's longer one. `lib/config-page.js` gives the paragraph `data-i18n-html` at boot instead (see
|
|
209
|
+
`tagFixedRows`), which is enough for every later pass.
|
|
156
210
|
-->
|
|
157
211
|
<p class="hint" id="speech-disclaimer">
|
|
158
212
|
<strong>碎碎念只是玩笑,请勿当真。</strong>它不会对你的项目或文件做任何实际操作,也不会干扰 DSH 的运行;低配机器上
|
|
@@ -160,50 +214,50 @@
|
|
|
160
214
|
</p>
|
|
161
215
|
<div class="card">
|
|
162
216
|
<div class="row">
|
|
163
|
-
<label class="k" for="pref-speech">开启</label>
|
|
164
|
-
<label class="inline"><input type="checkbox" id="pref-speech">
|
|
217
|
+
<label class="k" for="pref-speech" data-i18n="speechEnable">开启</label>
|
|
218
|
+
<label class="inline"><input type="checkbox" id="pref-speech"> <span data-i18n="speechTalk">说话</span></label>
|
|
165
219
|
</div>
|
|
166
220
|
<div class="row">
|
|
167
|
-
<label class="k" for="pref-speech-hold">每句显示</label>
|
|
168
|
-
<input type="number" id="pref-speech-hold" min="1" max="30" step="1">
|
|
169
|
-
<button id="pref-speech-hold-reset" class="ghost">复位</button>
|
|
221
|
+
<label class="k" for="pref-speech-hold" data-i18n="speechHold">每句显示</label>
|
|
222
|
+
<input type="number" id="pref-speech-hold" min="1" max="30" step="1"> <span data-i18n="secondsUnit">秒</span>
|
|
223
|
+
<button id="pref-speech-hold-reset" class="ghost" data-i18n="reset">复位</button>
|
|
170
224
|
</div>
|
|
171
225
|
<div class="row">
|
|
172
|
-
<label class="k" for="pref-speech-gap">最短间隔</label>
|
|
173
|
-
<input type="number" id="pref-speech-gap" min="0" max="600" step="1">
|
|
174
|
-
<button id="pref-speech-gap-reset" class="ghost">复位</button>
|
|
226
|
+
<label class="k" for="pref-speech-gap" data-i18n="speechGap">最短间隔</label>
|
|
227
|
+
<input type="number" id="pref-speech-gap" min="0" max="600" step="1"> <span data-i18n="secondsUnit">秒</span>
|
|
228
|
+
<button id="pref-speech-gap-reset" class="ghost" data-i18n="reset">复位</button>
|
|
175
229
|
</div>
|
|
176
230
|
<div class="row">
|
|
177
|
-
<label class="k" for="pref-speech-idle-min">空闲嘀咕间隔</label>
|
|
231
|
+
<label class="k" for="pref-speech-idle-min" data-i18n="speechIdleInterval">空闲嘀咕间隔</label>
|
|
178
232
|
<input type="number" id="pref-speech-idle-min" min="0" max="3600" step="5"> –
|
|
179
|
-
<input type="number" id="pref-speech-idle-max" min="0" max="3600" step="5">
|
|
180
|
-
<button id="pref-speech-idle-reset" class="ghost">复位</button>
|
|
233
|
+
<input type="number" id="pref-speech-idle-max" min="0" max="3600" step="5"> <span data-i18n="secondsUnit">秒</span>
|
|
234
|
+
<button id="pref-speech-idle-reset" class="ghost" data-i18n="reset">复位</button>
|
|
181
235
|
</div>
|
|
182
236
|
<!--
|
|
183
237
|
工作中 talks more often than 静息态 does (operator, 2026-10-13: 「工作中的嘀咕可以增加频率到12-36秒一次」), so it
|
|
184
238
|
gets its own window rather than a share of the idle one — the two are different remarks on different clocks.
|
|
185
239
|
-->
|
|
186
240
|
<div class="row">
|
|
187
|
-
<label class="k" for="pref-speech-work-min">工作嘀咕间隔</label>
|
|
241
|
+
<label class="k" for="pref-speech-work-min" data-i18n="speechWorkInterval">工作嘀咕间隔</label>
|
|
188
242
|
<input type="number" id="pref-speech-work-min" min="0" max="3600" step="5"> –
|
|
189
|
-
<input type="number" id="pref-speech-work-max" min="0" max="3600" step="5">
|
|
190
|
-
<button id="pref-speech-work-reset" class="ghost">复位</button>
|
|
243
|
+
<input type="number" id="pref-speech-work-max" min="0" max="3600" step="5"> <span data-i18n="secondsUnit">秒</span>
|
|
244
|
+
<button id="pref-speech-work-reset" class="ghost" data-i18n="reset">复位</button>
|
|
191
245
|
</div>
|
|
192
246
|
<div class="row">
|
|
193
|
-
<label class="k" for="pref-speech-source">台词来源</label>
|
|
247
|
+
<label class="k" for="pref-speech-source" data-i18n="speechSource">台词来源</label>
|
|
194
248
|
<select id="pref-speech-source">
|
|
195
|
-
<option value="builtin">内置短语</option>
|
|
196
|
-
<option value="custom">自定义短语</option>
|
|
197
|
-
<option value="ai">AI 生成</option>
|
|
249
|
+
<option value="builtin" data-i18n="sourceBuiltin">内置短语</option>
|
|
250
|
+
<option value="custom" data-i18n="sourceCustom">自定义短语</option>
|
|
251
|
+
<option value="ai" data-i18n="sourceAi">AI 生成</option>
|
|
198
252
|
</select>
|
|
199
253
|
</div>
|
|
200
254
|
<div id="speech-states"></div>
|
|
201
255
|
<div id="speech-ai">
|
|
202
256
|
<div class="row">
|
|
203
|
-
<label class="k" for="pref-speech-provider">AI 来源</label>
|
|
257
|
+
<label class="k" for="pref-speech-provider" data-i18n="speechAiFrom">AI 来源</label>
|
|
204
258
|
<select id="pref-speech-provider">
|
|
205
|
-
<option value="dsh">DSH 的 Provider</option>
|
|
206
|
-
<option value="custom">自定义 Provider</option>
|
|
259
|
+
<option value="dsh" data-i18n="providerDsh">DSH 的 Provider</option>
|
|
260
|
+
<option value="custom" data-i18n="providerCustom">自定义 Provider</option>
|
|
207
261
|
</select>
|
|
208
262
|
</div>
|
|
209
263
|
<div class="row" id="speech-dsh-provider-row">
|
|
@@ -211,7 +265,7 @@
|
|
|
211
265
|
<select id="pref-speech-dsh-provider"></select>
|
|
212
266
|
</div>
|
|
213
267
|
<div class="row" id="speech-dsh-model-row">
|
|
214
|
-
<label class="k" for="pref-speech-dsh-model">模型</label>
|
|
268
|
+
<label class="k" for="pref-speech-dsh-model" data-i18n="speechModel">模型</label>
|
|
215
269
|
<select id="pref-speech-dsh-model"></select>
|
|
216
270
|
</div>
|
|
217
271
|
<div class="row" id="speech-custom-url-row">
|
|
@@ -219,24 +273,24 @@
|
|
|
219
273
|
<input type="text" id="pref-speech-base-url" placeholder="https://api.example.com/v1" spellcheck="false">
|
|
220
274
|
</div>
|
|
221
275
|
<div class="row" id="speech-custom-model-row">
|
|
222
|
-
<label class="k" for="pref-speech-model">模型</label>
|
|
276
|
+
<label class="k" for="pref-speech-model" data-i18n="speechModel">模型</label>
|
|
223
277
|
<input type="text" id="pref-speech-model" placeholder="deepseek-chat" spellcheck="false">
|
|
224
278
|
</div>
|
|
225
279
|
<div class="row" id="speech-custom-key-row">
|
|
226
280
|
<label class="k" for="pref-speech-api-key">API Key</label>
|
|
227
281
|
<input type="password" id="pref-speech-api-key" spellcheck="false" autocomplete="off">
|
|
228
|
-
<button id="pref-speech-key-clear" class="ghost">清除</button>
|
|
282
|
+
<button id="pref-speech-key-clear" class="ghost" data-i18n="speechKeyClear">清除</button>
|
|
229
283
|
</div>
|
|
230
|
-
<div class="row"><span class="k">人格</span></div>
|
|
284
|
+
<div class="row"><span class="k" data-i18n="persona">人格</span></div>
|
|
231
285
|
<textarea id="pref-speech-persona" class="speech-persona" rows="3" spellcheck="false"></textarea>
|
|
232
286
|
<div class="row">
|
|
233
|
-
<label class="k" for="pref-speech-temperature">温度</label>
|
|
287
|
+
<label class="k" for="pref-speech-temperature" data-i18n="temperature">温度</label>
|
|
234
288
|
<input type="range" id="pref-speech-temperature" min="0" max="20" step="1">
|
|
235
289
|
<span class="value" id="pref-speech-temperature-value">0.50</span>
|
|
236
290
|
</div>
|
|
237
291
|
<div class="row">
|
|
238
|
-
<label class="k" for="pref-speech-timeout">超时</label>
|
|
239
|
-
<input type="number" id="pref-speech-timeout" min="1" max="60" step="1">
|
|
292
|
+
<label class="k" for="pref-speech-timeout" data-i18n="timeout">超时</label>
|
|
293
|
+
<input type="number" id="pref-speech-timeout" min="1" max="60" step="1"> <span data-i18n="secondsUnit">秒</span>
|
|
240
294
|
</div>
|
|
241
295
|
<!--
|
|
242
296
|
试一句 and its own answer, in ONE row.
|
|
@@ -246,9 +300,9 @@
|
|
|
246
300
|
sibling of 生成 now, on a full line of its own under it, and gold while it is a refusal.
|
|
247
301
|
-->
|
|
248
302
|
<div class="row">
|
|
249
|
-
<label class="k" for="pref-speech-test-state">试一句</label>
|
|
303
|
+
<label class="k" for="pref-speech-test-state" data-i18n="speechTest">试一句</label>
|
|
250
304
|
<select id="pref-speech-test-state"></select>
|
|
251
|
-
<button id="pref-speech-test" class="ghost">生成</button>
|
|
305
|
+
<button id="pref-speech-test" class="ghost" data-i18n="generate">生成</button>
|
|
252
306
|
<p class="hint" id="pref-speech-test-result" role="status" aria-live="polite"></p>
|
|
253
307
|
</div>
|
|
254
308
|
<p class="hint" id="speech-llm-note"></p>
|
|
@@ -258,17 +312,17 @@
|
|
|
258
312
|
</section>
|
|
259
313
|
|
|
260
314
|
<section class="settings-section" id="settings-5">
|
|
261
|
-
<h2>模型导入</h2>
|
|
315
|
+
<h2 data-i18n="sectionModelImport">模型导入</h2>
|
|
262
316
|
<div class="card">
|
|
263
317
|
<div class="row">
|
|
264
|
-
<span class="k">导入模型</span>
|
|
265
|
-
<button id="model-import-pick">选择模型文件…</button>
|
|
266
|
-
<button id="model-import-manual" class="ghost">手动输入路径</button>
|
|
318
|
+
<span class="k" data-i18n="importModel">导入模型</span>
|
|
319
|
+
<button id="model-import-pick" data-i18n="pickModelFile">选择模型文件…</button>
|
|
320
|
+
<button id="model-import-manual" class="ghost" data-i18n="manualPath">手动输入路径</button>
|
|
267
321
|
</div>
|
|
268
322
|
<div class="row" id="model-import-manual-row" hidden>
|
|
269
|
-
<label class="k" for="model-import-path">文件路径</label>
|
|
323
|
+
<label class="k" for="model-import-path" data-i18n="filePath">文件路径</label>
|
|
270
324
|
<input type="text" id="model-import-path" class="grow" placeholder="D:\Models\my.vrm">
|
|
271
|
-
<button id="model-import-add">导入</button>
|
|
325
|
+
<button id="model-import-add" data-i18n="import">导入</button>
|
|
272
326
|
</div>
|
|
273
327
|
<div id="model-import-list"></div>
|
|
274
328
|
<p class="hint" id="model-import-note"></p>
|
|
@@ -277,17 +331,17 @@
|
|
|
277
331
|
</section>
|
|
278
332
|
|
|
279
333
|
<section class="settings-section" id="settings-6">
|
|
280
|
-
<h2>动作导入</h2>
|
|
334
|
+
<h2 data-i18n="sectionMotionImport">动作导入</h2>
|
|
281
335
|
<div class="card">
|
|
282
336
|
<div class="row">
|
|
283
|
-
<span class="k">导入目录</span>
|
|
284
|
-
<button id="motion-import-pick">选择文件夹…</button>
|
|
285
|
-
<button id="motion-import-manual" class="ghost">手动输入路径</button>
|
|
337
|
+
<span class="k" data-i18n="importFolder">导入目录</span>
|
|
338
|
+
<button id="motion-import-pick" data-i18n="pickMotionDir">选择文件夹…</button>
|
|
339
|
+
<button id="motion-import-manual" class="ghost" data-i18n="manualPath">手动输入路径</button>
|
|
286
340
|
</div>
|
|
287
341
|
<div class="row" id="motion-import-manual-row" hidden>
|
|
288
|
-
<label class="k" for="motion-import-path">路径</label>
|
|
342
|
+
<label class="k" for="motion-import-path" data-i18n="path">路径</label>
|
|
289
343
|
<input type="text" id="motion-import-path" class="grow" placeholder="D:\Motions">
|
|
290
|
-
<button id="motion-import-add">添加</button>
|
|
344
|
+
<button id="motion-import-add" data-i18n="add">添加</button>
|
|
291
345
|
</div>
|
|
292
346
|
<div id="motion-import-list"></div>
|
|
293
347
|
<p class="hint" id="motion-import-note"></p>
|
|
@@ -296,11 +350,11 @@
|
|
|
296
350
|
</section>
|
|
297
351
|
|
|
298
352
|
<section class="settings-section" id="settings-7">
|
|
299
|
-
<h2>其他</h2>
|
|
353
|
+
<h2 data-i18n="sectionOther">其他</h2>
|
|
300
354
|
<div class="card">
|
|
301
355
|
<div class="row">
|
|
302
|
-
<span class="k">初次安装引导</span>
|
|
303
|
-
<button id="pref-setup-open">重新运行引导</button>
|
|
356
|
+
<span class="k" data-i18n="setupSection">初次安装引导</span>
|
|
357
|
+
<button id="pref-setup-open" data-i18n="setupRerun">重新运行引导</button>
|
|
304
358
|
<span class="note" id="pref-setup-note"></span>
|
|
305
359
|
</div>
|
|
306
360
|
</div>
|
|
@@ -312,23 +366,40 @@
|
|
|
312
366
|
A settings page that can only say "正在读取…" is useless when something fails: the text is the HTML's
|
|
313
367
|
own placeholder, and a script that never ran, 404'd or threw at its first line looks exactly like a
|
|
314
368
|
slow host. So this runs FIRST, catches what it can, and after two seconds says what is actually wrong.
|
|
369
|
+
|
|
370
|
+
It runs BEFORE `lib/core.js`, which is the whole point — the case it exists for is a core that never
|
|
371
|
+
loaded — so it cannot use `core.textFor` and carries its own tiny map instead: three sentences plus the
|
|
372
|
+
brackets a filename goes in, picked from `navigator.language` in one line. Chinese is the fallback,
|
|
373
|
+
because the markup around it is Chinese. Keep it small: this is a watchdog, not a second i18n system.
|
|
315
374
|
-->
|
|
316
375
|
<script>
|
|
317
376
|
(function () {
|
|
377
|
+
var ZH = /^(zh|cn|chinese)/i.test(navigator.language || '');
|
|
378
|
+
var WORDS = ZH
|
|
379
|
+
? {
|
|
380
|
+
error: "页面脚本出错:",
|
|
381
|
+
file: "(来自 {name})",
|
|
382
|
+
coreMissing: "初始化卡住:core.js 没有加载(检查 /vrmpet3d/window/lib/core.js 是否 404)。",
|
|
383
|
+
stuck: "初始化卡住:脚本已加载但没有完成初始化(看看窗口的控制台,或宿主是否提供 /vrmpet3d/settings)。",
|
|
384
|
+
}
|
|
385
|
+
: {
|
|
386
|
+
error: "Page script error: ",
|
|
387
|
+
file: "(from {name})",
|
|
388
|
+
coreMissing: "Stuck at startup: core.js did not load (check whether /vrmpet3d/window/lib/core.js is a 404).",
|
|
389
|
+
stuck: "Stuck at startup: the script loaded but never finished initialising (check the window console, or whether the host serves /vrmpet3d/settings).",
|
|
390
|
+
};
|
|
318
391
|
function show(message) {
|
|
319
392
|
var status = document.getElementById("status");
|
|
320
393
|
if (status) { status.textContent = message; status.className = "warn"; }
|
|
321
394
|
}
|
|
322
395
|
window.addEventListener("error", function (event) {
|
|
323
|
-
show(
|
|
396
|
+
show(WORDS.error + (event.message || event.type)
|
|
397
|
+
+ (event.filename ? " " + WORDS.file.replace("{name}", event.filename) : ""));
|
|
324
398
|
}, true);
|
|
325
399
|
window.setTimeout(function () {
|
|
326
400
|
if (window.__VRMPET3D_CONFIG_STATE__) return;
|
|
327
|
-
if (!window.__VRMPET3D_CORE__) {
|
|
328
|
-
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
show("初始化卡住:脚本已加载但没有完成初始化(看看窗口的控制台,或宿主是否提供 /vrmpet3d/settings)。");
|
|
401
|
+
if (!window.__VRMPET3D_CORE__) { show(WORDS.coreMissing); return; }
|
|
402
|
+
show(WORDS.stuck);
|
|
332
403
|
}, 2000);
|
|
333
404
|
})();
|
|
334
405
|
</script>
|