@jhb.software/payload-alt-text-plugin 0.2.0 → 0.2.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.
@@ -1 +1,3 @@
1
- export declare function BulkGenerateAltTextsButton(): false | import("react").JSX.Element;
1
+ export declare function BulkGenerateAltTextsButton({ collectionSlug }: {
2
+ collectionSlug: string;
3
+ }): false | import("react").JSX.Element;
@@ -6,7 +6,7 @@ import { useTransition } from 'react';
6
6
  import { Lightning } from './icons/Lightning.js';
7
7
  import { Spinner } from './icons/Spinner.js';
8
8
  import { usePluginTranslation } from '../utils/usePluginTranslation.js';
9
- export function BulkGenerateAltTextsButton() {
9
+ export function BulkGenerateAltTextsButton({ collectionSlug }) {
10
10
  const { t } = usePluginTranslation();
11
11
  const [isPending, startTransition] = useTransition();
12
12
  const { selected, setSelection } = useSelection();
@@ -14,11 +14,14 @@ export function BulkGenerateAltTextsButton() {
14
14
  const router = useRouter();
15
15
  const handleGenerateAltTexts = async ()=>{
16
16
  startTransition(async ()=>{
17
+ if (!collectionSlug) {
18
+ throw new Error('Collection slug is required');
19
+ }
17
20
  try {
18
21
  const response = await fetch('/api/alt-text-plugin/bulk-generate-alt-texts', {
19
22
  method: 'POST',
20
23
  body: JSON.stringify({
21
- collection: 'media',
24
+ collection: collectionSlug,
22
25
  ids: selectedIds
23
26
  })
24
27
  });
@@ -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() {\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 try {\n const response = await fetch('/api/alt-text-plugin/bulk-generate-alt-texts', {\n method: 'POST',\n body: JSON.stringify({\n collection: 'media',\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","t","isPending","startTransition","selected","setSelection","selectedIds","Array","from","entries","filter","isSelected","map","id","router","handleGenerateAltTexts","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;IACd,MAAM,EAAEC,CAAC,EAAE,GAAGF;IACd,MAAM,CAACG,WAAWC,gBAAgB,GAAGP;IACrC,MAAM,EAAEQ,QAAQ,EAAEC,YAAY,EAAE,GAAGX;IAEnC,MAAMY,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;gBACF,MAAMa,WAAW,MAAMC,MAAM,gDAAgD;oBAC3EC,QAAQ;oBACRC,MAAMC,KAAKC,SAAS,CAAC;wBACnBC,YAAY;wBACZC,KAAKjB;oBACP;gBACF;gBAEA,IAAI,CAACU,SAASQ,EAAE,EAAE;oBAChB/B,MAAMgC,KAAK,CAACxB,EAAE;oBACd;gBACF;gBAEA,MAAMyB,OAAQ,MAAMV,SAASW,IAAI;gBAMjC,IAAID,KAAKE,WAAW,CAACC,MAAM,GAAG,GAAG;oBAC/BpC,MAAMgC,KAAK,CACTxB,EAAE,8BAA8B6B,OAAO,CAAC,OAAOJ,KAAKE,WAAW,CAACC,MAAM,CAACE,QAAQ;gBAEnF;gBAEA,oFAAoF;gBACpF,IAAIL,KAAKM,WAAW,KAAKN,KAAKO,SAAS,EAAE;oBACvCxC,MAAMyC,OAAO,CACXjC,EAAE,qBACC6B,OAAO,CAAC,OAAOJ,KAAKM,WAAW,CAACD,QAAQ,IACxCD,OAAO,CAAC,OAAOJ,KAAKO,SAAS,CAACF,QAAQ;gBAE7C,OAAO;oBACLtC,MAAM0C,OAAO,CACXlC,EAAE,qBACC6B,OAAO,CAAC,OAAOJ,KAAKM,WAAW,CAACD,QAAQ,IACxCD,OAAO,CAAC,OAAOJ,KAAKO,SAAS,CAACF,QAAQ;gBAE7C;gBAEA,0CAA0C;gBAC1C,KAAK,MAAMlB,MAAMP,YAAa;oBAC5BD,aAAaQ;gBACf;gBAEAC,OAAOsB,OAAO;YAChB,EAAE,OAAOX,OAAO;gBACdY,QAAQZ,KAAK,CAAC,8BAA8BA;gBAC5ChC,MAAMgC,KAAK,CAACxB,EAAE;YAChB;QACF;IACF;IAEA,OACEK,YAAYuB,MAAM,GAAG,mBACnB,KAACS;QAAIC,OAAO;YAAEC,SAAS;YAAQC,gBAAgB;QAAQ;QAAGC,WAAU;kBAClE,cAAA,MAAClD;YACCmD,SAAS5B;YACT6B,UAAU1C,aAAaI,YAAYuB,MAAM,KAAK;YAC9CgB,MAAM3C,0BAAY,KAACJ,6BAAa,KAACD;YACjC6C,WAAU;;gBAETzC,EAAE;gBAAsB;gBAAEK,YAAYuB,MAAM;gBAAE;gBAC9CvB,YAAYuB,MAAM,KAAK,IAAI5B,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 { 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"}
package/dist/plugin.js CHANGED
@@ -64,9 +64,15 @@ export const payloadAltTextPlugin = (incomingPluginConfig)=>(incomingConfig)=>{
64
64
  ],
65
65
  components: {
66
66
  ...collectionConfig.admin?.components ?? {},
67
+ // TODO: use the beforeBulkAction custom component slot once available: https://github.com/payloadcms/payload/pull/11719
67
68
  beforeListTable: [
68
69
  ...collectionConfig.admin?.components?.beforeListTable ?? [],
69
- '@jhb.software/payload-alt-text-plugin/client#BulkGenerateAltTextsButton'
70
+ {
71
+ path: '@jhb.software/payload-alt-text-plugin/client#BulkGenerateAltTextsButton',
72
+ props: {
73
+ collectionSlug: collectionConfig.slug
74
+ }
75
+ }
70
76
  ]
71
77
  }
72
78
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type {\n AltTextPluginConfig,\n IncomingAltTextPluginConfig,\n} from './types/AltTextPluginConfig.js'\nimport { altTextField } from './fields/altTextField.js'\nimport { keywordsField } from './fields/keywordsField.js'\nimport { generateAltTextEndpoint } from './endpoints/generateAltText.js'\nimport { bulkGenerateAltTextsEndpoint } from './endpoints/bulkGenerateAltTexts.js'\nimport { translations } from './translations/index.js'\nimport { deepMergeSimple } from './utils/deepMergeSimple.js'\n\nexport const payloadAltTextPlugin =\n (incomingPluginConfig: IncomingAltTextPluginConfig) =>\n (incomingConfig: Config): Config => {\n const config = { ...incomingConfig }\n\n // If the plugin is disabled, return the config without modifying it\n if (incomingPluginConfig.enabled === false) {\n return config\n }\n\n const locales = config.localization\n ? config.localization.locales.map((localeConfig) =>\n typeof localeConfig === 'string' ? localeConfig : localeConfig.code,\n )\n : []\n\n const pluginConfig: AltTextPluginConfig = {\n enabled: incomingPluginConfig.enabled ?? true,\n openAIApiKey: incomingPluginConfig.openAIApiKey,\n collections: incomingPluginConfig.collections,\n maxBulkGenerateConcurrency: incomingPluginConfig.maxBulkGenerateConcurrency ?? 16,\n model: incomingPluginConfig.model ?? 'gpt-4.1-nano',\n locales: locales,\n locale: incomingPluginConfig.locale,\n getImageThumbnail: incomingPluginConfig.getImageThumbnail,\n fieldsOverride: incomingPluginConfig.fieldsOverride,\n }\n\n // Validate locale requirement for non-localized mode\n if (locales.length === 0 && !incomingPluginConfig.locale) {\n throw new Error(\n 'The alt-text plugin requires a \"locale\" option when Payload localization is disabled. ' +\n 'Please add { locale: \"en\" } (or your preferred locale) to your plugin configuration.',\n )\n }\n\n const defaultFields = [\n altTextField({\n localized: Boolean(config.localization),\n }),\n keywordsField({\n localized: Boolean(config.localization),\n }),\n ]\n\n const fields =\n incomingPluginConfig.fieldsOverride &&\n typeof incomingPluginConfig.fieldsOverride === 'function'\n ? incomingPluginConfig.fieldsOverride({ defaultFields })\n : defaultFields\n\n // Ensure collections array exists\n config.collections = config.collections || []\n\n // Map over collections and inject AI alt text fields into specified ones\n config.collections = config.collections.map((collectionConfig) => {\n if (pluginConfig.collections.includes(collectionConfig.slug)) {\n if (!collectionConfig.upload) {\n console.warn(\n `AI Alt Text Plugin: Collection \"${collectionConfig.slug}\" is not an upload collection. Skipping field injection.`,\n )\n return collectionConfig\n }\n\n return {\n ...collectionConfig,\n admin: {\n ...collectionConfig.admin,\n listSearchableFields: [\n // enhance the search by adding the keywords and alt fields (if not already included)\n ...(collectionConfig.admin?.listSearchableFields ?? []),\n ...(collectionConfig.admin?.listSearchableFields?.includes('keywords')\n ? []\n : ['keywords']),\n ...(collectionConfig.admin?.listSearchableFields?.includes('alt') ? [] : ['alt']),\n ],\n components: {\n ...(collectionConfig.admin?.components ?? {}),\n beforeListTable: [\n ...(collectionConfig.admin?.components?.beforeListTable ?? []),\n '@jhb.software/payload-alt-text-plugin/client#BulkGenerateAltTextsButton',\n ],\n },\n },\n fields: [...(collectionConfig.fields ?? []), ...fields],\n }\n }\n\n return collectionConfig\n })\n\n return {\n ...config,\n i18n: {\n ...config.i18n,\n translations: deepMergeSimple(translations, incomingConfig.i18n?.translations ?? {}),\n },\n custom: {\n ...config.custom,\n // Make plugin config available in hooks/actions\n altTextPluginConfig: pluginConfig,\n },\n endpoints: [\n ...(config.endpoints ?? []),\n {\n path: '/alt-text-plugin/generate-alt-text',\n method: 'post',\n handler: generateAltTextEndpoint,\n },\n {\n path: '/alt-text-plugin/bulk-generate-alt-texts',\n method: 'post',\n handler: bulkGenerateAltTextsEndpoint,\n },\n ],\n }\n }\n"],"names":["altTextField","keywordsField","generateAltTextEndpoint","bulkGenerateAltTextsEndpoint","translations","deepMergeSimple","payloadAltTextPlugin","incomingPluginConfig","incomingConfig","config","enabled","locales","localization","map","localeConfig","code","pluginConfig","openAIApiKey","collections","maxBulkGenerateConcurrency","model","locale","getImageThumbnail","fieldsOverride","length","Error","defaultFields","localized","Boolean","fields","collectionConfig","includes","slug","upload","console","warn","admin","listSearchableFields","components","beforeListTable","i18n","custom","altTextPluginConfig","endpoints","path","method","handler"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type {\n AltTextPluginConfig,\n IncomingAltTextPluginConfig,\n} from './types/AltTextPluginConfig.js'\nimport { altTextField } from './fields/altTextField.js'\nimport { keywordsField } from './fields/keywordsField.js'\nimport { generateAltTextEndpoint } from './endpoints/generateAltText.js'\nimport { bulkGenerateAltTextsEndpoint } from './endpoints/bulkGenerateAltTexts.js'\nimport { translations } from './translations/index.js'\nimport { deepMergeSimple } from './utils/deepMergeSimple.js'\n\nexport const payloadAltTextPlugin =\n (incomingPluginConfig: IncomingAltTextPluginConfig) =>\n (incomingConfig: Config): Config => {\n const config = { ...incomingConfig }\n\n // If the plugin is disabled, return the config without modifying it\n if (incomingPluginConfig.enabled === false) {\n return config\n }\n\n const locales = config.localization\n ? config.localization.locales.map((localeConfig) =>\n typeof localeConfig === 'string' ? localeConfig : localeConfig.code,\n )\n : []\n\n const pluginConfig: AltTextPluginConfig = {\n enabled: incomingPluginConfig.enabled ?? true,\n openAIApiKey: incomingPluginConfig.openAIApiKey,\n collections: incomingPluginConfig.collections,\n maxBulkGenerateConcurrency: incomingPluginConfig.maxBulkGenerateConcurrency ?? 16,\n model: incomingPluginConfig.model ?? 'gpt-4.1-nano',\n locales: locales,\n locale: incomingPluginConfig.locale,\n getImageThumbnail: incomingPluginConfig.getImageThumbnail,\n fieldsOverride: incomingPluginConfig.fieldsOverride,\n }\n\n // Validate locale requirement for non-localized mode\n if (locales.length === 0 && !incomingPluginConfig.locale) {\n throw new Error(\n 'The alt-text plugin requires a \"locale\" option when Payload localization is disabled. ' +\n 'Please add { locale: \"en\" } (or your preferred locale) to your plugin configuration.',\n )\n }\n\n const defaultFields = [\n altTextField({\n localized: Boolean(config.localization),\n }),\n keywordsField({\n localized: Boolean(config.localization),\n }),\n ]\n\n const fields =\n incomingPluginConfig.fieldsOverride &&\n typeof incomingPluginConfig.fieldsOverride === 'function'\n ? incomingPluginConfig.fieldsOverride({ defaultFields })\n : defaultFields\n\n // Ensure collections array exists\n config.collections = config.collections || []\n\n // Map over collections and inject AI alt text fields into specified ones\n config.collections = config.collections.map((collectionConfig) => {\n if (pluginConfig.collections.includes(collectionConfig.slug)) {\n if (!collectionConfig.upload) {\n console.warn(\n `AI Alt Text Plugin: Collection \"${collectionConfig.slug}\" is not an upload collection. Skipping field injection.`,\n )\n return collectionConfig\n }\n\n return {\n ...collectionConfig,\n admin: {\n ...collectionConfig.admin,\n listSearchableFields: [\n // enhance the search by adding the keywords and alt fields (if not already included)\n ...(collectionConfig.admin?.listSearchableFields ?? []),\n ...(collectionConfig.admin?.listSearchableFields?.includes('keywords')\n ? []\n : ['keywords']),\n ...(collectionConfig.admin?.listSearchableFields?.includes('alt') ? [] : ['alt']),\n ],\n components: {\n ...(collectionConfig.admin?.components ?? {}),\n // TODO: use the beforeBulkAction custom component slot once available: https://github.com/payloadcms/payload/pull/11719\n beforeListTable: [\n ...(collectionConfig.admin?.components?.beforeListTable ?? []),\n {\n path: '@jhb.software/payload-alt-text-plugin/client#BulkGenerateAltTextsButton',\n props: {\n collectionSlug: collectionConfig.slug,\n },\n },\n ],\n },\n },\n fields: [...(collectionConfig.fields ?? []), ...fields],\n }\n }\n\n return collectionConfig\n })\n\n return {\n ...config,\n i18n: {\n ...config.i18n,\n translations: deepMergeSimple(translations, incomingConfig.i18n?.translations ?? {}),\n },\n custom: {\n ...config.custom,\n // Make plugin config available in hooks/actions\n altTextPluginConfig: pluginConfig,\n },\n endpoints: [\n ...(config.endpoints ?? []),\n {\n path: '/alt-text-plugin/generate-alt-text',\n method: 'post',\n handler: generateAltTextEndpoint,\n },\n {\n path: '/alt-text-plugin/bulk-generate-alt-texts',\n method: 'post',\n handler: bulkGenerateAltTextsEndpoint,\n },\n ],\n }\n }\n"],"names":["altTextField","keywordsField","generateAltTextEndpoint","bulkGenerateAltTextsEndpoint","translations","deepMergeSimple","payloadAltTextPlugin","incomingPluginConfig","incomingConfig","config","enabled","locales","localization","map","localeConfig","code","pluginConfig","openAIApiKey","collections","maxBulkGenerateConcurrency","model","locale","getImageThumbnail","fieldsOverride","length","Error","defaultFields","localized","Boolean","fields","collectionConfig","includes","slug","upload","console","warn","admin","listSearchableFields","components","beforeListTable","path","props","collectionSlug","i18n","custom","altTextPluginConfig","endpoints","method","handler"],"mappings":"AAMA,SAASA,YAAY,QAAQ,2BAA0B;AACvD,SAASC,aAAa,QAAQ,4BAA2B;AACzD,SAASC,uBAAuB,QAAQ,iCAAgC;AACxE,SAASC,4BAA4B,QAAQ,sCAAqC;AAClF,SAASC,YAAY,QAAQ,0BAAyB;AACtD,SAASC,eAAe,QAAQ,6BAA4B;AAE5D,OAAO,MAAMC,uBACX,CAACC,uBACD,CAACC;QACC,MAAMC,SAAS;YAAE,GAAGD,cAAc;QAAC;QAEnC,oEAAoE;QACpE,IAAID,qBAAqBG,OAAO,KAAK,OAAO;YAC1C,OAAOD;QACT;QAEA,MAAME,UAAUF,OAAOG,YAAY,GAC/BH,OAAOG,YAAY,CAACD,OAAO,CAACE,GAAG,CAAC,CAACC,eAC/B,OAAOA,iBAAiB,WAAWA,eAAeA,aAAaC,IAAI,IAErE,EAAE;QAEN,MAAMC,eAAoC;YACxCN,SAASH,qBAAqBG,OAAO,IAAI;YACzCO,cAAcV,qBAAqBU,YAAY;YAC/CC,aAAaX,qBAAqBW,WAAW;YAC7CC,4BAA4BZ,qBAAqBY,0BAA0B,IAAI;YAC/EC,OAAOb,qBAAqBa,KAAK,IAAI;YACrCT,SAASA;YACTU,QAAQd,qBAAqBc,MAAM;YACnCC,mBAAmBf,qBAAqBe,iBAAiB;YACzDC,gBAAgBhB,qBAAqBgB,cAAc;QACrD;QAEA,qDAAqD;QACrD,IAAIZ,QAAQa,MAAM,KAAK,KAAK,CAACjB,qBAAqBc,MAAM,EAAE;YACxD,MAAM,IAAII,MACR,2FACE;QAEN;QAEA,MAAMC,gBAAgB;YACpB1B,aAAa;gBACX2B,WAAWC,QAAQnB,OAAOG,YAAY;YACxC;YACAX,cAAc;gBACZ0B,WAAWC,QAAQnB,OAAOG,YAAY;YACxC;SACD;QAED,MAAMiB,SACJtB,qBAAqBgB,cAAc,IACnC,OAAOhB,qBAAqBgB,cAAc,KAAK,aAC3ChB,qBAAqBgB,cAAc,CAAC;YAAEG;QAAc,KACpDA;QAEN,kCAAkC;QAClCjB,OAAOS,WAAW,GAAGT,OAAOS,WAAW,IAAI,EAAE;QAE7C,yEAAyE;QACzET,OAAOS,WAAW,GAAGT,OAAOS,WAAW,CAACL,GAAG,CAAC,CAACiB;YAC3C,IAAId,aAAaE,WAAW,CAACa,QAAQ,CAACD,iBAAiBE,IAAI,GAAG;gBAC5D,IAAI,CAACF,iBAAiBG,MAAM,EAAE;oBAC5BC,QAAQC,IAAI,CACV,CAAC,gCAAgC,EAAEL,iBAAiBE,IAAI,CAAC,wDAAwD,CAAC;oBAEpH,OAAOF;gBACT;gBAEA,OAAO;oBACL,GAAGA,gBAAgB;oBACnBM,OAAO;wBACL,GAAGN,iBAAiBM,KAAK;wBACzBC,sBAAsB;4BACpB,qFAAqF;+BACjFP,iBAAiBM,KAAK,EAAEC,wBAAwB,EAAE;+BAClDP,iBAAiBM,KAAK,EAAEC,sBAAsBN,SAAS,cACvD,EAAE,GACF;gCAAC;6BAAW;+BACZD,iBAAiBM,KAAK,EAAEC,sBAAsBN,SAAS,SAAS,EAAE,GAAG;gCAAC;6BAAM;yBACjF;wBACDO,YAAY;4BACV,GAAIR,iBAAiBM,KAAK,EAAEE,cAAc,CAAC,CAAC;4BAC5C,wHAAwH;4BACxHC,iBAAiB;mCACXT,iBAAiBM,KAAK,EAAEE,YAAYC,mBAAmB,EAAE;gCAC7D;oCACEC,MAAM;oCACNC,OAAO;wCACLC,gBAAgBZ,iBAAiBE,IAAI;oCACvC;gCACF;6BACD;wBACH;oBACF;oBACAH,QAAQ;2BAAKC,iBAAiBD,MAAM,IAAI,EAAE;2BAAMA;qBAAO;gBACzD;YACF;YAEA,OAAOC;QACT;QAEA,OAAO;YACL,GAAGrB,MAAM;YACTkC,MAAM;gBACJ,GAAGlC,OAAOkC,IAAI;gBACdvC,cAAcC,gBAAgBD,cAAcI,eAAemC,IAAI,EAAEvC,gBAAgB,CAAC;YACpF;YACAwC,QAAQ;gBACN,GAAGnC,OAAOmC,MAAM;gBAChB,gDAAgD;gBAChDC,qBAAqB7B;YACvB;YACA8B,WAAW;mBACLrC,OAAOqC,SAAS,IAAI,EAAE;gBAC1B;oBACEN,MAAM;oBACNO,QAAQ;oBACRC,SAAS9C;gBACX;gBACA;oBACEsC,MAAM;oBACNO,QAAQ;oBACRC,SAAS7C;gBACX;aACD;QACH;IACF,EAAC"}
@@ -1,4 +1,4 @@
1
- import { Field } from 'payload';
1
+ import { CollectionSlug, Field } from 'payload';
2
2
  /** Configuration options for the alt text plugin. */
3
3
  export type IncomingAltTextPluginConfig = {
4
4
  /** Whether the plugin is enabled. */
@@ -6,7 +6,7 @@ export type IncomingAltTextPluginConfig = {
6
6
  /** OpenAI API key for authentication. */
7
7
  openAIApiKey: string;
8
8
  /** Collection slugs to enable the plugin for. */
9
- collections: string[];
9
+ collections: CollectionSlug[];
10
10
  /** Maximum number of concurrent API requests for bulk operations. */
11
11
  maxBulkGenerateConcurrency?: number;
12
12
  /**
@@ -38,7 +38,7 @@ export type AltTextPluginConfig = {
38
38
  /** OpenAI API key for authentication. */
39
39
  openAIApiKey: string;
40
40
  /** Collection slugs to enable the plugin for. */
41
- collections: string[];
41
+ collections: CollectionSlug[];
42
42
  /** Maximum number of concurrent API requests for bulk generate operations. */
43
43
  maxBulkGenerateConcurrency: number;
44
44
  /** Function to get the thumbnail URL of an image document. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types/AltTextPluginConfig.ts"],"sourcesContent":["import { Field } from 'payload'\n\n/** Configuration options for the alt text plugin. */\nexport type IncomingAltTextPluginConfig = {\n /** Whether the plugin is enabled. */\n enabled?: boolean\n\n /** OpenAI API key for authentication. */\n openAIApiKey: string\n\n /** Collection slugs to enable the plugin for. */\n collections: string[]\n\n /** Maximum number of concurrent API requests for bulk operations. */\n maxBulkGenerateConcurrency?: number\n\n /**\n * Function to get the thumbnail URL of an image document.\n * This URL will be sent to the LLM for analysis.\n *\n * @remarks\n * - The URL must be publicly accessible so the LLM can fetch it\n * - Use a thumbnail/preview version of the image when possible (e.g. from the sizes field)\n */\n getImageThumbnail: (doc: Record<string, unknown>) => string\n\n /** The OpenAI LLM model to use for alt text generation. */\n model?: 'gpt-4.1-nano' | 'gpt-4.1-mini'\n\n /** Override the default fields inserted by the plugin via a function that receives the default fields and returns the new fields */\n fieldsOverride?: (args: { defaultFields: Field[] }) => Field[]\n\n /**\n * The locale to generate alt texts in when localization is disabled.\n * Required when localization is disabled, ignored when localization is enabled.\n * @example 'en', 'de'\n */\n locale?: string\n}\n\n/** Configuration of the alt text plugin after defaults have been applied. */\nexport type AltTextPluginConfig = {\n /** Whether the plugin is enabled. */\n enabled: boolean\n\n /** OpenAI API key for authentication. */\n openAIApiKey: string\n\n /** Collection slugs to enable the plugin for. */\n collections: string[]\n\n /** Maximum number of concurrent API requests for bulk generate operations. */\n maxBulkGenerateConcurrency: number\n\n /** Function to get the thumbnail URL of an image document. */\n getImageThumbnail: (doc: Record<string, unknown>) => string\n\n /** The OpenAI LLM model to use for alt text generation. */\n model: 'gpt-4.1-nano' | 'gpt-4.1-mini'\n\n /** Override the default fields inserted by the plugin via a function that receives the default fields and returns the new fields */\n fieldsOverride?: (args: { defaultFields: Field[] }) => Field[]\n\n /** The locales to generate alt texts for. */\n locales: string[]\n\n /** The locale to generate alt texts in when localization is disabled. */\n locale?: string\n}\n"],"names":[],"mappings":"AAwCA,2EAA2E,GAC3E,WA2BC"}
1
+ {"version":3,"sources":["../../src/types/AltTextPluginConfig.ts"],"sourcesContent":["import { CollectionSlug, Field } from 'payload'\n\n/** Configuration options for the alt text plugin. */\nexport type IncomingAltTextPluginConfig = {\n /** Whether the plugin is enabled. */\n enabled?: boolean\n\n /** OpenAI API key for authentication. */\n openAIApiKey: string\n\n /** Collection slugs to enable the plugin for. */\n collections: CollectionSlug[]\n\n /** Maximum number of concurrent API requests for bulk operations. */\n maxBulkGenerateConcurrency?: number\n\n /**\n * Function to get the thumbnail URL of an image document.\n * This URL will be sent to the LLM for analysis.\n *\n * @remarks\n * - The URL must be publicly accessible so the LLM can fetch it\n * - Use a thumbnail/preview version of the image when possible (e.g. from the sizes field)\n */\n getImageThumbnail: (doc: Record<string, unknown>) => string\n\n /** The OpenAI LLM model to use for alt text generation. */\n model?: 'gpt-4.1-nano' | 'gpt-4.1-mini'\n\n /** Override the default fields inserted by the plugin via a function that receives the default fields and returns the new fields */\n fieldsOverride?: (args: { defaultFields: Field[] }) => Field[]\n\n /**\n * The locale to generate alt texts in when localization is disabled.\n * Required when localization is disabled, ignored when localization is enabled.\n * @example 'en', 'de'\n */\n locale?: string\n}\n\n/** Configuration of the alt text plugin after defaults have been applied. */\nexport type AltTextPluginConfig = {\n /** Whether the plugin is enabled. */\n enabled: boolean\n\n /** OpenAI API key for authentication. */\n openAIApiKey: string\n\n /** Collection slugs to enable the plugin for. */\n collections: CollectionSlug[]\n\n /** Maximum number of concurrent API requests for bulk generate operations. */\n maxBulkGenerateConcurrency: number\n\n /** Function to get the thumbnail URL of an image document. */\n getImageThumbnail: (doc: Record<string, unknown>) => string\n\n /** The OpenAI LLM model to use for alt text generation. */\n model: 'gpt-4.1-nano' | 'gpt-4.1-mini'\n\n /** Override the default fields inserted by the plugin via a function that receives the default fields and returns the new fields */\n fieldsOverride?: (args: { defaultFields: Field[] }) => Field[]\n\n /** The locales to generate alt texts for. */\n locales: string[]\n\n /** The locale to generate alt texts in when localization is disabled. */\n locale?: string\n}\n"],"names":[],"mappings":"AAwCA,2EAA2E,GAC3E,WA2BC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhb.software/payload-alt-text-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A minimal Payload CMS plugin to generate image alt texts using OpenAI's Vision API.",
5
5
  "bugs": "https://github.com/jhb-software/payload-plugins/issues",
6
6
  "repository": "https://github.com/jhb-software/payload-plugins",