dsh-live2dwaifu 1.2.1 → 1.3.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 +14 -1
- package/assets/corpus.html +31 -31
- package/assets/persona.html +17 -17
- package/assets/tone.html +18 -18
- package/assets/viewer.js +40 -1
- package/lib/index.js +38 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -231,7 +231,7 @@ python tools/export-dsh-meme.py
|
|
|
231
231
|
| 自动播放动作 | 本机 | 开 | 模型带动作文件时,隔一会儿播一个 |
|
|
232
232
|
| 说话带电子音 | 本机 | 开 | 每个字一声短音 |
|
|
233
233
|
| 复述聊天内容 | 本机 | 开 | 聊天窗口出现新回复时,由她念出来 |
|
|
234
|
-
|
|
|
234
|
+
| 闲置主动说话的间隔 | 服务端 | 30 分钟 | 关闭 / 30 分钟 / 1 小时 / 2 小时 / 4 小时 |
|
|
235
235
|
| 回复附带选项按钮 | 服务端 | 开 | 她觉得需要选择时,气泡上方出现可点按钮 |
|
|
236
236
|
| 记住聊过的内容 | 服务端 | 开 | 关掉就不带记忆、不压缩 |
|
|
237
237
|
| 表情包(四档) | 服务端 | 不使用 | 测试功能,见上面那节 |
|
|
@@ -380,6 +380,19 @@ curl -X POST http://127.0.0.1:<端口>/dsh-live2d/api/push \
|
|
|
380
380
|
|
|
381
381
|
版本号怎么走:动了功能和结构就抬第二位(1.2.0),只补洞和小修就抬第三位(1.2.1)。
|
|
382
382
|
|
|
383
|
+
### 1.3.0
|
|
384
|
+
|
|
385
|
+
- 主动说话的间隔可以调了:⚙ 里一根滑轴,关闭 / 30 分钟 / 1 小时 / 2 小时 / 4 小时。默认从原来的十几分钟放宽到 30 分钟。
|
|
386
|
+
- 提示词改了顺序和说法:当前这句话和规则挪到最后,语料样例挪到前面并注明「只参考语气,别拿去回答」。之前她容易跟着样例跑,答非所问。
|
|
387
|
+
- 规则里加了硬要求:先接住这句话,别答非所问;接不住就说短的,不要编。
|
|
388
|
+
- 语料取样从「最多 3 组问答 + 4 句台词」收到「2 + 2」,少喂点无关内容。
|
|
389
|
+
|
|
390
|
+
### 1.2.2
|
|
391
|
+
|
|
392
|
+
- 人设卡、音色、语料三个页面的按钮和说明改成通用说法:保存/追加保存/覆盖保存/放弃修改/选择文件/播放/关掉。
|
|
393
|
+
- 状态提示统一成「读取中/已读取/保存中/已保存/保存失败」,不再出现「读…」「存好了」这种。
|
|
394
|
+
- 语料页的示例和解析结果里,标签从「他/你」换成「对方/角色」。
|
|
395
|
+
|
|
383
396
|
### 1.2.1
|
|
384
397
|
|
|
385
398
|
- 人设卡、音色、语料三个页面右上角各加了一个「关掉」。之前只能点外层那条栏上的按钮,页面本身没出口。
|
package/assets/corpus.html
CHANGED
|
@@ -48,32 +48,32 @@
|
|
|
48
48
|
<body>
|
|
49
49
|
|
|
50
50
|
<h1>语料</h1>
|
|
51
|
-
<p class="sub"
|
|
51
|
+
<p class="sub">导入角色说过的话,作为生成回复时的语气参考。不要求格式整齐,能解析即可。</p>
|
|
52
52
|
|
|
53
53
|
<div class="row">
|
|
54
54
|
<select id="models"></select>
|
|
55
55
|
<div class="btns">
|
|
56
|
-
<button class="act" id="pick"
|
|
57
|
-
<button class="act primary" id="append"
|
|
58
|
-
<button class="act" id="replace"
|
|
56
|
+
<button class="act" id="pick">选择文件</button>
|
|
57
|
+
<button class="act primary" id="append">追加保存</button>
|
|
58
|
+
<button class="act" id="replace">覆盖保存</button>
|
|
59
59
|
</div>
|
|
60
60
|
<span class="state" id="state"></span>
|
|
61
61
|
</div>
|
|
62
62
|
|
|
63
63
|
<div class="cols">
|
|
64
64
|
<div class="col box">
|
|
65
|
-
<h2
|
|
66
|
-
<textarea id="raw" spellcheck="false" placeholder="
|
|
65
|
+
<h2>粘贴文本</h2>
|
|
66
|
+
<textarea id="raw" spellcheck="false" placeholder="直接粘贴,支持以下几种写法:
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
对方:今天忙吗
|
|
69
|
+
角色:刚坐下,手还是湿的。
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
或者只粘贴角色台词,一行一句:
|
|
72
|
+
刚坐下,手还是湿的。
|
|
73
|
+
水在桌上。
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
<div id="drop"
|
|
75
|
+
也支持带引号的一句:「嗯,我也是。」"></textarea>
|
|
76
|
+
<div id="drop">拖放文件到此处,或点击选择文件(支持多选)<br /><span style="font-size:11px">支持文本、字幕、表格;图片与视频见下方说明</span></div>
|
|
77
77
|
<input id="files" type="file" multiple accept=".txt,.json,.jsonl,.csv,.md,.srt,.vtt,.ass,image/*,video/*" style="display:none" />
|
|
78
78
|
<div class="tips">
|
|
79
79
|
认得的写法:<code>对方:… / 角色:…</code>、<code>「…」</code>、<code>- …</code>、一行一句、
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
</div>
|
|
85
85
|
|
|
86
86
|
<div class="col box">
|
|
87
|
-
<h2
|
|
88
|
-
<div class="stat" id="stat"
|
|
87
|
+
<h2>解析结果</h2>
|
|
88
|
+
<div class="stat" id="stat">暂无内容</div>
|
|
89
89
|
<div id="preview"></div>
|
|
90
90
|
<div class="tips" id="saved"></div>
|
|
91
91
|
</div>
|
|
@@ -168,10 +168,10 @@
|
|
|
168
168
|
box.innerHTML = '';
|
|
169
169
|
var show = [];
|
|
170
170
|
for (var i = 0; i < parsed.pairs.length && show.length < 8; i += 1) {
|
|
171
|
-
show.push('
|
|
171
|
+
show.push('对方:' + parsed.pairs[i].user + '<br>角色:' + parsed.pairs[i].assistant);
|
|
172
172
|
}
|
|
173
173
|
for (var j = 0; j < parsed.lines.length && show.length < 14; j += 1) {
|
|
174
|
-
show.push('
|
|
174
|
+
show.push('角色:' + parsed.lines[j]);
|
|
175
175
|
}
|
|
176
176
|
show.forEach(function (html) {
|
|
177
177
|
var d = document.createElement('div');
|
|
@@ -191,15 +191,15 @@
|
|
|
191
191
|
|
|
192
192
|
function load() {
|
|
193
193
|
if (!modelId) return;
|
|
194
|
-
$('state').textContent = '
|
|
194
|
+
$('state').textContent = '读取中';
|
|
195
195
|
fetch('/dsh-live2d/api/corpus?id=' + encodeURIComponent(modelId))
|
|
196
196
|
.then(function (r) { return r.json(); })
|
|
197
197
|
.then(function (d) {
|
|
198
198
|
stored = { lines: d.lines || [], pairs: d.pairs || [] };
|
|
199
|
-
$('state').textContent = '
|
|
199
|
+
$('state').textContent = '已读取';
|
|
200
200
|
render();
|
|
201
201
|
})
|
|
202
|
-
.catch(function (e) { $('state').textContent = '
|
|
202
|
+
.catch(function (e) { $('state').textContent = '读取失败:' + e.message; });
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
function save(mode) {
|
|
@@ -214,10 +214,10 @@
|
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
if (!payload.lines.length && !payload.pairs.length) {
|
|
217
|
-
$('state').textContent = '
|
|
217
|
+
$('state').textContent = '没有可保存的内容';
|
|
218
218
|
return;
|
|
219
219
|
}
|
|
220
|
-
$('state').textContent = '
|
|
220
|
+
$('state').textContent = '保存中';
|
|
221
221
|
fetch('/dsh-live2d/api/corpus', {
|
|
222
222
|
method: 'POST',
|
|
223
223
|
headers: { 'content-type': 'application/json' },
|
|
@@ -225,13 +225,13 @@
|
|
|
225
225
|
})
|
|
226
226
|
.then(function (r) { return r.json(); })
|
|
227
227
|
.then(function (d) {
|
|
228
|
-
if (!d.ok) { $('state').textContent = '
|
|
229
|
-
$('state').textContent = '
|
|
228
|
+
if (!d.ok) { $('state').textContent = '保存失败:' + (d.error || '未知错误'); return; }
|
|
229
|
+
$('state').textContent = '已保存,共 ' + d.total + ' 条';
|
|
230
230
|
$('raw').value = '';
|
|
231
231
|
files = [];
|
|
232
232
|
load();
|
|
233
233
|
})
|
|
234
|
-
.catch(function (e) { $('state').textContent = '
|
|
234
|
+
.catch(function (e) { $('state').textContent = '保存失败:' + e.message; });
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
$('raw').addEventListener('input', reparse);
|
|
@@ -262,17 +262,17 @@
|
|
|
262
262
|
texts.push(f);
|
|
263
263
|
});
|
|
264
264
|
if (skipped.length) {
|
|
265
|
-
$('saved').textContent = '
|
|
265
|
+
$('saved').textContent = '未读取:' + skipped.join('、') + '。图片可留作形象参考;视频请先导出字幕(.srt)或直接粘贴台词。';
|
|
266
266
|
}
|
|
267
|
-
if (!texts.length) { $('state').textContent = '
|
|
268
|
-
$('state').textContent = '
|
|
267
|
+
if (!texts.length) { $('state').textContent = '没有可读取的文本文件'; return; }
|
|
268
|
+
$('state').textContent = '正在读取文件';
|
|
269
269
|
var left = texts.length;
|
|
270
270
|
texts.forEach(function (f) {
|
|
271
271
|
var reader = new FileReader();
|
|
272
272
|
reader.onload = function () {
|
|
273
273
|
files.push(String(reader.result || ''));
|
|
274
274
|
left -= 1;
|
|
275
|
-
if (left === 0) { $('state').textContent = '
|
|
275
|
+
if (left === 0) { $('state').textContent = '已读取 ' + texts.length + ' 个文件'; reparse(); }
|
|
276
276
|
};
|
|
277
277
|
reader.onerror = function () { left -= 1; if (left === 0) reparse(); };
|
|
278
278
|
reader.readAsText(f, 'utf-8');
|
|
@@ -305,10 +305,10 @@
|
|
|
305
305
|
});
|
|
306
306
|
modelId = d.selected || (list[0] && list[0].id) || '';
|
|
307
307
|
if (modelId) sel.value = modelId;
|
|
308
|
-
if (!list.length) { $('state').textContent = '
|
|
308
|
+
if (!list.length) { $('state').textContent = '尚未导入模型'; return; }
|
|
309
309
|
load();
|
|
310
310
|
})
|
|
311
|
-
.catch(function (e) { $('state').textContent = '
|
|
311
|
+
.catch(function (e) { $('state').textContent = '读取模型列表失败:' + e.message; });
|
|
312
312
|
|
|
313
313
|
$('models').addEventListener('change', function (e) {
|
|
314
314
|
modelId = e.target.value;
|
package/assets/persona.html
CHANGED
|
@@ -47,20 +47,20 @@
|
|
|
47
47
|
|
|
48
48
|
<div class="left">
|
|
49
49
|
<div class="card"><iframe id="live" src="/dsh-live2d/viewer?embed=1"></iframe></div>
|
|
50
|
-
<div class="tip"
|
|
50
|
+
<div class="tip">左侧是当前模型预览。保存后,下一次回答就会按新的设定生成。</div>
|
|
51
51
|
</div>
|
|
52
52
|
|
|
53
53
|
<div class="panel">
|
|
54
54
|
<h1>人设卡</h1>
|
|
55
|
-
<p class="sub"
|
|
55
|
+
<p class="sub">角色的身份、性格、说话方式,都写在这里。</p>
|
|
56
56
|
|
|
57
57
|
<div class="group">
|
|
58
|
-
<h2
|
|
58
|
+
<h2>目标模型</h2>
|
|
59
59
|
<select id="models"></select>
|
|
60
60
|
</div>
|
|
61
61
|
|
|
62
62
|
<div class="group">
|
|
63
|
-
<h2
|
|
63
|
+
<h2>角色设定</h2>
|
|
64
64
|
<textarea id="persona" spellcheck="false"></textarea>
|
|
65
65
|
</div>
|
|
66
66
|
|
|
@@ -69,18 +69,18 @@
|
|
|
69
69
|
<label class="switch">
|
|
70
70
|
<input type="checkbox" id="isolation" checked />
|
|
71
71
|
<span>
|
|
72
|
-
<b
|
|
73
|
-
<em
|
|
72
|
+
<b>角色视角隔离</b>
|
|
73
|
+
<em>打开后,角色不会提到程序、界面、插件、模型这些词,按设定世界的视角回答。关闭后角色会承认自己是插件里的角色。</em>
|
|
74
74
|
</span>
|
|
75
75
|
</label>
|
|
76
76
|
</div>
|
|
77
77
|
|
|
78
78
|
<div class="foot">
|
|
79
|
-
<button class="act primary" id="save"
|
|
80
|
-
<button class="act" id="reload"
|
|
79
|
+
<button class="act primary" id="save">保存</button>
|
|
80
|
+
<button class="act" id="reload">放弃修改</button>
|
|
81
81
|
<span class="state" id="state"></span>
|
|
82
82
|
</div>
|
|
83
|
-
<div class="tip"
|
|
83
|
+
<div class="tip">保存在 <code>~/.dsh/dsh-live2d/models/<id>/meta.json</code> 的 <code>persona</code> 字段,也可以直接编辑该文件。</div>
|
|
84
84
|
</div>
|
|
85
85
|
|
|
86
86
|
<script>
|
|
@@ -96,20 +96,20 @@
|
|
|
96
96
|
|
|
97
97
|
function load() {
|
|
98
98
|
if (!modelId) return;
|
|
99
|
-
state.textContent = '
|
|
99
|
+
state.textContent = '读取中';
|
|
100
100
|
fetch('/dsh-live2d/api/persona?id=' + encodeURIComponent(modelId))
|
|
101
101
|
.then(function (r) { return r.json(); })
|
|
102
102
|
.then(function (d) {
|
|
103
103
|
area.value = d.persona || '';
|
|
104
104
|
iso.checked = d.isolation !== false;
|
|
105
|
-
state.textContent = d.custom ? '
|
|
105
|
+
state.textContent = d.custom ? '已自定义' : '使用默认设定';
|
|
106
106
|
})
|
|
107
|
-
.catch(function (e) { state.textContent = '
|
|
107
|
+
.catch(function (e) { state.textContent = '读取失败:' + e.message; });
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
function save() {
|
|
111
111
|
if (!modelId) return;
|
|
112
|
-
state.textContent = '
|
|
112
|
+
state.textContent = '保存中';
|
|
113
113
|
fetch('/dsh-live2d/api/persona', {
|
|
114
114
|
method: 'POST',
|
|
115
115
|
headers: { 'content-type': 'application/json' },
|
|
@@ -117,12 +117,12 @@
|
|
|
117
117
|
})
|
|
118
118
|
.then(function (r) { return r.json(); })
|
|
119
119
|
.then(function (d) {
|
|
120
|
-
state.textContent = d.ok ? '
|
|
120
|
+
state.textContent = d.ok ? '已保存' : '保存失败:' + (d.error || '未知错误');
|
|
121
121
|
if (d.ok) {
|
|
122
122
|
frame.src = '/dsh-live2d/viewer?embed=1&t=' + Date.now();
|
|
123
123
|
}
|
|
124
124
|
})
|
|
125
|
-
.catch(function (e) { state.textContent = '
|
|
125
|
+
.catch(function (e) { state.textContent = '保存失败:' + e.message; });
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
$('save').addEventListener('click', save);
|
|
@@ -145,10 +145,10 @@
|
|
|
145
145
|
});
|
|
146
146
|
modelId = d.selected || (list[0] && list[0].id) || '';
|
|
147
147
|
if (modelId) sel.value = modelId;
|
|
148
|
-
if (!list.length) { state.textContent = '
|
|
148
|
+
if (!list.length) { state.textContent = '尚未导入模型'; return; }
|
|
149
149
|
load();
|
|
150
150
|
})
|
|
151
|
-
.catch(function (e) { state.textContent = '
|
|
151
|
+
.catch(function (e) { state.textContent = '读取模型列表失败:' + e.message; });
|
|
152
152
|
})();
|
|
153
153
|
</script>
|
|
154
154
|
<button id="sheet-close" class="sheet-close">关掉</button>
|
package/assets/tone.html
CHANGED
|
@@ -50,20 +50,20 @@
|
|
|
50
50
|
|
|
51
51
|
<div class="left">
|
|
52
52
|
<div class="card"><iframe id="live" src="/dsh-live2d/viewer?embed=1"></iframe></div>
|
|
53
|
-
<div class="tip"
|
|
53
|
+
<div class="tip">没有声音时,先点击一次页面——浏览器要求先有用户操作才允许播放。</div>
|
|
54
54
|
</div>
|
|
55
55
|
|
|
56
56
|
<div class="panel">
|
|
57
|
-
<h1
|
|
58
|
-
<p class="sub"
|
|
57
|
+
<h1>声音</h1>
|
|
58
|
+
<p class="sub">每说一个字发一声短音。音量调到 0 即静音,只保留文字和口型。</p>
|
|
59
59
|
|
|
60
60
|
<div class="group">
|
|
61
|
-
<h2
|
|
61
|
+
<h2>目标模型</h2>
|
|
62
62
|
<select id="models"></select>
|
|
63
63
|
</div>
|
|
64
64
|
|
|
65
65
|
<div class="group">
|
|
66
|
-
<h2
|
|
66
|
+
<h2>音色预设</h2>
|
|
67
67
|
<div class="presets" id="presets">
|
|
68
68
|
<button data-p="soft" class="on">柔和</button>
|
|
69
69
|
<button data-p="crisp">清脆</button>
|
|
@@ -82,19 +82,19 @@
|
|
|
82
82
|
</div>
|
|
83
83
|
|
|
84
84
|
<div class="group">
|
|
85
|
-
<h2
|
|
85
|
+
<h2>试听</h2>
|
|
86
86
|
<div class="typebox">
|
|
87
87
|
<input id="text" value="窗台那盆,红了。" />
|
|
88
|
-
<button class="act primary" id="say"
|
|
88
|
+
<button class="act primary" id="say">播放</button>
|
|
89
89
|
</div>
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
92
|
<div class="foot">
|
|
93
|
-
<button class="act primary" id="save"
|
|
93
|
+
<button class="act primary" id="save">保存</button>
|
|
94
94
|
<button class="act" id="reset">恢复默认</button>
|
|
95
95
|
<span class="state" id="state"></span>
|
|
96
96
|
</div>
|
|
97
|
-
<div class="tip"
|
|
97
|
+
<div class="tip">每只模型单独保存一份音色设置。</div>
|
|
98
98
|
</div>
|
|
99
99
|
|
|
100
100
|
<script>
|
|
@@ -185,12 +185,12 @@
|
|
|
185
185
|
voice = Object.assign({}, DEFAULTS);
|
|
186
186
|
show();
|
|
187
187
|
pushTone();
|
|
188
|
-
state.textContent = '
|
|
188
|
+
state.textContent = '已恢复默认值,尚未保存';
|
|
189
189
|
});
|
|
190
190
|
|
|
191
191
|
$('save').addEventListener('click', function () {
|
|
192
192
|
if (!modelId) return;
|
|
193
|
-
state.textContent = '
|
|
193
|
+
state.textContent = '保存中';
|
|
194
194
|
fetch('/dsh-live2d/api/voice', {
|
|
195
195
|
method: 'POST',
|
|
196
196
|
headers: { 'content-type': 'application/json' },
|
|
@@ -198,9 +198,9 @@
|
|
|
198
198
|
})
|
|
199
199
|
.then(function (r) { return r.json(); })
|
|
200
200
|
.then(function (d) {
|
|
201
|
-
state.textContent = d.ok ? '
|
|
201
|
+
state.textContent = d.ok ? '已保存' : '保存失败:' + (d.error || '未知错误');
|
|
202
202
|
})
|
|
203
|
-
.catch(function (e) { state.textContent = '
|
|
203
|
+
.catch(function (e) { state.textContent = '保存失败:' + e.message; });
|
|
204
204
|
});
|
|
205
205
|
|
|
206
206
|
sel.addEventListener('change', function () {
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
|
|
211
211
|
function loadVoice(alsoSwitch) {
|
|
212
212
|
if (!modelId) return;
|
|
213
|
-
state.textContent = '
|
|
213
|
+
state.textContent = '读取中';
|
|
214
214
|
if (alsoSwitch) {
|
|
215
215
|
fetch('/dsh-live2d/api/select', {
|
|
216
216
|
method: 'POST',
|
|
@@ -227,9 +227,9 @@
|
|
|
227
227
|
show();
|
|
228
228
|
pushTone();
|
|
229
229
|
setTimeout(pushTone, 900);
|
|
230
|
-
state.textContent = '
|
|
230
|
+
state.textContent = '已读取';
|
|
231
231
|
})
|
|
232
|
-
.catch(function (e) { state.textContent = '
|
|
232
|
+
.catch(function (e) { state.textContent = '读取失败:' + e.message; });
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
fetch('/dsh-live2d/api/models')
|
|
@@ -246,12 +246,12 @@
|
|
|
246
246
|
modelId = d.selected || (list[0] && list[0].id) || '';
|
|
247
247
|
if (modelId) sel.value = modelId;
|
|
248
248
|
if (!list.length) {
|
|
249
|
-
state.textContent = '
|
|
249
|
+
state.textContent = '尚未导入模型';
|
|
250
250
|
return;
|
|
251
251
|
}
|
|
252
252
|
loadVoice(false);
|
|
253
253
|
})
|
|
254
|
-
.catch(function (e) { state.textContent = '
|
|
254
|
+
.catch(function (e) { state.textContent = '读取模型列表失败:' + e.message; });
|
|
255
255
|
|
|
256
256
|
frame.addEventListener('load', function () {
|
|
257
257
|
pushTone();
|
package/assets/viewer.js
CHANGED
|
@@ -44345,10 +44345,11 @@ void main(void)\r
|
|
|
44345
44345
|
["follow", "\u590D\u8FF0\u804A\u5929\u5185\u5BB9"]
|
|
44346
44346
|
];
|
|
44347
44347
|
var SERVER_OPTS = [
|
|
44348
|
-
["proactive", "\u95F2\u7F6E\u65F6\u4E3B\u52A8\u8BF4\u8BDD"],
|
|
44349
44348
|
["choices", "\u56DE\u590D\u9644\u5E26\u9009\u9879\u6309\u94AE"],
|
|
44350
44349
|
["memory", "\u8BB0\u4F4F\u804A\u8FC7\u7684\u5185\u5BB9"]
|
|
44351
44350
|
];
|
|
44351
|
+
var GAP_LABELS = ["\u5173\u95ED", "30 \u5206\u949F", "1 \u5C0F\u65F6", "2 \u5C0F\u65F6", "4 \u5C0F\u65F6"];
|
|
44352
|
+
var GAP_VALUES = [0, 30, 60, 120, 240];
|
|
44352
44353
|
var LOCAL_HINT = {
|
|
44353
44354
|
gaze: "\u89D2\u8272\u7684\u773C\u775B\u548C\u5934\u4F1A\u8DDF\u7740\u9F20\u6807\u79FB\u52A8",
|
|
44354
44355
|
poke: "\u70B9\u89D2\u8272\u7684\u4E0D\u540C\u90E8\u4F4D\u4F1A\u6709\u4E0D\u540C\u53CD\u5E94",
|
|
@@ -44406,6 +44407,44 @@ void main(void)\r
|
|
|
44406
44407
|
row.appendChild(document.createTextNode(label));
|
|
44407
44408
|
box.appendChild(row);
|
|
44408
44409
|
}
|
|
44410
|
+
const hg = document.createElement("h3");
|
|
44411
|
+
hg.textContent = "\u95F2\u7F6E\u4E3B\u52A8\u8BF4\u8BDD\u7684\u95F4\u9694";
|
|
44412
|
+
box.appendChild(hg);
|
|
44413
|
+
const gapWrap = document.createElement("div");
|
|
44414
|
+
gapWrap.className = "opt-col";
|
|
44415
|
+
const gapSlider = document.createElement("input");
|
|
44416
|
+
gapSlider.type = "range";
|
|
44417
|
+
gapSlider.min = "0";
|
|
44418
|
+
gapSlider.max = String(GAP_VALUES.length - 1);
|
|
44419
|
+
gapSlider.step = "1";
|
|
44420
|
+
const curGap = Math.max(
|
|
44421
|
+
0,
|
|
44422
|
+
GAP_VALUES.indexOf(Number(serverSettings.proactiveInterval ?? 30))
|
|
44423
|
+
);
|
|
44424
|
+
gapSlider.value = String(curGap);
|
|
44425
|
+
const gapLab = document.createElement("div");
|
|
44426
|
+
gapLab.className = "opt-lab";
|
|
44427
|
+
gapLab.textContent = GAP_LABELS[curGap];
|
|
44428
|
+
gapSlider.oninput = () => {
|
|
44429
|
+
gapLab.textContent = GAP_LABELS[Number(gapSlider.value)];
|
|
44430
|
+
};
|
|
44431
|
+
gapSlider.onchange = () => {
|
|
44432
|
+
const v2 = GAP_VALUES[Number(gapSlider.value)];
|
|
44433
|
+
serverSettings.proactiveInterval = v2;
|
|
44434
|
+
fetch(BASE + "/api/settings", {
|
|
44435
|
+
method: "POST",
|
|
44436
|
+
headers: { "content-type": "application/json" },
|
|
44437
|
+
body: JSON.stringify({ settings: { proactiveInterval: v2 } })
|
|
44438
|
+
}).catch(() => {
|
|
44439
|
+
});
|
|
44440
|
+
};
|
|
44441
|
+
const gapNote = document.createElement("div");
|
|
44442
|
+
gapNote.className = "opt-note";
|
|
44443
|
+
gapNote.textContent = "\u591A\u4E45\u6CA1\u6709\u5BF9\u8BDD\u4E4B\u540E\uFF0C\u89D2\u8272\u4E3B\u52A8\u8BF4\u4E00\u53E5\u3002\u9009\u300C\u5173\u95ED\u300D\u5C31\u4E0D\u4F1A\u81EA\u5DF1\u5F00\u53E3\u3002";
|
|
44444
|
+
gapWrap.appendChild(gapSlider);
|
|
44445
|
+
gapWrap.appendChild(gapLab);
|
|
44446
|
+
gapWrap.appendChild(gapNote);
|
|
44447
|
+
box.appendChild(gapWrap);
|
|
44409
44448
|
const h3 = document.createElement("h3");
|
|
44410
44449
|
h3.textContent = "\u8868\u60C5\u5305\uFF08\u6D4B\u8BD5\u529F\u80FD\uFF09";
|
|
44411
44450
|
box.appendChild(h3);
|
package/lib/index.js
CHANGED
|
@@ -25,6 +25,8 @@ const MEME_TAGS = {
|
|
|
25
25
|
|
|
26
26
|
const DEFAULT_SETTINGS = {
|
|
27
27
|
proactive: true,
|
|
28
|
+
/** 多久没人说话才自己开口,单位分钟;0 = 关闭 */
|
|
29
|
+
proactiveInterval: 30,
|
|
28
30
|
/** off | own | both | external —— 测试功能,默认关 */
|
|
29
31
|
memesMode: 'off',
|
|
30
32
|
choices: true,
|
|
@@ -202,7 +204,7 @@ function writeCorpus(id, corpus) {
|
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
/** 从语料里挑几条最贴当前对话的,拼成样例 */
|
|
205
|
-
function pickCorpusSamples(corpus, text, toneN =
|
|
207
|
+
function pickCorpusSamples(corpus, text, toneN = 2) {
|
|
206
208
|
const out = [];
|
|
207
209
|
const chars = new Set(String(text || ''));
|
|
208
210
|
const scored = (corpus.pairs || [])
|
|
@@ -212,7 +214,7 @@ function pickCorpusSamples(corpus, text, toneN = 4) {
|
|
|
212
214
|
return { p, hit };
|
|
213
215
|
})
|
|
214
216
|
.sort((a, b) => b.hit - a.hit);
|
|
215
|
-
for (const s of scored.slice(0,
|
|
217
|
+
for (const s of scored.slice(0, 2)) {
|
|
216
218
|
if (s.p.user) out.push('他:' + s.p.user + ' —— 你:' + s.p.assistant);
|
|
217
219
|
else out.push('你:' + s.p.assistant);
|
|
218
220
|
}
|
|
@@ -403,10 +405,13 @@ let lastAutoAt = 0;
|
|
|
403
405
|
|
|
404
406
|
async function maybeTalk(ctx) {
|
|
405
407
|
const cfg = readConfig();
|
|
406
|
-
|
|
408
|
+
const st = settingsOf(cfg);
|
|
409
|
+
const mins = Number(st.proactiveInterval ?? 30);
|
|
410
|
+
if (!(mins > 0)) return;
|
|
411
|
+
const gap = mins * 60 * 1000;
|
|
407
412
|
const now = Date.now();
|
|
408
|
-
if (now - LAST_ACTIVE.at <
|
|
409
|
-
if (now - lastAutoAt <
|
|
413
|
+
if (now - LAST_ACTIVE.at < gap) return;
|
|
414
|
+
if (now - lastAutoAt < gap) return;
|
|
410
415
|
const id = String(cfg.selected || '');
|
|
411
416
|
const meta = id ? readMeta(id) : null;
|
|
412
417
|
if (!meta) return;
|
|
@@ -644,32 +649,31 @@ async function handle(req, res, ctx) {
|
|
|
644
649
|
.slice(-8)
|
|
645
650
|
.map((m) => (m.role === 'user' ? '他:' : '你:') + String(m.text ?? ''))
|
|
646
651
|
.join('\n');
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
.join('\n\n');
|
|
652
|
+
const prompt = [
|
|
653
|
+
personaOf(meta),
|
|
654
|
+
meta.isolation === false ? '' : ISOLATION,
|
|
655
|
+
'现在是 ' + nowText() + '。',
|
|
656
|
+
store.summary ? '之前聊过的,你还记得:\n' + store.summary : '',
|
|
657
|
+
facts.length ? '关于他,你记得这些:\n' + facts.map((f) => '- ' + f).join('\n') : '',
|
|
658
|
+
samples.length ? '这个人平时这么说话,只参考语气,别照抄,也别拿这些去回答:\n' + samples.join('\n') : '',
|
|
659
|
+
lines ? '刚才的对话:\n' + lines : '',
|
|
660
|
+
'他现在说:' + text,
|
|
661
|
+
'先接住他这句话:同意、拒绝、反问、解释、或者提一件和他这句有关的事。别答非所问,别自顾自说别的。',
|
|
662
|
+
'一到两句,四十字以内。不总结他,不讲道理,不说“我理解你”“希望帮到你”“还需要什么”;他没问就别建议。接不住就说短的,不要编。',
|
|
663
|
+
'只输出这句话本身:不要引号,不要旁白,不要括号里的动作。',
|
|
664
|
+
'说话密度参考(别照抄内容):\n' + TALK_SAMPLES,
|
|
665
|
+
st.choices ? '需要他挑的话,最后另起一行写 ||选项一|选项二,最多三项。' : '',
|
|
666
|
+
st.memesMode === 'own' || st.memesMode === 'both'
|
|
667
|
+
? '想附一张贴图,就在最后另起一行写 ||图:名字。能挑的:' +
|
|
668
|
+
readOwnMemes()
|
|
669
|
+
.map((it) => it.label)
|
|
670
|
+
.filter(Boolean)
|
|
671
|
+
.join('、') +
|
|
672
|
+
'。不想配就别写。'
|
|
673
|
+
: '',
|
|
674
|
+
]
|
|
675
|
+
.filter(Boolean)
|
|
676
|
+
.join('\n\n');
|
|
673
677
|
|
|
674
678
|
let reply = '';
|
|
675
679
|
try {
|
|
@@ -738,6 +742,9 @@ async function handle(req, res, ctx) {
|
|
|
738
742
|
for (const k of Object.keys(DEFAULT_SETTINGS)) {
|
|
739
743
|
if (typeof patch?.[k] === 'boolean') next[k] = patch[k];
|
|
740
744
|
}
|
|
745
|
+
if (typeof patch?.proactiveInterval === 'number' && Number.isFinite(patch.proactiveInterval)) {
|
|
746
|
+
next.proactiveInterval = Math.max(0, Math.min(720, Math.round(patch.proactiveInterval)));
|
|
747
|
+
}
|
|
741
748
|
if (typeof patch?.memesMode === 'string' && MEMES_MODES.includes(patch.memesMode)) {
|
|
742
749
|
next.memesMode = patch.memesMode;
|
|
743
750
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-live2dwaifu",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "给 DSH 网页右下角请一个 Live2D 角色:会眨眼、会呼吸、视线跟着鼠标、点她有反应、说话时嘴动、心情变了眉毛跟着动。模型不用内置——谁装谁放自己的文件夹;支持自定义 .exp3.json 表情和 .motion3.json 动作,卡片能拖能缩,背景可换。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|