This example is an ASP.NET Core application with integrated DevExpress Reports and an AI assistant. User requests and assistant responses are displayed on-screen using the DevExtreme dxChat
component.
The AI assistant's role depends on the associated DevExpress Reports component:
- Data Analysis Assistant: An assistant for the DevExpress Web Document Viewer. This assistant analyzes report content and answers questions related to information within the report.
- UI Assistant: An assistant for the DevExpress Web Report Designer. This assistant explains how to use the Designer UI to accomplish various tasks. Responses are based on information from end-user documentation for DevExpress Web Reporting components.
Please note that AI Assistant initialization takes time. The assistant tab appears once Microsoft Azure scans the source document on the server side.
Implementation Details
Common Settings
Add Personal Keys
[!NOTE]
DevExpress AI-powered extensions follow the "bring your own key" principle. DevExpress does not offer a REST API and does not ship any built-in LLMs/SLMs. You need an active Azure/Open AI subscription to obtain the REST API endpoint, key, and model deployment name. These variables must be specified at application startup to register AI clients and enable DevExpress AI-powered Extensions in your application.
You need to create an Azure OpenAI resource in the Azure portal to use AI Assistants for DevExpress Reporting. Refer to the following help topic for details: Microsoft - Create and deploy an Azure OpenAI Service resource.
Once you obtain a private endpoint and an API key, register them as AZURE_OPENAI_ENDPOINT
and AZURE_OPENAI_APIKEY
environment variables. The EnvSettings.cs reads these settings. DeploymentName
in this file is a name of your Azure model, for example, GPT4o
:
C#public static class EnvSettings {
public static string AzureOpenAIEndpoint { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); } }
public static string AzureOpenAIKey { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_APIKEY"); } }
public static string DeploymentName { get { return "GPT4o"; } }
}
Files to Review:
Register AI Services
Register AI services in your application. Add the following code to the Program.cs file:
C#using DevExpress.AIIntegration;
using Azure;
using Azure.AI.OpenAI;
using Microsoft.Extensions.AI;
using System;
// ...
var azureOpenAIClient = new AzureOpenAIClient(
new Uri(EnvSettings.AzureOpenAIEndpoint),
new AzureKeyCredential(EnvSettings.AzureOpenAIKey));
var chatClient = azureOpenAIClient.AsChatClient(EnvSettings.DeploymentName);
builder.Services.AddDevExpressAI(config =>
{
config.RegisterOpenAIAssistants(azureOpenAIClient, EnvSettings.DeploymentName);
});
[!NOTE]
The availability of Azure Open AI Assistants depends on region. For additional guidance in this regard, refer to the following document: Azure OpenAI Service models – Assistants (Preview).
Files to Review:
AI Assistant Provider
On the server side, the AIAssistantProvider
service manages assistants. An IAIAssistantFactory
instance creates assistants with keys specified in previous steps.
Codepublic interface IAIAssistantProvider {
IAIAssistant GetAssistant(string assistantName);
Task<string> CreateAssistant(AssistantType assistantType, Stream data);
Task<string> CreateAssistant(AssistantType assistantType);
void DisposeAssistant(string assistantName);
}
Files to Review:
Web Document Viewer (Data Analysis Assistant)
The following image displays Web Document Viewer UI implemented in this example. The AI Assistant tab uses a dxChat
component to display requests and responses:
Add a New Tab
On the BeforeRender
event, add a new tab (a container for the assistant interface):
Razor@model DevExpress.XtraReports.Web.WebDocumentViewer.WebDocumentViewerModel
@await Html.PartialAsync("_AILayout")
<script>
let aiTab;
function BeforeRender(sender, args) {
const previewModel = args;
const reportPreview = previewModel.reportPreview;
aiTab = createAssistantTab();
const model = aiTab.model;
previewModel.tabPanel.tabs.push(aiTab);
// ...
}
</script>
@{
var viewerRender = Html.DevExpress().WebDocumentViewer("DocumentViewer")
.Height("100%")
.ClientSideEvents(configure => {
configure.BeforeRender("BeforeRender");
})
.Bind(Model);
@viewerRender.RenderHtml()
}
@* ... *@
Access the Assistant
Once the document is ready, the DocumentReady
event handler sends a request to the server and obtains the assistant's ID:
JavaScriptasync function DocumentReady(sender, args) {
const response = await sender.PerformCustomDocumentOperation(null, true);
if (response.customData && aiTab?.model) {
aiTab.model.chatId = response.customData;
aiTab.visible = true;
}
}
The PerformCustomDocumentOperation
method exports the report to PDF and creates an assistant based on the exported document. See AIDocumentOperationService.cs for implementation details.
Communicate with the Assistant
Each time a user sends a message, the onMessageEntered
event handler passes the request to the assistant:
JavaScript//...
async function getAIResponse(text, id) {
const formData = new FormData();
formData.append('text', text);
formData.append('chatId', id);
lastUserQuery = text;
const response = await fetch(`/AI/GetAnswer`, {
method: 'POST',
body: formData
});
return await response.text();
}
// ...
function RenderAssistantMessage(instance, message) {
instance.option({ typingUsers: [] });
instance.renderMessage({ timestamp: new Date(), text: message, author: assistant.name, id: assistant.id });
}
// ...
onMessageEntered: async (e) => {
const instance = e.component;
instance.renderMessage(e.message);
instance.option({ typingUsers: [assistant] });
const userInput = e.message.text;
var response = await getAIResponse(userInput, assistant.id);
RenderAssistantMessage(instance, response);
}
// ...
AIController.GetAnswer
receives answers from the assistant.
Files to Review:
Web Report Designer (UI Assistant)
The following image displays Web Report Designer UI implemented in this example. The AI Assistant tab uses a dxChat
component to display requests and responses:
Add a New Tab
On the BeforeRender
event, add a new tab (a container for the assistant interface):
Razor@model DevExpress.XtraReports.Web.ReportDesigner.ReportDesignerModel
<script>
async function BeforeRender(sender, args) {
const tab = createAssistantTab(chatId);
args.tabPanel.tabs.push(tab);
}
</script>
@await Html.PartialAsync("_AILayout")
@{
var designerRender = Html.DevExpress().ReportDesigner("reportDesigner")
.Height("100%")
.ClientSideEvents(configure => {
configure.BeforeRender("BeforeRender");
})
.Bind(Model);
@designerRender.RenderHtml()
}
@section Scripts {
@* ... *@
<script src="~/js/aiIntegration.js"></script>
@designerRender.RenderScripts()
}
@* ... *@
Access the Assistant
On the BeforeRender
event, send a request to AIController
to create the assistant:
JavaScriptasync function BeforeRender(sender, args) {
const result = await fetch(`/AI/CreateUserAssistant`);
}
The AIAssistantProvider
service creates an assistant using the provided PDF documentation (the documentation.pdf file):
C#// ...
public async Task<string> CreateAssistant(AssistantType assistantType, Stream data) {
var assistantName = Guid.NewGuid().ToString();
var assistant = await assistantFactory.CreateAssistant(assistantName);
Assistants.TryAdd(assistantName, assistant);
var prompt = GetPrompt(assistantType);
if(assistantType == AssistantType.UserAssistant) {
await LoadDocumentation(assistant, prompt);
}
return assistantName;
}
Communicate with the Assistant
Each time a user sends a message, the onMessageEntered
event handler passes the request to the assistant:
JavaScript//...
async function getAIResponse(text, id) {
const formData = new FormData();
formData.append('text', text);
formData.append('chatId', id);
lastUserQuery = text;
const response = await fetch(`/AI/GetAnswer`, {
method: 'POST',
body: formData
});
return await response.text();
}
// ...
function RenderAssistantMessage(instance, message) {
instance.option({ typingUsers: [] });
instance.renderMessage({ timestamp: new Date(), text: message, author: assistant.name, id: assistant.id });
}
// ...
onMessageEntered: async (e) => {
const instance = e.component;
instance.renderMessage(e.message);
instance.option({ typingUsers: [assistant] });
const userInput = e.message.text;
var response = await getAIResponse(userInput, assistant.id);
RenderAssistantMessage(instance, response);
}
// ...
AIController.GetAnswer
receives answers from the specified assistant.
Files to Review:
Documentation
More Examples
- DevExtreme Chat - Getting Started
- Reporting for ASP.NET Core - Summarize and Translate DevExpress Reports Using Azure OpenAI
- Reporting for Blazor - Integrate AI-powered Summarize and Translate Features based on Azure OpenAI
- AI Chat for Blazor - How to add DxAIChat component in Blazor, MAUI, WPF, and WinForms applications
- Rich Text Editor and HTML Editor for Blazor - How to integrate AI-powered extensions
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using System;
namespace ReportingApp {
public static class EnvSettings {
public static string AzureOpenAIEndpoint { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); } }
public static string AzureOpenAIKey { get { return Environment.GetEnvironmentVariable("AZURE_OPENAI_APIKEY"); } }
public static string DeploymentName { get { return "GPT4o"; } }
}
}
C#using Azure.AI.OpenAI;
using DevExpress.AspNetCore;
using DevExpress.AspNetCore.Reporting;
using DevExpress.Security.Resources;
using DevExpress.XtraReports.Web.Extensions;
using DevExpress.XtraReports.Web.WebDocumentViewer;
using ReportingApp;
using ReportingApp.Data;
using ReportingApp.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.IO;
using Azure;
using DevExpress.AIIntegration;
using Microsoft.Extensions.AI;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDevExpressControls();
builder.Services.AddScoped<ReportStorageWebExtension, CustomReportStorageWebExtension>();
builder.Services.AddMvc();
builder.Services.ConfigureReportingServices(configurator => {
if(builder.Environment.IsDevelopment()) {
configurator.UseDevelopmentMode();
}
configurator.ConfigureReportDesigner(designerConfigurator => {
designerConfigurator.RegisterDataSourceWizardConnectionStringsProvider<CustomSqlDataSourceWizardConnectionStringsProvider>();
});
configurator.ConfigureWebDocumentViewer(viewerConfigurator => {
viewerConfigurator.UseCachedReportSourceBuilder();
viewerConfigurator.RegisterConnectionProviderFactory<CustomSqlDataConnectionProviderFactory>();
});
});
builder.Services.AddDbContext<ReportDbContext>(options => options.UseSqlite(builder.Configuration.GetConnectionString("ReportsDataConnectionString")));
builder.Services.AddScoped<DocumentOperationService, AIDocumentOperationService>();
var azureOpenAIClient = new AzureOpenAIClient(
new Uri(EnvSettings.AzureOpenAIEndpoint),
new AzureKeyCredential(EnvSettings.AzureOpenAIKey));
var chatClient = azureOpenAIClient.AsChatClient(EnvSettings.DeploymentName);
builder.Services.AddSingleton(chatClient);
builder.Services.AddSingleton<IAIAssistantProvider, AIAssistantProvider>();
builder.Services.AddDevExpressAI(config =>
{
config.RegisterOpenAIAssistants(azureOpenAIClient, EnvSettings.DeploymentName);
});
var app = builder.Build();
using(var scope = app.Services.CreateScope()) {
var services = scope.ServiceProvider;
services.GetService<ReportDbContext>().InitializeDatabase();
}
var contentDirectoryAllowRule = DirectoryAccessRule.Allow(new DirectoryInfo(Path.Combine(builder.Environment.ContentRootPath, "Content")).FullName);
AccessSettings.ReportingSpecificResources.TrySetRules(contentDirectoryAllowRule, UrlAccessRule.Allow());
DevExpress.XtraReports.Configuration.Settings.Default.UserDesignerOptions.DataBindingMode = DevExpress.XtraReports.UI.DataBindingMode.Expressions;
app.UseDevExpressControls();
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;
if(app.Environment.IsDevelopment()) {
app.UseDeveloperExceptionPage();
} else {
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
string contentPath = app.Environment.ContentRootPath;
AppDomain.CurrentDomain.SetData("DataDirectory", contentPath);
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
C#using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
using DevExpress.AIIntegration.OpenAI.Services;
using DevExpress.AIIntegration.Services.Assistant;
using Microsoft.AspNetCore.Hosting;
namespace ReportingApp.Services {
public class AIAssistantProvider : IAIAssistantProvider {
private readonly IAIAssistantFactory assistantFactory;
private readonly IWebHostEnvironment environment;
private ConcurrentDictionary<string, IAIAssistant> Assistants { get; set; } = new ();
public AIAssistantProvider(IAIAssistantFactory assistantFactory, IWebHostEnvironment environment) {
this.assistantFactory = assistantFactory;
this.environment = environment;
}
async Task LoadDocumentation(IAIAssistant assistant, string prompt) {
var dirPath = Path.Combine(environment.ContentRootPath, "Data");
var filePath = Path.Combine(dirPath, "documentation.pdf");
using(FileStream stream = File.OpenRead(filePath)) {
await assistant.InitializeAsync(new OpenAIAssistantOptions("documentation.pdf", stream, prompt));
}
}
string GetPrompt(AssistantType assistantType) {
switch(assistantType) {
case AssistantType.UserAssistant:
return "You are a user interface assistant (you help people use a software program). Your role is to read information from documentation files in PDF format. You assist users by providing accurate answers to their questions based on information from these files. \r\n\r\nTasks:\r\nExtract relevant information from PDF documentation to answer user questions.\r\nClearly explain your reasoning process and give step by step solutions to ensure users understand how you arrived at your answers.\r\nAlways provide precise and accurate information based on content from the documentation file.\r\nIf you cannot find an answer based on provided documentation, explicitly state: 'The requested information cannot be found in documentation provided.'\r\n Respond in plain text only, without markdown, sources, footnotes, or annotations.";
case AssistantType.DocumentAssistant:
return "You are a data analysis assistant. Your task is to read information from PDF files and provide users with accurate data-driven answers based on the contents of these files. \n Key Responsibilities: \n - Perform data analysis, including data summaries, calculations, filtering, and trend identification.\n - Clearly explain your analysis process to ensure users understand how you reached your conclusions.\n - Provide precise and accurate responses strictly based on data in the file.\n - If the requested information is not available in the provided file's content, state: \"The requested information cannot be found in the data provided.\"\n - Avoid giving responses when data is insufficient for a reliable answer.\n - Ask clarifying questions when a user’s query is unclear or lacks detail.\n - Your primary goal is to deliver helpful insights that directly address user questions. Do not make assumptions or infer details not supported by data. Respond in plain text only, without sources, footnotes, or annotations.\n Avoid giving information about provided file name, assistants' IDs and other internal data";
default:
return "";
}
}
public void DisposeAssistant(string assistantName) {
if(Assistants.TryRemove(assistantName, out IAIAssistant assistant)) {
assistant.Dispose();
} else {
throw new Exception("Assistant not found");
}
}
public IAIAssistant GetAssistant(string assistantName) {
if(!string.IsNullOrEmpty(assistantName) && Assistants.TryGetValue(assistantName, out var assistant)) {
return assistant;
} else {
throw new Exception("Assistant not found");
}
}
public async Task<string> CreateAssistant(AssistantType assistantType, Stream data) {
var assistantName = Guid.NewGuid().ToString();
var assistant = await assistantFactory.CreateAssistant(assistantName);
Assistants.TryAdd(assistantName, assistant);
var prompt = GetPrompt(assistantType);
if(assistantType == AssistantType.UserAssistant) {
await LoadDocumentation(assistant, prompt);
} else {
await assistant.InitializeAsync(new OpenAIAssistantOptions(Guid.NewGuid().ToString() + ".pdf", data, prompt));
}
return assistantName;
}
public Task<string> CreateAssistant(AssistantType assistantType) {
return CreateAssistant(assistantType, null);
}
}
}
C#using DevExpress.AIIntegration.Services.Assistant;
using System.IO;
using System.Threading.Tasks;
namespace ReportingApp.Services {
public enum AssistantType {
DocumentAssistant,
UserAssistant
}
public interface IAIAssistantProvider {
IAIAssistant GetAssistant(string assistantName);
Task<string> CreateAssistant(AssistantType assistantType, Stream data);
Task<string> CreateAssistant(AssistantType assistantType);
void DisposeAssistant(string assistantName);
}
}
C#using System.IO;
using System.Threading.Tasks;
using DevExpress.XtraPrinting;
using DevExpress.XtraReports.Web.WebDocumentViewer;
using DevExpress.XtraReports.Web.WebDocumentViewer.DataContracts;
namespace ReportingApp.Services {
public class AIDocumentOperationService : DocumentOperationService {
private IAIAssistantProvider AIAssistantProvider { get; set; }
public AIDocumentOperationService(IAIAssistantProvider assistantProvider) {
AIAssistantProvider = assistantProvider;
}
public override bool CanPerformOperation(DocumentOperationRequest request) {
return true;
}
public override async Task<DocumentOperationResponse> PerformOperationAsync(DocumentOperationRequest request, PrintingSystemBase printingSystem, PrintingSystemBase printingSystemWithEditingFields) {
using(var stream = new MemoryStream()) {
printingSystem.ExportToPdf(stream, printingSystem.ExportOptions.Pdf);
var assistantName = await AIAssistantProvider.CreateAssistant(AssistantType.DocumentAssistant, stream);
return new DocumentOperationResponse {
DocumentId = request.DocumentId,
CustomData = assistantName,
Succeeded = true
};
}
}
}
}
Razor@model DevExpress.XtraReports.Web.WebDocumentViewer.WebDocumentViewerModel
@await Html.PartialAsync("_AILayout")
<script>
let aiTab;
async function BeforeRender(sender, args) {
const previewModel = args;
const reportPreview = previewModel.reportPreview;
const result = await fetch(`/AI/CreateUserAssistant`);
const chatId = await result.text();
aiTab = createAssistantTab(chatId);
const model = aiTab.model;
previewModel.tabPanel.tabs.push(aiTab);
reportPreview.events.on('documentBuildingChanged', (args) => {
if (args.newValue === true) {
aiTab.visible = false;
if (model.chatId) {
const formData = new FormData();
formData.append('chatId', model.chatId);
fetch(`/AI/CloseChat`, {
method: 'POST',
body: formData
});
model.chatId = '';
}
}
});
}
async function DocumentReady(sender, args) {
const response = await sender.PerformCustomDocumentOperation(null, true);
if (response.customData && aiTab?.model) {
aiTab.model.chatId = response.customData;
aiTab.visible = true;
}
}
</script>
@{
var viewerRender = Html.DevExpress().WebDocumentViewer("DocumentViewer")
.Height("100%")
.ClientSideEvents(configure => {
configure.BeforeRender("BeforeRender");
configure.DocumentReady("DocumentReady");
})
.Bind(Model);
@viewerRender.RenderHtml()
}
@section Scripts {
<link href="~/css/dx-reporting-skeleton-screen.css" rel="stylesheet" />
<link rel="stylesheet" href="~/css/viewer.part.bundle.css" />
<link rel="stylesheet" href="~/css/dx.material.blue.light.bundle.css" />
<script src="~/js/reporting.thirdparty.bundle.js"></script>
<script src="~/js/viewer.part.bundle.js"></script>
<script src="~/js/aiIntegration.js"></script>
@viewerRender.RenderScripts()
}
C#using System.Threading.Tasks;
using ReportingApp.Services;
using Microsoft.AspNetCore.Mvc;
namespace ReportingApp.Controllers {
public class AIController : ControllerBase {
IAIAssistantProvider AIAssistantProvider { get; set; }
public AIController(IAIAssistantProvider assistantProvider) {
AIAssistantProvider = assistantProvider;
}
public async Task<string> CreateUserAssistant() {
var assistantName = await AIAssistantProvider.CreateAssistant(AssistantType.UserAssistant);
return assistantName;
}
public async Task<string> GetAnswer([FromForm] string chatId, [FromForm] string text) {
var assistant = AIAssistantProvider.GetAssistant(chatId);
return await assistant.GetAnswerAsync(text);
}
public ActionResult CloseChat([FromForm] string chatId) {
AIAssistantProvider.DisposeAssistant(chatId);
return Ok();
}
}
}
JavaScriptlet lastUserQuery;
const assistant = {
id: 'assistant',
name: 'Virtual Assistant',
};
const user = {
id: 'user',
};
function normilizeAIResponse(text) {
text = text.replace(/【\d+:\d+†[^\】]+】/g, "");
let html = marked.parse(text);
if (/<p>\.\s*<\/p>\s*$/.test(html))
html = html.replace(/<p>\.\s*<\/p>\s*$/, "")
return html;
}
function copyText(text) {
navigator.clipboard.writeText(text);
}
async function getAIResponse(text, id) {
const formData = new FormData();
formData.append('text', text);
formData.append('chatId', id);
lastUserQuery = text;
const response = await fetch(`/AI/GetAnswer`, {
method: 'POST',
body: formData
});
return await response.text();
}
function RenderAssistantMessage(instance, message) {
instance.option({ typingUsers: [] });
instance.renderMessage({ timestamp: new Date(), text: message, author: assistant.name, id: assistant.id });
}
async function refreshAnswer(instance) {
const items = instance.option('items');
const newItems = items.slice(0, -1);
instance.option({ items: newItems });
instance.option({ typingUsers: [assistant] });
const aiResponse = await getAIResponse(lastUserQuery, assistant.id);
setTimeout(() => {
instance.option({ typingUsers: [] });
RenderAssistantMessage(instance, aiResponse);
}, 200);
}
function createAssistantTab(chatId) {
assistant.id = chatId;
const model = {
title: 'AI Assistant',
showAvatar: false,
showUserName: false,
showMessageTimestamp: false,
user: user,
messageTemplate: (data, container) => {
const { message } = data;
if (message.author.id && message.author.id !== assistant.id)
return message.text;
const $textElement = $('<div>')
.html(normilizeAIResponse(message.text))
.appendTo(container);
const $buttonContainer = $('<div>')
.addClass('dx-bubble-button-containder');
$('<div>')
.dxButton({
icon: 'copy',
stylingMode: 'text',
onClick: () => {
const text = $textElement.text();
copyText(text);
}
})
.appendTo($buttonContainer);
$('<div>')
.dxButton({
icon: 'refresh',
stylingMode: 'text',
onClick: () => {
refreshAnswer(data.component);
},
})
.appendTo($buttonContainer);
$buttonContainer.appendTo(container);
},
onMessageEntered: async (e) => {
const instance = e.component;
instance.renderMessage(e.message);
instance.option({ typingUsers: [assistant] });
const userInput = e.message.text;
var response = await getAIResponse(userInput, assistant.id);
RenderAssistantMessage(instance, response);
}
};
return new DevExpress.Analytics.Utils.TabInfo({
text: 'AI Assistant',
template: 'dxrd-ai-panel',
imageTemplateName: 'dxrd-ai-icon',
imageClassName: 'aitab',
model: model
});
}
Razor@model DevExpress.XtraReports.Web.ReportDesigner.ReportDesignerModel
<script>
async function BeforeRender(sender, args) {
const result = await fetch(`/AI/CreateUserAssistant`);
const chatId = await result.text();
const tab = createAssistantTab(chatId);
args.tabPanel.tabs.push(tab);
}
</script>
@await Html.PartialAsync("_AILayout")
@{
var designerRender = Html.DevExpress().ReportDesigner("reportDesigner")
.Height("100%")
.ClientSideEvents(configure => {
configure.BeforeRender("BeforeRender");
})
.Bind(Model);
@designerRender.RenderHtml()
}
@section Scripts {
<link href="~/css/dx-reporting-skeleton-screen.css" rel="stylesheet" />
<link rel="stylesheet" href="~/css/viewer.part.bundle.css" />
<link rel="stylesheet" href="~/css/designer.part.bundle.css" />
<link rel="stylesheet" href="~/css/ace/ace.bundle.css" />
<link rel="stylesheet" href="~/css/dx.material.blue.light.bundle.css" />
<script src="~/js/reporting.thirdparty.bundle.js"></script>
<script src="~/js/viewer.part.bundle.js"></script>
<script src="~/js/designer.part.bundle.js"></script>
<script src="~/js/aiIntegration.js"></script>
@designerRender.RenderScripts()
}