@jhb.software/payload-alt-text-plugin 0.2.2 → 0.3.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.
Files changed (56) hide show
  1. package/README.md +93 -22
  2. package/dist/components/AltTextField.js +6 -6
  3. package/dist/components/AltTextField.js.map +1 -1
  4. package/dist/components/BulkGenerateAltTextsButton.js +7 -7
  5. package/dist/components/BulkGenerateAltTextsButton.js.map +1 -1
  6. package/dist/components/GenerateAltTextButton.js +12 -12
  7. package/dist/components/GenerateAltTextButton.js.map +1 -1
  8. package/dist/components/icons/Lightning.js +4 -4
  9. package/dist/components/icons/Lightning.js.map +1 -1
  10. package/dist/components/icons/Spinner.js +11 -11
  11. package/dist/components/icons/Spinner.js.map +1 -1
  12. package/dist/endpoints/bulkGenerateAltTexts.js +30 -77
  13. package/dist/endpoints/bulkGenerateAltTexts.js.map +1 -1
  14. package/dist/endpoints/generateAltText.d.ts +1 -1
  15. package/dist/endpoints/generateAltText.js +19 -74
  16. package/dist/endpoints/generateAltText.js.map +1 -1
  17. package/dist/exports/client.d.ts +1 -1
  18. package/dist/exports/client.js +1 -1
  19. package/dist/exports/client.js.map +1 -1
  20. package/dist/fields/altTextField.js +7 -7
  21. package/dist/fields/altTextField.js.map +1 -1
  22. package/dist/fields/keywordsField.js +7 -7
  23. package/dist/fields/keywordsField.js.map +1 -1
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/plugin.js +24 -32
  28. package/dist/plugin.js.map +1 -1
  29. package/dist/resolvers/openAI.d.ts +24 -0
  30. package/dist/resolvers/openAI.js +177 -0
  31. package/dist/resolvers/openAI.js.map +1 -0
  32. package/dist/resolvers/types.d.ts +68 -0
  33. package/dist/resolvers/types.js +6 -0
  34. package/dist/resolvers/types.js.map +1 -0
  35. package/dist/translations/de.d.ts +1 -1
  36. package/dist/translations/de.js +3 -3
  37. package/dist/translations/de.js.map +1 -1
  38. package/dist/translations/en.d.ts +1 -1
  39. package/dist/translations/en.js +3 -3
  40. package/dist/translations/en.js.map +1 -1
  41. package/dist/types/AltTextPluginConfig.d.ts +28 -26
  42. package/dist/types/AltTextPluginConfig.js.map +1 -1
  43. package/dist/utilities/localesFromConfig.d.ts +1 -1
  44. package/dist/utilities/localesFromConfig.js.map +1 -1
  45. package/dist/utils/translatedLabel.d.ts +1 -1
  46. package/dist/utils/translatedLabel.js.map +1 -1
  47. package/dist/utils/usePluginTranslation.d.ts +1 -1
  48. package/dist/utils/usePluginTranslation.js +1 -1
  49. package/dist/utils/usePluginTranslation.js.map +1 -1
  50. package/package.json +16 -14
  51. package/dist/utilities/getGenerationCost.d.ts +0 -12
  52. package/dist/utilities/getGenerationCost.js +0 -30
  53. package/dist/utilities/getGenerationCost.js.map +0 -1
  54. package/dist/utilities/zodResponseFormat.d.ts +0 -11
  55. package/dist/utilities/zodResponseFormat.js +0 -23
  56. package/dist/utilities/zodResponseFormat.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,27 @@
1
1
  # Image Alt Text Generation Plugin for Payload CMS
2
2
 
3
- A minimal plugin to generate image alt texts using OpenAI's Vision API. This plugin automatically adds an alt text field with a button to generate the alt text to upload collections, and includes a bulk generation feature in the list view for processing multiple images at once.
3
+ A [Payload CMS](https://payloadcms.com/) plugin that adds AI-powered alt text generation for images. I automatically adds an alt text field with a button to generate the alt text to specified upload collections, and includes a bulk generation feature in the list view for processing multiple images at once.
4
+
5
+ ## Features
6
+
7
+ - Generate alt text for images using AI in the Payload Admin UI
8
+ - Supports any AI provider using a resolver pattern (e.g., OpenAI, Anthropic, etc.)
9
+ - Comes with a ready-to-use OpenAI resolver out of the box
10
+ - Automatic keyword extraction for improved admin search
11
+ - Bulk generation for processing multiple images at once
12
+ - Full localization support
13
+
14
+
15
+ When the plugin is enabled for an upload collection, it will:
16
+
17
+ 1. Add an alt text field to the collection
18
+ - A button to AI-generate the alt text
19
+ - This field will include a description of what the alt text should be
20
+ 2. Add a keywords fields to the collection
21
+ - This field will be automatically filled when generating the alt text
22
+ - It will be used for improving the search of images in the admin panel
23
+ 2. Add a bulk generate button to the collection list view
24
+ - This button will allow you to generate alt text for multiple images at once
4
25
 
5
26
  ## Installation
6
27
 
@@ -10,21 +31,23 @@ pnpm add @jhb.software/payload-alt-text-plugin
10
31
 
11
32
  ## Setup
12
33
 
13
- ### 1. Add plugin to Payload config
34
+ Install the plugin and add it to your Payload config:
14
35
 
15
- ```typescript
16
- import { payloadAltTextPlugin } from '@jhb.software/payload-alt-text-plugin'
36
+ ```ts
37
+ import {
38
+ payloadAltTextPlugin,
39
+ openAIResolver,
40
+ } from '@jhb.software/payload-alt-text-plugin'
17
41
 
18
42
  export default buildConfig({
19
43
  plugins: [
20
44
  payloadAltTextPlugin({
21
- collections: ['media'], // Specify which upload collections to enable the plugin for
22
- openAIApiKey: process.env.OPENAI_API_KEY!,
23
- model: 'gpt-4.1-mini',
24
- getImageThumbnail: (doc: Record<string, unknown>) => {
25
- // a function to get a thumbnail URL (e.g. from the sizes)
26
- return doc.url as string
27
- },
45
+ collections: ['media'],
46
+ resolver: openAIResolver({
47
+ apiKey: process.env.OPENAI_API_KEY,
48
+ model: 'gpt-4.1-mini', // optional, defaults to 'gpt-4.1-nano'
49
+ }),
50
+ getImageThumbnail: (doc) => doc.url, // a function to get a thumbnail URL (e.g. from the sizes)
28
51
  }),
29
52
  ],
30
53
  })
@@ -32,22 +55,70 @@ export default buildConfig({
32
55
 
33
56
  Note: When localization is disabled in your Payload config (default), you need to specify the locale to generate the alt texts in via the `locale` plugin option.
34
57
 
35
- ## Features
58
+ ## Configuration
36
59
 
37
- When the plugin is enabled for an upload collection, it will
60
+ ### Plugin Options
38
61
 
39
- 1. Add an alt text field to the collection
40
- - A button to AI-generate the alt text
41
- - This field will include a description of what the alt text should be
42
- 2. Add a keywords fields to the collection
43
- - This field will be automatically filled when generating the alt text
44
- - It will be used for improving the search of images in the admin panel
45
- 2. Add a bulk generate button to the collection list view
46
- - This button will allow you to generate alt text for multiple images at once
62
+ | Option | Type | Required | Description |
63
+ | ---------------------------- | ------------------ | -------- | ------------------------------------------------------------------------ |
64
+ | `collections` | `CollectionSlug[]` | Yes | Collections to enable alt text generation for |
65
+ | `resolver` | `AltTextResolver` | Yes | Alt text resolver to use (e.g., `openAIResolver`) |
66
+ | `getImageThumbnail` | `Function` | Yes | Function to get the thumbnail URL from an image document |
67
+ | `enabled` | `boolean` | No | Whether to enable the plugin |
68
+ | `locale` | `string` | No | Locale for alt text generation (required when localization is disabled) |
69
+ | `maxBulkGenerateConcurrency` | `number` | No | Maximum concurrent API requests for bulk operations (default: 16) |
70
+ | `fieldsOverride` | `Function` | No | Override the default fields inserted by the plugin |
71
+
72
+ ### Resolvers
73
+
74
+ This plugin is designed to work seamlessly with various AI providers by accepting a customizable resolver as a configuration option.
75
+
76
+ An OpenAI resolver is provided out of the box, but you can use any AI provider by creating your own resolver and specifying it in the plugin configuration.
77
+
78
+ #### OpenAI Resolver
79
+
80
+ ```ts
81
+ import { openAIResolver } from '@jhb.software/payload-alt-text-plugin'
82
+
83
+ openAIResolver({
84
+ apiKey: process.env.OPENAI_API_KEY,
85
+ model: 'gpt-4.1-mini', // or 'gpt-4.1-nano' (default)
86
+ })
87
+ ```
88
+
89
+ ## Custom Resolver
90
+
91
+ You can create your own resolver by implementing the `AltTextResolver` interface.
92
+
93
+ ```ts
94
+ import type { AltTextResolver } from '@jhb.software/payload-alt-text-plugin'
95
+
96
+ export const customResolver = (): AltTextResolver => ({
97
+ key: 'custom',
98
+ resolve: async ({ imageThumbnailUrl, filename, locale, req }) => {
99
+ // Your custom alt text generation logic here
100
+ const altText = await generateAltText(imageThumbnailUrl, filename, locale, req)
101
+
102
+ return {
103
+ success: true,
104
+ result: altText,
105
+ }
106
+ },
107
+ resolveBulk: async ({ imageThumbnailUrl, filename, locales, req }) => {
108
+ // Your custom alt text generation logic here
109
+ const altTexts = await generateAltTextBulk(imageThumbnailUrl, filename, locales, req)
110
+
111
+ return {
112
+ success: true,
113
+ results: altTexts
114
+ }
115
+ },
116
+ })
117
+ ```
47
118
 
48
119
  ## Roadmap
49
120
 
50
- > ⚠️ **Warning**: This plugin is actively evolving and may undergo significant changes. While it is functional, please thoroughly test before using in production environments.
121
+ > **Warning**: This plugin is actively evolving and may undergo significant changes. While it is functional, please thoroughly test before using in production environments.
51
122
 
52
123
  Have a suggestion for the plugin? Any feedback is welcome!
53
124
 
@@ -4,7 +4,7 @@ import { FieldLabel, TextareaInput, useDocumentInfo, useField } from '@payloadcm
4
4
  import { GenerateAltTextButton } from './GenerateAltTextButton.js';
5
5
  export const AltTextField = (clientProps)=>{
6
6
  const { field, path } = clientProps;
7
- const { value, setValue } = useField({
7
+ const { setValue, value } = useField({
8
8
  path
9
9
  });
10
10
  const { id } = useDocumentInfo();
@@ -20,17 +20,17 @@ export const AltTextField = (clientProps)=>{
20
20
  /*#__PURE__*/ _jsx(FieldLabel, {
21
21
  htmlFor: `field-${path}`,
22
22
  label: field.label,
23
- required: required,
24
- localized: field.localized
23
+ localized: field.localized,
24
+ required: required
25
25
  }),
26
26
  /*#__PURE__*/ _jsx("div", {
27
27
  className: "field-type__wrap",
28
28
  children: /*#__PURE__*/ _jsx(TextareaInput, {
29
- value: value,
29
+ AfterInput: /*#__PURE__*/ _jsx(GenerateAltTextButton, {}),
30
+ onChange: (e)=>setValue(e.target.value),
30
31
  path: path,
31
32
  required: required,
32
- onChange: (e)=>setValue(e.target.value),
33
- AfterInput: /*#__PURE__*/ _jsx(GenerateAltTextButton, {})
33
+ value: value
34
34
  })
35
35
  })
36
36
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/AltTextField.tsx"],"sourcesContent":["'use client'\n\nimport { FieldLabel, TextareaInput, useDocumentInfo, useField } from '@payloadcms/ui'\nimport type { TextareaFieldClientProps } from 'payload'\n\nimport { GenerateAltTextButton } from './GenerateAltTextButton.js'\n\nexport const AltTextField = (clientProps: TextareaFieldClientProps) => {\n const { field, path } = clientProps\n\n const { value, setValue } = useField<string>({ path })\n const { id } = useDocumentInfo()\n\n // the field should be optional when the document is created\n // (since the alt text generation can only be used once the document is created and the image uploaded)\n const required = id ? field.required : false\n\n return (\n <div className=\"field-type textarea\" style={{ flex: '1 1 auto' }}>\n <FieldLabel\n htmlFor={`field-${path}`}\n label={field.label}\n required={required}\n localized={field.localized}\n />\n\n <div className=\"field-type__wrap\">\n <TextareaInput\n value={value}\n path={path!}\n required={required}\n onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value)}\n AfterInput={<GenerateAltTextButton />}\n />\n </div>\n </div>\n )\n}\n"],"names":["FieldLabel","TextareaInput","useDocumentInfo","useField","GenerateAltTextButton","AltTextField","clientProps","field","path","value","setValue","id","required","div","className","style","flex","htmlFor","label","localized","onChange","e","target","AfterInput"],"mappings":"AAAA;;AAEA,SAASA,UAAU,EAAEC,aAAa,EAAEC,eAAe,EAAEC,QAAQ,QAAQ,iBAAgB;AAGrF,SAASC,qBAAqB,QAAQ,6BAA4B;AAElE,OAAO,MAAMC,eAAe,CAACC;IAC3B,MAAM,EAAEC,KAAK,EAAEC,IAAI,EAAE,GAAGF;IAExB,MAAM,EAAEG,KAAK,EAAEC,QAAQ,EAAE,GAAGP,SAAiB;QAAEK;IAAK;IACpD,MAAM,EAAEG,EAAE,EAAE,GAAGT;IAEf,4DAA4D;IAC5D,uGAAuG;IACvG,MAAMU,WAAWD,KAAKJ,MAAMK,QAAQ,GAAG;IAEvC,qBACE,MAACC;QAAIC,WAAU;QAAsBC,OAAO;YAAEC,MAAM;QAAW;;0BAC7D,KAAChB;gBACCiB,SAAS,CAAC,MAAM,EAAET,MAAM;gBACxBU,OAAOX,MAAMW,KAAK;gBAClBN,UAAUA;gBACVO,WAAWZ,MAAMY,SAAS;;0BAG5B,KAACN;gBAAIC,WAAU;0BACb,cAAA,KAACb;oBACCQ,OAAOA;oBACPD,MAAMA;oBACNI,UAAUA;oBACVQ,UAAU,CAACC,IAA8CX,SAASW,EAAEC,MAAM,CAACb,KAAK;oBAChFc,0BAAY,KAACnB;;;;;AAKvB,EAAC"}
1
+ {"version":3,"sources":["../../src/components/AltTextField.tsx"],"sourcesContent":["'use client'\n\nimport type { TextareaFieldClientProps } from 'payload'\n\nimport { FieldLabel, TextareaInput, useDocumentInfo, useField } from '@payloadcms/ui'\n\nimport { GenerateAltTextButton } from './GenerateAltTextButton.js'\n\nexport const AltTextField = (clientProps: TextareaFieldClientProps) => {\n const { field, path } = clientProps\n\n const { setValue, value } = useField<string>({ path })\n const { id } = useDocumentInfo()\n\n // the field should be optional when the document is created\n // (since the alt text generation can only be used once the document is created and the image uploaded)\n const required = id ? field.required : false\n\n return (\n <div className=\"field-type textarea\" style={{ flex: '1 1 auto' }}>\n <FieldLabel\n htmlFor={`field-${path}`}\n label={field.label}\n localized={field.localized}\n required={required}\n />\n\n <div className=\"field-type__wrap\">\n <TextareaInput\n AfterInput={<GenerateAltTextButton />}\n onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value)}\n path={path}\n required={required}\n value={value}\n />\n </div>\n </div>\n )\n}\n"],"names":["FieldLabel","TextareaInput","useDocumentInfo","useField","GenerateAltTextButton","AltTextField","clientProps","field","path","setValue","value","id","required","div","className","style","flex","htmlFor","label","localized","AfterInput","onChange","e","target"],"mappings":"AAAA;;AAIA,SAASA,UAAU,EAAEC,aAAa,EAAEC,eAAe,EAAEC,QAAQ,QAAQ,iBAAgB;AAErF,SAASC,qBAAqB,QAAQ,6BAA4B;AAElE,OAAO,MAAMC,eAAe,CAACC;IAC3B,MAAM,EAAEC,KAAK,EAAEC,IAAI,EAAE,GAAGF;IAExB,MAAM,EAAEG,QAAQ,EAAEC,KAAK,EAAE,GAAGP,SAAiB;QAAEK;IAAK;IACpD,MAAM,EAAEG,EAAE,EAAE,GAAGT;IAEf,4DAA4D;IAC5D,uGAAuG;IACvG,MAAMU,WAAWD,KAAKJ,MAAMK,QAAQ,GAAG;IAEvC,qBACE,MAACC;QAAIC,WAAU;QAAsBC,OAAO;YAAEC,MAAM;QAAW;;0BAC7D,KAAChB;gBACCiB,SAAS,CAAC,MAAM,EAAET,MAAM;gBACxBU,OAAOX,MAAMW,KAAK;gBAClBC,WAAWZ,MAAMY,SAAS;gBAC1BP,UAAUA;;0BAGZ,KAACC;gBAAIC,WAAU;0BACb,cAAA,KAACb;oBACCmB,0BAAY,KAAChB;oBACbiB,UAAU,CAACC,IAA8Cb,SAASa,EAAEC,MAAM,CAACb,KAAK;oBAChFF,MAAMA;oBACNI,UAAUA;oBACVF,OAAOA;;;;;AAKjB,EAAC"}
@@ -3,27 +3,27 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Button, toast, useSelection } from '@payloadcms/ui';
4
4
  import { useRouter } from 'next/navigation.js';
5
5
  import { useTransition } from 'react';
6
+ import { usePluginTranslation } from '../utils/usePluginTranslation.js';
6
7
  import { Lightning } from './icons/Lightning.js';
7
8
  import { Spinner } from './icons/Spinner.js';
8
- import { usePluginTranslation } from '../utils/usePluginTranslation.js';
9
9
  export function BulkGenerateAltTextsButton({ collectionSlug }) {
10
10
  const { t } = usePluginTranslation();
11
11
  const [isPending, startTransition] = useTransition();
12
12
  const { selected, setSelection } = useSelection();
13
13
  const selectedIds = Array.from(selected.entries()).filter(([, isSelected])=>isSelected).map(([id])=>id);
14
14
  const router = useRouter();
15
- const handleGenerateAltTexts = async ()=>{
15
+ const handleGenerateAltTexts = ()=>{
16
16
  startTransition(async ()=>{
17
17
  if (!collectionSlug) {
18
18
  throw new Error('Collection slug is required');
19
19
  }
20
20
  try {
21
21
  const response = await fetch('/api/alt-text-plugin/bulk-generate-alt-texts', {
22
- method: 'POST',
23
22
  body: JSON.stringify({
24
23
  collection: collectionSlug,
25
24
  ids: selectedIds
26
- })
25
+ }),
26
+ method: 'POST'
27
27
  });
28
28
  if (!response.ok) {
29
29
  toast.error(t('failedToGenerate'));
@@ -51,16 +51,16 @@ export function BulkGenerateAltTextsButton({ collectionSlug }) {
51
51
  });
52
52
  };
53
53
  return selectedIds.length > 0 && /*#__PURE__*/ _jsx("div", {
54
+ className: "m-0",
54
55
  style: {
55
56
  display: 'flex',
56
57
  justifyContent: 'right'
57
58
  },
58
- className: "m-0",
59
59
  children: /*#__PURE__*/ _jsxs(Button, {
60
- onClick: handleGenerateAltTexts,
60
+ className: "m-0",
61
61
  disabled: isPending || selectedIds.length === 0,
62
62
  icon: isPending ? /*#__PURE__*/ _jsx(Spinner, {}) : /*#__PURE__*/ _jsx(Lightning, {}),
63
- className: "m-0",
63
+ onClick: handleGenerateAltTexts,
64
64
  children: [
65
65
  t('generateAltTextFor'),
66
66
  " ",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/BulkGenerateAltTextsButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useSelection } from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { useTransition } from 'react'\n\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\nimport { usePluginTranslation } from '../utils/usePluginTranslation.js'\n\nexport function BulkGenerateAltTextsButton({ collectionSlug }: { collectionSlug: string }) {\n const { t } = usePluginTranslation()\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 = async () => {\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 method: 'POST',\n body: JSON.stringify({\n collection: collectionSlug,\n ids: selectedIds,\n }),\n })\n\n if (!response.ok) {\n toast.error(t('failedToGenerate'))\n return\n }\n\n const data = (await response.json()) as {\n updatedDocs: number\n totalDocs: number\n erroredDocs: string[]\n }\n\n if (data.erroredDocs.length > 0) {\n toast.error(\n t('failedToGenerateForXImages').replace('{X}', data.erroredDocs.length.toString()),\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('xOfYImagesUpdated')\n .replace('{X}', data.updatedDocs.toString())\n .replace('{Y}', data.totalDocs.toString()),\n )\n } else {\n toast.warning(\n t('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('errorGeneratingAltText'))\n }\n })\n }\n\n return (\n selectedIds.length > 0 && (\n <div style={{ display: 'flex', justifyContent: 'right' }} className=\"m-0\">\n <Button\n onClick={handleGenerateAltTexts}\n disabled={isPending || selectedIds.length === 0}\n icon={isPending ? <Spinner /> : <Lightning />}\n className=\"m-0\"\n >\n {t('generateAltTextFor')} {selectedIds.length}{' '}\n {selectedIds.length === 1 ? t('image') : t('images')}\n </Button>\n </div>\n )\n )\n}\n"],"names":["Button","toast","useSelection","useRouter","useTransition","Lightning","Spinner","usePluginTranslation","BulkGenerateAltTextsButton","collectionSlug","t","isPending","startTransition","selected","setSelection","selectedIds","Array","from","entries","filter","isSelected","map","id","router","handleGenerateAltTexts","Error","response","fetch","method","body","JSON","stringify","collection","ids","ok","error","data","json","erroredDocs","length","replace","toString","updatedDocs","totalDocs","success","warning","refresh","console","div","style","display","justifyContent","className","onClick","disabled","icon"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,KAAK,EAAEC,YAAY,QAAQ,iBAAgB;AAC5D,SAASC,SAAS,QAAQ,qBAAoB;AAC9C,SAASC,aAAa,QAAQ,QAAO;AAErC,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAC5C,SAASC,oBAAoB,QAAQ,mCAAkC;AAEvE,OAAO,SAASC,2BAA2B,EAAEC,cAAc,EAA8B;IACvF,MAAM,EAAEC,CAAC,EAAE,GAAGH;IACd,MAAM,CAACI,WAAWC,gBAAgB,GAAGR;IACrC,MAAM,EAAES,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,SAASpB;IAEf,MAAMqB,yBAAyB;QAC7BZ,gBAAgB;YACd,IAAI,CAACH,gBAAgB;gBACnB,MAAM,IAAIgB,MAAM;YAClB;YAEA,IAAI;gBACF,MAAMC,WAAW,MAAMC,MAAM,gDAAgD;oBAC3EC,QAAQ;oBACRC,MAAMC,KAAKC,SAAS,CAAC;wBACnBC,YAAYvB;wBACZwB,KAAKlB;oBACP;gBACF;gBAEA,IAAI,CAACW,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,8BAA8B8B,OAAO,CAAC,OAAOJ,KAAKE,WAAW,CAACC,MAAM,CAACE,QAAQ;gBAEnF;gBAEA,oFAAoF;gBACpF,IAAIL,KAAKM,WAAW,KAAKN,KAAKO,SAAS,EAAE;oBACvC1C,MAAM2C,OAAO,CACXlC,EAAE,qBACC8B,OAAO,CAAC,OAAOJ,KAAKM,WAAW,CAACD,QAAQ,IACxCD,OAAO,CAAC,OAAOJ,KAAKO,SAAS,CAACF,QAAQ;gBAE7C,OAAO;oBACLxC,MAAM4C,OAAO,CACXnC,EAAE,qBACC8B,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,OAAO;YAAEC,SAAS;YAAQC,gBAAgB;QAAQ;QAAGC,WAAU;kBAClE,cAAA,MAACpD;YACCqD,SAAS7B;YACT8B,UAAU3C,aAAaI,YAAYwB,MAAM,KAAK;YAC9CgB,MAAM5C,0BAAY,KAACL,6BAAa,KAACD;YACjC+C,WAAU;;gBAET1C,EAAE;gBAAsB;gBAAEK,YAAYwB,MAAM;gBAAE;gBAC9CxB,YAAYwB,MAAM,KAAK,IAAI7B,EAAE,WAAWA,EAAE;;;;AAKrD"}
1
+ {"version":3,"sources":["../../src/components/BulkGenerateAltTextsButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useSelection } from '@payloadcms/ui'\nimport { useRouter } from 'next/navigation.js'\nimport { useTransition } from 'react'\n\nimport { usePluginTranslation } from '../utils/usePluginTranslation.js'\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\n\nexport function BulkGenerateAltTextsButton({ collectionSlug }: { collectionSlug: string }) {\n const { t } = usePluginTranslation()\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('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('failedToGenerateForXImages').replace('{X}', data.erroredDocs.length.toString()),\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('xOfYImagesUpdated')\n .replace('{X}', data.updatedDocs.toString())\n .replace('{Y}', data.totalDocs.toString()),\n )\n } else {\n toast.warning(\n t('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('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('generateAltTextFor')} {selectedIds.length}{' '}\n {selectedIds.length === 1 ? t('image') : t('images')}\n </Button>\n </div>\n )\n )\n}\n"],"names":["Button","toast","useSelection","useRouter","useTransition","usePluginTranslation","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,QAAQ,iBAAgB;AAC5D,SAASC,SAAS,QAAQ,qBAAoB;AAC9C,SAASC,aAAa,QAAQ,QAAO;AAErC,SAASC,oBAAoB,QAAQ,mCAAkC;AACvE,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAE5C,OAAO,SAASC,2BAA2B,EAAEC,cAAc,EAA8B;IACvF,MAAM,EAAEC,CAAC,EAAE,GAAGL;IACd,MAAM,CAACM,WAAWC,gBAAgB,GAAGR;IACrC,MAAM,EAAES,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,SAASpB;IAEf,MAAMqB,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,8BAA8B8B,OAAO,CAAC,OAAOJ,KAAKE,WAAW,CAACC,MAAM,CAACE,QAAQ;gBAEnF;gBAEA,oFAAoF;gBACpF,IAAIL,KAAKM,WAAW,KAAKN,KAAKO,SAAS,EAAE;oBACvC1C,MAAM2C,OAAO,CACXlC,EAAE,qBACC8B,OAAO,CAAC,OAAOJ,KAAKM,WAAW,CAACD,QAAQ,IACxCD,OAAO,CAAC,OAAOJ,KAAKO,SAAS,CAACF,QAAQ;gBAE7C,OAAO;oBACLxC,MAAM4C,OAAO,CACXnC,EAAE,qBACC8B,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;gBAAsB;gBAAEK,YAAYwB,MAAM;gBAAE;gBAC9CxB,YAAYwB,MAAM,KAAK,IAAI7B,EAAE,WAAWA,EAAE;;;;AAKrD"}
@@ -2,9 +2,9 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Button, toast, useDocumentInfo, useField, useLocale } from '@payloadcms/ui';
4
4
  import { useTransition } from 'react';
5
+ import { usePluginTranslation } from '../utils/usePluginTranslation.js';
5
6
  import { Lightning } from './icons/Lightning.js';
6
7
  import { Spinner } from './icons/Spinner.js';
7
- import { usePluginTranslation } from '../utils/usePluginTranslation.js';
8
8
  export function GenerateAltTextButton() {
9
9
  const { t } = usePluginTranslation();
10
10
  const { id, collectionSlug } = useDocumentInfo();
@@ -16,7 +16,7 @@ export function GenerateAltTextButton() {
16
16
  const { setValue: setAltText } = useField({
17
17
  path: 'alt'
18
18
  });
19
- const handleGenerateAltText = async ()=>{
19
+ const handleGenerateAltText = ()=>{
20
20
  if (!collectionSlug || !id) {
21
21
  toast.error(t('cannotGenerateMissingFields'));
22
22
  throw new Error('Missing required fields');
@@ -24,12 +24,12 @@ export function GenerateAltTextButton() {
24
24
  startTransition(async ()=>{
25
25
  try {
26
26
  const response = await fetch('/api/alt-text-plugin/generate-alt-text', {
27
- method: 'POST',
28
27
  body: JSON.stringify({
29
- collection: collectionSlug,
30
28
  id: id,
29
+ collection: collectionSlug,
31
30
  locale: locale?.code ?? null
32
- })
31
+ }),
32
+ method: 'POST'
33
33
  });
34
34
  if (!response.ok) {
35
35
  let errorMessage = t('failedToGenerate');
@@ -65,8 +65,8 @@ export function GenerateAltTextButton() {
65
65
  children: [
66
66
  /*#__PURE__*/ _jsxs("div", {
67
67
  style: {
68
- flex: '1',
69
- color: 'var(--theme-elevation-400)'
68
+ color: 'var(--theme-elevation-400)',
69
+ flex: '1'
70
70
  },
71
71
  children: [
72
72
  /*#__PURE__*/ _jsx("p", {
@@ -74,8 +74,8 @@ export function GenerateAltTextButton() {
74
74
  }),
75
75
  /*#__PURE__*/ _jsxs("ol", {
76
76
  style: {
77
- paddingLeft: '20px',
78
- margin: '10px 0'
77
+ margin: '10px 0',
78
+ paddingLeft: '20px'
79
79
  },
80
80
  children: [
81
81
  /*#__PURE__*/ _jsx("li", {
@@ -93,13 +93,13 @@ export function GenerateAltTextButton() {
93
93
  }),
94
94
  /*#__PURE__*/ _jsx("div", {
95
95
  style: {
96
- display: 'flex',
97
- alignItems: 'center'
96
+ alignItems: 'center',
97
+ display: 'flex'
98
98
  },
99
99
  children: /*#__PURE__*/ _jsx(Button, {
100
- onClick: handleGenerateAltText,
101
100
  disabled: isPending || !id,
102
101
  icon: isPending ? /*#__PURE__*/ _jsx(Spinner, {}) : /*#__PURE__*/ _jsx(Lightning, {}),
102
+ onClick: handleGenerateAltText,
103
103
  tooltip: !id ? t('pleaseSaveDocumentFirst') : undefined,
104
104
  children: t('generateAltText')
105
105
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/GenerateAltTextButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useDocumentInfo, useField, useLocale } from '@payloadcms/ui'\nimport { useTransition } from 'react'\n\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\nimport { usePluginTranslation } from '../utils/usePluginTranslation.js'\n\nexport function GenerateAltTextButton() {\n const { t } = usePluginTranslation()\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 = async () => {\n if (!collectionSlug || !id) {\n toast.error(t('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 method: 'POST',\n body: JSON.stringify({\n collection: collectionSlug,\n id: id as string,\n locale: locale?.code ?? null, // sent null when localization is disabled\n }),\n })\n\n if (!response.ok) {\n let errorMessage = t('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('altTextGeneratedSuccess'))\n } else {\n toast.error(t('noAltTextGenerated'))\n }\n } catch (error) {\n console.error('Error generating alt text:', error)\n toast.error(t('errorGeneratingAltText'))\n }\n })\n }\n\n return (\n <div style={{ display: 'flex', gap: '20px', marginTop: '10px' }}>\n <div style={{ flex: '1', color: 'var(--theme-elevation-400)' }}>\n <p>{t('altTextDescription')}</p>\n <ol style={{ paddingLeft: '20px', margin: '10px 0' }}>\n <li>{t('altTextRequirement1')}</li>\n <li>{t('altTextRequirement2')}</li>\n <li>{t('altTextRequirement3')}</li>\n </ol>\n </div>\n <div style={{ display: 'flex', alignItems: 'center' }}>\n <Button\n onClick={handleGenerateAltText}\n disabled={isPending || !id}\n icon={isPending ? <Spinner /> : <Lightning />}\n tooltip={!id ? t('pleaseSaveDocumentFirst') : undefined}\n >\n {t('generateAltText')}\n </Button>\n </div>\n </div>\n )\n}\n"],"names":["Button","toast","useDocumentInfo","useField","useLocale","useTransition","Lightning","Spinner","usePluginTranslation","GenerateAltTextButton","t","id","collectionSlug","locale","isPending","startTransition","setValue","setKeywords","path","setAltText","handleGenerateAltText","error","Error","response","fetch","method","body","JSON","stringify","collection","code","ok","errorMessage","errorData","json","console","data","altText","keywords","success","div","style","display","gap","marginTop","flex","color","p","ol","paddingLeft","margin","li","alignItems","onClick","disabled","icon","tooltip","undefined"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,KAAK,EAAEC,eAAe,EAAEC,QAAQ,EAAEC,SAAS,QAAQ,iBAAgB;AACpF,SAASC,aAAa,QAAQ,QAAO;AAErC,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAC5C,SAASC,oBAAoB,QAAQ,mCAAkC;AAEvE,OAAO,SAASC;IACd,MAAM,EAAEC,CAAC,EAAE,GAAGF;IACd,MAAM,EAAEG,EAAE,EAAEC,cAAc,EAAE,GAAGV;IAC/B,MAAMW,SAAST;IACf,MAAM,CAACU,WAAWC,gBAAgB,GAAGV;IAErC,MAAM,EAAEW,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,QAAQ;oBACRC,MAAMC,KAAKC,SAAS,CAAC;wBACnBC,YAAYjB;wBACZD,IAAIA;wBACJE,QAAQA,QAAQiB,QAAQ;oBAC1B;gBACF;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,MAAM;oBAAKC,OAAO;gBAA6B;;kCAC3D,KAACC;kCAAGrC,EAAE;;kCACN,MAACsC;wBAAGP,OAAO;4BAAEQ,aAAa;4BAAQC,QAAQ;wBAAS;;0CACjD,KAACC;0CAAIzC,EAAE;;0CACP,KAACyC;0CAAIzC,EAAE;;0CACP,KAACyC;0CAAIzC,EAAE;;;;;;0BAGX,KAAC8B;gBAAIC,OAAO;oBAAEC,SAAS;oBAAQU,YAAY;gBAAS;0BAClD,cAAA,KAACpD;oBACCqD,SAASjC;oBACTkC,UAAUxC,aAAa,CAACH;oBACxB4C,MAAMzC,0BAAY,KAACP,6BAAa,KAACD;oBACjCkD,SAAS,CAAC7C,KAAKD,EAAE,6BAA6B+C;8BAE7C/C,EAAE;;;;;AAKb"}
1
+ {"version":3,"sources":["../../src/components/GenerateAltTextButton.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useDocumentInfo, useField, useLocale } from '@payloadcms/ui'\nimport { useTransition } from 'react'\n\nimport { usePluginTranslation } from '../utils/usePluginTranslation.js'\nimport { Lightning } from './icons/Lightning.js'\nimport { Spinner } from './icons/Spinner.js'\n\nexport function GenerateAltTextButton() {\n const { t } = usePluginTranslation()\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('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('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('altTextGeneratedSuccess'))\n } else {\n toast.error(t('noAltTextGenerated'))\n }\n } catch (error) {\n console.error('Error generating alt text:', error)\n toast.error(t('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('altTextDescription')}</p>\n <ol style={{ margin: '10px 0', paddingLeft: '20px' }}>\n <li>{t('altTextRequirement1')}</li>\n <li>{t('altTextRequirement2')}</li>\n <li>{t('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={!id ? t('pleaseSaveDocumentFirst') : undefined}\n >\n {t('generateAltText')}\n </Button>\n </div>\n </div>\n )\n}\n"],"names":["Button","toast","useDocumentInfo","useField","useLocale","useTransition","usePluginTranslation","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,QAAQ,iBAAgB;AACpF,SAASC,aAAa,QAAQ,QAAO;AAErC,SAASC,oBAAoB,QAAQ,mCAAkC;AACvE,SAASC,SAAS,QAAQ,uBAAsB;AAChD,SAASC,OAAO,QAAQ,qBAAoB;AAE5C,OAAO,SAASC;IACd,MAAM,EAAEC,CAAC,EAAE,GAAGJ;IACd,MAAM,EAAEK,EAAE,EAAEC,cAAc,EAAE,GAAGV;IAC/B,MAAMW,SAAST;IACf,MAAM,CAACU,WAAWC,gBAAgB,GAAGV;IAErC,MAAM,EAAEW,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,SAAS,CAAC7C,KAAKD,EAAE,6BAA6B+C;8BAE7C/C,EAAE;;;;;AAKb"}
@@ -1,17 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  export function Lightning() {
3
3
  return /*#__PURE__*/ _jsx("svg", {
4
- xmlns: "http://www.w3.org/2000/svg",
5
- viewBox: "0 0 20 20",
6
4
  fill: "currentColor",
7
5
  style: {
8
6
  height: '1rem',
9
7
  width: '1rem'
10
8
  },
9
+ viewBox: "0 0 20 20",
10
+ xmlns: "http://www.w3.org/2000/svg",
11
11
  children: /*#__PURE__*/ _jsx("path", {
12
- fillRule: "evenodd",
12
+ clipRule: "evenodd",
13
13
  d: "M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z",
14
- clipRule: "evenodd"
14
+ fillRule: "evenodd"
15
15
  })
16
16
  });
17
17
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/icons/Lightning.tsx"],"sourcesContent":["export function Lightning() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n style={{\n height: '1rem',\n width: '1rem',\n }}\n >\n <path\n fillRule=\"evenodd\"\n d=\"M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z\"\n clipRule=\"evenodd\"\n />\n </svg>\n )\n}\n"],"names":["Lightning","svg","xmlns","viewBox","fill","style","height","width","path","fillRule","d","clipRule"],"mappings":";AAAA,OAAO,SAASA;IACd,qBACE,KAACC;QACCC,OAAM;QACNC,SAAQ;QACRC,MAAK;QACLC,OAAO;YACLC,QAAQ;YACRC,OAAO;QACT;kBAEA,cAAA,KAACC;YACCC,UAAS;YACTC,GAAE;YACFC,UAAS;;;AAIjB"}
1
+ {"version":3,"sources":["../../../src/components/icons/Lightning.tsx"],"sourcesContent":["export function Lightning() {\n return (\n <svg\n fill=\"currentColor\"\n style={{\n height: '1rem',\n width: '1rem',\n }}\n viewBox=\"0 0 20 20\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n clipRule=\"evenodd\"\n d=\"M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z\"\n fillRule=\"evenodd\"\n />\n </svg>\n )\n}\n"],"names":["Lightning","svg","fill","style","height","width","viewBox","xmlns","path","clipRule","d","fillRule"],"mappings":";AAAA,OAAO,SAASA;IACd,qBACE,KAACC;QACCC,MAAK;QACLC,OAAO;YACLC,QAAQ;YACRC,OAAO;QACT;QACAC,SAAQ;QACRC,OAAM;kBAEN,cAAA,KAACC;YACCC,UAAS;YACTC,GAAE;YACFC,UAAS;;;AAIjB"}
@@ -1,14 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  export function Spinner() {
3
3
  return /*#__PURE__*/ _jsxs("svg", {
4
- xmlns: "http://www.w3.org/2000/svg",
5
4
  fill: "none",
6
- viewBox: "0 0 24 24",
7
5
  style: {
6
+ animation: 'spin 1s linear infinite',
8
7
  height: '1rem',
9
- width: '1rem',
10
- animation: 'spin 1s linear infinite'
8
+ width: '1rem'
11
9
  },
10
+ viewBox: "0 0 24 24",
11
+ xmlns: "http://www.w3.org/2000/svg",
12
12
  children: [
13
13
  /*#__PURE__*/ _jsx("style", {
14
14
  children: `
@@ -19,21 +19,21 @@ export function Spinner() {
19
19
  `
20
20
  }),
21
21
  /*#__PURE__*/ _jsx("circle", {
22
- style: {
23
- opacity: 0.25
24
- },
25
22
  cx: "12",
26
23
  cy: "12",
27
24
  r: "10",
28
25
  stroke: "currentColor",
29
- strokeWidth: "4"
26
+ strokeWidth: "4",
27
+ style: {
28
+ opacity: 0.25
29
+ }
30
30
  }),
31
31
  /*#__PURE__*/ _jsx("path", {
32
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z",
33
+ fill: "currentColor",
32
34
  style: {
33
35
  opacity: 0.75
34
- },
35
- fill: "currentColor",
36
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
36
+ }
37
37
  })
38
38
  ]
39
39
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/icons/Spinner.tsx"],"sourcesContent":["export function Spinner() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n style={{\n height: '1rem',\n width: '1rem',\n animation: 'spin 1s linear infinite',\n }}\n >\n <style>\n {`\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n `}\n </style>\n <circle\n style={{ opacity: 0.25 }}\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n ></circle>\n <path\n style={{ opacity: 0.75 }}\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n ></path>\n </svg>\n )\n}\n"],"names":["Spinner","svg","xmlns","fill","viewBox","style","height","width","animation","circle","opacity","cx","cy","r","stroke","strokeWidth","path","d"],"mappings":";AAAA,OAAO,SAASA;IACd,qBACE,MAACC;QACCC,OAAM;QACNC,MAAK;QACLC,SAAQ;QACRC,OAAO;YACLC,QAAQ;YACRC,OAAO;YACPC,WAAW;QACb;;0BAEA,KAACH;0BACE,CAAC;;;;;QAKF,CAAC;;0BAEH,KAACI;gBACCJ,OAAO;oBAAEK,SAAS;gBAAK;gBACvBC,IAAG;gBACHC,IAAG;gBACHC,GAAE;gBACFC,QAAO;gBACPC,aAAY;;0BAEd,KAACC;gBACCX,OAAO;oBAAEK,SAAS;gBAAK;gBACvBP,MAAK;gBACLc,GAAE;;;;AAIV"}
1
+ {"version":3,"sources":["../../../src/components/icons/Spinner.tsx"],"sourcesContent":["export function Spinner() {\n return (\n <svg\n fill=\"none\"\n style={{\n animation: 'spin 1s linear infinite',\n height: '1rem',\n width: '1rem',\n }}\n viewBox=\"0 0 24 24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <style>\n {`\n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n `}\n </style>\n <circle\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n style={{ opacity: 0.25 }}\n ></circle>\n <path\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n fill=\"currentColor\"\n style={{ opacity: 0.75 }}\n ></path>\n </svg>\n )\n}\n"],"names":["Spinner","svg","fill","style","animation","height","width","viewBox","xmlns","circle","cx","cy","r","stroke","strokeWidth","opacity","path","d"],"mappings":";AAAA,OAAO,SAASA;IACd,qBACE,MAACC;QACCC,MAAK;QACLC,OAAO;YACLC,WAAW;YACXC,QAAQ;YACRC,OAAO;QACT;QACAC,SAAQ;QACRC,OAAM;;0BAEN,KAACL;0BACE,CAAC;;;;;QAKF,CAAC;;0BAEH,KAACM;gBACCC,IAAG;gBACHC,IAAG;gBACHC,GAAE;gBACFC,QAAO;gBACPC,aAAY;gBACZX,OAAO;oBAAEY,SAAS;gBAAK;;0BAEzB,KAACC;gBACCC,GAAE;gBACFf,MAAK;gBACLC,OAAO;oBAAEY,SAAS;gBAAK;;;;AAI/B"}