@simmalugnt-se/payload-editor-assistant 0.12.1 → 0.12.2
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/CHANGELOG.md +9 -0
- package/dist/components/EditorAssistantAside.d.ts +1 -1
- package/dist/components/EditorAssistantAside.js +2 -5
- package/dist/components/EditorAssistantDocumentBridge.js +1 -1
- package/dist/components/batch-link.js +2 -1
- package/dist/domain/batch.js +6 -2
- package/dist/domain/propose.js +6 -1
- package/dist/form/batch-revision.d.ts +6 -0
- package/dist/form/batch-revision.js +39 -0
- package/dist/styles/assistant.css +5 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.12.2
|
|
4
|
+
|
|
5
|
+
- Batch links remove their parameters from Next’s router state as well as browser history, so
|
|
6
|
+
refreshing cannot reopen a finished batch. Field prompts use the same URL integration.
|
|
7
|
+
- Collapsing the assistant preserves batch progress instead of resetting completed cards.
|
|
8
|
+
- Batch revision checks treat schema-defined empty values consistently, so the first draft saved
|
|
9
|
+
in one language does not falsely invalidate another language’s proposal on Postgres. Real content
|
|
10
|
+
changes anywhere in the allowlisted form still refuse the save.
|
|
11
|
+
|
|
3
12
|
## 0.12.1
|
|
4
13
|
|
|
5
14
|
- The review lists groups in the schema's order (a group's own position, blocks and rows by their
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function EditorAssistantAside(): import("react").JSX.Element
|
|
1
|
+
export declare function EditorAssistantAside(): import("react").JSX.Element;
|
|
@@ -441,11 +441,8 @@ export function EditorAssistantAside() {
|
|
|
441
441
|
void send(draft);
|
|
442
442
|
}
|
|
443
443
|
};
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
return null;
|
|
447
|
-
}
|
|
448
|
-
return (_jsxs("aside", { className: "ea-aside", "aria-label": copy.assistant, [KEEP_SELECTION_ATTR]: "", children: [_jsxs("div", { className: "ea-aside__head", children: [_jsxs("div", { className: "ea-aside__title", children: [_jsx("h2", { children: copy.assistant }), bridge ? _jsx("p", { className: "ea-aside__context", children: contextLine(bridge, config) }) : null] }), !batch && (messages.length > 0 || pendingApproval) && !pendingApproval?.applied ? (_jsx(Button, { buttonStyle: "subtle", margin: false, onClick: () => resetConversation(), size: "medium", children: copy.newChat })) : null, _jsx(Button, { "aria-label": copy.collapseLabel, buttonStyle: "icon-label", className: "ea-aside__collapse", icon: _jsx(ChevronIcon, { direction: "right" }), margin: false, onClick: () => setOpen(false), tooltip: copy.collapse })] }), batch ? (_jsx("div", { className: "ea-aside__body", children: _jsx(BatchPanel, { batch: batch, onClose: () => {
|
|
444
|
+
// Keep batch cards mounted while collapsed, so reopening never resets saved cards to tasks.
|
|
445
|
+
return (_jsxs("aside", { className: "ea-aside", "aria-label": copy.assistant, hidden: !open, [KEEP_SELECTION_ATTR]: "", children: [_jsxs("div", { className: "ea-aside__head", children: [_jsxs("div", { className: "ea-aside__title", children: [_jsx("h2", { children: copy.assistant }), bridge ? _jsx("p", { className: "ea-aside__context", children: contextLine(bridge, config) }) : null] }), !batch && (messages.length > 0 || pendingApproval) && !pendingApproval?.applied ? (_jsx(Button, { buttonStyle: "subtle", margin: false, onClick: () => resetConversation(), size: "medium", children: copy.newChat })) : null, _jsx(Button, { "aria-label": copy.collapseLabel, buttonStyle: "icon-label", className: "ea-aside__collapse", icon: _jsx(ChevronIcon, { direction: "right" }), margin: false, onClick: () => setOpen(false), tooltip: copy.collapse })] }), batch ? (_jsx("div", { className: "ea-aside__body", children: _jsx(BatchPanel, { batch: batch, onClose: () => {
|
|
449
446
|
releaseTakenBatches();
|
|
450
447
|
setBatch(null);
|
|
451
448
|
} }, batch.id) })) : (_jsxs("div", { className: "ea-aside__body", children: [_jsxs("div", { className: "ea-messages", ref: threadRef, onScroll: onThreadScroll, children: [messages.length === 0 ? (_jsx("div", { className: "ea-msg ea-msg--assistant", children: introText(view, bridge, collections, config, copy) })) : null, messages.map((message) => (_jsxs("div", { className: `ea-msg ea-msg--${message.role}`, children: [message.role === "assistant" ? _jsx(ReplyText, { text: message.text }) : message.text, message.candidates?.length ? (_jsx("div", { className: "ea-actions", children: message.candidates.map((candidate) => (_jsx(Button, { buttonStyle: "pill", margin: false, onClick: () => {
|
|
@@ -40,7 +40,7 @@ export function EditorAssistantDocumentBridge() {
|
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
url.searchParams.delete(ASK_PARAM);
|
|
43
|
-
window.history.replaceState(
|
|
43
|
+
window.history.replaceState(null, "", url);
|
|
44
44
|
if (prompt) {
|
|
45
45
|
setPrefill(prompt);
|
|
46
46
|
setOpen(true);
|
|
@@ -32,7 +32,8 @@ export function useBatchLink() {
|
|
|
32
32
|
for (const name of [BATCH_PARAM, BATCH_SCOPE_PARAM, ASK_PARAM]) {
|
|
33
33
|
url.searchParams.delete(name);
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
// Next copies its internal state itself; passing it back skips the router URL update.
|
|
36
|
+
window.history.replaceState(null, "", url.toString());
|
|
36
37
|
if (repeated) {
|
|
37
38
|
// The batch is still in the panel, which the editor closed: show it again.
|
|
38
39
|
setOpen(true);
|
package/dist/domain/batch.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hashBatchRevision } from "../form/batch-revision.js";
|
|
1
2
|
import { changedPaths } from "../form/changed-paths.js";
|
|
2
3
|
import { compileOperations } from "../form/compile.js";
|
|
3
4
|
import { hashFormRevision } from "../form/hash.js";
|
|
@@ -188,10 +189,13 @@ export async function applyBatchTarget(req, options, input) {
|
|
|
188
189
|
if (!saved) {
|
|
189
190
|
return fail("tool_denied", "Document is not accessible.");
|
|
190
191
|
}
|
|
191
|
-
|
|
192
|
+
const fields = entityFields(req, options, { collection: record.targetCollection });
|
|
193
|
+
const revision = record.formRevision?.startsWith("batch-v1:")
|
|
194
|
+
? await hashBatchRevision(saved.form, fields)
|
|
195
|
+
: await hashFormRevision(saved.form);
|
|
196
|
+
if (revision !== record.formRevision) {
|
|
192
197
|
return fail("stale_context", "The document changed after the proposal.");
|
|
193
198
|
}
|
|
194
|
-
const fields = entityFields(req, options, { collection: record.targetCollection });
|
|
195
199
|
const edited = withEdits(stored, input.edits, fields, saved.form);
|
|
196
200
|
if (!edited.ok) {
|
|
197
201
|
// Not the editor's approval to spend: the card can still be saved without the edit.
|
package/dist/domain/propose.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hashBatchRevision } from "../form/batch-revision.js";
|
|
1
2
|
import { changedPaths } from "../form/changed-paths.js";
|
|
2
3
|
import { compileOperations } from "../form/compile.js";
|
|
3
4
|
import { humanDiff, richTextLines } from "../form/diff.js";
|
|
@@ -85,7 +86,11 @@ export async function proposeChange(req, options, input) {
|
|
|
85
86
|
if (!relations.ok) {
|
|
86
87
|
return { ok: false, error: "tool_denied", message: relations.message };
|
|
87
88
|
}
|
|
88
|
-
const formRevision = input.capability === "draft.create"
|
|
89
|
+
const formRevision = input.capability === "draft.create"
|
|
90
|
+
? undefined
|
|
91
|
+
: input.capability === "content.update"
|
|
92
|
+
? await hashBatchRevision(current, fields)
|
|
93
|
+
: await hashFormRevision(current);
|
|
89
94
|
const canonicalHash = await hashFormRevision(typedOperations);
|
|
90
95
|
const nonce = crypto.randomUUID();
|
|
91
96
|
const expiresAt = new Date(Date.now() + TTL_MS).toISOString();
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FieldNode } from "../schema/fields.ts";
|
|
2
|
+
/**
|
|
3
|
+
* A first SQL draft may materialize absent localized columns as null. Hash their empty values
|
|
4
|
+
* consistently, without altering saved content or relaxing checks on nonempty fields or row ids.
|
|
5
|
+
*/
|
|
6
|
+
export declare function hashBatchRevision(data: Record<string, unknown>, fields: FieldNode[]): Promise<string>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { hashFormRevision } from "./hash.js";
|
|
2
|
+
/**
|
|
3
|
+
* A first SQL draft may materialize absent localized columns as null. Hash their empty values
|
|
4
|
+
* consistently, without altering saved content or relaxing checks on nonempty fields or row ids.
|
|
5
|
+
*/
|
|
6
|
+
export async function hashBatchRevision(data, fields) {
|
|
7
|
+
return `batch-v1:${await hashFormRevision(normalizeRecord(data, fields))}`;
|
|
8
|
+
}
|
|
9
|
+
function normalizeRecord(data, fields) {
|
|
10
|
+
const normalized = { ...data };
|
|
11
|
+
for (const field of fields) {
|
|
12
|
+
const value = data[field.name];
|
|
13
|
+
if (field.type === "group" && (value == null || isRecord(value))) {
|
|
14
|
+
normalized[field.name] = normalizeRecord(isRecord(value) ? value : {}, field.fields ?? []);
|
|
15
|
+
}
|
|
16
|
+
else if (field.type === "array" && Array.isArray(value)) {
|
|
17
|
+
normalized[field.name] = value.map((row) => isRecord(row) ? normalizeRecord(row, field.fields ?? []) : row);
|
|
18
|
+
}
|
|
19
|
+
else if (field.type === "blocks" && Array.isArray(value)) {
|
|
20
|
+
normalized[field.name] = value.map((row) => {
|
|
21
|
+
if (!isRecord(row))
|
|
22
|
+
return row;
|
|
23
|
+
const block = field.blocks?.find((entry) => entry.slug === row.blockType);
|
|
24
|
+
return block ? normalizeRecord(row, block.fields) : row;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
normalized[field.name] =
|
|
29
|
+
value === undefined ||
|
|
30
|
+
((field.type === "text" || field.type === "textarea") && value === "")
|
|
31
|
+
? null
|
|
32
|
+
: value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
39
|
+
}
|
|
@@ -836,3 +836,8 @@ html[data-theme="dark"] .ea-trigger--open .ea-trigger__icon {
|
|
|
836
836
|
padding: calc(var(--base) * 0.5) var(--base) calc(var(--base) * 0.8);
|
|
837
837
|
border-top: 1px solid var(--theme-elevation-100);
|
|
838
838
|
}
|
|
839
|
+
|
|
840
|
+
/* The batch keeps its progress when the editor collapses the panel. */
|
|
841
|
+
.ea-aside[hidden] {
|
|
842
|
+
display: none;
|
|
843
|
+
}
|