mirror of
https://github.com/suitenumerique/docs.git
synced 2026-04-26 01:25:05 +02:00
Compare commits
6 Commits
fix/link-p
...
feature/bl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f05c1b3c2 | ||
|
|
c1e07d5eb1 | ||
|
|
d235b32a2c | ||
|
|
b277ca70eb | ||
|
|
a0c5a911d7 | ||
|
|
6181e147ac |
1
.github/workflows/docker-hub.yml
vendored
1
.github/workflows/docker-hub.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
|
- 'feature/blocknote-ai'
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|||||||
@@ -15,9 +15,14 @@
|
|||||||
"test:watch": "jest --watch"
|
"test:watch": "jest --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blocknote/core": "0.21.0",
|
"ai": "^4.1.18",
|
||||||
"@blocknote/mantine": "0.21.0",
|
"zod": "^3.24.1",
|
||||||
"@blocknote/react": "0.21.0",
|
"@ai-sdk/openai": "^1.1.9",
|
||||||
|
"@blocknote/core": "*",
|
||||||
|
"@blocknote/mantine": "*",
|
||||||
|
"@blocknote/react": "*",
|
||||||
|
"@blocknote/xl-ai": "*",
|
||||||
|
"vitest": "^2.0.3",
|
||||||
"@blocknote/xl-docx-exporter": "0.21.0",
|
"@blocknote/xl-docx-exporter": "0.21.0",
|
||||||
"@blocknote/xl-pdf-exporter": "0.21.0",
|
"@blocknote/xl-pdf-exporter": "0.21.0",
|
||||||
"@gouvfr-lasuite/integration": "1.0.2",
|
"@gouvfr-lasuite/integration": "1.0.2",
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export function AIGroupButton() {
|
|||||||
return (
|
return (
|
||||||
<Components.Generic.Menu.Root>
|
<Components.Generic.Menu.Root>
|
||||||
<Components.Generic.Menu.Trigger>
|
<Components.Generic.Menu.Trigger>
|
||||||
<Components.FormattingToolbar.Button
|
<Components.Toolbar.Button
|
||||||
className="bn-button bn-menu-item"
|
className="bn-button bn-menu-item"
|
||||||
data-test="ai-actions"
|
data-test="ai-actions"
|
||||||
label="AI"
|
label="AI"
|
||||||
|
|||||||
@@ -1,12 +1,34 @@
|
|||||||
import { Dictionary, locales } from '@blocknote/core';
|
import { createOpenAI } from '@ai-sdk/openai';
|
||||||
|
import {
|
||||||
|
BlockNoteEditor as BNEditor,
|
||||||
|
BlockConfig,
|
||||||
|
Dictionary,
|
||||||
|
InlineContentSchema,
|
||||||
|
StyleSchema,
|
||||||
|
filterSuggestionItems,
|
||||||
|
locales,
|
||||||
|
} from '@blocknote/core';
|
||||||
import '@blocknote/core/fonts/inter.css';
|
import '@blocknote/core/fonts/inter.css';
|
||||||
import { BlockNoteView } from '@blocknote/mantine';
|
import { BlockNoteView } from '@blocknote/mantine';
|
||||||
import '@blocknote/mantine/style.css';
|
import '@blocknote/mantine/style.css';
|
||||||
import { useCreateBlockNote } from '@blocknote/react';
|
import {
|
||||||
|
SuggestionMenuController,
|
||||||
|
getDefaultReactSlashMenuItems,
|
||||||
|
useCreateBlockNote,
|
||||||
|
} from '@blocknote/react';
|
||||||
|
import {
|
||||||
|
AIShowSelectionPlugin,
|
||||||
|
BlockNoteAIContextProvider,
|
||||||
|
BlockNoteAIUI,
|
||||||
|
locales as aiLocales,
|
||||||
|
createBlockNoteAIClient,
|
||||||
|
getAISlashMenuItems,
|
||||||
|
useBlockNoteAIContext,
|
||||||
|
} from '@blocknote/xl-ai';
|
||||||
|
import '@blocknote/xl-ai/style.css';
|
||||||
import { HocuspocusProvider } from '@hocuspocus/provider';
|
import { HocuspocusProvider } from '@hocuspocus/provider';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { css } from 'styled-components';
|
|
||||||
import * as Y from 'yjs';
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
import { Box, TextErrors } from '@/components';
|
import { Box, TextErrors } from '@/components';
|
||||||
@@ -17,95 +39,38 @@ import { useUploadFile } from '../hook';
|
|||||||
import { useHeadings } from '../hook/useHeadings';
|
import { useHeadings } from '../hook/useHeadings';
|
||||||
import useSaveDoc from '../hook/useSaveDoc';
|
import useSaveDoc from '../hook/useSaveDoc';
|
||||||
import { useEditorStore } from '../stores';
|
import { useEditorStore } from '../stores';
|
||||||
|
import { cssEditor } from '../styles';
|
||||||
import { randomColor } from '../utils';
|
import { randomColor } from '../utils';
|
||||||
|
|
||||||
import { BlockNoteToolbar } from './BlockNoteToolbar';
|
import { BlockNoteToolbar } from './BlockNoteToolbar';
|
||||||
|
|
||||||
const cssEditor = (readonly: boolean) => css`
|
const blocknoteAIClient = createBlockNoteAIClient({
|
||||||
&,
|
apiKey: 'BLOCKNOTE-API-KEY-CURRENTLY-NOT-NEEDED',
|
||||||
& > .bn-container,
|
baseURL: 'https://blocknote-esy4.onrender.com/ai',
|
||||||
& .ProseMirror {
|
});
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
const model = createOpenAI({
|
||||||
height: 50px;
|
baseURL: 'https://albert.api.staging.etalab.gouv.fr/v1',
|
||||||
}
|
...blocknoteAIClient.getProviderSettings('albert-etalab'),
|
||||||
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
compatibility: 'compatible',
|
||||||
height: 43px;
|
})('neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8');
|
||||||
}
|
|
||||||
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 1.875rem;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--c--theme--colors--greyscale-500);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.bn-block-group
|
|
||||||
.bn-block-group
|
|
||||||
.bn-block-outer:not([data-prev-depth-changed]):before {
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bn-editor {
|
// We call the model via a proxy server (see above) that has the API key,
|
||||||
color: var(--c--theme--colors--greyscale-700);
|
// but we could also call the model directly from the frontend.
|
||||||
}
|
// i.e., this should work as well (but it would leak your albert key to the frontend):
|
||||||
|
/*
|
||||||
|
return createOpenAI({
|
||||||
|
baseURL: 'https://albert.api.staging.etalab.gouv.fr/v1',
|
||||||
|
apiKey: 'ALBERT-API-KEY',
|
||||||
|
compatibility: 'compatible',
|
||||||
|
})('albert-etalab/neuralmagic/Meta-Llama-3.1-70B-Instruct-FP8');
|
||||||
|
*/
|
||||||
|
|
||||||
.bn-block-outer:not(:first-child) {
|
export type DocsBlockNoteEditor = BNEditor<
|
||||||
&:has(h1) {
|
Record<string, BlockConfig>,
|
||||||
padding-top: 32px;
|
InlineContentSchema,
|
||||||
}
|
StyleSchema
|
||||||
&:has(h2) {
|
>;
|
||||||
padding-top: 24px;
|
|
||||||
}
|
|
||||||
&:has(h3) {
|
|
||||||
padding-top: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& .bn-inline-content code {
|
|
||||||
background-color: gainsboro;
|
|
||||||
padding: 2px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (width <= 560px) {
|
|
||||||
& .bn-editor {
|
|
||||||
${readonly && `padding-left: 10px;`}
|
|
||||||
}
|
|
||||||
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
|
||||||
height: 46px;
|
|
||||||
}
|
|
||||||
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
& .bn-editor h1 {
|
|
||||||
font-size: 1.6rem;
|
|
||||||
}
|
|
||||||
& .bn-editor h2 {
|
|
||||||
font-size: 1.35rem;
|
|
||||||
}
|
|
||||||
& .bn-editor h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
|
|
||||||
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface BlockNoteEditorProps {
|
interface BlockNoteEditorProps {
|
||||||
doc: Doc;
|
doc: Doc;
|
||||||
@@ -130,6 +95,10 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
|||||||
|
|
||||||
const editor = useCreateBlockNote(
|
const editor = useCreateBlockNote(
|
||||||
{
|
{
|
||||||
|
_extensions: {
|
||||||
|
aiSelection: new AIShowSelectionPlugin(),
|
||||||
|
},
|
||||||
|
|
||||||
collaboration: {
|
collaboration: {
|
||||||
provider,
|
provider,
|
||||||
fragment: provider.document.getXmlFragment('document-store'),
|
fragment: provider.document.getXmlFragment('document-store'),
|
||||||
@@ -163,7 +132,10 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
|||||||
return cursor;
|
return cursor;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dictionary: locales[lang as keyof typeof locales] as Dictionary,
|
dictionary: {
|
||||||
|
...(locales[lang as keyof typeof locales] as Dictionary),
|
||||||
|
ai: aiLocales['en'] as unknown as Dictionary,
|
||||||
|
},
|
||||||
uploadFile,
|
uploadFile,
|
||||||
},
|
},
|
||||||
[collabName, lang, provider, uploadFile],
|
[collabName, lang, provider, uploadFile],
|
||||||
@@ -199,13 +171,42 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
|||||||
formattingToolbar={false}
|
formattingToolbar={false}
|
||||||
editable={!readOnly}
|
editable={!readOnly}
|
||||||
theme="light"
|
theme="light"
|
||||||
|
slashMenu={false}
|
||||||
>
|
>
|
||||||
<BlockNoteToolbar />
|
<BlockNoteAIContextProvider
|
||||||
|
model={model}
|
||||||
|
dataFormat="markdown"
|
||||||
|
stream={false}
|
||||||
|
>
|
||||||
|
<BlockNoteAIUI />
|
||||||
|
<BlockNoteToolbar />
|
||||||
|
<SuggestionMenu editor={editor as unknown as DocsBlockNoteEditor} />
|
||||||
|
</BlockNoteAIContextProvider>
|
||||||
</BlockNoteView>
|
</BlockNoteView>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function SuggestionMenu(props: { editor: DocsBlockNoteEditor }) {
|
||||||
|
const ctx = useBlockNoteAIContext();
|
||||||
|
return (
|
||||||
|
<SuggestionMenuController
|
||||||
|
triggerCharacter="/"
|
||||||
|
getItems={async (query) =>
|
||||||
|
Promise.resolve(
|
||||||
|
filterSuggestionItems(
|
||||||
|
[
|
||||||
|
...getDefaultReactSlashMenuItems(props.editor),
|
||||||
|
...getAISlashMenuItems(props.editor, ctx),
|
||||||
|
],
|
||||||
|
query,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface BlockNoteEditorVersionProps {
|
interface BlockNoteEditorVersionProps {
|
||||||
initialContent: Y.XmlFragment;
|
initialContent: Y.XmlFragment;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import {
|
|||||||
FormattingToolbarProps,
|
FormattingToolbarProps,
|
||||||
getFormattingToolbarItems,
|
getFormattingToolbarItems,
|
||||||
} from '@blocknote/react';
|
} from '@blocknote/react';
|
||||||
import React, { useCallback } from 'react';
|
import { AIToolbarButton } from '@blocknote/xl-ai';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { AIGroupButton } from './AIButton';
|
|
||||||
import { MarkdownButton } from './MarkdownButton';
|
import { MarkdownButton } from './MarkdownButton';
|
||||||
|
|
||||||
export const BlockNoteToolbar = () => {
|
export const BlockNoteToolbar = () => {
|
||||||
@@ -17,7 +17,8 @@ export const BlockNoteToolbar = () => {
|
|||||||
{getFormattingToolbarItems(blockTypeSelectItems)}
|
{getFormattingToolbarItems(blockTypeSelectItems)}
|
||||||
|
|
||||||
{/* Extra button to do some AI powered actions */}
|
{/* Extra button to do some AI powered actions */}
|
||||||
<AIGroupButton key="AIButton" />
|
{/* <AIGroupButton key="AIButton" /> */}
|
||||||
|
<AIToolbarButton key="AIButton" />
|
||||||
|
|
||||||
{/* Extra button to convert from markdown to json */}
|
{/* Extra button to convert from markdown to json */}
|
||||||
<MarkdownButton key="customButton" />
|
<MarkdownButton key="customButton" />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
useSelectedBlocks,
|
useSelectedBlocks,
|
||||||
} from '@blocknote/react';
|
} from '@blocknote/react';
|
||||||
import { forEach, isArray } from 'lodash';
|
import { forEach, isArray } from 'lodash';
|
||||||
import React, { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Block = {
|
type Block = {
|
||||||
@@ -80,11 +80,11 @@ export function MarkdownButton() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Components.FormattingToolbar.Button
|
<Components.Toolbar.Button
|
||||||
mainTooltip={t('Convert Markdown')}
|
mainTooltip={t('Convert Markdown')}
|
||||||
onClick={handleConvertMarkdown}
|
onClick={handleConvertMarkdown}
|
||||||
>
|
>
|
||||||
M
|
M
|
||||||
</Components.FormattingToolbar.Button>
|
</Components.Toolbar.Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
|
export const cssEditor = (readonly: boolean) => css`
|
||||||
|
&,
|
||||||
|
& > .bn-container,
|
||||||
|
& .ProseMirror {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
||||||
|
height: 43px;
|
||||||
|
}
|
||||||
|
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 1.875rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--c--theme--colors--greyscale-500);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bn-block-group
|
||||||
|
.bn-block-group
|
||||||
|
.bn-block-outer:not([data-prev-depth-changed]):before {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bn-editor {
|
||||||
|
color: var(--c--theme--colors--greyscale-700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bn-block-outer:not(:first-child) {
|
||||||
|
&:has(h1) {
|
||||||
|
padding-top: 32px;
|
||||||
|
}
|
||||||
|
&:has(h2) {
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
|
&:has(h3) {
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .bn-inline-content code {
|
||||||
|
background-color: gainsboro;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (width <= 560px) {
|
||||||
|
& .bn-editor {
|
||||||
|
${readonly && `padding-left: 10px;`}
|
||||||
|
}
|
||||||
|
.bn-side-menu[data-block-type='heading'][data-level='1'] {
|
||||||
|
height: 46px;
|
||||||
|
}
|
||||||
|
.bn-side-menu[data-block-type='heading'][data-level='2'] {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.bn-side-menu[data-block-type='heading'][data-level='3'] {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
& .bn-editor h1 {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
& .bn-editor h2 {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
}
|
||||||
|
& .bn-editor h3 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
|
||||||
|
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -35,6 +35,10 @@
|
|||||||
"eslint": "8.57.0",
|
"eslint": "8.57.0",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"typescript": "5.7.3"
|
"typescript": "5.7.3",
|
||||||
|
"@blocknote/core": "https://gitpkg.vercel.app/typecellOS/blocknote/packages/core?ai-block-built&v=3",
|
||||||
|
"@blocknote/mantine": "https://gitpkg.vercel.app/typecellOS/blocknote/packages/mantine?ai-block-built&v=3",
|
||||||
|
"@blocknote/react": "https://gitpkg.vercel.app/typecellOS/blocknote/packages/react?ai-block-built&v=3",
|
||||||
|
"@blocknote/xl-ai": "https://gitpkg.vercel.app/typecellOS/blocknote/packages/xl-ai?ai-block-built&v=3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user