@pilotiq/tiptap 3.20.0 → 4.0.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.
@@ -1,7 +1,7 @@
1
1
  import { Extension } from '@tiptap/core';
2
2
  import { Plugin, PluginKey } from '@tiptap/pm/state';
3
3
  import { Decoration, DecorationSet } from '@tiptap/pm/view';
4
- export const aiSuggestionPluginKey = new PluginKey('pilotiqAiSuggestion');
4
+ export const suggestionChipPluginKey = new PluginKey('pilotiqSuggestionChip');
5
5
  /**
6
6
  * Append or replace by id. Pure — exported for tests and so the same dedupe
7
7
  * shape can drive consumer-side mirror state.
@@ -55,7 +55,7 @@ export function sortForApproveAll(suggestions) {
55
55
  *
56
56
  * Usage:
57
57
  * ```ts
58
- * editor.commands.addAiSuggestion({
58
+ * editor.commands.addSuggestion({
59
59
  * id: 'seo-1',
60
60
  * from: 12,
61
61
  * to: 18,
@@ -63,30 +63,30 @@ export function sortForApproveAll(suggestions) {
63
63
  * source: { agentLabel: 'SEO' },
64
64
  * })
65
65
  * // …user clicks ✓ on the chip, or:
66
- * editor.commands.approveAiSuggestion('seo-1')
66
+ * editor.commands.approveSuggestion('seo-1')
67
67
  * ```
68
68
  *
69
69
  * Mounted by default inside `TiptapEditor`; consumer code reaches it through
70
70
  * the editor's command surface.
71
71
  */
72
- export const AiSuggestionExtension = Extension.create({
73
- name: 'pilotiqAiSuggestion',
72
+ export const SuggestionChipExtension = Extension.create({
73
+ name: 'pilotiqSuggestionChip',
74
74
  addOptions() {
75
75
  return {
76
- classPrefix: 'pilotiq-ai-suggestion',
76
+ classPrefix: 'pilotiq-suggestion',
77
77
  };
78
78
  },
79
79
  onCreate() {
80
80
  // Inject minimal default styles for the chip + strikethrough on first
81
81
  // mount so consumers see the visualization without wiring CSS. Idempotent
82
- // via the `data-pilotiq-ai-suggestion-styles` sentinel; consumers who
82
+ // via the `data-pilotiq-suggestion-styles` sentinel; consumers who
83
83
  // want full control just add their own `<style>` with the same class
84
84
  // names (last wins — the cascade picks user overrides over our defaults
85
85
  // since the user stylesheet appears AFTER our injected one in `<head>`
86
86
  // when imported via Vite/Webpack, OR via higher specificity).
87
87
  if (typeof document === 'undefined')
88
88
  return;
89
- const SENTINEL = 'data-pilotiq-ai-suggestion-styles';
89
+ const SENTINEL = 'data-pilotiq-suggestion-styles';
90
90
  if (document.head.querySelector(`style[${SENTINEL}]`))
91
91
  return;
92
92
  const prefix = this.options.classPrefix;
@@ -134,10 +134,10 @@ export const AiSuggestionExtension = Extension.create({
134
134
  /* Banner — bottom-of-editor strip for whole-field suggestions on rich
135
135
  surfaces (markdown / richtext). Sibling to the chip styles above;
136
136
  lives here so both ship via the same extension-mount sentinel.
137
- Class names live under \`pilotiq-ai-banner-*\` (not \`-suggestion-\`)
137
+ Class names live under \`pilotiq-suggestion-banner-*\` (not \`-suggestion-\`)
138
138
  since the banner is a host-mounted React component, not a PM
139
139
  decoration. */
140
- .pilotiq-ai-banner {
140
+ .pilotiq-suggestion-banner {
141
141
  display: flex;
142
142
  align-items: center;
143
143
  gap: 0.5rem;
@@ -150,15 +150,15 @@ export const AiSuggestionExtension = Extension.create({
150
150
  font-size: 0.875rem;
151
151
  line-height: 1.4;
152
152
  }
153
- .pilotiq-ai-banner-icon { flex: 0 0 auto; }
154
- .pilotiq-ai-banner-label { flex: 1 1 auto; }
155
- .pilotiq-ai-banner-actions {
153
+ .pilotiq-suggestion-banner-icon { flex: 0 0 auto; }
154
+ .pilotiq-suggestion-banner-label { flex: 1 1 auto; }
155
+ .pilotiq-suggestion-banner-actions {
156
156
  display: inline-flex;
157
157
  gap: 0.375rem;
158
158
  flex: 0 0 auto;
159
159
  }
160
- .pilotiq-ai-banner-reject,
161
- .pilotiq-ai-banner-accept {
160
+ .pilotiq-suggestion-banner-reject,
161
+ .pilotiq-suggestion-banner-accept {
162
162
  appearance: none;
163
163
  cursor: pointer;
164
164
  font-size: 0.8125rem;
@@ -168,50 +168,50 @@ export const AiSuggestionExtension = Extension.create({
168
168
  border-radius: 0.25rem;
169
169
  border: 1px solid transparent;
170
170
  }
171
- .pilotiq-ai-banner-reject {
171
+ .pilotiq-suggestion-banner-reject {
172
172
  background-color: transparent;
173
173
  color: rgb(120, 53, 15);
174
174
  border-color: rgba(180, 83, 9, 0.4);
175
175
  }
176
- .pilotiq-ai-banner-reject:hover {
176
+ .pilotiq-suggestion-banner-reject:hover {
177
177
  background-color: rgba(254, 215, 170, 0.4);
178
178
  }
179
- .pilotiq-ai-banner-accept {
179
+ .pilotiq-suggestion-banner-accept {
180
180
  background-color: rgb(22, 101, 52);
181
181
  color: white;
182
182
  }
183
- .pilotiq-ai-banner-accept:hover { background-color: rgb(21, 128, 61); }
183
+ .pilotiq-suggestion-banner-accept:hover { background-color: rgb(21, 128, 61); }
184
184
  `;
185
185
  document.head.appendChild(style);
186
186
  },
187
187
  addCommands() {
188
188
  return {
189
- addAiSuggestion: (suggestion) => ({ tr, state, dispatch }) => {
190
- const current = aiSuggestionPluginKey.getState(state)?.suggestions ?? [];
189
+ addSuggestion: (suggestion) => ({ tr, state, dispatch }) => {
190
+ const current = suggestionChipPluginKey.getState(state)?.suggestions ?? [];
191
191
  const next = upsertSuggestion(current, suggestion);
192
192
  if (dispatch) {
193
- tr.setMeta(aiSuggestionPluginKey, { type: 'set', next });
193
+ tr.setMeta(suggestionChipPluginKey, { type: 'set', next });
194
194
  dispatch(tr);
195
195
  }
196
196
  return true;
197
197
  },
198
- addAiSuggestions: (suggestions) => ({ tr, state, dispatch }) => {
199
- const current = aiSuggestionPluginKey.getState(state)?.suggestions ?? [];
198
+ addSuggestions: (suggestions) => ({ tr, state, dispatch }) => {
199
+ const current = suggestionChipPluginKey.getState(state)?.suggestions ?? [];
200
200
  const next = upsertSuggestions(current, suggestions);
201
201
  if (dispatch) {
202
- tr.setMeta(aiSuggestionPluginKey, { type: 'set', next });
202
+ tr.setMeta(suggestionChipPluginKey, { type: 'set', next });
203
203
  dispatch(tr);
204
204
  }
205
205
  return true;
206
206
  },
207
- approveAiSuggestion: (id) => ({ tr, state, dispatch }) => {
208
- const current = aiSuggestionPluginKey.getState(state)?.suggestions ?? [];
207
+ approveSuggestion: (id) => ({ tr, state, dispatch }) => {
208
+ const current = suggestionChipPluginKey.getState(state)?.suggestions ?? [];
209
209
  const target = current.find(s => s.id === id);
210
210
  if (!target)
211
211
  return false;
212
212
  if (dispatch) {
213
213
  applyApprove(tr, state, target);
214
- tr.setMeta(aiSuggestionPluginKey, {
214
+ tr.setMeta(suggestionChipPluginKey, {
215
215
  type: 'set',
216
216
  next: removeSuggestion(current, id),
217
217
  });
@@ -219,13 +219,13 @@ export const AiSuggestionExtension = Extension.create({
219
219
  }
220
220
  return true;
221
221
  },
222
- rejectAiSuggestion: (id) => ({ tr, state, dispatch }) => {
223
- const current = aiSuggestionPluginKey.getState(state)?.suggestions ?? [];
222
+ rejectSuggestion: (id) => ({ tr, state, dispatch }) => {
223
+ const current = suggestionChipPluginKey.getState(state)?.suggestions ?? [];
224
224
  const target = current.find(s => s.id === id);
225
225
  if (!target)
226
226
  return false;
227
227
  if (dispatch) {
228
- tr.setMeta(aiSuggestionPluginKey, {
228
+ tr.setMeta(suggestionChipPluginKey, {
229
229
  type: 'set',
230
230
  next: removeSuggestion(current, id),
231
231
  });
@@ -233,14 +233,14 @@ export const AiSuggestionExtension = Extension.create({
233
233
  }
234
234
  return true;
235
235
  },
236
- approveAllAiSuggestions: () => ({ tr, state, dispatch }) => {
237
- const current = aiSuggestionPluginKey.getState(state)?.suggestions ?? [];
236
+ approveAllSuggestions: () => ({ tr, state, dispatch }) => {
237
+ const current = suggestionChipPluginKey.getState(state)?.suggestions ?? [];
238
238
  if (current.length === 0)
239
239
  return false;
240
240
  if (dispatch) {
241
241
  for (const s of sortForApproveAll(current))
242
242
  applyApprove(tr, state, s);
243
- tr.setMeta(aiSuggestionPluginKey, {
243
+ tr.setMeta(suggestionChipPluginKey, {
244
244
  type: 'set',
245
245
  next: [],
246
246
  });
@@ -248,12 +248,12 @@ export const AiSuggestionExtension = Extension.create({
248
248
  }
249
249
  return true;
250
250
  },
251
- rejectAllAiSuggestions: () => ({ tr, state, dispatch }) => {
252
- const current = aiSuggestionPluginKey.getState(state)?.suggestions ?? [];
251
+ rejectAllSuggestions: () => ({ tr, state, dispatch }) => {
252
+ const current = suggestionChipPluginKey.getState(state)?.suggestions ?? [];
253
253
  if (current.length === 0)
254
254
  return false;
255
255
  if (dispatch) {
256
- tr.setMeta(aiSuggestionPluginKey, {
256
+ tr.setMeta(suggestionChipPluginKey, {
257
257
  type: 'set',
258
258
  next: [],
259
259
  });
@@ -261,12 +261,12 @@ export const AiSuggestionExtension = Extension.create({
261
261
  }
262
262
  return true;
263
263
  },
264
- clearAiSuggestions: () => ({ tr, state, dispatch }) => {
265
- const current = aiSuggestionPluginKey.getState(state)?.suggestions ?? [];
264
+ clearSuggestions: () => ({ tr, state, dispatch }) => {
265
+ const current = suggestionChipPluginKey.getState(state)?.suggestions ?? [];
266
266
  if (current.length === 0)
267
267
  return false;
268
268
  if (dispatch) {
269
- tr.setMeta(aiSuggestionPluginKey, {
269
+ tr.setMeta(suggestionChipPluginKey, {
270
270
  type: 'set',
271
271
  next: [],
272
272
  });
@@ -280,11 +280,11 @@ export const AiSuggestionExtension = Extension.create({
280
280
  const ext = this;
281
281
  return [
282
282
  new Plugin({
283
- key: aiSuggestionPluginKey,
283
+ key: suggestionChipPluginKey,
284
284
  state: {
285
285
  init: () => ({ suggestions: [] }),
286
286
  apply(tr, prev) {
287
- const meta = tr.getMeta(aiSuggestionPluginKey);
287
+ const meta = tr.getMeta(suggestionChipPluginKey);
288
288
  const base = meta?.type === 'set' ? meta.next : prev.suggestions;
289
289
  if (!tr.docChanged)
290
290
  return { suggestions: base };
@@ -295,17 +295,17 @@ export const AiSuggestionExtension = Extension.create({
295
295
  },
296
296
  props: {
297
297
  decorations(state) {
298
- const ps = aiSuggestionPluginKey.getState(state);
298
+ const ps = suggestionChipPluginKey.getState(state);
299
299
  if (!ps || ps.suggestions.length === 0)
300
300
  return DecorationSet.empty;
301
301
  return buildDecorations(state, ps.suggestions, ext.options.classPrefix, ext.editor);
302
302
  },
303
303
  },
304
304
  view(view) {
305
- let last = aiSuggestionPluginKey.getState(view.state)?.suggestions;
305
+ let last = suggestionChipPluginKey.getState(view.state)?.suggestions;
306
306
  return {
307
307
  update(updated) {
308
- const next = aiSuggestionPluginKey.getState(updated.state)?.suggestions;
308
+ const next = suggestionChipPluginKey.getState(updated.state)?.suggestions;
309
309
  if (next === last)
310
310
  return;
311
311
  last = next;
@@ -344,13 +344,13 @@ function buildDecorations(state, suggestions, prefix, editor) {
344
344
  if (from < to) {
345
345
  decos.push(Decoration.inline(from, to, {
346
346
  class: `${prefix}-original`,
347
- 'data-pilotiq-ai-suggestion-id': s.id,
347
+ 'data-pilotiq-suggestion-id': s.id,
348
348
  }));
349
349
  }
350
350
  decos.push(Decoration.widget(to, () => buildChip(s, prefix, editor), {
351
351
  side: 1,
352
352
  ignoreSelection: true,
353
- key: `pilotiq-ai-suggestion:${s.id}`,
353
+ key: `pilotiq-suggestion:${s.id}`,
354
354
  }));
355
355
  }
356
356
  return DecorationSet.create(state.doc, decos);
@@ -368,7 +368,7 @@ export function clampPos(pos, max) {
368
368
  function buildChip(s, prefix, editor) {
369
369
  const root = document.createElement('span');
370
370
  root.className = `${prefix}-chip`;
371
- root.setAttribute('data-pilotiq-ai-suggestion-id', s.id);
371
+ root.setAttribute('data-pilotiq-suggestion-id', s.id);
372
372
  root.contentEditable = 'false';
373
373
  if (s.replacement.length > 0) {
374
374
  const insert = document.createElement('span');
@@ -377,16 +377,16 @@ function buildChip(s, prefix, editor) {
377
377
  root.appendChild(insert);
378
378
  }
379
379
  if (s.source?.agentLabel) {
380
- root.setAttribute('data-pilotiq-ai-suggestion-source', s.source.agentLabel);
380
+ root.setAttribute('data-pilotiq-suggestion-source', s.source.agentLabel);
381
381
  }
382
382
  if (s.source?.agentSlug) {
383
- root.setAttribute('data-pilotiq-ai-suggestion-source-slug', s.source.agentSlug);
383
+ root.setAttribute('data-pilotiq-suggestion-source-slug', s.source.agentSlug);
384
384
  }
385
385
  root.appendChild(buildButton(prefix, 'accept', '✓', 'Accept suggestion', () => {
386
- editor.chain().focus().approveAiSuggestion(s.id).run();
386
+ editor.chain().focus().approveSuggestion(s.id).run();
387
387
  }));
388
388
  root.appendChild(buildButton(prefix, 'reject', '✕', 'Reject suggestion', () => {
389
- editor.chain().focus().rejectAiSuggestion(s.id).run();
389
+ editor.chain().focus().rejectSuggestion(s.id).run();
390
390
  }));
391
391
  return root;
392
392
  }
package/dist/index.d.ts CHANGED
@@ -6,10 +6,10 @@ export { registerTiptap } from './register.js';
6
6
  export { createPlainTextEditor, plainTextOf, plainTextToDoc, type PlainTextEditorOptions, } from './PlainTextEditor.js';
7
7
  export { tiptap } from './plugin.js';
8
8
  export { TiptapEditor } from './react/TiptapEditor.js';
9
- export { AiSuggestionExtension, aiSuggestionPluginKey, upsertSuggestion, upsertSuggestions, removeSuggestion, remapSuggestions, sortForApproveAll, clampPos, type AiSuggestion, type AiSuggestionExtensionOptions, } from './extensions/AiSuggestionExtension.js';
10
- export { useAiSuggestionBridge } from './react/useAiSuggestionBridge.js';
11
- export { AiInlineDiffExtension, aiInlineDiffPluginKey, getAiInlineDiffState, type AiInlineDiffExtensionOptions, } from './extensions/AiInlineDiffExtension.js';
12
- export { planReplaceBlock, planInsertBlockBefore, planDeleteBlock, planWrapBlocks, planUpdateBlockMark, summarizeBlockStructure, type BlockMarkRange, type TransactionModifier, } from './surgicalOps.js';
9
+ export { SuggestionChipExtension, suggestionChipPluginKey, upsertSuggestion, upsertSuggestions, removeSuggestion, remapSuggestions, sortForApproveAll, clampPos, type InlineSuggestion, type SuggestionChipExtensionOptions, } from './extensions/SuggestionChipExtension.js';
10
+ export { useSuggestionBridge } from './react/useSuggestionBridge.js';
11
+ export { InlineDiffExtension, inlineDiffPluginKey, getInlineDiffState, type InlineDiffExtensionOptions, } from './extensions/InlineDiffExtension.js';
12
+ export { planReplaceBlock, planInsertBlockBefore, planDeleteBlock, planWrapBlocks, planUpdateBlockMark, planReplaceText, summarizeBlockStructure, type BlockMarkRange, type TransactionModifier, } from './surgicalOps.js';
13
13
  export { renderRichTextToHtml, isRichTextValue, type RenderRichTextOptions, type TiptapNode, type TiptapMark, } from './render.js';
14
14
  export { contentBlockNodes, Intro, Faq, FaqItem, FaqQuestion, FaqAnswer, Alert, AlertTitle, AlertBody, Summary, KeyTakeaways, ProsCons, ProsColumn, ConsColumn, ContentBlockKeymap, LabeledBlockExitKeymap, planExitLabeledBlock, isSelectionInAlert, ALERT_VARIANTS, ALERT_VARIANT_LABEL, coerceAlertType, type AlertType, } from './extensions/contentBlocks.js';
15
15
  export { shouldShowFloatingToolbar, TOOLBAR_MARKS } from './react/floatingToolbarVisibility.js';
package/dist/index.js CHANGED
@@ -6,10 +6,10 @@ export { registerTiptap } from './register.js';
6
6
  export { createPlainTextEditor, plainTextOf, plainTextToDoc, } from './PlainTextEditor.js';
7
7
  export { tiptap } from './plugin.js';
8
8
  export { TiptapEditor } from './react/TiptapEditor.js';
9
- export { AiSuggestionExtension, aiSuggestionPluginKey, upsertSuggestion, upsertSuggestions, removeSuggestion, remapSuggestions, sortForApproveAll, clampPos, } from './extensions/AiSuggestionExtension.js';
10
- export { useAiSuggestionBridge } from './react/useAiSuggestionBridge.js';
11
- export { AiInlineDiffExtension, aiInlineDiffPluginKey, getAiInlineDiffState, } from './extensions/AiInlineDiffExtension.js';
12
- export { planReplaceBlock, planInsertBlockBefore, planDeleteBlock, planWrapBlocks, planUpdateBlockMark, summarizeBlockStructure, } from './surgicalOps.js';
9
+ export { SuggestionChipExtension, suggestionChipPluginKey, upsertSuggestion, upsertSuggestions, removeSuggestion, remapSuggestions, sortForApproveAll, clampPos, } from './extensions/SuggestionChipExtension.js';
10
+ export { useSuggestionBridge } from './react/useSuggestionBridge.js';
11
+ export { InlineDiffExtension, inlineDiffPluginKey, getInlineDiffState, } from './extensions/InlineDiffExtension.js';
12
+ export { planReplaceBlock, planInsertBlockBefore, planDeleteBlock, planWrapBlocks, planUpdateBlockMark, planReplaceText, summarizeBlockStructure, } from './surgicalOps.js';
13
13
  export { renderRichTextToHtml, isRichTextValue, } from './render.js';
14
14
  // Default content-block node specs (Intro / FAQ / Alert / Summary / Key takeaways /
15
15
  // Pros & cons). `contentBlockNodes` is the exact array `TiptapEditor` registers,
@@ -5,11 +5,11 @@ import { Slice } from '@tiptap/pm/model';
5
5
  import { useCollabRoom, getCollabExtensions, } from '@pilotiq/pilotiq/react';
6
6
  import { useCollabSeed } from '@rudderjs/sync/react';
7
7
  import { createPlainTextEditor, plainTextOf, plainTextToDoc } from '../PlainTextEditor.js';
8
- import { AiSuggestionExtension } from '../extensions/AiSuggestionExtension.js';
9
- import { AiInlineDiffExtension } from '../extensions/AiInlineDiffExtension.js';
10
- import { useAiSuggestionBridge } from './useAiSuggestionBridge.js';
11
- import { useAiInlineDiff, useIsAiInlineDiffActive, readAiDiffViewMarker } from './useAiInlineDiff.js';
12
- import { AiSuggestionBanner } from './AiSuggestionBanner.js';
8
+ import { SuggestionChipExtension } from '../extensions/SuggestionChipExtension.js';
9
+ import { InlineDiffExtension } from '../extensions/InlineDiffExtension.js';
10
+ import { useSuggestionBridge } from './useSuggestionBridge.js';
11
+ import { useInlineDiff, useIsInlineDiffActive, readDiffViewMarker } from './useInlineDiff.js';
12
+ import { SuggestionBanner } from './SuggestionBanner.js';
13
13
  /**
14
14
  * Tiptap-backed plain-text editor for pilotiq's `TextField` / `TextareaField`
15
15
  * / similar single-line / multi-line text fields when collab is on.
@@ -100,11 +100,11 @@ export function CollabTextRenderer({ name, fragmentKey, multiline, defaultValue,
100
100
  // AI suggestions — chip extension (producer-supplied range
101
101
  // suggestions) + inline-diff extension (whole-field suggestions:
102
102
  // red strikethrough on removed runs, green on inserted, with the
103
- // `<AiSuggestionBanner>` Accept / Reject below). Both idle until
103
+ // `<SuggestionBanner>` Accept / Reject below). Both idle until
104
104
  // a suggestion arrives via the bridges below. Matches the
105
105
  // `TiptapEditor` wiring so the review surface reads identically
106
106
  // across RichTextField / MarkdownField / TextField+TextareaField.
107
- extensions: [...collabExtensions, AiSuggestionExtension, AiInlineDiffExtension],
107
+ extensions: [...collabExtensions, SuggestionChipExtension, InlineDiffExtension],
108
108
  onUpdate: (text) => onChange(text),
109
109
  ...(onSubmit ? { onSubmit: () => { onSubmit(); return false; } } : {}),
110
110
  ...(className || editorAttributes
@@ -128,11 +128,11 @@ export function CollabTextRenderer({ name, fragmentKey, multiline, defaultValue,
128
128
  editor.setEditable(!disabled);
129
129
  }, [editor, disabled]);
130
130
  // Cross-package suggestion bridge — sync the host's
131
- // `<PendingSuggestionsContext>` queue with the editor's `AiSuggestion`
131
+ // `<PendingSuggestionsContext>` queue with the editor's `InlineSuggestion`
132
132
  // extension. No-op when no provider is mounted (default no-op context).
133
133
  //
134
134
  // Whole-field suggestions do NOT synthesize a chip range anymore —
135
- // they render through `useAiInlineDiff` below (same red/green inline
135
+ // they render through `useInlineDiff` below (same red/green inline
136
136
  // diff + banner as `TiptapEditor`), replacing the old green-pill chip
137
137
  // that read differently from the rich-text surface. The bridge stays
138
138
  // mounted for producer-supplied `meta.editorRange` suggestions (precise
@@ -143,12 +143,12 @@ export function CollabTextRenderer({ name, fragmentKey, multiline, defaultValue,
143
143
  return;
144
144
  editor.commands.setContent(plainTextToDoc(value, !!multiline));
145
145
  };
146
- useAiSuggestionBridge(editor ?? null, name, {
146
+ useSuggestionBridge(editor ?? null, name, {
147
147
  onApplyWholeField: applyWholeField,
148
148
  });
149
149
  // Inline diff for whole-field suggestions — plain-text shape: each
150
150
  // line wraps in a `paragraph` node, mirroring `plainTextToDoc`.
151
- useAiInlineDiff(editor ?? null, name, {
151
+ useInlineDiff(editor ?? null, name, {
152
152
  parseSuggestion: (ed, value) => {
153
153
  try {
154
154
  const node = ed.schema.nodeFromJSON(plainTextToDoc(value, !!multiline));
@@ -158,9 +158,9 @@ export function CollabTextRenderer({ name, fragmentKey, multiline, defaultValue,
158
158
  return null;
159
159
  }
160
160
  },
161
- resolveDisplayMode: () => readAiDiffViewMarker(name),
161
+ resolveDisplayMode: () => readDiffViewMarker(name),
162
162
  });
163
- const isDiffActive = useIsAiInlineDiffActive(editor ?? null);
163
+ const isDiffActive = useIsInlineDiffActive(editor ?? null);
164
164
  // First-load seed when collab is active. Collaboration starts the editor
165
165
  // empty regardless of `defaultValue`; once the room's first sync
166
166
  // resolves, `useCollabSeed` runs the callback inside `ydoc.transact`.
@@ -216,10 +216,10 @@ export function CollabTextRenderer({ name, fragmentKey, multiline, defaultValue,
216
216
  // Banner mounts below the editor exactly like `TiptapEditor`'s — it
217
217
  // renders nothing while no suggestion is pending for this field, so
218
218
  // the single-line text surface keeps its normal footprint.
219
- return (_jsxs(_Fragment, { children: [_jsx(EditorContent, { editor: editor }), _jsx(AiSuggestionBanner, { fieldName: name, onApplyWholeField: applyWholeField, ...(isDiffActive && editor
219
+ return (_jsxs(_Fragment, { children: [_jsx(EditorContent, { editor: editor }), _jsx(SuggestionBanner, { fieldName: name, onApplyWholeField: applyWholeField, ...(isDiffActive && editor
220
220
  ? {
221
- onAcceptViaEditor: () => editor.commands.acceptAiInlineDiff(),
222
- onRejectViaEditor: () => editor.commands.rejectAiInlineDiff(),
221
+ onAcceptViaEditor: () => editor.commands.acceptInlineDiff(),
222
+ onRejectViaEditor: () => editor.commands.rejectInlineDiff(),
223
223
  }
224
224
  : {}) })] }));
225
225
  }
@@ -13,12 +13,12 @@ import { DOMParser as ProseMirrorDOMParser } from '@tiptap/pm/model';
13
13
  import { Markdown } from '../markdownExtension.js';
14
14
  import { useCollabRoom, getCollabExtensions, useToast, } from '@pilotiq/pilotiq/react';
15
15
  import { useCollabSeed } from '@rudderjs/sync/react';
16
- import { AiSuggestionExtension } from '../extensions/AiSuggestionExtension.js';
17
- import { AiInlineDiffExtension } from '../extensions/AiInlineDiffExtension.js';
16
+ import { SuggestionChipExtension } from '../extensions/SuggestionChipExtension.js';
17
+ import { InlineDiffExtension } from '../extensions/InlineDiffExtension.js';
18
18
  import { Alert, AlertTitle, AlertBody, ContentBlockKeymap } from '../extensions/contentBlocks.js';
19
- import { useAiSuggestionBridge } from './useAiSuggestionBridge.js';
20
- import { useAiInlineDiff, useIsAiInlineDiffActive, readAiDiffViewMarker } from './useAiInlineDiff.js';
21
- import { AiSuggestionBanner } from './AiSuggestionBanner.js';
19
+ import { useSuggestionBridge } from './useSuggestionBridge.js';
20
+ import { useInlineDiff, useIsInlineDiffActive, readDiffViewMarker } from './useInlineDiff.js';
21
+ import { SuggestionBanner } from './SuggestionBanner.js';
22
22
  import { getMarkdownString, parseMarkdownToHtml } from '../markdownStorage.js';
23
23
  // Inline lucide.dev SVGs — same posture as `toolbarButtons.tsx` so this
24
24
  // package doesn't pull `lucide-react` as a peer dep. Keep stroke / size
@@ -135,13 +135,13 @@ export function MarkdownEditor({ name, fragmentKey, defaultValue, placeholder, d
135
135
  Image.configure({ inline: false, allowBase64: false }),
136
136
  Placeholder.configure({ placeholder: placeholder ?? 'Write in markdown…' }),
137
137
  // AI suggestions — chip widget for surgical (range-anchored) edits.
138
- AiSuggestionExtension,
138
+ SuggestionChipExtension,
139
139
  // AI inline diff — Tiptap-Pro-style visualization for whole-field
140
140
  // suggestions (prosemirror-changeset under the hood). Decorations
141
141
  // show green-background inserts inline + red-strikethrough widgets
142
- // for deleted text. Host's `<AiSuggestionBanner>` drives Accept /
142
+ // for deleted text. Host's `<SuggestionBanner>` drives Accept /
143
143
  // Reject via the extension's commands.
144
- AiInlineDiffExtension,
144
+ InlineDiffExtension,
145
145
  // Alert content block — round-trips to `:::alert{type=…} Title` via the
146
146
  // node's `markdown` storage spec; renders the same shadcn NodeView.
147
147
  Alert,
@@ -167,13 +167,13 @@ export function MarkdownEditor({ name, fragmentKey, defaultValue, placeholder, d
167
167
  editor.setEditable(!disabled && tab === 'editor');
168
168
  }, [editor, disabled, tab]);
169
169
  // Cross-package suggestion bridge — sync the host's
170
- // `<PendingSuggestionsContext>` queue with the editor's `AiSuggestion`
170
+ // `<PendingSuggestionsContext>` queue with the editor's `InlineSuggestion`
171
171
  // extension. No-op when no provider is mounted (default no-op context).
172
172
  //
173
173
  // Whole-field handling: NO chip widget here. The chip's `textContent`
174
174
  // renderer surfaces raw markdown (`## Heading\n- item`) as literal text
175
175
  // inside the green pill — visually unparseable for multi-paragraph
176
- // rewrites. Instead, `<AiSuggestionBanner>` mounts below the editor
176
+ // rewrites. Instead, `<SuggestionBanner>` mounts below the editor
177
177
  // (see render below). Producer-supplied range suggestions still ride
178
178
  // the inline chip path — those have a precise anchor worth showing
179
179
  // in context.
@@ -182,7 +182,7 @@ export function MarkdownEditor({ name, fragmentKey, defaultValue, placeholder, d
182
182
  return;
183
183
  editor.commands.setContent(value);
184
184
  };
185
- useAiSuggestionBridge(editor ?? null, name, {
185
+ useSuggestionBridge(editor ?? null, name, {
186
186
  onApplyWholeField: applyWholeField,
187
187
  });
188
188
  // Inline diff for whole-field suggestions — replaces the editor doc with
@@ -194,7 +194,7 @@ export function MarkdownEditor({ name, fragmentKey, defaultValue, placeholder, d
194
194
  // that HTML into a Slice against THIS editor's schema — same path
195
195
  // the editor's own clipboard-paste uses, so the slice is guaranteed
196
196
  // schema-valid.
197
- useAiInlineDiff(editor ?? null, name, {
197
+ useInlineDiff(editor ?? null, name, {
198
198
  parseSuggestion: (ed, value) => {
199
199
  try {
200
200
  const html = parseMarkdownToHtml(ed, value);
@@ -208,9 +208,9 @@ export function MarkdownEditor({ name, fragmentKey, defaultValue, placeholder, d
208
208
  return null;
209
209
  }
210
210
  },
211
- resolveDisplayMode: () => readAiDiffViewMarker(name),
211
+ resolveDisplayMode: () => readDiffViewMarker(name),
212
212
  });
213
- const isDiffActive = useIsAiInlineDiffActive(editor ?? null);
213
+ const isDiffActive = useIsInlineDiffActive(editor ?? null);
214
214
  // First-load seed for collab. Collaboration starts the editor empty
215
215
  // regardless of `content`; once the room's first sync resolves,
216
216
  // `useCollabSeed` runs the callback inside `ydoc.transact`. Empty
@@ -439,10 +439,10 @@ export function MarkdownEditor({ name, fragmentKey, defaultValue, placeholder, d
439
439
  : 'hover:bg-accent hover:text-accent-foreground',
440
440
  'disabled:opacity-50',
441
441
  ].join(' '), onClick: () => exec(b), disabled: disabled || (isAttach && uploading), title: labels[b] ?? b, "aria-label": labels[b] ?? b, "aria-pressed": active, children: isAttach && uploading ? Spinner : icon }, b));
442
- }) }))] }), tab === 'editor' && (_jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none px-3 py-2 [&_.ProseMirror]:outline-none [&_.ProseMirror]:min-h-[6rem]", style: wrapperStyle, children: _jsx(EditorContent, { editor: editor }) })), tab === 'source' && (_jsx("textarea", { className: "w-full resize-y bg-transparent px-3 py-2 text-sm font-mono leading-relaxed outline-none disabled:opacity-50", style: wrapperStyle, value: sourceDraft, onChange: (e) => setSourceDraft(e.target.value), ...(placeholder !== undefined ? { placeholder } : {}), disabled: disabled, "aria-label": `${name} (markdown source)` })), tab === 'preview' && (_jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none px-3 py-2", style: wrapperStyle, dangerouslySetInnerHTML: { __html: previewHtml || '<p class="text-muted-foreground italic">Nothing to preview</p>' } })), _jsx(AiSuggestionBanner, { fieldName: name, onApplyWholeField: applyWholeField, ...(isDiffActive && editor
442
+ }) }))] }), tab === 'editor' && (_jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none px-3 py-2 [&_.ProseMirror]:outline-none [&_.ProseMirror]:min-h-[6rem]", style: wrapperStyle, children: _jsx(EditorContent, { editor: editor }) })), tab === 'source' && (_jsx("textarea", { className: "w-full resize-y bg-transparent px-3 py-2 text-sm font-mono leading-relaxed outline-none disabled:opacity-50", style: wrapperStyle, value: sourceDraft, onChange: (e) => setSourceDraft(e.target.value), ...(placeholder !== undefined ? { placeholder } : {}), disabled: disabled, "aria-label": `${name} (markdown source)` })), tab === 'preview' && (_jsx("div", { className: "prose prose-sm dark:prose-invert max-w-none px-3 py-2", style: wrapperStyle, dangerouslySetInnerHTML: { __html: previewHtml || '<p class="text-muted-foreground italic">Nothing to preview</p>' } })), _jsx(SuggestionBanner, { fieldName: name, onApplyWholeField: applyWholeField, ...(isDiffActive && editor
443
443
  ? {
444
- onAcceptViaEditor: () => editor.commands.acceptAiInlineDiff(),
445
- onRejectViaEditor: () => editor.commands.rejectAiInlineDiff(),
444
+ onAcceptViaEditor: () => editor.commands.acceptInlineDiff(),
445
+ onRejectViaEditor: () => editor.commands.rejectInlineDiff(),
446
446
  }
447
447
  : {}) })] }));
448
448
  }
@@ -25,7 +25,7 @@ import { type PendingSuggestion } from '@pilotiq/pilotiq/react';
25
25
  * same field stack — Accept all / Reject all collapse the queue in one
26
26
  * pass.
27
27
  */
28
- export interface AiSuggestionBannerProps {
28
+ export interface SuggestionBannerProps {
29
29
  /** Field name, matches the suggestion's `fieldName`. */
30
30
  fieldName: string;
31
31
  /**
@@ -35,8 +35,8 @@ export interface AiSuggestionBannerProps {
35
35
  * MarkdownEditor, HTML / JSON for TiptapEditor).
36
36
  *
37
37
  * Skipped when `onAcceptViaEditor` is supplied — that path means the
38
- * editor already holds the proposed state via `AiInlineDiffExtension`,
39
- * and Accept routes through `acceptAiInlineDiff()` instead. The host
38
+ * editor already holds the proposed state via `InlineDiffExtension`,
39
+ * and Accept routes through `acceptInlineDiff()` instead. The host
40
40
  * still calls `pendingSuggestions.approve(id)` afterwards to dismiss
41
41
  * the queue entry.
42
42
  */
@@ -64,10 +64,10 @@ export interface AiSuggestionBannerProps {
64
64
  * Hook variant — returns banner state without rendering, for renderers
65
65
  * that want to compose their own chrome. Renderer-agnostic.
66
66
  */
67
- export declare function useAiSuggestionBanner(fieldName: string): {
67
+ export declare function useSuggestionBanner(fieldName: string): {
68
68
  pending: readonly PendingSuggestion[];
69
69
  approveAll: (apply: (value: string) => void) => void;
70
70
  rejectAll: () => void;
71
71
  };
72
- export declare function AiSuggestionBanner({ fieldName, onApplyWholeField, onAcceptViaEditor, onRejectViaEditor, className, }: AiSuggestionBannerProps): React.ReactElement | null;
73
- //# sourceMappingURL=AiSuggestionBanner.d.ts.map
72
+ export declare function SuggestionBanner({ fieldName, onApplyWholeField, onAcceptViaEditor, onRejectViaEditor, className, }: SuggestionBannerProps): React.ReactElement | null;
73
+ //# sourceMappingURL=SuggestionBanner.d.ts.map
@@ -5,7 +5,7 @@ import { usePendingSuggestionsForField, usePendingSuggestions, } from '@pilotiq/
5
5
  * Hook variant — returns banner state without rendering, for renderers
6
6
  * that want to compose their own chrome. Renderer-agnostic.
7
7
  */
8
- export function useAiSuggestionBanner(fieldName) {
8
+ export function useSuggestionBanner(fieldName) {
9
9
  const { list, dismiss } = usePendingSuggestionsForField(fieldName);
10
10
  // Only whole-field suggestions land in the banner. Range-anchored ones
11
11
  // ride the editor chip widget.
@@ -28,8 +28,8 @@ function hasEditorRange(s) {
28
28
  const range = meta['editorRange'];
29
29
  return !!(range && typeof range.from === 'number' && typeof range.to === 'number');
30
30
  }
31
- export function AiSuggestionBanner({ fieldName, onApplyWholeField, onAcceptViaEditor, onRejectViaEditor, className, }) {
32
- const { pending, approveAll, rejectAll } = useAiSuggestionBanner(fieldName);
31
+ export function SuggestionBanner({ fieldName, onApplyWholeField, onAcceptViaEditor, onRejectViaEditor, className, }) {
32
+ const { pending, approveAll, rejectAll } = useSuggestionBanner(fieldName);
33
33
  const { dismiss } = usePendingSuggestions();
34
34
  if (pending.length === 0)
35
35
  return null;
@@ -63,9 +63,9 @@ export function AiSuggestionBanner({ fieldName, onApplyWholeField, onAcceptViaEd
63
63
  // Per-suggestion controls when there's more than one — keeps the UX
64
64
  // discoverable. Single suggestion: Accept / Reject only.
65
65
  const single = pending.length === 1;
66
- return (_jsxs("div", { role: "region", "aria-label": "AI suggested changes", "data-pilotiq-ai-banner": "", className: className ?? 'pilotiq-ai-banner', children: [_jsx("span", { className: "pilotiq-ai-banner-icon", "aria-hidden": "true", children: "\uD83D\uDCA1" }), _jsx("span", { className: "pilotiq-ai-banner-label", children: single
66
+ return (_jsxs("div", { role: "region", "aria-label": "AI suggested changes", "data-pilotiq-suggestion-banner": "", className: className ?? 'pilotiq-suggestion-banner', children: [_jsx("span", { className: "pilotiq-suggestion-banner-icon", "aria-hidden": "true", children: "\uD83D\uDCA1" }), _jsx("span", { className: "pilotiq-suggestion-banner-label", children: single
67
67
  ? sourceLabel
68
68
  ? `Changes suggested by ${sourceLabel}`
69
69
  : 'Changes suggested'
70
- : `${pending.length} changes suggested` }), _jsxs("div", { className: "pilotiq-ai-banner-actions", children: [_jsx("button", { type: "button", className: "pilotiq-ai-banner-reject", onClick: handleReject, children: single ? 'Reject' : 'Reject all' }), _jsx("button", { type: "button", className: "pilotiq-ai-banner-accept", onClick: handleAccept, children: single ? 'Accept' : 'Accept all' })] })] }));
70
+ : `${pending.length} changes suggested` }), _jsxs("div", { className: "pilotiq-suggestion-banner-actions", children: [_jsx("button", { type: "button", className: "pilotiq-suggestion-banner-reject", onClick: handleReject, children: single ? 'Reject' : 'Reject all' }), _jsx("button", { type: "button", className: "pilotiq-suggestion-banner-accept", onClick: handleAccept, children: single ? 'Accept' : 'Accept all' })] })] }));
71
71
  }