使用OpenAI生成AEM图像资源
了解如何使用OpenAI或DALL·E 2生成图像并将其上传到AEM DAM以提高内容速度。
此示例AEM内容片段控制台扩展是一个操作栏扩展,它使用OpenAI API或DALL·E 2从自然语言输入生成数字图像。 生成的图像已上传到AEM DAM,并且所选内容片段的图像属性已更新,以引用从DAM新生成的已上传图像。
在本例中,您将学习:
- 使用OpenAI API或DALL·E 2生成图像
- 正在将图像上传到AEM
- 内容片段属性更新
示例扩展的功能流程如下所示:
用于生成数字图像的
{align="center"}
- 选择内容片段,然后单击操作栏中扩展的
Generate Image
按钮以打开模式。 - 模式显示使用React Spectrum生成的自定义输入表单。
- 提交表单会将用户提供的
Image Description
文本、选定的内容片段和AEM主机发送到自定义Adobe I/O Runtime操作。 - Adobe I/O Runtime操作验证输入。
- 接下来,它调用OpenAI的图像生成 API,并使用
Image Description
文本来指定应该生成的图像。 - 图像生成终结点使用提示请求参数值创建大小为 1024x1024 像素的原始图像,并将生成的图像URL作为响应返回。
- Adobe I/O Runtime操作将生成的图像下载到App Builder运行时。
- 接下来,它会根据预定义的路径,启动从App Builder运行时到AEM DAM的图像上传。
- AEM as a Cloud Service将图像保存到DAM并返回对Adobe I/O Runtime操作的成功或失败响应。 成功的上传响应可使用从Adobe I/O Runtime操作向AEM发出的另一个HTTP请求来更新所选内容片段的图像属性值。
- 该模式窗口会收到来自Adobe I/O Runtime操作的响应,并提供新生成的已上传图像的AEM资源详细信息链接。
扩展点
此示例扩展到扩展点actionBar
以将自定义按钮添加到内容片段控制台。
扩展示例
该示例在通过aio app init
初始化Adobe Developer Console应用程序时使用现有的App Builder项目以及以下选项。
-
您要搜索哪些模板?:
All Extension Points
-
选择要安装的模板:
@adobe/aem-cf-admin-ui-ext-tpl
-
您希望如何命名扩展?:
Image generation
-
提供扩展的简短说明:
An example action bar extension that generates an image using OpenAI and uploads it to AEM DAM.
-
您希望以哪个版本开始?:
0.0.1
-
您接下来想要做什么?
-
Add a custom button to Action Bar
- 提供按钮的标签名称:
Generate Image
- 是否必须显示按钮的模式窗口?
y
- 提供按钮的标签名称:
-
Add server-side handler
- Adobe I/O Runtime允许您根据需要调用无服务器代码。 您希望如何命名此操作?:
generate-image
- Adobe I/O Runtime允许您根据需要调用无服务器代码。 您希望如何命名此操作?:
-
生成的App Builder扩展应用程序将按如下所述进行更新。
初始设置
-
注册免费的OpenAI API帐户并创建API密钥
-
将此密钥添加到您的App Builder项目的
.env
文件code language-none # Specify your secrets here # This file must not be committed to source control ## Adobe I/O Runtime credentials ... AIO_runtime_apihost=https://adobeioruntime.net ... # OpenAI secret API key OPENAI_API_KEY=my-openai-secrete-key-to-generate-images ...
-
将
OPENAI_API_KEY
作为参数传递给Adobe I/O Runtime操作,更新src/aem-cf-console-admin-1/ext.config.yaml
code language-yaml ... runtimeManifest: packages: aem-cf-console-admin-1: license: Apache-2.0 actions: generate-image: function: actions/generate-image/index.js web: 'yes' runtime: nodejs:16 inputs: LOG_LEVEL: debug OPENAI_API_KEY: $OPENAI_API_KEY ...
-
安装以下Node.js库
- OpenAI Node.js库 — 轻松调用OpenAI API
- AEM上传 — 将图像上传到AEM-CS实例。
web-src
和actions
文件夹中的密钥文件,请参阅adobe-appbuilder-cfc-ext-image-generation-code.zip。应用程序路由 app-routes
src/aem-cf-console-admin-1/web-src/src/components/App.js
包含React路由器。
路由有两种逻辑集:
-
第一个路由将请求映射到
index.html
,这将调用负责扩展注册的React组件。code language-javascript <Route index element={<ExtensionRegistration />} />
-
第二组路由将URL映射到渲染扩展模式内容的React组件。
:selection
参数表示分隔列表内容片段路径。如果扩展具有多个用于调用离散操作的按钮,则每个扩展注册都将映射到此处定义的路由。
code language-javascript <Route exact path="content-fragment/:selection/generate-image-modal" element={<GenerateImageModal />} />
延期注册
映射到index.html
路由的ExtensionRegistration.js
是AEM扩展的入口点并定义:
- 扩展按钮的位置显示在AEM创作体验中(
actionBar
或headerMenu
) getButtons()
函数中扩展按钮的定义onClick()
函数中按钮的点击处理程序
src/aem-cf-console-admin-1/web-src/src/components/ExtensionRegistration.js
import React from "react";
import { generatePath } from "react-router";
import { Text } from "@adobe/react-spectrum";
import { register } from "@adobe/uix-guest";
import { extensionId } from "./Constants";
function ExtensionRegistration() {
const init = async () => {
const guestConnection = await register({
id: extensionId, // Some unique ID for the extension used to facilitate communication between the extension and Content Fragment Console
methods: {
// Configure your Action Bar button here
actionBar: {
getButtons() {
return [{
'id': 'generate-image', // Unique ID for the button
'label': 'Generate Image', // Button label
'icon': 'PublishCheck', // Button icon; get name from: https://spectrum.adobe.com/page/icons/ (Remove spaces, keep uppercase)
// Click handler for the extension button
onClick(selections) {
// Collect the selected content fragment paths
const selectionIds = selections.map(selection => selection.id);
// Create a URL that maps to the
const modalURL = "/index.html#" + generatePath(
"/content-fragment/:selection/generate-image-modal",
{
// Set the :selection React route parameter to an encoded, delimited list of paths of the selected content fragments
selection: encodeURIComponent(selectionIds.join('|')),
}
);
// Open the route in the extension modal using the constructed URL
guestConnection.host.modal.showUrl({
title: "Generate Image",
url: modalURL
})
},
},
];
},
},
},
});
};
init().catch(console.error);
return <Text>IFrame for integration with Host (AEM)...</Text>;
}
export default ExtensionRegistration;
模态
App.js
中定义的扩展的每个路由都映射到一个在扩展模式中呈现的React组件。
在此示例应用程序中,有一个模态React组件(GenerateImageModal.js
)具有四种状态:
- 正在加载,指示用户必须等待
- 警告消息,建议用户一次只选择一个内容片段
- 允许用户以自然语言提供图像描述的“生成图像”表单。
- 图像生成操作的响应,提供新生成的已上传图像的AEM资产详细信息链接。
重要的是,与扩展中的AEM的任何交互都应该委派给AppBuilder Adobe I/O Runtime操作,该操作是在Adobe I/O Runtime中运行的单独的无服务器进程。
使用Adobe I/O Runtime操作与AEM进行通信,是为了避免跨源资源共享(CORS)连接问题。
提交 生成图像 表单后,自定义onSubmitHandler()
将调用Adobe I/O Runtime操作,传递图像描述、当前AEM主机(域)和用户的AEM访问令牌。 然后,该操作调用OpenAI的图像生成 API以使用提交的图像描述生成图像。 接下来,使用AEM上传节点模块的DirectBinaryUpload
类,它将生成的图像上传到AEM,最后使用AEM内容片段API更新内容片段。
当接收来自Adobe I/O Runtime操作的响应时,模式被更新以显示图像生成操作的结果。
src/aem-cf-console-admin-1/web-src/src/components/GenerateImageModal.js
export default function GenerateImageModal() {
// Set up state used by the React component
const [guestConnection, setGuestConnection] = useState();
// State hooks to manage the application state
const [imageDescription, setImageDescription] = useState(null);
const [validationState, setValidationState] = useState({});
const [actionInvokeInProgress, setActionInvokeInProgress] = useState(false);
const [actionResponse, setActionResponse] = useState();
// Get the selected content fragment paths from the route parameter `:selection`
const { selection } = useParams();
const fragmentIds = selection?.split('|') || [];
console.log('Selected Fragment Ids', fragmentIds);
if (!fragmentIds || fragmentIds.length === 0) {
console.error('The Content Fragments are not selected, can NOT generate images');
return;
}
// Asynchronously attach the extension to AEM, we must wait or the guestConnection to be set before doing anything in the modal
useEffect(() => {
(async () => {
const myGuestConnection = await attach({ id: extensionId });
setGuestConnection(myGuestConnection);
})();
}, []);
// Determine view to display in the modal
if (!guestConnection) {
// If the guestConnection is not initialized, display a loading spinner
return <Spinner />;
} if (actionInvokeInProgress) {
// If the 'Generate Image' action has been invoked but not completed, display a loading spinner
return <Spinner />;
} if (fragmentIds.length > 1) {
// If more than one CF selected show warning and suggest to select only one CF
return renderMoreThanOneCFSelectionError();
} if (fragmentIds.length === 1 && !actionResponse) {
// Display the 'Generate Image' modal and ask for image description
return renderImgGenerationForm();
} if (actionResponse) {
// If the 'Generate Image' action has completed, display the response
return renderActionResponse();
}
/**
* Renders the message suggesting to select only on CF at a time to not lose credits accidentally
*
* @returns the suggestion or error message to select one CF at a time
*/
function renderMoreThanOneCFSelectionError() {
return (
<Provider theme={defaultTheme} colorScheme="light">
<Content width="100%">
<Text>
As this operation
<strong> uses credits from Generative AI services</strong>
{' '}
such as DALL·E 2 (or Stable Dufusion), we allow only one Generate Image at a time.
<p />
<strong>So please select only one Content Fragment at this moment.</strong>
</Text>
<Flex width="100%" justifyContent="end" alignItems="center" marginTop="size-400">
<ButtonGroup align="end">
<Button variant="negative" onPress={() => guestConnection.host.modal.close()}>Close</Button>
</ButtonGroup>
</Flex>
</Content>
</Provider>
);
}
/**
* Renders the form asking for image description in the natural language and
* displays message this action uses credits from Generative AI services.
*
* @returns the image description input field and credit usage message
*/
function renderImgGenerationForm() {
return (
<Provider theme={defaultTheme} colorScheme="light">
<Content width="100%">
<Flex width="100%">
<Form
width="100%"
>
<TextField
label="Image Description"
description="The image description in natural language, for e.g. Alaskan adventure in wilderness, animals, and flowers."
isRequired
validationState={validationState?.propertyName}
onChange={setImageDescription}
contextualHelp={(
<ContextualHelp>
<Heading>Need help?</Heading>
<Content>
<Text>
The
<strong>description of an image</strong>
{' '}
you are looking for in the natural language, for e.g. "Family vacation on the beach with blue ocean, dolphins, boats and drink"
</Text>
</Content>
</ContextualHelp>
)}
/>
<Text>
<p />
Please note this will use credits from Generative AI services such as OpenAI/DALL·E 2. The AI-generated images are saved to this AEM as a Cloud Service Author service using logged user access (IMS) token.
</Text>
<ButtonGroup align="end">
<Button variant="accent" onPress={onSubmitHandler}>Use Credits</Button>
<Button variant="accent" onPress={() => guestConnection.host.modal.close()}>Close</Button>
</ButtonGroup>
</Form>
</Flex>
</Content>
</Provider>
);
}
function buildAssetDetailsURL(aemImgURL) {
const urlParts = aemImgURL.split('.com');
const aemAssetDetailsURL = `${urlParts[0]}.com/ui#/aem/assetdetails.html${urlParts[1]}`;
return aemAssetDetailsURL;
}
/**
* Displays the action response received from the App Builder
*
* @returns Displays App Builder action and details
*/
function renderActionResponse() {
return (
<Provider theme={defaultTheme} colorScheme="light">
<Content width="100%">
{actionResponse.status === 'success'
&& (
<>
<Heading level="4">
Successfully generated an image, uploaded it to this AEM-CS Author service, and associated it to the selected Content Fragment.
</Heading>
<Text>
{' '}
Please see generated image in AEM-CS
{' '}
<Link>
<a href={buildAssetDetailsURL(actionResponse.aemImgURL)} target="_blank" rel="noreferrer">
here.
</a>
</Link>
</Text>
</>
)}
{actionResponse.status === 'failure'
&& (
<Heading level="4">
Failed to generate, upload image, please check App Builder logs.
</Heading>
)}
<Flex width="100%" justifyContent="end" alignItems="center" marginTop="size-400">
<ButtonGroup align="end">
<Button variant="negative" onPress={() => guestConnection.host.modal.close()}>Close</Button>
</ButtonGroup>
</Flex>
</Content>
</Provider>
);
}
/**
* Handle the Generate Image form submission.
* This function calls the supporting Adobe I/O Runtime actions such as
* - Call the Generative AI service (DALL·E) with 'image description' to generate an image
* - Download the AI generated image to App Builder runtime
* - Save the downloaded image to AEM DAM and update Content Fragment's image reference property to use this new image
*
* When invoking the Adobe I/O Runtime actions, the following parameters are passed as they're used by the action to connect to AEM:
* - AEM Host to connect to
* - AEM access token to connect to AEM with
* - The Content Fragment path to update
*
* @returns In case of success the updated content fragment, otherwise failure message
*/
async function onSubmitHandler() {
console.log('Started Image Generation orchestration');
// Validate the form input fields
if (imageDescription?.length > 1) {
setValidationState({ imageDescription: 'valid' });
} else {
setValidationState({ imageDescription: 'invalid' });
return;
}
// Mark the extension as invoking the action, so the loading spinner is displayed
setActionInvokeInProgress(true);
// Set the HTTP headers to access the Adobe I/O runtime action
const headers = {
Authorization: `Bearer ${guestConnection.sharedContext.get('auth').imsToken}`,
'x-gw-ims-org-id': guestConnection.sharedContext.get('auth').imsOrg,
};
// Set the parameters to pass to the Adobe I/O Runtime action
const params = {
aemHost: `https://${guestConnection.sharedContext.get('aemHost')}`,
fragmentId: fragmentIds[0],
imageDescription,
};
const generateImageAction = 'generate-image';
try {
const generateImageActionResponse = await actionWebInvoke(allActions[generateImageAction], headers, params);
// Set the response from the Adobe I/O Runtime action
setActionResponse(generateImageActionResponse);
console.log(`Response from ${generateImageAction}:`, actionResponse);
} catch (e) {
// Log and store any errors
console.error(e);
}
// Set the action as no longer being invoked, so the loading spinner is hidden
setActionInvokeInProgress(false);
}
}
buildAssetDetailsURL()
函数中,aemAssetdetailsURL
变量值假定已启用Unified Shell。 如果您已禁用Unified Shell,则必须从变量值中删除/ui#/aem
。Adobe I/O Runtime操作
AEM扩展App Builder应用程序可以定义或使用0个或多个Adobe I/O Runtime操作。
Adobe运行时操作负责需要与AEM、Adobe或第三方Web服务交互的工作。
在此示例应用程序中,generate-image
Adobe I/O Runtime操作负责:
- 使用OpenAI API图像生成服务生成图像
- 使用AEM上传库将生成的图像上传到AEM-CS实例
- 向AEM内容片段API发出HTTP请求以更新内容片段的图像属性。
- 返回成功和失败的关键信息以供模式(
GenerateImageModal.js
)显示
进入点(index.js
)
index.js
使用相应的JavaScript模块(即generate-image-using-openai, upload-generated-image-to-aem, update-content-fragement
)协调了1到3项以上的任务。 下面的子部分中介绍了这些模块和相关代码。
src/aem-cf-console-admin-1/actions/generate-image/index.js
/**
*
* This action orchestrates an image generation by calling the OpenAI API (DALL·E 2) and saves generated image to AEM.
*
* It leverages following modules
* - 'generate-image-using-openai' - To generate an image using OpenAI API
* - 'upload-generated-image-to-aem' - To upload the generated image into AEM-CS instance
* - 'update-content-fragement' - To update the CF image property with generated image's DAM path
*
*/
const { Core } = require('@adobe/aio-sdk');
const {
errorResponse, stringParameters, getBearerToken, checkMissingRequestInputs,
} = require('../utils');
const { generateImageUsingOpenAI } = require('./generate-image-using-openai');
const { uploadGeneratedImageToAEM } = require('./upload-generated-image-to-aem');
const { updateContentFragmentToUseGeneratedImg } = require('./update-content-fragement');
// main function that will be executed by Adobe I/O Runtime
async function main(params) {
// create a Logger
const logger = Core.Logger('main', { level: params.LOG_LEVEL || 'info' });
try {
// 'info' is the default level if not set
logger.info('Calling the main action');
// log parameters, only if params.LOG_LEVEL === 'debug'
logger.debug(stringParameters(params));
// check for missing request input parameters and headers
const requiredParams = ['aemHost', 'fragmentId', 'imageDescription'];
const requiredHeaders = ['Authorization'];
const errorMessage = checkMissingRequestInputs(params, requiredParams, requiredHeaders);
if (errorMessage) {
// return and log client errors
return errorResponse(400, errorMessage, logger);
}
// extract the user Bearer token from the Authorization header
const token = getBearerToken(params);
// Call OpenAI (DALL·E 2) API to generate an image using image description
const generatedImageURL = await generateImageUsingOpenAI(params);
logger.info(`Generated image using OpenAI API and url is : ${generatedImageURL}`);
// Upload the generated image to AEM-CS
const uploadedImagePath = await uploadGeneratedImageToAEM(params, generatedImageURL, token);
logger.info(`Uploaded image to AEM, path is: ${uploadedImagePath}`);
// Update Content Fragment with the newly generated image reference
const updateContentFragmentPath = await updateContentFragmentToUseGeneratedImg(params, uploadedImagePath, token);
logger.info(`Updated Content Fragment path is: ${updateContentFragmentPath}`);
let result;
if (updateContentFragmentPath) {
result = {
status: 'success', message: 'Successfully generated and uploaded image to AEM', genTechServiceImageURL: generatedImageURL, aemImgURL: uploadedImagePath, fragmentPath: updateContentFragmentPath,
};
} else {
result = { status: 'failure', message: 'Failed to generated and uploaded image, please check App Builder logs' };
}
const response = {
statusCode: 200,
body: result,
};
logger.info('Adobe I/O Runtime action response', response);
// Return the response to the caller
return response;
} catch (error) {
// log any server errors
logger.error(error);
// return with 500
return errorResponse(500, 'server error', logger);
}
}
exports.main = main;
图像生成
此模块负责使用openai库调用OpenAI的图像生成端点。 要获取在.env
文件中定义的OpenAI API密钥,它使用params.OPENAI_API_KEY
。
src/aem-cf-console-admin-1/actions/generate-image/generate-image-using-openai.js
/**
* This module calls OpenAI API to generate an image based on image description provided to Action
*
*/
const { Configuration, OpenAIApi } = require('openai');
const { Core } = require('@adobe/aio-sdk');
// Placeholder than actual OpenAI Image
const PLACEHOLDER_IMG_URL = 'https://www.gstatic.com/webp/gallery/2.png';
async function generateImageUsingOpenAI(params) {
// create a Logger
const logger = Core.Logger('generateImageUsingOpenAI', { level: params.LOG_LEVEL || 'info' });
let generatedImageURL = PLACEHOLDER_IMG_URL;
// create configuration object with the API Key
const configuration = new Configuration({
apiKey: params.OPENAI_API_KEY,
});
// create OpenAIApi object
const openai = new OpenAIApi(configuration);
logger.info(`Generating image for input: ${params.imageDescription}`);
try {
// invoke createImage method with details
const response = await openai.createImage({
prompt: params.imageDescription,
n: 1,
size: '1024x1024',
});
generatedImageURL = response.data.data[0].url;
logger.info(`The OpenAI generate image url is: ${generatedImageURL}`);
} catch (error) {
logger.error(`Error while generating image, details are: ${error}`);
}
return generatedImageURL;
}
module.exports = {
generateImageUsingOpenAI,
};
上传至AEM
此模块负责使用AEM Upload库将OpenAI生成的图像上传到AEM。 首先使用Node.js File System库将生成的图像下载到App Builder运行时,一旦上传到AEM完成,就会将其删除。
在下面的代码中,uploadGeneratedImageToAEM
函数将生成的图像下载到运行时,将其上传到AEM并从运行时删除。 图像已上载到/content/dam/wknd-shared/en/generated
路径,请确保DAM中存在所有文件夹,这是使用AEM上传库的先决条件。
src/aem-cf-console-admin-1/actions/generate-image/upload-generated-image-to-aem.js
/**
* This module uploads the generated image to AEM-CS instance using current user's IMS token
*
*/
const { Core } = require('@adobe/aio-sdk');
const fs = require('fs');
const {
DirectBinaryUploadErrorCodes,
DirectBinaryUpload,
DirectBinaryUploadOptions,
} = require('@adobe/aem-upload');
const codes = DirectBinaryUploadErrorCodes;
const IMG_EXTENSION = '.png';
const GENERATED_IMAGES_DAM_PATH = '/content/dam/wknd-shared/en/generated';
async function downloadImageToRuntime(logger, generatedImageURL) {
logger.log('Downloading generated image to the runtime');
// placeholder image name
let generatedImageName = 'generated.png';
try {
// Get the generated image name from the image URL
const justImgURL = generatedImageURL.substring(0, generatedImageURL.indexOf(IMG_EXTENSION) + 4);
generatedImageName = justImgURL.substring(justImgURL.lastIndexOf('/') + 1);
// Read image from URL as the buffer
const response = await fetch(generatedImageURL);
const buffer = await response.buffer();
// Write/download image to the runtime
fs.writeFileSync(generatedImageName, buffer, (err) => {
if (err) throw err;
logger.log('Saved the generated image!');
});
} catch (error) {
logger.error(`Error while downloading image on the runtime, details are: ${error}`);
}
return generatedImageName;
}
function setupEventHandlers(binaryUpload, logger) {
binaryUpload.on('filestart', (data) => {
const { fileName } = data;
logger.log(`Started file upload ${fileName}`);
});
binaryUpload.on('fileprogress', (data) => {
const { fileName, transferred } = data;
logger.log(`Fileupload is in progress ${fileName} & ${transferred}`);
});
binaryUpload.on('fileend', (data) => {
const { fileName } = data;
logger.log(`Finished file upload ${fileName}`);
});
binaryUpload.on('fileerror', (data) => {
const { fileName, errors } = data;
logger.log(`Error in file upload ${fileName} and ${errors}`);
});
}
async function getImageSize(downloadedImgName) {
const stats = fs.statSync(downloadedImgName);
return stats.size;
}
async function uploadImageToAEMFromRuntime(logger, aemURL, downloadedImgName, accessToken) {
let aemImageURL;
try {
logger.log('Uploading generated image to AEM from the runtime');
const binaryUpload = new DirectBinaryUpload();
// setup event handlers to track the progress, success or error
setupEventHandlers(binaryUpload, logger);
// get downloaded image size
const imageSize = await getImageSize(downloadedImgName);
logger.info(`The image upload size is: ${imageSize}`);
// The deatils of the file to be uploaded
const uploadFiles = [
{
fileName: downloadedImgName, // name of the file as it will appear in AEM
fileSize: imageSize, // total size, in bytes, of the file
filePath: downloadedImgName, // Full path to the local file
},
];
// Provide AEM URL and DAM Path where images will be uploaded
const options = new DirectBinaryUploadOptions()
.withUrl(`${aemURL}${GENERATED_IMAGES_DAM_PATH}`)
.withUploadFiles(uploadFiles);
// Add headers like content type and authorization
options.withHeaders({
'content-type': 'image/png',
Authorization: `Bearer ${accessToken}`,
});
// Start the upload to AEM
await binaryUpload.uploadFiles(options)
.then((result) => {
// Handle Error
result.getErrors().forEach((error) => {
if (error.getCode() === codes.ALREADY_EXISTS) {
logger.error('The generated image already exists');
}
});
// Handle Upload result and check for errors
result.getFileUploadResults().forEach((fileResult) => {
// log file upload result
logger.info(`File upload result ${JSON.stringify(fileResult)}`);
fileResult.getErrors().forEach((fileErr) => {
if (fileErr.getCode() === codes.ALREADY_EXISTS) {
const fileName = fileResult.getFileName();
logger.error(`The generated image already exists ${fileName}`);
}
});
});
})
.catch((err) => {
logger.info(`Failed to uploaded generated image to AEM${err}`);
});
logger.info('Successfully uploaded generated image to AEM');
aemImageURL = `${aemURL + GENERATED_IMAGES_DAM_PATH}/${downloadedImgName}`;
} catch (error) {
logger.info(`Error while uploading generated image to AEM, see ${error}`);
}
return aemImageURL;
}
async function deleteFileFromRuntime(logger, downloadedImgName) {
try {
logger.log('Deleting the generated image from the runtime');
fs.unlinkSync(downloadedImgName);
logger.log('Successfully deleted the generated image from the runtime');
} catch (error) {
logger.error(`Error while deleting generated image from the runtime, details are: ${error}`);
}
}
async function uploadGeneratedImageToAEM(params, generatedImageURL, accessToken) {
// create a Logger
const logger = Core.Logger('uploadGeneratedImageToAEM', { level: params.LOG_LEVEL || 'info' });
const aemURL = params.aemHost;
logger.info(`Uploading generated image from ${generatedImageURL} to AEM ${aemURL} by streaming the bytes.`);
// download image to the App Builder runtime
const downloadedImgName = await downloadImageToRuntime(logger, generatedImageURL);
// Upload image to AEM from the App Builder runtime
const aemImageURL = await uploadImageToAEMFromRuntime(logger, aemURL, downloadedImgName, accessToken);
// Delete the downloaded image from the App Builder runtime
await deleteFileFromRuntime(logger, downloadedImgName);
return aemImageURL;
}
module.exports = {
uploadGeneratedImageToAEM,
};
更新内容片段
此模块负责使用AEM内容片段API使用新上传的图像的DAM路径更新给定内容片段的图像属性。
src/aem-cf-console-admin-1/actions/generate-image/update-content-fragement.js
/**
* This module updates the CF image property with generated image's DAM path
*
*/
const { Core } = require('@adobe/aio-sdk');
const ADVENTURE_MODEL_IMG_PROPERTY_NAME = 'primaryImage';
const ARTICLE_MODEL_IMG_PROPERTY_NAME = 'featuredImage';
const AUTHOR_MODEL_IMG_PROPERTY_NAME = 'profilePicture';
function findImgPropertyName(fragmenPath) {
if (fragmenPath && fragmenPath.includes('/adventures')) {
return ADVENTURE_MODEL_IMG_PROPERTY_NAME;
} if (fragmenPath && fragmenPath.includes('/magazine')) {
return ARTICLE_MODEL_IMG_PROPERTY_NAME;
}
return AUTHOR_MODEL_IMG_PROPERTY_NAME;
}
async function updateContentFragmentToUseGeneratedImg(params, uploadedImagePath, accessToken) {
// create a Logger
const logger = Core.Logger('updateContentFragment', { level: params.LOG_LEVEL || 'info' });
const fragmenPath = params.fragmentId;
const imgPropName = findImgPropertyName(fragmenPath);
const relativeImgPath = uploadedImagePath.substring(uploadedImagePath.indexOf('/content/dam'));
logger.info(`Update CF ${fragmenPath} to use ${relativeImgPath} image path`);
const body = {
properties: {
elements: {
[imgPropName]: {
value: relativeImgPath,
},
},
},
};
const res = await fetch(`${params.aemHost}${fragmenPath.replace('/content/dam/', '/api/assets/')}.json`, {
method: 'put',
body: JSON.stringify(body),
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
});
if (res.ok) {
logger.info(`Successfully updated ${fragmenPath}`);
return fragmenPath;
}
logger.info(`Failed to update ${fragmenPath}`);
return '';
}
module.exports = {
updateContentFragmentToUseGeneratedImg,
};