@jhb.software/payload-alt-text-plugin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +54 -0
  3. package/dist/components/AltTextField.d.ts +2 -0
  4. package/dist/components/AltTextField.js +40 -0
  5. package/dist/components/AltTextField.js.map +1 -0
  6. package/dist/components/BulkGenerateAltTextsButton.d.ts +1 -0
  7. package/dist/components/BulkGenerateAltTextsButton.js +69 -0
  8. package/dist/components/BulkGenerateAltTextsButton.js.map +1 -0
  9. package/dist/components/GenerateAltTextButton.d.ts +1 -0
  10. package/dist/components/GenerateAltTextButton.js +109 -0
  11. package/dist/components/GenerateAltTextButton.js.map +1 -0
  12. package/dist/components/icons/Lightning.d.ts +1 -0
  13. package/dist/components/icons/Lightning.js +19 -0
  14. package/dist/components/icons/Lightning.js.map +1 -0
  15. package/dist/components/icons/Spinner.d.ts +1 -0
  16. package/dist/components/icons/Spinner.js +42 -0
  17. package/dist/components/icons/Spinner.js.map +1 -0
  18. package/dist/endpoints/bulkGenerateAltTexts.d.ts +5 -0
  19. package/dist/endpoints/bulkGenerateAltTexts.js +150 -0
  20. package/dist/endpoints/bulkGenerateAltTexts.js.map +1 -0
  21. package/dist/endpoints/generateAltText.d.ts +6 -0
  22. package/dist/endpoints/generateAltText.js +128 -0
  23. package/dist/endpoints/generateAltText.js.map +1 -0
  24. package/dist/exports/client.d.ts +3 -0
  25. package/dist/exports/client.js +5 -0
  26. package/dist/exports/client.js.map +1 -0
  27. package/dist/fields/altTextField.d.ts +4 -0
  28. package/dist/fields/altTextField.js +27 -0
  29. package/dist/fields/altTextField.js.map +1 -0
  30. package/dist/fields/keywordsField.d.ts +4 -0
  31. package/dist/fields/keywordsField.js +17 -0
  32. package/dist/fields/keywordsField.js.map +1 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +3 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/plugin.d.ts +3 -0
  37. package/dist/plugin.js +90 -0
  38. package/dist/plugin.js.map +1 -0
  39. package/dist/types/AltTextPluginConfig.d.ts +48 -0
  40. package/dist/types/AltTextPluginConfig.js +3 -0
  41. package/dist/types/AltTextPluginConfig.js.map +1 -0
  42. package/dist/utilities/getGenerationCost.d.ts +12 -0
  43. package/dist/utilities/getGenerationCost.js +30 -0
  44. package/dist/utilities/getGenerationCost.js.map +1 -0
  45. package/dist/utilities/zodResponseFormat.d.ts +11 -0
  46. package/dist/utilities/zodResponseFormat.js +23 -0
  47. package/dist/utilities/zodResponseFormat.js.map +1 -0
  48. package/package.json +99 -0
@@ -0,0 +1,128 @@
1
+ import OpenAI from 'openai';
2
+ import { z } from 'zod';
3
+ import { getGenerationCost } from '../utilities/getGenerationCost.js';
4
+ import { zodResponseFormat } from '../utilities/zodResponseFormat.js';
5
+ /**
6
+ * Generates alt text for a single image using OpenAI Vision API.
7
+ * Returns result without updating the document.
8
+ */ export const generateAltTextEndpoint = async (req)=>{
9
+ try {
10
+ if (!req.user) {
11
+ return Response.json({
12
+ error: 'Unauthorized'
13
+ }, {
14
+ status: 401
15
+ });
16
+ }
17
+ const data = 'json' in req && typeof req.json === 'function' ? await req.json() : null;
18
+ const requestSchema = z.object({
19
+ collection: z.string(),
20
+ id: z.string(),
21
+ locale: z.string()
22
+ });
23
+ const { collection, id, locale } = requestSchema.parse(data);
24
+ const imageDoc = await req.payload.findByID({
25
+ collection,
26
+ id,
27
+ depth: 0
28
+ });
29
+ if (!imageDoc) {
30
+ return Response.json({
31
+ error: 'Image not found'
32
+ }, {
33
+ status: 404
34
+ });
35
+ }
36
+ const pluginConfig = req.payload.config.custom?.altTextPluginConfig;
37
+ if (!pluginConfig?.getImageThumbnail) {
38
+ return Response.json({
39
+ error: 'getImageThumbnail function not configured'
40
+ }, {
41
+ status: 500
42
+ });
43
+ }
44
+ const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc);
45
+ if (!imageThumbnailUrl) {
46
+ return Response.json({
47
+ error: 'Image thumbnail URL not defined'
48
+ }, {
49
+ status: 500
50
+ });
51
+ }
52
+ if (!imageThumbnailUrl.startsWith('https://') && !imageThumbnailUrl.includes('http://')) {
53
+ return Response.json({
54
+ error: 'Image thumbnail URL is not a valid URL. It must start with https:// or http://'
55
+ }, {
56
+ status: 500
57
+ });
58
+ }
59
+ const openai = new OpenAI({
60
+ apiKey: pluginConfig.openAIApiKey
61
+ });
62
+ const modelResponseSchema = z.object({
63
+ altText: z.string().describe('A concise, descriptive alt text for the image'),
64
+ keywords: z.array(z.string()).describe('Keywords that describe the content of the image')
65
+ });
66
+ const response = await openai.chat.completions.parse({
67
+ model: pluginConfig.model,
68
+ messages: [
69
+ {
70
+ role: 'system',
71
+ content: `
72
+ You are an expert at analyzing images and creating descriptive image alt text.
73
+
74
+ Please analyze the given image and provide the following:
75
+ - A concise, descriptive alt text (1-2 sentences) as "altText". Focus on the subject, action, and setting. Avoid phrases like 'Image of', 'A picture of', or 'Photo showing'. Be specific and include relevant details like location or context if visible. Make no assumptions.
76
+ - A list of keywords that describe the content (e.g., ["Camel", "Palm trees", "Desert"]) as "keywords"
77
+
78
+ If a context is provided, use it to enhance the alt text.
79
+
80
+ Format your response as a JSON object. You must respond in the ${locale} language.
81
+ `
82
+ },
83
+ {
84
+ role: 'user',
85
+ content: [
86
+ {
87
+ type: 'image_url',
88
+ image_url: {
89
+ url: imageThumbnailUrl
90
+ }
91
+ },
92
+ ...'filename' in imageDoc && imageDoc.filename ? [
93
+ {
94
+ type: 'text',
95
+ text: imageDoc.filename
96
+ }
97
+ ] : []
98
+ ]
99
+ }
100
+ ],
101
+ // limit the response tokens and costs per request
102
+ max_completion_tokens: 150,
103
+ response_format: zodResponseFormat(modelResponseSchema, 'data')
104
+ });
105
+ console.log({
106
+ imageId: id,
107
+ ...getGenerationCost(response, pluginConfig.model)
108
+ });
109
+ const result = response.choices[0]?.message?.parsed;
110
+ if (!result) {
111
+ return Response.json({
112
+ error: 'No result from OpenAI'
113
+ }, {
114
+ status: 500
115
+ });
116
+ }
117
+ return Response.json(result);
118
+ } catch (error) {
119
+ console.error('Error generating alt text:', error);
120
+ return Response.json({
121
+ error: `Error generating alt text: ${error instanceof Error ? error.message : 'Unknown error'}`
122
+ }, {
123
+ status: 500
124
+ });
125
+ }
126
+ };
127
+
128
+ //# sourceMappingURL=generateAltText.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/endpoints/generateAltText.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { ChatCompletionContentPartText } from 'openai/resources/chat/completions.mjs'\nimport type { PayloadHandler, PayloadRequest } from 'payload'\nimport { z } from 'zod'\nimport { getGenerationCost } from '../utilities/getGenerationCost.js'\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\nimport { zodResponseFormat } from '../utilities/zodResponseFormat.js'\n\n/**\n * Generates alt text for a single image using OpenAI Vision API.\n * Returns result without updating the document.\n */\nexport const generateAltTextEndpoint: PayloadHandler = async (req: PayloadRequest) => {\n try {\n if (!req.user) {\n return Response.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const data = 'json' in req && typeof req.json === 'function' ? await req.json() : null\n\n const requestSchema = z.object({\n collection: z.string(),\n id: z.string(),\n locale: z.string(),\n })\n\n const { collection, id, locale } = requestSchema.parse(data)\n\n const imageDoc = await req.payload.findByID({\n collection,\n id,\n depth: 0,\n })\n\n if (!imageDoc) {\n return Response.json({ error: 'Image not found' }, { status: 404 })\n }\n\n const pluginConfig = req.payload.config.custom?.altTextPluginConfig as\n | AltTextPluginConfig\n | undefined\n\n if (!pluginConfig?.getImageThumbnail) {\n return Response.json({ error: 'getImageThumbnail function not configured' }, { status: 500 })\n }\n const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc)\n\n if (!imageThumbnailUrl) {\n return Response.json({ error: 'Image thumbnail URL not defined' }, { status: 500 })\n }\n\n if (!imageThumbnailUrl.startsWith('https://') && !imageThumbnailUrl.includes('http://')) {\n return Response.json(\n { error: 'Image thumbnail URL is not a valid URL. It must start with https:// or http://' },\n { status: 500 },\n )\n }\n\n const openai = new OpenAI({\n apiKey: pluginConfig.openAIApiKey,\n })\n\n const modelResponseSchema = z.object({\n altText: z.string().describe('A concise, descriptive alt text for the image'),\n keywords: z.array(z.string()).describe('Keywords that describe the content of the image'),\n })\n\n const response = await openai.chat.completions.parse({\n model: pluginConfig.model,\n messages: [\n {\n role: 'system',\n content: `\n You are an expert at analyzing images and creating descriptive image alt text. \n \n Please analyze the given image and provide the following:\n - A concise, descriptive alt text (1-2 sentences) as \"altText\". Focus on the subject, action, and setting. Avoid phrases like 'Image of', 'A picture of', or 'Photo showing'. Be specific and include relevant details like location or context if visible. Make no assumptions.\n - A list of keywords that describe the content (e.g., [\"Camel\", \"Palm trees\", \"Desert\"]) as \"keywords\"\n\n If a context is provided, use it to enhance the alt text.\n\n Format your response as a JSON object. You must respond in the ${locale} language.\n `,\n },\n {\n role: 'user',\n content: [\n {\n type: 'image_url',\n image_url: { url: imageThumbnailUrl },\n },\n ...('filename' in imageDoc && imageDoc.filename\n ? [\n {\n type: 'text',\n text: imageDoc.filename,\n } satisfies ChatCompletionContentPartText,\n ]\n : []),\n ],\n },\n ],\n // limit the response tokens and costs per request\n max_completion_tokens: 150,\n response_format: zodResponseFormat(modelResponseSchema, 'data'),\n })\n\n console.log({ imageId: id, ...getGenerationCost(response, pluginConfig.model) })\n\n const result = response.choices[0]?.message?.parsed\n\n if (!result) {\n return Response.json({ error: 'No result from OpenAI' }, { status: 500 })\n }\n\n return Response.json(result)\n } catch (error) {\n console.error('Error generating alt text:', error)\n return Response.json(\n {\n error: `Error generating alt text: ${error instanceof Error ? error.message : 'Unknown error'}`,\n },\n { status: 500 },\n )\n }\n}\n"],"names":["OpenAI","z","getGenerationCost","zodResponseFormat","generateAltTextEndpoint","req","user","Response","json","error","status","data","requestSchema","object","collection","string","id","locale","parse","imageDoc","payload","findByID","depth","pluginConfig","config","custom","altTextPluginConfig","getImageThumbnail","imageThumbnailUrl","startsWith","includes","openai","apiKey","openAIApiKey","modelResponseSchema","altText","describe","keywords","array","response","chat","completions","model","messages","role","content","type","image_url","url","filename","text","max_completion_tokens","response_format","console","log","imageId","result","choices","message","parsed","Error"],"mappings":"AAAA,OAAOA,YAAY,SAAQ;AAG3B,SAASC,CAAC,QAAQ,MAAK;AACvB,SAASC,iBAAiB,QAAQ,oCAAmC;AAErE,SAASC,iBAAiB,QAAQ,oCAAmC;AAErE;;;CAGC,GACD,OAAO,MAAMC,0BAA0C,OAAOC;IAC5D,IAAI;QACF,IAAI,CAACA,IAAIC,IAAI,EAAE;YACb,OAAOC,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAAe,GAAG;gBAAEC,QAAQ;YAAI;QAChE;QAEA,MAAMC,OAAO,UAAUN,OAAO,OAAOA,IAAIG,IAAI,KAAK,aAAa,MAAMH,IAAIG,IAAI,KAAK;QAElF,MAAMI,gBAAgBX,EAAEY,MAAM,CAAC;YAC7BC,YAAYb,EAAEc,MAAM;YACpBC,IAAIf,EAAEc,MAAM;YACZE,QAAQhB,EAAEc,MAAM;QAClB;QAEA,MAAM,EAAED,UAAU,EAAEE,EAAE,EAAEC,MAAM,EAAE,GAAGL,cAAcM,KAAK,CAACP;QAEvD,MAAMQ,WAAW,MAAMd,IAAIe,OAAO,CAACC,QAAQ,CAAC;YAC1CP;YACAE;YACAM,OAAO;QACT;QAEA,IAAI,CAACH,UAAU;YACb,OAAOZ,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAAkB,GAAG;gBAAEC,QAAQ;YAAI;QACnE;QAEA,MAAMa,eAAelB,IAAIe,OAAO,CAACI,MAAM,CAACC,MAAM,EAAEC;QAIhD,IAAI,CAACH,cAAcI,mBAAmB;YACpC,OAAOpB,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAA4C,GAAG;gBAAEC,QAAQ;YAAI;QAC7F;QACA,MAAMkB,oBAAoBL,aAAaI,iBAAiB,CAACR;QAEzD,IAAI,CAACS,mBAAmB;YACtB,OAAOrB,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAAkC,GAAG;gBAAEC,QAAQ;YAAI;QACnF;QAEA,IAAI,CAACkB,kBAAkBC,UAAU,CAAC,eAAe,CAACD,kBAAkBE,QAAQ,CAAC,YAAY;YACvF,OAAOvB,SAASC,IAAI,CAClB;gBAAEC,OAAO;YAAiF,GAC1F;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAMqB,SAAS,IAAI/B,OAAO;YACxBgC,QAAQT,aAAaU,YAAY;QACnC;QAEA,MAAMC,sBAAsBjC,EAAEY,MAAM,CAAC;YACnCsB,SAASlC,EAAEc,MAAM,GAAGqB,QAAQ,CAAC;YAC7BC,UAAUpC,EAAEqC,KAAK,CAACrC,EAAEc,MAAM,IAAIqB,QAAQ,CAAC;QACzC;QAEA,MAAMG,WAAW,MAAMR,OAAOS,IAAI,CAACC,WAAW,CAACvB,KAAK,CAAC;YACnDwB,OAAOnB,aAAamB,KAAK;YACzBC,UAAU;gBACR;oBACEC,MAAM;oBACNC,SAAS,CAAC;;;;;;;;;2EASuD,EAAE5B,OAAO;UAC1E,CAAC;gBACH;gBACA;oBACE2B,MAAM;oBACNC,SAAS;wBACP;4BACEC,MAAM;4BACNC,WAAW;gCAAEC,KAAKpB;4BAAkB;wBACtC;2BACI,cAAcT,YAAYA,SAAS8B,QAAQ,GAC3C;4BACE;gCACEH,MAAM;gCACNI,MAAM/B,SAAS8B,QAAQ;4BACzB;yBACD,GACD,EAAE;qBACP;gBACH;aACD;YACD,kDAAkD;YAClDE,uBAAuB;YACvBC,iBAAiBjD,kBAAkB+B,qBAAqB;QAC1D;QAEAmB,QAAQC,GAAG,CAAC;YAAEC,SAASvC;YAAI,GAAGd,kBAAkBqC,UAAUhB,aAAamB,KAAK,CAAC;QAAC;QAE9E,MAAMc,SAASjB,SAASkB,OAAO,CAAC,EAAE,EAAEC,SAASC;QAE7C,IAAI,CAACH,QAAQ;YACX,OAAOjD,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAAwB,GAAG;gBAAEC,QAAQ;YAAI;QACzE;QAEA,OAAOH,SAASC,IAAI,CAACgD;IACvB,EAAE,OAAO/C,OAAO;QACd4C,QAAQ5C,KAAK,CAAC,8BAA8BA;QAC5C,OAAOF,SAASC,IAAI,CAClB;YACEC,OAAO,CAAC,2BAA2B,EAAEA,iBAAiBmD,QAAQnD,MAAMiD,OAAO,GAAG,iBAAiB;QACjG,GACA;YAAEhD,QAAQ;QAAI;IAElB;AACF,EAAC"}
@@ -0,0 +1,3 @@
1
+ export { AltTextField } from '../components/AltTextField.js';
2
+ export { GenerateAltTextButton } from '../components/GenerateAltTextButton.js';
3
+ export { BulkGenerateAltTextsButton } from '../components/BulkGenerateAltTextsButton.js';
@@ -0,0 +1,5 @@
1
+ export { AltTextField } from '../components/AltTextField.js';
2
+ export { GenerateAltTextButton } from '../components/GenerateAltTextButton.js';
3
+ export { BulkGenerateAltTextsButton } from '../components/BulkGenerateAltTextsButton.js';
4
+
5
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { AltTextField } from '../components/AltTextField.js'\nexport { GenerateAltTextButton } from '../components/GenerateAltTextButton.js'\nexport { BulkGenerateAltTextsButton } from '../components/BulkGenerateAltTextsButton.js'\n"],"names":["AltTextField","GenerateAltTextButton","BulkGenerateAltTextsButton"],"mappings":"AAAA,SAASA,YAAY,QAAQ,gCAA+B;AAC5D,SAASC,qBAAqB,QAAQ,yCAAwC;AAC9E,SAASC,0BAA0B,QAAQ,8CAA6C"}
@@ -0,0 +1,4 @@
1
+ import type { TextareaField } from 'payload';
2
+ export declare function altTextField({ localized, }: {
3
+ localized?: TextareaField['localized'];
4
+ }): TextareaField;
@@ -0,0 +1,27 @@
1
+ export function altTextField({ localized }) {
2
+ return {
3
+ name: 'alt',
4
+ label: 'Alternate text',
5
+ type: 'textarea',
6
+ required: true,
7
+ localized: localized,
8
+ validate: (value, ctx)=>{
9
+ // if the document has an id, the alt text is required
10
+ if (ctx.id) {
11
+ if (!value || value.trim().length === 0) {
12
+ return 'The alternate text is required.';
13
+ }
14
+ }
15
+ // The alt text is not required when the document is created because the alt text generation
16
+ // can only be used once the document is created and the image uploaded
17
+ return true;
18
+ },
19
+ admin: {
20
+ components: {
21
+ Field: '@jhb.software/payload-alt-text-plugin/client#AltTextField'
22
+ }
23
+ }
24
+ };
25
+ }
26
+
27
+ //# sourceMappingURL=altTextField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/fields/altTextField.ts"],"sourcesContent":["import type { TextareaField } from 'payload'\n\nexport function altTextField({\n localized,\n}: {\n localized?: TextareaField['localized']\n}): TextareaField {\n return {\n name: 'alt',\n label: 'Alternate text',\n type: 'textarea',\n required: true,\n localized: localized,\n validate: (value, ctx) => {\n // if the document has an id, the alt text is required\n if (ctx.id) {\n if (!value || value.trim().length === 0) {\n return 'The alternate text is required.'\n }\n }\n\n // The alt text is not required when the document is created because the alt text generation\n // can only be used once the document is created and the image uploaded\n return true\n },\n admin: {\n components: {\n Field: '@jhb.software/payload-alt-text-plugin/client#AltTextField',\n },\n },\n }\n}\n"],"names":["altTextField","localized","name","label","type","required","validate","value","ctx","id","trim","length","admin","components","Field"],"mappings":"AAEA,OAAO,SAASA,aAAa,EAC3BC,SAAS,EAGV;IACC,OAAO;QACLC,MAAM;QACNC,OAAO;QACPC,MAAM;QACNC,UAAU;QACVJ,WAAWA;QACXK,UAAU,CAACC,OAAOC;YAChB,sDAAsD;YACtD,IAAIA,IAAIC,EAAE,EAAE;gBACV,IAAI,CAACF,SAASA,MAAMG,IAAI,GAAGC,MAAM,KAAK,GAAG;oBACvC,OAAO;gBACT;YACF;YAEA,4FAA4F;YAC5F,uEAAuE;YACvE,OAAO;QACT;QACAC,OAAO;YACLC,YAAY;gBACVC,OAAO;YACT;QACF;IACF;AACF"}
@@ -0,0 +1,4 @@
1
+ import type { TextField } from 'payload';
2
+ export declare function keywordsField({ localized }: {
3
+ localized?: TextField['localized'];
4
+ }): TextField;
@@ -0,0 +1,17 @@
1
+ export function keywordsField({ localized }) {
2
+ return {
3
+ name: 'keywords',
4
+ label: 'Keywords',
5
+ type: 'text',
6
+ hasMany: true,
7
+ required: false,
8
+ localized: localized,
9
+ hidden: true,
10
+ admin: {
11
+ description: 'Keywords which describe the image. Used when searching for the image.',
12
+ readOnly: true
13
+ }
14
+ };
15
+ }
16
+
17
+ //# sourceMappingURL=keywordsField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/fields/keywordsField.ts"],"sourcesContent":["import type { TextField } from 'payload'\n\nexport function keywordsField({ localized }: { localized?: TextField['localized'] }): TextField {\n return {\n name: 'keywords',\n label: 'Keywords',\n type: 'text',\n hasMany: true,\n required: false,\n localized: localized,\n hidden: true, // this field is only meant to be used for improving the search\n admin: {\n description: 'Keywords which describe the image. Used when searching for the image.',\n readOnly: true,\n },\n }\n}\n"],"names":["keywordsField","localized","name","label","type","hasMany","required","hidden","admin","description","readOnly"],"mappings":"AAEA,OAAO,SAASA,cAAc,EAAEC,SAAS,EAA0C;IACjF,OAAO;QACLC,MAAM;QACNC,OAAO;QACPC,MAAM;QACNC,SAAS;QACTC,UAAU;QACVL,WAAWA;QACXM,QAAQ;QACRC,OAAO;YACLC,aAAa;YACbC,UAAU;QACZ;IACF;AACF"}
@@ -0,0 +1,2 @@
1
+ export { payloadAltTextPlugin } from './plugin.js';
2
+ export type { IncomingAltTextPluginConfig as AltTextPluginConfig } from './types/AltTextPluginConfig.js';
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { payloadAltTextPlugin } from './plugin.js';
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { payloadAltTextPlugin } from './plugin.js'\nexport type { IncomingAltTextPluginConfig as AltTextPluginConfig } from './types/AltTextPluginConfig.js'\n"],"names":["payloadAltTextPlugin"],"mappings":"AAAA,SAASA,oBAAoB,QAAQ,cAAa"}
@@ -0,0 +1,3 @@
1
+ import type { Config } from 'payload';
2
+ import type { IncomingAltTextPluginConfig } from './types/AltTextPluginConfig.js';
3
+ export declare const payloadAltTextPlugin: (incomingPluginConfig: IncomingAltTextPluginConfig) => (incomingConfig: Config) => Config;
package/dist/plugin.js ADDED
@@ -0,0 +1,90 @@
1
+ import { altTextField } from './fields/altTextField.js';
2
+ import { keywordsField } from './fields/keywordsField.js';
3
+ import { generateAltTextEndpoint } from './endpoints/generateAltText.js';
4
+ import { bulkGenerateAltTextsEndpoint } from './endpoints/bulkGenerateAltTexts.js';
5
+ export const payloadAltTextPlugin = (incomingPluginConfig)=>(incomingConfig)=>{
6
+ const config = {
7
+ ...incomingConfig
8
+ };
9
+ // If the plugin is disabled, return the config without modifying it
10
+ if (incomingPluginConfig.enabled === false) {
11
+ return config;
12
+ }
13
+ const locales = config.localization ? config.localization.locales.map((localeConfig)=>typeof localeConfig === 'string' ? localeConfig : localeConfig.code) : [];
14
+ if (locales.length === 0) {
15
+ throw new Error('The alt text plugin currently only supports localized setups. If you need to use this plugin in a non-localized setup, please open an issue at https://github.com/jhb-software/payload-plugins.');
16
+ }
17
+ const pluginConfig = {
18
+ enabled: incomingPluginConfig.enabled ?? true,
19
+ openAIApiKey: incomingPluginConfig.openAIApiKey,
20
+ collections: incomingPluginConfig.collections,
21
+ maxBulkGenerateConcurrency: incomingPluginConfig.maxBulkGenerateConcurrency ?? 16,
22
+ model: incomingPluginConfig.model ?? 'gpt-4.1-nano',
23
+ locales: locales,
24
+ getImageThumbnail: incomingPluginConfig.getImageThumbnail,
25
+ fieldsOverride: incomingPluginConfig.fieldsOverride
26
+ };
27
+ const defaultFields = [
28
+ altTextField({
29
+ localized: Boolean(config.localization)
30
+ }),
31
+ keywordsField({
32
+ localized: Boolean(config.localization)
33
+ })
34
+ ];
35
+ const fields = incomingPluginConfig.fieldsOverride && typeof incomingPluginConfig.fieldsOverride === 'function' ? incomingPluginConfig.fieldsOverride({
36
+ defaultFields
37
+ }) : defaultFields;
38
+ // Ensure collections array exists
39
+ config.collections = config.collections || [];
40
+ // Map over collections and inject AI alt text fields into specified ones
41
+ config.collections = config.collections.map((collectionConfig)=>{
42
+ if (pluginConfig.collections.includes(collectionConfig.slug)) {
43
+ if (!collectionConfig.upload) {
44
+ console.warn(`AI Alt Text Plugin: Collection "${collectionConfig.slug}" is not an upload collection. Skipping field injection.`);
45
+ return collectionConfig;
46
+ }
47
+ return {
48
+ ...collectionConfig,
49
+ admin: {
50
+ ...collectionConfig.admin,
51
+ components: {
52
+ ...collectionConfig.admin?.components ?? {},
53
+ beforeListTable: [
54
+ ...collectionConfig.admin?.components?.beforeListTable ?? [],
55
+ '@jhb.software/payload-alt-text-plugin/client#BulkGenerateAltTextsButton'
56
+ ]
57
+ }
58
+ },
59
+ fields: [
60
+ ...collectionConfig.fields ?? [],
61
+ ...fields
62
+ ]
63
+ };
64
+ }
65
+ return collectionConfig;
66
+ });
67
+ return {
68
+ ...config,
69
+ custom: {
70
+ ...config.custom,
71
+ // Make plugin config available in hooks/actions
72
+ altTextPluginConfig: pluginConfig
73
+ },
74
+ endpoints: [
75
+ ...config.endpoints ?? [],
76
+ {
77
+ path: '/alt-text-plugin/generate-alt-text',
78
+ method: 'post',
79
+ handler: generateAltTextEndpoint
80
+ },
81
+ {
82
+ path: '/alt-text-plugin/bulk-generate-alt-texts',
83
+ method: 'post',
84
+ handler: bulkGenerateAltTextsEndpoint
85
+ }
86
+ ]
87
+ };
88
+ };
89
+
90
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +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'\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 if (locales.length === 0) {\n throw new Error(\n 'The alt text plugin currently only supports localized setups. If you need to use this plugin in a non-localized setup, please open an issue at https://github.com/jhb-software/payload-plugins.',\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 getImageThumbnail: incomingPluginConfig.getImageThumbnail,\n fieldsOverride: incomingPluginConfig.fieldsOverride,\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 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 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","payloadAltTextPlugin","incomingPluginConfig","incomingConfig","config","enabled","locales","localization","map","localeConfig","code","length","Error","pluginConfig","openAIApiKey","collections","maxBulkGenerateConcurrency","model","getImageThumbnail","fieldsOverride","defaultFields","localized","Boolean","fields","collectionConfig","includes","slug","upload","console","warn","admin","components","beforeListTable","custom","altTextPluginConfig","endpoints","path","method","handler"],"mappings":"AAMA,SAASA,YAAY,QAAQ,2BAA0B;AACvD,SAASC,aAAa,QAAQ,4BAA2B;AACzD,SAASC,uBAAuB,QAAQ,iCAAgC;AACxE,SAASC,4BAA4B,QAAQ,sCAAqC;AAElF,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,IAAIJ,QAAQK,MAAM,KAAK,GAAG;YACxB,MAAM,IAAIC,MACR;QAEJ;QAEA,MAAMC,eAAoC;YACxCR,SAASH,qBAAqBG,OAAO,IAAI;YACzCS,cAAcZ,qBAAqBY,YAAY;YAC/CC,aAAab,qBAAqBa,WAAW;YAC7CC,4BAA4Bd,qBAAqBc,0BAA0B,IAAI;YAC/EC,OAAOf,qBAAqBe,KAAK,IAAI;YACrCX,SAASA;YACTY,mBAAmBhB,qBAAqBgB,iBAAiB;YACzDC,gBAAgBjB,qBAAqBiB,cAAc;QACrD;QAEA,MAAMC,gBAAgB;YACpBvB,aAAa;gBACXwB,WAAWC,QAAQlB,OAAOG,YAAY;YACxC;YACAT,cAAc;gBACZuB,WAAWC,QAAQlB,OAAOG,YAAY;YACxC;SACD;QAED,MAAMgB,SACJrB,qBAAqBiB,cAAc,IACnC,OAAOjB,qBAAqBiB,cAAc,KAAK,aAC3CjB,qBAAqBiB,cAAc,CAAC;YAAEC;QAAc,KACpDA;QAEN,kCAAkC;QAClChB,OAAOW,WAAW,GAAGX,OAAOW,WAAW,IAAI,EAAE;QAE7C,yEAAyE;QACzEX,OAAOW,WAAW,GAAGX,OAAOW,WAAW,CAACP,GAAG,CAAC,CAACgB;YAC3C,IAAIX,aAAaE,WAAW,CAACU,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,YAAY;4BACV,GAAIP,iBAAiBM,KAAK,EAAEC,cAAc,CAAC,CAAC;4BAC5CC,iBAAiB;mCACXR,iBAAiBM,KAAK,EAAEC,YAAYC,mBAAmB,EAAE;gCAC7D;6BACD;wBACH;oBACF;oBACAT,QAAQ;2BAAKC,iBAAiBD,MAAM,IAAI,EAAE;2BAAMA;qBAAO;gBACzD;YACF;YAEA,OAAOC;QACT;QAEA,OAAO;YACL,GAAGpB,MAAM;YACT6B,QAAQ;gBACN,GAAG7B,OAAO6B,MAAM;gBAChB,gDAAgD;gBAChDC,qBAAqBrB;YACvB;YACAsB,WAAW;mBACL/B,OAAO+B,SAAS,IAAI,EAAE;gBAC1B;oBACEC,MAAM;oBACNC,QAAQ;oBACRC,SAASvC;gBACX;gBACA;oBACEqC,MAAM;oBACNC,QAAQ;oBACRC,SAAStC;gBACX;aACD;QACH;IACF,EAAC"}
@@ -0,0 +1,48 @@
1
+ import { Field } from 'payload';
2
+ /** Configuration options for the alt text plugin. */
3
+ export type IncomingAltTextPluginConfig = {
4
+ /** Whether the plugin is enabled. */
5
+ enabled?: boolean;
6
+ /** OpenAI API key for authentication. */
7
+ openAIApiKey: string;
8
+ /** Collection slugs to enable the plugin for. */
9
+ collections: string[];
10
+ /** Maximum number of concurrent API requests for bulk operations. */
11
+ maxBulkGenerateConcurrency?: number;
12
+ /**
13
+ * Function to get the thumbnail URL of an image document.
14
+ * This URL will be sent to the LLM for analysis.
15
+ *
16
+ * @remarks
17
+ * - The URL must be publicly accessible so the LLM can fetch it
18
+ * - Use a thumbnail/preview version of the image when possible (e.g. from the sizes field)
19
+ */
20
+ getImageThumbnail: (doc: Record<string, unknown>) => string;
21
+ /** The OpenAI LLM model to use for alt text generation. */
22
+ model?: 'gpt-4.1-nano' | 'gpt-4.1-mini';
23
+ /** Override the default fields inserted by the plugin via a function that receives the default fields and returns the new fields */
24
+ fieldsOverride?: (args: {
25
+ defaultFields: Field[];
26
+ }) => Field[];
27
+ };
28
+ /** Configuration of the alt text plugin after defaults have been applied. */
29
+ export type AltTextPluginConfig = {
30
+ /** Whether the plugin is enabled. */
31
+ enabled: boolean;
32
+ /** OpenAI API key for authentication. */
33
+ openAIApiKey: string;
34
+ /** Collection slugs to enable the plugin for. */
35
+ collections: string[];
36
+ /** Maximum number of concurrent API requests for bulk generate operations. */
37
+ maxBulkGenerateConcurrency: number;
38
+ /** Function to get the thumbnail URL of an image document. */
39
+ getImageThumbnail: (doc: Record<string, unknown>) => string;
40
+ /** The OpenAI LLM model to use for alt text generation. */
41
+ model: 'gpt-4.1-nano' | 'gpt-4.1-mini';
42
+ /** Override the default fields inserted by the plugin via a function that receives the default fields and returns the new fields */
43
+ fieldsOverride?: (args: {
44
+ defaultFields: Field[];
45
+ }) => Field[];
46
+ /** The locales to generate alt texts for. */
47
+ locales: string[];
48
+ };
@@ -0,0 +1,3 @@
1
+ /** Configuration of the alt text plugin after defaults have been applied. */ export { };
2
+
3
+ //# sourceMappingURL=AltTextPluginConfig.js.map
@@ -0,0 +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/** 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"],"names":[],"mappings":"AAiCA,2EAA2E,GAC3E,WAwBC"}
@@ -0,0 +1,12 @@
1
+ import type OpenAI from 'openai';
2
+ import type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js';
3
+ type GenerationCost = {
4
+ model: 'gpt-4.1-nano' | 'gpt-4.1-mini';
5
+ inputCost: number;
6
+ outputCost: number;
7
+ totalCost: number;
8
+ totalTokens: number;
9
+ };
10
+ /** Calculates the cost of a generation. */
11
+ export declare function getGenerationCost(response: OpenAI.Chat.Completions.ChatCompletion, model: AltTextPluginConfig['model']): GenerationCost;
12
+ export {};
@@ -0,0 +1,30 @@
1
+ /** Calculates the cost of a generation. */ export function getGenerationCost(response, model) {
2
+ const modelCosts = {
3
+ // see https://platform.openai.com/docs/models/gpt-4.1-nano
4
+ 'gpt-4.1-nano': {
5
+ input: 0.1,
6
+ output: 0.4
7
+ },
8
+ // see https://platform.openai.com/docs/models/gpt-4.1-mini
9
+ 'gpt-4.1-mini': {
10
+ input: 0.4,
11
+ output: 1.6
12
+ }
13
+ };
14
+ // Calculate cost based on token usage
15
+ const inputTokens = response.usage?.prompt_tokens || 0;
16
+ const outputTokens = response.usage?.completion_tokens || 0;
17
+ const totalTokens = response.usage?.total_tokens || 0;
18
+ const inputCost = inputTokens / 1_000_000 * modelCosts[model].input;
19
+ const outputCost = outputTokens / 1_000_000 * modelCosts[model].output;
20
+ const totalCost = inputCost + outputCost;
21
+ return {
22
+ model,
23
+ inputCost,
24
+ outputCost,
25
+ totalCost,
26
+ totalTokens
27
+ };
28
+ }
29
+
30
+ //# sourceMappingURL=getGenerationCost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/getGenerationCost.ts"],"sourcesContent":["import type OpenAI from 'openai'\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\ntype GenerationCost = {\n model: 'gpt-4.1-nano' | 'gpt-4.1-mini'\n inputCost: number\n outputCost: number\n totalCost: number\n totalTokens: number\n}\n\n/** Calculates the cost of a generation. */\nexport function getGenerationCost(\n response: OpenAI.Chat.Completions.ChatCompletion,\n model: AltTextPluginConfig['model'],\n): GenerationCost {\n const modelCosts: Record<AltTextPluginConfig['model'], { input: number; output: number }> = {\n // see https://platform.openai.com/docs/models/gpt-4.1-nano\n 'gpt-4.1-nano': {\n input: 0.1,\n output: 0.4,\n },\n // see https://platform.openai.com/docs/models/gpt-4.1-mini\n 'gpt-4.1-mini': {\n input: 0.4,\n output: 1.6,\n },\n }\n\n // Calculate cost based on token usage\n const inputTokens = response.usage?.prompt_tokens || 0\n const outputTokens = response.usage?.completion_tokens || 0\n const totalTokens = response.usage?.total_tokens || 0\n\n const inputCost = (inputTokens / 1_000_000) * modelCosts[model].input\n const outputCost = (outputTokens / 1_000_000) * modelCosts[model].output\n const totalCost = inputCost + outputCost\n\n return {\n model,\n inputCost,\n outputCost,\n totalCost,\n totalTokens,\n }\n}\n"],"names":["getGenerationCost","response","model","modelCosts","input","output","inputTokens","usage","prompt_tokens","outputTokens","completion_tokens","totalTokens","total_tokens","inputCost","outputCost","totalCost"],"mappings":"AAWA,yCAAyC,GACzC,OAAO,SAASA,kBACdC,QAAgD,EAChDC,KAAmC;IAEnC,MAAMC,aAAsF;QAC1F,2DAA2D;QAC3D,gBAAgB;YACdC,OAAO;YACPC,QAAQ;QACV;QACA,2DAA2D;QAC3D,gBAAgB;YACdD,OAAO;YACPC,QAAQ;QACV;IACF;IAEA,sCAAsC;IACtC,MAAMC,cAAcL,SAASM,KAAK,EAAEC,iBAAiB;IACrD,MAAMC,eAAeR,SAASM,KAAK,EAAEG,qBAAqB;IAC1D,MAAMC,cAAcV,SAASM,KAAK,EAAEK,gBAAgB;IAEpD,MAAMC,YAAY,AAACP,cAAc,YAAaH,UAAU,CAACD,MAAM,CAACE,KAAK;IACrE,MAAMU,aAAa,AAACL,eAAe,YAAaN,UAAU,CAACD,MAAM,CAACG,MAAM;IACxE,MAAMU,YAAYF,YAAYC;IAE9B,OAAO;QACLZ;QACAW;QACAC;QACAC;QACAJ;IACF;AACF"}
@@ -0,0 +1,11 @@
1
+ import { AutoParseableResponseFormat } from 'openai/lib/parser.mjs';
2
+ import { ResponseFormatJSONSchema } from 'openai/resources/shared.mjs';
3
+ import { z } from 'zod';
4
+ /**
5
+ * Creates a chat completion `JSONSchema` response format object from
6
+ * the given Zod schema.
7
+ *
8
+ * This is a temporary drop in replacement for the zodResponseFormat from openai/helpers/zod.ts
9
+ * because of issue https://github.com/openai/openai-node/issues/1576
10
+ */
11
+ export declare function zodResponseFormat<ZodInput extends z.ZodType>(zodObject: ZodInput, name: string, props?: Omit<ResponseFormatJSONSchema.JSONSchema, 'schema' | 'strict' | 'name'>): AutoParseableResponseFormat<z.infer<ZodInput>>;
@@ -0,0 +1,23 @@
1
+ import { makeParseableResponseFormat } from 'openai/lib/parser.mjs';
2
+ import { z } from 'zod';
3
+ /**
4
+ * Creates a chat completion `JSONSchema` response format object from
5
+ * the given Zod schema.
6
+ *
7
+ * This is a temporary drop in replacement for the zodResponseFormat from openai/helpers/zod.ts
8
+ * because of issue https://github.com/openai/openai-node/issues/1576
9
+ */ export function zodResponseFormat(zodObject, name, props) {
10
+ return makeParseableResponseFormat({
11
+ type: 'json_schema',
12
+ json_schema: {
13
+ ...props,
14
+ name,
15
+ strict: true,
16
+ schema: z.toJSONSchema(zodObject, {
17
+ target: 'draft-7'
18
+ })
19
+ }
20
+ }, (content)=>zodObject.parse(JSON.parse(content)));
21
+ }
22
+
23
+ //# sourceMappingURL=zodResponseFormat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/zodResponseFormat.ts"],"sourcesContent":["import { AutoParseableResponseFormat, makeParseableResponseFormat } from 'openai/lib/parser.mjs'\nimport { ResponseFormatJSONSchema } from 'openai/resources/shared.mjs'\nimport { z } from 'zod'\n\n/**\n * Creates a chat completion `JSONSchema` response format object from\n * the given Zod schema.\n *\n * This is a temporary drop in replacement for the zodResponseFormat from openai/helpers/zod.ts\n * because of issue https://github.com/openai/openai-node/issues/1576\n */\nexport function zodResponseFormat<ZodInput extends z.ZodType>(\n zodObject: ZodInput,\n name: string,\n props?: Omit<ResponseFormatJSONSchema.JSONSchema, 'schema' | 'strict' | 'name'>,\n): AutoParseableResponseFormat<z.infer<ZodInput>> {\n return makeParseableResponseFormat(\n {\n type: 'json_schema',\n json_schema: {\n ...props,\n name,\n strict: true,\n schema: z.toJSONSchema(zodObject, { target: 'draft-7' }),\n },\n },\n (content) => zodObject.parse(JSON.parse(content)),\n )\n}\n"],"names":["makeParseableResponseFormat","z","zodResponseFormat","zodObject","name","props","type","json_schema","strict","schema","toJSONSchema","target","content","parse","JSON"],"mappings":"AAAA,SAAsCA,2BAA2B,QAAQ,wBAAuB;AAEhG,SAASC,CAAC,QAAQ,MAAK;AAEvB;;;;;;CAMC,GACD,OAAO,SAASC,kBACdC,SAAmB,EACnBC,IAAY,EACZC,KAA+E;IAE/E,OAAOL,4BACL;QACEM,MAAM;QACNC,aAAa;YACX,GAAGF,KAAK;YACRD;YACAI,QAAQ;YACRC,QAAQR,EAAES,YAAY,CAACP,WAAW;gBAAEQ,QAAQ;YAAU;QACxD;IACF,GACA,CAACC,UAAYT,UAAUU,KAAK,CAACC,KAAKD,KAAK,CAACD;AAE5C"}
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "@jhb.software/payload-alt-text-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Payload CMS plugin that adds essential fields for hierarchical page structure to collections.",
5
+ "bugs": "https://github.com/jhb-software/payload-plugins/issues",
6
+ "repository": "https://github.com/jhb-software/payload-plugins",
7
+ "keywords": [
8
+ "payload",
9
+ "plugin",
10
+ "alt-text",
11
+ "accessibility",
12
+ "ai"
13
+ ],
14
+ "author": "JHB Software",
15
+ "license": "MIT",
16
+ "type": "module",
17
+ "main": "./src/index.ts",
18
+ "types": "./src/index.ts",
19
+ "scripts": {
20
+ "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
21
+ "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
22
+ "build:types": "tsc --outDir dist --rootDir ./src",
23
+ "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
24
+ "clean": "rimraf --glob {dist,*.tsbuildinfo}",
25
+ "dev": "tsc -w",
26
+ "format": "prettier --write src",
27
+ "lint": "eslint ./src",
28
+ "lint:fix": "eslint ./src --fix",
29
+ "prepublishOnly": "pnpm clean && pnpm build"
30
+ },
31
+ "dependencies": {
32
+ "openai": "^6.6.0",
33
+ "p-map": "^7.0.3",
34
+ "zod": "^4.1.12"
35
+ },
36
+ "peerDependencies": {
37
+ "@payloadcms/ui": "^3.60.0",
38
+ "next": "15.5.6",
39
+ "payload": "^3.60.0",
40
+ "react": "19.2.0",
41
+ "react-dom": "19.2.0"
42
+ },
43
+ "devDependencies": {
44
+ "@swc/cli": "^0.7.8",
45
+ "@swc/core": "^1.13.3",
46
+ "@types/react": "19.2.2",
47
+ "@types/react-dom": "19.2.2",
48
+ "copyfiles": "2.4.1",
49
+ "prettier": "^3.6.2",
50
+ "rimraf": "6.0.1",
51
+ "typescript": "5.9.3"
52
+ },
53
+ "files": [
54
+ "dist"
55
+ ],
56
+ "publishConfig": {
57
+ "main": "./dist/index.js",
58
+ "types": "./dist/index.d.ts",
59
+ "registry": "https://registry.npmjs.org/",
60
+ "access": "public",
61
+ "exports": {
62
+ ".": {
63
+ "import": "./dist/index.js",
64
+ "types": "./dist/index.d.ts",
65
+ "default": "./dist/index.js"
66
+ },
67
+ "./client": {
68
+ "import": "./dist/exports/client.js",
69
+ "types": "./dist/exports/client.d.ts",
70
+ "default": "./dist/exports/client.js"
71
+ },
72
+ "./server": {
73
+ "import": "./dist/exports/server.js",
74
+ "types": "./dist/exports/server.d.ts",
75
+ "default": "./dist/exports/server.js"
76
+ }
77
+ }
78
+ },
79
+ "exports": {
80
+ ".": {
81
+ "import": "./src/index.ts",
82
+ "types": "./src/index.ts",
83
+ "default": "./src/index.ts"
84
+ },
85
+ "./client": {
86
+ "import": "./src/exports/client.ts",
87
+ "types": "./src/exports/client.ts",
88
+ "default": "./src/exports/client.ts"
89
+ },
90
+ "./server": {
91
+ "import": "./src/exports/server.ts",
92
+ "types": "./src/exports/server.ts",
93
+ "default": "./src/exports/server.ts"
94
+ }
95
+ },
96
+ "engines": {
97
+ "node": "^18.20.2 || >=20.9.0"
98
+ }
99
+ }