Skip to content

Commit

Permalink
Merge pull request #7 from aaronba/syncbranch
Browse files Browse the repository at this point in the history
Persist user "Settings" by user id in new CosmosDB container "usersettings"
  • Loading branch information
mattahearn authored Apr 2, 2024
2 parents 78fe015 + 936fe11 commit e207555
Show file tree
Hide file tree
Showing 24 changed files with 1,119 additions and 486 deletions.
943 changes: 488 additions & 455 deletions scripts/deploy/main.bicep

Large diffs are not rendered by default.

37 changes: 36 additions & 1 deletion scripts/deploy/main.json

Large diffs are not rendered by default.

105 changes: 105 additions & 0 deletions webapi/Controllers/UserSettingsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Threading.Tasks;
using CopilotChat.WebApi.Hubs;
using CopilotChat.WebApi.Models.Request;
using CopilotChat.WebApi.Models.Storage;
using CopilotChat.WebApi.Storage;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;

namespace CopilotChat.WebApi.Controllers;

/// <summary>
/// Controller for managing retrieving/updating user settings.
/// </summary>
[ApiController]
public class UserSettingsController : ControllerBase
{
private readonly ILogger<UserSettingsController> _logger;
private readonly UserSettingsRepository _userSettingsRepository;

/// <summary>
/// Initializes a new instance of the <see cref="UserSettingsController"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="userSettingsRepository">The user settings repository.</param>
public UserSettingsController(
ILogger<UserSettingsController> logger,
UserSettingsRepository userSettingsRepository)
{
this._logger = logger;
this._userSettingsRepository = userSettingsRepository;
}

/// <summary>
/// Get all settings for a user.
/// </summary>
/// <param name="userId">The user id to retrieve settings for.</param>
[HttpGet]
[Route("settings/{userId:guid}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetUserSettingsAsync(Guid userId)
{
var settings = await this._userSettingsRepository.FindSettingsByUserIdAsync(userId.ToString());

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>
/// Update user settings.
/// </summary>
/// <param name="msgParameters">Params to update settings.</param>
[HttpPatch]
[Route("settings/{userId:guid}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> UpdateUserSettingsAsync(
[FromServices] IHubContext<MessageRelayHub> messageRelayHubContext,
[FromBody] EditUserSettingsParameters msgParameters,
[FromRoute] Guid userId)
{
if (msgParameters.userId != null)
{
var settings = await this._userSettingsRepository.FindSettingsByUserIdAsync(userId.ToString());

foreach (var setting in settings)
{
// Update existing settings record for this user
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(setting);
}

// Create a new settings record for this user
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(newUserSettings);
}

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

ChatStoreOptions chatStoreConfig = services.BuildServiceProvider().GetRequiredService<IOptions<ChatStoreOptions>>().Value;

Expand All @@ -178,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 All @@ -198,6 +200,8 @@ public static IServiceCollection AddPersistentChatStore(this IServiceCollection
new FileInfo(Path.Combine(directory, $"{Path.GetFileNameWithoutExtension(fullPath)}_memorysources{Path.GetExtension(fullPath)}")));
chatParticipantStorageContext = new FileSystemContext<ChatParticipant>(
new FileInfo(Path.Combine(directory, $"{Path.GetFileNameWithoutExtension(fullPath)}_participants{Path.GetExtension(fullPath)}")));
userSettingsStorageContext = new FileSystemContext<UserSettings>(
new FileInfo(Path.Combine(directory, $"{Path.GetFileNameWithoutExtension(fullPath)}_participants{Path.GetExtension(fullPath)}")));
break;
}

Expand All @@ -216,6 +220,8 @@ public static IServiceCollection AddPersistentChatStore(this IServiceCollection
chatStoreConfig.Cosmos.ConnectionString, chatStoreConfig.Cosmos.Database, chatStoreConfig.Cosmos.ChatMemorySourcesContainer);
chatParticipantStorageContext = new CosmosDbContext<ChatParticipant>(
chatStoreConfig.Cosmos.ConnectionString, chatStoreConfig.Cosmos.Database, chatStoreConfig.Cosmos.ChatParticipantsContainer);
userSettingsStorageContext = new CosmosDbContext<UserSettings>(
chatStoreConfig.Cosmos.ConnectionString, chatStoreConfig.Cosmos.Database, chatStoreConfig.Cosmos.UserSettingsContainer);
#pragma warning restore CA2000 // Dispose objects before losing scope
break;
}
Expand All @@ -231,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;

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; }
}
70 changes: 70 additions & 0 deletions webapi/Models/Request/EditUserSettingsParameters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Text.Json.Serialization;

namespace CopilotChat.WebApi.Models.Request;

/// <summary>
/// Parameters for editing particular message.
/// </summary>
public class EditUserSettingsParameters
{
/// <summary>
/// User Settings ID
/// </summary>
[JsonPropertyName("id")]
public string? id { get; set; }

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

/// <summary>
/// Dark Mode enabled?
/// </summary>
[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; }

}
96 changes: 96 additions & 0 deletions webapi/Models/Storage/UserSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Text.Json.Serialization;
using CopilotChat.WebApi.Storage;

namespace CopilotChat.WebApi.Models.Storage;

/// <summary>
/// User specific settings set via the Settings dialog.
/// </summary>
public class UserSettings : IStorageEntity
{
/// <summary>
/// Settings ID that is persistent and unique.
/// </summary>
public string Id { get; set; }

/// <summary>
/// User ID that is persistent and unique.
/// </summary>
public string UserId { get; set; }

/// <summary>
/// Dark Mode enabled?
/// </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>
[JsonIgnore]
public string Partition => this.UserId;

/// <summary>
/// Initializes a new instance of the <see cref="UserSettings"/> class.
/// </summary>
/// <param name="userId">Settings belong to this user.</param>
/// <param name="darkMode">Is Dark Mode enabled?</param>
/// <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>
/// <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;
}
}
6 changes: 6 additions & 0 deletions webapi/Options/CosmosOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,10 @@ public class CosmosOptions
/// </summary>
[Required, NotEmptyOrWhitespace]
public string ChatParticipantsContainer { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the Cosmos container for user settings.
/// </summary>
[Required, NotEmptyOrWhitespace]
public string UserSettingsContainer { get; set; } = string.Empty;
}
3 changes: 2 additions & 1 deletion webapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ In an effort to optimize performance, each container must be created with a spec
| Chat Messages | chatmessages | /chatId |
| Chat Memory Sources | chatmemorysources | /chatId |
| Chat Partipants | chatparticipants | /userId |
| User Settings | usersettigns | /userId |

> For existing customers using CosmosDB before [Release 0.3](https://github.com/microsoft/chat-copilot/releases/tag/0.3), our recommendation is to remove the existing Cosmos DB containers and redeploy to realize the performance update related to the partition schema. To preserve existing chats, containers can be migrated as described [here](https://learn.microsoft.com/en-us/azure/cosmos-db/intra-account-container-copy#copy-a-container).
Expand Down Expand Up @@ -317,4 +318,4 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec
return services;
}

```
```
Loading

0 comments on commit e207555

Please sign in to comment.