@sanity/assist 1.2.16 → 2.0.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/LICENSE +1 -1
- package/README.md +551 -30
- package/dist/index.cjs.mjs +1 -0
- package/dist/index.d.ts +253 -11
- package/dist/index.esm.js +2385 -373
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2379 -366
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
- package/src/_lib/form/DocumentForm.tsx +2 -1
- package/src/_lib/form/constants.ts +1 -0
- package/src/assistDocument/AssistDocumentInput.tsx +24 -4
- package/src/assistDocument/RequestRunInstructionProvider.tsx +37 -21
- package/src/assistDocument/components/instruction/InstructionInput.tsx +5 -4
- package/src/assistDocument/components/instruction/InstructionOutputField.tsx +45 -0
- package/src/assistDocument/components/instruction/InstructionOutputInput.tsx +205 -0
- package/src/assistDocument/hooks/useStudioAssistDocument.ts +5 -32
- package/src/assistFormComponents/AssistField.tsx +11 -5
- package/src/assistFormComponents/AssistFormBlock.tsx +2 -3
- package/src/assistFormComponents/validation/listItem.tsx +2 -2
- package/src/assistInspector/AssistInspector.tsx +6 -0
- package/src/assistInspector/FieldAutocomplete.tsx +1 -0
- package/src/assistInspector/helpers.ts +9 -11
- package/src/assistLayout/AssistLayout.tsx +9 -9
- package/src/components/ImageContext.tsx +19 -9
- package/src/components/SafeValueInput.tsx +4 -1
- package/src/fieldActions/assistFieldActions.tsx +42 -13
- package/src/fieldActions/generateCaptionActions.tsx +2 -2
- package/src/fieldActions/generateImageActions.tsx +57 -0
- package/src/helpers/assistSupported.ts +10 -16
- package/src/helpers/conditionalMembers.test.ts +200 -0
- package/src/helpers/conditionalMembers.ts +127 -0
- package/src/helpers/typeUtils.ts +19 -5
- package/src/index.ts +3 -0
- package/src/plugin.tsx +14 -5
- package/src/schemas/assistDocumentSchema.tsx +40 -1
- package/src/schemas/serialize/serializeSchema.test.ts +239 -8
- package/src/schemas/serialize/serializeSchema.ts +77 -10
- package/src/schemas/typeDefExtensions.ts +89 -5
- package/src/translate/FieldTranslationProvider.tsx +360 -0
- package/src/translate/getLanguageParams.ts +26 -0
- package/src/translate/languageStore.ts +18 -0
- package/src/translate/paths.test.ts +133 -0
- package/src/translate/paths.ts +175 -0
- package/src/translate/translateActions.tsx +188 -0
- package/src/translate/types.ts +160 -0
- package/src/types.ts +33 -12
- package/src/useApiClient.ts +130 -2
- package/src/assistLayout/AlphaMigration.tsx +0 -310
- package/src/legacy-types.ts +0 -72
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/assist",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "You create the instructions; Sanity AI Assist does the rest.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
7
7
|
"sanity-plugin",
|
|
@@ -54,10 +54,11 @@
|
|
|
54
54
|
"release": "semantic-release"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sanity/icons": "^2.
|
|
57
|
+
"@sanity/icons": "^2.8.0",
|
|
58
58
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
59
|
-
"@sanity/ui": "^2.0.
|
|
59
|
+
"@sanity/ui": "^2.0.2",
|
|
60
60
|
"date-fns": "^2.30.0",
|
|
61
|
+
"lodash.get": "^4.4.2",
|
|
61
62
|
"react-fast-compare": "^3.2.1",
|
|
62
63
|
"react-is": "^18.2.0",
|
|
63
64
|
"rxjs": "^7.8.0",
|
|
@@ -72,8 +73,8 @@
|
|
|
72
73
|
"@sanity/semantic-release-preset": "^4.1.6",
|
|
73
74
|
"@types/react": "^18.2.37",
|
|
74
75
|
"@types/styled-components": "^5.1.30",
|
|
75
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
76
|
-
"@typescript-eslint/parser": "^
|
|
76
|
+
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
|
77
|
+
"@typescript-eslint/parser": "^7.0.1",
|
|
77
78
|
"date-fns": "^2.30.0",
|
|
78
79
|
"eslint": "^8.56.0",
|
|
79
80
|
"eslint-config-prettier": "^8.10.0",
|
|
@@ -85,15 +86,15 @@
|
|
|
85
86
|
"react": "^18.2.0",
|
|
86
87
|
"react-dom": "^18.2.0",
|
|
87
88
|
"rimraf": "^5.0.5",
|
|
88
|
-
"sanity": "^3.
|
|
89
|
+
"sanity": "^3.28.0",
|
|
89
90
|
"semantic-release": "^21.1.2",
|
|
90
91
|
"styled-components": "^6.1.1",
|
|
91
92
|
"typescript": "^5.3.3",
|
|
92
|
-
"vitest": "^
|
|
93
|
+
"vitest": "^1.2.1"
|
|
93
94
|
},
|
|
94
95
|
"peerDependencies": {
|
|
95
96
|
"react": "^18",
|
|
96
|
-
"sanity": "^3.
|
|
97
|
+
"sanity": "^3.26",
|
|
97
98
|
"styled-components": "^5.2 || ^6.0.0"
|
|
98
99
|
},
|
|
99
100
|
"engines": {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
useDocumentStore,
|
|
13
13
|
} from 'sanity'
|
|
14
14
|
import {useDocumentPane} from 'sanity/desk'
|
|
15
|
+
import {assistFormId} from './constants'
|
|
15
16
|
|
|
16
17
|
const preventDefault = (ev: React.FormEvent) => ev.preventDefault()
|
|
17
18
|
|
|
@@ -135,7 +136,7 @@ export function DocumentForm(
|
|
|
135
136
|
changed={formState.changed}
|
|
136
137
|
focused={formState.focused}
|
|
137
138
|
groups={formState.groups}
|
|
138
|
-
id=
|
|
139
|
+
id={assistFormId}
|
|
139
140
|
members={formState.members}
|
|
140
141
|
onChange={onChange}
|
|
141
142
|
onFieldGroupSelect={onSetActiveFieldGroup}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const assistFormId = 'assist'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {InputProps, ObjectInputProps} from 'sanity'
|
|
1
|
+
import {InputProps, ObjectInputProps, ObjectSchemaType} from 'sanity'
|
|
2
2
|
import {AssistDocumentContextProvider} from './AssistDocumentContextProvider'
|
|
3
3
|
import {FirstAssistedPathProvider} from '../onboarding/FirstAssistedPathProvider'
|
|
4
4
|
import {useInstructionToaster} from './hooks/useInstructionToaster'
|
|
@@ -7,9 +7,12 @@ import {useLayer} from '@sanity/ui'
|
|
|
7
7
|
import {useDocumentPane} from 'sanity/desk'
|
|
8
8
|
import {usePathKey} from '../helpers/misc'
|
|
9
9
|
import {ConnectFromRegion} from '../_lib/connector'
|
|
10
|
+
import {assistDocumentTypeName} from '../types'
|
|
11
|
+
import {useMemo} from 'react'
|
|
12
|
+
import {assistFormId} from '../_lib/form/constants'
|
|
10
13
|
|
|
11
14
|
export function AssistDocumentInputWrapper(props: InputProps) {
|
|
12
|
-
if (!isType(props.schemaType, 'document') && props.id !== 'root') {
|
|
15
|
+
if (!isType(props.schemaType, 'document') && props.id !== 'root' && props.id !== assistFormId) {
|
|
13
16
|
return <AssistInput {...props} />
|
|
14
17
|
}
|
|
15
18
|
|
|
@@ -24,10 +27,27 @@ export function AssistDocumentInputWrapper(props: InputProps) {
|
|
|
24
27
|
function AssistDocumentInput({documentId, ...props}: ObjectInputProps & {documentId: string}) {
|
|
25
28
|
useInstructionToaster(documentId, props.schemaType)
|
|
26
29
|
|
|
30
|
+
const schemaType = useMemo(() => {
|
|
31
|
+
if (props.schemaType.name !== assistDocumentTypeName) {
|
|
32
|
+
return props.schemaType
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
...props.schemaType,
|
|
36
|
+
type: {
|
|
37
|
+
...props.schemaType.type,
|
|
38
|
+
// compatability with i18nArrays plugin that requires this to be document
|
|
39
|
+
name: 'document',
|
|
40
|
+
},
|
|
41
|
+
} as ObjectSchemaType
|
|
42
|
+
}, [props.schemaType])
|
|
43
|
+
|
|
27
44
|
return (
|
|
28
45
|
<FirstAssistedPathProvider members={props.members}>
|
|
29
|
-
<AssistDocumentContextProvider schemaType={
|
|
30
|
-
{props.renderDefault(
|
|
46
|
+
<AssistDocumentContextProvider schemaType={schemaType} documentId={documentId}>
|
|
47
|
+
{props.renderDefault({
|
|
48
|
+
...props,
|
|
49
|
+
schemaType,
|
|
50
|
+
})}
|
|
31
51
|
</AssistDocumentContextProvider>
|
|
32
52
|
</FirstAssistedPathProvider>
|
|
33
53
|
)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {useRunInstruction} from '../assistLayout/RunInstructionProvider'
|
|
2
2
|
import {useCallback, useEffect, useState} from 'react'
|
|
3
3
|
import {ObjectSchemaType, PatchEvent, SanityDocument, unset} from 'sanity'
|
|
4
|
-
import {RunInstructionArgs} from '../assistLayout/AssistLayout'
|
|
5
4
|
import {publicId} from '../helpers/ids'
|
|
6
5
|
|
|
7
|
-
export interface
|
|
6
|
+
export interface DraftDelayedTaskArgs<T> {
|
|
8
7
|
documentOnChange: (event: PatchEvent) => void
|
|
9
8
|
// indicates if the document is a draft or liveEditable currently
|
|
10
9
|
isDocAssistable: boolean
|
|
10
|
+
task: (args: T) => void
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function isDocAssistable(
|
|
@@ -23,28 +23,44 @@ export function getAssistableDocId(documentSchemaType: ObjectSchemaType, documen
|
|
|
23
23
|
return documentSchemaType.liveEdit ? baseId : `drafts.${baseId}`
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export function useRequestRunInstruction(args:
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
export function useRequestRunInstruction(args: {
|
|
27
|
+
documentOnChange: (event: PatchEvent) => void
|
|
28
|
+
// indicates if the document is a draft or liveEditable currently
|
|
29
|
+
isDocAssistable: boolean
|
|
30
|
+
}) {
|
|
29
31
|
const {runInstruction, instructionLoading} = useRunInstruction()
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
runInstruction(queuedTask)
|
|
35
|
-
setQueuedTask(undefined)
|
|
36
|
-
}
|
|
37
|
-
}, [queuedTask, isDocAssistable, runInstruction])
|
|
32
|
+
const requestRunInstruction = useDraftDelayedTask({
|
|
33
|
+
...args,
|
|
34
|
+
task: runInstruction,
|
|
35
|
+
})
|
|
38
36
|
|
|
39
37
|
return {
|
|
40
38
|
instructionLoading,
|
|
41
|
-
requestRunInstruction
|
|
42
|
-
(task: RunInstructionArgs) => {
|
|
43
|
-
// make a dummy edit: this will trigger the document/draft to be created
|
|
44
|
-
documentOnChange(PatchEvent.from([unset(['_force_document_creation'])]))
|
|
45
|
-
setQueuedTask(task)
|
|
46
|
-
},
|
|
47
|
-
[setQueuedTask, documentOnChange]
|
|
48
|
-
),
|
|
39
|
+
requestRunInstruction,
|
|
49
40
|
}
|
|
50
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Ensures that the current document is a draft before running task
|
|
45
|
+
*/
|
|
46
|
+
export function useDraftDelayedTask<T>(args: DraftDelayedTaskArgs<T>) {
|
|
47
|
+
const {documentOnChange, isDocAssistable, task} = args
|
|
48
|
+
|
|
49
|
+
const [queuedArgs, setQueuedArgs] = useState<T | undefined>(undefined)
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (queuedArgs && isDocAssistable) {
|
|
53
|
+
task(queuedArgs)
|
|
54
|
+
setQueuedArgs(undefined)
|
|
55
|
+
}
|
|
56
|
+
}, [queuedArgs, isDocAssistable, task])
|
|
57
|
+
|
|
58
|
+
return useCallback(
|
|
59
|
+
(taskArgs: T) => {
|
|
60
|
+
// make a dummy edit: this will trigger the document/draft to be created
|
|
61
|
+
documentOnChange(PatchEvent.from([unset(['_force_document_creation'])]))
|
|
62
|
+
setQueuedArgs(taskArgs)
|
|
63
|
+
},
|
|
64
|
+
[setQueuedArgs, documentOnChange]
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -8,14 +8,15 @@ export function InstructionInput(props: ObjectInputProps) {
|
|
|
8
8
|
<Stack space={[4, 4, 4, 5]}>
|
|
9
9
|
<NameField {...props} />
|
|
10
10
|
<ShareField {...props} />
|
|
11
|
-
<
|
|
11
|
+
<ObjectMember fieldName={'prompt'} {...props} />
|
|
12
|
+
<ObjectMember fieldName={'output'} {...props} />
|
|
12
13
|
</Stack>
|
|
13
14
|
)
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
function
|
|
17
|
-
const
|
|
18
|
-
return
|
|
17
|
+
function ObjectMember({fieldName, ...props}: ObjectInputProps & {fieldName: string}) {
|
|
18
|
+
const member = findFieldMember(props.members, fieldName)
|
|
19
|
+
return member ? <ObjectInputMember {...props} member={member} /> : null
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const NONE: (FieldMember | FieldError)[] = []
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrayFieldProps,
|
|
3
|
+
ArraySchemaType,
|
|
4
|
+
isArrayOfObjectsSchemaType,
|
|
5
|
+
isObjectSchemaType,
|
|
6
|
+
ObjectSchemaType,
|
|
7
|
+
} from 'sanity'
|
|
8
|
+
import {useCallback, useContext, useState} from 'react'
|
|
9
|
+
import {SelectedFieldContext} from '../SelectedFieldContext'
|
|
10
|
+
|
|
11
|
+
export function InstructionOutputField(props: ArrayFieldProps) {
|
|
12
|
+
const {fieldSchema} = useContext(SelectedFieldContext) ?? {}
|
|
13
|
+
|
|
14
|
+
if (
|
|
15
|
+
!fieldSchema ||
|
|
16
|
+
!(isObjectSchemaType(fieldSchema) || isArrayOfObjectsSchemaType(fieldSchema))
|
|
17
|
+
) {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<EnabledOutputField {...props} fieldSchema={fieldSchema}>
|
|
23
|
+
{props.children}
|
|
24
|
+
</EnabledOutputField>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function EnabledOutputField({
|
|
29
|
+
fieldSchema,
|
|
30
|
+
...props
|
|
31
|
+
}: ArrayFieldProps & {fieldSchema: ObjectSchemaType | ArraySchemaType<ObjectSchemaType>}) {
|
|
32
|
+
const [open, setOpen] = useState(!!props.value?.length)
|
|
33
|
+
const onExpand = useCallback(() => setOpen(true), [])
|
|
34
|
+
const onCollapse = useCallback(() => setOpen(false), [])
|
|
35
|
+
|
|
36
|
+
return props.renderDefault({
|
|
37
|
+
...props,
|
|
38
|
+
collapsible: true,
|
|
39
|
+
onExpand,
|
|
40
|
+
onCollapse,
|
|
41
|
+
collapsed: !open,
|
|
42
|
+
level: 1,
|
|
43
|
+
title: isObjectSchemaType(fieldSchema) ? 'Allowed fields' : 'Allowed types',
|
|
44
|
+
})
|
|
45
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrayOfObjectsInputProps,
|
|
3
|
+
ArraySchemaType,
|
|
4
|
+
FormPatch,
|
|
5
|
+
insert,
|
|
6
|
+
isArrayOfObjectsSchemaType,
|
|
7
|
+
isObjectSchemaType,
|
|
8
|
+
ObjectSchemaType,
|
|
9
|
+
PatchEvent,
|
|
10
|
+
setIfMissing,
|
|
11
|
+
typed,
|
|
12
|
+
unset,
|
|
13
|
+
} from 'sanity'
|
|
14
|
+
import {useCallback, useContext, useEffect, useMemo} from 'react'
|
|
15
|
+
import {SelectedFieldContext} from '../SelectedFieldContext'
|
|
16
|
+
import {Card, Checkbox, Flex, Stack, Text} from '@sanity/ui'
|
|
17
|
+
import {isType} from '../../../helpers/typeUtils'
|
|
18
|
+
import {isAssistSupported} from '../../../helpers/assistSupported'
|
|
19
|
+
import {OutputFieldItem, outputFieldTypeName, OutputTypeItem} from '../../../types'
|
|
20
|
+
|
|
21
|
+
export function InstructionOutputInput(props: ArrayOfObjectsInputProps) {
|
|
22
|
+
const {fieldSchema} = useContext(SelectedFieldContext) ?? {}
|
|
23
|
+
|
|
24
|
+
if (!fieldSchema) {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isObjectSchemaType(fieldSchema)) {
|
|
29
|
+
return <ObjectOutputInput {...props} fieldSchema={fieldSchema} />
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isArrayOfObjectsSchemaType(fieldSchema)) {
|
|
33
|
+
return <ArrayOutputInput {...props} fieldSchema={fieldSchema} />
|
|
34
|
+
}
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function useEmptySelectAllValue(
|
|
39
|
+
value: (OutputTypeItem | OutputFieldItem)[],
|
|
40
|
+
allowedValues: {name: string}[],
|
|
41
|
+
onChange: (patch: FormPatch | FormPatch[] | PatchEvent) => void
|
|
42
|
+
) {
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const validValues = value?.filter((v) =>
|
|
45
|
+
allowedValues.find(
|
|
46
|
+
(f) => f.name === (v._type === outputFieldTypeName ? v.relativePath : v.type)
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
const valueLength = value?.length ?? 0
|
|
50
|
+
const validLength = validValues?.length ?? 0
|
|
51
|
+
if ((!validLength && valueLength) || validLength >= allowedValues.length) {
|
|
52
|
+
// if we end up here, we consider this a "no selected fields/types" selections. This should render and behave as all values selected.
|
|
53
|
+
// we need this behaviour to accommodate new fields/types being added to the model, so they get visited by instructions without having to update the filter
|
|
54
|
+
// when things have been explicitly selected, we let the selection remain as is
|
|
55
|
+
onChange(PatchEvent.from([unset()]))
|
|
56
|
+
}
|
|
57
|
+
}, [allowedValues, value, onChange])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ObjectOutputInput({
|
|
61
|
+
fieldSchema,
|
|
62
|
+
...props
|
|
63
|
+
}: ArrayOfObjectsInputProps & {fieldSchema: ObjectSchemaType}) {
|
|
64
|
+
const {value, onChange} = props
|
|
65
|
+
|
|
66
|
+
const fields = useMemo(
|
|
67
|
+
() => fieldSchema.fields.filter((field) => isAssistSupported(field.type)),
|
|
68
|
+
[fieldSchema.fields]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
useEmptySelectAllValue(value as OutputTypeItem[], fields, onChange)
|
|
72
|
+
|
|
73
|
+
const onSelectChange = useCallback(
|
|
74
|
+
(checked: boolean, selectedValue: string) => {
|
|
75
|
+
if (checked) {
|
|
76
|
+
if (value?.length) {
|
|
77
|
+
onChange(PatchEvent.from(unset([{_key: selectedValue}])))
|
|
78
|
+
} else {
|
|
79
|
+
// we went from empty array to everything selected but one
|
|
80
|
+
const items = fields
|
|
81
|
+
.filter((f) => f.name !== selectedValue)
|
|
82
|
+
.map((field) =>
|
|
83
|
+
typed<OutputFieldItem>({
|
|
84
|
+
_key: field.name,
|
|
85
|
+
_type: 'sanity.assist.output.field',
|
|
86
|
+
relativePath: field.name,
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
onChange(PatchEvent.from([setIfMissing([]), insert(items, 'after', [-1])]))
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
const patchValue: OutputFieldItem = {
|
|
93
|
+
_key: selectedValue,
|
|
94
|
+
_type: 'sanity.assist.output.field',
|
|
95
|
+
relativePath: selectedValue,
|
|
96
|
+
}
|
|
97
|
+
onChange(PatchEvent.from([setIfMissing([]), insert([patchValue], 'after', [-1])]))
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
[onChange, value, fields]
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Stack space={2}>
|
|
105
|
+
{fields.map((field) => {
|
|
106
|
+
return (
|
|
107
|
+
<Flex key={field.name} align="center" gap={2}>
|
|
108
|
+
<Selectable
|
|
109
|
+
value={field.name}
|
|
110
|
+
title={field.type.title ?? field.name}
|
|
111
|
+
arrayValue={value as OutputFieldItem[]}
|
|
112
|
+
onChange={onSelectChange}
|
|
113
|
+
/>
|
|
114
|
+
</Flex>
|
|
115
|
+
)
|
|
116
|
+
})}
|
|
117
|
+
</Stack>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ArrayOutputInput({
|
|
122
|
+
fieldSchema,
|
|
123
|
+
...props
|
|
124
|
+
}: ArrayOfObjectsInputProps & {fieldSchema: ArraySchemaType}) {
|
|
125
|
+
const {value, onChange} = props
|
|
126
|
+
|
|
127
|
+
const ofItems = useMemo(
|
|
128
|
+
() => fieldSchema.of.filter((itemType) => isAssistSupported(itemType)),
|
|
129
|
+
[fieldSchema.of]
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
useEmptySelectAllValue(value as OutputTypeItem[], ofItems, onChange)
|
|
133
|
+
|
|
134
|
+
const onSelectChange = useCallback(
|
|
135
|
+
(checked: boolean, selectedValue: string) => {
|
|
136
|
+
if (checked) {
|
|
137
|
+
if (value?.length) {
|
|
138
|
+
onChange(PatchEvent.from(unset([{_key: selectedValue}])))
|
|
139
|
+
} else {
|
|
140
|
+
// we went from empty array to everything selected but one
|
|
141
|
+
const items = ofItems
|
|
142
|
+
.filter((f) => f.name !== selectedValue)
|
|
143
|
+
.map((field) =>
|
|
144
|
+
typed<OutputTypeItem>({
|
|
145
|
+
_key: field.name,
|
|
146
|
+
_type: 'sanity.assist.output.type',
|
|
147
|
+
type: field.name,
|
|
148
|
+
})
|
|
149
|
+
)
|
|
150
|
+
onChange(PatchEvent.from([setIfMissing([]), insert(items, 'after', [-1])]))
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
const patchValue: OutputTypeItem = {
|
|
154
|
+
_key: selectedValue,
|
|
155
|
+
_type: 'sanity.assist.output.type',
|
|
156
|
+
type: selectedValue,
|
|
157
|
+
}
|
|
158
|
+
onChange(PatchEvent.from([setIfMissing([]), insert([patchValue], 'after', [-1])]))
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
[onChange, value, ofItems]
|
|
162
|
+
)
|
|
163
|
+
return (
|
|
164
|
+
<Stack space={2}>
|
|
165
|
+
{ofItems.map((itemType) => {
|
|
166
|
+
return (
|
|
167
|
+
<Flex key={itemType.name}>
|
|
168
|
+
<Selectable
|
|
169
|
+
value={itemType.name}
|
|
170
|
+
title={isType(itemType, 'block') ? 'Text' : itemType.title ?? itemType.name}
|
|
171
|
+
arrayValue={value as OutputTypeItem[] | undefined}
|
|
172
|
+
onChange={onSelectChange}
|
|
173
|
+
/>
|
|
174
|
+
</Flex>
|
|
175
|
+
)
|
|
176
|
+
})}
|
|
177
|
+
</Stack>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function Selectable({
|
|
182
|
+
title,
|
|
183
|
+
arrayValue,
|
|
184
|
+
value,
|
|
185
|
+
onChange,
|
|
186
|
+
}: {
|
|
187
|
+
title: string
|
|
188
|
+
value: string
|
|
189
|
+
arrayValue?: {_key: string}[]
|
|
190
|
+
onChange: (checked: boolean, value: string) => void
|
|
191
|
+
}) {
|
|
192
|
+
const checked = !arrayValue?.length || !!arrayValue?.find((v) => v._key === value)
|
|
193
|
+
const handleChange = useCallback(() => onChange(checked, value), [onChange, checked, value])
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<Flex gap={2} align="flex-start">
|
|
197
|
+
<Checkbox checked={checked} onChange={handleChange} />
|
|
198
|
+
<Card marginTop={1} onClick={handleChange}>
|
|
199
|
+
<Text style={{cursor: 'default'}} size={1}>
|
|
200
|
+
{title}
|
|
201
|
+
</Text>
|
|
202
|
+
</Card>
|
|
203
|
+
</Flex>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
@@ -3,24 +3,14 @@ import {
|
|
|
3
3
|
assistDocumentTypeName,
|
|
4
4
|
AssistTasksStatus,
|
|
5
5
|
assistTasksStatusTypeName,
|
|
6
|
-
FieldRef,
|
|
7
|
-
fieldReferenceTypeName,
|
|
8
6
|
InstructionTask,
|
|
9
7
|
StudioAssistDocument,
|
|
10
8
|
StudioAssistField,
|
|
11
9
|
StudioInstruction,
|
|
12
10
|
} from '../../types'
|
|
13
|
-
import {
|
|
14
|
-
ObjectSchemaType,
|
|
15
|
-
pathToString,
|
|
16
|
-
typed,
|
|
17
|
-
useClient,
|
|
18
|
-
useCurrentUser,
|
|
19
|
-
useValidationStatus,
|
|
20
|
-
ValidationMarker,
|
|
21
|
-
} from 'sanity'
|
|
11
|
+
import {ObjectSchemaType, typed, useClient, useCurrentUser} from 'sanity'
|
|
22
12
|
import {useDocumentState} from './useDocumentState'
|
|
23
|
-
import {assistDocumentId, assistTasksStatusId
|
|
13
|
+
import {assistDocumentId, assistTasksStatusId} from '../../helpers/ids'
|
|
24
14
|
import {maxHistoryVisibilityMs} from '../../constants'
|
|
25
15
|
|
|
26
16
|
interface UseAssistDocumentProps {
|
|
@@ -37,7 +27,6 @@ export function useStudioAssistDocument({
|
|
|
37
27
|
const documentTypeName = schemaType.name
|
|
38
28
|
const currentUser = useCurrentUser()
|
|
39
29
|
|
|
40
|
-
const validation = useValidationStatus(publicId(documentId), schemaType.name).validation
|
|
41
30
|
const assistDocument = useDocumentState<StudioAssistDocument>(
|
|
42
31
|
assistDocumentId(documentTypeName),
|
|
43
32
|
assistDocumentTypeName
|
|
@@ -73,7 +62,7 @@ export function useStudioAssistDocument({
|
|
|
73
62
|
tasks: tasks.filter((task) => task.path === assistField.path),
|
|
74
63
|
instructions: assistField.instructions
|
|
75
64
|
?.filter((p) => !p.userId || p.userId === currentUser?.id)
|
|
76
|
-
.map((instruction) => asStudioInstruction(instruction, tasks
|
|
65
|
+
.map((instruction) => asStudioInstruction(instruction, tasks)),
|
|
77
66
|
}
|
|
78
67
|
})
|
|
79
68
|
return typed<StudioAssistDocument>({
|
|
@@ -89,23 +78,13 @@ export function useStudioAssistDocument({
|
|
|
89
78
|
}),
|
|
90
79
|
fields: fields,
|
|
91
80
|
})
|
|
92
|
-
}, [assistDocument, assistTasksStatus, currentUser
|
|
81
|
+
}, [assistDocument, assistTasksStatus, currentUser])
|
|
93
82
|
}
|
|
94
83
|
|
|
95
84
|
function asStudioInstruction(
|
|
96
85
|
instruction: StudioInstruction,
|
|
97
|
-
run: InstructionTask[]
|
|
98
|
-
validation: ValidationMarker[]
|
|
86
|
+
run: InstructionTask[]
|
|
99
87
|
): StudioInstruction {
|
|
100
|
-
const errors = validation.filter((marker) => marker.level === 'error')
|
|
101
|
-
|
|
102
|
-
const fieldRefs: FieldRef[] = (instruction?.prompt ?? []).flatMap((block) => {
|
|
103
|
-
if (block._type === 'block') {
|
|
104
|
-
return block.children.filter((c): c is FieldRef => c._type === fieldReferenceTypeName)
|
|
105
|
-
}
|
|
106
|
-
return []
|
|
107
|
-
})
|
|
108
|
-
|
|
109
88
|
return {
|
|
110
89
|
...instruction,
|
|
111
90
|
tasks: run
|
|
@@ -115,11 +94,5 @@ function asStudioInstruction(
|
|
|
115
94
|
task.started &&
|
|
116
95
|
new Date().getTime() - new Date(task.started).getTime() < maxHistoryVisibilityMs
|
|
117
96
|
),
|
|
118
|
-
validation: errors.filter((marker) =>
|
|
119
|
-
fieldRefs
|
|
120
|
-
.map((r) => r.path)
|
|
121
|
-
.filter((p): p is string => !!p)
|
|
122
|
-
.find((path) => pathToString(marker.path) === path)
|
|
123
|
-
),
|
|
124
97
|
}
|
|
125
98
|
}
|
|
@@ -9,6 +9,7 @@ import {AiFieldPresence} from '../presence/AiFieldPresence'
|
|
|
9
9
|
import {AssistOnboardingPopover} from '../onboarding/FieldActionsOnboarding'
|
|
10
10
|
import {FirstAssistedPathContext} from '../onboarding/FirstAssistedPathProvider'
|
|
11
11
|
import {fieldOnboardingKey, useOnboardingFeature} from '../onboarding/onboardingStore'
|
|
12
|
+
import {assistFormId} from '../_lib/form/constants'
|
|
12
13
|
|
|
13
14
|
export function AssistFieldWrapper(props: FieldProps) {
|
|
14
15
|
const {schemaType} = props
|
|
@@ -22,7 +23,11 @@ export function AssistFieldWrapper(props: FieldProps) {
|
|
|
22
23
|
) {
|
|
23
24
|
return props.renderDefault(props)
|
|
24
25
|
}
|
|
25
|
-
if (
|
|
26
|
+
if (
|
|
27
|
+
!isType(props.schemaType, 'document') &&
|
|
28
|
+
props.inputId !== 'root' &&
|
|
29
|
+
props.inputId !== assistFormId
|
|
30
|
+
) {
|
|
26
31
|
return <AssistField {...props}>{props.children}</AssistField>
|
|
27
32
|
}
|
|
28
33
|
|
|
@@ -46,14 +51,15 @@ export function AssistField(props: FieldProps) {
|
|
|
46
51
|
)
|
|
47
52
|
|
|
48
53
|
const {showOnboarding, dismissOnboarding} = useOnboardingFeature(fieldOnboardingKey)
|
|
54
|
+
const singlePresence = presence[0]
|
|
49
55
|
|
|
50
56
|
const actions = (
|
|
51
57
|
<Flex gap={2} align="center" justify="space-between">
|
|
52
|
-
{
|
|
53
|
-
<Box
|
|
54
|
-
<AiFieldPresence
|
|
58
|
+
{singlePresence && (
|
|
59
|
+
<Box>
|
|
60
|
+
<AiFieldPresence presence={singlePresence} />
|
|
55
61
|
</Box>
|
|
56
|
-
)
|
|
62
|
+
)}
|
|
57
63
|
|
|
58
64
|
{isFirstAssisted && showOnboarding && <AssistOnboardingPopover dismiss={dismissOnboarding} />}
|
|
59
65
|
</Flex>
|
|
@@ -18,13 +18,12 @@ export function AssistFormBlock(props: BlockProps) {
|
|
|
18
18
|
},
|
|
19
19
|
[onChange, key]
|
|
20
20
|
)
|
|
21
|
+
const singlePresence = presence[0]
|
|
21
22
|
return (
|
|
22
23
|
<ErrorWrapper onChange={localOnChange}>
|
|
23
24
|
<Flex align="center" justify="space-between">
|
|
24
25
|
<Box flex={1}>{props.renderDefault(props)}</Box>
|
|
25
|
-
{presence
|
|
26
|
-
<AiFieldPresence key={pre.lastActiveAt} presence={pre} />
|
|
27
|
-
))}
|
|
26
|
+
{singlePresence && <AiFieldPresence presence={singlePresence} />}
|
|
28
27
|
</Flex>
|
|
29
28
|
</ErrorWrapper>
|
|
30
29
|
)
|
|
@@ -47,9 +47,9 @@ export function ListItem(props: ValidationListItemProps) {
|
|
|
47
47
|
{path}
|
|
48
48
|
</StyledText>
|
|
49
49
|
)}
|
|
50
|
-
{marker.item
|
|
50
|
+
{marker.item?.message && (
|
|
51
51
|
<StyledText muted size={1} textOverflow={truncate ? 'ellipsis' : undefined}>
|
|
52
|
-
{marker.item
|
|
52
|
+
{marker.item?.message}
|
|
53
53
|
</StyledText>
|
|
54
54
|
)}
|
|
55
55
|
</Stack>
|
|
@@ -31,6 +31,7 @@ import {InspectorOnboarding} from '../onboarding/InspectorOnboarding'
|
|
|
31
31
|
import {inspectorOnboardingKey, useOnboardingFeature} from '../onboarding/onboardingStore'
|
|
32
32
|
import {TypePathContext} from '../assistDocument/components/AssistDocumentForm'
|
|
33
33
|
import {FieldTitle} from './FieldAutocomplete'
|
|
34
|
+
import {getConditionalMembers} from '../helpers/conditionalMembers'
|
|
34
35
|
|
|
35
36
|
const CardWithShadowBelow = styled(Card)`
|
|
36
37
|
position: relative;
|
|
@@ -199,9 +200,13 @@ export function AssistInspector(props: DocumentInspectorProps) {
|
|
|
199
200
|
value: docValue,
|
|
200
201
|
schemaType,
|
|
201
202
|
onChange: documentOnChange,
|
|
203
|
+
formState,
|
|
202
204
|
} = documentPane
|
|
203
205
|
const {published, draft} = useEditState(documentId, documentType, 'low')
|
|
204
206
|
|
|
207
|
+
const formStateRef = useRef(formState)
|
|
208
|
+
formStateRef.current = formState
|
|
209
|
+
|
|
205
210
|
const assistableDocId = getAssistableDocId(schemaType, documentId)
|
|
206
211
|
const {instructionLoading, requestRunInstruction} = useRequestRunInstruction({
|
|
207
212
|
documentOnChange,
|
|
@@ -261,6 +266,7 @@ export function AssistInspector(props: DocumentInspectorProps) {
|
|
|
261
266
|
typePath,
|
|
262
267
|
assistDocumentId: assistDocumentId(documentType),
|
|
263
268
|
instruction,
|
|
269
|
+
conditionalMembers: formStateRef.current ? getConditionalMembers(formStateRef.current) : [],
|
|
264
270
|
}),
|
|
265
271
|
[pathKey, instruction, typePath, documentType, assistableDocId, requestRunInstruction]
|
|
266
272
|
)
|
|
@@ -32,6 +32,7 @@ export function FieldAutocomplete(props: FieldSelectorProps) {
|
|
|
32
32
|
() =>
|
|
33
33
|
fieldRefs
|
|
34
34
|
.filter((field) => (filter ? filter(field) : true))
|
|
35
|
+
.filter((f) => !isType(f.schemaType, 'reference'))
|
|
35
36
|
.map((field) => ({value: field.key, field})),
|
|
36
37
|
[fieldRefs, filter]
|
|
37
38
|
)
|