strapi-content-embeddings 0.2.1 → 0.2.2
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/admin/index.js +109 -41
- package/dist/admin/index.mjs +110 -42
- package/dist/server/index.js +56 -59
- package/dist/server/index.mjs +56 -59
- package/dist/server/src/services/embeddings.d.ts +2 -2
- package/package.json +3 -3
package/dist/admin/index.js
CHANGED
|
@@ -411,7 +411,7 @@ function MarkdownEditor({ content, onChange, height = 300 }) {
|
|
|
411
411
|
)
|
|
412
412
|
] });
|
|
413
413
|
}
|
|
414
|
-
|
|
414
|
+
styled__default.default(designSystem.Typography)`
|
|
415
415
|
display: block;
|
|
416
416
|
margin-top: 1rem;
|
|
417
417
|
margin-bottom: 0.5rem;
|
|
@@ -428,6 +428,7 @@ function EmbeddingsModal() {
|
|
|
428
428
|
const [title, setTitle] = react.useState("");
|
|
429
429
|
const [content, setContent] = react.useState("");
|
|
430
430
|
const [fieldName, setFieldName] = react.useState("");
|
|
431
|
+
const [availableFields, setAvailableFields] = react.useState([]);
|
|
431
432
|
const [isLoading, setIsLoading] = react.useState(false);
|
|
432
433
|
const [isCheckingExisting, setIsCheckingExisting] = react.useState(true);
|
|
433
434
|
const [existingEmbedding, setExistingEmbedding] = react.useState(null);
|
|
@@ -460,37 +461,84 @@ function EmbeddingsModal() {
|
|
|
460
461
|
}
|
|
461
462
|
checkExistingEmbedding();
|
|
462
463
|
}, [id, slug, get]);
|
|
463
|
-
const
|
|
464
|
-
if (!
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
477
|
-
return "";
|
|
478
|
-
}).join("\n\n");
|
|
479
|
-
if (text.trim()) {
|
|
480
|
-
setFieldName(name);
|
|
481
|
-
return text;
|
|
464
|
+
const extractTextFromField = react.useCallback((value, depth = 0) => {
|
|
465
|
+
if (!value || depth > 5) return "";
|
|
466
|
+
if (typeof value === "string") {
|
|
467
|
+
return value.trim();
|
|
468
|
+
}
|
|
469
|
+
if (Array.isArray(value)) {
|
|
470
|
+
const texts = [];
|
|
471
|
+
for (const item of value) {
|
|
472
|
+
if (item && typeof item === "object" && item.__component) {
|
|
473
|
+
for (const [key, fieldValue] of Object.entries(item)) {
|
|
474
|
+
if (key === "__component" || key === "id") continue;
|
|
475
|
+
const extracted = extractTextFromField(fieldValue, depth + 1);
|
|
476
|
+
if (extracted) texts.push(extracted);
|
|
482
477
|
}
|
|
478
|
+
} else if (item && item.children) {
|
|
479
|
+
const blockText = item.children.map((child) => child.text || "").join("");
|
|
480
|
+
if (blockText) texts.push(blockText);
|
|
481
|
+
} else if (item && typeof item === "object") {
|
|
482
|
+
const extracted = extractTextFromField(item, depth + 1);
|
|
483
|
+
if (extracted) texts.push(extracted);
|
|
483
484
|
}
|
|
484
485
|
}
|
|
486
|
+
return texts.join("\n\n").trim();
|
|
487
|
+
}
|
|
488
|
+
if (typeof value === "object") {
|
|
489
|
+
const texts = [];
|
|
490
|
+
for (const [key, fieldValue] of Object.entries(value)) {
|
|
491
|
+
if (["id", "__component", "documentId", "createdAt", "updatedAt"].includes(key)) continue;
|
|
492
|
+
const extracted = extractTextFromField(fieldValue, depth + 1);
|
|
493
|
+
if (extracted) texts.push(extracted);
|
|
494
|
+
}
|
|
495
|
+
return texts.join("\n\n").trim();
|
|
485
496
|
}
|
|
486
497
|
return "";
|
|
487
|
-
}, [
|
|
498
|
+
}, []);
|
|
499
|
+
const isDynamicZone = (value) => {
|
|
500
|
+
return Array.isArray(value) && value.length > 0 && value[0]?.__component;
|
|
501
|
+
};
|
|
502
|
+
const detectTextFields = react.useCallback(() => {
|
|
503
|
+
if (!modifiedValues) return [];
|
|
504
|
+
const fields = [];
|
|
505
|
+
for (const [name, value] of Object.entries(modifiedValues)) {
|
|
506
|
+
if (["id", "documentId", "createdAt", "updatedAt", "publishedAt", "locale", "localizations"].includes(name)) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const textValue = extractTextFromField(value);
|
|
510
|
+
if (textValue && textValue.length > 0) {
|
|
511
|
+
let label = name.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
|
|
512
|
+
if (isDynamicZone(value)) {
|
|
513
|
+
const componentCount = value.length;
|
|
514
|
+
label += ` (${componentCount} component${componentCount > 1 ? "s" : ""})`;
|
|
515
|
+
}
|
|
516
|
+
fields.push({
|
|
517
|
+
name,
|
|
518
|
+
label,
|
|
519
|
+
value: textValue,
|
|
520
|
+
charCount: textValue.length
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
fields.sort((a, b) => b.charCount - a.charCount);
|
|
525
|
+
return fields;
|
|
526
|
+
}, [modifiedValues, extractTextFromField]);
|
|
488
527
|
react.useEffect(() => {
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
528
|
+
const fields = detectTextFields();
|
|
529
|
+
setAvailableFields(fields);
|
|
530
|
+
if (fields.length > 0 && !fieldName) {
|
|
531
|
+
setFieldName(fields[0].name);
|
|
532
|
+
setContent(fields[0].value);
|
|
492
533
|
}
|
|
493
|
-
}, [
|
|
534
|
+
}, [detectTextFields, fieldName]);
|
|
535
|
+
const handleFieldChange = (selectedFieldName) => {
|
|
536
|
+
setFieldName(selectedFieldName);
|
|
537
|
+
const selectedField = availableFields.find((f) => f.name === selectedFieldName);
|
|
538
|
+
if (selectedField) {
|
|
539
|
+
setContent(selectedField.value);
|
|
540
|
+
}
|
|
541
|
+
};
|
|
494
542
|
const contentLength = content.length;
|
|
495
543
|
const willChunk = contentLength > CHUNK_SIZE;
|
|
496
544
|
const estimatedChunks = willChunk ? Math.ceil(contentLength / (CHUNK_SIZE - 200)) : 1;
|
|
@@ -507,9 +555,11 @@ function EmbeddingsModal() {
|
|
|
507
555
|
const isValid = title.trim() && content.trim();
|
|
508
556
|
function handleOpenCreate() {
|
|
509
557
|
setTitle("");
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
558
|
+
const fields = detectTextFields();
|
|
559
|
+
setAvailableFields(fields);
|
|
560
|
+
if (fields.length > 0) {
|
|
561
|
+
setFieldName(fields[0].name);
|
|
562
|
+
setContent(fields[0].value);
|
|
513
563
|
}
|
|
514
564
|
setIsVisible(true);
|
|
515
565
|
}
|
|
@@ -595,16 +645,6 @@ function EmbeddingsModal() {
|
|
|
595
645
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Root, { open: isVisible, onOpenChange: setIsVisible, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
|
|
596
646
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: "Create Embedding from Content" }) }),
|
|
597
647
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
598
|
-
/* @__PURE__ */ jsxRuntime.jsxs(StyledTypography, { variant: "omega", textColor: "neutral600", children: [
|
|
599
|
-
"Content: ",
|
|
600
|
-
contentLength,
|
|
601
|
-
" characters",
|
|
602
|
-
willChunk && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { textColor: "primary600", children: [
|
|
603
|
-
" (will create ~",
|
|
604
|
-
estimatedChunks,
|
|
605
|
-
" embeddings)"
|
|
606
|
-
] })
|
|
607
|
-
] }),
|
|
608
648
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
|
|
609
649
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Title" }),
|
|
610
650
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -616,10 +656,38 @@ function EmbeddingsModal() {
|
|
|
616
656
|
}
|
|
617
657
|
)
|
|
618
658
|
] }) }),
|
|
619
|
-
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, children: [
|
|
659
|
+
availableFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, children: [
|
|
620
660
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
|
|
621
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "
|
|
622
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children:
|
|
661
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Source Field" }),
|
|
662
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: "Select which field to use for the embedding content" })
|
|
663
|
+
] }),
|
|
664
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
665
|
+
designSystem.SingleSelect,
|
|
666
|
+
{
|
|
667
|
+
value: fieldName,
|
|
668
|
+
onChange: (value) => handleFieldChange(value),
|
|
669
|
+
placeholder: "Select a field",
|
|
670
|
+
children: availableFields.map((field) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: field.name, children: [
|
|
671
|
+
field.label,
|
|
672
|
+
" (",
|
|
673
|
+
field.charCount.toLocaleString(),
|
|
674
|
+
" chars)"
|
|
675
|
+
] }, field.name))
|
|
676
|
+
}
|
|
677
|
+
)
|
|
678
|
+
] }),
|
|
679
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, children: [
|
|
680
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
681
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Root, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Content Preview" }) }),
|
|
682
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
|
|
683
|
+
contentLength.toLocaleString(),
|
|
684
|
+
" characters",
|
|
685
|
+
willChunk && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { textColor: "primary600", children: [
|
|
686
|
+
" (~",
|
|
687
|
+
estimatedChunks,
|
|
688
|
+
" chunks)"
|
|
689
|
+
] })
|
|
690
|
+
] })
|
|
623
691
|
] }),
|
|
624
692
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
625
693
|
MarkdownEditor,
|
package/dist/admin/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useEffect, useState, useCallback } from "react";
|
|
3
|
-
import { Box, Typography, Loader, Button, Modal, Field, TextInput } from "@strapi/design-system";
|
|
3
|
+
import { Box, Typography, Loader, Button, Modal, Field, TextInput, SingleSelect, SingleSelectOption, Flex } from "@strapi/design-system";
|
|
4
4
|
import { useNavigate } from "react-router-dom";
|
|
5
5
|
import styled, { createGlobalStyle } from "styled-components";
|
|
6
6
|
import qs from "qs";
|
|
@@ -407,7 +407,7 @@ function MarkdownEditor({ content, onChange, height = 300 }) {
|
|
|
407
407
|
)
|
|
408
408
|
] });
|
|
409
409
|
}
|
|
410
|
-
|
|
410
|
+
styled(Typography)`
|
|
411
411
|
display: block;
|
|
412
412
|
margin-top: 1rem;
|
|
413
413
|
margin-bottom: 0.5rem;
|
|
@@ -424,6 +424,7 @@ function EmbeddingsModal() {
|
|
|
424
424
|
const [title, setTitle] = useState("");
|
|
425
425
|
const [content, setContent] = useState("");
|
|
426
426
|
const [fieldName, setFieldName] = useState("");
|
|
427
|
+
const [availableFields, setAvailableFields] = useState([]);
|
|
427
428
|
const [isLoading, setIsLoading] = useState(false);
|
|
428
429
|
const [isCheckingExisting, setIsCheckingExisting] = useState(true);
|
|
429
430
|
const [existingEmbedding, setExistingEmbedding] = useState(null);
|
|
@@ -456,37 +457,84 @@ function EmbeddingsModal() {
|
|
|
456
457
|
}
|
|
457
458
|
checkExistingEmbedding();
|
|
458
459
|
}, [id, slug, get]);
|
|
459
|
-
const
|
|
460
|
-
if (!
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
return "";
|
|
474
|
-
}).join("\n\n");
|
|
475
|
-
if (text.trim()) {
|
|
476
|
-
setFieldName(name);
|
|
477
|
-
return text;
|
|
460
|
+
const extractTextFromField = useCallback((value, depth = 0) => {
|
|
461
|
+
if (!value || depth > 5) return "";
|
|
462
|
+
if (typeof value === "string") {
|
|
463
|
+
return value.trim();
|
|
464
|
+
}
|
|
465
|
+
if (Array.isArray(value)) {
|
|
466
|
+
const texts = [];
|
|
467
|
+
for (const item of value) {
|
|
468
|
+
if (item && typeof item === "object" && item.__component) {
|
|
469
|
+
for (const [key, fieldValue] of Object.entries(item)) {
|
|
470
|
+
if (key === "__component" || key === "id") continue;
|
|
471
|
+
const extracted = extractTextFromField(fieldValue, depth + 1);
|
|
472
|
+
if (extracted) texts.push(extracted);
|
|
478
473
|
}
|
|
474
|
+
} else if (item && item.children) {
|
|
475
|
+
const blockText = item.children.map((child) => child.text || "").join("");
|
|
476
|
+
if (blockText) texts.push(blockText);
|
|
477
|
+
} else if (item && typeof item === "object") {
|
|
478
|
+
const extracted = extractTextFromField(item, depth + 1);
|
|
479
|
+
if (extracted) texts.push(extracted);
|
|
479
480
|
}
|
|
480
481
|
}
|
|
482
|
+
return texts.join("\n\n").trim();
|
|
483
|
+
}
|
|
484
|
+
if (typeof value === "object") {
|
|
485
|
+
const texts = [];
|
|
486
|
+
for (const [key, fieldValue] of Object.entries(value)) {
|
|
487
|
+
if (["id", "__component", "documentId", "createdAt", "updatedAt"].includes(key)) continue;
|
|
488
|
+
const extracted = extractTextFromField(fieldValue, depth + 1);
|
|
489
|
+
if (extracted) texts.push(extracted);
|
|
490
|
+
}
|
|
491
|
+
return texts.join("\n\n").trim();
|
|
481
492
|
}
|
|
482
493
|
return "";
|
|
483
|
-
}, [
|
|
494
|
+
}, []);
|
|
495
|
+
const isDynamicZone = (value) => {
|
|
496
|
+
return Array.isArray(value) && value.length > 0 && value[0]?.__component;
|
|
497
|
+
};
|
|
498
|
+
const detectTextFields = useCallback(() => {
|
|
499
|
+
if (!modifiedValues) return [];
|
|
500
|
+
const fields = [];
|
|
501
|
+
for (const [name, value] of Object.entries(modifiedValues)) {
|
|
502
|
+
if (["id", "documentId", "createdAt", "updatedAt", "publishedAt", "locale", "localizations"].includes(name)) {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const textValue = extractTextFromField(value);
|
|
506
|
+
if (textValue && textValue.length > 0) {
|
|
507
|
+
let label = name.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
|
|
508
|
+
if (isDynamicZone(value)) {
|
|
509
|
+
const componentCount = value.length;
|
|
510
|
+
label += ` (${componentCount} component${componentCount > 1 ? "s" : ""})`;
|
|
511
|
+
}
|
|
512
|
+
fields.push({
|
|
513
|
+
name,
|
|
514
|
+
label,
|
|
515
|
+
value: textValue,
|
|
516
|
+
charCount: textValue.length
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
fields.sort((a, b) => b.charCount - a.charCount);
|
|
521
|
+
return fields;
|
|
522
|
+
}, [modifiedValues, extractTextFromField]);
|
|
484
523
|
useEffect(() => {
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
524
|
+
const fields = detectTextFields();
|
|
525
|
+
setAvailableFields(fields);
|
|
526
|
+
if (fields.length > 0 && !fieldName) {
|
|
527
|
+
setFieldName(fields[0].name);
|
|
528
|
+
setContent(fields[0].value);
|
|
488
529
|
}
|
|
489
|
-
}, [
|
|
530
|
+
}, [detectTextFields, fieldName]);
|
|
531
|
+
const handleFieldChange = (selectedFieldName) => {
|
|
532
|
+
setFieldName(selectedFieldName);
|
|
533
|
+
const selectedField = availableFields.find((f) => f.name === selectedFieldName);
|
|
534
|
+
if (selectedField) {
|
|
535
|
+
setContent(selectedField.value);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
490
538
|
const contentLength = content.length;
|
|
491
539
|
const willChunk = contentLength > CHUNK_SIZE;
|
|
492
540
|
const estimatedChunks = willChunk ? Math.ceil(contentLength / (CHUNK_SIZE - 200)) : 1;
|
|
@@ -503,9 +551,11 @@ function EmbeddingsModal() {
|
|
|
503
551
|
const isValid = title.trim() && content.trim();
|
|
504
552
|
function handleOpenCreate() {
|
|
505
553
|
setTitle("");
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
554
|
+
const fields = detectTextFields();
|
|
555
|
+
setAvailableFields(fields);
|
|
556
|
+
if (fields.length > 0) {
|
|
557
|
+
setFieldName(fields[0].name);
|
|
558
|
+
setContent(fields[0].value);
|
|
509
559
|
}
|
|
510
560
|
setIsVisible(true);
|
|
511
561
|
}
|
|
@@ -591,16 +641,6 @@ function EmbeddingsModal() {
|
|
|
591
641
|
/* @__PURE__ */ jsx(Modal.Root, { open: isVisible, onOpenChange: setIsVisible, children: /* @__PURE__ */ jsxs(Modal.Content, { children: [
|
|
592
642
|
/* @__PURE__ */ jsx(Modal.Header, { children: /* @__PURE__ */ jsx(Modal.Title, { children: "Create Embedding from Content" }) }),
|
|
593
643
|
/* @__PURE__ */ jsx(Modal.Body, { children: /* @__PURE__ */ jsxs(Box, { children: [
|
|
594
|
-
/* @__PURE__ */ jsxs(StyledTypography, { variant: "omega", textColor: "neutral600", children: [
|
|
595
|
-
"Content: ",
|
|
596
|
-
contentLength,
|
|
597
|
-
" characters",
|
|
598
|
-
willChunk && /* @__PURE__ */ jsxs(Typography, { textColor: "primary600", children: [
|
|
599
|
-
" (will create ~",
|
|
600
|
-
estimatedChunks,
|
|
601
|
-
" embeddings)"
|
|
602
|
-
] })
|
|
603
|
-
] }),
|
|
604
644
|
/* @__PURE__ */ jsx(Box, { marginBottom: 4, children: /* @__PURE__ */ jsxs(Field.Root, { children: [
|
|
605
645
|
/* @__PURE__ */ jsx(Field.Label, { children: "Title" }),
|
|
606
646
|
/* @__PURE__ */ jsx(
|
|
@@ -612,10 +652,38 @@ function EmbeddingsModal() {
|
|
|
612
652
|
}
|
|
613
653
|
)
|
|
614
654
|
] }) }),
|
|
615
|
-
/* @__PURE__ */ jsxs(Box, { marginBottom: 4, children: [
|
|
655
|
+
availableFields.length > 0 && /* @__PURE__ */ jsxs(Box, { marginBottom: 4, children: [
|
|
616
656
|
/* @__PURE__ */ jsxs(Field.Root, { children: [
|
|
617
|
-
/* @__PURE__ */ jsx(Field.Label, { children: "
|
|
618
|
-
/* @__PURE__ */ jsx(Field.Hint, { children:
|
|
657
|
+
/* @__PURE__ */ jsx(Field.Label, { children: "Source Field" }),
|
|
658
|
+
/* @__PURE__ */ jsx(Field.Hint, { children: "Select which field to use for the embedding content" })
|
|
659
|
+
] }),
|
|
660
|
+
/* @__PURE__ */ jsx(
|
|
661
|
+
SingleSelect,
|
|
662
|
+
{
|
|
663
|
+
value: fieldName,
|
|
664
|
+
onChange: (value) => handleFieldChange(value),
|
|
665
|
+
placeholder: "Select a field",
|
|
666
|
+
children: availableFields.map((field) => /* @__PURE__ */ jsxs(SingleSelectOption, { value: field.name, children: [
|
|
667
|
+
field.label,
|
|
668
|
+
" (",
|
|
669
|
+
field.charCount.toLocaleString(),
|
|
670
|
+
" chars)"
|
|
671
|
+
] }, field.name))
|
|
672
|
+
}
|
|
673
|
+
)
|
|
674
|
+
] }),
|
|
675
|
+
/* @__PURE__ */ jsxs(Box, { marginBottom: 4, children: [
|
|
676
|
+
/* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
677
|
+
/* @__PURE__ */ jsx(Field.Root, { children: /* @__PURE__ */ jsx(Field.Label, { children: "Content Preview" }) }),
|
|
678
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
|
|
679
|
+
contentLength.toLocaleString(),
|
|
680
|
+
" characters",
|
|
681
|
+
willChunk && /* @__PURE__ */ jsxs(Typography, { textColor: "primary600", children: [
|
|
682
|
+
" (~",
|
|
683
|
+
estimatedChunks,
|
|
684
|
+
" chunks)"
|
|
685
|
+
] })
|
|
686
|
+
] })
|
|
619
687
|
] }),
|
|
620
688
|
/* @__PURE__ */ jsx(
|
|
621
689
|
MarkdownEditor,
|
package/dist/server/index.js
CHANGED
|
@@ -2061,12 +2061,12 @@ const embeddings = ({ strapi }) => ({
|
|
|
2061
2061
|
return chunks.length;
|
|
2062
2062
|
},
|
|
2063
2063
|
/**
|
|
2064
|
-
* Update
|
|
2065
|
-
*
|
|
2064
|
+
* Update a single chunk's content and embedding
|
|
2065
|
+
* Updates only the specified chunk without affecting other chunks in the group
|
|
2066
2066
|
*/
|
|
2067
2067
|
async updateChunkedEmbedding(id, data) {
|
|
2068
|
-
const { title, content, metadata
|
|
2069
|
-
|
|
2068
|
+
const { title, content, metadata } = data.data;
|
|
2069
|
+
this.getConfig();
|
|
2070
2070
|
const currentEntry = await strapi.documents(CONTENT_TYPE_UID$1).findOne({
|
|
2071
2071
|
documentId: id
|
|
2072
2072
|
});
|
|
@@ -2074,64 +2074,61 @@ const embeddings = ({ strapi }) => ({
|
|
|
2074
2074
|
throw new Error(`Embedding with id ${id} not found`);
|
|
2075
2075
|
}
|
|
2076
2076
|
const currentMetadata = currentEntry.metadata;
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2079
|
-
const
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
if (
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
fieldName: currentEntry.fieldName || "content",
|
|
2110
|
-
metadata: preservedMetadata,
|
|
2111
|
-
related: originalRelated,
|
|
2112
|
-
autoChunk: true
|
|
2113
|
-
}
|
|
2114
|
-
});
|
|
2115
|
-
} else {
|
|
2116
|
-
const entity = await this.createEmbedding({
|
|
2117
|
-
data: {
|
|
2118
|
-
title: newTitle.replace(/\s*\[Part \d+\/\d+\]$/, ""),
|
|
2119
|
-
// Remove old part suffix
|
|
2077
|
+
const contentChanged = content !== void 0 && content !== currentEntry.content;
|
|
2078
|
+
console.log(`[updateChunkedEmbedding] Updating single chunk ${id}, contentChanged: ${contentChanged}`);
|
|
2079
|
+
const updateData = {};
|
|
2080
|
+
if (title !== void 0) {
|
|
2081
|
+
const currentTitle = currentEntry.title || "";
|
|
2082
|
+
const partMatch = currentTitle.match(/\s*\[Part \d+\/\d+\]$/);
|
|
2083
|
+
updateData.title = partMatch ? `${title}${partMatch[0]}` : title;
|
|
2084
|
+
}
|
|
2085
|
+
if (content !== void 0) {
|
|
2086
|
+
updateData.content = content;
|
|
2087
|
+
}
|
|
2088
|
+
if (metadata !== void 0) {
|
|
2089
|
+
updateData.metadata = {
|
|
2090
|
+
...currentMetadata,
|
|
2091
|
+
...metadata
|
|
2092
|
+
};
|
|
2093
|
+
if (contentChanged) {
|
|
2094
|
+
updateData.metadata.estimatedTokens = estimateTokens(updateData.content || currentEntry.content);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
const updatedEntity = await strapi.documents(CONTENT_TYPE_UID$1).update({
|
|
2098
|
+
documentId: id,
|
|
2099
|
+
data: updateData
|
|
2100
|
+
});
|
|
2101
|
+
if (pluginManager.isInitialized() && (contentChanged || title !== void 0)) {
|
|
2102
|
+
try {
|
|
2103
|
+
console.log(`[updateChunkedEmbedding] Updating embedding in Neon for chunk ${id}`);
|
|
2104
|
+
await pluginManager.deleteEmbedding(id);
|
|
2105
|
+
const newContent = updateData.content || currentEntry.content;
|
|
2106
|
+
const result = await pluginManager.createEmbedding({
|
|
2107
|
+
id,
|
|
2108
|
+
title: updatedEntity.title || currentEntry.title,
|
|
2120
2109
|
content: newContent,
|
|
2121
2110
|
collectionType: currentEntry.collectionType || "standalone",
|
|
2122
|
-
fieldName: currentEntry.fieldName || "content"
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2111
|
+
fieldName: currentEntry.fieldName || "content"
|
|
2112
|
+
});
|
|
2113
|
+
await strapi.documents(CONTENT_TYPE_UID$1).update({
|
|
2114
|
+
documentId: id,
|
|
2115
|
+
data: {
|
|
2116
|
+
embeddingId: result.embeddingId,
|
|
2117
|
+
embedding: result.embedding
|
|
2118
|
+
}
|
|
2119
|
+
});
|
|
2120
|
+
console.log(`[updateChunkedEmbedding] Successfully updated embedding for chunk ${id}`);
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
console.error(`Failed to update vector store for ${id}:`, error);
|
|
2123
|
+
}
|
|
2134
2124
|
}
|
|
2125
|
+
const allChunks = await this.findRelatedChunks(id);
|
|
2126
|
+
return {
|
|
2127
|
+
entity: updatedEntity,
|
|
2128
|
+
chunks: allChunks,
|
|
2129
|
+
totalChunks: allChunks.length,
|
|
2130
|
+
wasChunked: allChunks.length > 1
|
|
2131
|
+
};
|
|
2135
2132
|
},
|
|
2136
2133
|
async updateEmbedding(id, data) {
|
|
2137
2134
|
const { title, content: rawContent, metadata, autoChunk } = data.data;
|
package/dist/server/index.mjs
CHANGED
|
@@ -2058,12 +2058,12 @@ const embeddings = ({ strapi }) => ({
|
|
|
2058
2058
|
return chunks.length;
|
|
2059
2059
|
},
|
|
2060
2060
|
/**
|
|
2061
|
-
* Update
|
|
2062
|
-
*
|
|
2061
|
+
* Update a single chunk's content and embedding
|
|
2062
|
+
* Updates only the specified chunk without affecting other chunks in the group
|
|
2063
2063
|
*/
|
|
2064
2064
|
async updateChunkedEmbedding(id, data) {
|
|
2065
|
-
const { title, content, metadata
|
|
2066
|
-
|
|
2065
|
+
const { title, content, metadata } = data.data;
|
|
2066
|
+
this.getConfig();
|
|
2067
2067
|
const currentEntry = await strapi.documents(CONTENT_TYPE_UID$1).findOne({
|
|
2068
2068
|
documentId: id
|
|
2069
2069
|
});
|
|
@@ -2071,64 +2071,61 @@ const embeddings = ({ strapi }) => ({
|
|
|
2071
2071
|
throw new Error(`Embedding with id ${id} not found`);
|
|
2072
2072
|
}
|
|
2073
2073
|
const currentMetadata = currentEntry.metadata;
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2076
|
-
const
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
if (
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
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
|
|
2074
|
+
const contentChanged = content !== void 0 && content !== currentEntry.content;
|
|
2075
|
+
console.log(`[updateChunkedEmbedding] Updating single chunk ${id}, contentChanged: ${contentChanged}`);
|
|
2076
|
+
const updateData = {};
|
|
2077
|
+
if (title !== void 0) {
|
|
2078
|
+
const currentTitle = currentEntry.title || "";
|
|
2079
|
+
const partMatch = currentTitle.match(/\s*\[Part \d+\/\d+\]$/);
|
|
2080
|
+
updateData.title = partMatch ? `${title}${partMatch[0]}` : title;
|
|
2081
|
+
}
|
|
2082
|
+
if (content !== void 0) {
|
|
2083
|
+
updateData.content = content;
|
|
2084
|
+
}
|
|
2085
|
+
if (metadata !== void 0) {
|
|
2086
|
+
updateData.metadata = {
|
|
2087
|
+
...currentMetadata,
|
|
2088
|
+
...metadata
|
|
2089
|
+
};
|
|
2090
|
+
if (contentChanged) {
|
|
2091
|
+
updateData.metadata.estimatedTokens = estimateTokens(updateData.content || currentEntry.content);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
const updatedEntity = await strapi.documents(CONTENT_TYPE_UID$1).update({
|
|
2095
|
+
documentId: id,
|
|
2096
|
+
data: updateData
|
|
2097
|
+
});
|
|
2098
|
+
if (pluginManager.isInitialized() && (contentChanged || title !== void 0)) {
|
|
2099
|
+
try {
|
|
2100
|
+
console.log(`[updateChunkedEmbedding] Updating embedding in Neon for chunk ${id}`);
|
|
2101
|
+
await pluginManager.deleteEmbedding(id);
|
|
2102
|
+
const newContent = updateData.content || currentEntry.content;
|
|
2103
|
+
const result = await pluginManager.createEmbedding({
|
|
2104
|
+
id,
|
|
2105
|
+
title: updatedEntity.title || currentEntry.title,
|
|
2117
2106
|
content: newContent,
|
|
2118
2107
|
collectionType: currentEntry.collectionType || "standalone",
|
|
2119
|
-
fieldName: currentEntry.fieldName || "content"
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2108
|
+
fieldName: currentEntry.fieldName || "content"
|
|
2109
|
+
});
|
|
2110
|
+
await strapi.documents(CONTENT_TYPE_UID$1).update({
|
|
2111
|
+
documentId: id,
|
|
2112
|
+
data: {
|
|
2113
|
+
embeddingId: result.embeddingId,
|
|
2114
|
+
embedding: result.embedding
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
console.log(`[updateChunkedEmbedding] Successfully updated embedding for chunk ${id}`);
|
|
2118
|
+
} catch (error) {
|
|
2119
|
+
console.error(`Failed to update vector store for ${id}:`, error);
|
|
2120
|
+
}
|
|
2131
2121
|
}
|
|
2122
|
+
const allChunks = await this.findRelatedChunks(id);
|
|
2123
|
+
return {
|
|
2124
|
+
entity: updatedEntity,
|
|
2125
|
+
chunks: allChunks,
|
|
2126
|
+
totalChunks: allChunks.length,
|
|
2127
|
+
wasChunked: allChunks.length > 1
|
|
2128
|
+
};
|
|
2132
2129
|
},
|
|
2133
2130
|
async updateEmbedding(id, data) {
|
|
2134
2131
|
const { title, content: rawContent, metadata, autoChunk } = data.data;
|
|
@@ -64,8 +64,8 @@ declare const embeddings: ({ strapi }: {
|
|
|
64
64
|
*/
|
|
65
65
|
deleteRelatedChunks(documentId: string): Promise<number>;
|
|
66
66
|
/**
|
|
67
|
-
* Update
|
|
68
|
-
*
|
|
67
|
+
* Update a single chunk's content and embedding
|
|
68
|
+
* Updates only the specified chunk without affecting other chunks in the group
|
|
69
69
|
*/
|
|
70
70
|
updateChunkedEmbedding(id: string, data: UpdateEmbeddingData): Promise<ChunkedEmbeddingResult>;
|
|
71
71
|
updateEmbedding(id: string, data: UpdateEmbeddingData): Promise<any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-content-embeddings",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"type": "commonjs",
|
|
28
28
|
"overrides": {
|
|
29
|
-
"@langchain/core": "1.1.
|
|
29
|
+
"@langchain/core": "^1.1.31"
|
|
30
30
|
},
|
|
31
31
|
"exports": {
|
|
32
32
|
"./package.json": "./package.json",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@langchain/community": "^1.1.2",
|
|
62
|
-
"@langchain/core": "1.1.
|
|
62
|
+
"@langchain/core": "^1.1.31",
|
|
63
63
|
"@langchain/openai": "^1.2.1",
|
|
64
64
|
"@mdxeditor/editor": "^3.52.3",
|
|
65
65
|
"@modelcontextprotocol/sdk": "^1.12.0",
|