@sanity/assist 4.3.0 → 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,32 +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
- title: 'Custom actions',
957
- useFieldActions: (props: AssistFieldActionProps) => {
958
- return useMemo(() => [
959
- defineAssistFieldAction({
960
- title: 'Do something',
961
- icon: ActionIcon,
962
- onAction: async () => {
963
- // perform an (async) action
964
- // errors will be caught and displayed in a toast
965
- // until the action completes or fails, AI Assist "presence" will show up on the top of the document
966
- },
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
+ },
967
971
  })], [])
968
- }
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
+ ]
969
985
  })
970
986
  ```
971
987
 
972
- ### `useFieldActions`
988
+ ### `useExampleFieldActions`
973
989
 
974
- `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.
975
991
  Actions returned by the hook will be added to the corresponding document or field menu.
976
992
 
977
- 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
+
978
996
 
979
997
  See TSDocs for [AssistFieldActionProps](./src/fieldActions/customFieldActions.tsx) for details on how each
980
998
  prop can be used to parameterize Agent Actions on sanity client.
@@ -989,46 +1007,44 @@ It will fix spelling mistakes for the field it is invoked for (and all child fie
989
1007
  by calling `client.agent.action.transform`.
990
1008
 
991
1009
  ```ts
992
- assist({
993
- title: 'Custom actions',
994
- useFieldActions: (props) => {
995
- const {
996
- documentSchemaType,
997
- schemaId,
998
- getDocumentValue,
999
- getConditionalPaths,
1000
- documentIdForAction,
1001
- path,
1002
- } = props
1003
- const client = useClient({apiVersion: 'vX'})
1004
- return useMemo(() => {
1005
- return [
1006
- defineAssistFieldAction({
1007
- title: 'Fix spelling',
1008
- icon: TranslateIcon,
1009
- onAction: async () => {
1010
- await client.agent.action.transform({
1011
- schemaId,
1012
- documentId: documentIdForAction,
1013
- instruction: 'Fix any spelling mistakes',
1014
- instructionParams: {field: {type: 'field', path}},
1015
- // no need to send path for document actions
1016
- target: path.length ? {path} : undefined,
1017
- conditionalPaths: {paths: getConditionalPaths()},
1018
- })
1019
- },
1020
- }),
1021
- ]
1022
- }, [
1023
- client,
1024
- documentSchemaType,
1025
- schemaId,
1026
- getDocumentValue,
1027
- getConditionalPaths,
1028
- documentIdForAction,
1029
- path,
1030
- ])
1031
- }})
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
+ }
1032
1048
  ```
1033
1049
 
1034
1050
  ##### Fill field (contextually aware)
@@ -1042,43 +1058,41 @@ The action will:
1042
1058
  - output to the field the action started from (`target.path`)
1043
1059
 
1044
1060
  ```ts
1045
- assist({
1046
- title: 'Custom actions',
1047
- useFieldActions: (props: AssistFieldActionProps) => {
1048
- const {
1049
- documentSchemaType,
1050
- actionType,
1051
- schemaId,
1052
- getDocumentValue,
1053
- getConditionalPaths,
1054
- documentIdForAction,
1055
- path,
1056
- schemaType,
1057
- } = props
1058
-
1059
- // hook usage has to happen outside onAction, so preassemble state in useFieldActions and pass to useMemo
1060
- const client = useClient({apiVersion: 'vX'})
1061
-
1062
- return useMemo(() => {
1063
- if (actionType === 'document') {
1064
- // in this case we dont want a document action
1065
- return []
1066
- }
1067
-
1068
- return [
1069
- defineAssistFieldAction({
1070
- title: 'Fill field',
1071
- icon: EditIcon,
1072
- onAction: async () => {
1073
- await client.agent.action.generate({
1074
- schemaId,
1075
- targetDocument: {
1076
- operation: 'createIfNotExists',
1077
- _id: documentIdForAction,
1078
- _type: documentSchemaType.name,
1079
- initialValues: getDocumentValue(),
1080
- },
1081
- instruction: `
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
+ }
1081
+
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: `
1082
1096
  We are generating a new value for a document field.
1083
1097
  The document type is ${documentSchemaType.name}, and the document type title is ${documentSchemaType.title}
1084
1098
  The document language is: "$lang" (use en-US if unspecified)
@@ -1093,34 +1107,33 @@ assist({
1093
1107
  Generate a new field value. The new value should be relevant to the document type and context.
1094
1108
  Keep it interesting. Generate using the document language.
1095
1109
  `,
1096
- instructionParams: {
1097
- doc: {type: 'document'},
1098
- field: {type: 'field', path},
1099
- lang: {type: 'field', path: ['language']},
1100
- },
1101
- target: {
1102
- path,
1103
- },
1104
- conditionalPaths: {
1105
- paths: getConditionalPaths(),
1106
- },
1107
- })
1108
- },
1109
- })
1110
- ]
1111
- }, [
1112
- client,
1113
- documentSchemaType,
1114
- schemaId,
1115
- getDocumentValue,
1116
- getConditionalPaths,
1117
- documentIdForAction,
1118
- actionType,
1119
- path,
1120
- schemaType,
1121
- ])
1122
- }
1123
- })
1110
+ instructionParams: {
1111
+ doc: {type: 'document'},
1112
+ field: {type: 'field', path},
1113
+ lang: {type: 'field', path: ['language']},
1114
+ },
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
+ }
1124
1137
  ```
1125
1138
 
1126
1139
  ### Define helpers
@@ -1129,7 +1142,7 @@ assist({
1129
1142
 
1130
1143
  Adds a single action that will appear in the document/field action menu.
1131
1144
 
1132
- `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`
1133
1146
 
1134
1147
  ```ts
1135
1148
  defineAssistFieldAction({
@@ -1144,42 +1157,38 @@ defineAssistFieldAction({
1144
1157
  #### `defineAssistFieldActionGroup`
1145
1158
 
1146
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.
1147
1162
 
1148
- 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`.
1149
1164
  ```ts
1150
- assist({
1151
- title: 'Default group',
1152
- useFieldActions: (props) => {
1153
- return [
1154
- defineAssistFieldAction({/* ... */}),
1155
- defineAssistFieldActionGroup({
1156
- title: 'More actions',
1157
- children: [
1158
- defineAssistFieldAction({/* ... */}),
1159
- ],
1160
- })
1161
- ]
1162
- }
1163
- })
1165
+ function useExampleFieldActions(props: AssistFieldActionProps) {
1166
+ return [
1167
+ defineAssistFieldAction({/* ... */}),
1168
+ defineAssistFieldActionGroup({
1169
+ title: 'More actions',
1170
+ children: [
1171
+ defineAssistFieldAction({/* ... */}),
1172
+ ],
1173
+ })
1174
+ ]
1175
+ }
1164
1176
  ```
1165
1177
 
1166
- #### Only groups in `useFieldActions`
1167
- 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.
1168
1180
 
1169
1181
  #### `defineFieldActionDivider`
1170
1182
  Adds a divider between actions or groups. Takes no arguments:
1171
1183
 
1172
1184
  ```ts
1173
- assist({
1174
- title: 'Custom actions',
1175
- useFieldActions: (props) => {
1176
- return useMemo(() => [
1177
- defineAssistFieldAction({/* ... */}),
1178
- defineFieldActionDivider(),
1179
- defineAssistFieldAction({/* ... */}),
1180
- ], [])
1181
- }
1182
- })
1185
+ function useExampleFieldActions(props: AssistFieldActionProps) {
1186
+ return useMemo(() => [
1187
+ defineAssistFieldAction({/* ... */}),
1188
+ defineFieldActionDivider(),
1189
+ defineAssistFieldAction({/* ... */}),
1190
+ ], [])
1191
+ }
1183
1192
  ```
1184
1193
 
1185
1194
  ### `useUserInput`
@@ -1196,15 +1205,14 @@ When the user completes the dialog, the user inputted text will be available (or
1196
1205
 
1197
1206
 
1198
1207
  ```ts
1199
- assist({
1200
- title: 'Custom actions',
1201
- useFieldActions: (props: AssistFieldActionProps) => {
1202
- const getUserInput = useUserInput()
1203
-
1204
- return useMemo(() => [
1208
+
1209
+ function useExampleFieldActions(props: AssistFieldActionProps) {
1210
+ const getUserInput = useUserInput()
1211
+
1212
+ return useMemo(
1213
+ () => [
1205
1214
  defineAssistFieldAction({
1206
1215
  title: 'Do something with user input',
1207
- icon: ActionIcon,
1208
1216
  onAction: async () => {
1209
1217
  const inputResult = await getUserInput({
1210
1218
  title: 'What do you want to do?', // dialog title
@@ -1216,20 +1224,22 @@ assist({
1216
1224
  {
1217
1225
  id: 'facts',
1218
1226
  title: 'Facts',
1219
- description: 'Provide additional facts that will be used by the action'
1227
+ description: 'Provide additional facts that will be used by the action',
1220
1228
  },
1221
1229
  ],
1222
1230
  })
1223
- if(!inputResult) {
1231
+ if (!inputResult) {
1224
1232
  return // user closed the dialog
1225
1233
  }
1226
1234
 
1227
1235
  //use the result from each input
1228
1236
  //const [{result: topic}, {result: facts}] = inputResult
1229
1237
  },
1230
- })], [getUserInput])
1231
- }
1232
- })
1238
+ }),
1239
+ ],
1240
+ [getUserInput],
1241
+ )
1242
+ }
1233
1243
  ```
1234
1244
 
1235
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
@@ -451,6 +458,14 @@ declare type InlinePromptBlock = PortableTextSpan | FieldRef | UserInputBlock |
451
458
 
452
459
  declare const instructionContextTypeName: 'sanity.assist.instruction.context'
453
460
 
461
+ /**
462
+ * Returns true if the `schemaType` or any of its parent types (`schemaType.type`)` has `name` equal
463
+ * to `typeName`.
464
+ *
465
+ * Useful for checking if `schemaType` is a type alias of `ìmage`, `code` or similar.
466
+ */
467
+ export declare function isType(schemaType: SchemaType, typeName: string): boolean
468
+
454
469
  export declare interface Language {
455
470
  id: string
456
471
  title?: string
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
@@ -451,6 +458,14 @@ declare type InlinePromptBlock = PortableTextSpan | FieldRef | UserInputBlock |
451
458
 
452
459
  declare const instructionContextTypeName: 'sanity.assist.instruction.context'
453
460
 
461
+ /**
462
+ * Returns true if the `schemaType` or any of its parent types (`schemaType.type`)` has `name` equal
463
+ * to `typeName`.
464
+ *
465
+ * Useful for checking if `schemaType` is a type alias of `ìmage`, `code` or similar.
466
+ */
467
+ export declare function isType(schemaType: SchemaType, typeName: string): boolean
468
+
454
469
  export declare interface Language {
455
470
  id: string
456
471
  title?: string
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;
@@ -4236,6 +4240,7 @@ export {
4236
4240
  defineAssistFieldAction,
4237
4241
  defineAssistFieldActionGroup,
4238
4242
  defineFieldActionDivider,
4243
+ isType,
4239
4244
  useUserInput
4240
4245
  };
4241
4246
  //# sourceMappingURL=index.esm.js.map