@jhb.software/payload-alt-text-plugin 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +7 -7
- package/dist/components/BulkGenerateAltTextsButton.js.map +1 -1
- package/dist/components/GenerateAltTextButton.js +12 -12
- 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 +7 -7
- 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 -29
- 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/dist/utils/usePluginTranslation.d.ts +1 -1
- package/dist/utils/usePluginTranslation.js +1 -1
- package/dist/utils/usePluginTranslation.js.map +1 -1
- package/package.json +17 -15
- 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/plugin.js.map
CHANGED
|
@@ -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 {
|
|
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'\n\nimport { bulkGenerateAltTextsEndpoint } from './endpoints/bulkGenerateAltTexts.js'\nimport { generateAltTextEndpoint } from './endpoints/generateAltText.js'\nimport { altTextField } from './fields/altTextField.js'\nimport { keywordsField } from './fields/keywordsField.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 collections: incomingPluginConfig.collections,\n enabled: incomingPluginConfig.enabled ?? true,\n fieldsOverride: incomingPluginConfig.fieldsOverride,\n getImageThumbnail: incomingPluginConfig.getImageThumbnail,\n locale: incomingPluginConfig.locale,\n locales,\n maxBulkGenerateConcurrency: incomingPluginConfig.maxBulkGenerateConcurrency ?? 16,\n resolver: incomingPluginConfig.resolver,\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 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 // enhance the search by adding the filename, keywords and alt fields (if the user has not provided their own listSearchableFields)\n listSearchableFields: collectionConfig.admin?.listSearchableFields ?? [\n 'filename',\n 'keywords',\n 'alt',\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 handler: generateAltTextEndpoint,\n method: 'post',\n path: '/alt-text-plugin/generate-alt-text',\n },\n {\n handler: bulkGenerateAltTextsEndpoint,\n method: 'post',\n path: '/alt-text-plugin/bulk-generate-alt-texts',\n },\n ],\n i18n: {\n ...config.i18n,\n translations: deepMergeSimple(translations, incomingConfig.i18n?.translations ?? {}),\n },\n }\n }\n"],"names":["bulkGenerateAltTextsEndpoint","generateAltTextEndpoint","altTextField","keywordsField","translations","deepMergeSimple","payloadAltTextPlugin","incomingPluginConfig","incomingConfig","config","enabled","locales","localization","map","localeConfig","code","pluginConfig","collections","fieldsOverride","getImageThumbnail","locale","maxBulkGenerateConcurrency","resolver","length","Error","defaultFields","localized","Boolean","fields","collectionConfig","includes","slug","upload","console","warn","admin","components","beforeListTable","path","props","collectionSlug","listSearchableFields","custom","altTextPluginConfig","endpoints","handler","method","i18n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AltTextResolver } from './types.js';
|
|
2
|
+
export type OpenAIResolverConfig = {
|
|
3
|
+
/** OpenAI API key for authentication */
|
|
4
|
+
apiKey: string;
|
|
5
|
+
/**
|
|
6
|
+
* The OpenAI LLM model to use for alt text generation.
|
|
7
|
+
* @default 'gpt-4.1-nano'
|
|
8
|
+
*/
|
|
9
|
+
model?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Creates an OpenAI-based resolver for alt text generation.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { openAIResolver } from '@jhb.software/payload-alt-text-plugin'
|
|
17
|
+
*
|
|
18
|
+
* openAIResolver({
|
|
19
|
+
* apiKey: process.env.OPENAI_API_KEY,
|
|
20
|
+
* model: 'gpt-4.1-mini', // optional, defaults to 'gpt-4.1-nano'
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const openAIResolver: (config: OpenAIResolverConfig) => AltTextResolver;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { makeParseableResponseFormat } from 'openai/lib/parser.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
|
+
*/ function zodResponseFormat(zodObject, name, props) {
|
|
11
|
+
return makeParseableResponseFormat({
|
|
12
|
+
type: 'json_schema',
|
|
13
|
+
json_schema: {
|
|
14
|
+
...props,
|
|
15
|
+
name,
|
|
16
|
+
schema: z.toJSONSchema(zodObject, {
|
|
17
|
+
target: 'draft-7'
|
|
18
|
+
}),
|
|
19
|
+
strict: true
|
|
20
|
+
}
|
|
21
|
+
}, (content)=>zodObject.parse(JSON.parse(content)));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates an OpenAI-based resolver for alt text generation.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { openAIResolver } from '@jhb.software/payload-alt-text-plugin'
|
|
29
|
+
*
|
|
30
|
+
* openAIResolver({
|
|
31
|
+
* apiKey: process.env.OPENAI_API_KEY,
|
|
32
|
+
* model: 'gpt-4.1-mini', // optional, defaults to 'gpt-4.1-nano'
|
|
33
|
+
* })
|
|
34
|
+
* ```
|
|
35
|
+
*/ export const openAIResolver = (config)=>{
|
|
36
|
+
const { apiKey, model = 'gpt-4.1-nano' } = config;
|
|
37
|
+
return {
|
|
38
|
+
key: 'openai',
|
|
39
|
+
resolve: async ({ filename, imageThumbnailUrl, locale })=>{
|
|
40
|
+
try {
|
|
41
|
+
const openai = new OpenAI({
|
|
42
|
+
apiKey
|
|
43
|
+
});
|
|
44
|
+
const modelResponseSchema = z.object({
|
|
45
|
+
altText: z.string().describe('A concise, descriptive alt text for the image'),
|
|
46
|
+
keywords: z.array(z.string()).describe('Keywords that describe the content of the image')
|
|
47
|
+
});
|
|
48
|
+
const response = await openai.chat.completions.parse({
|
|
49
|
+
max_completion_tokens: 150,
|
|
50
|
+
messages: [
|
|
51
|
+
{
|
|
52
|
+
content: `
|
|
53
|
+
You are an expert at analyzing images and creating descriptive image alt text.
|
|
54
|
+
|
|
55
|
+
Please analyze the given image and provide the following:
|
|
56
|
+
- 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.
|
|
57
|
+
- A list of keywords that describe the content (e.g., ["Camel", "Palm trees", "Desert"]) as "keywords"
|
|
58
|
+
|
|
59
|
+
If a context is provided, use it to enhance the alt text.
|
|
60
|
+
|
|
61
|
+
Format your response as a JSON object. You must respond in the ${locale} language.
|
|
62
|
+
`,
|
|
63
|
+
role: 'system'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
content: [
|
|
67
|
+
{
|
|
68
|
+
type: 'image_url',
|
|
69
|
+
image_url: {
|
|
70
|
+
url: imageThumbnailUrl
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
...filename ? [
|
|
74
|
+
{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: filename
|
|
77
|
+
}
|
|
78
|
+
] : []
|
|
79
|
+
],
|
|
80
|
+
role: 'user'
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
model,
|
|
84
|
+
response_format: zodResponseFormat(modelResponseSchema, 'data')
|
|
85
|
+
});
|
|
86
|
+
const result = response.choices[0]?.message?.parsed;
|
|
87
|
+
if (!result) {
|
|
88
|
+
return {
|
|
89
|
+
error: 'No result from OpenAI',
|
|
90
|
+
success: false
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
result,
|
|
95
|
+
success: true
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error('Error generating alt text:', error);
|
|
99
|
+
return {
|
|
100
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
101
|
+
success: false
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
resolveBulk: async ({ filename, imageThumbnailUrl, locales })=>{
|
|
106
|
+
try {
|
|
107
|
+
const openai = new OpenAI({
|
|
108
|
+
apiKey
|
|
109
|
+
});
|
|
110
|
+
const modelResponseSchema = z.object(Object.fromEntries(locales.map((locale)=>[
|
|
111
|
+
locale,
|
|
112
|
+
z.object({
|
|
113
|
+
altText: z.string().describe('A concise, descriptive alt text for the image'),
|
|
114
|
+
keywords: z.array(z.string()).describe('Keywords that describe the content of the image')
|
|
115
|
+
})
|
|
116
|
+
])));
|
|
117
|
+
const response = await openai.chat.completions.parse({
|
|
118
|
+
max_completion_tokens: 300,
|
|
119
|
+
messages: [
|
|
120
|
+
{
|
|
121
|
+
content: `
|
|
122
|
+
You are an expert at analyzing images and creating descriptive image alt text.
|
|
123
|
+
|
|
124
|
+
Please analyze the given image and provide the following in ${locales.join(', ')}:
|
|
125
|
+
- 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.
|
|
126
|
+
- A localized list of keywords that describe the content (e.g., ["Camel", "Palm trees", "Desert"]) as "keywords"
|
|
127
|
+
|
|
128
|
+
If a context is provided, use it to enhance the alt text.
|
|
129
|
+
|
|
130
|
+
Format your response as a JSON object with ${locales.join(', ')} keys, each containing "altText" and "keywords".
|
|
131
|
+
`,
|
|
132
|
+
role: 'system'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
type: 'image_url',
|
|
138
|
+
image_url: {
|
|
139
|
+
url: imageThumbnailUrl
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
...filename ? [
|
|
143
|
+
{
|
|
144
|
+
type: 'text',
|
|
145
|
+
text: filename
|
|
146
|
+
}
|
|
147
|
+
] : []
|
|
148
|
+
],
|
|
149
|
+
role: 'user'
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
model,
|
|
153
|
+
response_format: zodResponseFormat(modelResponseSchema, 'data')
|
|
154
|
+
});
|
|
155
|
+
const result = response.choices[0]?.message?.parsed;
|
|
156
|
+
if (!result) {
|
|
157
|
+
return {
|
|
158
|
+
error: 'No result from OpenAI',
|
|
159
|
+
success: false
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
results: result,
|
|
164
|
+
success: true
|
|
165
|
+
};
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Error generating bulk alt text:', error);
|
|
168
|
+
return {
|
|
169
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
170
|
+
success: false
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
//# sourceMappingURL=openAI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/resolvers/openAI.ts"],"sourcesContent":["import type { AutoParseableResponseFormat } from 'openai/lib/parser.mjs'\nimport type { ChatCompletionContentPartText } from 'openai/resources/chat/completions.mjs'\nimport type { ResponseFormatJSONSchema } from 'openai/resources/shared.mjs'\n\nimport OpenAI from 'openai'\nimport { makeParseableResponseFormat } from 'openai/lib/parser.mjs'\nimport { z } from 'zod'\n\nimport type {\n AltTextBulkResolverArgs,\n AltTextBulkResolverResponse,\n AltTextResolver,\n AltTextResolverArgs,\n AltTextResolverResponse,\n} from './types.js'\n\nexport type OpenAIResolverConfig = {\n /** OpenAI API key for authentication */\n apiKey: string\n /**\n * The OpenAI LLM model to use for alt text generation.\n * @default 'gpt-4.1-nano'\n */\n model?: string\n}\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 */\nfunction zodResponseFormat<ZodInput extends z.ZodType>(\n zodObject: ZodInput,\n name: string,\n props?: Omit<ResponseFormatJSONSchema.JSONSchema, 'name' | 'schema' | 'strict'>,\n): AutoParseableResponseFormat<z.infer<ZodInput>> {\n return makeParseableResponseFormat(\n {\n type: 'json_schema',\n json_schema: {\n ...props,\n name,\n schema: z.toJSONSchema(zodObject, { target: 'draft-7' }),\n strict: true,\n },\n },\n (content) => zodObject.parse(JSON.parse(content)),\n )\n}\n\n/**\n * Creates an OpenAI-based resolver for alt text generation.\n *\n * @example\n * ```typescript\n * import { openAIResolver } from '@jhb.software/payload-alt-text-plugin'\n *\n * openAIResolver({\n * apiKey: process.env.OPENAI_API_KEY,\n * model: 'gpt-4.1-mini', // optional, defaults to 'gpt-4.1-nano'\n * })\n * ```\n */\nexport const openAIResolver = (config: OpenAIResolverConfig): AltTextResolver => {\n const { apiKey, model = 'gpt-4.1-nano' } = config\n\n return {\n key: 'openai',\n resolve: async ({\n filename,\n imageThumbnailUrl,\n locale,\n }: AltTextResolverArgs): Promise<AltTextResolverResponse> => {\n try {\n const openai = new OpenAI({ apiKey })\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 max_completion_tokens: 150,\n messages: [\n {\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 role: 'system',\n },\n {\n content: [\n {\n type: 'image_url',\n image_url: { url: imageThumbnailUrl },\n },\n ...(filename\n ? [\n {\n type: 'text',\n text: filename,\n } satisfies ChatCompletionContentPartText,\n ]\n : []),\n ],\n role: 'user',\n },\n ],\n model,\n response_format: zodResponseFormat(modelResponseSchema, 'data'),\n })\n\n const result = response.choices[0]?.message?.parsed\n\n if (!result) {\n return { error: 'No result from OpenAI', success: false }\n }\n\n return {\n result,\n success: true,\n }\n } catch (error) {\n console.error('Error generating alt text:', error)\n return {\n error: error instanceof Error ? error.message : 'Unknown error',\n success: false,\n }\n }\n },\n resolveBulk: async ({\n filename,\n imageThumbnailUrl,\n locales,\n }: AltTextBulkResolverArgs): Promise<AltTextBulkResolverResponse> => {\n try {\n const openai = new OpenAI({ apiKey })\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\n .array(z.string())\n .describe('Keywords that describe the content of the image'),\n }),\n ]),\n ),\n )\n\n const response = await openai.chat.completions.parse({\n max_completion_tokens: 300,\n messages: [\n {\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\" and \"keywords\".\n `,\n role: 'system',\n },\n {\n content: [\n {\n type: 'image_url',\n image_url: { url: imageThumbnailUrl },\n },\n ...(filename\n ? [\n {\n type: 'text',\n text: filename,\n } satisfies ChatCompletionContentPartText,\n ]\n : []),\n ],\n role: 'user',\n },\n ],\n model,\n response_format: zodResponseFormat(modelResponseSchema, 'data'),\n })\n\n const result = response.choices[0]?.message?.parsed\n\n if (!result) {\n return { error: 'No result from OpenAI', success: false }\n }\n\n return {\n results: result,\n success: true,\n }\n } catch (error) {\n console.error('Error generating bulk alt text:', error)\n return {\n error: error instanceof Error ? error.message : 'Unknown error',\n success: false,\n }\n }\n },\n }\n}\n"],"names":["OpenAI","makeParseableResponseFormat","z","zodResponseFormat","zodObject","name","props","type","json_schema","schema","toJSONSchema","target","strict","content","parse","JSON","openAIResolver","config","apiKey","model","key","resolve","filename","imageThumbnailUrl","locale","openai","modelResponseSchema","object","altText","string","describe","keywords","array","response","chat","completions","max_completion_tokens","messages","role","image_url","url","text","response_format","result","choices","message","parsed","error","success","console","Error","resolveBulk","locales","Object","fromEntries","map","join","results"],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Result of generating alt text for a single image.
|
|
4
|
+
*/
|
|
5
|
+
export type AltTextResult = {
|
|
6
|
+
/** Concise descriptive alt text (1-2 sentences) */
|
|
7
|
+
altText: string;
|
|
8
|
+
/** Keywords describing the image content */
|
|
9
|
+
keywords: string[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Arguments passed to the resolver for single image generation.
|
|
13
|
+
*/
|
|
14
|
+
export type AltTextResolverArgs = {
|
|
15
|
+
/** Optional filename for additional context */
|
|
16
|
+
filename?: string;
|
|
17
|
+
/** URL of the image thumbnail (must be publicly accessible) */
|
|
18
|
+
imageThumbnailUrl: string;
|
|
19
|
+
/** Target locale for the generated alt text */
|
|
20
|
+
locale: string;
|
|
21
|
+
/** Payload request object for logging */
|
|
22
|
+
req: PayloadRequest;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Arguments passed to the resolver for bulk/multi-locale generation.
|
|
26
|
+
*/
|
|
27
|
+
export type AltTextBulkResolverArgs = {
|
|
28
|
+
/** Optional filename for additional context */
|
|
29
|
+
filename?: string;
|
|
30
|
+
/** URL of the image thumbnail (must be publicly accessible) */
|
|
31
|
+
imageThumbnailUrl: string;
|
|
32
|
+
/** Target locales for the generated alt texts */
|
|
33
|
+
locales: string[];
|
|
34
|
+
/** Payload request object for logging */
|
|
35
|
+
req: PayloadRequest;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Response from single image alt text generation.
|
|
39
|
+
*/
|
|
40
|
+
export type AltTextResolverResponse = {
|
|
41
|
+
error?: string;
|
|
42
|
+
success: false;
|
|
43
|
+
} | {
|
|
44
|
+
result: AltTextResult;
|
|
45
|
+
success: true;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Response from bulk/multi-locale alt text generation.
|
|
49
|
+
*/
|
|
50
|
+
export type AltTextBulkResolverResponse = {
|
|
51
|
+
error?: string;
|
|
52
|
+
success: false;
|
|
53
|
+
} | {
|
|
54
|
+
results: Record<string, AltTextResult>;
|
|
55
|
+
success: true;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Alt text resolver interface.
|
|
59
|
+
* Implement this to create custom resolvers for different providers.
|
|
60
|
+
*/
|
|
61
|
+
export type AltTextResolver = {
|
|
62
|
+
/** Unique key identifying this resolver */
|
|
63
|
+
key: string;
|
|
64
|
+
/** Generate alt text for a single image in one locale */
|
|
65
|
+
resolve: (args: AltTextResolverArgs) => Promise<AltTextResolverResponse>;
|
|
66
|
+
/** Generate alt text for a single image in multiple locales (bulk operation) */
|
|
67
|
+
resolveBulk: (args: AltTextBulkResolverArgs) => Promise<AltTextBulkResolverResponse>;
|
|
68
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/resolvers/types.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\n/**\n * Result of generating alt text for a single image.\n */\nexport type AltTextResult = {\n /** Concise descriptive alt text (1-2 sentences) */\n altText: string\n /** Keywords describing the image content */\n keywords: string[]\n}\n\n/**\n * Arguments passed to the resolver for single image generation.\n */\nexport type AltTextResolverArgs = {\n /** Optional filename for additional context */\n filename?: string\n /** URL of the image thumbnail (must be publicly accessible) */\n imageThumbnailUrl: string\n /** Target locale for the generated alt text */\n locale: string\n /** Payload request object for logging */\n req: PayloadRequest\n}\n\n/**\n * Arguments passed to the resolver for bulk/multi-locale generation.\n */\nexport type AltTextBulkResolverArgs = {\n /** Optional filename for additional context */\n filename?: string\n /** URL of the image thumbnail (must be publicly accessible) */\n imageThumbnailUrl: string\n /** Target locales for the generated alt texts */\n locales: string[]\n /** Payload request object for logging */\n req: PayloadRequest\n}\n\n/**\n * Response from single image alt text generation.\n */\nexport type AltTextResolverResponse =\n | { error?: string; success: false }\n | { result: AltTextResult; success: true }\n\n/**\n * Response from bulk/multi-locale alt text generation.\n */\nexport type AltTextBulkResolverResponse =\n | { error?: string; success: false }\n | { results: Record<string, AltTextResult>; success: true }\n\n/**\n * Alt text resolver interface.\n * Implement this to create custom resolvers for different providers.\n */\nexport type AltTextResolver = {\n /** Unique key identifying this resolver */\n key: string\n /** Generate alt text for a single image in one locale */\n resolve: (args: AltTextResolverArgs) => Promise<AltTextResolverResponse>\n /** Generate alt text for a single image in multiple locales (bulk operation) */\n resolveBulk: (args: AltTextBulkResolverArgs) => Promise<AltTextBulkResolverResponse>\n}\n"],"names":[],"mappings":"AAsDA;;;CAGC,GACD,WAOC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { GenericTranslationsObject } from './index.js';
|
|
1
|
+
import type { GenericTranslationsObject } from './index.js';
|
|
2
2
|
export declare const de: GenericTranslationsObject;
|
package/dist/translations/de.js
CHANGED
|
@@ -11,12 +11,12 @@ export const de = {
|
|
|
11
11
|
image: 'Bild',
|
|
12
12
|
images: 'Bilder',
|
|
13
13
|
// Toast messages
|
|
14
|
-
cannotGenerateMissingFields: 'Alternativtext kann nicht generiert werden. Erforderliche Felder fehlen.',
|
|
15
|
-
failedToGenerate: 'Generierung des Alternativtextes fehlgeschlagen. Bitte versuchen Sie es erneut.',
|
|
16
14
|
altTextGeneratedSuccess: 'Alternativtext erfolgreich generiert. Bitte überprüfen und speichern Sie das Dokument.',
|
|
17
|
-
|
|
15
|
+
cannotGenerateMissingFields: 'Alternativtext kann nicht generiert werden. Erforderliche Felder fehlen.',
|
|
18
16
|
errorGeneratingAltText: 'Fehler beim Generieren des Alternativtextes. Bitte versuchen Sie es erneut.',
|
|
17
|
+
failedToGenerate: 'Generierung des Alternativtextes fehlgeschlagen. Bitte versuchen Sie es erneut.',
|
|
19
18
|
failedToGenerateForXImages: 'Generierung des Alternativtextes für {X} Bilder fehlgeschlagen.',
|
|
19
|
+
noAltTextGenerated: 'Kein Alternativtext generiert. Bitte versuchen Sie es erneut.',
|
|
20
20
|
xOfYImagesUpdated: '{X} von {Y} Bildern aktualisiert.',
|
|
21
21
|
// Help text
|
|
22
22
|
altTextDescription: 'Alternativtext für das Bild. Dieser wird für Screenreader und SEO verwendet. Er sollte die folgenden Anforderungen erfüllen:',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/translations/de.ts"],"sourcesContent":["import { GenericTranslationsObject } from './index.js'\n\nexport const de: GenericTranslationsObject = {\n $schema: './translation-schema.json',\n '@jhb.software/payload-alt-text-plugin': {\n // Field labels\n alternateText: 'Alternativtext',\n keywords: 'Schlüsselwörter',\n keywordsDescription:\n 'Schlüsselwörter, die das Bild beschreiben. Wird bei der Suche nach dem Bild verwendet.',\n\n // Button labels\n generateAltText: 'Alternativtext generieren',\n generateAltTextFor: 'Alternativtext generieren für',\n image: 'Bild',\n images: 'Bilder',\n\n // Toast messages\n
|
|
1
|
+
{"version":3,"sources":["../../src/translations/de.ts"],"sourcesContent":["import type { GenericTranslationsObject } from './index.js'\n\nexport const de: GenericTranslationsObject = {\n $schema: './translation-schema.json',\n '@jhb.software/payload-alt-text-plugin': {\n // Field labels\n alternateText: 'Alternativtext',\n keywords: 'Schlüsselwörter',\n keywordsDescription:\n 'Schlüsselwörter, die das Bild beschreiben. Wird bei der Suche nach dem Bild verwendet.',\n\n // Button labels\n generateAltText: 'Alternativtext generieren',\n generateAltTextFor: 'Alternativtext generieren für',\n image: 'Bild',\n images: 'Bilder',\n\n // Toast messages\n altTextGeneratedSuccess:\n 'Alternativtext erfolgreich generiert. Bitte überprüfen und speichern Sie das Dokument.',\n cannotGenerateMissingFields:\n 'Alternativtext kann nicht generiert werden. Erforderliche Felder fehlen.',\n errorGeneratingAltText:\n 'Fehler beim Generieren des Alternativtextes. Bitte versuchen Sie es erneut.',\n failedToGenerate:\n 'Generierung des Alternativtextes fehlgeschlagen. Bitte versuchen Sie es erneut.',\n failedToGenerateForXImages: 'Generierung des Alternativtextes für {X} Bilder fehlgeschlagen.',\n noAltTextGenerated: 'Kein Alternativtext generiert. Bitte versuchen Sie es erneut.',\n xOfYImagesUpdated: '{X} von {Y} Bildern aktualisiert.',\n\n // Help text\n altTextDescription:\n 'Alternativtext für das Bild. Dieser wird für Screenreader und SEO verwendet. Er sollte die folgenden Anforderungen erfüllen:',\n altTextRequirement1: 'Beschreibt in 1-2 Sätzen, was auf dem Bild zu sehen ist.',\n altTextRequirement2:\n 'Sollte möglichst die gleichen Informationen oder den gleichen Zweck wie das Bild vermitteln.',\n altTextRequirement3:\n 'Phrasen wie \"Bild von\" oder \"Foto von\" sind überflüssig, da Screenreader bereits anzeigen, dass es sich um ein Bild handelt.',\n\n // Tooltips\n pleaseSaveDocumentFirst: 'Bitte speichern Sie zuerst das Dokument',\n\n // Validation messages\n theAlternateTextIsRequired: 'Der Alternativtext ist erforderlich.',\n },\n}\n"],"names":["de","$schema","alternateText","keywords","keywordsDescription","generateAltText","generateAltTextFor","image","images","altTextGeneratedSuccess","cannotGenerateMissingFields","errorGeneratingAltText","failedToGenerate","failedToGenerateForXImages","noAltTextGenerated","xOfYImagesUpdated","altTextDescription","altTextRequirement1","altTextRequirement2","altTextRequirement3","pleaseSaveDocumentFirst","theAlternateTextIsRequired"],"mappings":"AAEA,OAAO,MAAMA,KAAgC;IAC3CC,SAAS;IACT,yCAAyC;QACvC,eAAe;QACfC,eAAe;QACfC,UAAU;QACVC,qBACE;QAEF,gBAAgB;QAChBC,iBAAiB;QACjBC,oBAAoB;QACpBC,OAAO;QACPC,QAAQ;QAER,iBAAiB;QACjBC,yBACE;QACFC,6BACE;QACFC,wBACE;QACFC,kBACE;QACFC,4BAA4B;QAC5BC,oBAAoB;QACpBC,mBAAmB;QAEnB,YAAY;QACZC,oBACE;QACFC,qBAAqB;QACrBC,qBACE;QACFC,qBACE;QAEF,WAAW;QACXC,yBAAyB;QAEzB,sBAAsB;QACtBC,4BAA4B;IAC9B;AACF,EAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { GenericTranslationsObject } from './index.js';
|
|
1
|
+
import type { GenericTranslationsObject } from './index.js';
|
|
2
2
|
export declare const en: GenericTranslationsObject;
|
package/dist/translations/en.js
CHANGED
|
@@ -11,12 +11,12 @@ export const en = {
|
|
|
11
11
|
image: 'image',
|
|
12
12
|
images: 'images',
|
|
13
13
|
// Toast messages
|
|
14
|
-
cannotGenerateMissingFields: 'Cannot generate alt text. Missing required fields.',
|
|
15
|
-
failedToGenerate: 'Failed to generate alt text. Please try again.',
|
|
16
14
|
altTextGeneratedSuccess: 'Alt text generated successfully. Please review and save the document.',
|
|
17
|
-
|
|
15
|
+
cannotGenerateMissingFields: 'Cannot generate alt text. Missing required fields.',
|
|
18
16
|
errorGeneratingAltText: 'Error generating alt text. Please try again.',
|
|
17
|
+
failedToGenerate: 'Failed to generate alt text. Please try again.',
|
|
19
18
|
failedToGenerateForXImages: 'Failed to generate alt text for {X} images.',
|
|
19
|
+
noAltTextGenerated: 'No alt text generated. Please try again.',
|
|
20
20
|
xOfYImagesUpdated: '{X} of {Y} images updated.',
|
|
21
21
|
// Help text
|
|
22
22
|
altTextDescription: 'Alternate text for the image. This will be used for screen readers and SEO. It should meet the following requirements:',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/translations/en.ts"],"sourcesContent":["import { GenericTranslationsObject } from './index.js'\n\nexport const en: GenericTranslationsObject = {\n $schema: './translation-schema.json',\n '@jhb.software/payload-alt-text-plugin': {\n // Field labels\n alternateText: 'Alternate text',\n keywords: 'Keywords',\n keywordsDescription: 'Keywords which describe the image. Used when searching for the image.',\n\n // Button labels\n generateAltText: 'Generate alt text',\n generateAltTextFor: 'Generate alt text for',\n image: 'image',\n images: 'images',\n\n // Toast messages\n
|
|
1
|
+
{"version":3,"sources":["../../src/translations/en.ts"],"sourcesContent":["import type { GenericTranslationsObject } from './index.js'\n\nexport const en: GenericTranslationsObject = {\n $schema: './translation-schema.json',\n '@jhb.software/payload-alt-text-plugin': {\n // Field labels\n alternateText: 'Alternate text',\n keywords: 'Keywords',\n keywordsDescription: 'Keywords which describe the image. Used when searching for the image.',\n\n // Button labels\n generateAltText: 'Generate alt text',\n generateAltTextFor: 'Generate alt text for',\n image: 'image',\n images: 'images',\n\n // Toast messages\n altTextGeneratedSuccess:\n 'Alt text generated successfully. Please review and save the document.',\n cannotGenerateMissingFields: 'Cannot generate alt text. Missing required fields.',\n errorGeneratingAltText: 'Error generating alt text. Please try again.',\n failedToGenerate: 'Failed to generate alt text. Please try again.',\n failedToGenerateForXImages: 'Failed to generate alt text for {X} images.',\n noAltTextGenerated: 'No alt text generated. Please try again.',\n xOfYImagesUpdated: '{X} of {Y} images updated.',\n\n // Help text\n altTextDescription:\n 'Alternate text for the image. This will be used for screen readers and SEO. It should meet the following requirements:',\n altTextRequirement1: 'Describes in 1-2 sentences, what is visible in the image.',\n altTextRequirement2:\n 'Should convey the same information or purpose as the image, whenever possible.',\n altTextRequirement3:\n 'Phrases like \"image of\" or \"picture of\" are unnecessary, since screen readers already announce that it\\'s an image.',\n\n // Tooltips\n pleaseSaveDocumentFirst: 'Please save the document first',\n\n // Validation messages\n theAlternateTextIsRequired: 'An alternate text is required.',\n },\n}\n"],"names":["en","$schema","alternateText","keywords","keywordsDescription","generateAltText","generateAltTextFor","image","images","altTextGeneratedSuccess","cannotGenerateMissingFields","errorGeneratingAltText","failedToGenerate","failedToGenerateForXImages","noAltTextGenerated","xOfYImagesUpdated","altTextDescription","altTextRequirement1","altTextRequirement2","altTextRequirement3","pleaseSaveDocumentFirst","theAlternateTextIsRequired"],"mappings":"AAEA,OAAO,MAAMA,KAAgC;IAC3CC,SAAS;IACT,yCAAyC;QACvC,eAAe;QACfC,eAAe;QACfC,UAAU;QACVC,qBAAqB;QAErB,gBAAgB;QAChBC,iBAAiB;QACjBC,oBAAoB;QACpBC,OAAO;QACPC,QAAQ;QAER,iBAAiB;QACjBC,yBACE;QACFC,6BAA6B;QAC7BC,wBAAwB;QACxBC,kBAAkB;QAClBC,4BAA4B;QAC5BC,oBAAoB;QACpBC,mBAAmB;QAEnB,YAAY;QACZC,oBACE;QACFC,qBAAqB;QACrBC,qBACE;QACFC,qBACE;QAEF,WAAW;QACXC,yBAAyB;QAEzB,sBAAsB;QACtBC,4BAA4B;IAC9B;AACF,EAAC"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { CollectionSlug, Field } from 'payload';
|
|
1
|
+
import type { CollectionSlug, Field } from 'payload';
|
|
2
|
+
import type { AltTextResolver } from '../resolvers/types.js';
|
|
2
3
|
/** Configuration options for the alt text plugin. */
|
|
3
4
|
export type IncomingAltTextPluginConfig = {
|
|
4
|
-
/** Whether the plugin is enabled. */
|
|
5
|
-
enabled?: boolean;
|
|
6
|
-
/** OpenAI API key for authentication. */
|
|
7
|
-
openAIApiKey: string;
|
|
8
5
|
/** Collection slugs to enable the plugin for. */
|
|
9
6
|
collections: CollectionSlug[];
|
|
10
|
-
/**
|
|
11
|
-
|
|
7
|
+
/** Whether the plugin is enabled. */
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
/** Override the default fields inserted by the plugin via a function that receives the default fields and returns the new fields */
|
|
10
|
+
fieldsOverride?: (args: {
|
|
11
|
+
defaultFields: Field[];
|
|
12
|
+
}) => Field[];
|
|
12
13
|
/**
|
|
13
14
|
* Function to get the thumbnail URL of an image document.
|
|
14
15
|
* This URL will be sent to the LLM for analysis.
|
|
@@ -18,39 +19,40 @@ export type IncomingAltTextPluginConfig = {
|
|
|
18
19
|
* - Use a thumbnail/preview version of the image when possible (e.g. from the sizes field)
|
|
19
20
|
*/
|
|
20
21
|
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
22
|
/**
|
|
28
23
|
* The locale to generate alt texts in when localization is disabled.
|
|
24
|
+
*
|
|
29
25
|
* Required when localization is disabled, ignored when localization is enabled.
|
|
30
|
-
* @example 'en'
|
|
26
|
+
* @example 'en'
|
|
31
27
|
*/
|
|
32
28
|
locale?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Maximum number of concurrent API requests for bulk generate operations.
|
|
31
|
+
*
|
|
32
|
+
* @default 16
|
|
33
|
+
*/
|
|
34
|
+
maxBulkGenerateConcurrency?: number;
|
|
35
|
+
/** The resolver to use for generating alt text (e.g., openAIResolver) */
|
|
36
|
+
resolver: AltTextResolver;
|
|
33
37
|
};
|
|
34
38
|
/** Configuration of the alt text plugin after defaults have been applied. */
|
|
35
39
|
export type AltTextPluginConfig = {
|
|
36
|
-
/** Whether the plugin is enabled. */
|
|
37
|
-
enabled: boolean;
|
|
38
|
-
/** OpenAI API key for authentication. */
|
|
39
|
-
openAIApiKey: string;
|
|
40
40
|
/** Collection slugs to enable the plugin for. */
|
|
41
41
|
collections: CollectionSlug[];
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
/** Function to get the thumbnail URL of an image document. */
|
|
45
|
-
getImageThumbnail: (doc: Record<string, unknown>) => string;
|
|
46
|
-
/** The OpenAI LLM model to use for alt text generation. */
|
|
47
|
-
model: 'gpt-4.1-nano' | 'gpt-4.1-mini';
|
|
42
|
+
/** Whether the plugin is enabled. */
|
|
43
|
+
enabled: boolean;
|
|
48
44
|
/** Override the default fields inserted by the plugin via a function that receives the default fields and returns the new fields */
|
|
49
45
|
fieldsOverride?: (args: {
|
|
50
46
|
defaultFields: Field[];
|
|
51
47
|
}) => Field[];
|
|
52
|
-
/**
|
|
53
|
-
|
|
48
|
+
/** Function to get the thumbnail URL of an image document. */
|
|
49
|
+
getImageThumbnail: (doc: Record<string, unknown>) => string;
|
|
54
50
|
/** The locale to generate alt texts in when localization is disabled. */
|
|
55
51
|
locale?: string;
|
|
52
|
+
/** The locales to generate alt texts for. */
|
|
53
|
+
locales: string[];
|
|
54
|
+
/** Maximum number of concurrent API requests for bulk generate operations. */
|
|
55
|
+
maxBulkGenerateConcurrency: number;
|
|
56
|
+
/** The resolver to use for generating alt text */
|
|
57
|
+
resolver: AltTextResolver;
|
|
56
58
|
};
|
|
@@ -1 +1 @@
|
|
|
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 /**
|
|
1
|
+
{"version":3,"sources":["../../src/types/AltTextPluginConfig.ts"],"sourcesContent":["import type { CollectionSlug, Field } from 'payload'\n\nimport type { AltTextResolver } from '../resolvers/types.js'\n\n/** Configuration options for the alt text plugin. */\nexport type IncomingAltTextPluginConfig = {\n /** Collection slugs to enable the plugin for. */\n collections: CollectionSlug[]\n\n /** Whether the plugin is enabled. */\n enabled?: boolean\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 * 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 /**\n * The locale to generate alt texts in when localization is disabled.\n *\n * Required when localization is disabled, ignored when localization is enabled.\n * @example 'en'\n */\n locale?: string\n\n /**\n * Maximum number of concurrent API requests for bulk generate operations.\n *\n * @default 16\n */\n maxBulkGenerateConcurrency?: number\n\n /** The resolver to use for generating alt text (e.g., openAIResolver) */\n resolver: AltTextResolver\n}\n\n/** Configuration of the alt text plugin after defaults have been applied. */\nexport type AltTextPluginConfig = {\n /** Collection slugs to enable the plugin for. */\n collections: CollectionSlug[]\n\n /** Whether the plugin is enabled. */\n enabled: boolean\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 /** Function to get the thumbnail URL of an image document. */\n getImageThumbnail: (doc: Record<string, unknown>) => string\n\n /** The locale to generate alt texts in when localization is disabled. */\n locale?: string\n\n /** The locales to generate alt texts for. */\n locales: string[]\n\n /** Maximum number of concurrent API requests for bulk generate operations. */\n maxBulkGenerateConcurrency: number\n\n /** The resolver to use for generating alt text */\n resolver: AltTextResolver\n}\n"],"names":[],"mappings":"AA4CA,2EAA2E,GAC3E,WAwBC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { SanitizedConfig } from 'payload';
|
|
1
|
+
import type { SanitizedConfig } from 'payload';
|
|
2
2
|
/** Returns the locales from the config. Returns undefined when localization is disabled. */
|
|
3
3
|
export declare function localesFromConfig(config: SanitizedConfig): string[] | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utilities/localesFromConfig.ts"],"sourcesContent":["import { SanitizedConfig } from 'payload'\n\n/** Returns the locales from the config. Returns undefined when localization is disabled. */\nexport function localesFromConfig(config: SanitizedConfig): string[] | undefined {\n if (typeof config.localization === 'object' && config.localization) {\n return config.localization.localeCodes\n } else {\n return undefined\n }\n}\n"],"names":["localesFromConfig","config","localization","localeCodes","undefined"],"mappings":"AAEA,0FAA0F,GAC1F,OAAO,SAASA,kBAAkBC,MAAuB;IACvD,IAAI,OAAOA,OAAOC,YAAY,KAAK,YAAYD,OAAOC,YAAY,EAAE;QAClE,OAAOD,OAAOC,YAAY,CAACC,WAAW;IACxC,OAAO;QACL,OAAOC;IACT;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../src/utilities/localesFromConfig.ts"],"sourcesContent":["import type { SanitizedConfig } from 'payload'\n\n/** Returns the locales from the config. Returns undefined when localization is disabled. */\nexport function localesFromConfig(config: SanitizedConfig): string[] | undefined {\n if (typeof config.localization === 'object' && config.localization) {\n return config.localization.localeCodes\n } else {\n return undefined\n }\n}\n"],"names":["localesFromConfig","config","localization","localeCodes","undefined"],"mappings":"AAEA,0FAA0F,GAC1F,OAAO,SAASA,kBAAkBC,MAAuB;IACvD,IAAI,OAAOA,OAAOC,YAAY,KAAK,YAAYD,OAAOC,YAAY,EAAE;QAClE,OAAOD,OAAOC,YAAY,CAACC,WAAW;IACxC,OAAO;QACL,OAAOC;IACT;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/translatedLabel.ts"],"sourcesContent":["import { StaticLabel } from 'payload'\nimport { translations } from '../translations/index.js'\n\n/** Returns the StaticLabel object for the given translation to to use inside the field label. */\nexport function translatedLabel(key: string): StaticLabel {\n return Object.fromEntries(\n Object.entries(translations).map(([locale, translation]) => [\n locale,\n (translation['@jhb.software/payload-alt-text-plugin'] as Record<string, string>)[key] || key,\n ]),\n )\n}\n"],"names":["translations","translatedLabel","key","Object","fromEntries","entries","map","locale","translation"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/utils/translatedLabel.ts"],"sourcesContent":["import type { StaticLabel } from 'payload'\n\nimport { translations } from '../translations/index.js'\n\n/** Returns the StaticLabel object for the given translation to to use inside the field label. */\nexport function translatedLabel(key: string): StaticLabel {\n return Object.fromEntries(\n Object.entries(translations).map(([locale, translation]) => [\n locale,\n (translation['@jhb.software/payload-alt-text-plugin'] as Record<string, string>)[key] || key,\n ]),\n )\n}\n"],"names":["translations","translatedLabel","key","Object","fromEntries","entries","map","locale","translation"],"mappings":"AAEA,SAASA,YAAY,QAAQ,2BAA0B;AAEvD,+FAA+F,GAC/F,OAAO,SAASC,gBAAgBC,GAAW;IACzC,OAAOC,OAAOC,WAAW,CACvBD,OAAOE,OAAO,CAACL,cAAcM,GAAG,CAAC,CAAC,CAACC,QAAQC,YAAY,GAAK;YAC1DD;YACCC,WAAW,CAAC,wCAAwC,AAA2B,CAACN,IAAI,IAAIA;SAC1F;AAEL"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PluginAltTextTranslationKeys } from 'src/translations/index.js';
|
|
1
|
+
import type { PluginAltTextTranslationKeys } from 'src/translations/index.js';
|
|
2
2
|
/** Hook which returns a translation function for the plugin translations. */
|
|
3
3
|
export declare const usePluginTranslation: () => {
|
|
4
4
|
t: (key: PluginAltTextTranslationKeys) => string;
|
|
@@ -6,7 +6,7 @@ import { useTranslation } from '@payloadcms/ui';
|
|
|
6
6
|
t: (key)=>{
|
|
7
7
|
const translation = pluginTranslations[key];
|
|
8
8
|
if (!translation) {
|
|
9
|
-
console.
|
|
9
|
+
console.error('Plugin translation not found', key);
|
|
10
10
|
}
|
|
11
11
|
return translation ?? key;
|
|
12
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/usePluginTranslation.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../src/utils/usePluginTranslation.ts"],"sourcesContent":["import type {\n PluginAltTextTranslationKeys,\n PluginAltTextTranslations,\n} from 'src/translations/index.js'\n\nimport { useTranslation } from '@payloadcms/ui'\n\n/** Hook which returns a translation function for the plugin translations. */\nexport const usePluginTranslation = () => {\n const { i18n } = useTranslation<PluginAltTextTranslations, PluginAltTextTranslationKeys>()\n const pluginTranslations = i18n.translations[\n '@jhb.software/payload-alt-text-plugin'\n ] as PluginAltTextTranslations\n\n return {\n t: (key: PluginAltTextTranslationKeys) => {\n const translation = pluginTranslations[key] as string\n\n if (!translation) {\n console.error('Plugin translation not found', key)\n }\n return translation ?? key\n },\n }\n}\n"],"names":["useTranslation","usePluginTranslation","i18n","pluginTranslations","translations","t","key","translation","console","error"],"mappings":"AAKA,SAASA,cAAc,QAAQ,iBAAgB;AAE/C,2EAA2E,GAC3E,OAAO,MAAMC,uBAAuB;IAClC,MAAM,EAAEC,IAAI,EAAE,GAAGF;IACjB,MAAMG,qBAAqBD,KAAKE,YAAY,CAC1C,wCACD;IAED,OAAO;QACLC,GAAG,CAACC;YACF,MAAMC,cAAcJ,kBAAkB,CAACG,IAAI;YAE3C,IAAI,CAACC,aAAa;gBAChBC,QAAQC,KAAK,CAAC,gCAAgCH;YAChD;YACA,OAAOC,eAAeD;QACxB;IACF;AACF,EAAC"}
|