UNPKG

@sanity/assist

Version:

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

793 lines (743 loc) 24.7 kB
import {AgentActionPath} from '@sanity/client/stega' import {CurrentUser} from 'sanity' import {DocumentFieldActionDivider} from 'sanity' import {DocumentFieldActionGroup} from 'sanity' import {DocumentFieldActionItem} from 'sanity' import {JSX as JSX_2} from 'react/jsx-runtime' import {ObjectSchemaType} from 'sanity' import {Path} from 'sanity' import {Plugin as Plugin_2} from 'sanity' import {PortableTextBlock} from '@portabletext/types' import {PortableTextMarkDefinition} from '@portabletext/types' import {PortableTextSpan} from '@portabletext/types' import {SanityClient} from 'sanity' import type {SanityClient as SanityClient_2} from '@sanity/client' import {SanityDocumentLike} from 'sanity' import {SchemaType} from 'sanity' import {SchemaType as SchemaType_2} from '@sanity/types' declare interface AgentActionConditionalPath { path: AgentActionPath readOnly: boolean hidden: boolean } export declare const assist: Plugin_2<void | AssistPluginConfig> export declare interface AssistConfig { /** * As of v3.0 Assist can write to date and datetime fields. * * If this function is omitted from config, the plugin will use the default timeZone and locale * in the browser: * * ```ts * const {timeZone, locale} = Intl.DateTimeFormat().resolvedOptions() * ``` * * The function will be called any time an instruction runs. * * @see #LocaleSettings.locale * @see #LocaleSettings.timeZone * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#getcanonicalocales * @see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones */ localeSettings?: (context: LocaleSettingsContext) => LocaleSettings /** * The max depth for document paths AI Assist will write to. * * Depth is based on field path segments: * - `title` has depth 1 * - `array[_key="no"].title` has depth 3 * * Be careful not to set this too high in studios with recursive document schemas, as it could have * negative impact on performance. * * Depth will be counted from the field the instruction is run from. For example, if an instruction * is attached to depth 6, the count starts from there (at 0, not at 6). * * Default: 4 */ maxPathDepth?: number /** * Influences how much the output of an instruction will vary. * * Min: 0 – re-running an instruction will often produce the same outcomes * Max: 1 – re-running an instruction can produce wildly different outcomes * * This parameter applies to _all_ instructions in the studio. * * Prior to v3.0, this defaulted to 0 * * Default: 0.3 */ temperature?: number } export declare type AssistFieldActionGroup = Omit< DocumentFieldActionGroup, 'renderAsButton' | 'expanded' | 'children' > & { /** * `children` can include undefined entries in the action array. These will be filtered out. * If the group has no defined children, the group will also be filtered out. */ children: (AssistFieldActionNode | undefined)[] } export declare type AssistFieldActionItem = Omit< DocumentFieldActionItem, 'renderAsButton' | 'selected' | 'onAction' > & { onAction: () => void | Promise<void> } export declare type AssistFieldActionNode = | AssistFieldActionItem | AssistFieldActionGroup | DocumentFieldActionDivider export declare interface AssistFieldActionProps { /** * `actionType` will be `document` for action invoked from the top right document action menu, and * `field` when invoked from a field action menu. */ actionType: 'document' | 'field' /** * This is the id of the current document pane; it contains `drafts.`or `versions. prefix` ect depending on context. * Use this for `documentId` when calling any `client.agent.action`. * * It is generally recommended to call actions from the studio like this: * ```ts * await client.agent.action.generate({ * targetDocument: { * operation: 'createIfNotExists', * _id: props.documentIdForAction, * _type: props.documentSchemaType.name, * initialValues: props.getDocumentValue() * }, * //... * }) * ``` */ documentIdForAction: string /** * Schema type of the current document. * @see documentIdForAction */ documentSchemaType: ObjectSchemaType /** * Returns the current document value. * * Prefer passing this function to your hooks instead of passing the document value directly to avoid unnecessary re-renders. * @see documentIdForAction */ getDocumentValue: () => SanityDocumentLike /** * Returns the current readOnly and hidden state of all conditional members in the current document form. * * Intended to be passed to agent actions `conditionalPaths.paths`. */ getConditionalPaths: () => AgentActionConditionalPath[] /** * `schemaId` for the current workspace. * * Note: the workspace schema has to be deployed using `sanity schema deploy` or `sanity deploy`. * * Use this for `schemaId` when calling any `client.agent.action`. * * It is generally recommended to call actions from the studio like this: * ```ts * await client.agent.action.generate({ * targetDocument: { * operation: 'createIfNotExists', * _id: props.documentIdForAction, * _type: props.documentSchemaType.name, * initialValues: props.getDocumentValue() * }, * //... * }) * * ``` */ schemaId: string /** * This is the schema type of the field the actions will be attached to (ie, schemaType for `path`) * * It can be used with agent actions using `target.path`, to scope the action to a specific field. * * It is generally recommended to call actions from the studio like this: * ```ts * await client.agent.action.generate({ * targetDocument: { * operation: 'createIfNotExists', * _id: props.documentIdForAction, * _type: props.documentSchemaType.name, * initialValues: props.getDocumentValue() * }, * target: { * path: props.path * }, * }) * ``` */ path: AgentActionPath /** * This is the schema type of the field the actions will be attached to (ie, schemaType for `path`). * * Typically useful to dynamically return different actions based on the schema type of the field. * * ```ts * if(isObjectSchemaType(schemaType)) { * return [ * defineAssistFieldAction({ * title: 'Fill the object fields', * icon: RobotIcon, * onAction: () => { * //... * } * }) * ] * } * return useMemo(() => { * * * }, []) * * ``` */ schemaType: SchemaType_2 /** * Schema type of the parent field or array item holding this field. * * This can be undefined if the action was unable to resolve the parent type is excluded from AI Assist. * * @see schemaType * @see documentSchemaType */ parentSchemaType?: SchemaType_2 } export declare interface AssistOptions { aiAssist?: { /** Set to true to disable assistance for this field or type */ exclude?: boolean /** * Set to true to add translation field-action to the field. * Only has an effect in document types configured for document or field level translations. */ translateAction?: boolean } } declare interface AssistPluginConfig { translate?: TranslationConfig /** * Config that affects all instructions */ assist?: AssistConfig fieldActions?: { title?: string /** * The returned array can include `undefined` entries in the action array. These will be filtered out. */ useFieldActions?: (props: AssistFieldActionProps) => (AssistFieldActionNode | undefined)[] } /** * @internal */ __customApiClient?: (defaultClient: SanityClient_2) => SanityClient_2 /** * @internal */ __presets?: Record<string, AssistPreset> } declare interface AssistPreset { fields?: PresetField[] } declare interface ContextBlock { _type: typeof instructionContextTypeName reference?: { _type: 'reference' _ref?: string } } export declare const contextDocumentTypeName: 'assist.instruction.context' /** * */ export declare interface CustomInput { /** * Id for the input */ id: string /** * Title of the input field */ title: string /** * Additional info that will be displayed over the input */ description?: string } export declare type CustomInputResult = { /** * Identifies which custom input the `result`belongs to */ input: CustomInput /** * The text provided by the user in the input */ result: string } /** * Default implementation for plugin config `translate.field.translationOutputs` * * @see FieldTranslationConfig#translationOutputs */ export declare const defaultLanguageOutputs: TranslationOutputsFunction export declare function defineAssistFieldAction( action: Omit<AssistFieldActionItem, 'type'>, ): AssistFieldActionItem export declare function defineAssistFieldActionGroup( group: Omit<AssistFieldActionGroup, 'type'>, ): AssistFieldActionGroup export declare function defineFieldActionDivider(): DocumentFieldActionDivider export declare interface DocumentMember { schemaType: SchemaType path: Path name: string value: unknown } export declare interface DocumentTranslationConfig { /** * Path to language field in documents. Can be a hidden field. * For instance: 'config.language' * * For projects that use the `@sanity/document-internationalization` plugin, * this should be the same as `languageField` config for that plugin. * * Default: 'language' */ languageField: string /** * `documentTypes` should be an array of strings where each entry must match a name from your document schemas. * * If defined, this property will add a translate instruction to these document types. * If undefined, the instruction will be added to all documents with aiAssistance enabled and a field matching `documentLanguageField` config. * * Documents with translation support will get a "Translate document>" instruction added. **/ documentTypes?: string[] } declare interface FieldRef extends PortableTextMarkDefinition { _type: typeof fieldReferenceTypeName path?: string } declare const fieldReferenceTypeName: 'sanity.assist.instruction.fieldRef' export declare interface FieldTranslationConfig { /** * `documentTypes` should be an array of strings where each entry must match a name from your document schemas. * * If defined, matching document will get a "Translate fields" instruction added. **/ documentTypes?: string[] /** * * Used for display strings in the Studio, and to determine languages for field level translations * * If the studio is using the sanity-plugin-internationalized-array plugin, this * should be set to the same configuration. */ languages: Language[] | LanguageCallback /** * API version for client passed to LanguageCallback for languages * https://www.sanity.io/docs/api-versioning * @defaultValue '2022-11-27' */ apiVersion?: string /** * Specify fields that should be available in the languages callback: * ```tsx * { * select: { * markets: 'markets' * }, * languages: (client, {markets}) => * client.fetch('*[_type == "language" && market in $markets]{id,title}', {markets}) * } * ``` * * If the studio is using the sanity-plugin-internationalized-array plugin, this * should be set to the same configuration. */ selectLanguageParams?: Record<string, string> /** * `translationOutputs` is used when the "Translate fields" instruction is started by a Studio user. * * It determines the relationships between document paths: Given a document path and a language, into which * sibling paths should translations be output. * * `translationOutputs` is invoked once per path in the document (limited to a depth of 6), with the following: * * * `documentMember` - the field or array item for a given path; contains the path and its schemaType, * * `enclosingType` - the schema type of parent holding the member * * `translateFromLanguageId` - the languageId for the language the user want to translate from * * `translateToLanguageIds` - all languageIds the user can translate to * * The function should return a `TranslationOutput[]` array that contains all the paths where translations from * documentMember language (translateFromLanguageId) should be output. * * The function should return `undefined` for all documentMembers that should not be directly translated, * or are nested fields under a translated path. * * ## Default function * * The default function for `translationOutputs` is configured to be automatically compatible with sanity-plugin-internationalized-array * and object types prefixed with "locale". * * See <link to source for defaultTranslationOutputs> implementation details. * * ## Example * A document has the following document members: * * `{path: 'localeObject.en', schemaType: ObjectSchemaType}` * * `{path: 'localeObject.en.title', schemaType: StringSchemaType}` * * `{path: 'localeObject.de', schemaType: ObjectSchemaType}`, * * `{path: 'localeObject.de.title', schemaType: StringSchemaType}` * * `translationOutputs` for invoked with `translateFromLanguageId` `en`, * should only return [{id: 'de', outputPath: 'localeObject.de'}] for the `'localeObject.en'` path, * and undefined for all the other members. * * ### Example implementation * ```ts * function translationOutputs(member, enclosingType, translateFromLanguageId, translateToLanguageIds) * if (enclosingType.jsonType === 'object' && enclosingType.name.startsWith('locale') && translateFromLanguageId === member.name) { * return translateToLanguageIds.map((translateToId) => ({ * id: translateToId, * outputPath: [...member.path.slice(0, -1), translateToId], * })) * } * return undefined * } * ``` * * @see #maxPathDepth **/ translationOutputs?: TranslationOutputsFunction /** * The max depth for document paths AI Assist will translate. * * Depth is based on field path segments: * - `title` has depth 1 * - `array[_key="no"].title` has depth 3 * * Be careful not to set this too high in studios with recursive document schemas, as it could have * negative impact on performance. * * Default: 6 */ maxPathDepth?: number } export declare type GetUserInput = (args: { /** * Dialog title */ title: string /** * One titled input per array item */ inputs: CustomInput[] }) => Promise<CustomInputResult[] | undefined> declare type InlinePromptBlock = PortableTextSpan | FieldRef | UserInputBlock | ContextBlock declare const instructionContextTypeName: 'sanity.assist.instruction.context' /** * Returns true if the `schemaType` or any of its parent types (`schemaType.type`)` has `name` equal * to `typeName`. * * Useful for checking if `schemaType` is a type alias of `ìmage`, `code` or similar. */ export declare function isType(schemaType: SchemaType, typeName: string): boolean export declare interface Language { id: string title?: string } export declare type LanguageCallback = ( client: SanityClient, selectedLanguageParams: Record<string, unknown>, ) => Promise<Language[]> export declare interface LocaleSettings { /** * A valid Unicode BCP 47 locale identifier used to interpret and format * natural language inputs and date output. Examples include "en-US", "fr-FR", or "ja-JP". * * This affects how phrases like "next Friday" or "in two weeks" are parsed, * and how resulting dates are presented (e.g., 12-hour vs 24-hour format). * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#getcanonicalocales */ locale: string /** * A valid IANA time zone identifier used to resolve relative and absolute * date expressions to a specific point in time. Examples include * "America/New_York", "Europe/Paris", or "Asia/Tokyo". * * This ensures phrases like "tomorrow at 9am" are interpreted correctly * based on the user's local time. * * @see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones */ timeZone: string } export declare interface LocaleSettingsContext { user: CurrentUser defaultSettings: LocaleSettings } declare interface OutputFieldItem { _type: typeof outputFieldTypeName _key: string relativePath?: string } declare const outputFieldTypeName: 'sanity.assist.output.field' declare interface OutputTypeItem { _type: typeof outputTypeTypeName _key: string type?: string relativePath?: string } declare const outputTypeTypeName: 'sanity.assist.output.type' declare interface PresetField { path?: string instructions?: PresetInstruction[] } declare interface PresetInstruction { _key: string prompt?: PromptTextBlock[] title?: string /** * String key from `@sanity/icons` IconMap */ icon?: string /** * Type/field filter */ output?: (OutputFieldItem | OutputTypeItem)[] } declare type PromptTextBlock = Omit< PortableTextBlock<never, InlinePromptBlock, 'normal', never>, '_type' > & { _type: 'block' } export declare function SchemaTypeTool(): JSX_2.Element export declare type TranslateStyleguide = | string | ((context: TranslateStyleguideContext) => Promise<string>) export declare interface TranslateStyleguideContext { documentId: string schemaType: ObjectSchemaType client: SanityClient /** * Only provided for field translations */ translatePath?: Path } export declare interface TranslationConfig { /** * Config for document types with fields in multiple languages in the same document. */ field?: FieldTranslationConfig /** * Config for document types with a single language field that determines the language for the whole document. */ document?: DocumentTranslationConfig /** * A "style guide" that can be used to provide guidance on how to translate content. * Will be passed to the LLM - ergo this is only a guide and the model _may_ not * always follow it to the letter. * * When providing a function, consider caching the results of any async operation; it will invoked every time translate runs */ styleguide?: TranslateStyleguide } export declare interface TranslationOutput { /** Language id */ id: string outputPath: Path } export declare type TranslationOutputsFunction = ( documentMember: DocumentMember, enclosingType: SchemaType, translateFromLanguageId: string, translateToLanguageIds: string[], ) => TranslationOutput[] | undefined declare interface UserInputBlock { _type: typeof userInputTypeName _key: string message?: string description?: string } declare const userInputTypeName: 'sanity.assist.instruction.userInput' /** * `useUserInput` returns a function that can be used to await user input. * * Useful for custom `fieldActions` to get user input for populating Agent Action requests,. * * ```ts * fieldActions: { * useFieldActions: (props) => { * const { * documentSchemaType, * schemaId, * getDocumentValue, * getConditionalPaths, * documentIdForAction, * } = props * const client = useClient({apiVersion: 'vX'}) * const getUserInput = useUserInput() * return useMemo(() => { * return [ * defineAssistFieldAction({ * title: 'Log user input', * icon: UserIcon, * onAction: async () => { * const input = await getUserInput({ * title: 'Topic', * inputs: [{id: 'about', title: 'What should the article be about?'}], * }) * if (!input) return // user canceled input * await client.agent.action.generate({ * schemaId, * targetDocument: { * operation: 'createIfNotExists', * _id: documentIdForAction, * _type: documentSchemaType.name, * initialValues: getDocumentValue(), * }, * instruction: ` * Create a document about the following topic: * $about * --- * `, * instructionParams: {about: input[0].result}, * conditionalPaths: {paths: getConditionalPaths()}, * }) * }, * }), * ] * }, [ * client, * documentSchemaType, * schemaId, * getDocumentValue, * getConditionalPaths, * documentIdForAction, * getUserInput, * ]) * }, * } * ``` */ export declare function useUserInput(): GetUserInput export {} declare module 'sanity' { interface ArrayOptions extends AssistOptions {} interface BlockOptions extends AssistOptions {} interface BooleanOptions extends AssistOptions {} interface CrossDatasetReferenceOptions extends AssistOptions {} interface DateOptions extends AssistOptions {} interface DatetimeOptions extends AssistOptions {} interface DocumentOptions extends AssistOptions {} interface FileOptions extends AssistOptions {} interface GeopointOptions extends AssistOptions {} interface ImageOptions { aiAssist?: AssistOptions['aiAssist'] & { /** * When set, an image will be created whenever the `imageInstructionField` is written to by * an AI Assist instruction. * * The value output by AI Assist will be use as an image prompt for an generative image AI. * * This means that instructions directly for the field or instructions that visit the field when running, * will result in the image being changed. * * `imageInstructionField` must be a child-path relative to the image field, ie: * * field * * path.to.field * * ### Example * ```ts * defineType({ * type: 'image', * name: 'articleImage', * fields: [ * defineField({ * type: 'text', * name: 'imagePrompt', * title: 'Image prompt', * rows: 2, * }), * ], * options: { * aiAssist: { * imageInstructionField: 'imagePrompt', * } * }, * }) * ``` */ imageInstructionField?: string /** * When set, an image description will be automatically created for the image. * * `imageDescriptionField` must be a child-path relative to the image field, ie: * * field * * path.to.field * * Whenever the image asset for the field is changed in the Studio, * an image description is generated and set into the `imageDescriptionField`. * * ### Example * ```ts * defineType({ * type: 'image', * name: 'articleImage', * fields: [ * defineField({ * type: 'string', * name: 'altText', * title: 'Alt text', * }), * ], * options: { * aiAssist: { * imageDescriptionField: 'altText', * } * }, * }) * ``` */ imageDescriptionField?: | string | { path: string /** * When updateOnImageChange is true (or undefined), whenever the * image asset changes, imageDescriptionField will be regenerated. * * default: true * */ updateOnImageChange?: boolean } } } interface NumberOptions extends AssistOptions {} interface ObjectOptions extends AssistOptions {} interface ReferenceBaseOptions { aiAssist?: { /** Set to true to disable assistance for this field or type */ exclude?: boolean /** * When set, the reference field will allow instructions to be added to it. * Should be the name of the embeddings-index where assist will look for contextually relevant documents * */ embeddingsIndex?: string } } interface SlugOptions extends AssistOptions {} interface StringOptions extends AssistOptions {} interface TextOptions extends AssistOptions {} interface UrlOptions extends AssistOptions {} interface EmailOptions extends AssistOptions {} }