From 99d73dd56a32d0af59c6c65c5c66cae3e56d8c15 Mon Sep 17 00:00:00 2001
From: Linnea Huxford <7308162+mslinnea@users.noreply.github.com>
Date: Sat, 16 Nov 2024 07:50:58 -0800
Subject: [PATCH 1/4] adds alt text icon
---
package-lock.json | 2 ++
package.json | 2 ++
src/components/altText/index.js | 0
src/components/blockControls/index.js | 21 +++++++++++++++++++
src/components/imageControls/index.js | 19 +++++++++++++++++
.../index.js | 0
src/index.js | 3 ++-
7 files changed, 46 insertions(+), 1 deletion(-)
create mode 100644 src/components/altText/index.js
create mode 100644 src/components/blockControls/index.js
create mode 100644 src/components/imageControls/index.js
rename src/components/{settingsScreen => settingsPanel}/index.js (100%)
diff --git a/package-lock.json b/package-lock.json
index 2cd4ad9..8983925 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,8 @@
"@wordpress/components": "^28.0.3",
"@wordpress/data": "^10.0.2",
"@wordpress/editor": "^14.0.8",
+ "@wordpress/hooks": "^4.11.0",
+ "@wordpress/i18n": "^5.11.0",
"@wordpress/plugins": "^7.0.3",
"mustache": "^4.2.0",
"react": "^18.3.1",
diff --git a/package.json b/package.json
index c8151fb..da8d4a8 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,8 @@
"@wordpress/components": "^28.0.3",
"@wordpress/data": "^10.0.2",
"@wordpress/editor": "^14.0.8",
+ "@wordpress/hooks": "^4.11.0",
+ "@wordpress/i18n": "^5.11.0",
"@wordpress/plugins": "^7.0.3",
"mustache": "^4.2.0",
"react": "^18.3.1",
diff --git a/src/components/altText/index.js b/src/components/altText/index.js
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/blockControls/index.js b/src/components/blockControls/index.js
new file mode 100644
index 0000000..1df1159
--- /dev/null
+++ b/src/components/blockControls/index.js
@@ -0,0 +1,21 @@
+import { addFilter } from '@wordpress/hooks';
+import ImageControls from '../imageControls';
+
+const addToolbar = (BlockEdit) => (props) => {
+ const { name } = props;
+ if (name === 'core/image') {
+ return (
+ <>
+
+
+ >
+ );
+ }
+ return ;
+};
+
+addFilter(
+ 'editor.BlockEdit',
+ 'ai-seo-tools/add-block-toolbar',
+ addToolbar,
+);
diff --git a/src/components/imageControls/index.js b/src/components/imageControls/index.js
new file mode 100644
index 0000000..cbf1a92
--- /dev/null
+++ b/src/components/imageControls/index.js
@@ -0,0 +1,19 @@
+import { __ } from '@wordpress/i18n';
+import { Icon, ToolbarButton } from '@wordpress/components';
+import { useState } from 'react';
+import { BlockControls } from '@wordpress/block-editor';
+
+export default function ImageControls() {
+ const [inProgress, setInProgress] = useState(false);
+ return (
+
+ console.log('alt text clicked')}
+ />
+
+ );
+}
diff --git a/src/components/settingsScreen/index.js b/src/components/settingsPanel/index.js
similarity index 100%
rename from src/components/settingsScreen/index.js
rename to src/components/settingsPanel/index.js
diff --git a/src/index.js b/src/index.js
index 521a2a6..59c27f8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1 +1,2 @@
-import './components/settingsScreen';
+import './components/settingsPanel';
+import './components/blockControls';
From e3f6be2ba1c64b5ac3c3b350f8751826329a026a Mon Sep 17 00:00:00 2001
From: Linnea Huxford <7308162+mslinnea@users.noreply.github.com>
Date: Sat, 16 Nov 2024 08:55:14 -0800
Subject: [PATCH 2/4] integrate AI for alt text
---
src/components/altText/index.js | 66 +++++++++++++++++++++++++++
src/components/blockControls/index.js | 4 +-
src/components/imageControls/index.js | 20 +++++++-
src/helpers/image-helpers.js | 30 ++++++++++++
4 files changed, 116 insertions(+), 4 deletions(-)
create mode 100644 src/helpers/image-helpers.js
diff --git a/src/components/altText/index.js b/src/components/altText/index.js
index e69de29..d3cc771 100644
--- a/src/components/altText/index.js
+++ b/src/components/altText/index.js
@@ -0,0 +1,66 @@
+import { __ } from '@wordpress/i18n';
+import { getMimeType, getBase64Image } from '../../helpers/image-helpers';
+
+const { enums, helpers, store: aiStore } = window.aiServices.ai;
+const AI_CAPABILITIES = [enums.AiCapability.MULTIMODAL_INPUT, enums.AiCapability.TEXT_GENERATION];
+
+export default async function generateAltText(props, setInProgress, service) {
+ const { attributes, setAttributes } = props;
+
+ // Check if AI service is available
+ if (!service) {
+ console.error('AI service is not available.');
+ return;
+ }
+
+ // Check if image URL is available
+ if (!attributes.url) {
+ console.error('Image URL is missing.');
+ return;
+ }
+
+ setInProgress(true);
+
+ // Fetch the image data and prepare the request
+ try {
+ const mimeType = getMimeType(attributes.url);
+ const base64Image = await getBase64Image(attributes.url);
+
+ // Call the AI service to generate alt text
+ const candidates = await service.generateText(
+ {
+ role: enums.ContentRole.USER,
+ parts: [
+ {
+ text: __(
+ 'Create a brief description of what the following image shows, suitable as alternative text for screen readers.',
+ 'ai-seo-tools',
+ ),
+ },
+ {
+ inlineData: {
+ mimeType,
+ data: base64Image,
+ },
+ },
+ ],
+ },
+ {
+ feature: 'ai-seo-tools-alt-text',
+ capabilities: AI_CAPABILITIES,
+ },
+ );
+
+ // Extract the generated alt text
+ const alt = helpers
+ .getTextFromContents(helpers.getCandidateContents(candidates))
+ .replaceAll('\n\n\n\n', '\n\n');
+
+ // Set the alt text attribute
+ setAttributes({ alt });
+ } catch (error) {
+ console.error('Error generating alt text:', error);
+ } finally {
+ setInProgress(false);
+ }
+}
diff --git a/src/components/blockControls/index.js b/src/components/blockControls/index.js
index 1df1159..18c9acf 100644
--- a/src/components/blockControls/index.js
+++ b/src/components/blockControls/index.js
@@ -1,7 +1,7 @@
import { addFilter } from '@wordpress/hooks';
import ImageControls from '../imageControls';
-const addToolbar = (BlockEdit) => (props) => {
+const addToolbar = (BlockEdit) => function(props) {
const { name } = props;
if (name === 'core/image') {
return (
@@ -11,7 +11,7 @@ const addToolbar = (BlockEdit) => (props) => {
>
);
}
- return ;
+ return ;
};
addFilter(
diff --git a/src/components/imageControls/index.js b/src/components/imageControls/index.js
index cbf1a92..ce16dec 100644
--- a/src/components/imageControls/index.js
+++ b/src/components/imageControls/index.js
@@ -2,9 +2,25 @@ import { __ } from '@wordpress/i18n';
import { Icon, ToolbarButton } from '@wordpress/components';
import { useState } from 'react';
import { BlockControls } from '@wordpress/block-editor';
+import generateAltText from '../altText/index';
+import {useSelect} from "@wordpress/data";
-export default function ImageControls() {
+const { enums, helpers, store: aiStore } = window.aiServices.ai;
+const AI_CAPABILITIES = [enums.AiCapability.MULTIMODAL_INPUT, enums.AiCapability.TEXT_GENERATION];
+
+export default function ImageControls(props) {
const [inProgress, setInProgress] = useState(false);
+ const service = useSelect((select) => select(aiStore)
+ .getAvailableService(
+ { capabilities: AI_CAPABILITIES },
+ ));
+
+ const handleClick = async () => {
+ setInProgress(true);
+ await generateAltText(props, setInProgress, service);
+ setInProgress(false);
+ };
+
return (
console.log('alt text clicked')}
+ onClick={handleClick}
/>
);
diff --git a/src/helpers/image-helpers.js b/src/helpers/image-helpers.js
new file mode 100644
index 0000000..41a5fb3
--- /dev/null
+++ b/src/helpers/image-helpers.js
@@ -0,0 +1,30 @@
+export function getMimeType(url) {
+ const extension = url.split('.').pop().toLowerCase();
+ switch (extension) {
+ case 'png':
+ return 'image/png';
+ case 'gif':
+ return 'image/gif';
+ case 'avif':
+ return 'image/avif';
+ case 'webp':
+ return 'image/webp';
+ case 'jpg':
+ case 'jpeg':
+ default:
+ return 'image/jpeg';
+ }
+}
+
+export async function getBase64Image(url) {
+ const data = await fetch(url);
+ const blob = await data.blob();
+ return new Promise((resolve) => {
+ const reader = new FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = () => {
+ const base64data = reader.result;
+ resolve(base64data);
+ };
+ });
+}
From b731b28f68fd9379441f07016ffa3471f26cccc1 Mon Sep 17 00:00:00 2001
From: Linnea Huxford <7308162+mslinnea@users.noreply.github.com>
Date: Sat, 16 Nov 2024 20:50:48 -0800
Subject: [PATCH 3/4] add image alt text feature
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 28b6b81..bcecc42 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ be compatible with all AI services supported by the [AI Services Plugin](https:/
- Generate Meta Description
- Generate Meta Keywords
+- Generate Image Alt Text via Image Block toolbar button
# Demo with OpenAI gpt-3.5-turbo model
From bc5fa05b2880e7622fda400750b7e1b2925c1016 Mon Sep 17 00:00:00 2001
From: Linnea Huxford <7308162+mslinnea@users.noreply.github.com>
Date: Sat, 16 Nov 2024 20:57:51 -0800
Subject: [PATCH 4/4] lint
---
package-lock.json | 8 +++++---
package.json | 1 +
src/components/altText/index.js | 5 ++++-
src/components/blockControls/index.js | 16 +++++++++-------
src/components/imageControls/index.js | 10 +++++-----
5 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 8983925..ff97952 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,7 @@
"dependencies": {
"@alleyinteractive/block-editor-tools": "^0.5.1",
"@wordpress/api-fetch": "^7.0.1",
+ "@wordpress/block-editor": "^14.7.0",
"@wordpress/components": "^28.0.3",
"@wordpress/data": "^10.0.2",
"@wordpress/editor": "^14.0.8",
@@ -5819,9 +5820,9 @@
}
},
"node_modules/@wordpress/block-editor": {
- "version": "14.6.0",
- "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-14.6.0.tgz",
- "integrity": "sha512-Pala/+i7izwB7QJbr6tVfg2XR0p6wb2biIoF2DjzNh+draoTjPtxCbZQrOJSd8hfyXQOOQPuLiAGdsq50PEZsA==",
+ "version": "14.7.0",
+ "resolved": "https://registry.npmjs.org/@wordpress/block-editor/-/block-editor-14.7.0.tgz",
+ "integrity": "sha512-19PJE3rt0xTPhP/clFuL2qwJ00O03xyvw2/Jg1bFFvaLydm27KalO/7aiMJSDpOklT5w5xjWYwBKw6yl6IYJIA==",
"license": "GPL-2.0-or-later",
"dependencies": {
"@babel/runtime": "7.25.7",
@@ -5851,6 +5852,7 @@
"@wordpress/keycodes": "*",
"@wordpress/notices": "*",
"@wordpress/preferences": "*",
+ "@wordpress/priority-queue": "*",
"@wordpress/private-apis": "*",
"@wordpress/rich-text": "*",
"@wordpress/style-engine": "*",
diff --git a/package.json b/package.json
index da8d4a8..6be0e80 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"dependencies": {
"@alleyinteractive/block-editor-tools": "^0.5.1",
"@wordpress/api-fetch": "^7.0.1",
+ "@wordpress/block-editor": "^14.7.0",
"@wordpress/components": "^28.0.3",
"@wordpress/data": "^10.0.2",
"@wordpress/editor": "^14.0.8",
diff --git a/src/components/altText/index.js b/src/components/altText/index.js
index d3cc771..44c59b6 100644
--- a/src/components/altText/index.js
+++ b/src/components/altText/index.js
@@ -1,7 +1,7 @@
import { __ } from '@wordpress/i18n';
import { getMimeType, getBase64Image } from '../../helpers/image-helpers';
-const { enums, helpers, store: aiStore } = window.aiServices.ai;
+const { enums, helpers } = window.aiServices.ai;
const AI_CAPABILITIES = [enums.AiCapability.MULTIMODAL_INPUT, enums.AiCapability.TEXT_GENERATION];
export default async function generateAltText(props, setInProgress, service) {
@@ -9,12 +9,14 @@ export default async function generateAltText(props, setInProgress, service) {
// Check if AI service is available
if (!service) {
+ // eslint-disable-next-line no-console
console.error('AI service is not available.');
return;
}
// Check if image URL is available
if (!attributes.url) {
+ // eslint-disable-next-line no-console
console.error('Image URL is missing.');
return;
}
@@ -59,6 +61,7 @@ export default async function generateAltText(props, setInProgress, service) {
// Set the alt text attribute
setAttributes({ alt });
} catch (error) {
+ // eslint-disable-next-line no-console
console.error('Error generating alt text:', error);
} finally {
setInProgress(false);
diff --git a/src/components/blockControls/index.js b/src/components/blockControls/index.js
index 18c9acf..045584a 100644
--- a/src/components/blockControls/index.js
+++ b/src/components/blockControls/index.js
@@ -1,15 +1,17 @@
import { addFilter } from '@wordpress/hooks';
import ImageControls from '../imageControls';
-const addToolbar = (BlockEdit) => function(props) {
- const { name } = props;
+// eslint-disable-next-line func-names
+const addToolbar = (BlockEdit) => function (props) {
+ // eslint-disable-next-line react/prop-types
+ const { name } = props;
if (name === 'core/image') {
return (
- <>
-
-
- >
- );
+ <>
+
+
+ >
+ );
}
return ;
};
diff --git a/src/components/imageControls/index.js b/src/components/imageControls/index.js
index ce16dec..886dcbc 100644
--- a/src/components/imageControls/index.js
+++ b/src/components/imageControls/index.js
@@ -1,19 +1,19 @@
import { __ } from '@wordpress/i18n';
-import { Icon, ToolbarButton } from '@wordpress/components';
+import { ToolbarButton } from '@wordpress/components';
import { useState } from 'react';
import { BlockControls } from '@wordpress/block-editor';
+import { useSelect } from '@wordpress/data';
import generateAltText from '../altText/index';
-import {useSelect} from "@wordpress/data";
-const { enums, helpers, store: aiStore } = window.aiServices.ai;
+const { enums, store: aiStore } = window.aiServices.ai;
const AI_CAPABILITIES = [enums.AiCapability.MULTIMODAL_INPUT, enums.AiCapability.TEXT_GENERATION];
export default function ImageControls(props) {
const [inProgress, setInProgress] = useState(false);
const service = useSelect((select) => select(aiStore)
- .getAvailableService(
+ .getAvailableService(
{ capabilities: AI_CAPABILITIES },
- ));
+ ));
const handleClick = async () => {
setInProgress(true);