@sanity/assist 4.3.1 → 4.4.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/assist",
3
- "version": "4.3.1",
3
+ "version": "4.4.0",
4
4
  "description": "You create the instructions; Sanity AI Assist does the rest.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -65,7 +65,7 @@
65
65
  "@rollup/plugin-image": "^3.0.3",
66
66
  "@sanity/pkg-utils": "^6.13.4",
67
67
  "@sanity/plugin-kit": "^3.1.10",
68
- "@sanity/schema": "^3.91.0",
68
+ "@sanity/schema": "^3.93.0",
69
69
  "@sanity/semantic-release-preset": "^4.1.7",
70
70
  "@types/lodash": "^4.17.0",
71
71
  "@types/lodash-es": "^4.17.12",
@@ -83,7 +83,7 @@
83
83
  "react": "^18.2.0",
84
84
  "react-dom": "^18.2.0",
85
85
  "rimraf": "^5.0.5",
86
- "sanity": "^3.91.0",
86
+ "sanity": "^3.93.0",
87
87
  "semantic-release": "^23.0.8",
88
88
  "styled-components": "^6.1.16",
89
89
  "typescript": "^5.7.2",
@@ -2,6 +2,7 @@ import {createContext, useContext} from 'react'
2
2
  import {DocumentInspector, ObjectSchemaType, PatchEvent} from 'sanity'
3
3
 
4
4
  import {InstructionTask, StudioAssistDocument} from '../types'
5
+ import {FieldRef} from '../assistInspector/helpers'
5
6
 
6
7
  export type AssistDocumentContextValue = (
7
8
  | {assistDocument: StudioAssistDocument; loading: false}
@@ -32,6 +33,9 @@ export type AssistDocumentContextValue = (
32
33
  syntheticTasks?: InstructionTask[]
33
34
  addSyntheticTask: (syntheticTask: InstructionTask) => void
34
35
  removeSyntheticTask: (syntheticTask: InstructionTask) => void
36
+
37
+ fieldRefs: FieldRef[]
38
+ fieldRefsByTypePath: Record<string, FieldRef | undefined>
35
39
  }
36
40
 
37
41
  export const AssistDocumentContext = createContext<AssistDocumentContextValue | undefined>(
@@ -2,7 +2,7 @@ import {useCallback, useEffect, useMemo, useState} from 'react'
2
2
  import {getDraftId, getVersionId, type ObjectSchemaType, useSchema} from 'sanity'
3
3
  import {useDocumentPane} from 'sanity/structure'
4
4
 
5
- import {useAiPaneRouter} from '../../assistInspector/helpers'
5
+ import {asFieldRefsByTypePath, getFieldRefs, useAiPaneRouter} from '../../assistInspector/helpers'
6
6
  import {fieldPathParam, InstructionTask} from '../../types'
7
7
  import type {AssistDocumentContextValue} from '../AssistDocumentContext'
8
8
  import {isDocAssistable} from '../RequestRunInstructionProvider'
@@ -53,6 +53,10 @@ export function useAssistDocumentContextValue(documentId: string, documentType:
53
53
  })
54
54
  const {syntheticTasks, addSyntheticTask, removeSyntheticTask} =
55
55
  useSyntheticTasks(assistableDocumentId)
56
+
57
+ const fieldRefs = getFieldRefs(documentSchemaType)
58
+ const fieldRefsByTypePath = asFieldRefsByTypePath(fieldRefs)
59
+
56
60
  const value: AssistDocumentContextValue = useMemo(() => {
57
61
  const base = {
58
62
  assistableDocumentId,
@@ -67,6 +71,8 @@ export function useAssistDocumentContextValue(documentId: string, documentType:
67
71
  syntheticTasks,
68
72
  addSyntheticTask,
69
73
  removeSyntheticTask,
74
+ fieldRefs,
75
+ fieldRefsByTypePath,
70
76
  }
71
77
  if (!assistDocument) {
72
78
  return {...base, loading: true, assistDocument: undefined}
@@ -57,6 +57,14 @@ export function getTypeIcon(schemaType: SchemaType) {
57
57
  return DocumentIcon
58
58
  }
59
59
 
60
+ export function asFieldRefsByTypePath(fieldRefs: FieldRef[]): Record<string, FieldRef | undefined> {
61
+ const lookup: Record<string, FieldRef | undefined> = fieldRefs.reduce(
62
+ (acc, ref) => ({...acc, [ref.key]: ref}),
63
+ {},
64
+ )
65
+ return lookup
66
+ }
67
+
60
68
  export function getFieldRefsWithDocument(schemaType: ObjectSchemaType): FieldRef[] {
61
69
  const fields = getFieldRefs(schemaType)
62
70
  return [
@@ -75,6 +75,7 @@ export function RunInstructionProvider(props: PropsWithChildren<{}>) {
75
75
  message: input.title,
76
76
  description: input.description,
77
77
  }))
78
+
78
79
  if (!userInputBlocks.length) {
79
80
  return undefined
80
81
  }
@@ -4,6 +4,7 @@ import {
4
4
  type DocumentFieldAction,
5
5
  type DocumentFieldActionGroup,
6
6
  type DocumentFieldActionItem,
7
+ pathToString,
7
8
  stringToPath,
8
9
  typed,
9
10
  useCurrentUser,
@@ -14,7 +15,7 @@ import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
14
15
  import {getIcon} from '../assistDocument/components/instruction/appearance/IconInput'
15
16
  import {useRequestRunInstruction} from '../assistDocument/RequestRunInstructionProvider'
16
17
  import {aiInspectorId} from '../assistInspector/constants'
17
- import {useSelectedField, useTypePath} from '../assistInspector/helpers'
18
+ import {getTypePath, useSelectedField, useTypePath} from '../assistInspector/helpers'
18
19
  import {pluginTitleShort} from '../constants'
19
20
  import {isSchemaAssistEnabled} from '../helpers/assistSupported'
20
21
  import {getConditionalMembers} from '../helpers/conditionalMembers'
@@ -48,6 +49,7 @@ export const assistFieldActions: DocumentFieldAction = {
48
49
  documentSchemaType,
49
50
  selectedPath,
50
51
  assistableDocumentId,
52
+ fieldRefsByTypePath,
51
53
  } = useAssistDocumentContext()
52
54
 
53
55
  const {value: docValue, formState} = useDocumentPane()
@@ -204,6 +206,17 @@ export const assistFieldActions: DocumentFieldAction = {
204
206
  )
205
207
  }, [])
206
208
 
209
+ const parentSchemaType = useMemo(() => {
210
+ if (!props.path.length) {
211
+ return undefined
212
+ } else if (props.path.length === 1) {
213
+ return documentSchemaType
214
+ }
215
+ const parentPath = props.path.slice(0, -1)
216
+ const typePath = getTypePath(docValueRef.current, pathToString(parentPath))
217
+ return typePath ? fieldRefsByTypePath[typePath]?.schemaType : undefined
218
+ }, [fieldRefsByTypePath, props.path, documentSchemaType])
219
+
207
220
  const customActions = useCustomFieldActions({
208
221
  actionType: props.path.length ? 'field' : 'document',
209
222
  documentIdForAction: assistableDocumentId,
@@ -212,6 +225,7 @@ export const assistFieldActions: DocumentFieldAction = {
212
225
  path: props.path,
213
226
  getDocumentValue,
214
227
  getConditionalPaths,
228
+ parentSchemaType,
215
229
  })
216
230
 
217
231
  const manageInstructionsItem = useMemo(
@@ -20,6 +20,7 @@ import {
20
20
  instructionTaskTypeName,
21
21
  } from '../types'
22
22
  import {randomKey} from '../_lib/randomKey'
23
+ import {isDefined} from '../helpers/misc'
23
24
 
24
25
  export interface AgentActionConditionalPath {
25
26
  path: AgentActionPath
@@ -91,6 +92,8 @@ export interface AssistFieldActionProps {
91
92
  * },
92
93
  * //...
93
94
  * })
95
+ *
96
+ * ```
94
97
  */
95
98
  schemaId: string
96
99
 
@@ -141,6 +144,16 @@ export interface AssistFieldActionProps {
141
144
  * ```
142
145
  */
143
146
  schemaType: SchemaType
147
+
148
+ /**
149
+ * Schema type of the parent field or array item holding this field.
150
+ *
151
+ * This can be undefined if the action was unable to resolve the parent type is excluded from AI Assist.
152
+ *
153
+ * @see schemaType
154
+ * @see documentSchemaType
155
+ */
156
+ parentSchemaType?: SchemaType
144
157
  }
145
158
 
146
159
  export type AssistFieldActionNode =
@@ -159,7 +172,11 @@ export type AssistFieldActionGroup = Omit<
159
172
  DocumentFieldActionGroup,
160
173
  'renderAsButton' | 'expanded' | 'children'
161
174
  > & {
162
- children: AssistFieldActionNode[]
175
+ /**
176
+ * `children` can include undefined entries in the action array. These will be filtered out.
177
+ * If the group has no defined children, the group will also be filtered out.
178
+ */
179
+ children: (AssistFieldActionNode | undefined)[]
163
180
  }
164
181
 
165
182
  type PushToast = (params: ToastParams) => string
@@ -206,14 +223,17 @@ export function useCustomFieldActions(
206
223
 
207
224
  return useMemo(() => {
208
225
  const title = fieldActions?.title
209
- const customActions = configActions?.map((node) => {
210
- return createSafeNode({
211
- node,
212
- pushToast,
213
- addSyntheticTask,
214
- removeSyntheticTask,
226
+ const customActions = configActions
227
+ ?.filter(isDefined)
228
+ .map((node) => {
229
+ return createSafeNode({
230
+ node,
231
+ pushToast,
232
+ addSyntheticTask,
233
+ removeSyntheticTask,
234
+ })
215
235
  })
216
- })
236
+ .filter(isDefined)
217
237
  const onlyGroups =
218
238
  customActions?.length && customActions?.every((node) => node.type === 'group')
219
239
  const groups = customActions?.length
@@ -237,17 +257,25 @@ function createSafeNode(args: {
237
257
  pushToast: PushToast
238
258
  addSyntheticTask: (task: InstructionTask) => void
239
259
  removeSyntheticTask: (task: InstructionTask) => void
240
- }): DocumentFieldActionNode {
260
+ }): DocumentFieldActionNode | undefined {
241
261
  const {node} = args
242
262
  switch (node.type) {
243
263
  case 'action':
244
264
  return createSafeAction({...args, action: node})
245
265
  case 'group':
266
+ // eslint-disable-next-line no-case-declarations
267
+ const children = node.children
268
+ ?.filter(isDefined)
269
+ .map((child) => createSafeNode({...args, node: child}))
270
+ .filter(isDefined)
271
+ if (!children?.length) {
272
+ return undefined
273
+ }
246
274
  return {
247
275
  ...node,
248
276
  renderAsButton: false,
249
277
  expanded: true,
250
- children: node.children?.map((child) => createSafeNode({...args, node: child})),
278
+ children,
251
279
  }
252
280
  case 'divider':
253
281
  default:
@@ -19,3 +19,7 @@ export function getPathKey(path: Path | string) {
19
19
  export function getInstructionTitle(instruction?: StudioInstruction) {
20
20
  return instruction?.title ?? 'Untitled'
21
21
  }
22
+
23
+ export function isDefined<T>(t: T | undefined | null): t is T {
24
+ return t !== undefined && t !== null
25
+ }
package/src/plugin.tsx CHANGED
@@ -33,7 +33,10 @@ export interface AssistPluginConfig {
33
33
 
34
34
  fieldActions?: {
35
35
  title?: string
36
- useFieldActions?: (props: AssistFieldActionProps) => AssistFieldActionNode[]
36
+ /**
37
+ * The returned array can include `undefined` entries in the action array. These will be filtered out.
38
+ */
39
+ useFieldActions?: (props: AssistFieldActionProps) => (AssistFieldActionNode | undefined)[]
37
40
  }
38
41
 
39
42
  /**
package/src/types.ts CHANGED
@@ -74,7 +74,7 @@ export interface PresetInstruction {
74
74
 
75
75
  title?: string
76
76
  /**
77
- * String key from @sanity/icons IconMap
77
+ * String key from `@sanity/icons` IconMap
78
78
  */
79
79
  icon?: string
80
80