Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,32 @@
using Kendo.Mvc.UI;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OpenAI.Chat;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Telerik.AI.SmartComponents.Extensions;
using Telerik.Examples.Mvc.Models;
using Telerik.SvgIcons;

namespace Telerik.Examples.Mvc.Controllers.Grid
{
public class SmartGridController : Controller
{
private readonly AiService _smartGridService;
IChatClient _chatClient;

public SmartGridController(AiService smartGridService)
public SmartGridController(IChatClient smartGridService)
{
_smartGridService = smartGridService;
_chatClient = smartGridService;
}

public IActionResult SmartGrid()
Expand All @@ -32,10 +42,37 @@ public IActionResult GetSales([DataSourceRequest] DataSourceRequest request)
}

[HttpPost]
public async Task<IActionResult> Analyze([FromBody] GridAnalysisRequest request)
public async Task<IActionResult> Analyze([FromBody] GridAIRequest request)
{
var result = await _smartGridService.AnalyzeGridDataAsync(request.Instructions, request.GridJson);
return Json(new { result });
var messages = request.Contents.Select(dto => dto).ToList();

var options = new ChatOptions();
options.AddGridChatTools(request.Columns);

List<Microsoft.Extensions.AI.ChatMessage> conversationMessages = request.Contents
.Select(m => new Microsoft.Extensions.AI.ChatMessage(ChatRole.User, m.Text))
.ToList();

if (_chatClient == null)
{
return StatusCode(500, "Chat service is not available.");
}

ChatResponse completion = await _chatClient.GetResponseAsync(conversationMessages, options);

GridAIResponse response = completion.ExtractGridResponse();

return new ContentResult
{
Content = System.Text.Json.JsonSerializer.Serialize(
response,
new System.Text.Json.JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}),
ContentType = "application/json"
};

}

private List<SaleRecord> GetFullSalesData()
Expand Down
16 changes: 16 additions & 0 deletions Telerik.Examples.Mvc/Telerik.Examples.Mvc/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using AutoMapper;
using AutoMapper.Internal;
using Azure;
using Azure.AI.OpenAI;
using Kendo.Mvc.Extensions;
using Kendo.Mvc.UI;
using Microsoft.AspNetCore.Builder;
Expand All @@ -13,6 +15,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.AI;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
using Newtonsoft.Json.Serialization;
Expand All @@ -23,6 +26,7 @@
using System.Threading;
using System.Threading.Tasks;
using Telerik.Examples.Mvc.Controllers;
using Telerik.AI.SmartComponents.Extensions;
using Telerik.Examples.Mvc.Database;
using Telerik.Examples.Mvc.Hubs;
using Telerik.Examples.Mvc.Models;
Expand Down Expand Up @@ -57,6 +61,18 @@
builder.Services.AddTransient<CarsService>();
builder.Services.AddTransient<AiService>();

// Register the Azure OpenAI client.
builder.Services.AddSingleton(new AzureOpenAIClient(
new Uri(builder.Configuration["OpenAI:Endpoint"]),
new AzureKeyCredential(builder.Configuration["OpenAI:ApiKey"])
));

// Register the Chat client with the specified model.
builder.Services.AddChatClient(services =>
services.GetRequiredService<AzureOpenAIClient>()
.GetChatClient("gpt-4.1-nano").AsIChatClient()
);

builder.Services.AddMvc()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ContractResolver = new DefaultContractResolver());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<ItemGroup>
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.8.0-beta.1" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="9.0.2" />
Expand All @@ -18,11 +19,14 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.AI" Version="10.1.1" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="10.1.1-preview.1.25612.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.2" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageReference Include="System.Drawing.Common" Version="9.0.2" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="Telerik.AI.SmartComponents.Extensions" Version="2.0.0" />
<PackageReference Include="Telerik.Core.Export" Version="2025.3.812" />
<PackageReference Include="Telerik.UI.for.AspNet.Core" Version="2025.4.1111" />
<PackageReference Include="Telerik.Web.Captcha" Version="2.0.3" />
Expand Down
151 changes: 22 additions & 129 deletions Telerik.Examples.Mvc/Telerik.Examples.Mvc/Views/Grid/SmartGrid.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

@(Html.Kendo().Grid<SaleRecord>()
.Name("salesGrid")
.ToolBar(t =>
{
t.AIAssistant();
t.Spacer();
t.Custom().Name("resetChanges")
.Text("Reset changes")
.IconClass("k-icon k-i-arrow-rotate-ccw");
})
.Columns(columns =>
{
columns.Bound(c => c.Id).Width(80);
Expand All @@ -17,137 +25,22 @@
})
.Scrollable(s => s.Height("300px"))
.DataSource(ds => ds.Ajax()
.Model(m => m.Id(x => x.Id))
.Read(read => read.Url("/SmartGrid/GetSales"))
)
.AI(ai => ai
.Service("/SmartGrid/Analyze")
.AIAssistant(aiAsst => aiAsst
.PromptSuggestions(new[]
{
"Sort the grid by Total descending.",
"Filter to only show data from July.",
"Show only rows where Units Sold is greater than 130.",
})
.PromptTextArea(p => p.Rows(2).Resize(TextAreaResize.Auto).MaxRows(5))
)
.AIAssistantWindow(ws => ws.Width(558).Actions(a => a.Minimize().Close()))
)
.Sortable()
.Filterable()
)

<div class="k-mt-4" style="color: #ffffff;">
<label for="instructionBox" class="k-label">Ask AI about the data:</label>

<ul style="margin-top: 5px; list-style: disc; padding-left: 20px; font-size: 14px; color: #00ffe5;">
<li class="ai-suggestion">Which salesperson has the highest total sales?</li>
<li class="ai-suggestion">What is the average units sold per region?</li>
<li class="ai-suggestion">Show the month with the lowest performance.</li>
<li class="ai-suggestion">Sort the grid by Total descending</li>
<li class="ai-suggestion">Filter to only show data from July</li>
<li class="ai-suggestion">Show only rows where Units Sold is greater than 130</li>
<li class="ai-suggestion">What is the total revenue by month?</li>
<li class="ai-suggestion">How many units did Alice sell in total?</li>
</ul>


@(Html.Kendo().TextArea()
.Name("instructionBox")
.Placeholder("e.g. Which region had the lowest total?")
.Rows(4)
.HtmlAttributes(new { style = "width:100%; background-color:#1f1f1f; color:white;", @class = "k-textbox" })
)

<div class="k-mt-2">
@(Html.Kendo().Button()
.Name("analyzeButton")
.Content("Analyze")
.ThemeColor(ThemeColor.Success)
.HtmlAttributes(new { onclick = "analyzeGrid()" })
)
</div>
</div>

<div id="aiResponse" class="k-mt-4">
<strong>AI Response:</strong>
<div id="aiText" style="margin-top:10px;"></div>
</div>

@section scripts {
<script>
$(document).on("click", ".ai-suggestion", function () {
const text = $(this).text().trim();
$("#instructionBox").data("kendoTextArea").value(text);
});


async function analyzeGrid() {
const grid = $("#salesGrid").data("kendoGrid");
const allData = grid.dataSource.view().map(item => item.toJSON());
const instructions = $("#instructionBox").val();

if (!instructions.trim()) {
alert("Please enter a question or instruction for the AI.");
return;
}

$("#aiText").html("<em>Thinking...</em>");

const response = await fetch('/SmartGrid/Analyze', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
instructions: instructions,
gridJson: JSON.stringify(allData)
})
});

const result = await response.json();

try {
const json = JSON.parse(result.result);

if (json.action === "filter") {
grid.dataSource.filter({
field: json.field,
operator: json.operator,
value: json.value
});
$("#aiText").html(`<strong>Applied filter:</strong> ${json.field} ${json.operator} ${json.value}`);
} else if (json.action === "sort") {
grid.dataSource.sort({
field: json.field,
dir: json.dir
});
$("#aiText").html(`<strong>Sorted by:</strong> ${json.field} (${json.dir})`);
} else {
$("#aiText").text(result.result);
}
} catch {
$("#aiText").text(result.result);
}
}
</script>
}

<style>
textarea.k-textbox,
.k-input {
background-color: #1f1f1f;
color: #ffffff;
border-color: #444;
}

textarea.k-textbox::placeholder {
color: #888888;
}

#aiResponse {
background-color: #1f1f1f;
border: 1px solid #444;
padding: 1em;
border-radius: 6px;
margin-top: 1em;
color: #00ffe5;
font-size: 14px;
}

#aiResponse strong {
display: block;
margin-bottom: 6px;
color: #ffffff;
font-weight: 600;
font-size: 15px;
}

#aiText {
white-space: pre-line;
}
</style>