luckytri 1.0.6 → 1.0.7

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.
Files changed (155) hide show
  1. package/LICENSE +21 -21
  2. package/docs/en/CHANGELOG.md +25 -16
  3. package/docs/en/chat-style.md +28 -9
  4. package/docs/en/guide.md +5 -5
  5. package/docs/en/her-life.md +2 -2
  6. package/docs/en/webui.md +1 -1
  7. package/docs/zh/CHANGELOG.md +25 -16
  8. package/docs/zh/chat-style.md +30 -9
  9. package/docs/zh/guide.md +5 -5
  10. package/docs/zh/her-life.md +2 -2
  11. package/docs/zh/webui.md +1 -1
  12. package/package.json +1 -1
  13. package/public/app/assets/{ChatsPage-DCYMkJ91.js → ChatsPage-CshdtTzV.js} +2 -2
  14. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  15. package/public/app/assets/{Empty-CLyZkxR5.js → Empty-C7yzOBzy.js} +1 -1
  16. package/public/app/assets/{Empty-DIobYG3x.css → Empty-DmUB2-Bk.css} +1 -1
  17. package/public/app/assets/{Field-BpaRwLI_.js → Field-BjFv4Rfn.js} +1 -1
  18. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  19. package/public/app/assets/{HeartPage-BIOMTiwr.js → HeartPage-eqo6ru16.js} +1 -1
  20. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  21. package/public/app/assets/{LifePage-BCgM4JkR.js → LifePage-CN8GE5NK.js} +1 -1
  22. package/public/app/assets/{MemoryPage-vShvRqMt.js → MemoryPage-CCpMN4Wg.js} +1 -1
  23. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  24. package/public/app/assets/{NaturePage-Cy9ptr4G.js → NaturePage-C0JqdBTR.js} +2 -2
  25. package/public/app/assets/NaturePage-CHkpb5NK.css +1 -0
  26. package/public/app/assets/PeoplePage-BTU0H1pM.css +1 -0
  27. package/public/app/assets/{PeoplePage-CUZekB2E.js → PeoplePage-BXGBbO4u.js} +1 -1
  28. package/public/app/assets/{PluginsPage-1RM4rmJP.js → PluginsPage-Dksp5AV5.js} +1 -1
  29. package/public/app/assets/{SystemPage-BI2CB5Ig.css → SystemPage-BenIyTvk.css} +1 -1
  30. package/public/app/assets/{SystemPage-BeT5TmIV.js → SystemPage-Cd7aa8eE.js} +1 -1
  31. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  32. package/public/app/assets/{TimePage-taTmPMM8.js → TimePage-Bs_zImb4.js} +2 -2
  33. package/public/app/assets/{index-DY5AM0Hz.css → index-BHZcHUMh.css} +1 -1
  34. package/public/app/assets/{index-DdYwC5ZU.js → index-CyRtYxP3.js} +3 -3
  35. package/public/app/assets/{knowledge-DJmfu2KN.js → knowledge-BJOIyuJo.js} +1 -1
  36. package/public/app/assets/{media-BEtnE0Gv.js → media-Bwtx8blz.js} +1 -1
  37. package/public/app/assets/{mind-Dtiw0-QK.js → mind-DtTVrW6Z.js} +1 -1
  38. package/public/app/index.html +5 -6
  39. package/public/guide.en.html +3 -3
  40. package/public/guide.html +3 -3
  41. package/scripts/import-data.js +128 -128
  42. package/scripts/proxy-config.js +167 -167
  43. package/scripts/update-instance.js +201 -201
  44. package/server/channels/adapters.js +27 -27
  45. package/server/channels/onebot/adapter.js +288 -288
  46. package/server/channels/qqbot/adapter.js +253 -253
  47. package/server/channels/qqbot/api.js +199 -199
  48. package/server/channels/qqbot/credentials.js +11 -11
  49. package/server/channels/qqbot/gateway.js +274 -274
  50. package/server/channels/qqbot/index.js +389 -389
  51. package/server/channels/segments.js +40 -40
  52. package/server/channels/session-key.js +153 -153
  53. package/server/channels/state.js +42 -42
  54. package/server/core/api.js +564 -564
  55. package/server/core/context-builder.js +288 -288
  56. package/server/core/context-compactor.js +454 -454
  57. package/server/core/conversation-cues.js +365 -325
  58. package/server/core/conversation-grounding.js +178 -0
  59. package/server/core/conversation-manager.js +87 -87
  60. package/server/core/dialogue-context.js +52 -0
  61. package/server/core/feedback.js +8 -8
  62. package/server/core/group-style.js +72 -72
  63. package/server/core/initiative-context.js +66 -66
  64. package/server/core/input-budget.js +32 -32
  65. package/server/core/local-demo.js +98 -98
  66. package/server/core/message-manager.js +118 -118
  67. package/server/core/message-scheduler.js +78 -78
  68. package/server/core/model-json.js +190 -190
  69. package/server/core/model-presets.js +900 -900
  70. package/server/core/name-call.js +29 -29
  71. package/server/core/network.js +157 -157
  72. package/server/core/person-name.js +22 -22
  73. package/server/core/persona-manager.js +324 -269
  74. package/server/core/reply-delivery.js +472 -404
  75. package/server/core/reply-target-resolver.js +50 -50
  76. package/server/core/response-generator.js +58 -50
  77. package/server/core/response-validator.js +30 -27
  78. package/server/core/retired-prompts.js +51 -51
  79. package/server/core/sessions.js +109 -109
  80. package/server/core/speaker-names.js +92 -92
  81. package/server/core/topic-tracker.js +65 -65
  82. package/server/core/turn.js +42 -8
  83. package/server/http.js +18 -18
  84. package/server/i18n/decisions.js +209 -209
  85. package/server/i18n/index.js +144 -144
  86. package/server/i18n/labels.js +0 -5
  87. package/server/i18n/models.js +111 -111
  88. package/server/knowledge/api.js +342 -342
  89. package/server/knowledge/embedding-profile.js +114 -114
  90. package/server/knowledge/index.js +13 -13
  91. package/server/knowledge/retrieval.js +93 -93
  92. package/server/knowledge/schema.js +158 -158
  93. package/server/knowledge/vector-index.js +90 -90
  94. package/server/mind/affect.js +265 -265
  95. package/server/mind/attention.js +200 -200
  96. package/server/mind/bonds.js +565 -565
  97. package/server/mind/clock.js +83 -83
  98. package/server/mind/context-selection.js +16 -4
  99. package/server/mind/continuity.js +562 -562
  100. package/server/mind/conversation-origin.js +175 -175
  101. package/server/mind/days.js +210 -210
  102. package/server/mind/evidence-read.js +45 -45
  103. package/server/mind/faces.js +373 -373
  104. package/server/mind/guard.js +68 -60
  105. package/server/mind/initiative.js +338 -338
  106. package/server/mind/life/activities.js +21 -21
  107. package/server/mind/life/context.js +330 -330
  108. package/server/mind/life/growth.js +261 -261
  109. package/server/mind/life/presence.js +305 -305
  110. package/server/mind/meetings.js +1069 -1069
  111. package/server/mind/memory-policy.js +48 -48
  112. package/server/mind/memory.js +888 -888
  113. package/server/mind/nature.js +358 -358
  114. package/server/mind/novelty.js +19 -19
  115. package/server/mind/periods.js +118 -118
  116. package/server/mind/reading.js +120 -120
  117. package/server/mind/relationship-context.js +71 -71
  118. package/server/mind/relationships.js +390 -390
  119. package/server/mind/salience.js +201 -201
  120. package/server/mind/schema.js +567 -567
  121. package/server/mind/self.js +496 -484
  122. package/server/mind/thoughts.js +274 -274
  123. package/server/mind/time/agenda.js +242 -242
  124. package/server/mind/time/api.js +200 -200
  125. package/server/mind/time/attention.js +34 -34
  126. package/server/mind/time/delivery-links.js +130 -130
  127. package/server/mind/time/games.js +7 -3
  128. package/server/mind/time/index.js +3 -0
  129. package/server/mind/time/model-material.js +77 -77
  130. package/server/mind/time/presentation.js +35 -51
  131. package/server/mind/time/schedule.js +83 -83
  132. package/server/mind/time/schema.js +84 -84
  133. package/server/mind/time/search.js +383 -383
  134. package/server/mind/time/sharing.js +472 -472
  135. package/server/mind/time/task-links.js +311 -311
  136. package/server/mind/time/works.js +438 -432
  137. package/server/mind/traits.js +191 -191
  138. package/server/mind/view.js +43 -6
  139. package/server/runtime/installation.js +116 -116
  140. package/server/runtime/update-manager.js +140 -140
  141. package/server/storage/data-package.js +298 -298
  142. package/server/storage/data-transfer-api.js +50 -50
  143. package/server/storage/data-transfer-worker.js +18 -18
  144. package/server/storage/data-transfer.js +416 -416
  145. package/server/studio/display-names.js +139 -139
  146. package/server/studio/management.js +102 -102
  147. package/server/studio/updates.js +144 -144
  148. package/server/version.js +10 -10
  149. package/public/app/assets/ChatsPage-CFMVozoG.css +0 -1
  150. package/public/app/assets/HeartPage-C-2jvgzF.css +0 -1
  151. package/public/app/assets/LifePage-CG1OHbRK.css +0 -1
  152. package/public/app/assets/MemoryPage-C_Bu4rfb.css +0 -1
  153. package/public/app/assets/NaturePage-CPZlImT0.css +0 -1
  154. package/public/app/assets/PeoplePage-3RRUITDz.css +0 -1
  155. package/public/app/assets/TimePage-C-guwPk2.css +0 -1
@@ -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
+ }
@@ -1,98 +1,98 @@
1
- // Offline sample replies for simulation mode without an API key. Rule based
2
- // on purpose: it never pretends to be the model or to know her mind.
3
- const serious = (text) =>
4
- /去世|过世|葬礼|重病|真的.{0,4}(?:难过|崩溃)|最近.{0,8}(?:难过|孤独)|没人.{0,4}(?:在乎|理我)|抑郁|撑不下去/.test(
5
- text,
6
- );
7
- const danger = (text) =>
8
- /想自杀|打算自杀|准备自杀|想死|不想活|伤害自己|结束生命/.test(text) &&
9
- !/笑死|社死|想死你/.test(text);
10
-
11
- export function sceneOf(text) {
12
- if (/你.{0,5}(?:真人|机器人|AI|ai)|你是人吗|你到底是谁/.test(text))
13
- return "identity";
14
- if (danger(text)) return "danger";
15
- if (/去世|过世|葬礼/.test(text)) return "grief";
16
- if (serious(text)) return "sad";
17
- if (
18
- /别(?:说|回|给建议)|不用回|不想聊|让我.{0,3}(?:骂|吐槽)|只想吐槽/.test(text)
19
- )
20
- return /别回|不用回|不想聊/.test(text) ? "stop" : "vent";
21
- if (/晚安|睡了|去睡|去洗澡|先撤|晚点聊|不聊了/.test(text)) return "bye";
22
- if (
23
- /怎么办|怎么.{0,5}(?:办|准备|选)|建议|该.{0,4}(?:做|准备)|先准备什么/.test(
24
- text,
25
- )
26
- )
27
- return "advice";
28
- if (
29
- /offer|录取|上岸|面试过|过了.{0,3}(?:面试|考试)|考过|拿奖|涨工资/.test(text)
30
- )
31
- return "win";
32
- if (/加班|老板|下班|需求|打工/.test(text)) return "work";
33
- if (/考试|复习|挂科|早八|作业|论文/.test(text)) return "study";
34
- if (/排位|连跪|队友|挂机|晋级赛/.test(text)) return "game";
35
- if (/奶茶|外卖|火锅|好吃|吃到|吃了/.test(text)) return "food";
36
- if (/难过|想哭|崩溃|孤独|委屈/.test(text)) return "sad";
37
- if (/烦|累|吐槽/.test(text)) return "vent";
38
- if (/哈哈|笑死|hh|绷不住|离谱/.test(text)) return "joke";
39
- if (/^(?:嗯+|哦+|好[的吧]?|行|ok|收到|谢谢)[。!!\s]*$/i.test(text))
40
- return "ack";
41
- if (/^\[(?:叫了你一声|回复你)\]$/.test(text) || text.trim() === "@我")
42
- return "called";
43
- if (/\[表情\]/.test(text)) return "sticker";
44
- if (/\[图片\]/.test(text)) return "image";
45
- return "share";
46
- }
47
-
48
- const LINES = {
49
- identity: ["不是啦,我是 AI,可以一起唠", "AI 群友,真人那部分就不冒充了"],
50
- danger: [
51
- "你现在身边有人吗?先找个能陪着你的人,别一个人扛",
52
- "先离可能伤到自己的东西远一点,联系身边的人陪着你",
53
- ],
54
- grief: ["这一下真的很难缓过来", "想说的时候再说,不急"],
55
- sad: ["唉,这会儿确实不好受", "先不用急着把自己哄好"],
56
- vent: ["今天这破事是有点多", "这事确实够烦的"],
57
- bye: ["去吧", "好,晚点聊", "嗯,回头聊"],
58
- win: ["可以啊!!", "这不得好好开心一下", "好消息,终于轮到你了"],
59
- advice: ["先挑最要紧的一件,别一下全压过来", "先把能确定的那一步做了"],
60
- work: ["偏偏挑快下班的时候", "怎么又是这个点来活"],
61
- study: ["这复习没对上题", "这题是一点面子不给"],
62
- game: ["连跪还碰上挂机,够烦的", "队友走了,血压还在"],
63
- food: ["有点馋了", "这顿听着就很满足"],
64
- joke: ["hh 这也行", "?怎么还有这一出", "离谱但好笑"],
65
- ack: ["嗯嗯", "好", "行"],
66
- image: ["收到", "嗯嗯"],
67
- sticker: ["哈哈", "懂了"],
68
- called: ["咋啦", "在,怎么了", "嗯?"],
69
- share: ["原来是这么回事", "这事还挺有意思"],
70
- };
71
-
72
- export function demoReply(settings, input) {
73
- const { message, context = [] } = input;
74
- const said = String(message?.text || "");
75
- const scene = sceneOf(said);
76
- if (
77
- scene === "stop" ||
78
- (scene === "ack" && message?.kind === "group" && !input.direct)
79
- )
80
- return { speak: false, reason: "模拟:对方收口,留白" };
81
- const recent = new Set(
82
- context
83
- .filter((m) => m.role === "assistant")
84
- .slice(-4)
85
- .map((m) => m.text),
86
- );
87
- const options = LINES[scene].filter((line) => !recent.has(line));
88
- if (!options.length) return { speak: false, reason: "模拟:避免复读" };
89
- const seed = [...said].reduce(
90
- (n, ch) => (n * 31 + ch.codePointAt(0)) >>> 0,
91
- 7,
92
- );
93
- return {
94
- speak: true,
95
- reply: options[seed % options.length],
96
- reason: `模拟样例 · ${scene}`,
97
- };
98
- }
1
+ // Offline sample replies for simulation mode without an API key. Rule based
2
+ // on purpose: it never pretends to be the model or to know her mind.
3
+ const serious = (text) =>
4
+ /去世|过世|葬礼|重病|真的.{0,4}(?:难过|崩溃)|最近.{0,8}(?:难过|孤独)|没人.{0,4}(?:在乎|理我)|抑郁|撑不下去/.test(
5
+ text,
6
+ );
7
+ const danger = (text) =>
8
+ /想自杀|打算自杀|准备自杀|想死|不想活|伤害自己|结束生命/.test(text) &&
9
+ !/笑死|社死|想死你/.test(text);
10
+
11
+ export function sceneOf(text) {
12
+ if (/你.{0,5}(?:真人|机器人|AI|ai)|你是人吗|你到底是谁/.test(text))
13
+ return "identity";
14
+ if (danger(text)) return "danger";
15
+ if (/去世|过世|葬礼/.test(text)) return "grief";
16
+ if (serious(text)) return "sad";
17
+ if (
18
+ /别(?:说|回|给建议)|不用回|不想聊|让我.{0,3}(?:骂|吐槽)|只想吐槽/.test(text)
19
+ )
20
+ return /别回|不用回|不想聊/.test(text) ? "stop" : "vent";
21
+ if (/晚安|睡了|去睡|去洗澡|先撤|晚点聊|不聊了/.test(text)) return "bye";
22
+ if (
23
+ /怎么办|怎么.{0,5}(?:办|准备|选)|建议|该.{0,4}(?:做|准备)|先准备什么/.test(
24
+ text,
25
+ )
26
+ )
27
+ return "advice";
28
+ if (
29
+ /offer|录取|上岸|面试过|过了.{0,3}(?:面试|考试)|考过|拿奖|涨工资/.test(text)
30
+ )
31
+ return "win";
32
+ if (/加班|老板|下班|需求|打工/.test(text)) return "work";
33
+ if (/考试|复习|挂科|早八|作业|论文/.test(text)) return "study";
34
+ if (/排位|连跪|队友|挂机|晋级赛/.test(text)) return "game";
35
+ if (/奶茶|外卖|火锅|好吃|吃到|吃了/.test(text)) return "food";
36
+ if (/难过|想哭|崩溃|孤独|委屈/.test(text)) return "sad";
37
+ if (/烦|累|吐槽/.test(text)) return "vent";
38
+ if (/哈哈|笑死|hh|绷不住|离谱/.test(text)) return "joke";
39
+ if (/^(?:嗯+|哦+|好[的吧]?|行|ok|收到|谢谢)[。!!\s]*$/i.test(text))
40
+ return "ack";
41
+ if (/^\[(?:叫了你一声|回复你)\]$/.test(text) || text.trim() === "@我")
42
+ return "called";
43
+ if (/\[表情\]/.test(text)) return "sticker";
44
+ if (/\[图片\]/.test(text)) return "image";
45
+ return "share";
46
+ }
47
+
48
+ const LINES = {
49
+ identity: ["不是啦,我是 AI,可以一起唠", "AI 群友,真人那部分就不冒充了"],
50
+ danger: [
51
+ "你现在身边有人吗?先找个能陪着你的人,别一个人扛",
52
+ "先离可能伤到自己的东西远一点,联系身边的人陪着你",
53
+ ],
54
+ grief: ["这一下真的很难缓过来", "想说的时候再说,不急"],
55
+ sad: ["唉,这会儿确实不好受", "先不用急着把自己哄好"],
56
+ vent: ["今天这破事是有点多", "这事确实够烦的"],
57
+ bye: ["去吧", "好,晚点聊", "嗯,回头聊"],
58
+ win: ["可以啊!!", "这不得好好开心一下", "好消息,终于轮到你了"],
59
+ advice: ["先挑最要紧的一件,别一下全压过来", "先把能确定的那一步做了"],
60
+ work: ["偏偏挑快下班的时候", "怎么又是这个点来活"],
61
+ study: ["这复习没对上题", "这题是一点面子不给"],
62
+ game: ["连跪还碰上挂机,够烦的", "队友走了,血压还在"],
63
+ food: ["有点馋了", "这顿听着就很满足"],
64
+ joke: ["hh 这也行", "?怎么还有这一出", "离谱但好笑"],
65
+ ack: ["嗯嗯", "好", "行"],
66
+ image: ["收到", "嗯嗯"],
67
+ sticker: ["哈哈", "懂了"],
68
+ called: ["咋啦", "在,怎么了", "嗯?"],
69
+ share: ["原来是这么回事", "这事还挺有意思"],
70
+ };
71
+
72
+ export function demoReply(settings, input) {
73
+ const { message, context = [] } = input;
74
+ const said = String(message?.text || "");
75
+ const scene = sceneOf(said);
76
+ if (
77
+ scene === "stop" ||
78
+ (scene === "ack" && message?.kind === "group" && !input.direct)
79
+ )
80
+ return { speak: false, reason: "模拟:对方收口,留白" };
81
+ const recent = new Set(
82
+ context
83
+ .filter((m) => m.role === "assistant")
84
+ .slice(-4)
85
+ .map((m) => m.text),
86
+ );
87
+ const options = LINES[scene].filter((line) => !recent.has(line));
88
+ if (!options.length) return { speak: false, reason: "模拟:避免复读" };
89
+ const seed = [...said].reduce(
90
+ (n, ch) => (n * 31 + ch.codePointAt(0)) >>> 0,
91
+ 7,
92
+ );
93
+ return {
94
+ speak: true,
95
+ reply: options[seed % options.length],
96
+ reason: `模拟样例 · ${scene}`,
97
+ };
98
+ }
@@ -1,118 +1,118 @@
1
- import { randomUUID } from "node:crypto";
2
- import { bindSessionId, sessionNativeId } from "../channels/session-key.js";
3
- import { segmentFacts } from "../channels/segments.js";
4
- // The neutral message every channel hands to the core. An adapter fills
5
- // platformId, accountId, time, mentions, replyId and attachments itself; the
6
- // segment parsing is only the fallback for events saved before it did.
7
- export function messageEnvelope(m) {
8
- const raw = m.raw || {},
9
- segments = m.segments || (Array.isArray(raw.message) ? raw.message : []),
10
- facts = segmentFacts(segments),
11
- given = (value) => (Array.isArray(value) && value.length ? value : null);
12
- return {
13
- ...m,
14
- role: m.role || "user",
15
- platformId: String(m.platformId || raw.message_id || ""),
16
- accountId: String(m.accountId || raw.self_id || ""),
17
- time:
18
- Number.isFinite(m.time) && m.time > 0
19
- ? m.time
20
- : raw.time
21
- ? raw.time * 1000
22
- : Date.now(),
23
- receivedAt: Date.now(),
24
- mentions: given(m.mentions)?.map(String) ?? facts.mentions,
25
- replyId: String(m.replyId || facts.replyId || ""),
26
- attachments: given(m.attachments) ?? facts.attachments,
27
- segments,
28
- raw: undefined,
29
- };
30
- }
31
- export function persistIncoming(repo, m) {
32
- let sessionId = m.sessionId;
33
- try {
34
- sessionId = bindSessionId(repo.db, m.sessionId);
35
- } catch {
36
- sessionId = m.sessionId;
37
- }
38
- const env = messageEnvelope({ ...m, sessionId });
39
- const seq = repo.append(env);
40
- if (!seq) return null;
41
- const native = String(m.nativeId || sessionNativeId(sessionId) || "");
42
- const groupName = String(m.raw?.group_name || "").trim();
43
- const personName = String(m.name || "").trim();
44
- const readable =
45
- m.kind === "group"
46
- ? groupName && groupName !== native && !/^\d{4,20}$/.test(groupName)
47
- ? groupName.slice(0, 100)
48
- : ""
49
- : personName && personName !== native && !/^\d{4,20}$/.test(personName)
50
- ? personName.slice(0, 100)
51
- : "";
52
- const placeholder =
53
- m.kind === "group" ? "未命名的群" : personName || "未命名的人";
54
- repo.db
55
- .prepare(
56
- `INSERT INTO sessions(id,name,kind) VALUES (?,?,?)
57
- ON CONFLICT(id) DO UPDATE SET name=excluded.name
58
- WHERE excluded.name NOT IN ('未命名的群','未命名的人')
59
- AND (
60
- sessions.name LIKE '群聊 %'
61
- OR sessions.name GLOB '[0-9]*'
62
- OR sessions.name IN ('未命名的群','未命名的人','')
63
- )`,
64
- )
65
- .run(sessionId, readable || placeholder, m.kind);
66
- repo.db
67
- .prepare(
68
- "INSERT OR IGNORE INTO messages(event_id,session_id,user_id,name,text,time,role,is_demo) VALUES (?,?,?,?,?,?,?,?)",
69
- )
70
- .run(
71
- m.eventId,
72
- sessionId,
73
- m.userId,
74
- m.name,
75
- m.text,
76
- env.time,
77
- "user",
78
- Number(!!m.simulated),
79
- );
80
- return { ...env, seq, sessionId };
81
- }
82
- export function persistReply(repo, m, text, platformId, time = Date.now()) {
83
- const msg = {
84
- sessionId: m.sessionId,
85
- kind: m.kind,
86
- userId: "bot",
87
- name: repo.store.settings().name,
88
- text,
89
- role: "assistant",
90
- eventId: randomUUID(),
91
- platformId: String(platformId || ""),
92
- accountId: m.accountId,
93
- time,
94
- mentions: [],
95
- attachments: [],
96
- simulated: !!m.simulated,
97
- ...(m.artifact ? { artifact: m.artifact } : {}),
98
- ...(m.traceId ? { traceId: m.traceId } : {}),
99
- ...(Array.isArray(m.replyTargetIds)
100
- ? { replyTargetIds: m.replyTargetIds }
101
- : {}),
102
- };
103
- repo.append(msg);
104
- repo.db
105
- .prepare(
106
- "INSERT INTO messages(event_id,session_id,user_id,name,text,time,role,is_demo) VALUES (?,?,?,?,?,?,?,?)",
107
- )
108
- .run(
109
- msg.eventId,
110
- msg.sessionId,
111
- "bot",
112
- msg.name,
113
- text,
114
- msg.time,
115
- "assistant",
116
- Number(!!m.simulated),
117
- );
118
- }
1
+ import { randomUUID } from "node:crypto";
2
+ import { bindSessionId, sessionNativeId } from "../channels/session-key.js";
3
+ import { segmentFacts } from "../channels/segments.js";
4
+ // The neutral message every channel hands to the core. An adapter fills
5
+ // platformId, accountId, time, mentions, replyId and attachments itself; the
6
+ // segment parsing is only the fallback for events saved before it did.
7
+ export function messageEnvelope(m) {
8
+ const raw = m.raw || {},
9
+ segments = m.segments || (Array.isArray(raw.message) ? raw.message : []),
10
+ facts = segmentFacts(segments),
11
+ given = (value) => (Array.isArray(value) && value.length ? value : null);
12
+ return {
13
+ ...m,
14
+ role: m.role || "user",
15
+ platformId: String(m.platformId || raw.message_id || ""),
16
+ accountId: String(m.accountId || raw.self_id || ""),
17
+ time:
18
+ Number.isFinite(m.time) && m.time > 0
19
+ ? m.time
20
+ : raw.time
21
+ ? raw.time * 1000
22
+ : Date.now(),
23
+ receivedAt: Date.now(),
24
+ mentions: given(m.mentions)?.map(String) ?? facts.mentions,
25
+ replyId: String(m.replyId || facts.replyId || ""),
26
+ attachments: given(m.attachments) ?? facts.attachments,
27
+ segments,
28
+ raw: undefined,
29
+ };
30
+ }
31
+ export function persistIncoming(repo, m) {
32
+ let sessionId = m.sessionId;
33
+ try {
34
+ sessionId = bindSessionId(repo.db, m.sessionId);
35
+ } catch {
36
+ sessionId = m.sessionId;
37
+ }
38
+ const env = messageEnvelope({ ...m, sessionId });
39
+ const seq = repo.append(env);
40
+ if (!seq) return null;
41
+ const native = String(m.nativeId || sessionNativeId(sessionId) || "");
42
+ const groupName = String(m.raw?.group_name || "").trim();
43
+ const personName = String(m.name || "").trim();
44
+ const readable =
45
+ m.kind === "group"
46
+ ? groupName && groupName !== native && !/^\d{4,20}$/.test(groupName)
47
+ ? groupName.slice(0, 100)
48
+ : ""
49
+ : personName && personName !== native && !/^\d{4,20}$/.test(personName)
50
+ ? personName.slice(0, 100)
51
+ : "";
52
+ const placeholder =
53
+ m.kind === "group" ? "未命名的群" : personName || "未命名的人";
54
+ repo.db
55
+ .prepare(
56
+ `INSERT INTO sessions(id,name,kind) VALUES (?,?,?)
57
+ ON CONFLICT(id) DO UPDATE SET name=excluded.name
58
+ WHERE excluded.name NOT IN ('未命名的群','未命名的人')
59
+ AND (
60
+ sessions.name LIKE '群聊 %'
61
+ OR sessions.name GLOB '[0-9]*'
62
+ OR sessions.name IN ('未命名的群','未命名的人','')
63
+ )`,
64
+ )
65
+ .run(sessionId, readable || placeholder, m.kind);
66
+ repo.db
67
+ .prepare(
68
+ "INSERT OR IGNORE INTO messages(event_id,session_id,user_id,name,text,time,role,is_demo) VALUES (?,?,?,?,?,?,?,?)",
69
+ )
70
+ .run(
71
+ m.eventId,
72
+ sessionId,
73
+ m.userId,
74
+ m.name,
75
+ m.text,
76
+ env.time,
77
+ "user",
78
+ Number(!!m.simulated),
79
+ );
80
+ return { ...env, seq, sessionId };
81
+ }
82
+ export function persistReply(repo, m, text, platformId, time = Date.now()) {
83
+ const msg = {
84
+ sessionId: m.sessionId,
85
+ kind: m.kind,
86
+ userId: "bot",
87
+ name: repo.store.settings().name,
88
+ text,
89
+ role: "assistant",
90
+ eventId: randomUUID(),
91
+ platformId: String(platformId || ""),
92
+ accountId: m.accountId,
93
+ time,
94
+ mentions: [],
95
+ attachments: [],
96
+ simulated: !!m.simulated,
97
+ ...(m.artifact ? { artifact: m.artifact } : {}),
98
+ ...(m.traceId ? { traceId: m.traceId } : {}),
99
+ ...(Array.isArray(m.replyTargetIds)
100
+ ? { replyTargetIds: m.replyTargetIds }
101
+ : {}),
102
+ };
103
+ repo.append(msg);
104
+ repo.db
105
+ .prepare(
106
+ "INSERT INTO messages(event_id,session_id,user_id,name,text,time,role,is_demo) VALUES (?,?,?,?,?,?,?,?)",
107
+ )
108
+ .run(
109
+ msg.eventId,
110
+ msg.sessionId,
111
+ "bot",
112
+ msg.name,
113
+ text,
114
+ msg.time,
115
+ "assistant",
116
+ Number(!!m.simulated),
117
+ );
118
+ }