luckytri 1.0.6 → 1.0.8

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 (168) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +7 -3
  3. package/README.md +7 -3
  4. package/docs/en/CHANGELOG.md +35 -16
  5. package/docs/en/chat-style.md +28 -9
  6. package/docs/en/guide.md +5 -5
  7. package/docs/en/her-life.md +2 -2
  8. package/docs/en/webui.md +1 -1
  9. package/docs/zh/CHANGELOG.md +35 -16
  10. package/docs/zh/chat-style.md +30 -9
  11. package/docs/zh/guide.md +5 -5
  12. package/docs/zh/her-life.md +2 -2
  13. package/docs/zh/webui.md +1 -1
  14. package/package.json +1 -1
  15. package/public/app/assets/{ChatsPage-DCYMkJ91.js → ChatsPage-CshdtTzV.js} +2 -2
  16. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  17. package/public/app/assets/{Empty-CLyZkxR5.js → Empty-C7yzOBzy.js} +1 -1
  18. package/public/app/assets/{Empty-DIobYG3x.css → Empty-DmUB2-Bk.css} +1 -1
  19. package/public/app/assets/{Field-BpaRwLI_.js → Field-BjFv4Rfn.js} +1 -1
  20. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  21. package/public/app/assets/{HeartPage-BIOMTiwr.js → HeartPage-eqo6ru16.js} +1 -1
  22. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  23. package/public/app/assets/{LifePage-BCgM4JkR.js → LifePage-CN8GE5NK.js} +1 -1
  24. package/public/app/assets/{MemoryPage-vShvRqMt.js → MemoryPage-CCpMN4Wg.js} +1 -1
  25. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  26. package/public/app/assets/{NaturePage-Cy9ptr4G.js → NaturePage-C0JqdBTR.js} +2 -2
  27. package/public/app/assets/NaturePage-CHkpb5NK.css +1 -0
  28. package/public/app/assets/PeoplePage-BTU0H1pM.css +1 -0
  29. package/public/app/assets/{PeoplePage-CUZekB2E.js → PeoplePage-BXGBbO4u.js} +1 -1
  30. package/public/app/assets/{PluginsPage-1RM4rmJP.js → PluginsPage-Dksp5AV5.js} +1 -1
  31. package/public/app/assets/{SystemPage-BI2CB5Ig.css → SystemPage-BenIyTvk.css} +1 -1
  32. package/public/app/assets/{SystemPage-BeT5TmIV.js → SystemPage-Cd7aa8eE.js} +1 -1
  33. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  34. package/public/app/assets/{TimePage-taTmPMM8.js → TimePage-Bs_zImb4.js} +2 -2
  35. package/public/app/assets/{index-DY5AM0Hz.css → index-BHZcHUMh.css} +1 -1
  36. package/public/app/assets/{index-DdYwC5ZU.js → index-CyRtYxP3.js} +3 -3
  37. package/public/app/assets/{knowledge-DJmfu2KN.js → knowledge-BJOIyuJo.js} +1 -1
  38. package/public/app/assets/{media-BEtnE0Gv.js → media-Bwtx8blz.js} +1 -1
  39. package/public/app/assets/{mind-Dtiw0-QK.js → mind-DtTVrW6Z.js} +1 -1
  40. package/public/app/index.html +5 -6
  41. package/public/guide.en.html +3 -3
  42. package/public/guide.html +3 -3
  43. package/scripts/import-data.js +128 -128
  44. package/scripts/proxy-config.js +167 -167
  45. package/scripts/update-instance.js +201 -201
  46. package/server/channels/adapters.js +27 -27
  47. package/server/channels/onebot/adapter.js +288 -288
  48. package/server/channels/qqbot/adapter.js +253 -253
  49. package/server/channels/qqbot/api.js +199 -199
  50. package/server/channels/qqbot/credentials.js +11 -11
  51. package/server/channels/qqbot/gateway.js +274 -274
  52. package/server/channels/qqbot/index.js +389 -389
  53. package/server/channels/segments.js +40 -40
  54. package/server/channels/session-key.js +153 -153
  55. package/server/channels/state.js +42 -42
  56. package/server/core/api.js +564 -564
  57. package/server/core/context-builder.js +288 -288
  58. package/server/core/context-compactor.js +454 -454
  59. package/server/core/conversation-cues.js +385 -325
  60. package/server/core/conversation-grounding.js +304 -0
  61. package/server/core/conversation-manager.js +87 -87
  62. package/server/core/dialogue-context.js +244 -0
  63. package/server/core/dialogue-contract.js +35 -0
  64. package/server/core/feedback.js +8 -8
  65. package/server/core/group-style.js +72 -72
  66. package/server/core/initiative-context.js +68 -66
  67. package/server/core/input-budget.js +32 -32
  68. package/server/core/local-demo.js +98 -98
  69. package/server/core/message-manager.js +118 -118
  70. package/server/core/message-scheduler.js +78 -78
  71. package/server/core/model-json.js +190 -190
  72. package/server/core/model-manager.js +5 -8
  73. package/server/core/model-presets.js +900 -900
  74. package/server/core/name-call.js +29 -29
  75. package/server/core/network.js +157 -157
  76. package/server/core/person-name.js +22 -22
  77. package/server/core/persona-manager.js +359 -269
  78. package/server/core/reader-check.js +62 -0
  79. package/server/core/reply-delivery.js +537 -404
  80. package/server/core/reply-target-resolver.js +50 -50
  81. package/server/core/response-generator.js +92 -50
  82. package/server/core/response-validator.js +53 -32
  83. package/server/core/retired-prompts.js +51 -51
  84. package/server/core/sessions.js +109 -109
  85. package/server/core/speaker-names.js +92 -92
  86. package/server/core/topic-tracker.js +65 -65
  87. package/server/core/turn.js +82 -15
  88. package/server/http.js +18 -18
  89. package/server/i18n/decisions.js +209 -209
  90. package/server/i18n/errors.js +2 -0
  91. package/server/i18n/index.js +144 -144
  92. package/server/i18n/labels.js +6 -10
  93. package/server/i18n/models.js +111 -111
  94. package/server/knowledge/api.js +342 -342
  95. package/server/knowledge/embedding-profile.js +114 -114
  96. package/server/knowledge/index.js +13 -13
  97. package/server/knowledge/retrieval.js +93 -93
  98. package/server/knowledge/schema.js +158 -158
  99. package/server/knowledge/vector-index.js +90 -90
  100. package/server/mind/affect.js +265 -265
  101. package/server/mind/attention.js +200 -200
  102. package/server/mind/bonds.js +565 -565
  103. package/server/mind/clock.js +83 -83
  104. package/server/mind/context-selection.js +16 -4
  105. package/server/mind/continuity.js +562 -562
  106. package/server/mind/conversation-origin.js +175 -175
  107. package/server/mind/days.js +210 -210
  108. package/server/mind/evidence-read.js +45 -45
  109. package/server/mind/expression-grounding.js +21 -0
  110. package/server/mind/faces.js +373 -373
  111. package/server/mind/guard.js +68 -60
  112. package/server/mind/initiative.js +338 -338
  113. package/server/mind/life/activities.js +21 -21
  114. package/server/mind/life/context.js +330 -330
  115. package/server/mind/life/growth.js +261 -261
  116. package/server/mind/life/presence.js +305 -305
  117. package/server/mind/meetings.js +1069 -1069
  118. package/server/mind/memory-policy.js +48 -48
  119. package/server/mind/memory.js +888 -888
  120. package/server/mind/nature.js +358 -358
  121. package/server/mind/novelty.js +19 -19
  122. package/server/mind/own-voice.js +35 -0
  123. package/server/mind/periods.js +118 -118
  124. package/server/mind/reading.js +132 -120
  125. package/server/mind/relationship-context.js +71 -71
  126. package/server/mind/relationships.js +390 -390
  127. package/server/mind/salience.js +201 -201
  128. package/server/mind/schema.js +567 -567
  129. package/server/mind/self.js +496 -484
  130. package/server/mind/thoughts.js +274 -274
  131. package/server/mind/time/agenda.js +242 -242
  132. package/server/mind/time/api.js +200 -200
  133. package/server/mind/time/attention.js +34 -34
  134. package/server/mind/time/availability.js +23 -1
  135. package/server/mind/time/executor.js +4 -2
  136. package/server/mind/time/games.js +20 -11
  137. package/server/mind/time/index.js +9 -2
  138. package/server/mind/time/kinds.js +29 -2
  139. package/server/mind/time/material-relevance.js +19 -0
  140. package/server/mind/time/model-material.js +77 -77
  141. package/server/mind/time/presentation.js +35 -51
  142. package/server/mind/time/reading-input.js +31 -0
  143. package/server/mind/time/schedule.js +83 -83
  144. package/server/mind/time/schema.js +84 -84
  145. package/server/mind/time/search.js +383 -383
  146. package/server/mind/time/sharing.js +482 -472
  147. package/server/mind/time/task-links.js +311 -311
  148. package/server/mind/time/tasks.js +41 -3
  149. package/server/mind/time/works.js +527 -432
  150. package/server/mind/traits.js +191 -191
  151. package/server/mind/view.js +62 -12
  152. package/server/runtime/installation.js +116 -116
  153. package/server/runtime/update-manager.js +140 -140
  154. package/server/storage/data-package.js +298 -298
  155. package/server/storage/data-transfer-api.js +50 -50
  156. package/server/storage/data-transfer-worker.js +18 -18
  157. package/server/storage/data-transfer.js +416 -416
  158. package/server/studio/display-names.js +139 -139
  159. package/server/studio/management.js +102 -102
  160. package/server/studio/updates.js +144 -144
  161. package/server/version.js +10 -10
  162. package/public/app/assets/ChatsPage-CFMVozoG.css +0 -1
  163. package/public/app/assets/HeartPage-C-2jvgzF.css +0 -1
  164. package/public/app/assets/LifePage-CG1OHbRK.css +0 -1
  165. package/public/app/assets/MemoryPage-C_Bu4rfb.css +0 -1
  166. package/public/app/assets/NaturePage-CPZlImT0.css +0 -1
  167. package/public/app/assets/PeoplePage-3RRUITDz.css +0 -1
  168. package/public/app/assets/TimePage-C-guwPk2.css +0 -1
@@ -1,114 +1,114 @@
1
- import { createHash } from "node:crypto";
2
-
3
- const CONFIG = "embedding-profile";
4
- export const EMBEDDING_DEFAULTS = {
5
- enabled: false,
6
- label: "知识向量",
7
- baseUrl: "",
8
- model: "",
9
- apiKey: "",
10
- dimensions: 0,
11
- batchSize: 32,
12
- timeoutMs: 90000,
13
- };
14
-
15
- export function embeddingSignature(profile) {
16
- return createHash("sha256")
17
- .update(
18
- JSON.stringify([
19
- profile.baseUrl || "",
20
- profile.embeddingModel || profile.model || "",
21
- profile.dimensions || 0,
22
- ]),
23
- )
24
- .digest("hex");
25
- }
26
-
27
- export function publicEmbeddingProfile(repo) {
28
- const saved = repo.config(CONFIG, null);
29
- const { apiKey, ...profile } = { ...EMBEDDING_DEFAULTS, ...saved };
30
- return {
31
- ...profile,
32
- configured: !!saved,
33
- hasApiKey: !!(apiKey || process.env.EMBEDDING_API_KEY),
34
- };
35
- }
36
-
37
- export function saveEmbeddingProfile(repo, input) {
38
- const old = { ...EMBEDDING_DEFAULTS, ...repo.config(CONFIG, {}) };
39
- const next = Object.fromEntries(
40
- Object.keys(EMBEDDING_DEFAULTS).map((key) => [key, input[key] ?? old[key]]),
41
- );
42
- if (typeof next.enabled !== "boolean") throw Error("向量开关无效");
43
- if (typeof next.apiKey !== "string") throw Error("密钥格式无效");
44
- if (!next.apiKey) next.apiKey = old.apiKey;
45
- next.label = String(next.label).trim().slice(0, 100);
46
- next.model = String(next.model).trim().slice(0, 200);
47
- next.baseUrl = String(next.baseUrl).trim().replace(/\/$/, "");
48
- if (next.baseUrl) {
49
- const url = new URL(next.baseUrl);
50
- if (
51
- !["http:", "https:"].includes(url.protocol) ||
52
- url.username ||
53
- url.password ||
54
- url.search ||
55
- url.hash
56
- )
57
- throw Error("向量 API 地址无效");
58
- }
59
- if (next.enabled && (!next.baseUrl || !next.model))
60
- throw Error("启用向量需要独立地址和模型名");
61
- for (const [key, min, max] of [
62
- ["dimensions", 0, 65536],
63
- ["batchSize", 1, 128],
64
- ["timeoutMs", 1000, 300000],
65
- ])
66
- if (!Number.isSafeInteger(next[key]) || next[key] < min || next[key] > max)
67
- throw Error(`${key} 超出范围`);
68
- repo.saveConfig(CONFIG, next);
69
- return publicEmbeddingProfile(repo);
70
- }
71
-
72
- export function resolveEmbeddingProfile(repo, models) {
73
- const saved = repo.config(CONFIG, null);
74
- if (saved) {
75
- const profile = {
76
- ...EMBEDDING_DEFAULTS,
77
- ...saved,
78
- id: "embedding",
79
- embedding: saved.enabled,
80
- embeddingModel: saved.model,
81
- isolatedEmbeddingKey: true,
82
- };
83
- return { profile, signature: embeddingSignature(profile), legacy: false };
84
- }
85
- // Existing installations remain compatible until an independent profile is saved.
86
- const profile = models.profile("default");
87
- return { profile, signature: embeddingSignature(profile), legacy: true };
88
- }
89
-
90
- export async function embedBatches(models, profile, texts) {
91
- if (!profile.embedding) return [];
92
- const vectors = [];
93
- const size = profile.batchSize || 32;
94
- let dimension = profile.dimensions || 0;
95
- for (let start = 0; start < texts.length; start += size) {
96
- const batch = texts.slice(start, start + size);
97
- const result = await models.embed(profile, batch);
98
- if (!Array.isArray(result) || result.length !== batch.length)
99
- throw Error("向量数量与输入不一致");
100
- for (const vector of result) {
101
- if (
102
- !Array.isArray(vector) ||
103
- !vector.length ||
104
- !vector.every(Number.isFinite) ||
105
- !vector.some((value) => value !== 0)
106
- )
107
- throw Error("向量内容无效");
108
- dimension ||= vector.length;
109
- if (vector.length !== dimension) throw Error("向量维度不一致");
110
- vectors.push(vector);
111
- }
112
- }
113
- return vectors;
114
- }
1
+ import { createHash } from "node:crypto";
2
+
3
+ const CONFIG = "embedding-profile";
4
+ export const EMBEDDING_DEFAULTS = {
5
+ enabled: false,
6
+ label: "知识向量",
7
+ baseUrl: "",
8
+ model: "",
9
+ apiKey: "",
10
+ dimensions: 0,
11
+ batchSize: 32,
12
+ timeoutMs: 90000,
13
+ };
14
+
15
+ export function embeddingSignature(profile) {
16
+ return createHash("sha256")
17
+ .update(
18
+ JSON.stringify([
19
+ profile.baseUrl || "",
20
+ profile.embeddingModel || profile.model || "",
21
+ profile.dimensions || 0,
22
+ ]),
23
+ )
24
+ .digest("hex");
25
+ }
26
+
27
+ export function publicEmbeddingProfile(repo) {
28
+ const saved = repo.config(CONFIG, null);
29
+ const { apiKey, ...profile } = { ...EMBEDDING_DEFAULTS, ...saved };
30
+ return {
31
+ ...profile,
32
+ configured: !!saved,
33
+ hasApiKey: !!(apiKey || process.env.EMBEDDING_API_KEY),
34
+ };
35
+ }
36
+
37
+ export function saveEmbeddingProfile(repo, input) {
38
+ const old = { ...EMBEDDING_DEFAULTS, ...repo.config(CONFIG, {}) };
39
+ const next = Object.fromEntries(
40
+ Object.keys(EMBEDDING_DEFAULTS).map((key) => [key, input[key] ?? old[key]]),
41
+ );
42
+ if (typeof next.enabled !== "boolean") throw Error("向量开关无效");
43
+ if (typeof next.apiKey !== "string") throw Error("密钥格式无效");
44
+ if (!next.apiKey) next.apiKey = old.apiKey;
45
+ next.label = String(next.label).trim().slice(0, 100);
46
+ next.model = String(next.model).trim().slice(0, 200);
47
+ next.baseUrl = String(next.baseUrl).trim().replace(/\/$/, "");
48
+ if (next.baseUrl) {
49
+ const url = new URL(next.baseUrl);
50
+ if (
51
+ !["http:", "https:"].includes(url.protocol) ||
52
+ url.username ||
53
+ url.password ||
54
+ url.search ||
55
+ url.hash
56
+ )
57
+ throw Error("向量 API 地址无效");
58
+ }
59
+ if (next.enabled && (!next.baseUrl || !next.model))
60
+ throw Error("启用向量需要独立地址和模型名");
61
+ for (const [key, min, max] of [
62
+ ["dimensions", 0, 65536],
63
+ ["batchSize", 1, 128],
64
+ ["timeoutMs", 1000, 300000],
65
+ ])
66
+ if (!Number.isSafeInteger(next[key]) || next[key] < min || next[key] > max)
67
+ throw Error(`${key} 超出范围`);
68
+ repo.saveConfig(CONFIG, next);
69
+ return publicEmbeddingProfile(repo);
70
+ }
71
+
72
+ export function resolveEmbeddingProfile(repo, models) {
73
+ const saved = repo.config(CONFIG, null);
74
+ if (saved) {
75
+ const profile = {
76
+ ...EMBEDDING_DEFAULTS,
77
+ ...saved,
78
+ id: "embedding",
79
+ embedding: saved.enabled,
80
+ embeddingModel: saved.model,
81
+ isolatedEmbeddingKey: true,
82
+ };
83
+ return { profile, signature: embeddingSignature(profile), legacy: false };
84
+ }
85
+ // Existing installations remain compatible until an independent profile is saved.
86
+ const profile = models.profile("default");
87
+ return { profile, signature: embeddingSignature(profile), legacy: true };
88
+ }
89
+
90
+ export async function embedBatches(models, profile, texts) {
91
+ if (!profile.embedding) return [];
92
+ const vectors = [];
93
+ const size = profile.batchSize || 32;
94
+ let dimension = profile.dimensions || 0;
95
+ for (let start = 0; start < texts.length; start += size) {
96
+ const batch = texts.slice(start, start + size);
97
+ const result = await models.embed(profile, batch);
98
+ if (!Array.isArray(result) || result.length !== batch.length)
99
+ throw Error("向量数量与输入不一致");
100
+ for (const vector of result) {
101
+ if (
102
+ !Array.isArray(vector) ||
103
+ !vector.length ||
104
+ !vector.every(Number.isFinite) ||
105
+ !vector.some((value) => value !== 0)
106
+ )
107
+ throw Error("向量内容无效");
108
+ dimension ||= vector.length;
109
+ if (vector.length !== dimension) throw Error("向量维度不一致");
110
+ vectors.push(vector);
111
+ }
112
+ }
113
+ return vectors;
114
+ }
@@ -1,13 +1,13 @@
1
- export { KnowledgeManager } from "./manager.js";
2
- export { indexChunk, indexMemory, migrateKnowledge } from "./schema.js";
3
- export {
4
- asPlainDocument,
5
- chunkText,
6
- cosine,
7
- ftsMatchQuery,
8
- ftsTokens,
9
- lexicalTerms,
10
- overlapScore,
11
- packVector,
12
- unpackVector,
13
- } from "./retrieval.js";
1
+ export { KnowledgeManager } from "./manager.js";
2
+ export { indexChunk, indexMemory, migrateKnowledge } from "./schema.js";
3
+ export {
4
+ asPlainDocument,
5
+ chunkText,
6
+ cosine,
7
+ ftsMatchQuery,
8
+ ftsTokens,
9
+ lexicalTerms,
10
+ overlapScore,
11
+ packVector,
12
+ unpackVector,
13
+ } from "./retrieval.js";
@@ -1,93 +1,93 @@
1
- export function lexicalTerms(s) {
2
- return new Set(
3
- String(s)
4
- .toLowerCase()
5
- .match(/[a-z0-9]+|[\u4e00-\u9fff]{1,2}/g) || [],
6
- );
7
- }
8
-
9
- export function ftsTokens(s) {
10
- return [...lexicalTerms(s)].join(" ");
11
- }
12
-
13
- export function ftsMatchQuery(s) {
14
- return [...lexicalTerms(s)]
15
- .slice(0, 24)
16
- .map((t) => `"${t.replace(/"/g, "")}"`)
17
- .filter((t) => t.length > 2)
18
- .join(" OR ");
19
- }
20
-
21
- export function overlapScore(text, queryTerms) {
22
- if (!queryTerms?.size) return 0;
23
- return [...lexicalTerms(text)].filter((t) => queryTerms.has(t)).length;
24
- }
25
-
26
- export function cosine(a, b) {
27
- if (!a?.length || !b?.length || a.length !== b.length) return 0;
28
- let dot = 0,
29
- na = 0,
30
- nb = 0;
31
- for (let i = 0; i < a.length; i++) {
32
- dot += a[i] * b[i];
33
- na += a[i] * a[i];
34
- nb += b[i] * b[i];
35
- }
36
- return dot / (Math.sqrt(na) * Math.sqrt(nb) || 1);
37
- }
38
-
39
- export function packVector(values) {
40
- return Buffer.from(new Float32Array(values).buffer);
41
- }
42
-
43
- export function unpackVector(blob) {
44
- if (!blob) return null;
45
- const buf = Buffer.isBuffer(blob) ? blob : Buffer.from(blob);
46
- return Array.from(
47
- new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4),
48
- );
49
- }
50
-
51
- export function asPlainDocument(text) {
52
- let source = String(text || "").replace(/\r\n/g, "\n");
53
- if (/<\/?[a-z][\s\S]*>/i.test(source))
54
- source = source
55
- .replace(/<script[\s\S]*?<\/script>/gi, "\n")
56
- .replace(/<style[\s\S]*?<\/style>/gi, "\n")
57
- .replace(/<h([1-3])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, title) => {
58
- const hashes = "#".repeat(Number(level) || 1);
59
- return `\n${hashes} ${title.replace(/<[^>]+>/g, "").trim()}\n`;
60
- })
61
- .replace(/<[^>]+>/g, " ");
62
- return source
63
- .replace(/[ \t]+\n/g, "\n")
64
- .replace(/\n{3,}/g, "\n\n")
65
- .trim();
66
- }
67
-
68
- export function chunkText(text, { size = 600, overlap = 80 } = {}) {
69
- const source = String(text || "")
70
- .replace(/\r\n/g, "\n")
71
- .trim();
72
- if (!source) return [];
73
- const parts = source.split(/(?=^#{1,3} )/m);
74
- const chunks = [];
75
- for (const part of parts) {
76
- const heading = part.match(/^#{1,3} ([^\n]+)/)?.[1] || "";
77
- const chars = Array.from(part.trim());
78
- if (!chars.length) continue;
79
- if (chars.length <= size) {
80
- chunks.push({ heading, text: part.trim() });
81
- continue;
82
- }
83
- for (let i = 0; i < chars.length; i += Math.max(1, size - overlap)) {
84
- const slice = chars
85
- .slice(i, i + size)
86
- .join("")
87
- .trim();
88
- if (slice) chunks.push({ heading, text: slice });
89
- if (i + size >= chars.length) break;
90
- }
91
- }
92
- return chunks;
93
- }
1
+ export function lexicalTerms(s) {
2
+ return new Set(
3
+ String(s)
4
+ .toLowerCase()
5
+ .match(/[a-z0-9]+|[\u4e00-\u9fff]{1,2}/g) || [],
6
+ );
7
+ }
8
+
9
+ export function ftsTokens(s) {
10
+ return [...lexicalTerms(s)].join(" ");
11
+ }
12
+
13
+ export function ftsMatchQuery(s) {
14
+ return [...lexicalTerms(s)]
15
+ .slice(0, 24)
16
+ .map((t) => `"${t.replace(/"/g, "")}"`)
17
+ .filter((t) => t.length > 2)
18
+ .join(" OR ");
19
+ }
20
+
21
+ export function overlapScore(text, queryTerms) {
22
+ if (!queryTerms?.size) return 0;
23
+ return [...lexicalTerms(text)].filter((t) => queryTerms.has(t)).length;
24
+ }
25
+
26
+ export function cosine(a, b) {
27
+ if (!a?.length || !b?.length || a.length !== b.length) return 0;
28
+ let dot = 0,
29
+ na = 0,
30
+ nb = 0;
31
+ for (let i = 0; i < a.length; i++) {
32
+ dot += a[i] * b[i];
33
+ na += a[i] * a[i];
34
+ nb += b[i] * b[i];
35
+ }
36
+ return dot / (Math.sqrt(na) * Math.sqrt(nb) || 1);
37
+ }
38
+
39
+ export function packVector(values) {
40
+ return Buffer.from(new Float32Array(values).buffer);
41
+ }
42
+
43
+ export function unpackVector(blob) {
44
+ if (!blob) return null;
45
+ const buf = Buffer.isBuffer(blob) ? blob : Buffer.from(blob);
46
+ return Array.from(
47
+ new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4),
48
+ );
49
+ }
50
+
51
+ export function asPlainDocument(text) {
52
+ let source = String(text || "").replace(/\r\n/g, "\n");
53
+ if (/<\/?[a-z][\s\S]*>/i.test(source))
54
+ source = source
55
+ .replace(/<script[\s\S]*?<\/script>/gi, "\n")
56
+ .replace(/<style[\s\S]*?<\/style>/gi, "\n")
57
+ .replace(/<h([1-3])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, title) => {
58
+ const hashes = "#".repeat(Number(level) || 1);
59
+ return `\n${hashes} ${title.replace(/<[^>]+>/g, "").trim()}\n`;
60
+ })
61
+ .replace(/<[^>]+>/g, " ");
62
+ return source
63
+ .replace(/[ \t]+\n/g, "\n")
64
+ .replace(/\n{3,}/g, "\n\n")
65
+ .trim();
66
+ }
67
+
68
+ export function chunkText(text, { size = 600, overlap = 80 } = {}) {
69
+ const source = String(text || "")
70
+ .replace(/\r\n/g, "\n")
71
+ .trim();
72
+ if (!source) return [];
73
+ const parts = source.split(/(?=^#{1,3} )/m);
74
+ const chunks = [];
75
+ for (const part of parts) {
76
+ const heading = part.match(/^#{1,3} ([^\n]+)/)?.[1] || "";
77
+ const chars = Array.from(part.trim());
78
+ if (!chars.length) continue;
79
+ if (chars.length <= size) {
80
+ chunks.push({ heading, text: part.trim() });
81
+ continue;
82
+ }
83
+ for (let i = 0; i < chars.length; i += Math.max(1, size - overlap)) {
84
+ const slice = chars
85
+ .slice(i, i + size)
86
+ .join("")
87
+ .trim();
88
+ if (slice) chunks.push({ heading, text: slice });
89
+ if (i + size >= chars.length) break;
90
+ }
91
+ }
92
+ return chunks;
93
+ }