@sanity/assist 4.3.2 → 4.4.1

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.2",
3
+ "version": "4.4.1",
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(
@@ -92,6 +92,8 @@ export interface AssistFieldActionProps {
92
92
  * },
93
93
  * //...
94
94
  * })
95
+ *
96
+ * ```
95
97
  */
96
98
  schemaId: string
97
99
 
@@ -142,6 +144,16 @@ export interface AssistFieldActionProps {
142
144
  * ```
143
145
  */
144
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
145
157
  }
146
158
 
147
159
  export type AssistFieldActionNode =
@@ -23,7 +23,11 @@ export function isAssistSupported(type: SchemaType) {
23
23
 
24
24
  if (type.jsonType === 'object') {
25
25
  const unsupportedObject = type.fields.every((field) => isDisabled(field.type))
26
- return !unsupportedObject
26
+ return (
27
+ !unsupportedObject ||
28
+ /* to allow attaching custom actions on fieldless images */
29
+ isType(type, 'image')
30
+ )
27
31
  }
28
32
  return true
29
33
  }
@@ -123,7 +123,7 @@ describe('serializeSchema', () => {
123
123
  expect(serializedTypes).toEqual([])
124
124
  })
125
125
 
126
- test('should not serialize excluded fields or types or types with every member excluded', () => {
126
+ test('should not serialize excluded fields or types or types with every member excluded (except images)', () => {
127
127
  const options: AssistOptions = {aiAssist: {exclude: true}}
128
128
 
129
129
  const schema = Schema.compile({
@@ -131,7 +131,7 @@ describe('serializeSchema', () => {
131
131
  types: [
132
132
  {
133
133
  type: 'document',
134
- name: 'allFieldsExcluded',
134
+ name: 'mostFieldsExcluded',
135
135
  fields: [
136
136
  defineField({type: 'string', name: 'title', options}),
137
137
  defineField({
@@ -148,7 +148,7 @@ describe('serializeSchema', () => {
148
148
  of: [{type: 'object', name: 'remove', options}, {type: 'excluded'}],
149
149
  options: {aiAssist: {exclude: true}},
150
150
  }),
151
- //image without extra fields should be excluded
151
+ //image without extra fields should NOT be excluded
152
152
  defineField({type: 'image', name: 'image'}),
153
153
  defineField({type: 'file', name: 'file'}),
154
154
  defineField({type: 'reference', name: 'reference', to: [{type: 'excluded'}]}),
@@ -174,7 +174,21 @@ describe('serializeSchema', () => {
174
174
  const serializedTypes = serializeSchema(schema, {leanFormat: true})
175
175
 
176
176
  //everything excluded directly or indirectly
177
- expect(serializedTypes).toEqual([])
177
+ expect(serializedTypes).toEqual([
178
+ {
179
+ fields: [
180
+ {
181
+ fields: [],
182
+ name: 'image',
183
+ title: 'Image',
184
+ type: 'image',
185
+ },
186
+ ],
187
+ name: 'mostFieldsExcluded',
188
+ title: 'Most Fields Excluded',
189
+ type: 'document',
190
+ },
191
+ ])
178
192
  })
179
193
 
180
194
  test('should serialize opt-in inline object using custom typeName', () => {
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