@mvriu5/payload-ai 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +60 -0
  2. package/dist/ai/promptCaching.d.ts +23 -0
  3. package/dist/ai/promptCaching.js +120 -0
  4. package/dist/ai/proposalRepair.d.ts +30 -0
  5. package/dist/ai/proposalRepair.js +76 -0
  6. package/dist/ai/sensitiveData.d.ts +1 -0
  7. package/dist/ai/sensitiveData.js +3 -1
  8. package/dist/components/ai-input/AIInput.js +35 -5
  9. package/dist/components/ai-input/AIInput.module.css +49 -0
  10. package/dist/components/generate-field/GenerateField.d.ts +11 -0
  11. package/dist/components/generate-field/GenerateField.js +109 -0
  12. package/dist/components/generate-field/GenerateField.module.css +39 -0
  13. package/dist/components/generate-field/richText.d.ts +29 -0
  14. package/dist/components/generate-field/richText.js +30 -0
  15. package/dist/components/translate-document/TranslateDocumentButton.d.ts +2 -0
  16. package/dist/components/translate-document/TranslateDocumentButton.js +111 -0
  17. package/dist/components/translate-document/TranslateDocumentButton.module.css +11 -0
  18. package/dist/exports/client.d.ts +4 -0
  19. package/dist/exports/client.js +4 -0
  20. package/dist/handlers/chatHandler.d.ts +1 -0
  21. package/dist/handlers/chatHandler.js +464 -138
  22. package/dist/handlers/generateFieldHandler.d.ts +14 -0
  23. package/dist/handlers/generateFieldHandler.js +144 -0
  24. package/dist/handlers/translateDocumentHandler.d.ts +14 -0
  25. package/dist/handlers/translateDocumentHandler.js +186 -0
  26. package/dist/index.d.ts +4 -0
  27. package/dist/index.js +112 -4
  28. package/dist/payload/documentTranslation.d.ts +15 -0
  29. package/dist/payload/documentTranslation.js +120 -0
  30. package/dist/payload/textFieldGeneration.d.ts +26 -0
  31. package/dist/payload/textFieldGeneration.js +99 -0
  32. package/dist/payload/toolFieldSelection.d.ts +25 -0
  33. package/dist/payload/toolFieldSelection.js +118 -0
  34. package/dist/payload/toolSchemas.d.ts +7 -0
  35. package/dist/payload/toolSchemas.js +117 -0
  36. package/package.json +3 -3
@@ -0,0 +1,120 @@
1
+ const hasValue = (value)=>{
2
+ if (value === null || value === undefined || value === "") return false;
3
+ if (Array.isArray(value)) return value.some(hasValue);
4
+ if (typeof value === "object") return Object.values(value).some(hasValue);
5
+ return true;
6
+ };
7
+ const hasRichTextValue = (value)=>{
8
+ if (!value || typeof value !== "object") return false;
9
+ if (Array.isArray(value)) return value.some(hasRichTextValue);
10
+ const node = value;
11
+ if (node.type === "text") return typeof node.text === "string" && node.text.trim().length > 0;
12
+ if ([
13
+ "block",
14
+ "inlineBlock",
15
+ "relationship",
16
+ "upload"
17
+ ].includes(String(node.type))) return true;
18
+ if (node.root) return hasRichTextValue(node.root);
19
+ return Array.isArray(node.children) && node.children.some(hasRichTextValue);
20
+ };
21
+ const getFieldType = (field)=>[
22
+ "json",
23
+ "richText",
24
+ "text",
25
+ "textarea"
26
+ ].includes(field.type) ? field.type : "unknown";
27
+ const collectFields = ({ data, fields, parentPath = "", result })=>{
28
+ for (const field of fields){
29
+ if (field.type === "tabs") {
30
+ for (const tab of field.tabs){
31
+ const tabData = "name" in tab && tab.name ? data[tab.name] || {} : data;
32
+ collectFields({
33
+ data: tabData,
34
+ fields: tab.fields,
35
+ parentPath: "name" in tab && tab.name ? parentPath ? `${parentPath}.${tab.name}` : tab.name : parentPath,
36
+ result
37
+ });
38
+ }
39
+ continue;
40
+ }
41
+ if (!("name" in field) || !field.name) {
42
+ if ("fields" in field && Array.isArray(field.fields)) collectFields({
43
+ data,
44
+ fields: field.fields,
45
+ parentPath,
46
+ result
47
+ });
48
+ continue;
49
+ }
50
+ const path = parentPath ? `${parentPath}.${field.name}` : field.name;
51
+ const value = data[field.name];
52
+ if ("localized" in field && field.localized) {
53
+ const valueExists = field.type === "richText" ? hasRichTextValue(value) : hasValue(value);
54
+ if (valueExists) {
55
+ result.push({
56
+ fieldType: getFieldType(field),
57
+ path,
58
+ value
59
+ });
60
+ }
61
+ continue;
62
+ }
63
+ if (field.type === "array" && Array.isArray(value)) {
64
+ value.forEach((row, index)=>{
65
+ if (row && typeof row === "object") {
66
+ collectFields({
67
+ data: row,
68
+ fields: field.fields,
69
+ parentPath: `${path}.${index}`,
70
+ result
71
+ });
72
+ }
73
+ });
74
+ continue;
75
+ }
76
+ if (field.type === "blocks" && Array.isArray(value)) {
77
+ value.forEach((row, index)=>{
78
+ if (!row || typeof row !== "object") return;
79
+ const rowData = row;
80
+ const block = field.blocks.find((candidate)=>typeof candidate === "object" && candidate.slug === rowData.blockType);
81
+ if (block && typeof block === "object") {
82
+ collectFields({
83
+ data: rowData,
84
+ fields: block.fields,
85
+ parentPath: `${path}.${index}`,
86
+ result
87
+ });
88
+ }
89
+ });
90
+ continue;
91
+ }
92
+ if ("fields" in field && Array.isArray(field.fields) && value && typeof value === "object") {
93
+ collectFields({
94
+ data: value,
95
+ fields: field.fields,
96
+ parentPath: path,
97
+ result
98
+ });
99
+ }
100
+ }
101
+ };
102
+ export const getTranslationValues = (fields, data)=>{
103
+ if (!data || typeof data !== "object") return [];
104
+ const result = [];
105
+ collectFields({
106
+ data: data,
107
+ fields,
108
+ result
109
+ });
110
+ return result;
111
+ };
112
+ export const hasLocalizedFields = (fields)=>{
113
+ for (const field of fields){
114
+ if ("localized" in field && field.localized) return true;
115
+ if (field.type === "tabs" && field.tabs.some((tab)=>hasLocalizedFields(tab.fields))) return true;
116
+ if (field.type === "blocks" && field.blocks.some((block)=>typeof block === "object" && hasLocalizedFields(block.fields))) return true;
117
+ if ("fields" in field && Array.isArray(field.fields) && hasLocalizedFields(field.fields)) return true;
118
+ }
119
+ return false;
120
+ };
@@ -0,0 +1,26 @@
1
+ import type { Field } from "payload";
2
+ type GeneratableField = Extract<Field, {
3
+ type: "json" | "richText" | "text" | "textarea";
4
+ }>;
5
+ export type TextGenerationFieldContext = {
6
+ description?: string;
7
+ fieldType: GeneratableField["type"];
8
+ hasMany: boolean;
9
+ key: string;
10
+ label: string;
11
+ maxLength?: number;
12
+ name: string;
13
+ };
14
+ export type TextGenerationPageContext = {
15
+ fields: TextGenerationFieldContext[];
16
+ label: string;
17
+ slug: string;
18
+ type: "collection" | "global";
19
+ };
20
+ export declare const addTextGenerationFields: ({ fields, label, slug, type, }: {
21
+ fields: Field[];
22
+ label: string;
23
+ slug: string;
24
+ type: TextGenerationPageContext["type"];
25
+ }) => TextGenerationPageContext;
26
+ export {};
@@ -0,0 +1,99 @@
1
+ const generateFieldComponent = "@mvriu5/payload-ai/client#GenerateField";
2
+ const getLabel = (field)=>{
3
+ if (typeof field.label === "string") return field.label;
4
+ if (field.label && typeof field.label === "object") {
5
+ const label = Object.values(field.label).find((value)=>typeof value === "string");
6
+ if (typeof label === "string") return label;
7
+ }
8
+ return field.name;
9
+ };
10
+ const getDescription = (field)=>{
11
+ const description = field.admin?.description;
12
+ return typeof description === "string" ? description : undefined;
13
+ };
14
+ const addGenerateComponent = (field, key)=>{
15
+ field.admin = field.admin || {};
16
+ field.admin.components = field.admin.components || {};
17
+ const current = field.admin.components.afterInput || [];
18
+ const alreadyAdded = current.some((component)=>{
19
+ if (typeof component === "string") return component === generateFieldComponent;
20
+ return component && typeof component === "object" && "path" in component && component.path === generateFieldComponent;
21
+ });
22
+ if (alreadyAdded) return;
23
+ field.admin.components.afterInput = [
24
+ ...current,
25
+ {
26
+ clientProps: {
27
+ generationFieldKey: key,
28
+ generationFieldType: field.type
29
+ },
30
+ path: generateFieldComponent
31
+ }
32
+ ];
33
+ };
34
+ const visitFields = ({ fields, parentKey, result })=>{
35
+ for (const field of fields){
36
+ if (field.type === "tabs") {
37
+ for (const tab of field.tabs){
38
+ visitFields({
39
+ fields: tab.fields,
40
+ parentKey: "name" in tab ? `${parentKey}.${tab.name}` : parentKey,
41
+ result
42
+ });
43
+ }
44
+ continue;
45
+ }
46
+ if (field.type === "blocks") {
47
+ for (const block of field.blocks){
48
+ if (typeof block !== "object") continue;
49
+ visitFields({
50
+ fields: block.fields,
51
+ parentKey: `${parentKey}.${field.name}.${block.slug}`,
52
+ result
53
+ });
54
+ }
55
+ continue;
56
+ }
57
+ if ("fields" in field && Array.isArray(field.fields)) {
58
+ const nextParent = "name" in field && field.name ? `${parentKey}.${field.name}` : parentKey;
59
+ visitFields({
60
+ fields: field.fields,
61
+ parentKey: nextParent,
62
+ result
63
+ });
64
+ }
65
+ if (![
66
+ "json",
67
+ "richText",
68
+ "text",
69
+ "textarea"
70
+ ].includes(field.type) || !("name" in field) || !field.name) continue;
71
+ const key = `${parentKey}.${field.name}`;
72
+ const generatableField = field;
73
+ if (generatableField.admin?.hidden) continue;
74
+ addGenerateComponent(generatableField, key);
75
+ result.push({
76
+ description: getDescription(generatableField),
77
+ fieldType: generatableField.type,
78
+ hasMany: "hasMany" in generatableField ? Boolean(generatableField.hasMany) : false,
79
+ key,
80
+ label: getLabel(generatableField),
81
+ maxLength: "maxLength" in generatableField ? generatableField.maxLength : undefined,
82
+ name: generatableField.name
83
+ });
84
+ }
85
+ };
86
+ export const addTextGenerationFields = ({ fields, label, slug, type })=>{
87
+ const result = [];
88
+ visitFields({
89
+ fields,
90
+ parentKey: slug,
91
+ result
92
+ });
93
+ return {
94
+ fields: result,
95
+ label,
96
+ slug,
97
+ type
98
+ };
99
+ };
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ import { type CollectionConfig } from "./normalizeData.js";
3
+ export declare const maxToolSelectedFields = 12;
4
+ export type ReadCollectionConfig = CollectionConfig & {
5
+ admin?: {
6
+ useAsTitle?: string;
7
+ };
8
+ timestamps?: boolean;
9
+ upload?: boolean | Record<string, unknown>;
10
+ };
11
+ export declare const getToolSelectableFieldNames: (config: ReadCollectionConfig) => string[];
12
+ export declare const createToolFieldNamesSchema: (configs: ReadCollectionConfig[]) => z.ZodOptional<z.ZodArray<z.ZodString | z.ZodEnum<{
13
+ [x: string]: string;
14
+ }>>>;
15
+ export declare const resolveToolFieldSelection: ({ config, mode, requestedFields, }: {
16
+ config: ReadCollectionConfig;
17
+ mode?: "document" | "search";
18
+ requestedFields?: string[];
19
+ }) => {
20
+ allowedFields: string[];
21
+ depth: number;
22
+ fields: string[];
23
+ invalidFields: string[];
24
+ select: Record<string, true>;
25
+ };
@@ -0,0 +1,118 @@
1
+ import { z } from "zod";
2
+ import { isSensitiveKey } from "../ai/sensitiveData.js";
3
+ import { getSchemaFields } from "./normalizeData.js";
4
+ export const maxToolSelectedFields = 12;
5
+ const cheapFieldTypes = new Set([
6
+ "checkbox",
7
+ "date",
8
+ "email",
9
+ "number",
10
+ "point",
11
+ "radio",
12
+ "select",
13
+ "text"
14
+ ]);
15
+ const populatedFieldTypes = new Set([
16
+ "relationship",
17
+ "upload"
18
+ ]);
19
+ const systemFieldNames = [
20
+ "id",
21
+ "createdAt",
22
+ "updatedAt"
23
+ ];
24
+ const uploadFieldNames = [
25
+ "filename",
26
+ "mimeType",
27
+ "filesize",
28
+ "width",
29
+ "height",
30
+ "url",
31
+ "thumbnailURL"
32
+ ];
33
+ const getSelectableFields = (config)=>{
34
+ return getSchemaFields(config).filter((field)=>{
35
+ return Boolean(field.name && field.type !== "ui" && !isSensitiveKey(field.name));
36
+ });
37
+ };
38
+ export const getToolSelectableFieldNames = (config)=>{
39
+ const names = getSelectableFields(config).flatMap((field)=>field.name ? [
40
+ field.name
41
+ ] : []);
42
+ names.push("id");
43
+ if (config.timestamps !== false) names.push("createdAt", "updatedAt");
44
+ if (config.upload) names.push(...uploadFieldNames);
45
+ return [
46
+ ...new Set(names)
47
+ ];
48
+ };
49
+ const getDefaultFieldNames = (config, mode)=>{
50
+ const selectableFields = getSelectableFields(config);
51
+ const selectableNames = new Set(getToolSelectableFieldNames(config));
52
+ const candidates = [
53
+ config.admin?.useAsTitle,
54
+ "title",
55
+ "name",
56
+ "label",
57
+ "headline",
58
+ "slug",
59
+ "_status",
60
+ ...config.upload ? selectableFields.flatMap((field)=>field.name && cheapFieldTypes.has(field.type || "") ? [
61
+ field.name
62
+ ] : []).slice(0, 2) : [],
63
+ ...mode === "document" ? [
64
+ "updatedAt"
65
+ ] : [],
66
+ ...config.upload ? [
67
+ "filename",
68
+ "mimeType",
69
+ "filesize",
70
+ "url"
71
+ ] : []
72
+ ];
73
+ const defaults = candidates.filter((name)=>Boolean(name && (selectableNames.has(name) || systemFieldNames.includes(name))));
74
+ if (!defaults.some((fieldName)=>!systemFieldNames.includes(fieldName) && !uploadFieldNames.includes(fieldName))) {
75
+ const firstCheapField = selectableFields.find((field)=>field.name && cheapFieldTypes.has(field.type || ""));
76
+ const firstSelectableField = firstCheapField || selectableFields[0];
77
+ defaults.unshift(firstSelectableField?.name || "id");
78
+ }
79
+ return [
80
+ ...new Set(defaults)
81
+ ].slice(0, mode === "search" ? 4 : 6);
82
+ };
83
+ export const createToolFieldNamesSchema = (configs)=>{
84
+ const names = [
85
+ ...new Set(configs.flatMap(getToolSelectableFieldNames))
86
+ ];
87
+ const fieldNameSchema = names.length > 0 ? z.enum(names) : z.string().refine(()=>false, "No fields are selectable.");
88
+ return z.array(fieldNameSchema).max(maxToolSelectedFields).optional();
89
+ };
90
+ export const resolveToolFieldSelection = ({ config, mode = "document", requestedFields })=>{
91
+ const allowedNames = new Set(getToolSelectableFieldNames(config));
92
+ const normalizedRequestedFields = [
93
+ ...new Set((requestedFields || []).slice(0, maxToolSelectedFields).map((field)=>field.trim()).filter(Boolean))
94
+ ];
95
+ const invalidFields = normalizedRequestedFields.filter((field)=>!allowedNames.has(field));
96
+ const selectedFields = [
97
+ ...new Set((requestedFields?.length ? requestedFields : getDefaultFieldNames(config, mode)).map((field)=>field.trim()).filter((field)=>allowedNames.has(field)))
98
+ ].slice(0, maxToolSelectedFields);
99
+ const fields = selectedFields.length > 0 ? selectedFields : getDefaultFieldNames(config, mode);
100
+ const fieldTypesByName = new Map(getSelectableFields(config).flatMap((field)=>field.name ? [
101
+ [
102
+ field.name,
103
+ field.type
104
+ ]
105
+ ] : []));
106
+ return {
107
+ allowedFields: [
108
+ ...allowedNames
109
+ ],
110
+ depth: fields.some((field)=>populatedFieldTypes.has(fieldTypesByName.get(field) || "")) ? 1 : 0,
111
+ fields,
112
+ invalidFields,
113
+ select: Object.fromEntries(fields.map((field)=>[
114
+ field,
115
+ true
116
+ ]))
117
+ };
118
+ };
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ import { type CollectionConfig } from "./normalizeData.js";
3
+ export declare const genericPayloadDataSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
4
+ export declare const createPayloadDataSchema: (config?: CollectionConfig | null) => z.ZodRecord<z.ZodString, z.ZodUnknown> | z.ZodObject<{
5
+ [x: string]: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
6
+ }, z.core.$strict>;
7
+ export declare const createLocalizedPayloadDataSchema: (dataSchema: z.ZodType) => z.ZodRecord<z.ZodString, z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
@@ -0,0 +1,117 @@
1
+ import { z } from "zod";
2
+ import { getSchemaFields } from "./normalizeData.js";
3
+ const relationshipIDSchema = z.union([
4
+ z.string().min(1),
5
+ z.number()
6
+ ]);
7
+ const getOptionValues = (field)=>{
8
+ return (field.options || []).flatMap((option)=>{
9
+ const value = typeof option === "string" ? option : option.value;
10
+ return value ? [
11
+ value
12
+ ] : [];
13
+ });
14
+ };
15
+ const createSelectSchema = (field)=>{
16
+ const values = getOptionValues(field);
17
+ const valueSchema = values.length > 0 ? z.enum(values) : z.string();
18
+ return field.hasMany ? z.array(valueSchema) : valueSchema;
19
+ };
20
+ const createRelationshipSchema = (field)=>{
21
+ const polymorphicValueSchema = Array.isArray(field.relationTo) ? z.union([
22
+ relationshipIDSchema,
23
+ z.object({
24
+ relationTo: z.enum(field.relationTo),
25
+ value: relationshipIDSchema
26
+ }).strict()
27
+ ]) : relationshipIDSchema;
28
+ return field.hasMany ? z.array(polymorphicValueSchema) : polymorphicValueSchema;
29
+ };
30
+ const createObjectSchema = (fields)=>{
31
+ const shape = {};
32
+ const addFields = (nestedFields)=>{
33
+ for (const field of nestedFields){
34
+ if (field.type === "ui") continue;
35
+ if (!field.name) {
36
+ if (field.fields?.length) addFields(field.fields);
37
+ if (field.tabs?.length) {
38
+ for (const tab of field.tabs){
39
+ if (tab.name) {
40
+ shape[tab.name] = createObjectSchema(tab.fields || []).nullable().optional();
41
+ } else {
42
+ addFields(tab.fields || []);
43
+ }
44
+ }
45
+ }
46
+ continue;
47
+ }
48
+ shape[field.name] = createFieldValueSchema(field).nullable().optional();
49
+ }
50
+ };
51
+ addFields(fields);
52
+ return z.object(shape).strict();
53
+ };
54
+ const createBlocksSchema = (field)=>{
55
+ const blockSchemas = (field.blocks || []).map((block)=>createObjectSchema(block.fields || []).extend({
56
+ blockType: z.literal(block.slug)
57
+ }));
58
+ if (blockSchemas.length === 0) return z.array(z.record(z.string(), z.unknown()));
59
+ if (blockSchemas.length === 1) return z.array(blockSchemas[0]);
60
+ return z.array(z.union(blockSchemas));
61
+ };
62
+ const createFieldValueSchema = (field)=>{
63
+ switch(field.type){
64
+ case "array":
65
+ return z.array(createObjectSchema(field.fields || []));
66
+ case "blocks":
67
+ return createBlocksSchema(field);
68
+ case "checkbox":
69
+ return z.boolean();
70
+ case "date":
71
+ return z.string().min(1);
72
+ case "email":
73
+ return z.string();
74
+ case "group":
75
+ return createObjectSchema(field.fields || []);
76
+ case "number":
77
+ return z.number();
78
+ case "point":
79
+ return z.tuple([
80
+ z.number(),
81
+ z.number()
82
+ ]);
83
+ case "radio":
84
+ case "select":
85
+ return createSelectSchema(field);
86
+ case "relationship":
87
+ case "upload":
88
+ return createRelationshipSchema(field);
89
+ case "richText":
90
+ return z.union([
91
+ z.string(),
92
+ z.record(z.string(), z.unknown())
93
+ ]);
94
+ case "code":
95
+ case "text":
96
+ case "textarea":
97
+ return z.string();
98
+ case "json":
99
+ default:
100
+ return z.unknown();
101
+ }
102
+ };
103
+ export const genericPayloadDataSchema = z.record(z.string(), z.unknown());
104
+ const payloadDataSchemaCache = new WeakMap();
105
+ export const createPayloadDataSchema = (config)=>{
106
+ if (!config) return genericPayloadDataSchema;
107
+ const cached = payloadDataSchemaCache.get(config);
108
+ if (cached) return cached;
109
+ const schema = createObjectSchema(getSchemaFields(config));
110
+ payloadDataSchemaCache.set(config, schema);
111
+ return schema;
112
+ };
113
+ export const createLocalizedPayloadDataSchema = (dataSchema)=>{
114
+ return z.record(z.string(), dataSchema).refine((value)=>Object.keys(value).length > 0, {
115
+ message: "localizedData must include at least one locale entry."
116
+ });
117
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mvriu5/payload-ai",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "AI assistant plugin for Payload CMS with provider selection, CMS mentions, and signed action proposals.",
5
5
  "keywords": [
6
6
  "payload",
@@ -84,10 +84,10 @@
84
84
  "copyfiles": "2.4.1",
85
85
  "cross-env": "^10.1.0",
86
86
  "eslint": "^10.5.0",
87
- "graphql": "^17.0.1",
87
+ "graphql": "^16.8.1",
88
88
  "jsdom": "^29.1.1",
89
89
  "knip": "^6.18.0",
90
- "next": "16.2.10",
90
+ "next": "16.2.11",
91
91
  "payload": "3.86.0",
92
92
  "prettier": "^3.8.4",
93
93
  "react": "19.2.7",