Backend API
@section ExternalDependencies {
<script type="module">
import { AzureOpenAI } from "https://esm.sh/openai@4.73.1";
window.AzureOpenAI = AzureOpenAI;
</script>
}
@using DevExtreme.NETCore.Demos.ViewModels
@{
var textAreaText = @"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";
}
<div id="textarea-label" class="instruction">
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">
@(Html.DevExtreme().Button()
.Text("Copy Text")
.Icon("copy")
.Type("default")
.StylingMode(ButtonStylingMode.Contained)
.Width("fit-content")
.OnClick("onCopy")
)
@(Html.DevExtreme().TextArea()
.ID("textarea")
.Value(textAreaText)
.InputAttr("aria-labelledby", "textarea-label")
.StylingMode(EditorStylingMode.Filled)
.Height("100%")
)
</div>
@(Html.DevExtreme().Form<SmartPasteFormViewModel>()
.ID("form")
.AiIntegration(new JS("aiIntegration"))
.LabelMode(FormLabelMode.Outside)
.LabelLocation(FormLabelLocation.Top)
.ShowColonAfterLabel(false)
.MinColWidth(220)
.Items(items => {
items.AddGroup()
.Caption("Billing Summary")
.ColCountByScreen(c => c.Md(2).Sm(2).Lg(2))
.Items(groupItems => {
groupItems.AddSimpleFor(m => m.AmountDue)
.Editor(e => e
.TextBox()
.Placeholder("$0.00")
.StylingMode(EditorStylingMode.Filled)
)
.AiOptions(e => e
.Instruction("Format as the following: $0.00")
);
groupItems.AddSimpleFor(m => m.StatementDate)
.Editor(e => e
.DateBox()
.Placeholder("MM/DD/YYYY")
.StylingMode(EditorStylingMode.Filled)
)
.AiOptions(e => e
.Instruction("Format as the following: MM/DD/YYYY")
)
.IsRequired(false);
});
items.AddGroup()
.Caption("Billing Information")
.ColCountByScreen(c => c.Md(2).Sm(2).Lg(2))
.Items(groupItems => {
groupItems.AddSimpleFor(m => m.FirstName)
.Editor(e => e
.TextBox()
.StylingMode(EditorStylingMode.Filled)
);
groupItems.AddSimpleFor(m => m.LastName)
.Editor(e => e
.TextBox()
.StylingMode(EditorStylingMode.Filled)
);
groupItems.AddSimpleFor(m => m.PhoneNumber)
.Editor(e => e
.TextBox()
.Placeholder("(000) 000-0000")
.StylingMode(EditorStylingMode.Filled)
)
.AiOptions(e => e
.Instruction("Format as the following: (000) 000-0000")
);
groupItems.AddSimpleFor(m => m.Email)
.Editor(e => e
.TextBox()
.StylingMode(EditorStylingMode.Filled)
)
.ValidationRules(
vr => {
vr.AddEmail();
}
)
.AiOptions(e => e
.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")
);
});
items.AddGroup()
.Caption("Billing Address")
.ColCountByScreen(c => c.Md(2).Sm(2).Lg(2))
.Items(groupItems => {
groupItems.AddSimpleFor(m => m.StreetAddress)
.Editor(e => e
.TextBox()
.StylingMode(EditorStylingMode.Filled)
);
groupItems.AddSimpleFor(m => m.City)
.Editor(e => e
.TextBox()
.StylingMode(EditorStylingMode.Filled)
);
groupItems.AddSimpleFor(m => m.State)
.DataField("State/Province/Region")
.Editor(e => e
.TextBox()
.StylingMode(EditorStylingMode.Filled)
);
groupItems.AddSimpleFor(m => m.ZIP)
.Editor(e => e
.TextBox()
.StylingMode(EditorStylingMode.Filled)
)
.AiOptions(e => e
.Instruction("If the text does not contain a ZIP, determine the ZIP code from the provided address.")
);
});
items.AddGroup()
.ColCountByScreen(c => c.Md(2).Sm(2).Lg(2))
.CssClass("buttons-group")
.Items(groupItems => {
groupItems.AddButton().Name("smartPaste")
.ButtonOptions(b => b
.Type(ButtonType.Default)
.StylingMode(ButtonStylingMode.Contained)
);
groupItems.AddButton().Name("reset")
.ButtonOptions(b => b
.Type(ButtonType.Normal)
.StylingMode(ButtonStylingMode.Outlined)
);
});
})
)
<script>
let aiService;
const deployment = 'gpt-4o-mini';
const apiVersion = '2024-02-01';
const endpoint = 'https://public-api.devexpress.com/demo-openai';
const apiKey = 'DEMO';
async function getAIResponse(messages, signal) {
const params = {
messages,
model: deployment,
max_tokens: 1000,
temperature: 0.7,
};
return aiService.chat.completions.create(params, { signal });
}
const aiIntegration = new DevExpress.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 = new Promise(async (resolve, reject) => {
try {
const response = await getAIResponse(aiPrompt, signal);
const result = response.choices[0].message?.content;
resolve(result);
} catch {
showNotification('Something went wrong. Please try again.', '#form', true);
reject();
}
});
const result = {
promise,
abort: () => {
controller.abort();
},
};
return result;
},
});
$(() => {
aiService = new AzureOpenAI({
dangerouslyAllowBrowser: true,
deployment,
endpoint,
apiVersion,
apiKey,
});
const form = $('#form').dxForm('instance');
form.registerKeyHandler('V', (event) => {
if (event.ctrlKey && event.shiftKey) {
navigator.clipboard.readText()
.then((text) => {
if (text) {
form.smartPaste(text);
} else {
showNotification(
'Clipboard is empty. Copy text before pasting',
'#form',
);
}
})
.catch(() => {
showNotification(
'Could not access the clipboard',
'#form',
);
});
}
});
});
function showNotification(message, of, isError, offset) {
DevExpress.ui.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);
};
function onCopy(data) {
const textAreaText = $('#textarea').dxTextArea('instance').option('value');
navigator.clipboard.writeText(textAreaText);
showNotification('Text copied to clipboard', "#textarea", false, '0 -20');
}
</script>
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using DevExtreme.NETCore.Demos.Models.SampleData;
using DevExtreme.NETCore.Demos.ViewModels;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
namespace DevExtreme.NETCore.Demos.Controllers {
public class FormController : Controller {
public ActionResult SmartPaste() {
return View(new SmartPasteFormViewModel {});
}
}
}
using System;
using System.Collections.Generic;
namespace DevExtreme.NETCore.Demos.ViewModels {
public class FormViewModel {
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string CompanyName { get; set; }
public string Position { get; set; }
public string OfficeNo { get; set; }
public DateTime BirthDate { get; set; }
public DateTime HireDate { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Skype { get; set; }
public string Notes { get; set; }
}
public class DynamicFormViewModel {
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public List<string> Phones { get; set; }
}
public class SmartPasteFormViewModel {
public string AmountDue { get; set; }
public DateTime StatementDate { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZIP { get; set; }
}
}
.demo-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;
}
.dx-layout-manager .dx-field-item.dx-last-row {
padding-top: 4px;
}
.dx-toast-info .dx-toast-icon {
display: none;
}
.buttons-group {
display: flex;
width: 100%;
justify-content: end;
}
.buttons-group .dx-item-content {
gap: 8px;
}
.buttons-group .dx-field-item:not(.dx-first-col),
.buttons-group .dx-field-item:not(.dx-last-col) {
padding: 0;
}
.buttons-group .dx-item {
flex: unset !important;
}
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.