luckytri 1.1.2 → 1.2.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.en.md +14 -12
- package/README.md +4 -2
- package/docs/en/CHANGELOG.md +24 -0
- package/docs/en/architecture.md +12 -10
- package/docs/en/chat-style.md +13 -13
- package/docs/en/cognition.md +12 -4
- package/docs/en/evolution.md +35 -0
- package/docs/en/her-life.md +5 -2
- package/docs/en/vision.md +50 -0
- package/docs/zh/CHANGELOG.md +24 -0
- package/docs/zh/architecture.md +12 -10
- package/docs/zh/chat-style.md +17 -17
- package/docs/zh/cognition.md +12 -4
- package/docs/zh/evolution.md +35 -0
- package/docs/zh/her-life.md +5 -2
- package/docs/zh/vision.md +50 -0
- package/package.json +4 -1
- package/public/app/assets/{ChatsPage-IfBgNdaD.js → ChatsPage-eDqFIg97.js} +1 -1
- package/public/app/assets/{Empty-DK9nQPCf.js → Empty-CAGOJsfD.js} +1 -1
- package/public/app/assets/{Field-BzKFhuFd.js → Field-CRz-JmN1.js} +1 -1
- package/public/app/assets/{HeartPage-CKxrbfFQ.js → HeartPage-BGWoeMrC.js} +1 -1
- package/public/app/assets/{LifePage-CtxPEi6E.js → LifePage-iao3o3wt.js} +1 -1
- package/public/app/assets/{MemoryPage-B8FLXzLi.js → MemoryPage-Dwi2yi7E.js} +1 -1
- package/public/app/assets/NaturePage-DniImo9U.js +3 -0
- package/public/app/assets/{NaturePage-CHkpb5NK.css → NaturePage-zguDpGWY.css} +1 -1
- package/public/app/assets/{PeoplePage-D3yn0HV0.js → PeoplePage-D-MC3JEt.js} +1 -1
- package/public/app/assets/{PluginsPage-ojv8zawj.js → PluginsPage-D7dJTf96.js} +1 -1
- package/public/app/assets/SystemPage-C5CUHjGh.js +1 -0
- package/public/app/assets/{SystemPage-CGVTvNZw.css → SystemPage-C_Y5M7JU.css} +1 -1
- package/public/app/assets/{TimePage-Dyd-FFbq.js → TimePage-Dwhr3-92.js} +1 -1
- package/public/app/assets/{index-BJfzfgnm.js → index-L0bHax8C.js} +3 -3
- package/public/app/assets/{knowledge-C-bND0Ub.js → knowledge-BTDNafzl.js} +1 -1
- package/public/app/assets/{media-D7G3Nco6.js → media-BBzOAfNG.js} +1 -1
- package/public/app/assets/{mind-ec-G6iNZ.js → mind-CBTkFje2.js} +1 -1
- package/public/app/index.html +3 -3
- package/public/guide.en.html +3 -3
- package/public/guide.html +3 -3
- package/scripts/migrate-instance.js +27 -0
- package/scripts/update-instance.js +21 -2
- package/server/channels/contract.js +5 -1
- package/server/channels/hub.js +2 -2
- package/server/channels/onebot/adapter.js +291 -288
- package/server/channels/onebot/index.js +2 -2
- package/server/channels/qqbot/api.js +194 -199
- package/server/core/context-builder.js +20 -3
- package/server/core/conversation-cues.js +5 -4
- package/server/core/conversation-grounding.js +20 -17
- package/server/core/conversation-needs.js +32 -0
- package/server/core/dialogue-context.js +2 -5
- package/server/core/dialogue-contract.js +23 -16
- package/server/core/message-scheduler.js +85 -78
- package/server/core/model-manager.js +113 -16
- package/server/core/model-presets.js +1 -1
- package/server/core/orchestrator.js +18 -2
- package/server/core/persona-manager.js +14 -5
- package/server/core/reader-check.js +47 -42
- package/server/core/reply-delivery.js +125 -34
- package/server/core/response-validator.js +28 -1
- package/server/core/retired-prompts.js +5 -0
- package/server/core/speech-length.js +43 -0
- package/server/core/turn-processor.js +38 -5
- package/server/core/turn.js +70 -16
- package/server/i18n/errors.js +30 -0
- package/server/i18n/labels.js +5 -0
- package/server/index.js +3 -0
- package/server/knowledge/embedding-profile.js +126 -29
- package/server/knowledge/manager.js +103 -34
- package/server/knowledge/schema.js +197 -158
- package/server/mind/affect.js +267 -265
- package/server/mind/aftermath.js +169 -0
- package/server/mind/anticipations.js +2 -0
- package/server/mind/clock.js +91 -83
- package/server/mind/discretion.js +20 -0
- package/server/mind/episodes.js +2 -1
- package/server/mind/index.js +17 -2
- package/server/mind/initiative.js +29 -14
- package/server/mind/memory.js +47 -5
- package/server/mind/recall/cue.js +67 -0
- package/server/mind/recall/index.js +183 -29
- package/server/mind/recall/roots.js +231 -0
- package/server/mind/recall/schema.js +2 -0
- package/server/mind/recall/store.js +113 -30
- package/server/mind/salience.js +22 -1
- package/server/mind/schema.js +5 -0
- package/server/mind/thoughts.js +276 -274
- package/server/mind/view.js +100 -60
- package/server/plugins/install.js +1 -6
- package/server/plugins/zip.js +18 -4
- package/server/runtime/data-migration.js +193 -0
- package/server/runtime/update-execution.js +43 -4
- package/server/runtime/update-manager.js +4 -1
- package/server/storage/archive.js +1 -1
- package/server/storage/backup-scheduler.js +15 -3
- package/public/app/assets/NaturePage-C7i607ul.js +0 -3
- package/public/app/assets/SystemPage-Cv3rFvyT.js +0 -1
|
@@ -67,9 +67,14 @@ function allowedCollection(db, collection, session) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export class KnowledgeManager {
|
|
70
|
-
|
|
70
|
+
// `embedding` is her one embedding service. When given, a question is
|
|
71
|
+
// embedded through it: the vector the recall index just asked for is
|
|
72
|
+
// reused from its cache, a slow service gives up after a few seconds
|
|
73
|
+
// instead of holding the reply, and repeated failures pause it for both.
|
|
74
|
+
constructor(repo, models, { embedding = null } = {}) {
|
|
71
75
|
this.repo = repo;
|
|
72
76
|
this.models = models;
|
|
77
|
+
this.embedding = embedding;
|
|
73
78
|
}
|
|
74
79
|
collections(session = "") {
|
|
75
80
|
return this.repo.db
|
|
@@ -269,8 +274,11 @@ export class KnowledgeManager {
|
|
|
269
274
|
(!signature ||
|
|
270
275
|
chunk.embedding_profile === signature ||
|
|
271
276
|
(legacy && !chunk.embedding_profile))
|
|
272
|
-
)
|
|
273
|
-
|
|
277
|
+
) {
|
|
278
|
+
// A vector of another length (an old unsigned chunk) is not comparable.
|
|
279
|
+
const stored = unpackVector(chunk.embedding);
|
|
280
|
+
if (stored?.length === vec.length) score += cosine(vec, stored) * 4;
|
|
281
|
+
}
|
|
274
282
|
const ageDays = (cutoff - (chunk.document_created || 0)) / 86400000;
|
|
275
283
|
if (ageDays > 30) score *= 0.85;
|
|
276
284
|
if (score > (best.get(chunk.id) || 0)) best.set(chunk.id, score);
|
|
@@ -308,11 +316,20 @@ export class KnowledgeManager {
|
|
|
308
316
|
.get();
|
|
309
317
|
if (profile.embedding && queries.length && searchable) {
|
|
310
318
|
try {
|
|
311
|
-
vectors =
|
|
312
|
-
this.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
319
|
+
vectors = this.embedding
|
|
320
|
+
? this.embedding.available()
|
|
321
|
+
? (
|
|
322
|
+
await this.embedding.embed(queries, {
|
|
323
|
+
purpose: "query",
|
|
324
|
+
session,
|
|
325
|
+
})
|
|
326
|
+
).map((vector) => Array.from(vector))
|
|
327
|
+
: []
|
|
328
|
+
: await embedBatches(
|
|
329
|
+
this.models,
|
|
330
|
+
profile,
|
|
331
|
+
queries.map((query) => query.slice(0, 2000)),
|
|
332
|
+
);
|
|
316
333
|
} catch {
|
|
317
334
|
vectors = [];
|
|
318
335
|
}
|
|
@@ -322,7 +339,7 @@ export class KnowledgeManager {
|
|
|
322
339
|
async reembedDocument(id) {
|
|
323
340
|
const rows = this.repo.db
|
|
324
341
|
.prepare(
|
|
325
|
-
"SELECT id,text FROM core_chunks WHERE document_id=? ORDER BY ordinal",
|
|
342
|
+
"SELECT id,text,embedding_profile,previous_profile FROM core_chunks WHERE document_id=? ORDER BY ordinal",
|
|
326
343
|
)
|
|
327
344
|
.all(id);
|
|
328
345
|
if (!rows.length) throw Error("文档没有可建立向量的段落");
|
|
@@ -331,37 +348,89 @@ export class KnowledgeManager {
|
|
|
331
348
|
this.models,
|
|
332
349
|
);
|
|
333
350
|
if (!profile.embedding) throw Error("请先启用知识向量模型");
|
|
334
|
-
const vectors = await
|
|
351
|
+
const { vectors, ask } = await this.#vectorsFor(rows, signature, (texts) =>
|
|
352
|
+
embedBatches(this.models, profile, texts),
|
|
353
|
+
);
|
|
354
|
+
if (!this.#saveVectors(rows, vectors, signature, id))
|
|
355
|
+
throw Error("文档已变化,请重新建立向量");
|
|
356
|
+
return { chunks: rows.length, embedded: ask.length };
|
|
357
|
+
}
|
|
358
|
+
// After a model or length change, chunks made under the old profile are
|
|
359
|
+
// searched by words only. This brings them over a few at a time on the
|
|
360
|
+
// maintenance tick, through the paced embedding service. Returns how many
|
|
361
|
+
// chunks moved; 0 when nothing is left or the service is not available.
|
|
362
|
+
async reembedStale({ limit = 20 } = {}) {
|
|
363
|
+
if (!this.embedding?.available()) return 0;
|
|
364
|
+
const { signature, legacy } = resolveEmbeddingProfile(
|
|
365
|
+
this.repo,
|
|
335
366
|
this.models,
|
|
336
|
-
profile,
|
|
337
|
-
rows.map((row) => row.text),
|
|
338
367
|
);
|
|
368
|
+
if (!signature) return 0;
|
|
369
|
+
const rows = this.repo.db
|
|
370
|
+
.prepare(
|
|
371
|
+
`SELECT c.id,c.text,c.embedding_profile,c.previous_profile FROM core_chunks c
|
|
372
|
+
JOIN core_documents d ON d.id=c.document_id
|
|
373
|
+
WHERE d.status='ready' AND c.embedding IS NOT NULL AND c.embedding_profile!=?
|
|
374
|
+
${legacy ? "AND c.embedding_profile!=''" : ""}
|
|
375
|
+
ORDER BY c.created DESC LIMIT ?`,
|
|
376
|
+
)
|
|
377
|
+
.all(signature, limit);
|
|
378
|
+
if (!rows.length) return 0;
|
|
379
|
+
const { vectors } = await this.#vectorsFor(rows, signature, (texts) =>
|
|
380
|
+
this.embedding.embed(texts, { purpose: "document" }),
|
|
381
|
+
);
|
|
382
|
+
this.#saveVectors(rows, vectors, signature);
|
|
383
|
+
return rows.length;
|
|
384
|
+
}
|
|
385
|
+
// A chunk whose previous vector already belongs to this profile swaps
|
|
386
|
+
// back; only the rest are asked for.
|
|
387
|
+
async #vectorsFor(rows, signature, embed) {
|
|
388
|
+
const ask = rows.filter((row) => row.previous_profile !== signature);
|
|
389
|
+
const made = ask.length ? await embed(ask.map((row) => row.text)) : [];
|
|
390
|
+
const vectors = new Map(ask.map((row, i) => [row.id, made[i]]));
|
|
391
|
+
return { vectors, ask };
|
|
392
|
+
}
|
|
393
|
+
// The current vector becomes the previous one, so nothing is thrown away
|
|
394
|
+
// until a third profile arrives. A trigger re-indexes the chunk.
|
|
395
|
+
#saveVectors(rows, vectors, signature, documentId = null) {
|
|
339
396
|
const db = this.repo.db;
|
|
340
|
-
db.
|
|
397
|
+
const fresh = db.prepare(
|
|
398
|
+
`UPDATE core_chunks SET
|
|
399
|
+
previous_embedding=CASE WHEN embedding_profile!=? THEN embedding ELSE previous_embedding END,
|
|
400
|
+
previous_profile=CASE WHEN embedding_profile!=? THEN embedding_profile ELSE previous_profile END,
|
|
401
|
+
embedding=?,embedding_profile=?
|
|
402
|
+
WHERE id=? AND text=?`,
|
|
403
|
+
);
|
|
404
|
+
const swap = db.prepare(
|
|
405
|
+
`UPDATE core_chunks SET
|
|
406
|
+
embedding=previous_embedding,embedding_profile=previous_profile,
|
|
407
|
+
previous_embedding=embedding,previous_profile=embedding_profile
|
|
408
|
+
WHERE id=? AND previous_profile=?`,
|
|
409
|
+
);
|
|
410
|
+
db.exec("SAVEPOINT reembed_chunks");
|
|
341
411
|
try {
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
).changes
|
|
354
|
-
)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
db.exec("
|
|
360
|
-
|
|
361
|
-
throw error;
|
|
412
|
+
for (const row of rows) {
|
|
413
|
+
const vector = vectors.get(row.id);
|
|
414
|
+
const changed = vector
|
|
415
|
+
? fresh.run(
|
|
416
|
+
signature,
|
|
417
|
+
signature,
|
|
418
|
+
packVector(vector),
|
|
419
|
+
signature,
|
|
420
|
+
row.id,
|
|
421
|
+
row.text,
|
|
422
|
+
).changes
|
|
423
|
+
: swap.run(row.id, signature).changes;
|
|
424
|
+
if (!changed && documentId) throw Error("changed");
|
|
425
|
+
}
|
|
426
|
+
db.exec("RELEASE reembed_chunks");
|
|
427
|
+
} catch {
|
|
428
|
+
db.exec("ROLLBACK TO reembed_chunks");
|
|
429
|
+
db.exec("RELEASE reembed_chunks");
|
|
430
|
+
return false;
|
|
362
431
|
}
|
|
363
432
|
this.repo.store.revision++;
|
|
364
|
-
return
|
|
433
|
+
return true;
|
|
365
434
|
}
|
|
366
435
|
removeDocument(id) {
|
|
367
436
|
const row = this.repo.db
|
|
@@ -1,158 +1,197 @@
|
|
|
1
|
-
import { ftsTokens } from "./retrieval.js";
|
|
2
|
-
|
|
3
|
-
export function indexMemory(db, id, content) {
|
|
4
|
-
db.prepare("DELETE FROM core_memory_fts WHERE memory_id=?").run(id);
|
|
5
|
-
const tokens = ftsTokens(content);
|
|
6
|
-
if (tokens)
|
|
7
|
-
db.prepare(
|
|
8
|
-
"INSERT INTO core_memory_fts(memory_id, tokens) VALUES (?,?)",
|
|
9
|
-
).run(id, tokens);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function indexChunk(db, id, content) {
|
|
13
|
-
db.prepare("DELETE FROM core_chunk_fts WHERE chunk_id=?").run(id);
|
|
14
|
-
const tokens = ftsTokens(content);
|
|
15
|
-
if (tokens)
|
|
16
|
-
db.prepare("INSERT INTO core_chunk_fts(chunk_id, tokens) VALUES (?,?)").run(
|
|
17
|
-
id,
|
|
18
|
-
tokens,
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function mergeLegacyMemories(db) {
|
|
23
|
-
if (db.prepare("SELECT id FROM core_config WHERE id='memory-merge-v1'").get())
|
|
24
|
-
return;
|
|
25
|
-
db.exec("BEGIN IMMEDIATE");
|
|
26
|
-
try {
|
|
27
|
-
for (const m of db.prepare("SELECT * FROM memories").all()) {
|
|
28
|
-
const sessionId =
|
|
29
|
-
m.scope === "shared"
|
|
30
|
-
? "__shared__"
|
|
31
|
-
: m.scope === "private"
|
|
32
|
-
? `__private__:${m.user_id}`
|
|
33
|
-
: m.scope;
|
|
34
|
-
const id = "legacy:" + m.id;
|
|
35
|
-
if (db.prepare("SELECT id FROM core_memories WHERE id=?").get(id))
|
|
36
|
-
continue;
|
|
37
|
-
db.prepare(
|
|
38
|
-
"INSERT INTO core_memories(id,session_id,subject,content,type,confidence,importance,status,locked,sources,created,updated) VALUES (?,?,?,?,'reviewed',1,1,'confirmed',1,'[]',?,?)",
|
|
39
|
-
).run(
|
|
40
|
-
id,
|
|
41
|
-
sessionId,
|
|
42
|
-
m.user_id,
|
|
43
|
-
m.content,
|
|
44
|
-
m.time || Date.now(),
|
|
45
|
-
Date.now(),
|
|
46
|
-
);
|
|
47
|
-
indexMemory(db, id, m.content);
|
|
48
|
-
}
|
|
49
|
-
db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
|
|
50
|
-
"memory-merge-v1",
|
|
51
|
-
JSON.stringify({ time: Date.now() }),
|
|
52
|
-
);
|
|
53
|
-
db.exec("COMMIT");
|
|
54
|
-
} catch (e) {
|
|
55
|
-
db.exec("ROLLBACK");
|
|
56
|
-
throw e;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const KNOWLEDGE_TRIGGERS = [
|
|
61
|
-
`CREATE TRIGGER IF NOT EXISTS core_vector_changed AFTER UPDATE OF embedding ON core_chunks BEGIN
|
|
62
|
-
DELETE FROM core_vector_buckets WHERE chunk_id=NEW.id;
|
|
63
|
-
UPDATE core_chunks SET vector_indexed=0 WHERE id=NEW.id;
|
|
64
|
-
END`,
|
|
65
|
-
`CREATE TRIGGER IF NOT EXISTS core_vector_deleted AFTER DELETE ON core_chunks BEGIN
|
|
66
|
-
DELETE FROM core_vector_buckets WHERE chunk_id=OLD.id;
|
|
67
|
-
END`,
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
export function migrateKnowledge(db) {
|
|
71
|
-
db.exec(`
|
|
72
|
-
CREATE TABLE IF NOT EXISTS core_collections (
|
|
73
|
-
id TEXT PRIMARY KEY,
|
|
74
|
-
name TEXT NOT NULL,
|
|
75
|
-
scope TEXT NOT NULL,
|
|
76
|
-
created INTEGER NOT NULL,
|
|
77
|
-
updated INTEGER NOT NULL
|
|
78
|
-
);
|
|
79
|
-
CREATE TABLE IF NOT EXISTS core_collection_sessions (
|
|
80
|
-
collection_id TEXT NOT NULL,
|
|
81
|
-
session_id TEXT NOT NULL,
|
|
82
|
-
PRIMARY KEY(collection_id, session_id)
|
|
83
|
-
);
|
|
84
|
-
CREATE TABLE IF NOT EXISTS core_documents (
|
|
85
|
-
id TEXT PRIMARY KEY,
|
|
86
|
-
collection_id TEXT NOT NULL,
|
|
87
|
-
title TEXT NOT NULL,
|
|
88
|
-
source TEXT,
|
|
89
|
-
path TEXT,
|
|
90
|
-
status TEXT NOT NULL,
|
|
91
|
-
error TEXT,
|
|
92
|
-
created INTEGER NOT NULL,
|
|
93
|
-
updated INTEGER NOT NULL,
|
|
94
|
-
bytes INTEGER,
|
|
95
|
-
hash TEXT
|
|
96
|
-
);
|
|
97
|
-
CREATE TABLE IF NOT EXISTS core_chunks (
|
|
98
|
-
id TEXT PRIMARY KEY,
|
|
99
|
-
document_id TEXT NOT NULL,
|
|
100
|
-
collection_id TEXT NOT NULL,
|
|
101
|
-
ordinal INTEGER NOT NULL,
|
|
102
|
-
heading TEXT,
|
|
103
|
-
text TEXT NOT NULL,
|
|
104
|
-
tokens TEXT,
|
|
105
|
-
embedding BLOB,
|
|
106
|
-
hash TEXT,
|
|
107
|
-
created INTEGER NOT NULL
|
|
108
|
-
);
|
|
109
|
-
`);
|
|
110
|
-
db.exec(
|
|
111
|
-
"CREATE VIRTUAL TABLE IF NOT EXISTS core_memory_fts USING fts5(memory_id UNINDEXED, tokens)",
|
|
112
|
-
);
|
|
113
|
-
db.exec(
|
|
114
|
-
"CREATE VIRTUAL TABLE IF NOT EXISTS core_chunk_fts USING fts5(chunk_id UNINDEXED, tokens)",
|
|
115
|
-
);
|
|
116
|
-
if (
|
|
117
|
-
!db
|
|
118
|
-
.prepare("PRAGMA table_info(core_chunks)")
|
|
119
|
-
.all()
|
|
120
|
-
.some((c) => c.name === "vector_indexed")
|
|
121
|
-
)
|
|
122
|
-
db.exec(
|
|
123
|
-
"ALTER TABLE core_chunks ADD COLUMN vector_indexed INTEGER NOT NULL DEFAULT 0",
|
|
124
|
-
);
|
|
125
|
-
if (
|
|
126
|
-
!db
|
|
127
|
-
.prepare("PRAGMA table_info(core_chunks)")
|
|
128
|
-
.all()
|
|
129
|
-
.some((c) => c.name === "embedding_profile")
|
|
130
|
-
)
|
|
131
|
-
db.exec(
|
|
132
|
-
"ALTER TABLE core_chunks ADD COLUMN embedding_profile TEXT NOT NULL DEFAULT ''",
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
db
|
|
154
|
-
"
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
1
|
+
import { ftsTokens } from "./retrieval.js";
|
|
2
|
+
|
|
3
|
+
export function indexMemory(db, id, content) {
|
|
4
|
+
db.prepare("DELETE FROM core_memory_fts WHERE memory_id=?").run(id);
|
|
5
|
+
const tokens = ftsTokens(content);
|
|
6
|
+
if (tokens)
|
|
7
|
+
db.prepare(
|
|
8
|
+
"INSERT INTO core_memory_fts(memory_id, tokens) VALUES (?,?)",
|
|
9
|
+
).run(id, tokens);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function indexChunk(db, id, content) {
|
|
13
|
+
db.prepare("DELETE FROM core_chunk_fts WHERE chunk_id=?").run(id);
|
|
14
|
+
const tokens = ftsTokens(content);
|
|
15
|
+
if (tokens)
|
|
16
|
+
db.prepare("INSERT INTO core_chunk_fts(chunk_id, tokens) VALUES (?,?)").run(
|
|
17
|
+
id,
|
|
18
|
+
tokens,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function mergeLegacyMemories(db) {
|
|
23
|
+
if (db.prepare("SELECT id FROM core_config WHERE id='memory-merge-v1'").get())
|
|
24
|
+
return;
|
|
25
|
+
db.exec("BEGIN IMMEDIATE");
|
|
26
|
+
try {
|
|
27
|
+
for (const m of db.prepare("SELECT * FROM memories").all()) {
|
|
28
|
+
const sessionId =
|
|
29
|
+
m.scope === "shared"
|
|
30
|
+
? "__shared__"
|
|
31
|
+
: m.scope === "private"
|
|
32
|
+
? `__private__:${m.user_id}`
|
|
33
|
+
: m.scope;
|
|
34
|
+
const id = "legacy:" + m.id;
|
|
35
|
+
if (db.prepare("SELECT id FROM core_memories WHERE id=?").get(id))
|
|
36
|
+
continue;
|
|
37
|
+
db.prepare(
|
|
38
|
+
"INSERT INTO core_memories(id,session_id,subject,content,type,confidence,importance,status,locked,sources,created,updated) VALUES (?,?,?,?,'reviewed',1,1,'confirmed',1,'[]',?,?)",
|
|
39
|
+
).run(
|
|
40
|
+
id,
|
|
41
|
+
sessionId,
|
|
42
|
+
m.user_id,
|
|
43
|
+
m.content,
|
|
44
|
+
m.time || Date.now(),
|
|
45
|
+
Date.now(),
|
|
46
|
+
);
|
|
47
|
+
indexMemory(db, id, m.content);
|
|
48
|
+
}
|
|
49
|
+
db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
|
|
50
|
+
"memory-merge-v1",
|
|
51
|
+
JSON.stringify({ time: Date.now() }),
|
|
52
|
+
);
|
|
53
|
+
db.exec("COMMIT");
|
|
54
|
+
} catch (e) {
|
|
55
|
+
db.exec("ROLLBACK");
|
|
56
|
+
throw e;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const KNOWLEDGE_TRIGGERS = [
|
|
61
|
+
`CREATE TRIGGER IF NOT EXISTS core_vector_changed AFTER UPDATE OF embedding ON core_chunks BEGIN
|
|
62
|
+
DELETE FROM core_vector_buckets WHERE chunk_id=NEW.id;
|
|
63
|
+
UPDATE core_chunks SET vector_indexed=0 WHERE id=NEW.id;
|
|
64
|
+
END`,
|
|
65
|
+
`CREATE TRIGGER IF NOT EXISTS core_vector_deleted AFTER DELETE ON core_chunks BEGIN
|
|
66
|
+
DELETE FROM core_vector_buckets WHERE chunk_id=OLD.id;
|
|
67
|
+
END`,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
export function migrateKnowledge(db) {
|
|
71
|
+
db.exec(`
|
|
72
|
+
CREATE TABLE IF NOT EXISTS core_collections (
|
|
73
|
+
id TEXT PRIMARY KEY,
|
|
74
|
+
name TEXT NOT NULL,
|
|
75
|
+
scope TEXT NOT NULL,
|
|
76
|
+
created INTEGER NOT NULL,
|
|
77
|
+
updated INTEGER NOT NULL
|
|
78
|
+
);
|
|
79
|
+
CREATE TABLE IF NOT EXISTS core_collection_sessions (
|
|
80
|
+
collection_id TEXT NOT NULL,
|
|
81
|
+
session_id TEXT NOT NULL,
|
|
82
|
+
PRIMARY KEY(collection_id, session_id)
|
|
83
|
+
);
|
|
84
|
+
CREATE TABLE IF NOT EXISTS core_documents (
|
|
85
|
+
id TEXT PRIMARY KEY,
|
|
86
|
+
collection_id TEXT NOT NULL,
|
|
87
|
+
title TEXT NOT NULL,
|
|
88
|
+
source TEXT,
|
|
89
|
+
path TEXT,
|
|
90
|
+
status TEXT NOT NULL,
|
|
91
|
+
error TEXT,
|
|
92
|
+
created INTEGER NOT NULL,
|
|
93
|
+
updated INTEGER NOT NULL,
|
|
94
|
+
bytes INTEGER,
|
|
95
|
+
hash TEXT
|
|
96
|
+
);
|
|
97
|
+
CREATE TABLE IF NOT EXISTS core_chunks (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
document_id TEXT NOT NULL,
|
|
100
|
+
collection_id TEXT NOT NULL,
|
|
101
|
+
ordinal INTEGER NOT NULL,
|
|
102
|
+
heading TEXT,
|
|
103
|
+
text TEXT NOT NULL,
|
|
104
|
+
tokens TEXT,
|
|
105
|
+
embedding BLOB,
|
|
106
|
+
hash TEXT,
|
|
107
|
+
created INTEGER NOT NULL
|
|
108
|
+
);
|
|
109
|
+
`);
|
|
110
|
+
db.exec(
|
|
111
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS core_memory_fts USING fts5(memory_id UNINDEXED, tokens)",
|
|
112
|
+
);
|
|
113
|
+
db.exec(
|
|
114
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS core_chunk_fts USING fts5(chunk_id UNINDEXED, tokens)",
|
|
115
|
+
);
|
|
116
|
+
if (
|
|
117
|
+
!db
|
|
118
|
+
.prepare("PRAGMA table_info(core_chunks)")
|
|
119
|
+
.all()
|
|
120
|
+
.some((c) => c.name === "vector_indexed")
|
|
121
|
+
)
|
|
122
|
+
db.exec(
|
|
123
|
+
"ALTER TABLE core_chunks ADD COLUMN vector_indexed INTEGER NOT NULL DEFAULT 0",
|
|
124
|
+
);
|
|
125
|
+
if (
|
|
126
|
+
!db
|
|
127
|
+
.prepare("PRAGMA table_info(core_chunks)")
|
|
128
|
+
.all()
|
|
129
|
+
.some((c) => c.name === "embedding_profile")
|
|
130
|
+
)
|
|
131
|
+
db.exec(
|
|
132
|
+
"ALTER TABLE core_chunks ADD COLUMN embedding_profile TEXT NOT NULL DEFAULT ''",
|
|
133
|
+
);
|
|
134
|
+
// The vector a chunk had under the profile before the current one, so
|
|
135
|
+
// switching back costs no requests.
|
|
136
|
+
for (const [name, type] of [
|
|
137
|
+
["previous_embedding", "BLOB"],
|
|
138
|
+
["previous_profile", "TEXT NOT NULL DEFAULT ''"],
|
|
139
|
+
])
|
|
140
|
+
if (
|
|
141
|
+
!db
|
|
142
|
+
.prepare("PRAGMA table_info(core_chunks)")
|
|
143
|
+
.all()
|
|
144
|
+
.some((c) => c.name === name)
|
|
145
|
+
)
|
|
146
|
+
db.exec(`ALTER TABLE core_chunks ADD COLUMN ${name} ${type}`);
|
|
147
|
+
db.exec(`CREATE INDEX IF NOT EXISTS core_chunks_collection ON core_chunks(collection_id, document_id);
|
|
148
|
+
CREATE INDEX IF NOT EXISTS core_chunks_unindexed ON core_chunks(vector_indexed) WHERE embedding IS NOT NULL;
|
|
149
|
+
CREATE TABLE IF NOT EXISTS core_vector_buckets(dimension INTEGER NOT NULL,band INTEGER NOT NULL,bucket INTEGER NOT NULL,chunk_id TEXT NOT NULL,PRIMARY KEY(dimension,band,bucket,chunk_id));
|
|
150
|
+
CREATE INDEX IF NOT EXISTS core_vector_buckets_chunk ON core_vector_buckets(chunk_id);`);
|
|
151
|
+
db.exec(KNOWLEDGE_TRIGGERS.join(";") + ";");
|
|
152
|
+
if (
|
|
153
|
+
!db
|
|
154
|
+
.prepare("SELECT id FROM core_collections WHERE id='shared-default'")
|
|
155
|
+
.get()
|
|
156
|
+
)
|
|
157
|
+
db.prepare(
|
|
158
|
+
"INSERT INTO core_collections(id,name,scope,created,updated) VALUES ('shared-default','共享知识库','shared',?,?)",
|
|
159
|
+
).run(Date.now(), Date.now());
|
|
160
|
+
mergeLegacyMemories(db);
|
|
161
|
+
if (
|
|
162
|
+
!db.prepare("SELECT id FROM core_config WHERE id='memory-fts-v2'").get()
|
|
163
|
+
) {
|
|
164
|
+
db.exec("BEGIN IMMEDIATE");
|
|
165
|
+
try {
|
|
166
|
+
db.exec("DELETE FROM core_memory_fts");
|
|
167
|
+
for (const m of db.prepare("SELECT id,content FROM core_memories").all())
|
|
168
|
+
indexMemory(db, m.id, m.content);
|
|
169
|
+
db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
|
|
170
|
+
"memory-fts-v2",
|
|
171
|
+
JSON.stringify({ time: Date.now() }),
|
|
172
|
+
);
|
|
173
|
+
db.exec("COMMIT");
|
|
174
|
+
} catch (error) {
|
|
175
|
+
db.exec("ROLLBACK");
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (!db.prepare("SELECT id FROM core_config WHERE id='chunk-fts-v1'").get()) {
|
|
180
|
+
db.exec("BEGIN IMMEDIATE");
|
|
181
|
+
try {
|
|
182
|
+
db.exec("DELETE FROM core_chunk_fts");
|
|
183
|
+
for (const chunk of db
|
|
184
|
+
.prepare("SELECT id,text FROM core_chunks ORDER BY rowid")
|
|
185
|
+
.iterate())
|
|
186
|
+
indexChunk(db, chunk.id, chunk.text);
|
|
187
|
+
db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
|
|
188
|
+
"chunk-fts-v1",
|
|
189
|
+
JSON.stringify({ time: Date.now() }),
|
|
190
|
+
);
|
|
191
|
+
db.exec("COMMIT");
|
|
192
|
+
} catch (error) {
|
|
193
|
+
db.exec("ROLLBACK");
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|