@agent-spaces/server 0.3.63 → 0.3.64
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/dist/adapters/claude-code-runtime/message-format.js +1 -1
- package/dist/adapters/git.js +161 -57
- package/dist/agents/agent-designer.js +66 -12
- package/dist/agents/issue-task-controller.js +1 -1
- package/dist/app.js +50 -1
- package/dist/package.json +1 -1
- package/dist/routes/agent-commands.js +72 -0
- package/dist/routes/agent-sse.js +6 -0
- package/dist/routes/agent.js +15 -1
- package/dist/routes/database.js +345 -0
- package/dist/routes/git.js +52 -1
- package/dist/routes/kanban.js +39 -0
- package/dist/services/agent-commands.js +168 -0
- package/dist/services/builtin-tools/command-tools.js +117 -0
- package/dist/services/builtin-tools/database-tools.js +578 -0
- package/dist/services/builtin-tools/index.js +5 -0
- package/dist/services/builtin-tools/input-helpers.js +26 -0
- package/dist/services/builtin-tools/issue-tools.js +209 -0
- package/dist/services/builtin-tools/kanban-tools.js +277 -0
- package/dist/services/database-vector.js +244 -0
- package/dist/services/kanban.js +50 -0
- package/dist/storage/database-store.js +365 -0
- package/dist/storage/kanban-store.js +142 -0
- package/dist/web/404.html +1 -1
- package/dist/web/__next.__PAGE__.txt +4 -4
- package/dist/web/__next._full.txt +24 -29
- package/dist/web/__next._head.txt +4 -4
- package/dist/web/__next._index.txt +14 -14
- package/dist/web/__next._tree.txt +3 -8
- package/dist/web/_next/static/chunks/0-9194nbxy_6d.js +1 -0
- package/dist/web/_next/static/chunks/{13~y-b.b9rrw4.js → 01~quun6sefgl.js} +1 -1
- package/dist/web/_next/static/chunks/04a4vsq2wknm4.js +1 -0
- package/dist/web/_next/static/chunks/04dg4l4hv11q2.js +3 -0
- package/dist/web/_next/static/chunks/04fxddw-y20~-.js +7 -0
- package/dist/web/_next/static/chunks/04w7~5ooz_pwm.js +1 -0
- package/dist/web/_next/static/chunks/05oc21sj24wtq.js +1 -0
- package/dist/web/_next/static/chunks/{0-oog4zg4_m~m.css → 062f9zqctzg.b.css} +0 -3
- package/dist/web/_next/static/chunks/{014x2qbuiszni.js → 08ckpwmmwlors.js} +3 -3
- package/dist/web/_next/static/chunks/08k~meqbd8d6v.js +1 -0
- package/dist/web/_next/static/chunks/099h96wjsdb25.js +2 -0
- package/dist/web/_next/static/chunks/09h23logvq65m.js +1 -0
- package/dist/web/_next/static/chunks/0_mvduwjsm35w.js +1 -0
- package/dist/web/_next/static/chunks/0a5y-vhf4i5id.js +3 -0
- package/dist/web/_next/static/chunks/0ake2f3x2uan4.js +24 -0
- package/dist/web/_next/static/chunks/0ck8dw_9-1cdx.js +1 -0
- package/dist/web/_next/static/chunks/0f7cb9li~3jra.js +67 -0
- package/dist/web/_next/static/chunks/0fttswpzu69qx.js +1 -0
- package/dist/web/_next/static/chunks/0ghs9on7wodrm.js +1 -0
- package/dist/web/_next/static/chunks/0ghx3j_.-gdyd.js +1 -0
- package/dist/web/_next/static/chunks/0h9cu-k6z5o08.js +1 -0
- package/dist/web/_next/static/chunks/{02c22g~xd-y30.js → 0iuf30t0rjsjv.js} +2 -2
- package/dist/web/_next/static/chunks/{046me66nilkff.js → 0jb4~3qqkw2ps.js} +1 -1
- package/dist/web/_next/static/chunks/0k4zdm72k_bfy.js +1 -0
- package/dist/web/_next/static/chunks/{0kof5qdg8c2-k.js → 0n6_4so_afuwk.js} +37 -37
- package/dist/web/_next/static/chunks/0nb8.iteznuvx.js +1 -0
- package/dist/web/_next/static/chunks/0os58q93stt7j.js +3 -0
- package/dist/web/_next/static/chunks/0s9lmk8iauou3.js +1 -0
- package/dist/web/_next/static/chunks/0sp_zbx~w0am7.js +1 -0
- package/dist/web/_next/static/chunks/0tel~zsv4z8l_.js +1 -0
- package/dist/web/_next/static/chunks/0twamv4wr_dbv.js +1 -0
- package/dist/web/_next/static/chunks/{0_udc.6.tueey.js → 0uhol5wafdbl~.js} +1 -1
- package/dist/web/_next/static/chunks/0v3a_x4vt42kx.js +5 -0
- package/dist/web/_next/static/chunks/{002-dxhags4dj.js → 0v6hte2z-32ca.js} +2 -2
- package/dist/web/_next/static/chunks/0v7q4mys.mnm..css +1 -0
- package/dist/web/_next/static/chunks/{0ogpc3f3keopc.js → 0vbqaynjq8ga..js} +2 -2
- package/dist/web/_next/static/chunks/0xbaakj0dfc0..js +1 -0
- package/dist/web/_next/static/chunks/0z~vqzbm_q371.js +1 -0
- package/dist/web/_next/static/chunks/0~3xm80~j9ddg.js +1 -0
- package/dist/web/_next/static/chunks/{0y09i51u6ixq5.js → 10l-9myrdo3x9.js} +1 -1
- package/dist/web/_next/static/chunks/12jm4gwmvq-0q.js +90 -0
- package/dist/web/_next/static/chunks/{0aa6bcdmtpekn.js → 1442stwa0gbxe.js} +1 -1
- package/dist/web/_next/static/chunks/14s37n6h0_q3x.js +16 -0
- package/dist/web/_next/static/chunks/161ik5kfyuwtd.js +1 -0
- package/dist/web/_next/static/chunks/17k~zbv0_4rep.js +1 -0
- package/dist/web/_not-found/__next._full.txt +23 -23
- package/dist/web/_not-found/__next._head.txt +4 -4
- package/dist/web/_not-found/__next._index.txt +14 -14
- package/dist/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
- package/dist/web/_not-found/__next._not-found.txt +3 -3
- package/dist/web/_not-found/__next._tree.txt +3 -3
- package/dist/web/_not-found.html +1 -1
- package/dist/web/_not-found.txt +23 -23
- package/dist/web/index.html +1 -1
- package/dist/web/index.txt +24 -29
- package/dist/web/login/__next._full.txt +32 -36
- package/dist/web/login/__next._head.txt +4 -4
- package/dist/web/login/__next._index.txt +14 -14
- package/dist/web/login/__next._tree.txt +3 -8
- package/dist/web/login/__next.login.__PAGE__.txt +4 -4
- package/dist/web/login/__next.login.txt +3 -3
- package/dist/web/login.html +1 -1
- package/dist/web/login.txt +32 -36
- package/dist/web/settings/__next._full.txt +26 -31
- package/dist/web/settings/__next._head.txt +4 -4
- package/dist/web/settings/__next._index.txt +14 -14
- package/dist/web/settings/__next._tree.txt +3 -8
- package/dist/web/settings/__next.settings.__PAGE__.txt +4 -4
- package/dist/web/settings/__next.settings.txt +5 -5
- package/dist/web/settings/agents/__next._full.txt +26 -31
- package/dist/web/settings/agents/__next._head.txt +4 -4
- package/dist/web/settings/agents/__next._index.txt +14 -14
- package/dist/web/settings/agents/__next._tree.txt +3 -8
- package/dist/web/settings/agents/__next.settings.agents.__PAGE__.txt +4 -4
- package/dist/web/settings/agents/__next.settings.agents.txt +3 -3
- package/dist/web/settings/agents/__next.settings.txt +5 -5
- package/dist/web/settings/agents.html +1 -1
- package/dist/web/settings/agents.txt +26 -31
- package/dist/web/settings/mcps/__next._full.txt +26 -31
- package/dist/web/settings/mcps/__next._head.txt +4 -4
- package/dist/web/settings/mcps/__next._index.txt +14 -14
- package/dist/web/settings/mcps/__next._tree.txt +3 -8
- package/dist/web/settings/mcps/__next.settings.mcps.__PAGE__.txt +4 -4
- package/dist/web/settings/mcps/__next.settings.mcps.txt +3 -3
- package/dist/web/settings/mcps/__next.settings.txt +5 -5
- package/dist/web/settings/mcps.html +1 -1
- package/dist/web/settings/mcps.txt +26 -31
- package/dist/web/settings/models/__next._full.txt +26 -31
- package/dist/web/settings/models/__next._head.txt +4 -4
- package/dist/web/settings/models/__next._index.txt +14 -14
- package/dist/web/settings/models/__next._tree.txt +3 -8
- package/dist/web/settings/models/__next.settings.models.__PAGE__.txt +4 -4
- package/dist/web/settings/models/__next.settings.models.txt +3 -3
- package/dist/web/settings/models/__next.settings.txt +5 -5
- package/dist/web/settings/models.html +1 -1
- package/dist/web/settings/models.txt +26 -31
- package/dist/web/settings/output-styles/__next._full.txt +26 -31
- package/dist/web/settings/output-styles/__next._head.txt +4 -4
- package/dist/web/settings/output-styles/__next._index.txt +14 -14
- package/dist/web/settings/output-styles/__next._tree.txt +3 -8
- package/dist/web/settings/output-styles/__next.settings.output-styles.__PAGE__.txt +4 -4
- package/dist/web/settings/output-styles/__next.settings.output-styles.txt +3 -3
- package/dist/web/settings/output-styles/__next.settings.txt +5 -5
- package/dist/web/settings/output-styles.html +1 -1
- package/dist/web/settings/output-styles.txt +26 -31
- package/dist/web/settings/prompts/__next._full.txt +26 -31
- package/dist/web/settings/prompts/__next._head.txt +4 -4
- package/dist/web/settings/prompts/__next._index.txt +14 -14
- package/dist/web/settings/prompts/__next._tree.txt +3 -8
- package/dist/web/settings/prompts/__next.settings.prompts.__PAGE__.txt +4 -4
- package/dist/web/settings/prompts/__next.settings.prompts.txt +3 -3
- package/dist/web/settings/prompts/__next.settings.txt +5 -5
- package/dist/web/settings/prompts.html +1 -1
- package/dist/web/settings/prompts.txt +26 -31
- package/dist/web/settings/providers/__next._full.txt +26 -31
- package/dist/web/settings/providers/__next._head.txt +4 -4
- package/dist/web/settings/providers/__next._index.txt +14 -14
- package/dist/web/settings/providers/__next._tree.txt +3 -8
- package/dist/web/settings/providers/__next.settings.providers.__PAGE__.txt +4 -4
- package/dist/web/settings/providers/__next.settings.providers.txt +3 -3
- package/dist/web/settings/providers/__next.settings.txt +5 -5
- package/dist/web/settings/providers.html +1 -1
- package/dist/web/settings/providers.txt +26 -31
- package/dist/web/settings/skills/__next._full.txt +26 -31
- package/dist/web/settings/skills/__next._head.txt +4 -4
- package/dist/web/settings/skills/__next._index.txt +14 -14
- package/dist/web/settings/skills/__next._tree.txt +3 -8
- package/dist/web/settings/skills/__next.settings.skills.__PAGE__.txt +4 -4
- package/dist/web/settings/skills/__next.settings.skills.txt +3 -3
- package/dist/web/settings/skills/__next.settings.txt +5 -5
- package/dist/web/settings/skills.html +1 -1
- package/dist/web/settings/skills.txt +26 -31
- package/dist/web/settings/tools/__next._full.txt +38 -0
- package/dist/web/settings/tools/__next._head.txt +6 -0
- package/dist/web/settings/tools/__next._index.txt +15 -0
- package/dist/web/settings/tools/__next._tree.txt +3 -0
- package/dist/web/settings/tools/__next.settings.tools.__PAGE__.txt +9 -0
- package/dist/web/settings/tools/__next.settings.tools.txt +5 -0
- package/dist/web/settings/tools/__next.settings.txt +7 -0
- package/dist/web/settings/tools.html +1 -0
- package/dist/web/settings/tools.txt +38 -0
- package/dist/web/settings.html +1 -1
- package/dist/web/settings.txt +26 -31
- package/dist/web/workflows/__next._full.txt +34 -38
- package/dist/web/workflows/__next._head.txt +4 -4
- package/dist/web/workflows/__next._index.txt +14 -14
- package/dist/web/workflows/__next._tree.txt +3 -8
- package/dist/web/workflows/__next.workflows.__PAGE__.txt +4 -4
- package/dist/web/workflows/__next.workflows.txt +3 -3
- package/dist/web/workflows.html +1 -1
- package/dist/web/workflows.txt +34 -38
- package/dist/web/workspace/_/__next._full.txt +25 -29
- package/dist/web/workspace/_/__next._head.txt +4 -4
- package/dist/web/workspace/_/__next._index.txt +14 -14
- package/dist/web/workspace/_/__next._tree.txt +4 -8
- package/dist/web/workspace/_/__next.workspace.$d$id.__PAGE__.txt +3 -3
- package/dist/web/workspace/_/__next.workspace.$d$id.txt +3 -3
- package/dist/web/workspace/_/__next.workspace.txt +3 -3
- package/dist/web/workspace/_.html +1 -1
- package/dist/web/workspace/_.txt +25 -29
- package/dist/web/workspaces/__next._full.txt +24 -29
- package/dist/web/workspaces/__next._head.txt +4 -4
- package/dist/web/workspaces/__next._index.txt +14 -14
- package/dist/web/workspaces/__next._tree.txt +3 -8
- package/dist/web/workspaces/__next.workspaces.__PAGE__.txt +4 -4
- package/dist/web/workspaces/__next.workspaces.txt +3 -3
- package/dist/web/workspaces.html +1 -1
- package/dist/web/workspaces.txt +24 -29
- package/dist/ws/agent-prompt.js +45 -8
- package/dist/ws/agent-runner.js +6 -4
- package/package.json +11 -10
- package/dist/web/_next/static/chunks/0-1bfvfwifh-m.js +0 -1
- package/dist/web/_next/static/chunks/04v-pqqs-2lvp.css +0 -1
- package/dist/web/_next/static/chunks/06w~.izyfylap.js +0 -1
- package/dist/web/_next/static/chunks/09t62a89~jesc.js +0 -3
- package/dist/web/_next/static/chunks/09y_3krccdd~u.js +0 -1
- package/dist/web/_next/static/chunks/0_e~hmntdfop9.js +0 -1
- package/dist/web/_next/static/chunks/0cke33d38fnby.js +0 -90
- package/dist/web/_next/static/chunks/0cyq~8xb~r5fc.js +0 -1
- package/dist/web/_next/static/chunks/0fxa1edqr7dve.js +0 -1
- package/dist/web/_next/static/chunks/0hra80eyuu37u.js +0 -1
- package/dist/web/_next/static/chunks/0ie7y4k2ncvnn.js +0 -1
- package/dist/web/_next/static/chunks/0igc~uli58ncw.js +0 -2
- package/dist/web/_next/static/chunks/0msb2mi3hiu5_.js +0 -67
- package/dist/web/_next/static/chunks/0p1n8bvfnpl41.js +0 -1
- package/dist/web/_next/static/chunks/0s_dva~eh2yhi.js +0 -3
- package/dist/web/_next/static/chunks/0typj55ox8d5i.js +0 -1
- package/dist/web/_next/static/chunks/0w2p4ldi2aymp.js +0 -1
- package/dist/web/_next/static/chunks/0zc0xgp5ourcn.js +0 -1
- package/dist/web/_next/static/chunks/0zmhdridea1s5.js +0 -90
- package/dist/web/_next/static/chunks/10.alq.6stmld.js +0 -1
- package/dist/web/_next/static/chunks/11b3zgk1etlkw.js +0 -7
- package/dist/web/_next/static/chunks/11qp8r5dotf7d.js +0 -1
- package/dist/web/_next/static/chunks/12mxlicp247c8.js +0 -1
- package/dist/web/_next/static/chunks/135w27dd8c19s.js +0 -5
- package/dist/web/_next/static/chunks/176dc-dtokxxi.js +0 -16
- package/dist/web/_next/static/chunks/17g-0pqre.anw.js +0 -1
- package/dist/web/_next/static/media/0a7740363b4d4863-s.1110aazvokzp..woff2 +0 -0
- package/dist/web/_next/static/media/0da9c7f357bd9d4d-s.0ek5sshv8wk3m.woff2 +0 -0
- package/dist/web/_next/static/media/1b99372b3eaef0c8-s.p.0gx2haw2tmll8.woff2 +0 -0
- package/dist/web/_next/static/media/41e95f694c5c4549-s.0r1jrq7neqdsq.woff2 +0 -0
- package/dist/web/_next/static/media/5c285b27cdda1fe8-s.p.0yo6-5yoeeudq.woff2 +0 -0
- package/dist/web/_next/static/media/5f9d24ebef5d5292-s.0esuu2f5si~v8.woff2 +0 -0
- package/dist/web/_next/static/media/6c55a692938ebbbc-s.0g6rbszwfgn9y.woff2 +0 -0
- package/dist/web/_next/static/media/8e6fa89aa22d24ec-s.p.0uvzar8hswo3p.woff2 +0 -0
- package/dist/web/_next/static/media/a218039a3287bcfd-s.p.17-1enzs_j91b.woff2 +0 -0
- package/dist/web/_next/static/media/b2ea385cb5ae8625-s.0kjod.kaq1k69.woff2 +0 -0
- package/dist/web/_next/static/media/bdc7e24a509eb931-s.0apt5mko2.qn3.woff2 +0 -0
- package/dist/web/_next/static/media/c3cb240f9c892514-s.163v2i5cux7-c.woff2 +0 -0
- package/dist/web/_next/static/media/e2334d715941921e-s.p.12skym0rqknxy.woff2 +0 -0
- /package/dist/web/_next/static/{OoXAsPtrUWjC1l898FSvI → e8w3vd9Eov_LDYWj_0zsE}/_buildManifest.js +0 -0
- /package/dist/web/_next/static/{OoXAsPtrUWjC1l898FSvI → e8w3vd9Eov_LDYWj_0zsE}/_clientMiddlewareManifest.js +0 -0
- /package/dist/web/_next/static/{OoXAsPtrUWjC1l898FSvI → e8w3vd9Eov_LDYWj_0zsE}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import * as databaseStore from '../storage/database-store.js';
|
|
3
|
+
import * as llmStore from '../storage/llm-store.js';
|
|
4
|
+
const INDEX_BATCH_SIZE = 16;
|
|
5
|
+
const MAX_INDEX_TEXT_LENGTH = 24_000;
|
|
6
|
+
export class DatabaseVectorError extends Error {
|
|
7
|
+
debug;
|
|
8
|
+
constructor(message, debug) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'DatabaseVectorError';
|
|
11
|
+
this.debug = debug;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function indexDatabaseVectors(workspaceId, databaseId) {
|
|
15
|
+
const database = databaseStore.getDatabase(workspaceId, databaseId);
|
|
16
|
+
if (!database)
|
|
17
|
+
throw new Error(`Database not found: ${databaseId}`);
|
|
18
|
+
if (!database.embeddingModelId)
|
|
19
|
+
throw new Error('Embedding model is not bound to this database.');
|
|
20
|
+
const config = requireEmbeddingModelConfig(database.embeddingModelId);
|
|
21
|
+
const nodes = databaseStore.listNodes(workspaceId, databaseId).filter((node) => !node.isTrash);
|
|
22
|
+
const records = nodes
|
|
23
|
+
.map((node) => ({
|
|
24
|
+
node,
|
|
25
|
+
path: buildDatabaseNodePath(node, nodes),
|
|
26
|
+
text: normalizeIndexText(`${node.title}\n${stripHtml(node.content)}`),
|
|
27
|
+
}))
|
|
28
|
+
.filter((item) => item.text.length > 0);
|
|
29
|
+
let indexedCount = 0;
|
|
30
|
+
for (let index = 0; index < records.length; index += INDEX_BATCH_SIZE) {
|
|
31
|
+
const batch = records.slice(index, index + INDEX_BATCH_SIZE);
|
|
32
|
+
const batchDebug = {
|
|
33
|
+
batchStart: index,
|
|
34
|
+
batchSize: batch.length,
|
|
35
|
+
indexedCount,
|
|
36
|
+
};
|
|
37
|
+
console.info('[database-vector:index] embedding batch', {
|
|
38
|
+
workspaceId,
|
|
39
|
+
databaseId,
|
|
40
|
+
modelId: config.model.modelId,
|
|
41
|
+
providerName: config.provider.name,
|
|
42
|
+
...batchDebug,
|
|
43
|
+
inputLengths: batch.map((item) => item.text.length),
|
|
44
|
+
});
|
|
45
|
+
const embeddings = await embedTexts(config, batch.map((item) => item.text), batchDebug);
|
|
46
|
+
embeddings.forEach((embedding, offset) => {
|
|
47
|
+
const item = batch[offset];
|
|
48
|
+
databaseStore.upsertDatabaseEmbedding(workspaceId, databaseId, {
|
|
49
|
+
nodeId: item.node.id,
|
|
50
|
+
title: item.node.title,
|
|
51
|
+
path: item.path,
|
|
52
|
+
content: item.text,
|
|
53
|
+
contentHash: hashText(item.text),
|
|
54
|
+
embedding,
|
|
55
|
+
modelId: config.model.modelId,
|
|
56
|
+
agentId: config.model.id,
|
|
57
|
+
});
|
|
58
|
+
indexedCount++;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
databaseStore.deleteStaleDatabaseEmbeddings(workspaceId, databaseId, records.map((item) => item.node.id));
|
|
62
|
+
return {
|
|
63
|
+
...databaseStore.getVectorStats(workspaceId, databaseId),
|
|
64
|
+
indexedCount,
|
|
65
|
+
skippedCount: nodes.length - records.length,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export async function searchDatabaseVectors(workspaceId, databaseId, query, limit = 5) {
|
|
69
|
+
const database = databaseStore.getDatabase(workspaceId, databaseId);
|
|
70
|
+
if (!database)
|
|
71
|
+
throw new Error(`Database not found: ${databaseId}`);
|
|
72
|
+
if (!database.embeddingModelId)
|
|
73
|
+
throw new Error('Embedding model is not bound to this database.');
|
|
74
|
+
const cleanQuery = normalizeIndexText(query);
|
|
75
|
+
if (!cleanQuery)
|
|
76
|
+
throw new Error('query is required.');
|
|
77
|
+
const config = requireEmbeddingModelConfig(database.embeddingModelId);
|
|
78
|
+
const [queryEmbedding] = await embedTexts(config, [cleanQuery]);
|
|
79
|
+
return databaseStore.listDatabaseEmbeddings(workspaceId, databaseId)
|
|
80
|
+
.map((row) => ({
|
|
81
|
+
nodeId: row.nodeId,
|
|
82
|
+
title: row.title,
|
|
83
|
+
path: row.path,
|
|
84
|
+
content: row.content,
|
|
85
|
+
updatedAt: row.updatedAt,
|
|
86
|
+
score: cosineSimilarity(queryEmbedding, row.embedding),
|
|
87
|
+
}))
|
|
88
|
+
.sort((a, b) => b.score - a.score)
|
|
89
|
+
.slice(0, Math.max(1, Math.min(limit, 20)));
|
|
90
|
+
}
|
|
91
|
+
function requireEmbeddingModelConfig(modelId) {
|
|
92
|
+
const model = llmStore.getModel(modelId);
|
|
93
|
+
if (!model)
|
|
94
|
+
throw new Error(`Embedding model not found: ${modelId}`);
|
|
95
|
+
if (!model.embedding)
|
|
96
|
+
throw new Error(`Selected model is not marked as an embedding model: ${model.name}`);
|
|
97
|
+
const provider = llmStore.listProviders().find((item) => item.name === model.provider);
|
|
98
|
+
if (!provider)
|
|
99
|
+
throw new Error(`Provider not found for embedding model: ${model.provider}`);
|
|
100
|
+
if (!provider.apiBase || !provider.apiKey || !model.modelId) {
|
|
101
|
+
throw new Error(`Embedding provider is missing apiBase, apiKey, or modelId: ${provider.name}`);
|
|
102
|
+
}
|
|
103
|
+
return { model, provider };
|
|
104
|
+
}
|
|
105
|
+
async function embedTexts(config, input, extraDebug = {}) {
|
|
106
|
+
const requestUrl = getEmbeddingsUrl(config.provider.apiBase);
|
|
107
|
+
const requestDebug = {
|
|
108
|
+
stage: 'embedding_request',
|
|
109
|
+
providerName: config.provider.name,
|
|
110
|
+
modelId: config.model.modelId,
|
|
111
|
+
requestUrl,
|
|
112
|
+
inputCount: input.length,
|
|
113
|
+
inputLengths: input.map((item) => item.length),
|
|
114
|
+
...extraDebug,
|
|
115
|
+
};
|
|
116
|
+
console.info('[database-vector:embed] request', requestDebug);
|
|
117
|
+
const response = await fetch(requestUrl, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: {
|
|
120
|
+
Authorization: `Bearer ${config.provider.apiKey}`,
|
|
121
|
+
'Content-Type': 'application/json',
|
|
122
|
+
},
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
model: config.model.modelId,
|
|
125
|
+
input,
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
const responseText = await response.text();
|
|
129
|
+
const responseDebugBase = {
|
|
130
|
+
...requestDebug,
|
|
131
|
+
stage: 'embedding_response',
|
|
132
|
+
status: response.status,
|
|
133
|
+
responseContentType: response.headers.get('content-type'),
|
|
134
|
+
};
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
const debug = {
|
|
137
|
+
...responseDebugBase,
|
|
138
|
+
responsePreview: responseText.slice(0, 1000),
|
|
139
|
+
};
|
|
140
|
+
console.warn('[database-vector:embed] failed response', debug);
|
|
141
|
+
throw new DatabaseVectorError(`Embedding request failed with status ${response.status}`, debug);
|
|
142
|
+
}
|
|
143
|
+
let data;
|
|
144
|
+
try {
|
|
145
|
+
data = JSON.parse(responseText);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
const debug = {
|
|
149
|
+
...responseDebugBase,
|
|
150
|
+
stage: 'embedding_parse_json',
|
|
151
|
+
responsePreview: responseText.slice(0, 1000),
|
|
152
|
+
};
|
|
153
|
+
console.warn('[database-vector:embed] invalid json', debug);
|
|
154
|
+
throw new DatabaseVectorError('Embedding response is not valid JSON.', debug);
|
|
155
|
+
}
|
|
156
|
+
const responseData = isRecord(data) && Array.isArray(data.data) ? data.data : undefined;
|
|
157
|
+
const embeddings = responseData
|
|
158
|
+
?.map((item) => isRecord(item) ? item.embedding : undefined)
|
|
159
|
+
.filter((item) => Array.isArray(item) && item.every((value) => typeof value === 'number'));
|
|
160
|
+
const debug = {
|
|
161
|
+
...responseDebugBase,
|
|
162
|
+
responseDataCount: responseData?.length,
|
|
163
|
+
validEmbeddingCount: embeddings?.length ?? 0,
|
|
164
|
+
embeddingDimensions: embeddings?.map((embedding) => embedding.length).slice(0, 10),
|
|
165
|
+
responseKeys: isRecord(data) ? Object.keys(data) : undefined,
|
|
166
|
+
responsePreview: previewEmbeddingResponse(data),
|
|
167
|
+
};
|
|
168
|
+
console.info('[database-vector:embed] parsed response', debug);
|
|
169
|
+
if (!embeddings || embeddings.length !== input.length) {
|
|
170
|
+
throw new DatabaseVectorError(`Embedding response does not match input length. expected=${input.length}, data=${responseData?.length ?? 0}, validEmbeddings=${embeddings?.length ?? 0}`, debug);
|
|
171
|
+
}
|
|
172
|
+
return embeddings;
|
|
173
|
+
}
|
|
174
|
+
function isRecord(value) {
|
|
175
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
176
|
+
}
|
|
177
|
+
function previewEmbeddingResponse(value) {
|
|
178
|
+
if (!isRecord(value))
|
|
179
|
+
return value;
|
|
180
|
+
const data = Array.isArray(value.data)
|
|
181
|
+
? value.data.slice(0, 3).map((item) => {
|
|
182
|
+
if (!isRecord(item))
|
|
183
|
+
return item;
|
|
184
|
+
const embedding = Array.isArray(item.embedding) ? item.embedding : undefined;
|
|
185
|
+
return {
|
|
186
|
+
...item,
|
|
187
|
+
embedding: embedding ? `[number[${embedding.length}]]` : item.embedding,
|
|
188
|
+
};
|
|
189
|
+
})
|
|
190
|
+
: value.data;
|
|
191
|
+
return {
|
|
192
|
+
...value,
|
|
193
|
+
data,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function getEmbeddingsUrl(apiBase) {
|
|
197
|
+
const base = apiBase.replace(/\/+$/, '');
|
|
198
|
+
if (base.endsWith('/embeddings'))
|
|
199
|
+
return base;
|
|
200
|
+
return `${base}/embeddings`;
|
|
201
|
+
}
|
|
202
|
+
function normalizeIndexText(text) {
|
|
203
|
+
return text.replace(/\s+/g, ' ').trim().slice(0, MAX_INDEX_TEXT_LENGTH);
|
|
204
|
+
}
|
|
205
|
+
function stripHtml(content) {
|
|
206
|
+
return content
|
|
207
|
+
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
|
208
|
+
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
|
209
|
+
.replace(/<[^>]+>/g, ' ');
|
|
210
|
+
}
|
|
211
|
+
function hashText(text) {
|
|
212
|
+
return createHash('sha256').update(text).digest('hex');
|
|
213
|
+
}
|
|
214
|
+
function cosineSimilarity(a, b) {
|
|
215
|
+
if (a.length !== b.length || a.length === 0)
|
|
216
|
+
return 0;
|
|
217
|
+
let dot = 0;
|
|
218
|
+
let ma = 0;
|
|
219
|
+
let mb = 0;
|
|
220
|
+
for (let index = 0; index < a.length; index++) {
|
|
221
|
+
dot += a[index] * b[index];
|
|
222
|
+
ma += a[index] * a[index];
|
|
223
|
+
mb += b[index] * b[index];
|
|
224
|
+
}
|
|
225
|
+
if (!ma || !mb)
|
|
226
|
+
return 0;
|
|
227
|
+
return dot / (Math.sqrt(ma) * Math.sqrt(mb));
|
|
228
|
+
}
|
|
229
|
+
function buildDatabaseNodePath(node, nodes) {
|
|
230
|
+
const byId = new Map(nodes.map((item) => [item.id, item]));
|
|
231
|
+
const parts = [node.title || node.id];
|
|
232
|
+
let parentId = node.parentId;
|
|
233
|
+
let guard = 0;
|
|
234
|
+
while (parentId && guard < 100) {
|
|
235
|
+
const parent = byId.get(parentId);
|
|
236
|
+
if (!parent)
|
|
237
|
+
break;
|
|
238
|
+
parts.unshift(parent.title || parent.id);
|
|
239
|
+
parentId = parent.parentId;
|
|
240
|
+
guard++;
|
|
241
|
+
}
|
|
242
|
+
return `/${parts.join('/')}`;
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=database-vector.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as store from '../storage/kanban-store.js';
|
|
2
|
+
export function getBoard(workspaceId) {
|
|
3
|
+
return store.getBoard(workspaceId);
|
|
4
|
+
}
|
|
5
|
+
export function ensureBoard(workspaceId) {
|
|
6
|
+
let board = store.getBoard(workspaceId);
|
|
7
|
+
if (!board) {
|
|
8
|
+
board = store.createBoard(workspaceId);
|
|
9
|
+
const defaultColumns = [
|
|
10
|
+
{ id: 'todo', title: 'To Do', color: 'sky', order: 0 },
|
|
11
|
+
{ id: 'progress', title: 'In Progress', color: 'amber', order: 1 },
|
|
12
|
+
{ id: 'done', title: 'Done', color: 'emerald', order: 2 },
|
|
13
|
+
{ id: 'archive', title: 'Archive', color: 'slate', order: 3 },
|
|
14
|
+
];
|
|
15
|
+
store.saveColumns(board.id, defaultColumns);
|
|
16
|
+
board = store.getBoard(workspaceId);
|
|
17
|
+
}
|
|
18
|
+
return board;
|
|
19
|
+
}
|
|
20
|
+
export function updateBoard(workspaceId, updates) {
|
|
21
|
+
return store.updateBoard(workspaceId, updates);
|
|
22
|
+
}
|
|
23
|
+
export function saveColumns(workspaceId, columns) {
|
|
24
|
+
const board = store.getBoard(workspaceId);
|
|
25
|
+
if (!board)
|
|
26
|
+
return null;
|
|
27
|
+
store.saveColumns(board.id, columns);
|
|
28
|
+
return store.getBoard(workspaceId);
|
|
29
|
+
}
|
|
30
|
+
export function saveTasks(workspaceId, tasks) {
|
|
31
|
+
const board = store.getBoard(workspaceId);
|
|
32
|
+
if (!board)
|
|
33
|
+
return null;
|
|
34
|
+
store.saveTasks(board.id, tasks);
|
|
35
|
+
return store.getBoard(workspaceId);
|
|
36
|
+
}
|
|
37
|
+
export function saveBoard(workspaceId, data) {
|
|
38
|
+
let board = store.getBoard(workspaceId);
|
|
39
|
+
if (!board)
|
|
40
|
+
board = store.createBoard(workspaceId);
|
|
41
|
+
if (data.title || data.layoutMode) {
|
|
42
|
+
store.updateBoard(workspaceId, { title: data.title, layoutMode: data.layoutMode });
|
|
43
|
+
}
|
|
44
|
+
if (data.columns)
|
|
45
|
+
store.saveColumns(board.id, data.columns);
|
|
46
|
+
if (data.tasks)
|
|
47
|
+
store.saveTasks(board.id, data.tasks);
|
|
48
|
+
return store.getBoard(workspaceId);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=kanban.js.map
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { v4 as uuid } from 'uuid';
|
|
5
|
+
import { getDataDir, ensureDir } from './json-store.js';
|
|
6
|
+
let db = null;
|
|
7
|
+
function openDb() {
|
|
8
|
+
if (db)
|
|
9
|
+
return db;
|
|
10
|
+
const dir = join(getDataDir(), 'database');
|
|
11
|
+
ensureDir(dir);
|
|
12
|
+
db = new DatabaseSync(join(dir, 'database.sqlite'));
|
|
13
|
+
const hasDocNodes = Boolean(db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'doc_nodes'").get());
|
|
14
|
+
const hasDatabaseId = hasDocNodes
|
|
15
|
+
&& db.prepare('PRAGMA table_info(doc_nodes)').all()
|
|
16
|
+
.some((column) => column.name === 'database_id');
|
|
17
|
+
if (hasDocNodes && !hasDatabaseId) {
|
|
18
|
+
db.exec('DROP TABLE IF EXISTS doc_nodes; DROP TABLE IF EXISTS databases;');
|
|
19
|
+
}
|
|
20
|
+
db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS databases (
|
|
22
|
+
id TEXT PRIMARY KEY,
|
|
23
|
+
workspace_id TEXT NOT NULL,
|
|
24
|
+
name TEXT NOT NULL DEFAULT '',
|
|
25
|
+
description TEXT NOT NULL DEFAULT '',
|
|
26
|
+
embedding_model_id TEXT,
|
|
27
|
+
created_at INTEGER NOT NULL,
|
|
28
|
+
updated_at INTEGER NOT NULL
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS doc_nodes (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
workspace_id TEXT NOT NULL,
|
|
34
|
+
database_id TEXT NOT NULL,
|
|
35
|
+
title TEXT NOT NULL DEFAULT '',
|
|
36
|
+
icon TEXT NOT NULL DEFAULT '',
|
|
37
|
+
cover TEXT NOT NULL DEFAULT '',
|
|
38
|
+
content TEXT NOT NULL DEFAULT '',
|
|
39
|
+
parent_id TEXT,
|
|
40
|
+
is_trash INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
created_at INTEGER NOT NULL,
|
|
42
|
+
updated_at INTEGER NOT NULL
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE TABLE IF NOT EXISTS database_embeddings (
|
|
46
|
+
workspace_id TEXT NOT NULL,
|
|
47
|
+
database_id TEXT NOT NULL,
|
|
48
|
+
node_id TEXT NOT NULL,
|
|
49
|
+
title TEXT NOT NULL DEFAULT '',
|
|
50
|
+
path TEXT NOT NULL DEFAULT '',
|
|
51
|
+
content TEXT NOT NULL DEFAULT '',
|
|
52
|
+
content_hash TEXT NOT NULL,
|
|
53
|
+
embedding TEXT NOT NULL,
|
|
54
|
+
model_id TEXT NOT NULL DEFAULT '',
|
|
55
|
+
agent_id TEXT NOT NULL DEFAULT '',
|
|
56
|
+
updated_at INTEGER NOT NULL,
|
|
57
|
+
PRIMARY KEY (workspace_id, database_id, node_id)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE IF NOT EXISTS doc_node_versions (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
workspace_id TEXT NOT NULL,
|
|
63
|
+
database_id TEXT NOT NULL,
|
|
64
|
+
node_id TEXT NOT NULL,
|
|
65
|
+
title TEXT NOT NULL DEFAULT '',
|
|
66
|
+
patch_json TEXT NOT NULL,
|
|
67
|
+
old_hash TEXT NOT NULL,
|
|
68
|
+
new_hash TEXT NOT NULL,
|
|
69
|
+
created_at INTEGER NOT NULL
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_databases_workspace ON databases(workspace_id);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_doc_nodes_workspace ON doc_nodes(workspace_id);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_doc_nodes_database ON doc_nodes(workspace_id, database_id);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_doc_nodes_parent ON doc_nodes(workspace_id, database_id, parent_id);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_database_embeddings_database ON database_embeddings(workspace_id, database_id);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_doc_node_versions_node ON doc_node_versions(workspace_id, database_id, node_id, created_at DESC);
|
|
78
|
+
`);
|
|
79
|
+
ensureColumn('databases', 'embedding_model_id', 'TEXT');
|
|
80
|
+
migrateEmbeddingModelColumn();
|
|
81
|
+
return db;
|
|
82
|
+
}
|
|
83
|
+
function ensureColumn(table, column, type) {
|
|
84
|
+
const database = db;
|
|
85
|
+
const exists = database.prepare(`PRAGMA table_info(${table})`).all()
|
|
86
|
+
.some((item) => item.name === column);
|
|
87
|
+
if (!exists)
|
|
88
|
+
database.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
|
|
89
|
+
}
|
|
90
|
+
function migrateEmbeddingModelColumn() {
|
|
91
|
+
const database = db;
|
|
92
|
+
const columns = database.prepare('PRAGMA table_info(databases)').all();
|
|
93
|
+
const hasLegacyAgentColumn = columns.some((item) => item.name === 'embedding_agent_id');
|
|
94
|
+
if (!hasLegacyAgentColumn)
|
|
95
|
+
return;
|
|
96
|
+
database.exec(`
|
|
97
|
+
UPDATE databases
|
|
98
|
+
SET embedding_model_id = COALESCE(embedding_model_id, embedding_agent_id)
|
|
99
|
+
WHERE embedding_agent_id IS NOT NULL AND embedding_agent_id != ''
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
function rowToDatabase(row) {
|
|
103
|
+
return {
|
|
104
|
+
id: row.id,
|
|
105
|
+
workspaceId: row.workspace_id,
|
|
106
|
+
name: row.name,
|
|
107
|
+
description: row.description,
|
|
108
|
+
embeddingModelId: row.embedding_model_id || undefined,
|
|
109
|
+
createdAt: row.created_at,
|
|
110
|
+
updatedAt: row.updated_at,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function rowToNode(row) {
|
|
114
|
+
return {
|
|
115
|
+
id: row.id,
|
|
116
|
+
databaseId: row.database_id,
|
|
117
|
+
title: row.title,
|
|
118
|
+
icon: row.icon,
|
|
119
|
+
cover: row.cover,
|
|
120
|
+
content: row.content,
|
|
121
|
+
parentId: row.parent_id || null,
|
|
122
|
+
isTrash: !!row.is_trash,
|
|
123
|
+
createdAt: row.created_at,
|
|
124
|
+
updatedAt: row.updated_at,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function rowToVersionRow(row) {
|
|
128
|
+
return {
|
|
129
|
+
id: row.id,
|
|
130
|
+
nodeId: row.node_id,
|
|
131
|
+
databaseId: row.database_id,
|
|
132
|
+
title: row.title,
|
|
133
|
+
patch: JSON.parse(row.patch_json),
|
|
134
|
+
oldHash: row.old_hash,
|
|
135
|
+
newHash: row.new_hash,
|
|
136
|
+
createdAt: row.created_at,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function listDatabases(workspaceId) {
|
|
140
|
+
const database = openDb();
|
|
141
|
+
const rows = database.prepare('SELECT * FROM databases WHERE workspace_id = ? ORDER BY created_at ASC').all(workspaceId);
|
|
142
|
+
return rows.map(rowToDatabase);
|
|
143
|
+
}
|
|
144
|
+
export function getDatabase(workspaceId, databaseId) {
|
|
145
|
+
const database = openDb();
|
|
146
|
+
const row = database.prepare('SELECT * FROM databases WHERE workspace_id = ? AND id = ?').get(workspaceId, databaseId);
|
|
147
|
+
return row ? rowToDatabase(row) : null;
|
|
148
|
+
}
|
|
149
|
+
export function getDefaultDatabase(workspaceId) {
|
|
150
|
+
const existing = listDatabases(workspaceId)[0];
|
|
151
|
+
if (existing)
|
|
152
|
+
return existing;
|
|
153
|
+
return createDatabase(workspaceId, { name: 'Default Database' });
|
|
154
|
+
}
|
|
155
|
+
export function createDatabase(workspaceId, input) {
|
|
156
|
+
const database = openDb();
|
|
157
|
+
const id = input.id || uuid();
|
|
158
|
+
const now = Date.now();
|
|
159
|
+
database.prepare(`INSERT INTO databases (id, workspace_id, name, description, created_at, updated_at)
|
|
160
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(id, workspaceId, input.name?.trim() || 'Untitled Database', input.description || '', now, now);
|
|
161
|
+
return getDatabase(workspaceId, id);
|
|
162
|
+
}
|
|
163
|
+
export function updateDatabase(workspaceId, databaseId, updates) {
|
|
164
|
+
const existing = getDatabase(workspaceId, databaseId);
|
|
165
|
+
if (!existing)
|
|
166
|
+
return null;
|
|
167
|
+
const database = openDb();
|
|
168
|
+
const merged = { ...existing, ...updates, updatedAt: Date.now() };
|
|
169
|
+
database.prepare('UPDATE databases SET name = ?, description = ?, embedding_model_id = ?, updated_at = ? WHERE workspace_id = ? AND id = ?').run(merged.name.trim() || 'Untitled Database', merged.description, merged.embeddingModelId ?? null, merged.updatedAt, workspaceId, databaseId);
|
|
170
|
+
return getDatabase(workspaceId, databaseId);
|
|
171
|
+
}
|
|
172
|
+
export function deleteDatabase(workspaceId, databaseId) {
|
|
173
|
+
const database = openDb();
|
|
174
|
+
database.prepare('DELETE FROM doc_nodes WHERE workspace_id = ? AND database_id = ?').run(workspaceId, databaseId);
|
|
175
|
+
database.prepare('DELETE FROM database_embeddings WHERE workspace_id = ? AND database_id = ?').run(workspaceId, databaseId);
|
|
176
|
+
const result = database.prepare('DELETE FROM databases WHERE workspace_id = ? AND id = ?').run(workspaceId, databaseId);
|
|
177
|
+
if (result.changes > 0 && listDatabases(workspaceId).length === 0)
|
|
178
|
+
getDefaultDatabase(workspaceId);
|
|
179
|
+
return result.changes > 0;
|
|
180
|
+
}
|
|
181
|
+
export function listNodes(workspaceId, databaseId) {
|
|
182
|
+
const database = openDb();
|
|
183
|
+
const activeDatabaseId = databaseId ?? getDefaultDatabase(workspaceId).id;
|
|
184
|
+
const rows = database.prepare('SELECT * FROM doc_nodes WHERE workspace_id = ? AND database_id = ? ORDER BY created_at ASC').all(workspaceId, activeDatabaseId);
|
|
185
|
+
return rows.map(rowToNode);
|
|
186
|
+
}
|
|
187
|
+
export function getNode(workspaceId, nodeId, databaseId) {
|
|
188
|
+
const database = openDb();
|
|
189
|
+
const row = databaseId
|
|
190
|
+
? database.prepare('SELECT * FROM doc_nodes WHERE workspace_id = ? AND database_id = ? AND id = ?').get(workspaceId, databaseId, nodeId)
|
|
191
|
+
: database.prepare('SELECT * FROM doc_nodes WHERE workspace_id = ? AND id = ?').get(workspaceId, nodeId);
|
|
192
|
+
return row ? rowToNode(row) : null;
|
|
193
|
+
}
|
|
194
|
+
export function createNode(workspaceId, node) {
|
|
195
|
+
const database = openDb();
|
|
196
|
+
const databaseId = node.databaseId || getDefaultDatabase(workspaceId).id;
|
|
197
|
+
if (!getDatabase(workspaceId, databaseId))
|
|
198
|
+
throw new Error(`Database not found: ${databaseId}`);
|
|
199
|
+
const id = node.id || uuid();
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
database.prepare(`INSERT INTO doc_nodes (id, workspace_id, database_id, title, icon, cover, content, parent_id, is_trash, created_at, updated_at)
|
|
202
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, workspaceId, databaseId, node.title || 'Untitled Document', node.icon || '\u{1F4DD}', node.cover || '', node.content || '', node.parentId ?? null, node.isTrash ? 1 : 0, node.createdAt || now, node.updatedAt || now);
|
|
203
|
+
return getNode(workspaceId, id, databaseId);
|
|
204
|
+
}
|
|
205
|
+
export function updateNode(workspaceId, nodeId, updates, databaseId) {
|
|
206
|
+
const existing = getNode(workspaceId, nodeId, databaseId);
|
|
207
|
+
if (!existing)
|
|
208
|
+
return null;
|
|
209
|
+
const database = openDb();
|
|
210
|
+
const merged = { ...existing, ...updates, updatedAt: Date.now() };
|
|
211
|
+
if (Object.hasOwn(updates, 'content') && merged.content !== existing.content) {
|
|
212
|
+
insertNodeVersion(database, workspaceId, existing, merged.content);
|
|
213
|
+
}
|
|
214
|
+
database.prepare(`UPDATE doc_nodes SET title = ?, icon = ?, cover = ?, content = ?, parent_id = ?, is_trash = ?, updated_at = ?
|
|
215
|
+
WHERE workspace_id = ? AND database_id = ? AND id = ?`).run(merged.title, merged.icon, merged.cover, merged.content, merged.parentId, merged.isTrash ? 1 : 0, merged.updatedAt, workspaceId, existing.databaseId, nodeId);
|
|
216
|
+
return getNode(workspaceId, nodeId, existing.databaseId);
|
|
217
|
+
}
|
|
218
|
+
export function listNodeVersions(workspaceId, nodeId, databaseId, limit = 50) {
|
|
219
|
+
const node = getNode(workspaceId, nodeId, databaseId);
|
|
220
|
+
if (!node)
|
|
221
|
+
return [];
|
|
222
|
+
const database = openDb();
|
|
223
|
+
const normalizedLimit = Number.isFinite(limit) ? Math.max(1, Math.min(100, Math.trunc(limit))) : 50;
|
|
224
|
+
const rows = database.prepare(`SELECT * FROM doc_node_versions
|
|
225
|
+
WHERE workspace_id = ? AND database_id = ? AND node_id = ?
|
|
226
|
+
ORDER BY created_at DESC
|
|
227
|
+
LIMIT ?`).all(workspaceId, node.databaseId, nodeId, normalizedLimit);
|
|
228
|
+
const stored = rows.map(rowToVersionRow);
|
|
229
|
+
let after = node.content;
|
|
230
|
+
return stored.map((item) => {
|
|
231
|
+
const before = applyReversePatch(after, item.patch);
|
|
232
|
+
const version = {
|
|
233
|
+
id: item.id,
|
|
234
|
+
nodeId: item.nodeId,
|
|
235
|
+
databaseId: item.databaseId,
|
|
236
|
+
title: item.title,
|
|
237
|
+
patch: item.patch,
|
|
238
|
+
oldContent: before,
|
|
239
|
+
newContent: after,
|
|
240
|
+
createdAt: item.createdAt,
|
|
241
|
+
};
|
|
242
|
+
after = before;
|
|
243
|
+
return version;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
export function deleteNode(workspaceId, nodeId, databaseId) {
|
|
247
|
+
const database = openDb();
|
|
248
|
+
const existing = getNode(workspaceId, nodeId, databaseId);
|
|
249
|
+
if (!existing)
|
|
250
|
+
return false;
|
|
251
|
+
database.prepare('DELETE FROM database_embeddings WHERE workspace_id = ? AND database_id = ? AND node_id = ?')
|
|
252
|
+
.run(workspaceId, existing.databaseId, nodeId);
|
|
253
|
+
database.prepare('DELETE FROM doc_node_versions WHERE workspace_id = ? AND database_id = ? AND node_id = ?')
|
|
254
|
+
.run(workspaceId, existing.databaseId, nodeId);
|
|
255
|
+
const result = database.prepare('DELETE FROM doc_nodes WHERE workspace_id = ? AND database_id = ? AND id = ?').run(workspaceId, existing.databaseId, nodeId);
|
|
256
|
+
return result.changes > 0;
|
|
257
|
+
}
|
|
258
|
+
function insertNodeVersion(database, workspaceId, existing, nextContent) {
|
|
259
|
+
const patch = createContentPatch(existing.content, nextContent);
|
|
260
|
+
database.prepare(`INSERT INTO doc_node_versions
|
|
261
|
+
(id, workspace_id, database_id, node_id, title, patch_json, old_hash, new_hash, created_at)
|
|
262
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(uuid(), workspaceId, existing.databaseId, existing.id, existing.title, JSON.stringify(patch), hashContent(existing.content), hashContent(nextContent), Date.now());
|
|
263
|
+
}
|
|
264
|
+
function createContentPatch(oldContent, newContent) {
|
|
265
|
+
let start = 0;
|
|
266
|
+
while (start < oldContent.length
|
|
267
|
+
&& start < newContent.length
|
|
268
|
+
&& oldContent[start] === newContent[start]) {
|
|
269
|
+
start++;
|
|
270
|
+
}
|
|
271
|
+
let oldEnd = oldContent.length;
|
|
272
|
+
let newEnd = newContent.length;
|
|
273
|
+
while (oldEnd > start
|
|
274
|
+
&& newEnd > start
|
|
275
|
+
&& oldContent[oldEnd - 1] === newContent[newEnd - 1]) {
|
|
276
|
+
oldEnd--;
|
|
277
|
+
newEnd--;
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
start,
|
|
281
|
+
deleteText: oldContent.slice(start, oldEnd),
|
|
282
|
+
insertText: newContent.slice(start, newEnd),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function applyReversePatch(content, patch) {
|
|
286
|
+
const insertEnd = patch.start + patch.insertText.length;
|
|
287
|
+
return `${content.slice(0, patch.start)}${patch.deleteText}${content.slice(insertEnd)}`;
|
|
288
|
+
}
|
|
289
|
+
function hashContent(content) {
|
|
290
|
+
return createHash('sha256').update(content).digest('hex');
|
|
291
|
+
}
|
|
292
|
+
export function moveNode(workspaceId, nodeId, newParentId, databaseId) {
|
|
293
|
+
return updateNode(workspaceId, nodeId, { parentId: newParentId }, databaseId);
|
|
294
|
+
}
|
|
295
|
+
export function trashNode(workspaceId, nodeId, databaseId) {
|
|
296
|
+
return updateNode(workspaceId, nodeId, { isTrash: true }, databaseId);
|
|
297
|
+
}
|
|
298
|
+
export function restoreNode(workspaceId, nodeId, databaseId) {
|
|
299
|
+
const node = getNode(workspaceId, nodeId, databaseId);
|
|
300
|
+
if (!node)
|
|
301
|
+
return null;
|
|
302
|
+
if (node.parentId) {
|
|
303
|
+
const parent = getNode(workspaceId, node.parentId, node.databaseId);
|
|
304
|
+
if (parent && parent.isTrash) {
|
|
305
|
+
return updateNode(workspaceId, nodeId, { isTrash: false, parentId: null }, node.databaseId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return updateNode(workspaceId, nodeId, { isTrash: false }, node.databaseId);
|
|
309
|
+
}
|
|
310
|
+
export function getVectorStats(workspaceId, databaseId) {
|
|
311
|
+
const database = openDb();
|
|
312
|
+
const meta = getDatabase(workspaceId, databaseId);
|
|
313
|
+
const indexed = database.prepare('SELECT COUNT(*) AS count, MAX(updated_at) AS last_indexed_at FROM database_embeddings WHERE workspace_id = ? AND database_id = ?').get(workspaceId, databaseId);
|
|
314
|
+
const nodes = database.prepare('SELECT COUNT(*) AS count FROM doc_nodes WHERE workspace_id = ? AND database_id = ? AND is_trash = 0').get(workspaceId, databaseId);
|
|
315
|
+
return {
|
|
316
|
+
databaseId,
|
|
317
|
+
embeddingModelId: meta?.embeddingModelId ?? null,
|
|
318
|
+
indexedCount: indexed.count,
|
|
319
|
+
nodeCount: nodes.count,
|
|
320
|
+
lastIndexedAt: indexed.last_indexed_at,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
export function setDatabaseEmbeddingModel(workspaceId, databaseId, embeddingModelId) {
|
|
324
|
+
return updateDatabase(workspaceId, databaseId, { embeddingModelId: embeddingModelId ?? undefined });
|
|
325
|
+
}
|
|
326
|
+
export function upsertDatabaseEmbedding(workspaceId, databaseId, record) {
|
|
327
|
+
const database = openDb();
|
|
328
|
+
database.prepare(`INSERT INTO database_embeddings
|
|
329
|
+
(workspace_id, database_id, node_id, title, path, content, content_hash, embedding, model_id, agent_id, updated_at)
|
|
330
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
331
|
+
ON CONFLICT(workspace_id, database_id, node_id) DO UPDATE SET
|
|
332
|
+
title = excluded.title,
|
|
333
|
+
path = excluded.path,
|
|
334
|
+
content = excluded.content,
|
|
335
|
+
content_hash = excluded.content_hash,
|
|
336
|
+
embedding = excluded.embedding,
|
|
337
|
+
model_id = excluded.model_id,
|
|
338
|
+
agent_id = excluded.agent_id,
|
|
339
|
+
updated_at = excluded.updated_at`).run(workspaceId, databaseId, record.nodeId, record.title, record.path, record.content, record.contentHash, JSON.stringify(record.embedding), record.modelId, record.agentId, Date.now());
|
|
340
|
+
}
|
|
341
|
+
export function deleteStaleDatabaseEmbeddings(workspaceId, databaseId, activeNodeIds) {
|
|
342
|
+
const database = openDb();
|
|
343
|
+
if (activeNodeIds.length === 0) {
|
|
344
|
+
const result = database.prepare('DELETE FROM database_embeddings WHERE workspace_id = ? AND database_id = ?')
|
|
345
|
+
.run(workspaceId, databaseId);
|
|
346
|
+
return result.changes;
|
|
347
|
+
}
|
|
348
|
+
const placeholders = activeNodeIds.map(() => '?').join(',');
|
|
349
|
+
const result = database.prepare(`DELETE FROM database_embeddings WHERE workspace_id = ? AND database_id = ? AND node_id NOT IN (${placeholders})`).run(workspaceId, databaseId, ...activeNodeIds);
|
|
350
|
+
return result.changes;
|
|
351
|
+
}
|
|
352
|
+
export function listDatabaseEmbeddings(workspaceId, databaseId) {
|
|
353
|
+
const database = openDb();
|
|
354
|
+
const rows = database.prepare('SELECT node_id, title, path, content, embedding, updated_at FROM database_embeddings WHERE workspace_id = ? AND database_id = ?').all(workspaceId, databaseId);
|
|
355
|
+
return rows.map((row) => ({
|
|
356
|
+
nodeId: row.node_id,
|
|
357
|
+
title: row.title,
|
|
358
|
+
path: row.path,
|
|
359
|
+
content: row.content,
|
|
360
|
+
score: 0,
|
|
361
|
+
updatedAt: row.updated_at,
|
|
362
|
+
embedding: JSON.parse(row.embedding),
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
//# sourceMappingURL=database-store.js.map
|