@using DevExtreme.MVC.Demos.Models
@model IEnumerable<Vehicle>
@section ExternalDependencies {
<script type="module">
import { AzureOpenAI } from "https://esm.sh/openai@4.73.1";
window.AzureOpenAI = AzureOpenAI;
</script>
}
@(Html.DevExtreme().Popup()
.ID("popup")
.Width(360)
.Height(260)
.Visible(false)
.DragEnabled(false)
.HideOnOutsideClick(true)
.ShowCloseButton(true)
.Title("Image Info")
.Position(p => p
.At(HorizontalAlignment.Center, VerticalAlignment.Center)
.My(HorizontalAlignment.Center, VerticalAlignment.Center)
.Collision(PositionResolveCollision.Fit, PositionResolveCollision.Fit)
)
)
@(Html.DevExtreme().DataGrid<Vehicle>()
.ID("gridContainer")
.ShowBorders(true)
.DataSource(Model, "ID")
.AiIntegration(new JS("aiIntegration"))
.Paging(p => p.PageSize(10))
.Grouping(g => g.ContextMenuEnabled(false))
.Columns(c => {
c.Add()
.Caption("Trademark")
.Width(200)
.CellTemplate(@<text>
<div class="trademark__wrapper">
<div class="trademark__img-wrapper">
<img class="trademark__img"
src="@Url.Content("~/Content/images/vehicles/image_")<%- data.ID %>.png"
alt="<%- data.TrademarkName %> <%- data.Name %>"
tabindex="0"
role="button"
aria-haspopup="dialog"
aria-label="<%- data.TrademarkName %> <%- data.Name %> - press Enter for image info"
onclick="showVehiclePopup(<%- JSON.stringify(data) %>)"
onkeydown="if(event.key === 'Enter') showVehiclePopup(<%- JSON.stringify(data) %>)" />
</div>
<div class="trademark__text-wrapper">
<div class="trademark__text trademark__text--title"><%- data.TrademarkName %></div>
<div class="trademark__text trademark__text--subtitle"><%- data.Name %></div>
</div>
</div>
</text>);
c.AddFor(m => m.Price)
.Alignment(HorizontalAlignment.Left)
.Format(Format.Currency)
.Width(100);
c.AddFor(m => m.CategoryName)
.CellTemplate(@<text>
<div class="category__wrapper"><%- data.CategoryName %></div>
</text>)
.MinWidth(180);
c.AddFor(m => m.Modification)
.Width(180);
c.AddFor(m => m.Horsepower)
.Width(140);
c.AddFor(m => m.BodyStyleName)
.Width(180);
c.Add()
.Name("AIColumn")
.Caption("AI Column")
.Type(GridCommandColumnType.Ai)
.CssClass("ai__cell")
.Width(200)
.Fixed(true)
.FixedPosition(FixedPosition.Right)
.AI(ai => ai
.Prompt("Identify the country where the vehicle model is manufactured. When looking up a country, consider vehicle brand, model, and specifications.")
.Mode(AIColumnMode.Auto)
.NoDataText("No data")
);
})
.OnAIColumnRequestCreating("onAIColumnRequestCreating")
)
<script>
let chatService;
const deployment = "gpt-4o-mini";
const apiVersion = "2024-02-01";
const endpoint = "https://public-api.devexpress.com/demo-openai";
const apiKey = "DEMO";
const popupContentTemplate = function (vehicle) {
const {
Source,
LicenseName,
Author,
Edits,
} = vehicle;
const sourceLink = `https://${Source}`;
return $('<div>').append(
$('<p>')
.append($('<b>').text('Image licensed under: '))
.append($('<span>').text(LicenseName)),
$('<p>')
.append($('<b>').text('Author: '))
.append($('<span>').text(Author)),
$('<p>')
.append($('<b>').text('Source link: '))
.append(
$('<a>', {
href: sourceLink,
target: '_blank',
})
.text(sourceLink),
),
$('<p>')
.append($('<b>').text('Edits: '))
.append($('<span>').text(Edits)),
);
};
function showVehiclePopup(vehicle) {
const popup = $('#popup').dxPopup('instance');
popup.option('contentTemplate', () => popupContentTemplate(vehicle));
popup.show();
}
function onAIColumnRequestCreating(e) {
e.data = e.data.map((item) => ({
ID: item.ID,
TrademarkName: item.TrademarkName,
Name: item.Name,
Modification: item.Modification,
}));
}
async function getAIResponse(messages, signal) {
const params = {
messages,
model: deployment,
max_tokens: 1000,
temperature: 0.7,
};
const response = await chatService.chat.completions
.create(params, { signal });
const result = response.choices[0].message?.content;
return result;
}
async function getAIResponseRecursive(messages, signal) {
return getAIResponse(messages, signal)
.catch(async (error) => {
if (!error.message.includes('Connection error')) {
return Promise.reject(error);
}
DevExpress.ui.notify({
message: 'Our demo AI service reached a temporary request limit. Retrying in 30 seconds.',
width: 'auto',
type: 'error',
displayTime: 5000,
});
await new Promise((resolve) => setTimeout(resolve, 30000));
return getAIResponseRecursive(messages, signal);
});
}
const aiIntegration = new DevExpress.aiIntegration({
sendRequest({ prompt }) {
const isValidRequest = JSON.stringify(prompt.user).length < 5000;
if (!isValidRequest) {
return {
promise: Promise.reject(new Error('Request is too long. Specify a shorter prompt.')),
abort: () => {},
};
}
const controller = new AbortController();
const signal = controller.signal;
const aiPrompt = [
{ role: 'system', content: prompt.system },
{ role: 'user', content: prompt.user },
];
const promise = getAIResponseRecursive(aiPrompt, signal);
const result = {
promise,
abort: () => {
controller.abort();
},
};
return result;
},
});
$(() => {
chatService = new AzureOpenAI({
dangerouslyAllowBrowser: true,
deployment,
endpoint,
apiVersion,
apiKey,
});
});
</script>
using DevExtreme.MVC.Demos.Models;
using DevExtreme.MVC.Demos.Models.DataGrid;
using DevExtreme.MVC.Demos.Models.SampleData;
using System;
using System.Linq;
using System.Web.Mvc;
namespace DevExtreme.MVC.Demos.Controllers {
public class DataGridController : Controller {
public ActionResult AIColumns() {
return View(SampleData.Vehicles);
}
}
}
#gridContainer {
min-height: 560px;
}
#gridContainer .ai__cell {
background-color: var(--dx-datagrid-row-alternation-bg);
}
.trademark__wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.trademark__img-wrapper {
width: 40px;
height: 40px;
border: var(--dx-border-width) solid var(--dx-color-border);
border-radius: var(--dx-border-radius);
cursor: pointer;
}
.trademark__img-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
border-radius: var(--dx-border-radius);
}
.trademark__text-wrapper {
width: calc(100% - 48px);
}
.trademark__text {
margin: 0;
padding: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.trademark__text--title {
font-weight: 600;
}
.trademark__text--subtitle {
font-weight: 400;
}
.category__wrapper {
display: inline-block;
padding: 2px 8px;
border-radius: 24px;
background-color: var(--dx-color-separator);
}