@simmalugnt-se/payload-editor-assistant 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/capabilities.d.ts +10 -0
- package/dist/capabilities.js +43 -0
- package/dist/collections/approvals.d.ts +2 -0
- package/dist/collections/approvals.js +36 -0
- package/dist/collections/audit.d.ts +2 -0
- package/dist/collections/audit.js +40 -0
- package/dist/components/EditorAssistantAction.d.ts +1 -0
- package/dist/components/EditorAssistantAction.js +17 -0
- package/dist/components/EditorAssistantAside.d.ts +1 -0
- package/dist/components/EditorAssistantAside.js +335 -0
- package/dist/components/EditorAssistantDocumentBridge.d.ts +1 -0
- package/dist/components/EditorAssistantDocumentBridge.js +48 -0
- package/dist/components/EditorAssistantProvider.d.ts +8 -0
- package/dist/components/EditorAssistantProvider.js +14 -0
- package/dist/components/assistant-api.d.ts +9 -0
- package/dist/components/assistant-api.js +34 -0
- package/dist/components/assistant-context.d.ts +66 -0
- package/dist/components/assistant-context.js +69 -0
- package/dist/config/denied-fields.d.ts +3 -0
- package/dist/config/denied-fields.js +34 -0
- package/dist/config/validate.d.ts +15 -0
- package/dist/config/validate.js +86 -0
- package/dist/domain/allowlist.d.ts +12 -0
- package/dist/domain/allowlist.js +14 -0
- package/dist/domain/audit.d.ts +17 -0
- package/dist/domain/audit.js +25 -0
- package/dist/domain/content.d.ts +30 -0
- package/dist/domain/content.js +117 -0
- package/dist/domain/decide.d.ts +30 -0
- package/dist/domain/decide.js +162 -0
- package/dist/domain/guard.d.ts +13 -0
- package/dist/domain/guard.js +29 -0
- package/dist/domain/local-api.d.ts +16 -0
- package/dist/domain/local-api.js +3 -0
- package/dist/domain/navigate.d.ts +25 -0
- package/dist/domain/navigate.js +85 -0
- package/dist/domain/propose.d.ts +26 -0
- package/dist/domain/propose.js +150 -0
- package/dist/domain/relations.d.ts +12 -0
- package/dist/domain/relations.js +103 -0
- package/dist/endpoints/chat.d.ts +3 -0
- package/dist/endpoints/chat.js +88 -0
- package/dist/endpoints/csrf.d.ts +6 -0
- package/dist/endpoints/csrf.js +57 -0
- package/dist/endpoints/decide.d.ts +3 -0
- package/dist/endpoints/decide.js +33 -0
- package/dist/endpoints/propose.d.ts +3 -0
- package/dist/endpoints/propose.js +32 -0
- package/dist/exports/client.d.ts +4 -0
- package/dist/exports/client.js +4 -0
- package/dist/form/compile.d.ts +17 -0
- package/dist/form/compile.js +168 -0
- package/dist/form/diff.d.ts +9 -0
- package/dist/form/diff.js +155 -0
- package/dist/form/empty.d.ts +2 -0
- package/dist/form/empty.js +79 -0
- package/dist/form/hash.d.ts +2 -0
- package/dist/form/hash.js +22 -0
- package/dist/form/operations.d.ts +52 -0
- package/dist/form/operations.js +52 -0
- package/dist/form/path.d.ts +5 -0
- package/dist/form/path.js +79 -0
- package/dist/form/shape.d.ts +19 -0
- package/dist/form/shape.js +137 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/dist/package-name.d.ts +7 -0
- package/dist/package-name.js +7 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +110 -0
- package/dist/runtime/agent.d.ts +42 -0
- package/dist/runtime/agent.js +262 -0
- package/dist/runtime/context.d.ts +37 -0
- package/dist/runtime/context.js +92 -0
- package/dist/runtime/inflight.d.ts +2 -0
- package/dist/runtime/inflight.js +11 -0
- package/dist/runtime/language.d.ts +10 -0
- package/dist/runtime/language.js +27 -0
- package/dist/runtime/propose-loop.d.ts +7 -0
- package/dist/runtime/propose-loop.js +11 -0
- package/dist/runtime/rate-limit.d.ts +1 -0
- package/dist/runtime/rate-limit.js +14 -0
- package/dist/runtime/system-prompt.d.ts +3 -0
- package/dist/runtime/system-prompt.js +110 -0
- package/dist/runtime/tools.d.ts +56 -0
- package/dist/runtime/tools.js +189 -0
- package/dist/runtime/voice.d.ts +11 -0
- package/dist/runtime/voice.js +55 -0
- package/dist/schema/discover.d.ts +35 -0
- package/dist/schema/discover.js +93 -0
- package/dist/schema/fields.d.ts +21 -0
- package/dist/schema/fields.js +139 -0
- package/dist/schema/project.d.ts +15 -0
- package/dist/schema/project.js +111 -0
- package/dist/styles/assistant.css +362 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.js +3 -0
- package/dist/view.d.ts +22 -0
- package/dist/view.js +72 -0
- package/package.json +73 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function canonicalJson(value) {
|
|
2
|
+
return JSON.stringify(sortValue(value));
|
|
3
|
+
}
|
|
4
|
+
export async function hashFormRevision(value) {
|
|
5
|
+
const encoded = new TextEncoder().encode(canonicalJson(value));
|
|
6
|
+
const digest = await crypto.subtle.digest("SHA-256", encoded);
|
|
7
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
8
|
+
}
|
|
9
|
+
function sortValue(value) {
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
return value.map(sortValue);
|
|
12
|
+
}
|
|
13
|
+
if (value && typeof value === "object") {
|
|
14
|
+
const record = value;
|
|
15
|
+
const sorted = {};
|
|
16
|
+
for (const key of Object.keys(record).sort()) {
|
|
17
|
+
sorted[key] = sortValue(record[key]);
|
|
18
|
+
}
|
|
19
|
+
return sorted;
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type FieldPath = string;
|
|
2
|
+
export type FormOperation = {
|
|
3
|
+
op: "set";
|
|
4
|
+
path: FieldPath;
|
|
5
|
+
value: unknown;
|
|
6
|
+
} | {
|
|
7
|
+
op: "unset";
|
|
8
|
+
path: FieldPath;
|
|
9
|
+
} | {
|
|
10
|
+
op: "array.insert";
|
|
11
|
+
path: FieldPath;
|
|
12
|
+
index: number;
|
|
13
|
+
value: unknown;
|
|
14
|
+
} | {
|
|
15
|
+
op: "array.remove";
|
|
16
|
+
path: FieldPath;
|
|
17
|
+
index: number;
|
|
18
|
+
} | {
|
|
19
|
+
op: "array.move";
|
|
20
|
+
path: FieldPath;
|
|
21
|
+
from: number;
|
|
22
|
+
to: number;
|
|
23
|
+
} | {
|
|
24
|
+
op: "array.replace";
|
|
25
|
+
path: FieldPath;
|
|
26
|
+
index: number;
|
|
27
|
+
value: unknown;
|
|
28
|
+
} | {
|
|
29
|
+
op: "blocks.insert";
|
|
30
|
+
path: FieldPath;
|
|
31
|
+
index: number;
|
|
32
|
+
blockType: string;
|
|
33
|
+
data: Record<string, unknown>;
|
|
34
|
+
} | {
|
|
35
|
+
op: "blocks.remove";
|
|
36
|
+
path: FieldPath;
|
|
37
|
+
index: number;
|
|
38
|
+
} | {
|
|
39
|
+
op: "blocks.move";
|
|
40
|
+
path: FieldPath;
|
|
41
|
+
from: number;
|
|
42
|
+
to: number;
|
|
43
|
+
} | {
|
|
44
|
+
op: "blocks.replace";
|
|
45
|
+
path: FieldPath;
|
|
46
|
+
index: number;
|
|
47
|
+
blockType: string;
|
|
48
|
+
data: Record<string, unknown>;
|
|
49
|
+
};
|
|
50
|
+
export declare const FORM_OPERATIONS: readonly ["set", "unset", "array.insert", "array.remove", "array.move", "array.replace", "blocks.insert", "blocks.remove", "blocks.move", "blocks.replace"];
|
|
51
|
+
export declare function isFormOperation(value: unknown): value is FormOperation;
|
|
52
|
+
export declare function normalizeOperations(raw: unknown): unknown[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const FORM_OPERATIONS = [
|
|
2
|
+
"set",
|
|
3
|
+
"unset",
|
|
4
|
+
"array.insert",
|
|
5
|
+
"array.remove",
|
|
6
|
+
"array.move",
|
|
7
|
+
"array.replace",
|
|
8
|
+
"blocks.insert",
|
|
9
|
+
"blocks.remove",
|
|
10
|
+
"blocks.move",
|
|
11
|
+
"blocks.replace",
|
|
12
|
+
];
|
|
13
|
+
export function isFormOperation(value) {
|
|
14
|
+
if (!value || typeof value !== "object" || !("op" in value)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return FORM_OPERATIONS.includes(value.op);
|
|
18
|
+
}
|
|
19
|
+
export function normalizeOperations(raw) {
|
|
20
|
+
const parsed = parseOperations(raw);
|
|
21
|
+
return parsed.map(normalizeOperation);
|
|
22
|
+
}
|
|
23
|
+
function parseOperations(raw) {
|
|
24
|
+
if (typeof raw === "string") {
|
|
25
|
+
try {
|
|
26
|
+
return parseOperations(JSON.parse(raw));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(raw)) {
|
|
33
|
+
return raw;
|
|
34
|
+
}
|
|
35
|
+
if (raw && typeof raw === "object") {
|
|
36
|
+
return [raw];
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
function normalizeOperation(value) {
|
|
41
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
const record = value;
|
|
45
|
+
const op = record.op ?? record.operation ?? record.type;
|
|
46
|
+
const path = typeof record.path === "string" ? record.path.replaceAll(/\[(\d+)\]/g, ".$1") : record.path;
|
|
47
|
+
return {
|
|
48
|
+
...record,
|
|
49
|
+
op,
|
|
50
|
+
path,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type PathSegment = string | number;
|
|
2
|
+
export declare function parseFieldPath(path: string): PathSegment[];
|
|
3
|
+
export declare function getAtPath(data: unknown, path: PathSegment[]): unknown;
|
|
4
|
+
export declare function setAtPath(data: Record<string, unknown>, path: PathSegment[], value: unknown): void;
|
|
5
|
+
export declare function cloneData<T>(value: T): T;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export function parseFieldPath(path) {
|
|
2
|
+
if (!path || path.includes("..") || path.startsWith(".") || path.endsWith(".")) {
|
|
3
|
+
throw new Error(`Invalid field path "${path}".`);
|
|
4
|
+
}
|
|
5
|
+
return path.split(".").map((segment) => {
|
|
6
|
+
if (segment === "") {
|
|
7
|
+
throw new Error(`Invalid field path "${path}".`);
|
|
8
|
+
}
|
|
9
|
+
if (/^\d+$/.test(segment)) {
|
|
10
|
+
return Number(segment);
|
|
11
|
+
}
|
|
12
|
+
return segment;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function getAtPath(data, path) {
|
|
16
|
+
let current = data;
|
|
17
|
+
for (const segment of path) {
|
|
18
|
+
if (current === null || current === undefined) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
if (typeof segment === "number") {
|
|
22
|
+
if (!Array.isArray(current)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
current = current[segment];
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (typeof current !== "object" || Array.isArray(current)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
current = current[segment];
|
|
32
|
+
}
|
|
33
|
+
return current;
|
|
34
|
+
}
|
|
35
|
+
export function setAtPath(data, path, value) {
|
|
36
|
+
if (path.length === 0) {
|
|
37
|
+
throw new Error("Cannot set the document root.");
|
|
38
|
+
}
|
|
39
|
+
let current = data;
|
|
40
|
+
for (let index = 0; index < path.length - 1; index += 1) {
|
|
41
|
+
const segment = path[index];
|
|
42
|
+
const next = path[index + 1];
|
|
43
|
+
if (typeof segment === "number") {
|
|
44
|
+
if (!Array.isArray(current)) {
|
|
45
|
+
throw new Error("Path expected an array.");
|
|
46
|
+
}
|
|
47
|
+
const list = current;
|
|
48
|
+
if (list[segment] === undefined || list[segment] === null) {
|
|
49
|
+
list[segment] = typeof next === "number" ? [] : {};
|
|
50
|
+
}
|
|
51
|
+
current = list[segment];
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) {
|
|
55
|
+
throw new Error("Path expected an object.");
|
|
56
|
+
}
|
|
57
|
+
const record = current;
|
|
58
|
+
if (!(segment in record) || record[segment] === undefined || record[segment] === null) {
|
|
59
|
+
record[segment] = typeof next === "number" ? [] : {};
|
|
60
|
+
}
|
|
61
|
+
current = record[segment];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const last = path[path.length - 1];
|
|
65
|
+
if (typeof last === "number") {
|
|
66
|
+
if (!Array.isArray(current)) {
|
|
67
|
+
throw new Error("Path expected an array.");
|
|
68
|
+
}
|
|
69
|
+
current[last] = value;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (typeof current !== "object" || current === null || Array.isArray(current)) {
|
|
73
|
+
throw new Error("Path expected an object.");
|
|
74
|
+
}
|
|
75
|
+
current[last] = value;
|
|
76
|
+
}
|
|
77
|
+
export function cloneData(value) {
|
|
78
|
+
return structuredClone(value);
|
|
79
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FieldNode } from "../schema/fields.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Make compiled data safe to store: plain text in rich text fields becomes editor state,
|
|
4
|
+
* then every block must be typed. Mutates `data`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function finalizeCompiled(data: Record<string, unknown>, fields: FieldNode[]): {
|
|
7
|
+
ok: true;
|
|
8
|
+
data: Record<string, unknown>;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
error: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Check that every blocks field in compiled data holds typed blocks the schema knows.
|
|
15
|
+
* Drafts skip Payload validation, so an untyped block would otherwise be stored and break Admin.
|
|
16
|
+
*/
|
|
17
|
+
export declare function blockShapeError(data: Record<string, unknown>, fields: FieldNode[]): string | null;
|
|
18
|
+
/** Minimal Lexical editor state: one paragraph per blank-line-separated chunk. */
|
|
19
|
+
export declare function plainTextToLexical(text: string): Record<string, unknown>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make compiled data safe to store: plain text in rich text fields becomes editor state,
|
|
3
|
+
* then every block must be typed. Mutates `data`.
|
|
4
|
+
*/
|
|
5
|
+
export function finalizeCompiled(data, fields) {
|
|
6
|
+
coerceRichText(data, fields);
|
|
7
|
+
const error = blockShapeError(data, fields);
|
|
8
|
+
return error ? { ok: false, error } : { ok: true, data };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Check that every blocks field in compiled data holds typed blocks the schema knows.
|
|
12
|
+
* Drafts skip Payload validation, so an untyped block would otherwise be stored and break Admin.
|
|
13
|
+
*/
|
|
14
|
+
export function blockShapeError(data, fields) {
|
|
15
|
+
return checkFields(data, fields, "");
|
|
16
|
+
}
|
|
17
|
+
function checkFields(data, fields, prefix) {
|
|
18
|
+
for (const field of fields) {
|
|
19
|
+
const value = data[field.name];
|
|
20
|
+
if (value === undefined || value === null) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const path = prefix ? `${prefix}.${field.name}` : field.name;
|
|
24
|
+
const error = checkField(field, value, path);
|
|
25
|
+
if (error) {
|
|
26
|
+
return error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function checkField(field, value, path) {
|
|
32
|
+
if (field.type === "blocks") {
|
|
33
|
+
if (!Array.isArray(value)) {
|
|
34
|
+
return `"${path}" must be a list of blocks.`;
|
|
35
|
+
}
|
|
36
|
+
const blocks = field.blocks ?? [];
|
|
37
|
+
const slugs = blocks.map((block) => block.slug);
|
|
38
|
+
for (const [index, item] of value.entries()) {
|
|
39
|
+
const blockType = isRecord(item) ? item.blockType : undefined;
|
|
40
|
+
const block = blocks.find((entry) => entry.slug === blockType);
|
|
41
|
+
if (!isRecord(item) || !block) {
|
|
42
|
+
return `"${path}.${index}" needs a block type (one of: ${slugs.join(", ")}). Add blocks with blocks.insert, then set their fields.`;
|
|
43
|
+
}
|
|
44
|
+
const error = checkFields(item, block.fields, `${path}.${index}`);
|
|
45
|
+
if (error) {
|
|
46
|
+
return error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (!field.fields) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (field.type === "array") {
|
|
55
|
+
if (!Array.isArray(value)) {
|
|
56
|
+
return `"${path}" must be a list.`;
|
|
57
|
+
}
|
|
58
|
+
for (const [index, item] of value.entries()) {
|
|
59
|
+
if (!isRecord(item)) {
|
|
60
|
+
return `"${path}.${index}" must be an object.`;
|
|
61
|
+
}
|
|
62
|
+
const error = checkFields(item, field.fields, `${path}.${index}`);
|
|
63
|
+
if (error) {
|
|
64
|
+
return error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return isRecord(value) ? checkFields(value, field.fields, path) : null;
|
|
70
|
+
}
|
|
71
|
+
function coerceRichText(data, fields) {
|
|
72
|
+
for (const field of fields) {
|
|
73
|
+
const value = data[field.name];
|
|
74
|
+
if (field.type === "richText" && typeof value === "string") {
|
|
75
|
+
data[field.name] = plainTextToLexical(value);
|
|
76
|
+
}
|
|
77
|
+
else if (field.type === "blocks" && Array.isArray(value)) {
|
|
78
|
+
for (const item of value) {
|
|
79
|
+
const block = isRecord(item)
|
|
80
|
+
? field.blocks?.find((entry) => entry.slug === item.blockType)
|
|
81
|
+
: undefined;
|
|
82
|
+
if (block && isRecord(item)) {
|
|
83
|
+
coerceRichText(item, block.fields);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (field.fields && Array.isArray(value)) {
|
|
88
|
+
for (const item of value) {
|
|
89
|
+
if (isRecord(item)) {
|
|
90
|
+
coerceRichText(item, field.fields);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (field.fields && isRecord(value)) {
|
|
95
|
+
coerceRichText(value, field.fields);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Minimal Lexical editor state: one paragraph per blank-line-separated chunk. */
|
|
100
|
+
export function plainTextToLexical(text) {
|
|
101
|
+
const paragraphs = text
|
|
102
|
+
.split(/\n\s*\n/)
|
|
103
|
+
.map((chunk) => chunk.trim())
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
return {
|
|
106
|
+
root: {
|
|
107
|
+
type: "root",
|
|
108
|
+
format: "",
|
|
109
|
+
indent: 0,
|
|
110
|
+
version: 1,
|
|
111
|
+
direction: "ltr",
|
|
112
|
+
children: paragraphs.map((chunk) => ({
|
|
113
|
+
type: "paragraph",
|
|
114
|
+
format: "",
|
|
115
|
+
indent: 0,
|
|
116
|
+
version: 1,
|
|
117
|
+
direction: "ltr",
|
|
118
|
+
textFormat: 0,
|
|
119
|
+
textStyle: "",
|
|
120
|
+
children: [
|
|
121
|
+
{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: chunk,
|
|
124
|
+
format: 0,
|
|
125
|
+
style: "",
|
|
126
|
+
mode: "normal",
|
|
127
|
+
detail: 0,
|
|
128
|
+
version: 1,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
})),
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function isRecord(value) {
|
|
136
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
137
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { BuiltinCapability, CapabilityRisk, EntityCapability } from "./capabilities.ts";
|
|
2
|
+
export { BUILTIN_CAPABILITIES, BUILTIN_RISK, ENTITY_CAPABILITIES } from "./capabilities.ts";
|
|
3
|
+
export { editorAssistantPlugin } from "./plugin.ts";
|
|
4
|
+
export type { AssistantShortcut, EditorAssistantPluginOptions, EntityAllowlist, FieldAllowlist, HostLanguageModel, HostToolDefinition, ModelResolver, ProposalValidationResult, } from "./types.ts";
|
|
5
|
+
export { defineEditorAssistantConfig } from "./types.ts";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const PACKAGE_NAME = "@simmalugnt-se/payload-editor-assistant";
|
|
2
|
+
export declare const CLIENT_ENTRY = "@simmalugnt-se/payload-editor-assistant/client";
|
|
3
|
+
export declare const ENDPOINT_PREFIX = "/editor-assistant";
|
|
4
|
+
export declare const DEFAULT_AUDIT_SLUG = "editor-assistant-audit";
|
|
5
|
+
export declare const DEFAULT_APPROVAL_SLUG = "editor-assistant-approvals";
|
|
6
|
+
export declare const CSRF_COOKIE = "payload-editor-assistant-csrf";
|
|
7
|
+
export declare const CSRF_HEADER = "x-editor-assistant-csrf";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const PACKAGE_NAME = "@simmalugnt-se/payload-editor-assistant";
|
|
2
|
+
export const CLIENT_ENTRY = `${PACKAGE_NAME}/client`;
|
|
3
|
+
export const ENDPOINT_PREFIX = "/editor-assistant";
|
|
4
|
+
export const DEFAULT_AUDIT_SLUG = "editor-assistant-audit";
|
|
5
|
+
export const DEFAULT_APPROVAL_SLUG = "editor-assistant-approvals";
|
|
6
|
+
export const CSRF_COOKIE = "payload-editor-assistant-csrf";
|
|
7
|
+
export const CSRF_HEADER = "x-editor-assistant-csrf";
|
package/dist/plugin.d.ts
ADDED
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createApprovalCollection } from "./collections/approvals.js";
|
|
2
|
+
import { createAuditCollection } from "./collections/audit.js";
|
|
3
|
+
import { isDeniedCollectionSlug } from "./config/denied-fields.js";
|
|
4
|
+
import { validateEditorAssistantOptions } from "./config/validate.js";
|
|
5
|
+
import { createChatEndpoint } from "./endpoints/chat.js";
|
|
6
|
+
import { createCsrfEndpoint } from "./endpoints/csrf.js";
|
|
7
|
+
import { createDecideEndpoint } from "./endpoints/decide.js";
|
|
8
|
+
import { createProposeEndpoint } from "./endpoints/propose.js";
|
|
9
|
+
import { CLIENT_ENTRY, DEFAULT_APPROVAL_SLUG } from "./package-name.js";
|
|
10
|
+
export function editorAssistantPlugin(options = {}) {
|
|
11
|
+
const validated = validateEditorAssistantOptions(options);
|
|
12
|
+
return (config) => {
|
|
13
|
+
if (validated.disabled) {
|
|
14
|
+
return config;
|
|
15
|
+
}
|
|
16
|
+
const collections = [...(config.collections ?? [])];
|
|
17
|
+
const globals = [...(config.globals ?? [])];
|
|
18
|
+
for (const [index, collection] of collections.entries()) {
|
|
19
|
+
if (collection.auth && validated.collections[collection.slug]) {
|
|
20
|
+
throw new Error(`payload-editor-assistant: auth collection "${collection.slug}" cannot be allowlisted.`);
|
|
21
|
+
}
|
|
22
|
+
if (validated.collections[collection.slug]) {
|
|
23
|
+
collections[index] = withDocumentBridge(collection);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const [index, global] of globals.entries()) {
|
|
27
|
+
if (validated.globals[global.slug]) {
|
|
28
|
+
globals[index] = withGlobalBridge(global);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const existingSlugs = new Set(collections.map((collection) => collection.slug));
|
|
32
|
+
if (!existingSlugs.has(validated.audit.collection)) {
|
|
33
|
+
collections.push(createAuditCollection(validated.audit.collection));
|
|
34
|
+
}
|
|
35
|
+
if (!existingSlugs.has(DEFAULT_APPROVAL_SLUG) &&
|
|
36
|
+
!isDeniedCollectionSlug(DEFAULT_APPROVAL_SLUG)) {
|
|
37
|
+
collections.push(createApprovalCollection());
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...config,
|
|
41
|
+
admin: {
|
|
42
|
+
...config.admin,
|
|
43
|
+
components: {
|
|
44
|
+
...config.admin?.components,
|
|
45
|
+
providers: [
|
|
46
|
+
...(config.admin?.components?.providers ?? []),
|
|
47
|
+
{
|
|
48
|
+
path: `${CLIENT_ENTRY}#EditorAssistantProvider`,
|
|
49
|
+
clientProps: {
|
|
50
|
+
shortcuts: validated.shortcuts,
|
|
51
|
+
collections: Object.keys(validated.collections),
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
actions: [
|
|
56
|
+
...(config.admin?.components?.actions ?? []),
|
|
57
|
+
`${CLIENT_ENTRY}#EditorAssistantAction`,
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
collections,
|
|
62
|
+
globals,
|
|
63
|
+
endpoints: [
|
|
64
|
+
...(config.endpoints ?? []),
|
|
65
|
+
createCsrfEndpoint(),
|
|
66
|
+
createChatEndpoint(validated),
|
|
67
|
+
createProposeEndpoint(validated),
|
|
68
|
+
createDecideEndpoint(validated),
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function withDocumentBridge(collection) {
|
|
74
|
+
const edit = collection.admin?.components?.edit;
|
|
75
|
+
return {
|
|
76
|
+
...collection,
|
|
77
|
+
admin: {
|
|
78
|
+
...collection.admin,
|
|
79
|
+
components: {
|
|
80
|
+
...collection.admin?.components,
|
|
81
|
+
edit: {
|
|
82
|
+
...edit,
|
|
83
|
+
beforeDocumentControls: [
|
|
84
|
+
...(edit?.beforeDocumentControls ?? []),
|
|
85
|
+
`${CLIENT_ENTRY}#EditorAssistantDocumentBridge`,
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function withGlobalBridge(global) {
|
|
93
|
+
const elements = global.admin?.components?.elements;
|
|
94
|
+
return {
|
|
95
|
+
...global,
|
|
96
|
+
admin: {
|
|
97
|
+
...global.admin,
|
|
98
|
+
components: {
|
|
99
|
+
...global.admin?.components,
|
|
100
|
+
elements: {
|
|
101
|
+
...elements,
|
|
102
|
+
beforeDocumentControls: [
|
|
103
|
+
...(elements?.beforeDocumentControls ?? []),
|
|
104
|
+
`${CLIENT_ENTRY}#EditorAssistantDocumentBridge`,
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PayloadRequest } from "payload";
|
|
2
|
+
import type { ValidatedEditorAssistantOptions } from "../config/validate.ts";
|
|
3
|
+
import { type ToolContext, type ToolOutcome } from "./tools.ts";
|
|
4
|
+
export type AgentChatResult = {
|
|
5
|
+
ok: true;
|
|
6
|
+
text: string;
|
|
7
|
+
pendingApproval?: {
|
|
8
|
+
title: string;
|
|
9
|
+
summary: string;
|
|
10
|
+
approvalId: string;
|
|
11
|
+
nonce: string;
|
|
12
|
+
capability: "form.propose" | "draft.create";
|
|
13
|
+
};
|
|
14
|
+
navigate?: {
|
|
15
|
+
href: string;
|
|
16
|
+
label: string;
|
|
17
|
+
} | {
|
|
18
|
+
candidates: Array<{
|
|
19
|
+
href: string;
|
|
20
|
+
label: string;
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
} | {
|
|
24
|
+
ok: false;
|
|
25
|
+
error: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
};
|
|
28
|
+
type ChatMessage = {
|
|
29
|
+
role: "user" | "assistant" | "system";
|
|
30
|
+
text: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function runAgentTurn(req: PayloadRequest, options: ValidatedEditorAssistantOptions, input: {
|
|
33
|
+
message: string;
|
|
34
|
+
messages?: ChatMessage[];
|
|
35
|
+
bridge: ToolContext["bridge"];
|
|
36
|
+
formData?: Record<string, unknown>;
|
|
37
|
+
}): Promise<AgentChatResult>;
|
|
38
|
+
export declare function collectResult(text: string, result: {
|
|
39
|
+
steps?: unknown;
|
|
40
|
+
toolResults?: unknown;
|
|
41
|
+
}, captured: ToolOutcome[], locale: string, expectedProposal?: boolean): AgentChatResult;
|
|
42
|
+
export {};
|