@sanity/assist 1.1.4 → 1.2.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/dist/index.esm.js +4155 -559
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +4314 -715
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/assistDocument/components/AssistDocumentForm.tsx +16 -10
- package/src/assistDocument/components/InstructionsArrayField.tsx +1 -1
- package/src/assistDocument/components/instruction/FieldRefInput.tsx +16 -1
- package/src/assistInspector/AssistInspector.tsx +50 -27
- package/src/assistInspector/FieldAutocomplete.tsx +49 -29
- package/src/assistInspector/helpers.ts +84 -4
- package/src/fieldActions/assistFieldActions.tsx +16 -5
- package/src/helpers/useAssistSupported.ts +1 -4
- package/src/schemas/assistDocumentSchema.tsx +1 -1
- package/src/useApiClient.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/assist",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"react": "^18.2.0",
|
|
82
82
|
"react-dom": "^18.2.0",
|
|
83
83
|
"rimraf": "^4.4.0",
|
|
84
|
-
"sanity": "^3.16.
|
|
84
|
+
"sanity": "^3.16.7",
|
|
85
85
|
"semantic-release": "^21.0.5",
|
|
86
86
|
"styled-components": "^5.3.9",
|
|
87
87
|
"typescript": "^5.1.3",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AssistDocument,
|
|
3
|
+
AssistField,
|
|
2
4
|
assistFieldTypeName,
|
|
3
5
|
AssistInspectorRouteParams,
|
|
4
|
-
AssistDocument,
|
|
5
6
|
documentRootKey,
|
|
6
7
|
fieldPathParam,
|
|
7
|
-
AssistField,
|
|
8
8
|
instructionParam,
|
|
9
9
|
} from '../../types'
|
|
10
|
-
import {useEffect, useMemo, useRef} from 'react'
|
|
10
|
+
import {createContext, useContext, useEffect, useMemo, useRef} from 'react'
|
|
11
11
|
import {
|
|
12
12
|
FormCallbacksProvider,
|
|
13
13
|
FormCallbacksValue,
|
|
@@ -33,6 +33,8 @@ import {documentTypeFromAiDocumentId} from '../../helpers/ids'
|
|
|
33
33
|
|
|
34
34
|
const EMPTY_FIELDS: AssistField[] = []
|
|
35
35
|
|
|
36
|
+
export const TypePathContext = createContext<string | undefined>(undefined)
|
|
37
|
+
|
|
36
38
|
export function AssistDocumentForm(props: ObjectInputProps) {
|
|
37
39
|
const {onChange} = props
|
|
38
40
|
const value = props.value as AssistDocument | undefined
|
|
@@ -48,12 +50,15 @@ export function AssistDocumentForm(props: ObjectInputProps) {
|
|
|
48
50
|
|
|
49
51
|
const {params, setParams} = useAiPaneRouter()
|
|
50
52
|
const pathKey = params[fieldPathParam]
|
|
53
|
+
const typePath = useContext(TypePathContext)
|
|
51
54
|
const instruction = params[instructionParam]
|
|
52
55
|
|
|
53
|
-
const activeKey = useMemo(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
const activeKey = useMemo(() => {
|
|
57
|
+
if (!typePath) {
|
|
58
|
+
return undefined
|
|
59
|
+
}
|
|
60
|
+
return (fields ?? EMPTY_FIELDS).find((f) => f.path === typePath)?._key
|
|
61
|
+
}, [fields, typePath])
|
|
57
62
|
|
|
58
63
|
const activePath: Path | undefined = useMemo(() => {
|
|
59
64
|
if (!activeKey) {
|
|
@@ -72,6 +77,7 @@ export function AssistDocumentForm(props: ObjectInputProps) {
|
|
|
72
77
|
}, [schema, targetDocumentType])
|
|
73
78
|
|
|
74
79
|
const fieldSchema = useSelectedSchema(pathKey, documentSchema)
|
|
80
|
+
|
|
75
81
|
const context: SelectedFieldContextValue = useMemo(
|
|
76
82
|
() => ({
|
|
77
83
|
documentSchema,
|
|
@@ -88,7 +94,7 @@ export function AssistDocumentForm(props: ObjectInputProps) {
|
|
|
88
94
|
}
|
|
89
95
|
}, [title, documentSchema, onChange, id])
|
|
90
96
|
|
|
91
|
-
const fieldExists = !!fields?.some((f) => f._key ===
|
|
97
|
+
const fieldExists = !!fields?.some((f) => f._key === typePath)
|
|
92
98
|
|
|
93
99
|
const {onPathOpen, ...formCallbacks} = useFormCallbacks()
|
|
94
100
|
|
|
@@ -122,8 +128,8 @@ export function AssistDocumentForm(props: ObjectInputProps) {
|
|
|
122
128
|
<SelectedFieldContextProvider value={context}>
|
|
123
129
|
<Stack space={5}>
|
|
124
130
|
<FieldsInitializer
|
|
125
|
-
key={
|
|
126
|
-
pathKey={
|
|
131
|
+
key={typePath}
|
|
132
|
+
pathKey={typePath}
|
|
127
133
|
activePath={activePath}
|
|
128
134
|
fieldExists={fieldExists}
|
|
129
135
|
onChange={onChange}
|
|
@@ -3,9 +3,12 @@ import {useCallback, useContext, useEffect, useId, useRef} from 'react'
|
|
|
3
3
|
import {Box} from '@sanity/ui'
|
|
4
4
|
import {SelectedFieldContext} from '../SelectedFieldContext'
|
|
5
5
|
import {FieldAutocomplete} from '../../../assistInspector/FieldAutocomplete'
|
|
6
|
+
import {FieldRef} from '../../../assistInspector/helpers'
|
|
7
|
+
import {TypePathContext} from '../AssistDocumentForm'
|
|
6
8
|
|
|
7
9
|
export function FieldRefPathInput(props: StringInputProps) {
|
|
8
10
|
const documentSchema = useContext(SelectedFieldContext)?.documentSchema
|
|
11
|
+
const typePath = useContext(TypePathContext)
|
|
9
12
|
const ref = useRef<HTMLDivElement>(null)
|
|
10
13
|
const id = useId()
|
|
11
14
|
const {onChange} = props
|
|
@@ -16,17 +19,29 @@ export function FieldRefPathInput(props: StringInputProps) {
|
|
|
16
19
|
|
|
17
20
|
const onSelect = useCallback((path: string) => onChange(set(path)), [onChange])
|
|
18
21
|
|
|
22
|
+
const filter = useCallback(
|
|
23
|
+
(field: FieldRef) => {
|
|
24
|
+
if (!field.key.includes('|') || !typePath) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
const dotSplit = typePath.split('.')
|
|
28
|
+
const base = dotSplit.slice(0, dotSplit.length - 1).join('.')
|
|
29
|
+
return field.key.includes(base)
|
|
30
|
+
},
|
|
31
|
+
[typePath]
|
|
32
|
+
)
|
|
19
33
|
if (!documentSchema) {
|
|
20
34
|
return props.renderDefault(props)
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
return (
|
|
24
|
-
<Box flex={1} style={{minWidth:
|
|
38
|
+
<Box flex={1} style={{minWidth: 300}} ref={ref}>
|
|
25
39
|
<FieldAutocomplete
|
|
26
40
|
id={id}
|
|
27
41
|
schemaType={documentSchema}
|
|
28
42
|
onSelect={onSelect}
|
|
29
43
|
fieldPath={props.value}
|
|
44
|
+
filter={filter}
|
|
30
45
|
/>
|
|
31
46
|
</Box>
|
|
32
47
|
)
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from 'sanity/desk'
|
|
16
16
|
import {DocumentForm} from '../_lib/form'
|
|
17
17
|
import {assistDocumentTypeName, fieldPathParam, instructionParam} from '../types'
|
|
18
|
-
import {getFieldTitle, useAiPaneRouter, useSelectedField} from './helpers'
|
|
18
|
+
import {FieldRef, getFieldTitle, useAiPaneRouter, useSelectedField, useTypePath} from './helpers'
|
|
19
19
|
import styled from 'styled-components'
|
|
20
20
|
import {useStudioAssistDocument} from '../assistDocument/hooks/useStudioAssistDocument'
|
|
21
21
|
import {InstructionTaskHistoryButton} from './InstructionTaskHistoryButton'
|
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
} from '../assistDocument/RequestRunInstructionProvider'
|
|
30
30
|
import {InspectorOnboarding} from '../onboarding/InspectorOnboarding'
|
|
31
31
|
import {inspectorOnboardingKey, useOnboardingFeature} from '../onboarding/onboardingStore'
|
|
32
|
+
import {TypePathContext} from '../assistDocument/components/AssistDocumentForm'
|
|
33
|
+
import {FieldTitle} from './FieldAutocomplete'
|
|
32
34
|
|
|
33
35
|
const CardWithShadowBelow = styled(Card)`
|
|
34
36
|
position: relative;
|
|
@@ -191,7 +193,13 @@ export function AssistInspector(props: DocumentInspectorProps) {
|
|
|
191
193
|
const pathKey = params?.[fieldPathParam]
|
|
192
194
|
const instructionKey = params?.[instructionParam]
|
|
193
195
|
const documentPane = useDocumentPane()
|
|
194
|
-
const {
|
|
196
|
+
const {
|
|
197
|
+
documentId,
|
|
198
|
+
documentType,
|
|
199
|
+
value: docValue,
|
|
200
|
+
schemaType,
|
|
201
|
+
onChange: documentOnChange,
|
|
202
|
+
} = documentPane
|
|
195
203
|
const {published, draft} = useEditState(documentId, documentType, 'low')
|
|
196
204
|
|
|
197
205
|
const assistableDocId = getAssistableDocId(schemaType, documentId)
|
|
@@ -199,12 +207,14 @@ export function AssistInspector(props: DocumentInspectorProps) {
|
|
|
199
207
|
documentOnChange,
|
|
200
208
|
isDocAssistable: isDocAssistable(schemaType, published, draft),
|
|
201
209
|
})
|
|
202
|
-
|
|
210
|
+
|
|
211
|
+
const typePath = useTypePath(docValue, pathKey ?? '')
|
|
212
|
+
const selectedField = useSelectedField(schemaType, typePath)
|
|
203
213
|
|
|
204
214
|
const aiDocId = assistDocumentId(documentType)
|
|
205
215
|
|
|
206
216
|
const assistDocument = useStudioAssistDocument({documentId, schemaType})
|
|
207
|
-
const assistField = assistDocument?.fields?.find((f) => f.path ===
|
|
217
|
+
const assistField = assistDocument?.fields?.find((f) => f.path === typePath)
|
|
208
218
|
const instruction = assistField?.instructions?.find((i) => i._key === instructionKey)
|
|
209
219
|
const tasks = useMemo(
|
|
210
220
|
() =>
|
|
@@ -244,13 +254,15 @@ export function AssistInspector(props: DocumentInspectorProps) {
|
|
|
244
254
|
() =>
|
|
245
255
|
instruction &&
|
|
246
256
|
pathKey &&
|
|
257
|
+
typePath &&
|
|
247
258
|
requestRunInstruction({
|
|
248
259
|
documentId: assistableDocId,
|
|
249
260
|
path: pathKey,
|
|
261
|
+
typePath,
|
|
250
262
|
assistDocumentId: assistDocumentId(documentType),
|
|
251
263
|
instruction,
|
|
252
264
|
}),
|
|
253
|
-
[instruction,
|
|
265
|
+
[pathKey, instruction, typePath, documentType, assistableDocId, requestRunInstruction]
|
|
254
266
|
)
|
|
255
267
|
|
|
256
268
|
const Region = useCallback((_props: any) => {
|
|
@@ -282,7 +294,11 @@ export function AssistInspector(props: DocumentInspectorProps) {
|
|
|
282
294
|
sizing="border"
|
|
283
295
|
style={{lineHeight: 0}}
|
|
284
296
|
>
|
|
285
|
-
<AiInspectorHeader
|
|
297
|
+
<AiInspectorHeader
|
|
298
|
+
onClose={props.onClose}
|
|
299
|
+
field={selectedField}
|
|
300
|
+
fieldTitle={getFieldTitle(selectedField)}
|
|
301
|
+
/>
|
|
286
302
|
|
|
287
303
|
<Card as={Region} flex={1} overflow="auto">
|
|
288
304
|
<Flex direction="column" style={{minHeight: '100%'}}>
|
|
@@ -290,19 +306,21 @@ export function AssistInspector(props: DocumentInspectorProps) {
|
|
|
290
306
|
<PresenceOverlay>
|
|
291
307
|
<Box padding={4}>
|
|
292
308
|
{selectedField && (
|
|
293
|
-
<
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
<DocumentPaneProvider
|
|
298
|
-
paneKey={documentPane.paneKey}
|
|
299
|
-
index={documentPane.index}
|
|
300
|
-
itemId="ai"
|
|
301
|
-
pane={paneNode}
|
|
309
|
+
<TypePathContext.Provider value={typePath}>
|
|
310
|
+
<VirtualizerScrollInstanceProvider
|
|
311
|
+
scrollElement={boundary.current}
|
|
312
|
+
containerElement={boundary}
|
|
302
313
|
>
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
314
|
+
<DocumentPaneProvider
|
|
315
|
+
paneKey={documentPane.paneKey}
|
|
316
|
+
index={documentPane.index}
|
|
317
|
+
itemId="ai"
|
|
318
|
+
pane={paneNode}
|
|
319
|
+
>
|
|
320
|
+
<DocumentForm />
|
|
321
|
+
</DocumentPaneProvider>
|
|
322
|
+
</VirtualizerScrollInstanceProvider>
|
|
323
|
+
</TypePathContext.Provider>
|
|
306
324
|
)}
|
|
307
325
|
</Box>
|
|
308
326
|
</PresenceOverlay>
|
|
@@ -323,7 +341,6 @@ export function AssistInspector(props: DocumentInspectorProps) {
|
|
|
323
341
|
</Box>
|
|
324
342
|
</Flex>
|
|
325
343
|
</Card>
|
|
326
|
-
|
|
327
344
|
<CardWithShadowAbove flex="none" paddingX={4} paddingY={3} style={{justifySelf: 'flex-end'}}>
|
|
328
345
|
<Flex gap={2} flex={1} justify="flex-end">
|
|
329
346
|
{schemaType?.name && pathKey && instructionKey && (
|
|
@@ -352,22 +369,28 @@ export function AssistInspector(props: DocumentInspectorProps) {
|
|
|
352
369
|
)
|
|
353
370
|
}
|
|
354
371
|
|
|
355
|
-
function AiInspectorHeader(props: {fieldTitle: string; onClose: () => void}) {
|
|
356
|
-
const {onClose, fieldTitle} = props
|
|
372
|
+
function AiInspectorHeader(props: {fieldTitle: string; field?: FieldRef; onClose: () => void}) {
|
|
373
|
+
const {onClose, field, fieldTitle} = props
|
|
357
374
|
const {showOnboarding, dismissOnboarding} = useOnboardingFeature(inspectorOnboardingKey)
|
|
358
375
|
|
|
359
376
|
return (
|
|
360
377
|
<CardWithShadowBelow flex="none" padding={2}>
|
|
361
378
|
<Flex flex={1} align="center">
|
|
362
379
|
<Flex flex={1} padding={3} gap={2} align="center">
|
|
363
|
-
<Flex gap={1} align="center">
|
|
364
|
-
<
|
|
365
|
-
AI instructions for
|
|
366
|
-
</Text>
|
|
367
|
-
<Card radius={2} border padding={1} style={{margin: '-4px 0'}}>
|
|
380
|
+
<Flex gap={1} align="center" wrap="wrap" style={{marginTop: '-4px'}}>
|
|
381
|
+
<Box marginTop={1}>
|
|
368
382
|
<Text size={1} weight="semibold">
|
|
369
|
-
|
|
383
|
+
AI instructions for
|
|
370
384
|
</Text>
|
|
385
|
+
</Box>
|
|
386
|
+
<Card radius={2} border padding={1} marginTop={1}>
|
|
387
|
+
{field ? (
|
|
388
|
+
<FieldTitle field={field} />
|
|
389
|
+
) : (
|
|
390
|
+
<Text size={1} weight="semibold">
|
|
391
|
+
{fieldTitle}
|
|
392
|
+
</Text>
|
|
393
|
+
)}
|
|
371
394
|
</Card>
|
|
372
395
|
</Flex>
|
|
373
396
|
</Flex>
|
|
@@ -11,25 +11,29 @@ interface FieldSelectorProps {
|
|
|
11
11
|
fieldPath?: string
|
|
12
12
|
onSelect: (path: string) => void
|
|
13
13
|
includeDocument?: boolean
|
|
14
|
+
filter?: (field: FieldRef) => boolean
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export function FieldAutocomplete(props: FieldSelectorProps) {
|
|
17
|
-
const {id, schemaType, fieldPath, onSelect, includeDocument} = props
|
|
18
|
+
const {id, schemaType, fieldPath, onSelect, includeDocument, filter} = props
|
|
18
19
|
|
|
19
|
-
const
|
|
20
|
+
const fieldRefs = useMemo(() => {
|
|
20
21
|
if (includeDocument) {
|
|
21
22
|
return getFieldRefsWithDocument(schemaType)
|
|
22
23
|
}
|
|
23
24
|
return getFieldRefs(schemaType)
|
|
24
25
|
}, [schemaType, includeDocument])
|
|
25
26
|
const currentField = useMemo(
|
|
26
|
-
() =>
|
|
27
|
-
[fieldPath,
|
|
27
|
+
() => fieldRefs.find((f) => f.key === fieldPath),
|
|
28
|
+
[fieldPath, fieldRefs]
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
const autocompleteOptions = useMemo(
|
|
31
|
-
() =>
|
|
32
|
-
|
|
32
|
+
() =>
|
|
33
|
+
fieldRefs
|
|
34
|
+
.filter((field) => (filter ? filter(field) : true))
|
|
35
|
+
.map((field) => ({value: field.key, field})),
|
|
36
|
+
[fieldRefs, filter]
|
|
33
37
|
)
|
|
34
38
|
|
|
35
39
|
const renderOption = useCallback((option: {value: string; field: FieldRef}) => {
|
|
@@ -55,33 +59,12 @@ export function FieldAutocomplete(props: FieldSelectorProps) {
|
|
|
55
59
|
)
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
const splitTitle = field.title.split('/')
|
|
59
62
|
return (
|
|
60
63
|
<Card as="button" padding={3} radius={1}>
|
|
61
64
|
<Flex gap={3}>
|
|
62
65
|
<Text size={1}>{createElement(field.icon)}</Text>
|
|
63
66
|
|
|
64
|
-
<
|
|
65
|
-
<Breadcrumbs
|
|
66
|
-
separator={
|
|
67
|
-
<Text muted size={1}>
|
|
68
|
-
/
|
|
69
|
-
</Text>
|
|
70
|
-
}
|
|
71
|
-
space={1}
|
|
72
|
-
>
|
|
73
|
-
{splitTitle.slice(0, splitTitle.length - 1).map((pt, i) => (
|
|
74
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
75
|
-
<Text key={i} muted size={1}>
|
|
76
|
-
{pt.trim()}
|
|
77
|
-
</Text>
|
|
78
|
-
))}
|
|
79
|
-
|
|
80
|
-
<Text size={1} weight="medium">
|
|
81
|
-
{splitTitle[splitTitle.length - 1]}
|
|
82
|
-
</Text>
|
|
83
|
-
</Breadcrumbs>
|
|
84
|
-
</Box>
|
|
67
|
+
<FieldTitle field={field} />
|
|
85
68
|
</Flex>
|
|
86
69
|
</Card>
|
|
87
70
|
)
|
|
@@ -99,7 +82,6 @@ export function FieldAutocomplete(props: FieldSelectorProps) {
|
|
|
99
82
|
)
|
|
100
83
|
}, [])
|
|
101
84
|
|
|
102
|
-
// const id = useId()
|
|
103
85
|
return (
|
|
104
86
|
<Autocomplete
|
|
105
87
|
fontSize={1}
|
|
@@ -117,3 +99,41 @@ export function FieldAutocomplete(props: FieldSelectorProps) {
|
|
|
117
99
|
/>
|
|
118
100
|
)
|
|
119
101
|
}
|
|
102
|
+
|
|
103
|
+
export function FieldTitle(props: {field: FieldRef}) {
|
|
104
|
+
const splitTitle = props.field.title.split('/')
|
|
105
|
+
return (
|
|
106
|
+
<Box flex="none">
|
|
107
|
+
<Breadcrumbs
|
|
108
|
+
style={{
|
|
109
|
+
display: 'flex',
|
|
110
|
+
flexWrap: 'wrap',
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
marginTop: '-4px',
|
|
113
|
+
}}
|
|
114
|
+
separator={
|
|
115
|
+
<Box marginTop={1}>
|
|
116
|
+
<Text muted size={1}>
|
|
117
|
+
/
|
|
118
|
+
</Text>
|
|
119
|
+
</Box>
|
|
120
|
+
}
|
|
121
|
+
space={1}
|
|
122
|
+
>
|
|
123
|
+
{splitTitle.slice(0, splitTitle.length - 1).map((pt, i) => (
|
|
124
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
125
|
+
<Box key={i} marginTop={1}>
|
|
126
|
+
<Text muted size={1}>
|
|
127
|
+
{pt.trim()}
|
|
128
|
+
</Text>
|
|
129
|
+
</Box>
|
|
130
|
+
))}
|
|
131
|
+
<Box marginTop={1}>
|
|
132
|
+
<Text size={1} weight="medium">
|
|
133
|
+
{splitTitle[splitTitle.length - 1]}
|
|
134
|
+
</Text>
|
|
135
|
+
</Box>
|
|
136
|
+
</Breadcrumbs>
|
|
137
|
+
</Box>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
@@ -7,13 +7,24 @@ import {
|
|
|
7
7
|
OlistIcon,
|
|
8
8
|
StringIcon,
|
|
9
9
|
} from '@sanity/icons'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ArraySchemaType,
|
|
12
|
+
isKeySegment,
|
|
13
|
+
isObjectSchemaType,
|
|
14
|
+
ObjectSchemaType,
|
|
15
|
+
Path,
|
|
16
|
+
pathToString,
|
|
17
|
+
SanityDocumentLike,
|
|
18
|
+
SchemaType,
|
|
19
|
+
stringToPath,
|
|
20
|
+
} from 'sanity'
|
|
11
21
|
import {ComponentType, useContext, useMemo} from 'react'
|
|
12
22
|
import {AssistInspectorRouteParams, documentRootKey, fieldPathParam} from '../types'
|
|
13
23
|
import {usePaneRouter} from 'sanity/desk'
|
|
14
24
|
import {isAssistSupported} from '../helpers/assistSupported'
|
|
15
25
|
import {isPortableTextArray, isType} from '../helpers/typeUtils'
|
|
16
26
|
import {SelectedFieldContext} from '../assistDocument/components/SelectedFieldContext'
|
|
27
|
+
import {extractWithPath} from '@sanity/mutator'
|
|
17
28
|
|
|
18
29
|
export interface FieldRef {
|
|
19
30
|
key: string
|
|
@@ -21,9 +32,10 @@ export interface FieldRef {
|
|
|
21
32
|
title: string
|
|
22
33
|
schemaType: SchemaType
|
|
23
34
|
icon: ComponentType
|
|
35
|
+
synthetic?: boolean
|
|
24
36
|
}
|
|
25
37
|
|
|
26
|
-
const maxDepth =
|
|
38
|
+
const maxDepth = 6
|
|
27
39
|
|
|
28
40
|
export function getTypeIcon(schemaType: SchemaType) {
|
|
29
41
|
let t: SchemaType | undefined = schemaType
|
|
@@ -73,7 +85,7 @@ export function getFieldRefs(
|
|
|
73
85
|
const path: Path = parent ? [...parent.path, field.name] : [field.name]
|
|
74
86
|
const title = field.type.title ?? field.name
|
|
75
87
|
const fieldRef: FieldRef = {
|
|
76
|
-
key: pathToString(path),
|
|
88
|
+
key: patchableKey(pathToString(path)),
|
|
77
89
|
path,
|
|
78
90
|
title: parent ? [parent.title, title].join(' / ') : title,
|
|
79
91
|
schemaType: field.type,
|
|
@@ -82,13 +94,81 @@ export function getFieldRefs(
|
|
|
82
94
|
const fields =
|
|
83
95
|
field.type.jsonType === 'object' ? getFieldRefs(field.type, fieldRef, depth + 1) : []
|
|
84
96
|
|
|
97
|
+
const syntheticFields =
|
|
98
|
+
field.type.jsonType === 'array' ? getSyntheticFields(field.type, fieldRef, depth + 1) : []
|
|
85
99
|
if (!isAssistSupported(field.type, true)) {
|
|
100
|
+
return [...fields, ...syntheticFields]
|
|
101
|
+
}
|
|
102
|
+
return [fieldRef, ...fields, ...syntheticFields]
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getSyntheticFields(schemaType: ArraySchemaType, parent?: FieldRef, depth = 0) {
|
|
107
|
+
if (depth >= maxDepth) {
|
|
108
|
+
return []
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return schemaType.of
|
|
112
|
+
.filter((itemType) => !isType(itemType, 'block'))
|
|
113
|
+
.flatMap((itemType) => {
|
|
114
|
+
const segment = {_key: itemType.name}
|
|
115
|
+
const title = itemType.title ?? itemType.name
|
|
116
|
+
const path: Path = parent ? [...parent.path, segment] : [segment]
|
|
117
|
+
const fieldRef: FieldRef = {
|
|
118
|
+
key: patchableKey(pathToString(path)),
|
|
119
|
+
path,
|
|
120
|
+
title: parent ? [parent.title, title].join(' / ') : title,
|
|
121
|
+
schemaType: itemType,
|
|
122
|
+
icon: getTypeIcon(itemType),
|
|
123
|
+
synthetic: true,
|
|
124
|
+
}
|
|
125
|
+
const fields =
|
|
126
|
+
itemType.jsonType === 'object' ? getFieldRefs(itemType, fieldRef, depth + 1) : []
|
|
127
|
+
|
|
128
|
+
if (!isAssistSupported(itemType, true)) {
|
|
86
129
|
return fields
|
|
87
130
|
}
|
|
88
131
|
return [fieldRef, ...fields]
|
|
89
132
|
})
|
|
90
133
|
}
|
|
91
134
|
|
|
135
|
+
export function getTypePath(doc: SanityDocumentLike, pathString: string) {
|
|
136
|
+
if (!pathString) {
|
|
137
|
+
return undefined
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const path = stringToPath(pathString)
|
|
141
|
+
const currentPath: Path = []
|
|
142
|
+
let valid = true
|
|
143
|
+
const syntheticPath = path.map((segment) => {
|
|
144
|
+
currentPath.push(segment)
|
|
145
|
+
|
|
146
|
+
if (isKeySegment(segment)) {
|
|
147
|
+
const match = extractWithPath(pathToString(currentPath), doc)[0]
|
|
148
|
+
const value = match?.value
|
|
149
|
+
if (match && value && typeof value === 'object' && '_type' in value) {
|
|
150
|
+
return {_key: value._type as string}
|
|
151
|
+
}
|
|
152
|
+
valid = false
|
|
153
|
+
}
|
|
154
|
+
return segment
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
return valid ? patchableKey(pathToString(syntheticPath)) : undefined
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* mutator crashes if path contains certain letters
|
|
162
|
+
* @param pathKey
|
|
163
|
+
*/
|
|
164
|
+
function patchableKey(pathKey: string) {
|
|
165
|
+
return pathKey.replace(/[=]=/g, ':').replace(/[[\]]/g, '|').replace(/"/g, '')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function useTypePath(doc: SanityDocumentLike, pathString: string) {
|
|
169
|
+
return useMemo(() => getTypePath(doc, pathString), [doc, pathString])
|
|
170
|
+
}
|
|
171
|
+
|
|
92
172
|
export function useSelectedField(
|
|
93
173
|
documentSchemaType?: SchemaType,
|
|
94
174
|
path?: string
|
|
@@ -115,7 +195,7 @@ export function useSelectedFieldTitle() {
|
|
|
115
195
|
|
|
116
196
|
export function getFieldTitle(field?: FieldRef) {
|
|
117
197
|
const schemaType = field?.schemaType
|
|
118
|
-
return schemaType?.title ?? schemaType?.name ?? 'Untitled'
|
|
198
|
+
return field?.title ?? schemaType?.title ?? schemaType?.name ?? 'Untitled'
|
|
119
199
|
}
|
|
120
200
|
|
|
121
201
|
export function useAiPaneRouter() {
|
|
@@ -11,7 +11,7 @@ import {pluginTitle, pluginTitleShort} from '../constants'
|
|
|
11
11
|
import {useAssistSupported} from '../helpers/useAssistSupported'
|
|
12
12
|
import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
|
|
13
13
|
import {getInstructionTitle, usePathKey} from '../helpers/misc'
|
|
14
|
-
import {fieldPathParam, instructionParam, StudioInstruction} from '../types'
|
|
14
|
+
import {documentRootKey, fieldPathParam, instructionParam, StudioInstruction} from '../types'
|
|
15
15
|
import {aiInspectorId} from '../assistInspector/constants'
|
|
16
16
|
import {getIcon} from '../assistDocument/components/instruction/appearance/IconInput'
|
|
17
17
|
import {useAssistDocumentContextValue} from '../assistDocument/hooks/useAssistDocumentContextValue'
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
} from '../assistDocument/RequestRunInstructionProvider'
|
|
22
22
|
import {PrivateIcon} from './PrivateIcon'
|
|
23
23
|
import {generateCaptionsActions} from './generateCaptionActions'
|
|
24
|
+
import {useDocumentPane} from 'sanity/desk'
|
|
25
|
+
import {useSelectedField, useTypePath} from '../assistInspector/helpers'
|
|
24
26
|
|
|
25
27
|
function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) {
|
|
26
28
|
return node
|
|
@@ -30,7 +32,6 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
30
32
|
name: 'sanity-assist-actions',
|
|
31
33
|
useAction(props) {
|
|
32
34
|
const {schemaType} = props
|
|
33
|
-
const assistSupported = useAssistSupported(props.path, schemaType)
|
|
34
35
|
|
|
35
36
|
const isDocumentLevel = props.path.length === 0
|
|
36
37
|
|
|
@@ -54,9 +55,11 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
54
55
|
: // eslint-disable-next-line react-hooks/rules-of-hooks
|
|
55
56
|
useAssistDocumentContext()
|
|
56
57
|
|
|
58
|
+
const {value: docValue} = useDocumentPane()
|
|
57
59
|
const currentUser = useCurrentUser()
|
|
58
60
|
const isHidden = !assistDocument
|
|
59
61
|
const pathKey = usePathKey(props.path)
|
|
62
|
+
const typePath = useTypePath(docValue, pathKey)
|
|
60
63
|
const assistDocumentId = assistDocument?._id
|
|
61
64
|
|
|
62
65
|
const assistableDocId = getAssistableDocId(documentSchemaType, documentId)
|
|
@@ -65,10 +68,17 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
65
68
|
isDocAssistable: documentIsAssistable ?? false,
|
|
66
69
|
})
|
|
67
70
|
|
|
71
|
+
const isSelectable = !!useSelectedField(documentSchemaType, typePath)
|
|
72
|
+
const assistSupported = useAssistSupported(props.path, schemaType) && isSelectable
|
|
73
|
+
|
|
68
74
|
const fieldAssist = useMemo(
|
|
69
|
-
() =>
|
|
70
|
-
|
|
75
|
+
() =>
|
|
76
|
+
(assistDocument?.fields ?? []).find(
|
|
77
|
+
(f) => f.path === typePath || (pathKey === documentRootKey && f.path === pathKey)
|
|
78
|
+
),
|
|
79
|
+
[assistDocument?.fields, pathKey, typePath]
|
|
71
80
|
)
|
|
81
|
+
|
|
72
82
|
const fieldAssistKey = fieldAssist?._key
|
|
73
83
|
const isInspectorOpen = inspector?.name === aiInspectorId
|
|
74
84
|
const isPathSelected = pathKey === selectedPath
|
|
@@ -96,10 +106,11 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
96
106
|
documentId: assistableDocId,
|
|
97
107
|
assistDocumentId,
|
|
98
108
|
path: pathKey,
|
|
109
|
+
typePath,
|
|
99
110
|
instruction,
|
|
100
111
|
})
|
|
101
112
|
},
|
|
102
|
-
[requestRunInstruction, assistableDocId, pathKey, assistDocumentId, fieldAssistKey]
|
|
113
|
+
[requestRunInstruction, assistableDocId, pathKey, typePath, assistDocumentId, fieldAssistKey]
|
|
103
114
|
)
|
|
104
115
|
|
|
105
116
|
const privateInstructions = useMemo(
|
|
@@ -3,8 +3,5 @@ import {useMemo} from 'react'
|
|
|
3
3
|
import {isAssistSupported} from './assistSupported'
|
|
4
4
|
|
|
5
5
|
export function useAssistSupported(path: Path, schemaType: SchemaType) {
|
|
6
|
-
return useMemo(
|
|
7
|
-
() => path.every((p) => typeof p === 'string') && isAssistSupported(schemaType),
|
|
8
|
-
[path, schemaType]
|
|
9
|
-
)
|
|
6
|
+
return useMemo(() => isAssistSupported(schemaType), [schemaType])
|
|
10
7
|
}
|
|
@@ -367,7 +367,7 @@ export const fieldInstructions = defineType({
|
|
|
367
367
|
|
|
368
368
|
export const assistDocumentSchema = defineType({
|
|
369
369
|
//NOTE: this is a document type. Using object here ensures it does not appear in structure menus
|
|
370
|
-
type: '
|
|
370
|
+
type: 'document',
|
|
371
371
|
//workaround for using object and not document
|
|
372
372
|
...({liveEdit: true} as any),
|
|
373
373
|
name: assistDocumentTypeName,
|