UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 15.6 kB
{"version":3,"file":"format-custom-field-document-context.mjs","names":[],"sources":["../../../../src/services/ai/helpers/format-custom-field-document-context.ts"],"sourcesContent":["import type { RichTextJSON } from \"@lucidcms/rich-text\";\nimport { generateHTML } from \"@lucidcms/rich-text/server\";\nimport type BrickBuilder from \"../../../libs/collection/builders/brick-builder/index.js\";\nimport type CollectionBuilder from \"../../../libs/collection/builders/collection-builder/index.js\";\nimport type CustomField from \"../../../libs/collection/custom-fields/custom-field.js\";\nimport registeredFields from \"../../../libs/collection/custom-fields/registered-fields.js\";\nimport { isStorageMode } from \"../../../libs/collection/custom-fields/storage/index.js\";\nimport type {\n\tCFConfig,\n\tFieldTypes,\n} from \"../../../libs/collection/custom-fields/types.js\";\nimport type { BrickInputSchema } from \"../../../schemas/collection-bricks.js\";\nimport type { FieldInputSchema } from \"../../../schemas/collection-fields.js\";\nimport { tenantAccessAllowed } from \"../../../utils/helpers/index.js\";\nimport type { ServiceContext } from \"../../../utils/services/types.js\";\nimport getTranslatedFieldDetails from \"./get-translated-field-details.js\";\n\ntype DocumentInput = {\n\tfields?: FieldInputSchema[];\n\tbricks?: BrickInputSchema[];\n};\n\ntype FormatProps = {\n\tcontext: ServiceContext;\n\tcollection: CollectionBuilder;\n\tdocument?: DocumentInput;\n};\n\ntype DocumentContextFieldInput = FieldInputSchema;\n\ntype DocumentContextFields = Record<string, unknown>;\n\ntype DocumentContext = {\n\tfields: DocumentContextFields;\n\tbricks: Array<{\n\t\tkey: string;\n\t\ttype: \"builder\" | \"fixed\";\n\t\tfields: DocumentContextFields;\n\t}>;\n};\n\ntype DefinitionField = {\n\ttype: FieldTypes;\n\tlabel?: string;\n\tsummary?: string;\n\tlocalized: boolean;\n\tcollection?: string | string[];\n\tmultiple?: boolean;\n\tthumbs?: 1 | 2;\n\tmin?: number;\n\tmax?: number;\n\tstep?: number;\n\toptions?: Array<{\n\t\tvalue: string;\n\t\tlabel?: string;\n\t}>;\n\tfields?: Record<string, DefinitionField>;\n};\n\ntype DefinitionBrick = {\n\tname?: string;\n\tsummary?: string;\n\tfields: Record<string, DefinitionField>;\n};\n\ntype CollectionDefinition = {\n\tfields: Record<string, DefinitionField>;\n\tfixedBricks: Record<string, DefinitionBrick>;\n\tbuilderBricks: Record<string, DefinitionBrick>;\n};\n\n// TODO: should belong on the CF\nconst formatRichTextValue = (value: unknown) => {\n\tif (!value || typeof value !== \"object\") return value;\n\n\ttry {\n\t\treturn generateHTML(value as RichTextJSON);\n\t} catch {\n\t\treturn value;\n\t}\n};\n\nconst formatFieldValue = (\n\tfieldInstance: CustomField<FieldTypes>,\n\tvalue: unknown,\n) => {\n\tif (fieldInstance.type === \"rich-text\") {\n\t\treturn formatRichTextValue(value);\n\t}\n\n\treturn value;\n};\n\nconst formatFieldTranslations = (\n\tfieldInstance: CustomField<FieldTypes>,\n\ttranslations: Record<string, unknown>,\n) => {\n\tif (fieldInstance.type !== \"rich-text\") return translations;\n\n\treturn Object.fromEntries(\n\t\tObject.entries(translations).map(([locale, value]) => [\n\t\t\tlocale,\n\t\t\tformatRichTextValue(value),\n\t\t]),\n\t);\n};\n\n/**\n * Formats nested tree-table groups as ordered value objects.\n */\nconst getTreeFieldGroups = (\n\tfield: DocumentContextFieldInput,\n\tfieldInstances: Map<string, CustomField<FieldTypes>>,\n) => {\n\tconst groups =\n\t\tfield.groups\n\t\t\t?.toSorted((a, b) => (a.order ?? 0) - (b.order ?? 0))\n\t\t\t.map((group) => formatFields(group.fields, fieldInstances)) ?? [];\n\n\tif (groups.length === 0) return undefined;\n\n\treturn groups;\n};\n\n/**\n * Formats request document fields into a key-value prompt context object.\n */\nconst formatFields = (\n\tfields: DocumentContextFieldInput[] | undefined,\n\tfieldInstances: Map<string, CustomField<FieldTypes>>,\n): DocumentContextFields => {\n\tif (!fields?.length) return {};\n\n\treturn fields.reduce<DocumentContextFields>((acc, field) => {\n\t\tconst fieldInstance = fieldInstances.get(field.key);\n\t\tif (!fieldInstance) return acc;\n\t\tif (field.type !== fieldInstance.type) return acc;\n\n\t\tconst databaseConfig = registeredFields[fieldInstance.type].config.database;\n\t\tconst groups = isStorageMode(databaseConfig, \"tree-table\")\n\t\t\t? getTreeFieldGroups(field, fieldInstances)\n\t\t\t: undefined;\n\n\t\tif (groups !== undefined) {\n\t\t\tacc[fieldInstance.key] = groups;\n\t\t\treturn acc;\n\t\t}\n\n\t\tif (field.translations !== undefined) {\n\t\t\tacc[fieldInstance.key] = formatFieldTranslations(\n\t\t\t\tfieldInstance,\n\t\t\t\tfield.translations,\n\t\t\t);\n\t\t\treturn acc;\n\t\t}\n\n\t\tif (field.value !== undefined) {\n\t\t\tacc[fieldInstance.key] = formatFieldValue(fieldInstance, field.value);\n\t\t}\n\n\t\treturn acc;\n\t}, {});\n};\n\n/**\n * Finds the collection brick instance that owns a request brick.\n */\nconst getBrickInstance = (\n\tcollection: CollectionBuilder,\n\tbrick: BrickInputSchema,\n): BrickBuilder | undefined => {\n\treturn collection.brickInstances.find((instance) => {\n\t\treturn instance.key === brick.key;\n\t});\n};\n\n/**\n * Returns nested field config for fields that carry child fields.\n */\nconst getNestedFieldConfig = (\n\tfield: CFConfig<FieldTypes>,\n): CFConfig<FieldTypes>[] | undefined => {\n\tif (!(\"fields\" in field)) return undefined;\n\tif (!Array.isArray(field.fields)) return undefined;\n\treturn field.fields as CFConfig<FieldTypes>[];\n};\n\n/**\n * Translates select options for field definitions.\n */\nconst getOptionsDefinition = (\n\tcontext: ServiceContext,\n\tfield: CFConfig<FieldTypes>,\n) => {\n\tif (!(\"options\" in field)) return undefined;\n\tif (!Array.isArray(field.options)) return undefined;\n\n\tconst translate = context.translate.forLocale(\n\t\tcontext.config.i18n.defaultLocale,\n\t);\n\tconst options = field.options.flatMap((option) => {\n\t\tif (typeof option.value !== \"string\") return [];\n\n\t\tconst label = translate(option.label);\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tvalue: option.value,\n\t\t\t\t...(label ? { label } : {}),\n\t\t\t},\n\t\t];\n\t});\n\n\tif (options.length === 0) return undefined;\n\n\treturn options;\n};\n\n/**\n * Returns relation collection targets for field definitions.\n */\nconst getCollectionTargets = (field: CFConfig<FieldTypes>) => {\n\tif (!(\"collection\" in field)) return undefined;\n\treturn field.collection;\n};\n\n/**\n * Returns relation multiplicity for field definitions.\n */\nconst getMultiple = (field: CFConfig<FieldTypes>) => {\n\tif (!(\"multiple\" in field)) return undefined;\n\tif (typeof field.multiple !== \"boolean\") return undefined;\n\n\treturn field.multiple;\n};\n\n/** Returns the numeric constraints that describe a range field. */\nconst getRangeConstraints = (field: CFConfig<FieldTypes>) => {\n\tif (field.type !== \"range\") return undefined;\n\n\treturn {\n\t\tmin: field.min,\n\t\tmax: field.max,\n\t\tstep: field.step,\n\t\t...(field.thumbs ? { thumbs: field.thumbs } : {}),\n\t};\n};\n\n/**\n * Formats collection field definitions into a key-based lookup.\n */\nconst formatFieldDefinitions = (props: {\n\tcontext: ServiceContext;\n\tcollection: CollectionBuilder;\n\tfields: CFConfig<FieldTypes>[];\n\tinstances: Map<string, CustomField<FieldTypes>>;\n}): Record<string, DefinitionField> => {\n\treturn props.fields.reduce<Record<string, DefinitionField>>((acc, field) => {\n\t\tconst fieldInstance = props.instances.get(field.key);\n\t\tif (!fieldInstance) return acc;\n\t\tif (field.type !== fieldInstance.type) return acc;\n\n\t\tconst nestedFields = getNestedFieldConfig(field);\n\t\tconst details = getTranslatedFieldDetails(props.context, fieldInstance);\n\t\tconst collection = getCollectionTargets(field);\n\t\tconst multiple = getMultiple(field);\n\t\tconst rangeConstraints = getRangeConstraints(field);\n\t\tconst options = getOptionsDefinition(props.context, field);\n\n\t\tacc[fieldInstance.key] = {\n\t\t\ttype: fieldInstance.type,\n\t\t\tlocalized:\n\t\t\t\tprops.collection.getData.localized === true &&\n\t\t\t\tfieldInstance.localizedEnabled === true,\n\t\t\t...(details?.label ? { label: details.label } : {}),\n\t\t\t...(details?.summary ? { summary: details.summary } : {}),\n\t\t\t...(collection ? { collection } : {}),\n\t\t\t...(multiple !== undefined ? { multiple } : {}),\n\t\t\t...(rangeConstraints ?? {}),\n\t\t\t...(options ? { options } : {}),\n\t\t\t...(nestedFields\n\t\t\t\t? {\n\t\t\t\t\t\tfields: formatFieldDefinitions({\n\t\t\t\t\t\t\tcontext: props.context,\n\t\t\t\t\t\t\tcollection: props.collection,\n\t\t\t\t\t\t\tfields: nestedFields,\n\t\t\t\t\t\t\tinstances: props.instances,\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t};\n\n\t\treturn acc;\n\t}, {});\n};\n\n/**\n * Translates brick details for collection definitions.\n */\nexport const getTranslatedBrickDetails = (\n\tcontext: ServiceContext,\n\tbrick: BrickBuilder,\n) => {\n\tconst translate = context.translate.forLocale(\n\t\tcontext.config.i18n.defaultLocale,\n\t);\n\tconst name = translate(brick.config.details.name);\n\tconst summary = translate(brick.config.details.summary);\n\n\tif (!name && !summary) return undefined;\n\n\treturn {\n\t\t...(name ? { name } : {}),\n\t\t...(summary ? { summary } : {}),\n\t};\n};\n\n/**\n * Formats brick definitions into a key-based lookup.\n */\nconst formatBrickDefinitions = (props: {\n\tcontext: ServiceContext;\n\tcollection: CollectionBuilder;\n\tbricks?: BrickBuilder[];\n}): Record<string, DefinitionBrick> => {\n\treturn (props.bricks ?? [])\n\t\t.filter((brick) =>\n\t\t\ttenantAccessAllowed(\n\t\t\t\tbrick.config.tenants,\n\t\t\t\tprops.context.request.tenantKey,\n\t\t\t),\n\t\t)\n\t\t.reduce<Record<string, DefinitionBrick>>((acc, brick) => {\n\t\t\tconst details = getTranslatedBrickDetails(props.context, brick);\n\n\t\t\tacc[brick.key] = {\n\t\t\t\t...(details ?? {}),\n\t\t\t\tfields: formatFieldDefinitions({\n\t\t\t\t\tcontext: props.context,\n\t\t\t\t\tcollection: props.collection,\n\t\t\t\t\tfields: brick.fieldTree,\n\t\t\t\t\tinstances: brick.fields,\n\t\t\t\t}),\n\t\t\t};\n\n\t\t\treturn acc;\n\t\t}, {});\n};\n\n/**\n * Builds the collection definition sent to the remote custom-field AI request.\n */\nexport const formatCustomFieldCollectionDefinition = (props: {\n\tcontext: ServiceContext;\n\tcollection: CollectionBuilder;\n}): CollectionDefinition => {\n\treturn {\n\t\tfields: formatFieldDefinitions({\n\t\t\tcontext: props.context,\n\t\t\tcollection: props.collection,\n\t\t\tfields: props.collection.fieldTree,\n\t\t\tinstances: props.collection.fields,\n\t\t}),\n\t\tfixedBricks: formatBrickDefinitions({\n\t\t\tcontext: props.context,\n\t\t\tcollection: props.collection,\n\t\t\tbricks: props.collection.config.bricks?.fixed,\n\t\t}),\n\t\tbuilderBricks: formatBrickDefinitions({\n\t\t\tcontext: props.context,\n\t\t\tcollection: props.collection,\n\t\t\tbricks: props.collection.config.bricks?.builder,\n\t\t}),\n\t};\n};\n\n/**\n * Builds the document context sent to the remote custom-field AI request.\n */\nconst formatCustomFieldDocumentContext = (\n\tprops: FormatProps,\n): DocumentContext => {\n\treturn {\n\t\tfields: formatFields(props.document?.fields, props.collection.fields),\n\t\tbricks:\n\t\t\tprops.document?.bricks?.flatMap((brick) => {\n\t\t\t\tconst brickInstance = getBrickInstance(props.collection, brick);\n\t\t\t\tif (!brickInstance) return [];\n\t\t\t\tif (\n\t\t\t\t\t!tenantAccessAllowed(\n\t\t\t\t\t\tbrickInstance.config.tenants,\n\t\t\t\t\t\tprops.context.request.tenantKey,\n\t\t\t\t\t)\n\t\t\t\t) {\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\n\t\t\t\treturn [\n\t\t\t\t\t{\n\t\t\t\t\t\tkey: brick.key,\n\t\t\t\t\t\ttype: brick.type,\n\t\t\t\t\t\tfields: formatFields(brick.fields, brickInstance.fields),\n\t\t\t\t\t},\n\t\t\t\t];\n\t\t\t}) ?? [],\n\t};\n};\n\nexport default formatCustomFieldDocumentContext;\n"],"mappings":"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"}