Skip to content

Commit

Permalink
Persist user "Settings" by user id in new CosmosDB container "userset…
Browse files Browse the repository at this point in the history
…tings"
  • Loading branch information
mattahearn committed Apr 2, 2024
1 parent 7ef6d9b commit 936fe11
Show file tree
Hide file tree
Showing 19 changed files with 308 additions and 51 deletions.
36 changes: 27 additions & 9 deletions webapi/Controllers/UserSettingsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,15 @@ public async Task<IActionResult> GetUserSettingsAsync(Guid userId)
{
var settings = await this._userSettingsRepository.FindSettingsByUserIdAsync(userId.ToString());

return this.Ok(settings);
foreach (var setting in settings)
{
UserSettings us = new(setting.UserId, setting.DarkMode, setting.PlannersAndPersonas, setting.SimplifiedChatExperience,
setting.AzureContentSafety, setting.AzureAISearch, setting.ExportChatSessions, setting.LiveChatSessionSharing,
setting.FeedbackFromUser);
return this.Ok(us); // Only 1 record per user id
}

return this.NotFound("Did not find any user specific settings.");
}

/// <summary>
Expand All @@ -65,21 +73,31 @@ public async Task<IActionResult> UpdateUserSettingsAsync(
{
if (msgParameters.userId != null)
{
UserSettings? us = (UserSettings?)await this._userSettingsRepository.FindSettingsByUserIdAsync(userId.ToString());
if (us != null)
var settings = await this._userSettingsRepository.FindSettingsByUserIdAsync(userId.ToString());

foreach (var setting in settings)
{
// Update existing settings record for this user
us!.DarkMode = msgParameters.darkMode;
await this._userSettingsRepository.UpsertAsync(us);
setting!.DarkMode = msgParameters.darkMode;
setting!.PlannersAndPersonas = msgParameters.plannersAndPersonas;
setting!.SimplifiedChatExperience = msgParameters.simplifiedChatExperience;
setting!.AzureContentSafety = msgParameters.azureContentSafety;
setting!.AzureAISearch = msgParameters.azureAISearch;
setting!.ExportChatSessions = msgParameters.exportChatSessions;
setting!.LiveChatSessionSharing = msgParameters.liveChatSessionSharing;
setting!.FeedbackFromUser = msgParameters.feedbackFromUser;
await this._userSettingsRepository.UpsertAsync(setting);

return this.Ok(us);
return this.Ok(setting);
}

// Create a new settings record for this user
var newUserSettings = new UserSettings(msgParameters.userId, msgParameters.darkMode);
await this._userSettingsRepository.createUserSettings(newUserSettings);
var newUserSettings = new UserSettings(msgParameters.userId, msgParameters.darkMode, msgParameters.plannersAndPersonas,
msgParameters.simplifiedChatExperience, msgParameters.azureContentSafety, msgParameters.azureAISearch, msgParameters.exportChatSessions,
msgParameters.liveChatSessionSharing, msgParameters.feedbackFromUser);
await this._userSettingsRepository.CreateAsync(newUserSettings);

return this.Ok(us);
return this.Ok(newUserSettings);
}

return this.NotFound("User ID was not sent to update user settings'.");
Expand Down
2 changes: 2 additions & 0 deletions webapi/Extensions/ServiceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ public static IServiceCollection AddPersistentChatStore(this IServiceCollection
chatMessageStorageContext = new VolatileContext<CopilotChatMessage>();
chatMemorySourceStorageContext = new VolatileContext<MemorySource>();
chatParticipantStorageContext = new VolatileContext<ChatParticipant>();
userSettingsStorageContext = new VolatileContext<UserSettings>();
break;
}

Expand Down Expand Up @@ -236,6 +237,7 @@ public static IServiceCollection AddPersistentChatStore(this IServiceCollection
services.AddSingleton<ChatMessageRepository>(new ChatMessageRepository(chatMessageStorageContext));
services.AddSingleton<ChatMemorySourceRepository>(new ChatMemorySourceRepository(chatMemorySourceStorageContext));
services.AddSingleton<ChatParticipantRepository>(new ChatParticipantRepository(chatParticipantStorageContext));
services.AddSingleton<UserSettingsRepository>(new UserSettingsRepository(userSettingsStorageContext));

return services;
}
Expand Down
37 changes: 18 additions & 19 deletions webapi/Models/Request/EditMessageParameters.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;

Check warning on line 1 in webapi/Models/Request/EditMessageParameters.cs

View workflow job for this annotation

GitHub Actions / check-format

A source file is missing a required header.

namespace CopilotChat.WebApi.Models.Request;

Expand All @@ -7,24 +7,23 @@ namespace CopilotChat.WebApi.Models.Request;
/// </summary>
public class EditMessageParameters
{
public enum UserFeedback
{
Unknown,
Requested,
Positive,
Negative,
}
public enum UserFeedbackTypes
{
Unknown,
Requested,
Positive,
Negative
}

/// <summary>
/// Message ID
/// </summary>
[JsonPropertyName("id")]
public string? id { get; set; }

/// <summary>
/// User Feedback
/// </summary>
[JsonPropertyName("userFeedback")]
public UserFeedback? userFeedback { get; set; }
/// <summary>
/// Message ID
/// </summary>
[JsonPropertyName("id")]
public string? id { get; set; }

/// <summary>
/// User Feedback
/// </summary>
[JsonPropertyName("userFeedback")]
public UserFeedbackTypes? userFeedback { get; set; }
}
42 changes: 42 additions & 0 deletions webapi/Models/Request/EditUserSettingsParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,46 @@ public class EditUserSettingsParameters
[JsonPropertyName("darkMode")]
public bool? darkMode { get; set; }

/// <summary>
/// Planners and Personas enabled?
/// </summary>
[JsonPropertyName("plannersAndPersonas")]
public bool? plannersAndPersonas { get; set; }

/// <summary>
/// Simplified Chat Experience?
/// </summary>
[JsonPropertyName("simplifiedChatExperience")]
public bool? simplifiedChatExperience { get; set; }

/// <summary>
/// Azure Content Safety enabled?
/// </summary>
[JsonPropertyName("azureContentSafety")]
public bool? azureContentSafety { get; set; }

/// <summary>
/// Azure AI Search enabled?
/// </summary>
[JsonPropertyName("azureAISearch")]
public bool? azureAISearch { get; set; }

/// <summary>
/// Export Chat Sessions enabled?
/// </summary>
[JsonPropertyName("exportChatSessions")]
public bool? exportChatSessions { get; set; }

/// <summary>
/// Live Chat Session Sharing enabled?
/// </summary>
[JsonPropertyName("liveChatSessionSharing")]
public bool? liveChatSessionSharing { get; set; }

/// <summary>
/// Reinforced Learning from User Feedback enabled?
/// </summary>
[JsonPropertyName("feedbackFromUser")]
public bool? feedbackFromUser { get; set; }

}
52 changes: 51 additions & 1 deletion webapi/Models/Storage/UserSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,41 @@ public class UserSettings : IStorageEntity
/// </summary>
public bool? DarkMode { get; set; }

/// <summary>
/// Planners and Personas enabled?
/// </summary>
public bool? PlannersAndPersonas { get; set; }

/// <summary>
/// Simplified Chat Experience?
/// </summary>
public bool? SimplifiedChatExperience { get; set; }

/// <summary>
/// Azure Content Safety enabled?
/// </summary>
public bool? AzureContentSafety { get; set; }

/// <summary>
/// Azure AI Search enabled?
/// </summary>
public bool? AzureAISearch { get; set; }

/// <summary>
/// Export Chat Sessions enabled?
/// </summary>
public bool? ExportChatSessions { get; set; }

/// <summary>
/// Live Chat Session Sharing enabled?
/// </summary>
public bool? LiveChatSessionSharing { get; set; }

/// <summary>
/// Reinforced Learning from User Feedback enabled?
/// </summary>
public bool? FeedbackFromUser { get; set; }

/// <summary>
/// The partition key for the source.
/// </summary>
Expand All @@ -37,10 +72,25 @@ public class UserSettings : IStorageEntity
/// </summary>
/// <param name="userId">Settings belong to this user.</param>
/// <param name="darkMode">Is Dark Mode enabled?</param>
public UserSettings(string userId, bool? darkMode)
/// <param name="plannersAndPersonas">Planners and Personas enabled?</param>
/// <param name="simplifiedChatExperience">Simplified Chat Experience?</param>
/// <param name="azureContentSafety">Azure Content Safety enabled?</param>
/// <param name="azureAISearch">Azure AI Search enabled?</param>
/// <param name="exportChatSessions">Export Chat Sesssions enabled?</param>

Check warning on line 79 in webapi/Models/Storage/UserSettings.cs

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"Sesssions" should be "Sessions".
/// <param name="liveChatSessionSharing">Live Chat Session Sharing enabled?</param>
/// <param name="feedbackFromUser">Reinforced Learning From User Feedback enabled?</param>
public UserSettings(string userId, bool? darkMode, bool? plannersAndPersonas, bool? simplifiedChatExperience, bool? azureContentSafety,
bool? azureAISearch, bool? exportChatSessions, bool? liveChatSessionSharing, bool? feedbackFromUser)
{
this.Id = Guid.NewGuid().ToString();
this.UserId = userId;
this.DarkMode = darkMode ?? false;
this.PlannersAndPersonas = plannersAndPersonas ?? false;
this.SimplifiedChatExperience = simplifiedChatExperience ?? false;
this.AzureContentSafety = azureContentSafety ?? false;
this.AzureAISearch = azureAISearch ?? false;
this.ExportChatSessions = exportChatSessions ?? false;
this.LiveChatSessionSharing = liveChatSessionSharing ?? false;
this.FeedbackFromUser = feedbackFromUser ?? false;
}
}
51 changes: 47 additions & 4 deletions webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@ import { FluentProvider, makeStyles, shorthands, tokens } from '@fluentui/react-
import * as React from 'react';
import { useEffect } from 'react';

import logo from './assets/frontend-icons/FullVALogo.png';
import logo from './assets/frontend-icons/yourLogo.png';
import { UserSettingsMenu } from './components/header/UserSettingsMenu';
import { PluginGallery } from './components/open-api-plugins/PluginGallery';
import { BackendProbe, ChatView, Error, Loading, Login } from './components/views';
import { AuthHelper } from './libs/auth/AuthHelper';
import { useChat, useFile } from './libs/hooks';
import { useUserSettings } from './libs/hooks/useUserSettings';
import { AlertType } from './libs/models/AlertType';
import { useAppDispatch, useAppSelector } from './redux/app/hooks';
import { RootState, store } from './redux/app/store';
import { FeatureKeys } from './redux/features/app/AppState';
import { addAlert, setActiveUserInfo, setServiceInfo } from './redux/features/app/appSlice';
import { DefaultChatUser, FeatureKeys } from './redux/features/app/AppState';
import {
addAlert,
setActiveUserInfo,
setServiceInfo,
setUserSettings,
toggleFeatureFlag,
} from './redux/features/app/appSlice';
import { semanticKernelDarkTheme, semanticKernelLightTheme } from './styles';

export const useClasses = makeStyles({
Expand Down Expand Up @@ -57,6 +64,7 @@ enum AppState {
LoadingChats,
Chat,
SigningOut,
UserSettings,
}

const App = () => {
Expand All @@ -69,6 +77,7 @@ const App = () => {
const { instance, inProgress } = useMsal();
const { features, isMaintenance } = useAppSelector((state: RootState) => state.app);
const isAuthenticated = useIsAuthenticated();
const userSettingsHandler = useUserSettings();

const chat = useChat();
const file = useFile();
Expand Down Expand Up @@ -105,6 +114,21 @@ const App = () => {
);
}

setAppState(AppState.UserSettings);
void userSettingsHandler.getUserSettings(account.localAccountId).then((us) => {
if (us !== undefined) {
dispatch(setUserSettings(us));
if (us.darkMode) dispatch(toggleFeatureFlag(FeatureKeys.DarkMode)); // Turn on
if (us.plannersAndPersonas) dispatch(toggleFeatureFlag(FeatureKeys.PluginsPlannersAndPersonas)); // Turn on
if (us.simplifiedChatExperience) dispatch(toggleFeatureFlag(FeatureKeys.SimplifiedExperience)); // Turn on
if (us.azureContentSafety) dispatch(toggleFeatureFlag(FeatureKeys.AzureContentSafety)); // Turn on
if (us.azureAISearch) dispatch(toggleFeatureFlag(FeatureKeys.AzureAISearch)); // Turn on
if (us.exportChatSessions) dispatch(toggleFeatureFlag(FeatureKeys.ExportChatSessions)); // Turn on
if (us.liveChatSessionSharing) dispatch(toggleFeatureFlag(FeatureKeys.LiveChatSessionSharing)); // Turn on
if (us.feedbackFromUser) dispatch(toggleFeatureFlag(FeatureKeys.RLHF)); // Turn on
}
});

setAppState(AppState.LoadingChats);
}
}
Expand All @@ -131,13 +155,31 @@ const App = () => {
}
}),
]);

if (!AuthHelper.isAuthAAD()) {
setAppState(AppState.UserSettings);
// Use default User ID
void userSettingsHandler.getUserSettings(DefaultChatUser.id).then((us) => {
if (us !== undefined) {
dispatch(setUserSettings(us));
if (us.darkMode) dispatch(toggleFeatureFlag(FeatureKeys.DarkMode)); // Turn on
if (us.plannersAndPersonas) dispatch(toggleFeatureFlag(FeatureKeys.PluginsPlannersAndPersonas)); // Turn on
if (us.simplifiedChatExperience) dispatch(toggleFeatureFlag(FeatureKeys.SimplifiedExperience)); // Turn on
if (us.azureContentSafety) dispatch(toggleFeatureFlag(FeatureKeys.AzureContentSafety)); // Turn on
if (us.azureAISearch) dispatch(toggleFeatureFlag(FeatureKeys.AzureAISearch)); // Turn on
if (us.exportChatSessions) dispatch(toggleFeatureFlag(FeatureKeys.ExportChatSessions)); // Turn on
if (us.liveChatSessionSharing) dispatch(toggleFeatureFlag(FeatureKeys.LiveChatSessionSharing)); // Turn on
if (us.feedbackFromUser) dispatch(toggleFeatureFlag(FeatureKeys.RLHF)); // Turn on
}
});
}
}

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [instance, inProgress, isAuthenticated, appState, isMaintenance]);

useEffect(() => {
const title = pageTitle ?? 'VA Chat Copilot';
const title = pageTitle ?? 'Chat Copilot';

document.title = title;
}, [pageTitle]);
Expand Down Expand Up @@ -231,6 +273,7 @@ const Chat = ({
{appState === AppState.SettingUserInfo && (
<Loading text={'Hang tight while we fetch your information...'} />
)}
{appState === AppState.UserSettings && <Loading text={'Please wait while we fetch your settings...'} />}
{appState === AppState.ErrorLoadingUserInfo && (
<Error text={'Unable to load user info. Please try signing out and signing back in.'} />
)}
Expand Down
Binary file removed webapp/src/VA.png
Binary file not shown.
Binary file removed webapp/src/assets/frontend-icons/FullVALogo.png
Binary file not shown.
Binary file removed webapp/src/assets/frontend-icons/VA.ico
Binary file not shown.
Binary file removed webapp/src/assets/frontend-icons/VA.png
Binary file not shown.
Binary file added webapp/src/assets/frontend-icons/yourLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 936fe11

Please sign in to comment.