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 @@ -23,9 +23,10 @@ public partial class AppClientCoordinator : AppComponentBase
{
//#if (signalR == true)
[AutoInject] private Notification notification = default!;
[AutoInject] private HubConnection hubConnection = default!;
[AutoInject] private ThemeService themeService = default!;
[AutoInject] private HubConnection hubConnection = default!;
[AutoInject] private CultureService cultureService = default!;
[AutoInject] private SignInModalService signInModalService = default!;
//#endif
//#if (appInsights == true)
[AutoInject] private IApplicationInsights appInsights = default!;
Expand Down Expand Up @@ -293,6 +294,13 @@ await InvokeAsync(async () =>
return DiagnosticLogger.Store.LastOrDefault(l => l.Level is LogLevel.Error or LogLevel.Critical);
}));

hubConnection.Remove(SharedAppMessages.SHOW_SIGN_IN_MODAL);
signalROnDisposables.Add(hubConnection.On(SharedAppMessages.SHOW_SIGN_IN_MODAL, async () =>
{
await signInModalService.SignIn();
return await StorageService.GetItem("access_token");
}));

hubConnection.Closed += HubConnectionStateChange;
hubConnection.Reconnected += HubConnectionConnected;
hubConnection.Reconnecting += HubConnectionStateChange;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//+:cnd:noEmit
using System.Threading.Channels;
using Boilerplate.Shared.Dtos.Chatbot;
using Boilerplate.Shared.Dtos.Identity;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.SignalR.Client;

Expand All @@ -12,6 +13,8 @@ public partial class AppAiChatPanel

[CascadingParameter] public AppThemeType? CurrentTheme { get; set; }

[CascadingParameter] public UserDto? CurrentUser { get; set; }


[AutoInject] private HubConnection hubConnection = default!;

Expand Down Expand Up @@ -137,7 +140,7 @@ private void SetDefaultValues()
new()
{
Role = AiChatMessageRole.Assistant,
Content = Localizer[nameof(AppStrings.AiChatPanelInitialResponse)],
Content = Localizer[nameof(AppStrings.AiChatPanelInitialResponse), string.IsNullOrEmpty(CurrentUser?.DisplayName) ? string.Empty : $" {CurrentUser.DisplayName}"],
}
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,21 +72,25 @@ private static string GetInitialSystemPromptMarkdown()
* **Description:** Manage personal user information like name, profile picture, birthdate, and gender.
* **How to Use:**
- Navigate to the [Profile page](/settings/profile).
- Requires sign-in.

### 2.2. Account Settings
* **Description:** Manage account-specific details like email, phone number, enable passwordless sign-in, and account deletion.
* **How to Use:**
- Navigate to the [Account page](/settings/account).
- Requires sign-in.

### 2.3. Two-Factor Authentication (2FA)
* **Description:** Enhance account security by requiring a second form of verification (typically a code from an authenticator app) during sign-in.
* **How to Use:**
- Navigate to the [Two Factor Authentication page](/settings/tfa).
- Requires sign-in.

### 2.4. Session Management
* **Description:** View all devices and browsers where the user is currently signed in and provides the ability to sign out (revoke) specific sessions remotely.
* **How to Use:**
- Navigate to the [Sessions page](/settings/sessions).
- Requires sign-in.

## 3. Core Application Features

Expand All @@ -97,21 +101,25 @@ These are the primary functional areas of the application beyond account managem
* **Description:** Provides a high-level overview and analytics of key application data, such as categories and products.
* **How to Use:**
- Navigate to the [Dashboard page](/dashboard).
- Requires sign-in.

### 3.2. Categories Management
* **Description:** Allows users to view, create, edit, and delete categories, often used to organize products.
* **How to Use:**
- Navigate to the [Categories page](/categories).
- Requires sign-in.

### 3.3. Products Management
* **Description:** Allows users to view, create, edit, and delete products.
* **How to Use:**
- Navigate to the [Products page](/products).
- Requires sign-in.

### 3.4. Add/Edit Product
* **Description:** A form page for creating a new product or modifying an existing one.
* **How to Use:**
- Navigate to the [Add/Edit Products page](/add-edit-product).
- Requires sign-in.
" +
//#endif
//#if (module == 'Sales')
Expand All @@ -126,19 +134,22 @@ These are the primary functional areas of the application beyond account managem
* **Description:** A simple task management feature to keep track of personal tasks.
* **How to Use:**
- Navigate to the [Offline Todo page](/offline-todo).
- Requires sign-in.
" +
//#elseif (sample == true)
@"### 3.6. Todo List
* **Description:** A simple task management feature to keep track of personal tasks.
* **How to Use:**
- Navigate to the [Todo page](/todo).
- Requires sign-in.
" +
//#endif
//#if (ads == true)
@"### 3.7. Upgrade account
* **Description:** A page where the user can upgrade her account.
* **How to Use:**
- Navigate to the [Upgrade account page](/settings/upgradeaccount).
- Requires sign-in.
" +
//#endif
@"## 4. Informational Pages
Expand All @@ -159,6 +170,11 @@ These are the primary functional areas of the application beyond account managem

**[[[INSTRUCTIONS_BEGIN]]]**

- ### Authentication Tool:
- Accessing sign-in required pages needs {{IsAuthenticated}} to be `true`.
- You can use the `ShowSignInModal` tool if needed to prompt the user to authenticate. This tool will display the sign-in modal and return user information if successful, or null if cancelled/failed.
- You **MUST** greet the user after signing in.

- ### Language:
- Respond in the language of the user's query. If the query's language cannot be determined, use the {{UserCulture}} variable if provided.

Expand Down Expand Up @@ -187,8 +203,7 @@ These are the primary functional areas of the application beyond account managem
After retrieving the error information:
1. Acknowledge the issue with empathy (e.g., ""I see you're having trouble with..."", ""I understand that's frustrating"")
2. Offer practical, easy-to-follow steps to resolve the issue
3. If the error indicates a bug or system issue, acknowledge it and suggest providing their email for follow-up
4. Only provide technical details if the user specifically asks for more information
3. Only provide technical details if the user specifically asks for more information

**Important:** Do NOT use the `CheckLastError` tool for general questions about features or ""how to"" queries. Only use it when troubleshooting actual reported problems or errors.

Expand Down Expand Up @@ -239,16 +254,18 @@ 1. Acknowledge the issue with empathy (e.g., ""I see you're having trouble with.
**[[[ADS_TROUBLE_RULES_END]]]**

" +
//#endif
//#endif
@"- ### User Feedback and Suggestions:
- If a user provides feedback or suggests a feature, respond: ""Thank you for your feedback! It's valuable to us, and I'll pass it on to the product team."" If the feedback is unclear, ask for clarification: ""Could you please provide more details about your suggestion?""

- ### Handling Frustration or Confusion:
- If a user seems frustrated or confused, use calming language and offer to clarify: ""I'm sorry if this is confusing. I'm here to help! Would you like me to explain it again?""

- ### Unresolved Issues:
- If you cannot resolve the user's issue (either through the markdown info or the tool), respond with: ""I'm sorry I couldn't resolve your issue / fully satisfy your request. I understand how frustrating this must be for you. Please provide your email address so a human operator can follow up with you soon.""
- After receiving the email, confirm: ""Thank you for providing your email. A human operator will follow up with you soon."" Then ask: ""Do you have any other issues you'd like me to assist with?""
- If you cannot resolve the user's issue (either through the markdown info or the tool), respond with: ""I'm sorry I couldn't resolve your issue / fully satisfy your request. I understand how frustrating this must be for you.""
- If the user's email ({{UserEmail}} variable) is null, request their email.
- Invoke the `SaveUserEmailAndConversationHistory` tool.
- Confirm: ""Thank you for providing your email. A human operator will follow up with you soon."" Then ask: ""Do you have any other issues you'd like me to assist with?""

**[[[INSTRUCTIONS_END]]]**
";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//+:cnd:noEmit
using System.Text;
using System.ComponentModel;
using System.Text;
using System.Threading.Channels;
using Boilerplate.Server.Api.Services;
using Boilerplate.Shared.Dtos.Chatbot;
using Boilerplate.Shared.Dtos.Diagnostic;
using Boilerplate.Server.Api.Services;
using Boilerplate.Shared.Dtos.Identity;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Authentication.BearerToken;
using ModelContextProtocol.Server;

namespace Boilerplate.Server.Api.SignalR;
Expand All @@ -21,13 +23,14 @@ public partial class AppChatbot
{
private IChatClient? chatClient = default!;

[AutoInject] private AppDbContext dbContext = default!;
[AutoInject] private IFusionCache cache = default!;
[AutoInject] private IConfiguration configuration = default!;
[AutoInject] private AppDbContext dbContext = default!;
[AutoInject] private ILogger<AppChatbot> logger = default!;
[AutoInject] private IConfiguration configuration = default!;
[AutoInject] private IServiceProvider serviceProvider = default!;
[AutoInject] private IOptionsMonitor<BearerTokenOptions> bearerTokenOptions = default!;

private string? variablesPrompt;
private string? variablesDefault;
private string? supportSystemPrompt;
private List<ChatMessage> chatMessages = [];

Expand Down Expand Up @@ -66,8 +69,9 @@ public async Task StartChat(
},
token: cancellationToken);

variablesPrompt = @$"
### Variables:
// The following variables won't change unless SignalR connection restarts and StartChat gets called again, so setting variables once here is sufficient.
// For example, the user's culture won't change unless they restart the app.
variablesDefault = @$"
{{{{UserCulture}}}}: ""{culture?.NativeName ?? "English"}""
{{{{DeviceInfo}}}}: ""{request.DeviceInfo ?? "Generic Device"}""
{{{{SignalRConnectionId}}}}: ""{signalRConnectionId ?? "Unknown"}""
Expand All @@ -92,6 +96,7 @@ public async Task ProcessNewMessage(
bool generateFollowUpSuggestions,
string incomingMessage,
Uri? serverApiAddress,
ClaimsPrincipal? user,
CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(supportSystemPrompt))
Expand All @@ -106,6 +111,16 @@ public async Task ProcessNewMessage(

var chatOptions = CreateChatOptions(serverApiAddress, cancellationToken);

// The following variables might change without SignalR connection restarts, so these should set here every time a new message is about to be processed.
// For example, user can sign-in/sign-out during chat without restarting the app or SignalR connection.
var variablesPrompt = @$"
### Variables:
{variablesDefault}
{{{{IsAuthenticated}}}}: ""{user.IsAuthenticated()}""}}
{{{{UserId}}}}: ""{(user.IsAuthenticated() ? user!.GetUserId().ToString() : "null")}""
{{{{UserEmail}}}}: ""{(user.IsAuthenticated() ? user!.GetEmail()?.ToString() : "null")}""
";

await foreach (var response in chatClient.GetStreamingResponseAsync([
new (ChatRole.System, variablesPrompt),
new (ChatRole.System, supportSystemPrompt),
Expand Down Expand Up @@ -156,6 +171,7 @@ private ChatOptions CreateChatOptions(Uri? serverApiAddress, CancellationToken c
AIFunctionFactory.Create(GetCurrentDateTime),
AIFunctionFactory.Create(SaveUserEmailAndConversationHistory),
AIFunctionFactory.Create(NavigateToPage),
AIFunctionFactory.Create(ShowSignInModal),
AIFunctionFactory.Create(SetCulture),
AIFunctionFactory.Create(SetTheme),
AIFunctionFactory.Create(CheckLastError),
Expand Down Expand Up @@ -196,7 +212,7 @@ private string GetCurrentDateTime([Required, Description("User's timezone id")]
/// <summary>
/// Saves the user's email address and the conversation history for future reference.
/// </summary>
[Description("Saves the user's email address and the conversation history for future reference. Use this tool when the user provides their email address during the conversation.")]
[Description("Saves the user's email address and the conversation history for future reference.")]
[McpServerTool(Name = nameof(SaveUserEmailAndConversationHistory))]
private async Task<string?> SaveUserEmailAndConversationHistory(
[Required, Description("User's email address")] string emailAddress,
Expand Down Expand Up @@ -249,6 +265,34 @@ private string GetCurrentDateTime([Required, Description("User's timezone id")]
}
}

[Description(@"Displays the sign-in modal to the user and waits for either successful sign-in or cancellation")]
[McpServerTool(Name = nameof(ShowSignInModal))]
public async Task<UserDto?> ShowSignInModal([Required, Description("SignalR connection id")] string signalRConnectionId)
{
await using var scope = serviceProvider.CreateAsyncScope();

try
{
var accessToken = await scope.ServiceProvider.GetRequiredService<IHubContext<AppHub>>()
.Clients.Client(signalRConnectionId)
.InvokeAsync<string>(SharedAppMessages.SHOW_SIGN_IN_MODAL, CancellationToken.None);

var bearerTokenProtector = bearerTokenOptions.Get(IdentityConstants.BearerScheme).BearerTokenProtector;
var accessTokenTicket = bearerTokenProtector.Unprotect(accessToken);
var user = accessTokenTicket!.Principal;

return await scope.ServiceProvider.GetRequiredService<AppDbContext>()
.Users
.Project()
.FirstOrDefaultAsync(u => u.Id == user.GetUserId());
}
catch (Exception exp)
{
serviceProvider.GetRequiredService<ServerExceptionHandler>().Handle(exp);
return null;
}
}

/// <summary>
/// Changes the user's culture/language setting.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ async Task ReadIncomingMessages()
generateFollowUpSuggestions: true,
incomingMessage,
request.ServerApiAddress,
Context.GetHttpContext()!.User,
messageSpecificCancellationTokenSrc.Token);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ public async Task<DiagnosticLogDto[]> GetUserSessionLogs(Guid userSessionId, [Fr

private async Task ChangeAuthenticationStateImplementation(ClaimsPrincipal? user)
{
Context.GetHttpContext()!.User = user ?? new ClaimsPrincipal(new ClaimsIdentity()) /*Anonymous*/;

await using var scope = serviceProvider.CreateAsyncScope();
await using var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@
<value>لوحة الدردشة بالذكاء الاصطناعي</value>
</data>
<data name="AiChatPanelInitialResponse" xml:space="preserve">
<value>مرحبًا! أنا هنا لجعل تجربة التطبيق رائعة! هل لديك سؤال أو تحتاج مساعدة؟</value>
<value>مرحبًا{0}! أنا هنا لجعل تجربة التطبيق رائعة! هل لديك سؤال أو تحتاج مساعدة؟</value>
</data>
<data name="AiChatPanelPrompt1" xml:space="preserve">
<value>افتح صفحة إعادة تعيين كلمة المرور وأخبرني كيف تعمل</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@ Nach Erreichen von {0} haben zusätzliche Anmeldungen reduzierte Funktionen.</va
<value>AI-Chat-Panel</value>
</data>
<data name="AiChatPanelInitialResponse" xml:space="preserve">
<value>Hallo! Ich bin hier, um Ihr App-Erlebnis fantastisch zu machen! Haben Sie eine Frage oder brauchen Sie Hilfe?</value>
<value>Hallo{0}! Ich bin hier, um Ihr App-Erlebnis fantastisch zu machen! Haben Sie eine Frage oder brauchen Sie Hilfe?</value>
</data>
<data name="AiChatPanelPrompt1" xml:space="preserve">
<value>Öffnen Sie die Passwort-Reset-Seite und erklären Sie mir, wie sie funktioniert</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@ Después de alcanzar {0}, los inicios de sesión adicionales tendrán funciones
<value>Panel de chat con IA</value>
</data>
<data name="AiChatPanelInitialResponse" xml:space="preserve">
<value>¡Saludos! Estoy aquí para hacer que tu experiencia en la app sea genial. ¿Tienes una pregunta o necesitas ayuda?</value>
<value>¡Saludos{0}! Estoy aquí para hacer que tu experiencia en la app sea genial. ¿Tienes una pregunta o necesitas ayuda?</value>
</data>
<data name="AiChatPanelPrompt1" xml:space="preserve">
<value>Abrir página de restablecimiento de contraseña y dime cómo funciona</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@
<value>پنل هوش مصنوعی</value>
</data>
<data name="AiChatPanelInitialResponse" xml:space="preserve">
<value>سلام! من اینجا هستم تا تجربه اپلیکیشن شما رو فوق‌العاده کنم! سؤالی دارید یا به کمک نیاز دارید؟</value>
<value>سلام{0}! من اینجا هستم تا تجربه اپلیکیشن شما رو فوق‌العاده کنم! سؤالی دارید یا به کمک نیاز دارید؟</value>
</data>
<data name="AiChatPanelPrompt1" xml:space="preserve">
<value>صفحه ریست کردن پسورد رو باز کن و بگو چطوری کار میکنه</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@ Après avoir atteint {0}, les connexions supplémentaires auront des fonctions r
<value>Panneau de chat IA</value>
</data>
<data name="AiChatPanelInitialResponse" xml:space="preserve">
<value>Salutations ! Je suis là pour rendre votre expérience d'application géniale ! Une question ou besoin d'aide ?</value>
<value>Salutations{0}! Je suis là pour rendre votre expérience d'application géniale ! Une question ou besoin d'aide ?</value>
</data>
<data name="AiChatPanelPrompt1" xml:space="preserve">
<value>Ouvrir la page de réinitialisation du mot de passe et me dire comment ça marche</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@
<value>एआई चैट पैनल</value>
</data>
<data name="AiChatPanelInitialResponse" xml:space="preserve">
<value>नमस्ते! मैं यहां आपके ऐप अनुभव को शानदार बनाने के लिए हूं! कोई प्रश्न है या मदद चाहिए?</value>
<value>नमस्ते{0}! मैं यहां आपके ऐप अनुभव को शानदार बनाने के लिए हूं! कोई प्रश्न है या मदद चाहिए?</value>
</data>
<data name="AiChatPanelPrompt1" xml:space="preserve">
<value>रीसेट पासवर्ड पृष्ठ खोलें और बताएं कि यह कैसे काम करता है</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@ Na het bereiken van {0} zullen extra aanmeldingen verminderde functies hebben.</
<value>AI-chatpaneel</value>
</data>
<data name="AiChatPanelInitialResponse" xml:space="preserve">
<value>Hallo! Ik ben hier om je app-ervaring geweldig te maken! Heb je een vraag of heb je hulp nodig?</value>
<value>Hallo{0}! Ik ben hier om je app-ervaring geweldig te maken! Heb je een vraag of heb je hulp nodig?</value>
</data>
<data name="AiChatPanelPrompt1" xml:space="preserve">
<value>Open de reset-wachtwoordpagina en vertel me hoe het werkt</value>
Expand Down
Loading
Loading