Skip to content

merge from neozhu#14

Merged
hnjm merged 5 commits into
hnjm:mainfrom
neozhu:main
Jan 17, 2026
Merged

merge from neozhu#14
hnjm merged 5 commits into
hnjm:mainfrom
neozhu:main

Conversation

@hnjm
Copy link
Copy Markdown
Owner

@hnjm hnjm commented Jan 17, 2026

merge from neozhu

The ActiveUserSession component was removed from the <DialogContent> section of ProductFormDialog.razor, so it is no longer rendered within the product form dialog. This streamlines the dialog content and removes session tracking from this component.
Completely overhaul the ReconnectModal component:
- Replace legacy HTML, MudBlazor buttons, and animations with a glassmorphic modal featuring a status icon, pulse effect, and progress bar.
- Remove all manual retry/resume buttons; reconnection is now fully automatic.
- Inline and rewrite JS logic to probe server health and only reload when the server is truly ready (HTTP 200), improving reliability during restarts.
- Use MutationObserver to trigger modal and probing based on Blazor connection state.
- Rewrite CSS for a modern, responsive, and dark mode–friendly design.
- Delete obsolete ReconnectModal.razor.js file.
- Result: a more robust, user-friendly, and visually appealing reconnection experience.
Simplified the DeleteImage method by replacing manual dialog
creation and result handling with DialogServiceHelper
.ShowConfirmationDialogAsync. This centralizes confirmation
logic and improves code readability and maintainability.
Updated Theme.cs to use modern palette and typography, aligning with shadcn/ui and Tailwind standards. Refined layout properties and removed shadows. Revised app.css for consistent font sizing, improved chip/input/table styles, and removed error boundary styling for a cleaner look. Enhances accessibility, consistency, and overall visual professionalism.
Introduced a new /ai/chatbot page featuring a chat UI for interacting with an AI assistant powered by OpenAI. Integrated the OpenAI .NET SDK and added configuration for API keys and model selection in appsettings.json. Updated the navigation menu to include the Chatbot, and added the OpenAI NuGet package dependency. The chat interface supports avatars, message bubbles, copy-to-clipboard, auto-scroll, and error handling.
Copilot AI review requested due to automatic review settings January 17, 2026 01:53
@hnjm hnjm merged commit 28c5c67 into hnjm:main Jan 17, 2026
6 of 7 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request merges changes from neozhu that include dependency updates, a new AI chatbot feature, significant UI/theme redesign, and component refactoring.

Changes:

  • Updates NuGet packages across test projects and application layers to latest versions (EF Core 10.0.1→10.0.2, authentication libraries, etc.)
  • Adds new AI chatbot feature with OpenAI integration including UI page, menu integration, and configuration settings
  • Redesigns application theme with modern color palette inspired by shadcn/ui, updated typography using Inter font family, and refined spacing/layout properties
  • Refactors ReconnectModal component with improved reconnection logic and modern glassmorphic UI design
  • Simplifies dialog usage in ProductFormDialog using DialogServiceHelper

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
tests/Domain.UnitTests/Domain.UnitTests.csproj Updates NUnit3TestAdapter from 6.0.0 to 6.1.0
tests/Application.UnitTests/Application.UnitTests.csproj Updates NUnit3TestAdapter from 6.0.0 to 6.1.0
tests/Application.IntegrationTests/Application.IntegrationTests.csproj Updates NUnit3TestAdapter from 6.0.0 to 6.1.0
src/Server.UI/wwwroot/css/app.css Refines CSS with updated chip sizes, font sizes, and spacing; removes blazor-error-boundary styles
src/Server.UI/appsettings.json Adds AISettings configuration section with API keys for Gemini and OpenAI
src/Server.UI/Themes/Theme.cs Complete theme redesign with modern color palette, updated typography using Inter font, and removal of custom shadows
src/Server.UI/Services/Navigation/MenuService.cs Adds new Chatbot menu item linking to /ai/chatbot
src/Server.UI/Server.UI.csproj Adds OpenAI package; updates SignalR, HotKeys, and EF Core Tools packages
src/Server.UI/Pages/Products/Components/ProductFormDialog.razor Refactors dialog confirmation to use DialogServiceHelper; removes ActiveUserSession component
src/Server.UI/Pages/AI/Chatbot.razor New chatbot page with OpenAI integration, message history, and modern chat UI
src/Server.UI/Components/ReconnectModal.razor.js Removes separate JavaScript file (logic now inline in .razor)
src/Server.UI/Components/ReconnectModal.razor Complete redesign with inline JavaScript, improved reconnection detection, and modern glassmorphic UI
src/Infrastructure/Infrastructure.csproj Updates authentication packages and QuestPDF to latest versions
src/Domain/Domain.csproj Updates EF Core packages from 10.0.1 to 10.0.2; updates EFCore.NamingConventions from RC to stable
src/Application/Application.csproj Updates ASP.NET Core packages, Riok.Mapperly, and FusionCache to latest versions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +28 to +121
<script>
(function() {
if (window.reconnectModalInitialized) return;
window.reconnectModalInitialized = true;

#components-reconnect-modal {
background-color: var(--mud-palette-surface);
color: var(--mud-palette-text-primary);
width: 20rem;
margin: 10vh auto;
padding: 2rem;
border: 0;
border-radius: var(--mud-default-borderradius, 4px);
box-shadow: var(--mud-elevation-8);
opacity: 0;
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
}
let serverReallyDown = false;
let checkInterval = null;

#components-reconnect-modal[open] {
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
animation-fill-mode: both;
}
async function checkServerStatus() {
try {
// Add timestamp to prevent browser caching
const probeUrl = `/_framework/blazor.web.js?t=${new Date().getTime()}`;
console.log(`[Reconnect] Probing: ${probeUrl}`);

#components-reconnect-modal::backdrop {
background-color: var(--mud-palette-backdrop-background);
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
opacity: 1;
}
const response = await fetch(probeUrl, {
method: 'HEAD',
cache: 'no-store'
});

@@keyframes components-reconnect-modal-slideUp {
0% {
transform: translateY(30px) scale(0.95);
console.log('[Reconnect] Response status:', response.status);

// === Core fix starts ===
// Only reload when status code is 200 (OK).
// Nginx usually returns 502/503/504 during container restart, fetch won't throw an exception,
// so must manually check response.ok or response.status
if (response.ok) {
console.log('[Reconnect] Server is ready (200 OK). Reloading...');
window.location.reload();
} else {
// If it's 500, 502, 503, 504, 404, etc., it means the service hasn't fully recovered
// Throw an error to enter the catch block, keep the modal and wait for the next probe
throw new Error(`Server returned status ${response.status} (Not Ready)`);
}
// === Core fix ends ===

} catch (error) {
// Only when reaching here, it's truly "network unreachable" or "service not ready"
console.warn('[Reconnect] Probe failed or server not ready:', error);

if (!serverReallyDown) {
serverReallyDown = true;
// Update the text to tell the user the server is restarting
updateStatus("Server Restarting", "Waiting for the application to start...");

// Lock the modal
const modal = document.getElementById('components-reconnect-modal');
if (modal && !modal.open) modal.showModal();
}
}
}

100% {
transform: translateY(0);
function updateStatus(title, desc) {
const titleEl = document.getElementById('reconnect-title');
const descEl = document.getElementById('reconnect-desc');
if (titleEl) titleEl.innerText = title;
if (descEl) descEl.innerHTML = desc;
}
}

@@keyframes components-reconnect-modal-fadeInOpacity {
0% {
opacity: 0;
function init() {
const modal = document.getElementById('components-reconnect-modal');
if (!modal) { setTimeout(init, 50); return; }

const observer = new MutationObserver((mutations) => {
const activeClasses = ['components-reconnect-show', 'components-reconnect-failed', 'components-reconnect-rejected'];
const hasClass = activeClasses.some(c => modal.classList.contains(c));

if (hasClass) {
// Blazor reports disconnection
if (!modal.open) {
modal.showModal();
// Suggest slightly extending the probe interval to avoid excessive pressure on the server during restart
if (!checkInterval) checkInterval = setInterval(checkServerStatus, 2000);
checkServerStatus(); // Probe once immediately
}
} else {
// Blazor reports recovery
if (serverReallyDown) {
// If previously confirmed server was down, absolutely do not allow closing, wait for checkServerStatus to trigger reload
console.log('[Reconnect] Ignored fake reconnection signal. Waiting for reload.');
if (!modal.open) modal.showModal();
} else {
// Minor network fluctuation, allow closing
if (modal.open) modal.close();
if (checkInterval) { clearInterval(checkInterval); checkInterval = null; }
}
}
});

observer.observe(modal, { attributes: true, attributeFilter: ['class'] });
}

100% {
opacity: 1;
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chinese comments in JavaScript code. Multiple comments throughout the script are in Chinese (e.g., "添加时间戳以防止浏览器缓存", "等待应用程序启动", etc.). These should be translated to English for consistency with the rest of the codebase.

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +145
await DialogServiceHelper.ShowConfirmationDialogAsync(
$"{L["Erase imatge"]}",
$"{L["Are you sure you want to erase this image?"]}",
async () =>
{
Model.Pictures.Remove(picture);
});
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation. The code uses tabs for indentation starting at line 139, while the rest of the file appears to use spaces. This creates inconsistent formatting that can cause issues with code readability and version control diffs.

Suggested change
await DialogServiceHelper.ShowConfirmationDialogAsync(
$"{L["Erase imatge"]}",
$"{L["Are you sure you want to erase this image?"]}",
async () =>
{
Model.Pictures.Remove(picture);
});
await DialogServiceHelper.ShowConfirmationDialogAsync(
$"{L["Erase imatge"]}",
$"{L["Are you sure you want to erase this image?"]}",
async () =>
{
Model.Pictures.Remove(picture);
});

Copilot uses AI. Check for mistakes.
LineHeight = "1.75rem",
LetterSpacing = ".03333em",
TextTransform = "none"
}
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Shadows property has been completely removed from the theme configuration. While MudBlazor themes have default shadow values that will be used when this property is not set, this represents a significant visual change from the previous custom shadow configuration. Ensure this change is intentional and that the default MudBlazor shadows provide the desired visual appearance.

Suggested change
}
}
},
Shadows = new Shadow()
{

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +13
"GeminiApiKey": "your-gemini-api-key",
"OpenAIApiKey": "your-openai-api-key",
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The configuration uses placeholder API keys that should not be committed to version control. Even though these are placeholder values, it's a security best practice to use environment variables or user secrets for API keys rather than checking them into source control. Consider using user secrets for development or documenting that these values need to be overridden via environment variables in production.

Suggested change
"GeminiApiKey": "your-gemini-api-key",
"OpenAIApiKey": "your-openai-api-key",
// Configure these via environment variables or user secrets:
// AISettings__GeminiApiKey, AISettings__OpenAIApiKey
"GeminiApiKey": "",
"OpenAIApiKey": "",

Copilot uses AI. Check for mistakes.
Comment on lines +293 to +295
var modelId = Configuration["AISettings:OpenAIModel"] ?? "gpt-5-nano";
var client = new OpenAIClient(apiKey);
_chatClient = client.GetChatClient(modelId);
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling only validates if the API key is the placeholder value, but doesn't validate the model name. If an invalid model name is configured, the error will only be caught in the generic catch block with a less helpful error message. Consider adding validation for the model name as well.

Suggested change
var modelId = Configuration["AISettings:OpenAIModel"] ?? "gpt-5-nano";
var client = new OpenAIClient(apiKey);
_chatClient = client.GetChatClient(modelId);
// Validate and resolve model identifier
var configuredModelId = Configuration["AISettings:OpenAIModel"];
string modelId;
if (string.IsNullOrWhiteSpace(configuredModelId) || configuredModelId == "your-openai-model")
{
// Fall back to a known default model when configuration is missing or placeholder
modelId = "gpt-5-nano";
}
else
{
modelId = configuredModelId;
}
var client = new OpenAIClient(apiKey);
try
{
_chatClient = client.GetChatClient(modelId);
}
catch (Exception ex)
{
throw new InvalidOperationException($"OpenAI model configuration is invalid. Model '{modelId}' could not be initialized.", ex);
}

Copilot uses AI. Check for mistakes.
</style>

@code {
// 定义本地数据模型
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chinese comment in code. The comment "定义本地数据模型" (Define local data model) should be translated to English for consistency with the rest of the codebase, which uses English comments throughout.

Suggested change
// 定义本地数据模型
// Define local data model

Copilot uses AI. Check for mistakes.
var client = new OpenAIClient(apiKey);
_chatClient = client.GetChatClient(modelId);

// 添加系统提示
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chinese comment in code. The comment "添加系统提示" (Add system prompt) should be translated to English for consistency with the codebase standards.

Suggested change
// 添加系统提示
// Add system prompt

Copilot uses AI. Check for mistakes.
Comment on lines +327 to +336
// 添加用户消息到对话历史
_conversationHistory.Add(new UserChatMessage(userMessageContent));

// 调用 OpenAI API
var response = await _chatClient!.CompleteChatAsync(_conversationHistory);
var assistantResponseText = response.Value.Content[0].Text;

if (!string.IsNullOrEmpty(assistantResponseText))
{
// 添加助手回复到对话历史
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chinese comment in code. The comment "添加用户消息到对话历史" (Add user message to conversation history) should be translated to English for consistency.

Suggested change
// 添加用户消息到对话历史
_conversationHistory.Add(new UserChatMessage(userMessageContent));
// 调用 OpenAI API
var response = await _chatClient!.CompleteChatAsync(_conversationHistory);
var assistantResponseText = response.Value.Content[0].Text;
if (!string.IsNullOrEmpty(assistantResponseText))
{
// 添加助手回复到对话历史
// Add user message to conversation history
_conversationHistory.Add(new UserChatMessage(userMessageContent));
// Call OpenAI API
var response = await _chatClient!.CompleteChatAsync(_conversationHistory);
var assistantResponseText = response.Value.Content[0].Text;
if (!string.IsNullOrEmpty(assistantResponseText))
{
// Add assistant reply to conversation history

Copilot uses AI. Check for mistakes.
public string Content { get; set; } = string.Empty;
}

private List<ChatMessage> messages = new();
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field 'messages' can be 'readonly'.

Suggested change
private List<ChatMessage> messages = new();
private readonly List<ChatMessage> messages = new();

Copilot uses AI. Check for mistakes.
private bool isLoading = false;
private ElementReference messagesEndRef;
private ChatClient? _chatClient;
private List<OpenAI.Chat.ChatMessage> _conversationHistory = new();
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field '_conversationHistory' can be 'readonly'.

Suggested change
private List<OpenAI.Chat.ChatMessage> _conversationHistory = new();
private readonly List<OpenAI.Chat.ChatMessage> _conversationHistory = new();

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants