@sanity/assist 1.2.15 → 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.
Files changed (52) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +551 -30
  3. package/dist/index.cjs.mjs +1 -0
  4. package/dist/index.d.ts +253 -11
  5. package/dist/index.esm.js +2405 -392
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +2399 -385
  8. package/dist/index.js.map +1 -1
  9. package/package.json +15 -14
  10. package/src/_lib/form/DocumentForm.tsx +3 -2
  11. package/src/_lib/form/constants.ts +1 -0
  12. package/src/assistDocument/AssistDocumentInput.tsx +24 -4
  13. package/src/assistDocument/RequestRunInstructionProvider.tsx +37 -21
  14. package/src/assistDocument/components/instruction/InstructionInput.tsx +5 -4
  15. package/src/assistDocument/components/instruction/InstructionOutputField.tsx +45 -0
  16. package/src/assistDocument/components/instruction/InstructionOutputInput.tsx +205 -0
  17. package/src/assistDocument/hooks/useStudioAssistDocument.ts +5 -32
  18. package/src/assistFormComponents/AssistField.tsx +11 -5
  19. package/src/assistFormComponents/AssistFormBlock.tsx +2 -3
  20. package/src/assistFormComponents/validation/listItem.tsx +2 -2
  21. package/src/assistInspector/AssistInspector.tsx +6 -0
  22. package/src/assistInspector/FieldAutocomplete.tsx +1 -0
  23. package/src/assistInspector/InstructionTaskHistoryButton.tsx +2 -3
  24. package/src/assistInspector/helpers.ts +9 -11
  25. package/src/assistLayout/AssistLayout.tsx +9 -9
  26. package/src/components/ImageContext.tsx +19 -9
  27. package/src/components/SafeValueInput.tsx +4 -1
  28. package/src/fieldActions/assistFieldActions.tsx +42 -13
  29. package/src/fieldActions/generateCaptionActions.tsx +2 -2
  30. package/src/fieldActions/generateImageActions.tsx +57 -0
  31. package/src/helpers/assistSupported.ts +10 -16
  32. package/src/helpers/conditionalMembers.test.ts +200 -0
  33. package/src/helpers/conditionalMembers.ts +127 -0
  34. package/src/helpers/typeUtils.ts +19 -5
  35. package/src/index.ts +3 -0
  36. package/src/plugin.tsx +14 -5
  37. package/src/presence/AssistAvatar.tsx +1 -1
  38. package/src/schemas/assistDocumentSchema.tsx +40 -1
  39. package/src/schemas/serialize/serializeSchema.test.ts +239 -8
  40. package/src/schemas/serialize/serializeSchema.ts +77 -10
  41. package/src/schemas/typeDefExtensions.ts +89 -5
  42. package/src/translate/FieldTranslationProvider.tsx +360 -0
  43. package/src/translate/getLanguageParams.ts +26 -0
  44. package/src/translate/languageStore.ts +18 -0
  45. package/src/translate/paths.test.ts +133 -0
  46. package/src/translate/paths.ts +175 -0
  47. package/src/translate/translateActions.tsx +188 -0
  48. package/src/translate/types.ts +160 -0
  49. package/src/types.ts +33 -12
  50. package/src/useApiClient.ts +130 -2
  51. package/src/assistLayout/AlphaMigration.tsx +0 -310
  52. package/src/legacy-types.ts +0 -72
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanity/assist",
3
- "version": "1.2.15",
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,28 +54,29 @@
54
54
  "release": "semantic-release"
55
55
  },
56
56
  "dependencies": {
57
- "@sanity/icons": "^2.4.0",
57
+ "@sanity/icons": "^2.8.0",
58
58
  "@sanity/incompatible-plugin": "^1.0.4",
59
- "@sanity/ui": "^1.6.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",
64
65
  "rxjs-exhaustmap-with-trailing": "^2.1.1"
65
66
  },
66
67
  "devDependencies": {
67
- "@commitlint/cli": "^17.8.1",
68
- "@commitlint/config-conventional": "^17.8.1",
68
+ "@commitlint/cli": "^18.4.3",
69
+ "@commitlint/config-conventional": "^18.4.3",
69
70
  "@rollup/plugin-image": "^3.0.3",
70
71
  "@sanity/pkg-utils": "^2.4.10",
71
72
  "@sanity/plugin-kit": "^3.1.10",
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": "^5.62.0",
76
- "@typescript-eslint/parser": "^5.62.0",
76
+ "@typescript-eslint/eslint-plugin": "^7.0.1",
77
+ "@typescript-eslint/parser": "^7.0.1",
77
78
  "date-fns": "^2.30.0",
78
- "eslint": "^8.53.0",
79
+ "eslint": "^8.56.0",
79
80
  "eslint-config-prettier": "^8.10.0",
80
81
  "eslint-config-sanity": "^6.0.0",
81
82
  "eslint-plugin-prettier": "^4.2.1",
@@ -84,16 +85,16 @@
84
85
  "npm-run-all": "^4.1.5",
85
86
  "react": "^18.2.0",
86
87
  "react-dom": "^18.2.0",
87
- "rimraf": "^4.4.0",
88
- "sanity": "^3.19.3",
88
+ "rimraf": "^5.0.5",
89
+ "sanity": "^3.28.0",
89
90
  "semantic-release": "^21.1.2",
90
91
  "styled-components": "^6.1.1",
91
- "typescript": "^5.2.2",
92
- "vitest": "^0.34.6"
92
+ "typescript": "^5.3.3",
93
+ "vitest": "^1.2.1"
93
94
  },
94
95
  "peerDependencies": {
95
96
  "react": "^18",
96
- "sanity": "^3.16",
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="root"
139
+ id={assistFormId}
139
140
  members={formState.members}
140
141
  onChange={onChange}
141
142
  onFieldGroupSelect={onSetActiveFieldGroup}
@@ -148,7 +149,7 @@ export function DocumentForm(
148
149
  readOnly={formState.readOnly}
149
150
  schemaType={formState.schemaType}
150
151
  validation={validation}
151
- value={formState.value}
152
+ value={formState.value as any}
152
153
  />
153
154
  )
154
155
  ) : (
@@ -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={props.schemaType} documentId={documentId}>
30
- {props.renderDefault(props)}
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 DocumentArgs {
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: DocumentArgs) {
27
- const {documentOnChange, isDocAssistable} = args
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 [queuedTask, setQueuedTask] = useState<RunInstructionArgs | undefined>(undefined)
31
-
32
- useEffect(() => {
33
- if (queuedTask && isDocAssistable) {
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: useCallback(
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
- <PromptField {...props} />
11
+ <ObjectMember fieldName={'prompt'} {...props} />
12
+ <ObjectMember fieldName={'output'} {...props} />
12
13
  </Stack>
13
14
  )
14
15
  }
15
16
 
16
- function PromptField(props: ObjectInputProps) {
17
- const promptMember = findFieldMember(props.members, 'prompt')
18
- return promptMember ? <ObjectInputMember {...props} member={promptMember} /> : null
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, publicId} from '../../helpers/ids'
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, validation)),
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, validation])
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 (!isType(props.schemaType, 'document') && props.inputId !== 'root') {
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
- {presence.map((pre) => (
53
- <Box key={pre.user.id}>
54
- <AiFieldPresence key={pre.lastActiveAt} presence={pre} />
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.map((pre) => (
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.message && (
50
+ {marker.item?.message && (
51
51
  <StyledText muted size={1} textOverflow={truncate ? 'ellipsis' : undefined}>
52
- {marker.item.message}
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
  )