UNPKG

@lucidcms/core

Version:

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

1 lines 14.8 kB
{"version":3,"file":"custom-field-input-generate.mjs","names":["constants"],"sources":["../../../../src/services/ai/features/custom-field-input-generate.ts"],"sourcesContent":["import type { CustomFieldInputGenerateResponse } from \"@lucidcms/types\";\nimport constants from \"../../../constants/constants.js\";\nimport { copy } from \"../../../libs/i18n/index.js\";\nimport logger from \"../../../libs/logger/index.js\";\nimport type { CustomFieldInputV1Request } from \"../../../libs/lucid-remote/services/generate-cms-ai.js\";\nimport { generateCmsAi } from \"../../../libs/lucid-remote/services/index.js\";\nimport {\n\tgetCmsAiGenerateFailedMessage,\n\tisCmsAiGenerateAcceptedData,\n\tisCmsAiGenerateCompletedData,\n\tisCmsAiGenerateFailedData,\n} from \"../../../libs/lucid-remote/utils.js\";\nimport type { BrickInputSchema } from \"../../../schemas/collection-bricks.js\";\nimport type { FieldInputSchema } from \"../../../schemas/collection-fields.js\";\nimport type { CustomFieldAiContextItem } from \"../../../types.js\";\nimport { tenantAccessAllowed } from \"../../../utils/helpers/index.js\";\nimport type { ServiceFn } from \"../../../utils/services/types.js\";\nimport getLicenseKey from \"../../options/get-license-key.js\";\nimport checkFeatureEnabled from \"../checks/check-feature-enabled.js\";\nimport formatCustomFieldDocumentContext, {\n\tformatCustomFieldCollectionDefinition,\n\tgetTranslatedBrickDetails,\n} from \"../helpers/format-custom-field-document-context.js\";\nimport formatCustomFieldOutput from \"../helpers/format-custom-field-output.js\";\nimport getTranslatedCollectionDetails from \"../helpers/get-translated-collection-details.js\";\nimport getTranslatedFieldDetails from \"../helpers/get-translated-field-details.js\";\nimport normalizeCustomFieldGenerationLocale from \"../helpers/normalize-custom-field-generation-locale.js\";\nimport storeFailedGeneration from \"../storage/store-failed-generation.js\";\nimport storeGeneration from \"../storage/store-generation.js\";\n\nconst customFieldInputGenerate: ServiceFn<\n\t[\n\t\t{\n\t\t\tinstruction?: string;\n\t\t\tguidance?: string;\n\t\t\tvalue: Record<string, unknown>;\n\t\t\tdocument?: {\n\t\t\t\tfields?: FieldInputSchema[];\n\t\t\t\tbricks?: BrickInputSchema[];\n\t\t\t};\n\t\t\ttarget: {\n\t\t\t\tcollectionKey: string;\n\t\t\t\tbrickKey?: string;\n\t\t\t\tfieldKey: string;\n\t\t\t};\n\t\t\tlocale: {\n\t\t\t\tsource?: string;\n\t\t\t\ttarget: string[];\n\t\t\t};\n\t\t\tuserId: number;\n\t\t},\n\t],\n\tCustomFieldInputGenerateResponse\n> = async (context, props) => {\n\tconst featureEnabledRes = await checkFeatureEnabled(context, {\n\t\tfeature: \"customFieldGeneration\",\n\t});\n\tif (featureEnabledRes.error) return featureEnabledRes;\n\n\tconst requestStartedAt = Date.now();\n\tconst licenseKeyRes = await getLicenseKey(context);\n\tif (licenseKeyRes.error) return licenseKeyRes;\n\n\tconst collection = context.config.collections.find(\n\t\t(item) => item.key === props.target.collectionKey,\n\t);\n\tif (\n\t\t!collection ||\n\t\t!tenantAccessAllowed(collection.getData.tenants, context.request.tenantKey)\n\t) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 404,\n\t\t\t\tmessage: copy(\"server:core.collections.not.found.message\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tconst targetBrick = props.target.brickKey\n\t\t? collection.brickInstances.find(\n\t\t\t\t(brick) => brick.key === props.target.brickKey,\n\t\t\t)\n\t\t: undefined;\n\tif (\n\t\tprops.target.brickKey &&\n\t\t(!targetBrick ||\n\t\t\t!tenantAccessAllowed(\n\t\t\t\ttargetBrick.config.tenants,\n\t\t\t\tcontext.request.tenantKey,\n\t\t\t))\n\t) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 404,\n\t\t\t\tmessage: copy(\"server:core.ai.custom.field.input.target.not.found\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tconst fieldSource = targetBrick ?? collection;\n\n\tconst targetField = fieldSource.fields.get(props.target.fieldKey);\n\n\tif (!targetField) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 404,\n\t\t\t\tmessage: copy(\"server:core.ai.custom.field.input.target.not.found\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tif (!targetField.aiConfig.enabled) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 400,\n\t\t\t\tmessage: copy(\"server:core.ai.custom.field.input.no.supported.field\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tconst fieldIsLocalized =\n\t\tcollection.getData.localized === true &&\n\t\ttargetField.localizedEnabled === true;\n\n\tconst generationContext = normalizeCustomFieldGenerationLocale({\n\t\tdefaultLocale: context.config.localization.defaultLocale,\n\t\tfieldIsLocalized,\n\t\tlocale: props.locale,\n\t\tvalue: props.value,\n\t});\n\n\tconst input: CustomFieldInputV1Request[\"input\"] = [];\n\tif (props.instruction?.trim()) {\n\t\tinput.push({\n\t\t\ttype: \"text\",\n\t\t\trole: \"user-instruction\",\n\t\t\tvalue: props.instruction,\n\t\t});\n\t}\n\tif (targetField.aiConfig.instructions?.trim()) {\n\t\tinput.push({\n\t\t\ttype: \"text\",\n\t\t\trole: \"field-instruction\",\n\t\t\tvalue: targetField.aiConfig.instructions,\n\t\t});\n\t}\n\tif (props.guidance) {\n\t\tconst guidance = targetField.aiConfig.guidance.find(\n\t\t\t(item) => item.key === props.guidance,\n\t\t);\n\t\tif (!guidance) {\n\t\t\treturn {\n\t\t\t\terror: {\n\t\t\t\t\ttype: \"basic\",\n\t\t\t\t\tstatus: 400,\n\t\t\t\t\tmessage: copy(\"server:core.ai.custom.field.input.guidance.not.found\"),\n\t\t\t\t},\n\t\t\t\tdata: undefined,\n\t\t\t};\n\t\t}\n\n\t\tinput.push({\n\t\t\ttype: \"text\",\n\t\t\trole: \"guidance\",\n\t\t\tvalue: guidance.instructions,\n\t\t});\n\t}\n\n\tlet contextItems: CustomFieldAiContextItem[] = [];\n\tif (targetField.aiConfig.context) {\n\t\ttry {\n\t\t\tcontextItems = await targetField.aiConfig.context({\n\t\t\t\tcollection,\n\t\t\t\tbrick: targetBrick,\n\t\t\t\tfield: targetField,\n\t\t\t\tlocale: generationContext.locale,\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tlogger.error({\n\t\t\t\terror: err,\n\t\t\t\tevent: \"ai.field-context.failed\",\n\t\t\t\tscope: constants.logScopes.ai,\n\t\t\t\tmessage: \"Failed to resolve AI field context\",\n\t\t\t\tdata: {\n\t\t\t\t\terrorMessage: err instanceof Error ? err.message : String(err),\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\tconst collectionDetails = getTranslatedCollectionDetails(context, collection);\n\tconst targetFieldDetails = getTranslatedFieldDetails(context, targetField);\n\tconst targetBrickDetails = targetBrick\n\t\t? getTranslatedBrickDetails(context, targetBrick)\n\t\t: undefined;\n\tconst collectionDefinition = formatCustomFieldCollectionDefinition({\n\t\tcontext,\n\t\tcollection,\n\t});\n\n\tconst request: CustomFieldInputV1Request = {\n\t\tfeature: {\n\t\t\tkey: \"custom-field.input.generate\",\n\t\t\tversion: \"v1\",\n\t\t},\n\t\tinput,\n\t\toutputSchema: targetField.jsonSchema,\n\t\tcontext: {\n\t\t\tlocale: generationContext.locale,\n\t\t\ttarget: {\n\t\t\t\tcollection: {\n\t\t\t\t\tkey: props.target.collectionKey,\n\t\t\t\t\t...(collectionDetails ?? {}),\n\t\t\t\t},\n\t\t\t\t...(targetBrick\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tbrick: {\n\t\t\t\t\t\t\t\tkey: targetBrick.key,\n\t\t\t\t\t\t\t\t...(targetBrickDetails ?? {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\tfield: {\n\t\t\t\t\tkey: targetField.key,\n\t\t\t\t\ttype: targetField.type,\n\t\t\t\t\t...(targetFieldDetails ?? {}),\n\t\t\t\t\tvalue: generationContext.value,\n\t\t\t\t},\n\t\t\t},\n\t\t\tcollection: {\n\t\t\t\tkey: props.target.collectionKey,\n\t\t\t\t...(collectionDetails ?? {}),\n\t\t\t\t...collectionDefinition,\n\t\t\t},\n\t\t\titems: contextItems,\n\t\t\tdocument: formatCustomFieldDocumentContext({\n\t\t\t\tcontext,\n\t\t\t\tcollection,\n\t\t\t\tdocument: props.document,\n\t\t\t}),\n\t\t},\n\t};\n\n\tconst generateRes = await generateCmsAi(context, {\n\t\tlicenseKey: licenseKeyRes.data,\n\t\trequest,\n\t});\n\tif (generateRes.error) return generateRes;\n\tconst responseData = generateRes.data.json.data;\n\n\t//* This feature is sync-mode only; this guard is proofing against a remote contract mismatch and should never run.\n\tconst responseDataFailed = isCmsAiGenerateFailedData(responseData);\n\tif (isCmsAiGenerateAcceptedData(responseData) || responseDataFailed) {\n\t\tconst errorMessage = responseDataFailed\n\t\t\t? getCmsAiGenerateFailedMessage(\n\t\t\t\t\tresponseData,\n\t\t\t\t\tcontext.translate(\"server:core.routes.ai.generate.error.message\"),\n\t\t\t\t)\n\t\t\t: context.translate(\"server:core.routes.ai.generate.error.message\");\n\n\t\tconst storeRes = await storeFailedGeneration(context, {\n\t\t\tuserId: props.userId,\n\t\t\trequestId: responseData.requestId,\n\t\t\tfeature: responseData.feature,\n\t\t\ttargetType: \"custom-field\",\n\t\t\ttarget: {\n\t\t\t\tcollectionKey: props.target.collectionKey,\n\t\t\t\tbrickKey: props.target.brickKey,\n\t\t\t\tfieldKey: props.target.fieldKey,\n\t\t\t\tguidance: props.guidance ?? null,\n\t\t\t\tlocale: props.locale,\n\t\t\t},\n\t\t\trequestStartedAt,\n\t\t\terrorMessage,\n\t\t\tusage: responseDataFailed ? responseData.usage : undefined,\n\t\t});\n\t\tif (storeRes.error) return storeRes;\n\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 502,\n\t\t\t\tmessage: responseDataFailed\n\t\t\t\t\t? copy.literal(errorMessage)\n\t\t\t\t\t: copy(\"server:core.routes.ai.generate.error.message\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tif (!isCmsAiGenerateCompletedData(responseData)) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tstatus: 502,\n\t\t\t\tmessage: copy(\"server:core.routes.ai.generate.error.message\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\tconst responseForFormatting =\n\t\tresponseData as CustomFieldInputGenerateResponse;\n\n\tconst formattedOutputRes = formatCustomFieldOutput({\n\t\tfield: targetField,\n\t\toutput: responseForFormatting.output,\n\t});\n\n\tconst responseForStorage: CustomFieldInputGenerateResponse =\n\t\tformattedOutputRes.error === undefined\n\t\t\t? {\n\t\t\t\t\t...responseForFormatting,\n\t\t\t\t\toutput: formattedOutputRes.data,\n\t\t\t\t}\n\t\t\t: responseForFormatting;\n\tconst formattingErrorMessage =\n\t\tformattedOutputRes.error === undefined\n\t\t\t? null\n\t\t\t: context.translate(formattedOutputRes.error.message);\n\n\tconst storeRes = await storeGeneration(context, {\n\t\tuserId: props.userId,\n\t\tresponse: responseForStorage,\n\t\ttargetType: \"custom-field\",\n\t\trequestStartedAt,\n\t\tstatus: formattedOutputRes.error === undefined ? \"success\" : \"failed\",\n\t\terrorMessage: formattingErrorMessage,\n\t\ttarget: {\n\t\t\tcollectionKey: props.target.collectionKey,\n\t\t\tbrickKey: props.target.brickKey,\n\t\t\tfieldKey: props.target.fieldKey,\n\t\t\tguidance: props.guidance ?? null,\n\t\t\tlocale: props.locale,\n\t\t},\n\t});\n\tif (storeRes.error) return storeRes;\n\tif (formattedOutputRes.error) return formattedOutputRes;\n\n\treturn {\n\t\terror: undefined,\n\t\tdata: responseForStorage,\n\t};\n};\n\nexport default customFieldInputGenerate;\n"],"mappings":"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"}