@simmalugnt-se/payload-editor-assistant 0.9.1 → 0.11.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 +27 -0
- package/README.md +40 -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 +105 -9
- package/dist/endpoints/batch.d.ts +8 -0
- package/dist/endpoints/batch.js +102 -0
- package/dist/form/diff.js +15 -2
- package/dist/form/relation-shape.d.ts +9 -0
- package/dist/form/relation-shape.js +74 -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/context.d.ts +4 -0
- package/dist/runtime/context.js +9 -1
- package/dist/runtime/system-prompt.js +14 -3
- 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/schema/fields.d.ts +3 -0
- package/dist/schema/fields.js +2 -0
- package/dist/styles/assistant.css +203 -0
- package/package.json +1 -1
package/dist/runtime/tools.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PayloadRequest } from "payload";
|
|
2
2
|
import type { ValidatedEditorAssistantOptions } from "../config/validate.ts";
|
|
3
|
-
import { type ProposalPreview } from "../domain/propose.ts";
|
|
3
|
+
import { type ProposalLine, type ProposalPreview } from "../domain/propose.ts";
|
|
4
4
|
import type { AdminView } from "../view.ts";
|
|
5
5
|
import type { ChatLanguage } from "./language.ts";
|
|
6
6
|
export type ToolBridge = {
|
|
@@ -22,6 +22,15 @@ export type ToolBridge = {
|
|
|
22
22
|
openProposal?: {
|
|
23
23
|
changedPaths: string[];
|
|
24
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* One document of a batch from the work list: the saved document stands in for the form, and a
|
|
27
|
+
* proposal is a `content.update` that may reach only `allowedPaths`. Set by the server, never the
|
|
28
|
+
* client.
|
|
29
|
+
*/
|
|
30
|
+
batch?: {
|
|
31
|
+
allowedPaths: string[];
|
|
32
|
+
requestId?: string;
|
|
33
|
+
};
|
|
25
34
|
};
|
|
26
35
|
export type ToolContext = {
|
|
27
36
|
req: PayloadRequest;
|
|
@@ -42,8 +51,10 @@ export type ToolSuccess = {
|
|
|
42
51
|
summary: string;
|
|
43
52
|
approvalId: string;
|
|
44
53
|
nonce: string;
|
|
45
|
-
capability: "form.propose" | "draft.create";
|
|
54
|
+
capability: "form.propose" | "draft.create" | "content.update";
|
|
46
55
|
};
|
|
56
|
+
/** The change list; a batch shows it per document. */
|
|
57
|
+
lines?: ProposalLine[];
|
|
47
58
|
navigate?: {
|
|
48
59
|
href: string;
|
|
49
60
|
label: string;
|
|
@@ -71,6 +82,8 @@ export type ToolFailure = {
|
|
|
71
82
|
export type ToolOutcome = ToolSuccess | ToolFailure;
|
|
72
83
|
export declare const TOOL_JSON_SCHEMAS: Record<string, Record<string, unknown>>;
|
|
73
84
|
export declare const TOOL_DESCRIPTIONS: Record<string, string>;
|
|
85
|
+
/** The tools the model is given for this turn. */
|
|
86
|
+
export declare function toolNamesFor(bridge: ToolBridge): string[];
|
|
74
87
|
/** Provider APIs reject dots in tool names (`^[a-zA-Z0-9_-]+$`). Capability IDs stay dotted. */
|
|
75
88
|
export declare function toProviderToolName(capability: string): string;
|
|
76
89
|
export declare function fromProviderToolName(name: string): string;
|
package/dist/runtime/tools.js
CHANGED
|
@@ -80,6 +80,12 @@ export const TOOL_DESCRIPTIONS = {
|
|
|
80
80
|
"admin.navigate": "Navigate Admin to an allowlisted collection, global, or unique document match.",
|
|
81
81
|
"draft.create": 'Stage creation of one draft document. Requires editor approval. Never publishes. The document starts empty: set top-level fields with {"op":"set","path":"title","value":"Start"}, and add each layout block with {"op":"blocks.insert","path":"layout","index":0,"blockType":"hero","data":{"headline":"Välkommen"}} using only block types and fields from the quoted context. Rich text fields take Markdown (headings, lists, **bold**, [links](https://…)).',
|
|
82
82
|
};
|
|
83
|
+
/** Tools a batch turn has no use for: it stays on its one saved document. */
|
|
84
|
+
const NOT_IN_BATCH = new Set(["admin.navigate", "draft.create"]);
|
|
85
|
+
/** The tools the model is given for this turn. */
|
|
86
|
+
export function toolNamesFor(bridge) {
|
|
87
|
+
return Object.keys(TOOL_JSON_SCHEMAS).filter((name) => !(bridge.batch && NOT_IN_BATCH.has(name)));
|
|
88
|
+
}
|
|
83
89
|
/** Provider APIs reject dots in tool names (`^[a-zA-Z0-9_-]+$`). Capability IDs stay dotted. */
|
|
84
90
|
export function toProviderToolName(capability) {
|
|
85
91
|
return capability.replaceAll(".", "_");
|
|
@@ -194,29 +200,40 @@ export async function executeTool(name, input, ctx) {
|
|
|
194
200
|
}
|
|
195
201
|
case "form.propose":
|
|
196
202
|
case "draft.create": {
|
|
203
|
+
if (ctx.bridge.batch && name === "draft.create") {
|
|
204
|
+
return { ok: false, capability: name, error: "tool_denied", message: "Not in a batch." };
|
|
205
|
+
}
|
|
206
|
+
const batch = ctx.bridge.batch;
|
|
207
|
+
const capability = batch ? "content.update" : name;
|
|
197
208
|
const proposed = await proposeChange(ctx.req, ctx.options, {
|
|
198
|
-
capability
|
|
209
|
+
capability,
|
|
210
|
+
allowedPaths: batch?.allowedPaths,
|
|
211
|
+
requestId: batch?.requestId,
|
|
199
212
|
collection: name === "draft.create" ? asString(input.collection) : ctx.bridge.collection,
|
|
200
213
|
global: name === "form.propose" ? ctx.bridge.global : undefined,
|
|
201
214
|
documentId: name === "form.propose" ? ctx.bridge.documentId : undefined,
|
|
202
215
|
locale: ctx.bridge.locale,
|
|
203
216
|
language: ctx.bridge.uiLanguage ?? ctx.bridge.language,
|
|
204
217
|
operations: input.operations ?? input.patches ?? input.changes ?? input.ops,
|
|
205
|
-
formData: name === "form.propose" ? ctx.formData : undefined,
|
|
218
|
+
formData: name === "form.propose" && !batch ? ctx.formData : undefined,
|
|
206
219
|
});
|
|
207
220
|
if (!proposed.ok) {
|
|
208
221
|
return { ok: false, capability: name, error: proposed.error, message: proposed.message };
|
|
209
222
|
}
|
|
223
|
+
// Models count characters badly; the count lets them correct a text that misses a length.
|
|
224
|
+
const counted = Object.keys(proposed.characters).length > 0;
|
|
210
225
|
return {
|
|
211
226
|
ok: true,
|
|
212
227
|
capability: name,
|
|
228
|
+
...(counted ? { data: { characters: proposed.characters } } : {}),
|
|
213
229
|
pendingApproval: {
|
|
214
230
|
title: proposed.title,
|
|
215
231
|
summary: proposed.summary,
|
|
216
232
|
approvalId: proposed.approvalId,
|
|
217
233
|
nonce: proposed.nonce,
|
|
218
|
-
capability
|
|
234
|
+
capability,
|
|
219
235
|
},
|
|
236
|
+
...(batch ? { lines: proposed.lines } : {}),
|
|
220
237
|
preview: proposed.preview,
|
|
221
238
|
};
|
|
222
239
|
}
|
package/dist/runtime/voice.d.ts
CHANGED
|
@@ -2,13 +2,13 @@ export declare function editorFacingText(input: {
|
|
|
2
2
|
text: string;
|
|
3
3
|
locale: string;
|
|
4
4
|
pendingApproval?: {
|
|
5
|
-
capability: "form.propose" | "draft.create";
|
|
5
|
+
capability: "form.propose" | "draft.create" | "content.update";
|
|
6
6
|
};
|
|
7
7
|
expectedProposal?: boolean;
|
|
8
8
|
}): string;
|
|
9
9
|
export declare function stripInternalSpeech(text: string, locale: string): string;
|
|
10
10
|
export declare function withoutOverclaims(text: string): string;
|
|
11
|
-
export declare function approvalCopy(locale: string, capability: "form.propose" | "draft.create"): string;
|
|
11
|
+
export declare function approvalCopy(locale: string, capability: "form.propose" | "draft.create" | "content.update"): string;
|
|
12
12
|
export declare function couldNotStageCopy(locale: string): string;
|
|
13
13
|
/** Tells the editor the content is written in another language than the one they chat in. */
|
|
14
14
|
export declare function contentLanguageNote(chat: string, contentLocale: string): string | undefined;
|
package/dist/runtime/voice.js
CHANGED
|
@@ -3,9 +3,10 @@ const INTERNAL = /\b(form\.propose|form_propose|draft\.create|draft_create|schem
|
|
|
3
3
|
const ENGLISH_META = /\b(despite|therefore|however|the tool|I should|I've|has been issued|appropriate turn|proposed values)\b/i;
|
|
4
4
|
export function editorFacingText(input) {
|
|
5
5
|
if (input.pendingApproval) {
|
|
6
|
-
// A form change is in the form now,
|
|
7
|
-
// claim the panel contradicts. A draft is not
|
|
8
|
-
|
|
6
|
+
// A form change is in the form now, and a batch change shown beside its document, so the
|
|
7
|
+
// model's own words about it can stand, minus any claim the panel contradicts. A draft is not
|
|
8
|
+
// created until approved; that keeps fixed copy.
|
|
9
|
+
const own = input.pendingApproval.capability !== "draft.create"
|
|
9
10
|
? withoutOverclaims(stripInternalSpeech(input.text, input.locale))
|
|
10
11
|
: "";
|
|
11
12
|
return own || approvalCopy(input.locale, input.pendingApproval.capability);
|
|
@@ -39,6 +40,9 @@ export function withoutOverclaims(text) {
|
|
|
39
40
|
return kept.length <= MAX_OWN_TEXT ? kept : "";
|
|
40
41
|
}
|
|
41
42
|
export function approvalCopy(locale, capability) {
|
|
43
|
+
if (capability === "content.update") {
|
|
44
|
+
return isSwedish(locale) ? "Här är ett förslag." : "Here is a suggestion.";
|
|
45
|
+
}
|
|
42
46
|
if (isSwedish(locale)) {
|
|
43
47
|
return capability === "draft.create"
|
|
44
48
|
? "Utkastet ligger i panelen. Godkänn det så skapas det."
|
package/dist/schema/fields.d.ts
CHANGED
package/dist/schema/fields.js
CHANGED
|
@@ -55,6 +55,8 @@ function walkField(field, parentPath, allowlist, denyFields) {
|
|
|
55
55
|
required: field.required,
|
|
56
56
|
localized: field.localized,
|
|
57
57
|
hasMany: field.hasMany,
|
|
58
|
+
...(field.hasMany && typeof field.minRows === "number" ? { minRows: field.minRows } : {}),
|
|
59
|
+
...(field.hasMany && typeof field.maxRows === "number" ? { maxRows: field.maxRows } : {}),
|
|
58
60
|
relationTo: normalizeRelationTo(field.relationTo),
|
|
59
61
|
options: optionValues(field.options),
|
|
60
62
|
label: asLabel(field.label) ?? field.name,
|
|
@@ -432,3 +432,206 @@ html[data-theme="dark"] .ea-aside {
|
|
|
432
432
|
html[data-theme="dark"] .ea-trigger--open .ea-trigger__icon {
|
|
433
433
|
color: #60a5fa;
|
|
434
434
|
}
|
|
435
|
+
|
|
436
|
+
/* Batch: many documents from the work list, one card each. The head and buttons stay put and the
|
|
437
|
+
cards scroll between them. */
|
|
438
|
+
.ea-batch {
|
|
439
|
+
display: flex;
|
|
440
|
+
flex-direction: column;
|
|
441
|
+
flex: 1;
|
|
442
|
+
min-height: 0;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.ea-batch__head {
|
|
446
|
+
flex-shrink: 0;
|
|
447
|
+
padding: calc(var(--base) * 0.6) var(--base) calc(var(--base) * 0.4);
|
|
448
|
+
font-size: 13px;
|
|
449
|
+
line-height: 1.5;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.ea-batch__head h3 {
|
|
453
|
+
margin: 0 0 calc(var(--base) * 0.3);
|
|
454
|
+
font-size: 14px;
|
|
455
|
+
font-weight: 600;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.ea-batch__head p {
|
|
459
|
+
margin: 0 0 calc(var(--base) * 0.3);
|
|
460
|
+
color: var(--theme-elevation-700);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
.ea-batch__task span {
|
|
464
|
+
font-weight: 600;
|
|
465
|
+
color: var(--theme-text);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.ea-batch__cards {
|
|
469
|
+
flex: 1;
|
|
470
|
+
min-height: 0;
|
|
471
|
+
overflow-y: auto;
|
|
472
|
+
margin: 0;
|
|
473
|
+
padding: 0 var(--base);
|
|
474
|
+
list-style: none;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.ea-batch__card {
|
|
478
|
+
margin-bottom: calc(var(--base) * 0.5);
|
|
479
|
+
padding: calc(var(--base) * 0.5) calc(var(--base) * 0.6);
|
|
480
|
+
border: 1px solid var(--theme-elevation-150);
|
|
481
|
+
border-radius: var(--style-radius-m);
|
|
482
|
+
background: var(--theme-elevation-50);
|
|
483
|
+
font-size: 13px;
|
|
484
|
+
line-height: 1.45;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.ea-batch__card--proposed {
|
|
488
|
+
border-inline-start: 3px solid var(--ea-accent);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.ea-batch__card--saved,
|
|
492
|
+
.ea-batch__card--draft {
|
|
493
|
+
border-inline-start: 3px solid var(--theme-success-500, #16a34a);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.ea-batch__card--failed,
|
|
497
|
+
.ea-batch__card--stale,
|
|
498
|
+
.ea-batch__card--locked {
|
|
499
|
+
border-inline-start: 3px solid var(--theme-warning-500, #d97706);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.ea-batch__card--unavailable,
|
|
503
|
+
.ea-batch__card--rejected,
|
|
504
|
+
.ea-batch__card--nothing {
|
|
505
|
+
opacity: 0.75;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.ea-batch__card-head {
|
|
509
|
+
display: flex;
|
|
510
|
+
align-items: center;
|
|
511
|
+
gap: calc(var(--base) * 0.4);
|
|
512
|
+
min-width: 0;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.ea-batch__card-head input[type="checkbox"] {
|
|
516
|
+
flex-shrink: 0;
|
|
517
|
+
margin: 0;
|
|
518
|
+
accent-color: var(--ea-accent);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.ea-batch__thumb {
|
|
522
|
+
flex-shrink: 0;
|
|
523
|
+
width: 32px;
|
|
524
|
+
height: 32px;
|
|
525
|
+
object-fit: cover;
|
|
526
|
+
border-radius: var(--style-radius-s);
|
|
527
|
+
background: var(--theme-elevation-100);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.ea-batch__title {
|
|
531
|
+
flex: 1;
|
|
532
|
+
min-width: 0;
|
|
533
|
+
overflow: hidden;
|
|
534
|
+
font-weight: 600;
|
|
535
|
+
text-overflow: ellipsis;
|
|
536
|
+
white-space: nowrap;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.ea-batch__pill {
|
|
540
|
+
margin-inline-start: 0.4em;
|
|
541
|
+
padding: 0 0.4em;
|
|
542
|
+
border-radius: var(--style-radius-s);
|
|
543
|
+
background: var(--theme-elevation-100);
|
|
544
|
+
font-size: 11px;
|
|
545
|
+
font-weight: 400;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.ea-batch__status {
|
|
549
|
+
flex-shrink: 0;
|
|
550
|
+
color: var(--theme-elevation-600);
|
|
551
|
+
font-size: 12px;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.ea-batch__lines {
|
|
555
|
+
margin: calc(var(--base) * 0.4) 0 0;
|
|
556
|
+
padding-left: 1.1em;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
.ea-batch__edit {
|
|
560
|
+
display: flex;
|
|
561
|
+
flex-direction: column;
|
|
562
|
+
gap: 2px;
|
|
563
|
+
margin-inline-start: -1.1em;
|
|
564
|
+
list-style: none;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.ea-batch__label {
|
|
568
|
+
font-weight: 600;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
.ea-batch__before {
|
|
572
|
+
color: var(--theme-elevation-600);
|
|
573
|
+
overflow-wrap: anywhere;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.ea-batch__edit textarea {
|
|
577
|
+
box-sizing: border-box;
|
|
578
|
+
width: 100%;
|
|
579
|
+
padding: calc(var(--base) * 0.3) calc(var(--base) * 0.4);
|
|
580
|
+
border: 1px solid var(--theme-elevation-200);
|
|
581
|
+
border-radius: var(--style-radius-s);
|
|
582
|
+
background: var(--theme-input-bg, var(--theme-elevation-0));
|
|
583
|
+
color: var(--theme-text);
|
|
584
|
+
font: inherit;
|
|
585
|
+
resize: vertical;
|
|
586
|
+
/* Grows with the text where the browser can; two rows otherwise. */
|
|
587
|
+
field-sizing: content;
|
|
588
|
+
min-height: calc(2lh + calc(var(--base) * 0.6));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.ea-batch__count {
|
|
592
|
+
align-self: flex-end;
|
|
593
|
+
color: var(--theme-elevation-500);
|
|
594
|
+
font-size: 11px;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.ea-batch__text,
|
|
598
|
+
.ea-batch__hint {
|
|
599
|
+
margin: calc(var(--base) * 0.3) 0 0;
|
|
600
|
+
color: var(--theme-elevation-700);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.ea-batch__hint {
|
|
604
|
+
font-size: 12px;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.ea-batch__retry {
|
|
608
|
+
display: flex;
|
|
609
|
+
gap: calc(var(--base) * 0.3);
|
|
610
|
+
margin-top: calc(var(--base) * 0.4);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
.ea-batch__retry input {
|
|
614
|
+
flex: 1;
|
|
615
|
+
min-width: 0;
|
|
616
|
+
padding: calc(var(--base) * 0.2) calc(var(--base) * 0.4);
|
|
617
|
+
border: 1px solid var(--theme-elevation-200);
|
|
618
|
+
border-radius: var(--style-radius-s);
|
|
619
|
+
background: var(--theme-input-bg, var(--theme-elevation-0));
|
|
620
|
+
color: var(--theme-text);
|
|
621
|
+
font: inherit;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
.ea-batch__card-actions {
|
|
625
|
+
display: flex;
|
|
626
|
+
align-items: center;
|
|
627
|
+
gap: calc(var(--base) * 0.6);
|
|
628
|
+
margin-top: calc(var(--base) * 0.3);
|
|
629
|
+
font-size: 12px;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.ea-batch__actions {
|
|
633
|
+
flex-shrink: 0;
|
|
634
|
+
margin: 0;
|
|
635
|
+
padding: calc(var(--base) * 0.5) var(--base) calc(var(--base) * 0.8);
|
|
636
|
+
border-top: 1px solid var(--theme-elevation-100);
|
|
637
|
+
}
|