Skip to content

Commit 251e82b

Browse files
committed
- AI Menu input spans full block width
- AI Menu items look more similar to Notion - `getDefaultAIMenuItems` uses same pattern as `getDefaultSlashMenuItems` - AI Block actually sets the `timeGenerated` prop after generating a response - AI Block only appears in the block type dropdown when the selection is in one - AI button in Formatting Toolbar now opens the AI Menu normally instead of in a popover
1 parent 0fcb46a commit 251e82b

File tree

7 files changed

+67
-31
lines changed

7 files changed

+67
-31
lines changed

packages/ai/src/core/blocks/AIBlockContent/AIBlockContent.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,17 @@ export const aiRender = (
3030
editor: BlockNoteEditor<any, any, any>
3131
) => {
3232
if (!block.props.prompt) {
33-
const replaceContent = () => {
33+
const replaceContent = async () => {
3434
const prompt = span.innerText;
3535
// TODO: Updating text content in this way isn't working
3636
generateButton.textContent = "Generating...";
3737

38-
mockAIReplaceBlockContent(editor, prompt, block);
38+
await mockAIReplaceBlockContent(editor, prompt, block);
39+
editor.updateBlock(block, {
40+
props: {
41+
timeGenerated: Date.now(),
42+
},
43+
});
3944
};
4045

4146
const promptBox = document.createElement("div");

packages/ai/src/react/components/AIMenu/AIMenuController.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ export const AIMenuController = (props: { aiMenu?: FC<AIMenuProps> }) => {
3939
const Component = props.aiMenu || AIMenu;
4040

4141
return (
42-
<div ref={ref} style={style} {...getFloatingProps()}>
42+
<div
43+
ref={ref}
44+
style={{ ...style, width: state.referencePos.width }}
45+
{...getFloatingProps()}>
4346
<Component />
4447
</div>
4548
);

packages/ai/src/react/components/AIMenu/getDefaultAIMenuItems.tsx

+8-5
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ export function getDefaultAIMenuItems<
2323
editor: BlockNoteEditor<BSchema, I, S>,
2424
operation: "insertAfterSelection" | "replaceSelection"
2525
): DefaultReactSuggestionItem[] {
26-
return Object.values(getAIDictionary(editor).ai_menu).map((item) => ({
27-
name: item.title as any,
28-
...item,
26+
const items = ["make_longer", "make_shorter", "rewrite"] as const;
27+
28+
const dict = getAIDictionary(editor);
29+
30+
return items.map((item) => ({
31+
name: item,
32+
title: dict.ai_menu[item].title,
2933
onItemClick: async () => {
30-
editor.formattingToolbar.closeMenu();
3134
(editor.extensions.aiMenu as AIMenuProsemirrorPlugin).close();
3235
(
3336
editor.extensions.aiInlineToolbar as AIInlineToolbarProsemirrorPlugin
34-
).open(item.title, operation);
37+
).open(dict.ai_menu[item].title, operation);
3538
},
3639
}));
3740
}

packages/ai/src/react/components/FormattingToolbar/DefaultButtons/AIButton.tsx

+13-16
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { RiSparkling2Fill } from "react-icons/ri";
44

55
import { useBlockNoteEditor } from "@blocknote/react";
66
import { useAIDictionary } from "../../../hooks/useAIDictionary";
7-
import { AIMenu } from "../../AIMenu/AIMenu";
7+
import { AIMenuProsemirrorPlugin } from "../../../../core";
88

99
// TODO: name?
1010
export const AIButton = () => {
@@ -17,25 +17,22 @@ export const AIButton = () => {
1717
StyleSchema
1818
>();
1919

20+
const onClick = () => {
21+
editor.formattingToolbar.closeMenu();
22+
(editor.extensions.aiMenu as AIMenuProsemirrorPlugin).open();
23+
};
24+
2025
if (!editor.isEditable) {
2126
return null;
2227
}
2328

2429
return (
25-
<Components.Generic.Popover.Root>
26-
<Components.Generic.Popover.Trigger>
27-
<Components.Toolbar.Button
28-
className={"bn-button"}
29-
label={dict.formatting_toolbar.ai.tooltip}
30-
mainTooltip={dict.formatting_toolbar.ai.tooltip}
31-
icon={<RiSparkling2Fill />}
32-
/>
33-
</Components.Generic.Popover.Trigger>
34-
<Components.Generic.Popover.Content
35-
variant="form-popover"
36-
className={"bn-popover-content bn-form-popover"}>
37-
<AIMenu />
38-
</Components.Generic.Popover.Content>
39-
</Components.Generic.Popover.Root>
30+
<Components.Toolbar.Button
31+
className={"bn-button"}
32+
label={dict.formatting_toolbar.ai.tooltip}
33+
mainTooltip={dict.formatting_toolbar.ai.tooltip}
34+
icon={<RiSparkling2Fill />}
35+
onClick={onClick}
36+
/>
4037
);
4138
};

packages/ai/src/react/components/FormattingToolbar/DefaultSelects/BlockTypeSelect.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ export const aiBlockTypeSelectItems = (
1212
type: "ai",
1313
icon: RiSparkling2Fill,
1414
isSelected: (block) => block.type === "ai",
15+
showWhileSelected: true,
16+
showWhileNotSelected: false,
1517
},
1618
];

packages/ai/src/react/editor/style.css

+16-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
height: 58px;
33
}
44

5-
div:not(.bn-popover-content) > .bn-ai-menu {
5+
.bn-ai-menu {
6+
display: flex;
7+
flex-direction: column;
8+
gap: 4px;
9+
width: 100%;
10+
}
11+
12+
div:not(.bn-popover-content) > .bn-ai-menu-input,
13+
div:not(.bn-popover-content) > .bn-ai-menu-items:not(:empty) {
614
background-color: var(--bn-colors-menu-background);
715
border: var(--bn-border);
816
border-radius: var(--bn-border-radius-medium);
@@ -16,13 +24,18 @@ div:not(.bn-popover-content) > .bn-ai-menu {
1624
/* TODO: How to split UI libs CSS? */
1725
.bn-ai-menu-input .mantine-Input-input {
1826
border: none;
19-
font-size: 12px;
27+
font-size: 14px;
28+
height: 52px;
29+
}
30+
31+
.bn-ai-menu-items {
32+
max-width: 50%;
2033
}
2134

2235
.bn-ai-menu .bn-suggestion-menu-item {
2336
height: revert;
2437
}
2538

2639
.bn-ai-menu .bn-mt-suggestion-menu-item-title {
27-
font-size: 12px;
40+
/*font-size: 12px;*/
2841
}

packages/react/src/components/FormattingToolbar/DefaultSelects/BlockTypeSelect.tsx

+17-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export type BlockTypeSelectItem = {
3434
isSelected: (
3535
block: Block<BlockSchema, InlineContentSchema, StyleSchema>
3636
) => boolean;
37+
showWhileSelected?: boolean;
38+
showWhileNotSelected?: boolean;
3739
};
3840

3941
export const blockTypeSelectItems = (
@@ -110,10 +112,21 @@ export const BlockTypeSelect = (props: { items?: BlockTypeSelectItem[] }) => {
110112
const [block, setBlock] = useState(editor.getTextCursorPosition().block);
111113

112114
const filteredItems: BlockTypeSelectItem[] = useMemo(() => {
113-
return (props.items || blockTypeSelectItems(dict)).filter(
114-
(item) => item.type in editor.schema.blockSchema
115-
);
116-
}, [editor, dict, props.items]);
115+
return (props.items || blockTypeSelectItems(dict)).filter((item) => {
116+
const blockTypeInSchema = item.type in editor.schema.blockSchema;
117+
const isSelected = item.isSelected(block);
118+
119+
const showWhileSelected =
120+
item.showWhileSelected === undefined || item.showWhileSelected;
121+
const showWhileNotSelected =
122+
item.showWhileNotSelected === undefined || item.showWhileNotSelected;
123+
124+
return (
125+
blockTypeInSchema &&
126+
(isSelected ? showWhileSelected : showWhileNotSelected)
127+
);
128+
});
129+
}, [props.items, dict, editor.schema.blockSchema, block]);
117130

118131
const shouldShow: boolean = useMemo(
119132
() => filteredItems.find((item) => item.type === block.type) !== undefined,

0 commit comments

Comments
 (0)