Backend API
<div id="form-container">
<div class="instruction" id="textarea-label"
>Copy text from the editor below to the clipboard. Edit the text to see how
your changes affect Smart Paste result.</div
>
<div class="instruction"
>Paste text from the clipboard to populate the form. Press Ctrl+Shift+V
(when the form is focused) or use the "Smart Paste" button under the
form.</div
>
<div class="textarea-container">
<dx-button
id="copy"
text="Copy Text"
icon="copy"
stylingMode="contained"
type="default"
width="fit-content"
(onClick)="onCopy()"
></dx-button>
<dx-text-area
id="textarea"
[elementAttr]="{ 'aria-labelledby': 'textarea-label' }"
[(value)]="text"
stylingMode="filled"
height="100%"
></dx-text-area>
</div>
<dx-form
id="form"
labelMode="outside"
labelLocation="top"
[showColonAfterLabel]="false"
[minColWidth]="220"
[aiIntegration]="aiIntegration"
stylingMode="filled"
>
<dxi-form-item
itemType="group"
[colCountByScreen]="colCountByScreen"
caption="Billing Summary"
>
<dxi-form-item
dataField="Amount Due"
editorType="dxTextBox"
[editorOptions]="amountDueEditorOptions"
[aiOptions]="amountDueAIOptions"
></dxi-form-item>
<dxi-form-item
dataField="Statement Date"
editorType="dxDateBox"
[editorOptions]="statementDueEditorOptions"
[aiOptions]="statementDueAIOptions"
></dxi-form-item>
</dxi-form-item>
<dxi-form-item
itemType="group"
[colCountByScreen]="colCountByScreen"
caption="Billing Information"
>
<dxi-form-item
dataField="First Name"
editorType="dxTextBox"
[editorOptions]="textEditorOptions"
></dxi-form-item>
<dxi-form-item
dataField="Last Name"
editorType="dxTextBox"
[editorOptions]="textEditorOptions"
></dxi-form-item>
<dxi-form-item
dataField="Phone Number"
editorType="dxTextBox"
[editorOptions]="phoneEditorOptions"
[aiOptions]="phoneAIOptions"
>
</dxi-form-item>
<dxi-form-item
dataField="Email"
editorType="dxTextBox"
[editorOptions]="textEditorOptions"
[aiOptions]="emailAIOptions"
>
<dxi-form-validation-rule type="email"></dxi-form-validation-rule>
</dxi-form-item>
</dxi-form-item>
<dxi-form-item
itemType="group"
[colCountByScreen]="colCountByScreen"
caption="Billing Address"
>
<dxi-form-item
dataField="Street Address"
editorType="dxTextBox"
[editorOptions]="textEditorOptions"
></dxi-form-item>
<dxi-form-item
dataField="City"
editorType="dxTextBox"
[editorOptions]="textEditorOptions"
></dxi-form-item>
<dxi-form-item
dataField="State/Province/Region"
editorType="dxTextBox"
[editorOptions]="textEditorOptions"
></dxi-form-item>
<dxi-form-item
dataField="ZIP"
editorType="dxNumberBox"
[editorOptions]="zipEditorOptions"
[aiOptions]="zipAIOptions"
></dxi-form-item>
</dxi-form-item>
<dxi-form-item
itemType="group"
cssClass="buttons-group"
[colCountByScreen]="colCountByScreen"
>
<dxi-form-item
itemType="button"
name="smartPaste"
[buttonOptions]="smartPasteButtonOptions"
></dxi-form-item>
<dxi-form-item
itemType="button"
name="reset"
[buttonOptions]="resetButtonOptions"
></dxi-form-item>
</dxi-form-item>
</dx-form>
</div>
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, ViewChild, enableProdMode, provideZoneChangeDetection } from '@angular/core';
import { AzureOpenAI, type OpenAI } from 'openai';
import { DxTextAreaModule } from 'devextreme-angular';
import {
AIIntegration,
RequestParams,
Response,
} from 'devextreme-angular/common/ai-integration';
import { DxButtonModule, type DxButtonTypes } from 'devextreme-angular/ui/button';
import { DxFormModule, DxFormComponent } from 'devextreme-angular/ui/form';
import notify from 'devextreme/ui/notify';
import { Service } from './app.service';
if (!/localhost/.test(document.location.host)) {
enableProdMode();
}
let modulePrefix = '';
// @ts-ignore
if (window && window.config?.packageConfigPaths) {
modulePrefix = '/app';
}
const stylingMode = 'filled';
type AIMessage = (OpenAI.ChatCompletionUserMessageParam | OpenAI.ChatCompletionSystemMessageParam) & {
content: string;
};
const showNotification = (message: string, of: string, isError?: boolean, offset?: string) => {
notify({
message,
position: {
my: 'bottom center',
at: 'bottom center',
of,
offset: offset ?? '0 -50',
},
width: 'fit-content',
maxWidth: 'fit-content',
minWidth: 'fit-content',
}, isError ? 'error' : 'info', 1500);
};
@Component({
selector: 'demo-app',
templateUrl: `app/app.component.html`,
styleUrls: [`app/app.component.css`],
providers: [Service],
imports: [
DxButtonModule,
DxFormModule,
DxTextAreaModule,
],
})
export class AppComponent {
@ViewChild(DxFormComponent, { static: false }) form: DxFormComponent;
colCountByScreen = {
xs: 2,
sm: 2,
md: 2,
lg: 2,
};
amountDueEditorOptions = { placeholder: '$0.00', stylingMode };
amountDueAIOptions = { instruction: 'Format as the following: $0.00' };
statementDueEditorOptions = { placeholder: 'MM/DD/YYYY', stylingMode };
statementDueAIOptions = { instruction: 'Format as the following: MM/DD/YYYY' };
textEditorOptions = { stylingMode };
phoneEditorOptions = { placeholder: '(000) 000-0000', stylingMode };
phoneAIOptions = { instruction: 'Format as the following: (000) 000-0000' };
emailAIOptions = { instruction: 'Do not fill this field if the text contains an invalid email address. A valid email is in the following format: email@example.com' };
zipEditorOptions = { stylingMode, mode: 'text', value: null };
zipAIOptions = { instruction: 'If the text does not contain a ZIP, determine the ZIP code from the provided address.' };
resetButtonOptions: DxButtonTypes.Properties = {
stylingMode: 'outlined',
type: 'normal',
};
smartPasteButtonOptions: DxButtonTypes.Properties = {
stylingMode: 'contained',
type: 'default',
};
azureOpenAIConfig: any;
aiService: AzureOpenAI;
aiIntegration: AIIntegration;
valueContent: string;
text: string;
constructor(service: Service) {
this.azureOpenAIConfig = service.getAzureOpenAIConfig();
this.aiService = new AzureOpenAI(this.azureOpenAIConfig);
this.aiIntegration = new AIIntegration({
sendRequest: this.sendRequest.bind(this),
});
this.text = service.getDefaultText();
}
ngAfterViewInit() {
const form = this.form.instance;
form.registerKeyHandler('V', (event: KeyboardEvent) => {
if (event.ctrlKey && event.shiftKey) {
navigator.clipboard.readText()
.then((clipboardText) => {
if (clipboardText) {
form.smartPaste(clipboardText);
} else {
showNotification('Clipboard is empty. Copy text before pasting', '#form');
}
})
.catch(() => {
showNotification('Could not access the clipboard', '#form');
});
}
});
}
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 = this.getAIResponse(aiPrompt, signal);
promise.catch(() => {
showNotification('Something went wrong. Please try again.', '#form', true);
});
const result: Response = {
promise,
abort: () => {
controller.abort();
},
};
return result;
}
async getAIResponse(messages: AIMessage[], signal: AbortSignal) {
const params = {
messages,
model: this.azureOpenAIConfig.deployment,
max_tokens: 1000,
temperature: 0.7,
};
const response = await this.aiService.chat.completions.create(params, { signal });
const result = response.choices[0].message?.content;
return result;
}
onCopy() {
navigator.clipboard.writeText(this.text);
showNotification('Text copied to clipboard', '#textarea', false, '0 -20');
}
}
bootstrapApplication(AppComponent, {
providers: [
provideZoneChangeDetection({ eventCoalescing: true, runCoalescing: true }),
],
});
#form-container {
display: grid;
grid-template-columns: 1fr 2fr;
grid-template-rows: auto auto;
gap: 24px 40px;
min-width: 720px;
max-width: 900px;
margin: auto;
}
.instruction {
color: var(--dx-texteditor-color-label);
}
.textarea-container {
display: flex;
flex-direction: column;
gap: 16px;
}
::ng-deep .dx-layout-manager .dx-field-item.dx-last-row {
padding-top: 4px;
}
::ng-deep .dx-toast-info .dx-toast-icon {
display: none;
}
::ng-deep .buttons-group {
display: flex;
width: 100%;
justify-content: end;
}
::ng-deep .buttons-group .dx-item-content {
gap: 8px;
}
::ng-deep .buttons-group .dx-field-item:not(.dx-first-col),
::ng-deep .buttons-group .dx-field-item:not(.dx-last-col) {
padding: 0;
}
::ng-deep .buttons-group .dx-item {
flex: unset !important;
}
import { Injectable } from '@angular/core';
const AzureOpenAIConfig = {
dangerouslyAllowBrowser: true,
deployment: 'gpt-4o-mini',
apiVersion: '2024-02-01',
endpoint: 'https://public-api.devexpress.com/demo-openai',
apiKey: 'DEMO',
};
const defaultText = `Payment: Amount - $123.00
Statement Date: 10/15/2024
Name: John Smith
Contact: (123) 456-7890
Email: john@myemail.com
Address:
- 123 Elm St Apt 4B
- New York, NY 10001
`;
@Injectable({
providedIn: 'root',
})
export class Service {
getDefaultText() {
return defaultText;
}
getAzureOpenAIConfig() {
return AzureOpenAIConfig;
}
}
// In real applications, you should not transpile code in the browser.
// You can see how to create your own application with Angular and DevExtreme here:
// https://js.devexpress.com/Documentation/Guide/Angular_Components/Getting_Started/Create_a_DevExtreme_Application/
const componentNames = [
'accordion',
'action-sheet',
'autocomplete',
'bar-gauge',
'box',
'bullet',
'button-group',
'button',
'calendar',
'card-view',
'chart',
'chat',
'check-box',
'circular-gauge',
'color-box',
'context-menu',
'data-grid',
'date-box',
'date-range-box',
'defer-rendering',
'diagram',
'draggable',
'drawer',
'drop-down-box',
'drop-down-button',
'file-manager',
'file-uploader',
'filter-builder',
'form',
'funnel',
'gallery',
'gantt',
'html-editor',
'linear-gauge',
'list',
'load-indicator',
'load-panel',
'lookup',
'map',
'menu',
'multi-view',
'nested',
'number-box',
'pagination',
'pie-chart',
'pivot-grid-field-chooser',
'pivot-grid',
'polar-chart',
'popover',
'popup',
'progress-bar',
'radio-group',
'range-selector',
'range-slider',
'recurrence-editor',
'resizable',
'responsive-box',
'sankey',
'scheduler',
'scroll-view',
'select-box',
'slider',
'sortable',
'sparkline',
'speech-to-text',
'speed-dial-action',
'splitter',
'stepper',
'switch',
'tab-panel',
'tabs',
'tag-box',
'text-area',
'text-box',
'tile-view',
'toast',
'toolbar',
'tooltip',
'tree-list',
'tree-map',
'tree-view',
'validation-group',
'validation-summary',
'validator',
'vector-map',
];
window.exports = window.exports || {};
window.config = {
transpiler: 'ts',
typescriptOptions: {
module: 'system',
emitDecoratorMetadata: true,
experimentalDecorators: true,
},
meta: {
'typescript': {
'exports': 'ts',
},
'devextreme/time_zone_utils.js': {
'esModule': true,
},
'devextreme/localization.js': {
'esModule': true,
},
'devextreme/viz/palette.js': {
'esModule': true,
},
'@angular/platform-browser-dynamic': {
'esModule': true,
},
'@angular/platform-browser': {
'esModule': true,
},
'@angular/core': {
'esModule': true,
},
'@angular/common': {
'esModule': true,
},
'@angular/common/http': {
'esModule': true,
},
'@angular/animations': {
'esModule': true,
},
'@angular/forms': {
'esModule': true,
},
'openai': {
'esModule': true,
},
},
paths: {
'npm:': 'https://cdn.jsdelivr.net/npm/',
'bundles:': '../../../../bundles/',
'externals:': '../../../../bundles/externals/',
},
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',
/* @angular */
'@angular/compiler': 'bundles:@angular/compiler.umd.js',
'@angular/platform-browser-dynamic': 'bundles:@angular/platform-browser-dynamic.umd.js',
'@angular/core': 'bundles:@angular/core.umd.js',
'@angular/core/primitives/signals': 'bundles:@angular/core.primitives.signals.umd.js',
'@angular/core/primitives/di': 'bundles:@angular/core.primitives.di.umd.js',
'@angular/common': 'bundles:@angular/common.umd.js',
'@angular/common/http': 'bundles:@angular/common-http.umd.js',
'@angular/platform-browser': 'bundles:@angular/platform-browser.umd.js',
'@angular/platform-browser/animations': 'bundles:@angular/platform-browser.umd.js',
'@angular/forms': 'bundles:@angular/forms.umd.js',
/* devextreme */
'devextreme': 'npm:devextreme@link:../../packages/devextreme/artifacts/npm/devextreme/cjs',
'devextreme-quill': 'npm:devextreme-quill@1.7.7/dist/dx-quill.min.js',
'devexpress-diagram': 'npm:devexpress-diagram@2.2.24',
'devexpress-gantt': 'npm:devexpress-gantt@4.1.64',
/* devextreme-angular umd maps */
'devextreme-angular': 'bundles:devextreme-angular/devextreme-angular.umd.js',
'devextreme-angular/common/ai-integration': 'bundles:devextreme-angular/devextreme-angular-common-ai-integration.umd.js',
'devextreme-angular/core': 'bundles:devextreme-angular/devextreme-angular-core.umd.js',
'devextreme-angular/common/charts': 'bundles:devextreme-angular/devextreme-angular-common-charts.umd.js',
'devextreme-angular/common/core/animation': 'bundles:devextreme-angular/devextreme-angular-common-core-animation.umd.js',
'devextreme-angular/common/core/environment': 'bundles:devextreme-angular/devextreme-angular-common-core-environment.umd.js',
'devextreme-angular/common/core/events': 'bundles:devextreme-angular/devextreme-angular-common-core-events.umd.js',
'devextreme-angular/common/core/localization': 'bundles:devextreme-angular/devextreme-angular-common-core-localization.umd.js',
'devextreme-angular/common/core': 'bundles:devextreme-angular/devextreme-angular-common-core.umd.js',
'devextreme-angular/common/data/custom-store': 'bundles:devextreme-angular/devextreme-angular-common-data-custom-store.umd.js',
'devextreme-angular/common/data': 'bundles:devextreme-angular/devextreme-angular-common-data.umd.js',
'devextreme-angular/common/export/excel': 'bundles:devextreme-angular/devextreme-angular-common-export-excel.umd.js',
'devextreme-angular/common/export/pdf': 'bundles:devextreme-angular/devextreme-angular-common-export-pdf.umd.js',
'devextreme-angular/common/export': 'bundles:devextreme-angular/devextreme-angular-common-export.umd.js',
'devextreme-angular/common/grids': 'bundles:devextreme-angular/devextreme-angular-common-grids.umd.js',
'devextreme-angular/common': 'bundles:devextreme-angular/devextreme-angular-common.umd.js',
'devextreme-angular/http': 'bundles:devextreme-angular/devextreme-angular-http.umd.js',
'devextreme-angular/core/tokens': 'bundles:devextreme-angular/devextreme-angular-core-tokens.umd.js',
...componentNames.reduce((acc, name) => {
acc[`devextreme-angular/ui/${name}`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}.umd.js`;
acc[`devextreme-angular/ui/${name}/nested`] = `bundles:devextreme-angular/devextreme-angular-ui-${name}-nested.umd.js`;
return acc;
}, {}),
'openai': 'externals:openai.bundle.js',
'tslib': 'npm:tslib/tslib.js',
'rxjs': 'npm:rxjs@7.5.3/dist/bundles/rxjs.umd.js',
'rxjs/operators': 'npm:rxjs@7.5.3/dist/cjs/operators/index.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',
'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',
// Prettier
'prettier/standalone': 'npm:prettier@2.8.8/standalone.js',
'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js',
'zone.js': 'npm:zone.js@0.15.1/bundles/zone.umd.js',
},
packages: {
'app': {
main: './app.component.ts',
defaultExtension: 'ts',
},
'devextreme': {
defaultExtension: 'js',
},
'devextreme/events/utils': {
main: 'index',
},
'devextreme/common/core/events/utils': {
main: 'index',
},
'devextreme/events': {
main: 'index',
},
'es6-object-assign': {
main: './index.js',
defaultExtension: 'js',
},
'rxjs': {
defaultExtension: 'js',
},
'rxjs/operators': {
defaultExtension: 'js',
},
},
packageConfigPaths: [
'npm:@devextreme/*/package.json',
'npm:rxjs@7.5.3/package.json',
'npm:rxjs@7.5.3/operators/package.json',
'npm:devexpress-diagram@2.2.24/package.json',
'npm:devexpress-gantt@4.1.64/package.json',
],
};
window.process = {
env: {
NODE_ENV: 'production',
},
};
System.config(window.config);
// eslint-disable-next-line no-console
// System.import('@angular/compiler').catch(console.error.bind(console));
// eslint-disable-next-line
const useTgzInCSB = ['openai'];
let packagesInfo = {
"openai": {
"version": "4.73.1"
},
"@angular/core": {
"version": "21.0.5"
},
"core-js": {
"version": "2.6.12"
},
"typescript": {
"version": "5.9.3"
},
"zone.js": {
"version": "0.15.1"
}
};
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" 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.2.3/css/dx.light.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/zone.js@0.15.1/bundles/zone.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reflect-metadata@0.1.13/Reflect.js"></script>
<script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.3/dist/system.js"></script>
<script src="config.js"></script>
<script>
System.import("app").catch(console.error.bind(console));
</script>
</head>
<body class="dx-viewport">
<div class="demo-container">
<demo-app>Loading...</demo-app>
</div>
</body>
</html>
Use the following APIs to activate Smart Paste in our Form component:
- aiIntegration - accepts an AIIntegration object that contains AI Service settings.
- 'smartPaste' – adds a built-in Smart Paste button to the Form (see name for additional information). To use this capability in code, call the smartPaste(text) method. This sample leverages this method and implements a custom shortcut to activate Smart Paste.
Configure each Form item using aiOptions:
- disabled - prevents AI-generated text from being pasted into this item.
- instruction - specifies item instruction for the AI service.