@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 +178 -175
- package/dist/index.d.mts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.esm.js +9 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/fieldActions/customFieldActions.tsx +26 -10
- package/src/helpers/misc.ts +4 -0
- package/src/plugin.tsx +4 -1
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
|
-
- [
|
|
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
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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
|
-
### `
|
|
988
|
+
### `useExampleFieldActions`
|
|
975
989
|
|
|
976
|
-
`
|
|
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
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
return
|
|
1069
|
-
|
|
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
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
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
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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 `
|
|
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 `
|
|
1163
|
+
By default, any actions returned by `useExampleFieldActions` will be grouped under `title`.
|
|
1156
1164
|
```ts
|
|
1157
|
-
|
|
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 `
|
|
1171
|
-
If `
|
|
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
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2900
|
-
};
|
|
2903
|
+
children
|
|
2904
|
+
} : void 0;
|
|
2901
2905
|
case "divider":
|
|
2902
2906
|
default:
|
|
2903
2907
|
return node;
|