@blokkli/editor 2.0.0-alpha.62 → 2.0.0-alpha.63

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.
Files changed (128) hide show
  1. package/dist/global/types/colorOptions.d.ts +16 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +168 -14
  4. package/dist/modules/agent/runtime/app/components/Conversation/Item/Assistant/index.vue +14 -5
  5. package/dist/modules/agent/runtime/app/features/agent/ConversationsAdmin/ConversationsTab/Item.vue +2 -2
  6. package/dist/modules/agent/runtime/app/features/agent/ConversationsAdmin/RatingsTab/Item.vue +2 -2
  7. package/dist/modules/agent/runtime/app/features/agent/ConversationsAdmin/SplitView/ConversationDetail.vue +2 -2
  8. package/dist/modules/agent/runtime/app/features/agent/Transcript/MessageContent.vue +14 -1
  9. package/dist/modules/agent/runtime/app/features/agent/Transcript/index.vue +30 -12
  10. package/dist/modules/agent/runtime/app/helpers/linkifyBlockUuids.d.ts +11 -0
  11. package/dist/modules/agent/runtime/app/helpers/linkifyBlockUuids.js +47 -0
  12. package/dist/modules/agent/runtime/app/tools/auto_translate_paragraphs/Component.d.vue.ts +22 -0
  13. package/dist/modules/agent/runtime/app/tools/auto_translate_paragraphs/Component.vue +28 -26
  14. package/dist/modules/agent/runtime/app/tools/auto_translate_paragraphs/Component.vue.d.ts +22 -0
  15. package/dist/modules/agent/runtime/app/tools/auto_translate_paragraphs/index.d.ts +15 -0
  16. package/dist/modules/agent/runtime/app/tools/delegate_text_rewrite/Component.d.vue.ts +22 -0
  17. package/dist/modules/agent/runtime/app/tools/delegate_text_rewrite/Component.vue +59 -16
  18. package/dist/modules/agent/runtime/app/tools/delegate_text_rewrite/Component.vue.d.ts +22 -0
  19. package/dist/modules/agent/runtime/app/tools/delegate_text_rewrite/index.d.ts +15 -0
  20. package/dist/modules/agent/runtime/app/tools/fieldDiffApproval.d.ts +85 -15
  21. package/dist/modules/agent/runtime/app/tools/fieldDiffApproval.js +138 -28
  22. package/dist/modules/agent/runtime/app/tools/schemas.d.ts +15 -5
  23. package/dist/modules/agent/runtime/app/tools/schemas.js +29 -12
  24. package/dist/modules/agent/runtime/app/tools/search_text/index.js +1 -1
  25. package/dist/modules/agent/runtime/app/tools/update_text_fields/Component.d.vue.ts +22 -0
  26. package/dist/modules/agent/runtime/app/tools/update_text_fields/Component.vue +54 -30
  27. package/dist/modules/agent/runtime/app/tools/update_text_fields/Component.vue.d.ts +22 -0
  28. package/dist/modules/agent/runtime/app/tools/update_text_fields/index.d.ts +15 -0
  29. package/dist/modules/agent/runtime/server/default-system-prompts/architecture.js +1 -1
  30. package/dist/modules/agent/runtime/server/default-system-prompts/important-rules.js +1 -0
  31. package/dist/modules/charts/runtime/blokkli/tools/chart_schemas.d.ts +1 -1
  32. package/dist/modules/charts/runtime/blokkli/tools/chart_schemas.js +12 -5
  33. package/dist/modules/charts/runtime/components/ChartRenderer/index.vue +7 -16
  34. package/dist/modules/charts/runtime/features/charts/Editor/ColorDropdown/index.vue +7 -77
  35. package/dist/modules/charts/runtime/features/charts/Editor/CsvImport/csvHelpers.d.ts +1 -1
  36. package/dist/modules/charts/runtime/features/charts/Editor/useChartEditorState.d.ts +1 -1
  37. package/dist/modules/charts/runtime/helpers/index.d.ts +2 -1
  38. package/dist/modules/charts/runtime/helpers/index.js +4 -2
  39. package/dist/modules/drupal/graphql/base/fragment.paragraphsBlokkliMutationResult.graphql +3 -0
  40. package/dist/modules/drupal/runtime/adapter/index.js +2 -1
  41. package/dist/runtime/composables/useBlokkliRuntimeConfig.d.ts +50 -0
  42. package/dist/runtime/composables/useBlokkliRuntimeConfig.js +34 -0
  43. package/dist/runtime/editor/adapter/index.d.ts +2 -1
  44. package/dist/runtime/editor/components/ColorDropdown/index.d.vue.ts +26 -0
  45. package/dist/runtime/editor/components/ColorDropdown/index.vue +116 -0
  46. package/dist/runtime/editor/components/ColorDropdown/index.vue.d.ts +26 -0
  47. package/dist/runtime/editor/components/DiffApproval/Highlight/Item.d.vue.ts +14 -20
  48. package/dist/runtime/editor/components/DiffApproval/Highlight/Item.vue +97 -52
  49. package/dist/runtime/editor/components/DiffApproval/Highlight/Item.vue.d.ts +14 -20
  50. package/dist/runtime/editor/components/DiffApproval/Highlight/index.d.vue.ts +6 -6
  51. package/dist/runtime/editor/components/DiffApproval/Highlight/index.vue +30 -21
  52. package/dist/runtime/editor/components/DiffApproval/Highlight/index.vue.d.ts +6 -6
  53. package/dist/runtime/editor/components/DiffApproval/Toolbar/index.d.vue.ts +19 -9
  54. package/dist/runtime/editor/components/DiffApproval/Toolbar/index.vue +18 -9
  55. package/dist/runtime/editor/components/DiffApproval/Toolbar/index.vue.d.ts +19 -9
  56. package/dist/runtime/editor/components/DiffApproval/index.d.vue.ts +14 -6
  57. package/dist/runtime/editor/components/DiffApproval/index.vue +62 -35
  58. package/dist/runtime/editor/components/DiffApproval/index.vue.d.ts +14 -6
  59. package/dist/runtime/editor/components/DiffApproval/types.d.ts +32 -0
  60. package/dist/runtime/editor/components/DiffApproval/types.js +22 -0
  61. package/dist/runtime/editor/components/Dropdown/index.d.vue.ts +5 -4
  62. package/dist/runtime/editor/components/Dropdown/index.vue +89 -29
  63. package/dist/runtime/editor/components/Dropdown/index.vue.d.ts +5 -4
  64. package/dist/runtime/editor/components/FlexTextarea/index.d.vue.ts +1 -1
  65. package/dist/runtime/editor/components/FlexTextarea/index.vue.d.ts +1 -1
  66. package/dist/runtime/editor/components/NestedEditorOverlay/index.d.vue.ts +2 -2
  67. package/dist/runtime/editor/components/NestedEditorOverlay/index.vue +3 -3
  68. package/dist/runtime/editor/components/NestedEditorOverlay/index.vue.d.ts +2 -2
  69. package/dist/runtime/editor/components/PopupHost/index.d.vue.ts +13 -0
  70. package/dist/runtime/editor/components/PopupHost/index.vue +12 -0
  71. package/dist/runtime/editor/components/PopupHost/index.vue.d.ts +13 -0
  72. package/dist/runtime/editor/components/index.d.ts +2 -0
  73. package/dist/runtime/editor/components/index.js +2 -0
  74. package/dist/runtime/editor/css/output.css +1 -1
  75. package/dist/runtime/editor/features/analyze/Main.vue +15 -2
  76. package/dist/runtime/editor/features/analyze/Results/ResultsItemNodes.vue +4 -1
  77. package/dist/runtime/editor/features/analyze/Results/ResultsItemNodesTarget.vue +25 -16
  78. package/dist/runtime/editor/features/analyze/analyzers/defaults/validations.d.ts +10 -0
  79. package/dist/runtime/editor/features/analyze/analyzers/defaults/validations.js +39 -0
  80. package/dist/runtime/editor/features/analyze/analyzers/helpers/Context.d.ts +5 -1
  81. package/dist/runtime/editor/features/analyze/analyzers/helpers/Context.js +5 -0
  82. package/dist/runtime/editor/features/analyze/index.vue +0 -1
  83. package/dist/runtime/editor/features/changelog/changelog.json +9 -1
  84. package/dist/runtime/editor/features/comments/Comment/Meta/index.vue +1 -1
  85. package/dist/runtime/editor/features/comments/Comment/index.vue +1 -1
  86. package/dist/runtime/editor/features/publish/Dialog/Violations.d.vue.ts +12 -0
  87. package/dist/runtime/editor/features/publish/Dialog/Violations.vue +117 -0
  88. package/dist/runtime/editor/features/publish/Dialog/Violations.vue.d.ts +12 -0
  89. package/dist/runtime/editor/features/publish/Dialog/index.vue +61 -24
  90. package/dist/runtime/editor/features/publish/index.vue +2 -4
  91. package/dist/runtime/editor/features/publish/types.d.ts +0 -4
  92. package/dist/runtime/editor/helpers/color/index.d.ts +7 -0
  93. package/dist/runtime/editor/helpers/color/index.js +14 -0
  94. package/dist/runtime/editor/helpers/diff/index.d.ts +87 -0
  95. package/dist/runtime/editor/helpers/diff/index.js +256 -0
  96. package/dist/runtime/editor/helpers/injections.d.ts +11 -0
  97. package/dist/runtime/editor/helpers/injections.js +1 -0
  98. package/dist/runtime/editor/plugins/Sidebar/Detached/index.d.vue.ts +1 -1
  99. package/dist/runtime/editor/plugins/Sidebar/Detached/index.vue.d.ts +1 -1
  100. package/dist/runtime/editor/plugins/Sidebar/index.d.vue.ts +2 -2
  101. package/dist/runtime/editor/plugins/Sidebar/index.vue.d.ts +2 -2
  102. package/dist/runtime/editor/providers/analyze.js +5 -2
  103. package/dist/runtime/editor/providers/config.d.ts +3 -1
  104. package/dist/runtime/editor/providers/config.js +46 -15
  105. package/dist/runtime/editor/providers/state.d.ts +13 -0
  106. package/dist/runtime/editor/providers/state.js +7 -0
  107. package/dist/runtime/editor/translations/de.json +7 -4
  108. package/dist/runtime/editor/translations/fr.json +2 -4
  109. package/dist/runtime/editor/translations/gsw_CH.json +7 -4
  110. package/dist/runtime/editor/translations/it.json +2 -4
  111. package/dist/runtime/helpers/colors.d.ts +39 -0
  112. package/dist/runtime/helpers/colors.js +28 -0
  113. package/dist/runtime/types/colors.d.ts +11 -0
  114. package/package.json +1 -1
  115. package/dist/runtime/editor/features/validations/Overlay/Item.d.vue.ts +0 -7
  116. package/dist/runtime/editor/features/validations/Overlay/Item.vue +0 -36
  117. package/dist/runtime/editor/features/validations/Overlay/Item.vue.d.ts +0 -7
  118. package/dist/runtime/editor/features/validations/Overlay/index.d.vue.ts +0 -7
  119. package/dist/runtime/editor/features/validations/Overlay/index.vue +0 -115
  120. package/dist/runtime/editor/features/validations/Overlay/index.vue.d.ts +0 -7
  121. package/dist/runtime/editor/features/validations/SidebarItem/index.d.vue.ts +0 -10
  122. package/dist/runtime/editor/features/validations/SidebarItem/index.vue +0 -41
  123. package/dist/runtime/editor/features/validations/SidebarItem/index.vue.d.ts +0 -10
  124. package/dist/runtime/editor/features/validations/index.d.vue.ts +0 -3
  125. package/dist/runtime/editor/features/validations/index.vue +0 -91
  126. package/dist/runtime/editor/features/validations/index.vue.d.ts +0 -3
  127. package/dist/runtime/editor/types/config.d.ts +0 -5
  128. /package/dist/runtime/{editor/types/config.js → types/colors.js} +0 -0
@@ -1,3 +1,4 @@
1
+ import { flattenSegments, reassembleValue } from "#blokkli/editor/helpers/diff";
1
2
  export function skippedFieldsMessage(skipped) {
2
3
  if (!skipped.length) return void 0;
3
4
  const list = skipped.map((s) => `"${s.uuid}" ${s.fieldName} (${s.reason})`).join(", ");
@@ -10,55 +11,164 @@ export function appendAgentNote(message, note) {
10
11
 
11
12
  ${note}`;
12
13
  }
14
+ export function decideFieldUpdates(items, selected, reasons) {
15
+ const rejectedByUser = {};
16
+ const updates = [];
17
+ let acceptedCount = 0;
18
+ let totalCount = 0;
19
+ for (const item of items) {
20
+ if (item.segments) {
21
+ const atoms = flattenSegments(item.segments).filter(
22
+ (s) => s.status !== "matched" || s.beforeHtml !== s.afterHtml
23
+ );
24
+ totalCount += atoms.length;
25
+ const acceptedById = {};
26
+ const rejectedSegments = [];
27
+ let accepted = 0;
28
+ for (const atom of atoms) {
29
+ const key = `${item.id}:${atom.id}`;
30
+ const isAccepted = selected[key] !== false;
31
+ acceptedById[atom.id] = isAccepted;
32
+ if (isAccepted) {
33
+ accepted++;
34
+ } else {
35
+ rejectedSegments.push({
36
+ tag: atom.tag,
37
+ beforeHtml: atom.beforeHtml,
38
+ afterHtml: atom.afterHtml,
39
+ status: atom.status,
40
+ reasonForRejection: reasons[key] || ""
41
+ });
42
+ }
43
+ }
44
+ acceptedCount += accepted;
45
+ if (accepted > 0) {
46
+ updates.push({
47
+ itemId: item.id,
48
+ uuid: item.uuid,
49
+ fieldName: item.fieldName,
50
+ fieldValue: reassembleValue(item.segments, acceptedById)
51
+ });
52
+ }
53
+ if (rejectedSegments.length > 0) {
54
+ const fields = rejectedByUser[item.uuid] ?? {};
55
+ fields[item.fieldName] = {
56
+ reasonForRejection: "",
57
+ partial: {
58
+ accepted,
59
+ total: atoms.length,
60
+ rejectedSegments
61
+ }
62
+ };
63
+ rejectedByUser[item.uuid] = fields;
64
+ }
65
+ } else {
66
+ totalCount++;
67
+ const key = String(item.id);
68
+ const isAccepted = selected[key] !== false;
69
+ if (isAccepted) {
70
+ acceptedCount++;
71
+ updates.push({
72
+ itemId: item.id,
73
+ uuid: item.uuid,
74
+ fieldName: item.fieldName,
75
+ fieldValue: item.value
76
+ });
77
+ } else {
78
+ const fields = rejectedByUser[item.uuid] ?? {};
79
+ fields[item.fieldName] = {
80
+ reasonForRejection: reasons[key] || ""
81
+ };
82
+ rejectedByUser[item.uuid] = fields;
83
+ }
84
+ }
85
+ }
86
+ return { updates, rejectedByUser, acceptedCount, totalCount };
87
+ }
13
88
  export async function applyFieldDiffs(app, adapter, items, selected, reasons) {
14
89
  const { $t, state, context } = app;
15
90
  const entityUuid = context.value.entityUuid;
16
- const rejectedByUser = {};
91
+ const decision = decideFieldUpdates(items, selected, reasons);
17
92
  const batchItems = [];
18
93
  const entityItems = [];
19
- for (const item of items) {
20
- if (!selected[item.id]) {
21
- const fields = rejectedByUser[item.uuid] ?? {};
22
- fields[item.fieldName] = { reasonForRejection: reasons[item.id] || "" };
23
- rejectedByUser[item.uuid] = fields;
24
- continue;
25
- }
26
- if (item.uuid === entityUuid) {
27
- entityItems.push({ fieldName: item.fieldName, fieldValue: item.value });
94
+ const appliedByItemId = {};
95
+ for (const update of decision.updates) {
96
+ appliedByItemId[update.itemId] = update.fieldValue;
97
+ if (update.uuid === entityUuid) {
98
+ entityItems.push({
99
+ fieldName: update.fieldName,
100
+ fieldValue: update.fieldValue
101
+ });
28
102
  } else {
29
103
  batchItems.push({
30
- uuid: item.uuid,
31
- fieldName: item.fieldName,
32
- fieldValue: item.value
104
+ uuid: update.uuid,
105
+ fieldName: update.fieldName,
106
+ fieldValue: update.fieldValue
33
107
  });
34
108
  }
35
109
  }
36
110
  await state.mutateWithLoadingState(
37
111
  () => adapter.updateFieldValueBatched({ items: batchItems, entityItems })
38
112
  );
39
- const acceptedCount = batchItems.length + entityItems.length;
40
- const label = acceptedCount === items.length ? $t(
113
+ const label = decision.acceptedCount === decision.totalCount ? $t(
41
114
  "aiAgentBatchRewriteAllApplied",
42
115
  "All @count changes applied"
43
- ).replace("@count", String(acceptedCount)) : $t(
116
+ ).replace("@count", String(decision.acceptedCount)) : $t(
44
117
  "aiAgentBatchRewriteSomeApplied",
45
118
  "@applied of @total changes applied"
46
- ).replace("@applied", String(acceptedCount)).replace("@total", String(items.length));
47
- return { acceptedCount, rejectedByUser, label };
119
+ ).replace("@applied", String(decision.acceptedCount)).replace("@total", String(decision.totalCount));
120
+ return {
121
+ ...decision,
122
+ label,
123
+ appliedByItemId
124
+ };
48
125
  }
49
- export function rejectedWithoutReasonMessage(rejectedByUser) {
50
- const rejected = [];
51
- for (const [uuid, fields] of Object.entries(rejectedByUser)) {
126
+ export function partialRejectionGuidance(rejected) {
127
+ const partials = [];
128
+ for (const [uuid, fields] of Object.entries(rejected)) {
52
129
  for (const [fieldName, v] of Object.entries(fields)) {
53
- if (!v?.reasonForRejection) rejected.push({ uuid, fieldName });
130
+ if (v.partial && v.partial.accepted > 0) {
131
+ partials.push({ uuid, fieldName });
132
+ }
54
133
  }
55
134
  }
56
- if (rejected.length === 1 || rejected.length === 2) {
57
- const fieldList = rejected.map((r) => `"${r.fieldName}" of paragraph ${r.uuid}`).join(" and ");
58
- return `The user rejected ${fieldList} without a reason. Use the ask_question tool to present the user with 2 or more alternative texts for each rejected field.`;
135
+ if (partials.length === 0) return void 0;
136
+ const fieldList = partials.map((p) => `"${p.fieldName}" (paragraph ${p.uuid})`).join(", ");
137
+ const noun = partials.length === 1 ? "This field was" : "These fields were";
138
+ return `${noun} updated with a hybrid value \u2014 accepted chunks plus the original content of the rejected ones: ${fieldList}. If you follow up on the rejected chunks, scope your change to those chunks only \u2014 use update_text_fields patch mode (operations) with search matching the rejected chunk's original content. Do NOT re-rewrite the whole field; the accepted chunks must stay byte-for-byte identical.`;
139
+ }
140
+ export function rejectedWithoutReasonMessage(rejected) {
141
+ const entries = [];
142
+ for (const [uuid, fields] of Object.entries(rejected)) {
143
+ for (const [fieldName, v] of Object.entries(fields)) {
144
+ if (v.partial) {
145
+ for (const seg of v.partial.rejectedSegments) {
146
+ if (!seg.reasonForRejection) {
147
+ entries.push({
148
+ kind: "segment",
149
+ uuid,
150
+ fieldName,
151
+ tag: seg.tag,
152
+ status: seg.status
153
+ });
154
+ }
155
+ }
156
+ } else if (!v.reasonForRejection) {
157
+ entries.push({ kind: "field", uuid, fieldName });
158
+ }
159
+ }
160
+ }
161
+ if (entries.length === 0) return void 0;
162
+ const describe = (e) => e.kind === "segment" ? `a <${e.tag}> ${e.status === "matched" ? "change" : e.status === "inserted" ? "insertion" : "deletion"} in "${e.fieldName}" of paragraph ${e.uuid}` : `"${e.fieldName}" of paragraph ${e.uuid}`;
163
+ if (entries.length === 1) {
164
+ const it = entries[0];
165
+ if (it.kind === "segment") {
166
+ return `The user rejected ${describe(it)} without a reason. Use the ask_question tool to present 2 or more alternative wordings for that specific chunk \u2014 do not rewrite the whole field.`;
167
+ }
168
+ return `The user rejected ${describe(it)} without a reason. Use the ask_question tool to present the user with 2 or more alternative texts.`;
59
169
  }
60
- if (rejected.length > 2) {
61
- return "Some changes were rejected without a reason. Ask the user what they would like to change instead.";
170
+ if (entries.length === 2) {
171
+ return `The user rejected ${describe(entries[0])} and ${describe(entries[1])} without a reason. Use the ask_question tool to present 2 or more alternative texts for each.`;
62
172
  }
63
- return void 0;
173
+ return "Some changes were rejected without a reason. Ask the user what they would like to change instead.";
64
174
  }
@@ -86,15 +86,25 @@ export declare const optionValueSchema: z.ZodUnion<readonly [z.ZodString, z.ZodB
86
86
  * - `"before:<UUID>"` — insert before the block with the given UUID
87
87
  */
88
88
  export declare const positionSchema: z.ZodDefault<z.ZodOptional<z.ZodString>>;
89
- /**
90
- * Shared result schema for the interactive field-diff rewrite tools
91
- * (`update_text_fields`, `delegate_text_rewrite`). Both present a diff approval
92
- * UI and report what the user accepted/rejected.
93
- */
94
89
  export declare const fieldDiffResultSchema: z.ZodObject<{
95
90
  acceptedCount: z.ZodNumber;
96
91
  rejectedByUser: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
97
92
  reasonForRejection: z.ZodString;
93
+ partial: z.ZodOptional<z.ZodObject<{
94
+ accepted: z.ZodNumber;
95
+ total: z.ZodNumber;
96
+ rejectedSegments: z.ZodArray<z.ZodObject<{
97
+ tag: z.ZodString;
98
+ beforeHtml: z.ZodString;
99
+ afterHtml: z.ZodString;
100
+ status: z.ZodEnum<{
101
+ deleted: "deleted";
102
+ matched: "matched";
103
+ inserted: "inserted";
104
+ }>;
105
+ reasonForRejection: z.ZodString;
106
+ }, z.core.$strip>>;
107
+ }, z.core.$strip>>;
98
108
  }, z.core.$strip>>>;
99
109
  label: z.ZodString;
100
110
  agentMessage: z.ZodOptional<z.ZodString>;
@@ -68,20 +68,37 @@ export const optionValueSchema = z.union([
68
68
  export const positionSchema = z.string().optional().default("end").describe(
69
69
  'Where to place the paragraph(s). "start" = beginning, "end" (default) = append at end, "after:<UUID>" = after a specific paragraph, "before:<UUID>" = before a specific paragraph.'
70
70
  );
71
+ const rejectedSegmentSchema = z.object({
72
+ tag: z.string().describe('HTML tag of the rejected chunk (e.g. "p", "li", "h2")'),
73
+ beforeHtml: z.string().describe(
74
+ "The original innerHTML kept in the field (empty for newly inserted chunks that were rejected and dropped)"
75
+ ),
76
+ afterHtml: z.string().describe(
77
+ "The innerHTML you proposed for this chunk and the user rejected (empty for deletions that were rejected and restored)"
78
+ ),
79
+ status: z.enum(["matched", "inserted", "deleted"]).describe(
80
+ "Whether your proposal modified an existing chunk (matched), added a new one (inserted), or removed one (deleted)"
81
+ ),
82
+ reasonForRejection: z.string().describe("Per-chunk rejection reason; empty if none given")
83
+ });
84
+ const fieldRejectionSchema = z.object({
85
+ reasonForRejection: z.string().describe(
86
+ "Field-level rejection reason; empty when no reason given or when rejection is per-chunk (see `partial`)"
87
+ ),
88
+ partial: z.object({
89
+ accepted: z.number().describe("Number of changed chunks in this field the user accepted"),
90
+ total: z.number().describe("Total number of changed chunks in this field"),
91
+ rejectedSegments: z.array(rejectedSegmentSchema).describe(
92
+ "Details for each rejected chunk. The field still gets updated with a hybrid value combining the accepted chunks and the original content of the rejected ones."
93
+ )
94
+ }).optional().describe(
95
+ "Present when the user evaluated chunk-by-chunk. When `accepted > 0`, the field was updated with a hybrid value; when `accepted === 0`, every chunk was rejected and the field stayed at its original value."
96
+ )
97
+ });
71
98
  export const fieldDiffResultSchema = z.object({
72
99
  acceptedCount: z.number().describe("Number of changes accepted by the user"),
73
- rejectedByUser: z.record(
74
- z.string(),
75
- z.record(
76
- z.string(),
77
- z.object({
78
- reasonForRejection: z.string().describe(
79
- "Reason provided by the user for rejecting, empty if no reason given"
80
- )
81
- })
82
- )
83
- ).describe(
84
- "Map of rejected paragraph UUID to field name to rejection details"
100
+ rejectedByUser: z.record(z.string(), z.record(z.string(), fieldRejectionSchema)).describe(
101
+ "Map of paragraph UUID \u2192 field name \u2192 rejection details. A field appears here when at least one chunk was rejected (or, for non-chunked fields, when the whole field was rejected)."
85
102
  ),
86
103
  label: z.string().describe("Human-readable summary shown in the UI"),
87
104
  agentMessage: z.string().optional().describe(
@@ -57,7 +57,7 @@ export default defineBlokkliAgentTool({
57
57
  const getBlockOwnText = (el) => {
58
58
  if (!el) return "";
59
59
  const clone = el.cloneNode(true);
60
- const nestedBlocks = clone.querySelectorAll("[data-item-bundle]");
60
+ const nestedBlocks = clone.querySelectorAll("[data-bk-uuid]");
61
61
  nestedBlocks.forEach((nested) => nested.remove());
62
62
  const altTexts = element.queryAll(clone, "img", "searchTextAlt", (img) => {
63
63
  if (img instanceof HTMLImageElement) {
@@ -9,6 +9,17 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
9
9
  acceptedCount: number;
10
10
  rejectedByUser: Record<string, Record<string, {
11
11
  reasonForRejection: string;
12
+ partial?: {
13
+ accepted: number;
14
+ total: number;
15
+ rejectedSegments: {
16
+ tag: string;
17
+ beforeHtml: string;
18
+ afterHtml: string;
19
+ status: "deleted" | "matched" | "inserted";
20
+ reasonForRejection: string;
21
+ }[];
22
+ } | undefined;
12
23
  }>>;
13
24
  label: string;
14
25
  agentMessage?: string | undefined;
@@ -19,6 +30,17 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
19
30
  acceptedCount: number;
20
31
  rejectedByUser: Record<string, Record<string, {
21
32
  reasonForRejection: string;
33
+ partial?: {
34
+ accepted: number;
35
+ total: number;
36
+ rejectedSegments: {
37
+ tag: string;
38
+ beforeHtml: string;
39
+ afterHtml: string;
40
+ status: "deleted" | "matched" | "inserted";
41
+ reasonForRejection: string;
42
+ }[];
43
+ } | undefined;
22
44
  }>>;
23
45
  label: string;
24
46
  agentMessage?: string | undefined;
@@ -11,10 +11,12 @@
11
11
  <script setup>
12
12
  import { useBlokkli, ref, onMounted } from "#imports";
13
13
  import { DiffApproval } from "#blokkli/editor/components";
14
+ import { splitIntoSegments } from "#blokkli/editor/helpers/diff";
14
15
  import { applyOperations, resolveHost as resolveBlockHost } from "../helpers";
15
16
  import {
16
17
  applyFieldDiffs,
17
18
  rejectedWithoutReasonMessage,
19
+ partialRejectionGuidance,
18
20
  skippedFieldsMessage,
19
21
  appendAgentNote
20
22
  } from "../fieldDiffApproval";
@@ -63,6 +65,17 @@ function resolveFieldLabel(uuid, fieldName) {
63
65
  );
64
66
  return config?.label || fieldName;
65
67
  }
68
+ function getFieldType(uuid, fieldName) {
69
+ const host = resolveHost(uuid);
70
+ if (!host) return null;
71
+ const cfg = types.editableFieldConfig.forName(
72
+ host.entityType,
73
+ host.bundle,
74
+ fieldName
75
+ );
76
+ if (!cfg || cfg.type === "table") return null;
77
+ return cfg.type === "plain" ? "plain" : "markup";
78
+ }
66
79
  function getCurrentValue(uuid, fieldName) {
67
80
  const host = resolveHost(uuid);
68
81
  if (!host) return null;
@@ -72,27 +85,29 @@ function getCurrentValue(uuid, fieldName) {
72
85
  uuid
73
86
  });
74
87
  if (!el) return null;
75
- const cfg = types.editableFieldConfig.forName(
76
- host.entityType,
77
- host.bundle,
78
- fieldName
79
- );
80
- if (!cfg || cfg.type === "table") return null;
81
- return cfg.type === "plain" ? el.textContent || "" : el.innerHTML;
88
+ const fieldType = getFieldType(uuid, fieldName);
89
+ if (fieldType === null) return null;
90
+ return fieldType === "plain" ? el.textContent || "" : el.innerHTML;
82
91
  }
83
92
  let idCounter = 0;
84
93
  const beforeValues = /* @__PURE__ */ new Map();
85
94
  function buildItems() {
86
95
  const result = [];
96
+ const pushItem = (uuid, fieldName, value, fieldType) => {
97
+ result.push({
98
+ id: idCounter++,
99
+ uuid,
100
+ fieldName,
101
+ fieldLabel: resolveFieldLabel(uuid, fieldName),
102
+ value,
103
+ fieldType
104
+ });
105
+ };
87
106
  if (props.params.updates) {
88
107
  for (const { uuid, fieldName, value } of props.params.updates) {
89
- result.push({
90
- id: idCounter++,
91
- uuid,
92
- fieldName,
93
- fieldLabel: resolveFieldLabel(uuid, fieldName),
94
- value
95
- });
108
+ const fieldType = getFieldType(uuid, fieldName);
109
+ if (fieldType === null) continue;
110
+ pushItem(uuid, fieldName, value, fieldType);
96
111
  }
97
112
  }
98
113
  if (props.params.operations?.length) {
@@ -113,46 +128,55 @@ function buildItems() {
113
128
  for (const { uuid, fieldName, ops } of grouped.values()) {
114
129
  const current = getCurrentValue(uuid, fieldName);
115
130
  if (current === null) continue;
131
+ const fieldType = getFieldType(uuid, fieldName);
132
+ if (fieldType === null) continue;
116
133
  const newValue = applyOperations(current, ops);
117
134
  if (newValue === current) continue;
118
- result.push({
119
- id: idCounter++,
120
- uuid,
121
- fieldName,
122
- fieldLabel: resolveFieldLabel(uuid, fieldName),
123
- value: newValue
124
- });
135
+ pushItem(uuid, fieldName, newValue, fieldType);
125
136
  }
126
137
  }
127
138
  return result;
128
139
  }
129
- const items = buildItems().filter((item) => {
130
- const current = getCurrentValue(item.uuid, item.fieldName);
140
+ const items = [];
141
+ for (const draft of buildItems()) {
142
+ const current = getCurrentValue(draft.uuid, draft.fieldName);
131
143
  if (current !== null) {
132
- beforeValues.set(item.id, current);
144
+ beforeValues.set(draft.id, current);
145
+ if (current === draft.value) continue;
133
146
  }
134
- return current === null || current !== item.value;
135
- });
147
+ const segments = current !== null ? splitIntoSegments(current, draft.value, draft.fieldType) ?? void 0 : void 0;
148
+ items.push({
149
+ id: draft.id,
150
+ uuid: draft.uuid,
151
+ fieldName: draft.fieldName,
152
+ fieldLabel: draft.fieldLabel,
153
+ value: draft.value,
154
+ segments
155
+ });
156
+ }
136
157
  async function applySelected(data) {
137
158
  const { selected, reasons } = data;
138
159
  isApplying.value = true;
139
- const { acceptedCount, rejectedByUser, label } = await applyFieldDiffs(
160
+ const { acceptedCount, rejectedByUser, label, appliedByItemId } = await applyFieldDiffs(
140
161
  blokkli,
141
162
  props.context.adapter,
142
163
  items,
143
164
  selected,
144
165
  reasons
145
166
  );
146
- const _details = items.filter((item) => selected[item.id]).map((item) => ({
167
+ const _details = items.filter((item) => appliedByItemId[item.id] !== void 0).map((item) => ({
147
168
  fieldLabel: item.fieldLabel,
148
169
  before: beforeValues.get(item.id) || "",
149
- after: item.value
170
+ after: appliedByItemId[item.id]
150
171
  }));
151
172
  emitDone({
152
173
  acceptedCount,
153
174
  rejectedByUser,
154
175
  label,
155
- agentMessage: rejectedWithoutReasonMessage(rejectedByUser),
176
+ agentMessage: appendAgentNote(
177
+ partialRejectionGuidance(rejectedByUser),
178
+ rejectedWithoutReasonMessage(rejectedByUser)
179
+ ),
156
180
  historyIndex: state.currentMutationIndex.value,
157
181
  _details
158
182
  });
@@ -9,6 +9,17 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
9
9
  acceptedCount: number;
10
10
  rejectedByUser: Record<string, Record<string, {
11
11
  reasonForRejection: string;
12
+ partial?: {
13
+ accepted: number;
14
+ total: number;
15
+ rejectedSegments: {
16
+ tag: string;
17
+ beforeHtml: string;
18
+ afterHtml: string;
19
+ status: "deleted" | "matched" | "inserted";
20
+ reasonForRejection: string;
21
+ }[];
22
+ } | undefined;
12
23
  }>>;
13
24
  label: string;
14
25
  agentMessage?: string | undefined;
@@ -19,6 +30,17 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
19
30
  acceptedCount: number;
20
31
  rejectedByUser: Record<string, Record<string, {
21
32
  reasonForRejection: string;
33
+ partial?: {
34
+ accepted: number;
35
+ total: number;
36
+ rejectedSegments: {
37
+ tag: string;
38
+ beforeHtml: string;
39
+ afterHtml: string;
40
+ status: "deleted" | "matched" | "inserted";
41
+ reasonForRejection: string;
42
+ }[];
43
+ } | undefined;
22
44
  }>>;
23
45
  label: string;
24
46
  agentMessage?: string | undefined;
@@ -46,6 +46,21 @@ export declare const resultSchema: z.ZodObject<{
46
46
  acceptedCount: z.ZodNumber;
47
47
  rejectedByUser: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodObject<{
48
48
  reasonForRejection: z.ZodString;
49
+ partial: z.ZodOptional<z.ZodObject<{
50
+ accepted: z.ZodNumber;
51
+ total: z.ZodNumber;
52
+ rejectedSegments: z.ZodArray<z.ZodObject<{
53
+ tag: z.ZodString;
54
+ beforeHtml: z.ZodString;
55
+ afterHtml: z.ZodString;
56
+ status: z.ZodEnum<{
57
+ deleted: "deleted";
58
+ matched: "matched";
59
+ inserted: "inserted";
60
+ }>;
61
+ reasonForRejection: z.ZodString;
62
+ }, z.core.$strip>>;
63
+ }, z.core.$strip>>;
49
64
  }, z.core.$strip>>>;
50
65
  label: z.ZodString;
51
66
  agentMessage: z.ZodOptional<z.ZodString>;
@@ -25,7 +25,7 @@ export default defineBlokkliAgentSystemPrompt({
25
25
  - The available options change based on various factors, such as the value of other options, the specific state of the paragraph's field values, etc. Always first check which options are available.
26
26
 
27
27
  ### EXTRA UX FEATURES
28
- - You can create markdown links for a specific paragraph by using the paragraph UUID! For example: "You should rewrite [this text](#UUID)". This will be converted to a HTML link the user can click on!
28
+ - To reference a specific paragraph, write its bare UUID inline. For example: "You should rewrite aaaaaaaa-1111-2222-3333-444444444444." The frontend renders the UUID as a clickable chip labelled with the paragraph's bundle. Do **not** wrap the UUID in markdown link syntax \u2014 emit the UUID on its own.
29
29
  - You can address the user using the special "${PLACEHOLDER_USER_NAME}" placeholder for a friendly welcome message. This is magically replaced in the frontend with the name of the user!
30
30
 
31
31
  ### History and Undo/Redo
@@ -17,6 +17,7 @@ export default defineBlokkliAgentSystemPrompt({
17
17
  - The user's first message includes their selection inline as "[User has selected: <bundle> (<uuid>), ...]". Treat that as the target of vague references like "this", "these", "translate this", "make this bigger" \u2014 no extra tool call needed to identify them.
18
18
  - For LATER messages where the prompt implies acting on the current selection but the first-message annotation is absent or stale, call "get_selected_paragraphs" \u2014 the user may have changed their selection since the conversation started.
19
19
  - You can output text as you please, markdown is allowed!
20
+ - DO NOT use emojis when writing content, unless explicitly told to do so!
20
21
  `;
21
22
  }
22
23
  });
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import type { BlokkliChartData } from '#blokkli/charts/types';
3
3
  import type { McpToolContext } from '#blokkli/agent/app/types';
4
- import type { ColorOption } from '#blokkli/editor/types/config';
4
+ import type { ColorOption } from '#blokkli/types/colors';
5
5
  export declare const chartTypeEnum: z.ZodEnum<{
6
6
  [x: string]: string;
7
7
  }>;
@@ -1,11 +1,17 @@
1
1
  import { z } from "zod";
2
+ import { isColorIdValid } from "#blokkli/helpers/colors";
2
3
  import { getColorIdAtIndex } from "../../helpers/index.js";
3
4
  import { getChartTypeRuntime, getDefaultTypeOptions } from "../../chart-types/index.js";
4
5
  import { colorOptions } from "#blokkli-build/editor-config";
5
6
  import { definitionIds } from "#blokkli-build/charts-definitions";
6
7
  const agentChartTypeIds = definitionIds.filter((id) => id !== "advanced");
7
8
  export const chartTypeEnum = z.enum(agentChartTypeIds);
8
- const colorIds = Object.keys(colorOptions);
9
+ const colorIds = Object.entries(colorOptions).flatMap(([id, option]) => {
10
+ if ("shades" in option) {
11
+ return Object.keys(option.shades).map((shade) => `${id}.${shade}`);
12
+ }
13
+ return [id];
14
+ });
9
15
  export const chartColorEnum = z.enum(colorIds);
10
16
  export const chartSeriesSchema = z.object({
11
17
  name: z.string().describe("Series name (shown in legend)"),
@@ -89,8 +95,9 @@ export function validateAdvancedConfig(value) {
89
95
  return { value };
90
96
  }
91
97
  export function validateChartData(data, options) {
92
- const validIds = new Set(options.map((c) => c.id));
93
- const availableIds = options.map((c) => c.id);
98
+ const availableIds = options.flatMap(
99
+ (c) => c.shades?.length ? c.shades.map((s) => `${c.id}.${s.id}`) : [c.id]
100
+ );
94
101
  for (let i = 0; i < data.series.length; i++) {
95
102
  const series = data.series[i];
96
103
  if (series.data.length !== data.categories.length) {
@@ -103,7 +110,7 @@ export function validateChartData(data, options) {
103
110
  const series = data.series[i];
104
111
  if (!series.color) {
105
112
  series.color = getColorIdAtIndex(i, options);
106
- } else if (!validIds.has(series.color)) {
113
+ } else if (!isColorIdValid(series.color, options)) {
107
114
  return {
108
115
  error: `Invalid color ID "${series.color}" on series "${series.name}". Available colors: ${availableIds.join(", ")}`
109
116
  };
@@ -118,7 +125,7 @@ export function validateChartData(data, options) {
118
125
  } else {
119
126
  for (let i = 0; i < data.categoryColors.length; i++) {
120
127
  const id = data.categoryColors[i];
121
- if (!validIds.has(id)) {
128
+ if (!isColorIdValid(id, options)) {
122
129
  return {
123
130
  error: `Invalid categoryColor ID "${id}" at index ${i}. Available colors: ${availableIds.join(", ")}`
124
131
  };