Skip to content

Commit b22eb34

Browse files
author
Julia Volkova
committed
frameworks
1 parent 3056b14 commit b22eb34

File tree

20 files changed

+817
-474
lines changed

20 files changed

+817
-474
lines changed
Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,53 @@
1-
<dx-data-grid
2-
id="gridContainer"
3-
[dataSource]="customers"
4-
[columns]="['CompanyName', 'City', 'State', 'Phone', 'Fax']"
5-
[showBorders]="true"
6-
>
7-
</dx-data-grid>
1+
<div id="form-container">
2+
<div class="instruction">Copy text from the editor below to the clipboard. Edit the text to see how your changes affect Smart Paste result.</div>
3+
<div class="instruction">Paste text from the clipboard to populate the form. Press Ctrl+Shift+V or use the "Smart Paste" button under the form.</div>
4+
<div class="textarea-container">
5+
<dx-button id="copy" text="Copy Text" icon="copy" stylingMode="contained" type="default" width="fit-content" (onClick)="onCopy()"></dx-button>
6+
<dx-text-area id="textarea" [value]="text" stylingMode="filled" height="100%" (onValueChanged)="setText($event)"></dx-text-area>
7+
</div>
8+
<dx-form id="form" labelMode="outside" labelLocation="top" [minColWidth]="220" [aiIntegration]="aiIntegration">
9+
<dxi-form-item itemType="group" [colCountByScreen]="colCountByScreen" caption="Billing Summary">
10+
<dxi-form-item
11+
dataField="Amount Due"
12+
editorType="dxTextBox"
13+
[editorOptions]="amountDueEditorOptions"
14+
[aiOptions]="amountDueAIOptions"
15+
></dxi-form-item>
16+
<dxi-form-item
17+
dataField="Statement Date"
18+
editorType="dxDateBox"
19+
[editorOptions]="statementDueEditorOptions"
20+
[aiOptions]="statementDueAIOptions"
21+
></dxi-form-item>
22+
</dxi-form-item>
23+
<dxi-form-item itemType="group" [colCountByScreen]="colCountByScreen" caption="Billing Information">
24+
<dxi-form-item dataField="First Name" editorType="dxTextBox" [editorOptions]="textEditorOptions"></dxi-form-item>
25+
<dxi-form-item dataField="Last Name" editorType="dxTextBox" [editorOptions]="textEditorOptions"></dxi-form-item>
26+
<dxi-form-item
27+
dataField="Phone Number"
28+
editorType="dxTextBox"
29+
[editorOptions]="phoneEditorOptions"
30+
[aiOptions]="phoneAIOptions"
31+
>
32+
</dxi-form-item>
33+
<dxi-form-item
34+
dataField="Email"
35+
editorType="dxTextBox"
36+
[editorOptions]="textEditorOptions"
37+
[aiOptions]="emailAIOptions"
38+
>
39+
<dxi-validation-rule type="email"></dxi-validation-rule>
40+
</dxi-form-item>
41+
</dxi-form-item>
42+
<dxi-form-item itemType="group" [colCountByScreen]="colCountByScreen" caption="Billing Address">
43+
<dxi-form-item dataField="Street Address" editorType="dxTextBox" [editorOptions]="textEditorOptions"></dxi-form-item>
44+
<dxi-form-item dataField="City" editorType="dxTextBox" [editorOptions]="textEditorOptions"></dxi-form-item>
45+
<dxi-form-item dataField="State/Province/Region" editorType="dxTextBox" [editorOptions]="textEditorOptions"></dxi-form-item>
46+
<dxi-form-item dataField="ZIP" editorType="dxNumberBox" [editorOptions]="zipEditorOptions" [aiOptions]="zipAIOptions"></dxi-form-item>
47+
</dxi-form-item>
48+
<dxi-form-item itemType="group" cssClass="buttons-group" [colCountByScreen]="colCountByScreen">
49+
<dxi-form-item itemType="button" name="smartPaste" [buttonOptions]="smartPasteButtonOptions"></dxi-form-item>
50+
<dxi-form-item itemType="button" name="reset" [buttonOptions]="resetButtonOptions"></dxi-form-item>
51+
</dxi-form-item>
52+
</dx-form>
53+
</div>

apps/demos/Demos/Form/SmartPaste/Angular/app/app.component.ts

Lines changed: 163 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1-
import { NgModule, Component, enableProdMode } from '@angular/core';
1+
import { NgModule, Component, ViewChild, enableProdMode } from '@angular/core';
22
import { BrowserModule } from '@angular/platform-browser';
33
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
4-
import { DxDataGridModule } from 'devextreme-angular';
5-
import { Customer, Service } from './app.service';
4+
import {
5+
DxTextAreaModule,
6+
DxFormModule,
7+
DxFormComponent,
8+
DxDateBoxModule,
9+
DxButtonModule
10+
} from 'devextreme-angular';
11+
import {
12+
AIIntegration,
13+
RequestParams,
14+
Response,
15+
} from 'devextreme-angular/common/ai-integration';
16+
import notify from 'devextreme/ui/notify';
17+
import { AzureOpenAI, OpenAI } from 'openai';
18+
import { Service } from './app.service';
19+
import { ButtonStyle } from 'devextreme-angular/common';
20+
import type { ValueChangedEvent } from 'devextreme/ui/text_area';
621

722
if (!/localhost/.test(document.location.host)) {
823
enableProdMode();
@@ -14,23 +29,165 @@ if (window && window.config?.packageConfigPaths) {
1429
modulePrefix = '/app';
1530
}
1631

32+
const stylingMode = 'filled';
33+
34+
type AIMessage = (OpenAI.ChatCompletionUserMessageParam | OpenAI.ChatCompletionSystemMessageParam) & {
35+
content: string;
36+
};
37+
38+
const sendNotification = (message, of, offset) => {
39+
notify({
40+
message,
41+
position: {
42+
my: "bottom center",
43+
at: "bottom center",
44+
of,
45+
offset: offset ?? '0 -50',
46+
},
47+
width: 'fit-content',
48+
maxWidth: 'fit-content',
49+
minWidth: 'fit-content',
50+
}, 'info', 1500);
51+
}
52+
1753
@Component({
1854
selector: 'demo-app',
1955
templateUrl: `.${modulePrefix}/app.component.html`,
56+
styleUrls: [`.${modulePrefix}/app.component.css`],
2057
providers: [Service],
2158
})
2259
export class AppComponent {
23-
customers: Customer[];
60+
@ViewChild(DxFormComponent, { static: false }) form: DxFormComponent;
61+
62+
colCountByScreen = {
63+
xs: 2,
64+
sm: 2,
65+
md: 2,
66+
lg: 2,
67+
};
68+
69+
amountDueEditorOptions = { placeholder: '$0.00', stylingMode };
70+
amountDueAIOptions = { instruction: 'Format as the following: $0.00' };
71+
72+
statementDueEditorOptions = { placeholder: 'MM/DD/YYYY', stylingMode };
73+
statementDueAIOptions = { instruction: 'Format as the following: MM/DD/YYYY' };
74+
75+
textEditorOptions = { stylingMode };
76+
77+
phoneEditorOptions = { placeholder: '(000) 000-0000', stylingMode };
78+
phoneAIOptions = { instruction: 'Format as the following: (000) 000-0000' };
79+
80+
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' };
81+
82+
zipEditorOptions = { stylingMode, mode: 'text', value: null };
83+
zipAIOptions = { instruction: 'If the text does not contain a ZIP, determine the ZIP code from the provided address.' };
84+
85+
resetButtonOptions = {
86+
stylingMode: 'outlined' as ButtonStyle,
87+
type: 'normal'
88+
};
89+
90+
smartPasteButtonOptions = {
91+
stylingMode: 'contained' as ButtonStyle,
92+
type: 'default',
93+
}
94+
95+
azureOpenAIConfig: any;
96+
97+
aiService: AzureOpenAI;
98+
99+
aiIntegration: AIIntegration;
100+
101+
valueContent: string;
102+
103+
text: string;
24104

25105
constructor(service: Service) {
26-
this.customers = service.getCustomers();
106+
this.azureOpenAIConfig = service.getAzureOpenAIConfig();
107+
108+
this.aiService = new AzureOpenAI(this.azureOpenAIConfig);
109+
this.aiIntegration = new AIIntegration({
110+
sendRequest: this.sendRequest.bind(this),
111+
});
112+
113+
this.text = service.getDefaultText();
114+
}
115+
116+
ngAfterViewInit() {
117+
const form = this.form.instance;
118+
119+
form.registerKeyHandler('V', (event: KeyboardEvent) => {
120+
if (event.ctrlKey && event.shiftKey) {
121+
navigator.clipboard.readText()
122+
.then((clipboardText) => {
123+
if (clipboardText) {
124+
form.smartPaste(clipboardText);;
125+
} else {
126+
sendNotification('Copy the text to paste into the form', '#form');
127+
}
128+
})
129+
.catch(() => {
130+
sendNotification('Could not access the clipboard', '#form');
131+
});
132+
}
133+
});
134+
}
135+
136+
sendRequest({ prompt }: RequestParams): Response {
137+
const controller = new AbortController();
138+
const signal = controller.signal;
139+
140+
const aiPrompt: AIMessage[] = [
141+
{ role: 'system', content: prompt.system, },
142+
{ role: 'user', content: prompt.user, },
143+
];
144+
const promise = this.getAIResponse(aiPrompt, signal);
145+
146+
const result: Response = {
147+
promise,
148+
abort: () => {
149+
controller.abort();
150+
},
151+
};
152+
153+
return result;
154+
}
155+
156+
async getAIResponse(messages: AIMessage[], signal: AbortSignal) {
157+
const params = {
158+
messages,
159+
model: this.azureOpenAIConfig.deployment,
160+
max_tokens: 1000,
161+
temperature: 0.7,
162+
};
163+
164+
const response = await this.aiService.chat.completions.create(params, { signal });
165+
const result = response.choices[0].message?.content;
166+
167+
return result;
168+
}
169+
170+
onCopy() {
171+
navigator.clipboard.writeText(this.text);
172+
sendNotification('Text copied to clipboard', '#textarea', '0 -20');
173+
}
174+
175+
setText(event: ValueChangedEvent) {
176+
this.text = event.value;
177+
}
178+
179+
smartPaste() {
180+
form.instance().smartPaste();
27181
}
28182
}
29183

30184
@NgModule({
31185
imports: [
32186
BrowserModule,
33-
DxDataGridModule,
187+
DxTextAreaModule,
188+
DxFormModule,
189+
DxDateBoxModule,
190+
DxButtonModule,
34191
],
35192
declarations: [AppComponent],
36193
bootstrap: [AppComponent],

0 commit comments

Comments
 (0)