UNPKG

@sanity/assist

Version:

You create the instructions; Sanity AI Assist does the rest.

1 lines 407 kB
{"version":3,"file":"index.mjs","sources":["../src/_lib/connector/helpers.ts","../src/_lib/connector/useRegionRects.ts","../src/_lib/connector/ConnectorRegion.tsx","../src/_lib/connector/ConnectorsStoreContext.ts","../src/_lib/connector/useConnectorsStore.ts","../src/_lib/connector/ConnectFromRegion.tsx","../src/_lib/connector/ConnectorsStore.ts","../src/_lib/connector/ConnectorsProvider.tsx","../src/_lib/connector/mapConnectorToLine.ts","../src/_lib/form/constants.ts","../src/types.ts","../src/helpers/misc.ts","../src/helpers/typeUtils.ts","../src/helpers/assistSupported.ts","../src/onboarding/FirstAssistedPathProvider.tsx","../src/constants.ts","../src/helpers/ids.ts","../src/assistDocument/hooks/useDocumentState.ts","../src/assistDocument/hooks/useStudioAssistDocument.ts","../src/assistDocument/hooks/useInstructionToaster.tsx","../src/assistDocument/AssistDocumentInput.tsx","../src/assistDocument/AssistDocumentContext.tsx","../src/assistLayout/AiAssistanceConfigContext.tsx","../src/useApiClient.ts","../src/assistLayout/RunInstructionProvider.tsx","../src/assistDocument/RequestRunInstructionProvider.tsx","../src/assistInspector/helpers.ts","../src/assistDocument/hooks/useAssistDocumentContextValue.tsx","../src/assistDocument/AssistDocumentContextProvider.tsx","../src/assistDocument/AssistDocumentLayout.tsx","../src/components/AssistFeatureBadge.tsx","../src/onboarding/FieldActionsOnboarding.tsx","../src/onboarding/onboardingStore.ts","../src/components/FadeInContent.tsx","../../node_modules/@sanity/color/dist/index.js","../src/presence/AssistAvatar.tsx","../src/presence/AiFieldPresence.tsx","../src/presence/useAssistPresence.ts","../src/assistFormComponents/AssistField.tsx","../src/components/SafeValueInput.tsx","../src/assistFormComponents/AssistFormBlock.tsx","../src/assistFormComponents/AssistInlineFormBlock.tsx","../src/assistFormComponents/AssistItem.tsx","../src/_lib/form/DocumentForm.tsx","../src/assistDocument/components/AssistTypeContext.tsx","../src/helpers/conditionalMembers.ts","../src/onboarding/InspectorOnboarding.tsx","../src/assistInspector/FieldAutocomplete.tsx","../src/components/TimeAgo.tsx","../src/assistInspector/InstructionTaskHistoryButton.tsx","../src/assistInspector/AssistInspector.tsx","../src/assistInspector/constants.ts","../src/assistInspector/index.ts","../src/assistConnectors/draw/arrowPath.ts","../src/assistConnectors/draw/connectorPath.ts","../src/assistConnectors/ConnectorPath.tsx","../src/assistConnectors/AssistConnectorsOverlay.tsx","../src/helpers/styleguide.ts","../src/translate/getLanguageParams.ts","../src/translate/languageStore.ts","../src/translate/paths.ts","../src/translate/FieldTranslationProvider.tsx","../src/schemas/serialize/schemaUtils.ts","../src/schemas/serialize/serializeSchema.ts","../src/assistLayout/fieldRefCache.tsx","../src/assistLayout/AiAssistanceConfigProvider.tsx","../src/assistLayout/AssistLayout.tsx","../src/components/ImageContext.tsx","../src/assistDocument/components/instruction/appearance/IconInput.tsx","../src/helpers/useAssistSupported.ts","../src/translate/translateActions.tsx","../src/fieldActions/generateCaptionActions.tsx","../src/fieldActions/generateImageActions.tsx","../src/fieldActions/PrivateIcon.tsx","../../node_modules/get-random-values-esm/index.mjs","../src/_lib/randomKey.ts","../src/fieldActions/customFieldActions.tsx","../src/fieldActions/assistFieldActions.tsx","../src/presence/AssistDocumentPresence.tsx","../src/assistDocument/components/instruction/BackToInstructionsLink.tsx","../src/assistDocument/components/SelectedFieldContext.tsx","../src/assistDocument/components/AssistDocumentForm.tsx","../src/assistDocument/components/FieldRefPreview.tsx","../src/assistDocument/components/generic/HiddenFieldTitle.tsx","../src/assistDocument/components/instruction/appearance/InstructionVisibility.tsx","../src/assistDocument/components/instruction/FieldRefInput.tsx","../src/assistDocument/components/helpers.ts","../src/assistDocument/components/instruction/InstructionInput.tsx","../src/assistDocument/components/instruction/InstructionOutputField.tsx","../src/assistDocument/components/instruction/InstructionOutputInput.tsx","../src/assistDocument/components/instruction/PromptInput.tsx","../src/assistDocument/components/InstructionsArrayField.tsx","../src/assistDocument/components/InstructionsArrayInput.tsx","../src/components/HideReferenceChangedBannerInput.tsx","../src/schemas/contextDocumentSchema.tsx","../src/schemas/assistDocumentSchema.tsx","../src/schemas/index.ts","../src/plugin.tsx","../src/_lib/fixedListenQuery.ts","../src/_lib/useListeningQuery.ts","../src/schemas/serialize/SchemTypeTool.tsx","../src/fieldActions/useUserInput.ts"],"sourcesContent":["export function hasOverflowScroll(el: HTMLElement): boolean {\n const overflow = getComputedStyle(el).overflow\n\n return overflow.includes('auto') || overflow.includes('hidden') || overflow.includes('scroll')\n}\n","import {useEffect, useMemo, useRef, useState} from 'react'\n\nimport {hasOverflowScroll} from './helpers'\nimport {Rect, Scroll} from './types'\n\nexport function useRegionRects() {\n const ref = useRef<HTMLDivElement>(null)\n\n const [relativeBoundsRect, setRelativeBoundsRect] = useState<Rect | null>(null)\n const [relativeElementRect, setRelativeElementRect] = useState<Rect | null>(null)\n const [boundsScroll, setBoundsScroll] = useState<Scroll>({x: 0, y: 0})\n const [scroll, setScroll] = useState<Scroll>({x: 0, y: 0})\n\n const boundsScrollXRef = useRef(0)\n const boundsScrollYRef = useRef(0)\n\n const elementScrollXRef = useRef(0)\n const elementScrollYRef = useRef(0)\n\n useEffect(() => {\n const el = ref.current\n\n if (!el) return undefined\n\n const scrollParents: HTMLElement[] = []\n let parent = el.parentElement\n\n while (parent && parent !== document.body) {\n if (\n hasOverflowScroll(parent)\n // || parent.scrollHeight > parent.clientHeight\n ) {\n scrollParents.push(parent)\n }\n\n parent = parent.parentElement\n }\n\n function handleResize() {\n const scrollParent = scrollParents[0]\n\n const boundsRect = scrollParent?.getBoundingClientRect() || {\n x: 0,\n y: 0,\n width: window.innerWidth,\n height: window.innerHeight,\n }\n\n const domRect = el!.getBoundingClientRect()\n\n setRelativeBoundsRect({\n x: boundsRect.x + boundsScrollXRef.current,\n y: boundsRect.y + boundsScrollYRef.current,\n w: boundsRect.width,\n h: boundsRect.height,\n })\n\n setRelativeElementRect({\n x: domRect.x + elementScrollXRef.current,\n y: domRect.y + elementScrollYRef.current,\n w: domRect.width,\n h: domRect.height,\n })\n }\n\n function handleScroll() {\n let scrollX = window.scrollX\n let scrollY = window.scrollY\n\n for (const scrollParent of scrollParents) {\n scrollX += scrollParent.scrollLeft\n scrollY += scrollParent.scrollTop\n }\n\n const scrollParent = scrollParents[0]\n\n boundsScrollXRef.current = scrollX - (scrollParent?.scrollLeft || window.scrollX)\n\n boundsScrollYRef.current = scrollY - (scrollParent?.scrollTop || window.scrollY)\n\n setBoundsScroll({\n x: boundsScrollXRef.current,\n y: boundsScrollYRef.current,\n })\n\n elementScrollXRef.current = scrollX\n elementScrollYRef.current = scrollY\n\n setScroll({x: scrollX, y: scrollY})\n }\n\n window.addEventListener('scroll', handleScroll, {passive: true})\n\n const ro = new ResizeObserver(handleResize)\n\n ro.observe(el)\n\n for (const scrollParent of scrollParents) {\n scrollParent.addEventListener('scroll', handleScroll, {passive: true})\n ro.observe(scrollParent)\n }\n\n handleScroll()\n\n return () => {\n ro.unobserve(el)\n\n for (const scrollParent of scrollParents) {\n ro.unobserve(scrollParent)\n scrollParent.removeEventListener('scroll', handleScroll)\n }\n\n ro.disconnect()\n\n window.removeEventListener('scroll', handleScroll)\n }\n }, [])\n\n const bounds: Rect | null = useMemo(\n () =>\n relativeBoundsRect && {\n x: relativeBoundsRect.x - boundsScroll.x,\n y: relativeBoundsRect.y - boundsScroll.y,\n w: relativeBoundsRect.w,\n h: relativeBoundsRect.h,\n },\n [relativeBoundsRect, boundsScroll],\n )\n\n const element: Rect | null = useMemo(\n () =>\n relativeElementRect && {\n x: relativeElementRect.x - scroll.x,\n y: relativeElementRect.y - scroll.y,\n w: relativeElementRect.w,\n h: relativeElementRect.h,\n },\n [relativeElementRect, scroll],\n )\n\n return {bounds, element, ref}\n}\n","import {HTMLProps, useEffect} from 'react'\n\nimport {ConnectorRegionRects} from './types'\nimport {useRegionRects} from './useRegionRects'\n\nexport function ConnectorRegion(\n props: {\n onRectsChange?: (rects: ConnectorRegionRects | null) => void\n } & HTMLProps<HTMLDivElement>,\n) {\n const {children, onRectsChange, ...restProps} = props\n\n const {bounds, element, ref} = useRegionRects()\n\n useEffect(() => {\n onRectsChange?.(bounds && element ? {bounds, element} : null)\n }, [bounds, element, onRectsChange])\n\n return (\n <div {...restProps} ref={ref}>\n {children}\n </div>\n )\n}\n","import {createContext} from 'react'\n\nimport {ConnectorsStore} from './ConnectorsStore'\n\nexport const ConnectorsStoreContext = createContext<ConnectorsStore | null>(null)\n","import {useContext} from 'react'\n\nimport {ConnectorsStore} from './ConnectorsStore'\nimport {ConnectorsStoreContext} from './ConnectorsStoreContext'\n\nexport function useConnectorsStore(): ConnectorsStore {\n const store = useContext(ConnectorsStoreContext)\n\n if (!store) {\n throw new Error('Missing connectors store context')\n }\n\n return store\n}\n","import {HTMLProps, useEffect, useState} from 'react'\n\nimport {ConnectorRegion} from './ConnectorRegion'\nimport {ConnectorRegionRects} from './types'\nimport {useConnectorsStore} from './useConnectorsStore'\n\nexport function ConnectFromRegion(\n props: {_key: string; zIndex: number} & HTMLProps<HTMLDivElement>,\n) {\n const {children, _key: key, zIndex, ...restProps} = props\n const store = useConnectorsStore()\n const [rects, setRects] = useState<ConnectorRegionRects | null>(null)\n\n useEffect(() => store.from.subscribe(key, {zIndex}), [key, store, zIndex])\n\n useEffect(() => {\n if (rects) store.from.next(key, rects)\n }, [key, rects, store])\n\n return (\n <ConnectorRegion {...restProps} onRectsChange={setRects}>\n {children}\n </ConnectorRegion>\n )\n}\n","import {Connector, ConnectorRegionRects} from './types'\n\nexport interface ConnectorsStore {\n connectors: {\n subscribe: (observer: (connectors: Connector[]) => void) => () => void\n }\n\n from: {\n subscribe: (key: string, payload?: Record<string, unknown>) => () => void\n next: (key: string, rects: ConnectorRegionRects) => void\n }\n\n to: {\n subscribe: (key: string, payload?: Record<string, unknown>) => () => void\n next: (key: string, rects: ConnectorRegionRects) => void\n }\n}\n\nexport function createConnectorsStore(): ConnectorsStore {\n const configKeys: string[] = []\n const fieldKeys: string[] = []\n\n const channels = {\n from: new Map<string, ConnectorRegionRects | null>(),\n to: new Map<string, ConnectorRegionRects | null>(),\n }\n\n const payloads = {\n from: new Map<string, Record<string, unknown> | undefined>(),\n to: new Map<string, Record<string, unknown> | undefined>(),\n }\n\n const observers: ((connectors: Connector[]) => void)[] = []\n\n function notifyObservers() {\n const connectors: Connector[] = []\n\n for (const key of configKeys) {\n const toRects = channels.to.get(key)\n const toPayload = payloads.from.get(key)\n\n const fromRects = channels.from.get(key)\n const fromPayload = payloads.from.get(key)\n\n if (toRects && fromRects) {\n connectors.push({\n key,\n from: {...fromRects, payload: fromPayload},\n to: {...toRects, payload: toPayload},\n })\n }\n }\n\n for (const observer of observers) {\n observer(connectors)\n }\n }\n\n return {\n to: {\n subscribe(key, payload) {\n channels.to.set(key, null)\n payloads.to.set(key, payload)\n\n configKeys.push(key)\n\n return () => {\n channels.to.delete(key)\n payloads.to.delete(key)\n\n const idx = configKeys.indexOf(key)\n\n if (idx > -1) configKeys.splice(idx, 1)\n\n notifyObservers()\n }\n },\n next(key, rects) {\n channels.to.set(key, rects)\n\n if (fieldKeys.includes(key)) notifyObservers()\n },\n },\n\n connectors: {\n subscribe(observer) {\n observers.push(observer)\n\n return () => {\n const idx = observers.indexOf(observer)\n\n if (idx > -1) observers.splice(idx, 1)\n }\n },\n },\n\n from: {\n subscribe(key, payload) {\n channels.from.set(key, null)\n payloads.from.set(key, payload)\n\n fieldKeys.push(key)\n\n return () => {\n channels.from.delete(key)\n payloads.from.delete(key)\n\n const idx = fieldKeys.indexOf(key)\n\n if (idx > -1) fieldKeys.splice(idx, 1)\n\n notifyObservers()\n }\n },\n next(key, rects) {\n channels.from.set(key, rects)\n\n if (configKeys.includes(key)) notifyObservers()\n },\n },\n }\n}\n","import {ReactNode, useEffect, useMemo} from 'react'\n\nimport {ConnectorsStore, createConnectorsStore} from './ConnectorsStore'\nimport {ConnectorsStoreContext} from './ConnectorsStoreContext'\nimport {Connector} from './types'\n\nexport function ConnectorsProvider(props: {\n children?: ReactNode\n onConnectorsChange?: (connectors: Connector[]) => void\n}) {\n const {children, onConnectorsChange} = props\n const store: ConnectorsStore = useMemo(() => createConnectorsStore(), [])\n\n useEffect(\n () => onConnectorsChange && store.connectors.subscribe(onConnectorsChange),\n [onConnectorsChange, store],\n )\n\n return <ConnectorsStoreContext.Provider value={store}>{children}</ConnectorsStoreContext.Provider>\n}\n","import {\n ConnectorLine,\n ConnectorLinePoint,\n ConnectorOptions,\n ConnectorRegionRects,\n Rect,\n} from './types'\n\nfunction getConnectorLinePoint(\n options: ConnectorOptions,\n rect: Rect,\n bounds: Rect,\n): ConnectorLinePoint {\n const centerY = rect.y + rect.h / 2\n const isAbove = rect.y + rect.h < bounds.y + options.arrow.marginY\n const isBelow = rect.y > bounds.y + bounds.h - options.arrow.marginY\n\n return {\n bounds,\n x: rect.x,\n y: centerY,\n centerY,\n startY: rect.y + options.path.marginY,\n endY: rect.y + rect.h - options.path.marginY,\n isAbove,\n isBelow,\n outOfBounds: isAbove || isBelow,\n }\n}\n\nexport function mapConnectorToLine(\n options: ConnectorOptions,\n connector: {from: ConnectorRegionRects; to: ConnectorRegionRects},\n): ConnectorLine {\n const fromBounds: Rect = {\n y: connector.from.bounds.y + options.arrow.threshold,\n // bottom: connector.from.bounds.y + connector.from.bounds.h - options.arrow.threshold,\n x: connector.from.bounds.x,\n // right: connector.from.bounds.x + connector.from.bounds.w,\n w: connector.from.bounds.w,\n h: connector.from.bounds.h - options.arrow.threshold * 2,\n }\n\n const from = getConnectorLinePoint(options, connector.from.element, fromBounds)\n from.x = connector.from.element.x + connector.from.element.w // + 1\n\n const fromBottom = fromBounds.y + fromBounds.h\n\n const toBounds: Rect = {\n y: connector.to.bounds.y + options.arrow.threshold,\n // bottom: connector.to.bounds.y + connector.to.bounds.h - options.arrow.threshold,\n x: connector.to.bounds.x,\n // right: connector.to.bounds.x + connector.to.bounds.w,\n w: connector.to.bounds.w,\n h: connector.to.bounds.h - options.arrow.threshold * 2,\n }\n\n const toBottom = toBounds.y + toBounds.h\n\n const to = getConnectorLinePoint(options, connector.to.element, toBounds)\n\n const maxStartY = Math.max(to.startY, from.startY)\n\n // Align from <-> to vertically\n from.y = Math.min(maxStartY, from.endY)\n if (from.y < toBounds.y) {\n from.y = Math.min(toBounds.y, from.endY)\n } else if (from.y > toBottom) {\n from.y = Math.max(toBottom, from.startY)\n }\n to.y = Math.min(maxStartY, to.endY)\n if (to.y < fromBounds.y) {\n to.y = Math.min(fromBounds.y, to.endY)\n } else if (to.y > fromBottom) {\n to.y = Math.max(fromBottom, to.startY)\n }\n\n // Keep within bounds\n from.y = Math.min(Math.max(from.y, fromBounds.y), fromBottom)\n to.y = Math.min(Math.max(to.y, toBounds.y), toBottom)\n\n return {from, to}\n}\n","export const assistFormId = 'assist'\n","import {PortableTextBlock, PortableTextMarkDefinition, PortableTextSpan} from '@portabletext/types'\nimport {SanityDocument} from 'sanity'\n\n//id prefixes\nexport const assistDocumentIdPrefix = 'sanity.assist.schemaType.'\nexport const assistDocumentStatusIdPrefix = 'sanity.assist.status.'\nexport const assistSchemaIdPrefix = 'sanity.assist.schema.'\n\n// type names\nexport const assistDocumentTypeName = 'sanity.assist.schemaType.annotations' as const\nexport const assistFieldTypeName = 'sanity.assist.schemaType.field' as const\nexport const instructionTypeName = 'sanity.assist.instruction' as const\nexport const promptTypeName = 'sanity.assist.instruction.prompt' as const\n\nexport const userInputTypeName = 'sanity.assist.instruction.userInput' as const\nexport const instructionContextTypeName = 'sanity.assist.instruction.context' as const\nexport const fieldReferenceTypeName = 'sanity.assist.instruction.fieldRef' as const\n\n// user-facing type. Intentionally does not have sanity. prefix\nexport const contextDocumentTypeName = 'assist.instruction.context' as const\n\nexport const assistTasksStatusTypeName = 'sanity.assist.task.status' as const\nexport const instructionTaskTypeName = 'sanity.assist.instructionTask' as const\nexport const fieldPresenceTypeName = 'sanity.assist.instructionTask.presence' as const\n\nexport const assistSerializedTypeName = 'sanity.assist.serialized.type' as const\nexport const assistSerializedFieldTypeName = 'sanity.assist.serialized.field' as const\n\nexport const outputFieldTypeName = 'sanity.assist.output.field' as const\nexport const outputTypeTypeName = 'sanity.assist.output.type' as const\n\n//url params\nexport const inspectParam = 'inspect' as const\nexport const fieldPathParam = 'pathKey' as const\nexport const instructionParam = 'instruction' as const\n\n// other constants\nexport const documentRootKey = '<document>'\n\nexport type SerializedSchemaMember = Omit<SerializedSchemaType, 'name' | '_type'> & {\n _type?: typeof assistSerializedFieldTypeName\n name?: string\n}\n\nexport interface SerializedSchemaType {\n _type?: typeof assistSerializedTypeName\n _id?: string\n type: string\n name: string\n title?: string\n fields?: SerializedSchemaMember[]\n of?: SerializedSchemaMember[]\n to?: SerializedSchemaMember[]\n annotations?: SerializedSchemaMember[]\n inlineOf?: SerializedSchemaMember[]\n values?: string[] | {value: string; title?: string}[]\n hidden?: boolean | 'function'\n readOnly?: boolean | 'function'\n options?: {\n /** equivalent to options.aiAssist.imageDescriptionField - not renamed in the api for backwards compatability */\n imagePromptField?: string\n embeddingsIndex?: string\n }\n}\n\nexport interface AssistDocument extends SanityDocument {\n fields?: AssistField[]\n instructions?: StudioInstruction[]\n}\n\nexport interface PresetInstruction {\n _key: string\n prompt?: PromptTextBlock[]\n\n title?: string\n /**\n * String key from `@sanity/icons` IconMap\n */\n icon?: string\n\n /**\n * Type/field filter\n */\n output?: (OutputFieldItem | OutputTypeItem)[]\n}\n\nexport interface PresetField {\n path?: string\n instructions?: PresetInstruction[]\n}\n\nexport interface AssistPreset {\n fields?: PresetField[]\n}\n\nexport interface SanityAssistDocument {\n _id: string\n _type: typeof assistDocumentTypeName\n fields?: StudioAssistField[]\n}\n\nexport interface StudioAssistDocument extends SanityAssistDocument {\n // added after loading\n tasks?: InstructionTask[]\n}\n\nexport interface AssistField {\n _key: string\n _type: typeof assistFieldTypeName\n path?: string\n instructions?: StudioInstruction[]\n}\n\nexport interface StudioAssistField {\n _key: string\n path?: string\n instructions?: StudioInstruction[]\n\n // added\n tasks?: InstructionTask[]\n}\n\nexport interface FieldRef extends PortableTextMarkDefinition {\n _type: typeof fieldReferenceTypeName\n path?: string\n}\n\nexport interface ContextBlock {\n _type: typeof instructionContextTypeName\n reference?: {\n _type: 'reference'\n _ref?: string\n }\n}\n\nexport interface UserInputBlock {\n _type: typeof userInputTypeName\n _key: string\n message?: string\n description?: string\n}\n\nexport type InlinePromptBlock = PortableTextSpan | FieldRef | UserInputBlock | ContextBlock\nexport type PromptTextBlock = Omit<\n PortableTextBlock<never, InlinePromptBlock, 'normal', never>,\n '_type'\n> & {_type: 'block'}\n\nexport type PromptBlock = PromptTextBlock | FieldRef | ContextBlock | UserInputBlock\n\nexport interface AiPresence {\n _key: string\n _type: typeof fieldPresenceTypeName\n path?: string\n started?: string\n}\n\nexport type TaskEndedReason = 'success' | 'error' | 'aborted' | 'timeout'\n\nexport interface InstructionTask {\n _key: string\n _type: typeof instructionTaskTypeName\n instructionKey?: string\n title?: string\n path?: string\n started?: string\n updated?: string\n ended?: string\n message?: string\n reason?: TaskEndedReason\n presence?: AiPresence[]\n startedByUserId?: string\n\n //added by studio\n // eslint-disable-next-line no-use-before-define\n instruction?: StudioInstruction\n}\n\nexport interface StudioInstruction {\n _key: string\n _type: typeof instructionTypeName\n prompt?: PromptBlock[]\n\n icon?: string\n userId?: string\n title?: string\n placeholder?: string\n output?: (OutputFieldItem | OutputTypeItem)[]\n\n //added after query / synthetic fields\n tasks?: InstructionTask[]\n}\n\nexport interface AssistTasksStatus {\n _id: string\n _type: typeof assistTasksStatusTypeName\n tasks?: InstructionTask[]\n}\n\nexport interface AssistInspectorRouteParams {\n [inspectParam]?: string\n [fieldPathParam]?: string\n [instructionParam]?: string\n}\n\nexport interface OutputFieldItem {\n _type: typeof outputFieldTypeName\n _key: string\n //path relative to the field the instruction is for (same as _key)\n relativePath?: string\n}\n\nexport interface OutputTypeItem {\n _type: typeof outputTypeTypeName\n _key: string\n /* array item type name */\n type?: string\n //path relative to the array-field the instruction is for, can be empty string (the array itself, same as _key)\n relativePath?: string\n}\n","import {useMemo} from 'react'\nimport {Path, pathToString} from 'sanity'\n\nimport {documentRootKey, StudioInstruction} from '../types'\n\nexport function usePathKey(path: Path | string) {\n return useMemo(() => {\n return getPathKey(path)\n }, [path])\n}\n\nexport function getPathKey(path: Path | string) {\n if (path.length) {\n return Array.isArray(path) ? pathToString(path) : path\n }\n return documentRootKey\n}\n\nexport function getInstructionTitle(instruction?: StudioInstruction) {\n return instruction?.title ?? 'Untitled'\n}\n\nexport function isDefined<T>(t: T | undefined | null): t is T {\n return t !== undefined && t !== null\n}\n","import {ArraySchemaType, ImageOptions, SchemaType} from 'sanity'\n\nexport function isPortableTextArray(type: ArraySchemaType) {\n return type.of.find((t) => isType(t, 'block'))\n}\n\n/**\n * Returns true if the `schemaType` or any of its parent types (`schemaType.type`)` has `name` equal\n * to `typeName`.\n *\n * Useful for checking if `schemaType` is a type alias of `ìmage`, `code` or similar.\n */\nexport function isType(schemaType: SchemaType, typeName: string): boolean {\n if (schemaType.name === typeName) {\n return true\n }\n if (!schemaType.type) {\n return false\n }\n return isType(schemaType.type, typeName)\n}\n\nexport function isImage(schemaType: SchemaType) {\n return isType(schemaType, 'image')\n}\n\nexport function getDescriptionFieldOption(\n schemaType: SchemaType | undefined,\n): {path: string; updateOnImageChange: boolean} | undefined {\n if (!schemaType) {\n return undefined\n }\n const descriptionField = (schemaType.options as ImageOptions)?.aiAssist?.imageDescriptionField\n if (typeof descriptionField === 'string') {\n return {\n path: descriptionField,\n updateOnImageChange: true,\n }\n } else if (descriptionField) {\n return {\n path: descriptionField.path,\n updateOnImageChange: descriptionField.updateOnImageChange ?? true,\n }\n }\n return getDescriptionFieldOption(schemaType.type)\n}\n\nexport function getImageInstructionFieldOption(\n schemaType: SchemaType | undefined,\n): string | undefined {\n if (!schemaType) {\n return undefined\n }\n const imageInstructionField = (schemaType.options as ImageOptions)?.aiAssist\n ?.imageInstructionField\n if (imageInstructionField) {\n return imageInstructionField\n }\n return getImageInstructionFieldOption(schemaType.type)\n}\n","import {ReferenceOptions, SchemaType} from 'sanity'\n\nimport {AssistOptions} from '../schemas/typeDefExtensions'\nimport {isType} from './typeUtils'\n\nexport function isSchemaAssistEnabled(type: SchemaType) {\n return !(type.options as AssistOptions | undefined)?.aiAssist?.exclude\n}\n\nexport function isAssistSupported(type: SchemaType) {\n if (!isSchemaAssistEnabled(type)) {\n return false\n }\n\n if (isDisabled(type)) {\n return false\n }\n\n if (type.jsonType === 'array') {\n const unsupportedArray = type.of.every((t) => isDisabled(t))\n return !unsupportedArray\n }\n\n if (type.jsonType === 'object') {\n const unsupportedObject = type.fields.every((field) => isDisabled(field.type))\n return (\n !unsupportedObject ||\n /* to allow attaching custom actions on fieldless images */\n isType(type, 'image')\n )\n }\n return true\n}\n\nfunction isDisabled(type: SchemaType) {\n return !isSchemaAssistEnabled(type) || isUnsupportedType(type)\n}\n\nfunction isUnsupportedType(type: SchemaType) {\n return (\n type.name === 'sanity.imageCrop' ||\n type.name === 'sanity.imageHotspot' ||\n isType(type, 'globalDocumentReference') ||\n (isType(type, 'reference') &&\n !(type?.options as ReferenceOptions)?.aiAssist?.embeddingsIndex) ||\n isType(type, 'crossDatasetReference') ||\n isType(type, 'file')\n )\n}\n","import {createContext, PropsWithChildren, useMemo} from 'react'\nimport {FieldMember, ObjectInputProps, pathToString} from 'sanity'\n\nimport {isAssistSupported} from '../helpers/assistSupported'\n\nexport const FirstAssistedPathContext = createContext<string | undefined>(undefined)\n\nexport interface FirstAssistedPathProviderProps {\n members: ObjectInputProps['members']\n}\n\nexport function FirstAssistedPathProvider(\n props: PropsWithChildren<FirstAssistedPathProviderProps>,\n) {\n const {members} = props\n\n const firstAssistedPath = useMemo(() => {\n const firstAssisted = members.find(\n (member): member is FieldMember =>\n member.kind === 'field' && isAssistSupported(member.field.schemaType),\n )\n return firstAssisted?.field.path ? pathToString(firstAssisted?.field.path) : undefined\n }, [members])\n\n return (\n <FirstAssistedPathContext.Provider value={firstAssistedPath}>\n {props.children}\n </FirstAssistedPathContext.Provider>\n )\n}\n","import {minutesToMilliseconds} from 'date-fns'\n\nexport const releaseAnnouncementUrl =\n 'https://www.sanity.io/blog/sanity-ai-assist-announcement?utm_source=sanity-assist-plugin&utm_medium=organic_social&utm_campaign=ai-assist&utm_content='\n\nexport const instructionGuideUrl =\n 'https://sanity.io/guides/getting-started-with-ai-assist-instructions?utm_source=sanity-assist-plugin&utm_medium=organic_social&utm_campaign=ai-assist&utm_content='\n\nexport const giveFeedbackUrl = 'https://forms.gle/Kwz7CThxGeA2GiEU8'\n\nexport const salesUrl =\n 'https://www.sanity.io/contact/sales?utm_source=sanity-assist-plugin&utm_medium=organic_social&utm_campaign=ai-assist&utm_content='\n\nexport const packageName = '@sanity/assist'\n\nexport const pluginTitle = 'Sanity AI Assist'\n\nexport const pluginTitleShort = 'AI Assist'\n\nexport const maxHistoryVisibilityMs = minutesToMilliseconds(30)\n","import {getPublishedId, getVersionFromId, isVersionId} from 'sanity'\n\nimport {assistDocumentIdPrefix, assistDocumentStatusIdPrefix} from '../types'\n\nconst illegalIdChars = /[^a-zA-Z0-9._-]/g\n\nexport function publicId(id: string) {\n return id.replace('drafts.', '')\n}\n\nexport function assistDocumentId(documentType: string) {\n return `${assistDocumentIdPrefix}${documentType}`.replace(illegalIdChars, '_')\n}\n\nexport function assistTasksStatusId(documentId: string) {\n if (isVersionId(documentId)) {\n // Creates an id: sanity.assist.status.<versionName>.<documentId>\n return `${assistDocumentStatusIdPrefix}${getVersionFromId(documentId)}.${getPublishedId(documentId)}`\n }\n\n // Creates an id: sanity.assist.status<documentId>\n return `${assistDocumentStatusIdPrefix}${getPublishedId(documentId)}`\n}\n","import {useEditState} from 'sanity'\n\nexport function useDocumentState<T>(id: string, docType: string): T | undefined {\n const state = useEditState(id, docType)\n return (state.draft || state.published) as T | undefined\n}\n","import {useEffect, useMemo} from 'react'\nimport {type ObjectSchemaType, typed, useClient, useCurrentUser} from 'sanity'\n\nimport {maxHistoryVisibilityMs} from '../../constants'\nimport {assistDocumentId, assistTasksStatusId} from '../../helpers/ids'\nimport {\n assistDocumentTypeName,\n type AssistTasksStatus,\n assistTasksStatusTypeName,\n type InstructionTask,\n type StudioAssistDocument,\n type StudioAssistField,\n type StudioInstruction,\n} from '../../types'\nimport {useDocumentState} from './useDocumentState'\n\ninterface UseAssistDocumentProps {\n documentId: string\n schemaType: ObjectSchemaType\n initDoc?: boolean\n}\n\nexport function useStudioAssistDocument({\n documentId,\n schemaType,\n initDoc,\n}: UseAssistDocumentProps): StudioAssistDocument | undefined {\n const documentTypeName = schemaType.name\n const currentUser = useCurrentUser()\n\n const assistDocument = useDocumentState<StudioAssistDocument>(\n assistDocumentId(documentTypeName),\n assistDocumentTypeName,\n )\n const assistTasksStatus = useDocumentState<AssistTasksStatus>(\n assistTasksStatusId(documentId ?? ''),\n assistTasksStatusTypeName,\n )\n\n const client = useClient({apiVersion: '2023-01-01'})\n\n useEffect(() => {\n if (!assistDocument && initDoc) {\n client\n .createIfNotExists({\n _id: assistDocumentId(documentTypeName),\n _type: assistDocumentTypeName,\n })\n .catch(() => {\n // best effort\n })\n }\n }, [client, assistDocument, documentTypeName, initDoc])\n\n return useMemo(() => {\n if (!assistDocument) {\n return undefined\n }\n const tasks = assistTasksStatus?.tasks ?? []\n const fields = (assistDocument?.fields ?? []).map((assistField): StudioAssistField => {\n return {\n ...assistField,\n tasks: tasks.filter((task) => task.path === assistField.path),\n instructions: assistField.instructions\n ?.filter((p) => !p.userId || p.userId === currentUser?.id)\n .map((instruction) => asStudioInstruction(instruction, tasks)),\n }\n })\n return typed<StudioAssistDocument>({\n ...assistDocument,\n tasks: tasks?.map((task) => {\n const instruction = fields\n .find((f) => f.path === task.path)\n ?.instructions?.find((i) => i._key === task.instructionKey)\n return {\n ...task,\n instruction,\n }\n }),\n fields: fields,\n })\n }, [assistDocument, assistTasksStatus, currentUser])\n}\n\nfunction asStudioInstruction(\n instruction: StudioInstruction,\n run: InstructionTask[],\n): StudioInstruction {\n return {\n ...instruction,\n tasks: run\n .filter((task) => task.instructionKey === instruction._key)\n .filter(\n (task) =>\n task.started &&\n new Date().getTime() - new Date(task.started).getTime() < maxHistoryVisibilityMs,\n ),\n }\n}\n","import {useToast} from '@sanity/ui'\nimport {addSeconds, isAfter} from 'date-fns'\nimport {useEffect, useRef} from 'react'\nimport {ObjectSchemaType, useCurrentUser} from 'sanity'\n\nimport {getInstructionTitle} from '../../helpers/misc'\nimport {InstructionTask} from '../../types'\nimport {useStudioAssistDocument} from './useStudioAssistDocument'\n\nconst NO_TASKS: InstructionTask[] = []\n\nexport function useInstructionToaster(documentId: string, documentSchemaType: ObjectSchemaType) {\n const assistDocument = useStudioAssistDocument({documentId, schemaType: documentSchemaType})\n\n const assistDocLoaded = !!assistDocument\n const currentUser = useCurrentUser()\n const toast = useToast()\n const tasks = assistDocument?.tasks\n const previousTasks = useRef<InstructionTask[] | undefined | 'initial'>('initial')\n\n useEffect(() => {\n if (!assistDocLoaded) {\n return\n }\n\n if (previousTasks.current !== 'initial') {\n const prevTaskByKey = Object.fromEntries(\n (previousTasks.current ?? NO_TASKS).map((run) => [run._key, run]),\n )\n const endedTasks = tasks\n ?.filter((task) => task.startedByUserId === currentUser?.id)\n .filter((task) => {\n const prevTask = prevTaskByKey[task._key]\n return (!prevTask && task.ended) || (!prevTask?.ended && task.ended)\n })\n // filter out old stuff\n .filter((task) => task.ended && isAfter(addSeconds(new Date(task.ended), 30), new Date()))\n\n endedTasks?.forEach((task) => {\n const title = task.title ?? getInstructionTitle(task.instruction)\n if (task.reason === 'error') {\n toast.push({\n title: `Failed: ${title}`,\n status: 'error',\n description: `Instruction failed. ${task.message ?? ''}`,\n closable: true,\n duration: 10000,\n })\n } else if (task.reason === 'timeout') {\n toast.push({\n title: `Timeout: ${title}`,\n status: 'error',\n description: `Instruction timed out.`,\n closable: true,\n })\n } else if (task.reason === 'success') {\n toast.push({\n title: `Success: ${title}`,\n status: 'success',\n description: `Instruction completed.`,\n closable: true,\n })\n } else if (task.reason === 'aborted') {\n toast.push({\n title: `Canceled: ${title}`,\n status: 'warning',\n description: `Instruction canceled.`,\n closable: true,\n })\n }\n })\n }\n previousTasks.current = tasks\n }, [tasks, previousTasks, toast, currentUser, assistDocLoaded])\n}\n","import {useLayer} from '@sanity/ui'\nimport {useMemo} from 'react'\nimport type {InputProps, ObjectInputProps, ObjectSchemaType} from 'sanity'\nimport {useDocumentPane} from 'sanity/structure'\n\nimport {ConnectFromRegion} from '../_lib/connector'\nimport {assistFormId} from '../_lib/form/constants'\nimport {usePathKey} from '../helpers/misc'\nimport {isType} from '../helpers/typeUtils'\nimport {FirstAssistedPathProvider} from '../onboarding/FirstAssistedPathProvider'\nimport {assistDocumentTypeName} from '../types'\nimport {useInstructionToaster} from './hooks/useInstructionToaster'\n\nexport function AssistDocumentInputWrapper(props: InputProps) {\n if (!isType(props.schemaType, 'document') && props.id !== 'root' && props.id !== assistFormId) {\n return <AssistInput {...props} />\n }\n\n const documentId = (props.value as any)?._id as string | undefined\n if (!documentId) {\n return props.renderDefault(props)\n }\n\n return <AssistDocumentInput {...(props as ObjectInputProps)} documentId={documentId} />\n}\n\nfunction AssistDocumentInput({documentId, ...props}: ObjectInputProps & {documentId: string}) {\n useInstructionToaster(documentId, props.schemaType)\n\n const schemaType = useMemo(() => {\n if (props.schemaType.name !== assistDocumentTypeName) {\n return props.schemaType\n }\n return {\n ...props.schemaType,\n type: {\n ...props.schemaType.type,\n // compatability with i18nArrays plugin that requires this to be document\n name: 'document',\n },\n } as ObjectSchemaType\n }, [props.schemaType])\n\n return (\n <FirstAssistedPathProvider members={props.members}>\n {props.renderDefault({...props, schemaType})}\n </FirstAssistedPathProvider>\n )\n}\n\nfunction AssistInput(props: InputProps) {\n const {zIndex} = useLayer()\n const {paneKey} = useDocumentPane()\n const pathKey = usePathKey(props.path)\n\n return (\n <ConnectFromRegion _key={`${paneKey}_${pathKey}`} zIndex={zIndex} style={{minWidth: 0}}>\n {props.renderDefault(props)}\n </ConnectFromRegion>\n )\n}\n","import {createContext, useContext} from 'react'\nimport {DocumentInspector, ObjectSchemaType, PatchEvent} from 'sanity'\n\nimport {InstructionTask, StudioAssistDocument} from '../types'\nimport {FieldRef} from '../assistInspector/helpers'\n\nexport type AssistDocumentContextValue = (\n | {assistDocument: StudioAssistDocument; loading: false}\n | {assistDocument: undefined; loading: true}\n) & {\n documentIsNew: boolean\n /**\n * This is the _actual_ id of the current document (ie the document loaded in the pane); it contains draft. versions. prefix ect depending on context\n */\n assistableDocumentId: string\n documentIsAssistable: boolean\n documentSchemaType: ObjectSchemaType\n openInspector: (inspectorName: string, paneParams?: Record<string, string>) => void\n closeInspector: (inspectorName?: string) => void\n inspector: DocumentInspector | null\n selectedPath?: string\n documentOnChange: (event: PatchEvent) => void\n\n /**\n * Synthetic task is used to display AI presence at the document level for the user who started the action.\n * These are not persisted, so other users will not see them.\n * It is mostly a helper to give _some_ visual feedback to the user while a custom action is running.\n * This also means that reloading the page will remove the icon.\n *\n * Agent Actions add their own \"real\" tasks, so if a custom action calls an Agent action, _those_ tasks\n * are visible across sessions.\n */\n syntheticTasks?: InstructionTask[]\n addSyntheticTask: (syntheticTask: InstructionTask) => void\n removeSyntheticTask: (syntheticTask: InstructionTask) => void\n\n fieldRefs: FieldRef[]\n fieldRefsByTypePath: Record<string, FieldRef | undefined>\n}\n\nexport const AssistDocumentContext = createContext<AssistDocumentContextValue | undefined>(\n undefined,\n)\n\nexport function useAssistDocumentContext(): AssistDocumentContextValue {\n const context = useContext(AssistDocumentContext)\n if (!context) {\n throw new Error('AssistDocumentContext value is missing')\n }\n return context\n}\n","import {createContext, useContext} from 'react'\n\nimport {AssistPluginConfig} from '../plugin'\nimport {InstructStatus} from '../useApiClient'\nimport {SerializedSchemaType} from '../types'\nimport {FieldRef} from '../assistInspector/helpers'\n\nexport interface AiAssistanceConfigContextValue {\n config: AssistPluginConfig\n status?: InstructStatus\n statusLoading: boolean\n initLoading: boolean\n init: () => void\n error?: Error\n serializedTypes: SerializedSchemaType[]\n getFieldRefs: (documentType: string) => FieldRef[]\n getFieldRefsByTypePath: (documentType: string) => Record<string, FieldRef | undefined>\n}\n\nexport const AiAssistanceConfigContext = createContext<AiAssistanceConfigContextValue>({} as any)\n\nexport function useAiAssistanceConfig() {\n const context = useContext(AiAssistanceConfigContext)\n if (!context) {\n throw new Error('Missing AiAssistanceConfigContext')\n }\n return context\n}\n\nexport function useSerializedTypes() {\n return useAiAssistanceConfig().serializedTypes\n}\n","import type {SanityClient} from '@sanity/client'\nimport {useToast} from '@sanity/ui'\nimport {useCallback, useMemo, useState} from 'react'\nimport {Path, pathToString, useClient, useCurrentUser} from 'sanity'\n\nimport {useAiAssistanceConfig, useSerializedTypes} from './assistLayout/AiAssistanceConfigContext'\nimport {ConditionalMemberState} from './helpers/conditionalMembers'\nimport {FieldLanguageMap} from './translate/paths'\nimport {documentRootKey} from './types'\n\nexport interface UserTextInstance {\n blockKey: string\n userInput: string\n}\n\nexport interface RunInstructionRequest {\n documentId: string\n assistDocumentId: string\n path: string\n typePath?: string\n instructionKey: string\n userId?: string\n userTexts?: UserTextInstance[]\n conditionalMembers?: ConditionalMemberState[]\n}\n\nexport interface InstructStatus {\n enabled: boolean\n initialized: boolean\n validToken: boolean\n}\n\nexport interface TranslateRequest {\n documentId: string\n translatePath: Path\n languagePath?: string\n styleguide: () => Promise<string | undefined>\n fieldLanguageMap?: FieldLanguageMap[]\n conditionalMembers?: ConditionalMemberState[]\n}\n\nconst basePath = '/assist/tasks/instruction'\nexport const API_VERSION_WITH_EXTENDED_TYPES = '2025-04-01'\n\nexport function canUseAssist(status: InstructStatus | undefined) {\n return status?.enabled && status.initialized && status.validToken\n}\n\nexport function useApiClient(customApiClient?: (defaultClient: SanityClient) => SanityClient) {\n const client = useClient({apiVersion: API_VERSION_WITH_EXTENDED_TYPES})\n return useMemo(\n () => (customApiClient ? customApiClient(client) : client),\n [client, customApiClient],\n )\n}\n\nexport function useTranslate(apiClient: SanityClient) {\n const [loading, setLoading] = useState(false)\n const user = useCurrentUser()\n const types = useSerializedTypes()\n const toast = useToast()\n\n const translate = useCallback(\n ({\n documentId,\n languagePath,\n styleguide,\n translatePath,\n fieldLanguageMap,\n conditionalMembers,\n }: TranslateRequest) => {\n setLoading(true)\n\n async function run() {\n return apiClient.request({\n method: 'POST',\n url: `/assist/tasks/translate/${apiClient.config().dataset}?projectId=${\n apiClient.config().projectId\n }`,\n body: {\n documentId,\n types,\n languagePath,\n userStyleguide: await styleguide(),\n fieldLanguageMap,\n conditionalMembers,\n translatePath:\n translatePath.length === 0 ? documentRootKey : pathToString(translatePath),\n userId: user?.id,\n },\n })\n }\n\n return run()\n .catch((e) => {\n toast.push({\n status: 'error',\n title: 'Translate failed',\n description: e.message,\n })\n setLoading(false)\n throw e\n })\n .finally(() => {\n // adding some artificial delay here\n // server responds with 201 then proceeds; we dont need to allow spamming the button\n setTimeout(() => {\n setLoading(false)\n }, 2000)\n })\n },\n [setLoading, apiClient, toast, user, types],\n )\n\n return useMemo(\n () => ({\n translate,\n loading,\n }),\n [translate, loading],\n )\n}\n\nexport function useGenerateCaption(apiClient: SanityClient) {\n const [loading, setLoading] = useState(false)\n const user = useCurrentUser()\n const types = useSerializedTypes()\n const toast = useToast()\n\n const generateCaption = useCallback(\n ({path, documentId}: {path: string; documentId: string}) => {\n setLoading(true)\n\n return apiClient\n .request({\n method: 'POST',\n url: `/assist/tasks/generate-caption/${apiClient.config().dataset}?projectId=${\n apiClient.config().projectId\n }`,\n body: {\n path,\n documentId,\n types,\n userId: user?.id,\n },\n })\n .catch((e) => {\n toast.push({\n status: 'error',\n title: 'Generate image description failed',\n description: e.message,\n })\n setLoading(false)\n throw e\n })\n .finally(() => {\n // adding some artificial delay here\n // server responds with 201 then proceeds; we dont need to allow spamming the button\n setTimeout(() => {\n setLoading(false)\n }, 2000)\n })\n },\n [setLoading, apiClient, toast, user, types],\n )\n\n return useMemo(\n () => ({\n generateCaption,\n loading,\n }),\n [generateCaption, loading],\n )\n}\n\nexport function useGenerateImage(apiClient: SanityClient) {\n const [loading, setLoading] = useState(false)\n const user = useCurrentUser()\n const types = useSerializedTypes()\n const toast = useToast()\n\n const generateImage = useCallback(\n ({path, documentId}: {path: string; documentId: string}) => {\n setLoading(true)\n\n return apiClient\n .request({\n method: 'POST',\n url: `/assist/tasks/generate-image/${apiClient.config().dataset}?projectId=${\n apiClient.config().projectId\n }`,\n body: {\n path,\n documentId,\n types,\n userId: user?.id,\n },\n })\n .catch((e) => {\n toast.push({\n status: 'error',\n title: 'Generate image from prompt failed',\n description: e.message,\n })\n setLoading(false)\n throw e\n })\n .finally(() => {\n // adding some artificial delay here\n // server responds with 201 then proceeds; we dont need to allow spamming the button\n setTimeout(() => {\n setLoading(false)\n }, 2000)\n })\n },\n [setLoading, apiClient, toast, user, types],\n )\n\n return useMemo(\n () => ({\n generateImage,\n loading,\n }),\n [generateImage, loading],\n )\n}\n\nexport function useGetInstructStatus(apiClient: SanityClient) {\n const [loading, setLoading] = useState(true)\n\n const getInstructStatus = useCallback(async () => {\n setLoading(true)\n\n const projectId = apiClient.config().projectId\n try {\n const status = await apiClient.request<InstructStatus>({\n method: 'GET',\n url: `${basePath}/${apiClient.config().dataset}/status?projectId=${projectId}`,\n })\n\n return status\n } finally {\n setLoading(false)\n }\n }, [setLoading, apiClient])\n\n return {\n loading,\n getInstructStatus,\n }\n}\n\nexport function useInitInstruct(apiClient: SanityClient) {\n const [loading, setLoading] = useState(false)\n const initInstruct = useCallback(() => {\n setLoading(true)\n return apiClient\n .request({\n method: 'GET',\n url: `${basePath}/${apiClient.config().dataset}/init?projectId=${\n apiClient.config().projectId\n }`,\n })\n .finally(() => {\n setLoading(false)\n })\n }, [setLoading, apiClient])\n\n return {\n loading,\n initInstruct,\n }\n}\n\nexport function useRunInstructionApi(apiClient: SanityClient) {\n const toast = useToast()\n const [loading, setLoading] = useState(false)\n const user = useCurrentUser()\n const types = useSerializedTypes()\n\n const {\n config: {assist: assistConfig},\n } = useAiAssistanceConfig()\n\n const runInstruction = useCallback(\n (request: RunInstructionRequest) => {\n if (!user) {\n toast.push({\n status: 'error',\n title: 'Unable to get user for instruction.',\n })\n return undefined\n }\n setLoading(true)\n\n const {timeZone, locale} = Intl.DateTimeFormat().resolvedOptions()\n const defaultLocaleSettings = {timeZone, locale}\n const localeSettings =\n assistConfig?.localeSettings?.({user, defaultSettings: defaultLocaleSettings}) ??\n defaultLocaleSettings\n\n return apiClient\n .request({\n method: 'POST',\n url: `${basePath}/${apiClient.config().dataset}?projectId=${\n apiClient.config().projectId\n }`,\n body: {\n ...request,\n types,\n userId: user?.id,\n localeSettings,\n maxPathDepth: assistConfig?.maxPathDepth,\n },\n })\n .catch((e) => {\n toast.push({\n status: 'error',\n title: 'Instruction failed',\n description: e.message,\n })\n throw e