Skip to content

Commit cc4efe3

Browse files
Zedwagksercs
andauthored
HtmlEditor: add vue, react, angular AITextEditing demo (DevExpress#29852)
Co-authored-by: ksercs <[email protected]>
1 parent 0dd3f87 commit cc4efe3

File tree

21 files changed

+732
-14
lines changed

21 files changed

+732
-14
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
::ng-deep .dx-htmleditor-content img {
2+
vertical-align: middle;
3+
padding-right: 10px;
4+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<dx-html-editor
2+
height="530px"
3+
[value]="valueContent"
4+
[aiIntegration]="aiIntegration"
5+
>
6+
<dxo-toolbar>
7+
<dxi-item name="ai" commands="commands"></dxi-item>
8+
<dxi-item name="separator"></dxi-item>
9+
<dxi-item name="undo"></dxi-item>
10+
<dxi-item name="redo"></dxi-item>
11+
</dxo-toolbar>
12+
</dx-html-editor>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { NgModule, Component, enableProdMode } from '@angular/core';
2+
import { BrowserModule } from '@angular/platform-browser';
3+
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4+
import { DxHtmlEditorModule } from 'devextreme-angular';
5+
import {
6+
AIIntegration,
7+
RequestParams,
8+
Response,
9+
} from 'devextreme/common/ai-integration';
10+
import { AzureOpenAI, OpenAI } from 'openai';
11+
import { Service } from './app.service';
12+
13+
if (!/localhost/.test(document.location.host)) {
14+
enableProdMode();
15+
}
16+
17+
let modulePrefix = '';
18+
// @ts-ignore
19+
if (window && window.config?.packageConfigPaths) {
20+
modulePrefix = '/app';
21+
}
22+
23+
type AIMessage = (OpenAI.ChatCompletionUserMessageParam | OpenAI.ChatCompletionSystemMessageParam) & {
24+
content: string;
25+
};
26+
27+
@Component({
28+
selector: 'demo-app',
29+
templateUrl: `.${modulePrefix}/app.component.html`,
30+
styleUrls: [`.${modulePrefix}/app.component.css`],
31+
providers: [Service],
32+
})
33+
34+
export class AppComponent {
35+
azureOpenAIConfig: any;
36+
37+
aiService: AzureOpenAI;
38+
39+
aiIntegration: AIIntegration;
40+
41+
commands: any[];
42+
43+
valueContent: string;
44+
45+
constructor(service: Service) {
46+
this.azureOpenAIConfig = service.getAzureOpenAIConfig();
47+
this.commands = service.getCommands();
48+
this.valueContent = service.getMarkup();
49+
50+
this.aiService = new AzureOpenAI(this.azureOpenAIConfig);
51+
this.aiIntegration = new AIIntegration({
52+
sendRequest: this.sendRequest.bind(this),
53+
});
54+
}
55+
56+
sendRequest ({ prompt }: RequestParams): Response {
57+
const controller = new AbortController();
58+
const signal = controller.signal;
59+
60+
const aiPrompt: AIMessage[] = [
61+
{ role: 'system', content: prompt.system, },
62+
{ role: 'user', content: prompt.user, },
63+
];
64+
const promise = this.getAIResponse(aiPrompt, signal);
65+
66+
const result: Response = {
67+
promise,
68+
abort: () => {
69+
controller.abort();
70+
},
71+
};
72+
73+
return result;
74+
}
75+
76+
async getAIResponse(messages: AIMessage[], signal: AbortSignal) {
77+
const params = {
78+
messages,
79+
model: this.azureOpenAIConfig.deployment,
80+
max_tokens: 1000,
81+
temperature: 0.7,
82+
};
83+
84+
const response = await this.aiService.chat.completions.create(params, { signal });
85+
const result = response.choices[0].message?.content;
86+
87+
return result;
88+
}
89+
}
90+
91+
@NgModule({
92+
imports: [
93+
BrowserModule,
94+
DxHtmlEditorModule,
95+
],
96+
declarations: [AppComponent],
97+
bootstrap: [AppComponent],
98+
})
99+
100+
export class AppModule { }
101+
102+
platformBrowserDynamic().bootstrapModule(AppModule);
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Injectable } from '@angular/core';
2+
import { AICommand, AICommandName } from 'devextreme/ui/html_editor';
3+
4+
const AzureOpenAIConfig = {
5+
dangerouslyAllowBrowser: true,
6+
deployment: 'gpt-4o-mini',
7+
apiVersion: '2024-02-01',
8+
endpoint: 'https://public-api.devexpress.com/demo-openai',
9+
apiKey: 'DEMO',
10+
};
11+
12+
const commands: Array<AICommand | AICommandName> = [
13+
'summarize',
14+
'proofread',
15+
'expand',
16+
'shorten',
17+
'changeStyle',
18+
'changeTone',
19+
'translate',
20+
'askAI',
21+
{
22+
name: 'custom',
23+
text: 'Extract Keywords',
24+
prompt: () => {
25+
return 'Extract a list of keywords from the text and return it as a comma-separated string';
26+
},
27+
},
28+
];
29+
30+
const markup = `
31+
<h2>
32+
<img src="../../../../images/widgets/HtmlEditor.svg" alt="HtmlEditor">
33+
Formatted Text Editor (HTML Editor)
34+
</h2>
35+
<br>
36+
<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>
37+
<p>Supported features:</p>
38+
<ul>
39+
<li>Inline formats:
40+
<ul>
41+
<li>Bold, italic, strikethrough text formatting</li>
42+
<li>Font, size, color changes (HTML only)</li>
43+
</ul>
44+
</li>
45+
<li>Block formats:
46+
<ul>
47+
<li>Headers</li>
48+
<li>Text alignment</li>
49+
<li>Lists (ordered and unordered)</li>
50+
<li>Code blocks</li>
51+
<li>Quotes</li>
52+
</ul>
53+
</li>
54+
<li>Custom formats</li>
55+
<li>Mail-merge placeholders (for example, %username%)</li>
56+
<li>Adaptive toolbar for working images, links, and color formats</li>
57+
<li>Image upload: drag-and-drop images onto the form, select files from the file system, or specify a URL.</li>
58+
<li>Copy-paste rich content (unsupported formats are removed)</li>
59+
<li>Tables support</li>
60+
</ul>
61+
`;
62+
63+
@Injectable({
64+
providedIn: 'root',
65+
})
66+
export class Service {
67+
getMarkup(): string {
68+
return markup;
69+
}
70+
71+
getCommands() {
72+
return commands;
73+
}
74+
75+
getAzureOpenAIConfig() {
76+
return AzureOpenAIConfig;
77+
}
78+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
3+
<head>
4+
<title>DevExtreme Demo</title>
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
8+
<link rel="stylesheet" type="text/css" href="../../../../node_modules/devextreme-dist/css/dx.light.css" />
9+
10+
<script src="../../../../node_modules/core-js/client/shim.min.js"></script>
11+
<script src="../../../../node_modules/zone.js/bundles/zone.umd.js"></script>
12+
<script src="../../../../node_modules/reflect-metadata/Reflect.js"></script>
13+
<script src="../../../../node_modules/systemjs/dist/system.js"></script>
14+
15+
<script src="config.js"></script>
16+
<script>
17+
System.import("app").catch(console.error.bind(console));
18+
</script>
19+
</head>
20+
21+
<body class="dx-viewport">
22+
<div class="demo-container">
23+
<demo-app>Loading...</demo-app>
24+
</div>
25+
</body>
26+
</html>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import HtmlEditor, {
3+
Toolbar,
4+
Item,
5+
} from 'devextreme-react/html-editor';
6+
import {
7+
markup,
8+
toolbarItems,
9+
aiIntegration,
10+
} from './data.ts';
11+
12+
export default function App() {
13+
return (
14+
<HtmlEditor
15+
height={530}
16+
defaultValue={markup}
17+
aiIntegration={aiIntegration}
18+
>
19+
<Toolbar items={toolbarItems}></Toolbar>
20+
</HtmlEditor>
21+
);
22+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import {
2+
AIIntegration,
3+
RequestParams,
4+
Response,
5+
} from 'devextreme/common/ai-integration';
6+
import { AICommand, AICommandName } from 'devextreme/ui/html_editor';
7+
import { AzureOpenAI, OpenAI } from 'openai';
8+
9+
type AIMessage = (OpenAI.ChatCompletionUserMessageParam | OpenAI.ChatCompletionSystemMessageParam) & {
10+
content: string;
11+
};
12+
13+
const AzureOpenAIConfig = {
14+
dangerouslyAllowBrowser: true,
15+
deployment: 'gpt-4o-mini',
16+
apiVersion: '2024-02-01',
17+
endpoint: 'https://public-api.devexpress.com/demo-openai',
18+
apiKey: 'DEMO',
19+
};
20+
21+
const aiService = new AzureOpenAI(AzureOpenAIConfig);
22+
23+
async function getAIResponse(messages: AIMessage[], signal: AbortSignal) {
24+
const params = {
25+
messages,
26+
model: AzureOpenAIConfig.deployment,
27+
max_tokens: 1000,
28+
temperature: 0.7,
29+
};
30+
31+
const response = await aiService.chat.completions.create(params, { signal });
32+
const result = response.choices[0].message?.content;
33+
34+
return result;
35+
}
36+
37+
export const aiIntegration = new AIIntegration({
38+
sendRequest({ prompt }: RequestParams): Response {
39+
const controller = new AbortController();
40+
const signal = controller.signal;
41+
42+
const aiPrompt: AIMessage[] = [
43+
{ role: 'system', content: prompt.system, },
44+
{ role: 'user', content: prompt.user, },
45+
];
46+
47+
const promise = getAIResponse(aiPrompt, signal);
48+
49+
const result: Response = {
50+
promise,
51+
abort: () => {
52+
controller.abort();
53+
},
54+
};
55+
56+
return result;
57+
},
58+
});
59+
60+
export const markup = `
61+
<h2>
62+
<img src="../../../../images/widgets/HtmlEditor.svg" alt="HtmlEditor">
63+
Formatted Text Editor (HTML Editor)
64+
</h2>
65+
<br>
66+
<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>
67+
<p>Supported features:</p>
68+
<ul>
69+
<li>Inline formats:
70+
<ul>
71+
<li>Bold, italic, strikethrough text formatting</li>
72+
<li>Font, size, color changes (HTML only)</li>
73+
</ul>
74+
</li>
75+
<li>Block formats:
76+
<ul>
77+
<li>Headers</li>
78+
<li>Text alignment</li>
79+
<li>Lists (ordered and unordered)</li>
80+
<li>Code blocks</li>
81+
<li>Quotes</li>
82+
</ul>
83+
</li>
84+
<li>Custom formats</li>
85+
<li>Mail-merge placeholders (for example, %username%)</li>
86+
<li>Adaptive toolbar for working images, links, and color formats</li>
87+
<li>Image upload: drag-and-drop images onto the form, select files from the file system, or specify a URL.</li>
88+
<li>Copy-paste rich content (unsupported formats are removed)</li>
89+
<li>Tables support</li>
90+
</ul>
91+
`;
92+
93+
export const commands: Array<AICommand | AICommandName> = [
94+
'summarize',
95+
'proofread',
96+
'expand',
97+
'shorten',
98+
'changeStyle',
99+
'changeTone',
100+
'translate',
101+
'askAI',
102+
{
103+
name: 'custom',
104+
text: 'Extract Keywords',
105+
prompt: () => {
106+
return 'Extract a list of keywords from the text and return it as a comma-separated string';
107+
},
108+
},
109+
];
110+
111+
export const toolbarItems = [{ name: 'ai', commands }, { name: 'separator' }, { name: 'undo' }, { name: 'redo' }];
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>DevExtreme Demo</title>
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
8+
<link rel="stylesheet" type="text/css" href="../../../../node_modules/devextreme-dist/css/dx.light.css" />
9+
<link rel="stylesheet" type="text/css" href="styles.css" />
10+
11+
<script src="../../../../node_modules/core-js/client/shim.min.js"></script>
12+
<script src="../../../../node_modules/systemjs/dist/system.js"></script>
13+
<script type="text/javascript" src="config.js"></script>
14+
<script type="text/javascript">
15+
System.import("./index.tsx");
16+
</script>
17+
</head>
18+
19+
<body class="dx-viewport">
20+
<div class="demo-container">
21+
<div id="app"></div>
22+
</div>
23+
</body>
24+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
4+
import App from './App.tsx';
5+
6+
ReactDOM.render(
7+
<App />,
8+
document.getElementById('app'),
9+
);

0 commit comments

Comments
 (0)