Skip to content
Merged

Dev #2189

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 @@ -53,6 +53,9 @@ if ($.fn.dataTable !== undefined && $.fn.dataTable.ext) {
return csv;
},
};

$.fn.dataTable.Buttons.defaults.dom.button.className = 'btn custom-table-btn flex-none';
$.fn.dataTable.Buttons.defaults.dom.button.liner.tag = false;
}

// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ internal sealed record AIProviderResult(
string RawResponse = "",
string? Model = null,
string? FinishReason = null,
int? HttpStatusCode = null,
int? PromptTokens = null,
int? CompletionTokens = null,
int? TotalTokens = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ public async Task<ApplicationAnalysisResponse> GenerateApplicationAnalysisAsync(
applicationAnalysisContent,
systemPrompt,
ApplicationAnalysisCompletionTokens,
operationName: ApplicationAnalysisPromptType),
operationName: ApplicationAnalysisPromptType,
promptVersion: promptVersion),
AIProviderPayloadValidator.IsValidApplicationAnalysisJson,
"application analysis");
await LogPromptOutputAsync(ApplicationAnalysisPromptType, promptVersion, result.CaptureOutput);
Expand All @@ -161,7 +162,9 @@ private async Task<AIOperationResult> GenerateSummaryAsync(
string? systemPrompt,
int maxTokens = 150,
double? temperature = null,
string? operationName = null)
string? operationName = null,
string? promptVersion = null,
string? fileName = null)
{
var providerName = ResolveProviderName(operationName);
if (!string.Equals(providerName, DefaultProviderName, StringComparison.Ordinal))
Expand Down Expand Up @@ -216,13 +219,17 @@ private async Task<AIOperationResult> GenerateSummaryAsync(
var response = await _httpClient.PostAsync(ResolveApiUrl(operationName), httpContent);
var responseContent = await response.Content.ReadAsStringAsync();
var metadata = TryExtractProviderMetadata(responseContent);
var providerResponse = BuildProviderResponseFromMetadata(string.Empty, responseContent, metadata);
var providerResponse = BuildProviderResponseFromMetadata(
string.Empty,
responseContent,
metadata,
(int)response.StatusCode);

_logger.LogDebug(
"OpenAI chat completions response received. StatusCode: {StatusCode}, ResponseLength: {ResponseLength}",
response.StatusCode,
responseContent?.Length ?? 0);
LogProviderMetadata(operationName, providerResponse, response.IsSuccessStatusCode);
LogProviderMetadata(operationName, promptVersion, fileName, providerResponse, response.IsSuccessStatusCode);

if (!response.IsSuccessStatusCode)
{
Expand All @@ -245,7 +252,11 @@ private async Task<AIOperationResult> GenerateSummaryAsync(
var modelOutput = message.GetProperty("content").GetString();
return string.IsNullOrWhiteSpace(modelOutput)
? AIOperationResult.InvalidOutput(providerResponse)
: AIOperationResult.Success(BuildProviderResponseFromMetadata(modelOutput, responseContent, metadata));
: AIOperationResult.Success(BuildProviderResponseFromMetadata(
modelOutput,
responseContent,
metadata,
(int)response.StatusCode));
}

return AIOperationResult.InvalidOutput(providerResponse);
Expand Down Expand Up @@ -298,11 +309,13 @@ public async Task<AttachmentSummaryResponse> GenerateAttachmentSummaryAsync(Atta

await LogPromptInputAsync(AttachmentSummaryPromptType, promptVersion, prompt, contentToAnalyze);
var result = await GenerateWithRetryAsync(
() => GenerateSummaryAsync(
contentToAnalyze,
prompt,
AttachmentSummaryCompletionTokens,
operationName: AttachmentSummaryPromptType),
() => GenerateSummaryAsync(
contentToAnalyze,
prompt,
AttachmentSummaryCompletionTokens,
operationName: AttachmentSummaryPromptType,
promptVersion: promptVersion,
fileName: fileName),
AIProviderPayloadValidator.IsValidAttachmentSummaryText,
"attachment summary");
await LogPromptOutputAsync(AttachmentSummaryPromptType, promptVersion, result.CaptureOutput);
Expand Down Expand Up @@ -462,11 +475,12 @@ public async Task<ApplicationScoringResponse> GenerateApplicationScoringAsync(Ap

await LogPromptInputAsync(ApplicationScoringPromptType, promptVersion, systemPrompt, applicationScoringContent);
var result = await GenerateWithRetryAsync(
() => GenerateSummaryAsync(
applicationScoringContent,
systemPrompt,
ApplicationScoringCompletionTokens,
operationName: ApplicationScoringPromptType),
() => GenerateSummaryAsync(
applicationScoringContent,
systemPrompt,
ApplicationScoringCompletionTokens,
operationName: ApplicationScoringPromptType,
promptVersion: promptVersion),
content => AIProviderPayloadValidator.IsValidApplicationScoringJson(content, sectionJson),
$"application scoring section {request.SectionName}");
await LogPromptOutputAsync(ApplicationScoringPromptType, promptVersion, result.CaptureOutput);
Expand Down Expand Up @@ -564,13 +578,18 @@ private static AIOperationResult MapFailureOutcome(HttpStatusCode statusCode, AI
return AIOperationResult.PermanentFailure(response);
}

private static AIProviderResult BuildProviderResponseFromMetadata(string content, string? rawResponse, AIProviderResponseMetadata? metadata)
private static AIProviderResult BuildProviderResponseFromMetadata(
string content,
string? rawResponse,
AIProviderResponseMetadata? metadata,
int? httpStatusCode = null)
{
return new AIProviderResult(
content,
rawResponse ?? string.Empty,
metadata?.Model,
metadata?.FinishReason,
httpStatusCode,
metadata?.PromptTokens,
metadata?.CompletionTokens,
metadata?.TotalTokens,
Expand Down Expand Up @@ -629,10 +648,16 @@ private static AIProviderResult BuildProviderResponseFromMetadata(string content
}
}

private void LogProviderMetadata(string? operationName, AIProviderResult response, bool success)
private void LogProviderMetadata(
string? operationName,
string? promptVersion,
string? fileName,
AIProviderResult response,
bool success)
{
if (string.IsNullOrWhiteSpace(response.Model)
&& string.IsNullOrWhiteSpace(response.FinishReason)
&& response.HttpStatusCode == null
&& response.PromptTokens == null
&& response.CompletionTokens == null
&& response.TotalTokens == null
Expand All @@ -644,21 +669,26 @@ private void LogProviderMetadata(string? operationName, AIProviderResult respons
if (response.PromptTokens != null || response.CompletionTokens != null || response.TotalTokens != null)
{
_logger.LogInformation(
"AI token usage. FeatureName={FeatureName}, InputTokens={InputTokens}, CompletionTokens={CompletionTokens}, TotalTokens={TotalTokens}, Environment={Environment}, TenantId={TenantId}, Status={Status}",
"AI token usage. OperationName={OperationName}, InputTokens={InputTokens}, CompletionTokens={CompletionTokens}, TotalTokens={TotalTokens}, Environment={Environment}, TenantId={TenantId}, Status={Status}, PromptVersion={PromptVersion}, Model={Model}, HttpStatusCode={HttpStatusCode}, FileName={FileName}",
operationName ?? "completion",
response.PromptTokens,
response.CompletionTokens,
response.TotalTokens,
_hostEnvironment.EnvironmentName,
_currentTenant.Id,
success ? "success" : "failed");
success ? "success" : "failed",
promptVersion,
response.Model,
response.HttpStatusCode,
fileName);
}

_logger.LogDebug(
"AI provider response metadata for {OperationName}: Model={Model}, FinishReason={FinishReason}, PromptTokens={PromptTokens}, CompletionTokens={CompletionTokens}, TotalTokens={TotalTokens}, ReasoningTokens={ReasoningTokens}",
"AI provider response metadata for {OperationName}: Model={Model}, FinishReason={FinishReason}, HttpStatusCode={HttpStatusCode}, PromptTokens={PromptTokens}, CompletionTokens={CompletionTokens}, TotalTokens={TotalTokens}, ReasoningTokens={ReasoningTokens}",
operationName ?? "completion",
response.Model,
response.FinishReason,
response.HttpStatusCode,
response.PromptTokens,
response.CompletionTokens,
response.TotalTokens,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public async Task<IList<UserDto>> SearchAsync(UserSearchDto importUserSearchDto)
}
}

return users;
return users.GroupBy(u => u.UserGuid).Select(g => g.First()).ToList();
}

private async Task<IdentityUser?> CreateNewIdentityUserAsync(Guid newUserId, string? username, string? firstName, string? lastName, string? emailAddress)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@
</abp-row>
</zone-fieldset>

@* Zone Section : AdditionalContact *@
<zone id="@UnitySelector.Applicant.AdditionalContact.Default"
permission-requirement="@UnitySelector.Applicant.AdditionalContact.Default"
form-id="@Model.ApplicationFormId" show-legend="false"
editable-if="IsAdditionalContactEditable">
@await Component.InvokeAsync("ApplicationContactsWidget", new { applicationId = Model.ApplicationId })
</zone>

@* Zone Section : Address *@
<zone-fieldset id="@UnitySelector.Applicant.Location.Default" form-id="@Model.ApplicationFormId" show-legend="true"
update-permission-requirement="@UnitySelector.Applicant.Location.Update">
Expand Down Expand Up @@ -363,17 +371,6 @@
</form>
</abp-row>

@* Zone Section : AdditionalContact *@
<zone id="@UnitySelector.Applicant.AdditionalContact.Default"
permission-requirement="@UnitySelector.Applicant.AdditionalContact.Default"
form-id="@Model.ApplicationFormId" show-legend="false"
editable-if="IsAdditionalContactEditable">
<abp-row class="px-1 pb-2 mb-4 summary-container">
@await Component.InvokeAsync("ApplicationContactsWidget", new { applicationId = Model.ApplicationId })
</abp-row>
</zone>


<div class="modal fade" id="mergeDuplicateApplicantsModal" tabindex="-1" aria-labelledby="mergeApplicantsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
border-bottom: 0.25rem solid var(--bc-colors-white-background);
}

.contact-info-form, .mailing-address-form, .physical-address-form, .project-info-location, .signin-authority-form {
.contact-info-form, .mailing-address-form, .physical-address-form, .project-info-location, .signin-authority-form, .summary-container {
border-bottom: 0.25rem solid var(--bc-colors-white-background);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
bool IsAdditionalContactEditable = await PermissionChecker.IsGrantedAsync(UnitySelector.Applicant.AdditionalContact.Update);
}

<h6 class="ps-2 mb-3 fw-bold">@L["Summary:ContactsTitle"].Value</h6>
<div class="pb-2 summary-container">
<h6 class="ps-1 pb-3 pt-2 mt-3 fw-bold">@L["Summary:ContactsTitle"].Value</h6>
<input id="ApplicationContactsWidget_ApplicationId" value="@Model.ApplicationId" type="hidden"/>
@if (Model.ApplicationContacts.Count > 0) {
<p class="px-2 mb-0 fw-bold text-muted">Info</p>
Expand Down Expand Up @@ -75,3 +76,4 @@
button-type="Light" />
</div>
}
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
style: 'single'
},
info: false,
scrollX: true,
scrollX: false,
ajax: abp.libs.datatables.createAjax(
unity.notifications.emailNotifications.emailNotification.getHistoryByApplicationId, inputAction, responseCallback
),
Expand All @@ -37,26 +37,26 @@
className: 'dt-control',
orderable: false,
data: null,
width: '30px',
width: '2%',
defaultContent: ''
},
{
title: 'Subject',
data: 'subject',
className: 'data-table-header text-break',
width: '30%'
width: '38%'
},
{
title: 'Status',
data: 'status',
className: 'data-table-header',
width: '15%'
width: '13%'
},
{
title: 'Created',
data: 'creationTime',
className: 'data-table-header',
width: '180px',
width: '23%',
render: function (data) {
return data != null ? luxon.DateTime.fromISO(data, {
locale: abp.localization.currentCulture.name,
Expand All @@ -74,7 +74,7 @@
title: 'Sent By',
data: 'sentBy',
className: 'data-table-header',
width: '20%',
width: '16%',
render: function (data) {
return data ? data.name + ' ' + data.surname : '—';
},
Expand All @@ -99,7 +99,7 @@
},
{
data: 'status',
width: '60px',
width: '8%',
className: 'text-center',
render: function (data, _, full, meta) {
if (data === 'Draft' && abp.auth.isGranted('Notifications.Email.Send')) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
/* Updated for DataTables v2 - ensure proper width handling with scrollX */
#EmailHistoryTable_wrapper .dt-scroll-body {
overflow: unset !important;
}
/* Email History Table - Responsive layout without horizontal overflow */

/* Ensure table wrapper uses full width */
/* Container constraints */
#EmailHistoryTable_wrapper {
width: 100%;
box-sizing: border-box;
overflow: hidden;
}

/* Ensure scroll container properly sizes */
#EmailHistoryTable_wrapper .dt-scroll {
overflow-x: auto;
}
/* Disable scrolling and force fixed layout */
#EmailHistoryTable_wrapper .dt-scroll {
overflow: visible !important;
width: 100%;
box-sizing: border-box;
}

/* Ensure inner scroll elements take full width */
#EmailHistoryTable_wrapper .dt-scroll-head .dt-scroll-headInner,
#EmailHistoryTable_wrapper .dt-scroll-head .dt-scroll-headInner table {
width: 100% !important;
}
#EmailHistoryTable_wrapper .dt-scroll-body {
overflow: visible !important;
width: 100%;
box-sizing: border-box;
}

#EmailHistoryTable_wrapper .dt-scroll-body table {
width: 100% !important;
}
#EmailHistoryTable_wrapper .dt-scroll-head,
#EmailHistoryTable_wrapper .dt-scroll-body {
max-width: 100%;
}

/* Force fixed table layout to prevent column expansion */
#EmailHistoryTable {
width: 100% !important;
table-layout: fixed !important;
box-sizing: border-box;
}

/* Consistent cell sizing and text handling */
#EmailHistoryTable th,
#EmailHistoryTable td {
box-sizing: border-box;
word-wrap: break-word;
word-break: break-word;
overflow-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
}

/* Allow expansion for text content in Subject column only */
#EmailHistoryTable td:nth-child(2),
#EmailHistoryTable th:nth-child(2) {
white-space: normal;
}

@media (max-height: 768px) {
.dt-scroll-body {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ $(function () {
renderEnum: (data) => l('Enum:AssessmentState.' + data),
};

$.fn.dataTable.Buttons.defaults.dom.button.className = 'btn unt-btn-outline-primary btn-outline-primary';
$.fn.dataTable.Buttons.defaults.dom.button.liner.tag = false;

$.extend(DataTable.ext.buttons, {
unityWorkflow: {
className: 'btn unt-btn-outline-primary btn-outline-primary',
Expand Down
Loading