Skip to content

Commit

Permalink
feat(yfm): add GPT extensions (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
ReFFaT authored Sep 13, 2024
1 parent 65e4327 commit 74d6e67
Show file tree
Hide file tree
Showing 59 changed files with 2,278 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ Read more:
- [How to add Latex extension](docs/how-to-connect-latex-extension.md)
- [How to add Mermaid extension](docs/how-to-connect-mermaid-extension.md)
- [How to write extension](docs/how-to-create-extension.md)
- [How to add gpt extension](docs/how-to-connect-gpt-extensions.md)


### i18n

Expand Down
33 changes: 28 additions & 5 deletions demo/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18';
import {
MarkdownEditorMode,
MarkdownEditorView,
MarkdownEditorViewProps,
MarkupString,
NumberInput,
RenderPreview,
UseMarkdownEditorProps,
logger,
markupToolbarConfigs,
useMarkdownEditor,
Expand Down Expand Up @@ -59,6 +61,8 @@ const wCommandMenuConfig = wysiwygToolbarConfigs.wCommandMenuConfig.concat(
wysiwygToolbarConfigs.wYfmHtmlBlockItemData,
);

wCommandMenuConfig.unshift(wysiwygToolbarConfigs.wGptItemData);

export type PlaygroundProps = {
initial?: MarkupString;
allowHTML?: boolean;
Expand All @@ -77,7 +81,20 @@ export type PlaygroundProps = {
escapeConfig?: EscapeConfig;
onChangeEditorType?: (mode: MarkdownEditorMode) => void;
onChangeSplitModeEnabled?: (splitModeEnabled: boolean) => void;
};
} & Pick<
UseMarkdownEditorProps,
| 'needToSetDimensionsForUploadedImages'
| 'extraExtensions'
| 'renderPreview'
| 'extensionOptions'
> &
Pick<
MarkdownEditorViewProps,
| 'markupHiddenActionsConfig'
| 'wysiwygHiddenActionsConfig'
| 'markupToolbarConfig'
| 'wysiwygToolbarConfig'
>;

logger.setLogger({
metrics: console.info,

Check warning on line 100 in demo/Playground.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

Unexpected console statement
Expand All @@ -101,6 +118,9 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
stickyToolbar,
renderPreviewDefined,
height,
extraExtensions,
extensionOptions,
wysiwygToolbarConfig,
escapeConfig,
} = props;
const [editorMode, setEditorMode] = React.useState<MarkdownEditorMode>(
Expand Down Expand Up @@ -145,8 +165,9 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
: undefined,
extensionOptions: {
commandMenu: {actions: wCommandMenuConfig},
...extensionOptions,
},
extraExtensions: (builder) =>
extraExtensions: (builder) => {
builder
.use(Math, {
loadRuntimeScript: () => {
Expand All @@ -166,6 +187,7 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
);
},
})
.use(FoldingHeading)
.use(YfmHtmlBlock, {
useConfig: useYfmHtmlBlockStyles,
sanitize: getSanitizeYfmHtmlBlock({options: defaultOptions}),
Expand All @@ -178,8 +200,9 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
}
</style
`,
})
.use(FoldingHeading),
});
if (extraExtensions) builder.use(extraExtensions);
},
});

useEffect(() => {
Expand Down Expand Up @@ -311,7 +334,7 @@ export const Playground = React.memo<PlaygroundProps>((props) => {
toaster={toaster}
className={b('editor-view')}
stickyToolbar={Boolean(stickyToolbar)}
wysiwygToolbarConfig={wToolbarConfig}
wysiwygToolbarConfig={wysiwygToolbarConfig ?? wToolbarConfig}
markupToolbarConfig={mToolbarConfig}
settingsVisible={settingsVisible}
editor={mdEditor}
Expand Down
16 changes: 16 additions & 0 deletions demo/gptPlugin/PlaygroundGPT.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';

// eslint-disable-next-line import/no-extraneous-dependencies
import type {StoryFn} from '@storybook/react';

import {PlaygroundGPT} from './PlaygroundGPT';

export default {
title: 'Markdown Editor / YFM examples',
component: PlaygroundGPT,
};

type PlaygroundStoryProps = {};
export const Playground: StoryFn<PlaygroundStoryProps> = (props) => <PlaygroundGPT {...props} />;

Playground.storyName = 'GPT';
55 changes: 55 additions & 0 deletions demo/gptPlugin/PlaygroundGPT.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, {useState} from 'react';

import cloneDeep from 'lodash/cloneDeep';

import {
type MarkupString,
gptExtension,
logger,
wGptToolbarItem,
wysiwygToolbarConfigs,
} from '../../src';
import {Playground} from '../Playground';

import {gptWidgetProps} from './gptWidgetOptions';
import {initialMdContent} from './md-content';

import '../Playground.scss';

const wToolbarConfig = cloneDeep(wysiwygToolbarConfigs.wToolbarConfig);
wToolbarConfig.unshift([wGptToolbarItem]);

logger.setLogger({
metrics: console.info,
action: (data) => console.info(`Action: ${data.action}`, data),
...console,
});

export const PlaygroundGPT = React.memo(() => {
const [yfmRaw, setYfmRaw] = React.useState<MarkupString>(initialMdContent);

const [showedAlertGpt, setShowedAlertGpt] = useState(true);

const wSelectionMenuConfig = [[wGptToolbarItem], ...wysiwygToolbarConfigs.wSelectionMenuConfig];
return (
<Playground
settingsVisible
initial={yfmRaw}
extraExtensions={(builder) =>
builder.use(
gptExtension,
gptWidgetProps(setYfmRaw, {
showedGptAlert: Boolean(showedAlertGpt),
onCloseGptAlert: () => {
setShowedAlertGpt(false);
},
}),
)
}
extensionOptions={{selectionContext: {config: wSelectionMenuConfig}}}
wysiwygToolbarConfig={wToolbarConfig}
/>
);
});

PlaygroundGPT.displayName = 'GPT';
75 changes: 75 additions & 0 deletions demo/gptPlugin/gptWidgetOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react';

import type {GptWidgetOptions} from '../../src/extensions/yfm/GPT/gptExtension/gptExtension';

const gptRequestHandler = async ({
markup,
customPrompt,
promptData,
}: {
markup: string;
customPrompt?: string;
promptData: unknown;
}) => {
await new Promise((resolve) => setTimeout(resolve, 1000));

let gptResponseMarkup = markup;
if (customPrompt) {
gptResponseMarkup = markup + ` \`enhanced with ${customPrompt}\``;
} else if (promptData === 'do-uno-reverse') {
gptResponseMarkup = gptResponseMarkup.replace(/[\wа-яА-ЯёЁ]+/g, (match) =>
match.split('').reverse().join(''),
);
} else if (promptData === 'do-shout-out') {
gptResponseMarkup = gptResponseMarkup.toLocaleUpperCase();
}

return {
rawText: gptResponseMarkup,
};
};

export const gptWidgetProps = (
setYfmRaw: (yfmRaw: string) => void,
gptAlertProps?: GptWidgetOptions['gptAlertProps'],
): GptWidgetOptions => {
return {
answerRender: (data) => <div>{data.rawText}</div>,
customPromptPlaceholder: 'Ask GPT to edit the text highlighted text',
disabledPromptPlaceholder: 'Ask GPT to generate the text',
gptAlertProps: gptAlertProps,
promptPresets: [
{
hotKey: 'control+3',
data: 'do-uno-reverse',
display: 'Use the uno card',
key: 'do-uno-reverse',
},
{
hotKey: 'control+4',
data: 'do-shout-out',
display: 'Make the text flashy',
key: 'do-shout-out',
},
],
onCustomPromptApply: async ({markup, customPrompt, promptData}) => {
return gptRequestHandler({markup, customPrompt, promptData});
},
onPromptPresetClick: async ({markup, customPrompt, promptData}) => {
return gptRequestHandler({markup, customPrompt, promptData});
},
onTryAgain: async ({markup, customPrompt, promptData}) => {
return gptRequestHandler({markup, customPrompt, promptData});
},
onLike: async () => {},
onDislike: async () => {},
onApplyResult: (markup) => {
setYfmRaw(markup);
},
onUpdate: (event) => {
if (event?.rawText) {
setYfmRaw(event.rawText);
}
},
};
};
1 change: 1 addition & 0 deletions demo/gptPlugin/md-content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const initialMdContent = `Markdown Editor GPT Playground`;
Loading

0 comments on commit 74d6e67

Please sign in to comment.