dsh-live2dwaifu 1.3.0 → 1.3.1
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 +9 -0
- package/assets/viewer.js +13 -4
- package/lib/client.js +8 -0
- package/lib/index.js +54 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -380,6 +380,15 @@ 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.1
|
|
384
|
+
|
|
385
|
+
- 修:语料接口整个漏掉了(页面打得开,存不进去,返回 404)。补回来。
|
|
386
|
+
- 修:关掉「记住聊过的内容」之后不写聊天记录了,之前还在偷偷存。
|
|
387
|
+
- 修:语料里的问答样例只在有当前这句话时挑,主动开口不再乱塞无关问答。
|
|
388
|
+
- 修:主动说话间隔滑轴遇到旧配置值(比如 12 分钟)会显示成「关闭」,现在找最接近的档位。
|
|
389
|
+
- 修:语料接口的模型 id 只接受安全字符。
|
|
390
|
+
- 加:按 Esc 关掉覆盖层。
|
|
391
|
+
|
|
383
392
|
### 1.3.0
|
|
384
393
|
|
|
385
394
|
- 主动说话的间隔可以调了:⚙ 里一根滑轴,关闭 / 30 分钟 / 1 小时 / 2 小时 / 4 小时。默认从原来的十几分钟放宽到 30 分钟。
|
package/assets/viewer.js
CHANGED
|
@@ -44417,10 +44417,19 @@ void main(void)\r
|
|
|
44417
44417
|
gapSlider.min = "0";
|
|
44418
44418
|
gapSlider.max = String(GAP_VALUES.length - 1);
|
|
44419
44419
|
gapSlider.step = "1";
|
|
44420
|
-
const curGap =
|
|
44421
|
-
|
|
44422
|
-
|
|
44423
|
-
|
|
44420
|
+
const curGap = (() => {
|
|
44421
|
+
const v2 = Number(serverSettings.proactiveInterval ?? 30);
|
|
44422
|
+
let best = 0;
|
|
44423
|
+
let bestDiff = Infinity;
|
|
44424
|
+
GAP_VALUES.forEach((g2, i2) => {
|
|
44425
|
+
const diff = Math.abs(g2 - v2);
|
|
44426
|
+
if (diff < bestDiff) {
|
|
44427
|
+
bestDiff = diff;
|
|
44428
|
+
best = i2;
|
|
44429
|
+
}
|
|
44430
|
+
});
|
|
44431
|
+
return best;
|
|
44432
|
+
})();
|
|
44424
44433
|
gapSlider.value = String(curGap);
|
|
44425
44434
|
const gapLab = document.createElement("div");
|
|
44426
44435
|
gapLab.className = "opt-lab";
|
package/lib/client.js
CHANGED
|
@@ -106,6 +106,14 @@
|
|
|
106
106
|
window.addEventListener('message', onMsg);
|
|
107
107
|
return () => window.removeEventListener('message', onMsg);
|
|
108
108
|
}, []);
|
|
109
|
+
react.useEffect(() => {
|
|
110
|
+
if (!sheet) return undefined;
|
|
111
|
+
const onKey = (ev) => {
|
|
112
|
+
if (ev.key === 'Escape') setSheet(null);
|
|
113
|
+
};
|
|
114
|
+
window.addEventListener('keydown', onKey);
|
|
115
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
116
|
+
}, [sheet]);
|
|
109
117
|
const [nonce, setNonce] = react.useState(0);
|
|
110
118
|
const ref = react.useRef(null);
|
|
111
119
|
|
package/lib/index.js
CHANGED
|
@@ -206,17 +206,20 @@ function writeCorpus(id, corpus) {
|
|
|
206
206
|
/** 从语料里挑几条最贴当前对话的,拼成样例 */
|
|
207
207
|
function pickCorpusSamples(corpus, text, toneN = 2) {
|
|
208
208
|
const out = [];
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
209
|
+
const query = String(text || '').trim();
|
|
210
|
+
if (query) {
|
|
211
|
+
const chars = new Set(query);
|
|
212
|
+
const scored = (corpus.pairs || [])
|
|
213
|
+
.map((p) => {
|
|
214
|
+
let hit = 0;
|
|
215
|
+
for (const ch of String(p.user || '')) if (chars.has(ch)) hit += 1;
|
|
216
|
+
return { p, hit };
|
|
217
|
+
})
|
|
218
|
+
.sort((a, b) => b.hit - a.hit);
|
|
219
|
+
for (const s of scored.slice(0, 2)) {
|
|
220
|
+
if (s.p.user) out.push('他:' + s.p.user + ' —— 你:' + s.p.assistant);
|
|
221
|
+
else out.push('你:' + s.p.assistant);
|
|
222
|
+
}
|
|
220
223
|
}
|
|
221
224
|
const pool = (corpus.lines || []).slice();
|
|
222
225
|
for (let i = 0; i < toneN && pool.length; i += 1) {
|
|
@@ -318,7 +321,7 @@ function pickFacts(facts, text) {
|
|
|
318
321
|
});
|
|
319
322
|
scored.sort((a, b) => b.hit - a.hit);
|
|
320
323
|
const top = scored.filter((s) => s.hit > 0).slice(0, 6).map((s) => s.f);
|
|
321
|
-
return top
|
|
324
|
+
return top;
|
|
322
325
|
}
|
|
323
326
|
|
|
324
327
|
/** 去 AI 味:删套话、砍尾巴、压重复 */
|
|
@@ -332,7 +335,8 @@ const AI_WORDS = [
|
|
|
332
335
|
];
|
|
333
336
|
|
|
334
337
|
function humanize(input) {
|
|
335
|
-
|
|
338
|
+
const original = String(input || '');
|
|
339
|
+
let t = original;
|
|
336
340
|
for (const re of AI_WORDS) t = t.replace(re, '');
|
|
337
341
|
t = t
|
|
338
342
|
.replace(/[ \t]+/g, ' ')
|
|
@@ -346,7 +350,7 @@ function humanize(input) {
|
|
|
346
350
|
const cut = t.match(/^[\s\S]{0,60}?[。!?!?]/);
|
|
347
351
|
t = cut ? cut[0] : t.slice(0, 60);
|
|
348
352
|
}
|
|
349
|
-
return t.trim();
|
|
353
|
+
return t.trim() || original.trim();
|
|
350
354
|
}
|
|
351
355
|
|
|
352
356
|
/* ── 出口队列:谁能说话,都往这儿扔 ───────────────────────── */
|
|
@@ -643,7 +647,7 @@ async function handle(req, res, ctx) {
|
|
|
643
647
|
} catch {}
|
|
644
648
|
}
|
|
645
649
|
const facts = st.memory ? pickFacts(store.facts, text) : [];
|
|
646
|
-
const samples =
|
|
650
|
+
const samples = pickCorpusSamples(readCorpus(id), text);
|
|
647
651
|
const hist = store.messages;
|
|
648
652
|
const lines = hist
|
|
649
653
|
.slice(-8)
|
|
@@ -698,10 +702,17 @@ async function handle(req, res, ctx) {
|
|
|
698
702
|
? pickImage(st.memesMode, moodNow)
|
|
699
703
|
: null;
|
|
700
704
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
+
if (st.memory) {
|
|
706
|
+
writeChat(
|
|
707
|
+
id,
|
|
708
|
+
hist.concat([
|
|
709
|
+
{ role: 'user', text, at: Date.now() },
|
|
710
|
+
{ role: 'assistant', text: line, at: Date.now() },
|
|
711
|
+
]),
|
|
712
|
+
store.summary,
|
|
713
|
+
store.facts,
|
|
714
|
+
);
|
|
715
|
+
}
|
|
705
716
|
return sendJson(res, 200, { ok: true, reply: line, choices, image: image || undefined, id });
|
|
706
717
|
}
|
|
707
718
|
|
|
@@ -795,6 +806,30 @@ async function handle(req, res, ctx) {
|
|
|
795
806
|
}
|
|
796
807
|
|
|
797
808
|
// API
|
|
809
|
+
|
|
810
|
+
// 语料
|
|
811
|
+
if (path === '/api/corpus' && method === 'GET') {
|
|
812
|
+
const id = String(url.searchParams.get('id') ?? '');
|
|
813
|
+
if (id && !/^[\w.-]+$/.test(id)) return sendJson(res, 400, { error: 'bad id' });
|
|
814
|
+
const c = id ? readCorpus(id) : { lines: [], pairs: [] };
|
|
815
|
+
return sendJson(res, 200, { id, lines: c.lines, pairs: c.pairs, total: c.lines.length + c.pairs.length });
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (path === '/api/corpus' && method === 'POST') {
|
|
819
|
+
const body = JSON.parse((await readBody(req)).toString('utf8') || '{}');
|
|
820
|
+
const id = String(body.id ?? '');
|
|
821
|
+
if (!/^[\w.-]+$/.test(id)) return sendJson(res, 400, { error: 'bad id' });
|
|
822
|
+
const meta = readMeta(id);
|
|
823
|
+
if (!meta) return sendJson(res, 404, { error: 'model not found' });
|
|
824
|
+
const corpus = {
|
|
825
|
+
lines: Array.isArray(body.lines) ? body.lines.filter((x) => typeof x === 'string' && x.trim()) : [],
|
|
826
|
+
pairs: Array.isArray(body.pairs)
|
|
827
|
+
? body.pairs.filter((p) => p && typeof p.assistant === 'string' && p.assistant.trim())
|
|
828
|
+
: [],
|
|
829
|
+
};
|
|
830
|
+
writeCorpus(id, corpus);
|
|
831
|
+
return sendJson(res, 200, { ok: true, total: corpus.lines.length + corpus.pairs.length });
|
|
832
|
+
}
|
|
798
833
|
if (path === '/api/voice' && method === 'GET') {
|
|
799
834
|
const id = String(url.searchParams.get('id') ?? '');
|
|
800
835
|
const meta = id ? readMeta(id) : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-live2dwaifu",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "给 DSH 网页右下角请一个 Live2D 角色:会眨眼、会呼吸、视线跟着鼠标、点她有反应、说话时嘴动、心情变了眉毛跟着动。模型不用内置——谁装谁放自己的文件夹;支持自定义 .exp3.json 表情和 .motion3.json 动作,卡片能拖能缩,背景可换。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|