@sanity/assist 3.1.1 → 3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/assist",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
4
4
  "description": "You create the instructions; Sanity AI Assist does the rest.",
5
5
  "keywords": [
6
6
  "sanity",
@@ -1,4 +1,4 @@
1
- import {SanityClient} from '@sanity/client'
1
+ import type {SanityClient} from '@sanity/client'
2
2
  import {defer, delay, merge, Observable, of, partition, switchMap, throwError} from 'rxjs'
3
3
  import {filter, mergeMap, share, take} from 'rxjs/operators'
4
4
  import {exhaustMapToWithTrailing} from 'rxjs-exhaustmap-with-trailing'
@@ -15,6 +15,7 @@ import {
15
15
  import {styled} from 'styled-components'
16
16
 
17
17
  import {DocumentForm} from '../_lib/form'
18
+ import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
18
19
  import {AssistTypeContext} from '../assistDocument/components/AssistTypeContext'
19
20
  import {useStudioAssistDocument} from '../assistDocument/hooks/useStudioAssistDocument'
20
21
  import {useRequestRunInstruction} from '../assistDocument/RequestRunInstructionProvider'
@@ -34,7 +35,6 @@ import {
34
35
  useTypePath,
35
36
  } from './helpers'
36
37
  import {InstructionTaskHistoryButton} from './InstructionTaskHistoryButton'
37
- import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
38
38
 
39
39
  const CardWithShadowBelow = styled(Card)`
40
40
  position: relative;
@@ -226,4 +226,51 @@ describe('conditionalMembers', () => {
226
226
  },
227
227
  ])
228
228
  })
229
+
230
+ test('should respect max-depth', () => {
231
+ const docSchema: ObjectSchemaType = Schema.compile({
232
+ name: 'test',
233
+ types: [
234
+ defineType({
235
+ type: 'document',
236
+ name: 'article',
237
+ fields: [
238
+ {type: 'string', name: 'title', readOnly: () => false},
239
+ {
240
+ type: 'object',
241
+ name: 'object',
242
+ fields: [{type: 'string', name: 'title', readOnly: () => false}],
243
+ },
244
+ ],
245
+ }),
246
+ ],
247
+ }).get('article')
248
+
249
+ const docState = {
250
+ path: [],
251
+ schemaType: docSchema,
252
+ members: [
253
+ {
254
+ kind: 'field',
255
+ field: {path: [docSchema.fields[0].name], schemaType: docSchema.fields[0].type},
256
+ },
257
+ {
258
+ kind: 'field',
259
+ field: {
260
+ path: [docSchema.fields[1].name],
261
+ schemaType: docSchema.fields[1].type,
262
+ members: [
263
+ {
264
+ kind: 'field',
265
+ field: {path: ['object', 'title'], schemaType: docSchema.fields[0].type},
266
+ },
267
+ ],
268
+ },
269
+ },
270
+ ],
271
+ } as any
272
+ const conditionalMembers = getConditionalMembers(docState, 1)
273
+
274
+ expect(conditionalMembers).toEqual([{path: 'title', hidden: false, readOnly: false}])
275
+ })
229
276
  })
@@ -12,7 +12,8 @@ import {
12
12
  type SchemaType,
13
13
  } from 'sanity'
14
14
 
15
- const MAX_DEPTH = 8
15
+ const DEFAULT_MAX_DEPTH = 8
16
+ const ABSOLUTE_MAX_DEPTH = 50
16
17
 
17
18
  export interface ConditionalMemberState {
18
19
  path: string
@@ -37,7 +38,10 @@ interface ConditionalMemberInnerState extends ConditionalMemberState {
37
38
  * * If a parent path is readOnly, no child paths are included
38
39
  * * If a path is hidden, it is not included; only conditionally visible paths will be returned, with hidden: false
39
40
  */
40
- export function getConditionalMembers(docState: DocumentFormNode): ConditionalMemberState[] {
41
+ export function getConditionalMembers(
42
+ docState: DocumentFormNode,
43
+ maxDepth = DEFAULT_MAX_DEPTH,
44
+ ): ConditionalMemberState[] {
41
45
  const doc: ConditionalMemberInnerState = {
42
46
  path: '',
43
47
  hidden: false,
@@ -45,7 +49,7 @@ export function getConditionalMembers(docState: DocumentFormNode): ConditionalMe
45
49
  conditional: isConditional(docState.schemaType),
46
50
  }
47
51
  return (
48
- [doc, ...extractConditionalPaths(docState, MAX_DEPTH)]
52
+ [doc, ...extractConditionalPaths(docState, Math.min(maxDepth, ABSOLUTE_MAX_DEPTH))]
49
53
  .filter((v) => v.conditional)
50
54
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
51
55
  .map(({conditional, ...state}) => ({...state}))
package/src/index.ts CHANGED
@@ -1,9 +1,6 @@
1
- export * from './schemas/typeDefExtensions'
1
+ export {assist} from './plugin'
2
2
  export * from './schemas/serialize/SchemTypeTool'
3
-
3
+ export * from './schemas/typeDefExtensions'
4
4
  export {defaultLanguageOutputs} from './translate/paths'
5
5
  export * from './translate/types'
6
-
7
6
  export {contextDocumentTypeName} from './types'
8
-
9
- export {assist} from './plugin'
package/src/plugin.tsx CHANGED
@@ -1,23 +1,24 @@
1
+ import type {SanityClient} from '@sanity/client'
1
2
  import {definePlugin, isObjectSchemaType} from 'sanity'
2
- import {assistInspector} from './assistInspector'
3
+
4
+ import {AssistDocumentInputWrapper} from './assistDocument/AssistDocumentInput'
5
+ import {AssistDocumentLayout} from './assistDocument/AssistDocumentLayout'
3
6
  import {AssistFieldWrapper} from './assistFormComponents/AssistField'
4
- import {AssistLayout} from './assistLayout/AssistLayout'
5
7
  import {AssistFormBlock} from './assistFormComponents/AssistFormBlock'
8
+ import {AssistInlineFormBlock} from './assistFormComponents/AssistInlineFormBlock'
6
9
  import {AssistItem} from './assistFormComponents/AssistItem'
7
- import {SanityClient} from '@sanity/client'
10
+ import {assistInspector} from './assistInspector'
11
+ import {AssistLayout} from './assistLayout/AssistLayout'
12
+ import {ImageContextProvider} from './components/ImageContext'
8
13
  import {SafeValueInput} from './components/SafeValueInput'
9
- import {schemaTypes} from './schemas'
10
- import {AssistInlineFormBlock} from './assistFormComponents/AssistInlineFormBlock'
11
- import {assistFieldActions} from './fieldActions/assistFieldActions'
12
14
  import {packageName} from './constants'
13
- import {AssistDocumentInputWrapper} from './assistDocument/AssistDocumentInput'
14
- import {createAssistDocumentPresence} from './presence/AssistDocumentPresence'
15
+ import {assistFieldActions} from './fieldActions/assistFieldActions'
15
16
  import {isSchemaAssistEnabled} from './helpers/assistSupported'
16
17
  import {isImage} from './helpers/typeUtils'
17
- import {ImageContextProvider} from './components/ImageContext'
18
+ import {createAssistDocumentPresence} from './presence/AssistDocumentPresence'
19
+ import {schemaTypes} from './schemas'
18
20
  import {TranslationConfig} from './translate/types'
19
21
  import {assistDocumentTypeName, AssistPreset} from './types'
20
- import {AssistDocumentLayout} from './assistDocument/AssistDocumentLayout'
21
22
 
22
23
  export interface AssistPluginConfig {
23
24
  translate?: TranslationConfig
@@ -35,6 +36,14 @@ export interface AssistPluginConfig {
35
36
 
36
37
  export const assist = definePlugin<AssistPluginConfig | void>((config) => {
37
38
  const configWithDefaults = config ?? {}
39
+ const styleguide = configWithDefaults.translate?.styleguide || ''
40
+
41
+ if (styleguide.length > 2000) {
42
+ throw new Error(
43
+ `[${packageName}]: \`translate.styleguide\` value is too long. It must be 2000 characters or less, was ${styleguide.length} characters`,
44
+ )
45
+ }
46
+
38
47
  return {
39
48
  name: packageName,
40
49
 
@@ -68,6 +68,7 @@ function hasValuesToTranslate(
68
68
  export function FieldTranslationProvider(props: PropsWithChildren<{}>) {
69
69
  const {config: assistConfig} = useAiAssistanceConfig()
70
70
  const apiClient = useApiClient(assistConfig.__customApiClient)
71
+ const styleguide = assistConfig.translate?.styleguide
71
72
  const config = assistConfig.translate?.field
72
73
  const {translate: runTranslate} = useTranslate(apiClient)
73
74
 
@@ -112,7 +113,7 @@ export function FieldTranslationProvider(props: PropsWithChildren<{}>) {
112
113
  setToLanguages(filteredToLanguages)
113
114
  const fromId = from?.id
114
115
  const allToIds = allToLanguages?.map((l) => l.id) ?? []
115
- const docMembers = getDocumentMembersFlat(document, documentSchema)
116
+ const docMembers = getDocumentMembersFlat(document, documentSchema, config?.maxPathDepth)
116
117
  if (fromId && allToIds?.length) {
117
118
  const transMap = getFieldLanguageMap(
118
119
  documentSchema,
@@ -193,7 +194,8 @@ export function FieldTranslationProvider(props: PropsWithChildren<{}>) {
193
194
  if (fieldLanguageMaps && documentId && translatePath) {
194
195
  runTranslate({
195
196
  documentId,
196
- translatePath: translatePath,
197
+ translatePath,
198
+ styleguide,
197
199
  fieldLanguageMap: fieldLanguageMaps.map((map) => ({
198
200
  ...map,
199
201
  // eslint-disable-next-line max-nested-callbacks
@@ -207,6 +209,7 @@ export function FieldTranslationProvider(props: PropsWithChildren<{}>) {
207
209
  fieldLanguageMaps,
208
210
  documentId,
209
211
  runTranslate,
212
+ styleguide,
210
213
  close,
211
214
  toLanguages,
212
215
  fieldTranslationParams?.translatePath,
@@ -137,4 +137,45 @@ describe('paths', () => {
137
137
  'translations[_key=="en"].value',
138
138
  ])
139
139
  })
140
+
141
+ test('should limit depth to 1 when specified', () => {
142
+ const docSchema: ObjectSchemaType = Schema.compile({
143
+ name: 'test',
144
+ types: [
145
+ defineType({
146
+ type: 'document',
147
+ name: 'article',
148
+ fields: [
149
+ {
150
+ type: 'array',
151
+ name: 'translations',
152
+ of: [
153
+ {
154
+ type: 'object',
155
+ name: 'internationalizedArrayString',
156
+ fields: [{type: 'string', name: 'value'}],
157
+ },
158
+ ],
159
+ },
160
+ ],
161
+ }),
162
+ ],
163
+ }).get('article')
164
+
165
+ const doc: SanityDocumentLike = {
166
+ _id: 'na',
167
+ _type: 'article',
168
+ translations: [
169
+ {
170
+ //assume type is missing in the data for some reason
171
+ //_type: 'internationalizedArrayString',
172
+ _key: 'en',
173
+ value: 'some string',
174
+ },
175
+ ],
176
+ }
177
+
178
+ const members = getDocumentMembersFlat(doc, docSchema, 1)
179
+ expect(members.map((p) => pathToString(p.path))).toEqual(['translations'])
180
+ })
140
181
  })
@@ -16,15 +16,20 @@ export interface FieldLanguageMap {
16
16
  outputs: TranslationOutput[]
17
17
  }
18
18
 
19
- const MAX_DEPTH = 6
19
+ const DEFAULT_MAX_DEPTH = 6
20
+ const ABSOLUTE_MAX_DEPTH = 50
20
21
 
21
- export function getDocumentMembersFlat(doc: SanityDocumentLike, schemaType: ObjectSchemaType) {
22
+ export function getDocumentMembersFlat(
23
+ doc: SanityDocumentLike,
24
+ schemaType: ObjectSchemaType,
25
+ maxDepth = DEFAULT_MAX_DEPTH,
26
+ ) {
22
27
  if (!isDocumentSchemaType(schemaType)) {
23
28
  console.error(`Schema type is not a document`)
24
29
  return []
25
30
  }
26
31
 
27
- return extractPaths(doc, schemaType, [], MAX_DEPTH)
32
+ return extractPaths(doc, schemaType, [], Math.min(maxDepth, ABSOLUTE_MAX_DEPTH))
28
33
  }
29
34
 
30
35
  function extractPaths(
@@ -59,7 +64,9 @@ function extractPaths(
59
64
  } else if (
60
65
  fieldSchema.jsonType === 'array' &&
61
66
  fieldSchema.of.length &&
62
- fieldSchema.of.some((item) => 'fields' in item)
67
+ fieldSchema.of.some((item) => 'fields' in item) &&
68
+ // no reason to drill into arrays if the item fields will be culled by maxDepth, ie we need 1 extra path headroom
69
+ path.length + 1 < maxDepth
63
70
  ) {
64
71
  const {value: arrayValue} = extractWithPath(pathToString(fieldPath), doc)[0] ?? {}
65
72
 
@@ -73,6 +73,7 @@ export const translateActions: DocumentFieldAction = {
73
73
  task: translationApi.translate,
74
74
  })
75
75
 
76
+ const styleguide = config.translate?.styleguide
76
77
  const languagePath = config.translate?.document?.languageField
77
78
 
78
79
  // if this is true, it is stable, and not breaking rules of hooks
@@ -98,6 +99,7 @@ export const translateActions: DocumentFieldAction = {
98
99
  translate({
99
100
  languagePath,
100
101
  translatePath: path,
102
+ styleguide,
101
103
  documentId: documentId ?? '',
102
104
  conditionalMembers: formStateRef.current
103
105
  ? getConditionalMembers(formStateRef.current)
@@ -110,6 +112,7 @@ export const translateActions: DocumentFieldAction = {
110
112
  }, [
111
113
  languagePath,
112
114
  translate,
115
+ styleguide,
113
116
  documentId,
114
117
  translationApi.loading,
115
118
  documentTranslationEnabled,
@@ -123,6 +126,7 @@ export const translateActions: DocumentFieldAction = {
123
126
  task: fieldTranslate.openFieldTranslation,
124
127
  })
125
128
 
129
+ const maxDepth = config.translate?.field?.maxPathDepth
126
130
  const translateFieldsAction = useMemo(
127
131
  () =>
128
132
  fieldTransEnabled
@@ -148,7 +152,7 @@ export const translateActions: DocumentFieldAction = {
148
152
  documentSchema: documentSchemaType,
149
153
  translatePath: path,
150
154
  conditionalMembers: formStateRef.current
151
- ? getConditionalMembers(formStateRef.current)
155
+ ? getConditionalMembers(formStateRef.current, maxDepth)
152
156
  : [],
153
157
  })
154
158
  },
@@ -164,6 +168,7 @@ export const translateActions: DocumentFieldAction = {
164
168
  fieldTransEnabled,
165
169
  path,
166
170
  readOnly,
171
+ maxDepth,
167
172
  ],
168
173
  )
169
174
 
@@ -76,7 +76,6 @@ export interface FieldTranslationConfig {
76
76
  *
77
77
  * It determines the relationships between document paths: Given a document path and a language, into which
78
78
  * sibling paths should translations be output.
79
-
80
79
  *
81
80
  * `translationOutputs` is invoked once per path in the document (limited to a depth of 6), with the following:
82
81
  *
@@ -121,8 +120,24 @@ export interface FieldTranslationConfig {
121
120
  * return undefined
122
121
  * }
123
122
  * ```
123
+ *
124
+ * @see #maxPathDepth
124
125
  **/
125
126
  translationOutputs?: TranslationOutputsFunction
127
+
128
+ /**
129
+ * The max depth for document paths AI Assist will translate.
130
+ *
131
+ * Depth is based on field path segments:
132
+ * - `title` has depth 1
133
+ * - `array[_key="no"].title` has depth 3
134
+ *
135
+ * Be careful not to set this too high in studios with recursive document schemas, as it could have
136
+ * negative impact on performance.
137
+ *
138
+ * Default: 6
139
+ */
140
+ maxPathDepth?: number
126
141
  }
127
142
 
128
143
  export interface DocumentTranslationConfig {
@@ -157,4 +172,10 @@ export interface TranslationConfig {
157
172
  * Config for document types with a single language field that determines the language for the whole document.
158
173
  */
159
174
  document?: DocumentTranslationConfig
175
+ /**
176
+ * A "style guide" that can be used to provide guidance on how to translate content.
177
+ * Will be passed to the LLM - ergo this is only a guide and the model _may_ not
178
+ * always follow it to the letter.
179
+ */
180
+ styleguide?: string
160
181
  }
package/src/types.ts CHANGED
@@ -1,5 +1,5 @@
1
- import {SanityDocument} from 'sanity'
2
1
  import {PortableTextBlock, PortableTextMarkDefinition, PortableTextSpan} from '@portabletext/types'
2
+ import {SanityDocument} from 'sanity'
3
3
 
4
4
  //id prefixes
5
5
  export const assistDocumentIdPrefix = 'sanity.assist.schemaType.'
@@ -1,11 +1,12 @@
1
- import {Path, pathToString, useClient, useCurrentUser, useSchema} from 'sanity'
1
+ import type {SanityClient} from '@sanity/client'
2
+ import {useToast} from '@sanity/ui'
2
3
  import {useCallback, useMemo, useState} from 'react'
4
+ import {Path, pathToString, useClient, useCurrentUser, useSchema} from 'sanity'
5
+
6
+ import {ConditionalMemberState} from './helpers/conditionalMembers'
3
7
  import {serializeSchema} from './schemas/serialize/serializeSchema'
4
- import {useToast} from '@sanity/ui'
5
- import {SanityClient} from '@sanity/client'
6
8
  import {FieldLanguageMap} from './translate/paths'
7
9
  import {documentRootKey} from './types'
8
- import {ConditionalMemberState} from './helpers/conditionalMembers'
9
10
 
10
11
  export interface UserTextInstance {
11
12
  blockKey: string
@@ -33,6 +34,7 @@ export interface TranslateRequest {
33
34
  documentId: string
34
35
  translatePath: Path
35
36
  languagePath?: string
37
+ styleguide?: string
36
38
  fieldLanguageMap?: FieldLanguageMap[]
37
39
  conditionalMembers?: ConditionalMemberState[]
38
40
  }
@@ -62,6 +64,7 @@ export function useTranslate(apiClient: SanityClient) {
62
64
  ({
63
65
  documentId,
64
66
  languagePath,
67
+ styleguide,
65
68
  translatePath,
66
69
  fieldLanguageMap,
67
70
  conditionalMembers,
@@ -78,6 +81,7 @@ export function useTranslate(apiClient: SanityClient) {
78
81
  documentId,
79
82
  types,
80
83
  languagePath,
84
+ userStyleguide: styleguide,
81
85
  fieldLanguageMap,
82
86
  conditionalMembers,
83
87
  translatePath: