@pigment/auto-translate 1.5.0 β 1.6.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 +28 -10
- package/dist/collections/translationExclusions.js +101 -4
- package/dist/collections/translationExclusions.js.map +1 -1
- package/dist/components/LockTranslation/actions/lockTranslations.d.ts +1 -1
- package/dist/components/LockTranslation/actions/lockTranslations.js +2 -2
- package/dist/components/LockTranslation/actions/lockTranslations.js.map +1 -1
- package/dist/components/LockTranslation/index.js +7 -6
- package/dist/components/LockTranslation/index.js.map +1 -1
- package/dist/components/OpenAiModelField.d.ts +2 -0
- package/dist/components/OpenAiModelField.js +96 -0
- package/dist/components/OpenAiModelField.js.map +1 -0
- package/dist/components/TranslationControl.js +45 -51
- package/dist/components/TranslationControl.js.map +1 -1
- package/dist/endpoints/listOpenAiModels.d.ts +5 -0
- package/dist/endpoints/listOpenAiModels.js +93 -0
- package/dist/endpoints/listOpenAiModels.js.map +1 -0
- package/dist/exports/client.d.ts +1 -0
- package/dist/exports/client.js +1 -0
- package/dist/exports/client.js.map +1 -1
- package/dist/globals/translationSettings.js +24 -13
- package/dist/globals/translationSettings.js.map +1 -1
- package/dist/services/translationService.js +17 -17
- package/dist/services/translationService.js.map +1 -1
- package/dist/utilities/fieldHelpers.d.ts +0 -29
- package/dist/utilities/fieldHelpers.js +0 -152
- package/dist/utilities/fieldHelpers.js.map +1 -1
- package/dist/utilities/injectTranslationControls.d.ts +1 -1
- package/dist/utilities/injectTranslationControls.js +16 -11
- package/dist/utilities/injectTranslationControls.js.map +1 -1
- package/package.json +12 -12
- package/dist/components/TranslationSettingsLock.css +0 -87
- package/dist/components/TranslationSettingsLock.d.ts +0 -9
- package/dist/components/TranslationSettingsLock.js +0 -155
- package/dist/components/TranslationSettingsLock.js.map +0 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A powerful auto-translation plugin for [Payload CMS](https://payloadcms.com) tha
|
|
|
9
9
|
- π― **Smart Translation**: Preserves excluded fields when updating default language content
|
|
10
10
|
- π§ **Flexible Configuration**: Configure per-collection settings and global exclusions
|
|
11
11
|
- π **Protected Settings**: Lock/unlock translation settings to prevent accidental changes
|
|
12
|
-
- π€ **OpenAI Integration**:
|
|
12
|
+
- π€ **OpenAI Integration**: Translate with OpenAI (model selectable in Translation Settings)
|
|
13
13
|
- π¦ **Zero UI Overhead**: Seamlessly integrates with Payload's admin panel
|
|
14
14
|
- β‘ **Performance Optimized**: 10-15x faster translation with smart extraction and deduplication
|
|
15
15
|
|
|
@@ -75,6 +75,16 @@ OPENAI_BASE_URL=https://api.openai.com/v1
|
|
|
75
75
|
|
|
76
76
|
> **β οΈ Important:** Restart your server after updating `.env` or plugin settings.
|
|
77
77
|
|
|
78
|
+
### Translation Settings (Admin)
|
|
79
|
+
|
|
80
|
+
After install, open **Auto-Translate Settings β Translation Settings** in the Payload admin:
|
|
81
|
+
|
|
82
|
+
- **Model** β live OpenAI model dropdown (requires `OPENAI_API_KEY`). This value is what translation calls use.
|
|
83
|
+
- **Temperature** β hidden for GPT-5+ / o-series (those models reject custom temperature).
|
|
84
|
+
- **System prompt / rules / max tokens** β editable after unlocking settings.
|
|
85
|
+
|
|
86
|
+
`provider.model` in plugin config is only a fallback when the global has no model set.
|
|
87
|
+
|
|
78
88
|
---
|
|
79
89
|
|
|
80
90
|
## π§ Advanced Configuration
|
|
@@ -103,7 +113,8 @@ export default buildConfig({
|
|
|
103
113
|
// Optional: Translation provider settings
|
|
104
114
|
provider: {
|
|
105
115
|
type: 'openai', // or 'custom'
|
|
106
|
-
|
|
116
|
+
// Fallback only β prefer Admin β Translation Settings β Model
|
|
117
|
+
model: 'gpt-4o',
|
|
107
118
|
apiKey: process.env.OPENAI_API_KEY,
|
|
108
119
|
baseURL: process.env.OPENAI_BASE_URL,
|
|
109
120
|
|
|
@@ -329,7 +340,7 @@ src/
|
|
|
329
340
|
βββ components/ # UI components
|
|
330
341
|
β βββ TranslationControl.tsx # Field-level control button
|
|
331
342
|
βββ endpoints/ # Custom API endpoints
|
|
332
|
-
βββ
|
|
343
|
+
βββ listOpenAiModels.ts # Live OpenAI model list for Translation Settings
|
|
333
344
|
```
|
|
334
345
|
|
|
335
346
|
---
|
|
@@ -390,14 +401,21 @@ If you need custom access control for translation features, you can add hooks or
|
|
|
390
401
|
|
|
391
402
|
## π Additional Documentation
|
|
392
403
|
|
|
393
|
-
|
|
404
|
+
More detailed guides (architecture, integration examples, advanced usage) will be published on the project's GitHub Wiki.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## ποΈ Database Compatibility
|
|
409
|
+
|
|
410
|
+
The plugin is tested against both officially-supported Payload database adapters:
|
|
411
|
+
|
|
412
|
+
- **MongoDB** (`@payloadcms/db-mongodb`) β no extra setup required.
|
|
413
|
+
- **Postgres** (`@payloadcms/db-postgres`) β schema changes (e.g. adding localized fields, or fields from
|
|
414
|
+
companion plugins like `@payloadcms/plugin-nested-docs`) require running `payload migrate` before those
|
|
415
|
+
fields are usable. Unlike MongoDB, Postgres will reject writes to columns that don't exist in the schema yet.
|
|
394
416
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
- **[ARCHITECTURE.md](./docs/ARCHITECTURE.md)** - Technical architecture and implementation details
|
|
398
|
-
- **[INTEGRATION_EXAMPLES.md](./docs/INTEGRATION_EXAMPLES.md)** - Real-world integration examples
|
|
399
|
-
- **[TRANSLATION_SETTINGS_LOCK.md](./docs/TRANSLATION_SETTINGS_LOCK.md)** - Lock/unlock feature for translation settings
|
|
400
|
-
- **[FEATURE_SUMMARY.md](./docs/FEATURE_SUMMARY.md)** - Complete feature overview
|
|
417
|
+
Both translation and field-level locking behave identically across adapters; the only adapter-specific
|
|
418
|
+
behavior is the migration step required by Postgres.
|
|
401
419
|
|
|
402
420
|
---
|
|
403
421
|
|
|
@@ -1,10 +1,107 @@
|
|
|
1
1
|
export const getTranslationExclusionsCollection = (slug = 'translation-exclusions')=>({
|
|
2
2
|
slug,
|
|
3
3
|
access: {
|
|
4
|
-
create: ({ req
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
create: async ({ data, req })=>{
|
|
5
|
+
if (!req.user || !data?.collectionSlug || !data?.documentId) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
await req.payload.findByID({
|
|
10
|
+
id: data.documentId,
|
|
11
|
+
collection: data.collectionSlug,
|
|
12
|
+
overrideAccess: false,
|
|
13
|
+
req
|
|
14
|
+
});
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
delete: async ({ id, req })=>{
|
|
21
|
+
if (!req.user || !id) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const exclusion = await req.payload.findByID({
|
|
26
|
+
id,
|
|
27
|
+
collection: slug,
|
|
28
|
+
req
|
|
29
|
+
});
|
|
30
|
+
await req.payload.findByID({
|
|
31
|
+
id: exclusion.documentId,
|
|
32
|
+
collection: exclusion.collectionSlug,
|
|
33
|
+
overrideAccess: false,
|
|
34
|
+
req
|
|
35
|
+
});
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
read: async ({ req })=>{
|
|
42
|
+
if (!req.user) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const allowedSlugs = [];
|
|
46
|
+
for (const collectionConfig of req.payload.config.collections){
|
|
47
|
+
// Skip this collection itself β its `read` access is the function
|
|
48
|
+
// currently executing, so calling it here would recurse forever.
|
|
49
|
+
if (collectionConfig.slug === slug) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const readAccess = collectionConfig.access?.read;
|
|
54
|
+
const allowed = typeof readAccess === 'function' ? await readAccess({
|
|
55
|
+
req
|
|
56
|
+
}) : true;
|
|
57
|
+
if (allowed) {
|
|
58
|
+
allowedSlugs.push(collectionConfig.slug);
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Deny collections whose own access check throws
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
collectionSlug: {
|
|
66
|
+
in: allowedSlugs
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
},
|
|
70
|
+
update: async ({ id, data, req })=>{
|
|
71
|
+
if (!req.user || !id) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const exclusion = await req.payload.findByID({
|
|
76
|
+
id,
|
|
77
|
+
collection: slug,
|
|
78
|
+
req
|
|
79
|
+
});
|
|
80
|
+
// Check the persisted target and, if the request moves it, the new target too β
|
|
81
|
+
// otherwise a record for a doc you can't access could be repointed and taken over.
|
|
82
|
+
const targets = [
|
|
83
|
+
[
|
|
84
|
+
exclusion.collectionSlug,
|
|
85
|
+
exclusion.documentId
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
data?.collectionSlug ?? exclusion.collectionSlug,
|
|
89
|
+
data?.documentId ?? exclusion.documentId
|
|
90
|
+
]
|
|
91
|
+
];
|
|
92
|
+
for (const [collection, documentId] of targets){
|
|
93
|
+
await req.payload.findByID({
|
|
94
|
+
id: documentId,
|
|
95
|
+
collection,
|
|
96
|
+
overrideAccess: false,
|
|
97
|
+
req
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
8
105
|
},
|
|
9
106
|
admin: {
|
|
10
107
|
defaultColumns: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/translationExclusions.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport const getTranslationExclusionsCollection = (\n slug: string = 'translation-exclusions',\n): CollectionConfig => ({\n slug,\n access: {\n create: ({ req
|
|
1
|
+
{"version":3,"sources":["../../src/collections/translationExclusions.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport const getTranslationExclusionsCollection = (\n slug: string = 'translation-exclusions',\n): CollectionConfig => ({\n slug,\n access: {\n create: async ({ data, req }) => {\n if (!req.user || !data?.collectionSlug || !data?.documentId) {\n return false\n }\n try {\n await req.payload.findByID({\n id: data.documentId,\n collection: data.collectionSlug,\n overrideAccess: false,\n req,\n })\n return true\n } catch {\n return false\n }\n },\n delete: async ({ id, req }) => {\n if (!req.user || !id) {\n return false\n }\n try {\n const exclusion = await req.payload.findByID({\n id,\n collection: slug,\n req,\n })\n await req.payload.findByID({\n id: exclusion.documentId,\n collection: exclusion.collectionSlug,\n overrideAccess: false,\n req,\n })\n return true\n } catch {\n return false\n }\n },\n read: async ({ req }) => {\n if (!req.user) {\n return false\n }\n const allowedSlugs: string[] = []\n for (const collectionConfig of req.payload.config.collections) {\n // Skip this collection itself β its `read` access is the function\n // currently executing, so calling it here would recurse forever.\n if (collectionConfig.slug === slug) {\n continue\n }\n try {\n const readAccess = collectionConfig.access?.read\n const allowed = typeof readAccess === 'function' ? await readAccess({ req }) : true\n if (allowed) {\n allowedSlugs.push(collectionConfig.slug)\n }\n } catch {\n // Deny collections whose own access check throws\n }\n }\n return { collectionSlug: { in: allowedSlugs } }\n },\n update: async ({ id, data, req }) => {\n if (!req.user || !id) {\n return false\n }\n try {\n const exclusion = await req.payload.findByID({\n id,\n collection: slug,\n req,\n })\n // Check the persisted target and, if the request moves it, the new target too β\n // otherwise a record for a doc you can't access could be repointed and taken over.\n const targets = [\n [exclusion.collectionSlug, exclusion.documentId],\n [data?.collectionSlug ?? exclusion.collectionSlug, data?.documentId ?? exclusion.documentId],\n ]\n for (const [collection, documentId] of targets) {\n await req.payload.findByID({\n id: documentId,\n collection,\n overrideAccess: false,\n req,\n })\n }\n return true\n } catch {\n return false\n }\n },\n },\n admin: {\n defaultColumns: ['collectionSlug', 'documentId', 'locale', 'excludedPaths'],\n description:\n 'Stores field-level translation exclusions per document and locale. Each locale can have its own set of excluded fields. There should only be ONE record per (collectionSlug, documentId, locale) combination.',\n group: 'Auto-Translate Settings',\n useAsTitle: 'collectionSlug',\n },\n fields: [\n {\n name: 'collectionSlug',\n type: 'text',\n admin: {\n description: 'The collection this exclusion belongs to',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n required: true,\n },\n {\n name: 'documentId',\n type: 'text',\n admin: {\n description: 'The ID of the document',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n required: true,\n },\n {\n name: 'locale',\n type: 'text',\n admin: {\n description: 'The locale these exclusions apply to (e.g., \"en\", \"de\", \"fr\")',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n label: 'Locale',\n required: true,\n },\n {\n name: 'excludedPaths',\n type: 'array',\n admin: {\n description:\n 'Fields that should NOT be auto-translated in this specific locale. Each locale has its own independent set of exclusions.',\n },\n fields: [\n {\n name: 'path',\n type: 'text',\n admin: {\n description: 'Field path (e.g., \"title\", \"content.0.description\")',\n },\n label: 'Field Path',\n required: true,\n },\n ],\n label: `Excluded Fields for this Locale`,\n required: true,\n },\n ],\n})\n"],"names":["getTranslationExclusionsCollection","slug","access","create","data","req","user","collectionSlug","documentId","payload","findByID","id","collection","overrideAccess","delete","exclusion","read","allowedSlugs","collectionConfig","config","collections","readAccess","allowed","push","in","update","targets","admin","defaultColumns","description","group","useAsTitle","fields","name","type","position","readOnly","index","required","label"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Client-side function to update the lockTranslationSettings field
|
|
3
3
|
* Makes a fetch request to update the global
|
|
4
4
|
*/
|
|
5
|
-
export declare function updateLockTranslationSettingsField(isLocked: boolean): Promise<{
|
|
5
|
+
export declare function updateLockTranslationSettingsField(isLocked: boolean, apiURL: string): Promise<{
|
|
6
6
|
isLocked: boolean;
|
|
7
7
|
success: boolean;
|
|
8
8
|
}>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client-side function to update the lockTranslationSettings field
|
|
3
3
|
* Makes a fetch request to update the global
|
|
4
|
-
*/ export async function updateLockTranslationSettingsField(isLocked) {
|
|
4
|
+
*/ export async function updateLockTranslationSettingsField(isLocked, apiURL) {
|
|
5
5
|
try {
|
|
6
|
-
const response = await fetch(
|
|
6
|
+
const response = await fetch(`${apiURL}/globals/translation-settings`, {
|
|
7
7
|
body: JSON.stringify({
|
|
8
8
|
lockTranslationSettings: isLocked
|
|
9
9
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/LockTranslation/actions/lockTranslations.ts"],"sourcesContent":["/**\n * Client-side function to update the lockTranslationSettings field\n * Makes a fetch request to update the global\n */\nexport async function updateLockTranslationSettingsField(\n isLocked: boolean,\n): Promise<{ isLocked: boolean; success: boolean }> {\n try {\n const response = await fetch(
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/LockTranslation/actions/lockTranslations.ts"],"sourcesContent":["/**\n * Client-side function to update the lockTranslationSettings field\n * Makes a fetch request to update the global\n */\nexport async function updateLockTranslationSettingsField(\n isLocked: boolean,\n apiURL: string,\n): Promise<{ isLocked: boolean; success: boolean }> {\n try {\n const response = await fetch(`${apiURL}/globals/translation-settings`, {\n body: JSON.stringify({\n lockTranslationSettings: isLocked,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new Error(`Failed to update: ${response.statusText}`)\n }\n\n return {\n isLocked,\n success: true,\n }\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('[LockTranslation] Error updating lock state:', error)\n return {\n isLocked: !isLocked,\n success: false,\n }\n }\n}\n"],"names":["updateLockTranslationSettingsField","isLocked","apiURL","response","fetch","body","JSON","stringify","lockTranslationSettings","credentials","headers","method","ok","Error","statusText","success","error","console"],"mappings":"AAAA;;;CAGC,GACD,OAAO,eAAeA,mCACpBC,QAAiB,EACjBC,MAAc;IAEd,IAAI;QACF,MAAMC,WAAW,MAAMC,MAAM,GAAGF,OAAO,6BAA6B,CAAC,EAAE;YACrEG,MAAMC,KAAKC,SAAS,CAAC;gBACnBC,yBAAyBP;YAC3B;YACAQ,aAAa;YACbC,SAAS;gBACP,gBAAgB;YAClB;YACAC,QAAQ;QACV;QAEA,IAAI,CAACR,SAASS,EAAE,EAAE;YAChB,MAAM,IAAIC,MAAM,CAAC,kBAAkB,EAAEV,SAASW,UAAU,EAAE;QAC5D;QAEA,OAAO;YACLb;YACAc,SAAS;QACX;IACF,EAAE,OAAOC,OAAO;QACd,sCAAsC;QACtCC,QAAQD,KAAK,CAAC,gDAAgDA;QAC9D,OAAO;YACLf,UAAU,CAACA;YACXc,SAAS;QACX;IACF;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useForm, useFormFields } from '@payloadcms/ui';
|
|
3
|
+
import { useConfig, useForm, useFormFields } from '@payloadcms/ui';
|
|
4
4
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
5
5
|
import { updateLockTranslationSettingsField } from './actions/lockTranslations.js';
|
|
6
6
|
import './style.css';
|
|
@@ -13,19 +13,19 @@ import './style.css';
|
|
|
13
13
|
*/ export const LockTranslation = ()=>{
|
|
14
14
|
const lockField = useFormFields(([fields])=>fields?.lockTranslationSettings);
|
|
15
15
|
const { dispatchFields } = useForm();
|
|
16
|
+
const { config: { routes, serverURL } } = useConfig();
|
|
17
|
+
const apiURL = `${serverURL}${routes.api}`;
|
|
16
18
|
const isLocked = Boolean(lockField?.value ?? true);
|
|
17
19
|
const [isLoading, setIsLoading] = useState(false);
|
|
18
|
-
//
|
|
20
|
+
// Model select uses form lock state in OpenAiModelField (react-select).
|
|
19
21
|
const applyLockStateToFields = useCallback((locked)=>{
|
|
20
22
|
const fieldsToLock = [
|
|
21
23
|
'systemPrompt',
|
|
22
24
|
'translationRules',
|
|
23
|
-
'model',
|
|
24
25
|
'temperature',
|
|
25
26
|
'maxTokens'
|
|
26
27
|
];
|
|
27
28
|
fieldsToLock.forEach((fieldPath)=>{
|
|
28
|
-
// Find the input/textarea elements for this field
|
|
29
29
|
const inputs = document.querySelectorAll(`[name="${fieldPath}"], textarea[id*="${fieldPath}"], input[id*="${fieldPath}"]`);
|
|
30
30
|
inputs.forEach((input)=>{
|
|
31
31
|
if (locked) {
|
|
@@ -51,7 +51,7 @@ import './style.css';
|
|
|
51
51
|
const newLockState = !isLocked;
|
|
52
52
|
setIsLoading(true);
|
|
53
53
|
try {
|
|
54
|
-
const result = await updateLockTranslationSettingsField(newLockState);
|
|
54
|
+
const result = await updateLockTranslationSettingsField(newLockState, apiURL);
|
|
55
55
|
if (result.success) {
|
|
56
56
|
// Update the lockTranslationSettings field value
|
|
57
57
|
dispatchFields({
|
|
@@ -71,7 +71,8 @@ import './style.css';
|
|
|
71
71
|
}, [
|
|
72
72
|
isLocked,
|
|
73
73
|
dispatchFields,
|
|
74
|
-
applyLockStateToFields
|
|
74
|
+
applyLockStateToFields,
|
|
75
|
+
apiURL
|
|
75
76
|
]);
|
|
76
77
|
return /*#__PURE__*/ _jsx("div", {
|
|
77
78
|
className: "translation-settings-lock-container",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/LockTranslation/index.tsx"],"sourcesContent":["'use client'\n\nimport { useForm, useFormFields } from '@payloadcms/ui'\nimport React, { useCallback, useEffect, useState } from 'react'\n\nimport { updateLockTranslationSettingsField } from './actions/lockTranslations.js'\nimport './style.css'\n\n/**\n * Component that provides lock/unlock functionality for translation settings\n * - Fields are locked by default (read-only)\n * - User must click \"Unlock\" to edit\n * - After saving, fields automatically lock again\n * - Updates the hidden lockTranslationSettings checkbox field to enable server-side access control\n */\nexport const LockTranslation: React.FC = () => {\n const lockField = useFormFields(([fields]) => fields?.lockTranslationSettings)\n const { dispatchFields } = useForm()\n const isLocked = Boolean(lockField?.value ?? true)\n const [isLoading, setIsLoading] = useState(false)\n\n //
|
|
1
|
+
{"version":3,"sources":["../../../src/components/LockTranslation/index.tsx"],"sourcesContent":["'use client'\n\nimport { useConfig, useForm, useFormFields } from '@payloadcms/ui'\nimport React, { useCallback, useEffect, useState } from 'react'\n\nimport { updateLockTranslationSettingsField } from './actions/lockTranslations.js'\nimport './style.css'\n\n/**\n * Component that provides lock/unlock functionality for translation settings\n * - Fields are locked by default (read-only)\n * - User must click \"Unlock\" to edit\n * - After saving, fields automatically lock again\n * - Updates the hidden lockTranslationSettings checkbox field to enable server-side access control\n */\nexport const LockTranslation: React.FC = () => {\n const lockField = useFormFields(([fields]) => fields?.lockTranslationSettings)\n const { dispatchFields } = useForm()\n const {\n config: { routes, serverURL },\n } = useConfig()\n const apiURL = `${serverURL}${routes.api}`\n const isLocked = Boolean(lockField?.value ?? true)\n const [isLoading, setIsLoading] = useState(false)\n\n // Model select uses form lock state in OpenAiModelField (react-select).\n const applyLockStateToFields = useCallback((locked: boolean) => {\n const fieldsToLock = ['systemPrompt', 'translationRules', 'temperature', 'maxTokens']\n\n fieldsToLock.forEach((fieldPath) => {\n const inputs = document.querySelectorAll<HTMLInputElement | HTMLTextAreaElement>(\n `[name=\"${fieldPath}\"], textarea[id*=\"${fieldPath}\"], input[id*=\"${fieldPath}\"]`,\n )\n\n inputs.forEach((input) => {\n if (locked) {\n input.setAttribute('disabled', 'true')\n } else {\n input.removeAttribute('disabled')\n }\n })\n })\n }, [])\n\n // Apply initial lock state when component mounts or lock state changes\n useEffect(() => {\n // Small delay to ensure form fields are rendered\n const timer = setTimeout(() => {\n applyLockStateToFields(isLocked)\n }, 100)\n\n return () => clearTimeout(timer)\n }, [isLocked, applyLockStateToFields])\n\n const toggleLock = useCallback(async () => {\n const newLockState = !isLocked\n setIsLoading(true)\n\n try {\n const result = await updateLockTranslationSettingsField(newLockState, apiURL)\n\n if (result.success) {\n // Update the lockTranslationSettings field value\n dispatchFields({\n type: 'UPDATE',\n path: 'lockTranslationSettings',\n value: result.isLocked,\n })\n\n // Apply lock state to fields\n applyLockStateToFields(result.isLocked)\n }\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('[LockTranslation] Error:', error)\n } finally {\n setIsLoading(false)\n }\n }, [isLocked, dispatchFields, applyLockStateToFields, apiURL])\n\n return (\n <div className=\"translation-settings-lock-container\">\n <button\n className={`translation-settings-lock-button ${isLocked ? 'locked' : 'unlocked'}`}\n disabled={isLoading}\n onClick={toggleLock}\n title={\n isLocked\n ? 'π Settings are locked to prevent accidental changes. Click to unlock and edit.'\n : 'π Settings are unlocked. Click to lock after saving your changes.'\n }\n type=\"button\"\n >\n <span className=\"translation-settings-lock-icon\">\n {isLoading ? 'β³' : isLocked ? 'π' : 'π'}\n </span>\n <span className=\"translation-settings-lock-label\">\n {isLoading ? 'Updating...' : isLocked ? 'Unlock Settings' : 'Lock Settings'}\n </span>\n </button>\n </div>\n )\n}\n"],"names":["useConfig","useForm","useFormFields","React","useCallback","useEffect","useState","updateLockTranslationSettingsField","LockTranslation","lockField","fields","lockTranslationSettings","dispatchFields","config","routes","serverURL","apiURL","api","isLocked","Boolean","value","isLoading","setIsLoading","applyLockStateToFields","locked","fieldsToLock","forEach","fieldPath","inputs","document","querySelectorAll","input","setAttribute","removeAttribute","timer","setTimeout","clearTimeout","toggleLock","newLockState","result","success","type","path","error","console","div","className","button","disabled","onClick","title","span"],"mappings":"AAAA;;AAEA,SAASA,SAAS,EAAEC,OAAO,EAAEC,aAAa,QAAQ,iBAAgB;AAClE,OAAOC,SAASC,WAAW,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAE/D,SAASC,kCAAkC,QAAQ,gCAA+B;AAClF,OAAO,cAAa;AAEpB;;;;;;CAMC,GACD,OAAO,MAAMC,kBAA4B;IACvC,MAAMC,YAAYP,cAAc,CAAC,CAACQ,OAAO,GAAKA,QAAQC;IACtD,MAAM,EAAEC,cAAc,EAAE,GAAGX;IAC3B,MAAM,EACJY,QAAQ,EAAEC,MAAM,EAAEC,SAAS,EAAE,EAC9B,GAAGf;IACJ,MAAMgB,SAAS,GAAGD,YAAYD,OAAOG,GAAG,EAAE;IAC1C,MAAMC,WAAWC,QAAQV,WAAWW,SAAS;IAC7C,MAAM,CAACC,WAAWC,aAAa,GAAGhB,SAAS;IAE3C,wEAAwE;IACxE,MAAMiB,yBAAyBnB,YAAY,CAACoB;QAC1C,MAAMC,eAAe;YAAC;YAAgB;YAAoB;YAAe;SAAY;QAErFA,aAAaC,OAAO,CAAC,CAACC;YACpB,MAAMC,SAASC,SAASC,gBAAgB,CACtC,CAAC,OAAO,EAAEH,UAAU,kBAAkB,EAAEA,UAAU,eAAe,EAAEA,UAAU,EAAE,CAAC;YAGlFC,OAAOF,OAAO,CAAC,CAACK;gBACd,IAAIP,QAAQ;oBACVO,MAAMC,YAAY,CAAC,YAAY;gBACjC,OAAO;oBACLD,MAAME,eAAe,CAAC;gBACxB;YACF;QACF;IACF,GAAG,EAAE;IAEL,uEAAuE;IACvE5B,UAAU;QACR,iDAAiD;QACjD,MAAM6B,QAAQC,WAAW;YACvBZ,uBAAuBL;QACzB,GAAG;QAEH,OAAO,IAAMkB,aAAaF;IAC5B,GAAG;QAAChB;QAAUK;KAAuB;IAErC,MAAMc,aAAajC,YAAY;QAC7B,MAAMkC,eAAe,CAACpB;QACtBI,aAAa;QAEb,IAAI;YACF,MAAMiB,SAAS,MAAMhC,mCAAmC+B,cAActB;YAEtE,IAAIuB,OAAOC,OAAO,EAAE;gBAClB,iDAAiD;gBACjD5B,eAAe;oBACb6B,MAAM;oBACNC,MAAM;oBACNtB,OAAOmB,OAAOrB,QAAQ;gBACxB;gBAEA,6BAA6B;gBAC7BK,uBAAuBgB,OAAOrB,QAAQ;YACxC;QACF,EAAE,OAAOyB,OAAO;YACd,sCAAsC;YACtCC,QAAQD,KAAK,CAAC,4BAA4BA;QAC5C,SAAU;YACRrB,aAAa;QACf;IACF,GAAG;QAACJ;QAAUN;QAAgBW;QAAwBP;KAAO;IAE7D,qBACE,KAAC6B;QAAIC,WAAU;kBACb,cAAA,MAACC;YACCD,WAAW,CAAC,iCAAiC,EAAE5B,WAAW,WAAW,YAAY;YACjF8B,UAAU3B;YACV4B,SAASZ;YACTa,OACEhC,WACI,oFACA;YAENuB,MAAK;;8BAEL,KAACU;oBAAKL,WAAU;8BACbzB,YAAY,MAAMH,WAAW,OAAO;;8BAEvC,KAACiC;oBAAKL,WAAU;8BACbzB,YAAY,gBAAgBH,WAAW,oBAAoB;;;;;AAKtE,EAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { FieldDescription, FieldLabel, SelectInput, useConfig, useField, useFormFields } from '@payloadcms/ui';
|
|
4
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
5
|
+
export const OpenAiModelField = ({ field, path, readOnly })=>{
|
|
6
|
+
const { value, setValue, showError, errorMessage } = useField({
|
|
7
|
+
path
|
|
8
|
+
});
|
|
9
|
+
const { config: { routes, serverURL } } = useConfig();
|
|
10
|
+
const modelsURL = `${serverURL}${routes.api}/globals/translation-settings/openai-models`;
|
|
11
|
+
const lockField = useFormFields(([fields])=>fields?.lockTranslationSettings);
|
|
12
|
+
const isLocked = lockField === undefined ? Boolean(readOnly) : Boolean(lockField.value);
|
|
13
|
+
const [options, setOptions] = useState([]);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
const [loadError, setLoadError] = useState(null);
|
|
16
|
+
useEffect(()=>{
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const load = async ()=>{
|
|
19
|
+
setLoading(true);
|
|
20
|
+
setLoadError(null);
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(modelsURL, {
|
|
23
|
+
credentials: 'include',
|
|
24
|
+
signal: controller.signal
|
|
25
|
+
});
|
|
26
|
+
const data = await res.json();
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
throw new Error(data.error || `Failed to load models (${res.status})`);
|
|
29
|
+
}
|
|
30
|
+
setOptions(Array.isArray(data.models) ? data.models : []);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
if (err instanceof Error && err.name === 'AbortError') return;
|
|
33
|
+
setLoadError(err instanceof Error ? err.message : 'Failed to load models');
|
|
34
|
+
} finally{
|
|
35
|
+
if (!controller.signal.aborted) setLoading(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
void load();
|
|
39
|
+
return ()=>controller.abort();
|
|
40
|
+
}, [
|
|
41
|
+
modelsURL
|
|
42
|
+
]);
|
|
43
|
+
const optionsWithCurrent = useMemo(()=>{
|
|
44
|
+
if (!value || options.some((option)=>option.value === value)) return options;
|
|
45
|
+
return [
|
|
46
|
+
{
|
|
47
|
+
label: value,
|
|
48
|
+
value
|
|
49
|
+
},
|
|
50
|
+
...options
|
|
51
|
+
];
|
|
52
|
+
}, [
|
|
53
|
+
options,
|
|
54
|
+
value
|
|
55
|
+
]);
|
|
56
|
+
const description = typeof field.admin?.description === 'string' ? field.admin.description : undefined;
|
|
57
|
+
const disabled = isLocked || loading;
|
|
58
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
59
|
+
className: "field-type select",
|
|
60
|
+
children: [
|
|
61
|
+
/*#__PURE__*/ _jsx(FieldLabel, {
|
|
62
|
+
label: field.label,
|
|
63
|
+
path: path,
|
|
64
|
+
required: field.required
|
|
65
|
+
}),
|
|
66
|
+
/*#__PURE__*/ _jsx(SelectInput, {
|
|
67
|
+
description: description,
|
|
68
|
+
Error: showError && errorMessage ? /*#__PURE__*/ _jsx("div", {
|
|
69
|
+
className: "field-error",
|
|
70
|
+
children: errorMessage
|
|
71
|
+
}) : null,
|
|
72
|
+
isClearable: false,
|
|
73
|
+
name: path,
|
|
74
|
+
onChange: (option)=>{
|
|
75
|
+
if (disabled || !option || Array.isArray(option)) return;
|
|
76
|
+
if ('value' in option && typeof option.value === 'string') {
|
|
77
|
+
setValue(option.value);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
options: optionsWithCurrent,
|
|
81
|
+
path: path,
|
|
82
|
+
placeholder: loading ? 'Loading modelsβ¦' : 'Select a model',
|
|
83
|
+
readOnly: disabled,
|
|
84
|
+
required: field.required,
|
|
85
|
+
showError: showError,
|
|
86
|
+
value: value ?? ''
|
|
87
|
+
}),
|
|
88
|
+
loadError ? /*#__PURE__*/ _jsx(FieldDescription, {
|
|
89
|
+
description: `Could not load OpenAI models: ${loadError}`,
|
|
90
|
+
path: path
|
|
91
|
+
}) : null
|
|
92
|
+
]
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=OpenAiModelField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/OpenAiModelField.tsx"],"sourcesContent":["'use client'\n\nimport type { TextFieldClientComponent, OptionObject } from 'payload'\nimport {\n FieldDescription,\n FieldLabel,\n SelectInput,\n useConfig,\n useField,\n useFormFields,\n} from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\nexport const OpenAiModelField: TextFieldClientComponent = ({ field, path, readOnly }) => {\n const { value, setValue, showError, errorMessage } = useField<string>({ path })\n const {\n config: { routes, serverURL },\n } = useConfig()\n const modelsURL = `${serverURL}${routes.api}/globals/translation-settings/openai-models`\n const lockField = useFormFields(([fields]) => fields?.lockTranslationSettings)\n const isLocked = lockField === undefined ? Boolean(readOnly) : Boolean(lockField.value)\n const [options, setOptions] = useState<OptionObject[]>([])\n const [loading, setLoading] = useState(true)\n const [loadError, setLoadError] = useState<string | null>(null)\n\n useEffect(() => {\n const controller = new AbortController()\n\n const load = async () => {\n setLoading(true)\n setLoadError(null)\n try {\n const res = await fetch(modelsURL, {\n credentials: 'include',\n signal: controller.signal,\n })\n const data = (await res.json()) as { models?: OptionObject[]; error?: string }\n if (!res.ok) {\n throw new Error(data.error || `Failed to load models (${res.status})`)\n }\n setOptions(Array.isArray(data.models) ? data.models : [])\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') return\n setLoadError(err instanceof Error ? err.message : 'Failed to load models')\n } finally {\n if (!controller.signal.aborted) setLoading(false)\n }\n }\n\n void load()\n return () => controller.abort()\n }, [modelsURL])\n\n const optionsWithCurrent = useMemo(() => {\n if (!value || options.some((option) => option.value === value)) return options\n return [{ label: value, value }, ...options]\n }, [options, value])\n\n const description =\n typeof field.admin?.description === 'string' ? field.admin.description : undefined\n\n const disabled = isLocked || loading\n\n return (\n <div className=\"field-type select\">\n <FieldLabel label={field.label} path={path} required={field.required} />\n <SelectInput\n description={description}\n Error={showError && errorMessage ? <div className=\"field-error\">{errorMessage}</div> : null}\n isClearable={false}\n name={path}\n onChange={(option) => {\n if (disabled || !option || Array.isArray(option)) return\n if ('value' in option && typeof option.value === 'string') {\n setValue(option.value)\n }\n }}\n options={optionsWithCurrent}\n path={path}\n placeholder={loading ? 'Loading modelsβ¦' : 'Select a model'}\n readOnly={disabled}\n required={field.required}\n showError={showError}\n value={value ?? ''}\n />\n {loadError ? (\n <FieldDescription description={`Could not load OpenAI models: ${loadError}`} path={path} />\n ) : null}\n </div>\n )\n}\n"],"names":["FieldDescription","FieldLabel","SelectInput","useConfig","useField","useFormFields","React","useEffect","useMemo","useState","OpenAiModelField","field","path","readOnly","value","setValue","showError","errorMessage","config","routes","serverURL","modelsURL","api","lockField","fields","lockTranslationSettings","isLocked","undefined","Boolean","options","setOptions","loading","setLoading","loadError","setLoadError","controller","AbortController","load","res","fetch","credentials","signal","data","json","ok","Error","error","status","Array","isArray","models","err","name","message","aborted","abort","optionsWithCurrent","some","option","label","description","admin","disabled","div","className","required","isClearable","onChange","placeholder"],"mappings":"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"}
|