UNPKG

@lucidcms/core

Version:

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

1 lines 9.84 kB
{"version":3,"file":"media-image-generate.mjs","names":[],"sources":["../../../../src/services/ai/features/media-image-generate.ts"],"sourcesContent":["import type { MediaImageGenerateResponse } from \"@lucidcms/types\";\nimport { copy } from \"../../../libs/i18n/index.js\";\nimport type { MediaImageGenerateV1Request } 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 { ServiceFn } from \"../../../utils/services/types.js\";\nimport getLicenseKey from \"../../options/get-license-key.js\";\nimport checkFeatureEnabled from \"../checks/check-feature-enabled.js\";\nimport storeFailedGeneration from \"../storage/store-failed-generation.js\";\nimport storeGeneration from \"../storage/store-generation.js\";\nimport storePendingGeneration from \"../storage/store-pending-generation.js\";\n\nconst imageGuidanceInstructions = {\n\tnatural:\n\t\t\"Create a realistic photograph with natural light, believable texture, accurate perspective, and minimal stylization.\",\n\tlifestyle:\n\t\t\"Create a polished lifestyle image suited to a brand story or magazine article, with a real-world setting, composed framing, and natural lighting.\",\n\tproduct:\n\t\t\"Create a clean studio product image with crisp detail, controlled lighting, accurate materials, and minimal background distractions.\",\n\tillustration:\n\t\t\"Create a polished non-photographic illustration with clear shapes, cohesive colors, intentional texture, and no photorealistic camera effects.\",\n\tcinematic:\n\t\t\"Create a story-led cinematic image with dramatic composition, atmospheric lighting, depth, and a believable mood.\",\n} as const;\n\nconst mediaImageGenerate: ServiceFn<\n\t[\n\t\t{\n\t\t\tinstruction?: string;\n\t\t\tguidance?: string;\n\t\t\timage?: Extract<\n\t\t\t\tMediaImageGenerateV1Request[\"input\"][number],\n\t\t\t\t{ type: \"image\" }\n\t\t\t>[\"image\"];\n\t\t\tpreviousInstructions?: string[];\n\t\t\tgeneration: MediaImageGenerateV1Request[\"generation\"];\n\t\t\tidempotencyKey?: string;\n\t\t\tuserId: number;\n\t\t},\n\t],\n\tMediaImageGenerateResponse\n> = async (context, props) => {\n\tconst featureEnabledRes = await checkFeatureEnabled(context, {\n\t\tfeature: \"imageGeneration\",\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 idempotencyKey = props.idempotencyKey?.trim();\n\tif (!idempotencyKey) {\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(\n\t\t\t\t\t\"server:core.ai.media.image.generate.idempotency.key.required\",\n\t\t\t\t),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tconst input: MediaImageGenerateV1Request[\"input\"] = [];\n\tconst instruction = props.instruction?.trim();\n\tconst guidance = props.guidance\n\t\t? imageGuidanceInstructions[\n\t\t\t\tprops.guidance as keyof typeof imageGuidanceInstructions\n\t\t\t]\n\t\t: undefined;\n\n\tif (props.guidance && !guidance) {\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.guidance.not.found\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tif (!instruction && (!props.guidance || !props.image)) {\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(\n\t\t\t\t\t\"server:core.ai.media.image.generate.instruction.or.guidance.required\",\n\t\t\t\t),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tif (instruction) {\n\t\tinput.push({\n\t\t\ttype: \"text\",\n\t\t\trole: \"user-instruction\",\n\t\t\tvalue: instruction,\n\t\t});\n\t}\n\n\tif (guidance) {\n\t\tinput.push({\n\t\t\ttype: \"text\",\n\t\t\trole: \"guidance\",\n\t\t\tvalue: guidance,\n\t\t});\n\t}\n\n\tif (props.image) {\n\t\tinput.push({\n\t\t\ttype: \"image\",\n\t\t\trole: \"source-image\",\n\t\t\timage: props.image,\n\t\t});\n\t}\n\n\tconst request: MediaImageGenerateV1Request = {\n\t\tfeature: {\n\t\t\tkey: \"media.image.generate\",\n\t\t\tversion: \"v1\",\n\t\t},\n\t\tinput,\n\t\tcontext: {\n\t\t\tpreviousInstructions:\n\t\t\t\tprops.previousInstructions && props.previousInstructions.length > 0\n\t\t\t\t\t? props.previousInstructions\n\t\t\t\t\t: undefined,\n\t\t},\n\t\tgeneration: props.generation,\n\t};\n\n\tconst generateRes = await generateCmsAi(context, {\n\t\tlicenseKey: licenseKeyRes.data,\n\t\trequest,\n\t\tidempotencyKey,\n\t});\n\tif (generateRes.error) return generateRes;\n\tconst responseData = generateRes.data.json.data;\n\n\tconst target = {\n\t\tgeneration: props.generation,\n\t\tguidance: props.guidance ?? null,\n\t\tpreviousInstructions: props.previousInstructions ?? [],\n\t\tsourceImage: props.image\n\t\t\t? {\n\t\t\t\t\ttype: props.image.type,\n\t\t\t\t\tdetail: props.image.detail ?? null,\n\t\t\t\t\tfilename: props.image.filename ?? null,\n\t\t\t\t\tmimeType: props.image.mimeType ?? null,\n\t\t\t\t}\n\t\t\t: null,\n\t};\n\n\tif (isCmsAiGenerateAcceptedData(responseData)) {\n\t\tconst storeRes = await storePendingGeneration(context, {\n\t\t\tuserId: props.userId,\n\t\t\trequestId: responseData.requestId,\n\t\t\tfeature: {\n\t\t\t\tkey: \"media.image.generate\",\n\t\t\t\tversion: \"v1\",\n\t\t\t},\n\t\t\ttargetType: \"media-image\",\n\t\t\ttarget,\n\t\t});\n\t\tif (storeRes.error) return storeRes;\n\n\t\treturn {\n\t\t\terror: undefined,\n\t\t\tdata: {\n\t\t\t\tmode: \"async\",\n\t\t\t\trequestId: responseData.requestId,\n\t\t\t\tfeature: {\n\t\t\t\t\tkey: \"media.image.generate\",\n\t\t\t\t\tversion: \"v1\",\n\t\t\t\t},\n\t\t\t\tstatus: responseData.status,\n\t\t\t},\n\t\t};\n\t}\n\n\tif (isCmsAiGenerateFailedData(responseData)) {\n\t\tconst errorMessage = getCmsAiGenerateFailedMessage(\n\t\t\tresponseData,\n\t\t\tcontext.translate(\"server:core.routes.ai.generate.error.message\"),\n\t\t);\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: \"media-image\",\n\t\t\ttarget,\n\t\t\trequestStartedAt,\n\t\t\terrorMessage,\n\t\t\tusage: responseData.usage,\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: copy.literal(errorMessage),\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\n\tconst storeRes = await storeGeneration(context, {\n\t\tuserId: props.userId,\n\t\tresponse: responseData,\n\t\ttargetType: \"media-image\",\n\t\trequestStartedAt,\n\t\ttarget,\n\t});\n\tif (storeRes.error) return storeRes;\n\n\treturn {\n\t\terror: undefined,\n\t\tdata: {\n\t\t\tmode: \"async\",\n\t\t\trequestId: responseData.requestId,\n\t\t\tfeature: {\n\t\t\t\tkey: \"media.image.generate\",\n\t\t\t\tversion: \"v1\",\n\t\t\t},\n\t\t\tstatus: \"processing\",\n\t\t},\n\t};\n};\n\nexport default mediaImageGenerate;\n"],"mappings":"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"}