strapi-content-embeddings 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/_chunks/en-B4KWt_jN.js +0 -1
  2. package/dist/_chunks/en-Byx4XI2L.mjs +0 -1
  3. package/dist/admin/index.js +686 -4
  4. package/dist/admin/index.mjs +683 -4
  5. package/dist/server/index.js +293 -287
  6. package/dist/server/index.mjs +293 -287
  7. package/dist/server/src/index.d.ts +5 -0
  8. package/dist/server/src/mcp/schemas/index.d.ts +3 -0
  9. package/dist/server/src/mcp/tools/create-embedding.d.ts +3 -4
  10. package/dist/server/src/mcp/tools/get-embedding.d.ts +3 -3
  11. package/dist/server/src/mcp/tools/index.d.ts +1 -0
  12. package/dist/server/src/mcp/tools/list-embeddings.d.ts +3 -3
  13. package/dist/server/src/mcp/tools/rag-query.d.ts +3 -3
  14. package/dist/server/src/mcp/tools/semantic-search.d.ts +3 -3
  15. package/dist/server/src/services/ai-tools.d.ts +13 -0
  16. package/dist/server/src/services/embeddings.d.ts +2 -2
  17. package/dist/server/src/services/index.d.ts +5 -0
  18. package/dist/server/src/tools/create-embedding.d.ts +9 -0
  19. package/dist/server/src/tools/get-embedding.d.ts +8 -0
  20. package/dist/server/src/tools/index.d.ts +14 -0
  21. package/dist/server/src/tools/list-embeddings.d.ts +8 -0
  22. package/dist/server/src/tools/rag-query.d.ts +8 -0
  23. package/dist/server/src/tools/semantic-search.d.ts +8 -0
  24. package/dist/server/src/tools/types.d.ts +14 -0
  25. package/package.json +1 -1
  26. package/dist/_chunks/App-ByRBbkZn.js +0 -1600
  27. package/dist/_chunks/App-ByRBbkZn.js.map +0 -1
  28. package/dist/_chunks/App-MjsTrWRS.mjs +0 -1596
  29. package/dist/_chunks/App-MjsTrWRS.mjs.map +0 -1
  30. package/dist/_chunks/en-B4KWt_jN.js.map +0 -1
  31. package/dist/_chunks/en-Byx4XI2L.mjs.map +0 -1
  32. package/dist/_chunks/index-TWbcT-zJ.js +0 -785
  33. package/dist/_chunks/index-TWbcT-zJ.js.map +0 -1
  34. package/dist/_chunks/index-ifqYByO5.mjs +0 -783
  35. package/dist/_chunks/index-ifqYByO5.mjs.map +0 -1
  36. package/dist/admin/index.js.map +0 -1
  37. package/dist/admin/index.mjs.map +0 -1
  38. package/dist/server/index.js.map +0 -1
  39. package/dist/server/index.mjs.map +0 -1
@@ -438,7 +438,8 @@ const GetEmbeddingSchema = z.object({
438
438
  const CreateEmbeddingSchema = z.object({
439
439
  title: z.string().min(1, "Title is required"),
440
440
  content: z.string().min(1, "Content is required"),
441
- metadata: z.record(z.any()).optional()
441
+ metadata: z.record(z.any()).optional(),
442
+ autoChunk: z.boolean().optional().describe("Automatically split large content into chunks")
442
443
  });
443
444
  const ToolSchemas = {
444
445
  semantic_search: SemanticSearchSchema,
@@ -460,28 +461,12 @@ function validateToolInput(toolName, input) {
460
461
  return result.data;
461
462
  }
462
463
  const semanticSearchTool = {
463
- name: "semantic_search",
464
- description: 'TRIGGER: Use when user types "/rag" or asks to search embeddings/content. Search for semantically similar content using vector embeddings. Returns the most relevant documents matching your query based on meaning, not just keywords.',
465
- inputSchema: {
466
- type: "object",
467
- properties: {
468
- query: {
469
- type: "string",
470
- description: "The search query text to find similar content"
471
- },
472
- limit: {
473
- type: "number",
474
- description: "Maximum number of results to return (default: 5, max: 20)",
475
- default: 5
476
- }
477
- },
478
- required: ["query"]
479
- }
480
- };
481
- async function handleSemanticSearch(strapi, args) {
482
- const { query, limit = 5 } = args;
483
- const maxLimit = Math.min(limit, 20);
484
- try {
464
+ name: "semanticSearch",
465
+ description: "Search for semantically similar content using vector embeddings. Finds relevant documents by meaning, not just keywords.",
466
+ schema: SemanticSearchSchema,
467
+ execute: async (args, strapi) => {
468
+ const { query, limit = 5 } = args;
469
+ const maxLimit = Math.min(limit, 20);
485
470
  const pluginManager2 = strapi.contentEmbeddingsManager;
486
471
  if (!pluginManager2) {
487
472
  throw new Error("Content embeddings plugin not initialized");
@@ -494,49 +479,49 @@ async function handleSemanticSearch(strapi, args) {
494
479
  score: doc.score || null
495
480
  }));
496
481
  return {
497
- content: [
498
- {
499
- type: "text",
500
- text: JSON.stringify(
501
- {
502
- query,
503
- resultCount: formattedResults.length,
504
- results: formattedResults
505
- },
506
- null,
507
- 2
508
- )
509
- }
510
- ]
482
+ query,
483
+ resultCount: formattedResults.length,
484
+ results: formattedResults
511
485
  };
512
- } catch (error) {
513
- throw new Error(
514
- `Semantic search failed: ${error instanceof Error ? error.message : String(error)}`
515
- );
516
- }
517
- }
518
- const ragQueryTool = {
519
- name: "rag_query",
520
- description: 'TRIGGER: Use when user types "/rag" followed by a question. Ask a question and get an AI-generated answer based on your embedded content. Uses RAG (Retrieval-Augmented Generation) to find relevant documents and generate a contextual response. This is the PRIMARY tool for /rag queries.',
486
+ },
487
+ publicSafe: true
488
+ };
489
+ const semanticSearchMcpTool = {
490
+ name: "semantic_search",
491
+ description: semanticSearchTool.description,
521
492
  inputSchema: {
522
493
  type: "object",
523
494
  properties: {
524
495
  query: {
525
496
  type: "string",
526
- description: "The question or query to answer using embedded content"
497
+ description: "The search query text to find similar content"
527
498
  },
528
- includeSourceDocuments: {
529
- type: "boolean",
530
- description: "Include the source documents used to generate the answer (default: true)",
531
- default: true
499
+ limit: {
500
+ type: "number",
501
+ description: "Maximum number of results to return (default: 5, max: 20)",
502
+ default: 5
532
503
  }
533
504
  },
534
505
  required: ["query"]
535
506
  }
536
507
  };
537
- async function handleRagQuery(strapi, args) {
538
- const { query, includeSourceDocuments = true } = args;
539
- try {
508
+ async function handleSemanticSearch(strapi, args) {
509
+ const result = await semanticSearchTool.execute(args, strapi);
510
+ return {
511
+ content: [
512
+ {
513
+ type: "text",
514
+ text: JSON.stringify(result, null, 2)
515
+ }
516
+ ]
517
+ };
518
+ }
519
+ const ragQueryTool = {
520
+ name: "ragQuery",
521
+ description: "Ask a question and get an AI-generated answer grounded in embedded content. Uses retrieval-augmented generation (RAG) with vector search.",
522
+ schema: RagQuerySchema,
523
+ execute: async (args, strapi) => {
524
+ const { query, includeSourceDocuments = true } = args;
540
525
  const embeddingsService = strapi.plugin("strapi-content-embeddings").service("embeddings");
541
526
  const result = await embeddingsService.queryEmbeddings(query);
542
527
  const response = {
@@ -551,48 +536,47 @@ async function handleRagQuery(strapi, args) {
551
536
  }));
552
537
  response.sourceCount = result.sourceDocuments.length;
553
538
  }
554
- return {
555
- content: [
556
- {
557
- type: "text",
558
- text: JSON.stringify(response, null, 2)
559
- }
560
- ]
561
- };
562
- } catch (error) {
563
- throw new Error(
564
- `RAG query failed: ${error instanceof Error ? error.message : String(error)}`
565
- );
566
- }
567
- }
568
- const listEmbeddingsTool = {
569
- name: "list_embeddings",
570
- description: "List all embeddings stored in the database. Returns metadata without the full content to avoid context overflow.",
539
+ return response;
540
+ },
541
+ publicSafe: true
542
+ };
543
+ const ragQueryMcpTool = {
544
+ name: "rag_query",
545
+ description: ragQueryTool.description,
571
546
  inputSchema: {
572
547
  type: "object",
573
548
  properties: {
574
- page: {
575
- type: "number",
576
- description: "Page number (starts at 1)",
577
- default: 1
578
- },
579
- pageSize: {
580
- type: "number",
581
- description: "Number of items per page (max: 50)",
582
- default: 25
583
- },
584
- search: {
549
+ query: {
585
550
  type: "string",
586
- description: "Search filter for title"
551
+ description: "The question or query to answer using embedded content"
552
+ },
553
+ includeSourceDocuments: {
554
+ type: "boolean",
555
+ description: "Include the source documents used to generate the answer (default: true)",
556
+ default: true
587
557
  }
588
558
  },
589
- required: []
559
+ required: ["query"]
590
560
  }
591
561
  };
592
- async function handleListEmbeddings(strapi, args) {
593
- const { page = 1, pageSize = 25, search } = args;
594
- const limit = Math.min(pageSize, 50);
595
- try {
562
+ async function handleRagQuery(strapi, args) {
563
+ const result = await ragQueryTool.execute(args, strapi);
564
+ return {
565
+ content: [
566
+ {
567
+ type: "text",
568
+ text: JSON.stringify(result, null, 2)
569
+ }
570
+ ]
571
+ };
572
+ }
573
+ const listEmbeddingsTool = {
574
+ name: "listEmbeddings",
575
+ description: "List all embedded documents with pagination. Returns metadata and content preview without full text.",
576
+ schema: ListEmbeddingsSchema,
577
+ execute: async (args, strapi) => {
578
+ const { page = 1, pageSize = 25, search } = args;
579
+ const limit = Math.min(pageSize, 50);
596
580
  const embeddingsService = strapi.plugin("strapi-content-embeddings").service("embeddings");
597
581
  const filters = {};
598
582
  if (search) {
@@ -615,65 +599,63 @@ async function handleListEmbeddings(strapi, args) {
615
599
  updatedAt: emb.updatedAt
616
600
  }));
617
601
  return {
618
- content: [
619
- {
620
- type: "text",
621
- text: JSON.stringify(
622
- {
623
- embeddings: embeddings2,
624
- pagination: result.pagination || {
625
- page,
626
- pageSize: limit,
627
- total: embeddings2.length
628
- }
629
- },
630
- null,
631
- 2
632
- )
633
- }
634
- ]
602
+ embeddings: embeddings2,
603
+ pagination: result.pagination || {
604
+ page,
605
+ pageSize: limit,
606
+ total: embeddings2.length
607
+ }
635
608
  };
636
- } catch (error) {
637
- throw new Error(
638
- `Failed to list embeddings: ${error instanceof Error ? error.message : String(error)}`
639
- );
640
- }
641
- }
642
- const getEmbeddingTool = {
643
- name: "get_embedding",
644
- description: "Get a specific embedding by its document ID. Returns the full content and metadata.",
609
+ },
610
+ publicSafe: true
611
+ };
612
+ const listEmbeddingsMcpTool = {
613
+ name: "list_embeddings",
614
+ description: listEmbeddingsTool.description,
645
615
  inputSchema: {
646
616
  type: "object",
647
617
  properties: {
648
- documentId: {
649
- type: "string",
650
- description: "The document ID of the embedding to retrieve"
618
+ page: {
619
+ type: "number",
620
+ description: "Page number (starts at 1)",
621
+ default: 1
651
622
  },
652
- includeContent: {
653
- type: "boolean",
654
- description: "Include the full content text (default: true)",
655
- default: true
623
+ pageSize: {
624
+ type: "number",
625
+ description: "Number of items per page (max: 50)",
626
+ default: 25
627
+ },
628
+ search: {
629
+ type: "string",
630
+ description: "Search filter for title"
656
631
  }
657
632
  },
658
- required: ["documentId"]
633
+ required: []
659
634
  }
660
635
  };
661
- async function handleGetEmbedding(strapi, args) {
662
- const { documentId, includeContent = true } = args;
663
- try {
636
+ async function handleListEmbeddings(strapi, args) {
637
+ const result = await listEmbeddingsTool.execute(args, strapi);
638
+ return {
639
+ content: [
640
+ {
641
+ type: "text",
642
+ text: JSON.stringify(result, null, 2)
643
+ }
644
+ ]
645
+ };
646
+ }
647
+ const getEmbeddingTool = {
648
+ name: "getEmbedding",
649
+ description: "Get a specific embedded document by its document ID. Returns full content and metadata.",
650
+ schema: GetEmbeddingSchema,
651
+ execute: async (args, strapi) => {
652
+ const { documentId, includeContent = true } = args;
664
653
  const embeddingsService = strapi.plugin("strapi-content-embeddings").service("embeddings");
665
654
  const embedding2 = await embeddingsService.getEmbedding(documentId);
666
655
  if (!embedding2) {
667
656
  return {
668
- content: [
669
- {
670
- type: "text",
671
- text: JSON.stringify({
672
- error: true,
673
- message: `Embedding not found with documentId: ${documentId}`
674
- })
675
- }
676
- ]
657
+ error: true,
658
+ message: `Embedding not found with documentId: ${documentId}`
677
659
  };
678
660
  }
679
661
  const result = {
@@ -690,49 +672,46 @@ async function handleGetEmbedding(strapi, args) {
690
672
  if (includeContent) {
691
673
  result.content = embedding2.content;
692
674
  }
693
- return {
694
- content: [
695
- {
696
- type: "text",
697
- text: JSON.stringify(result, null, 2)
698
- }
699
- ]
700
- };
701
- } catch (error) {
702
- throw new Error(
703
- `Failed to get embedding: ${error instanceof Error ? error.message : String(error)}`
704
- );
705
- }
706
- }
707
- const createEmbeddingTool = {
708
- name: "create_embedding",
709
- description: "Create a new embedding from text content. The content will be vectorized and stored for semantic search. For large content (over 4000 characters), enable autoChunk to automatically split into multiple embeddings.",
675
+ return result;
676
+ },
677
+ publicSafe: true
678
+ };
679
+ const getEmbeddingMcpTool = {
680
+ name: "get_embedding",
681
+ description: getEmbeddingTool.description,
710
682
  inputSchema: {
711
683
  type: "object",
712
684
  properties: {
713
- title: {
714
- type: "string",
715
- description: "A descriptive title for the embedding"
716
- },
717
- content: {
685
+ documentId: {
718
686
  type: "string",
719
- description: "The text content to embed (will be vectorized)"
720
- },
721
- metadata: {
722
- type: "object",
723
- description: "Optional metadata to associate with the embedding (tags, source, etc.)"
687
+ description: "The document ID of the embedding to retrieve"
724
688
  },
725
- autoChunk: {
689
+ includeContent: {
726
690
  type: "boolean",
727
- description: "Automatically split large content into chunks (default: false). When enabled, content over 4000 characters will be split into multiple embeddings with overlap for context preservation."
691
+ description: "Include the full content text (default: true)",
692
+ default: true
728
693
  }
729
694
  },
730
- required: ["title", "content"]
695
+ required: ["documentId"]
731
696
  }
732
697
  };
733
- async function handleCreateEmbedding(strapi, args) {
734
- const { title, content, metadata, autoChunk } = args;
735
- try {
698
+ async function handleGetEmbedding(strapi, args) {
699
+ const result = await getEmbeddingTool.execute(args, strapi);
700
+ return {
701
+ content: [
702
+ {
703
+ type: "text",
704
+ text: JSON.stringify(result, null, 2)
705
+ }
706
+ ]
707
+ };
708
+ }
709
+ const createEmbeddingTool = {
710
+ name: "createEmbedding",
711
+ description: "Create a new embedding from text content for future semantic search. Large content can be auto-chunked into multiple embeddings.",
712
+ schema: CreateEmbeddingSchema,
713
+ execute: async (args, strapi) => {
714
+ const { title, content, metadata, autoChunk } = args;
736
715
  const embeddingsService = strapi.plugin("strapi-content-embeddings").service("embeddings");
737
716
  if (autoChunk) {
738
717
  const result = await embeddingsService.createChunkedEmbedding({
@@ -745,34 +724,23 @@ async function handleCreateEmbedding(strapi, args) {
745
724
  }
746
725
  });
747
726
  return {
748
- content: [
749
- {
750
- type: "text",
751
- text: JSON.stringify(
752
- {
753
- success: true,
754
- message: result.wasChunked ? `Content chunked into ${result.totalChunks} embeddings` : "Embedding created successfully (no chunking needed)",
755
- wasChunked: result.wasChunked,
756
- totalChunks: result.totalChunks,
757
- primaryEmbedding: {
758
- id: result.entity.id,
759
- documentId: result.entity.documentId,
760
- title: result.entity.title,
761
- embeddingId: result.entity.embeddingId
762
- },
763
- chunks: result.chunks.map((chunk) => ({
764
- documentId: chunk.documentId,
765
- title: chunk.title,
766
- contentLength: chunk.content?.length || 0
767
- })),
768
- contentLength: content.length,
769
- estimatedTokens: Math.ceil(content.length / 4)
770
- },
771
- null,
772
- 2
773
- )
774
- }
775
- ]
727
+ success: true,
728
+ message: result.wasChunked ? `Content chunked into ${result.totalChunks} embeddings` : "Embedding created successfully (no chunking needed)",
729
+ wasChunked: result.wasChunked,
730
+ totalChunks: result.totalChunks,
731
+ primaryEmbedding: {
732
+ id: result.entity.id,
733
+ documentId: result.entity.documentId,
734
+ title: result.entity.title,
735
+ embeddingId: result.entity.embeddingId
736
+ },
737
+ chunks: result.chunks.map((chunk) => ({
738
+ documentId: chunk.documentId,
739
+ title: chunk.title,
740
+ contentLength: chunk.content?.length || 0
741
+ })),
742
+ contentLength: content.length,
743
+ estimatedTokens: Math.ceil(content.length / 4)
776
744
  };
777
745
  }
778
746
  const embedding2 = await embeddingsService.createEmbedding({
@@ -785,42 +753,65 @@ async function handleCreateEmbedding(strapi, args) {
785
753
  }
786
754
  });
787
755
  return {
788
- content: [
789
- {
790
- type: "text",
791
- text: JSON.stringify(
792
- {
793
- success: true,
794
- message: "Embedding created successfully",
795
- embedding: {
796
- id: embedding2.id,
797
- documentId: embedding2.documentId,
798
- title: embedding2.title,
799
- embeddingId: embedding2.embeddingId,
800
- contentLength: content.length,
801
- metadata: embedding2.metadata,
802
- createdAt: embedding2.createdAt
803
- },
804
- hint: content.length > 4e3 ? "Content is large. Consider using autoChunk: true for better search results." : void 0
805
- },
806
- null,
807
- 2
808
- )
809
- }
810
- ]
756
+ success: true,
757
+ message: "Embedding created successfully",
758
+ embedding: {
759
+ id: embedding2.id,
760
+ documentId: embedding2.documentId,
761
+ title: embedding2.title,
762
+ embeddingId: embedding2.embeddingId,
763
+ contentLength: content.length,
764
+ metadata: embedding2.metadata,
765
+ createdAt: embedding2.createdAt
766
+ },
767
+ hint: content.length > 4e3 ? "Content is large. Consider using autoChunk: true for better search results." : void 0
811
768
  };
812
- } catch (error) {
813
- throw new Error(
814
- `Failed to create embedding: ${error instanceof Error ? error.message : String(error)}`
815
- );
769
+ },
770
+ publicSafe: false
771
+ };
772
+ const createEmbeddingMcpTool = {
773
+ name: "create_embedding",
774
+ description: createEmbeddingTool.description,
775
+ inputSchema: {
776
+ type: "object",
777
+ properties: {
778
+ title: {
779
+ type: "string",
780
+ description: "A descriptive title for the embedding"
781
+ },
782
+ content: {
783
+ type: "string",
784
+ description: "The text content to embed (will be vectorized)"
785
+ },
786
+ metadata: {
787
+ type: "object",
788
+ description: "Optional metadata to associate with the embedding (tags, source, etc.)"
789
+ },
790
+ autoChunk: {
791
+ type: "boolean",
792
+ description: "Automatically split large content into chunks (default: false). When enabled, content over 4000 characters will be split into multiple embeddings with overlap for context preservation."
793
+ }
794
+ },
795
+ required: ["title", "content"]
816
796
  }
797
+ };
798
+ async function handleCreateEmbedding(strapi, args) {
799
+ const result = await createEmbeddingTool.execute(args, strapi);
800
+ return {
801
+ content: [
802
+ {
803
+ type: "text",
804
+ text: JSON.stringify(result, null, 2)
805
+ }
806
+ ]
807
+ };
817
808
  }
818
- const tools = [
819
- semanticSearchTool,
820
- ragQueryTool,
821
- listEmbeddingsTool,
822
- getEmbeddingTool,
823
- createEmbeddingTool
809
+ const tools$1 = [
810
+ semanticSearchMcpTool,
811
+ ragQueryMcpTool,
812
+ listEmbeddingsMcpTool,
813
+ getEmbeddingMcpTool,
814
+ createEmbeddingMcpTool
824
815
  ];
825
816
  const toolHandlers = {
826
817
  semantic_search: handleSemanticSearch,
@@ -880,7 +871,7 @@ function createMcpServer(strapi) {
880
871
  }
881
872
  );
882
873
  server.setRequestHandler(ListToolsRequestSchema, async () => {
883
- return { tools };
874
+ return { tools: tools$1 };
884
875
  });
885
876
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
886
877
  return handleToolCall(strapi, request);
@@ -2067,12 +2058,12 @@ const embeddings = ({ strapi }) => ({
2067
2058
  return chunks.length;
2068
2059
  },
2069
2060
  /**
2070
- * Update a single chunk's content and embedding
2071
- * Updates only the specified chunk without affecting other chunks in the group
2061
+ * Update embeddings with automatic chunking support
2062
+ * Handles re-chunking when content changes and exceeds chunk size
2072
2063
  */
2073
2064
  async updateChunkedEmbedding(id, data) {
2074
- const { title, content, metadata } = data.data;
2075
- this.getConfig();
2065
+ const { title, content, metadata, autoChunk } = data.data;
2066
+ const config2 = this.getConfig();
2076
2067
  const currentEntry = await strapi.documents(CONTENT_TYPE_UID$1).findOne({
2077
2068
  documentId: id
2078
2069
  });
@@ -2080,61 +2071,64 @@ const embeddings = ({ strapi }) => ({
2080
2071
  throw new Error(`Embedding with id ${id} not found`);
2081
2072
  }
2082
2073
  const currentMetadata = currentEntry.metadata;
2083
- const contentChanged = content !== void 0 && content !== currentEntry.content;
2084
- console.log(`[updateChunkedEmbedding] Updating single chunk ${id}, contentChanged: ${contentChanged}`);
2085
- const updateData = {};
2086
- if (title !== void 0) {
2087
- const currentTitle = currentEntry.title || "";
2088
- const partMatch = currentTitle.match(/\s*\[Part \d+\/\d+\]$/);
2089
- updateData.title = partMatch ? `${title}${partMatch[0]}` : title;
2090
- }
2091
- if (content !== void 0) {
2092
- updateData.content = content;
2093
- }
2094
- if (metadata !== void 0) {
2095
- updateData.metadata = {
2096
- ...currentMetadata,
2097
- ...metadata
2098
- };
2099
- if (contentChanged) {
2100
- updateData.metadata.estimatedTokens = estimateTokens(updateData.content || currentEntry.content);
2101
- }
2102
- }
2103
- const updatedEntity = await strapi.documents(CONTENT_TYPE_UID$1).update({
2104
- documentId: id,
2105
- data: updateData
2106
- });
2107
- if (pluginManager.isInitialized() && (contentChanged || title !== void 0)) {
2108
- try {
2109
- console.log(`[updateChunkedEmbedding] Updating embedding in Neon for chunk ${id}`);
2110
- await pluginManager.deleteEmbedding(id);
2111
- const newContent = updateData.content || currentEntry.content;
2112
- const result = await pluginManager.createEmbedding({
2113
- id,
2114
- title: updatedEntity.title || currentEntry.title,
2074
+ const parentDocumentId = currentMetadata?.parentDocumentId || id;
2075
+ const newContent = content ?? currentEntry.content;
2076
+ const newTitle = title ?? currentMetadata?.originalTitle ?? currentEntry.title;
2077
+ const shouldChunk = autoChunk ?? config2.autoChunk;
2078
+ const chunkSize = config2.chunkSize || 4e3;
2079
+ const contentNeedsChunking = shouldChunk && needsChunking(newContent, chunkSize);
2080
+ const existingChunks = await this.findRelatedChunks(id);
2081
+ let originalRelated;
2082
+ const firstChunk = existingChunks.find(
2083
+ (c) => c.metadata?.chunkIndex === 0 || c.documentId === parentDocumentId
2084
+ );
2085
+ if (firstChunk?.related) {
2086
+ originalRelated = firstChunk.related;
2087
+ }
2088
+ const deletedCount = await this.deleteRelatedChunks(id);
2089
+ console.log(`Deleted ${deletedCount} existing chunk(s) for update`);
2090
+ const preservedMetadata = { ...metadata };
2091
+ delete preservedMetadata?.isChunk;
2092
+ delete preservedMetadata?.chunkIndex;
2093
+ delete preservedMetadata?.totalChunks;
2094
+ delete preservedMetadata?.startOffset;
2095
+ delete preservedMetadata?.endOffset;
2096
+ delete preservedMetadata?.originalTitle;
2097
+ delete preservedMetadata?.parentDocumentId;
2098
+ delete preservedMetadata?.estimatedTokens;
2099
+ if (contentNeedsChunking) {
2100
+ return await this.createChunkedEmbedding({
2101
+ data: {
2102
+ title: newTitle.replace(/\s*\[Part \d+\/\d+\]$/, ""),
2103
+ // Remove old part suffix
2115
2104
  content: newContent,
2116
2105
  collectionType: currentEntry.collectionType || "standalone",
2117
- fieldName: currentEntry.fieldName || "content"
2118
- });
2119
- await strapi.documents(CONTENT_TYPE_UID$1).update({
2120
- documentId: id,
2121
- data: {
2122
- embeddingId: result.embeddingId,
2123
- embedding: result.embedding
2124
- }
2125
- });
2126
- console.log(`[updateChunkedEmbedding] Successfully updated embedding for chunk ${id}`);
2127
- } catch (error) {
2128
- console.error(`Failed to update vector store for ${id}:`, error);
2129
- }
2106
+ fieldName: currentEntry.fieldName || "content",
2107
+ metadata: preservedMetadata,
2108
+ related: originalRelated,
2109
+ autoChunk: true
2110
+ }
2111
+ });
2112
+ } else {
2113
+ const entity = await this.createEmbedding({
2114
+ data: {
2115
+ title: newTitle.replace(/\s*\[Part \d+\/\d+\]$/, ""),
2116
+ // Remove old part suffix
2117
+ content: newContent,
2118
+ collectionType: currentEntry.collectionType || "standalone",
2119
+ fieldName: currentEntry.fieldName || "content",
2120
+ metadata: preservedMetadata,
2121
+ related: originalRelated,
2122
+ autoChunk: false
2123
+ }
2124
+ });
2125
+ return {
2126
+ entity,
2127
+ chunks: [entity],
2128
+ totalChunks: 1,
2129
+ wasChunked: false
2130
+ };
2130
2131
  }
2131
- const allChunks = await this.findRelatedChunks(id);
2132
- return {
2133
- entity: updatedEntity,
2134
- chunks: allChunks,
2135
- totalChunks: allChunks.length,
2136
- wasChunked: allChunks.length > 1
2137
- };
2138
2132
  },
2139
2133
  async updateEmbedding(id, data) {
2140
2134
  const { title, content: rawContent, metadata, autoChunk } = data.data;
@@ -2528,9 +2522,22 @@ const sync = ({ strapi }) => ({
2528
2522
  }
2529
2523
  }
2530
2524
  });
2525
+ const tools = [
2526
+ semanticSearchTool,
2527
+ ragQueryTool,
2528
+ listEmbeddingsTool,
2529
+ getEmbeddingTool,
2530
+ createEmbeddingTool
2531
+ ];
2532
+ const aiTools = ({ strapi }) => ({
2533
+ getTools() {
2534
+ return tools;
2535
+ }
2536
+ });
2531
2537
  const services = {
2532
2538
  embeddings,
2533
- sync
2539
+ sync,
2540
+ "ai-tools": aiTools
2534
2541
  };
2535
2542
  const index = {
2536
2543
  register,
@@ -2547,4 +2554,3 @@ const index = {
2547
2554
  export {
2548
2555
  index as default
2549
2556
  };
2550
- //# sourceMappingURL=index.mjs.map