@simmalugnt-se/payload-editor-assistant 0.10.0 → 0.11.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 +23 -0
- package/README.md +35 -0
- package/dist/batch/targets.d.ts +25 -0
- package/dist/batch/targets.js +47 -0
- package/dist/capabilities.d.ts +4 -4
- package/dist/capabilities.js +3 -0
- package/dist/components/BatchPanel.d.ts +9 -0
- package/dist/components/BatchPanel.js +161 -0
- package/dist/components/EditorAssistantAside.js +4 -3
- package/dist/components/EditorAssistantProvider.js +2 -0
- package/dist/components/assistant-context.d.ts +11 -0
- package/dist/components/assistant-context.js +4 -0
- package/dist/components/batch-link.d.ts +5 -0
- package/dist/components/batch-link.js +35 -0
- package/dist/components/batch-run.d.ts +65 -0
- package/dist/components/batch-run.js +67 -0
- package/dist/components/copy.d.ts +82 -0
- package/dist/components/copy.js +81 -0
- package/dist/config/validate.js +3 -0
- package/dist/domain/audit.d.ts +2 -0
- package/dist/domain/audit.js +1 -0
- package/dist/domain/batch.d.ts +74 -0
- package/dist/domain/batch.js +342 -0
- package/dist/domain/content.d.ts +16 -0
- package/dist/domain/content.js +14 -5
- package/dist/domain/decide.js +4 -0
- package/dist/domain/guard.d.ts +3 -1
- package/dist/domain/guard.js +2 -2
- package/dist/domain/propose.d.ts +27 -1
- package/dist/domain/propose.js +88 -5
- package/dist/endpoints/batch.d.ts +8 -0
- package/dist/endpoints/batch.js +102 -0
- package/dist/package-name.d.ts +3 -2
- package/dist/package-name.js +3 -2
- package/dist/plugin.js +12 -2
- package/dist/runtime/agent.d.ts +4 -2
- package/dist/runtime/agent.js +5 -3
- package/dist/runtime/system-prompt.js +12 -1
- package/dist/runtime/tools.d.ts +15 -2
- package/dist/runtime/tools.js +20 -3
- package/dist/runtime/voice.d.ts +2 -2
- package/dist/runtime/voice.js +7 -3
- package/dist/styles/assistant.css +201 -0
- package/package.json +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The batch panel's cards and what each step does to them. Pure: the panel runs the requests one
|
|
3
|
+
* after the other and feeds the answers in here.
|
|
4
|
+
*/
|
|
5
|
+
import { batchTargetKey } from "../batch/targets.js";
|
|
6
|
+
export function initialCards(targets, infos) {
|
|
7
|
+
return targets.map((target) => {
|
|
8
|
+
const key = batchTargetKey(target);
|
|
9
|
+
const info = infos?.find((entry) => entry.key === key);
|
|
10
|
+
return {
|
|
11
|
+
key,
|
|
12
|
+
target,
|
|
13
|
+
title: info?.title ?? target.id,
|
|
14
|
+
thumbnail: info?.thumbnail,
|
|
15
|
+
drafts: info?.drafts ?? false,
|
|
16
|
+
status: info && !info.allowed ? "unavailable" : "waiting",
|
|
17
|
+
checked: false,
|
|
18
|
+
edits: {},
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Documents still to ask the assistant about, in order. */
|
|
23
|
+
export function waitingKeys(cards) {
|
|
24
|
+
return cards.filter((card) => card.status === "waiting").map((card) => card.key);
|
|
25
|
+
}
|
|
26
|
+
export function cardAfterPropose(card, answer) {
|
|
27
|
+
const base = { ...card, edits: {}, proposal: undefined, error: undefined };
|
|
28
|
+
if (!answer.ok) {
|
|
29
|
+
return answer.error === "nothing_to_do"
|
|
30
|
+
? { ...base, status: "nothing", checked: false }
|
|
31
|
+
: { ...base, status: "failed", error: answer.error, text: answer.message, checked: false };
|
|
32
|
+
}
|
|
33
|
+
if (!answer.proposal) {
|
|
34
|
+
return { ...base, status: "nothing", text: answer.text, checked: false };
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
...base,
|
|
38
|
+
status: "proposed",
|
|
39
|
+
text: answer.text,
|
|
40
|
+
proposal: answer.proposal,
|
|
41
|
+
checked: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** What saving the batch does per card: apply the checked proposals, reject the others. */
|
|
45
|
+
export function savePlan(cards) {
|
|
46
|
+
return cards
|
|
47
|
+
.filter((card) => card.status === "proposed" && card.proposal)
|
|
48
|
+
.map((card) => ({ key: card.key, action: card.checked ? "apply" : "reject" }));
|
|
49
|
+
}
|
|
50
|
+
/** The editor's wording where it differs from the proposal's. */
|
|
51
|
+
export function changedEdits(card) {
|
|
52
|
+
const changed = {};
|
|
53
|
+
for (const line of card.proposal?.lines ?? []) {
|
|
54
|
+
const edited = card.edits[line.path];
|
|
55
|
+
if (line.edit && edited !== undefined && edited !== line.edit.value) {
|
|
56
|
+
changed[line.path] = edited;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return changed;
|
|
60
|
+
}
|
|
61
|
+
export function cardAfterApply(card, answer) {
|
|
62
|
+
if (answer.ok) {
|
|
63
|
+
return { ...card, status: answer.status };
|
|
64
|
+
}
|
|
65
|
+
const status = answer.error === "stale_context" ? "stale" : answer.error === "locked" ? "locked" : "failed";
|
|
66
|
+
return { ...card, status, error: answer.error, text: answer.message };
|
|
67
|
+
}
|
|
@@ -141,3 +141,85 @@ export declare function translateAction(language: UiLanguage, source: string): {
|
|
|
141
141
|
label: string;
|
|
142
142
|
prompt: string;
|
|
143
143
|
};
|
|
144
|
+
/** What the batch panel says: fixing many documents at once from the work list. */
|
|
145
|
+
export declare const BATCH_COPY: {
|
|
146
|
+
readonly en: {
|
|
147
|
+
readonly title: (count: number) => string;
|
|
148
|
+
readonly task: "Task";
|
|
149
|
+
readonly intro: (count: number) => string;
|
|
150
|
+
readonly start: "Start";
|
|
151
|
+
readonly continue: "Continue";
|
|
152
|
+
readonly stop: "Stop";
|
|
153
|
+
readonly cancel: "Cancel";
|
|
154
|
+
readonly close: "Close";
|
|
155
|
+
readonly progress: (done: number, total: number) => string;
|
|
156
|
+
readonly save: (count: number) => string;
|
|
157
|
+
readonly saving: "Saving…";
|
|
158
|
+
readonly review: "Check the suggestions. Uncheck any you do not want, or change the wording before saving.";
|
|
159
|
+
readonly savesDraft: "Saved as a draft; nothing is published.";
|
|
160
|
+
readonly savesDirectly: "Saved directly: this collection has no drafts.";
|
|
161
|
+
readonly before: "Before";
|
|
162
|
+
readonly empty: "empty";
|
|
163
|
+
readonly retry: "Try again";
|
|
164
|
+
readonly retryWith: "What should be different? (optional)";
|
|
165
|
+
readonly retrySend: "Ask again";
|
|
166
|
+
readonly open: "Open";
|
|
167
|
+
readonly select: (title: string) => string;
|
|
168
|
+
readonly status: {
|
|
169
|
+
readonly waiting: "Waiting";
|
|
170
|
+
readonly working: "Working…";
|
|
171
|
+
readonly proposed: "Suggested";
|
|
172
|
+
readonly nothing: "No change";
|
|
173
|
+
readonly failed: "Failed";
|
|
174
|
+
readonly saving: "Saving…";
|
|
175
|
+
readonly saved: "Saved";
|
|
176
|
+
readonly draft: "Saved as draft";
|
|
177
|
+
readonly stale: "Changed since the suggestion";
|
|
178
|
+
readonly locked: "Someone else is editing it";
|
|
179
|
+
readonly rejected: "Not saved";
|
|
180
|
+
readonly unavailable: "Not available";
|
|
181
|
+
};
|
|
182
|
+
readonly staleHint: "Someone changed the document after the suggestion. Open it, or try again.";
|
|
183
|
+
readonly summary: (saved: number, skipped: number) => string;
|
|
184
|
+
};
|
|
185
|
+
readonly sv: {
|
|
186
|
+
readonly title: (count: number) => string;
|
|
187
|
+
readonly task: "Uppgift";
|
|
188
|
+
readonly intro: (count: number) => string;
|
|
189
|
+
readonly start: "Starta";
|
|
190
|
+
readonly continue: "Fortsätt";
|
|
191
|
+
readonly stop: "Stoppa";
|
|
192
|
+
readonly cancel: "Avbryt";
|
|
193
|
+
readonly close: "Stäng";
|
|
194
|
+
readonly progress: (done: number, total: number) => string;
|
|
195
|
+
readonly save: (count: number) => string;
|
|
196
|
+
readonly saving: "Sparar…";
|
|
197
|
+
readonly review: "Granska förslagen. Kryssa ur dem du inte vill ha, eller ändra formuleringen innan du sparar.";
|
|
198
|
+
readonly savesDraft: "Sparas som utkast; inget publiceras.";
|
|
199
|
+
readonly savesDirectly: "Sparas direkt: den här samlingen har inga utkast.";
|
|
200
|
+
readonly before: "Före";
|
|
201
|
+
readonly empty: "tomt";
|
|
202
|
+
readonly retry: "Försök igen";
|
|
203
|
+
readonly retryWith: "Vad ska bli annorlunda? (valfritt)";
|
|
204
|
+
readonly retrySend: "Fråga igen";
|
|
205
|
+
readonly open: "Öppna";
|
|
206
|
+
readonly select: (title: string) => string;
|
|
207
|
+
readonly status: {
|
|
208
|
+
readonly waiting: "Väntar";
|
|
209
|
+
readonly working: "Arbetar…";
|
|
210
|
+
readonly proposed: "Förslag";
|
|
211
|
+
readonly nothing: "Ingen ändring";
|
|
212
|
+
readonly failed: "Misslyckades";
|
|
213
|
+
readonly saving: "Sparar…";
|
|
214
|
+
readonly saved: "Sparat";
|
|
215
|
+
readonly draft: "Sparat som utkast";
|
|
216
|
+
readonly stale: "Ändrat sedan förslaget";
|
|
217
|
+
readonly locked: "Någon annan redigerar";
|
|
218
|
+
readonly rejected: "Inte sparat";
|
|
219
|
+
readonly unavailable: "Inte tillgängligt";
|
|
220
|
+
};
|
|
221
|
+
readonly staleHint: "Någon ändrade dokumentet efter förslaget. Öppna det, eller försök igen.";
|
|
222
|
+
readonly summary: (saved: number, skipped: number) => string;
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
export type BatchCopy = (typeof BATCH_COPY)[UiLanguage];
|
package/dist/components/copy.js
CHANGED
|
@@ -151,3 +151,84 @@ function languageName(language, code) {
|
|
|
151
151
|
return code;
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
/** What the batch panel says: fixing many documents at once from the work list. */
|
|
155
|
+
export const BATCH_COPY = {
|
|
156
|
+
en: {
|
|
157
|
+
title: (count) => `Fix ${count} ${count === 1 ? "document" : "documents"}`,
|
|
158
|
+
task: "Task",
|
|
159
|
+
intro: (count) => `The assistant goes through them one at a time and suggests a change for each: ${count} ${count === 1 ? "request" : "requests"} to the AI model. Nothing is saved until you save the ones you want.`,
|
|
160
|
+
start: "Start",
|
|
161
|
+
continue: "Continue",
|
|
162
|
+
stop: "Stop",
|
|
163
|
+
cancel: "Cancel",
|
|
164
|
+
close: "Close",
|
|
165
|
+
progress: (done, total) => `${done} of ${total}`,
|
|
166
|
+
save: (count) => `Save ${count} selected`,
|
|
167
|
+
saving: "Saving…",
|
|
168
|
+
review: "Check the suggestions. Uncheck any you do not want, or change the wording before saving.",
|
|
169
|
+
savesDraft: "Saved as a draft; nothing is published.",
|
|
170
|
+
savesDirectly: "Saved directly: this collection has no drafts.",
|
|
171
|
+
before: "Before",
|
|
172
|
+
empty: "empty",
|
|
173
|
+
retry: "Try again",
|
|
174
|
+
retryWith: "What should be different? (optional)",
|
|
175
|
+
retrySend: "Ask again",
|
|
176
|
+
open: "Open",
|
|
177
|
+
select: (title) => `Save the change to ${title}`,
|
|
178
|
+
status: {
|
|
179
|
+
waiting: "Waiting",
|
|
180
|
+
working: "Working…",
|
|
181
|
+
proposed: "Suggested",
|
|
182
|
+
nothing: "No change",
|
|
183
|
+
failed: "Failed",
|
|
184
|
+
saving: "Saving…",
|
|
185
|
+
saved: "Saved",
|
|
186
|
+
draft: "Saved as draft",
|
|
187
|
+
stale: "Changed since the suggestion",
|
|
188
|
+
locked: "Someone else is editing it",
|
|
189
|
+
rejected: "Not saved",
|
|
190
|
+
unavailable: "Not available",
|
|
191
|
+
},
|
|
192
|
+
staleHint: "Someone changed the document after the suggestion. Open it, or try again.",
|
|
193
|
+
summary: (saved, skipped) => skipped ? `${saved} saved, ${skipped} not saved.` : `${saved} saved.`,
|
|
194
|
+
},
|
|
195
|
+
sv: {
|
|
196
|
+
title: (count) => `Åtgärda ${count} dokument`,
|
|
197
|
+
task: "Uppgift",
|
|
198
|
+
intro: (count) => `Assistenten går igenom dem ett i taget och föreslår en ändring för varje: ${count} anrop till AI-modellen. Inget sparas förrän du sparar de förslag du vill ha.`,
|
|
199
|
+
start: "Starta",
|
|
200
|
+
continue: "Fortsätt",
|
|
201
|
+
stop: "Stoppa",
|
|
202
|
+
cancel: "Avbryt",
|
|
203
|
+
close: "Stäng",
|
|
204
|
+
progress: (done, total) => `${done} av ${total}`,
|
|
205
|
+
save: (count) => `Spara ${count} valda`,
|
|
206
|
+
saving: "Sparar…",
|
|
207
|
+
review: "Granska förslagen. Kryssa ur dem du inte vill ha, eller ändra formuleringen innan du sparar.",
|
|
208
|
+
savesDraft: "Sparas som utkast; inget publiceras.",
|
|
209
|
+
savesDirectly: "Sparas direkt: den här samlingen har inga utkast.",
|
|
210
|
+
before: "Före",
|
|
211
|
+
empty: "tomt",
|
|
212
|
+
retry: "Försök igen",
|
|
213
|
+
retryWith: "Vad ska bli annorlunda? (valfritt)",
|
|
214
|
+
retrySend: "Fråga igen",
|
|
215
|
+
open: "Öppna",
|
|
216
|
+
select: (title) => `Spara ändringen i ${title}`,
|
|
217
|
+
status: {
|
|
218
|
+
waiting: "Väntar",
|
|
219
|
+
working: "Arbetar…",
|
|
220
|
+
proposed: "Förslag",
|
|
221
|
+
nothing: "Ingen ändring",
|
|
222
|
+
failed: "Misslyckades",
|
|
223
|
+
saving: "Sparar…",
|
|
224
|
+
saved: "Sparat",
|
|
225
|
+
draft: "Sparat som utkast",
|
|
226
|
+
stale: "Ändrat sedan förslaget",
|
|
227
|
+
locked: "Någon annan redigerar",
|
|
228
|
+
rejected: "Inte sparat",
|
|
229
|
+
unavailable: "Inte tillgängligt",
|
|
230
|
+
},
|
|
231
|
+
staleHint: "Någon ändrade dokumentet efter förslaget. Öppna det, eller försök igen.",
|
|
232
|
+
summary: (saved, skipped) => skipped ? `${saved} sparade, ${skipped} inte sparade.` : `${saved} sparade.`,
|
|
233
|
+
},
|
|
234
|
+
};
|
package/dist/config/validate.js
CHANGED
|
@@ -39,6 +39,9 @@ function validateEntityMap(kind, map, rules) {
|
|
|
39
39
|
if (capability === "draft.create" && !rules.allowDraftCreate) {
|
|
40
40
|
throw new Error(`payload-editor-assistant: globals cannot enable draft.create ("${slug}").`);
|
|
41
41
|
}
|
|
42
|
+
if (capability === "content.update" && kind === "globals") {
|
|
43
|
+
throw new Error(`payload-editor-assistant: content.update is for collections, not globals ("${slug}").`);
|
|
44
|
+
}
|
|
42
45
|
if (capability === "media.view" && kind === "globals") {
|
|
43
46
|
throw new Error(`payload-editor-assistant: media.view is for upload collections, not globals ("${slug}").`);
|
|
44
47
|
}
|
package/dist/domain/audit.d.ts
CHANGED
package/dist/domain/audit.js
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { PayloadRequest } from "payload";
|
|
2
|
+
import type { BatchScope, BatchTarget } from "../batch/targets.ts";
|
|
3
|
+
import type { ValidatedEditorAssistantOptions } from "../config/validate.ts";
|
|
4
|
+
import { runAgentTurn } from "../runtime/agent.ts";
|
|
5
|
+
import type { ProposalLine } from "./propose.ts";
|
|
6
|
+
export type BatchProposeInput = {
|
|
7
|
+
target: BatchTarget;
|
|
8
|
+
scope: BatchScope;
|
|
9
|
+
/** The task, as content health words it for the check. */
|
|
10
|
+
prompt: string;
|
|
11
|
+
/** What the editor added when asking again, e.g. "shorter". */
|
|
12
|
+
instruction?: string;
|
|
13
|
+
/** Admin's language: the reply and the change list are shown in it. */
|
|
14
|
+
language: "sv" | "en";
|
|
15
|
+
batchId: string;
|
|
16
|
+
};
|
|
17
|
+
export type BatchProposeResult = {
|
|
18
|
+
ok: true;
|
|
19
|
+
/** The model's one-sentence reply, shown with the change or instead of one. */
|
|
20
|
+
text: string;
|
|
21
|
+
proposal?: {
|
|
22
|
+
approvalId: string;
|
|
23
|
+
nonce: string;
|
|
24
|
+
lines: ProposalLine[];
|
|
25
|
+
};
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
error: string;
|
|
29
|
+
message?: string;
|
|
30
|
+
};
|
|
31
|
+
type Run = typeof runAgentTurn;
|
|
32
|
+
/**
|
|
33
|
+
* One document of a batch: a turn on the saved document, as if it were open with the finding's field
|
|
34
|
+
* selected. The model may only propose a `content.update` within the target's fields.
|
|
35
|
+
*/
|
|
36
|
+
export declare function proposeBatchTarget(req: PayloadRequest, options: ValidatedEditorAssistantOptions, input: BatchProposeInput, run?: Run): Promise<BatchProposeResult>;
|
|
37
|
+
export type BatchApplyInput = {
|
|
38
|
+
action: "apply" | "reject";
|
|
39
|
+
approvalId: string;
|
|
40
|
+
nonce: string;
|
|
41
|
+
batchId: string;
|
|
42
|
+
/** The editor's own wording for plain text values the proposal set, by path. */
|
|
43
|
+
edits?: Record<string, string>;
|
|
44
|
+
};
|
|
45
|
+
export type BatchApplyResult = {
|
|
46
|
+
ok: true;
|
|
47
|
+
status: "draft" | "saved" | "rejected";
|
|
48
|
+
} | {
|
|
49
|
+
ok: false;
|
|
50
|
+
error: "tool_denied" | "stale_context" | "locked";
|
|
51
|
+
message: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Saves one batch proposal to the document, or rejects it. Checked again as it is saved: the editor,
|
|
55
|
+
* the one-time approval, `content.update`, and that the saved document is still the one the proposal
|
|
56
|
+
* was made for. A collection with drafts gets a draft; one without is saved.
|
|
57
|
+
*/
|
|
58
|
+
export declare function applyBatchTarget(req: PayloadRequest, options: ValidatedEditorAssistantOptions, input: BatchApplyInput): Promise<BatchApplyResult>;
|
|
59
|
+
export type BatchTargetInfo = {
|
|
60
|
+
key: string;
|
|
61
|
+
/** The document's title, file name or id. */
|
|
62
|
+
title: string;
|
|
63
|
+
/** A small image for upload collections, as Payload serves it. */
|
|
64
|
+
thumbnail?: string;
|
|
65
|
+
/** Saved as a draft rather than directly. */
|
|
66
|
+
drafts: boolean;
|
|
67
|
+
/** The editor may read it and the assistant may change it in a batch. */
|
|
68
|
+
allowed: boolean;
|
|
69
|
+
};
|
|
70
|
+
/** What the panel shows before the batch runs: titles and whether each document can be changed. */
|
|
71
|
+
export declare function describeBatchTargets(req: PayloadRequest, options: ValidatedEditorAssistantOptions, targets: Array<BatchTarget & {
|
|
72
|
+
key: string;
|
|
73
|
+
}>): Promise<BatchTargetInfo[]>;
|
|
74
|
+
export {};
|