Vor Monaten begann ich mit der Zusammenarbeit an einem Projekt rund um KI-generierte Inhalte für einen Kunden mit Fokus auf den Technologiesektor. Meine Rolle konzentrierte sich hauptsächlich auf die Einrichtung von SSG mit WordPress als Headless CMS für ein Nuxt Frontend.
Der Kunde schrieb ein paar Mal pro Woche Artikel über verschiedene Trends oder Situationen, die sich auf die Branche auswirkten. In der Hoffnung, den Verkehr auf der Website und die Ausgabe von Artikeln zu erhöhen, entschied er sich, KI zur Generierung von Artikeln für ihn zu verwenden.
Nach einiger Zeit verfügte der Kunde mit den richtigen Eingabeaufforderungen über Informationen, die einem von Menschen geschriebenen Artikel nahezu genau entsprachen. Es ist sehr schwierig zu erkennen, dass sie maschinell erstellt wurden.
Irgendwann, nachdem ich an verschiedenen Funktionen gearbeitet hatte, wurde ich ständig nach einer bestimmten Sache gefragt.
Ey, kannst du das vorgestellte Bild für diesen Artikel aktualisieren?
Nach zwei Wochen täglicher Aktualisierung der Beiträge hatte ich einen kleinen Heureka-Moment.
Warum automatisiere ich die Generierung der vorgestellten Bilder für diese Artikel nicht mithilfe von Künstliche Intelligenz?
Wir haben das Schreiben von Beiträgen bereits automatisiert. Warum automatisieren wir nicht auch die vorgestellten Bilder?
In meiner Freizeit habe ich auf meinem Computer mit generativen LLMs experimentiert, sodass ich mehr oder weniger eine genaue Vorstellung davon hatte, wie ich diese Nebenaufgabe angehen sollte. Ich schickte dem Kunden eine Nachricht, in der er detailliert darlegte, wo das Problem liegt, was ich tun möchte und welche Vorteile es mit sich bringen würde. Ohne überzeugend wirken zu müssen, erhielt ich grünes Licht für die Arbeit an dieser Funktion und legte sofort los mein erster Schritt.
Angesichts der Tatsache, dass ich einige Erfahrung damit hatte, Models vor Ort zu leiten, wusste ich sofort, dass es nicht machbar war, diese Models selbst zu hosten. Nachdem ich das verworfen hatte, fing ich an, mit APIs herumzuspielen, die Bilder basierend auf Texteingabeaufforderungen generierten.
Die vorgestellten Bilder bestanden aus zwei Teilen: der Hauptgrafik und einem einprägsamen Slogan.
Die komponierte Grafik besteht aus einigen Elementen, die sich auf den Artikel beziehen, die auf schöne Weise angeordnet sind, mit einigen Farben und Texturen und einigen angewendeten Mischmodi, um nach dem Branding einige ausgefallene Effekte zu erzielen.
Taglines waren kurze Sätze mit 8–12 Wörtern und einem einfachen Schlagschatten darunter.
Anhand meiner Tests wurde mir klar, dass es nicht praktikabel ist, den KI-Weg zur Bilderzeugung zu verfolgen. Die Bildqualität entsprach nicht den Erwartungen und der Prozess war zu zeitaufwändig, um seinen Einsatz zu rechtfertigen. Wenn man bedenkt, dass dies als AWS Lambda-Funktion ausgeführt würde, bei der sich die Ausführungszeit direkt auf die Kosten auswirkt.
Nachdem ich das verworfen hatte, entschied ich mich für Plan B: Bilder und Design-Assets mithilfe der Canvas-API von JavaScript zusammenführen.
Bei näherer Betrachtung hatten wir hauptsächlich fünf Stile einfacher Beiträge und etwa vier Arten von Texturen, von denen drei die gleiche Textausrichtung, den gleichen Stil und die gleiche Position verwendeten. Nachdem ich etwas nachgerechnet hatte, dachte ich:
Hmm, wenn ich diese 3 Bilder mache, 8 Texturen nehme und mit den Mischmodi spiele, komme ich auf ungefähr 24 Variationen
Angesichts der Tatsache, dass diese drei Arten von Beiträgen den gleichen Textstil hatten, handelte es sich praktisch um eine Vorlage.
Nachdem das geklärt war, wechselte ich zum Slogan-Generator. Ich wollte einen Slogan erstellen, der auf dem Inhalt und Titel des Artikels basiert. Ich habe mich für die Verwendung der ChatGPT-API entschieden, da das Unternehmen bereits dafür bezahlt hat, und nach einigen Experimenten und Optimierungen der Eingabeaufforderungen hatte ich ein sehr gutes MVP für meinen Slogan-Generator.
Nachdem ich die beiden schwierigsten Teile der Aufgabe geklärt hatte, verbrachte ich einige Zeit in Figma, um das Diagramm für die endgültige Architektur meines Dienstes zusammenzustellen.
Der Plan bestand darin, eine Lambda-Funktion zu erstellen, die in der Lage ist, den Inhalt von Posts zu analysieren, einen Slogan zu generieren und ein vorgestelltes Bild zusammenzustellen – alles nahtlos in WordPress integriert.
Ich werde etwas Code bereitstellen, aber gerade genug, um die Gesamtidee zu vermitteln.
Die Lambda-Funktion beginnt mit dem Extrahieren der erforderlichen Parameter aus der Nutzlast des eingehenden Ereignisses:
const { title: request_title, content, backend, app_password} = JSON.parse(event.body);
Die erste Hauptaufgabe der Funktion besteht darin, mithilfe der Funktion „analysateContent“ einen Slogan zu generieren, der mithilfe der OpenAI-API einen anklickbaren Slogan basierend auf dem Titel und Inhalt des Artikels erstellt.
Unsere Funktion übernimmt den Beitragstitel und den Inhalt, gibt aber einen Slogan, eine Beitragsstimmung, um zu wissen, ob der Beitrag eine positive, negative oder neutrale Meinung ist, und ein optionales Firmensymbol der S&P-Indexunternehmen zurück.
const { Slogan, Sentiment, Unternehmen } = waitanalysateContent({ title: request_title, content });
Dieser Schritt ist entscheidend, da der Slogan direkten Einfluss auf die Ästhetik des Bildes hat.
Als nächstes greift die Funktion „generateImage“:
let buffer; buffer = await generateImage({ title: tagline, company_logo: company_logo, sentiment: sentiment, });
Diese Funktion behandelt:
Hier ist eine Schritt-für-Schritt-Anleitung, wie es funktioniert:
Die Funktion „generateImage“ beginnt damit, eine leere Leinwand einzurichten, ihre Abmessungen zu definieren und sie für die Verarbeitung aller Designelemente vorzubereiten.
let buffer; buffer = await generateImage({ title: tagline, company_logo: company_logo, sentiment: sentiment, });
Von dort wird ein zufälliges Hintergrundbild aus einer vordefinierten Sammlung von Assets geladen. Diese Bilder wurden kuratiert, um zum technologieorientierten Branding zu passen und gleichzeitig genügend Abwechslung zwischen den Beiträgen zu ermöglichen. Das Hintergrundbild wird zufällig basierend auf seiner Stimmung ausgewählt.
Um sicherzustellen, dass jedes Hintergrundbild großartig aussieht, habe ich seine Abmessungen dynamisch basierend auf dem Seitenverhältnis berechnet. Dadurch werden Verzerrungen vermieden, während das visuelle Gleichgewicht erhalten bleibt.
Der Slogan ist kurz, aber basierend auf einigen Regeln wird dieser wirkungsvolle Satz in überschaubare Teile aufgeteilt und dynamisch gestaltet, um sicherzustellen, dass er immer lesbar ist, unabhängig von Länge oder Leinwandgröße, basierend auf der Wortanzahl für die Zeile, der Wortlänge usw .
const COLOURS = { BLUE: "#33b8e1", BLACK: "#000000", } const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const images_path = path.join(__dirname, 'images/'); const files_length = fs.readdirSync(images_path).length; const images_folder = process.env.ENVIRONMENT === "local" ? "./images/" : "/var/task/images/"; registerFont("/var/task/fonts/open-sans.bold.ttf", { family: "OpenSansBold" }); registerFont("/var/task/fonts/open-sans.regular.ttf", { family: "OpenSans" }); console.log("1. Created canvas"); const canvas = createCanvas(1118, 806); let image = await loadImage(`${images_folder}/${Math.floor(Math.random() * (files_length - 1 + 1)) + 1}.jpg`); let textBlockHeight = 0; console.log("2. Image loaded"); const canvasWidth = canvas.width; const canvasHeight = canvas.height; const aspectRatio = image.width / image.height; console.log("3. Defined ASPECT RATIO",) let drawWidth, drawHeight; if (image.width > image.height) { // Landscape orientation: fit by width drawWidth = canvasWidth; drawHeight = canvasWidth / aspectRatio; } else { // Portrait orientation: fit by height drawHeight = canvasHeight; drawWidth = canvasHeight * aspectRatio; } // Center the image const x = (canvasWidth - drawWidth) / 2; const y = (canvasHeight - drawHeight) / 2; const ctx = canvas.getContext("2d"); console.log("4. Centered Image") ctx.drawImage(image, x, y, drawWidth, drawHeight);
Schließlich wird die Leinwand in einen PNG-Puffer umgewandelt.
console.log("4.1 Text splitting"); if (splitText.length === 1) { const isItWiderThanHalf = ctx.measureText(splitText[0]).width > ((canvasWidth / 2) + 160); const wordCount = splitText[0].split(" ").length; if (isItWiderThanHalf && wordCount > 4) { const refactored_line = splitText[0].split(" ").reduce((acc, curr, i) => { if (i % 3 === 0) { acc.push([curr]); } else { acc[acc.length - 1].push(curr); } return acc; }, []).map((item) => item.join(" ")); refactored_line[1] = "[s]" + refactored_line[1] + "[s]"; splitText = refactored_line } } let tagline = splitText.filter(item => item !== '' && item !== '[br]' && item !== '[s]' && item !== '[/s]' && item !== '[s]'); let headlineSentences = []; let lineCounter = { total: 0, reduced_line_counter: 0, reduced_lines_indexes: [] } console.log("4.2 Tagline Preparation", tagline); for (let i = 0; i < tagline.length; i++) { let line = tagline[i]; if (line.includes("[s]") || line.includes("[/s]")) { const finalLine = line.split(/(\[s\]|\[\/s\])/).filter(item => item !== '' && item !== '[s]' && item !== '[/s]'); const lineWidth = ctx.measureText(finalLine[0]).width const halfOfWidth = canvasWidth / 2; if (lineWidth > halfOfWidth && finalLine[0]) { let splitted_text = finalLine[0].split(" ").reduce((acc, curr, i) => { const modulus = finalLine[0].split(" ").length >= 5 ? 3 : 2; if (i % modulus === 0) { acc.push([curr]); } else { acc[acc.length - 1].push(curr); } return acc; }, []); let splitted_text_arr = [] splitted_text.forEach((item, _) => { let lineText = item.join(" "); item = lineText splitted_text_arr.push(item) }) headlineSentences[i] = splitted_text_arr[0] + '/s/' if (splitted_text_arr[1]) { headlineSentences.splice(i + 1, 0, splitted_text_arr[1] + '/s/') } } else { headlineSentences.push("/s/" + finalLine[0] + "/s/") } } else { headlineSentences.push(line) } } console.log("5. Drawing text on canvas", headlineSentences); const headlineSentencesLength = headlineSentences.length; let textHeightAccumulator = 0; for (let i = 0; i < headlineSentencesLength; i++) { headlineSentences = headlineSentences.filter(item => item !== '/s/'); const nextLine = headlineSentences[i + 1]; if (nextLine && /^\s*$/.test(nextLine)) { headlineSentences.splice(i + 1, 1); } let line = headlineSentences[i]; if (!line) continue; let lineText = line.trim(); let textY; ctx.font = " 72px OpenSans"; const cleanedUpLine = lineText.includes('/s/') ? lineText.replace(/\s+/g, ' ') : lineText; const lineWidth = ctx.measureText(cleanedUpLine).width const halfOfWidth = canvasWidth / 2; lineCounter.total += 1 const isLineTooLong = lineWidth > (halfOfWidth + 50); if (isLineTooLong) { if (lineText.includes(':')) { const split_line_arr = lineText.split(":") if (split_line_arr.length > 1) { lineText = split_line_arr[0] + ":"; if (split_line_arr[1]) { headlineSentences.splice(i + 1, 0, split_line_arr[1]) } } } ctx.font = "52px OpenSans"; lineCounter.reduced_line_counter += 1 if (i === 0 && headlineSentencesLength === 2) { is2LinesAndPreviewsWasReduced = true } lineCounter.reduced_lines_indexes.push(i) } else { if (i === 0 && headlineSentencesLength === 2) { is2LinesAndPreviewsWasReduced = false } } if (lineText.includes("/s/")) { lineText = lineText.replace(/\/s\//g, ""); if (headlineSentencesLength > (i + 1) && i < headlineSentencesLength - 1 && nextLine) { if (nextLine.slice(0, 2).includes("?") && nextLine.length < 3) { lineText += '?'; headlineSentences.pop(); } if (nextLine.slice(0, 2).includes(":")) { lineText += ':'; headlineSentences[i + 1] = headlineSentences[i + 1].slice(2); } } let lineWidth = ctx.measureText(lineText).width let assignedSize; if (lineText.split(" ").length <= 2) { if (lineWidth > (canvasWidth / 2.35)) { ctx.font = "84px OpenSansBold"; assignedSize = 80 } else { ctx.font = "84px OpenSansBold"; assignedSize = 84 } } else { if (i === headlineSentencesLength - 1 && lineWidth < (canvasWidth / 2.5) && lineText.split(" ").length === 3) { ctx.font = "84px OpenSansBold"; assignedSize = 84 } else { lineCounter.reduced_line_counter += 1; ctx.font = "52px OpenSansBold"; assignedSize = 52 } lineCounter.reduced_lines_indexes.push(i) } lineWidth = ctx.measureText(lineText).width if (lineWidth > (canvasWidth / 2) + 120) { if (assignedSize === 84) { ctx.font = "72px OpenSansBold"; } else if (assignedSize === 80) { ctx.font = "64px OpenSansBold"; textHeightAccumulator += 8 } else { ctx.font = "52px OpenSansBold"; } } } else { const textWidth = ctx.measureText(lineText).width if (textWidth > (canvasWidth / 2)) { ctx.font = "44px OpenSans"; textHeightAccumulator += 12 } else if (i === headlineSentencesLength - 1) { textHeightAccumulator += 12 } } ctx.fillStyle = "white"; ctx.textAlign = "center"; const textHeight = ctx.measureText(lineText).emHeightAscent; textHeightAccumulator += textHeight; if (headlineSentencesLength == 3) { textY = (canvasHeight / 3) } else if (headlineSentencesLength == 4) { textY = (canvasHeight / 3.5) } else { textY = 300 } textY += textHeightAccumulator; const words = lineText.split(' '); console.log("words", words, lineText, headlineSentences) const capitalizedWords = words.map(word => { if (word.length > 0) return word[0].toUpperCase() + word.slice(1) return word }); const capitalizedLineText = capitalizedWords.join(' '); ctx.fillText(capitalizedLineText, canvasWidth / 2, textY); }
Nach erfolgreicher Generierung des Bildpuffers wird die Funktion uploadImageToWordpress aufgerufen.
Diese Funktion übernimmt die schwere Arbeit, das Bild mithilfe seiner REST-API an WordPress zu senden, indem das Bild für WordPress kodiert wird.
Die Funktion bereitet zunächst den Slogan für die Verwendung als Dateiname vor, indem sie Leerzeichen und Sonderzeichen bereinigt:
const buffer = canvas.toBuffer("image/png"); return buffer;
Der Bildpuffer wird dann in ein Blob-Objekt umgewandelt, um es mit der WordPress-API kompatibel zu machen:
const file = new Blob([buffer], { type: "image/png" });
Vorbereiten der API-Anfrage Unter Verwendung des codierten Bilds und der Tagline erstellt die Funktion ein FormData-Objekt und ich füge optionale Metadaten hinzu, wie z. B. alt_text für die Barrierefreiheit und eine Beschriftung für den Kontext.
const createSlug = (string) => { return string.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, ''); }; const image_name = createSlug(tagline);
Zur Authentifizierung werden der Benutzername und das Anwendungspasswort in Base64 codiert und in die Anforderungsheader aufgenommen:
formData.append("file", file, image_name + ".png"); formData.append("alt_text", `${tagline} image`); formData.append("caption", "Uploaded via API");
Senden des Bildes Eine POST-Anfrage wird mit den vorbereiteten Daten und Headern an den WordPress-Medienendpunkt gestellt und nach dem Warten auf die Antwort überprüfe ich, ob Erfolg oder Fehler vorliegen.
const credentials = `${username}:${app_password}`; const base64Encoded = Buffer.from(credentials).toString("base64");
Bei Erfolg gebe ich dieselbe Medienreaktion im Lambda zurück.
So sieht mein Lambda am Ende aus.
const response = await fetch(`${wordpress_url}wp-json/wp/v2/media`, { method: "POST", headers: { Authorization: "Basic " + base64Encoded, contentType: "multipart/form-data", }, body: formData, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Error uploading image: ${response.statusText}, Details: ${errorText}`); }
Dies ist ein Beispielbild, das von meinem Skript erstellt wurde. Es wird nicht in der Produktion verwendet, sondern nur für dieses Beispiel mit generischen Assets erstellt.
Es ist einige Zeit vergangen und alle sind froh, dass wir keine schäbigen oder leer aussehenden Artikel ohne Bilder mehr haben, dass die Bilder denen, die der Designer erstellt hat, sehr nahe kommen, der Designer ist froh, dass er sich nur noch auf sie konzentrieren kann Gestaltung für andere Marketingmaßnahmen im gesamten Unternehmen.
Aber dann trat ein neues Problem auf: Manchmal gefiel dem Kunden das generierte Bild nicht und er bat mich, mein Skript zu starten, um ein neues für einen bestimmten Beitrag zu erstellen.
Das brachte mich zu meiner nächsten Nebenquest: Ein Wordpress-Plugin zum manuellen Generieren eines hervorgehobenen Bildes mithilfe künstlicher Intelligenz für einen bestimmten Beitrag
Das obige ist der detaillierte Inhalt vonAWS JavaScript WordPress = Unterhaltsame Content-Automatisierungsstrategien mit künstlicher Intelligenz. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!