dsh-live2dwaifu 0.8.0 → 0.8.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 +3 -1
- package/lib/index.js +86 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
|
|
34
34
|
**也能直接跟她说**:卡片底下有个小框,打一句回车,她回你。这条线**不进你的聊天记录**——后台直接问模型,往来只存在 `models/<id>/chat.json` 里。她说的话按人设卡现场写,不是复读;人设卡就是 `models/<id>/meta.json` 里的 `persona` 字段,没写就用默认那份(若妍)。
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
**她记得住,而且分两层记**:一层是最近二十来句原文,一层是攒出来的「关于你的事实」——名字、作息、口味、忌讳、答应过的事,聊得越久越攒得住,不用你重新讲一遍。攒满三十句就把前面压成小结,不丢。
|
|
37
|
+
|
|
38
|
+
**她说的话会过一道筛子**:套话会被删掉——“好的”“我理解你的感受”“希望帮到你”“还有什么需要”这些,出来就没。最多留两三句,允许她只回两个字。
|
|
37
39
|
|
|
38
40
|
**她知道几点、也知道自己此刻在干什么**:提示词里会让她按人设推断这个时间点的处境——早上在实验室、半夜还没睡——话里自然带着那个味道,不用你提醒。
|
|
39
41
|
|
package/lib/index.js
CHANGED
|
@@ -191,9 +191,10 @@ function readChatStore(id) {
|
|
|
191
191
|
return {
|
|
192
192
|
messages: Array.isArray(d.messages) ? d.messages : [],
|
|
193
193
|
summary: typeof d.summary === 'string' ? d.summary : '',
|
|
194
|
+
facts: Array.isArray(d.facts) ? d.facts : [],
|
|
194
195
|
};
|
|
195
196
|
} catch {
|
|
196
|
-
return { messages: [], summary: '' };
|
|
197
|
+
return { messages: [], summary: '', facts: [] };
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -201,33 +202,99 @@ function readChat(id) {
|
|
|
201
202
|
return readChatStore(id).messages;
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
function writeChat(id, messages, summary) {
|
|
205
|
+
function writeChat(id, messages, summary, facts) {
|
|
205
206
|
try {
|
|
206
207
|
const prev = readChatStore(id);
|
|
207
208
|
const data = {
|
|
208
209
|
messages: messages.slice(-40),
|
|
209
210
|
summary: summary === undefined ? prev.summary : summary,
|
|
211
|
+
facts: (facts === undefined ? prev.facts : facts).slice(-60),
|
|
210
212
|
};
|
|
211
213
|
writeFileSync(join(MODELS, id, 'chat.json'), JSON.stringify(data, null, 2), 'utf8');
|
|
212
214
|
} catch {}
|
|
213
215
|
}
|
|
214
216
|
|
|
215
|
-
/**
|
|
217
|
+
/** 把压缩出来的两段拆开:小结 + 关于他的事实 */
|
|
218
|
+
function parseDigest(raw) {
|
|
219
|
+
let text = String(raw || '').trim();
|
|
220
|
+
let summary = text;
|
|
221
|
+
const facts = [];
|
|
222
|
+
const at = text.search(/事实\s*[::]/);
|
|
223
|
+
if (at >= 0) {
|
|
224
|
+
summary = text.slice(0, at).replace(/^小结\s*[::]?\s*/, '').trim();
|
|
225
|
+
const body = text.slice(at).replace(/^事实\s*[::]\s*/, '');
|
|
226
|
+
for (const line of body.split(/\n+/)) {
|
|
227
|
+
const item = line.replace(/^[-*·\s]+/, '').trim();
|
|
228
|
+
if (item.length >= 2 && item.length <= 60) facts.push(item);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
summary = summary.replace(/^小结\s*[::]?\s*/, '').trim();
|
|
232
|
+
return { summary, facts: facts.slice(0, 12) };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** 攒够了就把前二十句压成小结 + 事实,人物记得住更早的事 */
|
|
216
236
|
async function compactChat(slot, store) {
|
|
217
237
|
const head = store.messages.slice(0, 20);
|
|
218
238
|
const tail = store.messages.slice(20);
|
|
219
239
|
if (!head.length) return store;
|
|
220
240
|
const text = head.map((m) => (m.role === 'user' ? '他:' : '你:') + String(m.text ?? '')).join('\n');
|
|
221
241
|
const prompt =
|
|
222
|
-
'
|
|
223
|
-
|
|
242
|
+
'下面是一段聊天记录。请用两段输出:\n' +
|
|
243
|
+
'第一段以「小结:」开头,把这段聊了什么压成不超过一百字的第三人称概述,只留事实和情绪走向。\n' +
|
|
244
|
+
'第二段以「事实:」开头,逐行列出从对话里能确定的、关于「他」的稳定信息(名字、作息、口味、忌讳、答应过的事、反复提到的人或物),' +
|
|
245
|
+
'每条一行、以短横线开头、不超过二十字;不确定的不要编。没有就写「事实:无」。\n\n' + text;
|
|
246
|
+
let raw = '';
|
|
224
247
|
try {
|
|
225
|
-
|
|
248
|
+
raw = await generate(slot, prompt, 900);
|
|
226
249
|
} catch {
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
const
|
|
230
|
-
|
|
250
|
+
raw = '';
|
|
251
|
+
}
|
|
252
|
+
const digest = parseDigest(raw);
|
|
253
|
+
const summary = [store.summary, digest.summary].filter(Boolean).join('\n').slice(-1200);
|
|
254
|
+
const merged = [...(store.facts || [])];
|
|
255
|
+
for (const f of digest.facts) {
|
|
256
|
+
if (f === '无') continue;
|
|
257
|
+
if (!merged.some((x) => x === f)) merged.push(f);
|
|
258
|
+
}
|
|
259
|
+
return { messages: tail, summary, facts: merged.slice(-60) };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** 跟他此刻说的话最贴的几条事实 */
|
|
263
|
+
function pickFacts(facts, text) {
|
|
264
|
+
if (!facts || !facts.length) return [];
|
|
265
|
+
const chars = new Set(String(text || '').replace(/[\s,。!?、,.!?]/g, ''));
|
|
266
|
+
const scored = facts.map((f) => {
|
|
267
|
+
let hit = 0;
|
|
268
|
+
for (const ch of String(f)) if (chars.has(ch)) hit += 1;
|
|
269
|
+
return { f, hit };
|
|
270
|
+
});
|
|
271
|
+
scored.sort((a, b) => b.hit - a.hit);
|
|
272
|
+
const top = scored.filter((s) => s.hit > 0).slice(0, 6).map((s) => s.f);
|
|
273
|
+
return top.length ? top : facts.slice(-6);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** 去 AI 味:删套话、砍尾巴、压重复 */
|
|
277
|
+
const AI_WORDS = [
|
|
278
|
+
/^(好的|当然|没问题|明白了|收到)[,,。!!、\s]+/,
|
|
279
|
+
/(希望|但愿)[^。!?]{0,12}帮(到|上)你[^。!?]*[。!!]?/g,
|
|
280
|
+
/还有什么(需要|想要|问题|可以帮|能帮)[^。!?]*[。!?]?/g,
|
|
281
|
+
/(总之|总的来说|综上|先说结论|首先|其次|另外|值得一提的是)[,,::]?/g,
|
|
282
|
+
/作为(一个)?(AI|人工智能|语言模型)[^。!?]*[。!?]?/g,
|
|
283
|
+
/(?:^|[。!?])(我理解你的感受|我很抱歉听到|这一定很不容易)[^。!?]*[。!?]?/g,
|
|
284
|
+
];
|
|
285
|
+
|
|
286
|
+
function humanize(input) {
|
|
287
|
+
let t = String(input || '');
|
|
288
|
+
for (const re of AI_WORDS) t = t.replace(re, '');
|
|
289
|
+
t = t
|
|
290
|
+
.replace(/[ \t]+/g, ' ')
|
|
291
|
+
.replace(/[。!?]{2,}/g, (m) => m[0])
|
|
292
|
+
.replace(/…{3,}/g, '……')
|
|
293
|
+
.replace(/[,,]\s*$/g, '')
|
|
294
|
+
.trim();
|
|
295
|
+
const parts = t.split(/(?<=[。!?!?~])/).filter((s) => s.trim());
|
|
296
|
+
if (parts.length > 3) t = parts.slice(0, 3).join('');
|
|
297
|
+
return t.trim();
|
|
231
298
|
}
|
|
232
299
|
|
|
233
300
|
/* ── 出口队列:谁能说话,都往这儿扔 ───────────────────────── */
|
|
@@ -316,7 +383,7 @@ async function maybeTalk(ctx) {
|
|
|
316
383
|
return;
|
|
317
384
|
}
|
|
318
385
|
if (!text) return;
|
|
319
|
-
const line = text.
|
|
386
|
+
const line = humanize(text).slice(0, 120);
|
|
320
387
|
pushOut({ text: line, from: 'proactive' });
|
|
321
388
|
writeChat(id, readChat(id).concat([{ role: 'assistant', text: line, at: Date.now() }]));
|
|
322
389
|
}
|
|
@@ -516,9 +583,10 @@ async function handle(req, res, ctx) {
|
|
|
516
583
|
if (st.memory && store.messages.length > 30) {
|
|
517
584
|
try {
|
|
518
585
|
store = await compactChat(slot, store);
|
|
519
|
-
writeChat(id, store.messages, store.summary);
|
|
586
|
+
writeChat(id, store.messages, store.summary, store.facts);
|
|
520
587
|
} catch {}
|
|
521
588
|
}
|
|
589
|
+
const facts = st.memory ? pickFacts(store.facts, text) : [];
|
|
522
590
|
const hist = store.messages;
|
|
523
591
|
const lines = hist
|
|
524
592
|
.slice(-8)
|
|
@@ -529,9 +597,12 @@ async function handle(req, res, ctx) {
|
|
|
529
597
|
meta.isolation === false ? '' : ISOLATION,
|
|
530
598
|
'现在是 ' + nowText() + '。按你这个人设,这个时间点你多半在做什么?让这句话带上那个处境,但不要说破。',
|
|
531
599
|
store.summary ? '之前聊过的,你还记得:\n' + store.summary : '',
|
|
600
|
+
facts.length ? '关于他,你记得这些:\n' + facts.map((f) => '- ' + f).join('\n') : '',
|
|
532
601
|
lines ? '你们之前说的:\n' + lines : '',
|
|
533
602
|
'他说:' + text,
|
|
534
|
-
'
|
|
603
|
+
'像人发消息那样回他:一到两句,短;不总结、不分点、不讲道理、不解释心情;' +
|
|
604
|
+
'不说“我理解你”“希望能帮到你”“还有什么需要”这类话;他没问就别给建议;可以只回两个字。' +
|
|
605
|
+
'只输出这句话本身:不要引号,不要旁白,不要括号里的动作。',
|
|
535
606
|
st.choices ? '如果你想让他挑,可以在最后另起一行写 ||选项一|选项二,最多三项。' : '',
|
|
536
607
|
st.memesMode === 'own' || st.memesMode === 'both'
|
|
537
608
|
? '如果你想附一张贴图,就在最后另起一行写 ||图:名字。能挑的名字有:' +
|
|
@@ -552,6 +623,7 @@ async function handle(req, res, ctx) {
|
|
|
552
623
|
return sendJson(res, 502, { error: '生成失败:' + (e instanceof Error ? e.message : String(e)) });
|
|
553
624
|
}
|
|
554
625
|
LAST_ACTIVE.at = Date.now();
|
|
626
|
+
reply = humanize(reply);
|
|
555
627
|
const parsed = splitMeta(reply || '');
|
|
556
628
|
let line = parsed.text;
|
|
557
629
|
let choices = st.choices ? parsed.choices : [];
|
|
@@ -573,7 +645,7 @@ async function handle(req, res, ctx) {
|
|
|
573
645
|
writeChat(id, hist.concat([
|
|
574
646
|
{ role: 'user', text, at: Date.now() },
|
|
575
647
|
{ role: 'assistant', text: line, at: Date.now() },
|
|
576
|
-
]), store.summary);
|
|
648
|
+
]), store.summary, store.facts);
|
|
577
649
|
return sendJson(res, 200, { ok: true, reply: line, choices, image: image || undefined, id });
|
|
578
650
|
}
|
|
579
651
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-live2dwaifu",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "给 DSH 网页右下角请一个 Live2D 角色:会眨眼、会呼吸、视线跟着鼠标、点她有反应、说话时嘴动、心情变了眉毛跟着动。模型不用内置——谁装谁放自己的文件夹;支持自定义 .exp3.json 表情和 .motion3.json 动作,卡片能拖能缩,背景可换。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|