datocms-plugin-alt-text-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,4 @@
1
+ # Alt Text AI
2
+
3
+ Alt Text AI is a plugin that serves as an integration bridge between DatoCMS and Alt Text AI. Its allow for the generation of alt text directly within the DatoCMS dashboard using the Alt Text AI API. Leveraging AI, it swiftly produces contextually relevant alt text for images: enhancing SEO, accessibility, and the overall user experience.
4
+
package/docs/cover.png ADDED
Binary file
package/docs/demo.mp4 ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "datocms-plugin-alt-text-ai",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "homepage": "https://github.com/marcelofinamorvieira/datocms-plugin-alt-text-ai",
6
+ "description": "A plugin that creates daily and weekly backups of your main environment on DatoCMS automatically.",
7
+ "keywords": [
8
+ "datocms",
9
+ "datocms-plugin",
10
+ "ai",
11
+ "alt",
12
+ "media",
13
+ "assets"
14
+ ],
15
+ "datoCmsPlugin": {
16
+ "title": "Alt Text AI",
17
+ "previewImage": "docs/demo.mp4",
18
+ "coverImage": "docs/cover.png",
19
+ "entryPoint": "build/index.html",
20
+ "permissions": [
21
+ "currentUserAccessToken"
22
+ ]
23
+ },
24
+ "dependencies": {
25
+ "@datocms/cma-client-browser": "^2.2.6",
26
+ "@types/node": "^18.18.6",
27
+ "@types/react": "^18.2.31",
28
+ "@types/react-dom": "^18.2.14",
29
+ "datocms-plugin-sdk": "^1.0.0",
30
+ "datocms-react-ui": "^1.0.0",
31
+ "lodash": "^4.17.21",
32
+ "react": "^18.2.0",
33
+ "react-dom": "^18.2.0",
34
+ "react-scripts": "5.0.1",
35
+ "typescript": "^4.9.5"
36
+ },
37
+ "scripts": {
38
+ "start": "cross-env BROWSER='none' PUBLIC_URL='/' react-scripts start",
39
+ "build": "cross-env PUBLIC_URL='.' react-scripts build",
40
+ "test": "react-scripts test",
41
+ "eject": "react-scripts eject",
42
+ "prepublishOnly": "npm run build"
43
+ },
44
+ "eslintConfig": {
45
+ "extends": [
46
+ "react-app"
47
+ ]
48
+ },
49
+ "browserslist": {
50
+ "production": [
51
+ ">0.2%",
52
+ "not dead",
53
+ "not op_mini all"
54
+ ],
55
+ "development": [
56
+ "last 1 chrome version",
57
+ "last 1 firefox version",
58
+ "last 1 safari version"
59
+ ]
60
+ },
61
+ "devDependencies": {
62
+ "@types/lodash": "^4.14.200",
63
+ "cross-env": "^7.0.3"
64
+ }
65
+ }
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ </head>
7
+ <body>
8
+ <noscript>You need to enable JavaScript to run this app.</noscript>
9
+ <div id="root"></div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,3 @@
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
@@ -0,0 +1,102 @@
1
+ import { Client, buildClient } from '@datocms/cma-client-browser';
2
+ import { RenderFieldExtensionCtx } from 'datocms-plugin-sdk';
3
+ import { Button, Canvas, Spinner } from 'datocms-react-ui';
4
+ import { isArray } from 'lodash';
5
+ import get from 'lodash/get';
6
+ import { useState } from 'react';
7
+
8
+ type PropTypes = {
9
+ ctx: RenderFieldExtensionCtx;
10
+ };
11
+
12
+ async function fetchAlt(
13
+ apiKey: string,
14
+ client: Client,
15
+ asset: Record<string, string>
16
+ ) {
17
+ const { url } = await client.uploads.find(asset.upload_id);
18
+
19
+ const result = await (
20
+ await fetch('https://alttext.ai/api/v1/images', {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/json',
24
+ 'X-API-Key': apiKey ?? '',
25
+ },
26
+ body: JSON.stringify({
27
+ image: {
28
+ url,
29
+ },
30
+ }),
31
+ })
32
+ ).json();
33
+
34
+ return result;
35
+ }
36
+
37
+ async function generateAlts(
38
+ currentFieldValue: unknown,
39
+ ctx: RenderFieldExtensionCtx,
40
+ setIsLoading: React.Dispatch<React.SetStateAction<boolean>>
41
+ ) {
42
+ setIsLoading(true);
43
+ try {
44
+ const client = buildClient({ apiToken: ctx.currentUserAccessToken || '' });
45
+ const isGallery = isArray(currentFieldValue);
46
+ if (isGallery) {
47
+ const newAssets = [];
48
+ for (const asset of currentFieldValue) {
49
+ const result = await fetchAlt(
50
+ ctx.plugin.attributes.parameters.apiKey as string,
51
+ client,
52
+ asset
53
+ );
54
+
55
+ asset.alt = result.alt_text;
56
+ newAssets.push(asset);
57
+ }
58
+ ctx.setFieldValue(ctx.fieldPath, newAssets);
59
+ } else {
60
+ const assetValue = currentFieldValue as Record<string, string>;
61
+
62
+ const result = await fetchAlt(
63
+ ctx.plugin.attributes.parameters.apiKey as string,
64
+ client,
65
+ assetValue
66
+ );
67
+
68
+ assetValue.alt = result.alt_text;
69
+ ctx.setFieldValue(ctx.fieldPath, assetValue);
70
+ }
71
+ } catch (error) {
72
+ console.log(error);
73
+ }
74
+
75
+ setIsLoading(false);
76
+ }
77
+
78
+ const AltTextAIButton = ({ ctx }: PropTypes) => {
79
+ const [isLoading, setIsLoading] = useState(false);
80
+ const currentFieldValue = get(ctx.formValues, ctx.fieldPath);
81
+ const isGallery = isArray(currentFieldValue);
82
+ const isGalleryWithItems =
83
+ isArray(currentFieldValue) && currentFieldValue.length;
84
+
85
+ return (
86
+ <Canvas ctx={ctx}>
87
+ {(isGalleryWithItems || (!isGallery && !!currentFieldValue)) && (
88
+ <Button
89
+ fullWidth
90
+ disabled={isLoading}
91
+ onClick={() => {
92
+ generateAlts(currentFieldValue, ctx, setIsLoading);
93
+ }}
94
+ >
95
+ Generate Alt Text {isLoading && <Spinner size={24} />}
96
+ </Button>
97
+ )}
98
+ </Canvas>
99
+ );
100
+ };
101
+
102
+ export default AltTextAIButton;
@@ -0,0 +1,66 @@
1
+ import { RenderConfigScreenCtx } from 'datocms-plugin-sdk';
2
+ import {
3
+ Button,
4
+ Canvas,
5
+ ContextInspector,
6
+ Spinner,
7
+ TextField,
8
+ } from 'datocms-react-ui';
9
+ import s from './styles.module.css';
10
+ import { useState } from 'react';
11
+
12
+ type Props = {
13
+ ctx: RenderConfigScreenCtx;
14
+ };
15
+
16
+ async function saveApiKey(
17
+ ctx: RenderConfigScreenCtx,
18
+ apiKey: string,
19
+ setIsLoading: React.Dispatch<React.SetStateAction<boolean>>
20
+ ) {
21
+ setIsLoading(true);
22
+ await ctx.updatePluginParameters({ apiKey });
23
+ setIsLoading(false);
24
+ ctx.customToast({
25
+ type: 'notice',
26
+ message: 'API Key Saved!',
27
+ dismissOnPageChange: true,
28
+ dismissAfterTimeout: 5000,
29
+ });
30
+ }
31
+
32
+ export default function ConfigScreen({ ctx }: Props) {
33
+ const [apiKey, setApiKey] = useState(
34
+ (ctx.plugin.attributes.parameters.apiKey as string) ?? ''
35
+ );
36
+ const [isLoading, setIsLoading] = useState(false);
37
+
38
+ return (
39
+ <Canvas ctx={ctx}>
40
+ <div className={s.form}>
41
+ <TextField
42
+ required
43
+ name="altTextAPIKey"
44
+ id="altTextAPIKey"
45
+ label="Alt Text API Key"
46
+ value={apiKey}
47
+ onChange={(newValue) => setApiKey(newValue)}
48
+ />
49
+ <p>
50
+ You can get your API key by going to{' '}
51
+ <a href="https://alttext.ai/account/api_keys" target="_blank">
52
+ https://alttext.ai/account/api_keys
53
+ </a>
54
+ </p>
55
+ </div>
56
+
57
+ <Button
58
+ disabled={isLoading}
59
+ fullWidth
60
+ onClick={() => saveApiKey(ctx, apiKey, setIsLoading)}
61
+ >
62
+ Save {isLoading && <Spinner size={24} />}
63
+ </Button>
64
+ </Canvas>
65
+ );
66
+ }
@@ -0,0 +1,7 @@
1
+ .inspector {
2
+ margin-top: var(--spacing-l);
3
+ }
4
+
5
+ .form {
6
+ margin-bottom: 1rem;
7
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,27 @@
1
+ import { render } from './utils/render';
2
+ import ConfigScreen from './entrypoints/ConfigScreen';
3
+ import 'datocms-react-ui/styles.css';
4
+ import { connect, Field, RenderFieldExtensionCtx } from 'datocms-plugin-sdk';
5
+ import AltTextAIButton from './entrypoints/AltTextAIButton';
6
+
7
+ connect({
8
+ renderConfigScreen(ctx) {
9
+ return render(<ConfigScreen ctx={ctx} />);
10
+ },
11
+ overrideFieldExtensions(field: Field) {
12
+ if (
13
+ field.attributes.field_type === 'gallery' ||
14
+ field.attributes.field_type === 'file'
15
+ ) {
16
+ return {
17
+ addons: [{ id: 'altTextAI' }],
18
+ };
19
+ }
20
+ },
21
+ renderFieldExtension(fieldExtensionId: string, ctx: RenderFieldExtensionCtx) {
22
+ switch (fieldExtensionId) {
23
+ case 'altTextAI':
24
+ return render(<AltTextAIButton ctx={ctx} />);
25
+ }
26
+ },
27
+ });
@@ -0,0 +1 @@
1
+ /// <reference types="react-scripts" />
@@ -0,0 +1,9 @@
1
+ import React, { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+
4
+ const container = document.getElementById('root');
5
+ const root = createRoot(container!);
6
+
7
+ export function render(component: React.ReactNode): void {
8
+ root.render(<StrictMode>{component}</StrictMode>);
9
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "esModuleInterop": true,
12
+ "allowSyntheticDefaultImports": true,
13
+ "strict": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "module": "esnext",
17
+ "moduleResolution": "node",
18
+ "resolveJsonModule": true,
19
+ "isolatedModules": true,
20
+ "noEmit": true,
21
+ "jsx": "react-jsx"
22
+ },
23
+ "include": [
24
+ "src"
25
+ ]
26
+ }