@sanity/assist 1.2.13 → 1.2.15-lang.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.
Files changed (36) hide show
  1. package/README.md +392 -6
  2. package/dist/index.d.ts +170 -3
  3. package/dist/index.esm.js +1986 -111
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +1980 -105
  6. package/dist/index.js.map +1 -1
  7. package/package.json +15 -14
  8. package/src/_lib/form/DocumentForm.tsx +1 -1
  9. package/src/assistDocument/components/instruction/InstructionInput.tsx +5 -4
  10. package/src/assistDocument/components/instruction/InstructionOutputField.tsx +45 -0
  11. package/src/assistDocument/components/instruction/InstructionOutputInput.tsx +205 -0
  12. package/src/assistDocument/hooks/useStudioAssistDocument.ts +5 -32
  13. package/src/assistFormComponents/AssistField.tsx +5 -4
  14. package/src/assistFormComponents/AssistFormBlock.tsx +2 -3
  15. package/src/assistFormComponents/validation/listItem.tsx +2 -2
  16. package/src/assistInspector/FieldAutocomplete.tsx +1 -0
  17. package/src/assistInspector/InstructionTaskHistoryButton.tsx +2 -3
  18. package/src/assistInspector/helpers.ts +7 -9
  19. package/src/assistLayout/AssistLayout.tsx +9 -6
  20. package/src/fieldActions/assistFieldActions.tsx +14 -8
  21. package/src/fieldActions/translateActions.tsx +118 -0
  22. package/src/helpers/assistSupported.ts +1 -1
  23. package/src/node_modules/.vitest/results.json +1 -0
  24. package/src/plugin.tsx +12 -2
  25. package/src/presence/AssistAvatar.tsx +1 -1
  26. package/src/schemas/assistDocumentSchema.tsx +39 -0
  27. package/src/schemas/serialize/serializeSchema.test.ts +15 -2
  28. package/src/schemas/serialize/serializeSchema.ts +8 -7
  29. package/src/schemas/typeDefExtensions.ts +12 -1
  30. package/src/translate/FieldTranslationProvider.tsx +254 -0
  31. package/src/translate/getLanguageParams.ts +26 -0
  32. package/src/translate/paths.test.ts +87 -0
  33. package/src/translate/paths.ts +151 -0
  34. package/src/translate/types.ts +159 -0
  35. package/src/types.ts +21 -2
  36. package/src/useApiClient.ts +63 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/assist",
3
- "version": "1.2.13",
3
+ "version": "1.2.15-lang.1",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "sanity",
@@ -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.0-beta.17",
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.7.2",
68
- "@commitlint/config-conventional": "^17.7.0",
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
- "@sanity/semantic-release-preset": "^4.1.4",
73
- "@types/react": "^18.2.27",
74
- "@types/styled-components": "^5.1.28",
73
+ "@sanity/semantic-release-preset": "^4.1.6",
74
+ "@types/react": "^18.2.37",
75
+ "@types/styled-components": "^5.1.30",
75
76
  "@typescript-eslint/eslint-plugin": "^5.62.0",
76
77
  "@typescript-eslint/parser": "^5.62.0",
77
78
  "date-fns": "^2.30.0",
78
- "eslint": "^8.51.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,17 +85,17 @@
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.1",
88
+ "rimraf": "^5.0.5",
89
+ "sanity": "^3.24.1",
89
90
  "semantic-release": "^21.1.2",
90
- "styled-components": "^5.3.11",
91
- "typescript": "^5.2.2",
91
+ "styled-components": "^6.1.1",
92
+ "typescript": "^5.3.3",
92
93
  "vitest": "^0.34.6"
93
94
  },
94
95
  "peerDependencies": {
95
96
  "react": "^18",
96
97
  "sanity": "^3.16",
97
- "styled-components": "^5.2"
98
+ "styled-components": "^5.2 || ^6.0.0"
98
99
  },
99
100
  "engines": {
100
101
  "node": ">=14"
@@ -148,7 +148,7 @@ export function DocumentForm(
148
148
  readOnly={formState.readOnly}
149
149
  schemaType={formState.schemaType}
150
150
  validation={validation}
151
- value={formState.value}
151
+ value={formState.value as any}
152
152
  />
153
153
  )
154
154
  ) : (
@@ -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
  }
@@ -46,14 +46,15 @@ export function AssistField(props: FieldProps) {
46
46
  )
47
47
 
48
48
  const {showOnboarding, dismissOnboarding} = useOnboardingFeature(fieldOnboardingKey)
49
+ const singlePresence = presence[0]
49
50
 
50
51
  const actions = (
51
52
  <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} />
53
+ {singlePresence && (
54
+ <Box>
55
+ <AiFieldPresence presence={singlePresence} />
55
56
  </Box>
56
- ))}
57
+ )}
57
58
 
58
59
  {isFirstAssisted && showOnboarding && <AssistOnboardingPopover dismiss={dismissOnboarding} />}
59
60
  </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>
@@ -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
  )
@@ -167,7 +167,7 @@ export function InstructionTaskHistoryButton(props: InstructionTaskHistoryButton
167
167
  )
168
168
  }
169
169
 
170
- const TASK_STATUS_BUTTON_TOOLTIP_PROPS: StatusButtonProps['tooltip'] = {
170
+ const TASK_STATUS_BUTTON_TOOLTIP_PROPS: StatusButtonProps['tooltipProps'] = {
171
171
  placement: 'top',
172
172
  }
173
173
 
@@ -190,11 +190,10 @@ const TaskStatusButton = forwardRef(function TaskStatusButton(
190
190
  mode="bleed"
191
191
  onClick={onClick}
192
192
  tone={hasErrors ? 'critical' : undefined}
193
- fontSize={1}
194
193
  disabled={disabled}
195
194
  ref={ref}
196
195
  selected={selected}
197
- tooltip={TASK_STATUS_BUTTON_TOOLTIP_PROPS}
196
+ tooltipProps={TASK_STATUS_BUTTON_TOOLTIP_PROPS}
198
197
  />
199
198
  )
200
199
  })
@@ -20,7 +20,7 @@ import {
20
20
  } from 'sanity'
21
21
  import {ComponentType, useContext, useMemo} from 'react'
22
22
  import {AssistInspectorRouteParams, documentRootKey, fieldPathParam} from '../types'
23
- import {usePaneRouter} from 'sanity/desk'
23
+ import {usePaneRouter, type PaneRouterContextValue} from 'sanity/desk'
24
24
  import {isAssistSupported} from '../helpers/assistSupported'
25
25
  import {isPortableTextArray, isType} from '../helpers/typeUtils'
26
26
  import {SelectedFieldContext} from '../assistDocument/components/SelectedFieldContext'
@@ -198,18 +198,16 @@ export function getFieldTitle(field?: FieldRef) {
198
198
  return field?.title ?? schemaType?.title ?? schemaType?.name ?? 'Untitled'
199
199
  }
200
200
 
201
+ export type AiPaneRouter = Omit<PaneRouterContextValue, 'setParams' | 'params'> & {
202
+ params: AssistInspectorRouteParams
203
+ setParams: (p: Record<keyof AssistInspectorRouteParams, string | undefined>) => void
204
+ }
205
+
201
206
  export function useAiPaneRouter() {
202
207
  const paneRouter = usePaneRouter()
203
208
 
204
209
  return useMemo(
205
- () =>
206
- ({...paneRouter, params: paneRouter.params ?? {}} as Omit<
207
- typeof paneRouter,
208
- 'setParams' | 'params'
209
- > & {
210
- params: AssistInspectorRouteParams
211
- setParams: (p: Record<keyof AssistInspectorRouteParams, string | undefined>) => void
212
- }),
210
+ () => ({...paneRouter, params: paneRouter.params ?? {}} as AiPaneRouter),
213
211
  [paneRouter]
214
212
  )
215
213
  }
@@ -9,6 +9,7 @@ import {StudioInstruction} from '../types'
9
9
  import {RunInstructionProvider} from './RunInstructionProvider'
10
10
  import {ThemeProvider} from '@sanity/ui'
11
11
  import {AlphaMigration} from './AlphaMigration'
12
+ import {FieldTranslationProvider} from '../translate/FieldTranslationProvider'
12
13
 
13
14
  export interface AIStudioLayoutProps extends LayoutProps {
14
15
  config: AssistPluginConfig
@@ -26,12 +27,14 @@ export function AssistLayout(props: AIStudioLayoutProps) {
26
27
  <AiAssistanceConfigProvider config={props.config}>
27
28
  {migrate ? <AlphaMigration /> : null}
28
29
  <RunInstructionProvider>
29
- <ConnectorsProvider onConnectorsChange={setConnectors}>
30
- {props.renderDefault(props)}
31
- <ThemeProvider tone="default">
32
- <AssistConnectorsOverlay connectors={connectors} />
33
- </ThemeProvider>
34
- </ConnectorsProvider>
30
+ <FieldTranslationProvider>
31
+ <ConnectorsProvider onConnectorsChange={setConnectors}>
32
+ {props.renderDefault(props)}
33
+ <ThemeProvider tone="default">
34
+ <AssistConnectorsOverlay connectors={connectors} />
35
+ </ThemeProvider>
36
+ </ConnectorsProvider>
37
+ </FieldTranslationProvider>
35
38
  </RunInstructionProvider>
36
39
  </AiAssistanceConfigProvider>
37
40
  )
@@ -24,6 +24,7 @@ import {generateCaptionsActions} from './generateCaptionActions'
24
24
  import {useDocumentPane} from 'sanity/desk'
25
25
  import {useSelectedField, useTypePath} from '../assistInspector/helpers'
26
26
  import {isSchemaAssistEnabled} from '../helpers/assistSupported'
27
+ import {translateActions} from './translateActions'
27
28
 
28
29
  function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) {
29
30
  return node
@@ -47,6 +48,7 @@ export const assistFieldActions: DocumentFieldAction = {
47
48
  documentSchemaType,
48
49
  documentId,
49
50
  selectedPath,
51
+ assistableDocumentId,
50
52
  } =
51
53
  // document field actions do not have access to the document context
52
54
  // conditional hook _should_ be safe here since the logical path will be stable
@@ -89,7 +91,7 @@ export const assistFieldActions: DocumentFieldAction = {
89
91
  const isSelected = isInspectorOpen && isPathSelected
90
92
 
91
93
  const imageCaptionAction = generateCaptionsActions.useAction(props)
92
-
94
+ const translateAction = translateActions.useAction({...props, documentId: assistableDocumentId})
93
95
  const manageInstructions = useCallback(
94
96
  () =>
95
97
  isSelected
@@ -134,7 +136,7 @@ export const assistFieldActions: DocumentFieldAction = {
134
136
  )
135
137
 
136
138
  const runInstructionsGroup = useMemo(() => {
137
- return instructions?.length || imageCaptionAction
139
+ return instructions?.length || imageCaptionAction || translateAction
138
140
  ? node({
139
141
  type: 'group',
140
142
  icon: () => null,
@@ -151,7 +153,7 @@ export const assistFieldActions: DocumentFieldAction = {
151
153
  })
152
154
  ),
153
155
  imageCaptionAction,
154
- ].filter(Boolean),
156
+ ].filter((a): a is DocumentFieldActionItem => !!a),
155
157
  expanded: true,
156
158
  })
157
159
  : undefined
@@ -163,6 +165,7 @@ export const assistFieldActions: DocumentFieldAction = {
163
165
  documentIsNew,
164
166
  assistSupported,
165
167
  imageCaptionAction,
168
+ translateAction,
166
169
  ])
167
170
 
168
171
  const instructionsLength = instructions?.length || 0
@@ -185,12 +188,14 @@ export const assistFieldActions: DocumentFieldAction = {
185
188
  type: 'group',
186
189
  icon: SparklesIcon,
187
190
  title: pluginTitleShort,
188
- children: [runInstructionsGroup, assistSupported && manageInstructionsItem].filter(
189
- (c): c is DocumentFieldActionItem | DocumentFieldActionGroup => !!c
190
- ),
191
+ children: [
192
+ runInstructionsGroup,
193
+ translateAction,
194
+ assistSupported && manageInstructionsItem,
195
+ ].filter((c): c is DocumentFieldActionItem | DocumentFieldActionGroup => !!c),
191
196
  expanded: false,
192
197
  renderAsButton: true,
193
- hidden: !assistSupported && !imageCaptionAction,
198
+ hidden: !assistSupported && !imageCaptionAction && !translateAction,
194
199
  }),
195
200
  [
196
201
  //documentIsNew,
@@ -198,6 +203,7 @@ export const assistFieldActions: DocumentFieldAction = {
198
203
  manageInstructionsItem,
199
204
  assistSupported,
200
205
  imageCaptionAction,
206
+ translateAction,
201
207
  ]
202
208
  )
203
209
 
@@ -216,7 +222,7 @@ export const assistFieldActions: DocumentFieldAction = {
216
222
  )
217
223
 
218
224
  // If there are no instructions, we don't want to render the group
219
- if (instructionsLength === 0 && !imageCaptionAction) {
225
+ if (instructionsLength === 0 && !imageCaptionAction && !translateAction) {
220
226
  return emptyAction
221
227
  }
222
228