@sanity/assist
Version:
You create the instructions; Sanity AI Assist does the rest.
793 lines (743 loc) • 24.7 kB
TypeScript
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 {}
}