@jhb.software/payload-alt-text-plugin 0.3.1 → 0.4.1

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.
Files changed (48) hide show
  1. package/README.md +86 -0
  2. package/dist/components/AltTextHealthWidget.d.ts +2 -0
  3. package/dist/components/AltTextHealthWidget.js +199 -0
  4. package/dist/components/AltTextHealthWidget.js.map +1 -0
  5. package/dist/components/BulkGenerateAltTextsButton.js +1 -1
  6. package/dist/components/BulkGenerateAltTextsButton.js.map +1 -1
  7. package/dist/components/GenerateAltTextButton.js +1 -1
  8. package/dist/components/GenerateAltTextButton.js.map +1 -1
  9. package/dist/endpoints/altTextHealth.d.ts +3 -0
  10. package/dist/endpoints/altTextHealth.js +30 -0
  11. package/dist/endpoints/altTextHealth.js.map +1 -0
  12. package/dist/endpoints/bulkGenerateAltTexts.d.ts +2 -1
  13. package/dist/endpoints/bulkGenerateAltTexts.js +89 -73
  14. package/dist/endpoints/bulkGenerateAltTexts.js.map +1 -1
  15. package/dist/endpoints/generateAltText.d.ts +8 -2
  16. package/dist/endpoints/generateAltText.js +112 -75
  17. package/dist/endpoints/generateAltText.js.map +1 -1
  18. package/dist/exports/server.d.ts +3 -0
  19. package/dist/exports/server.js +4 -0
  20. package/dist/exports/server.js.map +1 -0
  21. package/dist/hooks/revalidateAltTextHealth.d.ts +3 -0
  22. package/dist/hooks/revalidateAltTextHealth.js +32 -0
  23. package/dist/hooks/revalidateAltTextHealth.js.map +1 -0
  24. package/dist/plugin.js +86 -6
  25. package/dist/plugin.js.map +1 -1
  26. package/dist/translations/de.js +11 -1
  27. package/dist/translations/de.js.map +1 -1
  28. package/dist/translations/en.js +11 -1
  29. package/dist/translations/en.js.map +1 -1
  30. package/dist/translations/translation-schema.json +44 -26
  31. package/dist/types/AltTextPluginConfig.d.ts +23 -1
  32. package/dist/types/AltTextPluginConfig.js.map +1 -1
  33. package/dist/utilities/altTextHealth.d.ts +37 -0
  34. package/dist/utilities/altTextHealth.js +150 -0
  35. package/dist/utilities/altTextHealth.js.map +1 -0
  36. package/dist/utilities/altTextHealthCache.d.ts +11 -0
  37. package/dist/utilities/altTextHealthCache.js +8 -0
  38. package/dist/utilities/altTextHealthCache.js.map +1 -0
  39. package/dist/utilities/altTextHealthWidgetDisplay.d.ts +6 -0
  40. package/dist/utilities/altTextHealthWidgetDisplay.js +11 -0
  41. package/dist/utilities/altTextHealthWidgetDisplay.js.map +1 -0
  42. package/dist/utilities/getCollectionLabel.d.ts +2 -0
  43. package/dist/utilities/getCollectionLabel.js +17 -0
  44. package/dist/utilities/getCollectionLabel.js.map +1 -0
  45. package/dist/utilities/summarizeCollection.d.ts +17 -0
  46. package/dist/utilities/summarizeCollection.js +62 -0
  47. package/dist/utilities/summarizeCollection.js.map +1 -0
  48. package/package.json +16 -15
package/README.md CHANGED
@@ -10,6 +10,7 @@ A [Payload CMS](https://payloadcms.com/) plugin that adds AI-powered alt text ge
10
10
  - Automatic keyword extraction for improved admin search
11
11
  - Bulk generation for processing multiple images at once
12
12
  - Full localization support
13
+ - Dashboard health widget with cached coverage insights across all configured upload collections
13
14
 
14
15
 
15
16
  When the plugin is enabled for an upload collection, it will:
@@ -22,6 +23,10 @@ When the plugin is enabled for an upload collection, it will:
22
23
  - It will be used for improving the search of images in the admin panel
23
24
  2. Add a bulk generate button to the collection list view
24
25
  - This button will allow you to generate alt text for multiple images at once
26
+ 3. Register an `Alt text health` dashboard widget
27
+ - The widget is available in Payload's dashboard editor
28
+ - It is added to the default dashboard layout for first-time and reset layouts
29
+ - Results are cached and revalidated when documents in the configured upload collections change
25
30
 
26
31
  ## Installation
27
32
 
@@ -68,6 +73,13 @@ Note: When localization is disabled in your Payload config (default), you need t
68
73
  | `locale` | `string` | No | Locale for alt text generation (required when localization is disabled) |
69
74
  | `maxBulkGenerateConcurrency` | `number` | No | Maximum concurrent API requests for bulk operations (default: 16) |
70
75
  | `fieldsOverride` | `Function` | No | Override the default fields inserted by the plugin |
76
+ | `healthCheck` | `boolean` | No | Enable alt text health tracking: REST endpoint, cache revalidation hooks, and dashboard widget (default: `true`) |
77
+
78
+ ## Dashboard Widget
79
+
80
+ The plugin registers an `Alt text health` dashboard widget that shows alt text coverage across all configured upload collections, with cached queries that revalidate on document changes. Collections with missing alt text show a clickable badge linking to the affected images.
81
+
82
+ Set `healthCheck: false` in the plugin config to disable the REST endpoint, cache revalidation hooks, and dashboard widget. If your project replaces the default dashboard via `admin.components.views.dashboard`, you need to integrate the widget into your custom dashboard yourself.
71
83
 
72
84
  ### Resolvers
73
85
 
@@ -116,6 +128,80 @@ export const customResolver = (): AltTextResolver => ({
116
128
  })
117
129
  ```
118
130
 
131
+ ## REST API Endpoints
132
+
133
+ The plugin registers the following REST API endpoints under `/api/alt-text-plugin/`. All endpoints require authentication by default (configurable via the `access` option).
134
+
135
+ ### `POST /api/alt-text-plugin/generate`
136
+
137
+ Generates alt text for a single image. By default, returns the result without saving it (preview mode). Pass `update: true` to also persist the generated alt text and keywords to the document.
138
+
139
+ **Request body:**
140
+
141
+ | Field | Type | Required | Description |
142
+ | ------------ | ------------------ | -------- | ------------------------------------------------------------------------ |
143
+ | `id` | `string \| number` | Yes | The document ID |
144
+ | `collection` | `string` | Yes | The collection slug |
145
+ | `locale` | `string \| null` | Yes | Target locale (use `null` for non-localized setups) |
146
+ | `update` | `boolean` | No | When `true`, persists the result to the document (default: `false`) |
147
+
148
+ **Response:**
149
+
150
+ ```json
151
+ {
152
+ "id": "abc123",
153
+ "collection": "media",
154
+ "altText": "A canal scene in a European city with historic buildings.",
155
+ "keywords": ["canal", "buildings", "European city"]
156
+ }
157
+ ```
158
+
159
+ ### `POST /api/alt-text-plugin/generate/bulk`
160
+
161
+ Generates and persists alt text for multiple images across all configured locales.
162
+
163
+ **Request body:**
164
+
165
+ | Field | Type | Required | Description |
166
+ | ------------ | -------------------- | -------- | ------------------------------------------------------------------- |
167
+ | `collection` | `string` | Yes | The collection slug |
168
+ | `ids` | `(string \| number)[]` | Yes | Array of document IDs to process |
169
+
170
+ **Response:**
171
+
172
+ ```json
173
+ {
174
+ "updatedDocs": 5,
175
+ "totalDocs": 6,
176
+ "erroredDocs": ["abc789"]
177
+ }
178
+ ```
179
+
180
+ ### `GET /api/alt-text-plugin/health`
181
+
182
+ Returns alt text coverage statistics across all configured collections. Only available when `healthCheck` is enabled.
183
+
184
+ **Response:**
185
+
186
+ ```json
187
+ {
188
+ "checkedAt": "2025-01-01T00:00:00.000Z",
189
+ "collections": [
190
+ {
191
+ "collection": "media",
192
+ "totalDocs": 12,
193
+ "completeDocs": 10,
194
+ "partialDocs": 1,
195
+ "missingDocs": 1,
196
+ "invalidDocIds": ["abc123"]
197
+ }
198
+ ],
199
+ "isLocalized": true,
200
+ "localeCodes": ["en", "de"],
201
+ "errors": []
202
+ }
203
+ ```
204
+
119
205
  ## Roadmap
120
206
 
121
207
  > **Warning**: This plugin is actively evolving and may undergo significant changes. While it is functional, please thoroughly test before using in production environments.
@@ -0,0 +1,2 @@
1
+ import type { WidgetServerProps } from 'payload';
2
+ export declare function AltTextHealthWidget({ req }: WidgetServerProps): Promise<import("react").JSX.Element>;
@@ -0,0 +1,199 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { getAltTextHealthWidgetData } from '../utilities/altTextHealth.js';
3
+ import { getAltTextHealthWidgetDisplayState } from '../utilities/altTextHealthWidgetDisplay.js';
4
+ import { getCollectionLabel } from '../utilities/getCollectionLabel.js';
5
+ const badgeStyles = {
6
+ healthy: {
7
+ background: '#dcfce7',
8
+ color: '#15803d'
9
+ },
10
+ unhealthy: {
11
+ background: '#fee2e2',
12
+ color: '#991b1b'
13
+ }
14
+ };
15
+ export async function AltTextHealthWidget({ req }) {
16
+ // Plugin translation keys are not in Payload's built-in key union
17
+ const t = req.t;
18
+ const { collections, errors, isLocalized, localeCount, totalDocs } = await getAltTextHealthWidgetData(req);
19
+ return /*#__PURE__*/ _jsxs("div", {
20
+ className: "card",
21
+ style: {
22
+ display: 'flex',
23
+ flexDirection: 'column',
24
+ gap: '16px',
25
+ height: '100%'
26
+ },
27
+ children: [
28
+ /*#__PURE__*/ _jsxs("div", {
29
+ style: {
30
+ display: 'flex',
31
+ flexDirection: 'column',
32
+ gap: '4px'
33
+ },
34
+ children: [
35
+ /*#__PURE__*/ _jsx("h3", {
36
+ style: {
37
+ fontSize: '18px',
38
+ fontWeight: 600,
39
+ margin: 0
40
+ },
41
+ children: t('@jhb.software/payload-alt-text-plugin:altTextHealthWidget')
42
+ }),
43
+ /*#__PURE__*/ _jsx("p", {
44
+ style: {
45
+ color: 'var(--theme-text)',
46
+ fontSize: '14px',
47
+ margin: 0,
48
+ opacity: 0.75
49
+ },
50
+ children: t('@jhb.software/payload-alt-text-plugin:altTextHealthDescription')
51
+ })
52
+ ]
53
+ }),
54
+ totalDocs === 0 && errors.length === 0 && /*#__PURE__*/ _jsx("p", {
55
+ style: {
56
+ color: 'var(--theme-text)',
57
+ margin: 0,
58
+ opacity: 0.75
59
+ },
60
+ children: t('@jhb.software/payload-alt-text-plugin:noImagesFound')
61
+ }),
62
+ errors.length > 0 && /*#__PURE__*/ _jsx("p", {
63
+ style: {
64
+ color: '#92400e',
65
+ fontSize: '13px',
66
+ margin: 0
67
+ },
68
+ children: t('@jhb.software/payload-alt-text-plugin:healthCheckPartialWarning')
69
+ }),
70
+ /*#__PURE__*/ _jsx("div", {
71
+ style: {
72
+ display: 'flex',
73
+ flexDirection: 'column',
74
+ gap: '10px'
75
+ },
76
+ children: collections.map((collection)=>{
77
+ const displayState = getAltTextHealthWidgetDisplayState(collection);
78
+ return /*#__PURE__*/ _jsxs("div", {
79
+ style: {
80
+ alignItems: 'center',
81
+ background: 'var(--theme-elevation-50)',
82
+ border: '1px solid var(--theme-border-color)',
83
+ borderRadius: '10px',
84
+ display: 'flex',
85
+ gap: '12px',
86
+ justifyContent: 'space-between',
87
+ padding: '12px 16px'
88
+ },
89
+ children: [
90
+ /*#__PURE__*/ _jsxs("div", {
91
+ style: {
92
+ display: 'flex',
93
+ flexDirection: 'column',
94
+ gap: '2px',
95
+ minWidth: 0
96
+ },
97
+ children: [
98
+ /*#__PURE__*/ _jsx("a", {
99
+ href: `${req.payload.config.routes.admin}/collections/${collection.collection}`,
100
+ style: {
101
+ color: 'var(--theme-text)',
102
+ fontSize: '14px',
103
+ fontWeight: 500,
104
+ textDecoration: 'none'
105
+ },
106
+ children: getCollectionLabel(collection.collection, req.payload.config.collections, req.locale)
107
+ }),
108
+ displayState === 'unavailable' ? /*#__PURE__*/ _jsx("span", {
109
+ style: {
110
+ color: '#92400e',
111
+ fontSize: '13px'
112
+ },
113
+ children: t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')
114
+ }) : /*#__PURE__*/ _jsxs("span", {
115
+ style: {
116
+ fontSize: '13px',
117
+ opacity: 0.7
118
+ },
119
+ children: [
120
+ t('@jhb.software/payload-alt-text-plugin:totalImageCount').replace('{count}', String(collection.totalDocs)),
121
+ isLocalized && /*#__PURE__*/ _jsxs(_Fragment, {
122
+ children: [
123
+ ' · ',
124
+ t('@jhb.software/payload-alt-text-plugin:localeCount').replace('{count}', String(localeCount))
125
+ ]
126
+ })
127
+ ]
128
+ })
129
+ ]
130
+ }),
131
+ displayState === 'unhealthy' && collection.invalidDocIds && collection.invalidDocIds.length > 0 ? /*#__PURE__*/ _jsxs("a", {
132
+ href: `${req.payload.config.routes.admin}/collections/${collection.collection}?where[id][in]=${collection.invalidDocIds.join(',')}`,
133
+ style: {
134
+ background: badgeStyles.unhealthy.background,
135
+ borderRadius: '999px',
136
+ color: badgeStyles.unhealthy.color,
137
+ flexShrink: 0,
138
+ fontSize: '12px',
139
+ fontWeight: 600,
140
+ padding: '4px 10px',
141
+ textDecoration: 'none',
142
+ whiteSpace: 'nowrap'
143
+ },
144
+ children: [
145
+ collection.missingDocs + collection.partialDocs,
146
+ ' ',
147
+ t('@jhb.software/payload-alt-text-plugin:statusUnhealthy'),
148
+ " →"
149
+ ]
150
+ }) : displayState === 'healthy' ? /*#__PURE__*/ _jsxs("span", {
151
+ style: {
152
+ background: badgeStyles.healthy.background,
153
+ borderRadius: '999px',
154
+ color: badgeStyles.healthy.color,
155
+ flexShrink: 0,
156
+ fontSize: '12px',
157
+ fontWeight: 600,
158
+ padding: '4px 10px',
159
+ whiteSpace: 'nowrap'
160
+ },
161
+ children: [
162
+ t('@jhb.software/payload-alt-text-plugin:statusHealthy'),
163
+ " ✓"
164
+ ]
165
+ }) : displayState === 'unhealthy' ? /*#__PURE__*/ _jsxs("span", {
166
+ style: {
167
+ background: badgeStyles.unhealthy.background,
168
+ borderRadius: '999px',
169
+ color: badgeStyles.unhealthy.color,
170
+ flexShrink: 0,
171
+ fontSize: '12px',
172
+ fontWeight: 600,
173
+ padding: '4px 10px',
174
+ whiteSpace: 'nowrap'
175
+ },
176
+ children: [
177
+ collection.missingDocs + collection.partialDocs,
178
+ ' ',
179
+ t('@jhb.software/payload-alt-text-plugin:statusUnhealthy')
180
+ ]
181
+ }) : /*#__PURE__*/ _jsx("span", {
182
+ style: {
183
+ color: '#92400e',
184
+ flexShrink: 0,
185
+ fontSize: '12px',
186
+ fontWeight: 600,
187
+ whiteSpace: 'nowrap'
188
+ },
189
+ children: t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')
190
+ })
191
+ ]
192
+ }, collection.collection);
193
+ })
194
+ })
195
+ ]
196
+ });
197
+ }
198
+
199
+ //# sourceMappingURL=AltTextHealthWidget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/AltTextHealthWidget.tsx"],"sourcesContent":["import type { WidgetServerProps } from 'payload'\n\nimport { getAltTextHealthWidgetData } from '../utilities/altTextHealth.js'\nimport { getAltTextHealthWidgetDisplayState } from '../utilities/altTextHealthWidgetDisplay.js'\nimport { getCollectionLabel } from '../utilities/getCollectionLabel.js'\n\nconst badgeStyles = {\n healthy: { background: '#dcfce7', color: '#15803d' },\n unhealthy: { background: '#fee2e2', color: '#991b1b' },\n}\n\nexport async function AltTextHealthWidget({ req }: WidgetServerProps) {\n // Plugin translation keys are not in Payload's built-in key union\n const t = req.t as (key: string) => string\n const { collections, errors, isLocalized, localeCount, totalDocs } =\n await getAltTextHealthWidgetData(req)\n\n return (\n <div\n className=\"card\"\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: '16px',\n height: '100%',\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>\n <h3 style={{ fontSize: '18px', fontWeight: 600, margin: 0 }}>\n {t('@jhb.software/payload-alt-text-plugin:altTextHealthWidget')}\n </h3>\n <p style={{ color: 'var(--theme-text)', fontSize: '14px', margin: 0, opacity: 0.75 }}>\n {t('@jhb.software/payload-alt-text-plugin:altTextHealthDescription')}\n </p>\n </div>\n\n {totalDocs === 0 && errors.length === 0 && (\n <p style={{ color: 'var(--theme-text)', margin: 0, opacity: 0.75 }}>\n {t('@jhb.software/payload-alt-text-plugin:noImagesFound')}\n </p>\n )}\n\n {errors.length > 0 && (\n <p style={{ color: '#92400e', fontSize: '13px', margin: 0 }}>\n {t('@jhb.software/payload-alt-text-plugin:healthCheckPartialWarning')}\n </p>\n )}\n\n <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>\n {collections.map((collection) => {\n const displayState = getAltTextHealthWidgetDisplayState(collection)\n\n return (\n <div\n key={collection.collection}\n style={{\n alignItems: 'center',\n background: 'var(--theme-elevation-50)',\n border: '1px solid var(--theme-border-color)',\n borderRadius: '10px',\n display: 'flex',\n gap: '12px',\n justifyContent: 'space-between',\n padding: '12px 16px',\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', gap: '2px', minWidth: 0 }}>\n <a\n href={`${req.payload.config.routes.admin}/collections/${collection.collection}`}\n style={{\n color: 'var(--theme-text)',\n fontSize: '14px',\n fontWeight: 500,\n textDecoration: 'none',\n }}\n >\n {getCollectionLabel(\n collection.collection,\n req.payload.config.collections,\n req.locale,\n )}\n </a>\n\n {displayState === 'unavailable' ? (\n <span style={{ color: '#92400e', fontSize: '13px' }}>\n {t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')}\n </span>\n ) : (\n <span style={{ fontSize: '13px', opacity: 0.7 }}>\n {t('@jhb.software/payload-alt-text-plugin:totalImageCount').replace(\n '{count}',\n String(collection.totalDocs),\n )}\n {isLocalized && (\n <>\n {' · '}\n {t('@jhb.software/payload-alt-text-plugin:localeCount').replace(\n '{count}',\n String(localeCount),\n )}\n </>\n )}\n </span>\n )}\n </div>\n\n {displayState === 'unhealthy' &&\n collection.invalidDocIds &&\n collection.invalidDocIds.length > 0 ? (\n <a\n href={`${req.payload.config.routes.admin}/collections/${collection.collection}?where[id][in]=${collection.invalidDocIds.join(',')}`}\n style={{\n background: badgeStyles.unhealthy.background,\n borderRadius: '999px',\n color: badgeStyles.unhealthy.color,\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600,\n padding: '4px 10px',\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n }}\n >\n {collection.missingDocs + collection.partialDocs}{' '}\n {t('@jhb.software/payload-alt-text-plugin:statusUnhealthy')} →\n </a>\n ) : displayState === 'healthy' ? (\n <span\n style={{\n background: badgeStyles.healthy.background,\n borderRadius: '999px',\n color: badgeStyles.healthy.color,\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600,\n padding: '4px 10px',\n whiteSpace: 'nowrap',\n }}\n >\n {t('@jhb.software/payload-alt-text-plugin:statusHealthy')} ✓\n </span>\n ) : displayState === 'unhealthy' ? (\n <span\n style={{\n background: badgeStyles.unhealthy.background,\n borderRadius: '999px',\n color: badgeStyles.unhealthy.color,\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600,\n padding: '4px 10px',\n whiteSpace: 'nowrap',\n }}\n >\n {collection.missingDocs + collection.partialDocs}{' '}\n {t('@jhb.software/payload-alt-text-plugin:statusUnhealthy')}\n </span>\n ) : (\n <span\n style={{\n color: '#92400e',\n flexShrink: 0,\n fontSize: '12px',\n fontWeight: 600,\n whiteSpace: 'nowrap',\n }}\n >\n {t('@jhb.software/payload-alt-text-plugin:collectionCheckFailed')}\n </span>\n )}\n </div>\n )\n })}\n </div>\n </div>\n )\n}\n"],"names":["getAltTextHealthWidgetData","getAltTextHealthWidgetDisplayState","getCollectionLabel","badgeStyles","healthy","background","color","unhealthy","AltTextHealthWidget","req","t","collections","errors","isLocalized","localeCount","totalDocs","div","className","style","display","flexDirection","gap","height","h3","fontSize","fontWeight","margin","p","opacity","length","map","collection","displayState","alignItems","border","borderRadius","justifyContent","padding","minWidth","a","href","payload","config","routes","admin","textDecoration","locale","span","replace","String","invalidDocIds","join","flexShrink","whiteSpace","missingDocs","partialDocs"],"mappings":"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"}
@@ -17,7 +17,7 @@ export function BulkGenerateAltTextsButton({ collectionSlug }) {
17
17
  throw new Error('Collection slug is required');
18
18
  }
19
19
  try {
20
- const response = await fetch('/api/alt-text-plugin/bulk-generate-alt-texts', {
20
+ const response = await fetch('/api/alt-text-plugin/generate/bulk', {
21
21
  body: JSON.stringify({
22
22
  collection: collectionSlug,
23
23
  ids: selectedIds
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/BulkGenerateAltTextsButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useSelection, useTranslation } from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { useTransition } from 'react'\n\nimport type {\n PluginAltTextTranslationKeys,\n PluginAltTextTranslations,\n} from '../translations/index.js'\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\n\nexport function BulkGenerateAltTextsButton({ collectionSlug }: { collectionSlug: string }) {\n const { t } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const [isPending, startTransition] = useTransition()\n const { selected, setSelection } = useSelection()\n\n const selectedIds = Array.from(selected.entries())\n .filter(([, isSelected]) => isSelected)\n .map(([id]) => id) as string[]\n\n const router = useRouter()\n\n const handleGenerateAltTexts = () => {\n startTransition(async () => {\n if (!collectionSlug) {\n throw new Error('Collection slug is required')\n }\n\n try {\n const response = await fetch('/api/alt-text-plugin/bulk-generate-alt-texts', {\n body: JSON.stringify({\n collection: collectionSlug,\n ids: selectedIds,\n }),\n method: 'POST',\n })\n\n if (!response.ok) {\n toast.error(t('@jhb.software/payload-alt-text-plugin:failedToGenerate'))\n return\n }\n\n const data = (await response.json()) as {\n erroredDocs: string[]\n totalDocs: number\n updatedDocs: number\n }\n\n if (data.erroredDocs.length > 0) {\n toast.error(\n t('@jhb.software/payload-alt-text-plugin:failedToGenerateForXImages').replace(\n '{X}',\n data.erroredDocs.length.toString(),\n ),\n )\n }\n\n // in case not all images were updated, show a warning instead of a success message:\n if (data.updatedDocs === data.totalDocs) {\n toast.success(\n t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated')\n .replace('{X}', data.updatedDocs.toString())\n .replace('{Y}', data.totalDocs.toString()),\n )\n } else {\n toast.warning(\n t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated')\n .replace('{X}', data.updatedDocs.toString())\n .replace('{Y}', data.totalDocs.toString()),\n )\n }\n\n // deselect all previously selected images\n for (const id of selectedIds) {\n setSelection(id)\n }\n\n router.refresh()\n } catch (error) {\n console.error('Error generating alt text:', error)\n toast.error(t('@jhb.software/payload-alt-text-plugin:errorGeneratingAltText'))\n }\n })\n }\n\n return (\n selectedIds.length > 0 && (\n <div className=\"m-0\" style={{ display: 'flex', justifyContent: 'right' }}>\n <Button\n className=\"m-0\"\n disabled={isPending || selectedIds.length === 0}\n icon={isPending ? <Spinner /> : <Lightning />}\n onClick={handleGenerateAltTexts}\n >\n {t('@jhb.software/payload-alt-text-plugin:generateAltTextFor')} {selectedIds.length}{' '}\n {selectedIds.length === 1\n ? t('@jhb.software/payload-alt-text-plugin:image')\n : t('@jhb.software/payload-alt-text-plugin:images')}\n </Button>\n </div>\n )\n )\n}\n"],"names":["Button","toast","useSelection","useTranslation","useRouter","useTransition","Lightning","Spinner","BulkGenerateAltTextsButton","collectionSlug","t","isPending","startTransition","selected","setSelection","selectedIds","Array","from","entries","filter","isSelected","map","id","router","handleGenerateAltTexts","Error","response","fetch","body","JSON","stringify","collection","ids","method","ok","error","data","json","erroredDocs","length","replace","toString","updatedDocs","totalDocs","success","warning","refresh","console","div","className","style","display","justifyContent","disabled","icon","onClick"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,KAAK,EAAEC,YAAY,EAAEC,cAAc,QAAQ,iBAAgB;AAC5E,SAASC,SAAS,QAAQ,qBAAoB;AAC9C,SAASC,aAAa,QAAQ,QAAO;AAMrC,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAE5C,OAAO,SAASC,2BAA2B,EAAEC,cAAc,EAA8B;IACvF,MAAM,EAAEC,CAAC,EAAE,GAAGP;IACd,MAAM,CAACQ,WAAWC,gBAAgB,GAAGP;IACrC,MAAM,EAAEQ,QAAQ,EAAEC,YAAY,EAAE,GAAGZ;IAEnC,MAAMa,cAAcC,MAAMC,IAAI,CAACJ,SAASK,OAAO,IAC5CC,MAAM,CAAC,CAAC,GAAGC,WAAW,GAAKA,YAC3BC,GAAG,CAAC,CAAC,CAACC,GAAG,GAAKA;IAEjB,MAAMC,SAASnB;IAEf,MAAMoB,yBAAyB;QAC7BZ,gBAAgB;YACd,IAAI,CAACH,gBAAgB;gBACnB,MAAM,IAAIgB,MAAM;YAClB;YAEA,IAAI;gBACF,MAAMC,WAAW,MAAMC,MAAM,gDAAgD;oBAC3EC,MAAMC,KAAKC,SAAS,CAAC;wBACnBC,YAAYtB;wBACZuB,KAAKjB;oBACP;oBACAkB,QAAQ;gBACV;gBAEA,IAAI,CAACP,SAASQ,EAAE,EAAE;oBAChBjC,MAAMkC,KAAK,CAACzB,EAAE;oBACd;gBACF;gBAEA,MAAM0B,OAAQ,MAAMV,SAASW,IAAI;gBAMjC,IAAID,KAAKE,WAAW,CAACC,MAAM,GAAG,GAAG;oBAC/BtC,MAAMkC,KAAK,CACTzB,EAAE,oEAAoE8B,OAAO,CAC3E,OACAJ,KAAKE,WAAW,CAACC,MAAM,CAACE,QAAQ;gBAGtC;gBAEA,oFAAoF;gBACpF,IAAIL,KAAKM,WAAW,KAAKN,KAAKO,SAAS,EAAE;oBACvC1C,MAAM2C,OAAO,CACXlC,EAAE,2DACC8B,OAAO,CAAC,OAAOJ,KAAKM,WAAW,CAACD,QAAQ,IACxCD,OAAO,CAAC,OAAOJ,KAAKO,SAAS,CAACF,QAAQ;gBAE7C,OAAO;oBACLxC,MAAM4C,OAAO,CACXnC,EAAE,2DACC8B,OAAO,CAAC,OAAOJ,KAAKM,WAAW,CAACD,QAAQ,IACxCD,OAAO,CAAC,OAAOJ,KAAKO,SAAS,CAACF,QAAQ;gBAE7C;gBAEA,0CAA0C;gBAC1C,KAAK,MAAMnB,MAAMP,YAAa;oBAC5BD,aAAaQ;gBACf;gBAEAC,OAAOuB,OAAO;YAChB,EAAE,OAAOX,OAAO;gBACdY,QAAQZ,KAAK,CAAC,8BAA8BA;gBAC5ClC,MAAMkC,KAAK,CAACzB,EAAE;YAChB;QACF;IACF;IAEA,OACEK,YAAYwB,MAAM,GAAG,mBACnB,KAACS;QAAIC,WAAU;QAAMC,OAAO;YAAEC,SAAS;YAAQC,gBAAgB;QAAQ;kBACrE,cAAA,MAACpD;YACCiD,WAAU;YACVI,UAAU1C,aAAaI,YAAYwB,MAAM,KAAK;YAC9Ce,MAAM3C,0BAAY,KAACJ,6BAAa,KAACD;YACjCiD,SAAS/B;;gBAERd,EAAE;gBAA4D;gBAAEK,YAAYwB,MAAM;gBAAE;gBACpFxB,YAAYwB,MAAM,KAAK,IACpB7B,EAAE,iDACFA,EAAE;;;;AAKhB"}
1
+ {"version":3,"sources":["../../src/components/BulkGenerateAltTextsButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useSelection, useTranslation } from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { useTransition } from 'react'\n\nimport type {\n PluginAltTextTranslationKeys,\n PluginAltTextTranslations,\n} from '../translations/index.js'\n\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\n\nexport function BulkGenerateAltTextsButton({ collectionSlug }: { collectionSlug: string }) {\n const { t } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const [isPending, startTransition] = useTransition()\n const { selected, setSelection } = useSelection()\n\n const selectedIds = Array.from(selected.entries())\n .filter(([, isSelected]) => isSelected)\n .map(([id]) => id) as string[]\n\n const router = useRouter()\n\n const handleGenerateAltTexts = () => {\n startTransition(async () => {\n if (!collectionSlug) {\n throw new Error('Collection slug is required')\n }\n\n try {\n const response = await fetch('/api/alt-text-plugin/generate/bulk', {\n body: JSON.stringify({\n collection: collectionSlug,\n ids: selectedIds,\n }),\n method: 'POST',\n })\n\n if (!response.ok) {\n toast.error(t('@jhb.software/payload-alt-text-plugin:failedToGenerate'))\n return\n }\n\n const data = (await response.json()) as {\n erroredDocs: string[]\n totalDocs: number\n updatedDocs: number\n }\n\n if (data.erroredDocs.length > 0) {\n toast.error(\n t('@jhb.software/payload-alt-text-plugin:failedToGenerateForXImages').replace(\n '{X}',\n data.erroredDocs.length.toString(),\n ),\n )\n }\n\n // in case not all images were updated, show a warning instead of a success message:\n if (data.updatedDocs === data.totalDocs) {\n toast.success(\n t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated')\n .replace('{X}', data.updatedDocs.toString())\n .replace('{Y}', data.totalDocs.toString()),\n )\n } else {\n toast.warning(\n t('@jhb.software/payload-alt-text-plugin:xOfYImagesUpdated')\n .replace('{X}', data.updatedDocs.toString())\n .replace('{Y}', data.totalDocs.toString()),\n )\n }\n\n // deselect all previously selected images\n for (const id of selectedIds) {\n setSelection(id)\n }\n\n router.refresh()\n } catch (error) {\n console.error('Error generating alt text:', error)\n toast.error(t('@jhb.software/payload-alt-text-plugin:errorGeneratingAltText'))\n }\n })\n }\n\n return (\n selectedIds.length > 0 && (\n <div className=\"m-0\" style={{ display: 'flex', justifyContent: 'right' }}>\n <Button\n className=\"m-0\"\n disabled={isPending || selectedIds.length === 0}\n icon={isPending ? <Spinner /> : <Lightning />}\n onClick={handleGenerateAltTexts}\n >\n {t('@jhb.software/payload-alt-text-plugin:generateAltTextFor')} {selectedIds.length}{' '}\n {selectedIds.length === 1\n ? t('@jhb.software/payload-alt-text-plugin:image')\n : t('@jhb.software/payload-alt-text-plugin:images')}\n </Button>\n </div>\n )\n )\n}\n"],"names":["Button","toast","useSelection","useTranslation","useRouter","useTransition","Lightning","Spinner","BulkGenerateAltTextsButton","collectionSlug","t","isPending","startTransition","selected","setSelection","selectedIds","Array","from","entries","filter","isSelected","map","id","router","handleGenerateAltTexts","Error","response","fetch","body","JSON","stringify","collection","ids","method","ok","error","data","json","erroredDocs","length","replace","toString","updatedDocs","totalDocs","success","warning","refresh","console","div","className","style","display","justifyContent","disabled","icon","onClick"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,KAAK,EAAEC,YAAY,EAAEC,cAAc,QAAQ,iBAAgB;AAC5E,SAASC,SAAS,QAAQ,qBAAoB;AAC9C,SAASC,aAAa,QAAQ,QAAO;AAOrC,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAE5C,OAAO,SAASC,2BAA2B,EAAEC,cAAc,EAA8B;IACvF,MAAM,EAAEC,CAAC,EAAE,GAAGP;IACd,MAAM,CAACQ,WAAWC,gBAAgB,GAAGP;IACrC,MAAM,EAAEQ,QAAQ,EAAEC,YAAY,EAAE,GAAGZ;IAEnC,MAAMa,cAAcC,MAAMC,IAAI,CAACJ,SAASK,OAAO,IAC5CC,MAAM,CAAC,CAAC,GAAGC,WAAW,GAAKA,YAC3BC,GAAG,CAAC,CAAC,CAACC,GAAG,GAAKA;IAEjB,MAAMC,SAASnB;IAEf,MAAMoB,yBAAyB;QAC7BZ,gBAAgB;YACd,IAAI,CAACH,gBAAgB;gBACnB,MAAM,IAAIgB,MAAM;YAClB;YAEA,IAAI;gBACF,MAAMC,WAAW,MAAMC,MAAM,sCAAsC;oBACjEC,MAAMC,KAAKC,SAAS,CAAC;wBACnBC,YAAYtB;wBACZuB,KAAKjB;oBACP;oBACAkB,QAAQ;gBACV;gBAEA,IAAI,CAACP,SAASQ,EAAE,EAAE;oBAChBjC,MAAMkC,KAAK,CAACzB,EAAE;oBACd;gBACF;gBAEA,MAAM0B,OAAQ,MAAMV,SAASW,IAAI;gBAMjC,IAAID,KAAKE,WAAW,CAACC,MAAM,GAAG,GAAG;oBAC/BtC,MAAMkC,KAAK,CACTzB,EAAE,oEAAoE8B,OAAO,CAC3E,OACAJ,KAAKE,WAAW,CAACC,MAAM,CAACE,QAAQ;gBAGtC;gBAEA,oFAAoF;gBACpF,IAAIL,KAAKM,WAAW,KAAKN,KAAKO,SAAS,EAAE;oBACvC1C,MAAM2C,OAAO,CACXlC,EAAE,2DACC8B,OAAO,CAAC,OAAOJ,KAAKM,WAAW,CAACD,QAAQ,IACxCD,OAAO,CAAC,OAAOJ,KAAKO,SAAS,CAACF,QAAQ;gBAE7C,OAAO;oBACLxC,MAAM4C,OAAO,CACXnC,EAAE,2DACC8B,OAAO,CAAC,OAAOJ,KAAKM,WAAW,CAACD,QAAQ,IACxCD,OAAO,CAAC,OAAOJ,KAAKO,SAAS,CAACF,QAAQ;gBAE7C;gBAEA,0CAA0C;gBAC1C,KAAK,MAAMnB,MAAMP,YAAa;oBAC5BD,aAAaQ;gBACf;gBAEAC,OAAOuB,OAAO;YAChB,EAAE,OAAOX,OAAO;gBACdY,QAAQZ,KAAK,CAAC,8BAA8BA;gBAC5ClC,MAAMkC,KAAK,CAACzB,EAAE;YAChB;QACF;IACF;IAEA,OACEK,YAAYwB,MAAM,GAAG,mBACnB,KAACS;QAAIC,WAAU;QAAMC,OAAO;YAAEC,SAAS;YAAQC,gBAAgB;QAAQ;kBACrE,cAAA,MAACpD;YACCiD,WAAU;YACVI,UAAU1C,aAAaI,YAAYwB,MAAM,KAAK;YAC9Ce,MAAM3C,0BAAY,KAACJ,6BAAa,KAACD;YACjCiD,SAAS/B;;gBAERd,EAAE;gBAA4D;gBAAEK,YAAYwB,MAAM;gBAAE;gBACpFxB,YAAYwB,MAAM,KAAK,IACpB7B,EAAE,iDACFA,EAAE;;;;AAKhB"}
@@ -22,7 +22,7 @@ export function GenerateAltTextButton() {
22
22
  }
23
23
  startTransition(async ()=>{
24
24
  try {
25
- const response = await fetch('/api/alt-text-plugin/generate-alt-text', {
25
+ const response = await fetch('/api/alt-text-plugin/generate', {
26
26
  body: JSON.stringify({
27
27
  id: id,
28
28
  collection: collectionSlug,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/GenerateAltTextButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useDocumentInfo, useField, useLocale, useTranslation } from '@payloadcms/ui'\nimport { useTransition } from 'react'\n\nimport type {\n PluginAltTextTranslationKeys,\n PluginAltTextTranslations,\n} from '../translations/index.js'\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\n\nexport function GenerateAltTextButton() {\n const { t } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const { id, collectionSlug } = useDocumentInfo()\n const locale = useLocale()\n const [isPending, startTransition] = useTransition()\n\n const { setValue: setKeywords } = useField<string>({ path: 'keywords' })\n const { setValue: setAltText } = useField<string>({ path: 'alt' })\n\n const handleGenerateAltText = () => {\n if (!collectionSlug || !id) {\n toast.error(t('@jhb.software/payload-alt-text-plugin:cannotGenerateMissingFields'))\n throw new Error('Missing required fields')\n }\n\n startTransition(async () => {\n try {\n const response = await fetch('/api/alt-text-plugin/generate-alt-text', {\n body: JSON.stringify({\n id: id as string,\n collection: collectionSlug,\n locale: locale?.code ?? null, // sent null when localization is disabled\n }),\n method: 'POST',\n })\n\n if (!response.ok) {\n let errorMessage = t('@jhb.software/payload-alt-text-plugin:failedToGenerate')\n try {\n const errorData = (await response.json()) as { error: string }\n errorMessage = errorData.error\n } catch (error) {\n console.error('Error generating alt text:', error)\n }\n\n toast.error(errorMessage)\n return\n }\n\n const data = (await response.json()) as {\n altText: string\n keywords: string[]\n }\n\n if (data.altText && data.keywords) {\n setAltText(data.altText)\n setKeywords(data.keywords)\n toast.success(t('@jhb.software/payload-alt-text-plugin:altTextGeneratedSuccess'))\n } else {\n toast.error(t('@jhb.software/payload-alt-text-plugin:noAltTextGenerated'))\n }\n } catch (error) {\n console.error('Error generating alt text:', error)\n toast.error(t('@jhb.software/payload-alt-text-plugin:errorGeneratingAltText'))\n }\n })\n }\n\n return (\n <div style={{ display: 'flex', gap: '20px', marginTop: '10px' }}>\n <div style={{ color: 'var(--theme-elevation-400)', flex: '1' }}>\n <p>{t('@jhb.software/payload-alt-text-plugin:altTextDescription')}</p>\n <ol style={{ margin: '10px 0', paddingLeft: '20px' }}>\n <li>{t('@jhb.software/payload-alt-text-plugin:altTextRequirement1')}</li>\n <li>{t('@jhb.software/payload-alt-text-plugin:altTextRequirement2')}</li>\n <li>{t('@jhb.software/payload-alt-text-plugin:altTextRequirement3')}</li>\n </ol>\n </div>\n <div style={{ alignItems: 'center', display: 'flex' }}>\n <Button\n disabled={isPending || !id}\n icon={isPending ? <Spinner /> : <Lightning />}\n onClick={handleGenerateAltText}\n tooltip={\n !id ? t('@jhb.software/payload-alt-text-plugin:pleaseSaveDocumentFirst') : undefined\n }\n >\n {t('@jhb.software/payload-alt-text-plugin:generateAltText')}\n </Button>\n </div>\n </div>\n )\n}\n"],"names":["Button","toast","useDocumentInfo","useField","useLocale","useTranslation","useTransition","Lightning","Spinner","GenerateAltTextButton","t","id","collectionSlug","locale","isPending","startTransition","setValue","setKeywords","path","setAltText","handleGenerateAltText","error","Error","response","fetch","body","JSON","stringify","collection","code","method","ok","errorMessage","errorData","json","console","data","altText","keywords","success","div","style","display","gap","marginTop","color","flex","p","ol","margin","paddingLeft","li","alignItems","disabled","icon","onClick","tooltip","undefined"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,KAAK,EAAEC,eAAe,EAAEC,QAAQ,EAAEC,SAAS,EAAEC,cAAc,QAAQ,iBAAgB;AACpG,SAASC,aAAa,QAAQ,QAAO;AAMrC,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAE5C,OAAO,SAASC;IACd,MAAM,EAAEC,CAAC,EAAE,GAAGL;IACd,MAAM,EAAEM,EAAE,EAAEC,cAAc,EAAE,GAAGV;IAC/B,MAAMW,SAAST;IACf,MAAM,CAACU,WAAWC,gBAAgB,GAAGT;IAErC,MAAM,EAAEU,UAAUC,WAAW,EAAE,GAAGd,SAAiB;QAAEe,MAAM;IAAW;IACtE,MAAM,EAAEF,UAAUG,UAAU,EAAE,GAAGhB,SAAiB;QAAEe,MAAM;IAAM;IAEhE,MAAME,wBAAwB;QAC5B,IAAI,CAACR,kBAAkB,CAACD,IAAI;YAC1BV,MAAMoB,KAAK,CAACX,EAAE;YACd,MAAM,IAAIY,MAAM;QAClB;QAEAP,gBAAgB;YACd,IAAI;gBACF,MAAMQ,WAAW,MAAMC,MAAM,0CAA0C;oBACrEC,MAAMC,KAAKC,SAAS,CAAC;wBACnBhB,IAAIA;wBACJiB,YAAYhB;wBACZC,QAAQA,QAAQgB,QAAQ;oBAC1B;oBACAC,QAAQ;gBACV;gBAEA,IAAI,CAACP,SAASQ,EAAE,EAAE;oBAChB,IAAIC,eAAetB,EAAE;oBACrB,IAAI;wBACF,MAAMuB,YAAa,MAAMV,SAASW,IAAI;wBACtCF,eAAeC,UAAUZ,KAAK;oBAChC,EAAE,OAAOA,OAAO;wBACdc,QAAQd,KAAK,CAAC,8BAA8BA;oBAC9C;oBAEApB,MAAMoB,KAAK,CAACW;oBACZ;gBACF;gBAEA,MAAMI,OAAQ,MAAMb,SAASW,IAAI;gBAKjC,IAAIE,KAAKC,OAAO,IAAID,KAAKE,QAAQ,EAAE;oBACjCnB,WAAWiB,KAAKC,OAAO;oBACvBpB,YAAYmB,KAAKE,QAAQ;oBACzBrC,MAAMsC,OAAO,CAAC7B,EAAE;gBAClB,OAAO;oBACLT,MAAMoB,KAAK,CAACX,EAAE;gBAChB;YACF,EAAE,OAAOW,OAAO;gBACdc,QAAQd,KAAK,CAAC,8BAA8BA;gBAC5CpB,MAAMoB,KAAK,CAACX,EAAE;YAChB;QACF;IACF;IAEA,qBACE,MAAC8B;QAAIC,OAAO;YAAEC,SAAS;YAAQC,KAAK;YAAQC,WAAW;QAAO;;0BAC5D,MAACJ;gBAAIC,OAAO;oBAAEI,OAAO;oBAA8BC,MAAM;gBAAI;;kCAC3D,KAACC;kCAAGrC,EAAE;;kCACN,MAACsC;wBAAGP,OAAO;4BAAEQ,QAAQ;4BAAUC,aAAa;wBAAO;;0CACjD,KAACC;0CAAIzC,EAAE;;0CACP,KAACyC;0CAAIzC,EAAE;;0CACP,KAACyC;0CAAIzC,EAAE;;;;;;0BAGX,KAAC8B;gBAAIC,OAAO;oBAAEW,YAAY;oBAAUV,SAAS;gBAAO;0BAClD,cAAA,KAAC1C;oBACCqD,UAAUvC,aAAa,CAACH;oBACxB2C,MAAMxC,0BAAY,KAACN,6BAAa,KAACD;oBACjCgD,SAASnC;oBACToC,SACE,CAAC7C,KAAKD,EAAE,mEAAmE+C;8BAG5E/C,EAAE;;;;;AAKb"}
1
+ {"version":3,"sources":["../../src/components/GenerateAltTextButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useDocumentInfo, useField, useLocale, useTranslation } from '@payloadcms/ui'\nimport { useTransition } from 'react'\n\nimport type {\n PluginAltTextTranslationKeys,\n PluginAltTextTranslations,\n} from '../translations/index.js'\n\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\n\nexport function GenerateAltTextButton() {\n const { t } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const { id, collectionSlug } = useDocumentInfo()\n const locale = useLocale()\n const [isPending, startTransition] = useTransition()\n\n const { setValue: setKeywords } = useField<string>({ path: 'keywords' })\n const { setValue: setAltText } = useField<string>({ path: 'alt' })\n\n const handleGenerateAltText = () => {\n if (!collectionSlug || !id) {\n toast.error(t('@jhb.software/payload-alt-text-plugin:cannotGenerateMissingFields'))\n throw new Error('Missing required fields')\n }\n\n startTransition(async () => {\n try {\n const response = await fetch('/api/alt-text-plugin/generate', {\n body: JSON.stringify({\n id: id as string,\n collection: collectionSlug,\n locale: locale?.code ?? null, // sent null when localization is disabled\n }),\n method: 'POST',\n })\n\n if (!response.ok) {\n let errorMessage = t('@jhb.software/payload-alt-text-plugin:failedToGenerate')\n try {\n const errorData = (await response.json()) as { error: string }\n errorMessage = errorData.error\n } catch (error) {\n console.error('Error generating alt text:', error)\n }\n\n toast.error(errorMessage)\n return\n }\n\n const data = (await response.json()) as {\n altText: string\n keywords: string[]\n }\n\n if (data.altText && data.keywords) {\n setAltText(data.altText)\n setKeywords(data.keywords)\n toast.success(t('@jhb.software/payload-alt-text-plugin:altTextGeneratedSuccess'))\n } else {\n toast.error(t('@jhb.software/payload-alt-text-plugin:noAltTextGenerated'))\n }\n } catch (error) {\n console.error('Error generating alt text:', error)\n toast.error(t('@jhb.software/payload-alt-text-plugin:errorGeneratingAltText'))\n }\n })\n }\n\n return (\n <div style={{ display: 'flex', gap: '20px', marginTop: '10px' }}>\n <div style={{ color: 'var(--theme-elevation-400)', flex: '1' }}>\n <p>{t('@jhb.software/payload-alt-text-plugin:altTextDescription')}</p>\n <ol style={{ margin: '10px 0', paddingLeft: '20px' }}>\n <li>{t('@jhb.software/payload-alt-text-plugin:altTextRequirement1')}</li>\n <li>{t('@jhb.software/payload-alt-text-plugin:altTextRequirement2')}</li>\n <li>{t('@jhb.software/payload-alt-text-plugin:altTextRequirement3')}</li>\n </ol>\n </div>\n <div style={{ alignItems: 'center', display: 'flex' }}>\n <Button\n disabled={isPending || !id}\n icon={isPending ? <Spinner /> : <Lightning />}\n onClick={handleGenerateAltText}\n tooltip={\n !id ? t('@jhb.software/payload-alt-text-plugin:pleaseSaveDocumentFirst') : undefined\n }\n >\n {t('@jhb.software/payload-alt-text-plugin:generateAltText')}\n </Button>\n </div>\n </div>\n )\n}\n"],"names":["Button","toast","useDocumentInfo","useField","useLocale","useTranslation","useTransition","Lightning","Spinner","GenerateAltTextButton","t","id","collectionSlug","locale","isPending","startTransition","setValue","setKeywords","path","setAltText","handleGenerateAltText","error","Error","response","fetch","body","JSON","stringify","collection","code","method","ok","errorMessage","errorData","json","console","data","altText","keywords","success","div","style","display","gap","marginTop","color","flex","p","ol","margin","paddingLeft","li","alignItems","disabled","icon","onClick","tooltip","undefined"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,KAAK,EAAEC,eAAe,EAAEC,QAAQ,EAAEC,SAAS,EAAEC,cAAc,QAAQ,iBAAgB;AACpG,SAASC,aAAa,QAAQ,QAAO;AAOrC,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAE5C,OAAO,SAASC;IACd,MAAM,EAAEC,CAAC,EAAE,GAAGL;IACd,MAAM,EAAEM,EAAE,EAAEC,cAAc,EAAE,GAAGV;IAC/B,MAAMW,SAAST;IACf,MAAM,CAACU,WAAWC,gBAAgB,GAAGT;IAErC,MAAM,EAAEU,UAAUC,WAAW,EAAE,GAAGd,SAAiB;QAAEe,MAAM;IAAW;IACtE,MAAM,EAAEF,UAAUG,UAAU,EAAE,GAAGhB,SAAiB;QAAEe,MAAM;IAAM;IAEhE,MAAME,wBAAwB;QAC5B,IAAI,CAACR,kBAAkB,CAACD,IAAI;YAC1BV,MAAMoB,KAAK,CAACX,EAAE;YACd,MAAM,IAAIY,MAAM;QAClB;QAEAP,gBAAgB;YACd,IAAI;gBACF,MAAMQ,WAAW,MAAMC,MAAM,iCAAiC;oBAC5DC,MAAMC,KAAKC,SAAS,CAAC;wBACnBhB,IAAIA;wBACJiB,YAAYhB;wBACZC,QAAQA,QAAQgB,QAAQ;oBAC1B;oBACAC,QAAQ;gBACV;gBAEA,IAAI,CAACP,SAASQ,EAAE,EAAE;oBAChB,IAAIC,eAAetB,EAAE;oBACrB,IAAI;wBACF,MAAMuB,YAAa,MAAMV,SAASW,IAAI;wBACtCF,eAAeC,UAAUZ,KAAK;oBAChC,EAAE,OAAOA,OAAO;wBACdc,QAAQd,KAAK,CAAC,8BAA8BA;oBAC9C;oBAEApB,MAAMoB,KAAK,CAACW;oBACZ;gBACF;gBAEA,MAAMI,OAAQ,MAAMb,SAASW,IAAI;gBAKjC,IAAIE,KAAKC,OAAO,IAAID,KAAKE,QAAQ,EAAE;oBACjCnB,WAAWiB,KAAKC,OAAO;oBACvBpB,YAAYmB,KAAKE,QAAQ;oBACzBrC,MAAMsC,OAAO,CAAC7B,EAAE;gBAClB,OAAO;oBACLT,MAAMoB,KAAK,CAACX,EAAE;gBAChB;YACF,EAAE,OAAOW,OAAO;gBACdc,QAAQd,KAAK,CAAC,8BAA8BA;gBAC5CpB,MAAMoB,KAAK,CAACX,EAAE;YAChB;QACF;IACF;IAEA,qBACE,MAAC8B;QAAIC,OAAO;YAAEC,SAAS;YAAQC,KAAK;YAAQC,WAAW;QAAO;;0BAC5D,MAACJ;gBAAIC,OAAO;oBAAEI,OAAO;oBAA8BC,MAAM;gBAAI;;kCAC3D,KAACC;kCAAGrC,EAAE;;kCACN,MAACsC;wBAAGP,OAAO;4BAAEQ,QAAQ;4BAAUC,aAAa;wBAAO;;0CACjD,KAACC;0CAAIzC,EAAE;;0CACP,KAACyC;0CAAIzC,EAAE;;0CACP,KAACyC;0CAAIzC,EAAE;;;;;;0BAGX,KAAC8B;gBAAIC,OAAO;oBAAEW,YAAY;oBAAUV,SAAS;gBAAO;0BAClD,cAAA,KAAC1C;oBACCqD,UAAUvC,aAAa,CAACH;oBACxB2C,MAAMxC,0BAAY,KAACN,6BAAa,KAACD;oBACjCgD,SAASnC;oBACToC,SACE,CAAC7C,KAAKD,EAAE,mEAAmE+C;8BAG5E/C,EAAE;;;;;AAKb"}
@@ -0,0 +1,3 @@
1
+ import type { PayloadHandler } from 'payload';
2
+ import type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js';
3
+ export declare const altTextHealthEndpoint: (access: AltTextPluginConfig["access"]) => PayloadHandler;
@@ -0,0 +1,30 @@
1
+ import { ALT_TEXT_HEALTH_PLUGIN_SLUG, getAltTextHealth } from '../utilities/altTextHealth.js';
2
+ export const altTextHealthEndpoint = (access)=>async (req)=>{
3
+ if (!await access({
4
+ req
5
+ })) {
6
+ return Response.json({
7
+ error: 'Unauthorized'
8
+ }, {
9
+ status: 401
10
+ });
11
+ }
12
+ try {
13
+ const health = await getAltTextHealth(req);
14
+ return Response.json(health);
15
+ } catch (error) {
16
+ req.payload.logger.error({
17
+ err: error,
18
+ msg: 'Failed to build alt text health response.',
19
+ path: '/alt-text-plugin/health',
20
+ plugin: ALT_TEXT_HEALTH_PLUGIN_SLUG
21
+ });
22
+ return Response.json({
23
+ error: 'Failed to compute alt text health'
24
+ }, {
25
+ status: 500
26
+ });
27
+ }
28
+ };
29
+
30
+ //# sourceMappingURL=altTextHealth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/altTextHealth.ts"],"sourcesContent":["import type { PayloadHandler, PayloadRequest } from 'payload'\n\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\nimport { ALT_TEXT_HEALTH_PLUGIN_SLUG, getAltTextHealth } from '../utilities/altTextHealth.js'\n\nexport const altTextHealthEndpoint =\n (access: AltTextPluginConfig['access']): PayloadHandler =>\n async (req: PayloadRequest) => {\n if (!(await access({ req }))) {\n return Response.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n try {\n const health = await getAltTextHealth(req)\n\n return Response.json(health)\n } catch (error) {\n req.payload.logger.error({\n err: error,\n msg: 'Failed to build alt text health response.',\n path: '/alt-text-plugin/health',\n plugin: ALT_TEXT_HEALTH_PLUGIN_SLUG,\n })\n\n return Response.json({ error: 'Failed to compute alt text health' }, { status: 500 })\n }\n }\n"],"names":["ALT_TEXT_HEALTH_PLUGIN_SLUG","getAltTextHealth","altTextHealthEndpoint","access","req","Response","json","error","status","health","payload","logger","err","msg","path","plugin"],"mappings":"AAIA,SAASA,2BAA2B,EAAEC,gBAAgB,QAAQ,gCAA+B;AAE7F,OAAO,MAAMC,wBACX,CAACC,SACD,OAAOC;QACL,IAAI,CAAE,MAAMD,OAAO;YAAEC;QAAI,IAAK;YAC5B,OAAOC,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAAe,GAAG;gBAAEC,QAAQ;YAAI;QAChE;QAEA,IAAI;YACF,MAAMC,SAAS,MAAMR,iBAAiBG;YAEtC,OAAOC,SAASC,IAAI,CAACG;QACvB,EAAE,OAAOF,OAAO;YACdH,IAAIM,OAAO,CAACC,MAAM,CAACJ,KAAK,CAAC;gBACvBK,KAAKL;gBACLM,KAAK;gBACLC,MAAM;gBACNC,QAAQf;YACV;YAEA,OAAOK,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAAoC,GAAG;gBAAEC,QAAQ;YAAI;QACrF;IACF,EAAC"}
@@ -1,5 +1,6 @@
1
1
  import type { PayloadHandler } from 'payload';
2
+ import type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js';
2
3
  /**
3
4
  * Generates and updates alt text for multiple images in all locales.
4
5
  */
5
- export declare const bulkGenerateAltTextsEndpoint: PayloadHandler;
6
+ export declare const bulkGenerateAltTextsEndpoint: (access: AltTextPluginConfig["access"]) => PayloadHandler;