@sanity/assist 2.0.0 → 2.0.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.
- package/dist/index.d.ts +82 -0
- package/dist/index.esm.js +82 -21
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +82 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/assistDocument/components/AssistDocumentForm.tsx +65 -21
- package/src/components/ImageContext.tsx +11 -4
- package/src/fieldActions/generateCaptionActions.tsx +15 -4
- package/src/helpers/misc.ts +8 -4
- package/src/plugin.tsx +6 -1
- package/src/types.ts +34 -3
- package/src/useApiClient.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/assist",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "You create the instructions; Sanity AI Assist does the rest.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@rollup/plugin-image": "^3.0.3",
|
|
71
71
|
"@sanity/pkg-utils": "^2.4.10",
|
|
72
72
|
"@sanity/plugin-kit": "^3.1.10",
|
|
73
|
-
"@sanity/semantic-release-preset": "^4.1.
|
|
73
|
+
"@sanity/semantic-release-preset": "^4.1.7",
|
|
74
74
|
"@types/react": "^18.2.37",
|
|
75
75
|
"@types/styled-components": "^5.1.30",
|
|
76
76
|
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"react-dom": "^18.2.0",
|
|
88
88
|
"rimraf": "^5.0.5",
|
|
89
89
|
"sanity": "^3.28.0",
|
|
90
|
-
"semantic-release": "^
|
|
90
|
+
"semantic-release": "^23.0.2",
|
|
91
91
|
"styled-components": "^6.1.1",
|
|
92
92
|
"typescript": "^5.3.3",
|
|
93
93
|
"vitest": "^1.2.1"
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
documentRootKey,
|
|
7
7
|
fieldPathParam,
|
|
8
8
|
instructionParam,
|
|
9
|
+
StudioInstruction,
|
|
9
10
|
} from '../../types'
|
|
10
11
|
import {createContext, useContext, useEffect, useMemo, useRef} from 'react'
|
|
11
12
|
import {
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
KeyedSegment,
|
|
17
18
|
ObjectInputProps,
|
|
18
19
|
ObjectSchemaType,
|
|
20
|
+
PatchEvent,
|
|
19
21
|
Path,
|
|
20
22
|
SchemaType,
|
|
21
23
|
set,
|
|
@@ -30,6 +32,7 @@ import {useAiPaneRouter} from '../../assistInspector/helpers'
|
|
|
30
32
|
import {SelectedFieldContextProvider, SelectedFieldContextValue} from './SelectedFieldContext'
|
|
31
33
|
import {Card, Stack, Text} from '@sanity/ui'
|
|
32
34
|
import {documentTypeFromAiDocumentId} from '../../helpers/ids'
|
|
35
|
+
import {useAiAssistanceConfig} from '../../assistLayout/AiAssistanceConfigContext'
|
|
33
36
|
|
|
34
37
|
const EMPTY_FIELDS: AssistField[] = []
|
|
35
38
|
|
|
@@ -104,8 +107,6 @@ function AssistDocumentFormEditable(props: ObjectInputProps) {
|
|
|
104
107
|
}
|
|
105
108
|
}, [title, documentSchema, onChange, id])
|
|
106
109
|
|
|
107
|
-
const fieldExists = !!fields?.some((f) => f._key === typePath)
|
|
108
|
-
|
|
109
110
|
const {onPathOpen, ...formCallbacks} = useFormCallbacks()
|
|
110
111
|
|
|
111
112
|
const newCallbacks: FormCallbacksValue = useMemo(
|
|
@@ -141,7 +142,8 @@ function AssistDocumentFormEditable(props: ObjectInputProps) {
|
|
|
141
142
|
key={typePath}
|
|
142
143
|
pathKey={typePath}
|
|
143
144
|
activePath={activePath}
|
|
144
|
-
|
|
145
|
+
fields={fields}
|
|
146
|
+
documentSchema={documentSchema}
|
|
145
147
|
onChange={onChange}
|
|
146
148
|
/>
|
|
147
149
|
{instruction && <BackToInstructionListLink />}
|
|
@@ -195,36 +197,78 @@ function useSelectedSchema(
|
|
|
195
197
|
function FieldsInitializer({
|
|
196
198
|
pathKey,
|
|
197
199
|
activePath,
|
|
198
|
-
|
|
200
|
+
fields,
|
|
201
|
+
documentSchema,
|
|
199
202
|
onChange,
|
|
200
203
|
}: {
|
|
201
204
|
pathKey?: string
|
|
202
205
|
activePath?: Path
|
|
203
|
-
|
|
206
|
+
fields: AssistField[] | undefined
|
|
207
|
+
documentSchema: ObjectSchemaType | undefined
|
|
204
208
|
onChange: ObjectInputProps['onChange']
|
|
205
209
|
}) {
|
|
210
|
+
const {
|
|
211
|
+
config: {__presets: presets},
|
|
212
|
+
} = useAiAssistanceConfig()
|
|
213
|
+
|
|
214
|
+
const existingField = fields?.find((f) => f._key === pathKey)
|
|
215
|
+
const documentPresets = !!documentSchema?.name && presets?.[documentSchema?.name]
|
|
216
|
+
|
|
217
|
+
const missingPresetInstructions = useMemo(() => {
|
|
218
|
+
if (!documentPresets || !pathKey) {
|
|
219
|
+
return undefined
|
|
220
|
+
}
|
|
221
|
+
const existingInstructions = existingField?.instructions
|
|
222
|
+
const presetField = documentPresets.fields?.find((f) => f.path === pathKey)
|
|
223
|
+
return presetField?.instructions?.filter(
|
|
224
|
+
(i) => !existingInstructions?.some((ei) => ei._key === i._key)
|
|
225
|
+
)
|
|
226
|
+
}, [documentPresets, pathKey, existingField])
|
|
227
|
+
|
|
206
228
|
// need this to not fire onChange twice in React strict mode
|
|
207
229
|
const initialized = useRef(false)
|
|
208
230
|
useEffect(() => {
|
|
209
|
-
if (initialized.current ||
|
|
231
|
+
if (initialized.current || !pathKey) {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
if (existingField && !missingPresetInstructions?.length) {
|
|
210
235
|
return
|
|
211
236
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
237
|
+
|
|
238
|
+
let event = PatchEvent.from([setIfMissing([], ['fields'])])
|
|
239
|
+
if (!existingField) {
|
|
240
|
+
event = event.append(
|
|
241
|
+
insert(
|
|
242
|
+
[
|
|
243
|
+
typed<AssistField>({
|
|
244
|
+
_key: pathKey,
|
|
245
|
+
_type: assistFieldTypeName,
|
|
246
|
+
path: pathKey,
|
|
247
|
+
}),
|
|
248
|
+
],
|
|
249
|
+
'after',
|
|
250
|
+
['fields', -1]
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
if (missingPresetInstructions?.length) {
|
|
255
|
+
event = event.append(
|
|
256
|
+
insert(
|
|
257
|
+
missingPresetInstructions.map(
|
|
258
|
+
(preset): StudioInstruction => ({
|
|
259
|
+
...preset,
|
|
260
|
+
_type: 'sanity.assist.instruction',
|
|
261
|
+
prompt: preset.prompt?.map((p) => ({markDefs: [], ...p})),
|
|
262
|
+
})
|
|
263
|
+
),
|
|
264
|
+
'after',
|
|
265
|
+
['fields', {_key: pathKey}, 'instructions', -1]
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
onChange(event)
|
|
226
270
|
initialized.current = true
|
|
227
|
-
}, [activePath, onChange, pathKey,
|
|
271
|
+
}, [activePath, onChange, pathKey, existingField, missingPresetInstructions])
|
|
228
272
|
|
|
229
273
|
return null
|
|
230
274
|
}
|
|
@@ -2,7 +2,7 @@ import {createContext, useEffect, useMemo, useState} from 'react'
|
|
|
2
2
|
import {InputProps, pathToString, useSyncState} from 'sanity'
|
|
3
3
|
import {getDescriptionFieldOption, getImageInstructionFieldOption} from '../helpers/typeUtils'
|
|
4
4
|
import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
|
|
5
|
-
import {useApiClient, useGenerateCaption} from '../useApiClient'
|
|
5
|
+
import {canUseAssist, useApiClient, useGenerateCaption} from '../useApiClient'
|
|
6
6
|
import {useAiAssistanceConfig} from '../assistLayout/AiAssistanceConfigContext'
|
|
7
7
|
import {publicId} from '../helpers/ids'
|
|
8
8
|
|
|
@@ -20,7 +20,7 @@ export function ImageContextProvider(props: InputProps) {
|
|
|
20
20
|
const [assetRefState, setAssetRefState] = useState<string | undefined>(assetRef)
|
|
21
21
|
|
|
22
22
|
const {documentId, documentSchemaType} = useAssistDocumentContext()
|
|
23
|
-
const {config} = useAiAssistanceConfig()
|
|
23
|
+
const {config, status} = useAiAssistanceConfig()
|
|
24
24
|
const apiClient = useApiClient(config?.__customApiClient)
|
|
25
25
|
const {generateCaption} = useGenerateCaption(apiClient)
|
|
26
26
|
|
|
@@ -28,11 +28,18 @@ export function ImageContextProvider(props: InputProps) {
|
|
|
28
28
|
|
|
29
29
|
useEffect(() => {
|
|
30
30
|
const descriptionField = getDescriptionFieldOption(schemaType)
|
|
31
|
-
if (
|
|
31
|
+
if (
|
|
32
|
+
assetRef &&
|
|
33
|
+
documentId &&
|
|
34
|
+
descriptionField &&
|
|
35
|
+
assetRef !== assetRefState &&
|
|
36
|
+
!isSyncing &&
|
|
37
|
+
canUseAssist(status)
|
|
38
|
+
) {
|
|
32
39
|
setAssetRefState(assetRef)
|
|
33
40
|
generateCaption({path: pathToString([...path, descriptionField]), documentId: documentId})
|
|
34
41
|
}
|
|
35
|
-
}, [schemaType, path, assetRef, assetRefState, documentId, generateCaption, isSyncing])
|
|
42
|
+
}, [schemaType, path, assetRef, assetRefState, documentId, generateCaption, isSyncing, status])
|
|
36
43
|
|
|
37
44
|
const context: ImageContextValue = useMemo(() => {
|
|
38
45
|
const descriptionField = getDescriptionFieldOption(schemaType)
|
|
@@ -2,11 +2,14 @@ import {DocumentFieldAction, DocumentFieldActionGroup, DocumentFieldActionItem}
|
|
|
2
2
|
import {ImageIcon} from '@sanity/icons'
|
|
3
3
|
import {useContext, useMemo} from 'react'
|
|
4
4
|
import {usePathKey} from '../helpers/misc'
|
|
5
|
-
import {useApiClient, useGenerateCaption} from '../useApiClient'
|
|
5
|
+
import {canUseAssist, useApiClient, useGenerateCaption} from '../useApiClient'
|
|
6
6
|
import {useAiAssistanceConfig} from '../assistLayout/AiAssistanceConfigContext'
|
|
7
7
|
import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
|
|
8
8
|
import {ImageContext} from '../components/ImageContext'
|
|
9
9
|
import {Box, Spinner} from '@sanity/ui'
|
|
10
|
+
import {useDocumentPane} from 'sanity/desk'
|
|
11
|
+
import {aiInspectorId} from '../assistInspector/constants'
|
|
12
|
+
import {fieldPathParam, instructionParam} from '../types'
|
|
10
13
|
|
|
11
14
|
function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) {
|
|
12
15
|
return node
|
|
@@ -16,11 +19,11 @@ export const generateCaptionsActions: DocumentFieldAction = {
|
|
|
16
19
|
name: 'sanity-assist-generate-captions',
|
|
17
20
|
useAction(props) {
|
|
18
21
|
const pathKey = usePathKey(props.path)
|
|
22
|
+
const {openInspector} = useDocumentPane()
|
|
19
23
|
|
|
20
|
-
const {config} = useAiAssistanceConfig()
|
|
24
|
+
const {config, status} = useAiAssistanceConfig()
|
|
21
25
|
const apiClient = useApiClient(config?.__customApiClient)
|
|
22
26
|
const {generateCaption, loading} = useGenerateCaption(apiClient)
|
|
23
|
-
|
|
24
27
|
const imageContext = useContext(ImageContext)
|
|
25
28
|
|
|
26
29
|
if (imageContext && pathKey === imageContext?.imageDescriptionPath) {
|
|
@@ -43,13 +46,21 @@ export const generateCaptionsActions: DocumentFieldAction = {
|
|
|
43
46
|
if (loading) {
|
|
44
47
|
return
|
|
45
48
|
}
|
|
49
|
+
if (!canUseAssist(status)) {
|
|
50
|
+
openInspector(aiInspectorId, {
|
|
51
|
+
[fieldPathParam]: pathKey,
|
|
52
|
+
[instructionParam]: undefined as any,
|
|
53
|
+
})
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
generateCaption({path: pathKey, documentId: documentId ?? ''})
|
|
47
58
|
},
|
|
48
59
|
renderAsButton: true,
|
|
49
60
|
disabled: loading,
|
|
50
61
|
hidden: !imageContext.assetRef,
|
|
51
62
|
})
|
|
52
|
-
}, [generateCaption, pathKey, documentId, loading, imageContext])
|
|
63
|
+
}, [generateCaption, pathKey, documentId, loading, imageContext, status, openInspector])
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
// works but not supported by types
|
package/src/helpers/misc.ts
CHANGED
|
@@ -4,13 +4,17 @@ import {useMemo} from 'react'
|
|
|
4
4
|
|
|
5
5
|
export function usePathKey(path: Path | string) {
|
|
6
6
|
return useMemo(() => {
|
|
7
|
-
|
|
8
|
-
return Array.isArray(path) ? pathToString(path) : path
|
|
9
|
-
}
|
|
10
|
-
return documentRootKey
|
|
7
|
+
return getPathKey(path)
|
|
11
8
|
}, [path])
|
|
12
9
|
}
|
|
13
10
|
|
|
11
|
+
export function getPathKey(path: Path | string) {
|
|
12
|
+
if (path.length) {
|
|
13
|
+
return Array.isArray(path) ? pathToString(path) : path
|
|
14
|
+
}
|
|
15
|
+
return documentRootKey
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
export function getInstructionTitle(instruction?: StudioInstruction) {
|
|
15
19
|
return instruction?.title ?? 'Untitled'
|
|
16
20
|
}
|
package/src/plugin.tsx
CHANGED
|
@@ -16,7 +16,7 @@ import {isSchemaAssistEnabled} from './helpers/assistSupported'
|
|
|
16
16
|
import {isImage} from './helpers/typeUtils'
|
|
17
17
|
import {ImageContextProvider} from './components/ImageContext'
|
|
18
18
|
import {TranslationConfig} from './translate/types'
|
|
19
|
-
import {assistDocumentTypeName} from './types'
|
|
19
|
+
import {assistDocumentTypeName, AssistPreset} from './types'
|
|
20
20
|
|
|
21
21
|
export interface AssistPluginConfig {
|
|
22
22
|
translate?: TranslationConfig
|
|
@@ -25,6 +25,11 @@ export interface AssistPluginConfig {
|
|
|
25
25
|
* @internal
|
|
26
26
|
*/
|
|
27
27
|
__customApiClient?: (defaultClient: SanityClient) => SanityClient
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
__presets?: Record<string, AssistPreset>
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
export const assist = definePlugin<AssistPluginConfig | void>((config) => {
|
package/src/types.ts
CHANGED
|
@@ -68,14 +68,42 @@ export interface AssistDocument extends SanityDocument {
|
|
|
68
68
|
instructions?: StudioInstruction[]
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export interface
|
|
71
|
+
export interface PresetInstruction {
|
|
72
|
+
_key: string
|
|
73
|
+
prompt?: PromptTextBlock[]
|
|
74
|
+
|
|
75
|
+
title?: string
|
|
76
|
+
/**
|
|
77
|
+
* String key from @sanity/icons IconMap
|
|
78
|
+
*/
|
|
79
|
+
icon?: string
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Type/field filter
|
|
83
|
+
*/
|
|
84
|
+
output?: (OutputFieldItem | OutputTypeItem)[]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface PresetField {
|
|
88
|
+
path?: string
|
|
89
|
+
instructions?: PresetInstruction[]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AssistPreset {
|
|
93
|
+
fields?: PresetField[]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface SanityAssistDocument {
|
|
72
97
|
_id: string
|
|
73
98
|
_type: typeof assistDocumentTypeName
|
|
74
99
|
fields?: StudioAssistField[]
|
|
100
|
+
}
|
|
75
101
|
|
|
76
|
-
|
|
102
|
+
export interface StudioAssistDocument extends SanityAssistDocument {
|
|
103
|
+
// added after loading
|
|
77
104
|
tasks?: InstructionTask[]
|
|
78
105
|
}
|
|
106
|
+
|
|
79
107
|
export interface AssistField {
|
|
80
108
|
_key: string
|
|
81
109
|
_type: typeof assistFieldTypeName
|
|
@@ -113,7 +141,10 @@ export interface UserInputBlock {
|
|
|
113
141
|
}
|
|
114
142
|
|
|
115
143
|
export type InlinePromptBlock = PortableTextSpan | FieldRef | UserInputBlock | ContextBlock
|
|
116
|
-
export type PromptTextBlock =
|
|
144
|
+
export type PromptTextBlock = Omit<
|
|
145
|
+
PortableTextBlock<never, InlinePromptBlock, 'normal', never>,
|
|
146
|
+
'_type'
|
|
147
|
+
> & {_type: 'block'}
|
|
117
148
|
|
|
118
149
|
export type PromptBlock = PromptTextBlock | FieldRef | ContextBlock | UserInputBlock
|
|
119
150
|
|
package/src/useApiClient.ts
CHANGED
|
@@ -39,6 +39,10 @@ export interface TranslateRequest {
|
|
|
39
39
|
|
|
40
40
|
const basePath = '/assist/tasks/instruction'
|
|
41
41
|
|
|
42
|
+
export function canUseAssist(status: InstructStatus | undefined) {
|
|
43
|
+
return status?.enabled && status.initialized && status.validToken
|
|
44
|
+
}
|
|
45
|
+
|
|
42
46
|
export function useApiClient(customApiClient?: (defaultClient: SanityClient) => SanityClient) {
|
|
43
47
|
const client = useClient({apiVersion: '2023-06-05'})
|
|
44
48
|
return useMemo(
|