Generera AEM bildresurser med OpenAI
Lär dig hur du genererar en bild med OpenAI eller DALL ・ E 2 och överför den till AEM DAM för snabb innehållshantering.
Det här exemplet AEM tillägget Content Fragment Console är ett åtgärdsfältstillägg som genererar digital bild från indata på naturligt språk med OpenAI APIeller DALL ・ E 2. Den genererade bilden överförs till AEM DAM och den valda Content Fragment-bildegenskapen uppdateras för att referera till den nyligen genererade, överförda bilden från DAM.
I det här exemplet lär du dig:
- Bildgenerering med OpenAI API eller DALL ・ E 2
- Överför bilder till AEM
- Egenskapsuppdatering för innehållsfragment
Det funktionella flödet för exempeltillägget är följande:
{align="center"}
- Välj Innehållsfragment och klicka på tilläggets
Generate Image
-knapp i åtgärdsfältet för att öppna modal . - modal visar ett anpassat indataformulär som skapats med React Spectrum.
- När du skickar formuläret skickas
Image Description
-text, det markerade innehållsfragmentet och AEM värd till den anpassade Adobe I/O Runtime-åtgärden. - Adobe I/O Runtime-åtgärden validerar indata.
- Därefter anropas OpenAI:s Image generation -API och
Image Description
-text används för att ange vilken bild som ska genereras. - Slutpunkten för bildgenerering skapar en originalbild med storleken 1024x1024 pixlar med parametervärdet för promptbegäran och returnerar den genererade bild-URL:en som svar.
- Adobe I/O Runtime-åtgärden hämtar den genererade bilden till App Builder-miljön.
- Därefter initieras bildöverföringen från App Builder-miljön till AEM DAM under en fördefinierad sökväg.
- AEM as a Cloud Service sparar bilder till DAM och returnerar svar på om Adobe I/O Runtime-åtgärden lyckades eller misslyckades. Det överförda svaret uppdaterar det valda Content Fragments image-egenskapsvärde med en annan HTTP-begäran som AEM från Adobe I/O Runtime-åtgärden.
- modalen tar emot svar från Adobe I/O Runtime-åtgärden och tillhandahåller länken AEM resursinformation för den nyligen genererade, överförda bilden.
Tilläggspunkt
Det här exemplet utökar till tilläggspunkten actionBar
för att lägga till en anpassad knapp i konsolen för innehållsfragment.
Exempel på tillägg
I exemplet används ett befintligt Adobe Developer Console-projekt och följande alternativ när App Builder-appen initieras via aio app init
.
-
Vilka mallar vill du söka efter?:
All Extension Points
-
Välj de mallar som ska installeras:
@adobe/aem-cf-admin-ui-ext-tpl
-
Vad vill du kalla tillägget?
Image generation
-
Ange en kort beskrivning av tillägget:
An example action bar extension that generates an image using OpenAI and uploads it to AEM DAM.
-
Vilken version vill du börja med?:
0.0.1
-
Vad vill du göra nu?
-
Add a custom button to Action Bar
- Ange etikettnamn för knappen:
Generate Image
- Måste du visa en modal för knappen?
y
- Ange etikettnamn för knappen:
-
Add server-side handler
- Med Adobe I/O Runtime kan du anropa serverlös kod vid behov. Hur vill du namnge den här åtgärden?:
generate-image
- Med Adobe I/O Runtime kan du anropa serverlös kod vid behov. Hur vill du namnge den här åtgärden?:
-
Den genererade App Builder-tilläggsappen uppdateras enligt beskrivningen nedan.
Inledande konfiguration
-
Registrera dig för ett kostnadsfritt OpenAI API-konto och skapa en API-nyckel
-
Lägg till den här nyckeln i ditt App Builder-projekts
.env
-filcode 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 ...
-
Skicka
OPENAI_API_KEY
som param till Adobe I/O Runtime-åtgärden och uppdaterasrc/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 ...
-
Installera under Node.js-bibliotek
- OpenAI Node.js-biblioteket - kan enkelt anropa OpenAI API:t
- AEM Överför - för att överföra bilder till AEM-CS-instanser.
web-src
och actions
i AppBuilder-projektet finns i adobe-appbuilder-cfc-ext-image-generation-code.zip.Appvägar app-routes
src/aem-cf-console-admin-1/web-src/src/components/App.js
innehåller Reaktionsroutern.
Det finns två logiska uppsättningar vägar:
-
Den första vägen mappar begäranden till
index.html
, som anropar React-komponenten som ansvarar för tilläggsregistreringen.code language-javascript <Route index element={<ExtensionRegistration />} />
-
Den andra uppsättningen vägar mappar URL:er till React-komponenter som återger innehållet i tilläggets modal. Param
:selection
representerar en begränsad sökväg för innehållsfragment i listan.Om tillägget har flera knappar för att anropa diskreta åtgärder mappas varje tilläggsregistrering till en väg som definieras här.
code language-javascript <Route exact path="content-fragment/:selection/generate-image-modal" element={<GenerateImageModal />} />
Tillägg - registrering
ExtensionRegistration.js
, mappad till index.html
-vägen, är startpunkten för AEM och definierar:
- Platsen för tilläggsknappen visas i AEM-redigeringsgränssnittet (
actionBar
ellerheaderMenu
) - Tilläggsknappens definition i funktionen
getButtons()
- Knappens klickningshanterare i funktionen
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;
Modal
Varje väg i tillägget, enligt definition i App.js
, mappas till en React-komponent som återges i tilläggets modal.
I den här exempelappen finns en modal React-komponent (GenerateImageModal.js
) som har fyra lägen:
- Läser in, vilket anger att användaren måste vänta
- Varningsmeddelandet som föreslår att användarna bara väljer ett innehållsfragment åt gången
- Formuläret Generera bild där användaren kan ange en bildbeskrivning på det naturliga språket.
- Svaret på bildgenereringsåtgärden, som tillhandahåller länken AEM resursinformation för den nyligen genererade, överförda bilden.
Viktigt är att all interaktion med AEM från tillägget delegeras till en AppBuilder Adobe I/O Runtime-åtgärd, som är en separat serverlös process som körs i Adobe I/O Runtime.
Användning av Adobe I/O Runtime-åtgärder för att kommunicera med AEM, och för att undvika anslutningsproblem med korsorigo resursdelning (CORS).
När formuläret Generera bild skickas anropar en anpassad onSubmitHandler()
Adobe I/O Runtime-åtgärd och skickar bildbeskrivningen, den aktuella AEM (domänen) och användarens AEM åtkomsttoken. Åtgärden anropar sedan OpenAI:s Image generation -API för att generera en bild med den inskickade bildbeskrivningen. Därefter används AEM Upload -nodmodulens DirectBinaryUpload
-klass för att överföra den genererade bilden till AEM och slutligen använder AEM Content Fragment API för att uppdatera innehållsfragmenten.
När svaret från Adobe I/O Runtime-åtgärden tas emot uppdateras modalen så att resultatet av bildgenereringen visas.
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()
antar variabelvärdet aemAssetdetailsURL
att Unified Shell är aktiverat. Om du har inaktiverat det enhetliga skalet måste du ta bort /ui#/aem
från variabelvärdet.Adobe I/O Runtime action
Ett AEM tillägg som App Builder kan definiera eller använda 0 eller många Adobe I/O Runtime-åtgärder.
Åtgärden Adobe Runtime ansvarar för arbete som kräver interaktion med AEM, Adobe eller tredjeparts webbtjänster.
I den här exempelappen ansvarar Adobe I/O Runtime-åtgärden generate-image
för:
- Generera en bild med tjänsten OpenAI API Image Generation
- Överför den genererade bilden till AEM-CS-instansen med biblioteket AEM Upload
- Göra en HTTP-begäran till AEM Content Fragment API för att uppdatera innehållets image-egenskap.
- Returnerar nyckelinformationen för lyckade och misslyckade visningsuppgifter för modala (
GenerateImageModal.js
)
Startpunkt (index.js
)
index.js
strukturerar över 1 till 3 uppgifter med hjälp av respektive JavaScript-moduler, nämligen generate-image-using-openai, upload-generated-image-to-aem, update-content-fragement
. Dessa moduler och tillhörande kod beskrivs i de följande underavsnitten.
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;
Bildgenerering
Den här modulen ansvarar för att anropa OpenAI:s Image Generation -slutpunkt med hjälp av openai-biblioteket. För att hämta OpenAI API-hemlig nyckel som definierats i filen .env
används 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,
};
Överför till AEM
Den här modulen ansvarar för att överföra den OpenAI-genererade bilden till AEM med hjälp av biblioteket AEM Upload. Den genererade bilden hämtas först till App Builder-miljön med biblioteket Node.js File System och när överföringen till AEM är klar tas den bort.
I koden nedan sorterar funktionen uploadGeneratedImageToAEM
den genererade avbildningen som hämtas till körningsmiljön, överför den till AEM och tar bort den från körningsmiljön. Bilden överförs till sökvägen /content/dam/wknd-shared/en/generated
. Kontrollera att alla mappar finns i DAM, vilket är en förutsättning för att du ska kunna använda biblioteket AEM Upload.
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,
};
Uppdatera innehållsfragment
Den här modulen ansvarar för att uppdatera den angivna Content Fragment-bildegenskapen med den nyligen överförda bildens DAM-sökväg med hjälp av AEM Content Fragment API.
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,
};