Skip to content

Commit 78b8f98

Browse files
committed
support selections
1 parent d65ff5d commit 78b8f98

File tree

10 files changed

+215
-69
lines changed

10 files changed

+215
-69
lines changed

packages/core/src/api/nodeConversions/nodeToBlock.ts

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,36 @@ export function nodeToBlock<
425425
return block;
426426
}
427427

428+
/**
429+
* Convert a Prosemirror document to a BlockNote document (array of blocks)
430+
*/
431+
export function docToBlocks<
432+
BSchema extends BlockSchema,
433+
I extends InlineContentSchema,
434+
S extends StyleSchema
435+
>(
436+
doc: Node,
437+
blockSchema: BSchema,
438+
inlineContentSchema: I,
439+
styleSchema: S,
440+
blockCache?: WeakMap<Node, Block<BSchema, I, S>>
441+
) {
442+
const blocks: Block<BSchema, I, S>[] = [];
443+
doc.firstChild!.descendants((node) => {
444+
blocks.push(
445+
nodeToBlock(
446+
node,
447+
blockSchema,
448+
inlineContentSchema,
449+
styleSchema,
450+
blockCache
451+
)
452+
);
453+
return false;
454+
});
455+
return blocks;
456+
}
457+
428458
export type SlicedBlock<
429459
BSchema extends BlockSchema,
430460
I extends InlineContentSchema,
@@ -462,18 +492,18 @@ export function selectionToInsertionEnd(tr: Transaction, startLen: number) {
462492
return end;
463493
}
464494

465-
export function withSelectionMarkers<
466-
BSchema extends BlockSchema,
467-
I extends InlineContentSchema,
468-
S extends StyleSchema
469-
>(
495+
/**
496+
* Create a transaction that adds selection markers to the document at the given positions.
497+
*
498+
* @param state - The editor state.
499+
* @param from - The start position of the selection.
500+
* @param to - The end position of the selection.
501+
* @returns The transaction and the new end position.
502+
*/
503+
export function addSelectionMarkersTr(
470504
state: EditorState,
471505
from: number,
472-
to: number,
473-
blockSchema: BSchema,
474-
inlineContentSchema: I,
475-
styleSchema: S,
476-
blockCache?: WeakMap<Node, Block<BSchema, I, S>>
506+
to: number
477507
) {
478508
if (from >= to) {
479509
throw new Error("from must be less than to");
@@ -490,6 +520,52 @@ export function withSelectionMarkers<
490520
"tr.docChanged is false or insertText was not applied. Was a valid textselection passed?"
491521
);
492522
}
523+
return {
524+
tr,
525+
newEnd,
526+
};
527+
}
528+
529+
export function getDocumentWithSelectionMarkers<
530+
BSchema extends BlockSchema,
531+
I extends InlineContentSchema,
532+
S extends StyleSchema
533+
>(
534+
state: EditorState,
535+
from: number,
536+
to: number,
537+
blockSchema: BSchema,
538+
inlineContentSchema: I,
539+
styleSchema: S,
540+
blockCache?: WeakMap<Node, Block<BSchema, I, S>>
541+
) {
542+
const { tr } = addSelectionMarkersTr(state, from, to);
543+
return docToBlocks(
544+
tr.doc,
545+
blockSchema,
546+
inlineContentSchema,
547+
styleSchema,
548+
blockCache
549+
);
550+
}
551+
552+
/**
553+
* Add selection markers to the document at the given positions and return the blocks that span the selection.
554+
*/
555+
export function getSelectedBlocksWithSelectionMarkers<
556+
BSchema extends BlockSchema,
557+
I extends InlineContentSchema,
558+
S extends StyleSchema
559+
>(
560+
state: EditorState,
561+
from: number,
562+
to: number,
563+
blockSchema: BSchema,
564+
inlineContentSchema: I,
565+
styleSchema: S,
566+
blockCache?: WeakMap<Node, Block<BSchema, I, S>>
567+
) {
568+
const { tr, newEnd } = addSelectionMarkersTr(state, from, to);
493569

494570
return getBlocksBetween(
495571
from,
@@ -597,6 +673,8 @@ export function withoutSliceMetadata<
597673
* childrenCutAtEnd: true,
598674
* },
599675
* ]
676+
*
677+
* TODO: do we actually need / use this?
600678
*/
601679
export function prosemirrorSliceToSlicedBlocks<
602680
BSchema extends BlockSchema,

packages/core/src/api/nodeConversions/selectionMarkers.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ describe("Test ProseMirror selection HTML conversion", () => {
160160

161161
// const slice = editor._tiptapEditor.state.selection.content();
162162

163-
const blockNoteSelection = editor.getSelectionWithMarkers();
163+
const blockNoteSelection = editor.getSelectedBlocksWithSelectionMarkers();
164164

165165
expect(
166166
JSON.stringify(blockNoteSelection, undefined, 2)
@@ -282,7 +282,7 @@ describe("Test ProseMirror selection HTML conversion", () => {
282282
}
283283
editor.dispatch(editor._tiptapEditor.state.tr.setSelection(selection));
284284

285-
const blockNoteSelection = editor.getSelectionWithMarkers();
285+
const blockNoteSelection = editor.getSelectedBlocksWithSelectionMarkers();
286286
const JSONString = JSON.stringify(blockNoteSelection);
287287
ret += JSONString + "\n";
288288
});
@@ -301,7 +301,7 @@ describe("Test ProseMirror selection HTML conversion", () => {
301301
}
302302
editor.dispatch(editor._tiptapEditor.state.tr.setSelection(selection));
303303

304-
const blockNoteSelection = editor.getSelectionWithMarkers();
304+
const blockNoteSelection = editor.getSelectedBlocksWithSelectionMarkers();
305305
const JSONString = JSON.stringify(blockNoteSelection);
306306
ret += JSONString + "\n";
307307
});

packages/core/src/editor/BlockNoteEditor.ts

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@ import {
99
import { Node, Schema } from "prosemirror-model";
1010
// import "./blocknote.css";
1111
import * as Y from "yjs";
12-
import {
13-
getBlock,
14-
getNextBlock,
15-
getParentBlock,
16-
getPrevBlock,
17-
} from "../api/blockManipulation/getBlock/getBlock.js";
1812
import { insertBlocks } from "../api/blockManipulation/commands/insertBlocks/insertBlocks.js";
1913
import {
2014
moveBlocksDown,
@@ -29,15 +23,21 @@ import {
2923
import { removeBlocks } from "../api/blockManipulation/commands/removeBlocks/removeBlocks.js";
3024
import { replaceBlocks } from "../api/blockManipulation/commands/replaceBlocks/replaceBlocks.js";
3125
import { updateBlock } from "../api/blockManipulation/commands/updateBlock/updateBlock.js";
32-
import { insertContentAt } from "../api/blockManipulation/insertContentAt.js";
3326
import {
34-
getTextCursorPosition,
35-
setTextCursorPosition,
36-
} from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition.js";
27+
getBlock,
28+
getNextBlock,
29+
getParentBlock,
30+
getPrevBlock,
31+
} from "../api/blockManipulation/getBlock/getBlock.js";
32+
import { insertContentAt } from "../api/blockManipulation/insertContentAt.js";
3733
import {
3834
getSelection,
3935
setSelection,
4036
} from "../api/blockManipulation/selections/selection.js";
37+
import {
38+
getTextCursorPosition,
39+
setTextCursorPosition,
40+
} from "../api/blockManipulation/selections/textCursorPosition/textCursorPosition.js";
4141
import { createExternalHTMLExporter } from "../api/exporters/html/externalHTMLExporter.js";
4242
import { blocksToMarkdown } from "../api/exporters/markdown/markdownExporter.js";
4343
import { HTMLToBlocks } from "../api/parsers/html/parseHTML.js";
@@ -89,15 +89,16 @@ import { en } from "../i18n/locales/index.js";
8989

9090
import { Plugin, Transaction } from "@tiptap/pm/state";
9191
import { dropCursor } from "prosemirror-dropcursor";
92+
import { EditorView } from "prosemirror-view";
9293
import { createInternalHTMLSerializer } from "../api/exporters/html/internalHTMLSerializer.js";
9394
import { inlineContentToNodes } from "../api/nodeConversions/blockToNode.js";
9495
import {
95-
nodeToBlock,
96+
docToBlocks,
97+
getDocumentWithSelectionMarkers,
98+
getSelectedBlocksWithSelectionMarkers,
9699
prosemirrorSliceToSlicedBlocks,
97-
withSelectionMarkers,
98100
} from "../api/nodeConversions/nodeToBlock.js";
99101
import "../style.css";
100-
import { EditorView } from "prosemirror-view";
101102

102103
export type BlockNoteExtension =
103104
| AnyExtension
@@ -639,23 +640,13 @@ export class BlockNoteEditor<
639640
* @returns A snapshot of all top-level (non-nested) blocks in the editor.
640641
*/
641642
public get document(): Block<BSchema, ISchema, SSchema>[] {
642-
const blocks: Block<BSchema, ISchema, SSchema>[] = [];
643-
644-
this._tiptapEditor.state.doc.firstChild!.descendants((node) => {
645-
blocks.push(
646-
nodeToBlock(
647-
node,
648-
this.schema.blockSchema,
649-
this.schema.inlineContentSchema,
650-
this.schema.styleSchema,
651-
this.blockCache
652-
)
653-
);
654-
655-
return false;
656-
});
657-
658-
return blocks;
643+
return docToBlocks(
644+
this._tiptapEditor.state.doc,
645+
this.schema.blockSchema,
646+
this.schema.inlineContentSchema,
647+
this.schema.styleSchema,
648+
this.blockCache
649+
);
659650
}
660651

661652
/**
@@ -792,8 +783,19 @@ export class BlockNoteEditor<
792783
setTextCursorPosition(this, targetBlock, placement);
793784
}
794785

786+
public getDocumentWithSelectionMarkers() {
787+
return getDocumentWithSelectionMarkers(
788+
this._tiptapEditor.state,
789+
this._tiptapEditor.state.selection.from,
790+
this._tiptapEditor.state.selection.to,
791+
this.schema.blockSchema,
792+
this.schema.inlineContentSchema,
793+
this.schema.styleSchema
794+
);
795+
}
796+
795797
// TODO: what about node selections?
796-
public getSelectionWithMarkers() {
798+
public getSelectedBlocksWithSelectionMarkers() {
797799
const start = this._tiptapEditor.state.selection.$from;
798800
const end = this._tiptapEditor.state.selection.$to;
799801

@@ -817,7 +819,7 @@ export class BlockNoteEditor<
817819
// start = this._tiptapEditor.state.doc.resolve(start.pos + 1);
818820
// }
819821

820-
return withSelectionMarkers(
822+
return getSelectedBlocksWithSelectionMarkers(
821823
this._tiptapEditor.state,
822824
start.pos,
823825
end.pos,

packages/xl-ai/src/api/formats/json/json.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { BlockNoteEditor } from "@blocknote/core";
2-
import { LanguageModel, generateObject, jsonSchema, streamObject } from "ai";
2+
import {
3+
CoreMessage,
4+
LanguageModel,
5+
generateObject,
6+
jsonSchema,
7+
streamObject,
8+
} from "ai";
39

410
import {
511
executeAIOperation,
@@ -10,7 +16,10 @@ import { deleteFunction } from "../../functions/delete.js";
1016
import { AIFunction } from "../../functions/index.js";
1117
import { updateFunction } from "../../functions/update.js";
1218
import type { PromptOrMessages } from "../../index.js";
13-
import { promptManipulateDocumentUseJSONSchema } from "../../prompts/jsonSchemaPrompts.js";
19+
import {
20+
promptManipulateDocumentUseJSONSchema,
21+
promptManipulateSelectionJSONSchema,
22+
} from "../../prompts/jsonSchemaPrompts.js";
1423
import { createOperationsArraySchema } from "../../schema/operations.js";
1524
import { blockNoteSchemaToJSONSchema } from "../../schema/schemaToJSONSchema.js";
1625

@@ -43,19 +52,30 @@ export async function callLLM(
4352
editor: BlockNoteEditor<any, any, any>,
4453
opts: CallLLMOptionsWithOptional
4554
) {
46-
const { prompt, ...rest } = opts;
55+
const { prompt, useSelection, ...rest } = opts;
56+
57+
let messages: CoreMessage[];
58+
59+
if ("messages" in opts && opts.messages) {
60+
messages = opts.messages;
61+
} else if (useSelection) {
62+
messages = promptManipulateSelectionJSONSchema({
63+
editor,
64+
userPrompt: opts.prompt!,
65+
document: editor.getDocumentWithSelectionMarkers(),
66+
});
67+
} else {
68+
messages = promptManipulateDocumentUseJSONSchema({
69+
editor,
70+
userPrompt: opts.prompt!,
71+
document: editor.document,
72+
});
73+
}
4774

4875
const options: CallLLMOptions = {
4976
functions: [updateFunction, addFunction, deleteFunction],
5077
stream: true,
51-
messages:
52-
"messages" in opts && opts.messages !== undefined
53-
? opts.messages
54-
: promptManipulateDocumentUseJSONSchema({
55-
editor,
56-
userPrompt: opts.prompt!,
57-
document: editor.document,
58-
}),
78+
messages,
5979
...rest,
6080
};
6181

packages/xl-ai/src/api/formats/markdown/markdown.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import { addFunction } from "../../functions/add.js";
77
import { deleteFunction } from "../../functions/delete.js";
88
import { updateFunction } from "../../functions/update.js";
99
import type { PromptOrMessages } from "../../index.js";
10-
import { promptManipulateDocumentUseMarkdown } from "../../prompts/markdownPrompts.js";
10+
import {
11+
promptManipulateDocumentUseMarkdown,
12+
promptManipulateDocumentUseMarkdownWithSelection,
13+
} from "../../prompts/markdownPrompts.js";
1114
import { trimArray } from "../../util/trimArray.js";
1215

1316
type BasicLLMRequestOptions = {
@@ -22,19 +25,31 @@ export async function callLLM(
2225
editor: BlockNoteEditor<any, any, any>,
2326
options: MarkdownLLMRequestOptions
2427
) {
28+
let messages: CoreMessage[];
2529
const markdown = await editor.blocksToMarkdownLossy();
2630

31+
if ("messages" in options && options.messages) {
32+
messages = options.messages;
33+
} else if (options.useSelection) {
34+
const selection = editor.getDocumentWithSelectionMarkers();
35+
const markdown = await editor.blocksToMarkdownLossy(selection);
36+
messages = promptManipulateDocumentUseMarkdownWithSelection({
37+
editor,
38+
markdown,
39+
userPrompt: (options as any).prompt,
40+
});
41+
} else {
42+
messages = promptManipulateDocumentUseMarkdown({
43+
editor,
44+
markdown,
45+
userPrompt: (options as any).prompt,
46+
});
47+
}
48+
2749
const withDefaults: Required<
2850
Omit<MarkdownLLMRequestOptions & { messages: CoreMessage[] }, "prompt">
2951
> = {
30-
messages:
31-
"messages" in options
32-
? options.messages
33-
: promptManipulateDocumentUseMarkdown({
34-
editor,
35-
markdown,
36-
userPrompt: (options as any).prompt,
37-
}),
52+
messages,
3853
...(options as any), // TODO
3954
};
4055

0 commit comments

Comments
 (0)