Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: AI block UI #980

Open
wants to merge 111 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
3e1983b
Added AI block
matthewlipski Aug 1, 2024
0da498e
Added inline and slash menu AI
matthewlipski Aug 6, 2024
d48d91e
Small fix
matthewlipski Aug 6, 2024
82a56a9
UX improvements & refactor
matthewlipski Aug 6, 2024
5c66cfe
Extracted AI to separate package & changed AI block toolbar UX
matthewlipski Sep 9, 2024
22db2b4
Finished initial package split
matthewlipski Sep 9, 2024
e0f60a8
Moved last AI references to AI package
matthewlipski Sep 9, 2024
a2bab5d
Reverted minor unneeded changes
matthewlipski Sep 9, 2024
bcf9d31
refactor architecture
YousefED Sep 10, 2024
b814336
add extensions
YousefED Sep 10, 2024
cfc1bed
Refactored AI dictionary
matthewlipski Sep 10, 2024
ec36733
clean dictionary
YousefED Sep 10, 2024
78ac784
fix
YousefED Sep 10, 2024
2970e9d
fix
YousefED Sep 10, 2024
78924bb
Made AI button use suggestion menu components
matthewlipski Sep 11, 2024
4083cd9
Added keyboard navigation to AI button
matthewlipski Sep 11, 2024
d0d82a4
Refactored AI button
matthewlipski Sep 11, 2024
2df84f5
Changed AI from suggestion menu to propriety menu
matthewlipski Sep 11, 2024
644aa15
Minor changes
matthewlipski Sep 11, 2024
0fcb46a
Prevented focus swapping on suggestion menu items
matthewlipski Sep 11, 2024
251e82b
- AI Menu input spans full block width
matthewlipski Sep 11, 2024
736a8ff
Fixed AI Menu position for empty blocks
matthewlipski Sep 11, 2024
ffa466d
Made AI block react instead of vanilla
matthewlipski Sep 12, 2024
2c20238
fix build
YousefED Sep 16, 2024
f474949
schema
YousefED Sep 17, 2024
6ddf7b0
improve json schema methods
YousefED Sep 18, 2024
34abf80
Merge remote-tracking branch 'origin/main' into ai-block
YousefED Sep 23, 2024
b3926fe
merge
YousefED Sep 23, 2024
a9d25c9
improve json schema methods
YousefED Sep 23, 2024
2021ce7
fix build
YousefED Sep 23, 2024
10c6f5e
WIP: schemas and selections
YousefED Sep 25, 2024
a554ff1
selections wip
YousefED Sep 25, 2024
6833d9d
update selections
YousefED Sep 25, 2024
39642ce
wip selectionmarkers
YousefED Sep 25, 2024
7693b10
drop core / react structure
YousefED Sep 26, 2024
a8752e1
ai menu
YousefED Sep 26, 2024
979b917
add comment
YousefED Sep 26, 2024
f88a986
misc
YousefED Sep 26, 2024
3b7a80b
Added `size` field to React suggestion menu items
matthewlipski Sep 26, 2024
5f20d34
Merge branch 'ai-block' of github.com:TypeCellOS/BlockNote into ai-block
YousefED Sep 27, 2024
bb02ee6
basis for accept / reject menu
YousefED Sep 27, 2024
da2f06d
selection commands
YousefED Sep 27, 2024
f7bbf0e
Added `.env` file for API key and AI menu buttons for after an AI com…
matthewlipski Sep 27, 2024
6952a2e
Merge remote-tracking branch 'origin/ai-block' into ai-block
matthewlipski Sep 27, 2024
3ae2152
Added loader to AI menu
matthewlipski Sep 27, 2024
08d31a9
Updated styles
matthewlipski Sep 29, 2024
b0993e2
wip
YousefED Sep 30, 2024
5367284
gitignore
YousefED Sep 30, 2024
8d32b78
Merge remote-tracking branch 'origin/main' into ai-block
YousefED Nov 26, 2024
a271b22
move to xl-ai and add server
YousefED Nov 26, 2024
b8ac4db
fix test
YousefED Nov 27, 2024
0262670
small merge fixes
YousefED Nov 27, 2024
2934697
Merge remote-tracking branch 'origin/main' into ai-block
YousefED Dec 4, 2024
faa52f7
update lock
YousefED Dec 4, 2024
0b8c728
update ai sdk
YousefED Dec 4, 2024
908a3db
improve tests
YousefED Dec 4, 2024
4f8a337
model selector
YousefED Dec 5, 2024
075c025
add markdowndiff
YousefED Dec 10, 2024
fb0a2e1
wip
YousefED Dec 11, 2024
2829adc
split and update tests
YousefED Dec 11, 2024
f533c91
add list support
YousefED Dec 11, 2024
f7e0e81
wip
YousefED Dec 11, 2024
449d0b6
Merge remote-tracking branch 'origin/main' into ai-block
YousefED Dec 11, 2024
1b54e27
merge
YousefED Dec 11, 2024
0a376f1
Merge remote-tracking branch 'origin/main' into ai-block
YousefED Jan 21, 2025
cec0390
refactor
YousefED Jan 22, 2025
c7259bd
improve tests
YousefED Jan 22, 2025
b0ff91a
update tests
YousefED Jan 22, 2025
cfa26c4
wip
YousefED Jan 22, 2025
6fcf9ed
fixes
YousefED Jan 22, 2025
062deec
wip
YousefED Jan 22, 2025
6235cd7
fix tests
YousefED Jan 23, 2025
235c9f4
fix, before removing commented code
YousefED Jan 24, 2025
c0ce50b
fix
YousefED Jan 24, 2025
cd8b8dc
fix tests
YousefED Jan 24, 2025
36b9440
fix lint
YousefED Jan 24, 2025
9b13f4b
add comments
YousefED Jan 24, 2025
3625034
update tests
YousefED Jan 24, 2025
947a73a
fix
YousefED Jan 24, 2025
286b114
fix build
YousefED Jan 24, 2025
e6e18e0
fix lock
YousefED Jan 24, 2025
d30e060
wip
YousefED Jan 30, 2025
aaa08aa
misc
YousefED Feb 4, 2025
d1365c6
remove unused files, fix basic toolbar ux
YousefED Feb 4, 2025
d64d79f
fix build
YousefED Feb 4, 2025
c97af9a
fix lint
YousefED Feb 4, 2025
ff61213
fix build
YousefED Feb 4, 2025
947d24e
fix build
YousefED Feb 4, 2025
4691ee3
fix start command
YousefED Feb 4, 2025
11d6452
fix start
YousefED Feb 4, 2025
259c811
port
YousefED Feb 4, 2025
d15818a
fix deploy
YousefED Feb 4, 2025
02be4b4
fix build
YousefED Feb 4, 2025
19e2dae
ai client env variables
YousefED Feb 4, 2025
9b5d8d7
add + fix test
YousefED Feb 4, 2025
d446bc4
add prepare scripts
YousefED Feb 4, 2025
75e0275
test
YousefED Feb 4, 2025
e6d9435
albert
YousefED Feb 4, 2025
daac02d
fix build
YousefED Feb 4, 2025
9024e7a
Made editor non-editable until user finishes LLM generation
matthewlipski Feb 4, 2025
a11db32
Merge remote-tracking branch 'origin/ai-block' into ai-block
matthewlipski Feb 4, 2025
5d70d15
Implemented TODO LLM commands
matthewlipski Feb 4, 2025
d65ff5d
disable fix
YousefED Feb 4, 2025
78b8f98
support selections
YousefED Feb 5, 2025
a72d491
fix
YousefED Feb 5, 2025
47d7d49
fix
YousefED Feb 5, 2025
cc5f198
fix blank line processing with markdown
YousefED Feb 5, 2025
9cd4c38
improve prompt
YousefED Feb 5, 2025
4de0e4e
Merge remote-tracking branch 'origin/main' into ai-block
YousefED Feb 25, 2025
4cdf8a7
merge / undo button separation
YousefED Feb 25, 2025
11c4921
revert some changes
YousefED Feb 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ release
/test-results/
/playwright-report/
/playwright/.cache/
.env
*.pem
7 changes: 7 additions & 0 deletions .todo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

review toolbar code
fix design of menus (smaller items, border?)

see if addToHistory: false is needed
track last affected position to update toolbar
enable / disable show selection plugin
3 changes: 2 additions & 1 deletion docs/pages/examples/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"interoperability": "Interoperability",
"custom-schema": "Custom Schemas",
"collaboration": "Collaboration",
"extensions": "Extensions"
"extensions": "Extensions",
"ai": "AI"
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import "@blocknote/mantine/style.css";
import {
useBlockNoteEditor,
useComponentsContext,
useEditorContentOrSelectionChange,
} from "@blocknote/react";
import "@blocknote/mantine/style.css";
import { useState } from "react";

// Custom Formatting Toolbar Button to toggle blue text & background color.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import {
PartialBlock,
} from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
DefaultReactSuggestionItem,
getDefaultReactSlashMenuItems,
SuggestionMenuController,
useCreateBlockNote,
} from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { HiOutlineGlobeAlt } from "react-icons/hi";

// Custom Slash Menu item to insert a block after the current one.
const insertHelloWorldItem = (editor: BlockNoteEditor) => ({
key: "hello_world",
title: "Insert Hello World",
onItemClick: () => {
// Block that the text cursor is currently in.
Expand Down
1 change: 1 addition & 0 deletions examples/06-custom-schema/01-alert-block/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const schema = BlockNoteSchema.create({

// Slash menu item to insert an Alert block
const insertAlert = (editor: typeof schema.BlockNoteEditor) => ({
key: "alert",
title: "Alert",
onItemClick: () => {
insertOrUpdateBlock(editor, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
useCreateBlockNote,
} from "@blocknote/react";

import { Mention } from "./Mention";
import { Mention } from "./Mention.js";

// Our schema with inline content specs, which contain the configs and
// implementations for inline content that we want our editor to use.
Expand All @@ -32,6 +32,7 @@ const getMentionMenuItems = (
const users = ["Steve", "Bob", "Joe", "Mike"];

return users.map((user) => ({
key: user.toLowerCase(),
title: user,
onItemClick: () => {
editor.insertInlineContent([
Expand Down
1 change: 1 addition & 0 deletions examples/06-custom-schema/04-pdf-file-block/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const schema = BlockNoteSchema.create({

// Slash menu item to insert a PDF block
const insertPDF = (editor: typeof schema.BlockNoteEditor) => ({
key: "pdf",
title: "PDF",
onItemClick: () => {
insertOrUpdateBlock(editor, {
Expand Down
6 changes: 3 additions & 3 deletions examples/06-custom-schema/react-custom-styles/App.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { BlockNoteSchema, defaultStyleSpecs } from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
createReactStyleSpec,
FormattingToolbar,
FormattingToolbarController,
FormattingToolbarProps,
createReactStyleSpec,
useActiveStyles,
useBlockNoteEditor,
useComponentsContext,
useCreateBlockNote,
} from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";

const small = createReactStyleSpec(
{
Expand Down
13 changes: 13 additions & 0 deletions examples/09-ai/01-minimal/.bnexample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"playground": true,
"docs": true,
"author": "yousefed",
"tags": ["AI", "llm"],
"dependencies": {
"@blocknote/xl-ai": "latest",
"@mantine/core": "^7.10.1",
"ai": "^4.1.0",
"@ai-sdk/openai": "^1.1.0",
"@ai-sdk/groq": "^1.1.0"
}
}
184 changes: 184 additions & 0 deletions examples/09-ai/01-minimal/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import {
AIShowSelectionPlugin,
AIToolbarButton,
BlockNoteAIContextProvider,
BlockNoteAIUI,
locales as aiLocales,
createBlockNoteAIClient,
getAISlashMenuItems,
useBlockNoteAIContext,
} from "@blocknote/xl-ai";

import { createGroq } from "@ai-sdk/groq";
import { createOpenAI } from "@ai-sdk/openai";
import {
BlockNoteEditor,
BlockNoteSchema,
defaultBlockSpecs,
filterSuggestionItems,
locales,
} from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import {
FormattingToolbar,
FormattingToolbarController,
SuggestionMenuController,
blockTypeSelectItems,
getDefaultReactSlashMenuItems,
getFormattingToolbarItems,
useCreateBlockNote,
} from "@blocknote/react";
import "@blocknote/xl-ai/style.css";
import { Fieldset, Switch } from "@mantine/core";
import { useMemo, useState } from "react";
import { BasicAutocomplete } from "./AutoComplete.js";
import RadioGroupComponent from "./components/RadioGroupComponent.js";

const schema = BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
// ai: AIBlock,
},
});

const client = createBlockNoteAIClient({
apiKey: import.meta.env.VITE_BLOCKNOTE_AI_SERVER_API_KEY || "PLACEHOLDER",
baseURL:
import.meta.env.VITE_BLOCKNOTE_AI_SERVER_BASE_URL ||
"https://localhost:3000/ai",
});

export default function App() {
const [aiModelString, setAiModelString] = useState(
"openai/gpt-4o-2024-08-06"
);
// Creates a new editor instance.
const editor = useCreateBlockNote({
schema,
dictionary: {
...locales.en,
ai: aiLocales.en,
} as any,
_extensions: {
// TODO: things will break when user provides different keys. Define name on plugins instead?
// aiBlockToolbar: new AIBlockToolbarProsemirrorPlugin(),
aiSelection: new AIShowSelectionPlugin(),
},
});

const model = useMemo(() => {
const [provider, ...modelNameParts] = aiModelString.split("/");
const modelName = modelNameParts.join("/");
if (provider === "openai") {
return createOpenAI({
...client.getProviderSettings("openai"),
})(modelName, {});
} else if (provider === "groq") {
return createGroq({
...client.getProviderSettings("groq"),
})(modelName);
} else if (provider === "albert-etalab") {
return createOpenAI({
// albert-etalab/neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8
baseURL: "https://albert.api.staging.etalab.gouv.fr/v1",
...client.getProviderSettings("albert-etalab"),
compatibility: "compatible",
})(modelName);
}
throw new Error(`Unknown model: ${aiModelString}`);
}, [aiModelString]);

const [dataFormat, setDataFormat] = useState<"json" | "markdown">("json");

const [streamStateValue, setStream] = useState(true);

const stream = dataFormat === "markdown" ? false : streamStateValue;

// Renders the editor instance using a React component.
return (
<div>
<Fieldset legend="Model settings" style={{ maxWidth: "500px" }}>
<BasicAutocomplete value={aiModelString} onChange={setAiModelString} />

<RadioGroupComponent
label="Data format"
items={[
{ name: "JSON", description: "JSON format", value: "json" },
{
name: "Markdown",
description: "Markdown format",
value: "markdown",
},
]}
value={dataFormat}
onChange={(value) => setDataFormat(value as "json" | "markdown")}
/>

<Switch
checked={stream}
disabled={dataFormat === "markdown"}
onChange={(e) => setStream(e.target.checked)}
label="Streaming"
/>
</Fieldset>

<BlockNoteView
editor={editor}
formattingToolbar={false}
slashMenu={false}>
<BlockNoteAIContextProvider
model={model}
dataFormat={dataFormat}
// TODO: doesn't work well with typings
stream={streamStateValue as any}>
<BlockNoteAIUI />
<FormattingToolbarController
formattingToolbar={() => (
<FormattingToolbar>
{...getFormattingToolbarItems([
...blockTypeSelectItems(editor.dictionary),
// ...aiBlockTypeSelectItems(aiLocales.en),
])}
<AIToolbarButton />
</FormattingToolbar>
)}
/>
<SuggestionMenu editor={editor} />
</BlockNoteAIContextProvider>
{/* TODO: Side Menu customization */}
</BlockNoteView>
</div>
);
}

function SuggestionMenu(props: { editor: BlockNoteEditor<any, any, any> }) {
const ctx = useBlockNoteAIContext();
return (
<SuggestionMenuController
triggerCharacter="/"
getItems={async (query) =>
filterSuggestionItems(
[
...getDefaultReactSlashMenuItems(props.editor),
...getAISlashMenuItems(props.editor, ctx),
],
query
)
}
/>
);
}

/**
* - correct commands
* - FIX UI
*
* API:
* - context (which part of document to pass along)
* - based on selection
*
* - box with proposal
*
*/
60 changes: 60 additions & 0 deletions examples/09-ai/01-minimal/AutoComplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Combobox, TextInput, useCombobox } from "@mantine/core";
import { AI_MODELS } from "./data/aimodels.js";

// https://mantine.dev/combobox/?e=BasicAutocomplete
export function BasicAutocomplete(props: {
value: string;
onChange: (value: string) => void;
}) {
const { value, onChange } = props;
const combobox = useCombobox();

const shouldFilterOptions = !AI_MODELS.some((item) => item === value);
const filteredOptions = shouldFilterOptions
? AI_MODELS.filter((item) =>
item.toLowerCase().includes(value.toLowerCase().trim())
)
: AI_MODELS;

const options = filteredOptions.map((item) => (
<Combobox.Option value={item} key={item}>
{item}
</Combobox.Option>
));

return (
<Combobox
onOptionSubmit={(optionValue) => {
onChange(optionValue);
combobox.closeDropdown();
}}
store={combobox}
withinPortal={false}>
<Combobox.Target>
<TextInput
label="Select model or type `<provider>/<model>`:"
placeholder="Select model or type `<provider>/<model>`"
value={value}
onChange={(event) => {
onChange(event.currentTarget.value);
combobox.openDropdown();
combobox.updateSelectedOptionIndex();
}}
onClick={() => combobox.openDropdown()}
onFocus={() => combobox.openDropdown()}
onBlur={() => combobox.closeDropdown()}
/>
</Combobox.Target>

<Combobox.Dropdown>
<Combobox.Options mah={200} style={{ overflowY: "auto" }}>
{options.length === 0 ? (
<Combobox.Empty>Nothing found</Combobox.Empty>
) : (
options
)}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}
7 changes: 7 additions & 0 deletions examples/09-ai/01-minimal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# AI integration

This example shows how to use the `@blocknote/xl-ai` package to add AI capabilities to BlockNote.

**Relevant Docs:**

- TODO
Loading
Loading