Skip to content
Merged

Dev #2400

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
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
using System.Threading.Tasks;
using Unity.Modules.Shared.Permissions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;

namespace Unity.AI.Web.PromptTools;

public class AIPromptToolAccessProvider(
IAuthorizationService authorizationService,
ICurrentPrincipalAccessor currentPrincipalAccessor,
IConfiguration configuration) : IAIPromptToolAccessProvider, ITransientDependency
{
public async Task<bool> CanViewPromptToolsAsync()
{
var principal = currentPrincipalAccessor.Principal;
if (principal?.Identity?.IsAuthenticated != true)
{
return false;
}

var authorizationResult = await authorizationService.AuthorizeAsync(
principal,
IdentityConsts.ITOperationsPolicyName);

return authorizationResult.Succeeded;
}

public string DefaultPromptVersion
{
get
{
var configuredPromptVersion = configuration["Azure:Operations:Defaults:PromptVersion"];
if (string.IsNullOrWhiteSpace(configuredPromptVersion))
{
configuredPromptVersion = configuration["Azure:OpenAI:PromptVersion"];
}

return string.IsNullOrWhiteSpace(configuredPromptVersion)
? "v1"
: configuredPromptVersion.Trim().ToLowerInvariant();
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;

namespace Unity.AI.Web.PromptTools;

public interface IAIPromptToolAccessProvider
{
Task<bool> CanViewPromptToolsAsync();
string DefaultPromptVersion { get; }
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,15 @@ public override void OnApplicationInitialization(ApplicationInitializationContex

app.UseAbpRequestLocalization();

app.UseStatusCodePagesWithReExecute("/Error", "?httpStatusCode={0}");
app.UseErrorPage();
if (env.IsProduction() || env.IsStaging())
{
app.UseStatusCodePagesWithReExecute("/Error", "?httpStatusCode={0}");
app.UseErrorPage();
}
else
{
app.UseDeveloperExceptionPage();
}

if (Convert.ToBoolean(configuration["AuthServer:IsBehindTlsTerminationProxy"]))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,168 @@
@page
@page
@model Unity.GrantManager.Web.Pages.AIReporting.IndexModel
@using Unity.GrantManager.Web.Pages.AIReporting
@using Unity.Modules.Shared.Permissions
@using Volo.Abp.Features

@section styles {
<abp-style src="/libs/bootstrap/css/bootstrap.css" />
@if (Model.CanViewAiReporting)
{
<abp-style src="/Pages/AIReporting/Index.css" />
}
}
@section scripts
{
<abp-script src="/libs/jquery/jquery.js" />
<abp-script src="/libs/bootstrap-4/dist/js/bootstrap.min.js"></abp-script>
@if (await FeatureChecker.IsEnabledAsync("Unity.AIReporting") || User.IsInRole(IdentityConsts.ITAdminRoleName))

@section scripts {
@if (Model.CanViewAiReporting)
{
<script>
window.reportingAiUrl = '@Model.ReportingAiUrl';
window.reportingAiApiBaseUrl = @Json.Serialize(Model.ReportingAiApiBaseUrl);
</script>
<abp-script src="/Pages/AIReporting/Index.js" />
}
}
@inject IFeatureChecker FeatureChecker

<style>
#container {
height: calc(100vh - 6.75rem);
}
@if (Model.CanViewAiReporting)
{
<div id="ai-reporting-root" class="outer-container">

.unity-app-main-container {
padding: 0px;
}
</style>
<aside class="sidebar">
<div class="sidebar-header">
<button type="button" class="new-chat-btn" data-action="new-chat">
<span class="new-chat-icon">+</span>
New Report
</button>
<h2>Reports</h2>
</div>
<div class="sidebar-content">
<div id="chat-list" class="chat-list"></div>
</div>
<div class="sidebar-footer">
<button type="button" class="footer-btn info-btn" title="Information"
data-bs-toggle="modal" data-bs-target="#ai-reporting-info-modal">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
</svg>
</button>
</div>
</aside>

<div id="container"></div>
<div class="ai-reporting-container">

<section id="empty-state" class="empty-chat-container">
<div class="welcome-content">
<h1 class="welcome-title">What would you like to know?</h1>
<div class="ask-row-container welcome-ask-row">
<div class="ask-row">
<input type="text" id="question-input-empty" aria-label="Type a prompt to generate a report" placeholder="Type a prompt to generate a report..." />
<button type="button" class="ask-question-btn" data-action="ask" aria-label="Generate report">
<svg width="20" height="20" viewBox="0 0 48 48" fill="none" aria-hidden="true">
<path d="M24 6L24 42" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 18L24 6L36 18" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
</div>
</section>

<section id="chat-container" class="chat-container" hidden>
<div id="turns-container" class="turns"></div>

<div class="ask-row-container">
<div class="ask-row">
<input type="text" id="question-input-active" aria-label="Refine the report prompt" placeholder="Refine the report..." />
</div>
<div class="ask-row-bottom">
<div class="bottom-left-controls">
<div id="action-buttons" class="action-buttons">
<button type="button" class="action-button" id="btn-metabase" data-action="metabase" title="View in Metabase" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M18 13V19C18 20.1046 17.1046 21 16 21H5C3.89543 21 3 20.1046 3 19V8C3 6.89543 3.89543 6 5 6H11" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 3H21V9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 14L21 3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button type="button" class="action-button" id="btn-sql" data-action="toggle-sql" title="Show/Hide SQL" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M13 2L3 14H12L11 22L21 10H12L13 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button type="button" class="action-button" id="btn-explain" data-action="explain-sql" title="Generate SQL Explanation" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 9C9 7.34315 10.3431 6 12 6C13.6569 6 15 7.34315 15 9C15 10.6569 13.6569 12 12 12V14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="12" cy="17" r="0.5" fill="currentColor" stroke="currentColor"/>
</svg>
</button>
<button type="button" class="action-button delete-action" id="btn-delete-question" data-action="delete-question" title="Delete Card" disabled>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M10 11V17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 11V17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 7H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 7H12H18V18C18 19.6569 16.6569 21 15 21H9C7.34315 21 6 19.6569 6 18V7Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 5C9 3.89543 9.89543 3 11 3H13C14.1046 3 15 3.89543 15 5V7H9V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div id="nav-controls" class="nav-controls" hidden>
<button type="button" class="action-button nav-button" id="btn-prev-turn" data-action="prev-turn" title="Previous Report" aria-label="Previous report">
<svg width="16" height="16" viewBox="0 0 48 48" fill="none" aria-hidden="true">
<path d="M24 6L24 42" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 18L24 6L36 18" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<span id="turn-counter" class="turn-counter">1 / 1</span>
<button type="button" class="action-button nav-button" id="btn-next-turn" data-action="next-turn" title="Next Report" aria-label="Next report">
<svg width="16" height="16" viewBox="0 0 48 48" fill="none" aria-hidden="true" style="transform: rotate(180deg);">
<path d="M24 6L24 42" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 18L24 6L36 18" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
<button type="button" class="ask-question-btn" data-action="ask" aria-label="Generate report">
<svg width="20" height="20" viewBox="0 0 48 48" fill="none" aria-hidden="true">
<path d="M24 6L24 42" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 18L24 6L36 18" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
</div>
</section>

</div>
</div>

<div id="ai-reporting-info-modal" class="modal fade" tabindex="-1" aria-labelledby="ai-reporting-info-modal-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="ai-reporting-info-modal-title">About Unity AI Reporting</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<section class="info-section">
<h6>What is this tool?</h6>
<p>Unity AI Reporting is an intelligent report generation tool that converts your natural language questions into data visualizations and reports.</p>
</section>
<section class="info-section">
<h6>How to use:</h6>
<ul>
<li><strong>Ask questions</strong> in plain English about your data</li>
<li><strong>View reports</strong> generated automatically from your questions</li>
<li><strong>Save reports</strong> &mdash; they&rsquo;re automatically saved to your sidebar</li>
</ul>
</section>
<section class="info-section">
<h6>Tips:</h6>
<ul>
<li>Be specific about the data you want to see</li>
<li>Ask follow-up questions to refine your reports</li>
<li>Click the external link button to view reports in Metabase</li>
</ul>
</section>
</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Got it</button>
</div>
</div>
</div>
</div>
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Unity.GrantManager.Integrations;
using Unity.Modules.Shared.Permissions;
using Volo.Abp.Features;

namespace Unity.GrantManager.Web.Pages.AIReporting
{
public class IndexModel(IEndpointManagementAppService endpointManagementAppService) : PageModel
public class IndexModel(
IEndpointManagementAppService endpointManagementAppService,
IFeatureChecker featureChecker,
IAuthorizationService authorizationService) : PageModel
{
public string ReportingAiUrl { get; set; } = string.Empty;
public bool CanViewAiReporting { get; private set; }
public string ReportingAiApiBaseUrl { get; private set; } = string.Empty;

public async Task OnGetAsync()
{
ReportingAiUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.REPORTING_AI);
CanViewAiReporting = await featureChecker.IsEnabledAsync("Unity.AIReporting")
|| (await authorizationService.AuthorizeAsync(User, IdentityConsts.ITAdminPolicyName)).Succeeded;

if (!CanViewAiReporting)
{
return;
}

ReportingAiApiBaseUrl = await endpointManagementAppService.GetUgmUrlByKeyNameAsync(DynamicUrlKeyNames.REPORTING_AI);
}
}
}
Loading
Loading