Data Grids / Data Management ▸ AI Columns

DevExtreme DataGrid allows you to add multiple AI columns to the grid. These columns auto generate meaningful cell values based on component data and a custom prompt, transforming DevExtreme DataGrid into an AI-powered data analysis tool. In this demo, an AI column is fixed to the right side of the component.

[note]

AI services used for this demo have been rate and data limited. As such, you may experience performance-related delays when exploring the capabilities of DataGrid AI Columns.

When connected to your own AI model/service without rate and data limits, DataGrid AI Columns will perform seamlessly, without artificial delays. Note that DevExtreme does not offer an AI REST API and does not ship any built-in LLMs/SLMs.

[/note]

This demo instructs the AI service to identify the origin country of each car. You can modify the default prompt or enter a custom prompt in the AI column header menu.

To integrate an AI column into the DevExtreme DataGrid, you must:

  • Configure the aiIntegration property at the component or column level (aiIntegration or columns[].ai.aiIntegration).
  • Set the column type to "ai".
  • Specify the column name.
  • Configure columns[].ai options, such as generation mode, predefined prompt, and no data text (displayed when the AI service returns no data for a row).

Our DevExtreme DataGrid component uses all visible row data in AI requests, including fields not bound to a column and hidden column fields. This data gives LLMs broader context, but increases the use of AI resources. To limit data included in AI requests, modify the AIColumnRequestCreatingEvent.data parameter in the onAIColumnRequestCreating event handler.

@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);
}