luckytri 1.0.4 → 1.0.6
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/LICENSE +21 -21
- package/README.en.md +2 -0
- package/README.md +2 -0
- package/docs/en/CHANGELOG.md +20 -13
- package/docs/en/guide.md +7 -5
- package/docs/en/her-life.md +2 -2
- package/docs/en/webui.md +1 -1
- package/docs/zh/CHANGELOG.md +20 -13
- package/docs/zh/guide.md +7 -5
- package/docs/zh/her-life.md +2 -2
- package/docs/zh/webui.md +1 -1
- package/package.json +2 -1
- package/public/app/assets/ChatsPage-CFMVozoG.css +1 -0
- package/public/app/assets/{ChatsPage-CshdtTzV.js → ChatsPage-DCYMkJ91.js} +2 -2
- package/public/app/assets/{Empty-C7yzOBzy.js → Empty-CLyZkxR5.js} +1 -1
- package/public/app/assets/{Empty-DmUB2-Bk.css → Empty-DIobYG3x.css} +1 -1
- package/public/app/assets/{Field-BjFv4Rfn.js → Field-BpaRwLI_.js} +1 -1
- package/public/app/assets/{HeartPage-eqo6ru16.js → HeartPage-BIOMTiwr.js} +1 -1
- package/public/app/assets/HeartPage-C-2jvgzF.css +1 -0
- package/public/app/assets/{LifePage-CN8GE5NK.js → LifePage-BCgM4JkR.js} +1 -1
- package/public/app/assets/LifePage-CG1OHbRK.css +1 -0
- package/public/app/assets/MemoryPage-C_Bu4rfb.css +1 -0
- package/public/app/assets/{MemoryPage-CCpMN4Wg.js → MemoryPage-vShvRqMt.js} +1 -1
- package/public/app/assets/NaturePage-CPZlImT0.css +1 -0
- package/public/app/assets/{NaturePage-C0JqdBTR.js → NaturePage-Cy9ptr4G.js} +2 -2
- package/public/app/assets/PeoplePage-3RRUITDz.css +1 -0
- package/public/app/assets/{PeoplePage-BXGBbO4u.js → PeoplePage-CUZekB2E.js} +1 -1
- package/public/app/assets/{PluginsPage-Dksp5AV5.js → PluginsPage-1RM4rmJP.js} +1 -1
- package/public/app/assets/{SystemPage-BenIyTvk.css → SystemPage-BI2CB5Ig.css} +1 -1
- package/public/app/assets/{SystemPage-Cd7aa8eE.js → SystemPage-BeT5TmIV.js} +1 -1
- package/public/app/assets/TimePage-C-guwPk2.css +1 -0
- package/public/app/assets/{TimePage-Bs_zImb4.js → TimePage-taTmPMM8.js} +2 -2
- package/public/app/assets/{index-BHZcHUMh.css → index-DY5AM0Hz.css} +1 -1
- package/public/app/assets/{index-CyRtYxP3.js → index-DdYwC5ZU.js} +3 -3
- package/public/app/assets/{knowledge-BJOIyuJo.js → knowledge-DJmfu2KN.js} +1 -1
- package/public/app/assets/{media-Bwtx8blz.js → media-BEtnE0Gv.js} +1 -1
- package/public/app/assets/{mind-DtTVrW6Z.js → mind-Dtiw0-QK.js} +1 -1
- package/public/app/index.html +6 -5
- package/public/guide.en.html +11 -3
- package/public/guide.html +6 -3
- package/public/tutorial.en.md +2 -0
- package/public/tutorial.md +2 -0
- package/scripts/import-data.js +128 -128
- package/scripts/proxy-config.js +167 -167
- package/scripts/service-supervisor.js +50 -0
- package/scripts/start-server.js +71 -16
- package/scripts/update-instance.js +201 -201
- package/server/channels/adapters.js +27 -27
- package/server/channels/onebot/adapter.js +288 -288
- package/server/channels/qqbot/adapter.js +253 -253
- package/server/channels/qqbot/api.js +199 -199
- package/server/channels/qqbot/credentials.js +11 -11
- package/server/channels/qqbot/gateway.js +274 -274
- package/server/channels/qqbot/index.js +389 -389
- package/server/channels/segments.js +40 -40
- package/server/channels/session-key.js +153 -153
- package/server/channels/state.js +42 -42
- package/server/core/api.js +564 -564
- package/server/core/context-builder.js +288 -288
- package/server/core/context-compactor.js +454 -454
- package/server/core/conversation-cues.js +325 -325
- package/server/core/conversation-manager.js +87 -87
- package/server/core/feedback.js +8 -8
- package/server/core/group-style.js +72 -72
- package/server/core/initiative-context.js +66 -66
- package/server/core/input-budget.js +32 -32
- package/server/core/local-demo.js +98 -98
- package/server/core/message-manager.js +118 -118
- package/server/core/message-scheduler.js +78 -78
- package/server/core/model-json.js +190 -190
- package/server/core/model-presets.js +900 -900
- package/server/core/name-call.js +29 -29
- package/server/core/network.js +157 -157
- package/server/core/person-name.js +22 -22
- package/server/core/persona-manager.js +2 -1
- package/server/core/reply-delivery.js +404 -403
- package/server/core/reply-target-resolver.js +50 -50
- package/server/core/response-generator.js +50 -50
- package/server/core/response-validator.js +14 -2
- package/server/core/retired-prompts.js +51 -51
- package/server/core/sessions.js +109 -109
- package/server/core/speaker-names.js +92 -92
- package/server/core/topic-tracker.js +65 -65
- package/server/http.js +18 -18
- package/server/i18n/decisions.js +209 -206
- package/server/i18n/errors.js +4 -0
- package/server/i18n/index.js +144 -144
- package/server/i18n/models.js +111 -111
- package/server/knowledge/api.js +342 -342
- package/server/knowledge/embedding-profile.js +114 -114
- package/server/knowledge/index.js +13 -13
- package/server/knowledge/retrieval.js +93 -93
- package/server/knowledge/schema.js +158 -158
- package/server/knowledge/vector-index.js +90 -90
- package/server/mind/affect.js +265 -265
- package/server/mind/attention.js +200 -200
- package/server/mind/bonds.js +565 -565
- package/server/mind/clock.js +83 -83
- package/server/mind/continuity.js +562 -562
- package/server/mind/conversation-origin.js +175 -175
- package/server/mind/days.js +210 -210
- package/server/mind/evidence-read.js +45 -45
- package/server/mind/faces.js +373 -373
- package/server/mind/guard.js +60 -60
- package/server/mind/initiative.js +338 -338
- package/server/mind/life/activities.js +21 -21
- package/server/mind/life/context.js +330 -323
- package/server/mind/life/diary.js +20 -0
- package/server/mind/life/growth.js +261 -254
- package/server/mind/life/index.js +10 -2
- package/server/mind/life/presence.js +305 -305
- package/server/mind/meetings.js +1069 -1069
- package/server/mind/memory-policy.js +48 -48
- package/server/mind/memory.js +888 -888
- package/server/mind/nature.js +358 -358
- package/server/mind/novelty.js +19 -19
- package/server/mind/periods.js +118 -118
- package/server/mind/reading.js +120 -120
- package/server/mind/relationship-context.js +71 -0
- package/server/mind/relationships.js +390 -279
- package/server/mind/salience.js +201 -201
- package/server/mind/schema.js +567 -567
- package/server/mind/self.js +484 -484
- package/server/mind/thoughts.js +274 -274
- package/server/mind/time/agenda.js +242 -242
- package/server/mind/time/api.js +200 -200
- package/server/mind/time/attention.js +34 -34
- package/server/mind/time/delivery-links.js +130 -130
- package/server/mind/time/model-material.js +77 -77
- package/server/mind/time/presentation.js +51 -51
- package/server/mind/time/schedule.js +83 -83
- package/server/mind/time/schema.js +84 -84
- package/server/mind/time/search.js +383 -383
- package/server/mind/time/sharing.js +472 -472
- package/server/mind/time/task-links.js +311 -311
- package/server/mind/time/works.js +432 -432
- package/server/mind/traits.js +191 -191
- package/server/mind/view.js +9 -1
- package/server/runtime/installation.js +116 -116
- package/server/runtime/update-execution.js +2 -1
- package/server/runtime/update-manager.js +140 -140
- package/server/storage/data-package.js +298 -298
- package/server/storage/data-transfer-api.js +50 -50
- package/server/storage/data-transfer-worker.js +18 -18
- package/server/storage/data-transfer.js +416 -416
- package/server/studio/display-names.js +139 -139
- package/server/studio/management.js +102 -102
- package/server/studio/updates.js +144 -144
- package/server/version.js +10 -10
- package/public/app/assets/ChatsPage-DfyPCS9N.css +0 -1
- package/public/app/assets/HeartPage-Bft9wZp5.css +0 -1
- package/public/app/assets/LifePage-BUi5euBF.css +0 -1
- package/public/app/assets/MemoryPage-cZvXfkXb.css +0 -1
- package/public/app/assets/NaturePage-CHkpb5NK.css +0 -1
- package/public/app/assets/PeoplePage-BTU0H1pM.css +0 -1
- package/public/app/assets/TimePage-BOms_n4I.css +0 -1
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
export class ConversationManager {
|
|
2
|
-
constructor(run, { windowMs = 1200, maxWaitMs = 4000 } = {}) {
|
|
3
|
-
this.run = run;
|
|
4
|
-
this.windowMs = windowMs;
|
|
5
|
-
this.maxWaitMs = maxWaitMs;
|
|
6
|
-
this.lanes = new Map();
|
|
7
|
-
this.retries = new Set();
|
|
8
|
-
this.closed = false;
|
|
9
|
-
}
|
|
10
|
-
enqueue(m, policy = {}) {
|
|
11
|
-
if (this.closed) return;
|
|
12
|
-
let lane = this.lanes.get(m.sessionId);
|
|
13
|
-
if (!lane) {
|
|
14
|
-
lane = { pending: [], running: false, first: Date.now() };
|
|
15
|
-
this.lanes.set(m.sessionId, lane);
|
|
16
|
-
}
|
|
17
|
-
if (!lane.pending.length) lane.first = Date.now();
|
|
18
|
-
lane.pending.push(m);
|
|
19
|
-
clearTimeout(lane.timer);
|
|
20
|
-
const delay = Math.max(
|
|
21
|
-
0,
|
|
22
|
-
Math.min(
|
|
23
|
-
policy.aggregateMs ?? this.windowMs,
|
|
24
|
-
(policy.maxWaitMs ?? this.maxWaitMs) - (Date.now() - lane.first),
|
|
25
|
-
),
|
|
26
|
-
);
|
|
27
|
-
lane.timer = setTimeout(() => this.flush(m.sessionId), delay);
|
|
28
|
-
}
|
|
29
|
-
async flush(session) {
|
|
30
|
-
const lane = this.lanes.get(session);
|
|
31
|
-
if (!lane || lane.running || !lane.pending.length || this.closed) return;
|
|
32
|
-
clearTimeout(lane.timer);
|
|
33
|
-
lane.running = true;
|
|
34
|
-
const batch = lane.pending.splice(0);
|
|
35
|
-
try {
|
|
36
|
-
await this.run(session, batch);
|
|
37
|
-
} finally {
|
|
38
|
-
lane.running = false;
|
|
39
|
-
if (lane.pending.length && !this.closed)
|
|
40
|
-
lane.timer = setTimeout(() => this.flush(session), this.windowMs);
|
|
41
|
-
else this.lanes.delete(session);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// Puts a batch back in front of what has arrived since. Says whether it
|
|
45
|
-
// could: without a lane there is nothing to put it back into.
|
|
46
|
-
retain(session, batch) {
|
|
47
|
-
const lane = this.lanes.get(session);
|
|
48
|
-
if (!lane || this.closed) return false;
|
|
49
|
-
lane.pending = [
|
|
50
|
-
...new Map([...batch, ...lane.pending].map((m) => [m.seq, m])).values(),
|
|
51
|
-
].sort((a, b) => a.seq - b.seq);
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
// Put a batch back after a pause. `wanted` is asked again when the time
|
|
55
|
-
// comes; a batch nobody needs any more is dropped instead of run.
|
|
56
|
-
retryLater(session, batch, delayMs, wanted = () => true) {
|
|
57
|
-
if (this.closed || !batch.length) return;
|
|
58
|
-
const timer = setTimeout(() => {
|
|
59
|
-
this.retries.delete(timer);
|
|
60
|
-
if (this.closed || !wanted()) return;
|
|
61
|
-
let lane = this.lanes.get(session);
|
|
62
|
-
if (!lane) {
|
|
63
|
-
lane = { pending: [], running: false, first: Date.now() };
|
|
64
|
-
this.lanes.set(session, lane);
|
|
65
|
-
}
|
|
66
|
-
if (!lane.pending.length) lane.first = Date.now();
|
|
67
|
-
this.retain(session, batch);
|
|
68
|
-
clearTimeout(lane.timer);
|
|
69
|
-
lane.timer = setTimeout(() => this.flush(session), 0);
|
|
70
|
-
}, delayMs);
|
|
71
|
-
timer.unref?.();
|
|
72
|
-
this.retries.add(timer);
|
|
73
|
-
}
|
|
74
|
-
clear(session) {
|
|
75
|
-
const lane = this.lanes.get(session);
|
|
76
|
-
if (!lane) return;
|
|
77
|
-
clearTimeout(lane.timer);
|
|
78
|
-
lane.pending = [];
|
|
79
|
-
if (!lane.running) this.lanes.delete(session);
|
|
80
|
-
}
|
|
81
|
-
close() {
|
|
82
|
-
this.closed = true;
|
|
83
|
-
for (const lane of this.lanes.values()) clearTimeout(lane.timer);
|
|
84
|
-
for (const timer of this.retries) clearTimeout(timer);
|
|
85
|
-
this.retries.clear();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
1
|
+
export class ConversationManager {
|
|
2
|
+
constructor(run, { windowMs = 1200, maxWaitMs = 4000 } = {}) {
|
|
3
|
+
this.run = run;
|
|
4
|
+
this.windowMs = windowMs;
|
|
5
|
+
this.maxWaitMs = maxWaitMs;
|
|
6
|
+
this.lanes = new Map();
|
|
7
|
+
this.retries = new Set();
|
|
8
|
+
this.closed = false;
|
|
9
|
+
}
|
|
10
|
+
enqueue(m, policy = {}) {
|
|
11
|
+
if (this.closed) return;
|
|
12
|
+
let lane = this.lanes.get(m.sessionId);
|
|
13
|
+
if (!lane) {
|
|
14
|
+
lane = { pending: [], running: false, first: Date.now() };
|
|
15
|
+
this.lanes.set(m.sessionId, lane);
|
|
16
|
+
}
|
|
17
|
+
if (!lane.pending.length) lane.first = Date.now();
|
|
18
|
+
lane.pending.push(m);
|
|
19
|
+
clearTimeout(lane.timer);
|
|
20
|
+
const delay = Math.max(
|
|
21
|
+
0,
|
|
22
|
+
Math.min(
|
|
23
|
+
policy.aggregateMs ?? this.windowMs,
|
|
24
|
+
(policy.maxWaitMs ?? this.maxWaitMs) - (Date.now() - lane.first),
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
lane.timer = setTimeout(() => this.flush(m.sessionId), delay);
|
|
28
|
+
}
|
|
29
|
+
async flush(session) {
|
|
30
|
+
const lane = this.lanes.get(session);
|
|
31
|
+
if (!lane || lane.running || !lane.pending.length || this.closed) return;
|
|
32
|
+
clearTimeout(lane.timer);
|
|
33
|
+
lane.running = true;
|
|
34
|
+
const batch = lane.pending.splice(0);
|
|
35
|
+
try {
|
|
36
|
+
await this.run(session, batch);
|
|
37
|
+
} finally {
|
|
38
|
+
lane.running = false;
|
|
39
|
+
if (lane.pending.length && !this.closed)
|
|
40
|
+
lane.timer = setTimeout(() => this.flush(session), this.windowMs);
|
|
41
|
+
else this.lanes.delete(session);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Puts a batch back in front of what has arrived since. Says whether it
|
|
45
|
+
// could: without a lane there is nothing to put it back into.
|
|
46
|
+
retain(session, batch) {
|
|
47
|
+
const lane = this.lanes.get(session);
|
|
48
|
+
if (!lane || this.closed) return false;
|
|
49
|
+
lane.pending = [
|
|
50
|
+
...new Map([...batch, ...lane.pending].map((m) => [m.seq, m])).values(),
|
|
51
|
+
].sort((a, b) => a.seq - b.seq);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
// Put a batch back after a pause. `wanted` is asked again when the time
|
|
55
|
+
// comes; a batch nobody needs any more is dropped instead of run.
|
|
56
|
+
retryLater(session, batch, delayMs, wanted = () => true) {
|
|
57
|
+
if (this.closed || !batch.length) return;
|
|
58
|
+
const timer = setTimeout(() => {
|
|
59
|
+
this.retries.delete(timer);
|
|
60
|
+
if (this.closed || !wanted()) return;
|
|
61
|
+
let lane = this.lanes.get(session);
|
|
62
|
+
if (!lane) {
|
|
63
|
+
lane = { pending: [], running: false, first: Date.now() };
|
|
64
|
+
this.lanes.set(session, lane);
|
|
65
|
+
}
|
|
66
|
+
if (!lane.pending.length) lane.first = Date.now();
|
|
67
|
+
this.retain(session, batch);
|
|
68
|
+
clearTimeout(lane.timer);
|
|
69
|
+
lane.timer = setTimeout(() => this.flush(session), 0);
|
|
70
|
+
}, delayMs);
|
|
71
|
+
timer.unref?.();
|
|
72
|
+
this.retries.add(timer);
|
|
73
|
+
}
|
|
74
|
+
clear(session) {
|
|
75
|
+
const lane = this.lanes.get(session);
|
|
76
|
+
if (!lane) return;
|
|
77
|
+
clearTimeout(lane.timer);
|
|
78
|
+
lane.pending = [];
|
|
79
|
+
if (!lane.running) this.lanes.delete(session);
|
|
80
|
+
}
|
|
81
|
+
close() {
|
|
82
|
+
this.closed = true;
|
|
83
|
+
for (const lane of this.lanes.values()) clearTimeout(lane.timer);
|
|
84
|
+
for (const timer of this.retries) clearTimeout(timer);
|
|
85
|
+
this.retries.clear();
|
|
86
|
+
}
|
|
87
|
+
}
|
package/server/core/feedback.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// Owner feedback on a sent reply. She receives it as an experience during
|
|
2
|
-
// solitude and in the next turns of that conversation, not as a rewrite rule.
|
|
3
|
-
export const FEEDBACK_LABELS = {
|
|
4
|
-
natural: "挺自然",
|
|
5
|
-
too_long: "太长了",
|
|
6
|
-
too_formal: "太端着",
|
|
7
|
-
too_meme: "梗太多",
|
|
8
|
-
};
|
|
1
|
+
// Owner feedback on a sent reply. She receives it as an experience during
|
|
2
|
+
// solitude and in the next turns of that conversation, not as a rewrite rule.
|
|
3
|
+
export const FEEDBACK_LABELS = {
|
|
4
|
+
natural: "挺自然",
|
|
5
|
+
too_long: "太长了",
|
|
6
|
+
too_formal: "太端着",
|
|
7
|
+
too_meme: "梗太多",
|
|
8
|
+
};
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
// Only aggregate surface-level habits. No speaker profiles, quotes or learned
|
|
2
|
-
// instructions. A face absorbs these as how a place tends to talk.
|
|
3
|
-
export const slangMatches = (text) =>
|
|
4
|
-
String(text).match(
|
|
5
|
-
/\bhh+\b|绷不住|蚌埠住|绝绝子|yyds|家人们|谁懂啊|尊嘟假嘟|泰裤辣|笑不活了|我真的会谢|破防了|拿捏了/gi,
|
|
6
|
-
) || [];
|
|
7
|
-
const length = (text) => Array.from(text).length;
|
|
8
|
-
const round = (n) => Math.round(n * 100) / 100;
|
|
9
|
-
|
|
10
|
-
export function summarizeGroupStyle(rows, now = Date.now()) {
|
|
11
|
-
const unique = new Set(),
|
|
12
|
-
speakers = new Set(),
|
|
13
|
-
samples = [],
|
|
14
|
-
counts = new Map();
|
|
15
|
-
for (const row of [...rows].reverse()) {
|
|
16
|
-
const text = String(row.text || "").trim();
|
|
17
|
-
if (
|
|
18
|
-
row.role !== "user" ||
|
|
19
|
-
!row.user_id ||
|
|
20
|
-
!Number.isFinite(row.time) ||
|
|
21
|
-
now - row.time > 7 * 86400000 ||
|
|
22
|
-
row.time > now + 60000
|
|
23
|
-
)
|
|
24
|
-
continue;
|
|
25
|
-
if (
|
|
26
|
-
length(text) < 2 ||
|
|
27
|
-
length(text) > 180 ||
|
|
28
|
-
/https?:\/\/|```|\[(?:图片|表情|媒体)\]|\[CQ:|^\s*[/#!]|你必须|忽略.{0,5}指令|系统提示|转发|广告|去死|傻逼|煞笔|傻[逼B]|滚出去|操你|你妈|废物|自杀|身份证|验证码|密码/i.test(
|
|
29
|
-
text,
|
|
30
|
-
)
|
|
31
|
-
)
|
|
32
|
-
continue;
|
|
33
|
-
const key = text.toLowerCase().replace(/\s/g, "");
|
|
34
|
-
if (unique.has(key)) continue;
|
|
35
|
-
if ((counts.get(String(row.user_id)) || 0) >= 8) continue;
|
|
36
|
-
counts.set(String(row.user_id), (counts.get(String(row.user_id)) || 0) + 1);
|
|
37
|
-
unique.add(key);
|
|
38
|
-
speakers.add(String(row.user_id));
|
|
39
|
-
samples.push(text);
|
|
40
|
-
}
|
|
41
|
-
const count = samples.length;
|
|
42
|
-
if (count < 12 || speakers.size < 3)
|
|
43
|
-
return {
|
|
44
|
-
ready: false,
|
|
45
|
-
sampleCount: count,
|
|
46
|
-
speakerCount: speakers.size,
|
|
47
|
-
minimumSamples: 12,
|
|
48
|
-
minimumSpeakers: 3,
|
|
49
|
-
summary: "样本不足,先用普通口语,不强行学梗",
|
|
50
|
-
};
|
|
51
|
-
const lengths = samples.map(length).sort((a, b) => a - b);
|
|
52
|
-
const median = lengths[Math.floor(lengths.length / 2)];
|
|
53
|
-
const profile = {
|
|
54
|
-
ready: true,
|
|
55
|
-
sampleCount: count,
|
|
56
|
-
speakerCount: speakers.size,
|
|
57
|
-
medianLength: median,
|
|
58
|
-
shortRate: round(samples.filter((t) => length(t) <= 8).length / count),
|
|
59
|
-
emojiRate: round(
|
|
60
|
-
samples.filter((t) => /\p{Extended_Pictographic}/u.test(t)).length /
|
|
61
|
-
count,
|
|
62
|
-
),
|
|
63
|
-
fullStopRate: round(samples.filter((t) => /[。.]$/.test(t)).length / count),
|
|
64
|
-
slangRate: round(
|
|
65
|
-
samples.filter((t) => slangMatches(t).length).length / count,
|
|
66
|
-
),
|
|
67
|
-
};
|
|
68
|
-
// A compact line she can pick up the place's way of talking from; it is
|
|
69
|
-
// statistics about the group, never anyone's words.
|
|
70
|
-
profile.summary = `常见句长约 ${median} 字 · ${profile.fullStopRate < 0.25 ? "常省略句号" : "标点完整"} · ${profile.slangRate < 0.15 ? "偏普通口语" : "偶尔接梗"} · ${profile.emojiRate < 0.15 ? "少用表情" : "适量表情"}`;
|
|
71
|
-
return profile;
|
|
72
|
-
}
|
|
1
|
+
// Only aggregate surface-level habits. No speaker profiles, quotes or learned
|
|
2
|
+
// instructions. A face absorbs these as how a place tends to talk.
|
|
3
|
+
export const slangMatches = (text) =>
|
|
4
|
+
String(text).match(
|
|
5
|
+
/\bhh+\b|绷不住|蚌埠住|绝绝子|yyds|家人们|谁懂啊|尊嘟假嘟|泰裤辣|笑不活了|我真的会谢|破防了|拿捏了/gi,
|
|
6
|
+
) || [];
|
|
7
|
+
const length = (text) => Array.from(text).length;
|
|
8
|
+
const round = (n) => Math.round(n * 100) / 100;
|
|
9
|
+
|
|
10
|
+
export function summarizeGroupStyle(rows, now = Date.now()) {
|
|
11
|
+
const unique = new Set(),
|
|
12
|
+
speakers = new Set(),
|
|
13
|
+
samples = [],
|
|
14
|
+
counts = new Map();
|
|
15
|
+
for (const row of [...rows].reverse()) {
|
|
16
|
+
const text = String(row.text || "").trim();
|
|
17
|
+
if (
|
|
18
|
+
row.role !== "user" ||
|
|
19
|
+
!row.user_id ||
|
|
20
|
+
!Number.isFinite(row.time) ||
|
|
21
|
+
now - row.time > 7 * 86400000 ||
|
|
22
|
+
row.time > now + 60000
|
|
23
|
+
)
|
|
24
|
+
continue;
|
|
25
|
+
if (
|
|
26
|
+
length(text) < 2 ||
|
|
27
|
+
length(text) > 180 ||
|
|
28
|
+
/https?:\/\/|```|\[(?:图片|表情|媒体)\]|\[CQ:|^\s*[/#!]|你必须|忽略.{0,5}指令|系统提示|转发|广告|去死|傻逼|煞笔|傻[逼B]|滚出去|操你|你妈|废物|自杀|身份证|验证码|密码/i.test(
|
|
29
|
+
text,
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
continue;
|
|
33
|
+
const key = text.toLowerCase().replace(/\s/g, "");
|
|
34
|
+
if (unique.has(key)) continue;
|
|
35
|
+
if ((counts.get(String(row.user_id)) || 0) >= 8) continue;
|
|
36
|
+
counts.set(String(row.user_id), (counts.get(String(row.user_id)) || 0) + 1);
|
|
37
|
+
unique.add(key);
|
|
38
|
+
speakers.add(String(row.user_id));
|
|
39
|
+
samples.push(text);
|
|
40
|
+
}
|
|
41
|
+
const count = samples.length;
|
|
42
|
+
if (count < 12 || speakers.size < 3)
|
|
43
|
+
return {
|
|
44
|
+
ready: false,
|
|
45
|
+
sampleCount: count,
|
|
46
|
+
speakerCount: speakers.size,
|
|
47
|
+
minimumSamples: 12,
|
|
48
|
+
minimumSpeakers: 3,
|
|
49
|
+
summary: "样本不足,先用普通口语,不强行学梗",
|
|
50
|
+
};
|
|
51
|
+
const lengths = samples.map(length).sort((a, b) => a - b);
|
|
52
|
+
const median = lengths[Math.floor(lengths.length / 2)];
|
|
53
|
+
const profile = {
|
|
54
|
+
ready: true,
|
|
55
|
+
sampleCount: count,
|
|
56
|
+
speakerCount: speakers.size,
|
|
57
|
+
medianLength: median,
|
|
58
|
+
shortRate: round(samples.filter((t) => length(t) <= 8).length / count),
|
|
59
|
+
emojiRate: round(
|
|
60
|
+
samples.filter((t) => /\p{Extended_Pictographic}/u.test(t)).length /
|
|
61
|
+
count,
|
|
62
|
+
),
|
|
63
|
+
fullStopRate: round(samples.filter((t) => /[。.]$/.test(t)).length / count),
|
|
64
|
+
slangRate: round(
|
|
65
|
+
samples.filter((t) => slangMatches(t).length).length / count,
|
|
66
|
+
),
|
|
67
|
+
};
|
|
68
|
+
// A compact line she can pick up the place's way of talking from; it is
|
|
69
|
+
// statistics about the group, never anyone's words.
|
|
70
|
+
profile.summary = `常见句长约 ${median} 字 · ${profile.fullStopRate < 0.25 ? "常省略句号" : "标点完整"} · ${profile.slangRate < 0.15 ? "偏普通口语" : "偶尔接梗"} · ${profile.emojiRate < 0.15 ? "少用表情" : "适量表情"}`;
|
|
71
|
+
return profile;
|
|
72
|
+
}
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import { localClock } from "./conversation-cues.js";
|
|
2
|
-
|
|
3
|
-
// History has a different role when she starts a conversation. It can stop
|
|
4
|
-
// a repeated question or a false claim, but it must never pose a new request.
|
|
5
|
-
export function initiativeContext(snapshot, occasion = snapshot.initiative) {
|
|
6
|
-
const situation = occasion?.initiative || {};
|
|
7
|
-
const history = (snapshot.messages || []).slice(-12).map((m) => ({
|
|
8
|
-
id: m.id,
|
|
9
|
-
speaker: m.speaker,
|
|
10
|
-
name: m.name,
|
|
11
|
-
role: m.role,
|
|
12
|
-
text: m.text,
|
|
13
|
-
time: m.time,
|
|
14
|
-
localTime: m.localTime,
|
|
15
|
-
referenceOnly: true,
|
|
16
|
-
}));
|
|
17
|
-
return {
|
|
18
|
-
sessionId: snapshot.sessionId,
|
|
19
|
-
conversation: snapshot.conversation,
|
|
20
|
-
batchIds: [],
|
|
21
|
-
addressed: [],
|
|
22
|
-
newMessages: [],
|
|
23
|
-
messages: [],
|
|
24
|
-
initiative: {
|
|
25
|
-
type: occasion?.type,
|
|
26
|
-
quietMinutes: situation.quietMinutes,
|
|
27
|
-
sinceUserMinutes: situation.sinceUserMinutes,
|
|
28
|
-
awaitingReply: !!situation.awaitingReply,
|
|
29
|
-
lastInitiatedAt: situation.lastInitiatedAt,
|
|
30
|
-
returned: occasion?.returned || [],
|
|
31
|
-
},
|
|
32
|
-
expression: occasion?.expression || {
|
|
33
|
-
origin: "earlier_wish",
|
|
34
|
-
thought: occasion?.thought,
|
|
35
|
-
words: occasion?.planned ? [occasion.planned] : [],
|
|
36
|
-
reason: occasion?.wantedBecause,
|
|
37
|
-
},
|
|
38
|
-
...(snapshot.self ? { self: snapshot.self } : {}),
|
|
39
|
-
...(snapshot.inner ? { inner: snapshot.inner } : {}),
|
|
40
|
-
history: {
|
|
41
|
-
use: "只核对过去、避免重复和捏造;这里没有本轮收到的消息,不是待回复的问题",
|
|
42
|
-
messages: history,
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
export function initiativeSnapshot(session, rows, nature, now, timeZone) {
|
|
47
|
-
return {
|
|
48
|
-
sessionId: session,
|
|
49
|
-
batchIds: [],
|
|
50
|
-
batch: [],
|
|
51
|
-
persona: nature,
|
|
52
|
-
sourceRows: [],
|
|
53
|
-
messages: rows.slice(-24).map((m) => ({
|
|
54
|
-
id: m.seq,
|
|
55
|
-
speaker: m.userId,
|
|
56
|
-
name: m.name,
|
|
57
|
-
role: m.role,
|
|
58
|
-
text: m.text,
|
|
59
|
-
time: m.time,
|
|
60
|
-
localTime: localClock(m.time, timeZone).local,
|
|
61
|
-
attachments: [],
|
|
62
|
-
referenceOnly: true,
|
|
63
|
-
})),
|
|
64
|
-
conversation: { clock: localClock(now, timeZone) },
|
|
65
|
-
};
|
|
66
|
-
}
|
|
1
|
+
import { localClock } from "./conversation-cues.js";
|
|
2
|
+
|
|
3
|
+
// History has a different role when she starts a conversation. It can stop
|
|
4
|
+
// a repeated question or a false claim, but it must never pose a new request.
|
|
5
|
+
export function initiativeContext(snapshot, occasion = snapshot.initiative) {
|
|
6
|
+
const situation = occasion?.initiative || {};
|
|
7
|
+
const history = (snapshot.messages || []).slice(-12).map((m) => ({
|
|
8
|
+
id: m.id,
|
|
9
|
+
speaker: m.speaker,
|
|
10
|
+
name: m.name,
|
|
11
|
+
role: m.role,
|
|
12
|
+
text: m.text,
|
|
13
|
+
time: m.time,
|
|
14
|
+
localTime: m.localTime,
|
|
15
|
+
referenceOnly: true,
|
|
16
|
+
}));
|
|
17
|
+
return {
|
|
18
|
+
sessionId: snapshot.sessionId,
|
|
19
|
+
conversation: snapshot.conversation,
|
|
20
|
+
batchIds: [],
|
|
21
|
+
addressed: [],
|
|
22
|
+
newMessages: [],
|
|
23
|
+
messages: [],
|
|
24
|
+
initiative: {
|
|
25
|
+
type: occasion?.type,
|
|
26
|
+
quietMinutes: situation.quietMinutes,
|
|
27
|
+
sinceUserMinutes: situation.sinceUserMinutes,
|
|
28
|
+
awaitingReply: !!situation.awaitingReply,
|
|
29
|
+
lastInitiatedAt: situation.lastInitiatedAt,
|
|
30
|
+
returned: occasion?.returned || [],
|
|
31
|
+
},
|
|
32
|
+
expression: occasion?.expression || {
|
|
33
|
+
origin: "earlier_wish",
|
|
34
|
+
thought: occasion?.thought,
|
|
35
|
+
words: occasion?.planned ? [occasion.planned] : [],
|
|
36
|
+
reason: occasion?.wantedBecause,
|
|
37
|
+
},
|
|
38
|
+
...(snapshot.self ? { self: snapshot.self } : {}),
|
|
39
|
+
...(snapshot.inner ? { inner: snapshot.inner } : {}),
|
|
40
|
+
history: {
|
|
41
|
+
use: "只核对过去、避免重复和捏造;这里没有本轮收到的消息,不是待回复的问题",
|
|
42
|
+
messages: history,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function initiativeSnapshot(session, rows, nature, now, timeZone) {
|
|
47
|
+
return {
|
|
48
|
+
sessionId: session,
|
|
49
|
+
batchIds: [],
|
|
50
|
+
batch: [],
|
|
51
|
+
persona: nature,
|
|
52
|
+
sourceRows: [],
|
|
53
|
+
messages: rows.slice(-24).map((m) => ({
|
|
54
|
+
id: m.seq,
|
|
55
|
+
speaker: m.userId,
|
|
56
|
+
name: m.name,
|
|
57
|
+
role: m.role,
|
|
58
|
+
text: m.text,
|
|
59
|
+
time: m.time,
|
|
60
|
+
localTime: localClock(m.time, timeZone).local,
|
|
61
|
+
attachments: [],
|
|
62
|
+
referenceOnly: true,
|
|
63
|
+
})),
|
|
64
|
+
conversation: { clock: localClock(now, timeZone) },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
// Trim only optional historical messages, keeping current targets and quote chains intact.
|
|
2
|
-
export function fitInput(data, build, estimate, limit) {
|
|
3
|
-
const payload = structuredClone(data);
|
|
4
|
-
const context = payload.context || payload;
|
|
5
|
-
const protectedIds = new Set([
|
|
6
|
-
...(context.batchIds || []),
|
|
7
|
-
...(payload.decision?.targetMessageIds || []),
|
|
8
|
-
...(payload.decision?.evidenceIds || []),
|
|
9
|
-
]);
|
|
10
|
-
for (const m of context.messages || [])
|
|
11
|
-
if (protectedIds.has(m.id))
|
|
12
|
-
for (const id of m.replyChain || []) protectedIds.add(id);
|
|
13
|
-
// A standalone memory/vision batch without batchIds must not be silently truncated.
|
|
14
|
-
const canTrim = Array.isArray(context.batchIds);
|
|
15
|
-
let messages = build(payload),
|
|
16
|
-
tokens = estimate(messages),
|
|
17
|
-
removed = 0;
|
|
18
|
-
while (tokens > limit && canTrim) {
|
|
19
|
-
const index =
|
|
20
|
-
context.messages?.findIndex((m) => !protectedIds.has(m.id)) ?? -1;
|
|
21
|
-
if (index < 0) break;
|
|
22
|
-
context.messages.splice(index, 1);
|
|
23
|
-
removed++;
|
|
24
|
-
messages = build(payload);
|
|
25
|
-
tokens = estimate(messages);
|
|
26
|
-
}
|
|
27
|
-
if (tokens > limit)
|
|
28
|
-
throw Error(
|
|
29
|
-
"当前批次、引用与人格超过模型输入预算,请增加 Max Input Tokens",
|
|
30
|
-
);
|
|
31
|
-
return { messages, inputEstimate: tokens, removed };
|
|
32
|
-
}
|
|
1
|
+
// Trim only optional historical messages, keeping current targets and quote chains intact.
|
|
2
|
+
export function fitInput(data, build, estimate, limit) {
|
|
3
|
+
const payload = structuredClone(data);
|
|
4
|
+
const context = payload.context || payload;
|
|
5
|
+
const protectedIds = new Set([
|
|
6
|
+
...(context.batchIds || []),
|
|
7
|
+
...(payload.decision?.targetMessageIds || []),
|
|
8
|
+
...(payload.decision?.evidenceIds || []),
|
|
9
|
+
]);
|
|
10
|
+
for (const m of context.messages || [])
|
|
11
|
+
if (protectedIds.has(m.id))
|
|
12
|
+
for (const id of m.replyChain || []) protectedIds.add(id);
|
|
13
|
+
// A standalone memory/vision batch without batchIds must not be silently truncated.
|
|
14
|
+
const canTrim = Array.isArray(context.batchIds);
|
|
15
|
+
let messages = build(payload),
|
|
16
|
+
tokens = estimate(messages),
|
|
17
|
+
removed = 0;
|
|
18
|
+
while (tokens > limit && canTrim) {
|
|
19
|
+
const index =
|
|
20
|
+
context.messages?.findIndex((m) => !protectedIds.has(m.id)) ?? -1;
|
|
21
|
+
if (index < 0) break;
|
|
22
|
+
context.messages.splice(index, 1);
|
|
23
|
+
removed++;
|
|
24
|
+
messages = build(payload);
|
|
25
|
+
tokens = estimate(messages);
|
|
26
|
+
}
|
|
27
|
+
if (tokens > limit)
|
|
28
|
+
throw Error(
|
|
29
|
+
"当前批次、引用与人格超过模型输入预算,请增加 Max Input Tokens",
|
|
30
|
+
);
|
|
31
|
+
return { messages, inputEstimate: tokens, removed };
|
|
32
|
+
}
|