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 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 = Math.max(
44421
- 0,
44422
- GAP_VALUES.indexOf(Number(serverSettings.proactiveInterval ?? 30))
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 chars = new Set(String(text || ''));
210
- const scored = (corpus.pairs || [])
211
- .map((p) => {
212
- let hit = 0;
213
- for (const ch of String(p.user || '')) if (chars.has(ch)) hit += 1;
214
- return { p, hit };
215
- })
216
- .sort((a, b) => b.hit - a.hit);
217
- for (const s of scored.slice(0, 2)) {
218
- if (s.p.user) out.push('他:' + s.p.user + ' —— 你:' + s.p.assistant);
219
- else out.push('你:' + s.p.assistant);
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.length ? top : facts.slice(-6);
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
- let t = String(input || '');
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 = st.memory ? pickCorpusSamples(readCorpus(id), text) : [];
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
- writeChat(id, hist.concat([
702
- { role: 'user', text, at: Date.now() },
703
- { role: 'assistant', text: line, at: Date.now() },
704
- ]), store.summary, store.facts);
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.0",
3
+ "version": "1.3.1",
4
4
  "description": "给 DSH 网页右下角请一个 Live2D 角色:会眨眼、会呼吸、视线跟着鼠标、点她有反应、说话时嘴动、心情变了眉毛跟着动。模型不用内置——谁装谁放自己的文件夹;支持自定义 .exp3.json 表情和 .motion3.json 动作,卡片能拖能缩,背景可换。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",