@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.
Files changed (34) hide show
  1. package/README.md +28 -10
  2. package/dist/collections/translationExclusions.js +101 -4
  3. package/dist/collections/translationExclusions.js.map +1 -1
  4. package/dist/components/LockTranslation/actions/lockTranslations.d.ts +1 -1
  5. package/dist/components/LockTranslation/actions/lockTranslations.js +2 -2
  6. package/dist/components/LockTranslation/actions/lockTranslations.js.map +1 -1
  7. package/dist/components/LockTranslation/index.js +7 -6
  8. package/dist/components/LockTranslation/index.js.map +1 -1
  9. package/dist/components/OpenAiModelField.d.ts +2 -0
  10. package/dist/components/OpenAiModelField.js +96 -0
  11. package/dist/components/OpenAiModelField.js.map +1 -0
  12. package/dist/components/TranslationControl.js +45 -51
  13. package/dist/components/TranslationControl.js.map +1 -1
  14. package/dist/endpoints/listOpenAiModels.d.ts +5 -0
  15. package/dist/endpoints/listOpenAiModels.js +93 -0
  16. package/dist/endpoints/listOpenAiModels.js.map +1 -0
  17. package/dist/exports/client.d.ts +1 -0
  18. package/dist/exports/client.js +1 -0
  19. package/dist/exports/client.js.map +1 -1
  20. package/dist/globals/translationSettings.js +24 -13
  21. package/dist/globals/translationSettings.js.map +1 -1
  22. package/dist/services/translationService.js +17 -17
  23. package/dist/services/translationService.js.map +1 -1
  24. package/dist/utilities/fieldHelpers.d.ts +0 -29
  25. package/dist/utilities/fieldHelpers.js +0 -152
  26. package/dist/utilities/fieldHelpers.js.map +1 -1
  27. package/dist/utilities/injectTranslationControls.d.ts +1 -1
  28. package/dist/utilities/injectTranslationControls.js +16 -11
  29. package/dist/utilities/injectTranslationControls.js.map +1 -1
  30. package/package.json +12 -12
  31. package/dist/components/TranslationSettingsLock.css +0 -87
  32. package/dist/components/TranslationSettingsLock.d.ts +0 -9
  33. package/dist/components/TranslationSettingsLock.js +0 -155
  34. 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**: Uses GPT-4o for high-quality translations (with custom provider support)
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
- model: 'gpt-4o', // OpenAI model to use
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
- └── translationExclusionsEndpoint.ts
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
- For more detailed information, check out these guides:
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
- - **[QUICKSTART.md](./docs/QUICKSTART.md)** - Quick setup and basic usage
396
- - **[USAGE_GUIDE.md](./docs/USAGE_GUIDE.md)** - Detailed usage examples and best practices
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: { user } })=>Boolean(user),
5
- delete: ({ req: { user } })=>Boolean(user),
6
- read: ({ req: { user } })=>Boolean(user),
7
- update: ({ req: { user } })=>Boolean(user)
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: { user } }) => Boolean(user),\n delete: ({ req: { user } }) => Boolean(user),\n read: ({ req: { user } }) => Boolean(user),\n update: ({ req: { user } }) => Boolean(user),\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","req","user","Boolean","delete","read","update","admin","defaultColumns","description","group","useAsTitle","fields","name","type","position","readOnly","index","required","label"],"mappings":"AAEA,OAAO,MAAMA,qCAAqC,CAChDC,OAAe,wBAAwB,GACjB,CAAA;QACtBA;QACAC,QAAQ;YACNC,QAAQ,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKC,QAAQD;YACvCE,QAAQ,CAAC,EAAEH,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKC,QAAQD;YACvCG,MAAM,CAAC,EAAEJ,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKC,QAAQD;YACrCI,QAAQ,CAAC,EAAEL,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKC,QAAQD;QACzC;QACAK,OAAO;YACLC,gBAAgB;gBAAC;gBAAkB;gBAAc;gBAAU;aAAgB;YAC3EC,aACE;YACFC,OAAO;YACPC,YAAY;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPE,OAAO;gBACPD,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aACE;gBACJ;gBACAG,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNP,OAAO;4BACLE,aAAa;wBACf;wBACAU,OAAO;wBACPD,UAAU;oBACZ;iBACD;gBACDC,OAAO,CAAC,+BAA+B,CAAC;gBACxCD,UAAU;YACZ;SACD;IACH,CAAA,EAAE"}
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('/api/globals/translation-settings', {
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('/api/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","response","fetch","body","JSON","stringify","lockTranslationSettings","credentials","headers","method","ok","Error","statusText","success","error","console"],"mappings":"AAAA;;;CAGC,GACD,OAAO,eAAeA,mCACpBC,QAAiB;IAEjB,IAAI;QACF,MAAMC,WAAW,MAAMC,MAAM,qCAAqC;YAChEC,MAAMC,KAAKC,SAAS,CAAC;gBACnBC,yBAAyBN;YAC3B;YACAO,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;YACLZ;YACAa,SAAS;QACX;IACF,EAAE,OAAOC,OAAO;QACd,sCAAsC;QACtCC,QAAQD,KAAK,CAAC,gDAAgDA;QAC9D,OAAO;YACLd,UAAU,CAACA;YACXa,SAAS;QACX;IACF;AACF"}
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
- // Apply lock state to fields by directly manipulating their disabled state
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 // Apply lock state to fields by directly manipulating their disabled state\n const applyLockStateToFields = useCallback((locked: boolean) => {\n const fieldsToLock = ['systemPrompt', 'translationRules', 'model', 'temperature', 'maxTokens']\n\n fieldsToLock.forEach((fieldPath) => {\n // Find the input/textarea elements for this field\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)\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])\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":["useForm","useFormFields","React","useCallback","useEffect","useState","updateLockTranslationSettingsField","LockTranslation","lockField","fields","lockTranslationSettings","dispatchFields","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,OAAO,EAAEC,aAAa,QAAQ,iBAAgB;AACvD,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,MAAMY,WAAWC,QAAQL,WAAWM,SAAS;IAC7C,MAAM,CAACC,WAAWC,aAAa,GAAGX,SAAS;IAE3C,2EAA2E;IAC3E,MAAMY,yBAAyBd,YAAY,CAACe;QAC1C,MAAMC,eAAe;YAAC;YAAgB;YAAoB;YAAS;YAAe;SAAY;QAE9FA,aAAaC,OAAO,CAAC,CAACC;YACpB,kDAAkD;YAClD,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;IACvEvB,UAAU;QACR,iDAAiD;QACjD,MAAMwB,QAAQC,WAAW;YACvBZ,uBAAuBL;QACzB,GAAG;QAEH,OAAO,IAAMkB,aAAaF;IAC5B,GAAG;QAAChB;QAAUK;KAAuB;IAErC,MAAMc,aAAa5B,YAAY;QAC7B,MAAM6B,eAAe,CAACpB;QACtBI,aAAa;QAEb,IAAI;YACF,MAAMiB,SAAS,MAAM3B,mCAAmC0B;YAExD,IAAIC,OAAOC,OAAO,EAAE;gBAClB,iDAAiD;gBACjDvB,eAAe;oBACbwB,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;QAAUD;QAAgBM;KAAuB;IAErD,qBACE,KAACsB;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"}
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,2 @@
1
+ import type { TextFieldClientComponent } from 'payload';
2
+ export declare const OpenAiModelField: TextFieldClientComponent;
@@ -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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"}