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.
@@ -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-tCUlQhF4.js");
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 !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: "auto", fontWeight: 400, opacity: 0.6 }, children: "completed" })
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-C6UZ9D-c.mjs";
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 !== void 0 && /* @__PURE__ */ jsx("span", { style: { marginLeft: "auto", fontWeight: 400, opacity: 0.6 }, children: "completed" })
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
  }),
@@ -36,7 +36,7 @@ const index = {
36
36
  defaultMessage: PLUGIN_ID
37
37
  },
38
38
  Component: async () => {
39
- const { App } = await import("./App-k5doNU8o.mjs");
39
+ const { App } = await import("./App-DKyCb0BY.mjs");
40
40
  return App;
41
41
  }
42
42
  });
@@ -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-31DoeBYs.js"));
40
+ const { App } = await Promise.resolve().then(() => require("./App-C_BH5Ir4.js"));
41
41
  return App;
42
42
  }
43
43
  });
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-tCUlQhF4.js");
2
+ const index = require("../_chunks/index-DCEjJ0as.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-C6UZ9D-c.mjs";
1
+ import { i } from "../_chunks/index-BV9DET_M.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -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 { resolvedPath: fieldPath, populate: [topField] };
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
- async function paginateAll(strapi, contentType, baseQuery) {
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, fieldPath) {
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
- const raw = resolveField(doc, fieldPath);
769
- const value = toDisplayValue(raw);
770
- counts.set(value, (counts.get(value) ?? 0) + 1);
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 { resolvedPath } = resolveFieldPath(strapi, contentType, groupByField);
810
- const docs = await paginateAll(strapi, contentType, baseQuery);
811
- const groups = groupDocs(docs, resolvedPath);
812
- return { total: docs.length, groups, resolvedField: resolvedPath };
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;
@@ -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 { resolvedPath: fieldPath, populate: [topField] };
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
- async function paginateAll(strapi, contentType, baseQuery) {
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, fieldPath) {
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
- const raw = resolveField(doc, fieldPath);
749
- const value = toDisplayValue(raw);
750
- counts.set(value, (counts.get(value) ?? 0) + 1);
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 { resolvedPath } = resolveFieldPath(strapi, contentType, groupByField);
790
- const docs = await paginateAll(strapi, contentType, baseQuery);
791
- const groups = groupDocs(docs, resolvedPath);
792
- return { total: docs.length, groups, resolvedField: resolvedPath };
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
- * Given a field path that the AI provided (e.g. "author", "category", "author.name"),
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.7",
2
+ "version": "0.6.8",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",