strapi-plugin-ai-sdk 0.6.7 → 0.6.8
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-31DoeBYs.js → App-C_BH5Ir4.js} +49 -4
- package/dist/_chunks/{App-k5doNU8o.mjs → App-DKyCb0BY.mjs} +49 -4
- package/dist/_chunks/{index-C6UZ9D-c.mjs → index-BV9DET_M.mjs} +1 -1
- package/dist/_chunks/{index-tCUlQhF4.js → index-DCEjJ0as.js} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +57 -18
- package/dist/server/index.mjs +57 -18
- package/dist/server/src/tool-logic/index.d.ts +1 -0
- package/dist/server/src/tool-logic/schema-utils.d.ts +9 -11
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ const reactRouterDom = require("react-router-dom");
|
|
|
6
6
|
const designSystem = require("@strapi/design-system");
|
|
7
7
|
const react = require("react");
|
|
8
8
|
const styled = require("styled-components");
|
|
9
|
-
const index = require("./index-
|
|
9
|
+
const index = require("./index-DCEjJ0as.js");
|
|
10
10
|
const icons = require("@strapi/icons");
|
|
11
11
|
const Markdown = require("react-markdown");
|
|
12
12
|
const remarkGfm = require("remark-gfm");
|
|
@@ -688,6 +688,21 @@ const ToolCallHeader = styled__default.default.button`
|
|
|
688
688
|
background: #dcdce4;
|
|
689
689
|
}
|
|
690
690
|
`;
|
|
691
|
+
const Spinner = styled__default.default.span`
|
|
692
|
+
display: inline-block;
|
|
693
|
+
width: 12px;
|
|
694
|
+
height: 12px;
|
|
695
|
+
border: 2px solid #a5a5ba;
|
|
696
|
+
border-top-color: #4945ff;
|
|
697
|
+
border-radius: 50%;
|
|
698
|
+
animation: spin 0.8s linear infinite;
|
|
699
|
+
margin-left: auto;
|
|
700
|
+
flex-shrink: 0;
|
|
701
|
+
|
|
702
|
+
@keyframes spin {
|
|
703
|
+
to { transform: rotate(360deg); }
|
|
704
|
+
}
|
|
705
|
+
`;
|
|
691
706
|
const ToolCallContent = styled__default.default.pre`
|
|
692
707
|
margin: 0;
|
|
693
708
|
padding: 8px 12px;
|
|
@@ -739,7 +754,7 @@ function ToolCallDisplay({ toolCall }) {
|
|
|
739
754
|
"Tool: ",
|
|
740
755
|
toolCall.toolName
|
|
741
756
|
] }),
|
|
742
|
-
toolCall.output
|
|
757
|
+
toolCall.output === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: "auto", fontWeight: 400, opacity: 0.6 }, children: "completed" })
|
|
743
758
|
] }),
|
|
744
759
|
contentLinks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ContentLinksRow, { children: contentLinks.map((link) => /* @__PURE__ */ jsxRuntime.jsx(ContentLinkChip, { to: link.to, children: link.label }, link.to)) }),
|
|
745
760
|
expanded && /* @__PURE__ */ jsxRuntime.jsx(ToolCallContent, { children: toolCall.output === void 0 ? "Waiting for result..." : JSON.stringify(toolCall.output, null, 2) })
|
|
@@ -846,6 +861,29 @@ const TypingDots = styled__default.default.span`
|
|
|
846
861
|
40% { transform: scale(1); opacity: 1; }
|
|
847
862
|
}
|
|
848
863
|
`;
|
|
864
|
+
const ThinkingIndicator = styled__default.default.div`
|
|
865
|
+
display: flex;
|
|
866
|
+
align-items: center;
|
|
867
|
+
gap: 6px;
|
|
868
|
+
margin-top: 8px;
|
|
869
|
+
padding: 6px 0;
|
|
870
|
+
font-size: 12px;
|
|
871
|
+
color: #666687;
|
|
872
|
+
`;
|
|
873
|
+
const ThinkingSpinner = styled__default.default.span`
|
|
874
|
+
display: inline-block;
|
|
875
|
+
width: 14px;
|
|
876
|
+
height: 14px;
|
|
877
|
+
border: 2px solid #dcdce4;
|
|
878
|
+
border-top-color: #4945ff;
|
|
879
|
+
border-radius: 50%;
|
|
880
|
+
animation: spin 0.8s linear infinite;
|
|
881
|
+
flex-shrink: 0;
|
|
882
|
+
|
|
883
|
+
@keyframes spin {
|
|
884
|
+
to { transform: rotate(360deg); }
|
|
885
|
+
}
|
|
886
|
+
`;
|
|
849
887
|
const EmptyState = styled__default.default.div`
|
|
850
888
|
display: flex;
|
|
851
889
|
flex-direction: column;
|
|
@@ -884,8 +922,11 @@ const MessageList = react.forwardRef(
|
|
|
884
922
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", textColor: "neutral400", children: "AI Chat" }),
|
|
885
923
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral500", children: "Send a message to start the conversation" }) })
|
|
886
924
|
] }),
|
|
887
|
-
messages.map((message) => {
|
|
925
|
+
messages.map((message, index2) => {
|
|
888
926
|
const displayContent = message.role === "assistant" && message.content ? autoLinkContentTypeUids(message.content) : message.content;
|
|
927
|
+
const isLastMessage = index2 === messages.length - 1;
|
|
928
|
+
const hasToolsRunning = message.toolCalls?.some((tc) => tc.output === void 0) ?? false;
|
|
929
|
+
const showThinking = isLoading && isLastMessage && message.role === "assistant" && displayContent && hasToolsRunning;
|
|
889
930
|
return /* @__PURE__ */ jsxRuntime.jsxs(MessageRow, { $isUser: message.role === "user", children: [
|
|
890
931
|
message.role === "assistant" && /* @__PURE__ */ jsxRuntime.jsx(SparkleIcon, { children: /* @__PURE__ */ jsxRuntime.jsx(icons.Sparkle, {}) }),
|
|
891
932
|
/* @__PURE__ */ jsxRuntime.jsxs(MessageBubble, { $isUser: message.role === "user", children: [
|
|
@@ -897,7 +938,11 @@ const MessageList = react.forwardRef(
|
|
|
897
938
|
/* @__PURE__ */ jsxRuntime.jsx("span", {}),
|
|
898
939
|
/* @__PURE__ */ jsxRuntime.jsx("span", {})
|
|
899
940
|
] }),
|
|
900
|
-
message.toolCalls?.filter((tc) => !HIDDEN_TOOLS.has(tc.toolName)).map((tc) => /* @__PURE__ */ jsxRuntime.jsx(ToolCallDisplay, { toolCall: tc }, tc.toolCallId))
|
|
941
|
+
message.toolCalls?.filter((tc) => !HIDDEN_TOOLS.has(tc.toolName)).map((tc) => /* @__PURE__ */ jsxRuntime.jsx(ToolCallDisplay, { toolCall: tc }, tc.toolCallId)),
|
|
942
|
+
showThinking && /* @__PURE__ */ jsxRuntime.jsxs(ThinkingIndicator, { children: [
|
|
943
|
+
/* @__PURE__ */ jsxRuntime.jsx(ThinkingSpinner, {}),
|
|
944
|
+
"Working on it…"
|
|
945
|
+
] })
|
|
901
946
|
] })
|
|
902
947
|
] }, message.id);
|
|
903
948
|
}),
|
|
@@ -4,7 +4,7 @@ import { Link, useNavigate, Routes, Route } from "react-router-dom";
|
|
|
4
4
|
import { Box, Typography, TextInput, Button, Main, SearchForm, Searchbar, Table, Thead, Tr, Th, Tbody, Td, Flex, Pagination, Modal, Field, Textarea, SingleSelect, SingleSelectOption } from "@strapi/design-system";
|
|
5
5
|
import { useState, useEffect, useCallback, forwardRef, useRef, useMemo } from "react";
|
|
6
6
|
import styled from "styled-components";
|
|
7
|
-
import { P as PLUGIN_ID } from "./index-
|
|
7
|
+
import { P as PLUGIN_ID } from "./index-BV9DET_M.mjs";
|
|
8
8
|
import { Plus, Trash, Sparkle, ArrowLeft, Pencil } from "@strapi/icons";
|
|
9
9
|
import Markdown from "react-markdown";
|
|
10
10
|
import remarkGfm from "remark-gfm";
|
|
@@ -682,6 +682,21 @@ const ToolCallHeader = styled.button`
|
|
|
682
682
|
background: #dcdce4;
|
|
683
683
|
}
|
|
684
684
|
`;
|
|
685
|
+
const Spinner = styled.span`
|
|
686
|
+
display: inline-block;
|
|
687
|
+
width: 12px;
|
|
688
|
+
height: 12px;
|
|
689
|
+
border: 2px solid #a5a5ba;
|
|
690
|
+
border-top-color: #4945ff;
|
|
691
|
+
border-radius: 50%;
|
|
692
|
+
animation: spin 0.8s linear infinite;
|
|
693
|
+
margin-left: auto;
|
|
694
|
+
flex-shrink: 0;
|
|
695
|
+
|
|
696
|
+
@keyframes spin {
|
|
697
|
+
to { transform: rotate(360deg); }
|
|
698
|
+
}
|
|
699
|
+
`;
|
|
685
700
|
const ToolCallContent = styled.pre`
|
|
686
701
|
margin: 0;
|
|
687
702
|
padding: 8px 12px;
|
|
@@ -733,7 +748,7 @@ function ToolCallDisplay({ toolCall }) {
|
|
|
733
748
|
"Tool: ",
|
|
734
749
|
toolCall.toolName
|
|
735
750
|
] }),
|
|
736
|
-
toolCall.output
|
|
751
|
+
toolCall.output === void 0 ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto", fontWeight: 400, opacity: 0.6 }, children: "completed" })
|
|
737
752
|
] }),
|
|
738
753
|
contentLinks.length > 0 && /* @__PURE__ */ jsx(ContentLinksRow, { children: contentLinks.map((link) => /* @__PURE__ */ jsx(ContentLinkChip, { to: link.to, children: link.label }, link.to)) }),
|
|
739
754
|
expanded && /* @__PURE__ */ jsx(ToolCallContent, { children: toolCall.output === void 0 ? "Waiting for result..." : JSON.stringify(toolCall.output, null, 2) })
|
|
@@ -840,6 +855,29 @@ const TypingDots = styled.span`
|
|
|
840
855
|
40% { transform: scale(1); opacity: 1; }
|
|
841
856
|
}
|
|
842
857
|
`;
|
|
858
|
+
const ThinkingIndicator = styled.div`
|
|
859
|
+
display: flex;
|
|
860
|
+
align-items: center;
|
|
861
|
+
gap: 6px;
|
|
862
|
+
margin-top: 8px;
|
|
863
|
+
padding: 6px 0;
|
|
864
|
+
font-size: 12px;
|
|
865
|
+
color: #666687;
|
|
866
|
+
`;
|
|
867
|
+
const ThinkingSpinner = styled.span`
|
|
868
|
+
display: inline-block;
|
|
869
|
+
width: 14px;
|
|
870
|
+
height: 14px;
|
|
871
|
+
border: 2px solid #dcdce4;
|
|
872
|
+
border-top-color: #4945ff;
|
|
873
|
+
border-radius: 50%;
|
|
874
|
+
animation: spin 0.8s linear infinite;
|
|
875
|
+
flex-shrink: 0;
|
|
876
|
+
|
|
877
|
+
@keyframes spin {
|
|
878
|
+
to { transform: rotate(360deg); }
|
|
879
|
+
}
|
|
880
|
+
`;
|
|
843
881
|
const EmptyState = styled.div`
|
|
844
882
|
display: flex;
|
|
845
883
|
flex-direction: column;
|
|
@@ -878,8 +916,11 @@ const MessageList = forwardRef(
|
|
|
878
916
|
/* @__PURE__ */ jsx(Typography, { variant: "beta", textColor: "neutral400", children: "AI Chat" }),
|
|
879
917
|
/* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral500", children: "Send a message to start the conversation" }) })
|
|
880
918
|
] }),
|
|
881
|
-
messages.map((message) => {
|
|
919
|
+
messages.map((message, index) => {
|
|
882
920
|
const displayContent = message.role === "assistant" && message.content ? autoLinkContentTypeUids(message.content) : message.content;
|
|
921
|
+
const isLastMessage = index === messages.length - 1;
|
|
922
|
+
const hasToolsRunning = message.toolCalls?.some((tc) => tc.output === void 0) ?? false;
|
|
923
|
+
const showThinking = isLoading && isLastMessage && message.role === "assistant" && displayContent && hasToolsRunning;
|
|
883
924
|
return /* @__PURE__ */ jsxs(MessageRow, { $isUser: message.role === "user", children: [
|
|
884
925
|
message.role === "assistant" && /* @__PURE__ */ jsx(SparkleIcon, { children: /* @__PURE__ */ jsx(Sparkle, {}) }),
|
|
885
926
|
/* @__PURE__ */ jsxs(MessageBubble, { $isUser: message.role === "user", children: [
|
|
@@ -891,7 +932,11 @@ const MessageList = forwardRef(
|
|
|
891
932
|
/* @__PURE__ */ jsx("span", {}),
|
|
892
933
|
/* @__PURE__ */ jsx("span", {})
|
|
893
934
|
] }),
|
|
894
|
-
message.toolCalls?.filter((tc) => !HIDDEN_TOOLS.has(tc.toolName)).map((tc) => /* @__PURE__ */ jsx(ToolCallDisplay, { toolCall: tc }, tc.toolCallId))
|
|
935
|
+
message.toolCalls?.filter((tc) => !HIDDEN_TOOLS.has(tc.toolName)).map((tc) => /* @__PURE__ */ jsx(ToolCallDisplay, { toolCall: tc }, tc.toolCallId)),
|
|
936
|
+
showThinking && /* @__PURE__ */ jsxs(ThinkingIndicator, { children: [
|
|
937
|
+
/* @__PURE__ */ jsx(ThinkingSpinner, {}),
|
|
938
|
+
"Working on it…"
|
|
939
|
+
] })
|
|
895
940
|
] })
|
|
896
941
|
] }, message.id);
|
|
897
942
|
}),
|
|
@@ -37,7 +37,7 @@ const index = {
|
|
|
37
37
|
defaultMessage: PLUGIN_ID
|
|
38
38
|
},
|
|
39
39
|
Component: async () => {
|
|
40
|
-
const { App } = await Promise.resolve().then(() => require("./App-
|
|
40
|
+
const { App } = await Promise.resolve().then(() => require("./App-C_BH5Ir4.js"));
|
|
41
41
|
return App;
|
|
42
42
|
}
|
|
43
43
|
});
|
package/dist/admin/index.js
CHANGED
package/dist/admin/index.mjs
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -671,24 +671,33 @@ function getDisplayField(strapi, contentType) {
|
|
|
671
671
|
}
|
|
672
672
|
return "id";
|
|
673
673
|
}
|
|
674
|
+
const MANY_RELATIONS = /* @__PURE__ */ new Set(["oneToMany", "manyToMany"]);
|
|
674
675
|
function resolveFieldPath(strapi, contentType, fieldPath) {
|
|
675
676
|
const parts = fieldPath.split(".");
|
|
676
677
|
const topField = parts[0];
|
|
677
678
|
const attr = getAttribute(strapi, contentType, topField);
|
|
678
679
|
if (!attr) {
|
|
679
|
-
return { resolvedPath: fieldPath, populate: [] };
|
|
680
|
+
return { resolvedPath: fieldPath, populate: [], isArray: false };
|
|
680
681
|
}
|
|
681
682
|
if (attr.type === "relation" && attr.target) {
|
|
683
|
+
const isArray = MANY_RELATIONS.has(attr.relation ?? "");
|
|
682
684
|
if (parts.length === 1) {
|
|
683
685
|
const displayField = getDisplayField(strapi, attr.target);
|
|
684
686
|
return {
|
|
685
687
|
resolvedPath: `${topField}.${displayField}`,
|
|
686
|
-
populate: [topField]
|
|
688
|
+
populate: [topField],
|
|
689
|
+
isArray,
|
|
690
|
+
selectField: displayField
|
|
687
691
|
};
|
|
688
692
|
}
|
|
689
|
-
return {
|
|
693
|
+
return {
|
|
694
|
+
resolvedPath: fieldPath,
|
|
695
|
+
populate: [topField],
|
|
696
|
+
isArray,
|
|
697
|
+
selectField: parts.slice(1).join(".")
|
|
698
|
+
};
|
|
690
699
|
}
|
|
691
|
-
return { resolvedPath: fieldPath, populate: [] };
|
|
700
|
+
return { resolvedPath: fieldPath, populate: [], isArray: false };
|
|
692
701
|
}
|
|
693
702
|
const MAX_PAGINATE = 1e3;
|
|
694
703
|
const PAGE_SIZE = 100;
|
|
@@ -727,17 +736,30 @@ function resolveField(doc, fieldPath) {
|
|
|
727
736
|
let current = doc;
|
|
728
737
|
for (const part of parts) {
|
|
729
738
|
if (current == null || typeof current !== "object") return void 0;
|
|
739
|
+
if (Array.isArray(current)) return void 0;
|
|
730
740
|
current = current[part];
|
|
731
741
|
}
|
|
732
742
|
return current;
|
|
733
743
|
}
|
|
734
|
-
|
|
744
|
+
function buildPopulate(resolved) {
|
|
745
|
+
if (resolved.populate.length === 0) return "*";
|
|
746
|
+
const populate = {};
|
|
747
|
+
for (const rel of resolved.populate) {
|
|
748
|
+
if (resolved.selectField) {
|
|
749
|
+
populate[rel] = { fields: [resolved.selectField] };
|
|
750
|
+
} else {
|
|
751
|
+
populate[rel] = true;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return populate;
|
|
755
|
+
}
|
|
756
|
+
async function paginateAll(strapi, contentType, baseQuery, populate = "*") {
|
|
735
757
|
const docs = [];
|
|
736
758
|
let page = 1;
|
|
737
759
|
while (docs.length < MAX_PAGINATE) {
|
|
738
760
|
const results = await strapi.documents(contentType).findMany({
|
|
739
761
|
...baseQuery,
|
|
740
|
-
populate
|
|
762
|
+
populate,
|
|
741
763
|
page,
|
|
742
764
|
pageSize: PAGE_SIZE
|
|
743
765
|
});
|
|
@@ -752,22 +774,38 @@ const DISPLAY_CANDIDATES = ["name", "title", "username", "label", "slug", "email
|
|
|
752
774
|
function toDisplayValue(raw) {
|
|
753
775
|
if (raw == null) return "(empty)";
|
|
754
776
|
if (typeof raw !== "object") return String(raw);
|
|
755
|
-
if (Array.isArray(raw)) {
|
|
756
|
-
if (raw.length === 0) return "(empty)";
|
|
757
|
-
return raw.map((item) => toDisplayValue(item)).join(", ");
|
|
758
|
-
}
|
|
759
777
|
const obj = raw;
|
|
760
778
|
for (const key of DISPLAY_CANDIDATES) {
|
|
761
779
|
if (obj[key] != null) return String(obj[key]);
|
|
762
780
|
}
|
|
763
781
|
return obj.id != null ? String(obj.id) : "(empty)";
|
|
764
782
|
}
|
|
765
|
-
function groupDocs(docs,
|
|
783
|
+
function groupDocs(docs, resolved) {
|
|
766
784
|
const counts = /* @__PURE__ */ new Map();
|
|
785
|
+
const topField = resolved.resolvedPath.split(".")[0];
|
|
786
|
+
const subPath = resolved.resolvedPath.split(".").slice(1).join(".");
|
|
767
787
|
for (const doc of docs) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
788
|
+
if (resolved.isArray) {
|
|
789
|
+
const items = doc[topField];
|
|
790
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
791
|
+
counts.set("(empty)", (counts.get("(empty)") ?? 0) + 1);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
for (const item of items) {
|
|
795
|
+
let value;
|
|
796
|
+
if (subPath && typeof item === "object" && item != null) {
|
|
797
|
+
const sub = resolveField(item, subPath);
|
|
798
|
+
value = sub != null ? String(sub) : toDisplayValue(item);
|
|
799
|
+
} else {
|
|
800
|
+
value = toDisplayValue(item);
|
|
801
|
+
}
|
|
802
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
803
|
+
}
|
|
804
|
+
} else {
|
|
805
|
+
const raw = resolveField(doc, resolved.resolvedPath);
|
|
806
|
+
const value = toDisplayValue(raw);
|
|
807
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
808
|
+
}
|
|
771
809
|
}
|
|
772
810
|
return Array.from(counts.entries()).map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count);
|
|
773
811
|
}
|
|
@@ -806,10 +844,11 @@ async function aggregateContent(strapi, params) {
|
|
|
806
844
|
if (!groupByField) {
|
|
807
845
|
throw new Error("groupByField is required for countByField operation");
|
|
808
846
|
}
|
|
809
|
-
const
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
|
|
847
|
+
const resolved = resolveFieldPath(strapi, contentType, groupByField);
|
|
848
|
+
const populate = buildPopulate(resolved);
|
|
849
|
+
const docs = await paginateAll(strapi, contentType, baseQuery, populate);
|
|
850
|
+
const groups = groupDocs(docs, resolved);
|
|
851
|
+
return { total: docs.length, groups, resolvedField: resolved.resolvedPath };
|
|
813
852
|
}
|
|
814
853
|
case "countByDateRange": {
|
|
815
854
|
const { dateField = "createdAt", granularity = "month" } = params;
|
package/dist/server/index.mjs
CHANGED
|
@@ -651,24 +651,33 @@ function getDisplayField(strapi, contentType) {
|
|
|
651
651
|
}
|
|
652
652
|
return "id";
|
|
653
653
|
}
|
|
654
|
+
const MANY_RELATIONS = /* @__PURE__ */ new Set(["oneToMany", "manyToMany"]);
|
|
654
655
|
function resolveFieldPath(strapi, contentType, fieldPath) {
|
|
655
656
|
const parts = fieldPath.split(".");
|
|
656
657
|
const topField = parts[0];
|
|
657
658
|
const attr = getAttribute(strapi, contentType, topField);
|
|
658
659
|
if (!attr) {
|
|
659
|
-
return { resolvedPath: fieldPath, populate: [] };
|
|
660
|
+
return { resolvedPath: fieldPath, populate: [], isArray: false };
|
|
660
661
|
}
|
|
661
662
|
if (attr.type === "relation" && attr.target) {
|
|
663
|
+
const isArray = MANY_RELATIONS.has(attr.relation ?? "");
|
|
662
664
|
if (parts.length === 1) {
|
|
663
665
|
const displayField = getDisplayField(strapi, attr.target);
|
|
664
666
|
return {
|
|
665
667
|
resolvedPath: `${topField}.${displayField}`,
|
|
666
|
-
populate: [topField]
|
|
668
|
+
populate: [topField],
|
|
669
|
+
isArray,
|
|
670
|
+
selectField: displayField
|
|
667
671
|
};
|
|
668
672
|
}
|
|
669
|
-
return {
|
|
673
|
+
return {
|
|
674
|
+
resolvedPath: fieldPath,
|
|
675
|
+
populate: [topField],
|
|
676
|
+
isArray,
|
|
677
|
+
selectField: parts.slice(1).join(".")
|
|
678
|
+
};
|
|
670
679
|
}
|
|
671
|
-
return { resolvedPath: fieldPath, populate: [] };
|
|
680
|
+
return { resolvedPath: fieldPath, populate: [], isArray: false };
|
|
672
681
|
}
|
|
673
682
|
const MAX_PAGINATE = 1e3;
|
|
674
683
|
const PAGE_SIZE = 100;
|
|
@@ -707,17 +716,30 @@ function resolveField(doc, fieldPath) {
|
|
|
707
716
|
let current = doc;
|
|
708
717
|
for (const part of parts) {
|
|
709
718
|
if (current == null || typeof current !== "object") return void 0;
|
|
719
|
+
if (Array.isArray(current)) return void 0;
|
|
710
720
|
current = current[part];
|
|
711
721
|
}
|
|
712
722
|
return current;
|
|
713
723
|
}
|
|
714
|
-
|
|
724
|
+
function buildPopulate(resolved) {
|
|
725
|
+
if (resolved.populate.length === 0) return "*";
|
|
726
|
+
const populate = {};
|
|
727
|
+
for (const rel of resolved.populate) {
|
|
728
|
+
if (resolved.selectField) {
|
|
729
|
+
populate[rel] = { fields: [resolved.selectField] };
|
|
730
|
+
} else {
|
|
731
|
+
populate[rel] = true;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return populate;
|
|
735
|
+
}
|
|
736
|
+
async function paginateAll(strapi, contentType, baseQuery, populate = "*") {
|
|
715
737
|
const docs = [];
|
|
716
738
|
let page = 1;
|
|
717
739
|
while (docs.length < MAX_PAGINATE) {
|
|
718
740
|
const results = await strapi.documents(contentType).findMany({
|
|
719
741
|
...baseQuery,
|
|
720
|
-
populate
|
|
742
|
+
populate,
|
|
721
743
|
page,
|
|
722
744
|
pageSize: PAGE_SIZE
|
|
723
745
|
});
|
|
@@ -732,22 +754,38 @@ const DISPLAY_CANDIDATES = ["name", "title", "username", "label", "slug", "email
|
|
|
732
754
|
function toDisplayValue(raw) {
|
|
733
755
|
if (raw == null) return "(empty)";
|
|
734
756
|
if (typeof raw !== "object") return String(raw);
|
|
735
|
-
if (Array.isArray(raw)) {
|
|
736
|
-
if (raw.length === 0) return "(empty)";
|
|
737
|
-
return raw.map((item) => toDisplayValue(item)).join(", ");
|
|
738
|
-
}
|
|
739
757
|
const obj = raw;
|
|
740
758
|
for (const key of DISPLAY_CANDIDATES) {
|
|
741
759
|
if (obj[key] != null) return String(obj[key]);
|
|
742
760
|
}
|
|
743
761
|
return obj.id != null ? String(obj.id) : "(empty)";
|
|
744
762
|
}
|
|
745
|
-
function groupDocs(docs,
|
|
763
|
+
function groupDocs(docs, resolved) {
|
|
746
764
|
const counts = /* @__PURE__ */ new Map();
|
|
765
|
+
const topField = resolved.resolvedPath.split(".")[0];
|
|
766
|
+
const subPath = resolved.resolvedPath.split(".").slice(1).join(".");
|
|
747
767
|
for (const doc of docs) {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
768
|
+
if (resolved.isArray) {
|
|
769
|
+
const items = doc[topField];
|
|
770
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
771
|
+
counts.set("(empty)", (counts.get("(empty)") ?? 0) + 1);
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
for (const item of items) {
|
|
775
|
+
let value;
|
|
776
|
+
if (subPath && typeof item === "object" && item != null) {
|
|
777
|
+
const sub = resolveField(item, subPath);
|
|
778
|
+
value = sub != null ? String(sub) : toDisplayValue(item);
|
|
779
|
+
} else {
|
|
780
|
+
value = toDisplayValue(item);
|
|
781
|
+
}
|
|
782
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
783
|
+
}
|
|
784
|
+
} else {
|
|
785
|
+
const raw = resolveField(doc, resolved.resolvedPath);
|
|
786
|
+
const value = toDisplayValue(raw);
|
|
787
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
788
|
+
}
|
|
751
789
|
}
|
|
752
790
|
return Array.from(counts.entries()).map(([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count);
|
|
753
791
|
}
|
|
@@ -786,10 +824,11 @@ async function aggregateContent(strapi, params) {
|
|
|
786
824
|
if (!groupByField) {
|
|
787
825
|
throw new Error("groupByField is required for countByField operation");
|
|
788
826
|
}
|
|
789
|
-
const
|
|
790
|
-
const
|
|
791
|
-
const
|
|
792
|
-
|
|
827
|
+
const resolved = resolveFieldPath(strapi, contentType, groupByField);
|
|
828
|
+
const populate = buildPopulate(resolved);
|
|
829
|
+
const docs = await paginateAll(strapi, contentType, baseQuery, populate);
|
|
830
|
+
const groups = groupDocs(docs, resolved);
|
|
831
|
+
return { total: docs.length, groups, resolvedField: resolved.resolvedPath };
|
|
793
832
|
}
|
|
794
833
|
case "countByDateRange": {
|
|
795
834
|
const { dateField = "createdAt", granularity = "month" } = params;
|
|
@@ -19,3 +19,4 @@ export type { RecallPublicMemoriesParams, RecallPublicMemoriesResult } from './r
|
|
|
19
19
|
export { aggregateContent, aggregateContentSchema, aggregateContentDescription } from './aggregate-content';
|
|
20
20
|
export type { AggregateContentParams, AggregateContentResult } from './aggregate-content';
|
|
21
21
|
export { resolveFieldPath, getDisplayField, isRelation, getRelationTarget, getSchema } from './schema-utils';
|
|
22
|
+
export type { ResolvedField } from './schema-utils';
|
|
@@ -35,17 +35,15 @@ export declare function getRelationTarget(strapi: Core.Strapi, contentType: stri
|
|
|
35
35
|
* then falls back to the first string/text field, then 'id'.
|
|
36
36
|
*/
|
|
37
37
|
export declare function getDisplayField(strapi: Core.Strapi, contentType: string): string;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* resolve it to the actual dot-path needed to extract a display value from a populated document.
|
|
41
|
-
*
|
|
42
|
-
* If the field is a relation with no sub-field specified, appends the best display field
|
|
43
|
-
* from the target schema (e.g. "author" → "author.name").
|
|
44
|
-
*
|
|
45
|
-
* Returns the resolved path and any top-level relations that need populating.
|
|
46
|
-
*/
|
|
47
|
-
export declare function resolveFieldPath(strapi: Core.Strapi, contentType: string, fieldPath: string): {
|
|
38
|
+
export interface ResolvedField {
|
|
39
|
+
/** The dot-path to extract the display value (e.g. "author.name") */
|
|
48
40
|
resolvedPath: string;
|
|
41
|
+
/** Top-level relations that must be populated */
|
|
49
42
|
populate: string[];
|
|
50
|
-
|
|
43
|
+
/** True if the relation is a *ToMany (value is an array of items) */
|
|
44
|
+
isArray: boolean;
|
|
45
|
+
/** The sub-field to select from the relation (for targeted populate) */
|
|
46
|
+
selectField?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function resolveFieldPath(strapi: Core.Strapi, contentType: string, fieldPath: string): ResolvedField;
|
|
51
49
|
export {};
|
package/package.json
CHANGED