DevExtreme v25.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

React Html Editor - AI-powered Text Editing

The DevExtreme HTML Editor supports AI-powered text editing operations. Users can initiate AI requests and insert results directly into the editor. To activate this feature, you must:

  1. Link the HTML Editor to an AI service through the aiIntegration option.
  2. Specify the "ai" toolbar item in the toolbar configuration.
Backend API
import React from 'react'; import HtmlEditor, { Toolbar, ToolbarItem, Command } from 'devextreme-react/html-editor'; import { markup, extractKeywordsPrompt, aiIntegration } from './data.ts'; export default function App() { return ( <HtmlEditor height={530} defaultValue={markup} aiIntegration={aiIntegration} > <Toolbar> <ToolbarItem name="ai"> <Command name="summarize" /> <Command name="proofread" /> <Command name="expand" /> <Command name="shorten" /> <Command name="changeStyle" /> <Command name="changeTone" /> <Command name="translate" /> <Command name="askAI" /> <Command name="custom" text="Extract Keywords" prompt={extractKeywordsPrompt} /> </ToolbarItem> <ToolbarItem name="separator" /> <ToolbarItem name="undo" /> <ToolbarItem name="redo" /> </Toolbar> </HtmlEditor> ); }
import React from 'react'; import HtmlEditor, { Toolbar, ToolbarItem, Command } from 'devextreme-react/html-editor'; import { markup, extractKeywordsPrompt, aiIntegration } from './data.js'; export default function App() { return ( <HtmlEditor height={530} defaultValue={markup} aiIntegration={aiIntegration} > <Toolbar> <ToolbarItem name="ai"> <Command name="summarize" /> <Command name="proofread" /> <Command name="expand" /> <Command name="shorten" /> <Command name="changeStyle" /> <Command name="changeTone" /> <Command name="translate" /> <Command name="askAI" /> <Command name="custom" text="Extract Keywords" prompt={extractKeywordsPrompt} /> </ToolbarItem> <ToolbarItem name="separator" /> <ToolbarItem name="undo" /> <ToolbarItem name="redo" /> </Toolbar> </HtmlEditor> ); }
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render( <App />, document.getElementById('app'), );
import { type HtmlEditorTypes } from 'devextreme-react/html-editor'; import { AIIntegration, RequestParams, Response, } from 'devextreme-react/common/ai-integration'; import { AzureOpenAI, OpenAI } from 'openai'; type AIMessage = (OpenAI.ChatCompletionUserMessageParam | OpenAI.ChatCompletionSystemMessageParam) & { content: string; }; const AzureOpenAIConfig = { dangerouslyAllowBrowser: true, deployment: 'gpt-4o-mini', apiVersion: '2024-02-01', endpoint: 'https://public-api.devexpress.com/demo-openai', apiKey: 'DEMO', }; const aiService = new AzureOpenAI(AzureOpenAIConfig); async function getAIResponse(messages: AIMessage[], signal: AbortSignal) { const params = { messages, model: AzureOpenAIConfig.deployment, max_tokens: 1000, temperature: 0.7, }; const response = await aiService.chat.completions.create(params, { signal }); const result = response.choices[0].message?.content; return result; } export const aiIntegration = new AIIntegration({ sendRequest({ prompt }: RequestParams): Response { const controller = new AbortController(); const signal = controller.signal; const aiPrompt: AIMessage[] = [ { role: 'system', content: prompt.system, }, { role: 'user', content: prompt.user, }, ]; const promise = getAIResponse(aiPrompt, signal); const result: Response = { promise, abort: () => { controller.abort(); }, }; return result; }, }); export const markup = ` <h2> <img src="../../../../images/widgets/HtmlEditor.svg" alt="HtmlEditor"> Formatted Text Editor (HTML Editor) </h2> <br> <p>DevExtreme JavaScript HTML Editor is a client-side WYSIWYG text editor that allows its users to format textual and visual content and store it as HTML.</p> <p>Supported features:</p> <ul> <li>Inline formats: <ul> <li>Bold, italic, strikethrough text formatting</li> <li>Font, size, color changes (HTML only)</li> </ul> </li> <li>Block formats: <ul> <li>Headers</li> <li>Text alignment</li> <li>Lists (ordered and unordered)</li> <li>Code blocks</li> <li>Quotes</li> </ul> </li> <li>Custom formats</li> <li>Mail-merge placeholders (for example, %username%)</li> <li>Adaptive toolbar for working images, links, and color formats</li> <li>Image upload: drag-and-drop images onto the form, select files from the file system, or specify a URL.</li> <li>Copy-paste rich content (unsupported formats are removed)</li> <li>Tables support</li> </ul> `; export const extractKeywordsPrompt: HtmlEditorTypes.AICustomCommand['prompt'] = () => 'Extract a list of keywords from the text and return it as a comma-separated string';
window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, jsx: 'react', }, meta: { 'react': { 'esModule': true, }, 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, 'openai': { 'esModule': true, }, }, paths: { 'npm:': 'https://cdn.jsdelivr.net/npm/', 'bundles:': '../../../../bundles/', 'externals:': '../../../../bundles/externals/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@8.0.0/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'react': 'npm:react@17.0.2/umd/react.development.js', 'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js', 'prop-types': 'npm:prop-types/prop-types.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@3.4.4/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign', 'devextreme': 'npm:devextreme@25.1.3/cjs', 'devextreme-react': 'npm:devextreme-react@25.1.3/cjs', 'openai': 'externals:openai.bundle.js', 'devextreme-quill': 'npm:devextreme-quill@1.7.3/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.19/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.62/dist/dx-gantt.js', 'inferno': 'npm:inferno@8.2.3/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@8.2.3/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', '@preact/signals-core': 'npm:@preact/signals-core@1.8.0/dist/signals-core.min.js', 'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3', // SystemJS plugins 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'devextreme': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme-react/common': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/common/core/events/utils': { main: 'index', }, 'devextreme/localization/messages': { format: 'json', defaultExtension: 'json', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render(<App />, document.getElementById('app'));
import { AIIntegration } from 'devextreme-react/common/ai-integration'; import { AzureOpenAI } from 'openai'; const AzureOpenAIConfig = { dangerouslyAllowBrowser: true, deployment: 'gpt-4o-mini', apiVersion: '2024-02-01', endpoint: 'https://public-api.devexpress.com/demo-openai', apiKey: 'DEMO', }; const aiService = new AzureOpenAI(AzureOpenAIConfig); async function getAIResponse(messages, signal) { const params = { messages, model: AzureOpenAIConfig.deployment, max_tokens: 1000, temperature: 0.7, }; const response = await aiService.chat.completions.create(params, { signal }); const result = response.choices[0].message?.content; return result; } export const aiIntegration = new AIIntegration({ sendRequest({ prompt }) { const controller = new AbortController(); const signal = controller.signal; const aiPrompt = [ { role: 'system', content: prompt.system }, { role: 'user', content: prompt.user }, ]; const promise = getAIResponse(aiPrompt, signal); const result = { promise, abort: () => { controller.abort(); }, }; return result; }, }); export const markup = ` <h2> <img src="../../../../images/widgets/HtmlEditor.svg" alt="HtmlEditor"> Formatted Text Editor (HTML Editor) </h2> <br> <p>DevExtreme JavaScript HTML Editor is a client-side WYSIWYG text editor that allows its users to format textual and visual content and store it as HTML.</p> <p>Supported features:</p> <ul> <li>Inline formats: <ul> <li>Bold, italic, strikethrough text formatting</li> <li>Font, size, color changes (HTML only)</li> </ul> </li> <li>Block formats: <ul> <li>Headers</li> <li>Text alignment</li> <li>Lists (ordered and unordered)</li> <li>Code blocks</li> <li>Quotes</li> </ul> </li> <li>Custom formats</li> <li>Mail-merge placeholders (for example, %username%)</li> <li>Adaptive toolbar for working images, links, and color formats</li> <li>Image upload: drag-and-drop images onto the form, select files from the file system, or specify a URL.</li> <li>Copy-paste rich content (unsupported formats are removed)</li> <li>Tables support</li> </ul> `; export const extractKeywordsPrompt = () => 'Extract a list of keywords from the text and return it as a comma-separated string';
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/25.1.3/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <script type="text/javascript"> System.import("./index.tsx"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"></div> </div> </body> </html>
.dx-htmleditor-content img { vertical-align: middle; padding-right: 10px; }

The default "ai" toolbar item includes the following predefined commands:

  • Summarize
  • Proofread
  • Expand
  • Shorten
  • Change style
  • Change tone
  • Translate
  • Ask AI assistant (allows users to run their own prompts to alter HTML Editor content)

You can also specify predefined commands to include in the "ai" item and customize standard command default options (for example, by setting a custom list of target languages for translation). Additionally, you can add a new custom command to the "ai" item by specifying your prompt. In this demo, the custom command is "Extract Keywords". For additional information, check the prompt in code.

NOTE

This implementation works with selected text. If nothing is selected, the entire text is altered.