@sanity/sdk-react 2.6.0 → 2.8.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/README.md +68 -0
- package/dist/index.d.ts +544 -20
- package/dist/index.js +118 -72
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/_exports/sdk-react.ts +1 -0
- package/src/components/SanityApp.test.tsx +72 -2
- package/src/components/SanityApp.tsx +52 -10
- package/src/components/auth/AuthBoundary.test.tsx +3 -0
- package/src/components/auth/AuthBoundary.tsx +5 -5
- package/src/components/auth/LoginError.test.tsx +5 -0
- package/src/components/auth/LoginError.tsx +22 -1
- package/src/context/ComlinkTokenRefresh.test.tsx +2 -2
- package/src/context/ComlinkTokenRefresh.tsx +3 -2
- package/src/context/SDKStudioContext.test.tsx +126 -0
- package/src/context/SDKStudioContext.ts +65 -0
- package/src/hooks/agent/agentActions.ts +436 -21
- package/src/hooks/dashboard/useDispatchIntent.test.ts +5 -5
- package/src/hooks/dashboard/useDispatchIntent.ts +5 -5
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.test.ts +2 -3
- package/src/hooks/dashboard/utils/useResourceIdFromDocumentHandle.ts +3 -3
- package/src/hooks/helpers/useNormalizedSourceOptions.ts +85 -0
- package/src/hooks/projection/useDocumentProjection.ts +15 -4
- package/src/hooks/query/useQuery.ts +23 -17
- package/src/hooks/context/useSource.tsx +0 -34
|
@@ -39,11 +39,69 @@ interface Subscribable<T> {
|
|
|
39
39
|
/**
|
|
40
40
|
* @alpha
|
|
41
41
|
* Generates content for a document (or specific fields) via Sanity Agent Actions.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* This hook provides a stable callback to trigger AI-powered content generation for documents.
|
|
45
|
+
*
|
|
46
|
+
* Features:
|
|
42
47
|
* - Uses instruction templates with `$variables` and supports `instructionParams` (constants, fields, documents, GROQ queries).
|
|
43
48
|
* - Can target specific paths/fields; supports image generation when targeting image fields.
|
|
44
49
|
* - Supports optional `temperature`, `async`, `noWrite`, and `conditionalPaths`.
|
|
50
|
+
* - Returns a Subscribable stream for tracking generation progress.
|
|
51
|
+
*
|
|
52
|
+
* @returns A stable callback that triggers the action and yields a Subscribable stream.
|
|
53
|
+
*
|
|
54
|
+
* @example Basic content generation
|
|
55
|
+
* ```tsx
|
|
56
|
+
* import {useAgentGenerate} from '@sanity/sdk-react'
|
|
57
|
+
*
|
|
58
|
+
* function GenerateDescription({documentId}: {documentId: string}) {
|
|
59
|
+
* const generate = useAgentGenerate()
|
|
60
|
+
*
|
|
61
|
+
* const handleGenerate = () => {
|
|
62
|
+
* generate({
|
|
63
|
+
* documentId,
|
|
64
|
+
* instruction: 'Write a compelling product description based on the title: $title',
|
|
65
|
+
* instructionParams: {
|
|
66
|
+
* title: {type: 'field', path: 'title'},
|
|
67
|
+
* },
|
|
68
|
+
* targetPaths: ['description'],
|
|
69
|
+
* }).subscribe({
|
|
70
|
+
* next: (result) => console.log('Generation progress:', result),
|
|
71
|
+
* complete: () => console.log('Generation complete'),
|
|
72
|
+
* error: (err) => console.error('Generation failed:', err),
|
|
73
|
+
* })
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* return <button onClick={handleGenerate}>Generate Description</button>
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @example Image generation
|
|
81
|
+
* ```tsx
|
|
82
|
+
* import {useAgentGenerate} from '@sanity/sdk-react'
|
|
83
|
+
*
|
|
84
|
+
* function GenerateProductImage({documentId}: {documentId: string}) {
|
|
85
|
+
* const generate = useAgentGenerate()
|
|
86
|
+
*
|
|
87
|
+
* const handleGenerateImage = () => {
|
|
88
|
+
* generate({
|
|
89
|
+
* documentId,
|
|
90
|
+
* instruction: 'Generate a product photo for: $productName',
|
|
91
|
+
* instructionParams: {
|
|
92
|
+
* productName: {type: 'field', path: 'name'},
|
|
93
|
+
* },
|
|
94
|
+
* targetPaths: ['mainImage'],
|
|
95
|
+
* }).subscribe({
|
|
96
|
+
* complete: () => console.log('Image generated'),
|
|
97
|
+
* })
|
|
98
|
+
* }
|
|
45
99
|
*
|
|
46
|
-
*
|
|
100
|
+
* return <button onClick={handleGenerateImage}>Generate Image</button>
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @category Agent Actions
|
|
47
105
|
*/
|
|
48
106
|
export const useAgentGenerate: () => (options: AgentGenerateOptions) => Subscribable<unknown> =
|
|
49
107
|
createCallbackHook(agentGenerate) as unknown as () => (
|
|
@@ -53,12 +111,73 @@ export const useAgentGenerate: () => (options: AgentGenerateOptions) => Subscrib
|
|
|
53
111
|
/**
|
|
54
112
|
* @alpha
|
|
55
113
|
* Transforms an existing document or selected fields using Sanity Agent Actions.
|
|
114
|
+
*
|
|
115
|
+
* @remarks
|
|
116
|
+
* This hook provides a stable callback to apply AI-powered transformations to document content.
|
|
117
|
+
*
|
|
118
|
+
* Features:
|
|
56
119
|
* - Accepts `instruction` and `instructionParams` (constants, fields, documents, GROQ queries).
|
|
57
120
|
* - Can write to the same or a different `targetDocument` (create/edit), and target specific paths.
|
|
58
121
|
* - Supports per-path image transform instructions and image description operations.
|
|
59
122
|
* - Optional `temperature`, `async`, `noWrite`, `conditionalPaths`.
|
|
60
123
|
*
|
|
61
|
-
*
|
|
124
|
+
* @returns A stable callback that triggers the action and yields a Subscribable stream.
|
|
125
|
+
*
|
|
126
|
+
* @example Transform text content
|
|
127
|
+
* ```tsx
|
|
128
|
+
* import {useAgentTransform} from '@sanity/sdk-react'
|
|
129
|
+
*
|
|
130
|
+
* function SummarizeArticle({documentId}: {documentId: string}) {
|
|
131
|
+
* const transform = useAgentTransform()
|
|
132
|
+
*
|
|
133
|
+
* const handleSummarize = () => {
|
|
134
|
+
* transform({
|
|
135
|
+
* documentId,
|
|
136
|
+
* instruction: 'Summarize the following content into 2-3 sentences: $body',
|
|
137
|
+
* instructionParams: {
|
|
138
|
+
* body: {type: 'field', path: 'body'},
|
|
139
|
+
* },
|
|
140
|
+
* targetPaths: ['summary'],
|
|
141
|
+
* }).subscribe({
|
|
142
|
+
* complete: () => console.log('Summary generated'),
|
|
143
|
+
* error: (err) => console.error('Transform failed:', err),
|
|
144
|
+
* })
|
|
145
|
+
* }
|
|
146
|
+
*
|
|
147
|
+
* return <button onClick={handleSummarize}>Generate Summary</button>
|
|
148
|
+
* }
|
|
149
|
+
* ```
|
|
150
|
+
*
|
|
151
|
+
* @example Transform and write to a different document
|
|
152
|
+
* ```tsx
|
|
153
|
+
* import {useAgentTransform} from '@sanity/sdk-react'
|
|
154
|
+
*
|
|
155
|
+
* function CreateVariant({sourceDocumentId}: {sourceDocumentId: string}) {
|
|
156
|
+
* const transform = useAgentTransform()
|
|
157
|
+
*
|
|
158
|
+
* const handleCreateVariant = () => {
|
|
159
|
+
* transform({
|
|
160
|
+
* documentId: sourceDocumentId,
|
|
161
|
+
* instruction: 'Rewrite this product description for a younger audience: $description',
|
|
162
|
+
* instructionParams: {
|
|
163
|
+
* description: {type: 'field', path: 'description'},
|
|
164
|
+
* },
|
|
165
|
+
* targetDocument: {
|
|
166
|
+
* operation: 'create',
|
|
167
|
+
* _type: 'product',
|
|
168
|
+
* },
|
|
169
|
+
* targetPaths: ['description'],
|
|
170
|
+
* }).subscribe({
|
|
171
|
+
* next: (result) => console.log('New document:', result),
|
|
172
|
+
* complete: () => console.log('Variant created'),
|
|
173
|
+
* })
|
|
174
|
+
* }
|
|
175
|
+
*
|
|
176
|
+
* return <button onClick={handleCreateVariant}>Create Youth Variant</button>
|
|
177
|
+
* }
|
|
178
|
+
* ```
|
|
179
|
+
*
|
|
180
|
+
* @category Agent Actions
|
|
62
181
|
*/
|
|
63
182
|
export const useAgentTransform: () => (options: AgentTransformOptions) => Subscribable<unknown> =
|
|
64
183
|
createCallbackHook(agentTransform) as unknown as () => (
|
|
@@ -68,11 +187,92 @@ export const useAgentTransform: () => (options: AgentTransformOptions) => Subscr
|
|
|
68
187
|
/**
|
|
69
188
|
* @alpha
|
|
70
189
|
* Translates documents or fields using Sanity Agent Actions.
|
|
190
|
+
*
|
|
191
|
+
* @remarks
|
|
192
|
+
* This hook provides a stable callback to translate document content between languages using AI.
|
|
193
|
+
*
|
|
194
|
+
* Features:
|
|
71
195
|
* - Configure `fromLanguage`/`toLanguage`, optional `styleGuide`, and `protectedPhrases`.
|
|
72
196
|
* - Can write into a different `targetDocument`, and/or store language in a field.
|
|
73
197
|
* - Optional `temperature`, `async`, `noWrite`, `conditionalPaths`.
|
|
74
198
|
*
|
|
75
|
-
*
|
|
199
|
+
* @returns A stable callback that triggers the action and yields a Subscribable stream.
|
|
200
|
+
*
|
|
201
|
+
* @example Basic translation
|
|
202
|
+
* ```tsx
|
|
203
|
+
* import {useAgentTranslate} from '@sanity/sdk-react'
|
|
204
|
+
*
|
|
205
|
+
* function TranslateArticle({documentId}: {documentId: string}) {
|
|
206
|
+
* const translate = useAgentTranslate()
|
|
207
|
+
*
|
|
208
|
+
* const handleTranslate = () => {
|
|
209
|
+
* translate({
|
|
210
|
+
* documentId,
|
|
211
|
+
* fromLanguage: 'en',
|
|
212
|
+
* toLanguage: 'es',
|
|
213
|
+
* targetPaths: ['title', 'body'],
|
|
214
|
+
* }).subscribe({
|
|
215
|
+
* complete: () => console.log('Translation complete'),
|
|
216
|
+
* error: (err) => console.error('Translation failed:', err),
|
|
217
|
+
* })
|
|
218
|
+
* }
|
|
219
|
+
*
|
|
220
|
+
* return <button onClick={handleTranslate}>Translate to Spanish</button>
|
|
221
|
+
* }
|
|
222
|
+
* ```
|
|
223
|
+
*
|
|
224
|
+
* @example Translation with style guide and protected phrases
|
|
225
|
+
* ```tsx
|
|
226
|
+
* import {useAgentTranslate} from '@sanity/sdk-react'
|
|
227
|
+
*
|
|
228
|
+
* function TranslateWithBrandTerms({documentId}: {documentId: string}) {
|
|
229
|
+
* const translate = useAgentTranslate()
|
|
230
|
+
*
|
|
231
|
+
* const handleTranslate = () => {
|
|
232
|
+
* translate({
|
|
233
|
+
* documentId,
|
|
234
|
+
* fromLanguage: 'en',
|
|
235
|
+
* toLanguage: 'fr',
|
|
236
|
+
* styleGuide: 'Use formal French appropriate for business communication.',
|
|
237
|
+
* protectedPhrases: ['Acme Corp', 'PowerWidget Pro'],
|
|
238
|
+
* targetPaths: ['title', 'description'],
|
|
239
|
+
* }).subscribe({
|
|
240
|
+
* complete: () => console.log('Translation complete'),
|
|
241
|
+
* })
|
|
242
|
+
* }
|
|
243
|
+
*
|
|
244
|
+
* return <button onClick={handleTranslate}>Translate to French</button>
|
|
245
|
+
* }
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* @example Translate to a new document
|
|
249
|
+
* ```tsx
|
|
250
|
+
* import {useAgentTranslate} from '@sanity/sdk-react'
|
|
251
|
+
*
|
|
252
|
+
* function CreateTranslatedCopy({documentId}: {documentId: string}) {
|
|
253
|
+
* const translate = useAgentTranslate()
|
|
254
|
+
*
|
|
255
|
+
* const handleCreateTranslation = () => {
|
|
256
|
+
* translate({
|
|
257
|
+
* documentId,
|
|
258
|
+
* fromLanguage: 'en',
|
|
259
|
+
* toLanguage: 'de',
|
|
260
|
+
* targetDocument: {
|
|
261
|
+
* operation: 'create',
|
|
262
|
+
* _type: 'article',
|
|
263
|
+
* },
|
|
264
|
+
* languageFieldPath: 'language',
|
|
265
|
+
* }).subscribe({
|
|
266
|
+
* next: (result) => console.log('New translated document:', result),
|
|
267
|
+
* complete: () => console.log('Translated copy created'),
|
|
268
|
+
* })
|
|
269
|
+
* }
|
|
270
|
+
*
|
|
271
|
+
* return <button onClick={handleCreateTranslation}>Create German Copy</button>
|
|
272
|
+
* }
|
|
273
|
+
* ```
|
|
274
|
+
*
|
|
275
|
+
* @category Agent Actions
|
|
76
276
|
*/
|
|
77
277
|
export const useAgentTranslate: () => (options: AgentTranslateOptions) => Subscribable<unknown> =
|
|
78
278
|
createCallbackHook(agentTranslate) as unknown as () => (
|
|
@@ -80,12 +280,8 @@ export const useAgentTranslate: () => (options: AgentTranslateOptions) => Subscr
|
|
|
80
280
|
) => Subscribable<unknown>
|
|
81
281
|
|
|
82
282
|
/**
|
|
83
|
-
* @
|
|
84
|
-
*
|
|
85
|
-
* - `format`: 'string' or 'json' (instruction must contain the word "json" for JSON responses).
|
|
86
|
-
* - Optional `temperature`.
|
|
87
|
-
*
|
|
88
|
-
* Returns a stable callback that triggers the action and resolves a Promise with the prompt result.
|
|
283
|
+
* @internal
|
|
284
|
+
* Adapter to convert the agentPrompt observable to a Promise.
|
|
89
285
|
*/
|
|
90
286
|
function promptAdapter(
|
|
91
287
|
instance: SanityInstance,
|
|
@@ -96,24 +292,104 @@ function promptAdapter(
|
|
|
96
292
|
|
|
97
293
|
/**
|
|
98
294
|
* @alpha
|
|
99
|
-
* Prompts the
|
|
295
|
+
* Prompts the Content Agent using the same instruction template format as other agent actions.
|
|
296
|
+
*
|
|
297
|
+
* @remarks
|
|
298
|
+
* This hook provides a stable callback to send prompts to the Content Agent and receive responses.
|
|
299
|
+
* Unlike the other agent action hooks, this one does not modify documents—it simply
|
|
300
|
+
* returns the AI's response.
|
|
301
|
+
*
|
|
302
|
+
* Features:
|
|
303
|
+
* - Uses the same instruction template format with `$variables` as other actions.
|
|
100
304
|
* - `format`: 'string' or 'json' (instruction must contain the word "json" for JSON responses).
|
|
101
|
-
* -
|
|
305
|
+
* - Supports `instructionParams` for dynamic content (constants, fields, documents, GROQ queries).
|
|
306
|
+
* - Optional `temperature` for controlling response creativity.
|
|
307
|
+
*
|
|
308
|
+
* @returns A stable callback that triggers the action and resolves a Promise with the prompt result.
|
|
309
|
+
*
|
|
310
|
+
* @example Basic string prompt
|
|
311
|
+
* ```tsx
|
|
312
|
+
* import {useState} from 'react'
|
|
313
|
+
* import {useAgentPrompt} from '@sanity/sdk-react'
|
|
314
|
+
*
|
|
315
|
+
* function AskQuestion() {
|
|
316
|
+
* const prompt = useAgentPrompt()
|
|
317
|
+
* const [answer, setAnswer] = useState<string>('')
|
|
318
|
+
*
|
|
319
|
+
* const handleAsk = async () => {
|
|
320
|
+
* const result = await prompt({
|
|
321
|
+
* instruction: 'What are the top 3 benefits of content modeling?',
|
|
322
|
+
* format: 'string',
|
|
323
|
+
* })
|
|
324
|
+
* setAnswer(result.output)
|
|
325
|
+
* }
|
|
326
|
+
*
|
|
327
|
+
* return (
|
|
328
|
+
* <div>
|
|
329
|
+
* <button onClick={handleAsk}>Ask AI</button>
|
|
330
|
+
* {answer && <p>{answer}</p>}
|
|
331
|
+
* </div>
|
|
332
|
+
* )
|
|
333
|
+
* }
|
|
334
|
+
* ```
|
|
335
|
+
*
|
|
336
|
+
* @example JSON response with instruction params
|
|
337
|
+
* ```tsx
|
|
338
|
+
* import {useState} from 'react'
|
|
339
|
+
* import {useAgentPrompt} from '@sanity/sdk-react'
|
|
340
|
+
*
|
|
341
|
+
* interface TagSuggestions {
|
|
342
|
+
* tags: string[]
|
|
343
|
+
* reasoning: string
|
|
344
|
+
* }
|
|
345
|
+
*
|
|
346
|
+
* function SuggestTags({documentId}: {documentId: string}) {
|
|
347
|
+
* const prompt = useAgentPrompt()
|
|
348
|
+
* const [suggestions, setSuggestions] = useState<TagSuggestions | null>(null)
|
|
102
349
|
*
|
|
103
|
-
*
|
|
350
|
+
* const handleSuggest = async () => {
|
|
351
|
+
* const result = await prompt({
|
|
352
|
+
* instruction: `
|
|
353
|
+
* Based on the following article title and content, suggest relevant tags.
|
|
354
|
+
* Return as json with "tags" (array of strings) and "reasoning" (string).
|
|
355
|
+
* Title: $title
|
|
356
|
+
* Content: $body
|
|
357
|
+
* `,
|
|
358
|
+
* instructionParams: {
|
|
359
|
+
* title: {type: 'field', path: 'title', documentId},
|
|
360
|
+
* body: {type: 'field', path: 'body', documentId},
|
|
361
|
+
* },
|
|
362
|
+
* format: 'json',
|
|
363
|
+
* })
|
|
364
|
+
* setSuggestions(result.output as TagSuggestions)
|
|
365
|
+
* }
|
|
366
|
+
*
|
|
367
|
+
* return (
|
|
368
|
+
* <div>
|
|
369
|
+
* <button onClick={handleSuggest}>Suggest Tags</button>
|
|
370
|
+
* {suggestions && (
|
|
371
|
+
* <div>
|
|
372
|
+
* <p>Reasoning: {suggestions.reasoning}</p>
|
|
373
|
+
* <ul>
|
|
374
|
+
* {suggestions.tags.map((tag) => (
|
|
375
|
+
* <li key={tag}>{tag}</li>
|
|
376
|
+
* ))}
|
|
377
|
+
* </ul>
|
|
378
|
+
* </div>
|
|
379
|
+
* )}
|
|
380
|
+
* </div>
|
|
381
|
+
* )
|
|
382
|
+
* }
|
|
383
|
+
* ```
|
|
384
|
+
*
|
|
385
|
+
* @category Agent Actions
|
|
104
386
|
*/
|
|
105
387
|
export const useAgentPrompt: () => (options: AgentPromptOptions) => Promise<AgentPromptResult> =
|
|
106
388
|
createCallbackHook(promptAdapter)
|
|
107
389
|
|
|
108
390
|
/**
|
|
109
|
-
* @
|
|
110
|
-
*
|
|
111
|
-
* - Validates provided paths/values against the document schema and merges object values safely.
|
|
112
|
-
* - Prevents duplicate keys and supports array appends (including after a specific keyed item).
|
|
113
|
-
* - Accepts `documentId` or `targetDocument` (mutually exclusive).
|
|
114
|
-
* - Optional `async`, `noWrite`, `conditionalPaths`.
|
|
115
|
-
*
|
|
116
|
-
* Returns a stable callback that triggers the action and resolves a Promise with the patch result.
|
|
391
|
+
* @internal
|
|
392
|
+
* Adapter to convert the agentPatch observable to a Promise.
|
|
117
393
|
*/
|
|
118
394
|
function patchAdapter(
|
|
119
395
|
instance: SanityInstance,
|
|
@@ -125,12 +401,151 @@ function patchAdapter(
|
|
|
125
401
|
/**
|
|
126
402
|
* @alpha
|
|
127
403
|
* Schema-aware patching with Sanity Agent Actions.
|
|
404
|
+
*
|
|
405
|
+
* @remarks
|
|
406
|
+
* This hook provides a stable callback to apply schema-validated patches to documents.
|
|
407
|
+
* Unlike {@link useEditDocument}, this uses the Agent Actions API which provides
|
|
408
|
+
* additional schema validation and safe merging capabilities.
|
|
409
|
+
*
|
|
410
|
+
* Features:
|
|
128
411
|
* - Validates provided paths/values against the document schema and merges object values safely.
|
|
129
412
|
* - Prevents duplicate keys and supports array appends (including after a specific keyed item).
|
|
130
413
|
* - Accepts `documentId` or `targetDocument` (mutually exclusive).
|
|
414
|
+
* - Requires `schemaId` (e.g., `'_.schemas.default'`) and `target` to specify patch operations.
|
|
131
415
|
* - Optional `async`, `noWrite`, `conditionalPaths`.
|
|
132
416
|
*
|
|
133
|
-
*
|
|
417
|
+
* Each entry in `target` specifies a `path`, an `operation` (`'set'`, `'append'`, `'mixed'`, or `'unset'`),
|
|
418
|
+
* and a `value` (required for all operations except `'unset'`).
|
|
419
|
+
*
|
|
420
|
+
* @returns A stable callback that triggers the action and resolves a Promise with the patch result.
|
|
421
|
+
*
|
|
422
|
+
* @example Basic field update
|
|
423
|
+
* ```tsx
|
|
424
|
+
* import {useAgentPatch} from '@sanity/sdk-react'
|
|
425
|
+
*
|
|
426
|
+
* function UpdateTitle({documentId}: {documentId: string}) {
|
|
427
|
+
* const patch = useAgentPatch()
|
|
428
|
+
*
|
|
429
|
+
* const handleUpdate = async () => {
|
|
430
|
+
* const result = await patch({
|
|
431
|
+
* documentId,
|
|
432
|
+
* schemaId: '_.schemas.default',
|
|
433
|
+
* target: [
|
|
434
|
+
* {
|
|
435
|
+
* path: 'title',
|
|
436
|
+
* operation: 'set',
|
|
437
|
+
* value: 'Updated Title',
|
|
438
|
+
* },
|
|
439
|
+
* {
|
|
440
|
+
* path: 'lastModified',
|
|
441
|
+
* operation: 'set',
|
|
442
|
+
* value: new Date().toISOString(),
|
|
443
|
+
* },
|
|
444
|
+
* ],
|
|
445
|
+
* })
|
|
446
|
+
* console.log('Patch result:', result)
|
|
447
|
+
* }
|
|
448
|
+
*
|
|
449
|
+
* return <button onClick={handleUpdate}>Update Title</button>
|
|
450
|
+
* }
|
|
451
|
+
* ```
|
|
452
|
+
*
|
|
453
|
+
* @example Append items to an array
|
|
454
|
+
* ```tsx
|
|
455
|
+
* import {useAgentPatch} from '@sanity/sdk-react'
|
|
456
|
+
*
|
|
457
|
+
* function AddTag({documentId}: {documentId: string}) {
|
|
458
|
+
* const patch = useAgentPatch()
|
|
459
|
+
*
|
|
460
|
+
* const handleAddTag = async (newTag: string) => {
|
|
461
|
+
* await patch({
|
|
462
|
+
* documentId,
|
|
463
|
+
* schemaId: '_.schemas.default',
|
|
464
|
+
* target: {
|
|
465
|
+
* path: 'tags',
|
|
466
|
+
* operation: 'append',
|
|
467
|
+
* value: [newTag],
|
|
468
|
+
* },
|
|
469
|
+
* })
|
|
470
|
+
* }
|
|
471
|
+
*
|
|
472
|
+
* return (
|
|
473
|
+
* <button onClick={() => handleAddTag('featured')}>
|
|
474
|
+
* Add Featured Tag
|
|
475
|
+
* </button>
|
|
476
|
+
* )
|
|
477
|
+
* }
|
|
478
|
+
* ```
|
|
479
|
+
*
|
|
480
|
+
* @example Insert array item after a specific key
|
|
481
|
+
* ```tsx
|
|
482
|
+
* import {useAgentPatch} from '@sanity/sdk-react'
|
|
483
|
+
*
|
|
484
|
+
* function InsertContentBlock({
|
|
485
|
+
* documentId,
|
|
486
|
+
* afterKey,
|
|
487
|
+
* }: {
|
|
488
|
+
* documentId: string
|
|
489
|
+
* afterKey: string
|
|
490
|
+
* }) {
|
|
491
|
+
* const patch = useAgentPatch()
|
|
492
|
+
*
|
|
493
|
+
* const handleInsert = async () => {
|
|
494
|
+
* await patch({
|
|
495
|
+
* documentId,
|
|
496
|
+
* schemaId: '_.schemas.default',
|
|
497
|
+
* target: {
|
|
498
|
+
* path: ['content', {_key: afterKey}],
|
|
499
|
+
* operation: 'append',
|
|
500
|
+
* value: [{_type: 'block', text: 'New paragraph inserted here.'}],
|
|
501
|
+
* },
|
|
502
|
+
* })
|
|
503
|
+
* }
|
|
504
|
+
*
|
|
505
|
+
* return <button onClick={handleInsert}>Insert Block</button>
|
|
506
|
+
* }
|
|
507
|
+
* ```
|
|
508
|
+
*
|
|
509
|
+
* @example Create a new document with targetDocument
|
|
510
|
+
* ```tsx
|
|
511
|
+
* import {useAgentPatch} from '@sanity/sdk-react'
|
|
512
|
+
*
|
|
513
|
+
* function CreateProduct() {
|
|
514
|
+
* const patch = useAgentPatch()
|
|
515
|
+
*
|
|
516
|
+
* const handleCreate = async () => {
|
|
517
|
+
* const result = await patch({
|
|
518
|
+
* targetDocument: {
|
|
519
|
+
* operation: 'create',
|
|
520
|
+
* _type: 'product',
|
|
521
|
+
* },
|
|
522
|
+
* schemaId: '_.schemas.default',
|
|
523
|
+
* target: [
|
|
524
|
+
* {
|
|
525
|
+
* path: 'title',
|
|
526
|
+
* operation: 'set',
|
|
527
|
+
* value: 'New Product',
|
|
528
|
+
* },
|
|
529
|
+
* {
|
|
530
|
+
* path: 'price',
|
|
531
|
+
* operation: 'set',
|
|
532
|
+
* value: 29.99,
|
|
533
|
+
* },
|
|
534
|
+
* {
|
|
535
|
+
* path: 'inStock',
|
|
536
|
+
* operation: 'set',
|
|
537
|
+
* value: true,
|
|
538
|
+
* },
|
|
539
|
+
* ],
|
|
540
|
+
* })
|
|
541
|
+
* console.log('Created document:', result.documentId)
|
|
542
|
+
* }
|
|
543
|
+
*
|
|
544
|
+
* return <button onClick={handleCreate}>Create Product</button>
|
|
545
|
+
* }
|
|
546
|
+
* ```
|
|
547
|
+
*
|
|
548
|
+
* @category Agent Actions
|
|
134
549
|
*/
|
|
135
550
|
export const useAgentPatch: () => (options: AgentPatchOptions) => Promise<AgentPatchResult> =
|
|
136
551
|
createCallbackHook(patchAdapter)
|
|
@@ -166,11 +166,11 @@ describe('useDispatchIntent', () => {
|
|
|
166
166
|
})
|
|
167
167
|
|
|
168
168
|
it('should send intent message with media library source', () => {
|
|
169
|
-
const mockMediaLibraryHandle
|
|
169
|
+
const mockMediaLibraryHandle = {
|
|
170
170
|
documentId: 'test-asset-id',
|
|
171
171
|
documentType: 'sanity.asset',
|
|
172
172
|
sourceName: 'media-library',
|
|
173
|
-
}
|
|
173
|
+
} as const
|
|
174
174
|
|
|
175
175
|
const {result} = renderHook(() =>
|
|
176
176
|
useDispatchIntent({
|
|
@@ -195,11 +195,11 @@ describe('useDispatchIntent', () => {
|
|
|
195
195
|
})
|
|
196
196
|
|
|
197
197
|
it('should send intent message with canvas source', () => {
|
|
198
|
-
const mockCanvasHandle
|
|
198
|
+
const mockCanvasHandle = {
|
|
199
199
|
documentId: 'test-canvas-document-id',
|
|
200
200
|
documentType: 'sanity.canvas.document',
|
|
201
201
|
sourceName: 'canvas',
|
|
202
|
-
}
|
|
202
|
+
} as const
|
|
203
203
|
|
|
204
204
|
const {result} = renderHook(() =>
|
|
205
205
|
useDispatchIntent({
|
|
@@ -238,7 +238,7 @@ describe('useDispatchIntent', () => {
|
|
|
238
238
|
)
|
|
239
239
|
|
|
240
240
|
expect(() => result.current.dispatchIntent()).toThrow(
|
|
241
|
-
'useDispatchIntent: Either `sourceName
|
|
241
|
+
'useDispatchIntent: Unable to determine resource. Either `source`, `sourceName`, or both `projectId` and `dataset` must be provided in documentHandle.',
|
|
242
242
|
)
|
|
243
243
|
})
|
|
244
244
|
})
|
|
@@ -3,6 +3,7 @@ import {type DocumentHandle, type FrameMessage} from '@sanity/sdk'
|
|
|
3
3
|
import {useCallback} from 'react'
|
|
4
4
|
|
|
5
5
|
import {useWindowConnection} from '../comlink/useWindowConnection'
|
|
6
|
+
import {type WithSourceNameSupport} from '../helpers/useNormalizedSourceOptions'
|
|
6
7
|
import {useResourceIdFromDocumentHandle} from './utils/useResourceIdFromDocumentHandle'
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -41,7 +42,7 @@ interface DispatchIntent {
|
|
|
41
42
|
interface UseDispatchIntentParams {
|
|
42
43
|
action?: 'edit'
|
|
43
44
|
intentId?: string
|
|
44
|
-
documentHandle: DocumentHandle
|
|
45
|
+
documentHandle: WithSourceNameSupport<DocumentHandle>
|
|
45
46
|
parameters?: Record<string, unknown>
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -111,8 +112,6 @@ export function useDispatchIntent(params: UseDispatchIntentParams): DispatchInte
|
|
|
111
112
|
throw new Error('useDispatchIntent: Either `action` or `intentId` must be provided.')
|
|
112
113
|
}
|
|
113
114
|
|
|
114
|
-
const {projectId, dataset, sourceName} = documentHandle
|
|
115
|
-
|
|
116
115
|
if (action && intentId) {
|
|
117
116
|
// eslint-disable-next-line no-console -- warn if both action and intentId are provided
|
|
118
117
|
console.warn(
|
|
@@ -120,9 +119,10 @@ export function useDispatchIntent(params: UseDispatchIntentParams): DispatchInte
|
|
|
120
119
|
)
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
|
|
122
|
+
// Validate that we have a resource ID (which is computed from source/sourceName or projectId+dataset)
|
|
123
|
+
if (!resource.id) {
|
|
124
124
|
throw new Error(
|
|
125
|
-
'useDispatchIntent: Either `sourceName
|
|
125
|
+
'useDispatchIntent: Unable to determine resource. Either `source`, `sourceName`, or both `projectId` and `dataset` must be provided in documentHandle.',
|
|
126
126
|
)
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {type DocumentHandle} from '@sanity/sdk'
|
|
2
1
|
import {describe, expect, it} from 'vitest'
|
|
3
2
|
|
|
4
3
|
import {renderHook} from '../../../../test/test-utils'
|
|
@@ -25,11 +24,11 @@ describe('getResourceIdFromDocumentHandle', () => {
|
|
|
25
24
|
|
|
26
25
|
describe('with DocumentHandleWithSource - media library', () => {
|
|
27
26
|
it('should return media library ID and resourceType when media library source is provided', () => {
|
|
28
|
-
const documentHandle
|
|
27
|
+
const documentHandle = {
|
|
29
28
|
documentId: 'test-asset-id',
|
|
30
29
|
documentType: 'sanity.asset',
|
|
31
30
|
sourceName: 'media-library',
|
|
32
|
-
}
|
|
31
|
+
} as const
|
|
33
32
|
|
|
34
33
|
const {result} = renderHook(() => useResourceIdFromDocumentHandle(documentHandle))
|
|
35
34
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
isMediaLibrarySource,
|
|
6
6
|
} from '@sanity/sdk'
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {useNormalizedSourceOptions} from '../../helpers/useNormalizedSourceOptions'
|
|
9
9
|
|
|
10
10
|
interface DashboardMessageResource {
|
|
11
11
|
id: string
|
|
@@ -18,8 +18,8 @@ interface DashboardMessageResource {
|
|
|
18
18
|
export function useResourceIdFromDocumentHandle(
|
|
19
19
|
documentHandle: DocumentHandle,
|
|
20
20
|
): DashboardMessageResource {
|
|
21
|
-
const
|
|
22
|
-
const {projectId, dataset} =
|
|
21
|
+
const options = useNormalizedSourceOptions(documentHandle)
|
|
22
|
+
const {projectId, dataset, source} = options
|
|
23
23
|
let resourceId: string = ''
|
|
24
24
|
let resourceType: 'media-library' | 'canvas' | undefined
|
|
25
25
|
if (projectId && dataset) {
|