{"version":3,"file":"media-alt-generate.mjs","names":[],"sources":["../../../../src/services/ai/features/media-alt-generate.ts"],"sourcesContent":["import type { MediaAltGenerateResponse } from \"@lucidcms/types\";\nimport z from \"zod\";\nimport { copy } from \"../../../libs/i18n/index.js\";\nimport type { MediaAltGenerateV1Request } 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\";\n\nconst mediaAltOutputSchema = z.record(z.string(), z.string());\n\nconst mediaAltGenerate: ServiceFn<\n\t[\n\t\t{\n\t\t\tinstruction?: string;\n\t\t\tpreviousResponses?: {\n\t\t\t\tinstruction?: string;\n\t\t\t\toutput: Record<string, string>;\n\t\t\t}[];\n\t\t\timage: {\n\t\t\t\tdata: string;\n\t\t\t\tmimeType: \"image/webp\";\n\t\t\t\tdetail: \"low\" | \"high\" | \"auto\";\n\t\t\t\tfilename?: string;\n\t\t\t};\n\t\t\tmedia: {\n\t\t\t\tid?: string | number;\n\t\t\t\tname?: Record<string, string>;\n\t\t\t\talt?: Record<string, 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\tMediaAltGenerateResponse\n> = async (context, props) => {\n\tconst featureEnabledRes = await checkFeatureEnabled(context, {\n\t\tfeature: \"altGeneration\",\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 input: MediaAltGenerateV1Request[\"input\"] = [];\n\n\tif (props.instruction) {\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\n\tinput.push({\n\t\ttype: \"image\",\n\t\trole: \"source-image\",\n\t\timage: {\n\t\t\ttype: \"base64\",\n\t\t\tdata: props.image.data,\n\t\t\tmimeType: props.image.mimeType,\n\t\t\tdetail: props.image.detail,\n\t\t\tfilename: props.image.filename,\n\t\t},\n\t});\n\n\tconst request: MediaAltGenerateV1Request = {\n\t\tfeature: {\n\t\t\tkey: \"media.alt.generate\",\n\t\t\tversion: \"v1\",\n\t\t},\n\t\tinput,\n\t\tcontext: {\n\t\t\tlocale: props.locale,\n\t\t\tmedia: props.media,\n\t\t\tpreviousResponses: props.previousResponses,\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: \"media-alt\",\n\t\t\ttarget: {\n\t\t\t\tmediaId: props.media.id ?? null,\n\t\t\t\tlocale: props.locale,\n\t\t\t\timage: {\n\t\t\t\t\tdetail: props.image.detail,\n\t\t\t\t\tfilename: props.image.filename ?? null,\n\t\t\t\t\tmimeType: props.image.mimeType,\n\t\t\t\t},\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\n\tconst outputParse = mediaAltOutputSchema.safeParse(responseData.output);\n\tif (!outputParse.success) {\n\t\tconst storeRes = await storeGeneration(context, {\n\t\t\tuserId: props.userId,\n\t\t\tresponse: responseData,\n\t\t\ttargetType: \"media-alt\",\n\t\t\trequestStartedAt,\n\t\t\tstatus: \"failed\",\n\t\t\terrorMessage: context.translate(\n\t\t\t\t\"server:core.routes.ai.generate.error.message\",\n\t\t\t),\n\t\t\ttarget: {\n\t\t\t\tmediaId: props.media.id ?? null,\n\t\t\t\tlocale: props.locale,\n\t\t\t\timage: {\n\t\t\t\t\tdetail: props.image.detail,\n\t\t\t\t\tfilename: props.image.filename ?? null,\n\t\t\t\t\tmimeType: props.image.mimeType,\n\t\t\t\t},\n\t\t\t},\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(\"server:core.routes.ai.generate.error.message\"),\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tconst missingLocale = props.locale.target.find(\n\t\t(locale) => outputParse.data[locale] === undefined,\n\t);\n\tif (missingLocale) {\n\t\tconst storeRes = await storeGeneration(context, {\n\t\t\tuserId: props.userId,\n\t\t\tresponse: responseData,\n\t\t\ttargetType: \"media-alt\",\n\t\t\trequestStartedAt,\n\t\t\tstatus: \"failed\",\n\t\t\terrorMessage: context.translate(\n\t\t\t\t\"server:core.routes.ai.generate.error.message\",\n\t\t\t),\n\t\t\ttarget: {\n\t\t\t\tmediaId: props.media.id ?? null,\n\t\t\t\tlocale: props.locale,\n\t\t\t\timage: {\n\t\t\t\t\tdetail: props.image.detail,\n\t\t\t\t\tfilename: props.image.filename ?? null,\n\t\t\t\t\tmimeType: props.image.mimeType,\n\t\t\t\t},\n\t\t\t},\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(\"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-alt\",\n\t\trequestStartedAt,\n\t\ttarget: {\n\t\t\tmediaId: props.media.id ?? null,\n\t\t\tlocale: props.locale,\n\t\t\timage: {\n\t\t\t\tdetail: props.image.detail,\n\t\t\t\tfilename: props.image.filename ?? null,\n\t\t\t\tmimeType: props.image.mimeType,\n\t\t\t},\n\t\t},\n\t});\n\tif (storeRes.error) return storeRes;\n\n\treturn {\n\t\terror: undefined,\n\t\tdata: {\n\t\t\tmode: responseData.mode,\n\t\t\trequestId: responseData.requestId,\n\t\t\tfeature: {\n\t\t\t\tkey: \"media.alt.generate\",\n\t\t\t\tversion: \"v1\",\n\t\t\t},\n\t\t\toutput: outputParse.data,\n\t\t\tusage: responseData.usage,\n\t\t},\n\t};\n};\n\nexport default mediaAltGenerate;\n"],"mappings":"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"}