dsh-live2dwaifu 1.2.2 → 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 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,13 @@ 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
+
383
390
  ### 1.2.2
384
391
 
385
392
  - 人设卡、音色、语料三个页面的按钮和说明改成通用说法:保存/追加保存/覆盖保存/放弃修改/选择文件/播放/关掉。
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 = 4) {
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, 3)) {
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
- if (!settingsOf(cfg).proactive) return;
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 < IDLE_MS) return;
409
- if (now - lastAutoAt < IDLE_MS) return;
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
- const prompt = [
648
- personaOf(meta),
649
- meta.isolation === false ? '' : ISOLATION,
650
- '现在是 ' + nowText() + '。',
651
- store.summary ? '之前聊过的,你还记得:\n' + store.summary : '',
652
- facts.length ? '关于他,你记得这些:\n' + facts.map((f) => '- ' + f).join('\n') : '',
653
- lines ? '你们之前说的:\n' + lines : '',
654
- '他说:' + text,
655
- '照上面这个人说话,回他一句。',
656
- '一到两句,四十字以内。要具体:答应、拒绝、反问、吐槽、提一件正在做的事,都行。' +
657
- '别总结他的话,别讲道理,别说“我理解你”“希望帮到你”“还需要什么”;他没问就别建议。' +
658
- '可以不接他的话,扔一句自己的事;允许只回两个字。',
659
- '只输出这句话本身:不要引号,不要旁白,不要括号里的动作。',
660
- '往下是这个人的说话密度,只学节奏和信息量,别照抄内容:\n' + TALK_SAMPLES,
661
- st.choices ? '如果你想让他挑,可以在最后另起一行写 ||选项一|选项二,最多三项。' : '',
662
- st.memesMode === 'own' || st.memesMode === 'both'
663
- ? '如果你想附一张贴图,就在最后另起一行写 ||图:名字。能挑的名字有:' +
664
- readOwnMemes()
665
- .map((it) => it.label)
666
- .filter(Boolean)
667
- .join('、') +
668
- '。不想配就别写。'
669
- : '',
670
- ]
671
- .filter(Boolean)
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.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "给 DSH 网页右下角请一个 Live2D 角色:会眨眼、会呼吸、视线跟着鼠标、点她有反应、说话时嘴动、心情变了眉毛跟着动。模型不用内置——谁装谁放自己的文件夹;支持自定义 .exp3.json 表情和 .motion3.json 动作,卡片能拖能缩,背景可换。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",