@sanity/assist 4.3.1 → 4.3.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/README.md CHANGED
@@ -35,7 +35,7 @@
35
35
  - [Adding translation actions to fields](#adding-translation-actions-to-fields)
36
36
  - [Translation style guide](#translation-style-guide)
37
37
  - [Custom field actions](#custom-field-actions)
38
- - [useFieldActions](#usefieldaction)
38
+ - [useExampleFieldActions](#usefieldaction)
39
39
  - [Define helpers](#define-helpers)
40
40
  - [useUserInput](#useuserinput)
41
41
  - [License](#license)
@@ -949,34 +949,50 @@ assist({
949
949
  <img width="513" alt="Field action menu with custom actions" src="https://github.com/user-attachments/assets/c613f692-4983-4acc-a8c2-8fb60294682a" />
950
950
 
951
951
  To incorporate [Agent Actions](https://www.sanity.io/docs/agent-actions?utm_source=github.com&utm_medium=organic_social&utm_campaign=ai-assist&utm_content=)
952
- or other custom actions into the AI Assist document and field action menus, use `fieldActions` plugin config:
952
+ or other custom actions into the AI Assist document and field action menus, use `fieldActions` plugin config.
953
+
954
+ Because of react hook linting, we recommend defining the `useExampleFieldActions` outside the plugin config:
953
955
 
954
956
  ```ts
955
- assist({
956
- fieldActions: {
957
- title: 'Custom actions',
958
- useFieldActions: (props: AssistFieldActionProps) => {
959
- return useMemo(() => [
960
- defineAssistFieldAction({
961
- title: 'Do something',
962
- icon: ActionIcon,
963
- onAction: async () => {
964
- // perform an (async) action
965
- // errors will be caught and displayed in a toast
966
- // until the action completes or fails, AI Assist "presence" will show up on the top of the document
967
- },
968
- })], [])
969
- }
970
- }
957
+ //sanity.config.ts
958
+ import {defineConfig} from 'sanity'
959
+ import {assist, type AssistFieldActionProps, defineAssistFieldAction} from '@sanity/assist'
960
+
961
+ function useExampleFieldActions(props: AssistFieldActionProps) {
962
+ return useMemo(() => [
963
+ defineAssistFieldAction({
964
+ title: 'Do something',
965
+ icon: ActionIcon,
966
+ onAction: async () => {
967
+ // perform an (async) action
968
+ // errors will be caught and displayed in a toast
969
+ // until the action completes or fails, AI Assist "presence" will show up on the top of the document
970
+ },
971
+ })], [])
972
+ }
973
+
974
+ export default defineConfig({
975
+ //...
976
+ plugins: [
977
+ //...other plugins
978
+ assist({
979
+ fieldActions: {
980
+ title: 'Custom actions',
981
+ useExampleFieldActions
982
+ }
983
+ })
984
+ ]
971
985
  })
972
986
  ```
973
987
 
974
- ### `useFieldActions`
988
+ ### `useExampleFieldActions`
975
989
 
976
- `useFieldActions` is called for the document itself and for all fields within it. It can call React hooks.
990
+ `useExampleFieldActions` is called for the document itself and for all fields within it. It can call React hooks.
977
991
  Actions returned by the hook will be added to the corresponding document or field menu.
978
992
 
979
- It is recommended to wrap the returned actions in `useMemo`.
993
+ It is recommended to wrap the returned actions in `useMemo`. The returned array can contain `undefined` values.
994
+ These will be filtered out.
995
+
980
996
 
981
997
  See TSDocs for [AssistFieldActionProps](./src/fieldActions/customFieldActions.tsx) for details on how each
982
998
  prop can be used to parameterize Agent Actions on sanity client.
@@ -991,49 +1007,44 @@ It will fix spelling mistakes for the field it is invoked for (and all child fie
991
1007
  by calling `client.agent.action.transform`.
992
1008
 
993
1009
  ```ts
994
- assist({
995
- fieldActions: {
996
- title: 'Custom actions',
997
- useFieldActions: (props) => {
998
- const {
999
- documentSchemaType,
1000
- schemaId,
1001
- getDocumentValue,
1002
- getConditionalPaths,
1003
- documentIdForAction,
1004
- path,
1005
- } = props
1006
- const client = useClient({apiVersion: 'vX'})
1007
- return useMemo(() => {
1008
- return [
1009
- defineAssistFieldAction({
1010
- title: 'Fix spelling',
1011
- icon: TranslateIcon,
1012
- onAction: async () => {
1013
- await client.agent.action.transform({
1014
- schemaId,
1015
- documentId: documentIdForAction,
1016
- instruction: 'Fix any spelling mistakes',
1017
- instructionParams: {field: {type: 'field', path}},
1018
- // no need to send path for document actions
1019
- target: path.length ? {path} : undefined,
1020
- conditionalPaths: {paths: getConditionalPaths()},
1021
- })
1022
- },
1023
- }),
1024
- ]
1025
- }, [
1026
- client,
1027
- documentSchemaType,
1028
- schemaId,
1029
- getDocumentValue,
1030
- getConditionalPaths,
1031
- documentIdForAction,
1032
- path,
1033
- ])
1034
- },
1035
- },
1036
- })
1010
+ function useExampleFieldActions(props: AssistFieldActionProps) {
1011
+ const {
1012
+ documentSchemaType,
1013
+ schemaId,
1014
+ getDocumentValue,
1015
+ getConditionalPaths,
1016
+ documentIdForAction,
1017
+ path,
1018
+ } = props
1019
+ const client = useClient({apiVersion: 'vX'})
1020
+ return useMemo(() => {
1021
+ return [
1022
+ defineAssistFieldAction({
1023
+ title: 'Fix spelling',
1024
+ icon: TranslateIcon,
1025
+ onAction: async () => {
1026
+ await client.agent.action.transform({
1027
+ schemaId,
1028
+ documentId: documentIdForAction,
1029
+ instruction: 'Fix any spelling mistakes',
1030
+ instructionParams: {field: {type: 'field', path}},
1031
+ // no need to send path for document actions
1032
+ target: path.length ? {path} : undefined,
1033
+ conditionalPaths: {paths: getConditionalPaths()},
1034
+ })
1035
+ },
1036
+ }),
1037
+ ]
1038
+ }, [
1039
+ client,
1040
+ documentSchemaType,
1041
+ schemaId,
1042
+ getDocumentValue,
1043
+ getConditionalPaths,
1044
+ documentIdForAction,
1045
+ path,
1046
+ ])
1047
+ }
1037
1048
  ```
1038
1049
 
1039
1050
  ##### Fill field (contextually aware)
@@ -1047,44 +1058,41 @@ The action will:
1047
1058
  - output to the field the action started from (`target.path`)
1048
1059
 
1049
1060
  ```ts
1050
- assist({
1051
- fieldActions: {
1052
- title: 'Custom actions',
1053
- useFieldActions: (props) => {
1054
- const {
1055
- documentSchemaType,
1056
- actionType,
1057
- schemaId,
1058
- getDocumentValue,
1059
- getConditionalPaths,
1060
- documentIdForAction,
1061
- path,
1062
- schemaType,
1063
- } = props
1064
-
1065
- // hook usage has to happen outside onAction, so preassemble state in useFieldActions and pass to useMemo
1066
- const client = useClient({apiVersion: 'vX'})
1067
-
1068
- return useMemo(() => {
1069
- if (actionType === 'document') {
1070
- // in this case we dont want a document action
1071
- return []
1072
- }
1061
+ function useExampleFieldActions(props: AssistFieldActionProps) {
1062
+ const {
1063
+ documentSchemaType,
1064
+ actionType,
1065
+ schemaId,
1066
+ getDocumentValue,
1067
+ getConditionalPaths,
1068
+ documentIdForAction,
1069
+ path,
1070
+ schemaType,
1071
+ } = props
1072
+
1073
+ // hook usage has to happen outside onAction, so preassemble state in useExampleFieldActions and pass to useMemo
1074
+ const client = useClient({apiVersion: 'vX'})
1075
+
1076
+ return useMemo(() => {
1077
+ if (actionType === 'document') {
1078
+ // in this case we dont want a document action
1079
+ return []
1080
+ }
1073
1081
 
1074
- return [
1075
- defineAssistFieldAction({
1076
- title: 'Fill field',
1077
- icon: EditIcon,
1078
- onAction: async () => {
1079
- await client.agent.action.generate({
1080
- schemaId,
1081
- targetDocument: {
1082
- operation: 'createIfNotExists',
1083
- _id: documentIdForAction,
1084
- _type: documentSchemaType.name,
1085
- initialValues: getDocumentValue(),
1086
- },
1087
- instruction: `
1082
+ return [
1083
+ defineAssistFieldAction({
1084
+ title: 'Fill field',
1085
+ icon: EditIcon,
1086
+ onAction: async () => {
1087
+ await client.agent.action.generate({
1088
+ schemaId,
1089
+ targetDocument: {
1090
+ operation: 'createIfNotExists',
1091
+ _id: documentIdForAction,
1092
+ _type: documentSchemaType.name,
1093
+ initialValues: getDocumentValue(),
1094
+ },
1095
+ instruction: `
1088
1096
  We are generating a new value for a document field.
1089
1097
  The document type is ${documentSchemaType.name}, and the document type title is ${documentSchemaType.title}
1090
1098
  The document language is: "$lang" (use en-US if unspecified)
@@ -1099,35 +1107,33 @@ The action will:
1099
1107
  Generate a new field value. The new value should be relevant to the document type and context.
1100
1108
  Keep it interesting. Generate using the document language.
1101
1109
  `,
1102
- instructionParams: {
1103
- doc: {type: 'document'},
1104
- field: {type: 'field', path},
1105
- lang: {type: 'field', path: ['language']},
1106
- },
1107
- target: {
1108
- path,
1109
- },
1110
- conditionalPaths: {
1111
- paths: getConditionalPaths(),
1112
- },
1113
- })
1110
+ instructionParams: {
1111
+ doc: {type: 'document'},
1112
+ field: {type: 'field', path},
1113
+ lang: {type: 'field', path: ['language']},
1114
1114
  },
1115
- }),
1116
- ]
1117
- }, [
1118
- client,
1119
- documentSchemaType,
1120
- schemaId,
1121
- getDocumentValue,
1122
- getConditionalPaths,
1123
- documentIdForAction,
1124
- actionType,
1125
- path,
1126
- schemaType,
1127
- ])
1128
- },
1129
- },
1130
- })
1115
+ target: {
1116
+ path,
1117
+ },
1118
+ conditionalPaths: {
1119
+ paths: getConditionalPaths(),
1120
+ },
1121
+ })
1122
+ },
1123
+ }),
1124
+ ]
1125
+ }, [
1126
+ client,
1127
+ documentSchemaType,
1128
+ schemaId,
1129
+ getDocumentValue,
1130
+ getConditionalPaths,
1131
+ documentIdForAction,
1132
+ actionType,
1133
+ path,
1134
+ schemaType,
1135
+ ])
1136
+ }
1131
1137
  ```
1132
1138
 
1133
1139
  ### Define helpers
@@ -1136,7 +1142,7 @@ The action will:
1136
1142
 
1137
1143
  Adds a single action that will appear in the document/field action menu.
1138
1144
 
1139
- `onAction` _cannot_ call hooks. If state from hook is needed, it should be pre-assembled by `useFieldActions`
1145
+ `onAction` _cannot_ call hooks. If state from hook is needed, it should be pre-assembled by `useExampleFieldActions`
1140
1146
 
1141
1147
  ```ts
1142
1148
  defineAssistFieldAction({
@@ -1151,10 +1157,12 @@ defineAssistFieldAction({
1151
1157
  #### `defineAssistFieldActionGroup`
1152
1158
 
1153
1159
  Adds a group to hold one or more actions (or nested groups).
1160
+ `children` can contain `undefined` values. These will be filtered out.
1161
+ A group that has an empty `children` array (or only undefined values) will be filtered out.
1154
1162
 
1155
- By default, any actions returned by `useFieldActions` will be grouped under `title`.
1163
+ By default, any actions returned by `useExampleFieldActions` will be grouped under `title`.
1156
1164
  ```ts
1157
- useFieldActions: (props) => {
1165
+ function useExampleFieldActions(props: AssistFieldActionProps) {
1158
1166
  return [
1159
1167
  defineAssistFieldAction({/* ... */}),
1160
1168
  defineAssistFieldActionGroup({
@@ -1167,15 +1175,14 @@ useFieldActions: (props) => {
1167
1175
  }
1168
1176
  ```
1169
1177
 
1170
- #### Only groups in `useFieldActions`
1171
- If `useFieldActions` _only_ returns groups, the default wrapper group will be omitted. This allows full control over each group title.
1178
+ #### Only groups in `useExampleFieldActions`
1179
+ If `useExampleFieldActions` _only_ returns groups, the default wrapper group will be omitted. This allows full control over each group title.
1172
1180
 
1173
1181
  #### `defineFieldActionDivider`
1174
1182
  Adds a divider between actions or groups. Takes no arguments:
1175
1183
 
1176
1184
  ```ts
1177
-
1178
- useFieldActions: (props) => {
1185
+ function useExampleFieldActions(props: AssistFieldActionProps) {
1179
1186
  return useMemo(() => [
1180
1187
  defineAssistFieldAction({/* ... */}),
1181
1188
  defineFieldActionDivider(),
@@ -1198,45 +1205,41 @@ When the user completes the dialog, the user inputted text will be available (or
1198
1205
 
1199
1206
 
1200
1207
  ```ts
1201
- ({
1202
- fieldActions: {
1203
- title: 'Custom actions',
1204
- useFieldActions: (props) => {
1205
- const getUserInput = useUserInput()
1206
-
1207
- return useMemo(
1208
- () => [
1209
- defineAssistFieldAction({
1210
- title: 'Do something with user input',
1211
- onAction: async () => {
1212
- const inputResult = await getUserInput({
1213
- title: 'What do you want to do?', // dialog title
1214
- inputs: [
1215
- {
1216
- id: 'topic',
1217
- title: 'Topic',
1218
- },
1219
- {
1220
- id: 'facts',
1221
- title: 'Facts',
1222
- description: 'Provide additional facts that will be used by the action',
1223
- },
1224
- ],
1225
- })
1226
- if (!inputResult) {
1227
- return // user closed the dialog
1228
- }
1229
-
1230
- //use the result from each input
1231
- //const [{result: topic}, {result: facts}] = inputResult
1232
- },
1233
- }),
1234
- ],
1235
- [getUserInput],
1236
- )
1237
- },
1238
- },
1239
- })
1208
+
1209
+ function useExampleFieldActions(props: AssistFieldActionProps) {
1210
+ const getUserInput = useUserInput()
1211
+
1212
+ return useMemo(
1213
+ () => [
1214
+ defineAssistFieldAction({
1215
+ title: 'Do something with user input',
1216
+ onAction: async () => {
1217
+ const inputResult = await getUserInput({
1218
+ title: 'What do you want to do?', // dialog title
1219
+ inputs: [
1220
+ {
1221
+ id: 'topic',
1222
+ title: 'Topic',
1223
+ },
1224
+ {
1225
+ id: 'facts',
1226
+ title: 'Facts',
1227
+ description: 'Provide additional facts that will be used by the action',
1228
+ },
1229
+ ],
1230
+ })
1231
+ if (!inputResult) {
1232
+ return // user closed the dialog
1233
+ }
1234
+
1235
+ //use the result from each input
1236
+ //const [{result: topic}, {result: facts}] = inputResult
1237
+ },
1238
+ }),
1239
+ ],
1240
+ [getUserInput],
1241
+ )
1242
+ }
1240
1243
  ```
1241
1244
 
1242
1245
  ## Caveats
package/dist/index.d.mts CHANGED
@@ -78,7 +78,11 @@ export declare type AssistFieldActionGroup = Omit<
78
78
  DocumentFieldActionGroup,
79
79
  'renderAsButton' | 'expanded' | 'children'
80
80
  > & {
81
- children: AssistFieldActionNode[]
81
+ /**
82
+ * `children` can include undefined entries in the action array. These will be filtered out.
83
+ * If the group has no defined children, the group will also be filtered out.
84
+ */
85
+ children: (AssistFieldActionNode | undefined)[]
82
86
  }
83
87
 
84
88
  export declare type AssistFieldActionItem = Omit<
@@ -223,7 +227,10 @@ declare interface AssistPluginConfig {
223
227
  assist?: AssistConfig
224
228
  fieldActions?: {
225
229
  title?: string
226
- useFieldActions?: (props: AssistFieldActionProps) => AssistFieldActionNode[]
230
+ /**
231
+ * The returned array can include `undefined` entries in the action array. These will be filtered out.
232
+ */
233
+ useFieldActions?: (props: AssistFieldActionProps) => (AssistFieldActionNode | undefined)[]
227
234
  }
228
235
  /**
229
236
  * @internal
package/dist/index.d.ts CHANGED
@@ -78,7 +78,11 @@ export declare type AssistFieldActionGroup = Omit<
78
78
  DocumentFieldActionGroup,
79
79
  'renderAsButton' | 'expanded' | 'children'
80
80
  > & {
81
- children: AssistFieldActionNode[]
81
+ /**
82
+ * `children` can include undefined entries in the action array. These will be filtered out.
83
+ * If the group has no defined children, the group will also be filtered out.
84
+ */
85
+ children: (AssistFieldActionNode | undefined)[]
82
86
  }
83
87
 
84
88
  export declare type AssistFieldActionItem = Omit<
@@ -223,7 +227,10 @@ declare interface AssistPluginConfig {
223
227
  assist?: AssistConfig
224
228
  fieldActions?: {
225
229
  title?: string
226
- useFieldActions?: (props: AssistFieldActionProps) => AssistFieldActionNode[]
230
+ /**
231
+ * The returned array can include `undefined` entries in the action array. These will be filtered out.
232
+ */
233
+ useFieldActions?: (props: AssistFieldActionProps) => (AssistFieldActionNode | undefined)[]
227
234
  }
228
235
  /**
229
236
  * @internal
package/dist/index.esm.js CHANGED
@@ -211,6 +211,9 @@ function getPathKey(path) {
211
211
  function getInstructionTitle(instruction2) {
212
212
  return instruction2?.title ?? "Untitled";
213
213
  }
214
+ function isDefined(t) {
215
+ return t != null;
216
+ }
214
217
  function isPortableTextArray(type) {
215
218
  return type.of.find((t) => isType(t, "block"));
216
219
  }
@@ -2870,12 +2873,12 @@ function useCustomFieldActions(props) {
2870
2873
  path: props.path
2871
2874
  });
2872
2875
  return useMemo(() => {
2873
- const title = fieldActions?.title, customActions = configActions?.map((node) => createSafeNode({
2876
+ const title = fieldActions?.title, customActions = configActions?.filter(isDefined).map((node) => createSafeNode({
2874
2877
  node,
2875
2878
  pushToast,
2876
2879
  addSyntheticTask,
2877
2880
  removeSyntheticTask
2878
- })), onlyGroups = customActions?.length && customActions?.every((node) => node.type === "group");
2881
+ })).filter(isDefined), onlyGroups = customActions?.length && customActions?.every((node) => node.type === "group");
2879
2882
  return (customActions?.length ? onlyGroups ? customActions : [
2880
2883
  {
2881
2884
  type: "group",
@@ -2892,12 +2895,13 @@ function createSafeNode(args) {
2892
2895
  case "action":
2893
2896
  return createSafeAction({ ...args, action: node });
2894
2897
  case "group":
2895
- return {
2898
+ const children = node.children?.filter(isDefined).map((child) => createSafeNode({ ...args, node: child })).filter(isDefined);
2899
+ return children?.length ? {
2896
2900
  ...node,
2897
2901
  renderAsButton: !1,
2898
2902
  expanded: !0,
2899
- children: node.children?.map((child) => createSafeNode({ ...args, node: child }))
2900
- };
2903
+ children
2904
+ } : void 0;
2901
2905
  case "divider":
2902
2906
  default:
2903
2907
  return node;