strapi-content-embeddings 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{App-j180lztd.mjs → App-C5NFY1UT.mjs} +256 -70
- package/dist/_chunks/{App-Rq72tIgS.js → App-CA5bQnKQ.js} +255 -69
- package/dist/_chunks/en-B4KWt_jN.js +0 -1
- package/dist/_chunks/en-Byx4XI2L.mjs +0 -1
- package/dist/_chunks/{index-B3j0IFUi.mjs → index-CIpGvEcJ.mjs} +60 -85
- package/dist/_chunks/{index-jf6vikTZ.js → index-CVCA8dDp.js} +57 -82
- package/dist/admin/index.js +1 -2
- package/dist/admin/index.mjs +1 -2
- package/dist/admin/src/components/custom/EmbeddingsTable.d.ts +1 -1
- package/dist/server/index.js +292 -32
- package/dist/server/index.mjs +292 -32
- package/dist/server/src/controllers/controller.d.ts +18 -0
- package/dist/server/src/controllers/index.d.ts +3 -0
- package/dist/server/src/index.d.ts +4 -0
- package/dist/server/src/plugin-manager.d.ts +16 -0
- package/dist/server/src/services/index.d.ts +1 -0
- package/dist/server/src/services/sync.d.ts +23 -0
- package/package.json +1 -1
- package/dist/_chunks/App-Rq72tIgS.js.map +0 -1
- package/dist/_chunks/App-j180lztd.mjs.map +0 -1
- package/dist/_chunks/en-B4KWt_jN.js.map +0 -1
- package/dist/_chunks/en-Byx4XI2L.mjs.map +0 -1
- package/dist/_chunks/index-B3j0IFUi.mjs.map +0 -1
- package/dist/_chunks/index-jf6vikTZ.js.map +0 -1
- package/dist/admin/index.js.map +0 -1
- package/dist/admin/index.mjs.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/server/index.mjs.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { OpenAIEmbeddings, ChatOpenAI } from "@langchain/openai";
|
|
2
2
|
import { PGVectorStore } from "@langchain/community/vectorstores/pgvector";
|
|
3
|
-
import { Document } from "@langchain/core/documents";
|
|
4
3
|
import { StringOutputParser } from "@langchain/core/output_parsers";
|
|
5
4
|
import { ChatPromptTemplate } from "@langchain/core/prompts";
|
|
6
5
|
import { RunnableSequence, RunnablePassthrough } from "@langchain/core/runnables";
|
|
@@ -170,39 +169,46 @@ class PluginManager {
|
|
|
170
169
|
console.log("Plugin Manager Initialization Complete");
|
|
171
170
|
}
|
|
172
171
|
async createEmbedding(docData) {
|
|
173
|
-
if (!this.embeddings || !this.vectorStoreConfig) {
|
|
172
|
+
if (!this.embeddings || !this.vectorStoreConfig || !this.pool) {
|
|
174
173
|
throw new Error("Plugin manager not initialized");
|
|
175
174
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
175
|
+
const maxRetries = 3;
|
|
176
|
+
const retryDelay = 2e3;
|
|
177
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
178
|
+
try {
|
|
179
|
+
const embeddingVector = await this.embeddings.embedQuery(docData.content);
|
|
180
|
+
const metadata = {
|
|
181
181
|
id: docData.id,
|
|
182
182
|
title: docData.title,
|
|
183
183
|
collectionType: docData.collectionType || "standalone",
|
|
184
184
|
fieldName: docData.fieldName || "content"
|
|
185
|
+
};
|
|
186
|
+
const vectorString = `[${embeddingVector.join(",")}]`;
|
|
187
|
+
const result = await this.pool.query(
|
|
188
|
+
`INSERT INTO embeddings_documents (content, metadata, embedding)
|
|
189
|
+
VALUES ($1, $2::jsonb, $3::vector)
|
|
190
|
+
RETURNING id`,
|
|
191
|
+
[docData.content, JSON.stringify(metadata), vectorString]
|
|
192
|
+
);
|
|
193
|
+
return {
|
|
194
|
+
embeddingId: result.rows[0]?.id || "",
|
|
195
|
+
embedding: embeddingVector
|
|
196
|
+
};
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const isRateLimit = error.message?.includes("429") || error.message?.includes("rate");
|
|
199
|
+
const isLastAttempt = attempt === maxRetries;
|
|
200
|
+
if (isRateLimit && !isLastAttempt) {
|
|
201
|
+
console.log(`[createEmbedding] Rate limited, waiting ${retryDelay}ms before retry ${attempt + 1}/${maxRetries}...`);
|
|
202
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay * attempt));
|
|
203
|
+
continue;
|
|
185
204
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
);
|
|
192
|
-
const result = await this.pool.query(
|
|
193
|
-
`SELECT id FROM embeddings_documents
|
|
194
|
-
WHERE metadata->>'id' = $1
|
|
195
|
-
ORDER BY id DESC LIMIT 1`,
|
|
196
|
-
[docData.id]
|
|
197
|
-
);
|
|
198
|
-
return {
|
|
199
|
-
embeddingId: result.rows[0]?.id || "",
|
|
200
|
-
embedding: embeddingVector
|
|
201
|
-
};
|
|
202
|
-
} catch (error) {
|
|
203
|
-
console.error(`Failed to create embedding: ${error}`);
|
|
204
|
-
throw new Error(`Failed to create embedding: ${error}`);
|
|
205
|
+
console.error(`[createEmbedding] Failed (attempt ${attempt}/${maxRetries}):`, error.message || error);
|
|
206
|
+
if (isLastAttempt) {
|
|
207
|
+
throw new Error(`Failed to create embedding after ${maxRetries} attempts: ${error.message || error}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
205
210
|
}
|
|
211
|
+
throw new Error("Failed to create embedding: unexpected error");
|
|
206
212
|
}
|
|
207
213
|
async deleteEmbedding(strapiId) {
|
|
208
214
|
if (!this.pool) {
|
|
@@ -228,8 +234,14 @@ class PluginManager {
|
|
|
228
234
|
this.vectorStoreConfig
|
|
229
235
|
);
|
|
230
236
|
const resultsWithScores = await vectorStore.similaritySearchWithScore(query, 6);
|
|
231
|
-
|
|
237
|
+
console.log(`[queryEmbedding] Query: "${query}"`);
|
|
238
|
+
console.log(`[queryEmbedding] Found ${resultsWithScores.length} results:`);
|
|
239
|
+
resultsWithScores.forEach(([doc, score], i) => {
|
|
240
|
+
console.log(` ${i + 1}. Score: ${score.toFixed(4)}, Title: ${doc.metadata?.title || "N/A"}`);
|
|
241
|
+
});
|
|
242
|
+
const SIMILARITY_THRESHOLD = 1;
|
|
232
243
|
const relevantResults = resultsWithScores.filter(([_, score]) => score < SIMILARITY_THRESHOLD);
|
|
244
|
+
console.log(`[queryEmbedding] ${relevantResults.length} results passed threshold (< ${SIMILARITY_THRESHOLD})`);
|
|
233
245
|
const topResults = relevantResults.slice(0, 3);
|
|
234
246
|
const sourceDocuments = topResults.map(([doc]) => doc);
|
|
235
247
|
const bestMatchForDisplay = topResults.length > 0 ? [topResults[0][0]] : [];
|
|
@@ -348,6 +360,59 @@ Context:
|
|
|
348
360
|
this.chat = null;
|
|
349
361
|
this.vectorStoreConfig = null;
|
|
350
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* Clear all embeddings from Neon DB
|
|
365
|
+
* Returns the number of deleted rows
|
|
366
|
+
*/
|
|
367
|
+
async clearAllNeonEmbeddings() {
|
|
368
|
+
if (!this.pool) {
|
|
369
|
+
throw new Error("Plugin manager not initialized");
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
const result = await this.pool.query(`
|
|
373
|
+
DELETE FROM embeddings_documents
|
|
374
|
+
RETURNING id
|
|
375
|
+
`);
|
|
376
|
+
console.log(`[clearAllNeonEmbeddings] Deleted ${result.rowCount} embeddings from Neon`);
|
|
377
|
+
return result.rowCount || 0;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.error(`Failed to clear Neon embeddings: ${error}`);
|
|
380
|
+
throw new Error(`Failed to clear Neon embeddings: ${error}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Debug method to inspect raw data in Neon DB
|
|
385
|
+
*/
|
|
386
|
+
async debugNeonEmbeddings() {
|
|
387
|
+
if (!this.pool) {
|
|
388
|
+
throw new Error("Plugin manager not initialized");
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const result = await this.pool.query(`
|
|
392
|
+
SELECT
|
|
393
|
+
id,
|
|
394
|
+
content,
|
|
395
|
+
metadata,
|
|
396
|
+
pg_typeof(metadata) as metadata_type,
|
|
397
|
+
embedding IS NOT NULL as has_embedding,
|
|
398
|
+
CASE WHEN embedding IS NOT NULL THEN array_length(embedding::float[], 1) ELSE 0 END as embedding_length
|
|
399
|
+
FROM embeddings_documents
|
|
400
|
+
ORDER BY id
|
|
401
|
+
LIMIT 20
|
|
402
|
+
`);
|
|
403
|
+
return result.rows.map((row) => ({
|
|
404
|
+
id: row.id,
|
|
405
|
+
content: row.content?.substring(0, 200) + (row.content?.length > 200 ? "..." : ""),
|
|
406
|
+
metadata: row.metadata,
|
|
407
|
+
metadataType: row.metadata_type,
|
|
408
|
+
hasEmbedding: row.has_embedding,
|
|
409
|
+
embeddingLength: row.embedding_length || 0
|
|
410
|
+
}));
|
|
411
|
+
} catch (error) {
|
|
412
|
+
console.error(`Failed to debug Neon embeddings: ${error}`);
|
|
413
|
+
throw new Error(`Failed to debug Neon embeddings: ${error}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
351
416
|
}
|
|
352
417
|
const pluginManager = new PluginManager();
|
|
353
418
|
const SemanticSearchSchema = z.object({
|
|
@@ -994,9 +1059,12 @@ const PLUGIN_ID$3 = "strapi-content-embeddings";
|
|
|
994
1059
|
const controller = ({ strapi }) => ({
|
|
995
1060
|
async createEmbedding(ctx) {
|
|
996
1061
|
try {
|
|
1062
|
+
console.log("[createEmbedding] Starting, autoChunk:", ctx.request.body?.data?.autoChunk);
|
|
997
1063
|
const result = await strapi.plugin(PLUGIN_ID$3).service("embeddings").createEmbedding(ctx.request.body);
|
|
1064
|
+
console.log("[createEmbedding] Completed, documentId:", result?.documentId);
|
|
998
1065
|
ctx.body = result;
|
|
999
1066
|
} catch (error) {
|
|
1067
|
+
console.error("[createEmbedding] Error:", error.message);
|
|
1000
1068
|
ctx.throw(500, error.message || "Failed to create embedding");
|
|
1001
1069
|
}
|
|
1002
1070
|
},
|
|
@@ -1055,6 +1123,23 @@ const controller = ({ strapi }) => ({
|
|
|
1055
1123
|
ctx.throw(500, error.message || "Failed to query embeddings");
|
|
1056
1124
|
}
|
|
1057
1125
|
},
|
|
1126
|
+
/**
|
|
1127
|
+
* Get all chunks related to a document
|
|
1128
|
+
* GET /api/strapi-content-embeddings/embeddings/related-chunks/:id
|
|
1129
|
+
*/
|
|
1130
|
+
async getRelatedChunks(ctx) {
|
|
1131
|
+
try {
|
|
1132
|
+
const { id } = ctx.params;
|
|
1133
|
+
const result = await strapi.plugin(PLUGIN_ID$3).service("embeddings").findRelatedChunks(id);
|
|
1134
|
+
console.log(`[getRelatedChunks] Found ${result.length} chunks for document ${id}`);
|
|
1135
|
+
ctx.body = {
|
|
1136
|
+
data: result,
|
|
1137
|
+
count: result.length
|
|
1138
|
+
};
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
ctx.throw(500, error.message || "Failed to get related chunks");
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1058
1143
|
/**
|
|
1059
1144
|
* Sync embeddings from Neon DB to Strapi DB
|
|
1060
1145
|
* GET /api/strapi-content-embeddings/sync
|
|
@@ -1086,6 +1171,39 @@ const controller = ({ strapi }) => ({
|
|
|
1086
1171
|
} catch (error) {
|
|
1087
1172
|
ctx.throw(500, error.message || "Failed to get sync status");
|
|
1088
1173
|
}
|
|
1174
|
+
},
|
|
1175
|
+
/**
|
|
1176
|
+
* Debug endpoint to inspect Neon DB contents
|
|
1177
|
+
* GET /api/strapi-content-embeddings/debug/neon
|
|
1178
|
+
*/
|
|
1179
|
+
async debugNeon(ctx) {
|
|
1180
|
+
try {
|
|
1181
|
+
const { pluginManager: pluginManager2 } = require("../plugin-manager");
|
|
1182
|
+
const result = await pluginManager2.debugNeonEmbeddings();
|
|
1183
|
+
ctx.body = {
|
|
1184
|
+
count: result.length,
|
|
1185
|
+
embeddings: result
|
|
1186
|
+
};
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
ctx.throw(500, error.message || "Failed to debug Neon");
|
|
1189
|
+
}
|
|
1190
|
+
},
|
|
1191
|
+
/**
|
|
1192
|
+
* Recreate all embeddings in Neon from Strapi data
|
|
1193
|
+
* POST /api/strapi-content-embeddings/recreate
|
|
1194
|
+
*
|
|
1195
|
+
* Use this when embeddings were created with incorrect metadata format
|
|
1196
|
+
* WARNING: This will delete ALL existing Neon embeddings and recreate them
|
|
1197
|
+
*/
|
|
1198
|
+
async recreateEmbeddings(ctx) {
|
|
1199
|
+
try {
|
|
1200
|
+
console.log("[recreateEmbeddings] Starting recreation of all embeddings...");
|
|
1201
|
+
const result = await strapi.plugin(PLUGIN_ID$3).service("sync").recreateAllEmbeddings();
|
|
1202
|
+
ctx.body = result;
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
console.error("[recreateEmbeddings] Error:", error.message);
|
|
1205
|
+
ctx.throw(500, error.message || "Failed to recreate embeddings");
|
|
1206
|
+
}
|
|
1089
1207
|
}
|
|
1090
1208
|
});
|
|
1091
1209
|
const PLUGIN_ID$2 = "strapi-content-embeddings";
|
|
@@ -1354,6 +1472,45 @@ const admin = [
|
|
|
1354
1472
|
}
|
|
1355
1473
|
]
|
|
1356
1474
|
}
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
method: "GET",
|
|
1478
|
+
path: "/embeddings/related-chunks/:id",
|
|
1479
|
+
handler: "controller.getRelatedChunks",
|
|
1480
|
+
config: {
|
|
1481
|
+
policies: [
|
|
1482
|
+
{
|
|
1483
|
+
name: "admin::hasPermissions",
|
|
1484
|
+
config: { actions: ["plugin::strapi-content-embeddings.read"] }
|
|
1485
|
+
}
|
|
1486
|
+
]
|
|
1487
|
+
}
|
|
1488
|
+
},
|
|
1489
|
+
{
|
|
1490
|
+
method: "GET",
|
|
1491
|
+
path: "/debug/neon",
|
|
1492
|
+
handler: "controller.debugNeon",
|
|
1493
|
+
config: {
|
|
1494
|
+
policies: [
|
|
1495
|
+
{
|
|
1496
|
+
name: "admin::hasPermissions",
|
|
1497
|
+
config: { actions: ["plugin::strapi-content-embeddings.read"] }
|
|
1498
|
+
}
|
|
1499
|
+
]
|
|
1500
|
+
}
|
|
1501
|
+
},
|
|
1502
|
+
{
|
|
1503
|
+
method: "POST",
|
|
1504
|
+
path: "/recreate",
|
|
1505
|
+
handler: "controller.recreateEmbeddings",
|
|
1506
|
+
config: {
|
|
1507
|
+
policies: [
|
|
1508
|
+
{
|
|
1509
|
+
name: "admin::hasPermissions",
|
|
1510
|
+
config: { actions: ["plugin::strapi-content-embeddings.update"] }
|
|
1511
|
+
}
|
|
1512
|
+
]
|
|
1513
|
+
}
|
|
1357
1514
|
}
|
|
1358
1515
|
];
|
|
1359
1516
|
const routes = {
|
|
@@ -1606,10 +1763,11 @@ const embeddings = ({ strapi }) => ({
|
|
|
1606
1763
|
wasChunked: false
|
|
1607
1764
|
};
|
|
1608
1765
|
}
|
|
1609
|
-
console.log(`Chunking content into ${chunks.length} parts (chunkSize: ${chunkSize}, overlap: ${chunkOverlap})`);
|
|
1766
|
+
console.log(`[createChunkedEmbedding] Chunking content into ${chunks.length} parts (chunkSize: ${chunkSize}, overlap: ${chunkOverlap})`);
|
|
1610
1767
|
const createdChunks = [];
|
|
1611
1768
|
let parentDocumentId = null;
|
|
1612
1769
|
for (const chunk of chunks) {
|
|
1770
|
+
console.log(`[createChunkedEmbedding] Processing chunk ${chunk.chunkIndex + 1}/${chunks.length}`);
|
|
1613
1771
|
const chunkTitle = formatChunkTitle(title, chunk.chunkIndex, chunk.totalChunks);
|
|
1614
1772
|
const chunkMetadata = {
|
|
1615
1773
|
...metadata,
|
|
@@ -1632,12 +1790,15 @@ const embeddings = ({ strapi }) => ({
|
|
|
1632
1790
|
if (chunk.chunkIndex === 0 && related && related.__type && related.id) {
|
|
1633
1791
|
entityData.related = related;
|
|
1634
1792
|
}
|
|
1793
|
+
console.log(`[chunk ${chunk.chunkIndex + 1}] Creating entity in DB...`);
|
|
1635
1794
|
const entity = await strapi.documents(CONTENT_TYPE_UID$1).create({
|
|
1636
1795
|
data: entityData
|
|
1637
1796
|
});
|
|
1797
|
+
console.log(`[chunk ${chunk.chunkIndex + 1}] Entity created: ${entity.documentId}`);
|
|
1638
1798
|
if (chunk.chunkIndex === 0) {
|
|
1639
1799
|
parentDocumentId = entity.documentId;
|
|
1640
1800
|
} else {
|
|
1801
|
+
console.log(`[chunk ${chunk.chunkIndex + 1}] Updating metadata with parent ref...`);
|
|
1641
1802
|
await strapi.documents(CONTENT_TYPE_UID$1).update({
|
|
1642
1803
|
documentId: entity.documentId,
|
|
1643
1804
|
data: {
|
|
@@ -1647,9 +1808,11 @@ const embeddings = ({ strapi }) => ({
|
|
|
1647
1808
|
}
|
|
1648
1809
|
}
|
|
1649
1810
|
});
|
|
1811
|
+
console.log(`[chunk ${chunk.chunkIndex + 1}] Metadata updated`);
|
|
1650
1812
|
}
|
|
1651
1813
|
if (pluginManager.isInitialized()) {
|
|
1652
1814
|
try {
|
|
1815
|
+
console.log(`[chunk ${chunk.chunkIndex + 1}] Creating OpenAI embedding...`);
|
|
1653
1816
|
const result = await pluginManager.createEmbedding({
|
|
1654
1817
|
id: entity.documentId,
|
|
1655
1818
|
title: chunkTitle,
|
|
@@ -1657,6 +1820,8 @@ const embeddings = ({ strapi }) => ({
|
|
|
1657
1820
|
collectionType: collectionType || "standalone",
|
|
1658
1821
|
fieldName: fieldName || "content"
|
|
1659
1822
|
});
|
|
1823
|
+
console.log(`[chunk ${chunk.chunkIndex + 1}] OpenAI embedding created`);
|
|
1824
|
+
console.log(`[chunk ${chunk.chunkIndex + 1}] Saving embedding to DB...`);
|
|
1660
1825
|
const updatedEntity = await strapi.documents(CONTENT_TYPE_UID$1).update({
|
|
1661
1826
|
documentId: entity.documentId,
|
|
1662
1827
|
data: {
|
|
@@ -1664,15 +1829,17 @@ const embeddings = ({ strapi }) => ({
|
|
|
1664
1829
|
embedding: result.embedding
|
|
1665
1830
|
}
|
|
1666
1831
|
});
|
|
1832
|
+
console.log(`[chunk ${chunk.chunkIndex + 1}] Chunk complete`);
|
|
1667
1833
|
createdChunks.push(updatedEntity);
|
|
1668
1834
|
} catch (error) {
|
|
1669
|
-
console.error(`
|
|
1835
|
+
console.error(`[chunk ${chunk.chunkIndex + 1}] FAILED:`, error.message || error);
|
|
1670
1836
|
createdChunks.push(entity);
|
|
1671
1837
|
}
|
|
1672
1838
|
} else {
|
|
1673
1839
|
createdChunks.push(entity);
|
|
1674
1840
|
}
|
|
1675
1841
|
}
|
|
1842
|
+
console.log(`[createChunkedEmbedding] Completed, created ${createdChunks.length} chunks, first documentId: ${createdChunks[0]?.documentId}`);
|
|
1676
1843
|
return {
|
|
1677
1844
|
entity: createdChunks[0],
|
|
1678
1845
|
chunks: createdChunks,
|
|
@@ -1719,8 +1886,11 @@ const embeddings = ({ strapi }) => ({
|
|
|
1719
1886
|
metadata: {
|
|
1720
1887
|
$containsi: `"parentDocumentId":"${documentId}"`
|
|
1721
1888
|
}
|
|
1722
|
-
}
|
|
1889
|
+
},
|
|
1890
|
+
limit: -1
|
|
1891
|
+
// No limit - get all
|
|
1723
1892
|
});
|
|
1893
|
+
console.log(`[findRelatedChunks] Found ${children.length} children for parent ${documentId}`);
|
|
1724
1894
|
if (children.length === 0) {
|
|
1725
1895
|
return [entry];
|
|
1726
1896
|
}
|
|
@@ -1736,8 +1906,11 @@ const embeddings = ({ strapi }) => ({
|
|
|
1736
1906
|
}
|
|
1737
1907
|
}
|
|
1738
1908
|
]
|
|
1739
|
-
}
|
|
1909
|
+
},
|
|
1910
|
+
limit: -1
|
|
1911
|
+
// No limit - get all
|
|
1740
1912
|
});
|
|
1913
|
+
console.log(`[findRelatedChunks] Found ${allChunks.length} total chunks for parent ${parentId}`);
|
|
1741
1914
|
return allChunks.sort((a, b) => {
|
|
1742
1915
|
const aIndex = a.metadata?.chunkIndex ?? 0;
|
|
1743
1916
|
const bIndex = b.metadata?.chunkIndex ?? 0;
|
|
@@ -2134,6 +2307,94 @@ const sync = ({ strapi }) => ({
|
|
|
2134
2307
|
missingInNeon,
|
|
2135
2308
|
contentDifferences
|
|
2136
2309
|
};
|
|
2310
|
+
},
|
|
2311
|
+
/**
|
|
2312
|
+
* Recreate all embeddings in Neon DB from Strapi data
|
|
2313
|
+
*
|
|
2314
|
+
* This will:
|
|
2315
|
+
* 1. Delete ALL embeddings from Neon DB
|
|
2316
|
+
* 2. Re-create embeddings for each Strapi embedding entry
|
|
2317
|
+
* 3. Update Strapi entries with new embedding IDs
|
|
2318
|
+
*
|
|
2319
|
+
* Use this when embeddings were created with incorrect metadata format
|
|
2320
|
+
*/
|
|
2321
|
+
async recreateAllEmbeddings() {
|
|
2322
|
+
const result = {
|
|
2323
|
+
success: false,
|
|
2324
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2325
|
+
deletedFromNeon: 0,
|
|
2326
|
+
processedFromStrapi: 0,
|
|
2327
|
+
recreatedInNeon: 0,
|
|
2328
|
+
errors: [],
|
|
2329
|
+
details: {
|
|
2330
|
+
recreated: [],
|
|
2331
|
+
failed: []
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
if (!pluginManager.isInitialized()) {
|
|
2335
|
+
result.errors.push(
|
|
2336
|
+
"Plugin manager not initialized. Check your Neon and OpenAI configuration."
|
|
2337
|
+
);
|
|
2338
|
+
return result;
|
|
2339
|
+
}
|
|
2340
|
+
try {
|
|
2341
|
+
console.log("[recreateAllEmbeddings] Step 1: Clearing Neon DB...");
|
|
2342
|
+
result.deletedFromNeon = await pluginManager.clearAllNeonEmbeddings();
|
|
2343
|
+
console.log(`[recreateAllEmbeddings] Deleted ${result.deletedFromNeon} embeddings from Neon`);
|
|
2344
|
+
console.log("[recreateAllEmbeddings] Step 2: Fetching Strapi embeddings...");
|
|
2345
|
+
const strapiEmbeddings = await strapi.documents(CONTENT_TYPE_UID).findMany({
|
|
2346
|
+
limit: -1
|
|
2347
|
+
// Get all
|
|
2348
|
+
});
|
|
2349
|
+
result.processedFromStrapi = strapiEmbeddings.length;
|
|
2350
|
+
console.log(`[recreateAllEmbeddings] Found ${strapiEmbeddings.length} embeddings in Strapi`);
|
|
2351
|
+
if (strapiEmbeddings.length === 0) {
|
|
2352
|
+
result.success = true;
|
|
2353
|
+
return result;
|
|
2354
|
+
}
|
|
2355
|
+
console.log("[recreateAllEmbeddings] Step 3: Recreating embeddings in Neon...");
|
|
2356
|
+
for (let i = 0; i < strapiEmbeddings.length; i++) {
|
|
2357
|
+
const entry = strapiEmbeddings[i];
|
|
2358
|
+
const progress = `[${i + 1}/${strapiEmbeddings.length}]`;
|
|
2359
|
+
if (!entry.content) {
|
|
2360
|
+
console.log(`${progress} Skipping ${entry.documentId} - no content`);
|
|
2361
|
+
result.details.failed.push(`${entry.documentId}: no content`);
|
|
2362
|
+
continue;
|
|
2363
|
+
}
|
|
2364
|
+
try {
|
|
2365
|
+
console.log(`${progress} Creating embedding for: ${entry.title || entry.documentId}`);
|
|
2366
|
+
const embeddingResult = await pluginManager.createEmbedding({
|
|
2367
|
+
id: entry.documentId,
|
|
2368
|
+
title: entry.title || "",
|
|
2369
|
+
content: entry.content,
|
|
2370
|
+
collectionType: entry.collectionType || "standalone",
|
|
2371
|
+
fieldName: entry.fieldName || "content"
|
|
2372
|
+
});
|
|
2373
|
+
await strapi.documents(CONTENT_TYPE_UID).update({
|
|
2374
|
+
documentId: entry.documentId,
|
|
2375
|
+
data: {
|
|
2376
|
+
embeddingId: embeddingResult.embeddingId,
|
|
2377
|
+
embedding: embeddingResult.embedding
|
|
2378
|
+
}
|
|
2379
|
+
});
|
|
2380
|
+
result.recreatedInNeon++;
|
|
2381
|
+
result.details.recreated.push(`${entry.documentId} (${entry.title || "untitled"})`);
|
|
2382
|
+
if (i < strapiEmbeddings.length - 1) {
|
|
2383
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2384
|
+
}
|
|
2385
|
+
} catch (error) {
|
|
2386
|
+
console.error(`${progress} Failed:`, error.message || error);
|
|
2387
|
+
result.errors.push(`${entry.documentId}: ${error.message || error}`);
|
|
2388
|
+
result.details.failed.push(`${entry.documentId}: ${error.message || error}`);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
result.success = result.errors.length === 0;
|
|
2392
|
+
console.log(`[recreateAllEmbeddings] Complete. Recreated: ${result.recreatedInNeon}, Failed: ${result.details.failed.length}`);
|
|
2393
|
+
return result;
|
|
2394
|
+
} catch (error) {
|
|
2395
|
+
result.errors.push(`Recreate failed: ${error.message || error}`);
|
|
2396
|
+
return result;
|
|
2397
|
+
}
|
|
2137
2398
|
}
|
|
2138
2399
|
});
|
|
2139
2400
|
const services = {
|
|
@@ -2155,4 +2416,3 @@ const index = {
|
|
|
2155
2416
|
export {
|
|
2156
2417
|
index as default
|
|
2157
2418
|
};
|
|
2158
|
-
//# sourceMappingURL=index.mjs.map
|
|
@@ -8,6 +8,11 @@ declare const controller: ({ strapi }: {
|
|
|
8
8
|
getEmbeddings(ctx: any): Promise<void>;
|
|
9
9
|
getEmbedding(ctx: any): Promise<void>;
|
|
10
10
|
queryEmbeddings(ctx: any): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Get all chunks related to a document
|
|
13
|
+
* GET /api/strapi-content-embeddings/embeddings/related-chunks/:id
|
|
14
|
+
*/
|
|
15
|
+
getRelatedChunks(ctx: any): Promise<void>;
|
|
11
16
|
/**
|
|
12
17
|
* Sync embeddings from Neon DB to Strapi DB
|
|
13
18
|
* GET /api/strapi-content-embeddings/sync
|
|
@@ -22,5 +27,18 @@ declare const controller: ({ strapi }: {
|
|
|
22
27
|
* GET /api/strapi-content-embeddings/sync/status
|
|
23
28
|
*/
|
|
24
29
|
getSyncStatus(ctx: any): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Debug endpoint to inspect Neon DB contents
|
|
32
|
+
* GET /api/strapi-content-embeddings/debug/neon
|
|
33
|
+
*/
|
|
34
|
+
debugNeon(ctx: any): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Recreate all embeddings in Neon from Strapi data
|
|
37
|
+
* POST /api/strapi-content-embeddings/recreate
|
|
38
|
+
*
|
|
39
|
+
* Use this when embeddings were created with incorrect metadata format
|
|
40
|
+
* WARNING: This will delete ALL existing Neon embeddings and recreate them
|
|
41
|
+
*/
|
|
42
|
+
recreateEmbeddings(ctx: any): Promise<void>;
|
|
25
43
|
};
|
|
26
44
|
export default controller;
|
|
@@ -8,8 +8,11 @@ declare const _default: {
|
|
|
8
8
|
getEmbeddings(ctx: any): Promise<void>;
|
|
9
9
|
getEmbedding(ctx: any): Promise<void>;
|
|
10
10
|
queryEmbeddings(ctx: any): Promise<void>;
|
|
11
|
+
getRelatedChunks(ctx: any): Promise<void>;
|
|
11
12
|
syncFromNeon(ctx: any): Promise<void>;
|
|
12
13
|
getSyncStatus(ctx: any): Promise<void>;
|
|
14
|
+
debugNeon(ctx: any): Promise<void>;
|
|
15
|
+
recreateEmbeddings(ctx: any): Promise<void>;
|
|
13
16
|
};
|
|
14
17
|
mcp: ({ strapi }: {
|
|
15
18
|
strapi: import("@strapi/types/dist/core").Strapi;
|
|
@@ -29,8 +29,11 @@ declare const _default: {
|
|
|
29
29
|
getEmbeddings(ctx: any): Promise<void>;
|
|
30
30
|
getEmbedding(ctx: any): Promise<void>;
|
|
31
31
|
queryEmbeddings(ctx: any): Promise<void>;
|
|
32
|
+
getRelatedChunks(ctx: any): Promise<void>;
|
|
32
33
|
syncFromNeon(ctx: any): Promise<void>;
|
|
33
34
|
getSyncStatus(ctx: any): Promise<void>;
|
|
35
|
+
debugNeon(ctx: any): Promise<void>;
|
|
36
|
+
recreateEmbeddings(ctx: any): Promise<void>;
|
|
34
37
|
};
|
|
35
38
|
mcp: ({ strapi }: {
|
|
36
39
|
strapi: import("@strapi/types/dist/core").Strapi;
|
|
@@ -127,6 +130,7 @@ declare const _default: {
|
|
|
127
130
|
missingInNeon: number;
|
|
128
131
|
contentDifferences: number;
|
|
129
132
|
}>;
|
|
133
|
+
recreateAllEmbeddings(): Promise<import("./services/sync").RecreateResult>;
|
|
130
134
|
};
|
|
131
135
|
};
|
|
132
136
|
contentTypes: {
|
|
@@ -56,6 +56,22 @@ declare class PluginManager {
|
|
|
56
56
|
*/
|
|
57
57
|
deleteNeonEmbeddingById(neonId: string): Promise<void>;
|
|
58
58
|
destroy(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Clear all embeddings from Neon DB
|
|
61
|
+
* Returns the number of deleted rows
|
|
62
|
+
*/
|
|
63
|
+
clearAllNeonEmbeddings(): Promise<number>;
|
|
64
|
+
/**
|
|
65
|
+
* Debug method to inspect raw data in Neon DB
|
|
66
|
+
*/
|
|
67
|
+
debugNeonEmbeddings(): Promise<Array<{
|
|
68
|
+
id: string;
|
|
69
|
+
content: string;
|
|
70
|
+
metadata: any;
|
|
71
|
+
metadataType: string;
|
|
72
|
+
hasEmbedding: boolean;
|
|
73
|
+
embeddingLength: number;
|
|
74
|
+
}>>;
|
|
59
75
|
}
|
|
60
76
|
export declare const pluginManager: PluginManager;
|
|
61
77
|
export type { PluginConfig, EmbeddingDocument, QueryResponse, CreateEmbeddingResult };
|
|
@@ -16,6 +16,18 @@ export interface SyncResult {
|
|
|
16
16
|
};
|
|
17
17
|
errors: string[];
|
|
18
18
|
}
|
|
19
|
+
export interface RecreateResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
deletedFromNeon: number;
|
|
23
|
+
processedFromStrapi: number;
|
|
24
|
+
recreatedInNeon: number;
|
|
25
|
+
errors: string[];
|
|
26
|
+
details: {
|
|
27
|
+
recreated: string[];
|
|
28
|
+
failed: string[];
|
|
29
|
+
};
|
|
30
|
+
}
|
|
19
31
|
declare const sync: ({ strapi }: {
|
|
20
32
|
strapi: Core.Strapi;
|
|
21
33
|
}) => {
|
|
@@ -44,5 +56,16 @@ declare const sync: ({ strapi }: {
|
|
|
44
56
|
missingInNeon: number;
|
|
45
57
|
contentDifferences: number;
|
|
46
58
|
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Recreate all embeddings in Neon DB from Strapi data
|
|
61
|
+
*
|
|
62
|
+
* This will:
|
|
63
|
+
* 1. Delete ALL embeddings from Neon DB
|
|
64
|
+
* 2. Re-create embeddings for each Strapi embedding entry
|
|
65
|
+
* 3. Update Strapi entries with new embedding IDs
|
|
66
|
+
*
|
|
67
|
+
* Use this when embeddings were created with incorrect metadata format
|
|
68
|
+
*/
|
|
69
|
+
recreateAllEmbeddings(): Promise<RecreateResult>;
|
|
47
70
|
};
|
|
48
71
|
export default sync;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-content-embeddings",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Strapi v5 plugin for vector embeddings with OpenAI and Neon PostgreSQL. Enables semantic search, RAG chat, and MCP (Model Context Protocol) integration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|