Skip to content

Commit 74d6e67

Browse files
authored
feat(yfm): add GPT extensions (#361)
1 parent 65e4327 commit 74d6e67

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2278
-9
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ Read more:
5656
- [How to add Latex extension](docs/how-to-connect-latex-extension.md)
5757
- [How to add Mermaid extension](docs/how-to-connect-mermaid-extension.md)
5858
- [How to write extension](docs/how-to-create-extension.md)
59+
- [How to add gpt extension](docs/how-to-connect-gpt-extensions.md)
60+
5961

6062
### i18n
6163

demo/Playground.tsx

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18';
77
import {
88
MarkdownEditorMode,
99
MarkdownEditorView,
10+
MarkdownEditorViewProps,
1011
MarkupString,
1112
NumberInput,
1213
RenderPreview,
14+
UseMarkdownEditorProps,
1315
logger,
1416
markupToolbarConfigs,
1517
useMarkdownEditor,
@@ -59,6 +61,8 @@ const wCommandMenuConfig = wysiwygToolbarConfigs.wCommandMenuConfig.concat(
5961
wysiwygToolbarConfigs.wYfmHtmlBlockItemData,
6062
);
6163

64+
wCommandMenuConfig.unshift(wysiwygToolbarConfigs.wGptItemData);
65+
6266
export type PlaygroundProps = {
6367
initial?: MarkupString;
6468
allowHTML?: boolean;
@@ -77,7 +81,20 @@ export type PlaygroundProps = {
7781
escapeConfig?: EscapeConfig;
7882
onChangeEditorType?: (mode: MarkdownEditorMode) => void;
7983
onChangeSplitModeEnabled?: (splitModeEnabled: boolean) => void;
80-
};
84+
} & Pick<
85+
UseMarkdownEditorProps,
86+
| 'needToSetDimensionsForUploadedImages'
87+
| 'extraExtensions'
88+
| 'renderPreview'
89+
| 'extensionOptions'
90+
> &
91+
Pick<
92+
MarkdownEditorViewProps,
93+
| 'markupHiddenActionsConfig'
94+
| 'wysiwygHiddenActionsConfig'
95+
| 'markupToolbarConfig'
96+
| 'wysiwygToolbarConfig'
97+
>;
8198

8299
logger.setLogger({
83100
metrics: console.info,
@@ -101,6 +118,9 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
101118
stickyToolbar,
102119
renderPreviewDefined,
103120
height,
121+
extraExtensions,
122+
extensionOptions,
123+
wysiwygToolbarConfig,
104124
escapeConfig,
105125
} = props;
106126
const [editorMode, setEditorMode] = React.useState<MarkdownEditorMode>(
@@ -145,8 +165,9 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
145165
: undefined,
146166
extensionOptions: {
147167
commandMenu: {actions: wCommandMenuConfig},
168+
...extensionOptions,
148169
},
149-
extraExtensions: (builder) =>
170+
extraExtensions: (builder) => {
150171
builder
151172
.use(Math, {
152173
loadRuntimeScript: () => {
@@ -166,6 +187,7 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
166187
);
167188
},
168189
})
190+
.use(FoldingHeading)
169191
.use(YfmHtmlBlock, {
170192
useConfig: useYfmHtmlBlockStyles,
171193
sanitize: getSanitizeYfmHtmlBlock({options: defaultOptions}),
@@ -178,8 +200,9 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
178200
}
179201
</style
180202
`,
181-
})
182-
.use(FoldingHeading),
203+
});
204+
if (extraExtensions) builder.use(extraExtensions);
205+
},
183206
});
184207

185208
useEffect(() => {
@@ -311,7 +334,7 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
311334
toaster={toaster}
312335
className={b('editor-view')}
313336
stickyToolbar={Boolean(stickyToolbar)}
314-
wysiwygToolbarConfig={wToolbarConfig}
337+
wysiwygToolbarConfig={wysiwygToolbarConfig ?? wToolbarConfig}
315338
markupToolbarConfig={mToolbarConfig}
316339
settingsVisible={settingsVisible}
317340
editor={mdEditor}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
3+
// eslint-disable-next-line import/no-extraneous-dependencies
4+
import type {StoryFn} from '@storybook/react';
5+
6+
import {PlaygroundGPT} from './PlaygroundGPT';
7+
8+
export default {
9+
title: 'Markdown Editor / YFM examples',
10+
component: PlaygroundGPT,
11+
};
12+
13+
type PlaygroundStoryProps = {};
14+
export const Playground: StoryFn<PlaygroundStoryProps> = (props) => <PlaygroundGPT {...props} />;
15+
16+
Playground.storyName = 'GPT';

demo/gptPlugin/PlaygroundGPT.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React, {useState} from 'react';
2+
3+
import cloneDeep from 'lodash/cloneDeep';
4+
5+
import {
6+
type MarkupString,
7+
gptExtension,
8+
logger,
9+
wGptToolbarItem,
10+
wysiwygToolbarConfigs,
11+
} from '../../src';
12+
import {Playground} from '../Playground';
13+
14+
import {gptWidgetProps} from './gptWidgetOptions';
15+
import {initialMdContent} from './md-content';
16+
17+
import '../Playground.scss';
18+
19+
const wToolbarConfig = cloneDeep(wysiwygToolbarConfigs.wToolbarConfig);
20+
wToolbarConfig.unshift([wGptToolbarItem]);
21+
22+
logger.setLogger({
23+
metrics: console.info,
24+
action: (data) => console.info(`Action: ${data.action}`, data),
25+
...console,
26+
});
27+
28+
export const PlaygroundGPT = React.memo(() => {
29+
const [yfmRaw, setYfmRaw] = React.useState<MarkupString>(initialMdContent);
30+
31+
const [showedAlertGpt, setShowedAlertGpt] = useState(true);
32+
33+
const wSelectionMenuConfig = [[wGptToolbarItem], ...wysiwygToolbarConfigs.wSelectionMenuConfig];
34+
return (
35+
<Playground
36+
settingsVisible
37+
initial={yfmRaw}
38+
extraExtensions={(builder) =>
39+
builder.use(
40+
gptExtension,
41+
gptWidgetProps(setYfmRaw, {
42+
showedGptAlert: Boolean(showedAlertGpt),
43+
onCloseGptAlert: () => {
44+
setShowedAlertGpt(false);
45+
},
46+
}),
47+
)
48+
}
49+
extensionOptions={{selectionContext: {config: wSelectionMenuConfig}}}
50+
wysiwygToolbarConfig={wToolbarConfig}
51+
/>
52+
);
53+
});
54+
55+
PlaygroundGPT.displayName = 'GPT';
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React from 'react';
2+
3+
import type {GptWidgetOptions} from '../../src/extensions/yfm/GPT/gptExtension/gptExtension';
4+
5+
const gptRequestHandler = async ({
6+
markup,
7+
customPrompt,
8+
promptData,
9+
}: {
10+
markup: string;
11+
customPrompt?: string;
12+
promptData: unknown;
13+
}) => {
14+
await new Promise((resolve) => setTimeout(resolve, 1000));
15+
16+
let gptResponseMarkup = markup;
17+
if (customPrompt) {
18+
gptResponseMarkup = markup + ` \`enhanced with ${customPrompt}\``;
19+
} else if (promptData === 'do-uno-reverse') {
20+
gptResponseMarkup = gptResponseMarkup.replace(/[\wа-яА-ЯёЁ]+/g, (match) =>
21+
match.split('').reverse().join(''),
22+
);
23+
} else if (promptData === 'do-shout-out') {
24+
gptResponseMarkup = gptResponseMarkup.toLocaleUpperCase();
25+
}
26+
27+
return {
28+
rawText: gptResponseMarkup,
29+
};
30+
};
31+
32+
export const gptWidgetProps = (
33+
setYfmRaw: (yfmRaw: string) => void,
34+
gptAlertProps?: GptWidgetOptions['gptAlertProps'],
35+
): GptWidgetOptions => {
36+
return {
37+
answerRender: (data) => <div>{data.rawText}</div>,
38+
customPromptPlaceholder: 'Ask GPT to edit the text highlighted text',
39+
disabledPromptPlaceholder: 'Ask GPT to generate the text',
40+
gptAlertProps: gptAlertProps,
41+
promptPresets: [
42+
{
43+
hotKey: 'control+3',
44+
data: 'do-uno-reverse',
45+
display: 'Use the uno card',
46+
key: 'do-uno-reverse',
47+
},
48+
{
49+
hotKey: 'control+4',
50+
data: 'do-shout-out',
51+
display: 'Make the text flashy',
52+
key: 'do-shout-out',
53+
},
54+
],
55+
onCustomPromptApply: async ({markup, customPrompt, promptData}) => {
56+
return gptRequestHandler({markup, customPrompt, promptData});
57+
},
58+
onPromptPresetClick: async ({markup, customPrompt, promptData}) => {
59+
return gptRequestHandler({markup, customPrompt, promptData});
60+
},
61+
onTryAgain: async ({markup, customPrompt, promptData}) => {
62+
return gptRequestHandler({markup, customPrompt, promptData});
63+
},
64+
onLike: async () => {},
65+
onDislike: async () => {},
66+
onApplyResult: (markup) => {
67+
setYfmRaw(markup);
68+
},
69+
onUpdate: (event) => {
70+
if (event?.rawText) {
71+
setYfmRaw(event.rawText);
72+
}
73+
},
74+
};
75+
};

demo/gptPlugin/md-content.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const initialMdContent = `Markdown Editor GPT Playground`;

0 commit comments

Comments
 (0)