@sanity/assist 1.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 (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/index.d.ts +52 -0
  4. package/dist/index.esm.js +2341 -0
  5. package/dist/index.esm.js.map +1 -0
  6. package/dist/index.js +2341 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +98 -0
  9. package/sanity.json +8 -0
  10. package/src/_lib/connector/ConnectFromRegion.tsx +24 -0
  11. package/src/_lib/connector/ConnectToRegion.tsx +22 -0
  12. package/src/_lib/connector/ConnectorRegion.tsx +23 -0
  13. package/src/_lib/connector/ConnectorsProvider.tsx +19 -0
  14. package/src/_lib/connector/ConnectorsStore.ts +122 -0
  15. package/src/_lib/connector/ConnectorsStoreContext.ts +4 -0
  16. package/src/_lib/connector/helpers.ts +5 -0
  17. package/src/_lib/connector/index.ts +9 -0
  18. package/src/_lib/connector/mapConnectorToLine.ts +83 -0
  19. package/src/_lib/connector/types.ts +56 -0
  20. package/src/_lib/connector/useConnectorsStore.ts +13 -0
  21. package/src/_lib/connector/useRegionRects.ts +141 -0
  22. package/src/_lib/fixedListenQuery.ts +101 -0
  23. package/src/_lib/form/DocumentForm.tsx +197 -0
  24. package/src/_lib/form/helpers.ts +31 -0
  25. package/src/_lib/form/index.ts +1 -0
  26. package/src/_lib/randomKey.ts +29 -0
  27. package/src/_lib/useListeningQuery.ts +61 -0
  28. package/src/_lib/usePrevious.ts +9 -0
  29. package/src/assistConnectors/AssistConnectorsOverlay.tsx +132 -0
  30. package/src/assistConnectors/ConnectorPath.tsx +62 -0
  31. package/src/assistConnectors/draw/arrowPath.ts +9 -0
  32. package/src/assistConnectors/draw/connectorPath.ts +142 -0
  33. package/src/assistConnectors/index.ts +1 -0
  34. package/src/assistDocument/AssistDocumentContext.tsx +31 -0
  35. package/src/assistDocument/AssistDocumentContextProvider.tsx +17 -0
  36. package/src/assistDocument/AssistDocumentInput.tsx +46 -0
  37. package/src/assistDocument/RequestRunInstructionProvider.tsx +50 -0
  38. package/src/assistDocument/components/AssistDocumentForm.tsx +188 -0
  39. package/src/assistDocument/components/FieldRefPreview.tsx +27 -0
  40. package/src/assistDocument/components/InstructionsArrayField.tsx +8 -0
  41. package/src/assistDocument/components/InstructionsArrayInput.tsx +26 -0
  42. package/src/assistDocument/components/SelectedFieldContext.tsx +10 -0
  43. package/src/assistDocument/components/generic/HiddenFieldTitle.tsx +5 -0
  44. package/src/assistDocument/components/helpers.ts +21 -0
  45. package/src/assistDocument/components/instruction/BackToInstructionsLink.tsx +31 -0
  46. package/src/assistDocument/components/instruction/FieldRefInput.tsx +33 -0
  47. package/src/assistDocument/components/instruction/InstructionInput.tsx +87 -0
  48. package/src/assistDocument/components/instruction/PromptInput.tsx +52 -0
  49. package/src/assistDocument/components/instruction/appearance/IconInput.tsx +46 -0
  50. package/src/assistDocument/components/instruction/appearance/InstructionVisibility.tsx +37 -0
  51. package/src/assistDocument/hooks/useAssistDocumentContextValue.tsx +68 -0
  52. package/src/assistDocument/hooks/useDocumentState.ts +6 -0
  53. package/src/assistDocument/hooks/useInstructionToaster.tsx +74 -0
  54. package/src/assistDocument/hooks/useStudioAssistDocument.ts +119 -0
  55. package/src/assistDocument/index.ts +1 -0
  56. package/src/assistFormComponents/AssistField.tsx +51 -0
  57. package/src/assistFormComponents/AssistFormBlock.tsx +31 -0
  58. package/src/assistFormComponents/AssistInlineFormBlock.tsx +14 -0
  59. package/src/assistFormComponents/AssistItem.tsx +20 -0
  60. package/src/assistFormComponents/validation/listItem.tsx +63 -0
  61. package/src/assistFormComponents/validation/validationList.tsx +89 -0
  62. package/src/assistInspector/AssistInspector.tsx +379 -0
  63. package/src/assistInspector/FieldAutocomplete.tsx +119 -0
  64. package/src/assistInspector/InstructionTaskHistoryButton.tsx +261 -0
  65. package/src/assistInspector/constants.ts +1 -0
  66. package/src/assistInspector/helpers.ts +125 -0
  67. package/src/assistInspector/index.ts +26 -0
  68. package/src/assistLayout/AiAssistanceConfigContext.tsx +81 -0
  69. package/src/assistLayout/AlphaMigration.tsx +311 -0
  70. package/src/assistLayout/AssistLayout.tsx +38 -0
  71. package/src/assistLayout/RunInstructionProvider.tsx +222 -0
  72. package/src/components/AssistFeatureBadge.tsx +9 -0
  73. package/src/components/Delay.tsx +25 -0
  74. package/src/components/HideReferenceChangedBannerInput.tsx +25 -0
  75. package/src/components/SafeValueInput.tsx +73 -0
  76. package/src/components/TimeAgo.tsx +18 -0
  77. package/src/constants.ts +20 -0
  78. package/src/fieldActions/PrivateIcon.tsx +20 -0
  79. package/src/fieldActions/assistFieldActions.tsx +230 -0
  80. package/src/globals.d.ts +4 -0
  81. package/src/helpers/assistSupported.ts +44 -0
  82. package/src/helpers/ids.ts +19 -0
  83. package/src/helpers/misc.ts +16 -0
  84. package/src/helpers/typeUtils.ts +15 -0
  85. package/src/helpers/useAssistSupported.ts +10 -0
  86. package/src/index.ts +6 -0
  87. package/src/legacy-types.ts +72 -0
  88. package/src/onboarding/FieldActionsOnboarding.tsx +90 -0
  89. package/src/onboarding/FirstAssistedPathProvider.tsx +29 -0
  90. package/src/onboarding/InspectorOnboarding.tsx +46 -0
  91. package/src/onboarding/onboardingStore.ts +33 -0
  92. package/src/plugin.tsx +80 -0
  93. package/src/presence/AiFieldPresence.tsx +28 -0
  94. package/src/presence/AssistAvatar.tsx +96 -0
  95. package/src/presence/AssistDocumentPresence.tsx +58 -0
  96. package/src/presence/useAssistPresence.ts +61 -0
  97. package/src/schemas/assistDocumentSchema.tsx +450 -0
  98. package/src/schemas/contextDocumentSchema.tsx +56 -0
  99. package/src/schemas/index.ts +25 -0
  100. package/src/schemas/serialize/SchemTypeTool.tsx +102 -0
  101. package/src/schemas/serialize/schemaUtils.ts +37 -0
  102. package/src/schemas/serialize/serializeSchema.test.ts +382 -0
  103. package/src/schemas/serialize/serializeSchema.ts +162 -0
  104. package/src/schemas/serializedSchemaTypeSchema.ts +59 -0
  105. package/src/schemas/typeDefExtensions.ts +30 -0
  106. package/src/types.ts +167 -0
  107. package/src/useApiClient.ts +140 -0
  108. package/src/vite.config.ts +9 -0
  109. package/v2-incompatible.js +11 -0
@@ -0,0 +1,261 @@
1
+ import {AssistTasksStatus, InstructionTask, StudioInstruction, TaskEndedReason} from '../types'
2
+ import {createElement, ForwardedRef, forwardRef, useCallback, useMemo, useState} from 'react'
3
+ import {
4
+ Box,
5
+ Button,
6
+ Card,
7
+ Flex,
8
+ Popover,
9
+ Spinner,
10
+ Stack,
11
+ Text,
12
+ useClickOutside,
13
+ useGlobalKeyDown,
14
+ useLayer,
15
+ } from '@sanity/ui'
16
+ import {
17
+ CheckmarkCircleIcon,
18
+ ClockIcon,
19
+ CloseCircleIcon,
20
+ ErrorOutlineIcon,
21
+ SyncIcon,
22
+ } from '@sanity/icons'
23
+ import {TimeAgo} from '../components/TimeAgo'
24
+ import {StatusButton, StatusButtonProps, typed, useClient} from 'sanity'
25
+ import {getInstructionTitle} from '../helpers/misc'
26
+ import styled, {keyframes} from 'styled-components'
27
+ import {assistTasksStatusId} from '../helpers/ids'
28
+ import {maxHistoryVisibilityMs, pluginTitle} from '../constants'
29
+
30
+ export interface InstructionTaskHistoryButtonProps {
31
+ documentId?: string
32
+ tasks: InstructionTask[] | undefined
33
+ instructions: StudioInstruction[] | undefined
34
+ showTitles: boolean
35
+ }
36
+
37
+ interface CancelableInstructionTask extends InstructionTask {
38
+ cancel: () => void
39
+ title?: string
40
+ }
41
+
42
+ const rotate = keyframes`
43
+ 0% {
44
+ transform: rotate(0);
45
+ }
46
+ 100% {
47
+ transform: rotate(360deg);
48
+ }
49
+ `
50
+
51
+ const SyncSpinningIcon = styled(SyncIcon)`
52
+ animation: ${rotate} 1s linear infinite;
53
+ `
54
+
55
+ const TASK_CONFIG = {
56
+ aborted: {
57
+ title: 'Canceled',
58
+ icon: CloseCircleIcon,
59
+ tone: 'transparent',
60
+ },
61
+ error: {
62
+ title: 'Error',
63
+ icon: ErrorOutlineIcon,
64
+ tone: 'critical',
65
+ },
66
+ success: {
67
+ title: 'Completed',
68
+ icon: CheckmarkCircleIcon,
69
+ tone: 'positive',
70
+ },
71
+ timeout: {
72
+ title: 'Timeout',
73
+ icon: ClockIcon,
74
+ tone: 'caution',
75
+ },
76
+ } as const
77
+
78
+ export function InstructionTaskHistoryButton(props: InstructionTaskHistoryButtonProps) {
79
+ const {tasks, instructions, documentId, showTitles} = props
80
+
81
+ const client = useClient({apiVersion: '2023-01-01'})
82
+ const cancelRun = useCallback(
83
+ (taskKey: string) => {
84
+ if (!documentId) {
85
+ return
86
+ }
87
+ const statusDocId = assistTasksStatusId(documentId)
88
+ const basePath = `${typed<keyof AssistTasksStatus>('tasks')}[_key=="${taskKey}"]`
89
+ client
90
+ .patch(statusDocId)
91
+ .set({
92
+ [`${basePath}.${typed<keyof InstructionTask>('ended')}`]: new Date().toISOString(),
93
+ [`${basePath}.${typed<keyof InstructionTask>('reason')}`]:
94
+ typed<TaskEndedReason>('aborted'),
95
+ })
96
+ .commit()
97
+ .catch(console.error)
98
+ },
99
+ [client, documentId]
100
+ )
101
+
102
+ const titledTasks = useMemo(() => {
103
+ const t =
104
+ tasks
105
+ ?.filter(
106
+ (task) =>
107
+ task.started &&
108
+ new Date().getTime() - new Date(task.started).getTime() < maxHistoryVisibilityMs
109
+ )
110
+ .map((task): CancelableInstructionTask => {
111
+ const instruction = instructions?.find((i) => i._key === task.instructionKey)
112
+ return {
113
+ ...task,
114
+ title: showTitles ? getInstructionTitle(instruction) : undefined,
115
+ cancel: () => cancelRun(task._key),
116
+ }
117
+ }) ?? []
118
+ t.sort((a, b) => new Date(b.started ?? '').getTime() - new Date(a.started ?? '').getTime())
119
+ return t
120
+ }, [tasks, instructions, cancelRun, showTitles])
121
+
122
+ // const id = useId()
123
+
124
+ const isRunning = useMemo(() => titledTasks.some((r) => !r.ended), [titledTasks])
125
+ const hasErrors = useMemo(
126
+ () => titledTasks.some((r) => r.reason === 'error' || r.reason === 'timeout'),
127
+ [titledTasks]
128
+ )
129
+
130
+ const [open, setOpen] = useState(false)
131
+
132
+ const toggleOpen = useCallback(() => setOpen((v) => !v), [])
133
+
134
+ const [button, setButton] = useState<HTMLButtonElement | null>(null)
135
+ const [popover, setPopover] = useState<HTMLDivElement | null>(null)
136
+
137
+ const handleClickOutside = useCallback(() => {
138
+ setOpen(false)
139
+ }, [])
140
+
141
+ useClickOutside(handleClickOutside, [button, popover])
142
+
143
+ const handleEscape = useCallback(() => {
144
+ setOpen(false)
145
+ button?.focus()
146
+ }, [button])
147
+
148
+ return (
149
+ <Popover
150
+ constrainSize
151
+ content={<TaskList onEscape={handleEscape} tasks={titledTasks} />}
152
+ open={open && !!titledTasks?.length}
153
+ placement="top"
154
+ portal
155
+ ref={setPopover}
156
+ width={0}
157
+ >
158
+ <TaskStatusButton
159
+ disabled={!titledTasks?.length}
160
+ hasErrors={hasErrors}
161
+ isRunning={isRunning}
162
+ onClick={toggleOpen}
163
+ ref={setButton}
164
+ selected={open}
165
+ />
166
+ </Popover>
167
+ )
168
+ }
169
+
170
+ const TASK_STATUS_BUTTON_TOOLTIP_PROPS: StatusButtonProps['tooltip'] = {
171
+ placement: 'top',
172
+ }
173
+
174
+ const TaskStatusButton = forwardRef(function TaskStatusButton(
175
+ props: {
176
+ disabled: boolean
177
+ hasErrors: boolean
178
+ isRunning: boolean
179
+ onClick: () => void
180
+ selected: boolean
181
+ },
182
+ ref: ForwardedRef<HTMLButtonElement>
183
+ ) {
184
+ const {disabled, hasErrors, isRunning, onClick, selected} = props
185
+
186
+ return (
187
+ <StatusButton
188
+ label={`${pluginTitle} status`}
189
+ icon={isRunning ? SyncSpinningIcon : hasErrors ? ErrorOutlineIcon : CheckmarkCircleIcon}
190
+ mode="bleed"
191
+ onClick={onClick}
192
+ tone={hasErrors ? 'critical' : undefined}
193
+ fontSize={1}
194
+ disabled={disabled}
195
+ ref={ref}
196
+ selected={selected}
197
+ tooltip={TASK_STATUS_BUTTON_TOOLTIP_PROPS}
198
+ />
199
+ )
200
+ })
201
+
202
+ function TaskList(props: {onEscape: () => void; tasks: CancelableInstructionTask[]}) {
203
+ const {onEscape, tasks} = props
204
+
205
+ const {isTopLayer} = useLayer()
206
+
207
+ useGlobalKeyDown(
208
+ useCallback(
209
+ (event) => {
210
+ if (isTopLayer && event.key === 'Escape') {
211
+ onEscape()
212
+ }
213
+ },
214
+ [isTopLayer, onEscape]
215
+ )
216
+ )
217
+
218
+ return (
219
+ <Stack padding={1} space={1}>
220
+ {tasks.map((task) => (
221
+ <TaskItem key={task._key} task={task} />
222
+ ))}
223
+ </Stack>
224
+ )
225
+ }
226
+
227
+ function TaskItem(props: {task: CancelableInstructionTask}) {
228
+ const {task} = props
229
+
230
+ const taskType = task.reason && TASK_CONFIG[task.reason]
231
+ return (
232
+ <Card radius={2} tone={taskType && taskType?.tone}>
233
+ <Flex align="center" gap={1}>
234
+ <Flex align="flex-start" flex={1} gap={3} padding={3}>
235
+ <Box flex="none">
236
+ <Text size={1}>
237
+ {taskType && createElement(taskType.icon)}
238
+ {!task.reason && <Spinner />}
239
+ </Text>
240
+ </Box>
241
+ <Stack flex={1} space={2}>
242
+ <Text size={1} weight="medium">
243
+ {taskType ? taskType.title : 'Running'}
244
+ {task.title && <>: {task.title}</>}
245
+ </Text>
246
+ {task.message ? <Text size={1}>{task.message}</Text> : null}
247
+ <Text muted size={1}>
248
+ <TimeAgo date={task.ended ?? task.started} />
249
+ </Text>
250
+ </Stack>
251
+ </Flex>
252
+
253
+ {!task.ended && (
254
+ <Box flex="none" padding={1}>
255
+ <Button fontSize={1} mode="bleed" onClick={task.cancel} text="Cancel" />
256
+ </Box>
257
+ )}
258
+ </Flex>
259
+ </Card>
260
+ )
261
+ }
@@ -0,0 +1 @@
1
+ export const aiInspectorId = 'ai-assistance'
@@ -0,0 +1,125 @@
1
+ import {
2
+ BlockContentIcon,
3
+ BlockquoteIcon,
4
+ DocumentIcon,
5
+ ImageIcon,
6
+ LinkIcon,
7
+ OlistIcon,
8
+ StringIcon,
9
+ } from '@sanity/icons'
10
+ import {isObjectSchemaType, ObjectSchemaType, Path, pathToString, SchemaType} from 'sanity'
11
+ import {ComponentType, useContext, useMemo} from 'react'
12
+ import {AssistInspectorRouteParams, documentRootKey, fieldPathParam} from '../types'
13
+ import {usePaneRouter} from 'sanity/desk'
14
+ import {isAssistSupported} from '../helpers/assistSupported'
15
+ import {isPortableTextArray, isType} from '../helpers/typeUtils'
16
+ import {SelectedFieldContext} from '../assistDocument/components/SelectedFieldContext'
17
+
18
+ export interface FieldRef {
19
+ key: string
20
+ path: Path
21
+ title: string
22
+ schemaType: SchemaType
23
+ icon: ComponentType
24
+ }
25
+
26
+ export function getTypeIcon(schemaType: SchemaType) {
27
+ let t: SchemaType | undefined = schemaType
28
+
29
+ while (t) {
30
+ if (t.icon) return t.icon
31
+ t = t.type
32
+ }
33
+
34
+ if (isType(schemaType, 'slug')) return LinkIcon
35
+ if (isType(schemaType, 'image')) return ImageIcon
36
+ if (schemaType.jsonType === 'array' && isPortableTextArray(schemaType)) return BlockContentIcon
37
+
38
+ if (schemaType.jsonType === 'array') return OlistIcon
39
+ if (schemaType.jsonType === 'object') return BlockquoteIcon
40
+ if (schemaType.jsonType === 'string') return StringIcon
41
+
42
+ return DocumentIcon
43
+ }
44
+
45
+ export function getFieldRefsWithDocument(schemaType: ObjectSchemaType): FieldRef[] {
46
+ const fields = getFieldRefs(schemaType)
47
+ return [
48
+ {
49
+ key: documentRootKey,
50
+ icon: schemaType.icon ?? DocumentIcon,
51
+ title: `The entire document`,
52
+ path: [],
53
+ schemaType: schemaType,
54
+ },
55
+
56
+ ...fields,
57
+ ]
58
+ }
59
+
60
+ export function getFieldRefs(schemaType: ObjectSchemaType, parent?: FieldRef): FieldRef[] {
61
+ return schemaType.fields
62
+ .filter((f) => !f.name.startsWith('_'))
63
+ .flatMap((field) => {
64
+ const path: Path = parent ? [...parent.path, field.name] : [field.name]
65
+ const title = field.type.title ?? field.name
66
+ const fieldRef: FieldRef = {
67
+ key: pathToString(path),
68
+ path,
69
+ title: parent ? [parent.title, title].join(' / ') : title,
70
+ schemaType: field.type,
71
+ icon: getTypeIcon(field.type),
72
+ }
73
+ const fields = field.type.jsonType === 'object' ? getFieldRefs(field.type, fieldRef) : []
74
+
75
+ if (!isAssistSupported(field.type)) {
76
+ return fields
77
+ }
78
+ return [fieldRef, ...fields]
79
+ })
80
+ }
81
+
82
+ export function useSelectedField(
83
+ documentSchemaType?: SchemaType,
84
+ path?: string
85
+ ): FieldRef | undefined {
86
+ const selectableFields = useMemo(
87
+ () =>
88
+ documentSchemaType && isObjectSchemaType(documentSchemaType)
89
+ ? getFieldRefsWithDocument(documentSchemaType)
90
+ : [],
91
+ [documentSchemaType]
92
+ )
93
+
94
+ return useMemo(() => {
95
+ return path ? selectableFields?.find((f) => f.key === path) : undefined
96
+ }, [selectableFields, path])
97
+ }
98
+
99
+ export function useSelectedFieldTitle() {
100
+ const {params} = useAiPaneRouter()
101
+ const {documentSchema} = useContext(SelectedFieldContext) ?? {}
102
+ const selectedField = useSelectedField(documentSchema, params[fieldPathParam])
103
+ return getFieldTitle(selectedField)
104
+ }
105
+
106
+ export function getFieldTitle(field?: FieldRef) {
107
+ const schemaType = field?.schemaType
108
+ return schemaType?.title ?? schemaType?.name ?? 'Untitled'
109
+ }
110
+
111
+ export function useAiPaneRouter() {
112
+ const paneRouter = usePaneRouter()
113
+
114
+ return useMemo(
115
+ () =>
116
+ ({...paneRouter, params: paneRouter.params ?? {}} as Omit<
117
+ typeof paneRouter,
118
+ 'setParams' | 'params'
119
+ > & {
120
+ params: AssistInspectorRouteParams
121
+ setParams: (p: Record<keyof AssistInspectorRouteParams, string | undefined>) => void
122
+ }),
123
+ [paneRouter]
124
+ )
125
+ }
@@ -0,0 +1,26 @@
1
+ import {SparklesIcon} from '@sanity/icons'
2
+ import {DocumentInspector, typed} from 'sanity'
3
+ import {aiInspectorId} from './constants'
4
+ import {AssistInspectorWrapper} from './AssistInspector'
5
+ import {AssistInspectorRouteParams, fieldPathParam, instructionParam} from '../types'
6
+ import {pluginTitle} from '../constants'
7
+
8
+ export const assistInspector: DocumentInspector = {
9
+ name: aiInspectorId,
10
+ useMenuItem: () => ({
11
+ icon: SparklesIcon,
12
+ title: pluginTitle,
13
+ hidden: true,
14
+ showAsAction: false,
15
+ }),
16
+ component: AssistInspectorWrapper,
17
+ onClose({params}) {
18
+ return {
19
+ params: typed<AssistInspectorRouteParams>({
20
+ ...params,
21
+ [fieldPathParam]: undefined,
22
+ [instructionParam]: undefined,
23
+ }) as typeof params,
24
+ }
25
+ },
26
+ }
@@ -0,0 +1,81 @@
1
+ import {
2
+ createContext,
3
+ ReactNode,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useState,
9
+ } from 'react'
10
+ import {AssistPluginConfig} from '../plugin'
11
+ import {InstructStatus, useApiClient, useGetInstructStatus, useInitInstruct} from '../useApiClient'
12
+
13
+ export interface AiAssistanceConfigContextValue {
14
+ config: AssistPluginConfig
15
+ status?: InstructStatus
16
+ statusLoading: boolean
17
+ initLoading: boolean
18
+ init: () => void
19
+ error?: Error
20
+ }
21
+
22
+ export const AiAssistanceConfigContext = createContext<AiAssistanceConfigContextValue>({} as any)
23
+
24
+ export function useAiAssistanceConfig() {
25
+ const context = useContext(AiAssistanceConfigContext)
26
+ if (!context) {
27
+ throw new Error('Missing AiAssistanceConfigContext')
28
+ }
29
+ return context
30
+ }
31
+
32
+ export function AiAssistanceConfigProvider(props: {
33
+ children?: ReactNode
34
+ config: AssistPluginConfig
35
+ }) {
36
+ const [status, setStatus] = useState<InstructStatus | undefined>()
37
+ const [error, setError] = useState<Error | undefined>()
38
+
39
+ const apiClient = useApiClient(props.config?.__customApiClient)
40
+ const {getInstructStatus, loading: statusLoading} = useGetInstructStatus(apiClient)
41
+ const {initInstruct, loading: initLoading} = useInitInstruct(apiClient)
42
+
43
+ useEffect(() => {
44
+ getInstructStatus()
45
+ .then((s) => setStatus(s))
46
+ .catch((e) => {
47
+ console.error(e)
48
+ setError(e as Error)
49
+ })
50
+ }, [getInstructStatus])
51
+
52
+ const init = useCallback(async () => {
53
+ setError(undefined)
54
+ try {
55
+ await initInstruct()
56
+ const status = await getInstructStatus()
57
+ setStatus(status)
58
+ } catch (e) {
59
+ console.error('Failed to init ai assistance', e)
60
+ setError(e as Error)
61
+ }
62
+ }, [initInstruct, getInstructStatus, setStatus])
63
+
64
+ const {config, children} = props
65
+ const context = useMemo<AiAssistanceConfigContextValue>(() => {
66
+ return {
67
+ config,
68
+ status,
69
+ statusLoading,
70
+ init,
71
+ initLoading,
72
+ error,
73
+ }
74
+ }, [config, status, init, statusLoading, initLoading, error])
75
+
76
+ return (
77
+ <AiAssistanceConfigContext.Provider value={context}>
78
+ {children}
79
+ </AiAssistanceConfigContext.Provider>
80
+ )
81
+ }