Skip to content

Commit 8419c04

Browse files
docs: Custom AI menu items docs (#1690)
* Added custom AI menu items example * Updated AI example READMEs
1 parent 80f4267 commit 8419c04

File tree

18 files changed

+521
-36
lines changed

18 files changed

+521
-36
lines changed

examples/09-ai/01-minimal/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ export default function App() {
9999
editor={editor}
100100
// We're disabling some default UI elements
101101
formattingToolbar={false}
102-
slashMenu={false}>
102+
slashMenu={false}
103+
>
103104
{/* This has AI specific components like the AI Command menu */}
104105
<BlockNoteAIUI />
105106

examples/09-ai/01-minimal/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ Select some text and click the AI (magic wand) button, or type `/ai` anywhere in
66

77
**Relevant Docs:**
88

9-
- TODO
9+
- [Editor Setup](/docs/editor-basics/setup)
10+
- [Changing the Formatting Toolbar](/docs/ui-components/formatting-toolbar#changing-the-formatting-toolbar)
11+
- [Changing Slash Menu Items](/docs/ui-components/suggestion-menus#changing-slash-menu-items)
12+
- [Getting Stared with BlockNote AI](/docs/ai/setup)

examples/09-ai/02-playground/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ Change the configuration, the highlight some text to access the AI menu, or type
66

77
**Relevant Docs:**
88

9-
- TODO
9+
- [Editor Setup](/docs/editor-basics/setup)
10+
- [Changing the Formatting Toolbar](/docs/ui-components/formatting-toolbar#changing-the-formatting-toolbar)
11+
- [Changing Slash Menu Items](/docs/ui-components/suggestion-menus#changing-slash-menu-items)
12+
- [Getting Stared with BlockNote AI](/docs/ai/setup)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"playground": true,
3+
"docs": true,
4+
"author": "matthewlipski",
5+
"tags": ["AI", "llm"],
6+
"dependencies": {
7+
"@blocknote/xl-ai": "latest",
8+
"@mantine/core": "^7.10.1",
9+
"ai": "^4.1.0",
10+
"@ai-sdk/openai": "^1.1.0",
11+
"@ai-sdk/groq": "^1.1.0",
12+
"react-icons": "^5.2.1",
13+
"zustand": "^5.0.3"
14+
}
15+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/// <reference types="./vite-env.d.ts" />
2+
3+
import { createGroq } from "@ai-sdk/groq";
4+
import { BlockNoteEditor, filterSuggestionItems } from "@blocknote/core";
5+
import "@blocknote/core/fonts/inter.css";
6+
import { en } from "@blocknote/core/locales";
7+
import { BlockNoteView } from "@blocknote/mantine";
8+
import "@blocknote/mantine/style.css";
9+
import {
10+
FormattingToolbar,
11+
FormattingToolbarController,
12+
SuggestionMenuController,
13+
getDefaultReactSlashMenuItems,
14+
getFormattingToolbarItems,
15+
useCreateBlockNote,
16+
} from "@blocknote/react";
17+
import {
18+
AIToolbarButton,
19+
BlockNoteAIUI,
20+
locales as aiLocales,
21+
createAIExtension,
22+
createBlockNoteAIClient,
23+
getAISlashMenuItems,
24+
AIMenu,
25+
getDefaultAIMenuItemsWithSelection,
26+
getDefaultAIMenuItemsWithoutSelection,
27+
getDefaultAIActionMenuItems,
28+
AIMenuController,
29+
} from "@blocknote/xl-ai";
30+
import "@blocknote/xl-ai/style.css";
31+
32+
import { findRelatedTopics, makeCasual } from "./customAIMenuItems.js";
33+
34+
// Optional: proxy requests through the `@blocknote/xl-ai-server` proxy server
35+
// so that we don't have to expose our API keys to the client
36+
const client = createBlockNoteAIClient({
37+
apiKey: import.meta.env.VITE_BLOCKNOTE_AI_SERVER_API_KEY || "PLACEHOLDER",
38+
baseURL:
39+
import.meta.env.VITE_BLOCKNOTE_AI_SERVER_BASE_URL ||
40+
"https://localhost:3000/ai",
41+
});
42+
43+
// Use an "open" model such as llama, in this case via groq.com
44+
const model = createGroq({
45+
// call via our proxy client
46+
...client.getProviderSettings("groq"),
47+
})("llama-3.3-70b-versatile");
48+
49+
/*
50+
ALTERNATIVES:
51+
52+
Call a model directly (without the proxy):
53+
54+
const model = createGroq({
55+
apiKey: "<YOUR_GROQ_API_KEY>",
56+
})("llama-3.3-70b-versatile");
57+
58+
Or, use a different provider like OpenAI:
59+
60+
const model = createOpenAI({
61+
...client.getProviderSettings("openai"),
62+
})("gpt-4", {});
63+
*/
64+
65+
export default function App() {
66+
// Creates a new editor instance.
67+
const editor = useCreateBlockNote({
68+
dictionary: {
69+
...en,
70+
ai: aiLocales.en, // add default translations for the AI extension
71+
},
72+
// Register the AI extension
73+
extensions: [
74+
createAIExtension({
75+
model,
76+
}),
77+
],
78+
// We set some initial content for demo purposes
79+
initialContent: [
80+
{
81+
type: "heading",
82+
props: {
83+
level: 1,
84+
},
85+
content: "I love cats",
86+
},
87+
{
88+
type: "paragraph",
89+
content:
90+
"Cats are one of the most beloved and fascinating animals in the world. Known for their agility, independence, and charm, cats have been companions to humans for thousands of years. Domesticated cats, scientifically named Felis catus, come in various breeds, colors, and personalities, making them a popular choice for pet owners everywhere. Their mysterious behavior, sharp reflexes, and quiet affection have earned them a special place in countless households.",
91+
},
92+
{
93+
type: "paragraph",
94+
content:
95+
"Beyond their role as pets, cats have a rich history and cultural significance. In ancient Egypt, they were revered and even worshipped as symbols of protection and grace. Throughout history, they’ve appeared in folklore, art, and literature, often associated with curiosity, luck, and mystery. Despite superstitions surrounding black cats in some cultures, many societies around the world admire and cherish these sleek and graceful animals.",
96+
},
97+
{
98+
type: "paragraph",
99+
content:
100+
"Cats also offer emotional and physical benefits to their owners. Studies have shown that interacting with cats can reduce stress, lower blood pressure, and improve mental well-being. Their gentle purring, playful antics, and warm companionship provide comfort to people of all ages. Whether lounging in the sun, chasing a toy, or curling up on a lap, cats bring joy, peace, and a bit of magic to the lives of those who welcome them into their homes.",
101+
},
102+
],
103+
});
104+
105+
// Renders the editor instance using a React component.
106+
return (
107+
<div>
108+
<BlockNoteView
109+
editor={editor}
110+
formattingToolbar={false}
111+
slashMenu={false}
112+
>
113+
{/* This has AI specific components like the AI Command menu */}
114+
{/* We pass `aiMenu=false` as we want to render an AIMenu with our own
115+
items (defined below). */}
116+
<BlockNoteAIUI aiMenu={false}></BlockNoteAIUI>
117+
{/* Creates a new AIMenu with the default items, as well as our custom
118+
ones. */}
119+
<AIMenuController
120+
aiMenu={() => (
121+
<AIMenu
122+
items={(editor, aiResponseStatus) => {
123+
if (aiResponseStatus === "user-input") {
124+
// Returns different items based on whether the AI Menu was
125+
// opened via the Formatting Toolbar or the Slash Menu.
126+
return editor.getSelection()
127+
? [
128+
// Gets the default AI Menu items for when it's opened via
129+
// the Formatting Toolbar.
130+
...getDefaultAIMenuItemsWithSelection(editor),
131+
// Adds our custom item to make the text more casual.
132+
// Only appears when the AI Menu is opened via the
133+
// Formatting Toolbar.
134+
makeCasual(editor),
135+
]
136+
: [
137+
// Gets the default AI Menu items for when it's opened
138+
// via the Slash Menu.
139+
...getDefaultAIMenuItemsWithoutSelection(editor),
140+
// Adds our custom item to find related topics. Only
141+
// appears when the AI Menu is opened via the Slash
142+
// Menu.
143+
findRelatedTopics(editor),
144+
];
145+
}
146+
147+
if (aiResponseStatus === "user-reviewing") {
148+
// Returns different items once the AI has finished writing,
149+
// so the user can choose to accept or reject the changes.
150+
return getDefaultAIActionMenuItems(editor);
151+
}
152+
153+
// Return no items in other states, e.g. when the AI is writing
154+
// or when an error occurred.
155+
return [];
156+
}}
157+
/>
158+
)}
159+
/>
160+
161+
{/* We disabled the default formatting toolbar with `formattingToolbar=false`
162+
and replace it for one with an "AI button" (defined below).
163+
(See "Formatting Toolbar" in docs)
164+
*/}
165+
<FormattingToolbarWithAI />
166+
167+
{/* We disabled the default SlashMenu with `slashMenu=false`
168+
and replace it for one with an AI option (defined below).
169+
(See "Suggestion Menus" in docs)
170+
*/}
171+
<SuggestionMenuWithAI editor={editor} />
172+
</BlockNoteView>
173+
</div>
174+
);
175+
}
176+
177+
// Formatting toolbar with the `AIToolbarButton` added
178+
function FormattingToolbarWithAI() {
179+
return (
180+
<FormattingToolbarController
181+
formattingToolbar={() => (
182+
<FormattingToolbar>
183+
{...getFormattingToolbarItems()}
184+
<AIToolbarButton />
185+
</FormattingToolbar>
186+
)}
187+
/>
188+
);
189+
}
190+
191+
// Slash menu with the AI option added
192+
function SuggestionMenuWithAI(props: {
193+
editor: BlockNoteEditor<any, any, any>;
194+
}) {
195+
return (
196+
<SuggestionMenuController
197+
triggerCharacter="/"
198+
getItems={async (query) =>
199+
filterSuggestionItems(
200+
[
201+
...getDefaultReactSlashMenuItems(props.editor),
202+
...getAISlashMenuItems(props.editor),
203+
],
204+
query,
205+
)
206+
}
207+
/>
208+
);
209+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Adding AI Menu Items
2+
3+
In this example, we add two items to the AI Menu. The first prompts the AI to make the selected text more casual, and can be found by selecting some text and click the AI (magic wand) button. The second prompts the AI to give ideas on related topics to extend the document with, and can be found by clicking the "Ask AI" Slash Menu item.
4+
5+
Select some text and click the AI (magic wand) button, or type `/ai` anywhere in the editor to access AI functionality.
6+
7+
**Relevant Docs:**
8+
9+
- [Editor Setup](/docs/editor-basics/setup)
10+
- [Changing the Formatting Toolbar](/docs/ui-components/formatting-toolbar#changing-the-formatting-toolbar)
11+
- [Changing Slash Menu Items](/docs/ui-components/suggestion-menus#changing-slash-menu-items)
12+
- [Getting Stared with BlockNote AI](/docs/ai/setup)
13+
- [Custom AI Menu Items](/docs/ai/custom-commands)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { BlockNoteEditor } from "@blocknote/core";
2+
import { AIMenuSuggestionItem, getAIExtension } from "@blocknote/xl-ai";
3+
import { RiApps2AddFill, RiEmotionHappyFill } from "react-icons/ri";
4+
5+
// Custom item to make the text more casual.
6+
export const makeCasual = (editor: BlockNoteEditor): AIMenuSuggestionItem => ({
7+
key: "make_casual",
8+
title: "Make Casual",
9+
// Aliases used when filtering AI Menu items from
10+
// text in prompt input.
11+
aliases: ["casual", "informal", "make informal"],
12+
icon: <RiEmotionHappyFill size={18} />,
13+
onItemClick: async () => {
14+
await getAIExtension(editor).callLLM({
15+
userPrompt: "Make casual",
16+
// Set to true to tell the LLM to specifically
17+
// use the selected content as context. Defaults
18+
// to false.
19+
useSelection: true,
20+
// Sets what operations the LLM is allowed to do.
21+
// In this case, we only want to allow updating
22+
// the selected content, so only `update` is set
23+
// to true. Defaults to `true` for all
24+
// operations.
25+
defaultStreamTools: {
26+
add: false,
27+
delete: false,
28+
update: true,
29+
},
30+
});
31+
},
32+
size: "small",
33+
});
34+
35+
// Custom item to find related topics.
36+
export const findRelatedTopics = (
37+
editor: BlockNoteEditor,
38+
): AIMenuSuggestionItem => ({
39+
key: "find_related_topics",
40+
title: "Find Related Topics",
41+
// Aliases used when filtering AI Menu items from
42+
// text in prompt input.
43+
aliases: ["related topics", "find topics"],
44+
icon: <RiApps2AddFill size={18} />,
45+
onItemClick: async () => {
46+
await getAIExtension(editor).callLLM({
47+
userPrompt:
48+
"Find several related topics to the current text and write a sentence about each",
49+
// Sets what operations the LLM is allowed to do.
50+
// In this case, we only want to allow adding new
51+
// content, so only `add` is set to true.
52+
// Defaults to `true` for all operations.
53+
defaultStreamTools: {
54+
add: true,
55+
delete: false,
56+
update: false,
57+
},
58+
});
59+
},
60+
size: "small",
61+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<html lang="en">
2+
<head>
3+
<script>
4+
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY -->
5+
</script>
6+
<meta charset="UTF-8" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<title>Adding AI Menu Items</title>
9+
</head>
10+
<body>
11+
<div id="root"></div>
12+
<script type="module" src="./main.tsx"></script>
13+
</body>
14+
</html>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
2+
import React from "react";
3+
import { createRoot } from "react-dom/client";
4+
import App from "./App.jsx";
5+
6+
const root = createRoot(document.getElementById("root")!);
7+
root.render(
8+
<React.StrictMode>
9+
<App />
10+
</React.StrictMode>
11+
);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "@blocknote/example-ai-ai-menu-items",
3+
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
4+
"private": true,
5+
"version": "0.12.4",
6+
"scripts": {
7+
"start": "vite",
8+
"dev": "vite",
9+
"build:prod": "tsc && vite build",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"@blocknote/core": "latest",
14+
"@blocknote/react": "latest",
15+
"@blocknote/ariakit": "latest",
16+
"@blocknote/mantine": "latest",
17+
"@blocknote/shadcn": "latest",
18+
"react": "^18.3.1",
19+
"react-dom": "^18.3.1",
20+
"@blocknote/xl-ai": "latest",
21+
"@mantine/core": "^7.10.1",
22+
"ai": "^4.1.0",
23+
"@ai-sdk/openai": "^1.1.0",
24+
"@ai-sdk/groq": "^1.1.0",
25+
"react-icons": "^5.2.1",
26+
"zustand": "^5.0.3"
27+
},
28+
"devDependencies": {
29+
"@types/react": "^18.0.25",
30+
"@types/react-dom": "^18.0.9",
31+
"@vitejs/plugin-react": "^4.3.1",
32+
"vite": "^5.3.4"
33+
}
34+
}

0 commit comments

Comments
 (0)