@sanity/assist 1.2.16 → 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/LICENSE +1 -1
- package/README.md +551 -30
- package/dist/index.cjs.mjs +1 -0
- package/dist/index.d.ts +333 -9
- package/dist/index.esm.js +2463 -390
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2457 -383
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
- 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/AssistDocumentForm.tsx +65 -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 +30 -13
- package/src/components/SafeValueInput.tsx +4 -1
- package/src/fieldActions/assistFieldActions.tsx +42 -13
- package/src/fieldActions/generateCaptionActions.tsx +17 -6
- 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/misc.ts +8 -4
- package/src/helpers/typeUtils.ts +19 -5
- package/src/index.ts +3 -0
- package/src/plugin.tsx +18 -4
- 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 +67 -15
- package/src/useApiClient.ts +134 -2
- package/src/assistLayout/AlphaMigration.tsx +0 -310
- package/src/legacy-types.ts +0 -72
|
@@ -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
|
)
|
|
@@ -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 {type PaneRouterContextValue, usePaneRouter} 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'
|
|
@@ -96,7 +96,7 @@ export function getFieldRefs(
|
|
|
96
96
|
|
|
97
97
|
const syntheticFields =
|
|
98
98
|
field.type.jsonType === 'array' ? getSyntheticFields(field.type, fieldRef, depth + 1) : []
|
|
99
|
-
if (!isAssistSupported(field.type
|
|
99
|
+
if (!isAssistSupported(field.type)) {
|
|
100
100
|
return [...fields, ...syntheticFields]
|
|
101
101
|
}
|
|
102
102
|
return [fieldRef, ...fields, ...syntheticFields]
|
|
@@ -125,7 +125,7 @@ function getSyntheticFields(schemaType: ArraySchemaType, parent?: FieldRef, dept
|
|
|
125
125
|
const fields =
|
|
126
126
|
itemType.jsonType === 'object' ? getFieldRefs(itemType, fieldRef, depth + 1) : []
|
|
127
127
|
|
|
128
|
-
if (!isAssistSupported(itemType
|
|
128
|
+
if (!isAssistSupported(itemType)) {
|
|
129
129
|
return fields
|
|
130
130
|
}
|
|
131
131
|
return [fieldRef, ...fields]
|
|
@@ -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
|
}
|
|
@@ -8,7 +8,7 @@ import {RunInstructionRequest} from '../useApiClient'
|
|
|
8
8
|
import {StudioInstruction} from '../types'
|
|
9
9
|
import {RunInstructionProvider} from './RunInstructionProvider'
|
|
10
10
|
import {ThemeProvider} from '@sanity/ui'
|
|
11
|
-
import {
|
|
11
|
+
import {FieldTranslationProvider} from '../translate/FieldTranslationProvider'
|
|
12
12
|
|
|
13
13
|
export interface AIStudioLayoutProps extends LayoutProps {
|
|
14
14
|
config: AssistPluginConfig
|
|
@@ -20,18 +20,18 @@ export type RunInstructionArgs = Omit<RunInstructionRequest, 'instructionKey' |
|
|
|
20
20
|
|
|
21
21
|
export function AssistLayout(props: AIStudioLayoutProps) {
|
|
22
22
|
const [connectors, setConnectors] = useState<Connector[]>([])
|
|
23
|
-
const migrate = props.config.alphaMigration ?? true
|
|
24
23
|
|
|
25
24
|
return (
|
|
26
25
|
<AiAssistanceConfigProvider config={props.config}>
|
|
27
|
-
{migrate ? <AlphaMigration /> : null}
|
|
28
26
|
<RunInstructionProvider>
|
|
29
|
-
<
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
<FieldTranslationProvider>
|
|
28
|
+
<ConnectorsProvider onConnectorsChange={setConnectors}>
|
|
29
|
+
{props.renderDefault(props)}
|
|
30
|
+
<ThemeProvider tone="default">
|
|
31
|
+
<AssistConnectorsOverlay connectors={connectors} />
|
|
32
|
+
</ThemeProvider>
|
|
33
|
+
</ConnectorsProvider>
|
|
34
|
+
</FieldTranslationProvider>
|
|
35
35
|
</RunInstructionProvider>
|
|
36
36
|
</AiAssistanceConfigProvider>
|
|
37
37
|
)
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import {createContext, useEffect, useMemo, useState} from 'react'
|
|
2
2
|
import {InputProps, pathToString, useSyncState} from 'sanity'
|
|
3
|
-
import {
|
|
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
|
|
|
9
9
|
export interface ImageContextValue {
|
|
10
|
-
|
|
10
|
+
imageDescriptionPath?: string
|
|
11
|
+
imageInstructionPath?: string
|
|
11
12
|
assetRef?: string
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export const ImageContext = createContext<ImageContextValue
|
|
15
|
+
export const ImageContext = createContext<ImageContextValue>({})
|
|
15
16
|
|
|
16
17
|
export function ImageContextProvider(props: InputProps) {
|
|
17
18
|
const {schemaType, path, value} = props
|
|
@@ -19,23 +20,39 @@ export function ImageContextProvider(props: InputProps) {
|
|
|
19
20
|
const [assetRefState, setAssetRefState] = useState<string | undefined>(assetRef)
|
|
20
21
|
|
|
21
22
|
const {documentId, documentSchemaType} = useAssistDocumentContext()
|
|
22
|
-
const {config} = useAiAssistanceConfig()
|
|
23
|
+
const {config, status} = useAiAssistanceConfig()
|
|
23
24
|
const apiClient = useApiClient(config?.__customApiClient)
|
|
24
25
|
const {generateCaption} = useGenerateCaption(apiClient)
|
|
25
26
|
|
|
26
27
|
const {isSyncing} = useSyncState(publicId(documentId), documentSchemaType.name)
|
|
27
28
|
|
|
28
29
|
useEffect(() => {
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
30
|
+
const descriptionField = getDescriptionFieldOption(schemaType)
|
|
31
|
+
if (
|
|
32
|
+
assetRef &&
|
|
33
|
+
documentId &&
|
|
34
|
+
descriptionField &&
|
|
35
|
+
assetRef !== assetRefState &&
|
|
36
|
+
!isSyncing &&
|
|
37
|
+
canUseAssist(status)
|
|
38
|
+
) {
|
|
31
39
|
setAssetRefState(assetRef)
|
|
32
|
-
generateCaption({path: pathToString([...path,
|
|
40
|
+
generateCaption({path: pathToString([...path, descriptionField]), documentId: documentId})
|
|
41
|
+
}
|
|
42
|
+
}, [schemaType, path, assetRef, assetRefState, documentId, generateCaption, isSyncing, status])
|
|
43
|
+
|
|
44
|
+
const context: ImageContextValue = useMemo(() => {
|
|
45
|
+
const descriptionField = getDescriptionFieldOption(schemaType)
|
|
46
|
+
const imageInstructionField = getImageInstructionFieldOption(schemaType)
|
|
47
|
+
return {
|
|
48
|
+
imageDescriptionPath: descriptionField
|
|
49
|
+
? pathToString([...path, descriptionField])
|
|
50
|
+
: undefined,
|
|
51
|
+
imageInstructionPath: imageInstructionField
|
|
52
|
+
? pathToString([...path, imageInstructionField])
|
|
53
|
+
: undefined,
|
|
54
|
+
assetRef,
|
|
33
55
|
}
|
|
34
|
-
}, [schemaType, path, assetRef, assetRefState, documentId, generateCaption, isSyncing])
|
|
35
|
-
|
|
36
|
-
const context: ImageContextValue | undefined = useMemo(() => {
|
|
37
|
-
const captionField = getCaptionFieldOption(schemaType)
|
|
38
|
-
return captionField ? {captionPath: pathToString([...path, captionField]), assetRef} : undefined
|
|
39
56
|
}, [schemaType, path, assetRef])
|
|
40
57
|
|
|
41
58
|
return <ImageContext.Provider value={context}>{props.renderDefault(props)}</ImageContext.Provider>
|
|
@@ -31,7 +31,10 @@ export function ErrorWrapper(
|
|
|
31
31
|
[setError]
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
const unsetValue = useCallback(() =>
|
|
34
|
+
const unsetValue = useCallback(() => {
|
|
35
|
+
onChange(PatchEvent.from(unset()))
|
|
36
|
+
setError(undefined)
|
|
37
|
+
}, [onChange])
|
|
35
38
|
const dismiss = useCallback(() => setError(undefined), [])
|
|
36
39
|
const catcher = <ErrorBoundary onCatch={catchError}>{props.children}</ErrorBoundary>
|
|
37
40
|
|
|
@@ -3,11 +3,12 @@ import {
|
|
|
3
3
|
DocumentFieldActionGroup,
|
|
4
4
|
DocumentFieldActionItem,
|
|
5
5
|
ObjectSchemaType,
|
|
6
|
+
typed,
|
|
6
7
|
useCurrentUser,
|
|
7
8
|
} from 'sanity'
|
|
8
9
|
import {ControlsIcon, SparklesIcon} from '@sanity/icons'
|
|
9
|
-
import {useCallback, useMemo} from 'react'
|
|
10
|
-
import {
|
|
10
|
+
import {useCallback, useMemo, useRef} from 'react'
|
|
11
|
+
import {pluginTitleShort} from '../constants'
|
|
11
12
|
import {useAssistSupported} from '../helpers/useAssistSupported'
|
|
12
13
|
import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
|
|
13
14
|
import {getInstructionTitle, usePathKey} from '../helpers/misc'
|
|
@@ -24,6 +25,9 @@ import {generateCaptionsActions} from './generateCaptionActions'
|
|
|
24
25
|
import {useDocumentPane} from 'sanity/desk'
|
|
25
26
|
import {useSelectedField, useTypePath} from '../assistInspector/helpers'
|
|
26
27
|
import {isSchemaAssistEnabled} from '../helpers/assistSupported'
|
|
28
|
+
import {translateActions, TranslateProps} from '../translate/translateActions'
|
|
29
|
+
import {generateImagActions} from './generateImageActions'
|
|
30
|
+
import {getConditionalMembers} from '../helpers/conditionalMembers'
|
|
27
31
|
|
|
28
32
|
function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) {
|
|
29
33
|
return node
|
|
@@ -47,6 +51,7 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
47
51
|
documentSchemaType,
|
|
48
52
|
documentId,
|
|
49
53
|
selectedPath,
|
|
54
|
+
assistableDocumentId,
|
|
50
55
|
} =
|
|
51
56
|
// document field actions do not have access to the document context
|
|
52
57
|
// conditional hook _should_ be safe here since the logical path will be stable
|
|
@@ -56,7 +61,10 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
56
61
|
: // eslint-disable-next-line react-hooks/rules-of-hooks
|
|
57
62
|
useAssistDocumentContext()
|
|
58
63
|
|
|
59
|
-
const {value: docValue} = useDocumentPane()
|
|
64
|
+
const {value: docValue, formState} = useDocumentPane()
|
|
65
|
+
const formStateRef = useRef(formState)
|
|
66
|
+
formStateRef.current = formState
|
|
67
|
+
|
|
60
68
|
const currentUser = useCurrentUser()
|
|
61
69
|
const isHidden = !assistDocument
|
|
62
70
|
const pathKey = usePathKey(props.path)
|
|
@@ -73,7 +81,8 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
73
81
|
const assistSupported =
|
|
74
82
|
useAssistSupported(props.path, schemaType) &&
|
|
75
83
|
isSelectable &&
|
|
76
|
-
isSchemaAssistEnabled(documentSchemaType)
|
|
84
|
+
isSchemaAssistEnabled(documentSchemaType) &&
|
|
85
|
+
schemaType.readOnly !== true
|
|
77
86
|
|
|
78
87
|
const fieldAssist = useMemo(
|
|
79
88
|
() =>
|
|
@@ -89,7 +98,15 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
89
98
|
const isSelected = isInspectorOpen && isPathSelected
|
|
90
99
|
|
|
91
100
|
const imageCaptionAction = generateCaptionsActions.useAction(props)
|
|
92
|
-
|
|
101
|
+
const imageGenAction = generateImagActions.useAction(props)
|
|
102
|
+
const translateAction = translateActions.useAction(
|
|
103
|
+
typed<TranslateProps>({
|
|
104
|
+
...props,
|
|
105
|
+
documentId: assistableDocumentId,
|
|
106
|
+
documentIsAssistable,
|
|
107
|
+
documentSchemaType,
|
|
108
|
+
})
|
|
109
|
+
)
|
|
93
110
|
const manageInstructions = useCallback(
|
|
94
111
|
() =>
|
|
95
112
|
isSelected
|
|
@@ -112,6 +129,9 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
112
129
|
path: pathKey,
|
|
113
130
|
typePath,
|
|
114
131
|
instruction,
|
|
132
|
+
conditionalMembers: formStateRef.current
|
|
133
|
+
? getConditionalMembers(formStateRef.current)
|
|
134
|
+
: [],
|
|
115
135
|
})
|
|
116
136
|
},
|
|
117
137
|
[requestRunInstruction, assistableDocId, pathKey, typePath, assistDocumentId, fieldAssistKey]
|
|
@@ -134,7 +154,7 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
134
154
|
)
|
|
135
155
|
|
|
136
156
|
const runInstructionsGroup = useMemo(() => {
|
|
137
|
-
return instructions?.length || imageCaptionAction
|
|
157
|
+
return instructions?.length || imageCaptionAction || translateAction || imageGenAction
|
|
138
158
|
? node({
|
|
139
159
|
type: 'group',
|
|
140
160
|
icon: () => null,
|
|
@@ -151,7 +171,8 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
151
171
|
})
|
|
152
172
|
),
|
|
153
173
|
imageCaptionAction,
|
|
154
|
-
|
|
174
|
+
imageGenAction,
|
|
175
|
+
].filter((a): a is DocumentFieldActionItem => !!a),
|
|
155
176
|
expanded: true,
|
|
156
177
|
})
|
|
157
178
|
: undefined
|
|
@@ -163,6 +184,8 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
163
184
|
documentIsNew,
|
|
164
185
|
assistSupported,
|
|
165
186
|
imageCaptionAction,
|
|
187
|
+
translateAction,
|
|
188
|
+
imageGenAction,
|
|
166
189
|
])
|
|
167
190
|
|
|
168
191
|
const instructionsLength = instructions?.length || 0
|
|
@@ -185,12 +208,16 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
185
208
|
type: 'group',
|
|
186
209
|
icon: SparklesIcon,
|
|
187
210
|
title: pluginTitleShort,
|
|
188
|
-
children: [
|
|
189
|
-
|
|
190
|
-
|
|
211
|
+
children: [
|
|
212
|
+
runInstructionsGroup,
|
|
213
|
+
translateAction,
|
|
214
|
+
assistSupported && manageInstructionsItem,
|
|
215
|
+
]
|
|
216
|
+
.filter((c): c is DocumentFieldActionItem | DocumentFieldActionGroup => !!c)
|
|
217
|
+
.filter((c) => (c.type === 'group' ? c.children.length : true)),
|
|
191
218
|
expanded: false,
|
|
192
219
|
renderAsButton: true,
|
|
193
|
-
hidden: !assistSupported && !imageCaptionAction,
|
|
220
|
+
hidden: !assistSupported && !imageCaptionAction && !translateAction && !imageGenAction,
|
|
194
221
|
}),
|
|
195
222
|
[
|
|
196
223
|
//documentIsNew,
|
|
@@ -198,6 +225,8 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
198
225
|
manageInstructionsItem,
|
|
199
226
|
assistSupported,
|
|
200
227
|
imageCaptionAction,
|
|
228
|
+
translateAction,
|
|
229
|
+
imageGenAction,
|
|
201
230
|
]
|
|
202
231
|
)
|
|
203
232
|
|
|
@@ -216,7 +245,7 @@ export const assistFieldActions: DocumentFieldAction = {
|
|
|
216
245
|
)
|
|
217
246
|
|
|
218
247
|
// If there are no instructions, we don't want to render the group
|
|
219
|
-
if (instructionsLength === 0 && !imageCaptionAction) {
|
|
248
|
+
if (instructionsLength === 0 && !imageCaptionAction && !translateAction && !imageGenAction) {
|
|
220
249
|
return emptyAction
|
|
221
250
|
}
|
|
222
251
|
|
|
@@ -239,7 +268,7 @@ function instructionItem(props: {
|
|
|
239
268
|
iconRight: isPrivate ? PrivateIcon : undefined,
|
|
240
269
|
title: getInstructionTitle(instruction),
|
|
241
270
|
onAction: () => onInstructionAction(instruction),
|
|
242
|
-
disabled: assistSupported
|
|
271
|
+
disabled: !assistSupported,
|
|
243
272
|
hidden,
|
|
244
273
|
})
|
|
245
274
|
}
|
|
@@ -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,14 +19,14 @@ 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
|
-
if (imageContext && pathKey === imageContext?.
|
|
29
|
+
if (imageContext && pathKey === imageContext?.imageDescriptionPath) {
|
|
27
30
|
//if this is true, it is stable, and not breaking rules of hooks
|
|
28
31
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
29
32
|
const {documentId} = useAssistDocumentContext()
|
|
@@ -38,18 +41,26 @@ export const generateCaptionsActions: DocumentFieldAction = {
|
|
|
38
41
|
</Box>
|
|
39
42
|
)
|
|
40
43
|
: ImageIcon,
|
|
41
|
-
title: 'Generate
|
|
44
|
+
title: 'Generate image description',
|
|
42
45
|
onAction: () => {
|
|
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
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {DocumentFieldAction, DocumentFieldActionGroup, DocumentFieldActionItem} from 'sanity'
|
|
2
|
+
import {ImageIcon} from '@sanity/icons'
|
|
3
|
+
import {useContext, useMemo} from 'react'
|
|
4
|
+
import {usePathKey} from '../helpers/misc'
|
|
5
|
+
import {useApiClient, useGenerateImage} from '../useApiClient'
|
|
6
|
+
import {useAiAssistanceConfig} from '../assistLayout/AiAssistanceConfigContext'
|
|
7
|
+
import {useAssistDocumentContext} from '../assistDocument/AssistDocumentContext'
|
|
8
|
+
import {ImageContext} from '../components/ImageContext'
|
|
9
|
+
import {Box, Spinner} from '@sanity/ui'
|
|
10
|
+
|
|
11
|
+
function node(node: DocumentFieldActionItem | DocumentFieldActionGroup) {
|
|
12
|
+
return node
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const generateImagActions: DocumentFieldAction = {
|
|
16
|
+
name: 'sanity-assist-generate-image',
|
|
17
|
+
useAction(props) {
|
|
18
|
+
const pathKey = usePathKey(props.path)
|
|
19
|
+
|
|
20
|
+
const {config} = useAiAssistanceConfig()
|
|
21
|
+
const apiClient = useApiClient(config?.__customApiClient)
|
|
22
|
+
const {generateImage, loading} = useGenerateImage(apiClient)
|
|
23
|
+
|
|
24
|
+
const imageContext = useContext(ImageContext)
|
|
25
|
+
|
|
26
|
+
if (imageContext && pathKey === imageContext?.imageInstructionPath) {
|
|
27
|
+
//if this is true, it is stable, and not breaking rules of hooks
|
|
28
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
29
|
+
const {documentId} = useAssistDocumentContext()
|
|
30
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
31
|
+
return useMemo(() => {
|
|
32
|
+
return node({
|
|
33
|
+
type: 'action',
|
|
34
|
+
icon: loading
|
|
35
|
+
? () => (
|
|
36
|
+
<Box style={{height: 17}}>
|
|
37
|
+
<Spinner style={{transform: 'translateY(6px)'}} />
|
|
38
|
+
</Box>
|
|
39
|
+
)
|
|
40
|
+
: ImageIcon,
|
|
41
|
+
title: 'Generate image from prompt',
|
|
42
|
+
onAction: () => {
|
|
43
|
+
if (loading) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
generateImage({path: pathKey, documentId: documentId ?? ''})
|
|
47
|
+
},
|
|
48
|
+
renderAsButton: true,
|
|
49
|
+
disabled: loading,
|
|
50
|
+
})
|
|
51
|
+
}, [generateImage, pathKey, documentId, loading])
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// works but not supported by types
|
|
55
|
+
return undefined as unknown as DocumentFieldActionItem
|
|
56
|
+
},
|
|
57
|
+
}
|