@jhb.software/payload-alt-text-plugin 0.2.2 → 0.3.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.
- package/README.md +93 -22
- package/dist/components/AltTextField.js +6 -6
- package/dist/components/AltTextField.js.map +1 -1
- package/dist/components/BulkGenerateAltTextsButton.js +15 -16
- package/dist/components/BulkGenerateAltTextsButton.js.map +1 -1
- package/dist/components/GenerateAltTextButton.js +24 -25
- package/dist/components/GenerateAltTextButton.js.map +1 -1
- package/dist/components/icons/Lightning.js +4 -4
- package/dist/components/icons/Lightning.js.map +1 -1
- package/dist/components/icons/Spinner.js +11 -11
- package/dist/components/icons/Spinner.js.map +1 -1
- package/dist/endpoints/bulkGenerateAltTexts.js +30 -77
- package/dist/endpoints/bulkGenerateAltTexts.js.map +1 -1
- package/dist/endpoints/generateAltText.d.ts +1 -1
- package/dist/endpoints/generateAltText.js +19 -74
- package/dist/endpoints/generateAltText.js.map +1 -1
- package/dist/exports/client.d.ts +1 -1
- package/dist/exports/client.js +1 -1
- package/dist/exports/client.js.map +1 -1
- package/dist/fields/altTextField.js +20 -15
- package/dist/fields/altTextField.js.map +1 -1
- package/dist/fields/keywordsField.js +7 -7
- package/dist/fields/keywordsField.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +24 -32
- package/dist/plugin.js.map +1 -1
- package/dist/resolvers/openAI.d.ts +24 -0
- package/dist/resolvers/openAI.js +177 -0
- package/dist/resolvers/openAI.js.map +1 -0
- package/dist/resolvers/types.d.ts +68 -0
- package/dist/resolvers/types.js +6 -0
- package/dist/resolvers/types.js.map +1 -0
- package/dist/translations/de.d.ts +1 -1
- package/dist/translations/de.js +3 -3
- package/dist/translations/de.js.map +1 -1
- package/dist/translations/en.d.ts +1 -1
- package/dist/translations/en.js +3 -3
- package/dist/translations/en.js.map +1 -1
- package/dist/types/AltTextPluginConfig.d.ts +28 -26
- package/dist/types/AltTextPluginConfig.js.map +1 -1
- package/dist/utilities/localesFromConfig.d.ts +1 -1
- package/dist/utilities/localesFromConfig.js.map +1 -1
- package/dist/utils/translatedLabel.d.ts +1 -1
- package/dist/utils/translatedLabel.js.map +1 -1
- package/package.json +20 -18
- package/dist/utilities/getGenerationCost.d.ts +0 -12
- package/dist/utilities/getGenerationCost.js +0 -30
- package/dist/utilities/getGenerationCost.js.map +0 -1
- package/dist/utilities/zodResponseFormat.d.ts +0 -11
- package/dist/utilities/zodResponseFormat.js +0 -23
- package/dist/utilities/zodResponseFormat.js.map +0 -1
- package/dist/utils/usePluginTranslation.d.ts +0 -5
- package/dist/utils/usePluginTranslation.js +0 -16
- package/dist/utils/usePluginTranslation.js.map +0 -1
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
1
|
import pMap from 'p-map';
|
|
3
2
|
import { z } from 'zod';
|
|
4
|
-
import { getGenerationCost } from '../utilities/getGenerationCost.js';
|
|
5
|
-
import { zodResponseFormat } from '../utilities/zodResponseFormat.js';
|
|
6
3
|
import { localesFromConfig } from '../utilities/localesFromConfig.js';
|
|
7
4
|
/**
|
|
8
5
|
* Generates and updates alt text for multiple images in all locales.
|
|
@@ -32,15 +29,14 @@ import { localesFromConfig } from '../utilities/localesFromConfig.js';
|
|
|
32
29
|
status: 500
|
|
33
30
|
});
|
|
34
31
|
}
|
|
35
|
-
if (!pluginConfig.
|
|
32
|
+
if (!pluginConfig.resolver) {
|
|
36
33
|
return Response.json({
|
|
37
|
-
error: '
|
|
34
|
+
error: 'No alt text resolver configured'
|
|
38
35
|
}, {
|
|
39
36
|
status: 500
|
|
40
37
|
});
|
|
41
38
|
}
|
|
42
|
-
|
|
43
|
-
const concurrency = pluginConfig.maxBulkGenerateConcurrency || 16;
|
|
39
|
+
const concurrency = pluginConfig.maxBulkGenerateConcurrency;
|
|
44
40
|
// determine target locales based on config
|
|
45
41
|
const locales = localesFromConfig(req.payload.config);
|
|
46
42
|
const targetLocales = locales ?? [
|
|
@@ -56,11 +52,12 @@ import { localesFromConfig } from '../utilities/localesFromConfig.js';
|
|
|
56
52
|
await pMap(ids, async (id)=>{
|
|
57
53
|
try {
|
|
58
54
|
await generateAndUpdateAltText({
|
|
59
|
-
payload: req.payload,
|
|
60
55
|
id,
|
|
61
56
|
collection,
|
|
57
|
+
locales: targetLocales,
|
|
58
|
+
payload: req.payload,
|
|
62
59
|
pluginConfig,
|
|
63
|
-
|
|
60
|
+
req
|
|
64
61
|
});
|
|
65
62
|
updatedDocs++;
|
|
66
63
|
console.log(`${updatedDocs}/${ids.length} updated (${Math.round(updatedDocs / ids.length * 100)}%)`);
|
|
@@ -75,9 +72,9 @@ import { localesFromConfig } from '../utilities/localesFromConfig.js';
|
|
|
75
72
|
console.error(`Failed for: ${erroredDocs.join(', ')}`);
|
|
76
73
|
}
|
|
77
74
|
return Response.json({
|
|
78
|
-
|
|
75
|
+
erroredDocs,
|
|
79
76
|
totalDocs: ids.length,
|
|
80
|
-
|
|
77
|
+
updatedDocs
|
|
81
78
|
});
|
|
82
79
|
} catch (error) {
|
|
83
80
|
console.error('Error in bulk generation:', error);
|
|
@@ -88,82 +85,38 @@ import { localesFromConfig } from '../utilities/localesFromConfig.js';
|
|
|
88
85
|
});
|
|
89
86
|
}
|
|
90
87
|
};
|
|
91
|
-
async function generateAndUpdateAltText({
|
|
88
|
+
async function generateAndUpdateAltText({ id, collection, locales, payload, pluginConfig, req }) {
|
|
92
89
|
const imageDoc = await payload.findByID({
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
id,
|
|
91
|
+
collection,
|
|
95
92
|
depth: 0
|
|
96
93
|
});
|
|
97
94
|
if (!imageDoc) {
|
|
98
95
|
throw new Error('Image not found');
|
|
99
96
|
}
|
|
100
97
|
const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc);
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
z.object({
|
|
107
|
-
altText: z.string().describe('A concise, descriptive alt text for the image'),
|
|
108
|
-
keywords: z.array(z.string()).describe('Keywords that describe the content of the image')
|
|
109
|
-
})
|
|
110
|
-
])));
|
|
111
|
-
const response = await openai.chat.completions.parse({
|
|
112
|
-
model: pluginConfig.model,
|
|
113
|
-
messages: [
|
|
114
|
-
{
|
|
115
|
-
role: 'system',
|
|
116
|
-
content: `
|
|
117
|
-
You are an expert at analyzing images and creating descriptive image alt text.
|
|
118
|
-
|
|
119
|
-
Please analyze the given image and provide the following in ${locales.join(', ')}:
|
|
120
|
-
- A concise, localized 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.
|
|
121
|
-
- A localized list of keywords that describe the content (e.g., ["Camel", "Palm trees", "Desert"]) as "keywords"
|
|
122
|
-
|
|
123
|
-
If a context is provided, use it to enhance the alt text.
|
|
124
|
-
|
|
125
|
-
Format your response as a JSON object with ${locales.join(', ')} keys, each containing "altText", "keywords" and "slug".
|
|
126
|
-
`
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
role: 'user',
|
|
130
|
-
content: [
|
|
131
|
-
{
|
|
132
|
-
type: 'image_url',
|
|
133
|
-
image_url: {
|
|
134
|
-
url: imageThumbnailUrl
|
|
135
|
-
}
|
|
136
|
-
},
|
|
137
|
-
...'filename' in imageDoc && imageDoc.filename ? [
|
|
138
|
-
{
|
|
139
|
-
type: 'text',
|
|
140
|
-
text: imageDoc.filename
|
|
141
|
-
}
|
|
142
|
-
] : []
|
|
143
|
-
]
|
|
144
|
-
}
|
|
145
|
-
],
|
|
146
|
-
max_completion_tokens: 300,
|
|
147
|
-
response_format: zodResponseFormat(modelResponseSchema, 'data')
|
|
148
|
-
});
|
|
149
|
-
console.log({
|
|
150
|
-
imageId: id,
|
|
151
|
-
...getGenerationCost(response, pluginConfig.model)
|
|
98
|
+
const result = await pluginConfig.resolver.resolveBulk({
|
|
99
|
+
filename: 'filename' in imageDoc && typeof imageDoc.filename === 'string' ? imageDoc.filename : undefined,
|
|
100
|
+
imageThumbnailUrl,
|
|
101
|
+
locales,
|
|
102
|
+
req
|
|
152
103
|
});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
throw new Error('No result from OpenAI');
|
|
104
|
+
if (!result.success) {
|
|
105
|
+
throw new Error(result.error || 'Failed to generate alt text');
|
|
156
106
|
}
|
|
157
107
|
for (const locale of locales){
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
108
|
+
const localeResult = result.results[locale];
|
|
109
|
+
if (localeResult) {
|
|
110
|
+
await payload.update({
|
|
111
|
+
id,
|
|
112
|
+
collection,
|
|
113
|
+
data: {
|
|
114
|
+
alt: localeResult.altText,
|
|
115
|
+
keywords: localeResult.keywords
|
|
116
|
+
},
|
|
117
|
+
locale
|
|
118
|
+
});
|
|
119
|
+
}
|
|
167
120
|
}
|
|
168
121
|
}
|
|
169
122
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/bulkGenerateAltTexts.ts"],"sourcesContent":["import OpenAI from 'openai'\nimport { ChatCompletionContentPartText } from 'openai/resources/chat/completions.mjs'\nimport pMap from 'p-map'\nimport type { BasePayload, CollectionSlug, 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'\nimport { localesFromConfig } from '../utilities/localesFromConfig.js'\n\n/**\n * Generates and updates alt text for multiple images in all locales.\n */\nexport const bulkGenerateAltTextsEndpoint: 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 schema = z.object({\n collection: z.string(),\n ids: z.array(z.string()),\n })\n\n const { collection, ids } = schema.parse(data)\n\n let updatedDocs = 0\n const erroredDocs: string[] = []\n\n // Get plugin config from payload config\n const pluginConfig = req.payload.config.custom?.altTextPluginConfig as\n | AltTextPluginConfig\n | undefined\n\n if (!pluginConfig) {\n return Response.json({ error: 'Plugin config not found' }, { status: 500 })\n }\n\n if (!pluginConfig.openAIApiKey) {\n return Response.json({ error: 'OpenAI API key not configured' }, { status: 500 })\n }\n\n // Use concurrency from config\n const concurrency = pluginConfig.maxBulkGenerateConcurrency || 16\n\n // determine target locales based on config\n const locales = localesFromConfig(req.payload.config)\n const targetLocales = locales ?? [pluginConfig.locale!]\n if (!targetLocales) {\n return Response.json(\n {\n error:\n 'Could not determine target locales for alt text generation. Please check your plugin configuration.',\n },\n { status: 500 },\n )\n }\n\n await pMap(\n ids,\n async (id) => {\n try {\n await generateAndUpdateAltText({\n payload: req.payload,\n id,\n collection,\n pluginConfig,\n locales: targetLocales,\n })\n updatedDocs++\n console.log(\n `${updatedDocs}/${ids.length} updated (${Math.round((updatedDocs / ids.length) * 100)}%)`,\n )\n } catch (error) {\n console.error(`Error generating alt text for ${id}:`, error)\n erroredDocs.push(id)\n }\n },\n { concurrency },\n )\n\n if (erroredDocs.length > 0) {\n console.error(`Failed for: ${erroredDocs.join(', ')}`)\n }\n\n return Response.json({\n updatedDocs,\n totalDocs: ids.length,\n erroredDocs,\n })\n } catch (error) {\n console.error('Error in bulk generation:', 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\nasync function generateAndUpdateAltText({\n payload,\n id,\n collection,\n pluginConfig,\n locales,\n}: {\n payload: BasePayload\n id: string\n collection: CollectionSlug\n pluginConfig: AltTextPluginConfig\n locales: string[]\n}) {\n const imageDoc = await payload.findByID({\n collection: collection,\n id: id as string,\n depth: 0,\n })\n\n if (!imageDoc) {\n throw new Error('Image not found')\n }\n\n const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc)\n\n const openai = new OpenAI({\n apiKey: pluginConfig.openAIApiKey,\n })\n\n const modelResponseSchema = z.object(\n Object.fromEntries(\n locales.map((locale) => [\n locale,\n 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 ),\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 in ${locales.join(', ')}:\n - A concise, localized 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 localized 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 with ${locales.join(', ')} keys, each containing \"altText\", \"keywords\" and \"slug\".\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 max_completion_tokens: 300,\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 throw new Error('No result from OpenAI')\n }\n\n for (const locale of locales) {\n await payload.update({\n collection: collection as CollectionSlug,\n id: id as string,\n locale: locale,\n data: {\n alt: (result as any)[locale]?.altText,\n keywords: (result as any)[locale]?.keywords,\n },\n })\n }\n}\n"],"names":["OpenAI","pMap","z","getGenerationCost","zodResponseFormat","localesFromConfig","bulkGenerateAltTextsEndpoint","req","user","Response","json","error","status","data","schema","object","collection","string","ids","array","parse","updatedDocs","erroredDocs","pluginConfig","payload","config","custom","altTextPluginConfig","openAIApiKey","concurrency","maxBulkGenerateConcurrency","locales","targetLocales","locale","id","generateAndUpdateAltText","console","log","length","Math","round","push","join","totalDocs","Error","message","imageDoc","findByID","depth","imageThumbnailUrl","getImageThumbnail","openai","apiKey","modelResponseSchema","Object","fromEntries","map","altText","describe","keywords","response","chat","completions","model","messages","role","content","type","image_url","url","filename","text","max_completion_tokens","response_format","imageId","result","choices","parsed","update","alt"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/bulkGenerateAltTexts.ts"],"sourcesContent":["import type { BasePayload, CollectionSlug, PayloadHandler, PayloadRequest } from 'payload'\n\nimport pMap from 'p-map'\nimport { z } from 'zod'\n\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\nimport { localesFromConfig } from '../utilities/localesFromConfig.js'\n\n/**\n * Generates and updates alt text for multiple images in all locales.\n */\nexport const bulkGenerateAltTextsEndpoint: 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 schema = z.object({\n collection: z.string(),\n ids: z.array(z.string()),\n })\n\n const { collection, ids } = schema.parse(data)\n\n let updatedDocs = 0\n const erroredDocs: string[] = []\n\n // Get plugin config from payload config\n const pluginConfig = req.payload.config.custom?.altTextPluginConfig as\n | AltTextPluginConfig\n | undefined\n\n if (!pluginConfig) {\n return Response.json({ error: 'Plugin config not found' }, { status: 500 })\n }\n\n if (!pluginConfig.resolver) {\n return Response.json({ error: 'No alt text resolver configured' }, { status: 500 })\n }\n\n const concurrency = pluginConfig.maxBulkGenerateConcurrency\n\n // determine target locales based on config\n const locales = localesFromConfig(req.payload.config)\n const targetLocales = locales ?? [pluginConfig.locale!]\n if (!targetLocales) {\n return Response.json(\n {\n error:\n 'Could not determine target locales for alt text generation. Please check your plugin configuration.',\n },\n { status: 500 },\n )\n }\n\n await pMap(\n ids,\n async (id) => {\n try {\n await generateAndUpdateAltText({\n id,\n collection,\n locales: targetLocales,\n payload: req.payload,\n pluginConfig,\n req,\n })\n updatedDocs++\n console.log(\n `${updatedDocs}/${ids.length} updated (${Math.round((updatedDocs / ids.length) * 100)}%)`,\n )\n } catch (error) {\n console.error(`Error generating alt text for ${id}:`, error)\n erroredDocs.push(id)\n }\n },\n { concurrency },\n )\n\n if (erroredDocs.length > 0) {\n console.error(`Failed for: ${erroredDocs.join(', ')}`)\n }\n\n return Response.json({\n erroredDocs,\n totalDocs: ids.length,\n updatedDocs,\n })\n } catch (error) {\n console.error('Error in bulk generation:', 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\nasync function generateAndUpdateAltText({\n id,\n collection,\n locales,\n payload,\n pluginConfig,\n req,\n}: {\n collection: CollectionSlug\n id: string\n locales: string[]\n payload: BasePayload\n pluginConfig: AltTextPluginConfig\n req: PayloadRequest\n}) {\n const imageDoc = await payload.findByID({\n id,\n collection,\n depth: 0,\n })\n\n if (!imageDoc) {\n throw new Error('Image not found')\n }\n\n const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc)\n\n const result = await pluginConfig.resolver.resolveBulk({\n filename:\n 'filename' in imageDoc && typeof imageDoc.filename === 'string'\n ? imageDoc.filename\n : undefined,\n imageThumbnailUrl,\n locales,\n req,\n })\n\n if (!result.success) {\n throw new Error(result.error || 'Failed to generate alt text')\n }\n\n for (const locale of locales) {\n const localeResult = result.results[locale]\n if (localeResult) {\n await payload.update({\n id,\n collection,\n data: {\n alt: localeResult.altText,\n keywords: localeResult.keywords,\n },\n locale,\n })\n }\n }\n}\n"],"names":["pMap","z","localesFromConfig","bulkGenerateAltTextsEndpoint","req","user","Response","json","error","status","data","schema","object","collection","string","ids","array","parse","updatedDocs","erroredDocs","pluginConfig","payload","config","custom","altTextPluginConfig","resolver","concurrency","maxBulkGenerateConcurrency","locales","targetLocales","locale","id","generateAndUpdateAltText","console","log","length","Math","round","push","join","totalDocs","Error","message","imageDoc","findByID","depth","imageThumbnailUrl","getImageThumbnail","result","resolveBulk","filename","undefined","success","localeResult","results","update","alt","altText","keywords"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PayloadHandler } from 'payload';
|
|
2
2
|
/**
|
|
3
|
-
* Generates alt text for a single image using
|
|
3
|
+
* Generates alt text for a single image using the configured resolver.
|
|
4
4
|
* Returns result without updating the document.
|
|
5
5
|
*/
|
|
6
6
|
export declare const generateAltTextEndpoint: PayloadHandler;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
1
|
import { z } from 'zod';
|
|
3
|
-
import { getGenerationCost } from '../utilities/getGenerationCost.js';
|
|
4
|
-
import { zodResponseFormat } from '../utilities/zodResponseFormat.js';
|
|
5
2
|
/**
|
|
6
|
-
* Generates alt text for a single image using
|
|
3
|
+
* Generates alt text for a single image using the configured resolver.
|
|
7
4
|
* Returns result without updating the document.
|
|
8
5
|
*/ export const generateAltTextEndpoint = async (req)=>{
|
|
9
6
|
try {
|
|
@@ -16,14 +13,14 @@ import { zodResponseFormat } from '../utilities/zodResponseFormat.js';
|
|
|
16
13
|
}
|
|
17
14
|
const data = 'json' in req && typeof req.json === 'function' ? await req.json() : null;
|
|
18
15
|
const requestSchema = z.object({
|
|
19
|
-
collection: z.string(),
|
|
20
16
|
id: z.string(),
|
|
17
|
+
collection: z.string(),
|
|
21
18
|
locale: z.string().nullable()
|
|
22
19
|
});
|
|
23
|
-
const {
|
|
20
|
+
const { id, collection, locale } = requestSchema.parse(data);
|
|
24
21
|
const imageDoc = await req.payload.findByID({
|
|
25
|
-
collection,
|
|
26
22
|
id,
|
|
23
|
+
collection,
|
|
27
24
|
depth: 0
|
|
28
25
|
});
|
|
29
26
|
if (!imageDoc) {
|
|
@@ -48,89 +45,37 @@ import { zodResponseFormat } from '../utilities/zodResponseFormat.js';
|
|
|
48
45
|
status: 500
|
|
49
46
|
});
|
|
50
47
|
}
|
|
51
|
-
|
|
52
|
-
const targetLocale = locale ?? pluginConfig.locale;
|
|
53
|
-
if (!targetLocale) {
|
|
54
|
-
return Response.json({
|
|
55
|
-
error: 'Could not determine target locale for alt text generation. Please check your plugin configuration.'
|
|
56
|
-
}, {
|
|
57
|
-
status: 500
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc);
|
|
61
|
-
if (!imageThumbnailUrl) {
|
|
48
|
+
if (!pluginConfig.resolver) {
|
|
62
49
|
return Response.json({
|
|
63
|
-
error: '
|
|
50
|
+
error: 'No alt text resolver configured'
|
|
64
51
|
}, {
|
|
65
52
|
status: 500
|
|
66
53
|
});
|
|
67
54
|
}
|
|
68
|
-
|
|
55
|
+
// determine target locale
|
|
56
|
+
const targetLocale = locale ?? pluginConfig.locale;
|
|
57
|
+
if (!targetLocale) {
|
|
69
58
|
return Response.json({
|
|
70
|
-
error: '
|
|
59
|
+
error: 'Could not determine target locale for alt text generation. Please check your plugin configuration.'
|
|
71
60
|
}, {
|
|
72
61
|
status: 500
|
|
73
62
|
});
|
|
74
63
|
}
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
82
|
-
const response = await openai.chat.completions.parse({
|
|
83
|
-
model: pluginConfig.model,
|
|
84
|
-
messages: [
|
|
85
|
-
{
|
|
86
|
-
role: 'system',
|
|
87
|
-
content: `
|
|
88
|
-
You are an expert at analyzing images and creating descriptive image alt text.
|
|
89
|
-
|
|
90
|
-
Please analyze the given image and provide the following:
|
|
91
|
-
- 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.
|
|
92
|
-
- A list of keywords that describe the content (e.g., ["Camel", "Palm trees", "Desert"]) as "keywords"
|
|
93
|
-
|
|
94
|
-
If a context is provided, use it to enhance the alt text.
|
|
95
|
-
|
|
96
|
-
Format your response as a JSON object. You must respond in the ${targetLocale} language.
|
|
97
|
-
`
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
role: 'user',
|
|
101
|
-
content: [
|
|
102
|
-
{
|
|
103
|
-
type: 'image_url',
|
|
104
|
-
image_url: {
|
|
105
|
-
url: imageThumbnailUrl
|
|
106
|
-
}
|
|
107
|
-
},
|
|
108
|
-
...'filename' in imageDoc && imageDoc.filename ? [
|
|
109
|
-
{
|
|
110
|
-
type: 'text',
|
|
111
|
-
text: imageDoc.filename
|
|
112
|
-
}
|
|
113
|
-
] : []
|
|
114
|
-
]
|
|
115
|
-
}
|
|
116
|
-
],
|
|
117
|
-
// limit the response tokens and costs per request
|
|
118
|
-
max_completion_tokens: 150,
|
|
119
|
-
response_format: zodResponseFormat(modelResponseSchema, 'data')
|
|
120
|
-
});
|
|
121
|
-
console.log({
|
|
122
|
-
imageId: id,
|
|
123
|
-
...getGenerationCost(response, pluginConfig.model)
|
|
64
|
+
const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc);
|
|
65
|
+
const result = await pluginConfig.resolver.resolve({
|
|
66
|
+
filename: 'filename' in imageDoc && typeof imageDoc.filename === 'string' ? imageDoc.filename : undefined,
|
|
67
|
+
imageThumbnailUrl,
|
|
68
|
+
locale: targetLocale,
|
|
69
|
+
req
|
|
124
70
|
});
|
|
125
|
-
|
|
126
|
-
if (!result) {
|
|
71
|
+
if (!result.success) {
|
|
127
72
|
return Response.json({
|
|
128
|
-
error: '
|
|
73
|
+
error: result.error || 'Failed to generate alt text'
|
|
129
74
|
}, {
|
|
130
75
|
status: 500
|
|
131
76
|
});
|
|
132
77
|
}
|
|
133
|
-
return Response.json(result);
|
|
78
|
+
return Response.json(result.result);
|
|
134
79
|
} catch (error) {
|
|
135
80
|
console.error('Error generating alt text:', error);
|
|
136
81
|
return Response.json({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/endpoints/generateAltText.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/generateAltText.ts"],"sourcesContent":["import type { PayloadHandler, PayloadRequest } from 'payload'\n\nimport { z } from 'zod'\n\nimport type { AltTextPluginConfig } from '../types/AltTextPluginConfig.js'\n\n/**\n * Generates alt text for a single image using the configured resolver.\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 id: z.string(),\n collection: z.string(),\n locale: z.string().nullable(),\n })\n\n const { id, collection, locale } = requestSchema.parse(data)\n\n const imageDoc = await req.payload.findByID({\n id,\n collection,\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) {\n return Response.json({ error: 'Plugin config not found' }, { status: 500 })\n }\n\n if (!pluginConfig.getImageThumbnail) {\n return Response.json({ error: 'getImageThumbnail function not configured' }, { status: 500 })\n }\n\n if (!pluginConfig.resolver) {\n return Response.json({ error: 'No alt text resolver configured' }, { status: 500 })\n }\n\n // determine target locale\n const targetLocale = locale ?? pluginConfig.locale\n if (!targetLocale) {\n return Response.json(\n {\n error:\n 'Could not determine target locale for alt text generation. Please check your plugin configuration.',\n },\n { status: 500 },\n )\n }\n\n const imageThumbnailUrl = pluginConfig.getImageThumbnail(imageDoc)\n\n const result = await pluginConfig.resolver.resolve({\n filename:\n 'filename' in imageDoc && typeof imageDoc.filename === 'string'\n ? imageDoc.filename\n : undefined,\n imageThumbnailUrl,\n locale: targetLocale,\n req,\n })\n\n if (!result.success) {\n return Response.json(\n { error: result.error || 'Failed to generate alt text' },\n { status: 500 },\n )\n }\n\n return Response.json(result.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":["z","generateAltTextEndpoint","req","user","Response","json","error","status","data","requestSchema","object","id","string","collection","locale","nullable","parse","imageDoc","payload","findByID","depth","pluginConfig","config","custom","altTextPluginConfig","getImageThumbnail","resolver","targetLocale","imageThumbnailUrl","result","resolve","filename","undefined","success","console","Error","message"],"mappings":"AAEA,SAASA,CAAC,QAAQ,MAAK;AAIvB;;;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,gBAAgBT,EAAEU,MAAM,CAAC;YAC7BC,IAAIX,EAAEY,MAAM;YACZC,YAAYb,EAAEY,MAAM;YACpBE,QAAQd,EAAEY,MAAM,GAAGG,QAAQ;QAC7B;QAEA,MAAM,EAAEJ,EAAE,EAAEE,UAAU,EAAEC,MAAM,EAAE,GAAGL,cAAcO,KAAK,CAACR;QAEvD,MAAMS,WAAW,MAAMf,IAAIgB,OAAO,CAACC,QAAQ,CAAC;YAC1CR;YACAE;YACAO,OAAO;QACT;QAEA,IAAI,CAACH,UAAU;YACb,OAAOb,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAAkB,GAAG;gBAAEC,QAAQ;YAAI;QACnE;QAEA,MAAMc,eAAenB,IAAIgB,OAAO,CAACI,MAAM,CAACC,MAAM,EAAEC;QAIhD,IAAI,CAACH,cAAc;YACjB,OAAOjB,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAA0B,GAAG;gBAAEC,QAAQ;YAAI;QAC3E;QAEA,IAAI,CAACc,aAAaI,iBAAiB,EAAE;YACnC,OAAOrB,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAA4C,GAAG;gBAAEC,QAAQ;YAAI;QAC7F;QAEA,IAAI,CAACc,aAAaK,QAAQ,EAAE;YAC1B,OAAOtB,SAASC,IAAI,CAAC;gBAAEC,OAAO;YAAkC,GAAG;gBAAEC,QAAQ;YAAI;QACnF;QAEA,0BAA0B;QAC1B,MAAMoB,eAAeb,UAAUO,aAAaP,MAAM;QAClD,IAAI,CAACa,cAAc;YACjB,OAAOvB,SAASC,IAAI,CAClB;gBACEC,OACE;YACJ,GACA;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAMqB,oBAAoBP,aAAaI,iBAAiB,CAACR;QAEzD,MAAMY,SAAS,MAAMR,aAAaK,QAAQ,CAACI,OAAO,CAAC;YACjDC,UACE,cAAcd,YAAY,OAAOA,SAASc,QAAQ,KAAK,WACnDd,SAASc,QAAQ,GACjBC;YACNJ;YACAd,QAAQa;YACRzB;QACF;QAEA,IAAI,CAAC2B,OAAOI,OAAO,EAAE;YACnB,OAAO7B,SAASC,IAAI,CAClB;gBAAEC,OAAOuB,OAAOvB,KAAK,IAAI;YAA8B,GACvD;gBAAEC,QAAQ;YAAI;QAElB;QAEA,OAAOH,SAASC,IAAI,CAACwB,OAAOA,MAAM;IACpC,EAAE,OAAOvB,OAAO;QACd4B,QAAQ5B,KAAK,CAAC,8BAA8BA;QAC5C,OAAOF,SAASC,IAAI,CAClB;YACEC,OAAO,CAAC,2BAA2B,EAAEA,iBAAiB6B,QAAQ7B,MAAM8B,OAAO,GAAG,iBAAiB;QACjG,GACA;YAAE7B,QAAQ;QAAI;IAElB;AACF,EAAC"}
|
package/dist/exports/client.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { AltTextField } from '../components/AltTextField.js';
|
|
2
|
-
export { GenerateAltTextButton } from '../components/GenerateAltTextButton.js';
|
|
3
2
|
export { BulkGenerateAltTextsButton } from '../components/BulkGenerateAltTextsButton.js';
|
|
3
|
+
export { GenerateAltTextButton } from '../components/GenerateAltTextButton.js';
|
package/dist/exports/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AltTextField } from '../components/AltTextField.js';
|
|
2
|
-
export { GenerateAltTextButton } from '../components/GenerateAltTextButton.js';
|
|
3
2
|
export { BulkGenerateAltTextsButton } from '../components/BulkGenerateAltTextsButton.js';
|
|
3
|
+
export { GenerateAltTextButton } from '../components/GenerateAltTextButton.js';
|
|
4
4
|
|
|
5
5
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { AltTextField } from '../components/AltTextField.js'\nexport {
|
|
1
|
+
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { AltTextField } from '../components/AltTextField.js'\nexport { BulkGenerateAltTextsButton } from '../components/BulkGenerateAltTextsButton.js'\nexport { GenerateAltTextButton } from '../components/GenerateAltTextButton.js'\n"],"names":["AltTextField","BulkGenerateAltTextsButton","GenerateAltTextButton"],"mappings":"AAAA,SAASA,YAAY,QAAQ,gCAA+B;AAC5D,SAASC,0BAA0B,QAAQ,8CAA6C;AACxF,SAASC,qBAAqB,QAAQ,yCAAwC"}
|
|
@@ -2,26 +2,31 @@ import { translatedLabel } from '../utils/translatedLabel.js';
|
|
|
2
2
|
export function altTextField({ localized }) {
|
|
3
3
|
return {
|
|
4
4
|
name: 'alt',
|
|
5
|
-
label: translatedLabel('alternateText'),
|
|
6
5
|
type: 'textarea',
|
|
7
|
-
required: true,
|
|
8
|
-
localized: localized,
|
|
9
|
-
validate: (value, { id, req: { t } })=>{
|
|
10
|
-
// if the document has an id, which means a media file was uploaded, the alt text is required
|
|
11
|
-
if (id) {
|
|
12
|
-
if (!value || value.trim().length === 0) {
|
|
13
|
-
// @ts-expect-error - the translation key type does not include the custom key
|
|
14
|
-
return t('@jhb.software/payload-alt-text-plugin:theAlternateTextIsRequired');
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
// The alt text is not required when the media file was not uploaded yet
|
|
18
|
-
// (since the alt text generation needs an URL to fetch the file)
|
|
19
|
-
return true;
|
|
20
|
-
},
|
|
21
6
|
admin: {
|
|
22
7
|
components: {
|
|
23
8
|
Field: '@jhb.software/payload-alt-text-plugin/client#AltTextField'
|
|
24
9
|
}
|
|
10
|
+
},
|
|
11
|
+
label: translatedLabel('alternateText'),
|
|
12
|
+
localized,
|
|
13
|
+
required: true,
|
|
14
|
+
validate: (value, { data, operation, req: { t } })=>{
|
|
15
|
+
// Since https://github.com/payloadcms/payload/pull/14988, when using external storage (e.g., S3),
|
|
16
|
+
// it is no longer possible to detect whether this validation runs during the initial upload
|
|
17
|
+
// or a regular update by checking the existence of the ID.
|
|
18
|
+
// Instead, compare the timestamps of the createdAt and updatedAt fields.
|
|
19
|
+
const isInitialUpload = operation === 'create' || 'createdAt' in data && 'updatedAt' in data && data.createdAt === data.updatedAt;
|
|
20
|
+
// initial upload: allow without alt text
|
|
21
|
+
if (isInitialUpload) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
// regular update: require alt text
|
|
25
|
+
if (!value || value.trim().length === 0) {
|
|
26
|
+
// @ts-expect-error - the translation key type does not include the custom key
|
|
27
|
+
return t('@jhb.software/payload-alt-text-plugin:theAlternateTextIsRequired');
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
25
30
|
}
|
|
26
31
|
};
|
|
27
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fields/altTextField.ts"],"sourcesContent":["import type { TextareaField } from 'payload'\nimport { translatedLabel } from '../utils/translatedLabel.js'\n\nexport function altTextField({\n localized,\n}: {\n localized?: TextareaField['localized']\n}): TextareaField {\n return {\n name: 'alt',\n
|
|
1
|
+
{"version":3,"sources":["../../src/fields/altTextField.ts"],"sourcesContent":["import type { TextareaField } from 'payload'\n\nimport { translatedLabel } from '../utils/translatedLabel.js'\n\nexport function altTextField({\n localized,\n}: {\n localized?: TextareaField['localized']\n}): TextareaField {\n return {\n name: 'alt',\n type: 'textarea',\n admin: {\n components: {\n Field: '@jhb.software/payload-alt-text-plugin/client#AltTextField',\n },\n },\n label: translatedLabel('alternateText'),\n localized,\n required: true,\n validate: (value, { data, operation, req: { t } }) => {\n // Since https://github.com/payloadcms/payload/pull/14988, when using external storage (e.g., S3),\n // it is no longer possible to detect whether this validation runs during the initial upload\n // or a regular update by checking the existence of the ID.\n // Instead, compare the timestamps of the createdAt and updatedAt fields.\n const isInitialUpload =\n operation === 'create' ||\n ('createdAt' in data && 'updatedAt' in data && data.createdAt === data.updatedAt)\n\n // initial upload: allow without alt text\n if (isInitialUpload) {\n return true\n }\n\n // regular update: require alt text\n if (!value || value.trim().length === 0) {\n // @ts-expect-error - the translation key type does not include the custom key\n return t('@jhb.software/payload-alt-text-plugin:theAlternateTextIsRequired')\n }\n\n return true\n },\n }\n}\n"],"names":["translatedLabel","altTextField","localized","name","type","admin","components","Field","label","required","validate","value","data","operation","req","t","isInitialUpload","createdAt","updatedAt","trim","length"],"mappings":"AAEA,SAASA,eAAe,QAAQ,8BAA6B;AAE7D,OAAO,SAASC,aAAa,EAC3BC,SAAS,EAGV;IACC,OAAO;QACLC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,YAAY;gBACVC,OAAO;YACT;QACF;QACAC,OAAOR,gBAAgB;QACvBE;QACAO,UAAU;QACVC,UAAU,CAACC,OAAO,EAAEC,IAAI,EAAEC,SAAS,EAAEC,KAAK,EAAEC,CAAC,EAAE,EAAE;YAC/C,kGAAkG;YAClG,4FAA4F;YAC5F,2DAA2D;YAC3D,yEAAyE;YACzE,MAAMC,kBACJH,cAAc,YACb,eAAeD,QAAQ,eAAeA,QAAQA,KAAKK,SAAS,KAAKL,KAAKM,SAAS;YAElF,yCAAyC;YACzC,IAAIF,iBAAiB;gBACnB,OAAO;YACT;YAEA,mCAAmC;YACnC,IAAI,CAACL,SAASA,MAAMQ,IAAI,GAAGC,MAAM,KAAK,GAAG;gBACvC,8EAA8E;gBAC9E,OAAOL,EAAE;YACX;YAEA,OAAO;QACT;IACF;AACF"}
|
|
@@ -2,16 +2,16 @@ import { translatedLabel } from '../utils/translatedLabel.js';
|
|
|
2
2
|
export function keywordsField({ localized }) {
|
|
3
3
|
return {
|
|
4
4
|
name: 'keywords',
|
|
5
|
-
label: translatedLabel('keywords'),
|
|
6
5
|
type: 'text',
|
|
7
|
-
hasMany: true,
|
|
8
|
-
required: false,
|
|
9
|
-
localized: localized,
|
|
10
6
|
admin: {
|
|
11
7
|
description: 'Keywords which describe the image. Used when searching for the image.',
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
8
|
+
hidden: true,
|
|
9
|
+
readOnly: true
|
|
10
|
+
},
|
|
11
|
+
hasMany: true,
|
|
12
|
+
label: translatedLabel('keywords'),
|
|
13
|
+
localized,
|
|
14
|
+
required: false
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/fields/keywordsField.ts"],"sourcesContent":["import type { TextField } from 'payload'\nimport { translatedLabel } from '../utils/translatedLabel.js'\n\nexport function keywordsField({ localized }: { localized?: TextField['localized'] }): TextField {\n return {\n name: 'keywords',\n
|
|
1
|
+
{"version":3,"sources":["../../src/fields/keywordsField.ts"],"sourcesContent":["import type { TextField } from 'payload'\n\nimport { translatedLabel } from '../utils/translatedLabel.js'\n\nexport function keywordsField({ localized }: { localized?: TextField['localized'] }): TextField {\n return {\n name: 'keywords',\n type: 'text',\n admin: {\n description: 'Keywords which describe the image. Used when searching for the image.', // does not need to be translated because it is only used for the JSDoc\n hidden: true, // this field is only meant to be used for improving the search, therefore hide it from the UI\n readOnly: true,\n },\n hasMany: true,\n label: translatedLabel('keywords'),\n localized,\n required: false,\n }\n}\n"],"names":["translatedLabel","keywordsField","localized","name","type","admin","description","hidden","readOnly","hasMany","label","required"],"mappings":"AAEA,SAASA,eAAe,QAAQ,8BAA6B;AAE7D,OAAO,SAASC,cAAc,EAAEC,SAAS,EAA0C;IACjF,OAAO;QACLC,MAAM;QACNC,MAAM;QACNC,OAAO;YACLC,aAAa;YACbC,QAAQ;YACRC,UAAU;QACZ;QACAC,SAAS;QACTC,OAAOV,gBAAgB;QACvBE;QACAS,UAAU;IACZ;AACF"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +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"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { payloadAltTextPlugin } from './plugin.js'\nexport { openAIResolver } from './resolvers/openAI.js'\nexport * from './resolvers/types.js'\nexport type { IncomingAltTextPluginConfig as AltTextPluginConfig } from './types/AltTextPluginConfig.js'\n"],"names":["payloadAltTextPlugin","openAIResolver"],"mappings":"AAAA,SAASA,oBAAoB,QAAQ,cAAa;AAClD,SAASC,cAAc,QAAQ,wBAAuB;AACtD,cAAc,uBAAsB"}
|