@simmalugnt-se/payload-editor-assistant 0.13.2 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +1 -1
- package/dist/components/EditorAssistantAside.js +5 -9
- package/dist/components/EditorAssistantDocumentBridge.js +19 -17
- package/dist/components/assistant-context.d.ts +3 -3
- package/dist/components/assistant-context.js +4 -4
- package/dist/components/copy.d.ts +6 -0
- package/dist/components/copy.js +6 -0
- package/dist/styles/assistant.css +5 -0
- package/package.json +2 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.1
|
|
4
|
+
|
|
5
|
+
- npm links to simmalugnt.se instead of the private source repository.
|
|
6
|
+
|
|
7
|
+
## 0.14.0
|
|
8
|
+
|
|
9
|
+
- A work-list link (`?ask=`) opens the panel with the prompt as a task to start or cancel, like a
|
|
10
|
+
batch, instead of putting it in the message box. The task belongs to the document it opened.
|
|
11
|
+
|
|
3
12
|
## 0.13.2
|
|
4
13
|
|
|
5
14
|
- Keep compact result cards in the conversation after accepted draft creations, form changes and
|
package/README.md
CHANGED
|
@@ -177,7 +177,7 @@ with a `visual-editing:select` event. Without it the selection follows the form
|
|
|
177
177
|
`@simmalugnt-se/payload-content-health` lists content issues and offers "Fix with the assistant" in
|
|
178
178
|
the collections the assistant works in. The link opens the document with the field selected (a
|
|
179
179
|
`content-health:select` event) and `?ask=<prompt>` in the address: the assistant opens with the
|
|
180
|
-
prompt
|
|
180
|
+
prompt as a task, and the editor starts it. A link alone never makes the assistant act.
|
|
181
181
|
|
|
182
182
|
The plugin publishes its allowlisted collections as `config.admin.custom.editorAssistant.collections`
|
|
183
183
|
so the work list knows where to offer it; the packages do not import each other.
|
|
@@ -19,7 +19,7 @@ import { ReplyText } from "./ReplyText.js";
|
|
|
19
19
|
import { KEEP_SELECTION_ATTR } from "./selection-dom.js";
|
|
20
20
|
import { useTranslationOffer } from "./translation-offer.js";
|
|
21
21
|
export function EditorAssistantAside() {
|
|
22
|
-
const { open, setOpen, status, messages, shortcuts, collections, bridge, formAdapter, pendingApproval, selection, setSelection,
|
|
22
|
+
const { open, setOpen, status, messages, shortcuts, collections, bridge, formAdapter, pendingApproval, selection, setSelection, task, setTask, resetConversation, setStatus, setMessages, setPendingApproval, stop, setAbortController, batch, setBatch, } = useAssistantState();
|
|
23
23
|
const { config, getEntityConfig } = useConfig();
|
|
24
24
|
const router = useRouter();
|
|
25
25
|
const locale = useLocale();
|
|
@@ -33,13 +33,6 @@ export function EditorAssistantAside() {
|
|
|
33
33
|
const visibleShortcuts = shortcuts.filter((shortcut) => shortcutMatches(shortcut, view));
|
|
34
34
|
const [draft, setDraft] = useState("");
|
|
35
35
|
const attachments = useChatImages(`${pathname}:${locale.code}`, status === "streaming" || Boolean(batch), copy);
|
|
36
|
-
// A ready prompt replaces the draft; it is only sent when the editor sends it.
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
if (prefill !== null) {
|
|
39
|
-
setDraft(prefill);
|
|
40
|
-
setPrefill(null);
|
|
41
|
-
}
|
|
42
|
-
}, [prefill, setPrefill]);
|
|
43
36
|
const threadRef = useRef(null);
|
|
44
37
|
const composerRef = useRef(null);
|
|
45
38
|
// Grow on typing/prefill and remeasure when the panel reopens or its width changes.
|
|
@@ -506,7 +499,10 @@ export function EditorAssistantAside() {
|
|
|
506
499
|
setBatch(null);
|
|
507
500
|
} }, 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.result ? (_jsx(CompletedResultCard, { result: message.result, copy: copy })) : message.role === "assistant" ? (_jsx(ReplyText, { text: message.text })) : (message.text), _jsx(ChatImages, { images: message.images, copy: copy }), message.candidates?.length ? (_jsx("div", { className: "ea-actions", children: message.candidates.map((candidate) => (_jsx(Button, { buttonStyle: "pill", margin: false, onClick: () => {
|
|
508
501
|
router.push(candidate.href);
|
|
509
|
-
}, size: "small", children: candidate.label }, candidate.href))) })) : null] }, message.id))), status === "streaming" ? (_jsxs("div", { className: "ea-msg ea-msg--working", children: [_jsxs("span", { className: "ea-dots", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), _jsx("span", { children: step ? talk.steps[step] : talk.working })] })) : null] }), pendingApproval ? (_jsxs("div", { className: "ea-approval", children: [
|
|
502
|
+
}, size: "small", children: candidate.label }, candidate.href))) })) : null] }, message.id))), status === "streaming" ? (_jsxs("div", { className: "ea-msg ea-msg--working", children: [_jsxs("span", { className: "ea-dots", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }), _jsx("span", { children: step ? talk.steps[step] : talk.working })] })) : null] }), task && !pendingApproval && status !== "streaming" ? (_jsxs("div", { className: "ea-approval ea-task", children: [_jsxs("p", { className: "ea-task__text", children: [_jsxs("strong", { children: [copy.task, ":"] }), " ", task] }), _jsx("p", { className: "ea-approval__hint", children: copy.taskHint }), _jsxs("div", { className: "ea-actions", children: [_jsx(Button, { buttonStyle: "primary", disabled: attachments.preparing, margin: false, onClick: () => {
|
|
503
|
+
setTask(null);
|
|
504
|
+
void send(task);
|
|
505
|
+
}, size: "medium", children: copy.start }), _jsx(Button, { buttonStyle: "secondary", margin: false, onClick: () => setTask(null), size: "medium", children: copy.cancel })] })] })) : null, pendingApproval ? (_jsxs("div", { className: "ea-approval", children: [_jsx("h3", { children: pendingApproval.title }), pendingApproval.applied ? (_jsxs("p", { className: "ea-approval__hint", children: [autosave ? copy.previewHintAutosave : copy.previewHint, pendingApproval.earlier?.length ? ` ${copy.refinedHint}` : ""] })) : null, pendingApproval.lines?.length ? (_jsx("div", { className: "ea-approval__review", children: _jsx(ProposalReview, { lines: pendingApproval.lines, language: uiLanguage(i18n.language) }) })) : (_jsx("ul", { className: "ea-approval__diff", children: pendingApproval.summary.split("\n").map((line, index) => line.startsWith(" ") ? (
|
|
510
506
|
// biome-ignore lint/suspicious/noArrayIndexKey: lines are static for one approval
|
|
511
507
|
_jsx("li", { className: "ea-approval__sub", children: line.trim() }, index)) : (
|
|
512
508
|
// biome-ignore lint/suspicious/noArrayIndexKey: lines are static for one approval
|
|
@@ -6,14 +6,14 @@ import { ASK_PARAM, CONTENT_HEALTH_SELECT_EVENT, selectionFromElement, selection
|
|
|
6
6
|
/** Collapsed rows render their labels only once visual editing has opened them. */
|
|
7
7
|
const RELABEL_MS = [150, 500, 1200];
|
|
8
8
|
export function EditorAssistantDocumentBridge() {
|
|
9
|
-
const { setBridge, setFormAdapter, setOpen,
|
|
9
|
+
const { setBridge, setFormAdapter, setOpen, setSelection, setTask } = useAssistantState();
|
|
10
10
|
const documentInfo = useDocumentInfo();
|
|
11
11
|
const locale = useLocale();
|
|
12
12
|
const modified = useFormModified();
|
|
13
13
|
const form = useDocumentForm();
|
|
14
14
|
const formRef = useRef(form);
|
|
15
15
|
// Kept across effect re-runs: the parameter is gone from the address after the first read.
|
|
16
|
-
const asked = useRef(
|
|
16
|
+
const asked = useRef(undefined);
|
|
17
17
|
useEffect(() => {
|
|
18
18
|
formRef.current = form;
|
|
19
19
|
});
|
|
@@ -27,25 +27,27 @@ export function EditorAssistantDocumentBridge() {
|
|
|
27
27
|
setFormAdapter(null);
|
|
28
28
|
};
|
|
29
29
|
}, [setFormAdapter]);
|
|
30
|
-
// A work list link (`?ask=`) opens the panel with a ready
|
|
31
|
-
// link alone never makes the assistant act. The parameter is removed once read
|
|
30
|
+
// A work list link (`?ask=`) opens the panel with a ready task. The editor starts it, so a
|
|
31
|
+
// link alone never makes the assistant act. The parameter is removed once read, and the task
|
|
32
|
+
// belongs to this document: it goes when the editor leaves.
|
|
32
33
|
useEffect(() => {
|
|
33
|
-
if (asked.current) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
if (asked.current === undefined) {
|
|
35
|
+
const url = new URL(window.location.href);
|
|
36
|
+
const prompt = url.searchParams.get(ASK_PARAM)?.trim().slice(0, 2000);
|
|
37
|
+
asked.current = prompt || null;
|
|
38
|
+
if (prompt !== undefined) {
|
|
39
|
+
url.searchParams.delete(ASK_PARAM);
|
|
40
|
+
window.history.replaceState(null, "", url);
|
|
41
|
+
}
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (prompt) {
|
|
45
|
-
setPrefill(prompt);
|
|
43
|
+
if (asked.current) {
|
|
44
|
+
setTask(asked.current);
|
|
46
45
|
setOpen(true);
|
|
47
46
|
}
|
|
48
|
-
|
|
47
|
+
return () => {
|
|
48
|
+
setTask(null);
|
|
49
|
+
};
|
|
50
|
+
}, [setOpen, setTask]);
|
|
49
51
|
// "This" in the editor's message is what they last clicked or focused: a field, a block or a row,
|
|
50
52
|
// in the form, (through visual editing) in the preview, or brought into view by content health. Clicks elsewhere in Admin clear it;
|
|
51
53
|
// clicks in the assistant itself keep it, so the editor can click, then type.
|
|
@@ -89,8 +89,8 @@ type AssistantContextValue = {
|
|
|
89
89
|
pendingApproval: PendingApproval | null;
|
|
90
90
|
/** What the editor last clicked or focused in the open form. */
|
|
91
91
|
selection: DomSelection | null;
|
|
92
|
-
/** A
|
|
93
|
-
|
|
92
|
+
/** A ready task from a work list link: the panel shows it until the editor starts or cancels it. */
|
|
93
|
+
task: string | null;
|
|
94
94
|
batch: BatchRequest | null;
|
|
95
95
|
setBatch: (batch: BatchRequest | null) => void;
|
|
96
96
|
/** `remember: false` when the panel makes way on its own (see nav-room.ts), not the editor. */
|
|
@@ -101,7 +101,7 @@ type AssistantContextValue = {
|
|
|
101
101
|
setFormAdapter: (adapter: FormAdapter | null) => void;
|
|
102
102
|
setPendingApproval: (pending: PendingApproval | null) => void;
|
|
103
103
|
setSelection: (selection: DomSelection | null) => void;
|
|
104
|
-
|
|
104
|
+
setTask: (task: string | null) => void;
|
|
105
105
|
setStatus: (status: AssistantChromeStatus) => void;
|
|
106
106
|
setMessages: (updater: (current: AssistantMessage[]) => AssistantMessage[]) => void;
|
|
107
107
|
resetConversation: () => void;
|
|
@@ -12,7 +12,7 @@ export function AssistantStateProvider({ children, shortcuts, collections, userI
|
|
|
12
12
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
13
13
|
const [abortController, setAbortController] = useState(null);
|
|
14
14
|
const [selection, setSelection] = useState(null);
|
|
15
|
-
const [
|
|
15
|
+
const [task, setTask] = useState(null);
|
|
16
16
|
const [batch, setBatch] = useState(null);
|
|
17
17
|
// Read after mount, so the server render (closed) and the first client render agree.
|
|
18
18
|
useEffect(() => {
|
|
@@ -76,7 +76,7 @@ export function AssistantStateProvider({ children, shortcuts, collections, userI
|
|
|
76
76
|
formAdapter,
|
|
77
77
|
pendingApproval,
|
|
78
78
|
selection,
|
|
79
|
-
|
|
79
|
+
task,
|
|
80
80
|
batch,
|
|
81
81
|
setBatch,
|
|
82
82
|
setOpen,
|
|
@@ -84,7 +84,7 @@ export function AssistantStateProvider({ children, shortcuts, collections, userI
|
|
|
84
84
|
setFormAdapter,
|
|
85
85
|
setPendingApproval,
|
|
86
86
|
setSelection,
|
|
87
|
-
|
|
87
|
+
setTask,
|
|
88
88
|
setStatus,
|
|
89
89
|
setMessages,
|
|
90
90
|
resetConversation,
|
|
@@ -101,7 +101,7 @@ export function AssistantStateProvider({ children, shortcuts, collections, userI
|
|
|
101
101
|
formAdapter,
|
|
102
102
|
pendingApproval,
|
|
103
103
|
selection,
|
|
104
|
-
|
|
104
|
+
task,
|
|
105
105
|
batch,
|
|
106
106
|
setOpen,
|
|
107
107
|
resetConversation,
|
|
@@ -40,6 +40,9 @@ export declare const COPY: {
|
|
|
40
40
|
readonly undo: "Undo";
|
|
41
41
|
readonly undoAnyway: "Undo anyway";
|
|
42
42
|
readonly cancel: "Cancel";
|
|
43
|
+
readonly task: "Task";
|
|
44
|
+
readonly taskHint: "The assistant suggests a change in the form, which you keep or undo. Nothing is saved yet.";
|
|
45
|
+
readonly start: "Start";
|
|
43
46
|
readonly previewHint: "Shown in the form and the preview. Nothing is saved yet.";
|
|
44
47
|
readonly previewHintAutosave: "Shown in the form and the preview. The draft saves itself, but nothing is published.";
|
|
45
48
|
readonly undoLosesEdits: "You edited the form after the suggestion. Undo removes those edits too.";
|
|
@@ -113,6 +116,9 @@ export declare const COPY: {
|
|
|
113
116
|
readonly undo: "Ångra";
|
|
114
117
|
readonly undoAnyway: "Ångra ändå";
|
|
115
118
|
readonly cancel: "Avbryt";
|
|
119
|
+
readonly task: "Uppgift";
|
|
120
|
+
readonly taskHint: "Assistenten föreslår en ändring i formuläret som du behåller eller ångrar. Inget sparas än.";
|
|
121
|
+
readonly start: "Starta";
|
|
116
122
|
readonly previewHint: "Syns i formuläret och förhandsvisningen. Inget är sparat än.";
|
|
117
123
|
readonly previewHintAutosave: "Syns i formuläret och förhandsvisningen. Utkastet sparas automatiskt, men inget publiceras.";
|
|
118
124
|
readonly undoLosesEdits: "Du har ändrat i formuläret efter förslaget. Ångra tar bort de ändringarna också.";
|
package/dist/components/copy.js
CHANGED
|
@@ -41,6 +41,9 @@ export const COPY = {
|
|
|
41
41
|
undo: "Undo",
|
|
42
42
|
undoAnyway: "Undo anyway",
|
|
43
43
|
cancel: "Cancel",
|
|
44
|
+
task: "Task",
|
|
45
|
+
taskHint: "The assistant suggests a change in the form, which you keep or undo. Nothing is saved yet.",
|
|
46
|
+
start: "Start",
|
|
44
47
|
previewHint: "Shown in the form and the preview. Nothing is saved yet.",
|
|
45
48
|
previewHintAutosave: "Shown in the form and the preview. The draft saves itself, but nothing is published.",
|
|
46
49
|
undoLosesEdits: "You edited the form after the suggestion. Undo removes those edits too.",
|
|
@@ -114,6 +117,9 @@ export const COPY = {
|
|
|
114
117
|
undo: "Ångra",
|
|
115
118
|
undoAnyway: "Ångra ändå",
|
|
116
119
|
cancel: "Avbryt",
|
|
120
|
+
task: "Uppgift",
|
|
121
|
+
taskHint: "Assistenten föreslår en ändring i formuläret som du behåller eller ångrar. Inget sparas än.",
|
|
122
|
+
start: "Starta",
|
|
117
123
|
previewHint: "Syns i formuläret och förhandsvisningen. Inget är sparat än.",
|
|
118
124
|
previewHintAutosave: "Syns i formuläret och förhandsvisningen. Utkastet sparas automatiskt, men inget publiceras.",
|
|
119
125
|
undoLosesEdits: "Du har ändrat i formuläret efter förslaget. Ångra tar bort de ändringarna också.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simmalugnt-se/payload-editor-assistant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Context-aware editor assistant plugin for Payload Admin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -8,11 +8,7 @@
|
|
|
8
8
|
],
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"author": "Simmalugnt AB",
|
|
11
|
-
"
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/simmalugnt-se/payload-plugins.git",
|
|
14
|
-
"directory": "packages/payload-editor-assistant"
|
|
15
|
-
},
|
|
11
|
+
"homepage": "https://simmalugnt.se",
|
|
16
12
|
"type": "module",
|
|
17
13
|
"engines": {
|
|
18
14
|
"node": ">=22"
|