@ai-stack/payloadcms 3.68.0-beta.2 → 3.68.0-beta.3
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/dist/ai/core/media/image/generateImage.js +2 -6
- package/dist/ai/core/media/image/generateImage.js.map +1 -1
- package/dist/ai/providers/blocks/elevenlabs.js +1 -1
- package/dist/ai/providers/blocks/elevenlabs.js.map +1 -1
- package/dist/ai/providers/blocks/google.js +9 -5
- package/dist/ai/providers/blocks/google.js.map +1 -1
- package/dist/ai/providers/blocks/openai.js +1 -1
- package/dist/ai/providers/blocks/openai.js.map +1 -1
- package/dist/collections/AISettings.js +44 -17
- package/dist/collections/AISettings.js.map +1 -1
- package/dist/defaults.d.ts +1 -0
- package/dist/defaults.js +8 -0
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/fetchFields.js +10 -0
- package/dist/endpoints/fetchFields.js.map +1 -1
- package/dist/plugin.js +16 -32
- package/dist/plugin.js.map +1 -1
- package/dist/providers/InstructionsProvider/InstructionsProvider.js +44 -15
- package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
- package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +36 -16
- package/dist/providers/InstructionsProvider/context.d.ts +3 -0
- package/dist/providers/InstructionsProvider/context.js +2 -0
- package/dist/providers/InstructionsProvider/context.js.map +1 -1
- package/dist/providers/InstructionsProvider/useInstructions.js +3 -1
- package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
- package/dist/types.d.ts +0 -3
- package/dist/types.js.map +1 -1
- package/dist/ui/AIConfigDashboard/index.js +198 -22
- package/dist/ui/AIConfigDashboard/index.js.map +1 -1
- package/dist/ui/AIConfigDashboard/index.jsx +159 -13
- package/dist/ui/Compose/Compose.js +5 -3
- package/dist/ui/Compose/Compose.js.map +1 -1
- package/dist/ui/Compose/Compose.jsx +3 -3
- package/dist/ui/Compose/hooks/useGenerate.js +34 -131
- package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
- package/dist/ui/Compose/hooks/useGenerateUpload.d.ts +11 -0
- package/dist/ui/Compose/hooks/useGenerateUpload.js +123 -0
- package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -0
- package/dist/ui/Compose/hooks/useStreamingUpdate.d.ts +8 -0
- package/dist/ui/Compose/hooks/useStreamingUpdate.js +48 -0
- package/dist/ui/Compose/hooks/useStreamingUpdate.js.map +1 -0
- package/dist/ui/EncryptedTextField/index.js +4 -4
- package/dist/ui/EncryptedTextField/index.js.map +1 -1
- package/dist/ui/EncryptedTextField/index.jsx +4 -4
- package/dist/utilities/seedProperties.d.ts +7 -0
- package/dist/utilities/seedProperties.js +117 -0
- package/dist/utilities/seedProperties.js.map +1 -0
- package/dist/utilities/setSafeLexicalState.js +80 -6
- package/dist/utilities/setSafeLexicalState.js.map +1 -1
- package/dist/utilities/updateFieldsConfig.d.ts +1 -1
- package/dist/utilities/updateFieldsConfig.js +1 -0
- package/dist/utilities/updateFieldsConfig.js.map +1 -1
- package/package.json +2 -1
- package/dist/init.d.ts +0 -7
- package/dist/init.js +0 -152
- package/dist/init.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { InstructionsContext } from '@ai-stack/payloadcms/client';
|
|
2
1
|
import { useDocumentInfo } from '@payloadcms/ui';
|
|
3
2
|
import { useContext, useEffect, useMemo, useState } from 'react';
|
|
4
3
|
import { PLUGIN_INSTRUCTIONS_TABLE } from '../../defaults.js';
|
|
5
4
|
import { handlebarsHelpers, handlebarsHelpersMap } from '../../libraries/handlebars/helpersMap.js';
|
|
5
|
+
import { InstructionsContext } from './context.js';
|
|
6
6
|
/**
|
|
7
7
|
* Normalize a schema path by removing array indices.
|
|
8
8
|
* This allows fields inside arrays to match their instruction records.
|
|
@@ -112,9 +112,11 @@ export const useInstructions = (update = {})=>{
|
|
|
112
112
|
if (debugging && !pathInstructions && schemaPath && hasInstructions) {
|
|
113
113
|
warnOnceOnMissingInstructions(schemaPath);
|
|
114
114
|
}
|
|
115
|
+
const isCollectionEnabled = context.enabledCollections?.includes(collectionSlug || activeCollection || '') ?? false;
|
|
115
116
|
return {
|
|
116
117
|
...context,
|
|
117
118
|
...pathInstructions || {},
|
|
119
|
+
disabled: !isCollectionEnabled || (pathInstructions?.disabled ?? false),
|
|
118
120
|
promptEditorSuggestions
|
|
119
121
|
};
|
|
120
122
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/providers/InstructionsProvider/useInstructions.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"sources":["../../../src/providers/InstructionsProvider/useInstructions.ts"],"sourcesContent":["import { useDocumentInfo } from '@payloadcms/ui'\nimport { useContext, useEffect, useMemo, useState } from 'react'\n\nimport { PLUGIN_INSTRUCTIONS_TABLE } from '../../defaults.js'\nimport { handlebarsHelpers, handlebarsHelpersMap } from '../../libraries/handlebars/helpersMap.js'\nimport { InstructionsContext } from './context.js'\n\n/**\n * Normalize a schema path by removing array indices.\n * This allows fields inside arrays to match their instruction records.\n * \n * Example:\n * 'array-test-cases.keywords.0.keyword' -> 'array-test-cases.keywords.keyword'\n * 'characters.views.2.description' -> 'characters.views.description'\n * 'posts.title' -> 'posts.title' (no change)\n */\nconst normalizeSchemaPath = (path: string): string => {\n if (!path) {\n return path\n }\n // Remove numeric path segments (array indices)\n return path.split('.').filter(segment => !/^\\d+$/.test(segment)).join('.')\n}\n\nconst warnedOnceOnNoInstructionId = new Set<string>()\nconst warnOnceOnMissingInstructions = (path: string) => {\n if (!warnedOnceOnNoInstructionId.has(path)) {\n warnedOnceOnNoInstructionId.add(path)\n // eslint-disable-next-line no-console\n console.info(`[AI Plugin] There are no AI instructions for this field: ${path}. Enable \"generatePromptOnInit\" option to enable them.`)\n }\n}\n\nexport const useInstructions = (\n update: {\n schemaPath?: unknown\n } = {},\n) => {\n const context = useContext(InstructionsContext)\n const { collectionSlug } = useDocumentInfo()\n const { activeCollection, debugging, hasInstructions, instructions, promptFields, setActiveCollection } = context\n\n const [schemaPath, setSchemaPath] = useState(update.schemaPath as string)\n\n useEffect(() => {\n if (update.schemaPath !== schemaPath) {\n setSchemaPath((update.schemaPath as string) ?? '')\n }\n }, [update.schemaPath, schemaPath])\n\n useEffect(() => {\n if (\n activeCollection !== collectionSlug &&\n collectionSlug !== PLUGIN_INSTRUCTIONS_TABLE &&\n typeof setActiveCollection === 'function'\n ) {\n setActiveCollection(collectionSlug ?? '')\n }\n }, [activeCollection, collectionSlug, setActiveCollection])\n\n const groupedFields = useMemo(() => {\n const result: Record<string, string[]> = {}\n\n for (const fullKey of Object.keys(instructions || {})) {\n const [collection, ...pathParts] = fullKey.split('.')\n const path = pathParts.join('.')\n if (!result[collection]) {\n result[collection] = []\n }\n result[collection].push(path)\n }\n\n return result\n }, [instructions])\n\n // Suggestions for prompt editor\n const promptEditorSuggestions = useMemo(() => {\n const activeFields = groupedFields[activeCollection as string] || []\n const suggestions: string[] = []\n\n // Build instruction lookup map once for O(1) access instead of O(n) search per field\n const instructionLookup = new Map(\n Object.entries(instructions).map(([key, value]) => {\n const path = key.split('.').slice(1).join('.')\n return [path, value]\n })\n )\n\n activeFields.forEach((f) => {\n const fieldInfo = instructionLookup.get(f)\n\n if (!fieldInfo) {return}\n\n if (fieldInfo.fieldType === 'upload') {\n return\n }\n\n const helpers = handlebarsHelpers.filter(\n (h) => (handlebarsHelpersMap as Record<string, { field?: string }>)[h]?.field === fieldInfo.fieldType,\n )\n\n if (helpers.length) {\n for (const helper of helpers) {\n suggestions.push(`${helper} ${f}`)\n }\n } else {\n suggestions.push(f)\n }\n })\n\n promptFields.forEach(({ name, collections }) => {\n if (!activeCollection) {return}\n\n if (!collections || collections.includes(activeCollection)) {\n suggestions.push(name)\n }\n })\n\n return suggestions\n }, [groupedFields, activeCollection, instructions, promptFields])\n\n // Normalize the schema path to handle array indices\n const normalizedSchemaPath = normalizeSchemaPath(schemaPath)\n const pathInstructions = instructions[normalizedSchemaPath]\n\n if (debugging && !pathInstructions && schemaPath && hasInstructions) {\n warnOnceOnMissingInstructions(schemaPath)\n }\n\n const isCollectionEnabled = context.enabledCollections?.includes(collectionSlug || activeCollection || '') ?? false\n\n return {\n ...context,\n ...(pathInstructions || {}),\n disabled: !isCollectionEnabled || (pathInstructions?.disabled ?? false),\n promptEditorSuggestions,\n }\n}\n\n"],"names":["useDocumentInfo","useContext","useEffect","useMemo","useState","PLUGIN_INSTRUCTIONS_TABLE","handlebarsHelpers","handlebarsHelpersMap","InstructionsContext","normalizeSchemaPath","path","split","filter","segment","test","join","warnedOnceOnNoInstructionId","Set","warnOnceOnMissingInstructions","has","add","console","info","useInstructions","update","context","collectionSlug","activeCollection","debugging","hasInstructions","instructions","promptFields","setActiveCollection","schemaPath","setSchemaPath","groupedFields","result","fullKey","Object","keys","collection","pathParts","push","promptEditorSuggestions","activeFields","suggestions","instructionLookup","Map","entries","map","key","value","slice","forEach","f","fieldInfo","get","fieldType","helpers","h","field","length","helper","name","collections","includes","normalizedSchemaPath","pathInstructions","isCollectionEnabled","enabledCollections","disabled"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -39,9 +39,6 @@ export interface PluginConfig {
|
|
|
39
39
|
* By default, all AI features require authentication
|
|
40
40
|
*/
|
|
41
41
|
access?: PluginConfigAccess;
|
|
42
|
-
collections: {
|
|
43
|
-
[key: CollectionSlug]: boolean;
|
|
44
|
-
};
|
|
45
42
|
debugging?: boolean;
|
|
46
43
|
disableSponsorMessage?: boolean;
|
|
47
44
|
editorConfig?: {
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { JSONSchema } from 'openai/lib/jsonschema'\nimport type { ImageGenerateParams } from 'openai/resources/images'\nimport type {\n CollectionConfig,\n CollectionSlug,\n DataFromCollectionSlug,\n Endpoint,\n Field,\n File,\n GlobalConfig,\n GroupField,\n PayloadRequest,\n TypedCollection,\n} from 'payload'\nimport type { CSSProperties, MouseEventHandler } from 'react'\n\nimport type { MediaResult } from './ai/core/index.js'\nimport type {PLUGIN_INSTRUCTIONS_TABLE} from \"./defaults.js\";\n\nexport interface PluginConfigAccess {\n /**\n * Control access to AI generation features (generate text, images, audio)\n * @default () => !!req.user (requires authentication)\n */\n generate?: ({ req }: { req: PayloadRequest }) => boolean | Promise<boolean>\n /**\n * Control access to AI settings/configuration\n * @default () => !!req.user (requires authentication)\n */\n settings?: ({ req }: { req: PayloadRequest }) => boolean | Promise<boolean>\n}\n\nexport interface PluginOptions {\n /**\n * Provide local tags to filter language options from the Translate Menu\n * Check for the available local tags,\n * visit: https://www.npmjs.com/package/locale-codes\n * Example: [\"en-US\", \"zh-SG\", \"zh-CN\", \"en\"]\n */\n enabledLanguages?: string[]\n}\n\nexport type PluginConfigMediaUploadFunction = (\n result: MediaResult,\n {\n collection,\n request,\n }: {\n collection: CollectionSlug\n request: PayloadRequest\n },\n) => Promise<DataFromCollectionSlug<CollectionSlug>>\n\nexport interface PluginConfig {\n /**\n * Access control configuration for AI features\n * By default, all AI features require authentication\n */\n access?: PluginConfigAccess\n
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { JSONSchema } from 'openai/lib/jsonschema'\nimport type { ImageGenerateParams } from 'openai/resources/images'\nimport type {\n CollectionConfig,\n CollectionSlug,\n DataFromCollectionSlug,\n Endpoint,\n Field,\n File,\n GlobalConfig,\n GroupField,\n PayloadRequest,\n TypedCollection,\n} from 'payload'\nimport type { CSSProperties, MouseEventHandler } from 'react'\n\nimport type { MediaResult } from './ai/core/index.js'\nimport type {PLUGIN_INSTRUCTIONS_TABLE} from \"./defaults.js\";\n\nexport interface PluginConfigAccess {\n /**\n * Control access to AI generation features (generate text, images, audio)\n * @default () => !!req.user (requires authentication)\n */\n generate?: ({ req }: { req: PayloadRequest }) => boolean | Promise<boolean>\n /**\n * Control access to AI settings/configuration\n * @default () => !!req.user (requires authentication)\n */\n settings?: ({ req }: { req: PayloadRequest }) => boolean | Promise<boolean>\n}\n\nexport interface PluginOptions {\n /**\n * Provide local tags to filter language options from the Translate Menu\n * Check for the available local tags,\n * visit: https://www.npmjs.com/package/locale-codes\n * Example: [\"en-US\", \"zh-SG\", \"zh-CN\", \"en\"]\n */\n enabledLanguages?: string[]\n}\n\nexport type PluginConfigMediaUploadFunction = (\n result: MediaResult,\n {\n collection,\n request,\n }: {\n collection: CollectionSlug\n request: PayloadRequest\n },\n) => Promise<DataFromCollectionSlug<CollectionSlug>>\n\nexport interface PluginConfig {\n /**\n * Access control configuration for AI features\n * By default, all AI features require authentication\n */\n access?: PluginConfigAccess\n debugging?: boolean\n disableSponsorMessage?: boolean\n editorConfig?: { nodes: JSONSchema[] }\n fields?: Field[]\n generatePromptOnInit?: boolean\n generationModels?: ((defaultModels: GenerationModel[]) => GenerationModel[]) | GenerationModel[]\n globals?: {\n [key: GlobalConfig['slug']]: boolean\n }\n interfaceName?: string\n mediaUpload?: PluginConfigMediaUploadFunction\n options?: PluginOptions\n // Override the instructions collection config\n overrideInstructions?: Partial<CollectionConfig>\n promptFields?: PromptField[]\n /**\n * Custom action prompts for AI text generation\n * If not provided, uses default prompts\n * You can access default prompts by importing { defaultPrompts } from '@ai-stack/payloadcms'\n */\n prompts?: ActionPrompt[]\n /**\n * Custom seed prompt function for generating field-specific prompts\n * If not provided, uses default seed prompt function\n * You can access default seed prompts by importing { defaultSeedPrompts } from '@ai-stack/payloadcms'\n */\n seedPrompts?: SeedPromptFunction\n uploadCollectionSlug?: CollectionSlug\n}\n\nexport interface GenerationModel {\n fields: string[]\n generateText?: (prompt: string, system: string) => Promise<string>\n handler?: (prompt: string, options: any) => File | Promise<any> | Response\n id: string\n name: string\n output: 'audio' | 'file' | 'image' | 'json' | 'text' | 'video'\n settings?: GroupField\n supportsPromptOptimization?: boolean\n}\n\nexport interface GenerationConfig {\n models: GenerationModel[]\n provider: string\n}\n\nexport type GenerateTextarea<T = any> = (args: {\n collectionSlug: CollectionSlug\n doc: T\n documentId?: number | string\n locale?: string\n options?: any\n}) => Promise<string> | string\n\nexport interface Endpoints {\n fetchVoices?: Omit<Endpoint, 'root'>\n textarea: Omit<Endpoint, 'root'>\n upload: Omit<Endpoint, 'root'>\n videogenWebhook?: Omit<Endpoint, 'root'>\n}\n\nexport type ActionMenuItems =\n | 'Compose'\n | 'Expand'\n | 'Proofread'\n | 'Rephrase'\n | 'Settings'\n | 'Simplify'\n | 'Summarize'\n | 'Tone'\n | 'Translate'\n\nexport type ActionPromptOptions = {\n layout?: string\n locale?: string\n prompt?: string\n systemPrompt?: string\n}\n\nexport type ActionPrompt = {\n layout?: (options?: ActionPromptOptions) => string\n name: ActionMenuItems\n system: (options: ActionPromptOptions) => string\n}\n\nexport type SeedPromptOptions = {\n fieldLabel: string\n fieldSchemaPaths: Record<string, any>\n fieldType: string\n path: string\n}\n\nexport type SeedPromptData = Omit<\n TypedCollection[typeof PLUGIN_INSTRUCTIONS_TABLE],\n 'createdAt' | 'id' | 'updatedAt'\n>\n\nexport type SeedPromptResult =\n | {\n data?: SeedPromptData\n }\n | {\n data?: SeedPromptData\n prompt: string\n system: string\n }\n | false\n | undefined\n | void\n\nexport type SeedPromptFunction = (\n options: SeedPromptOptions,\n) => Promise<SeedPromptResult> | SeedPromptResult\n\nexport type ActionMenuEvents =\n | 'onCompose'\n | 'onExpand'\n | 'onProofread'\n | 'onRephrase'\n | 'onSettings'\n | 'onSimplify'\n | 'onSummarize'\n | 'onTone'\n | 'onTranslate'\n\nexport type UseMenuEvents = {\n [key in ActionMenuEvents]?: (data?: unknown) => void\n}\n\nexport type UseMenuOptions = {\n isConfigAllowed: boolean\n}\n\nexport type BaseItemProps<T = any> = {\n children?: React.ReactNode\n disabled?: boolean\n hideIcon?: boolean\n isActive?: boolean\n isMenu?: boolean\n onClick: (data?: unknown) => void\n onMouseEnter?: MouseEventHandler<T> | undefined\n onMouseLeave?: MouseEventHandler<T> | undefined\n style?: CSSProperties | undefined\n title?: string\n}\n\nexport type ImageReference = {\n data: Blob\n name: string\n size: number\n type: string\n url: string\n}\n\nexport type GenerateImageParams = {\n images?: ImageReference[]\n size?: ImageGenerateParams['size']\n style?: ImageGenerateParams['style']\n version?: ImageGenerateParams['model']\n}\n\nexport type SerializedPromptField = {\n collections?: CollectionSlug[]\n name: string\n}\n\nexport type PromptFieldGetterContext = {\n collection: CollectionSlug\n type: string\n}\n\nexport type PromptField = {\n // If not provided, the value will be returned from the data object as-is\n getter?: (data: object, ctx: PromptFieldGetterContext) => Promise<string> | string\n} & SerializedPromptField\n"],"names":[],"mappings":"AAsOA,WAGyB"}
|
|
@@ -1,43 +1,219 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import
|
|
3
|
+
import { Button, toast, useConfig } from '@payloadcms/ui';
|
|
4
|
+
// @ts-expect-error - Next.js types are not resolving correctly with nodenext but runtime is fine
|
|
5
|
+
import { useRouter } from 'next/navigation';
|
|
6
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
7
|
+
import { excludeCollections } from '../../defaults.js';
|
|
8
|
+
import { InstructionsContext } from '../../providers/InstructionsProvider/context.js';
|
|
4
9
|
export const AIConfigDashboard = ()=>{
|
|
10
|
+
const { config: { collections, routes: { admin: adminRoute, api: apiRoute } } } = useConfig();
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const { refresh, setEnabledCollections: setEnabledCollectionsInContext } = useContext(InstructionsContext);
|
|
13
|
+
const [enabledCollections, setEnabledCollections] = useState([]);
|
|
14
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
15
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
16
|
+
const availableCollections = collections.filter((c)=>!excludeCollections.includes(c.slug) && !c.admin?.hidden);
|
|
17
|
+
useEffect(()=>{
|
|
18
|
+
const fetchSettings = async ()=>{
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(`${apiRoute}/globals/ai-settings`);
|
|
21
|
+
if (response.ok) {
|
|
22
|
+
const data = await response.json();
|
|
23
|
+
// Handle both simple array and object wrapper if Payload wraps it
|
|
24
|
+
const storedEnabled = data.enabledCollections || [];
|
|
25
|
+
setEnabledCollections(Array.isArray(storedEnabled) ? storedEnabled : []);
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Failed to fetch AI settings:', error);
|
|
29
|
+
} finally{
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
fetchSettings().catch((e)=>{
|
|
34
|
+
console.log(e);
|
|
35
|
+
});
|
|
36
|
+
}, [
|
|
37
|
+
apiRoute
|
|
38
|
+
]);
|
|
39
|
+
const handleToggle = (slug)=>{
|
|
40
|
+
setEnabledCollections((prev)=>{
|
|
41
|
+
if (prev.includes(slug)) {
|
|
42
|
+
return prev.filter((s)=>s !== slug);
|
|
43
|
+
}
|
|
44
|
+
return [
|
|
45
|
+
...prev,
|
|
46
|
+
slug
|
|
47
|
+
];
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const handleSave = async ()=>{
|
|
51
|
+
setIsSaving(true);
|
|
52
|
+
try {
|
|
53
|
+
// First fetch current settings to get ID or just rely on global update behavior
|
|
54
|
+
// We need to adhere to Payload's global update API
|
|
55
|
+
const response = await fetch(`${apiRoute}/globals/ai-settings`, {
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
enabledCollections
|
|
58
|
+
}),
|
|
59
|
+
headers: {
|
|
60
|
+
'Content-Type': 'application/json'
|
|
61
|
+
},
|
|
62
|
+
method: 'POST'
|
|
63
|
+
});
|
|
64
|
+
if (response.ok) {
|
|
65
|
+
toast.success('Settings saved successfully');
|
|
66
|
+
if (setEnabledCollectionsInContext) {
|
|
67
|
+
setEnabledCollectionsInContext(enabledCollections);
|
|
68
|
+
}
|
|
69
|
+
if (refresh) {
|
|
70
|
+
await refresh();
|
|
71
|
+
}
|
|
72
|
+
router.refresh();
|
|
73
|
+
} else {
|
|
74
|
+
toast.error('Failed to save settings');
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Error saving settings:', error);
|
|
78
|
+
toast.error('Error saving settings');
|
|
79
|
+
} finally{
|
|
80
|
+
setIsSaving(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
if (isLoading) {
|
|
84
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
85
|
+
style: {
|
|
86
|
+
padding: '20px',
|
|
87
|
+
textAlign: 'center'
|
|
88
|
+
},
|
|
89
|
+
children: "Loading AI configuration..."
|
|
90
|
+
});
|
|
91
|
+
}
|
|
5
92
|
return /*#__PURE__*/ _jsxs("div", {
|
|
6
93
|
style: {
|
|
7
|
-
alignItems: 'center',
|
|
8
94
|
background: 'var(--theme-elevation-50)',
|
|
9
|
-
border: '1px solid var(--theme-elevation-150)',
|
|
10
|
-
borderRadius: '8px',
|
|
11
|
-
display: 'flex',
|
|
12
|
-
justifyContent: 'space-between',
|
|
95
|
+
// border: '1px solid var(--theme-elevation-150)',
|
|
96
|
+
// borderRadius: '8px',
|
|
13
97
|
marginBottom: '20px',
|
|
14
|
-
|
|
98
|
+
overflow: 'hidden'
|
|
15
99
|
},
|
|
16
100
|
children: [
|
|
17
101
|
/*#__PURE__*/ _jsxs("div", {
|
|
102
|
+
style: {
|
|
103
|
+
alignItems: 'center',
|
|
104
|
+
borderBottom: '1px solid var(--theme-elevation-150)',
|
|
105
|
+
display: 'flex',
|
|
106
|
+
justifyContent: 'space-between',
|
|
107
|
+
padding: '20px'
|
|
108
|
+
},
|
|
18
109
|
children: [
|
|
19
|
-
/*#__PURE__*/
|
|
110
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
111
|
+
children: [
|
|
112
|
+
/*#__PURE__*/ _jsx("h4", {
|
|
113
|
+
style: {
|
|
114
|
+
margin: '0 0 5px 0'
|
|
115
|
+
},
|
|
116
|
+
children: "AI Configuration"
|
|
117
|
+
}),
|
|
118
|
+
/*#__PURE__*/ _jsx("p", {
|
|
119
|
+
style: {
|
|
120
|
+
color: 'var(--theme-elevation-500)',
|
|
121
|
+
fontSize: '14px',
|
|
122
|
+
margin: '0'
|
|
123
|
+
},
|
|
124
|
+
children: "Manage your AI providers, API keys, and enable AI for specific collections."
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
}),
|
|
128
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
20
129
|
style: {
|
|
21
|
-
|
|
130
|
+
display: 'flex',
|
|
131
|
+
gap: '10px'
|
|
22
132
|
},
|
|
23
|
-
children:
|
|
133
|
+
children: [
|
|
134
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
135
|
+
buttonStyle: "secondary",
|
|
136
|
+
el: "link",
|
|
137
|
+
to: `${adminRoute}/globals/ai-settings`,
|
|
138
|
+
children: "Settings"
|
|
139
|
+
}),
|
|
140
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
141
|
+
disabled: isSaving,
|
|
142
|
+
onClick: handleSave,
|
|
143
|
+
children: isSaving ? 'Saving...' : 'Save Changes'
|
|
144
|
+
})
|
|
145
|
+
]
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
}),
|
|
149
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
150
|
+
style: {
|
|
151
|
+
padding: '20px'
|
|
152
|
+
},
|
|
153
|
+
children: [
|
|
154
|
+
/*#__PURE__*/ _jsx("h5", {
|
|
155
|
+
style: {
|
|
156
|
+
marginBottom: '15px'
|
|
157
|
+
},
|
|
158
|
+
children: "Enabled Collections"
|
|
24
159
|
}),
|
|
25
|
-
/*#__PURE__*/ _jsx("
|
|
160
|
+
/*#__PURE__*/ _jsx("div", {
|
|
26
161
|
style: {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
162
|
+
display: 'grid',
|
|
163
|
+
gap: '15px',
|
|
164
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))'
|
|
30
165
|
},
|
|
31
|
-
children:
|
|
166
|
+
children: availableCollections.map((collection)=>{
|
|
167
|
+
const isEnabled = enabledCollections.includes(collection.slug);
|
|
168
|
+
return /*#__PURE__*/ _jsxs("button", {
|
|
169
|
+
onClick: ()=>handleToggle(collection.slug),
|
|
170
|
+
style: {
|
|
171
|
+
alignItems: 'center',
|
|
172
|
+
background: isEnabled ? 'var(--theme-elevation-100)' : 'var(--theme-elevation-50)',
|
|
173
|
+
border: `1px solid ${isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)'}`,
|
|
174
|
+
borderRadius: '6px',
|
|
175
|
+
cursor: 'pointer',
|
|
176
|
+
display: 'flex',
|
|
177
|
+
gap: '10px',
|
|
178
|
+
padding: '10px 15px',
|
|
179
|
+
textAlign: 'left',
|
|
180
|
+
transition: 'all 0.2s ease',
|
|
181
|
+
width: '100%'
|
|
182
|
+
},
|
|
183
|
+
type: "button",
|
|
184
|
+
children: [
|
|
185
|
+
/*#__PURE__*/ _jsx("div", {
|
|
186
|
+
style: {
|
|
187
|
+
alignItems: 'center',
|
|
188
|
+
background: isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)',
|
|
189
|
+
borderRadius: '12px',
|
|
190
|
+
display: 'flex',
|
|
191
|
+
height: '24px',
|
|
192
|
+
justifyContent: isEnabled ? 'flex-end' : 'flex-start',
|
|
193
|
+
padding: '2px',
|
|
194
|
+
transition: 'all 0.2s ease',
|
|
195
|
+
width: '44px'
|
|
196
|
+
},
|
|
197
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
198
|
+
style: {
|
|
199
|
+
background: 'white',
|
|
200
|
+
borderRadius: '50%',
|
|
201
|
+
height: '20px',
|
|
202
|
+
width: '20px'
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
}),
|
|
206
|
+
/*#__PURE__*/ _jsx("span", {
|
|
207
|
+
style: {
|
|
208
|
+
fontWeight: 500
|
|
209
|
+
},
|
|
210
|
+
children: typeof collection.labels?.singular === 'string' ? collection.labels.singular : collection.labels?.singular?.en || collection.slug
|
|
211
|
+
})
|
|
212
|
+
]
|
|
213
|
+
}, collection.slug);
|
|
214
|
+
})
|
|
32
215
|
})
|
|
33
216
|
]
|
|
34
|
-
}),
|
|
35
|
-
/*#__PURE__*/ _jsx("a", {
|
|
36
|
-
href: "/admin/globals/ai-settings",
|
|
37
|
-
children: /*#__PURE__*/ _jsx("button", {
|
|
38
|
-
className: "btn btn--style-primary btn--size-small",
|
|
39
|
-
children: "Manage AI Settings"
|
|
40
|
-
})
|
|
41
217
|
})
|
|
42
218
|
]
|
|
43
219
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/AIConfigDashboard/index.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\n\nexport const AIConfigDashboard: React.FC = () => {\n return (\n <div\n style={{\n alignItems: 'center',\n background: 'var(--theme-elevation-50)',\n border: '1px solid var(--theme-elevation-150)',\n borderRadius: '8px',\n display: 'flex',\n justifyContent: 'space-between',\n marginBottom: '20px',\n padding: '20px',\n }}\n >\n <div>\n <h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>\n <p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>\n Manage your AI providers, API keys, and default models.\n </p>\n </div>\n <a href=\"/admin/globals/ai-settings\">\n <button className=\"btn btn--style-primary btn--size-small\">Manage AI Settings</button>\n </a>\n </div>\n )\n}\n"],"names":["React","AIConfigDashboard","div","style","alignItems","background","border","borderRadius","display","justifyContent","marginBottom","padding","h4","margin","p","color","fontSize","a","href","button","className"],"mappings":"AAAA;;AAEA,OAAOA,WAAW,QAAO;AAEzB,OAAO,MAAMC,oBAA8B;IACzC,qBACE,MAACC;QACCC,OAAO;YACLC,YAAY;YACZC,YAAY;YACZC,QAAQ;YACRC,cAAc;YACdC,SAAS;YACTC,gBAAgB;YAChBC,cAAc;YACdC,SAAS;QACX;;0BAEA,MAACT;;kCACC,KAACU;wBAAGT,OAAO;4BAAEU,QAAQ;wBAAY;kCAAG;;kCACpC,KAACC;wBAAEX,OAAO;4BAAEY,OAAO;4BAA8BC,UAAU;4BAAQH,QAAQ;wBAAI;kCAAG;;;;0BAIpF,KAACI;gBAAEC,MAAK;0BACN,cAAA,KAACC;oBAAOC,WAAU;8BAAyC;;;;;AAInE,EAAC"}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/AIConfigDashboard/index.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useConfig } from '@payloadcms/ui'\n// @ts-expect-error - Next.js types are not resolving correctly with nodenext but runtime is fine\nimport { useRouter } from 'next/navigation'\nimport React, { useContext, useEffect, useState } from 'react'\n\nimport { excludeCollections } from '../../defaults.js'\nimport { InstructionsContext } from '../../providers/InstructionsProvider/context.js'\n\nexport const AIConfigDashboard: React.FC = () => {\n const {\n config: {\n collections,\n routes: { admin: adminRoute, api: apiRoute },\n },\n } = useConfig()\n const router = useRouter()\n const { refresh, setEnabledCollections: setEnabledCollectionsInContext } =\n useContext(InstructionsContext)\n\n const [enabledCollections, setEnabledCollections] = useState<string[]>([])\n const [isLoading, setIsLoading] = useState(true)\n const [isSaving, setIsSaving] = useState(false)\n\n const availableCollections = collections.filter(\n (c) =>\n !excludeCollections.includes(c.slug) &&\n !(c.admin as unknown as { hidden?: ((args: any) => boolean) | boolean })?.hidden,\n )\n\n useEffect(() => {\n const fetchSettings = async () => {\n try {\n const response = await fetch(`${apiRoute}/globals/ai-settings`)\n if (response.ok) {\n const data = await response.json()\n // Handle both simple array and object wrapper if Payload wraps it\n const storedEnabled = data.enabledCollections || []\n setEnabledCollections(Array.isArray(storedEnabled) ? storedEnabled : [])\n }\n } catch (error) {\n console.error('Failed to fetch AI settings:', error)\n } finally {\n setIsLoading(false)\n }\n }\n\n fetchSettings().catch((e) => {\n console.log(e)\n })\n }, [apiRoute])\n\n const handleToggle = (slug: string) => {\n setEnabledCollections((prev) => {\n if (prev.includes(slug)) {\n return prev.filter((s) => s !== slug)\n }\n return [...prev, slug]\n })\n }\n\n const handleSave = async () => {\n setIsSaving(true)\n try {\n // First fetch current settings to get ID or just rely on global update behavior\n // We need to adhere to Payload's global update API\n const response = await fetch(`${apiRoute}/globals/ai-settings`, {\n body: JSON.stringify({\n enabledCollections,\n }),\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (response.ok) {\n toast.success('Settings saved successfully')\n if (setEnabledCollectionsInContext) {\n setEnabledCollectionsInContext(enabledCollections)\n }\n if (refresh) {\n await refresh()\n }\n router.refresh()\n } else {\n toast.error('Failed to save settings')\n }\n } catch (error) {\n console.error('Error saving settings:', error)\n toast.error('Error saving settings')\n } finally {\n setIsSaving(false)\n }\n }\n\n if (isLoading) {\n return <div style={{ padding: '20px', textAlign: 'center' }}>Loading AI configuration...</div>\n }\n\n return (\n <div\n style={{\n background: 'var(--theme-elevation-50)',\n // border: '1px solid var(--theme-elevation-150)',\n // borderRadius: '8px',\n marginBottom: '20px',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n alignItems: 'center',\n borderBottom: '1px solid var(--theme-elevation-150)',\n display: 'flex',\n justifyContent: 'space-between',\n padding: '20px',\n }}\n >\n <div>\n <h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>\n <p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>\n Manage your AI providers, API keys, and enable AI for specific collections.\n </p>\n </div>\n <div style={{ display: 'flex', gap: '10px' }}>\n <Button buttonStyle=\"secondary\" el=\"link\" to={`${adminRoute}/globals/ai-settings`}>\n Settings\n </Button>\n <Button\n disabled={isSaving}\n onClick={handleSave}\n >\n {isSaving ? 'Saving...' : 'Save Changes'}\n </Button>\n </div>\n </div>\n\n <div style={{ padding: '20px' }}>\n <h5 style={{ marginBottom: '15px' }}>Enabled Collections</h5>\n <div\n style={{\n display: 'grid',\n gap: '15px',\n gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',\n }}\n >\n {availableCollections.map((collection) => {\n const isEnabled = enabledCollections.includes(collection.slug)\n return (\n <button\n key={collection.slug}\n onClick={() => handleToggle(collection.slug)}\n style={{\n alignItems: 'center',\n background: isEnabled\n ? 'var(--theme-elevation-100)'\n : 'var(--theme-elevation-50)',\n border: `1px solid ${isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)'}`,\n borderRadius: '6px',\n cursor: 'pointer',\n display: 'flex',\n gap: '10px',\n padding: '10px 15px',\n textAlign: 'left',\n transition: 'all 0.2s ease',\n width: '100%',\n }}\n type=\"button\"\n >\n <div\n style={{\n alignItems: 'center',\n background: isEnabled\n ? 'var(--theme-text-success)'\n : 'var(--theme-elevation-200)',\n borderRadius: '12px',\n display: 'flex',\n height: '24px',\n justifyContent: isEnabled ? 'flex-end' : 'flex-start',\n padding: '2px',\n transition: 'all 0.2s ease',\n width: '44px',\n }}\n >\n <div\n style={{\n background: 'white',\n borderRadius: '50%',\n height: '20px',\n width: '20px',\n }}\n />\n </div>\n <span style={{ fontWeight: 500 }}>\n {typeof collection.labels?.singular === 'string'\n ? collection.labels.singular\n : collection.labels?.singular?.en || collection.slug}\n </span>\n </button>\n )\n })}\n </div>\n </div>\n </div>\n )\n}\n"],"names":["Button","toast","useConfig","useRouter","React","useContext","useEffect","useState","excludeCollections","InstructionsContext","AIConfigDashboard","config","collections","routes","admin","adminRoute","api","apiRoute","router","refresh","setEnabledCollections","setEnabledCollectionsInContext","enabledCollections","isLoading","setIsLoading","isSaving","setIsSaving","availableCollections","filter","c","includes","slug","hidden","fetchSettings","response","fetch","ok","data","json","storedEnabled","Array","isArray","error","console","catch","e","log","handleToggle","prev","s","handleSave","body","JSON","stringify","headers","method","success","div","style","padding","textAlign","background","marginBottom","overflow","alignItems","borderBottom","display","justifyContent","h4","margin","p","color","fontSize","gap","buttonStyle","el","to","disabled","onClick","h5","gridTemplateColumns","map","collection","isEnabled","button","border","borderRadius","cursor","transition","width","type","height","span","fontWeight","labels","singular","en"],"mappings":"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"}
|
|
@@ -1,24 +1,170 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import
|
|
2
|
+
import { Button, toast, useConfig } from '@payloadcms/ui';
|
|
3
|
+
// @ts-expect-error - Next.js types are not resolving correctly with nodenext but runtime is fine
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
6
|
+
import { excludeCollections } from '../../defaults.js';
|
|
7
|
+
import { InstructionsContext } from '../../providers/InstructionsProvider/context.js';
|
|
3
8
|
export const AIConfigDashboard = () => {
|
|
9
|
+
const { config: { collections, routes: { admin: adminRoute, api: apiRoute }, }, } = useConfig();
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const { refresh, setEnabledCollections: setEnabledCollectionsInContext } = useContext(InstructionsContext);
|
|
12
|
+
const [enabledCollections, setEnabledCollections] = useState([]);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
14
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
15
|
+
const availableCollections = collections.filter((c) => !excludeCollections.includes(c.slug) &&
|
|
16
|
+
!c.admin?.hidden);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const fetchSettings = async () => {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(`${apiRoute}/globals/ai-settings`);
|
|
21
|
+
if (response.ok) {
|
|
22
|
+
const data = await response.json();
|
|
23
|
+
// Handle both simple array and object wrapper if Payload wraps it
|
|
24
|
+
const storedEnabled = data.enabledCollections || [];
|
|
25
|
+
setEnabledCollections(Array.isArray(storedEnabled) ? storedEnabled : []);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('Failed to fetch AI settings:', error);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
setIsLoading(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
fetchSettings().catch((e) => {
|
|
36
|
+
console.log(e);
|
|
37
|
+
});
|
|
38
|
+
}, [apiRoute]);
|
|
39
|
+
const handleToggle = (slug) => {
|
|
40
|
+
setEnabledCollections((prev) => {
|
|
41
|
+
if (prev.includes(slug)) {
|
|
42
|
+
return prev.filter((s) => s !== slug);
|
|
43
|
+
}
|
|
44
|
+
return [...prev, slug];
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const handleSave = async () => {
|
|
48
|
+
setIsSaving(true);
|
|
49
|
+
try {
|
|
50
|
+
// First fetch current settings to get ID or just rely on global update behavior
|
|
51
|
+
// We need to adhere to Payload's global update API
|
|
52
|
+
const response = await fetch(`${apiRoute}/globals/ai-settings`, {
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
enabledCollections,
|
|
55
|
+
}),
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
},
|
|
59
|
+
method: 'POST',
|
|
60
|
+
});
|
|
61
|
+
if (response.ok) {
|
|
62
|
+
toast.success('Settings saved successfully');
|
|
63
|
+
if (setEnabledCollectionsInContext) {
|
|
64
|
+
setEnabledCollectionsInContext(enabledCollections);
|
|
65
|
+
}
|
|
66
|
+
if (refresh) {
|
|
67
|
+
await refresh();
|
|
68
|
+
}
|
|
69
|
+
router.refresh();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
toast.error('Failed to save settings');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('Error saving settings:', error);
|
|
77
|
+
toast.error('Error saving settings');
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
setIsSaving(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
if (isLoading) {
|
|
84
|
+
return <div style={{ padding: '20px', textAlign: 'center' }}>Loading AI configuration...</div>;
|
|
85
|
+
}
|
|
4
86
|
return (<div style={{
|
|
5
|
-
alignItems: 'center',
|
|
6
87
|
background: 'var(--theme-elevation-50)',
|
|
7
|
-
border: '1px solid var(--theme-elevation-150)',
|
|
8
|
-
borderRadius: '8px',
|
|
88
|
+
// border: '1px solid var(--theme-elevation-150)',
|
|
89
|
+
// borderRadius: '8px',
|
|
90
|
+
marginBottom: '20px',
|
|
91
|
+
overflow: 'hidden',
|
|
92
|
+
}}>
|
|
93
|
+
<div style={{
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
borderBottom: '1px solid var(--theme-elevation-150)',
|
|
9
96
|
display: 'flex',
|
|
10
97
|
justifyContent: 'space-between',
|
|
11
|
-
marginBottom: '20px',
|
|
12
98
|
padding: '20px',
|
|
13
99
|
}}>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
100
|
+
<div>
|
|
101
|
+
<h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>
|
|
102
|
+
<p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>
|
|
103
|
+
Manage your AI providers, API keys, and enable AI for specific collections.
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
<div style={{ display: 'flex', gap: '10px' }}>
|
|
107
|
+
<Button buttonStyle="secondary" el="link" to={`${adminRoute}/globals/ai-settings`}>
|
|
108
|
+
Settings
|
|
109
|
+
</Button>
|
|
110
|
+
<Button disabled={isSaving} onClick={handleSave}>
|
|
111
|
+
{isSaving ? 'Saving...' : 'Save Changes'}
|
|
112
|
+
</Button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div style={{ padding: '20px' }}>
|
|
117
|
+
<h5 style={{ marginBottom: '15px' }}>Enabled Collections</h5>
|
|
118
|
+
<div style={{
|
|
119
|
+
display: 'grid',
|
|
120
|
+
gap: '15px',
|
|
121
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
|
|
122
|
+
}}>
|
|
123
|
+
{availableCollections.map((collection) => {
|
|
124
|
+
const isEnabled = enabledCollections.includes(collection.slug);
|
|
125
|
+
return (<button key={collection.slug} onClick={() => handleToggle(collection.slug)} style={{
|
|
126
|
+
alignItems: 'center',
|
|
127
|
+
background: isEnabled
|
|
128
|
+
? 'var(--theme-elevation-100)'
|
|
129
|
+
: 'var(--theme-elevation-50)',
|
|
130
|
+
border: `1px solid ${isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)'}`,
|
|
131
|
+
borderRadius: '6px',
|
|
132
|
+
cursor: 'pointer',
|
|
133
|
+
display: 'flex',
|
|
134
|
+
gap: '10px',
|
|
135
|
+
padding: '10px 15px',
|
|
136
|
+
textAlign: 'left',
|
|
137
|
+
transition: 'all 0.2s ease',
|
|
138
|
+
width: '100%',
|
|
139
|
+
}} type="button">
|
|
140
|
+
<div style={{
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
background: isEnabled
|
|
143
|
+
? 'var(--theme-text-success)'
|
|
144
|
+
: 'var(--theme-elevation-200)',
|
|
145
|
+
borderRadius: '12px',
|
|
146
|
+
display: 'flex',
|
|
147
|
+
height: '24px',
|
|
148
|
+
justifyContent: isEnabled ? 'flex-end' : 'flex-start',
|
|
149
|
+
padding: '2px',
|
|
150
|
+
transition: 'all 0.2s ease',
|
|
151
|
+
width: '44px',
|
|
152
|
+
}}>
|
|
153
|
+
<div style={{
|
|
154
|
+
background: 'white',
|
|
155
|
+
borderRadius: '50%',
|
|
156
|
+
height: '20px',
|
|
157
|
+
width: '20px',
|
|
158
|
+
}}/>
|
|
159
|
+
</div>
|
|
160
|
+
<span style={{ fontWeight: 500 }}>
|
|
161
|
+
{typeof collection.labels?.singular === 'string'
|
|
162
|
+
? collection.labels.singular
|
|
163
|
+
: collection.labels?.singular?.en || collection.slug}
|
|
164
|
+
</span>
|
|
165
|
+
</button>);
|
|
166
|
+
})}
|
|
167
|
+
</div>
|
|
19
168
|
</div>
|
|
20
|
-
<a href="/admin/globals/ai-settings">
|
|
21
|
-
<button className="btn btn--style-primary btn--size-small">Manage AI Settings</button>
|
|
22
|
-
</a>
|
|
23
169
|
</div>);
|
|
24
170
|
};
|
|
@@ -135,10 +135,12 @@ export const Compose = ({ descriptionProps, forceVisible, instructionId, isConfi
|
|
|
135
135
|
});
|
|
136
136
|
const setIfValueIsLexicalState = useCallback((val)=>{
|
|
137
137
|
if (val && typeof val === 'object' && 'root' in val && lexicalEditor) {
|
|
138
|
-
|
|
138
|
+
// Pass the object directly to our safe setter which handles validation
|
|
139
|
+
setSafeLexicalState(val, lexicalEditor);
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
}, [
|
|
142
|
+
lexicalEditor
|
|
143
|
+
]);
|
|
142
144
|
const popupRender = useCallback(({ close })=>{
|
|
143
145
|
return /*#__PURE__*/ _jsx(Menu, {
|
|
144
146
|
isLoading: isProcessing || isLoading,
|