@simmalugnt-se/payload-editor-assistant 0.13.2 → 0.14.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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.0
4
+
5
+ - A work-list link (`?ask=`) opens the panel with the prompt as a task to start or cancel, like a
6
+ batch, instead of putting it in the message box. The task belongs to the document it opened.
7
+
3
8
  ## 0.13.2
4
9
 
5
10
  - 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 in the message box, and the editor sends it. A link alone never makes the assistant act.
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, prefill, setPrefill, resetConversation, setStatus, setMessages, setPendingApproval, stop, setAbortController, batch, setBatch, } = useAssistantState();
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: [_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(" ") ? (
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, setPrefill, setSelection } = useAssistantState();
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(false);
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 prompt. The editor sends it, so a
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
- return;
35
- }
36
- asked.current = true;
37
- const url = new URL(window.location.href);
38
- const prompt = url.searchParams.get(ASK_PARAM)?.trim();
39
- if (prompt === undefined) {
40
- return;
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
- url.searchParams.delete(ASK_PARAM);
43
- window.history.replaceState(null, "", url);
44
- if (prompt) {
45
- setPrefill(prompt);
43
+ if (asked.current) {
44
+ setTask(asked.current);
46
45
  setOpen(true);
47
46
  }
48
- }, [setOpen, setPrefill]);
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 prompt to put in the message box, e.g. from a work list link; the panel takes it once. */
93
- prefill: string | null;
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
- setPrefill: (prefill: string | null) => void;
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 [prefill, setPrefill] = useState(null);
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
- prefill,
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
- setPrefill,
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
- prefill,
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å.";
@@ -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å.",
@@ -263,6 +263,11 @@ a.ea-result__title {
263
263
  line-height: 1.4;
264
264
  }
265
265
 
266
+ .ea-task__text {
267
+ margin: 0 0 calc(var(--base) * 0.4);
268
+ line-height: 1.5;
269
+ }
270
+
266
271
  .ea-approval__diff {
267
272
  max-height: 40vh;
268
273
  overflow-y: auto;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simmalugnt-se/payload-editor-assistant",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "Context-aware editor assistant plugin for Payload Admin",
5
5
  "keywords": [
6
6
  "payload",